diff --git a/migrations/Version20260206200000.php b/migrations/Version20260206200000.php new file mode 100644 index 0000000..3ffb33a --- /dev/null +++ b/migrations/Version20260206200000.php @@ -0,0 +1,37 @@ +addSql('ALTER TABLE etat_lieux ADD account_id INT DEFAULT NULL'); + $this->addSql("ALTER TABLE etat_lieux ADD status VARCHAR(50) DEFAULT 'delivery_progress' NOT NULL"); + $this->addSql('ALTER TABLE etat_lieux ADD CONSTRAINT FK_D71603599B6B5FBA FOREIGN KEY (account_id) REFERENCES account (id)'); + $this->addSql('CREATE INDEX IDX_D71603599B6B5FBA ON etat_lieux (account_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE etat_lieux DROP FOREIGN KEY FK_D71603599B6B5FBA'); + $this->addSql('DROP INDEX IDX_D71603599B6B5FBA ON etat_lieux'); + $this->addSql('ALTER TABLE etat_lieux DROP account_id'); + $this->addSql('ALTER TABLE etat_lieux DROP status'); + } +} diff --git a/migrations/Version20260206210000.php b/migrations/Version20260206210000.php new file mode 100644 index 0000000..49c5ce6 --- /dev/null +++ b/migrations/Version20260206210000.php @@ -0,0 +1,32 @@ +addSql("ALTER TABLE etat_lieux ALTER status SET DEFAULT 'delivery_ready'"); + $this->addSql("UPDATE etat_lieux SET status = 'delivery_ready' WHERE status = 'delivery_progress'"); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql("ALTER TABLE etat_lieux ALTER status SET DEFAULT 'delivery_progress'"); + } +} diff --git a/src/Controller/EtlController.php b/src/Controller/EtlController.php index 9b8e48b..9582784 100644 --- a/src/Controller/EtlController.php +++ b/src/Controller/EtlController.php @@ -3,9 +3,13 @@ namespace App\Controller; use App\Entity\Account; +use App\Entity\Contrats; +use App\Entity\ContratsPayments; +use App\Entity\EtatLieux; use App\Entity\Prestaire; use App\Form\PrestairePasswordType; use App\Repository\ContratsRepository; +use App\Service\Mailer\Mailer; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -29,19 +33,117 @@ class EtlController extends AbstractController $missions = []; $states = ['ready', 'pending']; + $qb = $contratsRepository->createQueryBuilder('c'); + $qb->select('count(c.id)'); + if ($user instanceof Prestaire) { + $qb->andWhere('c.prestataire = :user')->setParameter('user', $user); + } + $totalMissions = $qb->getQuery()->getSingleScalarResult(); + + $qb = $contratsRepository->createQueryBuilder('c'); + $qb->select('count(c.id)'); + $qb->andWhere('c.dateAt >= :now')->setParameter('now', new \DateTime()); + $qb->andWhere('c.reservationState IN (:states)')->setParameter('states', $states); + if ($user instanceof Prestaire) { + $qb->andWhere('c.prestataire = :user')->setParameter('user', $user); + } + $upcomingMissions = $qb->getQuery()->getSingleScalarResult(); + if ($user instanceof Account) { - // Admins see all active missions - $missions = $contratsRepository->findBy(['reservationState' => $states], ['dateAt' => 'ASC']); + $missions = $contratsRepository->findBy(['reservationState' => $states], ['dateAt' => 'ASC'], 5); } elseif ($user instanceof Prestaire) { - // Providers see only their missions - $missions = $contratsRepository->findBy(['reservationState' => $states, 'prestataire' => $user], ['dateAt' => 'ASC']); + $missions = $contratsRepository->findBy(['reservationState' => $states, 'prestataire' => $user], ['dateAt' => 'ASC'], 5); } return $this->render('etl/home.twig', [ + 'missions' => $missions, + 'totalMissions' => $totalMissions, + 'upcomingMissions' => $upcomingMissions + ]); + } + + #[Route('/etl/contrats', name: 'etl_contrats')] + public function eltContrats(ContratsRepository $contratsRepository): Response + { + $user = $this->getUser(); + if (!$user) { + return $this->redirectToRoute('etl_login'); + } + + $missions = []; + $states = ['ready', 'pending']; + + if ($user instanceof Account) { + $missions = $contratsRepository->findBy(['reservationState' => $states], ['dateAt' => 'ASC']); + } elseif ($user instanceof Prestaire) { + $missions = $contratsRepository->findBy(['reservationState' => $states, 'prestataire' => $user], ['dateAt' => 'ASC']); + } + + return $this->render('etl/contrats.twig', [ 'missions' => $missions ]); } + #[Route('/etl/mission/{id}', name: 'etl_contrat_view', methods: ['GET'])] + public function eltContratView(Contrats $contrat): Response + { + $user = $this->getUser(); + if (!$user) { + return $this->redirectToRoute('etl_login'); + } + + // Security check for Prestaire + if ($user instanceof Prestaire && $contrat->getPrestataire() !== $user) { + throw $this->createAccessDeniedException('Vous n\'avez pas accès à cette mission.'); + } + + return $this->render('etl/view.twig', [ + 'mission' => $contrat + ]); + } + + #[Route('/etl/mission/{id}/start', name: 'etl_mission_start', methods: ['POST'])] + public function eltMissionStart(Contrats $contrat, EntityManagerInterface $em, Mailer $mailer): Response + { + $user = $this->getUser(); + if (!$user) { + return $this->redirectToRoute('etl_login'); + } + + if ($user instanceof Prestaire && $contrat->getPrestataire() !== $user) { + throw $this->createAccessDeniedException('Vous n\'avez pas accès à cette mission.'); + } + + $etatLieux = $contrat->getEtatLieux(); + if (!$etatLieux) { + $etatLieux = new EtatLieux(); + $etatLieux->setContrat($contrat); + if ($user instanceof Prestaire) { + $etatLieux->setPrestataire($user); + } elseif ($user instanceof Account) { + $etatLieux->setAccount($user); + } + $em->persist($etatLieux); + } + + $etatLieux->setStatus('delivery_progress'); + $em->flush(); + + // Notification client + if ($contrat->getCustomer()) { + $mailer->send( + $contrat->getCustomer()->getEmail(), + $contrat->getCustomer()->getName(), + "Votre commande est en route ! - #" . $contrat->getNumReservation(), + "mails/customer/delivery_start.twig", + ['contrat' => $contrat] + ); + } + + $this->addFlash('success', 'Livraison démarrée, le client a été notifié.'); + return $this->redirectToRoute('etl_contrat_view', ['id' => $contrat->getId()]); + } + #[Route('/etl/account', name: 'etl_account', methods: ['GET', 'POST'])] public function eltAccount( Request $request, diff --git a/src/Entity/Account.php b/src/Entity/Account.php index dee39d1..e950c80 100644 --- a/src/Entity/Account.php +++ b/src/Entity/Account.php @@ -75,6 +75,13 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, TwoF */ #[ORM\OneToMany(targetEntity: AuditLog::class, mappedBy: 'account')] private Collection $auditLogs; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: EtatLieux::class, mappedBy: 'account')] + private Collection $etatLieuxes; + #[ORM\Column(type: 'string', nullable: true)] private ?string $confirmationToken; @@ -82,6 +89,7 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, TwoF { $this->accountLoginRegisters = new ArrayCollection(); $this->auditLogs = new ArrayCollection(); + $this->etatLieuxes = new ArrayCollection(); } public function getId(): ?int @@ -89,6 +97,36 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface, TwoF return $this->id; } + /** + * @return Collection + */ + public function getEtatLieuxes(): Collection + { + return $this->etatLieuxes; + } + + public function addEtatLieux(EtatLieux $etatLieux): static + { + if (!$this->etatLieuxes->contains($etatLieux)) { + $this->etatLieuxes->add($etatLieux); + $etatLieux->setAccount($this); + } + + return $this; + } + + public function removeEtatLieux(EtatLieux $etatLieux): static + { + if ($this->etatLieuxes->removeElement($etatLieux)) { + // set the owning side to null (unless already changed) + if ($etatLieux->getAccount() === $this) { + $etatLieux->setAccount(null); + } + } + + return $this; + } + public function getUsername(): ?string { return $this->username; diff --git a/src/Entity/EtatLieux.php b/src/Entity/EtatLieux.php index 5b03a56..93f9de4 100644 --- a/src/Entity/EtatLieux.php +++ b/src/Entity/EtatLieux.php @@ -19,11 +19,41 @@ class EtatLieux #[ORM\ManyToOne(inversedBy: 'etatLieuxes')] private ?Prestaire $prestataire = null; + #[ORM\ManyToOne(inversedBy: 'etatLieuxes')] + private ?Account $account = null; + + #[ORM\Column(length: 50, options: ['default' => 'delivery_ready'])] + private string $status = 'delivery_ready'; + public function getId(): ?int { return $this->id; } + public function getStatus(): string + { + return $this->status; + } + + public function setStatus(string $status): static + { + $this->status = $status; + + return $this; + } + + public function getAccount(): ?Account + { + return $this->account; + } + + public function setAccount(?Account $account): static + { + $this->account = $account; + + return $this; + } + public function getContrat(): ?Contrats { return $this->contrat; diff --git a/templates/etl/base.twig b/templates/etl/base.twig index 791b155..b1b57d3 100644 --- a/templates/etl/base.twig +++ b/templates/etl/base.twig @@ -50,7 +50,7 @@ Accueil - + Missions diff --git a/templates/etl/contrats.twig b/templates/etl/contrats.twig new file mode 100644 index 0000000..21bff77 --- /dev/null +++ b/templates/etl/contrats.twig @@ -0,0 +1,54 @@ +{% extends 'etl/base.twig' %} + +{% block title %}Mes Missions{% endblock %} + +{% block body %} + +{% endblock %} diff --git a/templates/etl/home.twig b/templates/etl/home.twig index a178c4d..cbb86fe 100644 --- a/templates/etl/home.twig +++ b/templates/etl/home.twig @@ -15,11 +15,11 @@
Missions - 0 + {{ totalMissions }}
À venir - 0 + {{ upcomingMissions }}
@@ -28,37 +28,50 @@

Accès Rapide

- - - {# UPCOMING LIST (Placeholder) #} -
-

Prochainement

- -
-
- -
-

Aucune mission à venir

-

Votre planning est vide pour le moment.

-
-
-
{% endblock %} diff --git a/templates/etl/view.twig b/templates/etl/view.twig new file mode 100644 index 0000000..f84a840 --- /dev/null +++ b/templates/etl/view.twig @@ -0,0 +1,151 @@ +{% extends 'etl/base.twig' %} + +{% block title %}Mission #{{ mission.numReservation }}{% endblock %} + +{% block body %} +
+ + {# HEADER #} +
+ + + +
+

Détails Mission

+

Réf: #{{ mission.numReservation }}

+
+
+ + {# ACTION LIVRAISON #} + {% if not mission.etatLieux or mission.etatLieux.status == 'delivery_ready' %} +
+ +
+ {% elseif mission.etatLieux.status == 'delivery_progress' %} +
+ + + + + Livraison en cours +
+ {% endif %} + + {# DATES #} +
+
+
+
+

Début

+

{{ mission.dateAt|date('d/m') }}

+

{{ mission.dateAt|date('H:i') }}

+
+
+
+

Fin

+

{{ mission.endAt|date('d/m') }}

+

{{ mission.endAt|date('H:i') }}

+
+
+
+ + {# CLIENT #} +
+

Client

+
+
+

{{ mission.customer.surname }} {{ mission.customer.name }}

+
+ + + +
+
+ + {# ADRESSE & GPS #} +
+

Lieu de l'événement

+ +
+

+ {{ mission.addressEvent }}
+ {% if mission.address2Event %}{{ mission.address2Event }}
{% endif %} + {{ mission.zipCodeEvent }} {{ mission.townEvent }} +

+
+ + +
+ + {# PRODUCTS & OPTIONS #} +
+

Matériel & Options

+ +
+ {% for line in mission.contratsLines %} + {% if 'livraison' not in line.name|lower %} +
+
+ +
+
+

{{ line.name }}

+ {% if line.caution > 0 %} +

Caution: {{ line.caution }}€

+ {% endif %} +
+
+ {% endif %} + {% endfor %} + + {% for option in mission.contratsOptions %} + {% if 'livraison' not in option.name|lower %} +
+
+ +
+
+

{{ option.name }}

+ {% if option.details %} +

{{ option.details }}

+ {% endif %} +
+
+ {% endif %} + {% endfor %} +
+
+ + {# DETAILS / NOTES #} + {% if mission.details or mission.notes %} +
+

Notes & Détails

+ {% if mission.details %} +
+

Détails

+

{{ mission.details }}

+
+ {% endif %} + {% if mission.notes %} +
+

Notes internes

+

{{ mission.notes }}

+
+ {% endif %} +
+ {% endif %} + +
+{% endblock %} diff --git a/templates/mails/customer/delivery_start.twig b/templates/mails/customer/delivery_start.twig new file mode 100644 index 0000000..f82d553 --- /dev/null +++ b/templates/mails/customer/delivery_start.twig @@ -0,0 +1,20 @@ +{% extends 'mails/base.twig' %} + +{% block content %} + + Bonjour {{ datas.contrat.customer.surname }} {{ datas.contrat.customer.name }}, + + + Nous avons le plaisir de vous informer que votre commande liée à la réservation #{{ datas.contrat.numReservation }} est en route ! + + + Notre équipe logistique a pris en charge le matériel et se dirige vers le lieu de livraison prévu : + + + {{ datas.contrat.addressEvent }}
+ {{ datas.contrat.zipCodeEvent }} {{ datas.contrat.townEvent }} +
+ + Merci de vous assurer qu'une personne est présente pour réceptionner la livraison. + +{% endblock %}