created_by / updated_by * restent null (« Systeme » cote front), c'est attendu. * * Portee : DONNEES DE DEMONSTRATION (dev uniquement). En environnement `test`, la * fixture ne charge rien : les tests seedent et nettoient leurs propres * transporteurs et comptent sur une table `carrier` vierge — y injecter des * transporteurs de demo casserait les comptages de liste et les cleanups. Meme * garde-fou que ClientFixtures / SupplierFixtures. */ class CarrierFixtures extends Fixture implements DependentFixtureInterface { /** SIRET de la ligne qualimat_carrier de demo (cle naturelle, insert idempotent). */ private const string QUALIMAT_DEMO_SIRET = '90000000000017'; public function __construct( private readonly CarrierFieldNormalizer $normalizer, private readonly SiteProviderInterface $siteProvider, #[Autowire('%kernel.environment%')] private readonly string $environment, ) {} /** * @return array */ public function getDependencies(): array { // Les prix referencent des Client/Supplier/Site de demo (relations ORM // partagees) : ces fixtures doivent tourner avant. return [ SitesFixtures::class, ClientFixtures::class, SupplierFixtures::class, ]; } public function load(ObjectManager $manager): void { // Donnees de demo : dev uniquement. En test, on laisse la table vierge. if ('test' === $this->environment) { return; } // === Transporteur QUALIMAT (RG-4.01) — adresse copiee + validite PASSEE (RG-4.04) === [$grelillier, $isNew] = $this->ensureCarrier($manager, 'Transports Grelillier'); if ($isNew) { $grelillier->setQualimatCarrier($this->ensureQualimatDemoLine($manager)); $grelillier->setCertificationType('QUALIMAT'); // Adresse pre-remplie depuis la copie QUALIMAT (RG-4.05). $this->addAddress($grelillier, '86000', 'Poitiers', '12 rue des Acacias'); $this->addContact($grelillier, 'Marie', 'Martin', 'Exploitation', '06 12 34 56 78', null, 'marie.martin@grelillier.fr'); } // === Transporteur AUTRE + Decharge (RG-4.02) === [$pandele, $isNew] = $this->ensureCarrier($manager, 'Transports Pandele'); if ($isNew) { $pandele->setCertificationType('AUTRE'); $pandele->setDischargeDocument($this->buildDischargeDocument($manager)); $this->addContact($pandele, 'Luc', 'Pandele', 'Gerant', '05 49 11 22 33', null, 'luc.pandele@pandele.fr'); } // === Transporteur affrete (RG-4.03) — indexation + benne + volume === [$affrete, $isNew] = $this->ensureCarrier($manager, 'Affreteurs Reunis'); if ($isNew) { $affrete->setCertificationType('GMP_PLUS'); $affrete->setIsChartered(true); $affrete->setIndexationRate('5.00'); $affrete->setContainerType('BENNE'); $affrete->setVolumeM3('90.00'); $this->addAddress($affrete, '17000', 'La Rochelle', '4 quai des Affreteurs'); } // === Cas LIOT (RG-4.01) — immatriculations, certification non requise === [$liot, $isNew] = $this->ensureCarrier($manager, 'LIOT'); if ($isNew) { $liot->setLiotPlates($this->normalizer->normalizeLiotPlates('ab-123-cd ; ef-456-gh ; gh-789-ij')); } // === Transporteur COMPLET — contacts + adresses + prix CLIENT et FOURNISSEUR === [$complet, $isNew] = $this->ensureCarrier($manager, 'Transports Logistique Globale'); if ($isNew) { $complet->setCertificationType('OVOCOM'); $this->addAddress($complet, '86100', 'Châtellerault', '20 zone des Transporteurs'); $this->addContact($complet, 'Sophie', 'Bernard', 'Directrice', '05 49 44 55 66', '06 99 88 77 66', 'sophie.bernard@logistique-globale.fr', 0); $this->addContact($complet, 'Marc', 'Lopez', 'Affretement', '05 49 44 55 67', null, 'marc.lopez@logistique-globale.fr', 1); $this->addPrices($manager, $complet); } // === Transporteur archive (RG-4.14) === [$archive, $isNew] = $this->ensureCarrier($manager, 'Transports Anciens', isArchived: true); if ($isNew) { $archive->setCertificationType('COMPTE_PROPRE'); $this->addContact($archive, 'Paul', 'Ancien', 'Ex-gerant', '05 49 00 00 00', null, 'paul.ancien@anciens.fr'); } $manager->flush(); } /** * Cree un transporteur (nom normalise UPPERCASE) s'il n'existe pas encore, * sinon retourne l'existant. Retourne [Carrier, isNew] : isNew=false bloque la * reconstruction des sous-collections (idempotence sans doublon). * * @return array{0: Carrier, 1: bool} */ private function ensureCarrier(ObjectManager $manager, string $name, bool $isArchived = false): array { $normalizedName = (string) $this->normalizer->normalizeName($name); $existing = $manager->getRepository(Carrier::class)->findOneBy(['name' => $normalizedName]); if ($existing instanceof Carrier) { return [$existing, false]; } $carrier = new Carrier(); $carrier->setName($normalizedName); if ($isArchived) { $carrier->setIsArchived(true); $carrier->setArchivedAt(new DateTimeImmutable()); } $manager->persist($carrier); return [$carrier, true]; } /** * Ajoute une adresse au transporteur (cascade persist via Carrier.addresses). */ private function addAddress(Carrier $carrier, string $postalCode, string $city, string $street): void { $address = new CarrierAddress(); $address->setPostalCode($postalCode); $address->setCity($city); $address->setStreet($street); $carrier->addAddress($address); } /** * Ajoute un contact normalise au transporteur (cascade persist via * Carrier.contacts). Au moins un champ est toujours fourni (RG-4.08). */ private function addContact( Carrier $carrier, ?string $firstName, ?string $lastName, ?string $jobTitle, ?string $phonePrimary, ?string $phoneSecondary, ?string $email, int $position = 0, ): void { $contact = new CarrierContact(); $contact->setFirstName($this->normalizer->normalizePersonName($firstName)); $contact->setLastName($this->normalizer->normalizePersonName($lastName)); $contact->setJobTitle($jobTitle); $contact->setPhonePrimary($this->normalizer->normalizePhone($phonePrimary)); $contact->setPhoneSecondary($this->normalizer->normalizePhone($phoneSecondary)); $contact->setEmail($this->normalizer->normalizeEmail($email)); $contact->setPosition($position); $carrier->addContact($contact); } /** * Ajoute un prix CLIENT et un prix FOURNISSEUR au transporteur (RG-4.10/4.11), * en resolvant les relations cross-module (client/adresse de livraison + site * de depart ; fournisseur/adresse d'appro + site de livraison) via les contrats * Shared. Si la demo Commercial/Sites n'est pas disponible, les prix sont omis. */ private function addPrices(ObjectManager $manager, Carrier $carrier): void { $site = $this->siteProvider->findByName('Chatellerault'); // Branche CLIENT (RG-4.10) : 1ere adresse de livraison de la demo M1. $clientAddress = $manager->getRepository(ClientAddressInterface::class)->findOneBy(['isDelivery' => true]); if ($site instanceof SiteInterface && $clientAddress instanceof ClientAddressInterface && null !== $clientAddress->getClient()) { $clientPrice = new CarrierPrice(); $clientPrice->setDirection('CLIENT'); $clientPrice->setClient($clientAddress->getClient()); $clientPrice->setClientDeliveryAddress($clientAddress); $clientPrice->setDepartureSite($site); $clientPrice->setContainerType('BENNE'); $clientPrice->setPricingUnit('TONNE'); $clientPrice->setPrice('42.50'); $clientPrice->setPriceState('VALIDE'); $carrier->addPrice($clientPrice); } // Branche FOURNISSEUR (RG-4.11) : 1ere adresse de DEPART de la demo M2. $supplierAddress = $manager->getRepository(SupplierAddressInterface::class)->findOneBy(['addressType' => 'DEPART']); if ($site instanceof SiteInterface && $supplierAddress instanceof SupplierAddressInterface && null !== $supplierAddress->getSupplier()) { $supplierPrice = new CarrierPrice(); $supplierPrice->setDirection('FOURNISSEUR'); $supplierPrice->setSupplier($supplierAddress->getSupplier()); $supplierPrice->setSupplierSupplyAddress($supplierAddress); $supplierPrice->setDeliverySite($site); $supplierPrice->setContainerType('FOND_MOUVANT'); $supplierPrice->setPricingUnit('FORFAIT'); $supplierPrice->setPrice('320.00'); $supplierPrice->setPriceState('EN_COURS'); $carrier->addPrice($supplierPrice); } } /** * Construit (non persiste explicitement — cascade via la FK Carrier) un * UploadedDocument de demo pour la Decharge (RG-4.02). Pas de fichier reel sur * disque : metadonnees factices suffisantes pour la demo. */ private function buildDischargeDocument(ObjectManager $manager): UploadedDocument { $document = new UploadedDocument( 'decharge-demo.pdf', 'demo/decharge-demo.pdf', 'application/pdf', 12_345, str_repeat('0', 64), new DateTimeImmutable(), ); $manager->persist($document); return $document; } /** * Insere (idempotent, par SIRET) une ligne `qualimat_carrier` de demo a * validite PASSEE (RG-4.04) puis retourne l'entite (lecture seule) rechargee. * La table est normalement alimentee par `app:qualimat:sync` ; en demo on pose * une ligne directe en DBAL (l'entite mappee n'expose aucune ecriture API). */ private function ensureQualimatDemoLine(ObjectManager $manager): QualimatCarrier { $repository = $manager->getRepository(QualimatCarrier::class); $existing = $repository->findOneBy(['siret' => self::QUALIMAT_DEMO_SIRET]); if ($existing instanceof QualimatCarrier) { return $existing; } if ($manager instanceof EntityManagerInterface) { $manager->getConnection()->insert('qualimat_carrier', [ 'siret' => self::QUALIMAT_DEMO_SIRET, 'name' => 'TRANSPORTS GRELILLIER', 'address' => '12 rue des Acacias', 'postal_code' => '86000', 'city' => 'Poitiers', 'status' => 'Valide', // Validite PASSEE : exerce le fond rouge RG-4.04 cote front. 'validity_date' => '2024-12-31', 'is_active' => 'true', 'last_synced_at' => new DateTimeImmutable()->format('Y-m-d H:i:s'), ]); } // @var QualimatCarrier $line return $repository->findOneBy(['siret' => self::QUALIMAT_DEMO_SIRET]); } }