= 1 site sur le formulaire principal (RG-3.03), >= 1 * contact, >= 1 adresse multi-sites (RG-3.05), comptabilite + RIB ; * - reglement LCR avec RIB (RG-3.08) ; reglement VIREMENT avec banque (RG-3.07) ; * - 1 prestataire archive (isArchived + archivedAt) pour l'exclusion de la liste * (RG-3.16) ; * - prestataires repartis sur des sites DIFFERENTS (86 / 17 / 82) pour exercer le * cloisonnement par site (RG-3.17) ; * - mono et multi-categories de type PRESTATAIRE (RG-3.09). * * Resolution inter-modules conforme a la regle n°1 (pas d'import de logique) : * - categories resolues via le contrat Shared CategoryInterface ; * - sites resolus via le contrat Shared SiteProviderInterface. * * Normalisation : valeurs fournies BRUTES, normalisees par ProviderFieldNormalizer * avant persist, exactement comme le ferait le ProviderProcessor via l'API * (companyName UPPERCASE, first/last Capitalize, telephones chiffres seuls, emails * lowercase — RG-3.11). * * Idempotence : lookup par companyName normalise (coherent avec l'index unique * partiel uq_provider_company_name_active). Un prestataire deja present n'est pas * reconstruit (sous-collections non redupliquees). Rejouable sans doublon. * * Portee : DONNEES DE DEMONSTRATION (dev uniquement). En environnement `test`, la * fixture ne charge rien : les tests seedent et nettoient leurs propres * prestataires et comptent sur une table `provider` vierge. Meme garde-fou que * SupplierFixtures / CategoryFixtures. */ class ProviderFixtures extends Fixture implements DependentFixtureInterface { /** * Type de categorie exige pour un prestataire et ses adresses (RG-3.09). * Miroir de Provider::REQUIRED_CATEGORY_TYPE_CODE (non importable — regle n°1). */ private const string PROVIDER_CATEGORY_TYPE_CODE = 'PRESTATAIRE'; /** Cache des categories resolues par nom. */ private array $categoryCache = []; /** Cache des sites resolus par nom. */ private array $siteCache = []; /** ObjectManager courant, capture en debut de load. */ private ObjectManager $manager; public function __construct( private readonly ProviderFieldNormalizer $normalizer, private readonly SiteProviderInterface $siteProvider, #[Autowire('%kernel.environment%')] private readonly string $environment, ) {} /** * @return array */ public function getDependencies(): array { return [ CategoryFixtures::class, SitesFixtures::class, CommercialReferentialFixtures::class, ]; } public function load(ObjectManager $manager): void { // Donnees de demo : dev uniquement. En test, on laisse la table vierge. if ('test' === $this->environment) { return; } $this->manager = $manager; // === Prestataire COMPLET — VIREMENT + banque (RG-3.07), compta + RIB, === // === multi-sites sur le formulaire principal ET sur l'adresse. === [$maintenance, $isNew] = $this->ensureProvider($manager, 'Maintenance Pro SAS', ['Maintenance industrielle'], ['Chatellerault', 'Saint-Jean']); if ($isNew) { $maintenance->setSiren('841611054'); $maintenance->setAccountNumber('P0001'); $maintenance->setTvaMode($this->tvaMode($manager, 'FRANCE_VENTES')); $maintenance->setNTva('FR12841611054'); $maintenance->setPaymentDelay($this->paymentDelay($manager, 'J30')); $maintenance->setPaymentType($this->paymentType($manager, 'VIREMENT')); $maintenance->setBank($this->bank($manager, 'SG')); $this->addContact($maintenance, 'Marie', 'Martin', 'Responsable', '05 49 00 00 01', null, 'marie.martin@maintenance-pro.fr'); $this->addAddress($maintenance, ['Chatellerault', 'Saint-Jean'], '86000', 'Poitiers', '12 rue des Acacias', categoryNames: ['Maintenance industrielle']); $this->addRib($maintenance, 'Compte principal', 'BNPAFRPPXXX', 'FR1420041010050500013M02606', 0); } // === LCR avec RIB (RG-3.08) — site Pommevic === [$nettoyage, $isNew] = $this->ensureProvider($manager, 'Nettoyage Sud-Ouest', ['Nettoyage'], ['Pommevic']); if ($isNew) { $nettoyage->setSiren('775680459'); $nettoyage->setTvaMode($this->tvaMode($manager, 'FRANCE_VENTES')); $nettoyage->setPaymentDelay($this->paymentDelay($manager, 'J15')); $nettoyage->setPaymentType($this->paymentType($manager, 'LCR')); $this->addContact($nettoyage, 'Sophie', 'Marchand', 'Directrice', '05 56 10 20 30', '06 11 22 33 44', 'sophie.marchand@nettoyage-so.fr', 0); $this->addContact($nettoyage, 'Marc', 'Girard', 'Chef d\'equipe', '05 56 10 20 31', null, 'marc.girard@nettoyage-so.fr', 1); $this->addAddress($nettoyage, ['Pommevic'], '82400', 'Pommevic', '8 route des Prestations'); $this->addRib($nettoyage, 'Compte principal', 'BNPAFRPPXXX', 'FR7630006000011234567890189', 0); } // === Multi-categories PRESTATAIRE + reglement CHEQUE (sans banque ni RIB) === [$transport, $isNew] = $this->ensureProvider($manager, 'Transport Express Atlantique', ['Transport', 'Maintenance industrielle'], ['Saint-Jean']); if ($isNew) { $transport->setPaymentDelay($this->paymentDelay($manager, 'A_RECEPTION')); $transport->setPaymentType($this->paymentType($manager, 'CHEQUE')); $this->addContact($transport, 'Thomas', 'Petit', 'Responsable logistique', '05 56 31 32 33', null, 'thomas.petit@transport-express.fr'); $this->addAddress($transport, ['Saint-Jean'], '17400', 'Fontenet', '4 zone des Transporteurs', categoryNames: ['Transport']); } // === Prestataire minimal — contact par le seul nom (RG-3.04), site 86 === [$petit, $isNew] = $this->ensureProvider($manager, 'Atelier Soudure Locale', ['Maintenance industrielle'], ['Chatellerault']); if ($isNew) { $this->addContact($petit, null, 'Caron', 'Gerant', '05 49 81 82 83', null, 'contact@atelier-soudure.fr'); $this->addAddress($petit, ['Chatellerault'], '86100', 'Châtellerault', '6 chemin de l\'Atelier'); } // === Prestataire archive (RG-3.16) === [$ancien, $isNew] = $this->ensureProvider($manager, 'Ancien Prestataire Ferme', ['Nettoyage'], ['Chatellerault'], isArchived: true); if ($isNew) { $this->addContact($ancien, null, 'Lambert', 'Ancien contact', '05 49 99 99 99', null, 'contact@ancien-prestataire.fr'); $this->addAddress($ancien, ['Chatellerault'], '86100', 'Châtellerault', '99 rue Fermée'); } $manager->flush(); } /** * Cree un prestataire (base normalisee + categories PRESTATAIRE + sites directs) * s'il n'existe pas, sinon retourne l'existant. Retourne [Provider, isNew] : * isNew=false bloque la reconstruction des sous-collections (idempotence). * * @param list $categoryNames categories de type PRESTATAIRE (RG-3.09) * @param list $siteNames sites du formulaire principal (RG-3.03, >= 1) * * @return array{0: Provider, 1: bool} */ private function ensureProvider( ObjectManager $manager, string $companyName, array $categoryNames, array $siteNames, bool $isArchived = false, ): array { $normalizedName = (string) $this->normalizer->normalizeCompanyName($companyName); $existing = $manager->getRepository(Provider::class)->findOneBy(['companyName' => $normalizedName]); if ($existing instanceof Provider) { return [$existing, false]; } $provider = new Provider(); $provider->setCompanyName($normalizedName); foreach ($categoryNames as $categoryName) { $provider->addCategory($this->category($manager, $categoryName)); } foreach ($siteNames as $siteName) { $provider->addSite($this->site($siteName)); } if ($isArchived) { $provider->setIsArchived(true); $provider->setArchivedAt(new DateTimeImmutable()); } $manager->persist($provider); return [$provider, true]; } /** * Ajoute un contact normalise au prestataire (cascade persist via * Provider.contacts). Au moins un champ est rempli (RG-3.04). */ private function addContact( Provider $provider, ?string $firstName, ?string $lastName, ?string $jobTitle, ?string $phonePrimary, ?string $phoneSecondary, ?string $email, int $position = 0, ): void { $contact = new ProviderContact(); $contact->setProvider($provider); $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); $provider->addContact($contact); } /** * Ajoute une adresse au prestataire (cascade persist via Provider.addresses). * Adresse simplifiee M3 : PAS de addressType / bennes / triageProvider. Au * moins un site est rattache (RG-3.05) ; categories d'adresse de type * PRESTATAIRE (RG-3.09). * * @param list $siteNames au moins un site (RG-3.05) * @param list $categoryNames categories de type PRESTATAIRE (RG-3.09) */ private function addAddress( Provider $provider, array $siteNames, string $postalCode, string $city, string $street, ?string $streetComplement = null, array $categoryNames = [], int $position = 0, ): void { $address = new ProviderAddress(); $address->setProvider($provider); $address->setCountry('France'); $address->setPostalCode($postalCode); $address->setCity($city); $address->setStreet($street); $address->setStreetComplement($streetComplement); $address->setPosition($position); foreach ($siteNames as $siteName) { $address->addSite($this->site($siteName)); } foreach ($categoryNames as $categoryName) { $address->addCategory($this->category($this->manager, $categoryName)); } $provider->addAddress($address); } /** * Ajoute un RIB au prestataire (cascade persist via Provider.ribs). */ private function addRib(Provider $provider, string $label, string $bic, string $iban, int $position = 0): void { $rib = new ProviderRib(); $rib->setProvider($provider); $rib->setLabel($label); $rib->setBic($bic); $rib->setIban($iban); $rib->setPosition($position); $provider->addRib($rib); } /** * Resout une categorie par son nom via le contrat Shared CategoryInterface, * sans importer le module Catalog (regle n°1). Verifie le type PRESTATAIRE * (RG-3.09). Mise en cache par nom. */ private function category(ObjectManager $manager, string $name): CategoryInterface { if (isset($this->categoryCache[$name])) { return $this->categoryCache[$name]; } $candidates = $manager->getRepository(CategoryInterface::class)->findBy([ 'name' => $name, 'deletedAt' => null, ]); foreach ($candidates as $candidate) { if ($candidate instanceof CategoryInterface && in_array(self::PROVIDER_CATEGORY_TYPE_CODE, $candidate->getCategoryTypeCodes(), true)) { return $this->categoryCache[$name] = $candidate; } } throw new RuntimeException(sprintf( 'Categorie PRESTATAIRE "%s" introuvable : CategoryFixtures doit tourner avant ProviderFixtures.', $name, )); } /** * Resout un site par son nom via le contrat Shared SiteProviderInterface, sans * importer le module Sites (regle n°1). Mise en cache par nom. */ private function site(string $name): SiteInterface { if (isset($this->siteCache[$name])) { return $this->siteCache[$name]; } $site = $this->siteProvider->findByName($name); if (!$site instanceof SiteInterface) { throw new RuntimeException(sprintf( 'Site "%s" introuvable : SitesFixtures doit tourner avant ProviderFixtures.', $name, )); } return $this->siteCache[$name] = $site; } private function tvaMode(ObjectManager $manager, string $code): TvaMode { $mode = $manager->getRepository(TvaMode::class)->findOneBy(['code' => $code]); if (!$mode instanceof TvaMode) { throw new RuntimeException(sprintf( 'TvaMode "%s" introuvable : CommercialReferentialFixtures doit tourner avant ProviderFixtures.', $code, )); } return $mode; } private function paymentDelay(ObjectManager $manager, string $code): PaymentDelay { $delay = $manager->getRepository(PaymentDelay::class)->findOneBy(['code' => $code]); if (!$delay instanceof PaymentDelay) { throw new RuntimeException(sprintf( 'PaymentDelay "%s" introuvable : CommercialReferentialFixtures doit tourner avant ProviderFixtures.', $code, )); } return $delay; } private function paymentType(ObjectManager $manager, string $code): PaymentType { $type = $manager->getRepository(PaymentType::class)->findOneBy(['code' => $code]); if (!$type instanceof PaymentType) { throw new RuntimeException(sprintf( 'PaymentType "%s" introuvable : CommercialReferentialFixtures doit tourner avant ProviderFixtures.', $code, )); } return $type; } private function bank(ObjectManager $manager, string $code): Bank { $bank = $manager->getRepository(Bank::class)->findOneBy(['code' => $code]); if (!$bank instanceof Bank) { throw new RuntimeException(sprintf( 'Bank "%s" introuvable : CommercialReferentialFixtures doit tourner avant ProviderFixtures.', $code, )); } return $bank; } }