diff --git a/migrations/Version20260109081315.php b/migrations/Version20260109081315.php new file mode 100644 index 0000000..cb99d64 --- /dev/null +++ b/migrations/Version20260109081315.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE booking_edit_draft ADD travel_date DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\''); + $this->addSql('CREATE INDEX IDX_DRAFT_TRAVEL_DATE ON booking_edit_draft (travel_date)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX IDX_DRAFT_TRAVEL_DATE ON booking_edit_draft'); + $this->addSql('ALTER TABLE booking_edit_draft DROP travel_date'); + } +} diff --git a/src/Command/DraftCleanupCommand.php b/src/Command/DraftCleanupCommand.php new file mode 100644 index 0000000..71ab80a --- /dev/null +++ b/src/Command/DraftCleanupCommand.php @@ -0,0 +1,79 @@ +addOption('dry-run', null, InputOption::VALUE_NONE, 'Show what would be deleted without actually deleting'); + } + + /** + * @see \Symfony\Component\Console\Command\Command::execute() + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $dryRun = $input->getOption('dry-run'); + + if ($dryRun) { + $expiredDrafts = $this->draftRepository->createQueryBuilder('d') + ->where('d.travelDate < :today') + ->setParameter('today', new \DateTimeImmutable('today')) + ->getQuery() + ->getResult(); + + $count = \count($expiredDrafts); + + if (0 === $count) { + $io->success('No expired drafts found.'); + + return Command::SUCCESS; + } + + $io->note(sprintf('[DRY RUN] Would delete %d expired draft(s)', $count)); + + return Command::SUCCESS; + } + + $deletedCount = $this->draftRepository->deleteExpiredDrafts(); + + if (0 === $deletedCount) { + $io->success('No expired drafts found.'); + + return Command::SUCCESS; + } + + $this->logger->info('Deleted expired booking edit drafts', [ + 'count' => $deletedCount, + ]); + + $io->success(sprintf('Deleted %d expired draft(s).', $deletedCount)); + + return Command::SUCCESS; + } +} diff --git a/src/Command/DraftInspectCommand.php b/src/Command/DraftInspectCommand.php index 2fd8821..8d11d17 100644 --- a/src/Command/DraftInspectCommand.php +++ b/src/Command/DraftInspectCommand.php @@ -75,6 +75,7 @@ class DraftInspectCommand extends Command $draft->getId(), $draft->getBookingId(), $draft->getUser()->getEmail(), + $draft->getTravelDate()->format('Y-m-d'), $draft->getCreatedAt()->format('Y-m-d H:i:s'), $draft->getUpdatedAt()->format('Y-m-d H:i:s'), $this->getAgeDays($draft->getUpdatedAt()), @@ -83,7 +84,7 @@ class DraftInspectCommand extends Command } $io->table( - ['ID', 'Booking ID', 'User Email', 'Created At', 'Updated At', 'Days Old', 'Participants'], + ['ID', 'Booking ID', 'User Email', 'Travel Date', 'Created At', 'Updated At', 'Days Old', 'Participants'], $rows ); diff --git a/src/Entity/BookingEditDraft.php b/src/Entity/BookingEditDraft.php index bed85d8..c10c93a 100644 --- a/src/Entity/BookingEditDraft.php +++ b/src/Entity/BookingEditDraft.php @@ -10,6 +10,7 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: BookingEditDraftRepository::class)] #[ORM\Table(name: 'booking_edit_draft')] #[ORM\UniqueConstraint(name: 'user_booking_unique', columns: ['user_id', 'booking_id'])] +#[ORM\Index(name: 'IDX_DRAFT_TRAVEL_DATE', columns: ['travel_date'])] class BookingEditDraft { #[ORM\Id] @@ -24,6 +25,9 @@ class BookingEditDraft #[ORM\Column(type: 'integer')] private int $bookingId; + #[ORM\Column(type: 'date_immutable')] + private \DateTimeImmutable $travelDate; + #[ORM\Column(type: 'json')] private array $formData = []; @@ -33,10 +37,11 @@ class BookingEditDraft #[ORM\Column(type: 'datetime_immutable')] private \DateTimeImmutable $updatedAt; - public function __construct(User $user, int $bookingId, array $formData) + public function __construct(User $user, int $bookingId, \DateTimeImmutable $travelDate, array $formData) { $this->user = $user; $this->bookingId = $bookingId; + $this->travelDate = $travelDate; $this->formData = $formData; $this->createdAt = new \DateTimeImmutable(); $this->updatedAt = new \DateTimeImmutable(); @@ -57,6 +62,11 @@ class BookingEditDraft return $this->bookingId; } + public function getTravelDate(): \DateTimeImmutable + { + return $this->travelDate; + } + public function getFormData(): array { return $this->formData; diff --git a/src/Repository/BookingEditDraftRepository.php b/src/Repository/BookingEditDraftRepository.php index 27c2991..399fb51 100644 --- a/src/Repository/BookingEditDraftRepository.php +++ b/src/Repository/BookingEditDraftRepository.php @@ -52,4 +52,19 @@ class BookingEditDraftRepository extends ServiceEntityRepository ->getQuery() ->execute(); } + + /** + * Deletes all drafts where the travel date has passed. + * + * @return int Number of deleted drafts + */ + public function deleteExpiredDrafts(): int + { + return (int) $this->createQueryBuilder('d') + ->delete() + ->where('d.travelDate < :today') + ->setParameter('today', new \DateTimeImmutable('today')) + ->getQuery() + ->execute(); + } } diff --git a/src/Service/BookingEditDraftService.php b/src/Service/BookingEditDraftService.php index d5f5780..036ac1d 100644 --- a/src/Service/BookingEditDraftService.php +++ b/src/Service/BookingEditDraftService.php @@ -59,12 +59,13 @@ class BookingEditDraftService public function saveDraft(User $user, int $bookingId, BookingDto $bookingDto): void { $formData = $this->fingerprintService->extractUserData($bookingDto); + $travelDate = $bookingDto->travel->dateFrom; $existingDraft = $this->findDraft($user, $bookingId); if (null !== $existingDraft) { $existingDraft->setFormData($formData); } else { - $draft = new BookingEditDraft($user, $bookingId, $formData); + $draft = new BookingEditDraft($user, $bookingId, $travelDate, $formData); $this->entityManager->persist($draft); }