d3d00425f7
Branche l'API REST du repertoire clients (M1) sur l'entite Client preparee en ERP-54. Operations GetCollection / Get / Post / Patch (pas de Delete au M1 : l'archivage passe par PATCH isArchived). ClientProvider : - liste paginee (Paginator ORM, aligne sur la convention ERP-72) + echappatoire ?pagination=false - exclut archives + soft-deletes par defaut (RG-1.24), ?includeArchived=true reintegre les archives (RG-1.25) - tri companyName ASC (RG-1.26), filtres ?search (fuzzy companyName/lastName/ email) et ?categoryType=<code> - detail : 404 sur soft-delete, embarque contacts/adresses/ribs ClientProcessor : - normalisation serveur via ClientFieldNormalizer (RG-1.18 a 1.21) - 409 sur doublon de nom de societe (RG-1.16) ; 409 dedie sur conflit de restauration (RG-1.23) - gating par onglet : champ comptable -> accounting.manage, isArchived -> archive, mode strict 403 sur tout le payload (RG-1.28) ; archivage exclusif (RG-1.22) + pose/retrait archivedAt - regles metier RG-1.01 (prenom/nom), RG-1.03 (distributor/broker exclusifs + controle du type de categorie), RG-1.12 (Virement -> banque), RG-1.13 (LCR -> >= 1 RIB), RG-1.04 (completude Information pour le role Commerciale) Lecture comptable conditionnelle : ClientReadGroupContextBuilder ajoute le groupe client:read:accounting selon commercial.clients.accounting.view. Resolution des references categorie : CategoryReferenceDenormalizer resout les IRI vers Category quand la propriete est type-hintee par le contrat CategoryInterface (denormalisation impossible sur une interface sinon). Contrats Shared : - CategoryInterface::getCategoryTypeCode() (implemente par Category) pour la verification de type sans import inter-modules - BusinessRoleAwareInterface (implemente par User) + BusinessRoles::COMMERCIALE pour detecter le role metier ; le code de role sera seede par ERP-74 et reutilise par ERP-59/60. RG-1.04 reste dormante tant qu'aucun user ne porte ce role. Coordination stack : - chaines de permission commercial.clients.* referencees ici, declarees en ERP-59 (tests RBAC complets en ERP-60) - config globale de pagination (itemsPerPage client, max 50) portee par ERP-72 - referentiels comptables (PaymentType/Bank/...) exposes en ERP-56 Tests : 31 tests Commercial (integration admin sur les regles metier + unitaires sur le gating, RG-1.04/1.12/1.13 et le context builder). Suite complete verte (339 tests).
77 lines
2.7 KiB
PHP
77 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\Commercial\Application\Validator;
|
|
|
|
use ApiPlatform\Validator\Exception\ValidationException;
|
|
use App\Module\Commercial\Domain\Entity\Client;
|
|
use Symfony\Component\Validator\ConstraintViolation;
|
|
use Symfony\Component\Validator\ConstraintViolationList;
|
|
|
|
/**
|
|
* Validator metier RG-1.04 : pour un utilisateur portant le role metier
|
|
* Commerciale, TOUS les champs de l'onglet Information deviennent obligatoires
|
|
* lors d'un PATCH touchant le groupe `client:write:information`.
|
|
*
|
|
* Invoque par le ClientProcessor UNIQUEMENT quand les deux conditions sont
|
|
* reunies (role Commerciale + payload touchant l'onglet Information). Pour les
|
|
* autres roles, ces champs restent optionnels — le validator n'est pas appele.
|
|
*
|
|
* Tant qu'aucun user ne porte le role `commerciale` (seede par ERP-74,
|
|
* cf. App\Shared\Domain\Security\BusinessRoles::COMMERCIALE), cette regle reste
|
|
* DORMANTE : aucun appelant ne la declenche.
|
|
*
|
|
* Leve une ValidationException (HTTP 422) listant chaque champ manquant, par
|
|
* coherence avec les violations Symfony rendues par API Platform.
|
|
*/
|
|
final class ClientInformationCompletenessValidator
|
|
{
|
|
public function validate(Client $client): void
|
|
{
|
|
// Map champ -> valeur courante de l'onglet Information.
|
|
$fields = [
|
|
'description' => $client->getDescription(),
|
|
'competitors' => $client->getCompetitors(),
|
|
'foundedAt' => $client->getFoundedAt(),
|
|
'employeesCount' => $client->getEmployeesCount(),
|
|
'revenueAmount' => $client->getRevenueAmount(),
|
|
'directorName' => $client->getDirectorName(),
|
|
'profitAmount' => $client->getProfitAmount(),
|
|
];
|
|
|
|
$violations = new ConstraintViolationList();
|
|
|
|
foreach ($fields as $property => $value) {
|
|
if ($this->isMissing($value)) {
|
|
$violations->add(new ConstraintViolation(
|
|
sprintf('Ce champ est obligatoire pour le role Commerciale (champ "%s").', $property),
|
|
null,
|
|
[],
|
|
$client,
|
|
$property,
|
|
$value,
|
|
));
|
|
}
|
|
}
|
|
|
|
if (count($violations) > 0) {
|
|
throw new ValidationException($violations);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Une valeur est manquante si null ou, pour une chaine, vide apres trim.
|
|
* Les zeros numeriques (employeesCount = 0, profitAmount = "0.00") sont des
|
|
* valeurs valides : on ne les considere pas manquants.
|
|
*/
|
|
private function isMissing(mixed $value): bool
|
|
{
|
|
if (null === $value) {
|
|
return true;
|
|
}
|
|
|
|
return is_string($value) && '' === trim($value);
|
|
}
|
|
}
|