diff --git a/composer.json b/composer.json index 2e15d18..186ba59 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,12 @@ "license": "proprietary", "minimum-stability": "stable", "prefer-stable": true, + "repositories": [ + { + "type": "composer", + "url": "https://www.setasign.com/downloads/" + } + ], "require": { "php": ">=8.1", "ext-ctype": "*", @@ -18,6 +24,8 @@ "oneup/uploader-bundle": "^4.0", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpdoc-parser": "^1.22", + "setasign/fpdf": "^1.8", + "setasign/fpdi": "^2.5", "symfony/apache-pack": "^1.0", "symfony/asset": "6.3.*", "symfony/console": "6.3.*", diff --git a/composer.lock b/composer.lock index a819068..24306c4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ffce78078f5185643c9ddfa6e2146e49", + "content-hash": "69e7597627d8e6c883d063d873a08d78", "packages": [ { "name": "doctrine/cache", @@ -2671,6 +2671,124 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "setasign/fpdf", + "version": "1.8.6", + "source": { + "type": "git", + "url": "https://github.com/Setasign/FPDF.git", + "reference": "0838e0ee4925716fcbbc50ad9e1799b5edfae0a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Setasign/FPDF/zipball/0838e0ee4925716fcbbc50ad9e1799b5edfae0a0", + "reference": "0838e0ee4925716fcbbc50ad9e1799b5edfae0a0", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-zlib": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "fpdf.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Olivier Plathey", + "email": "oliver@fpdf.org", + "homepage": "http://fpdf.org/" + } + ], + "description": "FPDF is a PHP class which allows to generate PDF files with pure PHP. F from FPDF stands for Free: you may use it for any kind of usage and modify it to suit your needs.", + "homepage": "http://www.fpdf.org", + "keywords": [ + "fpdf", + "pdf" + ], + "support": { + "source": "https://github.com/Setasign/FPDF/tree/1.8.6" + }, + "time": "2023-06-26T14:44:25+00:00" + }, + { + "name": "setasign/fpdi", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/Setasign/FPDI.git", + "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/ecf0459643ec963febfb9a5d529dcd93656006a4", + "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "conflict": { + "setasign/tfpdf": "<1.31" + }, + "require-dev": { + "phpunit/phpunit": "~5.7", + "setasign/fpdf": "~1.8", + "setasign/tfpdf": "~1.31", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "~6.2" + }, + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured." + }, + "type": "library", + "autoload": { + "psr-4": { + "setasign\\Fpdi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" + }, + { + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" + } + ], + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "homepage": "https://www.setasign.com/fpdi", + "keywords": [ + "fpdf", + "fpdi", + "pdf" + ], + "support": { + "issues": "https://github.com/Setasign/FPDI/issues", + "source": "https://github.com/Setasign/FPDI/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", + "type": "tidelift" + } + ], + "time": "2023-09-28T10:46:27+00:00" + }, { "name": "symfony/apache-pack", "version": "v1.0.1", diff --git a/migrations/Version20231010125452.php b/migrations/Version20231010125452.php new file mode 100644 index 0000000..5c01d87 --- /dev/null +++ b/migrations/Version20231010125452.php @@ -0,0 +1,39 @@ +addSql('ALTER TABLE disposition ADD contract_pdf_id INT DEFAULT NULL, ADD invoice_pdf_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE disposition ADD CONSTRAINT FK_4C58BF606D5F2C5F FOREIGN KEY (contract_pdf_id) REFERENCES upload (id)'); + $this->addSql('ALTER TABLE disposition ADD CONSTRAINT FK_4C58BF60EA295441 FOREIGN KEY (invoice_pdf_id) REFERENCES upload (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_4C58BF606D5F2C5F ON disposition (contract_pdf_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_4C58BF60EA295441 ON disposition (invoice_pdf_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE disposition DROP FOREIGN KEY FK_4C58BF606D5F2C5F'); + $this->addSql('ALTER TABLE disposition DROP FOREIGN KEY FK_4C58BF60EA295441'); + $this->addSql('DROP INDEX UNIQ_4C58BF606D5F2C5F ON disposition'); + $this->addSql('DROP INDEX UNIQ_4C58BF60EA295441 ON disposition'); + $this->addSql('ALTER TABLE disposition DROP contract_pdf_id, DROP invoice_pdf_id'); + } +} diff --git a/src/Controller/Admin/Application/IndexController.php b/src/Controller/Admin/Application/IndexController.php index 83d478e..a95f1ee 100644 --- a/src/Controller/Admin/Application/IndexController.php +++ b/src/Controller/Admin/Application/IndexController.php @@ -2,17 +2,43 @@ namespace App\Controller\Admin\Application; +use App\Repository\ApplicationRepository; +use Knp\Component\Pager\PaginatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; class IndexController extends AbstractController { + public function __construct( + private readonly ApplicationRepository $applicationRepository, + private readonly PaginatorInterface $paginator + ) { + } + #[Route('/admin/application', name: 'app_admin_application_index')] #[IsGranted('ROLE_ADMINISTRATIVE')] - public function index(): Response + public function index(Request $request): Response { - return $this->render('admin/application/index.html.twig'); + $query = $this + ->applicationRepository + ->getListQuery() + ; + + $pagination = $this->paginator->paginate( + $query, + $request->query->getInt('page', 1), + 10, + [ + 'defaultSortFieldName' => 'destination.dateFrom', + 'defaultSortDirection' => 'asc', + ] + ); + + return $this->render('admin/application/index.html.twig', [ + 'pagination' => $pagination, + ]); } } \ No newline at end of file diff --git a/src/Controller/Admin/DIsposition/CreateController.php b/src/Controller/Admin/DIsposition/CreateController.php new file mode 100644 index 0000000..db40aa0 --- /dev/null +++ b/src/Controller/Admin/DIsposition/CreateController.php @@ -0,0 +1,42 @@ +entityManager->persist($disposition); + $this->entityManager->remove($application); + + $this->entityManager->flush(); + + $this->addFlash('success', 'Der Teamer wurde eingeteilt'); + $this->logger->info('Create disposition', [ + 'disposition' => $disposition->getUuid(), + ]); + + return $this->redirectToRoute('app_admin_assignment_detail', [ + 'uuid' => $application->getAssignment()->getUuid(), + ]); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/AssignmentController.php b/src/Controller/Teamer/AssignmentController.php index 0080e9f..ea83bc1 100644 --- a/src/Controller/Teamer/AssignmentController.php +++ b/src/Controller/Teamer/AssignmentController.php @@ -2,22 +2,30 @@ namespace App\Controller\Teamer; +use App\Controller\Traits\ReturnUrlTrait; use App\Entity\Assignment; use App\Entity\User; use App\Repository\ApplicationRepository; +use App\Repository\DispositionRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; class AssignmentController extends AbstractController { - public function __construct(private readonly ApplicationRepository $applicationRepository) - {} + use ReturnUrlTrait; + + public function __construct( + private readonly ApplicationRepository $applicationRepository, + private readonly DispositionRepository $dispositionRepository + ) { + } #[Route('/teamer/assignment/{uuid}', name: 'app_teamer_assignment')] #[IsGranted('ROLE_TEAMER')] - public function index(Assignment $assignment): Response + public function index(Assignment $assignment, Request $request): Response { /** @var User $user */ $user = $this->getUser(); @@ -31,11 +39,23 @@ class AssignmentController extends AbstractController ]) ; - return $this->render('teamer/assignment.html.twig', [ + $disposition = $this + ->dispositionRepository + ->findOneBy([ + 'assignment' => $assignment, + 'teamer' => $teamer, + ]) + ; + + $isBookmarked = $teamer->getBookmarks()->contains($assignment); + + return $this->render('teamer/assignment/index.html.twig', [ 'teamer' => $teamer, 'assignment' => $assignment, + 'disposition' => $disposition, 'application' => $application, - 'isBookmarked' => $teamer->getBookmarks()->contains($assignment), + 'isBookmarked' => $isBookmarked, + 'returnUrl' => $this->getReturnUrl($request, 'app_teamer_index'), ]); } } \ No newline at end of file diff --git a/src/Controller/Teamer/Bookmark/IndexController.php b/src/Controller/Teamer/Bookmark/IndexController.php new file mode 100644 index 0000000..e8c873a --- /dev/null +++ b/src/Controller/Teamer/Bookmark/IndexController.php @@ -0,0 +1,49 @@ +getUser(); + $teamer = $user->getTeamer(); + + $query = $this + ->assignmentRepository + ->getBookmarkQueryForTeamer($teamer) + ; + + $pagination = $this->paginator->paginate( + $query, + $request->query->getInt('page', 1), + 10, + [ + 'defaultSortFieldName' => 'assignment.dateFrom', + 'defaultSortDirection' => 'asc', + ] + ); + + return $this->render('teamer/bookmark/index.html.twig', [ + 'pagination' => $pagination, + ]); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/ContactController.php b/src/Controller/Teamer/ContactController.php index da277e3..7cc2d96 100644 --- a/src/Controller/Teamer/ContactController.php +++ b/src/Controller/Teamer/ContactController.php @@ -11,6 +11,6 @@ class ContactController extends AbstractController #[Route('/teamer/contact', name: 'app_teamer_contact')] public function index(): Response { - return $this->render('teamer/contact.html.twig'); + return $this->render('teamer/contact/index.html.twig'); } } \ No newline at end of file diff --git a/src/Controller/Teamer/Disposition/DetailController.php b/src/Controller/Teamer/Disposition/DetailController.php new file mode 100644 index 0000000..4214e38 --- /dev/null +++ b/src/Controller/Teamer/Disposition/DetailController.php @@ -0,0 +1,21 @@ +render('teamer/disposition/detail.html.twig', [ + 'disposition' => $disposition, + ]); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/Disposition/IndexController.php b/src/Controller/Teamer/Disposition/IndexController.php deleted file mode 100644 index a517a1b..0000000 --- a/src/Controller/Teamer/Disposition/IndexController.php +++ /dev/null @@ -1,18 +0,0 @@ -render(''); - } -} \ No newline at end of file diff --git a/src/Controller/Teamer/Disposition/RecentController.php b/src/Controller/Teamer/Disposition/RecentController.php new file mode 100644 index 0000000..c9bd1f8 --- /dev/null +++ b/src/Controller/Teamer/Disposition/RecentController.php @@ -0,0 +1,49 @@ +getUser(); + $teamer = $user->getTeamer(); + + $query = $this + ->dispositionRepository + ->getRecentQuery($teamer) + ; + + $pagination = $this->paginator->paginate( + $query, + $request->query->getInt('page', 1), + 10, + [ + 'defaultSortFieldName' => 'assignment.dateFrom', + 'defaultSortDirection' => 'asc', + ] + ); + + return $this->render('teamer/disposition/recent.html.twig', [ + 'pagination' => $pagination, + ]); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/Disposition/UpcomingController.php b/src/Controller/Teamer/Disposition/UpcomingController.php new file mode 100644 index 0000000..1efd9a7 --- /dev/null +++ b/src/Controller/Teamer/Disposition/UpcomingController.php @@ -0,0 +1,49 @@ +getUser(); + $teamer = $user->getTeamer(); + + $query = $this + ->dispositionRepository + ->getUpcomingQuery($teamer) + ; + + $pagination = $this->paginator->paginate( + $query, + $request->query->getInt('page', 1), + 10, + [ + 'defaultSortFieldName' => 'assignment.dateFrom', + 'defaultSortDirection' => 'asc', + ] + ); + + return $this->render('teamer/disposition/upcoming.html.twig', [ + 'pagination' => $pagination, + ]); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/Disposition/WatchlistController.php b/src/Controller/Teamer/Disposition/WatchlistController.php deleted file mode 100644 index 9d88c1d..0000000 --- a/src/Controller/Teamer/Disposition/WatchlistController.php +++ /dev/null @@ -1,18 +0,0 @@ -render(''); - } -} \ No newline at end of file diff --git a/src/Controller/Teamer/FaqController.php b/src/Controller/Teamer/FaqController.php index 7e865f8..e0e3eb9 100644 --- a/src/Controller/Teamer/FaqController.php +++ b/src/Controller/Teamer/FaqController.php @@ -19,7 +19,7 @@ class FaqController extends AbstractController { $faqs = $this->faqRepository->findBy([], ['sorting' => 'ASC']); - return $this->render('teamer/faq.html.twig', [ + return $this->render('teamer/faq/index.html.twig', [ 'faqs' => $faqs, ]); } diff --git a/src/Entity/Application.php b/src/Entity/Application.php index 1405e61..9acab6d 100644 --- a/src/Entity/Application.php +++ b/src/Entity/Application.php @@ -15,6 +15,7 @@ class Application implements TimestampableEntityInterface public const STATUS_NEW = 'new'; public const STATUS_PENDING = 'pending'; + public const STATUS_REJECTED = 'rejected'; #[ORM\Id] #[ORM\GeneratedValue] diff --git a/src/Entity/Assignment.php b/src/Entity/Assignment.php index 69a71fb..0cb0d8a 100644 --- a/src/Entity/Assignment.php +++ b/src/Entity/Assignment.php @@ -73,12 +73,16 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa #[ORM\ManyToOne] private ?User $contact = null; + #[ORM\ManyToMany(targetEntity: Teamer::class, mappedBy: 'bookmarks')] + private Collection $teamers; + public function __construct() { $this->uuid = Uuid::v4(); $this->fees = new ArrayCollection(); $this->applications = new ArrayCollection(); $this->dispositions = new ArrayCollection(); + $this->teamers = new ArrayCollection(); $this->benefits = "Anreise im E&P Reisebus\nUnterkunft\nVerpflegung\nSkipass\n"; } @@ -316,4 +320,28 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa return $this; } + + /** + * @return Collection + */ + public function getTeamers(): Collection + { + return $this->teamers; + } + + public function addTeamer(Teamer $teamer): static + { + if (!$this->teamers->contains($teamer)) { + $this->teamers->add($teamer); + } + + return $this; + } + + public function removeTeamer(Teamer $teamer): static + { + $this->teamers->removeElement($teamer); + + return $this; + } } diff --git a/src/Entity/Disposition.php b/src/Entity/Disposition.php index 608aee2..5dd5b78 100644 --- a/src/Entity/Disposition.php +++ b/src/Entity/Disposition.php @@ -24,17 +24,26 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf private string $uuid; #[ORM\ManyToOne(inversedBy: 'dispositions')] - private ?Assignment $assignment = null; + private ?Assignment $assignment; #[ORM\ManyToOne(inversedBy: 'dispositions')] - private ?Teamer $teamer = null; + private ?Teamer $teamer; #[ORM\Column(type: Types::TEXT, nullable: true)] - private ?string $remarks = null; + private ?string $remarks; - public function __construct() + #[ORM\OneToOne(cascade: ['persist', 'remove'])] + private ?Upload $contractPdf = null; + + #[ORM\OneToOne(cascade: ['persist', 'remove'])] + private ?Upload $invoicePdf = null; + + public function __construct(Application $application) { $this->uuid = Uuid::v4(); + $this->assignment = $application->getAssignment(); + $this->teamer = $application->getTeamer(); + $this->remarks = $application->getRemarks(); } public function getId(): ?int @@ -82,4 +91,28 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf return $this; } + + public function getContractPdf(): ?Upload + { + return $this->contractPdf; + } + + public function setContractPdf(?Upload $contractPdf): static + { + $this->contractPdf = $contractPdf; + + return $this; + } + + public function getInvoicePdf(): ?Upload + { + return $this->invoicePdf; + } + + public function setInvoicePdf(?Upload $invoicePdf): static + { + $this->invoicePdf = $invoicePdf; + + return $this; + } } diff --git a/src/Entity/Teamer.php b/src/Entity/Teamer.php index d315220..3d6ebd0 100644 --- a/src/Entity/Teamer.php +++ b/src/Entity/Teamer.php @@ -94,25 +94,25 @@ class Teamer implements TimestampableEntityInterface #[Assert\NotNull(message: 'Bitte lade ein Foto von dir hoch', groups: ['profile', 'profile_preflight'])] private ?Upload $photo = null; - #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: Feedback::class)] + #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: Feedback::class, cascade: ['remove'])] private Collection $feedback; - #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: TrainingAttendance::class)] + #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: TrainingAttendance::class, cascade: ['remove'])] private Collection $trainingAttendances; - #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: License::class)] + #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: License::class, cascade: ['remove'])] private Collection $licenses; - #[ORM\ManyToMany(targetEntity: JobProfile::class)] + #[ORM\ManyToMany(targetEntity: JobProfile::class, orphanRemoval: true)] private Collection $jobProfiles; - #[ORM\ManyToMany(targetEntity: Assignment::class)] + #[ORM\ManyToMany(targetEntity: Assignment::class, inversedBy: 'teamers', orphanRemoval: true)] private Collection $bookmarks; - #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: Application::class)] + #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: Application::class, cascade: ['remove'])] private Collection $applications; - #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: Disposition::class)] + #[ORM\OneToMany(mappedBy: 'teamer', targetEntity: Disposition::class, cascade: ['remove'])] private Collection $dispositions; #[ORM\OneToOne(mappedBy: 'teamer')] diff --git a/src/Menu/TeamerMenuBuilder.php b/src/Menu/TeamerMenuBuilder.php index 7021c7c..2e30cbf 100644 --- a/src/Menu/TeamerMenuBuilder.php +++ b/src/Menu/TeamerMenuBuilder.php @@ -47,27 +47,27 @@ class TeamerMenuBuilder extends AbstractMenuBuilder 'icon' => 'bus', 'children' => [ [ - 'route' => 'app_teamer_disposition_watchlist', + 'route' => 'app_teamer_bookmark_index', 'title' => 'Merkliste', ], [ - 'route' => 'app_teamer_disposition_watchlist', + 'route' => 'app_teamer_bookmark_index', 'title' => 'Offene Bewerbungen', ], [ - 'route' => 'app_teamer_disposition_watchlist', + 'route' => 'app_teamer_disposition_upcoming', 'title' => 'Nächste Einsätze', ], [ - 'route' => 'app_teamer_disposition_watchlist', + 'route' => 'app_teamer_disposition_recent', 'title' => 'Letzte Einsätze', ], [ - 'route' => 'app_teamer_disposition_watchlist', + 'route' => 'app_teamer_bookmark_index', 'title' => 'Meine Dokumente', ], [ - 'route' => 'app_teamer_disposition_watchlist', + 'route' => 'app_teamer_bookmark_index', 'title' => 'Mein Feedback', ], ], diff --git a/src/Repository/ApplicationRepository.php b/src/Repository/ApplicationRepository.php index cd222d1..d766531 100644 --- a/src/Repository/ApplicationRepository.php +++ b/src/Repository/ApplicationRepository.php @@ -4,6 +4,7 @@ namespace App\Repository; use App\Entity\Application; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\Query; use Doctrine\Persistence\ManagerRegistry; /** @@ -20,4 +21,18 @@ class ApplicationRepository extends ServiceEntityRepository { parent::__construct($registry, Application::class); } + + public function getListQuery(): Query + { + $qb = $this->createQueryBuilder('application'); + + return $qb + ->select('application', 'assignment', 'destination', 'job_profile', 'teamer') + ->innerJoin('application.assignment', 'assignment') + ->innerJoin('assignment.destination', 'destination') + ->innerJoin('assignment.jobProfile', 'job_profile') + ->innerJoin('application.teamer', 'teamer') + ->getQuery() + ; + } } diff --git a/src/Repository/AssignmentRepository.php b/src/Repository/AssignmentRepository.php index de53b61..914d612 100644 --- a/src/Repository/AssignmentRepository.php +++ b/src/Repository/AssignmentRepository.php @@ -43,4 +43,20 @@ class AssignmentRepository extends ServiceEntityRepository return $qb->getQuery(); } + + public function getBookmarkQueryForTeamer(Teamer $teamer): Query + { + $qb = $this->createQueryBuilder('assignment'); + + $qb + ->select('assignment', 'destination', 'job_profile') + ->innerJoin('assignment.teamers', 'teamer', 'WITH', 'teamer = :teamer') + ->innerJoin('assignment.destination', 'destination') + ->innerJoin('assignment.jobProfile', 'job_profile') + ->leftJoin('assignment.applications', 'application') + ->setParameter('teamer', $teamer) + ; + + return $qb->getQuery(); + } } diff --git a/src/Repository/DispositionRepository.php b/src/Repository/DispositionRepository.php index 0abfbd5..fd588c4 100644 --- a/src/Repository/DispositionRepository.php +++ b/src/Repository/DispositionRepository.php @@ -3,7 +3,9 @@ namespace App\Repository; use App\Entity\Disposition; +use App\Entity\Teamer; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\Query; use Doctrine\Persistence\ManagerRegistry; /** @@ -21,46 +23,59 @@ class DispositionRepository extends ServiceEntityRepository parent::__construct($registry, Disposition::class); } - public function save(Disposition $entity, bool $flush = false): void + public function getUpcomingQuery(Teamer $teamer): Query { - $this->getEntityManager()->persist($entity); + $qb = $this->createQueryBuilder('disposition'); - if ($flush) { - $this->getEntityManager()->flush(); - } + return $qb + ->select('disposition', 'assignment', 'job_profile', 'destination') + ->innerJoin('disposition.assignment', 'assignment') + ->innerJoin('assignment.jobProfile', 'job_profile') + ->innerJoin('assignment.destination', 'destination') + ->where($qb->expr()->andX( + $qb->expr()->eq('disposition.teamer', ':teamer'), + $qb->expr()->orX( + $qb->expr()->andX( + $qb->expr()->isNull('assignment.dateFrom'), + $qb->expr()->gte('destination.dateFrom', ':date') + ), + $qb->expr()->andX( + $qb->expr()->isNotNull('assignment.dateFrom'), + $qb->expr()->gte('assignment.dateFrom', ':date') + ) + ) + )) + ->setParameter('teamer', $teamer) + ->setParameter('date', new \DateTimeImmutable()) + ->getQuery() + ; } - public function remove(Disposition $entity, bool $flush = false): void + public function getRecentQuery(Teamer $teamer): Query { - $this->getEntityManager()->remove($entity); + $qb = $this->createQueryBuilder('disposition'); - if ($flush) { - $this->getEntityManager()->flush(); - } + return $qb + ->select('disposition', 'assignment', 'job_profile', 'destination') + ->innerJoin('disposition.assignment', 'assignment') + ->innerJoin('assignment.jobProfile', 'job_profile') + ->innerJoin('assignment.destination', 'destination') + ->where($qb->expr()->andX( + $qb->expr()->eq('disposition.teamer', ':teamer'), + $qb->expr()->orX( + $qb->expr()->andX( + $qb->expr()->isNull('assignment.dateTo'), + $qb->expr()->lte('destination.dateTo', ':date') + ), + $qb->expr()->andX( + $qb->expr()->isNotNull('assignment.dateTo'), + $qb->expr()->lte('assignment.dateTo', ':date') + ) + ) + )) + ->setParameter('teamer', $teamer) + ->setParameter('date', new \DateTimeImmutable()) + ->getQuery() + ; } - -// /** -// * @return Disposition[] Returns an array of Disposition objects -// */ -// public function findByExampleField($value): array -// { -// return $this->createQueryBuilder('d') -// ->andWhere('d.exampleField = :val') -// ->setParameter('val', $value) -// ->orderBy('d.id', 'ASC') -// ->setMaxResults(10) -// ->getQuery() -// ->getResult() -// ; -// } - -// public function findOneBySomeField($value): ?Disposition -// { -// return $this->createQueryBuilder('d') -// ->andWhere('d.exampleField = :val') -// ->setParameter('val', $value) -// ->getQuery() -// ->getOneOrNullResult() -// ; -// } } diff --git a/src/Security/Voter/DispositionVoter.php b/src/Security/Voter/DispositionVoter.php new file mode 100644 index 0000000..1629196 --- /dev/null +++ b/src/Security/Voter/DispositionVoter.php @@ -0,0 +1,49 @@ +getRoleNames())) { + return true; + } + + // Teamers may only view or edit their own dispositions + if (in_array('ROLE_TEAMER', $token->getRoleNames()) && in_array($attribute, [static::VIEW, static::EDIT])) { + /** @var User $user */ + $user = $token->getUser(); + $teamer = $user->getTeamer(); + /** @var Disposition $disposition */ + $disposition = $subject; + + return $teamer === $disposition->getTeamer(); + } + + return false; + } +} \ No newline at end of file diff --git a/templates/admin/application/index.html.twig b/templates/admin/application/index.html.twig index 7f2f823..4eab495 100644 --- a/templates/admin/application/index.html.twig +++ b/templates/admin/application/index.html.twig @@ -11,9 +11,64 @@ + + + + + + + + {% for application in pagination %} + {% set assignment = application.assignment %} + + + + + + + + + + {% else %} + + + + {% endfor %}
+ {{ knp_pagination_sortable(pagination, 'Einsatzbeginn', 'destination.dateFrom') }} + + {{ knp_pagination_sortable(pagination, 'Einsatzende', 'destination.dateTo') }} + + {{ knp_pagination_sortable(pagination, 'Jobprofil', 'job_profile.name') }} + + {{ knp_pagination_sortable(pagination, 'Destination', 'destination.product') }} + + {{ knp_pagination_sortable(pagination, 'Busbegleitung ab', 'destination.pickup') }} + + {{ knp_pagination_sortable(pagination, 'Bewerber*in', 'teamer.lastName') }} +
+ {{ assignment.destination.dateFrom|date('d.m.Y') }} + + {{ assignment.destination.dateTo|date('d.m.Y') }} + + {{ assignment.jobProfile.name }} + + {{ assignment.destination.product }} +
+ {{ assignment.destination.hotel }} +
+ {{ assignment.pickup ? assignment.pickup|bpn_pickup_label : '-' }} + + {{ application.teamer }} + + +
+
+ Keine Daten +
+
diff --git a/templates/admin/assignment/detail.html.twig b/templates/admin/assignment/detail.html.twig index a54dba0..9c673df 100644 --- a/templates/admin/assignment/detail.html.twig +++ b/templates/admin/assignment/detail.html.twig @@ -6,4 +6,27 @@

Einsatzübersicht

+
+
+ {% include '_partials/_assignment_info.html.twig' %} +
+
+ {% for application in applications %} + + {{ application.teamer }} + + {{ application.requests }} + + {% endfor %} +
+
{% endblock %} \ No newline at end of file diff --git a/templates/teamer/assignment.html.twig b/templates/teamer/assignment/index.html.twig similarity index 59% rename from templates/teamer/assignment.html.twig rename to templates/teamer/assignment/index.html.twig index e4488fc..76bf4f4 100644 --- a/templates/teamer/assignment.html.twig +++ b/templates/teamer/assignment/index.html.twig @@ -15,20 +15,15 @@ Voraussetzungen für diesen Einsatz {% include '_partials/_assignment_requirements_info.html.twig' %} - {% if application is null %} -
- Erfüllst du mehrere oder alle Voraussetzungen für diesen Einsatz? -
-
- - Hier bewerben! - -
- {% else %} -
- Du hast dich am {{ application.createdAt|date('d.m.Y') }} auf diesen Einsatz beworben. -
-
+
+ {% if disposition is not null %} +
+ Du wurdest am {{ disposition.createdAt|date('d.m.Y') }} für diesen Einsatz eingeteilt. +
+ {% elseif application is not null %} +
+ Du hast dich am {{ application.createdAt|date('d.m.Y') }} auf diesen Einsatz beworben. +
-
- {% endif %} - {% if not isBookmarked %} - - auf die Merkliste setzen + {% else %} +
+ Erfüllst du mehrere oder alle Voraussetzungen für diesen Einsatz? +
+
+ Hier bewerben! + + {% endif %} + {% if not isBookmarked %} + + auf die Merkliste setzen + + {% endif %} + + zurück - {% endif %} +
{% endblock %} \ No newline at end of file diff --git a/templates/teamer/bookmark/index.html.twig b/templates/teamer/bookmark/index.html.twig new file mode 100644 index 0000000..2ec026f --- /dev/null +++ b/templates/teamer/bookmark/index.html.twig @@ -0,0 +1,78 @@ +{% extends 'teamer/layout.html.twig' %} + +{% block title %}Meine Merkliste{% endblock %} + +{% block content %} +

+ Meine Merkliste +

+
+
+ + + + + + + + + + + + + {% for assignment in pagination %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
+ {{ knp_pagination_sortable(pagination, 'Einsatz­beginn', 'assignment.dateFrom') }} + + {{ knp_pagination_sortable(pagination, 'Einsatz­ende', 'assignment.dateTo') }} + + {{ knp_pagination_sortable(pagination, 'Jobprofil', 'assignment.jobProfile') }} + + {{ knp_pagination_sortable(pagination, 'Destination', 'destination.dateFrom') }} + + {{ knp_pagination_sortable(pagination, 'Busbegleitung ab', 'assignment.pickup') }} +
+ {% set dateFrom = assignment.dateFrom ? assignment.dateFrom : assignment.destination.dateFrom %} + {{ dateFrom|date('d.m.Y') }} + + {% set dateTo = assignment.dateTo ? assignment.dateTo : assignment.destination.dateTo %} + {{ dateTo|date('d.m.Y') }} + + {{ assignment.jobProfile.name }} + + {{ assignment.destination.product }} +
+ {{ assignment.destination.hotel }} +
+ {{ assignment.pickup ? assignment.pickup|bpn_pickup_label|default('-') : '-' }} +
+ {{ assignment.pickupDate ? assignment.pickupDate|date('d.m.Y') : '' }} +
+ +
+
+ Keine Daten +
+
+ {{ knp_pagination_render(pagination) }} +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/teamer/contact.html.twig b/templates/teamer/contact/index.html.twig similarity index 100% rename from templates/teamer/contact.html.twig rename to templates/teamer/contact/index.html.twig diff --git a/templates/teamer/disposition/_table.html.twig b/templates/teamer/disposition/_table.html.twig new file mode 100644 index 0000000..0db0d89 --- /dev/null +++ b/templates/teamer/disposition/_table.html.twig @@ -0,0 +1,70 @@ +
+
+ + + + + + + + + + + + + {% for disposition in pagination %} + {% set assignment = disposition.assignment %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
+ {{ knp_pagination_sortable(pagination, 'Einsatz­beginn', 'assignment.dateFrom') }} + + {{ knp_pagination_sortable(pagination, 'Einsatz­ende', 'assignment.dateTo') }} + + {{ knp_pagination_sortable(pagination, 'Jobprofil', 'assignment.jobProfile') }} + + {{ knp_pagination_sortable(pagination, 'Destination', 'destination.dateFrom') }} + + {{ knp_pagination_sortable(pagination, 'Busbegleitung ab', 'assignment.pickup') }} +
+ {% set dateFrom = assignment.dateFrom ? assignment.dateFrom : assignment.destination.dateFrom %} + {{ dateFrom|date('d.m.Y') }} + + {% set dateTo = assignment.dateTo ? assignment.dateTo : assignment.destination.dateTo %} + {{ dateTo|date('d.m.Y') }} + + {{ assignment.jobProfile.name }} + + {{ assignment.destination.product }} +
+ {{ assignment.destination.hotel }} +
+ {{ assignment.pickup ? assignment.pickup|bpn_pickup_label|default('-') : '-' }} +
+ {{ assignment.pickupDate ? assignment.pickupDate|date('d.m.Y') : '' }} +
+ +
+
+ Keine Daten +
+
+ {{ knp_pagination_render(pagination) }} +
+
diff --git a/templates/teamer/disposition/detail.html.twig b/templates/teamer/disposition/detail.html.twig new file mode 100644 index 0000000..72f2562 --- /dev/null +++ b/templates/teamer/disposition/detail.html.twig @@ -0,0 +1,25 @@ +{% extends 'teamer/layout.html.twig' %} + +{% block title %}Einsatzdetails{% endblock %} + +{% block content %} +

+ Einsatzdetails +

+
+
+ {% set assignment = disposition.assignment %} + {% include '_partials/_assignment_info.html.twig' %} +
+
+

+ Dein Einsatz wurde bestätigt! +

+

+ Dein Ansprechpartner für alle reisebezogenen Themen ist: +
+ {{ assignment.contact }}, {{ assignment.contact.email }} +

+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/teamer/disposition/recent.html.twig b/templates/teamer/disposition/recent.html.twig new file mode 100644 index 0000000..99b0c25 --- /dev/null +++ b/templates/teamer/disposition/recent.html.twig @@ -0,0 +1,10 @@ +{% extends 'teamer/layout.html.twig' %} + +{% block title %}Einsatzübersicht{% endblock %} + +{% block content %} +

+ Meine letzten Einsätze +

+ {% include 'teamer/disposition/_table.html.twig' %} +{% endblock %} \ No newline at end of file diff --git a/templates/teamer/disposition/upcoming.html.twig b/templates/teamer/disposition/upcoming.html.twig new file mode 100644 index 0000000..ce056bc --- /dev/null +++ b/templates/teamer/disposition/upcoming.html.twig @@ -0,0 +1,10 @@ +{% extends 'teamer/layout.html.twig' %} + +{% block title %}Einsatzübersicht{% endblock %} + +{% block content %} +

+ Meine nächsten Einsätze +

+ {% include 'teamer/disposition/_table.html.twig' %} +{% endblock %} \ No newline at end of file diff --git a/templates/teamer/faq.html.twig b/templates/teamer/faq/index.html.twig similarity index 100% rename from templates/teamer/faq.html.twig rename to templates/teamer/faq/index.html.twig diff --git a/templates/teamer/index.html.twig b/templates/teamer/index.html.twig index 3ac9592..43f2eea 100644 --- a/templates/teamer/index.html.twig +++ b/templates/teamer/index.html.twig @@ -66,7 +66,7 @@
- + {{ icon('info', 'w-5 h-5') }}