createAdminClient(); $seed = $this->seedSupplier('Virement No Bank'); $response = $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [ 'headers' => ['Content-Type' => self::MERGE, 'Accept' => self::LD], 'json' => ['paymentType' => '/api/payment_types/'.$this->paymentType('VIREMENT')->getId()], ]); self::assertResponseStatusCodeSame(422); self::assertArrayHasKey('bank', $this->violationsByPath($response->toArray(false))); } public function testVirementWithBankReturns200(): void { $client = $this->createAdminClient(); $seed = $this->seedSupplier('Virement With Bank'); $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [ 'headers' => ['Content-Type' => self::MERGE], 'json' => [ 'paymentType' => '/api/payment_types/'.$this->paymentType('VIREMENT')->getId(), 'bank' => '/api/banks/'.$this->bank('SG')->getId(), ], ]); self::assertResponseStatusCodeSame(200); } // === RG-2.08 : LCR impose au moins un RIB === public function testLcrWithoutRibReturns422OnPaymentTypePath(): void { $client = $this->createAdminClient(); $seed = $this->seedSupplier('Lcr No Rib'); $response = $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [ 'headers' => ['Content-Type' => self::MERGE, 'Accept' => self::LD], 'json' => ['paymentType' => '/api/payment_types/'.$this->paymentType('LCR')->getId()], ]); self::assertResponseStatusCodeSame(422); // Miroir client : violation portee sur `paymentType` (select « Type de // règlement »), `ribs` n'ayant pas de champ de formulaire pour l'ancrer. self::assertArrayHasKey('paymentType', $this->violationsByPath($response->toArray(false))); } public function testLcrWithRibReturns200(): void { $client = $this->createAdminClient(); $seed = $this->seedSupplier('Lcr With Rib'); $this->addRib($seed); $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [ 'headers' => ['Content-Type' => self::MERGE], 'json' => ['paymentType' => '/api/payment_types/'.$this->paymentType('LCR')->getId()], ]); self::assertResponseStatusCodeSame(200); } // === Completude de l'onglet Comptabilite (six scalaires obligatoires) === /** * spec-front M2 § Onglet Comptabilite : a la validation COMPLETE de l'onglet * (les six champs requis presents dans le payload), chacun vide doit renvoyer * une 422 sur son propre propertyPath (mapping inline front, ERP-101). Miroir * du comportement client (ClientAccountingCompletenessValidator). */ public function testIncompleteAccountingTabReturns422OnEachField(): void { $client = $this->createAdminClient(); $seed = $this->seedSupplier('Accounting Incomplete'); $response = $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [ 'headers' => ['Content-Type' => self::MERGE, 'Accept' => self::LD], 'json' => [ 'siren' => null, 'accountNumber' => null, 'tvaMode' => null, 'nTva' => null, 'paymentDelay' => null, 'paymentType' => null, ], ]); self::assertResponseStatusCodeSame(422); $paths = $this->violationsByPath($response->toArray(false)); self::assertArrayHasKey('siren', $paths); self::assertArrayHasKey('accountNumber', $paths); self::assertArrayHasKey('tvaMode', $paths); self::assertArrayHasKey('nTva', $paths); self::assertArrayHasKey('paymentDelay', $paths); self::assertArrayHasKey('paymentType', $paths); } /** * Un PATCH ciblant un sous-ensemble de champs comptables n'est PAS une * validation d'onglet : la completude ne se declenche pas (edition ponctuelle * preservee, cf. validateAccountingCompleteness). */ public function testPartialAccountingPatchSkipsCompleteness(): void { $client = $this->createAdminClient(); $seed = $this->seedSupplier('Accounting Partial'); $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [ 'headers' => ['Content-Type' => self::MERGE], 'json' => ['nTva' => 'FR12345678901'], ]); self::assertResponseStatusCodeSame(200); } // violationsByPath() : helper mutualise dans AbstractSupplierApiTestCase. }