From b3e3471c97574ae49beddd4786d475e6729da8f0 Mon Sep 17 00:00:00 2001 From: Alexander Elchlepp Date: Sat, 26 Feb 2022 13:53:33 +0100 Subject: [PATCH 01/21] #49 added ical export calendar --- config/packages/security.yaml | 1 + migrations/Version20220212121500.php | 69 ++ .../AppartmentServiceController.php | 370 ++++---- .../ReservationServiceController.php | 2 + src/DataFixtures/ReservationFixtures.php | 302 +++---- src/DataFixtures/SettingsFixtures.php | 392 +++++---- src/Entity/Appartment.php | 294 ++++--- src/Entity/CalendarSync.php | 149 ++++ src/Entity/Reservation.php | 830 +++++++++--------- src/Entity/ReservationStatus.php | 33 + src/Form/CalendarSyncExportType.php | 50 ++ src/Repository/CalendarSyncRepository.php | 50 ++ src/Service/CalendarService.php | 72 ++ src/Service/CalendarSyncService.php | 46 + templates/Appartments/_sync_form.html.twig | 47 + templates/Appartments/index.html.twig | 228 ++--- templates/Appartments/sync_edit.html.twig | 12 + translations/Appartments/messages.de.xlf | 156 ++-- 18 files changed, 1894 insertions(+), 1209 deletions(-) create mode 100644 migrations/Version20220212121500.php create mode 100644 src/Entity/CalendarSync.php create mode 100644 src/Form/CalendarSyncExportType.php create mode 100644 src/Repository/CalendarSyncRepository.php create mode 100644 src/Service/CalendarSyncService.php create mode 100644 templates/Appartments/_sync_form.html.twig create mode 100644 templates/Appartments/sync_edit.html.twig diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 76556f7..8bd662c 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -40,5 +40,6 @@ security: access_control: - { path: ^/login, roles: PUBLIC_ACCESS } - { path: ^/reset-password, roles: PUBLIC_ACCESS } + - { path: ^/apartments/calendar, roles: PUBLIC_ACCESS } - { path: ^/users, roles: ROLE_ADMIN } - { path: ^/, roles: IS_AUTHENTICATED_REMEMBERED } diff --git a/migrations/Version20220212121500.php b/migrations/Version20220212121500.php new file mode 100644 index 0000000..543d7dc --- /dev/null +++ b/migrations/Version20220212121500.php @@ -0,0 +1,69 @@ +addSql('CREATE TABLE calendar_sync (id INT AUTO_INCREMENT NOT NULL, apartment_id INT NOT NULL, uuid BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', is_public TINYINT(1) NOT NULL, last_export DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_7EBD762ED17F50A6 (uuid), UNIQUE INDEX UNIQ_7EBD762E176DFE85 (apartment_id), INDEX uuid_idx (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE calendar_sync_reservation_status (calendar_sync_id INT NOT NULL, reservation_status_id INT NOT NULL, INDEX IDX_88FEA5C99827BFE5 (calendar_sync_id), INDEX IDX_88FEA5C971B06122 (reservation_status_id), PRIMARY KEY(calendar_sync_id, reservation_status_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE calendar_sync ADD CONSTRAINT FK_7EBD762E176DFE85 FOREIGN KEY (apartment_id) REFERENCES appartments (id)'); + $this->addSql('ALTER TABLE calendar_sync_reservation_status ADD CONSTRAINT FK_88FEA5C99827BFE5 FOREIGN KEY (calendar_sync_id) REFERENCES calendar_sync (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE calendar_sync_reservation_status ADD CONSTRAINT FK_88FEA5C971B06122 FOREIGN KEY (reservation_status_id) REFERENCES reservation_status (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE calendar_sync ADD export_guest_name TINYINT(1) NOT NULL'); + + $this->addSql('ALTER TABLE reservations CHANGE reservation_date reservation_date DATETIME NOT NULL'); + + $this->addSql('ALTER TABLE reservations ADD uuid BINARY(16) COMMENT \'(DC2Type:uuid)\''); + $this->addSql('CREATE UNIQUE INDEX UNIQ_4DA239D17F50A6 ON reservations (uuid)'); + $this->addSql('CREATE INDEX idx_uuid ON reservations (uuid)'); + + $sql = "SELECT id FROM reservations"; + $ids = $this->connection->fetchAllAssociative($sql); + foreach($ids as $id) { + $uuid = Uuid::v4(); + $this->addSql("UPDATE reservations SET uuid = ? WHERE id = ? ", [ $uuid->toBinary(), $id['id'] ]); + } + + $sql = "SELECT id FROM appartments"; + $ids = $this->connection->fetchAllAssociative($sql); + foreach($ids as $id) { + $uuid = Uuid::v4(); + $this->addSql("INSERT INTO calendar_sync (apartment_id, uuid, is_public) VALUES (?, ?, ?,?)", [ $id['id'], $uuid->toBinary(), false, false ]); + } + $this->addSql('ALTER TABLE reservations CHANGE uuid uuid BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE calendar_sync_reservation_status DROP FOREIGN KEY FK_88FEA5C99827BFE5'); + $this->addSql('DROP TABLE calendar_sync'); + $this->addSql('DROP TABLE calendar_sync_reservation_status'); + $this->addSql('ALTER TABLE reservations CHANGE reservation_date reservation_date DATE NOT NULL'); + $this->addSql('DROP INDEX UNIQ_4DA239D17F50A6 ON reservations'); + $this->addSql('DROP INDEX idx_uuid ON reservations'); + $this->addSql('ALTER TABLE reservations DROP uuid'); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/src/Controller/AppartmentServiceController.php b/src/Controller/AppartmentServiceController.php index ff1a6b8..308419c 100644 --- a/src/Controller/AppartmentServiceController.php +++ b/src/Controller/AppartmentServiceController.php @@ -1,158 +1,212 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace App\Controller; - -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Doctrine\Persistence\ManagerRegistry; - -use App\Service\CSRFProtectionService; -use App\Service\AppartmentService; -use App\Entity\Appartment; -use App\Entity\Subsidiary; -use App\Entity\RoomCategory; -use Symfony\Component\Routing\Annotation\Route; - -#[Route('/appartments')] -class AppartmentServiceController extends AbstractController -{ - - #[Route('/', name: 'appartments.overview', methods: ['GET'])] - public function indexAction(ManagerRegistry $doctrine) - { - $em = $doctrine->getManager(); - $appartments = $em->getRepository(Appartment::class)->findAll(); - - return $this->render('Appartments/index.html.twig', array( - "appartments" => $appartments - )); - } - - #[Route('/{id}/get', name: 'appartments.get.appartment', methods: ['GET'], defaults: ['id' => '0'])] - public function getAppartmentAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf, $id) - { - $em = $doctrine->getManager(); - - $appartment = $em->getRepository(Appartment::class)->find($id); - $objects = $em->getRepository(Subsidiary::class)->findAll(); - $categories = $em->getRepository(RoomCategory::class)->findAll(); - - return $this->render('Appartments/appartment_form_edit.html.twig', array( - 'objects' => $objects, - 'categories' => $categories, - 'appartment' => $appartment, - 'token' => $csrf->getCSRFTokenForForm() - )); - } - - #[Route('/new', name: 'appartments.new.appartment', methods: ['GET'])] - public function newAppartmentAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf) - { - $em = $doctrine->getManager(); - - $objects = $em->getRepository(Subsidiary::class)->findAll(); - $categories = $em->getRepository(RoomCategory::class)->findAll(); - $appartment = new Appartment(); - $appartment->setId('new'); - - return $this->render('Appartments/appartment_form_create.html.twig', array( - 'objects' => $objects, - 'categories' => $categories, - "appartment" => $appartment, - 'token' => $csrf->getCSRFTokenForForm() - )); - } - - #[Route('/create', name: 'appartments.create.appartment', methods: ['POST'])] - public function createAppartmentAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf, AppartmentService $as, Request $request) - { - $error = false; - if (($csrf->validateCSRFToken($request))) { - /* @var $appartment Appartment */ - $appartment = $as->getAppartmentFromForm($request, "new"); - - // check for mandatory fields - if (strlen($appartment->getNumber()) == 0 || strlen($appartment->getBedsMax()) == 0 - || strlen($appartment->getDescription()) == 0 - ) { - $error = true; - $this->addFlash('warning', 'flash.mandatory'); - } else { - $em = $doctrine->getManager(); - $em->persist($appartment); - $em->flush(); - - // add succes message - $this->addFlash('success', 'appartment.flash.create.success'); - } - } - - return $this->render('feedback.html.twig', array( - "error" => $error - )); - } - - - #[Route('/{id}/edit', name: 'appartments.edit.appartment', methods: ['POST'], defaults: ['id' => '0'])] - public function editAppartmentAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf, AppartmentService $as, Request $request, $id) - { - $error = false; - if (($csrf->validateCSRFToken($request))) { - /* @var $appartment Appartment */ - $appartment = $as->getAppartmentFromForm($request, $id); - $em = $doctrine->getManager(); - - // check for mandatory fields - if (strlen($appartment->getNumber()) == 0 || strlen($appartment->getBedsMax()) == 0 - || strlen($appartment->getDescription()) == 0) { - $error = true; - $this->addFlash('warning', 'flash.mandatory'); - // stop auto commit of doctrine with invalid field values - $em->clear(Appartment::class); - } else { - $em->persist($appartment); - $em->flush(); - - // add success message - $this->addFlash('success', 'appartment.flash.edit.success'); - } - } - - return $this->render('feedback.html.twig', array( - "error" => $error - )); - } - - #[Route('/{id}/delete', name: 'appartments.delete.appartment', methods: ['GET', 'POST'])] - public function deleteAppartmentAction(CSRFProtectionService $csrf, AppartmentService $as, Request $request, $id) - { - if ($request->getMethod() == 'POST') { - if (($csrf->validateCSRFToken($request, true))) { - $appartment = $as->deleteAppartment($id); - - if ($appartment) { - $this->addFlash('success', 'appartment.flash.delete.success'); - } else { - $this->addFlash('warning', 'appartment.flash.delete.error.still.in.use'); - } - } - return new Response('', Response::HTTP_NO_CONTENT); - } else { - // initial get load (ask for deleting) - return $this->render('common/form_delete_entry.html.twig', array( - "id" => $id, - 'token' => $csrf->getCSRFTokenForForm() - )); - } - } -} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\HttpFoundation\HeaderUtils; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +use App\Service\CSRFProtectionService; +use App\Service\AppartmentService; +use App\Entity\Appartment; +use App\Entity\Subsidiary; +use App\Entity\RoomCategory; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Uid\Uuid; +use App\Service\CalendarService; +use App\Form\CalendarSyncExportType; +use App\Entity\CalendarSync; +use App\Service\CalendarSyncService; + +#[Route('/apartments')] +class AppartmentServiceController extends AbstractController +{ + + #[Route('/', name: 'appartments.overview', methods: ['GET'])] + public function indexAction(ManagerRegistry $doctrine) + { + $em = $doctrine->getManager(); + $appartments = $em->getRepository(Appartment::class)->findAll(); + + return $this->render('Appartments/index.html.twig', array( + "appartments" => $appartments + )); + } + + #[Route('/{id}/get', name: 'appartments.get.appartment', methods: ['GET'], defaults: ['id' => '0'])] + public function getAppartmentAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf, $id) + { + $em = $doctrine->getManager(); + + $appartment = $em->getRepository(Appartment::class)->find($id); + $objects = $em->getRepository(Subsidiary::class)->findAll(); + $categories = $em->getRepository(RoomCategory::class)->findAll(); + + return $this->render('Appartments/appartment_form_edit.html.twig', array( + 'objects' => $objects, + 'categories' => $categories, + 'appartment' => $appartment, + 'token' => $csrf->getCSRFTokenForForm() + )); + } + + #[Route('/new', name: 'appartments.new.appartment', methods: ['GET'])] + public function newAppartmentAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf) + { + $em = $doctrine->getManager(); + + $objects = $em->getRepository(Subsidiary::class)->findAll(); + $categories = $em->getRepository(RoomCategory::class)->findAll(); + $appartment = new Appartment(); + $appartment->setId('new'); + + return $this->render('Appartments/appartment_form_create.html.twig', array( + 'objects' => $objects, + 'categories' => $categories, + "appartment" => $appartment, + 'token' => $csrf->getCSRFTokenForForm() + )); + } + + #[Route('/create', name: 'appartments.create.appartment', methods: ['POST'])] + public function createAppartmentAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf, AppartmentService $as, Request $request, CalendarSyncService $css) + { + $error = false; + if (($csrf->validateCSRFToken($request))) { + /* @var $appartment Appartment */ + $appartment = $as->getAppartmentFromForm($request, "new"); + + // check for mandatory fields + if (strlen($appartment->getNumber()) == 0 || strlen($appartment->getBedsMax()) == 0 + || strlen($appartment->getDescription()) == 0 + ) { + $error = true; + $this->addFlash('warning', 'flash.mandatory'); + } else { + $em = $doctrine->getManager(); + $em->persist($appartment); + $em->flush(); + $css->initSync($appartment); + + // add succes message + $this->addFlash('success', 'appartment.flash.create.success'); + } + } + + return $this->render('feedback.html.twig', array( + "error" => $error + )); + } + + + #[Route('/{id}/edit', name: 'appartments.edit.appartment', methods: ['POST'], defaults: ['id' => '0'])] + public function editAppartmentAction(ManagerRegistry $doctrine, CSRFProtectionService $csrf, AppartmentService $as, Request $request, $id) + { + $error = false; + if (($csrf->validateCSRFToken($request))) { + /* @var $appartment Appartment */ + $appartment = $as->getAppartmentFromForm($request, $id); + $em = $doctrine->getManager(); + + // check for mandatory fields + if (strlen($appartment->getNumber()) == 0 || strlen($appartment->getBedsMax()) == 0 + || strlen($appartment->getDescription()) == 0) { + $error = true; + $this->addFlash('warning', 'flash.mandatory'); + // stop auto commit of doctrine with invalid field values + $em->clear(Appartment::class); + } else { + $em->persist($appartment); + $em->flush(); + + // add success message + $this->addFlash('success', 'appartment.flash.edit.success'); + } + } + + return $this->render('feedback.html.twig', array( + "error" => $error + )); + } + + #[Route('/{id}/delete', name: 'appartments.delete.appartment', methods: ['GET', 'POST'])] + public function deleteAppartmentAction(CSRFProtectionService $csrf, AppartmentService $as, Request $request, $id) + { + if ($request->getMethod() == 'POST') { + if (($csrf->validateCSRFToken($request, true))) { + $appartment = $as->deleteAppartment($id); + + if ($appartment) { + $this->addFlash('success', 'appartment.flash.delete.success'); + } else { + $this->addFlash('warning', 'appartment.flash.delete.error.still.in.use'); + } + } + return new Response('', Response::HTTP_NO_CONTENT); + } else { + // initial get load (ask for deleting) + return $this->render('common/form_delete_entry.html.twig', array( + "id" => $id, + 'token' => $csrf->getCSRFTokenForForm() + )); + } + } + + #[Route('/sync/{id}/edit', name: 'apartments.sync.edit', methods: ['GET', 'POST'])] + public function editSync(ManagerRegistry $doctrine, Request $request, CalendarSync $sync): Response + { + $form = $this->createForm(CalendarSyncExportType::class, $sync); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $doctrine->getManager()->flush(); + + // add succes message + $this->addFlash('success', 'category.flash.edit.success'); + return new Response('', Response::HTTP_NO_CONTENT); + } + + return $this->render('Appartments/sync_edit.html.twig', [ + 'sync' => $sync, + 'form' => $form->createView(), + ]); + } + + #[Route('/calendar/{uuid}/calendar.ics', name: 'apartments.get.calendar', methods: ['GET'], requirements: ['uuid' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'])] + public function getCalendarAction(ManagerRegistry $doctrine, CalendarService $cs, string $uuid, CalendarSyncService $css) + { + $em = $doctrine->getManager(); + $sync = $em->getRepository(CalendarSync::class)->findOneBy(['uuid' => Uuid::fromString($uuid)]); + /* @var $sync CalendarSync */ + if( !$sync instanceof CalendarSync || !$sync->getIsPublic() ) { + throw new NotFoundHttpException(); + } + $css->updateExportDate($sync); + + $response = new Response( + $cs->getIcalContent($sync), + Response::HTTP_OK, + ['content-type' => 'text/calendar; charset=utf-8'] + ); + $disposition = HeaderUtils::makeDisposition( + HeaderUtils::DISPOSITION_ATTACHMENT, + 'calendar.ics' + ); + $response->headers->set('Content-Disposition', $disposition); + + return $response; + + } +} diff --git a/src/Controller/ReservationServiceController.php b/src/Controller/ReservationServiceController.php index 510d98e..11aaac1 100644 --- a/src/Controller/ReservationServiceController.php +++ b/src/Controller/ReservationServiceController.php @@ -19,6 +19,7 @@ use Symfony\Component\Intl\Countries; use Symfony\Component\HttpFoundation\RequestStack; use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\Uid\Uuid; use App\Controller\CustomerServiceController; use App\Service\CSRFProtectionService; @@ -569,6 +570,7 @@ public function createNewReservationAction(ManagerRegistry $doctrine, CSRFProtec foreach ($reservations as $reservation) { $reservation->setRemark($request->request->get('remark')); $reservation->setReservationOrigin($origin); + $reservation->setUuid(Uuid::v4()); foreach($customersInReservation as $guest) { // add guest only if he is in the appartment diff --git a/src/DataFixtures/ReservationFixtures.php b/src/DataFixtures/ReservationFixtures.php index 9ae98e9..aeba459 100644 --- a/src/DataFixtures/ReservationFixtures.php +++ b/src/DataFixtures/ReservationFixtures.php @@ -1,150 +1,152 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace App\DataFixtures; - -use Doctrine\Bundle\FixturesBundle\Fixture; -use Doctrine\Persistence\ObjectManager; -use Symfony\Contracts\Translation\TranslatorInterface; -use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; -use Doctrine\Common\DataFixtures\DependentFixtureInterface; -use App\DataFixtures\SettingsFixtures; - -use App\Entity\ReservationOrigin; -use App\Entity\Appartment; -use App\Entity\Price; -use App\Entity\Reservation; -use App\Entity\Customer; -use App\Entity\Invoice; -use App\Entity\InvoiceAppartment; -use App\Entity\InvoicePosition; -use App\Entity\CustomerAddresses; -use App\Entity\ReservationStatus; - -class ReservationFixtures extends Fixture implements FixtureGroupInterface, DependentFixtureInterface -{ - private $translator; - public function __construct(TranslatorInterface $translator) { - $this->translator = $translator; - } - - public static function getGroups(): array - { - return ['reservation', 'invoices']; - } - - public function getDependencies() - { - return array( - SettingsFixtures::class, - ); - } - - public function load(ObjectManager $manager) - { - - $apps = $manager->getRepository(Appartment::class)->findAll(); - $origins = $manager->getRepository(ReservationOrigin::class)->findAll(); - $customer = $manager->getRepository(Customer::class)->findOneBy(['lastname' => 'Mustermann']); - $status = $manager->getRepository(ReservationStatus::class)->find(1); - - if(!($customer instanceof Customer)) { - return; - } - /* @var $app Appartment */ - foreach($apps as $app) { - $start = new \DateTime(); - - $intvall1 = new \DateInterval('P'. rand(1,10) .'D'); - $intvall2 = new \DateInterval('P'. rand(0,5) .'D'); - $start->add($intvall2); - $end = clone $start; - $end->add($intvall1); - - $res = new Reservation(); - $res->setReservationOrigin($origins[0]); - $res->setBooker($customer); - $res->addCustomer($customer); - $res->setPersons($app->getBedsMax()); - $res->setReservationStatus($status); - $res->setStartDate($start); - $res->setEndDate($end); - $res->setAppartment($app); - $res->setReservationDate(new \DateTime()); - - $manager->persist($res); - } - - $manager->flush(); - - $this->createInvoices($manager); - - $manager->flush(); - } - - private function createInvoices(ObjectManager $manager) { - $res = $manager->getRepository(Reservation::class)->findAll(); - /* @var $bPrice Price */ - $bPrice = $manager->getRepository(Price::class)->findOneBy(['type' => 1]); - $invoiceId = 100; - /* @var $re Reservation */ - foreach($res as $re) { - /* @var $aPrice Price */ - $aPrice = $manager->getRepository(Price::class)->findOneBy(['type' => 2, 'numberOfPersons' => $re->getPersons()]); - - $interval = $re->getStartDate()->diff($re->getEndDate()); - $days = $interval->format('%a'); - - $miscPos = new InvoicePosition(); - $appPos = new InvoiceAppartment(); - $invoice = new Invoice(); - - $miscPos->setVat($bPrice->getVat()); - $miscPos->setPrice($bPrice->getPrice()); - $miscPos->setAmount($days * $re->getPersons()); - $miscPos->setDescription($bPrice->getDescription()); - - $manager->persist($miscPos); - - $appPos->setBeds($re->getPersons()); - $appPos->setDescription($aPrice->getDescription()); - $appPos->setStartDate($re->getStartDate()); - $appPos->setEndDate($re->getEndDate()); - $appPos->setVat($aPrice->getVat()); - $appPos->setPrice($aPrice->getPrice()); - $appPos->setPersons($re->getPersons()); - $appPos->setNumber($re->getAppartment()->getNumber()); - - $manager->persist($appPos); - - /* @var $address CustomerAddresses */ - $address = $re->getBooker()->getCustomerAddresses()[0]; - $invoice->setAddress($address->getAddress()); - $invoice->setCity($address->getCity()); - $invoice->setDate(new \DateTime()); - $invoice->setFirstname($re->getBooker()->getFirstname()); - $invoice->setLastname($re->getBooker()->getLastname()); - $invoice->setNumber($invoiceId++); - $invoice->setSalutation($re->getBooker()->getSalutation()); - $invoice->setZip($address->getZip()); - $invoice->setStatus(1); - - $manager->persist($invoice); - - $miscPos->setInvoice($invoice); - $appPos->setInvoice($invoice); - $re->addInvoice($invoice); - - $manager->persist($re); - $manager->persist($appPos); - $manager->persist($miscPos); - } - } -} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\DataFixtures; + +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Persistence\ObjectManager; +use Symfony\Contracts\Translation\TranslatorInterface; +use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; +use App\DataFixtures\SettingsFixtures; +use Symfony\Component\Uid\Uuid; + +use App\Entity\ReservationOrigin; +use App\Entity\Appartment; +use App\Entity\Price; +use App\Entity\Reservation; +use App\Entity\Customer; +use App\Entity\Invoice; +use App\Entity\InvoiceAppartment; +use App\Entity\InvoicePosition; +use App\Entity\CustomerAddresses; +use App\Entity\ReservationStatus; + +class ReservationFixtures extends Fixture implements FixtureGroupInterface, DependentFixtureInterface +{ + private $translator; + public function __construct(TranslatorInterface $translator) { + $this->translator = $translator; + } + + public static function getGroups(): array + { + return ['reservation', 'invoices']; + } + + public function getDependencies() + { + return array( + SettingsFixtures::class, + ); + } + + public function load(ObjectManager $manager) + { + + $apps = $manager->getRepository(Appartment::class)->findAll(); + $origins = $manager->getRepository(ReservationOrigin::class)->findAll(); + $customer = $manager->getRepository(Customer::class)->findOneBy(['lastname' => 'Mustermann']); + $status = $manager->getRepository(ReservationStatus::class)->find(1); + + if(!($customer instanceof Customer)) { + return; + } + /* @var $app Appartment */ + foreach($apps as $app) { + $start = new \DateTime(); + + $intvall1 = new \DateInterval('P'. rand(1,10) .'D'); + $intvall2 = new \DateInterval('P'. rand(0,5) .'D'); + $start->add($intvall2); + $end = clone $start; + $end->add($intvall1); + + $res = new Reservation(); + $res->setReservationOrigin($origins[0]); + $res->setBooker($customer); + $res->addCustomer($customer); + $res->setPersons($app->getBedsMax()); + $res->setReservationStatus($status); + $res->setStartDate($start); + $res->setEndDate($end); + $res->setAppartment($app); + $res->setReservationDate(new \DateTime()); + $res->setUuid(Uuid::v4()); + + $manager->persist($res); + } + + $manager->flush(); + + $this->createInvoices($manager); + + $manager->flush(); + } + + private function createInvoices(ObjectManager $manager) { + $res = $manager->getRepository(Reservation::class)->findAll(); + /* @var $bPrice Price */ + $bPrice = $manager->getRepository(Price::class)->findOneBy(['type' => 1]); + $invoiceId = 100; + /* @var $re Reservation */ + foreach($res as $re) { + /* @var $aPrice Price */ + $aPrice = $manager->getRepository(Price::class)->findOneBy(['type' => 2, 'numberOfPersons' => $re->getPersons()]); + + $interval = $re->getStartDate()->diff($re->getEndDate()); + $days = $interval->format('%a'); + + $miscPos = new InvoicePosition(); + $appPos = new InvoiceAppartment(); + $invoice = new Invoice(); + + $miscPos->setVat($bPrice->getVat()); + $miscPos->setPrice($bPrice->getPrice()); + $miscPos->setAmount($days * $re->getPersons()); + $miscPos->setDescription($bPrice->getDescription()); + + $manager->persist($miscPos); + + $appPos->setBeds($re->getPersons()); + $appPos->setDescription($aPrice->getDescription()); + $appPos->setStartDate($re->getStartDate()); + $appPos->setEndDate($re->getEndDate()); + $appPos->setVat($aPrice->getVat()); + $appPos->setPrice($aPrice->getPrice()); + $appPos->setPersons($re->getPersons()); + $appPos->setNumber($re->getAppartment()->getNumber()); + + $manager->persist($appPos); + + /* @var $address CustomerAddresses */ + $address = $re->getBooker()->getCustomerAddresses()[0]; + $invoice->setAddress($address->getAddress()); + $invoice->setCity($address->getCity()); + $invoice->setDate(new \DateTime()); + $invoice->setFirstname($re->getBooker()->getFirstname()); + $invoice->setLastname($re->getBooker()->getLastname()); + $invoice->setNumber($invoiceId++); + $invoice->setSalutation($re->getBooker()->getSalutation()); + $invoice->setZip($address->getZip()); + $invoice->setStatus(1); + + $manager->persist($invoice); + + $miscPos->setInvoice($invoice); + $appPos->setInvoice($invoice); + $re->addInvoice($invoice); + + $manager->persist($re); + $manager->persist($appPos); + $manager->persist($miscPos); + } + } +} diff --git a/src/DataFixtures/SettingsFixtures.php b/src/DataFixtures/SettingsFixtures.php index e1ab5c2..99bdac5 100644 --- a/src/DataFixtures/SettingsFixtures.php +++ b/src/DataFixtures/SettingsFixtures.php @@ -1,194 +1,198 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace App\DataFixtures; - -use Doctrine\Bundle\FixturesBundle\Fixture; -use Doctrine\Persistence\ObjectManager; -use Symfony\Contracts\Translation\TranslatorInterface; -use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; - -use App\Entity\RoomCategory; -use App\Entity\ReservationOrigin; -use App\Entity\Appartment; -use App\Entity\Subsidiary; -use App\Entity\Price; -use App\Entity\Customer; -use App\Entity\CustomerAddresses; -use App\Entity\ReservationStatus; - -class SettingsFixtures extends Fixture implements FixtureGroupInterface -{ - private $translator; - public function __construct(TranslatorInterface $translator) { - $this->translator = $translator; - } - - public static function getGroups(): array - { - return ['settings', 'customer']; - } - - public function load(ObjectManager $manager) - { - $this->createRoomCategories($manager); - $this->createOrigins($manager); - - $manager->flush(); - - $categories = $manager->getRepository(RoomCategory::class)->findAll(); - $subsid = $manager->getRepository(Subsidiary::class)->find(1); // assuemes app:first-run was executed already - - $this->createRooms($manager, $categories, $subsid); - - $origins = $manager->getRepository(ReservationOrigin::class)->findAll(); - - $this->createPrices($manager, $categories, $origins); - - $this->createCustomer($manager); - - $this->createReservationStatus($manager); - - $manager->flush(); - } - - private function createRoomCategories(ObjectManager $manager) { - $roomCats = [ - $this->translator->trans('category.single'), - $this->translator->trans('category.double') - ]; - foreach($roomCats as $roomCat) { - $cat = new RoomCategory(); - $cat->setName( $roomCat ); - $manager->persist($cat); - } - } - - private function createOrigins(ObjectManager $manager) { - $origins = [ - $this->translator->trans('reservationorigin.private'), - $this->translator->trans('reservationorigin.business') - ]; - foreach($origins as $origin) { - $o = new ReservationOrigin(); - $o->setName( $origin ); - $manager->persist($o); - } - } - - private function createRooms(ObjectManager $manager, $roomCats, Subsidiary $subsidiary) { - // create 10 room, 5 single, 5 double - for($i = 1; $i <= 10; $i++) { - $app = new Appartment(); - $app->setNumber($i); - $app->setObject($subsidiary); - if($i > 5) { - $app->setBedsMax(2); - $app->setRoomCategory($roomCats[1]); - $app->setDescription($this->translator->trans('category.double')); - } else { - $app->setBedsMax(1); - $app->setRoomCategory($roomCats[0]); - $app->setDescription($this->translator->trans('category.single')); - } - $manager->persist($app); - } - } - - private function createPrices(ObjectManager $manager, $roomCats, $origins) { - $persons = 1; - $amount = 30; - foreach($roomCats as $roomCat) { - $price = new Price(); - $price->setActive(true); - $price->setRoomCategory($roomCat); - $price->setType(2); - $price->setMinStay(1); - $price->setAllDays(true); - $price->setAllPeriods(true); - $price->setNumberOfPersons($persons); - $price->setVat(7); - $price->setPrice($amount); - $amount += 20; - if($persons === 1) { - $price->setDescription( $this->translator->trans('price.single') ); - } else { - $price->setDescription( $this->translator->trans('price.double') ); - } - foreach($origins as $origin) { - $price->addReservationOrigin($origin); - } - - $manager->persist($price); - $persons++; - } - - // breakfast - $price = new Price(); - $price->setActive(true); - $price->setType(1); - $price->setAllDays(true); - $price->setAllPeriods(true); - $price->setVat(19); - $price->setPrice(10); - $price->setDescription( $this->translator->trans('price.breakfast') ); - foreach($origins as $origin) { - $price->addReservationOrigin($origin); - } - - $manager->persist($price); - } - - private function createCustomer(ObjectManager $manager) { - $address = new CustomerAddresses(); - $address->setAddress("Musterstr. 1"); - $address->setCity("Musterhausen"); - $address->setCountry("DE"); - $address->setEmail("max.mustermann@muster.de"); - $address->setType('CUSTOMER_ADDRESS_TYPE_PRIVATE'); - $address->setFax("123456789"); - $address->setMobilePhone("0176123456"); - $address->setPhone("987654321"); - $address->setZip("12345"); - - $manager->persist($address); - - $cus = new Customer(); - $cus->addCustomerAddress($address); - $cus->setFirstname("Max"); - $cus->setLastname("Mustermann"); - $cus->setBirthday(new \DateTime("1987-12-01")); - $cus->setSalutation("Herr"); - - $manager->persist($cus); - } - - private function createReservationStatus(ObjectManager $manager) { - $reservationStatus = [ - [ - 'name' => $this->translator->trans('status.confirmed'), - 'color' => '#2D9434', - 'contrast' => '#ffffff' - ], [ - 'name' => $this->translator->trans('status.option'), - 'color' => '#f6e95c', - 'contrast' => '#000000' - ] - - ]; - foreach($reservationStatus as $status) { - $rs = new ReservationStatus(); - $rs->setName( $status['name'] ); - $rs->setColor($status['color']); - $rs->setContrastColor($status['contrast']); - $manager->persist($rs); - } - } -} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\DataFixtures; + +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Persistence\ObjectManager; +use Symfony\Contracts\Translation\TranslatorInterface; +use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; + +use App\Entity\RoomCategory; +use App\Entity\ReservationOrigin; +use App\Entity\Appartment; +use App\Entity\Subsidiary; +use App\Entity\Price; +use App\Entity\Customer; +use App\Entity\CustomerAddresses; +use App\Entity\ReservationStatus; +use App\Service\CalendarSyncService; + +class SettingsFixtures extends Fixture implements FixtureGroupInterface +{ + private $translator; + private $syncService; + public function __construct(TranslatorInterface $translator, CalendarSyncService $css) { + $this->translator = $translator; + $this->syncService = $css; + } + + public static function getGroups(): array + { + return ['settings', 'customer']; + } + + public function load(ObjectManager $manager) + { + $this->createRoomCategories($manager); + $this->createOrigins($manager); + + $manager->flush(); + + $categories = $manager->getRepository(RoomCategory::class)->findAll(); + $subsid = $manager->getRepository(Subsidiary::class)->find(1); // assuemes app:first-run was executed already + + $this->createRooms($manager, $categories, $subsid); + + $origins = $manager->getRepository(ReservationOrigin::class)->findAll(); + + $this->createPrices($manager, $categories, $origins); + + $this->createCustomer($manager); + + $this->createReservationStatus($manager); + + $manager->flush(); + } + + private function createRoomCategories(ObjectManager $manager) { + $roomCats = [ + $this->translator->trans('category.single'), + $this->translator->trans('category.double') + ]; + foreach($roomCats as $roomCat) { + $cat = new RoomCategory(); + $cat->setName( $roomCat ); + $manager->persist($cat); + } + } + + private function createOrigins(ObjectManager $manager) { + $origins = [ + $this->translator->trans('reservationorigin.private'), + $this->translator->trans('reservationorigin.business') + ]; + foreach($origins as $origin) { + $o = new ReservationOrigin(); + $o->setName( $origin ); + $manager->persist($o); + } + } + + private function createRooms(ObjectManager $manager, $roomCats, Subsidiary $subsidiary) { + // create 10 room, 5 single, 5 double + for($i = 1; $i <= 10; $i++) { + $app = new Appartment(); + $app->setNumber($i); + $app->setObject($subsidiary); + if($i > 5) { + $app->setBedsMax(2); + $app->setRoomCategory($roomCats[1]); + $app->setDescription($this->translator->trans('category.double')); + } else { + $app->setBedsMax(1); + $app->setRoomCategory($roomCats[0]); + $app->setDescription($this->translator->trans('category.single')); + } + $this->syncService->initSync($app); + $manager->persist($app); + } + } + + private function createPrices(ObjectManager $manager, $roomCats, $origins) { + $persons = 1; + $amount = 30; + foreach($roomCats as $roomCat) { + $price = new Price(); + $price->setActive(true); + $price->setRoomCategory($roomCat); + $price->setType(2); + $price->setMinStay(1); + $price->setAllDays(true); + $price->setAllPeriods(true); + $price->setNumberOfPersons($persons); + $price->setVat(7); + $price->setPrice($amount); + $amount += 20; + if($persons === 1) { + $price->setDescription( $this->translator->trans('price.single') ); + } else { + $price->setDescription( $this->translator->trans('price.double') ); + } + foreach($origins as $origin) { + $price->addReservationOrigin($origin); + } + + $manager->persist($price); + $persons++; + } + + // breakfast + $price = new Price(); + $price->setActive(true); + $price->setType(1); + $price->setAllDays(true); + $price->setAllPeriods(true); + $price->setVat(19); + $price->setPrice(10); + $price->setDescription( $this->translator->trans('price.breakfast') ); + foreach($origins as $origin) { + $price->addReservationOrigin($origin); + } + + $manager->persist($price); + } + + private function createCustomer(ObjectManager $manager) { + $address = new CustomerAddresses(); + $address->setAddress("Musterstr. 1"); + $address->setCity("Musterhausen"); + $address->setCountry("DE"); + $address->setEmail("max.mustermann@muster.de"); + $address->setType('CUSTOMER_ADDRESS_TYPE_PRIVATE'); + $address->setFax("123456789"); + $address->setMobilePhone("0176123456"); + $address->setPhone("987654321"); + $address->setZip("12345"); + + $manager->persist($address); + + $cus = new Customer(); + $cus->addCustomerAddress($address); + $cus->setFirstname("Max"); + $cus->setLastname("Mustermann"); + $cus->setBirthday(new \DateTime("1987-12-01")); + $cus->setSalutation("Herr"); + + $manager->persist($cus); + } + + private function createReservationStatus(ObjectManager $manager) { + $reservationStatus = [ + [ + 'name' => $this->translator->trans('status.confirmed'), + 'color' => '#2D9434', + 'contrast' => '#ffffff' + ], [ + 'name' => $this->translator->trans('status.option'), + 'color' => '#f6e95c', + 'contrast' => '#000000' + ] + + ]; + foreach($reservationStatus as $status) { + $rs = new ReservationStatus(); + $rs->setName( $status['name'] ); + $rs->setColor($status['color']); + $rs->setContrastColor($status['contrast']); + $manager->persist($rs); + } + } +} diff --git a/src/Entity/Appartment.php b/src/Entity/Appartment.php index a047d9d..40b28d0 100644 --- a/src/Entity/Appartment.php +++ b/src/Entity/Appartment.php @@ -1,136 +1,158 @@ -reservations = new ArrayCollection(); - } - - public function getId() - { - return $this->id; - } - - public function getNumber() - { - return $this->number; - } - - public function getBedsMax() - { - return $this->beds_max; - } - - public function getDescription() - { - return $this->description; - } - - public function getReservations() - { - return $this->reservations; - } - - /** - * Set id - * - * @param int $id - * @return Appartment - */ - public function setId($id) - { - $this->id = $id; - - return $this; - } - - public function setNumber($number) - { - $this->number = $number; - } - - public function setBedsMax($beds_max) - { - $this->beds_max = $beds_max; - } - - public function setDescription($description) - { - $this->description = $description; - } - - public function getObject() - { - return $this->object; - } - - public function setObject($object) - { - $this->object = $object; - } - - public function setReservations($reservations) - { - $this->reservations = $reservations; - } - - public function addReservation(\App\Entity\Reservation $reservation) - { - $this->reservations[] = $reservation; - return $this; - } - - public function removeReservation(\App\Entity\Reservation $reservation) - { - $this->reservations->removeElement($reservation); - } - - public function getRoomCategory(): ?RoomCategory - { - return $this->roomCategory; - } - - public function setRoomCategory(?RoomCategory $roomCategory): self - { - $this->roomCategory = $roomCategory; - - return $this; - } -} +reservations = new ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + public function getNumber() + { + return $this->number; + } + + public function getBedsMax() + { + return $this->beds_max; + } + + public function getDescription() + { + return $this->description; + } + + public function getReservations() + { + return $this->reservations; + } + + /** + * Set id + * + * @param int $id + * @return Appartment + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + public function setNumber($number) + { + $this->number = $number; + } + + public function setBedsMax($beds_max) + { + $this->beds_max = $beds_max; + } + + public function setDescription($description) + { + $this->description = $description; + } + + public function getObject() + { + return $this->object; + } + + public function setObject($object) + { + $this->object = $object; + } + + public function setReservations($reservations) + { + $this->reservations = $reservations; + } + + public function addReservation(\App\Entity\Reservation $reservation) + { + $this->reservations[] = $reservation; + return $this; + } + + public function removeReservation(\App\Entity\Reservation $reservation) + { + $this->reservations->removeElement($reservation); + } + + public function getRoomCategory(): ?RoomCategory + { + return $this->roomCategory; + } + + public function setRoomCategory(?RoomCategory $roomCategory): self + { + $this->roomCategory = $roomCategory; + + return $this; + } + + public function getCalendarSync(): ?CalendarSync + { + return $this->calendarSync; + } + + public function setCalendarSync(CalendarSync $calendarSync): self + { + // set the owning side of the relation if necessary + if ($calendarSync->getApartment() !== $this) { + $calendarSync->setApartment($this); + } + + $this->calendarSync = $calendarSync; + + return $this; + } +} diff --git a/src/Entity/CalendarSync.php b/src/Entity/CalendarSync.php new file mode 100644 index 0000000..351c170 --- /dev/null +++ b/src/Entity/CalendarSync.php @@ -0,0 +1,149 @@ +reservationStatus = new ArrayCollection(); + $this->isPublic = false; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getUuid() + { + return $this->uuid; + } + + public function setUuid($uuid): self + { + $this->uuid = $uuid; + + return $this; + } + + public function getIsPublic(): ?bool + { + return $this->isPublic; + } + + public function setIsPublic(bool $isPublic): self + { + $this->isPublic = $isPublic; + + return $this; + } + + /** + * @return Collection|ReservationStatus[] + */ + public function getReservationStatus(): Collection + { + return $this->reservationStatus; + } + + public function addReservationStatus(ReservationStatus $reservationStatus): self + { + if (!$this->reservationStatus->contains($reservationStatus)) { + $this->reservationStatus[] = $reservationStatus; + $reservationStatus->addCalendarSync($this); + } + + return $this; + } + + public function removeReservationStatus(ReservationStatus $reservationStatus): self + { + $this->reservationStatus->removeElement($reservationStatus); + + return $this; + } + + public function getLastExport(): ?\DateTimeInterface + { + return $this->lastExport; + } + + public function setLastExport(?\DateTimeInterface $lastExport): self + { + $this->lastExport = $lastExport; + + return $this; + } + + public function getApartment(): ?Appartment + { + return $this->apartment; + } + + public function setApartment(Appartment $apartment): self + { + $this->apartment = $apartment; + + return $this; + } + + public function getExportGuestName(): ?bool + { + return $this->exportGuestName; + } + + public function setExportGuestName(bool $exportGuestName): self + { + $this->exportGuestName = $exportGuestName; + + return $this; + } +} diff --git a/src/Entity/Reservation.php b/src/Entity/Reservation.php index 24bb3a1..eea9493 100644 --- a/src/Entity/Reservation.php +++ b/src/Entity/Reservation.php @@ -1,406 +1,424 @@ -reservationDate = new \DateTime('now'); - $this->registrationBookEntries = new ArrayCollection(); - $this->customers = new ArrayCollection(); - $this->correspondences = new ArrayCollection(); - $this->invoices = new ArrayCollection(); - $this->prices = new ArrayCollection(); - } - - public function getId() - { - return $this->id; - } - - public function getStartDate() - { - return $this->startDate; - } - - public function getEndDate() - { - return $this->endDate; - } - - public function getPersons() - { - return $this->persons; - } - - public function getOptionDate() - { - return $this->optionDate; - } - - public function getRemark() - { - return $this->remark; - } - - public function getRemarkF() - { - return nl2br($this->remark); - } - - public function getReservationDate() - { - return $this->reservationDate; - } - - /** - * - * @return Appartment - */ - public function getAppartment() - { - return $this->appartment; - } - - public function getCustomer() - { - return $this->customer; - } - - public function setId($id) - { - $this->id = $id; - } - - public function setStartDate($startDate) - { - $this->startDate = $startDate; - } - - public function setEndDate($endDate) - { - $this->endDate = $endDate; - } - - public function setPersons($persons) - { - $this->persons = $persons; - } - - public function setOptionDate($optionDate) - { - $this->optionDate = $optionDate; - } - - public function setRemark($remark) - { - $this->remark = $remark; - } - - public function setReservationDate($reservationDate) - { - $this->reservationDate = $reservationDate; - } - - public function setAppartment($appartment) - { - $this->appartment = $appartment; - } - - public function setCustomers($customers) - { - $this->customers = $customers; - } - - public function setRegistrationBookEntries($registrationBookEntries) - { - $this->registrationBookEntries = $registrationBookEntries; - } - - public function addRegistrationBookEntry(\App\Entity\RegistrationBookEntry $registrationBookEntry) - { - $this->registrationBookEntries[] = $registrationBookEntry; - return $this; - } - - public function removeRegistrationBookEntry(\App\Entity\RegistrationBookEntry $registrationBookEntry) - { - $this->registrationBookEntries->removeElement($registrationBookEntry); - } - - /** - * Add customers - * - * @param \App\Entity\Customer $customers - * @return Reservation - */ - public function addCustomer(\App\Entity\Customer $customers) - { - $this->customers[] = $customers; - - return $this; - } - - /** - * Remove customers - * - * @param \App\Entity\Customer $customers - */ - public function removeCustomer(\App\Entity\Customer $customers) - { - $this->customers->removeElement($customers); - } - - /** - * Get customers - * - * @return \Doctrine\Common\Collections\Collection - */ - public function getCustomers() - { - return $this->customers; - } - - /** - * Get registrationBookEntries - * - * @return \Doctrine\Common\Collections\Collection - */ - public function getRegistrationBookEntries() - { - return $this->registrationBookEntries; - } - - /** - * Set booker - * - * @param \App\Entity\Customer $booker - * @return Reservation - */ - public function setBooker(\App\Entity\Customer $booker = null) - { - $this->booker = $booker; - - return $this; - } - - /** - * Get booker - * - * @return \App\Entity\Customer - */ - public function getBooker() - { - return $this->booker; - } - - public function getAmount() - { - $interval = $this->startDate->diff($this->endDate); - return $interval->format('%a'); - } - - /** - * Set reservationOrigin - * - * @param \App\Entity\ReservationOrigin $reservationOrigin - * @return Reservation - */ - public function setReservationOrigin(\App\Entity\ReservationOrigin $reservationOrigin = null) - { - $this->reservationOrigin = $reservationOrigin; - - return $this; - } - - /** - * Get reservationOrigin - * - * @return \App\Entity\ReservationOrigin - */ - public function getReservationOrigin() - { - return $this->reservationOrigin; - } - - /** - * Add correspondence - * - * @param \App\Entity\Correspondence $correspondence - * - * @return Reservation - */ - public function addCorrespondence(\App\Entity\Correspondence $correspondence) - { - $this->correspondences[] = $correspondence; - - return $this; - } - - /** - * Remove correspondence - * - * @param \App\Entity\Correspondence $correspondence - */ - public function removeCorrespondence(\App\Entity\Correspondence $correspondence) - { - $this->correspondences->removeElement($correspondence); - } - - /** - * Get correspondences - * - * @return \Doctrine\Common\Collections\Collection - */ - public function getCorrespondences() - { - return $this->correspondences; - } - - /** - * Add invoice - * - * @param \App\Entity\Invoice $invoice - * - * @return Reservation - */ - public function addInvoice(\App\Entity\Invoice $invoice) - { - $this->invoices[] = $invoice; - - return $this; - } - - /** - * Remove invoice - * - * @param \App\Entity\Invoice $invoice - */ - public function removeInvoice(\App\Entity\Invoice $invoice) - { - $this->invoices->removeElement($invoice); - } - - /** - * Get invoices - * - * @return \Doctrine\Common\Collections\Collection - */ - public function getInvoices() - { - return $this->invoices; - } - - /** - * @return Collection|Price[] - */ - public function getPrices(): Collection - { - return $this->prices; - } - - public function addPrice(Price $price): self - { - if (!$this->prices->contains($price)) { - $this->prices[] = $price; - } - - return $this; - } - - public function removePrice(Price $price): self - { - $this->prices->removeElement($price); - - return $this; - } - - public function getReservationStatus(): ?ReservationStatus - { - return $this->reservationStatus; - } - - public function setReservationStatus(?ReservationStatus $reservationStatus): self - { - $this->reservationStatus = $reservationStatus; - - return $this; - } -} +reservationDate = new \DateTime('now'); + $this->registrationBookEntries = new ArrayCollection(); + $this->customers = new ArrayCollection(); + $this->correspondences = new ArrayCollection(); + $this->invoices = new ArrayCollection(); + $this->prices = new ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + public function getStartDate() + { + return $this->startDate; + } + + public function getEndDate() + { + return $this->endDate; + } + + public function getPersons() + { + return $this->persons; + } + + public function getOptionDate() + { + return $this->optionDate; + } + + public function getRemark() + { + return $this->remark; + } + + public function getRemarkF() + { + return nl2br($this->remark); + } + + public function getReservationDate() + { + return $this->reservationDate; + } + + /** + * + * @return Appartment + */ + public function getAppartment() + { + return $this->appartment; + } + + public function getCustomer() + { + return $this->customer; + } + + public function setId($id) + { + $this->id = $id; + } + + public function setStartDate($startDate) + { + $this->startDate = $startDate; + } + + public function setEndDate($endDate) + { + $this->endDate = $endDate; + } + + public function setPersons($persons) + { + $this->persons = $persons; + } + + public function setOptionDate($optionDate) + { + $this->optionDate = $optionDate; + } + + public function setRemark($remark) + { + $this->remark = $remark; + } + + public function setReservationDate($reservationDate) + { + $this->reservationDate = $reservationDate; + } + + public function setAppartment($appartment) + { + $this->appartment = $appartment; + } + + public function setCustomers($customers) + { + $this->customers = $customers; + } + + public function setRegistrationBookEntries($registrationBookEntries) + { + $this->registrationBookEntries = $registrationBookEntries; + } + + public function addRegistrationBookEntry(\App\Entity\RegistrationBookEntry $registrationBookEntry) + { + $this->registrationBookEntries[] = $registrationBookEntry; + return $this; + } + + public function removeRegistrationBookEntry(\App\Entity\RegistrationBookEntry $registrationBookEntry) + { + $this->registrationBookEntries->removeElement($registrationBookEntry); + } + + /** + * Add customers + * + * @param \App\Entity\Customer $customers + * @return Reservation + */ + public function addCustomer(\App\Entity\Customer $customers) + { + $this->customers[] = $customers; + + return $this; + } + + /** + * Remove customers + * + * @param \App\Entity\Customer $customers + */ + public function removeCustomer(\App\Entity\Customer $customers) + { + $this->customers->removeElement($customers); + } + + /** + * Get customers + * + * @return \Doctrine\Common\Collections\Collection + */ + public function getCustomers() + { + return $this->customers; + } + + /** + * Get registrationBookEntries + * + * @return \Doctrine\Common\Collections\Collection + */ + public function getRegistrationBookEntries() + { + return $this->registrationBookEntries; + } + + /** + * Set booker + * + * @param \App\Entity\Customer $booker + * @return Reservation + */ + public function setBooker(\App\Entity\Customer $booker = null) + { + $this->booker = $booker; + + return $this; + } + + /** + * Get booker + * + * @return \App\Entity\Customer + */ + public function getBooker() + { + return $this->booker; + } + + public function getAmount() + { + $interval = $this->startDate->diff($this->endDate); + return $interval->format('%a'); + } + + /** + * Set reservationOrigin + * + * @param \App\Entity\ReservationOrigin $reservationOrigin + * @return Reservation + */ + public function setReservationOrigin(\App\Entity\ReservationOrigin $reservationOrigin = null) + { + $this->reservationOrigin = $reservationOrigin; + + return $this; + } + + /** + * Get reservationOrigin + * + * @return \App\Entity\ReservationOrigin + */ + public function getReservationOrigin() + { + return $this->reservationOrigin; + } + + /** + * Add correspondence + * + * @param \App\Entity\Correspondence $correspondence + * + * @return Reservation + */ + public function addCorrespondence(\App\Entity\Correspondence $correspondence) + { + $this->correspondences[] = $correspondence; + + return $this; + } + + /** + * Remove correspondence + * + * @param \App\Entity\Correspondence $correspondence + */ + public function removeCorrespondence(\App\Entity\Correspondence $correspondence) + { + $this->correspondences->removeElement($correspondence); + } + + /** + * Get correspondences + * + * @return \Doctrine\Common\Collections\Collection + */ + public function getCorrespondences() + { + return $this->correspondences; + } + + /** + * Add invoice + * + * @param \App\Entity\Invoice $invoice + * + * @return Reservation + */ + public function addInvoice(\App\Entity\Invoice $invoice) + { + $this->invoices[] = $invoice; + + return $this; + } + + /** + * Remove invoice + * + * @param \App\Entity\Invoice $invoice + */ + public function removeInvoice(\App\Entity\Invoice $invoice) + { + $this->invoices->removeElement($invoice); + } + + /** + * Get invoices + * + * @return \Doctrine\Common\Collections\Collection + */ + public function getInvoices() + { + return $this->invoices; + } + + /** + * @return Collection|Price[] + */ + public function getPrices(): Collection + { + return $this->prices; + } + + public function addPrice(Price $price): self + { + if (!$this->prices->contains($price)) { + $this->prices[] = $price; + } + + return $this; + } + + public function removePrice(Price $price): self + { + $this->prices->removeElement($price); + + return $this; + } + + public function getReservationStatus(): ?ReservationStatus + { + return $this->reservationStatus; + } + + public function setReservationStatus(?ReservationStatus $reservationStatus): self + { + $this->reservationStatus = $reservationStatus; + + return $this; + } + + public function getUuid(): ?Uuid + { + return $this->uuid; + } + + public function setUuid(Uuid $uuid): self + { + $this->uuid = $uuid; + + return $this; + } +} diff --git a/src/Entity/ReservationStatus.php b/src/Entity/ReservationStatus.php index 9c4a12d..27d4c80 100644 --- a/src/Entity/ReservationStatus.php +++ b/src/Entity/ReservationStatus.php @@ -42,9 +42,15 @@ class ReservationStatus */ private $contrastColor; + /** + * @ORM\ManyToMany(targetEntity=CalendarSync::class, mappedBy="reservationStatus") + */ + private $calendarSyncs; + public function __construct() { $this->reservations = new ArrayCollection(); + $this->calendarSyncs = new ArrayCollection(); } public function getId(): ?int @@ -117,4 +123,31 @@ public function setContrastColor(string $contrastColor): self return $this; } + + /** + * @return Collection|CalendarSync[] + */ + public function getCalendarSyncs(): Collection + { + return $this->calendarSyncs; + } + + public function addCalendarSync(CalendarSync $calendarSync): self + { + if (!$this->calendarSyncs->contains($calendarSync)) { + $this->calendarSyncs[] = $calendarSync; + $calendarSync->addReservationStatus($this); + } + + return $this; + } + + public function removeCalendarSync(CalendarSync $calendarSync): self + { + if ($this->calendarSyncs->removeElement($calendarSync)) { + $calendarSync->removeReservationStatus($this); + } + + return $this; + } } diff --git a/src/Form/CalendarSyncExportType.php b/src/Form/CalendarSyncExportType.php new file mode 100644 index 0000000..72fb5d2 --- /dev/null +++ b/src/Form/CalendarSyncExportType.php @@ -0,0 +1,50 @@ +add('isPublic', CheckboxType::class, [ + 'label' => 'calendar.sync.export.access.public.label', + 'help' => 'calendar.sync.export.access.public.hint', + 'label_attr' => ['class' => 'checkbox-inline checkbox-switch'], + 'required' => false + ]) + ->add('exportGuestName', CheckboxType::class, [ + 'label' => 'calendar.sync.export.option.guestname.label', + 'help' => 'calendar.sync.export.option.guestname.hint', + 'label_attr' => ['class' => 'checkbox-inline checkbox-switch'], + 'required' => false + ]) + ->add('reservationStatus', EntityType::class, [ + // looks for choices from this entity + 'class' => ReservationStatus::class, + 'choice_label' => 'name', + // used to render a select box, check boxes or radios + 'multiple' => true, + 'expanded' => true, + 'label_attr' => ['class' => 'checkbox-inline checkbox-switch'], + 'help' => 'calendar.sync.export.option.status.hint', + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => CalendarSync::class, + ]); + } +} diff --git a/src/Repository/CalendarSyncRepository.php b/src/Repository/CalendarSyncRepository.php new file mode 100644 index 0000000..1772a33 --- /dev/null +++ b/src/Repository/CalendarSyncRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('c') + ->andWhere('c.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('c.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?CalendarSync + { + return $this->createQueryBuilder('c') + ->andWhere('c.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Service/CalendarService.php b/src/Service/CalendarService.php index afb2509..777d34f 100644 --- a/src/Service/CalendarService.php +++ b/src/Service/CalendarService.php @@ -9,6 +9,10 @@ use Symfony\Component\Intl\Countries; use Symfony\Contracts\Translation\TranslatorInterface; +use App\Entity\Appartment; +use App\Entity\Reservation; +use App\Entity\CalendarSync; + /* * This file is part of the guesthouse administration package. * @@ -120,5 +124,73 @@ private function getSubdivisions(string $code): array { return $filtered; } + + public function getIcalContent(CalendarSync $sync): string { + $room = $sync->getApartment(); + $content = $this->getIcalHeader($room); + + /* @var $reservation \App\Entity\Reservation */ + foreach($room->getReservations() as $reservation) { + // filter reservation status + if( $sync->getReservationStatus()->contains($reservation->getReservationStatus()) ) { + $content .= $this->getIcalEventBody($reservation, $sync); + } + } + + $content .= $this->getIcalFooter(); + + return $content; + } + + private function getIcalHeader(Appartment $room): string { + if(ini_get('date.timezone') && strlen(ini_get('date.timezone') > 0)) { + $timezone = ini_get('date.timezone'); + } else { + $timezone = "Europe/Berlin"; + } + + return "BEGIN:VCALENDAR\r\n" . + "PRODID:-//FewohBee//Calendar 1.0//EN\r\nVERSION:2.0\r\n" . + "CALSCALE:GREGORIAN\r\n" . + "METHOD:PUBLISH\r\n" . + "X-WR-CALNAME:Bookings Apartment " . $room->getNumber() . "\r\n" . + "X-WR-TIMEZONE:" . $timezone . "\r\n"; + } + + private function getIcalEventBody(Reservation $resevation, CalendarSync $sync): string { + //The "DTEND" property for a "VEVENT" calendar component specifies the non-inclusive end of the event. + // therefore we need to add one day to the actual end date + $endDate = clone $resevation->getEndDate(); + $endDate->add(new \DateInterval('P1D')); + + if( $sync->getExportGuestName() ) { + $title = $resevation->getBooker()->getSalutation() . ' ' . $resevation->getBooker()->getFirstname() .' ' . + $resevation->getBooker()->getLastname() . ' (' . $title = $resevation->getReservationStatus()->getName() . ')'; + } else { + $title = $resevation->getReservationStatus()->getName(); + } + + return "BEGIN:VEVENT\r\n" . + "DTSTART;VALUE=DATE:" . $resevation->getStartDate()->format('Ymd') . "\r\n" . + "DTEND;VALUE=DATE:" . $endDate->format('Ymd') . "\r\n" . + // the date of the cration of this ics file + "DTSTAMP: " . date('Ymd') . "T" . date('His') . "Z\r\n" . + "UID:" . $resevation->getUuid()->toBase32() . "@fewohbee\r\n" . + // the date of the creation of the reservation itself + "CREATED:" . $resevation->getReservationDate()->format('Ymd') . "T" . $resevation->getReservationDate()->format('His') . "Z\r\n" . + "DESCRIPION:\r\n" . + // the date of the creation of the reservation itself + "LAST-MODIFIED:" . $resevation->getReservationDate()->format('Ymd') . "T" . $resevation->getReservationDate()->format('His') . "Z\r\n" . + "LOCATION:\r\n" . + "SEQUENCE:0\r\n" . + "STATUS:CONFIRMED\r\n" . + "SUMMARY:" . $title . "\r\n" . + "TRANSP:TRANSPARENT\r\n" . + "END:VEVENT\r\n"; + } + + private function getIcalFooter(): string { + return "END:VCALENDAR\r\n"; + } } diff --git a/src/Service/CalendarSyncService.php b/src/Service/CalendarSyncService.php new file mode 100644 index 0000000..7cde6ec --- /dev/null +++ b/src/Service/CalendarSyncService.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +class CalendarSyncService { + private $em = null; + + public function __construct(EntityManagerInterface $em) + { + $this->em = $em; + } + + public function initSync(Appartment $room): void { + if($room->getCalendarSync() === null) { + $sync = new CalendarSync(); + $sync->setApartment($room) + ->setUuid(Uuid::v4()); + $this->em->persist($sync); + $this->em->flush(); + } + } + + public function updateExportDate(CalendarSync $sync): void { + $sync->setLastExport(new \DateTime()); + $this->em->persist($sync); + $this->em->flush(); + } +} diff --git a/templates/Appartments/_sync_form.html.twig b/templates/Appartments/_sync_form.html.twig new file mode 100644 index 0000000..f01aa05 --- /dev/null +++ b/templates/Appartments/_sync_form.html.twig @@ -0,0 +1,47 @@ +
+
{{ 'calendar.sync.export.hint'|trans }}
+
+
{{ 'calendar.sync.export.option.title'|trans }}
+
+
+ {{ form_widget(form.reservationStatus) }} + {{ form_help(form.reservationStatus) }} +
+
+
+
+ {{ form_widget(form.exportGuestName) }} + {{ form_help(form.exportGuestName) }} +
+
+
{{ 'calendar.sync.export.access.title'|trans }}
+
+
+ {{ form_widget(form.isPublic) }} + {{ form_help(form.isPublic) }} +
+
+ +{{ form_widget(form) }} + +
+ + +

{{ 'calendar.sync.export.access.url.hint'|trans }}

+
+
+
+ {{ 'calendar.sync.export.access.last'|trans }}
+ + {% if sync.lastExport is not null%} + {{ sync.lastExport|date('d.m.Y H:i') }} + {% else %} + - + {% endif %} + +
+
+ + + diff --git a/templates/Appartments/index.html.twig b/templates/Appartments/index.html.twig index 895b144..0641522 100644 --- a/templates/Appartments/index.html.twig +++ b/templates/Appartments/index.html.twig @@ -1,112 +1,118 @@ -{% extends 'base.html.twig' %} - -{% block title %} - {{ parent() }} - Appartmentverwaltung -{% endblock %} - -{% block description %} - {{ parent() }} - Appartmentverwaltung -{% endblock %} - -{% block content %} -
-
-
- -
-
-
-
- - - - - - - - - - - {% for appartment in appartments %} - - - - - - - - - - {% endfor %} - -
{{ 'appartment.number'|trans }}{{ 'appartment.description'|trans }}{{ 'appartment.bedsmax'|trans }}{{ 'appartment.action'|trans }}
{{ appartment.number }}{{ appartment.description }}{{ appartment.bedsmax }} - - - - - - -
- -
-
-
-
-{% endblock %} - -{% block javascripts %} - {{ parent() }} - -{% endblock %} -{% block bodyjs %} - {{ parent() }} - +{% extends 'base.html.twig' %} + +{% block title %} + {{ parent() }} - Appartmentverwaltung +{% endblock %} + +{% block description %} + {{ parent() }} - Appartmentverwaltung +{% endblock %} + +{% block content %} +
+
+
+ +
+
+
+
+ + + + + + + + + + + {% for appartment in appartments %} + + + + + + + + + + {% endfor %} + +
{{ 'appartment.number'|trans }}{{ 'appartment.description'|trans }}{{ 'appartment.bedsmax'|trans }}{{ 'appartment.action'|trans }}
{{ appartment.number }}{{ appartment.description }}{{ appartment.bedsmax }} + + + + + + + + +
+ +
+
+
+
+{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} +{% block bodyjs %} + {{ parent() }} + {% endblock %} \ No newline at end of file diff --git a/templates/Appartments/sync_edit.html.twig b/templates/Appartments/sync_edit.html.twig new file mode 100644 index 0000000..de9be79 --- /dev/null +++ b/templates/Appartments/sync_edit.html.twig @@ -0,0 +1,12 @@ +{{ form_start(form, {'attr': {'id':'entry-form-'~sync.id, 'onsubmit':'return saveSync('~ sync.id ~', "'~ path('apartments.sync.edit', {'id': sync.id}) ~'");'} }) }} + + +{{ form_end(form) }} diff --git a/translations/Appartments/messages.de.xlf b/translations/Appartments/messages.de.xlf index e3e3a0c..4b5c94f 100644 --- a/translations/Appartments/messages.de.xlf +++ b/translations/Appartments/messages.de.xlf @@ -1,55 +1,103 @@ - - - - - - appartment.delete.ask - Dieses Zimmer wirklich löschen? - - - appartment.number - Zimmernummer - - - appartment.bedsmin - normale Belegung - - - appartment.bedsmax - max. Personen - - - appartment.action - Aktion - - - appartment.description - Beschreibung - - - appartment.flash.delete.success - Zimmer wurde erfolgreich gelöscht. - - - appartment.flash.create.success - Zimmer wurde erfolgreich angelegt. - - - appartment.object - Niederlassung - - - appartment.flash.edit.success - Zimmer wurde erfolgreich bearbeitet. - - - appartment.flash.delete.error.still.in.use - Zimmer kann nicht gelöscht werden da es bereits reserviert wurde. - - - appartment.category - Zimmerkategorie - - - + + + + + + appartment.delete.ask + Dieses Zimmer wirklich löschen? + + + appartment.number + Zimmernummer + + + appartment.bedsmin + normale Belegung + + + appartment.bedsmax + max. Personen + + + appartment.action + Aktion + + + appartment.description + Beschreibung + + + appartment.flash.delete.success + Zimmer wurde erfolgreich gelöscht. + + + appartment.flash.create.success + Zimmer wurde erfolgreich angelegt. + + + appartment.object + Niederlassung + + + appartment.flash.edit.success + Zimmer wurde erfolgreich bearbeitet. + + + appartment.flash.delete.error.still.in.use + Zimmer kann nicht gelöscht werden da es bereits reserviert wurde. + + + appartment.category + Zimmerkategorie + + + calendar.sync.title + Kalender synchronisieren + + + calendar.sync.export.hint + Geben Sie den Belegungsplan für dieses Zimmer für andere Anwendungen frei, die den sog. iCal Import (Kalender synchronisieren) unterstützen. + + + calendar.sync.export.option.title + Exportoptionen + + + calendar.sync.export.option.status.hint + Nur Reservierungen des angegebenen Status werden exportiert. + + + calendar.sync.export.option.guestname.label + Name des Gastes exportieren + + + calendar.sync.export.option.guestname.hint + Der Titel des exportierten Kalendereintrages enthält den Namen des Gastes. + + + calendar.sync.export.access.title + Zugriff + + + calendar.sync.export.access.public.label + öffentlich + + + calendar.sync.export.access.public.hint + Aktiviert/Deaktiviert den öffentlichen Zugriff auf diesen Kalender. + + + calendar.sync.export.access.url.title + Öffentliche Adresse im iCal-Format + + + calendar.sync.export.access.url.hint + Mit dieser Adresse können Sie von anderen Anwendungen aus auf diesen Kalender zugreifen. + + + calendar.sync.export.access.last + letzter Aufruf + + + \ No newline at end of file From c44b57d4b3cdd45e46b7ff3ec5071c2d9dca17f3 Mon Sep 17 00:00:00 2001 From: Alexander Elchlepp Date: Sun, 27 Feb 2022 14:29:21 +0100 Subject: [PATCH 02/21] #49 added ical export calendar --- composer.json | 1 + translations/Appartments/messages.en.yaml | 60 ++++++++++++++++++----- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 56db796..cb5b4da 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "symfony/serializer": "^6.0", "symfony/translation": "^6.0", "symfony/twig-bundle": "^6.0", + "symfony/uid": "^6.0", "symfony/validator": "^6.0", "symfony/web-link": "^6.0", "symfony/yaml": "^6.0", diff --git a/translations/Appartments/messages.en.yaml b/translations/Appartments/messages.en.yaml index f854f11..48b8146 100644 --- a/translations/Appartments/messages.en.yaml +++ b/translations/Appartments/messages.en.yaml @@ -1,12 +1,48 @@ -appartment.action: Action -appartment.bedsmax: Max. people -appartment.bedsmin: Normal occupancy -appartment.category: Room Category -appartment.delete.ask: Are you sure you want to delete this room? -appartment.description: Description -appartment.flash.create.success: Room was successfully created . -appartment.flash.delete.error.still.in.use: Room cannot be deleted because it has already been booked. -appartment.flash.delete.success: Room was successfully deleted. -appartment.flash.edit.success: Room was successfully processed. -appartment.number: Room Number -appartment.object: Branch +appartment: + action: Action + bedsmax: Max. people + bedsmin: Normal occupancy + category: Room Category + delete: + ask: Are you sure you want to delete this room? + description: Description + flash: + create: + success: Room was successfully created . + delete: + error: + still: + in: + use: Room cannot be deleted because it has already been booked. + success: Room was successfully deleted. + edit: + success: Room was successfully processed. + number: Room Number + object: Branch +calendar: + sync: + export: + acess: + last: last access + public: + hint: Enables/disables public access to this calendar. + label: public + title: Access + url: + hint: >- + With this address you can access this calendar from other + applications. + title: Public address in iCal format + hint: >- + Share the booking plan for this room with other applications that + support the so-called iCal import (synchronize calendar). + option: + guestname: + hint: >- + The title of the exported calendar entry contains the name of the + guest. + label: Export name of the guest + status: + hint: Only reservations of the specified status will be exported. + title: Export options + title: Syncing your calendar From 493d13d1b0e561859e3b94e28ec7691878d41300 Mon Sep 17 00:00:00 2001 From: Alexander Elchlepp Date: Fri, 6 May 2022 19:26:01 +0200 Subject: [PATCH 03/21] #51 added yearly overview --- public/resources/css/style.css | 316 ++++++----- .../ReservationServiceController.php | 108 +++- src/Repository/AppartmentRepository.php | 146 ++--- src/Repository/ReservationRepository.php | 516 +++++++++--------- src/Twig/AppTwigExtensions.php | 16 + .../Reservations/_index_table_form.html.twig | 15 + .../Reservations/_index_yearly_form.html.twig | 12 + .../_resevation_popover.html.twig | 33 ++ templates/Reservations/index.html.twig | 35 +- .../Reservations/reservation_table.html.twig | 28 +- .../reservation_table_month.html.twig | 118 ++++ ...tion_table_settings_input_fields.html.twig | 4 +- .../reservation_table_year.html.twig | 14 + 13 files changed, 848 insertions(+), 513 deletions(-) create mode 100644 templates/Reservations/_index_table_form.html.twig create mode 100644 templates/Reservations/_index_yearly_form.html.twig create mode 100644 templates/Reservations/_resevation_popover.html.twig create mode 100644 templates/Reservations/reservation_table_month.html.twig create mode 100644 templates/Reservations/reservation_table_year.html.twig diff --git a/public/resources/css/style.css b/public/resources/css/style.css index b459650..09a3fc1 100644 --- a/public/resources/css/style.css +++ b/public/resources/css/style.css @@ -1,137 +1,179 @@ -body { - font-size: 0.9rem -} - -.btn { - font-size: 0.9rem; -} - -.dropdown-menu { - font-size: 0.9rem; -} - -.popover-body, .popover-header { - font-size: 0.9rem; -} - -.form-switch > .form-check-input { - margin-top: .25em; - margin-right: .75em; -} - -.login { - border-color: lightgray; - border-width: 1px; - border-style: solid; - border-radius: 15px; - padding: 15px; - background-color: #F5F5F5; -} - -.button-hidden { - visibility: hidden; -} - -.two-td-left { - border-right: none !important; -} - -.two-td-right { - border-left: none !important; -} - -.reservation { - border-left: none !important; - border-right: none !important; - padding: 8px 2px !important; -} - -.reservation-inner { - border: 1px solid #666; - color: #fff; - padding: 3px; -} - -.reservation-confirmed { - background-color: #2D9434; -} - -.reservation-option { - background-color: #f6e95c; - color: #666; - border-color: #999; -} - -.reservation-table-weekend { - /*background-color: #EEE !important;*/ -} - -.mouse-pointer { - cursor: pointer; -} - -.border-left-thick { - border-left: 2px solid #ddd; -} - -.cell-selected { - background-color: #f5f5f5; -} - -.table-reservation .ui-selecting { - background: #FECA40; -} -.table-reservation .ui-selected { - background: #F39814; -} - -/* reset 2px border of tbody */ -.table-reservation > :not(:first-child) { - border-top: 1px solid currentColor; -} - -.table-reservation { - overflow: hidden; -} - -.table-reservation .table-days > th { - position: relative; -} - -.reservation-table-weekend::before { - content: ''; - position: absolute; - top: 0; - left: -1px; - right: -1px; - bottom: -5000px; - z-index: -1; - background-color: #EEE !important; -} - -#reservation-table .room-info { - white-space: nowrap; - vertical-align: middle; -} - -/* fix for autocomplete not shown */ -.ui-front { - z-index: 9999; -} - -/* reduce default font size of labels in modals */ -.modal-body .col-form-label { - font-size: 14px; -} - -.price-period .badge { - font-size: 90%; - margin: 4px; -} - -.badge .close { - margin-left: .25rem; - color: inherit; - font-size: medium; - font-weight: bold; -} +body { + font-size: 0.9rem +} + +.btn { + font-size: 0.9rem; +} + +.dropdown-menu { + font-size: 0.9rem; +} + +.popover-body, .popover-header { + font-size: 0.9rem; +} + +.form-switch > .form-check-input { + margin-top: .25em; + margin-right: .75em; +} + +.login { + border-color: lightgray; + border-width: 1px; + border-style: solid; + border-radius: 15px; + padding: 15px; + background-color: #F5F5F5; +} + +.button-hidden { + visibility: hidden; +} + +.two-td-left { + border-right: none !important; +} + +.two-td-right { + border-left: none !important; +} + +.reservation { + border-left: none !important; + border-right: none !important; + padding: 8px 2px !important; +} + +.reservation-inner { + border: 1px solid #666; + color: #fff; + padding: 3px; +} + +.reservation-confirmed { + background-color: #2D9434; +} + +.reservation-option { + background-color: #f6e95c; + color: #666; + border-color: #999; +} + +.reservation-table-weekend-single { + background-color: #EEE !important; +} + +.mouse-pointer { + cursor: pointer; +} + +.border-left-thick { + border-left: 2px solid #ddd; +} + +.cell-selected { + background-color: #f5f5f5; +} + +.table-reservation .ui-selecting { + background: #FECA40; +} +.table-reservation .ui-selected { + background: #F39814; +} + +/* reset 2px border of tbody */ +.table-reservation > :not(:first-child) { + border-top: 1px solid currentColor; +} + +.table-reservation { + overflow: hidden; +} + +.table-reservation .table-days > th { + position: relative; +} + +.reservation-table-weekend::before { + content: ''; + position: absolute; + top: 0; + left: -1px; + right: -1px; + bottom: -5000px; + z-index: -1; + background-color: #EEE !important; +} + +#reservation-table .room-info { + white-space: nowrap; + vertical-align: middle; +} + +/* fix for autocomplete not shown */ +.ui-front { + z-index: 9999; +} + +/* reduce default font size of labels in modals */ +.modal-body .col-form-label { + font-size: 14px; +} + +.price-period .badge { + font-size: 90%; + margin: 4px; +} + +.badge .close { + margin-left: .25rem; + color: inherit; + font-size: medium; + font-weight: bold; +} + +/* yearly reservation overview */ +.reservation-yearly-parent { + padding: 0 !important; + position: relative; + overflow:hidden; +} + +.no-pointer-events { + pointer-events: none; +} + +div .reservation-yearly { + padding: .5rem; + width: 100%; + height: 100%; + pointer-events: auto; + z-index: 1; + cursor: pointer; +} + +div .reservation-yearly-day { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + z-index: 2; + padding: .5rem; +} +.two-reservations { + flex: 1 0 100%; +} +.two-reservations-left { + transform: skewY(-45deg); + transform-origin: bottom left; +} + +.two-reservations-right { + transform: skewX(-45deg); + transform-origin: top right; +} diff --git a/src/Controller/ReservationServiceController.php b/src/Controller/ReservationServiceController.php index 11aaac1..cbc8808 100644 --- a/src/Controller/ReservationServiceController.php +++ b/src/Controller/ReservationServiceController.php @@ -20,6 +20,7 @@ use Symfony\Component\HttpFoundation\RequestStack; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Uid\Uuid; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use App\Controller\CustomerServiceController; use App\Service\CSRFProtectionService; @@ -56,31 +57,79 @@ public function indexAction(ManagerRegistry $doctrine, RequestStack $requestStac { $em = $doctrine->getManager(); $objects = $em->getRepository(Subsidiary::class)->findAll(); - + $today = strtotime(date("Y").'-'.date("m").'-'.(date("d")-2). ' UTC'); $start = $requestStack->getSession()->get("reservation-overview-start", $today); $interval = $requestStack->getSession()->get("reservation-overview-interval", 15); + + $year = $requestStack->getSession()->get("reservation-overview-year", date('Y')); + $objectId = $requestStack->getSession()->get("reservation-overview-objectid", "all"); + $apartments = $em->getRepository(Appartment::class)->findAllByProperty($objectId); + $selectedApartmentId = $requestStack->getSession()->get("reservation-overview-apartment", $apartments[0]->getId()); + + $show = $requestStack->getSession()->get("reservation-overview", "table"); + return $this->render('Reservations/index.html.twig', array( 'objects' => $objects, 'today' => $start, 'interval' => $interval, + 'year' => $year, + 'selectedApartmentId' => $selectedApartmentId, + 'apartments' => $apartments, 'objectId' => $objectId, 'holidayCountries' => $cs->getHolidayCountries($requestStack->getCurrentRequest()->getLocale()), 'selectedCountry' => 'DE', - 'selectedSubdivision' => 'all' + 'selectedSubdivision' => 'all', + 'show' => $show )); } + + /** + * Triggered by the buttons to switch reservation view table/yearly + * @param RequestStack $requestStack + * @param string $show + * @return Response + */ + #[Route('/view/{show}', name: 'start.toggle.view', methods: ['GET'])] + public function indexActionToggle(RequestStack $requestStack, string $show) : Response + { + if($show === 'yearly') { + $requestStack->getSession()->set("reservation-overview", "yearly"); + } else { + $requestStack->getSession()->set("reservation-overview", "table"); + } + + return $this->forward('App\Controller\ReservationServiceController::indexAction'); + } /** - * Gets the reservation overview as a table - * + * Gets the reservation overview + * @param ManagerRegistry $doctrine + * @param RequestStack $requestStack * @param Request $request - * @return mixed + * @return Response */ #[Route('/table', name: 'reservations.get.table', methods: ['GET'])] - public function getTableAction(ManagerRegistry $doctrine, RequestStack $requestStack, Request $request) + public function getTableAction(ManagerRegistry $doctrine, RequestStack $requestStack, Request $request): Response + { + $year = $request->query->get("year", null); + if($year === null) { + return $this->_handleTableRequest($doctrine, $requestStack, $request); + } else { + return $this->_handleTableYearlyRequest($doctrine, $requestStack, $request); + } + } + + /** + * Displays the regular table overview based on a start date and a period + * @param ManagerRegistry $doctrine + * @param RequestStack $requestStack + * @param Request $request + * @return Response + */ + private function _handleTableRequest(ManagerRegistry $doctrine, RequestStack $requestStack, Request $request): Response { $em = $doctrine->getManager(); $date = $request->query->get("start"); @@ -109,6 +158,7 @@ public function getTableAction(ManagerRegistry $doctrine, RequestStack $requestS $requestStack->getSession()->set("reservation-overview-start", $date); $requestStack->getSession()->set("reservation-overview-interval", $intervall); $requestStack->getSession()->set("reservation-overview-objectid", $objectId); + $requestStack->getSession()->set("reservation-overview", "table"); return $this->render('Reservations/reservation_table.html.twig', array( "appartments" => $appartments, @@ -116,6 +166,51 @@ public function getTableAction(ManagerRegistry $doctrine, RequestStack $requestS "intervall" => $intervall, "holidayCountry" => $holidayCountry, 'selectedSubdivision' => $selectedSubdivision + )); + } + + /** + * Loads the actual table based on a given year and apartment + * @param ManagerRegistry $doctrine + * @param RequestStack $requestStack + * @param Request $request + * @return Response + * @throws NotFoundHttpException + */ + private function _handleTableYearlyRequest(ManagerRegistry $doctrine, RequestStack $requestStack, Request $request): Response + { + $em = $doctrine->getManager(); + $objectId = $request->query->get("object"); + $year = $request->query->get("year", date('Y')); + $apartmentId = $request->query->get("apartment"); + $apartment = $em->getRepository(Appartment::class)->find($apartmentId); + + if(!$apartment instanceof Appartment) { + throw new NotFoundHttpException(); + } + + if(!preg_match("/[0-9]{4}/", $year)) { + throw new NotFoundHttpException(); + } + + if ($objectId == null || $objectId == "all") { + $appartments = $em->getRepository(Appartment::class)->findAll(); + } else { + $object = $em->getRepository(Subsidiary::class)->findById($objectId); + $appartments = $em->getRepository(Appartment::class)->findByObject($object); + } + + $requestStack->getSession()->set("reservation-overview-objectid", $objectId); + $requestStack->getSession()->set("reservation-overview-year", $year); + $requestStack->getSession()->set("reservation-overview-apartment", $apartment->getId()); + $requestStack->getSession()->set("reservation-overview", "yearly"); + + return $this->render('Reservations/reservation_table_year.html.twig', array( + "appartments" => $appartments, + "year" => $year, + "apartment" => $apartment, + //"holidayCountry" => $holidayCountry, + //'selectedSubdivision' => $selectedSubdivision )); } @@ -127,6 +222,7 @@ public function tableSettingsAction(ManagerRegistry $doctrine, RequestStack $req $objects = $em->getRepository(Subsidiary::class)->findAll(); $selectedCountry = $request->request->get("holidayCountry", 'DE'); $selectedSubdivision = $request->request->get("holidaySubdivision", 'all'); + $requestStack->getSession()->set("reservation-overview", "table"); $objectId = $requestStack->getSession()->get("reservation-overview-objectid", "all"); diff --git a/src/Repository/AppartmentRepository.php b/src/Repository/AppartmentRepository.php index cc10be7..74fbeec 100644 --- a/src/Repository/AppartmentRepository.php +++ b/src/Repository/AppartmentRepository.php @@ -1,67 +1,79 @@ -format('%a'); - - $em = $this->getEntityManager(); - if ($object == "all") { - $appartments = $em->getRepository(Appartment::class)->findAll(); - } else { - $appartments = $em->getRepository(Appartment::class)->findByObject($object); - } - - $appartmentsAvailable = array(); - foreach ($appartments as $appartment) { - $reservationsForAppartment = $em->getRepository(Reservation::class)->loadReservationsForPeriodForSingleAppartmentWithoutStartAndEndDate(strtotime($startDate), $intervall, $appartment); - if ($reservationsForAppartment == null) { - $appartmentsAvailable[] = $appartment; - } - } - - return $appartmentsAvailable; - } - - public function loadSumBedsMinForObject($objectId) - { - if($objectId === "all") { - $query = $this->createQueryBuilder('u') - ->select('SUM(u.beds_max)') - //->addGroupBy('u.object') - ->getQuery(); - } else { - $query = $this->createQueryBuilder('u') - ->select('SUM(u.beds_max)') - ->where('u.object = :id') - //->addGroupBy('u.object') - ->setParameter('id', $objectId) - ->getQuery(); - } - - try { - return $query->getSingleScalarResult(); - } catch (NoResultException $ex) { - return 0; - } - } -} +format('%a'); + + $em = $this->getEntityManager(); + if ($object == "all") { + $appartments = $em->getRepository(Appartment::class)->findAll(); + } else { + $appartments = $em->getRepository(Appartment::class)->findByObject($object); + } + + $appartmentsAvailable = array(); + foreach ($appartments as $appartment) { + $reservationsForAppartment = $em->getRepository(Reservation::class)->loadReservationsForPeriodForSingleAppartmentWithoutStartAndEndDate(strtotime($startDate), $intervall, $appartment); + if ($reservationsForAppartment == null) { + $appartmentsAvailable[] = $appartment; + } + } + + return $appartmentsAvailable; + } + + public function loadSumBedsMinForObject($objectId) + { + if($objectId === "all") { + $query = $this->createQueryBuilder('u') + ->select('SUM(u.beds_max)') + //->addGroupBy('u.object') + ->getQuery(); + } else { + $query = $this->createQueryBuilder('u') + ->select('SUM(u.beds_max)') + ->where('u.object = :id') + //->addGroupBy('u.object') + ->setParameter('id', $objectId) + ->getQuery(); + } + + try { + return $query->getSingleScalarResult(); + } catch (NoResultException $ex) { + return 0; + } + } + + public function findAllByProperty($propertyId = 'all'): array { + if($propertyId === 'all') { + return $this->findAll(); + } + + return $this->createQueryBuilder('a') + ->andWhere('a.object.id = :id') + ->setParameter('id', $propertyId) + ->getQuery() + ->getResult(); + } +} diff --git a/src/Repository/ReservationRepository.php b/src/Repository/ReservationRepository.php index 36a72ee..c8d08b2 100644 --- a/src/Repository/ReservationRepository.php +++ b/src/Repository/ReservationRepository.php @@ -1,258 +1,258 @@ -createQueryBuilder('u') - ->select('u') - ->where('u.appartment = :app ') - ->andWhere('((u.startDate >= :start AND u.endDate <= :end) OR' - . '(u.startDate < :start AND u.endDate >= :start) OR' - . '(u.startDate <= :end AND u.endDate > :end) OR' - . '(u.startDate < :start AND u.endDate > :end))') - ->setParameter('start', $start) - ->setParameter('end', $end) - ->setParameter('app', $appartment->getId()) - ->addOrderBy('u.endDate', 'ASC') - ->getQuery(); - - $reservations = null; - try { - $reservations = $q->getResult(); - } catch (NoResultException $e) { - - } - - return $reservations; - } - - public function loadReservationsForPeriod($startDate, $endDate) - { - $start = date('Y-m-d', strtotime($startDate)); - $end = date('Y-m-d', strtotime($endDate)); - -// if($customer == null) { - $q = $this - ->createQueryBuilder('u') - ->select('u') - ->andWhere('((u.startDate >= :start AND u.startDate < :end AND u.endDate > :start AND u.endDate <= :end) OR' - . '(u.startDate <= :start AND u.endDate > :start AND u.endDate <= :end) OR' - . '(u.startDate >= :start AND u.startDate < :end AND u.endDate > :end) OR' - . '(u.startDate <= :start AND u.endDate >= :end))') - //->andWhere('u.invoice IS NULL') - ->setParameter('start', $start) - ->setParameter('end', $end) - ->addOrderBy('u.endDate', 'ASC') - ->getQuery(); -// } else { -// $q = $this -// ->createQueryBuilder('u') -// ->select('u') -// ->andWhere('((u.startDate >= :start AND u.startDate < :end AND u.endDate > :start AND u.endDate <= :end) OR' -// . '(u.startDate <= :start AND u.endDate > :start AND u.endDate <= :end) OR' -// . '(u.startDate >= :start AND u.startDate < :end AND u.endDate > :end) OR' -// . '(u.startDate <= :start AND u.endDate >= :end))') -// ->andWhere('u.invoice IS NULL') -// ->andWhere('u.booker = :customer') -// ->setParameter('start', $start) -// ->setParameter('end', $end) -// ->setParameter('customer', $customer) -// ->addOrderBy('u.endDate', 'ASC') -// ->getQuery(); -// } - - $reservations = null; - try { - $reservations = $q->getResult(); - } catch (NoResultException $e) { - - } - - return $reservations; - } - - public function supportsClass($class) - { - return $this->getEntityName() === $class - || is_subclass_of($class, $this->getEntityName()); - } - - public function loadReservationsForPeriodForSingleAppartmentWithoutStartAndEndDate($startDate, $period, \App\Entity\Appartment $appartment) - { - $start = date('Y-m-d', $startDate); - $end = date('Y-m-d', $startDate + ($period * 3600 * 24)); - - $q = $this - ->createQueryBuilder('u') - ->select('u') - ->where('u.appartment = :app ') - ->andWhere('((u.startDate >= :start AND u.startDate < :end AND u.endDate > :start AND u.endDate <= :end) OR' - . '(u.startDate <= :start AND u.endDate > :start AND u.endDate <= :end) OR' - . '(u.startDate >= :start AND u.startDate < :end AND u.endDate > :end) OR' - . '(u.startDate <= :start AND u.endDate >= :end))') - ->setParameter('start', $start) - ->setParameter('end', $end) - ->setParameter('app', $appartment->getId()) - ->addOrderBy('u.endDate', 'ASC') - ->getQuery(); - - $reservations = null; - try { - $reservations = $q->getResult(); - } catch (NoResultException $e) { - - } - - return $reservations; - } - - public function loadReservationsWithoutInvoiceForCustomer(\App\Entity\Customer $customer) - { - $q = $this - ->createQueryBuilder('u') - ->select('u') - ->where('u.booker = :booker') - //->andWhere('u.invoice IS NULL') - ->setParameter('booker', $customer->getId()) - ->addOrderBy('u.endDate', 'ASC') - ->getQuery(); - - $reservations = null; - try { - $reservations = $q->getResult(); - } catch (NoResultException $e) { - - } - - return $reservations; - } - - public function loadUtilizationForDay($day, $objectId) - { - if($objectId === "all") { - $query = $this->createQueryBuilder('u') - ->select('SUM(u.persons)') - ->where(':day >= u.startDate and :day < u.endDate') - //->andWhere('u.status=1') - //->addGroupBy('u.persons') - ->setParameter('day', $day) - ->getQuery(); - } else { - $query = $this->createQueryBuilder('u') - ->select('SUM(u.persons)') - ->where('a.object = :objId and :day >= u.startDate and :day < u.endDate') - //->andWhere('u.status=1') - ->join('u.appartment', 'a') - //->addGroupBy('u.persons') - ->setParameter('day', $day) - ->setParameter('objId', $objectId) - ->getQuery(); - } - - - try { - return $query->getSingleScalarResult(); - } catch (NoResultException $ex) { - return 0; - } - } - - public function getMinEndDate() - { - $q = $this - ->createQueryBuilder('r') - ->select('MIN(r.endDate)') - ->getQuery(); - return $q->getSingleScalarResult(); - } - - public function getMaxStartDate() - { - $q = $this - ->createQueryBuilder('r') - ->select('MAX(r.startDate)') - ->getQuery(); - return $q->getSingleScalarResult(); - } - - public function loadReservationsForMonth($month, $year, $objectId) - { - $startTs = strtotime($year.'-'.$month.'-01'); - $endDate = new \DateTime($year.'-'.$month.'-'.date("t", $startTs)); - $start = date('Y-m-d', $startTs); - $end = $endDate->format('Y-m-d'); - - $q = $this - ->createQueryBuilder('u') - ->select('u') - ->join('u.appartment', 'a') - //->where('u.status=1') - ->where('((u.startDate >= :start AND u.startDate < :end AND u.endDate > :start AND u.endDate <= :end) OR' - . '(u.startDate <= :start AND u.endDate > :start AND u.endDate <= :end) OR' - . '(u.startDate >= :start AND u.startDate < :end AND u.endDate > :end) OR' - . '(u.startDate <= :start AND u.endDate >= :end))') - ->setParameter('start', $start) - ->setParameter('end', $end) - ->addOrderBy('u.endDate', 'ASC'); - - if($objectId !== 'all') { - $q->andWhere('a.object = :objId') - ->setParameter('objId', $objectId); - } - - try { - return $q->getQuery()->getResult(); - } catch (NoResultException $e) { - return Array(); - } - } - - public function loadOriginStatisticForPeriod($start, $end, $objectId) - { - if($objectId === "all") { - $query = $this->createQueryBuilder('u') - ->select('ro.id, COUNT(u.id) as origins') - ->join('u.reservationOrigin', 'ro') - ->where('u.startDate >= :start and u.endDate <= :end') - //->andWhere('u.status=1') - ->addGroupBy('u.reservationOrigin') - ->setParameter('start', $start) - ->setParameter('end', $end) - ->getQuery(); - } else { - $query = $this->createQueryBuilder('u') - ->select('ro.id, COUNT(u.id) as origins') - ->join('u.reservationOrigin', 'ro') - ->where('a.object = :objId and u.startDate >= :start and u.endDate <= :end') - //->andWhere('u.status=1') - ->join('u.appartment', 'a') - ->addGroupBy('u.reservationOrigin') - ->setParameter('start', $start) - ->setParameter('end', $end) - ->setParameter('objId', $objectId) - ->getQuery(); - } - - try { - return $query->getArrayResult(); - } catch (NoResultException $ex) { - return 0; - } - } -} +createQueryBuilder('u') + ->select('u') + ->where('u.appartment = :app ') + ->andWhere('((u.startDate >= :start AND u.endDate <= :end) OR' + . '(u.startDate < :start AND u.endDate >= :start) OR' + . '(u.startDate <= :end AND u.endDate > :end) OR' + . '(u.startDate < :start AND u.endDate > :end))') + ->setParameter('start', $start) + ->setParameter('end', $end) + ->setParameter('app', $appartment->getId()) + ->addOrderBy('u.endDate', 'ASC') + ->getQuery(); + + $reservations = null; + try { + $reservations = $q->getResult(); + } catch (NoResultException $e) { + + } + + return $reservations; + } + + public function loadReservationsForPeriod($startDate, $endDate) + { + $start = date('Y-m-d', strtotime($startDate)); + $end = date('Y-m-d', strtotime($endDate)); + +// if($customer == null) { + $q = $this + ->createQueryBuilder('u') + ->select('u') + ->andWhere('((u.startDate >= :start AND u.startDate < :end AND u.endDate > :start AND u.endDate <= :end) OR' + . '(u.startDate <= :start AND u.endDate > :start AND u.endDate <= :end) OR' + . '(u.startDate >= :start AND u.startDate < :end AND u.endDate > :end) OR' + . '(u.startDate <= :start AND u.endDate >= :end))') + //->andWhere('u.invoice IS NULL') + ->setParameter('start', $start) + ->setParameter('end', $end) + ->addOrderBy('u.endDate', 'ASC') + ->getQuery(); +// } else { +// $q = $this +// ->createQueryBuilder('u') +// ->select('u') +// ->andWhere('((u.startDate >= :start AND u.startDate < :end AND u.endDate > :start AND u.endDate <= :end) OR' +// . '(u.startDate <= :start AND u.endDate > :start AND u.endDate <= :end) OR' +// . '(u.startDate >= :start AND u.startDate < :end AND u.endDate > :end) OR' +// . '(u.startDate <= :start AND u.endDate >= :end))') +// ->andWhere('u.invoice IS NULL') +// ->andWhere('u.booker = :customer') +// ->setParameter('start', $start) +// ->setParameter('end', $end) +// ->setParameter('customer', $customer) +// ->addOrderBy('u.endDate', 'ASC') +// ->getQuery(); +// } + + $reservations = null; + try { + $reservations = $q->getResult(); + } catch (NoResultException $e) { + + } + + return $reservations; + } + + public function supportsClass($class) + { + return $this->getEntityName() === $class + || is_subclass_of($class, $this->getEntityName()); + } + + public function loadReservationsForPeriodForSingleAppartmentWithoutStartAndEndDate($startDate, $period, \App\Entity\Appartment $appartment) + { + $start = date('Y-m-d', $startDate); + $end = date('Y-m-d', $startDate + ($period * 3600 * 24)); + + $q = $this + ->createQueryBuilder('u') + ->select('u') + ->where('u.appartment = :app ') + ->andWhere('((u.startDate >= :start AND u.startDate < :end AND u.endDate > :start AND u.endDate <= :end) OR' + . '(u.startDate <= :start AND u.endDate > :start AND u.endDate <= :end) OR' + . '(u.startDate >= :start AND u.startDate < :end AND u.endDate > :end) OR' + . '(u.startDate <= :start AND u.endDate >= :end))') + ->setParameter('start', $start) + ->setParameter('end', $end) + ->setParameter('app', $appartment->getId()) + ->addOrderBy('u.endDate', 'ASC') + ->getQuery(); + + $reservations = null; + try { + $reservations = $q->getResult(); + } catch (NoResultException $e) { + + } + + return $reservations; + } + + public function loadReservationsWithoutInvoiceForCustomer(\App\Entity\Customer $customer) + { + $q = $this + ->createQueryBuilder('u') + ->select('u') + ->where('u.booker = :booker') + //->andWhere('u.invoice IS NULL') + ->setParameter('booker', $customer->getId()) + ->addOrderBy('u.endDate', 'ASC') + ->getQuery(); + + $reservations = null; + try { + $reservations = $q->getResult(); + } catch (NoResultException $e) { + + } + + return $reservations; + } + + public function loadUtilizationForDay($day, $objectId) + { + if($objectId === "all") { + $query = $this->createQueryBuilder('u') + ->select('SUM(u.persons)') + ->where(':day >= u.startDate and :day < u.endDate') + //->andWhere('u.status=1') + //->addGroupBy('u.persons') + ->setParameter('day', $day) + ->getQuery(); + } else { + $query = $this->createQueryBuilder('u') + ->select('SUM(u.persons)') + ->where('a.object = :objId and :day >= u.startDate and :day < u.endDate') + //->andWhere('u.status=1') + ->join('u.appartment', 'a') + //->addGroupBy('u.persons') + ->setParameter('day', $day) + ->setParameter('objId', $objectId) + ->getQuery(); + } + + + try { + return $query->getSingleScalarResult(); + } catch (NoResultException $ex) { + return 0; + } + } + + public function getMinEndDate() + { + $q = $this + ->createQueryBuilder('r') + ->select('MIN(r.endDate)') + ->getQuery(); + return $q->getSingleScalarResult(); + } + + public function getMaxStartDate() + { + $q = $this + ->createQueryBuilder('r') + ->select('MAX(r.startDate)') + ->getQuery(); + return $q->getSingleScalarResult(); + } + + public function loadReservationsForMonth($month, $year, $objectId) + { + $startTs = strtotime($year.'-'.$month.'-01'); + $endDate = new \DateTime($year.'-'.$month.'-'.date("t", $startTs)); + $start = date('Y-m-d', $startTs); + $end = $endDate->format('Y-m-d'); + + $q = $this + ->createQueryBuilder('u') + ->select('u') + ->join('u.appartment', 'a') + //->where('u.status=1') + ->where('((u.startDate >= :start AND u.startDate < :end AND u.endDate > :start AND u.endDate <= :end) OR' + . '(u.startDate <= :start AND u.endDate > :start AND u.endDate <= :end) OR' + . '(u.startDate >= :start AND u.startDate < :end AND u.endDate > :end) OR' + . '(u.startDate <= :start AND u.endDate >= :end))') + ->setParameter('start', $start) + ->setParameter('end', $end) + ->addOrderBy('u.endDate', 'ASC'); + + if($objectId !== 'all') { + $q->andWhere('a.object = :objId') + ->setParameter('objId', $objectId); + } + + try { + return $q->getQuery()->getResult(); + } catch (NoResultException $e) { + return Array(); + } + } + + public function loadOriginStatisticForPeriod($start, $end, $objectId) + { + if($objectId === "all") { + $query = $this->createQueryBuilder('u') + ->select('ro.id, COUNT(u.id) as origins') + ->join('u.reservationOrigin', 'ro') + ->where('u.startDate >= :start and u.endDate <= :end') + //->andWhere('u.status=1') + ->addGroupBy('u.reservationOrigin') + ->setParameter('start', $start) + ->setParameter('end', $end) + ->getQuery(); + } else { + $query = $this->createQueryBuilder('u') + ->select('ro.id, COUNT(u.id) as origins') + ->join('u.reservationOrigin', 'ro') + ->where('a.object = :objId and u.startDate >= :start and u.endDate <= :end') + //->andWhere('u.status=1') + ->join('u.appartment', 'a') + ->addGroupBy('u.reservationOrigin') + ->setParameter('start', $start) + ->setParameter('end', $end) + ->setParameter('objId', $objectId) + ->getQuery(); + } + + try { + return $query->getArrayResult(); + } catch (NoResultException $ex) { + return 0; + } + } +} diff --git a/src/Twig/AppTwigExtensions.php b/src/Twig/AppTwigExtensions.php index 1f1e797..cf561aa 100644 --- a/src/Twig/AppTwigExtensions.php +++ b/src/Twig/AppTwigExtensions.php @@ -44,6 +44,7 @@ public function getFunctions() { new TwigFunction('getLocalizedDate', array($this, 'getLocalizedDateFilter')), new TwigFunction('existsById', array($this, 'existsById')), new TwigFunction('getPublicdaysForDay', [$this, 'getPublicdaysForDay']), + new TwigFunction('getReservationsForDay', [$this, 'getReservationsForDay']), ); } @@ -168,4 +169,19 @@ public function existsById($array, $compare) { public function getPublicdaysForDay($date, $code, $locale) { return $this->calendarService->getPublicdaysForDay($date, $code, $locale); } + + public function getReservationsForDay(\DateTimeInterface $day, array $reservations): array { + $result = []; + + /* @var $reservation Reservation */ + foreach($reservations as $reservation) { + $start = new \DateTimeImmutable($reservation->getStartDate()->format("Y-m-d").' UTC'); + $end = new \DateTimeImmutable($reservation->getEndDate()->format("Y-m-d").' UTC'); + // todo store all reservation dates as UTC time + if($day >= $start && $day <= $end) { + $result[] = $reservation; + } + } + return $result; + } } diff --git a/templates/Reservations/_index_table_form.html.twig b/templates/Reservations/_index_table_form.html.twig new file mode 100644 index 0000000..9a25ae2 --- /dev/null +++ b/templates/Reservations/_index_table_form.html.twig @@ -0,0 +1,15 @@ +
+ +
+
+ +
\ No newline at end of file diff --git a/templates/Reservations/_index_yearly_form.html.twig b/templates/Reservations/_index_yearly_form.html.twig new file mode 100644 index 0000000..f2bc35e --- /dev/null +++ b/templates/Reservations/_index_yearly_form.html.twig @@ -0,0 +1,12 @@ +
+ +
+
+ +
\ No newline at end of file diff --git a/templates/Reservations/_resevation_popover.html.twig b/templates/Reservations/_resevation_popover.html.twig new file mode 100644 index 0000000..32c4bf6 --- /dev/null +++ b/templates/Reservations/_resevation_popover.html.twig @@ -0,0 +1,33 @@ + +{% block popTitle %} + {% if reservation is defined and reservation is not null %} + {% set popTitle = reservation.booker.salutation ~ " " ~ reservation.booker.firstname~ " " ~ reservation.booker.lastname %} + {{ popTitle }} + {% endif %} +{% endblock %} +{% block popBody %} + {% if reservation is defined and reservation is not null %} + {% set popBody = reservation.startdate|date('d.m.Y') ~ " - " ~ reservation.enddate|date('d.m.Y') %} + {% set phones = "" %}{% set mails = "" %}{% set mobiles = "" %} + {% for address in reservation.booker.customerAddresses %} + {% if address.phone|length > 0 %} + {% set phones = phones ~ address.phone %} + {% if not loop.last %}{% set phones = phones ~ ", " %}{% endif %} + {% endif %} + {% if address.mobilePhone|length > 0 %} + {% set mobiles = mobiles ~ address.mobilePhone %} + {% if not loop.last %}{% set mobiles = mobiles ~ ", " %}{% endif %} + {% endif %} + {% if address.email|length > 0 %} + {% set mails = mails ~ address.email %} + {% if not loop.last %}{% set mails = mails ~ ", " %}{% endif %} + {% endif %} + {% endfor %} + {% set popBody = popBody ~ " ("~ reservation.reservationStatus.name ~")" %} + {% if phones|length > 0 %} {% set popBody = popBody ~ "
"~ 'customer.phone'|trans~": " ~ phones %}{% endif %} + {% if mobiles|length > 0 %} {% set popBody = popBody ~ "
"~ 'customer.mobilephone'|trans~": " ~ mobiles %}{% endif %} + {% if mails|length > 0 %} {% set popBody = popBody ~ "
"~ 'customer.email'|trans~": " ~ mails %}{% endif %} + {% if reservation.remark|length > 0 %}{% set popBody = popBody ~ "
" ~ reservation.remark|nl2br %}{% endif %} + {{ popBody }} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/Reservations/index.html.twig b/templates/Reservations/index.html.twig index 9003487..054bce9 100644 --- a/templates/Reservations/index.html.twig +++ b/templates/Reservations/index.html.twig @@ -28,21 +28,11 @@
-
- -
-
- -
+ {% if show == 'table' %} + {% include 'Reservations/_index_table_form.html.twig' %} + {% else %} + {% include 'Reservations/_index_yearly_form.html.twig' %} + {% endif %}
-
- {% include 'Reservations/reservation_table_settings.html.twig' %} +
+ + + + + + {% include 'Reservations/reservation_table_settings.html.twig' %}
@@ -73,11 +68,11 @@ $(document).ready(function () { // add dynamic onchage listener for table settings fields - delegate(document.getElementById("table-filter"), 'change', '#table-filter #start, #table-filter select[name="intervall"], #objects, #holidayCountry, #holidaySubdivision', (event) => { + delegate(document.getElementById("table-filter"), 'change', '#table-filter #start, #table-filter #year, #table-filter select[name="intervall"], #table-filter select[name="apartment"], #objects, #holidayCountry, #holidaySubdivision', (event) => { getNewTable(); }); - // add dynamic onchange listener for hiliday country field to load subdivisions of the country + // add dynamic onchange listener for holiday country field to load subdivisions of the country delegate(document.getElementById("modal-content-settings"), 'change', '#holidayCountry', (event) => { loadTableSettings(tableSettingsUrl); }); @@ -85,6 +80,7 @@ // initially load table settings based on local storage values getLocalTableSetting('intervall', 'reservations-intervall', 'int'); getLocalTableSetting('holidayCountry', 'reservations-table-holidaycountry'); + getLocalTableSetting('apartment', 'reservations-apartment', 'int'); loadTableSettings(tableSettingsUrl, true); }); @@ -149,6 +145,7 @@ setLocalTableSetting('intervall', 'reservations-intervall', 'int'); setLocalTableSetting('holidayCountry', 'reservations-table-holidaycountry'); setLocalTableSetting('holidaySubdivision', 'reservations-table-holidaysubdivision'); + setLocalTableSetting('apartment', 'reservations-apartment', 'int'); $("#table-ajax").html(content); diff --git a/templates/Reservations/reservation_table.html.twig b/templates/Reservations/reservation_table.html.twig index b658856..312b8c3 100644 --- a/templates/Reservations/reservation_table.html.twig +++ b/templates/Reservations/reservation_table.html.twig @@ -140,34 +140,14 @@ {% if shortName != name %} {% set shortName = shortName ~ "..." %} {% endif %} - {# popover #} - {% set popTitle = reservation.booker.salutation ~ " " ~ reservation.booker.firstname~ " " ~ reservation.booker.lastname %} - {% set popBody = reservation.startdate|date('d.m.Y') ~ " - " ~ reservation.enddate|date('d.m.Y') %} - {% set phones = "" %}{% set mails = "" %}{% set mobiles = "" %} - {% for address in reservation.booker.customerAddresses %} - {% if address.phone|length > 0 %} - {% set phones = phones ~ address.phone %} - {% if not loop.last %}{% set phones = phones ~ ", " %}{% endif %} - {% endif %} - {% if address.mobilePhone|length > 0 %} - {% set mobiles = mobiles ~ address.mobilePhone %} - {% if not loop.last %}{% set mobiles = mobiles ~ ", " %}{% endif %} - {% endif %} - {% if address.email|length > 0 %} - {% set mails = mails ~ address.email %} - {% if not loop.last %}{% set mails = mails ~ ", " %}{% endif %} - {% endif %} - {% endfor %} - {% set popBody = popBody ~ " ("~ reservation.reservationStatus.name ~")" %} - {% if phones|length > 0 %} {% set popBody = popBody ~ "
"~ 'customer.phone'|trans~": " ~ phones %}{% endif %} - {% if mobiles|length > 0 %} {% set popBody = popBody ~ "
"~ 'customer.mobilephone'|trans~": " ~ mobiles %}{% endif %} - {% if mails|length > 0 %} {% set popBody = popBody ~ "
"~ 'customer.email'|trans~": " ~ mails %}{% endif %} - {% if reservation.remark|length > 0 %}{% set popBody = popBody ~ "
" ~ reservation.remark|nl2br %}{% endif %} + + {# popover #} + {% use 'Reservations/_resevation_popover.html.twig' %}
{{ shortName }}
+ title="{{ block('popTitle') }}" data-bs-content="{{ block('popBody') }}">{{ shortName }}
{% if printSpacerAfter %} diff --git a/templates/Reservations/reservation_table_month.html.twig b/templates/Reservations/reservation_table_month.html.twig new file mode 100644 index 0000000..a84bbd5 --- /dev/null +++ b/templates/Reservations/reservation_table_month.html.twig @@ -0,0 +1,118 @@ +{# year and month are provided #} +{% set startDate = date(year ~ '-' ~ month ~ '-01 UTC') %} + +{# this is a dummy date where the first of month is monday which is used for the table header #} +{% set dummyDate = date('2021-11-01 UTC') %} +{% set showCalenderWeek = true %} +{% set daysInMonth = startDate|date('t') %} +{% set reservations = get_reservations_for_period(startDate.timestamp, daysInMonth-1, apartment) %} +{# popover #} +{% use 'Reservations/_resevation_popover.html.twig' %} + + + {# placeholder for calendarweek #} + {% if showCalenderWeek %}{% set headerColspan = 15 %}{% else %}{% set headerColspan = 14 %}{% endif %} + + + + + {# placeholder for calendarweek #} + {% if showCalenderWeek %} + + {% endfor %} + + + + {% set lastWeek = startDate|date('W') %} + {% set weekDayStart = startDate|date('w') %} + {# if sunday (0) set it to 7 otherwise the loop to print the td before the actual start of the month starts will fail #} + {% if weekDayStart == 0 %}{% set weekDayStart = 7 %}{% endif %} + {% set tmpDate = startDate %} + + {# always start line at the beginning but not if 1. of month is a monday (this is done in the main loop) #} + {% if weekDayStart != 1 %} + + {% if showCalenderWeek %} + + {% endif %} + {% endif %} + + {# print days before loop, print missing tds minus one for the first day of month #} + {% set i = 1 %} + {% for i in 1..weekDayStart %} + {% if i < weekDayStart %} + + {% endif %} + {% endfor %} + + {# print main month loop #} + {% for i in 0..(daysInMonth-1) %} + {% set addToDate = "+" ~ i ~ "day" %} + {% set tmpDate = startDate|date_modify(addToDate) %} + {# always start line on monday #} + {% if tmpDate|date('w') == 1 %} + + {% if showCalenderWeek %} + + {% endif %} + {% endif %} + + {% set reservationsForDay = getReservationsForDay(tmpDate, reservations) %} + {% set style = '' %}{% set action = '' %}{% set popClass = '' %}{% set tdContent = '' %}{% set resCss = 'reservation-popover reservation-yearly' %} + {% set noEvents = '' %} + {# currently max two events are allowed for one day (end of one reservation and start of another reservation) #} + {% for reservation in reservationsForDay %} + {% set style = 'background-color:' ~ reservation.reservationStatus.color %} + {% set action = ' onclick="return getReservation(' ~ reservation.id ~ ');"' %} + {% set noEvents = ' no-pointer-events' %} + {% if loop.length == 1 %} + {% set resCol = 2 %} + {% else %} + {% set resCol = 1 %} + {% if loop.index == 1 %} + {% set resCss = resCss ~ ' two-reservations two-reservations-left' %} + {% else %} + {% set resCss = resCss ~ ' two-reservations two-reservations-right' %} + {% endif %} + {% endif %} + + {% set tdContent = tdContent ~ '
 
' %} + {% endfor %} + + + + {# always end line on sunday #} + {% if tmpDate|date('w') == 0 %} + + {% endif %} + {% endfor %} + + {% set weekDayEnd = tmpDate|date('w') %} + {# if sunday (0) set it to 7 otherwise the loop to print the td after the actual end of the month will fail #} + {% if weekDayEnd == 0 %}{% set weekDayEnd = 7 %}{% endif %} + {# print days after loop #} + {% set i = 1 %} + {% for i in weekDayEnd..7 %} + {% if i < 7 %} + + {% endif %} + {% endfor %} + + {# always end line at the end but not if last day of month is a sunday (this is done in the main loop) #} + {% if weekDayEnd != 7 %} + + {% endif %} + +
{{ getLocalizedMonth(month, 'MMMM', app.request.locale) }} {{ year }}
W + {% endif %} + {# print week days #} + {% for i in 0..6 %} + {% set addToDate = "+" ~ i ~ "day" %} + {% set tmpDate = dummyDate|date_modify(addToDate) %} + {# check for weekend #} + {% if tmpDate|date('N') == 6 or tmpDate|date('N') == 7 %} + {% set weekendCss = ' reservation-table-weekend-single' %} + {% else %} + {% set weekendCss = '' %} + {% endif %} + {{ getLocalizedDate(tmpDate, 'EEEEEE', app.request.locale) }}
{{ tmpDate|date('W') }}
{{ tmpDate|date('W') }}
{{ tdContent|raw }}
{{ tmpDate|date('d') }}
diff --git a/templates/Reservations/reservation_table_settings_input_fields.html.twig b/templates/Reservations/reservation_table_settings_input_fields.html.twig index 9e7bcff..776d09d 100644 --- a/templates/Reservations/reservation_table_settings_input_fields.html.twig +++ b/templates/Reservations/reservation_table_settings_input_fields.html.twig @@ -1,6 +1,6 @@