refactor(commercial) : suppression du contact principal inline du Client (M1)
Le contact principal (firstName, lastName, phonePrimary, phoneSecondary, email) n'est plus porte par l'entite Client : les contacts vivent uniquement dans ClientContact (onglet Contact). RG-1.01 et RG-1.02 supprimees du Client (equivalent RG-1.05 / RG-1.14 sur ClientContact). - Migration (namespace racine DoctrineMigrations, ordre par timestamp) : backfill des clients sans contact vers client_contact (position 0) puis DROP des 5 colonnes inline. down() best-effort documente. - Entite Client : retrait des 5 props + getters/setters + groupes. - ClientProcessor : MAIN_FIELDS / changedBusinessFields / normalize alleges, validateMainContact (RG-1.01) supprimee. - Recherche repertoire : companyName seul (D1). - Export XLSX : colonnes de contact retirees (D2). - Fixtures + catalogue de commentaires SQL alignes. - Tests fonctionnels et unitaires mis a jour.
This commit is contained in:
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M1 — Suppression du contact principal inline du `Client` (refonte contact).
|
||||||
|
*
|
||||||
|
* Modele AVANT : le `Client` portait 5 colonnes de contact principal
|
||||||
|
* (first_name, last_name, phone_primary, phone_secondary, email) en doublon
|
||||||
|
* conceptuel de la sous-entite `ClientContact` (onglet Contact).
|
||||||
|
*
|
||||||
|
* Modele APRES (decision produit, README refonte-contact) : les contacts vivent
|
||||||
|
* UNIQUEMENT dans `client_contact`. Les 5 colonnes inline disparaissent du
|
||||||
|
* `client`. RG-1.01 (firstName OU lastName sur Client) et RG-1.02 (max 2
|
||||||
|
* telephones sur Client) sont supprimees : leur equivalent vit deja sur
|
||||||
|
* `client_contact` (RG-1.05 / RG-1.14).
|
||||||
|
*
|
||||||
|
* Le code etant deja en prod, la suppression est precedee d'un BACKFILL : pour
|
||||||
|
* tout client n'ayant encore AUCUN contact, on materialise son contact principal
|
||||||
|
* inline en une ligne `client_contact` (position 0) avant le DROP, afin de ne
|
||||||
|
* perdre aucune donnee.
|
||||||
|
*
|
||||||
|
* Namespace racine `DoctrineMigrations` (regle ABSOLUE n°11) et NON modulaire
|
||||||
|
* Commercial : avec plusieurs migrations_paths, Doctrine Migrations 3.x trie par
|
||||||
|
* FQCN alphabetique (AlphabeticalComparator). Une migration
|
||||||
|
* `App\Module\Commercial\...` trierait AVANT toutes les `DoctrineMigrations\...`
|
||||||
|
* sur base vide -> ce DROP s'executerait avant le CREATE TABLE client
|
||||||
|
* (Version20260601000000). Le namespace racine garantit l'ordre par timestamp.
|
||||||
|
*/
|
||||||
|
final class Version20260603120000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
/** Colonnes de contact inline supprimees du `client`. */
|
||||||
|
private const array INLINE_CONTACT_COLUMNS = [
|
||||||
|
'first_name', 'last_name', 'phone_primary', 'phone_secondary', 'email',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'M1 : suppression du contact inline du Client (backfill vers client_contact puis DROP des 5 colonnes).';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// 1. Backfill : tout client SANS contact recoit une ligne client_contact
|
||||||
|
// (position 0) reprenant ses champs inline. phone_primary / email du
|
||||||
|
// client sont NOT NULL -> toujours une donnee a reporter. Le CHECK
|
||||||
|
// chk_client_contact_name (first_name OU last_name) est garanti par le
|
||||||
|
// fallback company_name si jamais les deux noms etaient null (cas qui ne
|
||||||
|
// devrait pas exister, RG-1.01 ayant ete appliquee a l'ecriture).
|
||||||
|
// created_at/updated_at NOT NULL -> NOW() ; created_by/updated_by null
|
||||||
|
// (backfill hors contexte HTTP, libelle « Systeme » cote front).
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
INSERT INTO client_contact (
|
||||||
|
client_id, first_name, last_name, phone_primary, phone_secondary,
|
||||||
|
email, position, created_at, updated_at
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
c.id,
|
||||||
|
c.first_name,
|
||||||
|
CASE
|
||||||
|
WHEN c.first_name IS NULL AND c.last_name IS NULL THEN c.company_name
|
||||||
|
ELSE c.last_name
|
||||||
|
END,
|
||||||
|
c.phone_primary,
|
||||||
|
c.phone_secondary,
|
||||||
|
c.email,
|
||||||
|
0,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
FROM client c
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM client_contact cc WHERE cc.client_id = c.id
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
// 2. DROP des 5 colonnes inline (rien a documenter : suppression).
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE client
|
||||||
|
DROP COLUMN first_name,
|
||||||
|
DROP COLUMN last_name,
|
||||||
|
DROP COLUMN phone_primary,
|
||||||
|
DROP COLUMN phone_secondary,
|
||||||
|
DROP COLUMN email
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// Best-effort : on RECREE les 5 colonnes (en NULLABLE — l'etat NOT NULL
|
||||||
|
// d'origine de phone_primary/email ne peut etre restaure sur une table
|
||||||
|
// peuplee sans risque) et on retro-alimente depuis le contact principal
|
||||||
|
// (position minimale) de chaque client. La donnee n'est pas garantie
|
||||||
|
// identique a l'origine : ce down() sert au rollback technique, pas a une
|
||||||
|
// restauration fidele.
|
||||||
|
$this->addSql('ALTER TABLE client ADD COLUMN first_name VARCHAR(120) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE client ADD COLUMN last_name VARCHAR(120) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE client ADD COLUMN phone_primary VARCHAR(20) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE client ADD COLUMN phone_secondary VARCHAR(20) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE client ADD COLUMN email VARCHAR(180) DEFAULT NULL');
|
||||||
|
|
||||||
|
// Retro-alimentation depuis le contact de position la plus basse.
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
UPDATE client c SET
|
||||||
|
first_name = cc.first_name,
|
||||||
|
last_name = cc.last_name,
|
||||||
|
phone_primary = cc.phone_primary,
|
||||||
|
phone_secondary = cc.phone_secondary,
|
||||||
|
email = cc.email
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT ON (client_id)
|
||||||
|
client_id, first_name, last_name, phone_primary, phone_secondary, email
|
||||||
|
FROM client_contact
|
||||||
|
ORDER BY client_id, position ASC, id ASC
|
||||||
|
) cc
|
||||||
|
WHERE cc.client_id = c.id
|
||||||
|
SQL);
|
||||||
|
|
||||||
|
// Re-pose des commentaires d'origine (regle ABSOLUE n°12) — dollar-quoting
|
||||||
|
// Postgres pour eviter tout echappement d apostrophe.
|
||||||
|
$this->addSql('COMMENT ON COLUMN client.first_name IS $_$Prenom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).$_$');
|
||||||
|
$this->addSql('COMMENT ON COLUMN client.last_name IS $_$Nom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).$_$');
|
||||||
|
$this->addSql('COMMENT ON COLUMN client.phone_primary IS $_$Telephone principal — stocke en chiffres uniquement (RG-1.20). Obligatoire.$_$');
|
||||||
|
$this->addSql('COMMENT ON COLUMN client.phone_secondary IS $_$Telephone secondaire optionnel — chiffres uniquement (RG-1.20).$_$');
|
||||||
|
$this->addSql('COMMENT ON COLUMN client.email IS $_$Email principal (lowercase serveur, RG-1.21). NON unique (RG-1.17 supprimee, Q4).$_$');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -151,31 +151,9 @@ class Client implements TimestampableInterface, BlamableInterface
|
|||||||
#[Groups(['client:read', 'client:write:main'])]
|
#[Groups(['client:read', 'client:write:main'])]
|
||||||
private ?string $companyName = null;
|
private ?string $companyName = null;
|
||||||
|
|
||||||
// RG-1.01 : firstName OU lastName obligatoire (validation au futur Processor).
|
// Le contact principal n'est plus porte inline par le Client : les contacts
|
||||||
#[ORM\Column(length: 120, nullable: true)]
|
// vivent uniquement dans ClientContact (onglet Contact). RG-1.01 / RG-1.02
|
||||||
#[Assert\Length(max: 120, normalizer: 'trim')]
|
// supprimees du Client (equivalent RG-1.05 / RG-1.14 sur ClientContact).
|
||||||
#[Groups(['client:read', 'client:write:main'])]
|
|
||||||
private ?string $firstName = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 120, nullable: true)]
|
|
||||||
#[Assert\Length(max: 120, normalizer: 'trim')]
|
|
||||||
#[Groups(['client:read', 'client:write:main'])]
|
|
||||||
private ?string $lastName = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 20)]
|
|
||||||
#[Assert\NotBlank]
|
|
||||||
#[Groups(['client:read', 'client:write:main'])]
|
|
||||||
private ?string $phonePrimary = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 20, nullable: true)]
|
|
||||||
#[Groups(['client:read', 'client:write:main'])]
|
|
||||||
private ?string $phoneSecondary = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
|
||||||
#[Assert\NotBlank]
|
|
||||||
#[Assert\Email]
|
|
||||||
#[Groups(['client:read', 'client:write:main'])]
|
|
||||||
private ?string $email = null;
|
|
||||||
|
|
||||||
// RG-1.03 : distributor / broker auto-references mutuellement exclusives
|
// RG-1.03 : distributor / broker auto-references mutuellement exclusives
|
||||||
// (CHECK chk_client_distrib_or_broker en base).
|
// (CHECK chk_client_distrib_or_broker en base).
|
||||||
@@ -326,66 +304,6 @@ class Client implements TimestampableInterface, BlamableInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFirstName(): ?string
|
|
||||||
{
|
|
||||||
return $this->firstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFirstName(?string $firstName): static
|
|
||||||
{
|
|
||||||
$this->firstName = $firstName;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastName(): ?string
|
|
||||||
{
|
|
||||||
return $this->lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLastName(?string $lastName): static
|
|
||||||
{
|
|
||||||
$this->lastName = $lastName;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPhonePrimary(): ?string
|
|
||||||
{
|
|
||||||
return $this->phonePrimary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPhonePrimary(string $phonePrimary): static
|
|
||||||
{
|
|
||||||
$this->phonePrimary = $phonePrimary;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPhoneSecondary(): ?string
|
|
||||||
{
|
|
||||||
return $this->phoneSecondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPhoneSecondary(?string $phoneSecondary): static
|
|
||||||
{
|
|
||||||
$this->phoneSecondary = $phoneSecondary;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEmail(): ?string
|
|
||||||
{
|
|
||||||
return $this->email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEmail(string $email): static
|
|
||||||
{
|
|
||||||
$this->email = $email;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDistributor(): ?Client
|
public function getDistributor(): ?Client
|
||||||
{
|
{
|
||||||
return $this->distributor;
|
return $this->distributor;
|
||||||
|
|||||||
+8
-37
@@ -29,7 +29,7 @@ use Symfony\Component\Validator\ConstraintViolationList;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Processor d'ecriture du repertoire clients (M1). Cf. spec-back M1 § 2.8 /
|
* Processor d'ecriture du repertoire clients (M1). Cf. spec-back M1 § 2.8 /
|
||||||
* § 2.9 / § 4.3 / § 4.4 + RG-1.01 a RG-1.28.
|
* § 2.9 / § 4.3 / § 4.4 + RG-1.03 a RG-1.28 (RG-1.01/1.02 supprimees : contact inline retire).
|
||||||
*
|
*
|
||||||
* Sequence (POST / PATCH) :
|
* Sequence (POST / PATCH) :
|
||||||
* 1. Autorisation additionnelle par groupe d'onglet. La security d'operation
|
* 1. Autorisation additionnelle par groupe d'onglet. La security d'operation
|
||||||
@@ -41,7 +41,7 @@ use Symfony\Component\Validator\ConstraintViolationList;
|
|||||||
* - champ isArchived dans le payload -> exige archive (RG-1.22, 403) et
|
* - champ isArchived dans le payload -> exige archive (RG-1.22, 403) et
|
||||||
* interdit toute autre modification dans la meme requete (RG-1.22, 422).
|
* interdit toute autre modification dans la meme requete (RG-1.22, 422).
|
||||||
* 2. Normalisation serveur (RG-1.18 a 1.21) via ClientFieldNormalizer.
|
* 2. Normalisation serveur (RG-1.18 a 1.21) via ClientFieldNormalizer.
|
||||||
* 3. Regles metier : RG-1.01 (prenom/nom), RG-1.03 (distributor/broker
|
* 3. Regles metier : RG-1.03 (distributor/broker
|
||||||
* exclusifs + type de categorie), RG-1.12 (Virement -> banque),
|
* exclusifs + type de categorie), RG-1.12 (Virement -> banque),
|
||||||
* RG-1.13 (LCR -> >= 1 RIB), RG-1.04 (completude Information exigee sur POST
|
* RG-1.13 (LCR -> >= 1 RIB), RG-1.04 (completude Information exigee sur POST
|
||||||
* et tout PATCH pour le role Commerciale).
|
* et tout PATCH pour le role Commerciale).
|
||||||
@@ -60,8 +60,7 @@ final class ClientProcessor implements ProcessorInterface
|
|||||||
{
|
{
|
||||||
/** Champs de l'onglet principal (groupe client:write:main). */
|
/** Champs de l'onglet principal (groupe client:write:main). */
|
||||||
private const array MAIN_FIELDS = [
|
private const array MAIN_FIELDS = [
|
||||||
'companyName', 'firstName', 'lastName', 'phonePrimary', 'phoneSecondary',
|
'companyName', 'distributor', 'broker', 'triageService', 'categories',
|
||||||
'email', 'distributor', 'broker', 'triageService', 'categories',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Champs de l'onglet Information (groupe client:write:information). */
|
/** Champs de l'onglet Information (groupe client:write:information). */
|
||||||
@@ -124,7 +123,6 @@ final class ClientProcessor implements ProcessorInterface
|
|||||||
// valeurs normalisees des deux cotes (l'etat persiste l'a deja ete).
|
// valeurs normalisees des deux cotes (l'etat persiste l'a deja ete).
|
||||||
$this->guardManage($data);
|
$this->guardManage($data);
|
||||||
|
|
||||||
$this->validateMainContact($data);
|
|
||||||
$this->validateDistributorBroker($data);
|
$this->validateDistributorBroker($data);
|
||||||
$this->validateAccountingConsistency($data);
|
$this->validateAccountingConsistency($data);
|
||||||
$this->validateInformationCompleteness($data);
|
$this->validateInformationCompleteness($data);
|
||||||
@@ -274,11 +272,6 @@ final class ClientProcessor implements ProcessorInterface
|
|||||||
{
|
{
|
||||||
$newValues = [
|
$newValues = [
|
||||||
'companyName' => $data->getCompanyName(),
|
'companyName' => $data->getCompanyName(),
|
||||||
'firstName' => $data->getFirstName(),
|
|
||||||
'lastName' => $data->getLastName(),
|
|
||||||
'phonePrimary' => $data->getPhonePrimary(),
|
|
||||||
'phoneSecondary' => $data->getPhoneSecondary(),
|
|
||||||
'email' => $data->getEmail(),
|
|
||||||
'distributor' => $data->getDistributor(),
|
'distributor' => $data->getDistributor(),
|
||||||
'broker' => $data->getBroker(),
|
'broker' => $data->getBroker(),
|
||||||
'triageService' => $data->isTriageService(),
|
'triageService' => $data->isTriageService(),
|
||||||
@@ -420,39 +413,17 @@ final class ClientProcessor implements ProcessorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalisation serveur (RG-1.18 a 1.21). Les setters non-nullables
|
* Normalisation serveur du formulaire principal (RG-1.18). Seul companyName
|
||||||
* (companyName, email, phonePrimary) ne sont touches que si une valeur est
|
* subsiste cote Client depuis la suppression du contact inline (les champs de
|
||||||
* presente, pour ne jamais ecraser l'existant lors d'un PATCH partiel.
|
* contact — noms, telephones, email — sont normalises par ClientContactProcessor).
|
||||||
|
* Le setter non-nullable n'est touche que si une valeur est presente, pour ne
|
||||||
|
* jamais ecraser l'existant lors d'un PATCH partiel.
|
||||||
*/
|
*/
|
||||||
private function normalize(Client $data): void
|
private function normalize(Client $data): void
|
||||||
{
|
{
|
||||||
if (null !== $data->getCompanyName()) {
|
if (null !== $data->getCompanyName()) {
|
||||||
$data->setCompanyName((string) $this->normalizer->normalizeCompanyName($data->getCompanyName()));
|
$data->setCompanyName((string) $this->normalizer->normalizeCompanyName($data->getCompanyName()));
|
||||||
}
|
}
|
||||||
if (null !== $data->getEmail()) {
|
|
||||||
$data->setEmail((string) $this->normalizer->normalizeEmail($data->getEmail()));
|
|
||||||
}
|
|
||||||
if (null !== $data->getPhonePrimary()) {
|
|
||||||
$data->setPhonePrimary((string) $this->normalizer->normalizePhone($data->getPhonePrimary()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$data->setFirstName($this->normalizer->normalizePersonName($data->getFirstName()));
|
|
||||||
$data->setLastName($this->normalizer->normalizePersonName($data->getLastName()));
|
|
||||||
$data->setPhoneSecondary($this->normalizer->normalizePhone($data->getPhoneSecondary()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RG-1.01 : au moins le prenom OU le nom du contact principal.
|
|
||||||
*/
|
|
||||||
private function validateMainContact(Client $data): void
|
|
||||||
{
|
|
||||||
if (null === $data->getFirstName() && null === $data->getLastName()) {
|
|
||||||
$this->throwViolation(
|
|
||||||
'firstName',
|
|
||||||
'Le prénom ou le nom du contact principal est obligatoire.',
|
|
||||||
$data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ final class ClientExportController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Colonnes dans l'ordre impose par la spec § 4.6. SIREN inseree avant la
|
* Colonnes de l'export. Depuis la suppression du contact inline (refonte
|
||||||
|
* contact, D2), les colonnes de contact principal sont retirees : l'export
|
||||||
|
* ne porte plus que les donnees propres au Client. SIREN inseree avant la
|
||||||
* date de creation, uniquement si l'utilisateur a accounting.view.
|
* date de creation, uniquement si l'utilisateur a accounting.view.
|
||||||
*
|
*
|
||||||
* @return list<string>
|
* @return list<string>
|
||||||
@@ -95,11 +97,6 @@ final class ClientExportController
|
|||||||
{
|
{
|
||||||
$headers = [
|
$headers = [
|
||||||
'Nom entreprise',
|
'Nom entreprise',
|
||||||
'Nom contact principal',
|
|
||||||
'Prénom',
|
|
||||||
'Téléphone principal',
|
|
||||||
'Téléphone secondaire',
|
|
||||||
'Email',
|
|
||||||
'Catégories',
|
'Catégories',
|
||||||
'Sites',
|
'Sites',
|
||||||
];
|
];
|
||||||
@@ -123,11 +120,6 @@ final class ClientExportController
|
|||||||
foreach ($clients as $client) {
|
foreach ($clients as $client) {
|
||||||
$row = [
|
$row = [
|
||||||
$client->getCompanyName(),
|
$client->getCompanyName(),
|
||||||
$client->getLastName(),
|
|
||||||
$client->getFirstName(),
|
|
||||||
$client->getPhonePrimary(),
|
|
||||||
$client->getPhoneSecondary(),
|
|
||||||
$client->getEmail(),
|
|
||||||
$this->formatCategories($client),
|
$this->formatCategories($client),
|
||||||
$this->formatSites($client),
|
$this->formatSites($client),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -112,10 +112,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$gso, $gsoIsNew] = $this->ensureClient(
|
[$gso, $gsoIsNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Distrib Grand Sud-Ouest',
|
companyName: 'Distrib Grand Sud-Ouest',
|
||||||
firstName: 'Paul',
|
|
||||||
lastName: 'Garnier',
|
|
||||||
phonePrimary: '05 56 10 20 30',
|
|
||||||
email: 'contact@distrib-gso.fr',
|
|
||||||
categoryNames: ['Distributeur'],
|
categoryNames: ['Distributeur'],
|
||||||
);
|
);
|
||||||
if ($gsoIsNew) {
|
if ($gsoIsNew) {
|
||||||
@@ -127,10 +123,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$leonard, $leonardIsNew] = $this->ensureClient(
|
[$leonard, $leonardIsNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Cabinet Léonard Assurances',
|
companyName: 'Cabinet Léonard Assurances',
|
||||||
firstName: 'Sophie',
|
|
||||||
lastName: 'Léonard',
|
|
||||||
phonePrimary: '05 49 11 22 33',
|
|
||||||
email: 'contact@cabinet-leonard.fr',
|
|
||||||
categoryNames: ['Courtier'],
|
categoryNames: ['Courtier'],
|
||||||
);
|
);
|
||||||
if ($leonardIsNew) {
|
if ($leonardIsNew) {
|
||||||
@@ -142,10 +134,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$dubois, $isNew] = $this->ensureClient(
|
[$dubois, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Menuiserie Dubois',
|
companyName: 'Menuiserie Dubois',
|
||||||
firstName: 'Jean',
|
|
||||||
lastName: 'Dubois',
|
|
||||||
phonePrimary: '05 49 00 00 01',
|
|
||||||
email: 'contact@menuiserie-dubois.fr',
|
|
||||||
categoryNames: ['BTP'],
|
categoryNames: ['BTP'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -159,10 +147,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$garage, $isNew] = $this->ensureClient(
|
[$garage, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Garage Martin',
|
companyName: 'Garage Martin',
|
||||||
firstName: 'Luc',
|
|
||||||
lastName: 'Martin',
|
|
||||||
phonePrimary: '05 56 44 55 66',
|
|
||||||
email: 'accueil@garage-martin.fr',
|
|
||||||
categoryNames: ['Services'],
|
categoryNames: ['Services'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -175,10 +159,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$boulangerie, $isNew] = $this->ensureClient(
|
[$boulangerie, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Boulangerie Lemoine',
|
companyName: 'Boulangerie Lemoine',
|
||||||
firstName: 'Marie',
|
|
||||||
lastName: 'Lemoine',
|
|
||||||
phonePrimary: '05 49 77 88 99',
|
|
||||||
email: 'bonjour@boulangerie-lemoine.fr',
|
|
||||||
categoryNames: ['Agro-alimentaire'],
|
categoryNames: ['Agro-alimentaire'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -191,10 +171,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$transports, $isNew] = $this->ensureClient(
|
[$transports, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Transports Rapides',
|
companyName: 'Transports Rapides',
|
||||||
firstName: null,
|
|
||||||
lastName: 'Bernard',
|
|
||||||
phonePrimary: '05 56 12 13 14',
|
|
||||||
email: 'exploitation@transports-rapides.fr',
|
|
||||||
categoryNames: ['Transport/Logistique'],
|
categoryNames: ['Transport/Logistique'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -209,10 +185,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$industries, $isNew] = $this->ensureClient(
|
[$industries, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Industries Vertes',
|
companyName: 'Industries Vertes',
|
||||||
firstName: 'Claire',
|
|
||||||
lastName: 'Moreau',
|
|
||||||
phonePrimary: '05 49 21 22 23',
|
|
||||||
email: 'contact@industries-vertes.fr',
|
|
||||||
categoryNames: ['Industrie'],
|
categoryNames: ['Industrie'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -229,12 +201,7 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$agro, $isNew] = $this->ensureClient(
|
[$agro, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Agro Distribution Sud',
|
companyName: 'Agro Distribution Sud',
|
||||||
firstName: 'Thomas',
|
|
||||||
lastName: 'Petit',
|
|
||||||
phonePrimary: '05 56 31 32 33',
|
|
||||||
email: 'contact@agro-sud.fr',
|
|
||||||
categoryNames: ['Agro-alimentaire'],
|
categoryNames: ['Agro-alimentaire'],
|
||||||
phoneSecondary: '06 01 02 03 04',
|
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
$this->addContact($agro, 'Thomas', 'Petit', 'Directeur des achats', '05 56 31 32 33', '06 01 02 03 04', 'thomas.petit@agro-sud.fr', 0);
|
$this->addContact($agro, 'Thomas', 'Petit', 'Directeur des achats', '05 56 31 32 33', '06 01 02 03 04', 'thomas.petit@agro-sud.fr', 0);
|
||||||
@@ -247,10 +214,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$ancienne, $isNew] = $this->ensureClient(
|
[$ancienne, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Ancienne Société Oubliée',
|
companyName: 'Ancienne Société Oubliée',
|
||||||
firstName: null,
|
|
||||||
lastName: 'Durand',
|
|
||||||
phonePrimary: '05 49 99 99 99',
|
|
||||||
email: 'contact@ancienne-societe.fr',
|
|
||||||
categoryNames: ['Association'],
|
categoryNames: ['Association'],
|
||||||
isArchived: true,
|
isArchived: true,
|
||||||
);
|
);
|
||||||
@@ -263,10 +226,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$services, $isNew] = $this->ensureClient(
|
[$services, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Services Pro Conseil',
|
companyName: 'Services Pro Conseil',
|
||||||
firstName: 'Nadia',
|
|
||||||
lastName: 'Benali',
|
|
||||||
phonePrimary: '05 49 41 42 43',
|
|
||||||
email: 'contact@services-pro.fr',
|
|
||||||
categoryNames: ['Services'],
|
categoryNames: ['Services'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -279,10 +238,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$holding, $isNew] = $this->ensureClient(
|
[$holding, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Holding Premium Invest',
|
companyName: 'Holding Premium Invest',
|
||||||
firstName: 'Antoine',
|
|
||||||
lastName: 'Lefèvre',
|
|
||||||
phonePrimary: '05 56 51 52 53',
|
|
||||||
email: 'direction@holding-premium.fr',
|
|
||||||
categoryNames: ['Industrie'],
|
categoryNames: ['Industrie'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -301,10 +256,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$conglo, $isNew] = $this->ensureClient(
|
[$conglo, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Conglomérat Multi Activités',
|
companyName: 'Conglomérat Multi Activités',
|
||||||
firstName: 'Hélène',
|
|
||||||
lastName: 'Faure',
|
|
||||||
phonePrimary: '05 49 61 62 63',
|
|
||||||
email: 'contact@conglomerat-multi.fr',
|
|
||||||
categoryNames: ['BTP', 'Industrie', 'Services'],
|
categoryNames: ['BTP', 'Industrie', 'Services'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -316,10 +267,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$prospect, $isNew] = $this->ensureClient(
|
[$prospect, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Prospect Futur Client',
|
companyName: 'Prospect Futur Client',
|
||||||
firstName: 'Olivier',
|
|
||||||
lastName: 'Renard',
|
|
||||||
phonePrimary: '05 56 71 72 73',
|
|
||||||
email: 'olivier.renard@prospect-futur.fr',
|
|
||||||
categoryNames: ['BTP'],
|
categoryNames: ['BTP'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -331,10 +278,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
[$association, $isNew] = $this->ensureClient(
|
[$association, $isNew] = $this->ensureClient(
|
||||||
$manager,
|
$manager,
|
||||||
companyName: 'Association des Riverains',
|
companyName: 'Association des Riverains',
|
||||||
firstName: null,
|
|
||||||
lastName: 'Caron',
|
|
||||||
phonePrimary: '05 49 81 82 83',
|
|
||||||
email: 'contact@asso-riverains.fr',
|
|
||||||
categoryNames: ['Association'],
|
categoryNames: ['Association'],
|
||||||
);
|
);
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
@@ -350,6 +293,9 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
* sinon retourne l'existant. Retourne [Client, isNew] : isNew=false bloque la
|
* sinon retourne l'existant. Retourne [Client, isNew] : isNew=false bloque la
|
||||||
* reconstruction des sous-collections (idempotence sans doublon).
|
* reconstruction des sous-collections (idempotence sans doublon).
|
||||||
*
|
*
|
||||||
|
* Le contact principal n'est plus porte par le Client (refonte contact) : les
|
||||||
|
* coordonnees de contact sont fournies via addContact() dans le bloc isNew.
|
||||||
|
*
|
||||||
* @param list<string> $categoryNames
|
* @param list<string> $categoryNames
|
||||||
*
|
*
|
||||||
* @return array{0: Client, 1: bool}
|
* @return array{0: Client, 1: bool}
|
||||||
@@ -357,12 +303,7 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
private function ensureClient(
|
private function ensureClient(
|
||||||
ObjectManager $manager,
|
ObjectManager $manager,
|
||||||
string $companyName,
|
string $companyName,
|
||||||
?string $firstName,
|
|
||||||
?string $lastName,
|
|
||||||
string $phonePrimary,
|
|
||||||
string $email,
|
|
||||||
array $categoryNames,
|
array $categoryNames,
|
||||||
?string $phoneSecondary = null,
|
|
||||||
bool $isArchived = false,
|
bool $isArchived = false,
|
||||||
): array {
|
): array {
|
||||||
$normalizedName = (string) $this->normalizer->normalizeCompanyName($companyName);
|
$normalizedName = (string) $this->normalizer->normalizeCompanyName($companyName);
|
||||||
@@ -374,11 +315,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
|||||||
|
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
$client->setCompanyName($normalizedName);
|
$client->setCompanyName($normalizedName);
|
||||||
$client->setFirstName($this->normalizer->normalizePersonName($firstName));
|
|
||||||
$client->setLastName($this->normalizer->normalizePersonName($lastName));
|
|
||||||
$client->setPhonePrimary((string) $this->normalizer->normalizePhone($phonePrimary));
|
|
||||||
$client->setPhoneSecondary($this->normalizer->normalizePhone($phoneSecondary));
|
|
||||||
$client->setEmail((string) $this->normalizer->normalizeEmail($email));
|
|
||||||
|
|
||||||
foreach ($categoryNames as $categoryName) {
|
foreach ($categoryNames as $categoryName) {
|
||||||
$client->addCategory($this->category($manager, $categoryName));
|
$client->addCategory($this->category($manager, $categoryName));
|
||||||
|
|||||||
@@ -103,9 +103,11 @@ class DoctrineClientRepository extends ServiceEntityRepository implements Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recherche fuzzy insensible a la casse sur companyName + lastName + email.
|
* Recherche fuzzy insensible a la casse sur companyName (D1, refonte contact).
|
||||||
* Les metacaracteres LIKE (%, _, \) saisis sont echappes pour rester
|
* Depuis la suppression du contact inline du Client, la recherche ne porte
|
||||||
* litteraux.
|
* plus que sur le nom d'entreprise (les anciens criteres lastName / email
|
||||||
|
* vivaient sur les colonnes inline disparues). Les metacaracteres LIKE
|
||||||
|
* (%, _, \) saisis sont echappes pour rester litteraux.
|
||||||
*/
|
*/
|
||||||
private function applySearch(QueryBuilder $qb, ?string $search): void
|
private function applySearch(QueryBuilder $qb, ?string $search): void
|
||||||
{
|
{
|
||||||
@@ -116,11 +118,9 @@ class DoctrineClientRepository extends ServiceEntityRepository implements Client
|
|||||||
$escaped = str_replace(['\\', '%', '_'], ['\\\\', '\%', '\_'], trim($search));
|
$escaped = str_replace(['\\', '%', '_'], ['\\\\', '\%', '\_'], trim($search));
|
||||||
$pattern = '%'.mb_strtolower($escaped, 'UTF-8').'%';
|
$pattern = '%'.mb_strtolower($escaped, 'UTF-8').'%';
|
||||||
|
|
||||||
$qb->andWhere(
|
$qb->andWhere('LOWER(c.companyName) LIKE :search')
|
||||||
'LOWER(c.companyName) LIKE :search '
|
->setParameter('search', $pattern)
|
||||||
.'OR LOWER(c.lastName) LIKE :search '
|
;
|
||||||
.'OR LOWER(c.email) LIKE :search',
|
|
||||||
)->setParameter('search', $pattern);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -166,14 +166,12 @@ final class ColumnCommentsCatalog
|
|||||||
],
|
],
|
||||||
|
|
||||||
'client' => [
|
'client' => [
|
||||||
'_table' => 'Repertoire clients (M1 Commercial) — entites archivables (is_archived) et soft-deletables (deleted_at, HP M2).',
|
'_table' => 'Repertoire clients (M1 Commercial) — entites archivables (is_archived) et soft-deletables (deleted_at, HP M2).',
|
||||||
'id' => 'Identifiant interne auto-incremente.',
|
'id' => 'Identifiant interne auto-incremente.',
|
||||||
'company_name' => 'Raison sociale (stockee en MAJUSCULES, RG-1.18). Unique case-insensitive parmi les actifs non archives/non supprimes (RG-1.16, uq_client_company_name_active).',
|
'company_name' => 'Raison sociale (stockee en MAJUSCULES, RG-1.18). Unique case-insensitive parmi les actifs non archives/non supprimes (RG-1.16, uq_client_company_name_active).',
|
||||||
'first_name' => 'Prenom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).',
|
// Contact principal inline supprime (refonte contact) : first_name,
|
||||||
'last_name' => 'Nom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).',
|
// last_name, phone_primary, phone_secondary, email vivent desormais
|
||||||
'phone_primary' => 'Telephone principal — stocke en chiffres uniquement (RG-1.20). Obligatoire.',
|
// uniquement sur client_contact.
|
||||||
'phone_secondary' => 'Telephone secondaire optionnel — chiffres uniquement (RG-1.20).',
|
|
||||||
'email' => 'Email principal (lowercase serveur, RG-1.21). NON unique (RG-1.17 supprimee, Q4).',
|
|
||||||
'distributor_id' => 'FK auto-referente vers un client porteur de la categorie DISTRIBUTEUR — exclusive avec broker_id (RG-1.03, chk_client_distrib_or_broker). FK -> client.id, ON DELETE SET NULL.',
|
'distributor_id' => 'FK auto-referente vers un client porteur de la categorie DISTRIBUTEUR — exclusive avec broker_id (RG-1.03, chk_client_distrib_or_broker). FK -> client.id, ON DELETE SET NULL.',
|
||||||
'broker_id' => 'FK auto-referente vers un client porteur de la categorie COURTIER — exclusive avec distributor_id (RG-1.03). FK -> client.id, ON DELETE SET NULL.',
|
'broker_id' => 'FK auto-referente vers un client porteur de la categorie COURTIER — exclusive avec distributor_id (RG-1.03). FK -> client.id, ON DELETE SET NULL.',
|
||||||
'triage_service' => 'Drapeau service triage active pour le client. Faux par defaut.',
|
'triage_service' => 'Drapeau service triage active pour le client. Faux par defaut.',
|
||||||
|
|||||||
@@ -126,9 +126,6 @@ abstract class AbstractCommercialApiTestCase extends AbstractApiTestCase
|
|||||||
// Stocke en MAJUSCULES pour refleter l'etat normalise (RG-1.18) qu'aurait
|
// Stocke en MAJUSCULES pour refleter l'etat normalise (RG-1.18) qu'aurait
|
||||||
// produit le ClientProcessor via l'API.
|
// produit le ClientProcessor via l'API.
|
||||||
$client->setCompanyName(mb_strtoupper($companyName, 'UTF-8'));
|
$client->setCompanyName(mb_strtoupper($companyName, 'UTF-8'));
|
||||||
$client->setLastName('Seed');
|
|
||||||
$client->setPhonePrimary('0102030405');
|
|
||||||
$client->setEmail(strtolower(str_replace(' ', '', $companyName)).'@seed.test');
|
|
||||||
$client->addCategory($this->createCategory($categoryCode));
|
$client->addCategory($this->createCategory($categoryCode));
|
||||||
$client->setIsArchived($isArchived);
|
$client->setIsArchived($isArchived);
|
||||||
if ($isArchived) {
|
if ($isArchived) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
{
|
{
|
||||||
private const string LD = 'application/ld+json';
|
private const string LD = 'application/ld+json';
|
||||||
|
|
||||||
public function testPostNormalizesTextFields(): void
|
public function testPostNormalizesCompanyName(): void
|
||||||
{
|
{
|
||||||
$client = $this->createAdminClient();
|
$client = $this->createAdminClient();
|
||||||
$cat = $this->createCategory('SECTEUR');
|
$cat = $this->createCategory('SECTEUR');
|
||||||
@@ -33,23 +33,18 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
$response = $client->request('POST', '/api/clients', [
|
$response = $client->request('POST', '/api/clients', [
|
||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'acme sas',
|
'companyName' => 'acme sas',
|
||||||
'firstName' => 'JEAN',
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
'lastName' => 'dupont',
|
|
||||||
'phonePrimary' => '06.12.34.56.78',
|
|
||||||
'email' => 'Jean.DUPONT@ACME.FR',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
self::assertResponseStatusCodeSame(201);
|
self::assertResponseStatusCodeSame(201);
|
||||||
$data = $response->toArray();
|
$data = $response->toArray();
|
||||||
// RG-1.18 / 1.19 / 1.20 / 1.21
|
// RG-1.18 : companyName normalise en MAJUSCULES. Les champs de contact
|
||||||
|
// inline ont disparu (refonte contact) -> plus de normalisation ici.
|
||||||
self::assertSame('ACME SAS', $data['companyName']);
|
self::assertSame('ACME SAS', $data['companyName']);
|
||||||
self::assertSame('Jean', $data['firstName']);
|
self::assertArrayNotHasKey('firstName', $data);
|
||||||
self::assertSame('Dupont', $data['lastName']);
|
self::assertArrayNotHasKey('email', $data);
|
||||||
self::assertSame('0612345678', $data['phonePrimary']);
|
|
||||||
self::assertSame('jean.dupont@acme.fr', $data['email']);
|
|
||||||
self::assertFalse($data['isArchived']);
|
self::assertFalse($data['isArchived']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,41 +55,18 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
$iri = '/api/categories/'.$cat->getId();
|
$iri = '/api/categories/'.$cat->getId();
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'companyName' => 'Doublon SARL',
|
'companyName' => 'Doublon SARL',
|
||||||
'firstName' => 'A',
|
'categories' => [$iri],
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'dup@test.fr',
|
|
||||||
'categories' => [$iri],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$client->request('POST', '/api/clients', ['headers' => ['Content-Type' => self::LD], 'json' => $payload]);
|
$client->request('POST', '/api/clients', ['headers' => ['Content-Type' => self::LD], 'json' => $payload]);
|
||||||
self::assertResponseStatusCodeSame(201);
|
self::assertResponseStatusCodeSame(201);
|
||||||
|
|
||||||
// Meme nom (insensible a la casse via l'index LOWER) -> 409 (RG-1.16).
|
// Meme nom (insensible a la casse via l'index LOWER) -> 409 (RG-1.16).
|
||||||
$payload['email'] = 'dup2@test.fr';
|
|
||||||
$client->request('POST', '/api/clients', ['headers' => ['Content-Type' => self::LD], 'json' => $payload]);
|
$client->request('POST', '/api/clients', ['headers' => ['Content-Type' => self::LD], 'json' => $payload]);
|
||||||
self::assertResponseStatusCodeSame(409);
|
self::assertResponseStatusCodeSame(409);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPostWithoutFirstOrLastNameReturns422(): void
|
|
||||||
{
|
|
||||||
$client = $this->createAdminClient();
|
|
||||||
$cat = $this->createCategory('SECTEUR');
|
|
||||||
|
|
||||||
$client->request('POST', '/api/clients', [
|
|
||||||
'headers' => ['Content-Type' => self::LD],
|
|
||||||
'json' => [
|
|
||||||
'companyName' => 'No Contact Name',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'nc@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// RG-1.01
|
|
||||||
self::assertResponseStatusCodeSame(422);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testPostWithoutCategoryReturns422(): void
|
public function testPostWithoutCategoryReturns422(): void
|
||||||
{
|
{
|
||||||
$client = $this->createAdminClient();
|
$client = $this->createAdminClient();
|
||||||
@@ -102,11 +74,8 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
$client->request('POST', '/api/clients', [
|
$client->request('POST', '/api/clients', [
|
||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'No Category',
|
'companyName' => 'No Category',
|
||||||
'firstName' => 'A',
|
'categories' => [],
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'nocat@test.fr',
|
|
||||||
'categories' => [],
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -124,9 +93,6 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'Mutex Client',
|
'companyName' => 'Mutex Client',
|
||||||
'firstName' => 'A',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'mutex@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
'distributor' => '/api/clients/'.$distributor->getId(),
|
'distributor' => '/api/clients/'.$distributor->getId(),
|
||||||
'broker' => '/api/clients/'.$distributor->getId(),
|
'broker' => '/api/clients/'.$distributor->getId(),
|
||||||
@@ -147,9 +113,6 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'Bad Distrib Ref',
|
'companyName' => 'Bad Distrib Ref',
|
||||||
'firstName' => 'A',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'baddistrib@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
'distributor' => '/api/clients/'.$notDistro->getId(),
|
'distributor' => '/api/clients/'.$notDistro->getId(),
|
||||||
],
|
],
|
||||||
@@ -169,9 +132,6 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'Client Avec Distrib',
|
'companyName' => 'Client Avec Distrib',
|
||||||
'firstName' => 'A',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'okdistrib@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
'distributor' => '/api/clients/'.$distributor->getId(),
|
'distributor' => '/api/clients/'.$distributor->getId(),
|
||||||
],
|
],
|
||||||
@@ -190,9 +150,6 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'Bad Broker Ref',
|
'companyName' => 'Bad Broker Ref',
|
||||||
'firstName' => 'A',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'badbroker@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
'broker' => '/api/clients/'.$notBroker->getId(),
|
'broker' => '/api/clients/'.$notBroker->getId(),
|
||||||
],
|
],
|
||||||
@@ -212,9 +169,6 @@ final class ClientApiTest extends AbstractCommercialApiTestCase
|
|||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'Client Avec Courtier',
|
'companyName' => 'Client Avec Courtier',
|
||||||
'firstName' => 'A',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'okbroker@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
'broker' => '/api/clients/'.$broker->getId(),
|
'broker' => '/api/clients/'.$broker->getId(),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -68,9 +68,6 @@ final class ClientAuditTest extends AbstractCommercialApiTestCase
|
|||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'Blamable Co',
|
'companyName' => 'Blamable Co',
|
||||||
'firstName' => 'A',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'blamable@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
],
|
],
|
||||||
])->toArray();
|
])->toArray();
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ namespace App\Tests\Module\Commercial\Api;
|
|||||||
use App\Module\Commercial\Domain\Entity\Client as ClientEntity;
|
use App\Module\Commercial\Domain\Entity\Client as ClientEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests fonctionnels du formulaire principal — combler les trous (ERP-60).
|
* Tests fonctionnels du formulaire principal apres la refonte contact.
|
||||||
*
|
*
|
||||||
* RG-1.01 (prenom OU nom obligatoire) et RG-1.03 (distributor/broker exclusifs
|
* RG-1.01 (prenom OU nom) et RG-1.02 (telephone secondaire) ont ete SUPPRIMEES
|
||||||
* + type de categorie) sont DEJA couverts par ClientApiTest (ERP-55) : on ne les
|
* du Client : le contact principal n'est plus porte inline, il vit uniquement
|
||||||
* reduplique pas ici. Ce fichier ne couvre que RG-1.02 (telephone secondaire),
|
* dans ClientContact (onglet Contact). Ce fichier verifie que :
|
||||||
* non encore testee.
|
* - le formulaire principal se cree avec les seuls champs subsistants
|
||||||
|
* (companyName + categories), sans aucun champ de contact ;
|
||||||
|
* - les anciens champs de contact (firstName, lastName, phonePrimary,
|
||||||
|
* phoneSecondary, email) ne sont plus exposes ni persistes.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@@ -21,11 +24,10 @@ final class ClientFormulaireMainTest extends AbstractCommercialApiTestCase
|
|||||||
private const string LD = 'application/ld+json';
|
private const string LD = 'application/ld+json';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RG-1.02 : le telephone secondaire est optionnel mais persiste (2 colonnes
|
* Le formulaire principal n'exige plus que companyName + au moins une
|
||||||
* distinctes). Verifie aussi la normalisation chiffres-seuls (RG-1.20) sur
|
* categorie (RG-1.16 / RG sur categories). Aucun champ de contact requis.
|
||||||
* la colonne secondaire.
|
|
||||||
*/
|
*/
|
||||||
public function testPostPersistsSecondaryPhoneNormalized(): void
|
public function testPostMainFormWithoutContactFields(): void
|
||||||
{
|
{
|
||||||
$client = $this->createAdminClient();
|
$client = $this->createAdminClient();
|
||||||
$cat = $this->createCategory('SECTEUR');
|
$cat = $this->createCategory('SECTEUR');
|
||||||
@@ -33,26 +35,28 @@ final class ClientFormulaireMainTest extends AbstractCommercialApiTestCase
|
|||||||
$data = $client->request('POST', '/api/clients', [
|
$data = $client->request('POST', '/api/clients', [
|
||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'Two Phones SARL',
|
'companyName' => 'Main Form SARL',
|
||||||
'firstName' => 'A',
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
'phonePrimary' => '06.12.34.56.78',
|
|
||||||
'phoneSecondary' => '05 49 00 11 22',
|
|
||||||
'email' => 'twophones@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
|
||||||
],
|
],
|
||||||
])->toArray();
|
])->toArray();
|
||||||
|
|
||||||
self::assertResponseStatusCodeSame(201);
|
self::assertResponseStatusCodeSame(201);
|
||||||
self::assertSame('0612345678', $data['phonePrimary']);
|
self::assertSame('MAIN FORM SARL', $data['companyName']);
|
||||||
self::assertSame('0549001122', $data['phoneSecondary']);
|
|
||||||
|
// Les champs de contact inline ont disparu de la representation.
|
||||||
|
self::assertArrayNotHasKey('firstName', $data);
|
||||||
|
self::assertArrayNotHasKey('lastName', $data);
|
||||||
|
self::assertArrayNotHasKey('phonePrimary', $data);
|
||||||
|
self::assertArrayNotHasKey('phoneSecondary', $data);
|
||||||
|
self::assertArrayNotHasKey('email', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RG-1.02 : maximum 2 telephones — le modele n'expose que phonePrimary et
|
* Les anciens champs de contact envoyes par un appel API direct (payload
|
||||||
* phoneSecondary. Un eventuel 3e champ envoye par un appel API direct est
|
* historique) sont ignores par le denormaliseur : ils n'apparaissent pas
|
||||||
* ignore (aucune 3e colonne), il ne peut donc pas creer un troisieme numero.
|
* dans la representation et ne creent aucune colonne sur le client.
|
||||||
*/
|
*/
|
||||||
public function testThirdPhoneFieldIsIgnored(): void
|
public function testLegacyContactFieldsAreIgnored(): void
|
||||||
{
|
{
|
||||||
$client = $this->createAdminClient();
|
$client = $this->createAdminClient();
|
||||||
$cat = $this->createCategory('SECTEUR');
|
$cat = $this->createCategory('SECTEUR');
|
||||||
@@ -60,25 +64,25 @@ final class ClientFormulaireMainTest extends AbstractCommercialApiTestCase
|
|||||||
$data = $client->request('POST', '/api/clients', [
|
$data = $client->request('POST', '/api/clients', [
|
||||||
'headers' => ['Content-Type' => self::LD],
|
'headers' => ['Content-Type' => self::LD],
|
||||||
'json' => [
|
'json' => [
|
||||||
'companyName' => 'Third Phone SARL',
|
'companyName' => 'Legacy Fields SARL',
|
||||||
'firstName' => 'A',
|
'firstName' => 'Ignored',
|
||||||
|
'lastName' => 'Ignored',
|
||||||
'phonePrimary' => '0612345678',
|
'phonePrimary' => '0612345678',
|
||||||
'phoneSecondary' => '0549001122',
|
'phoneSecondary' => '0549001122',
|
||||||
'phoneTertiary' => '0700000000',
|
'email' => 'ignored@test.fr',
|
||||||
'email' => 'thirdphone@test.fr',
|
|
||||||
'categories' => ['/api/categories/'.$cat->getId()],
|
'categories' => ['/api/categories/'.$cat->getId()],
|
||||||
],
|
],
|
||||||
])->toArray();
|
])->toArray();
|
||||||
|
|
||||||
self::assertResponseStatusCodeSame(201);
|
self::assertResponseStatusCodeSame(201);
|
||||||
// Le champ inconnu est ignore par le denormaliseur : il n'apparait pas
|
self::assertArrayNotHasKey('firstName', $data);
|
||||||
// dans la representation et n'a pas ete persiste.
|
self::assertArrayNotHasKey('phonePrimary', $data);
|
||||||
self::assertArrayNotHasKey('phoneTertiary', $data);
|
self::assertArrayNotHasKey('email', $data);
|
||||||
|
|
||||||
// Confirmation cote base : seules les 2 colonnes telephone existent.
|
// Confirmation cote base : le client cree ne porte aucun contact inline
|
||||||
|
// (les colonnes n'existent plus, l'entite n'a plus les proprietes).
|
||||||
$persisted = $this->getEm()->getRepository(ClientEntity::class)->find($data['id']);
|
$persisted = $this->getEm()->getRepository(ClientEntity::class)->find($data['id']);
|
||||||
self::assertNotNull($persisted);
|
self::assertNotNull($persisted);
|
||||||
self::assertSame('0612345678', $persisted->getPhonePrimary());
|
self::assertSame('LEGACY FIELDS SARL', $persisted->getCompanyName());
|
||||||
self::assertSame('0549001122', $persisted->getPhoneSecondary());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,41 @@ namespace App\Tests\Module\Commercial\Api;
|
|||||||
* - les anciens index uq_client_siren_active (RG-1.15) et uq_client_email_active
|
* - les anciens index uq_client_siren_active (RG-1.15) et uq_client_email_active
|
||||||
* (RG-1.17) ont ete supprimes / ne sont jamais crees.
|
* (RG-1.17) ont ete supprimes / ne sont jamais crees.
|
||||||
*
|
*
|
||||||
|
* Verifie aussi la refonte contact (Version20260603120000) : les 5 colonnes de
|
||||||
|
* contact principal inline ont ete supprimees de la table `client`.
|
||||||
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class ClientMigrationTest extends AbstractCommercialApiTestCase
|
final class ClientMigrationTest extends AbstractCommercialApiTestCase
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Refonte contact : first_name / last_name / phone_primary / phone_secondary
|
||||||
|
* / email ne doivent plus exister sur la table `client` (deplaces vers
|
||||||
|
* client_contact). NB : le backfill de la migration ne s'exerce que sur une
|
||||||
|
* base portant des donnees pre-refonte ; sur le schema de test (table client
|
||||||
|
* vierge au moment de la migration) il est un no-op, donc non assertable ici
|
||||||
|
* au runtime — seul l'etat de schema final est verifie.
|
||||||
|
*/
|
||||||
|
public function testInlineContactColumnsAreDropped(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
|
||||||
|
/** @var list<array{column_name: string}> $columns */
|
||||||
|
$columns = $this->getEm()->getConnection()->fetchAllAssociative(
|
||||||
|
"SELECT column_name FROM information_schema.columns "
|
||||||
|
."WHERE table_schema = 'public' AND table_name = 'client'",
|
||||||
|
);
|
||||||
|
$names = array_map(static fn (array $r): string => $r['column_name'], $columns);
|
||||||
|
|
||||||
|
foreach (['first_name', 'last_name', 'phone_primary', 'phone_secondary', 'email'] as $dropped) {
|
||||||
|
self::assertNotContains(
|
||||||
|
$dropped,
|
||||||
|
$names,
|
||||||
|
sprintf('La colonne client.%s aurait du etre supprimee (refonte contact).', $dropped),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testCompanyNameActivePartialIndexExistsExactlyOnce(): void
|
public function testCompanyNameActivePartialIndexExistsExactlyOnce(): void
|
||||||
{
|
{
|
||||||
$rows = $this->clientIndexes();
|
$rows = $this->clientIndexes();
|
||||||
|
|||||||
@@ -324,9 +324,9 @@ final class ClientRBACMatrixTest extends AbstractCommercialApiTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Payload minimal valide de l'onglet principal (RG-1.01 : un nom de contact ;
|
* Payload minimal valide de l'onglet principal (companyName + une categorie
|
||||||
* une categorie SECTEUR). Si $categoryId est null, une categorie est creee a
|
* SECTEUR ; le contact inline a ete supprime). Si $categoryId est null, une
|
||||||
* la volee.
|
* categorie est creee a la volee.
|
||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
@@ -335,11 +335,8 @@ final class ClientRBACMatrixTest extends AbstractCommercialApiTestCase
|
|||||||
$categoryId ??= $this->createCategory('SECTEUR')->getId();
|
$categoryId ??= $this->createCategory('SECTEUR')->getId();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'companyName' => $companyName,
|
'companyName' => $companyName,
|
||||||
'firstName' => 'Jean',
|
'categories' => ['/api/categories/'.$categoryId],
|
||||||
'phonePrimary' => '0612345678',
|
|
||||||
'email' => strtolower(str_replace(' ', '', $companyName)).'@matrix.test',
|
|
||||||
'categories' => ['/api/categories/'.$categoryId],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,9 +232,6 @@ final class ClientSerializationContractTest extends AbstractCommercialApiTestCas
|
|||||||
|
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
$client->setCompanyName(mb_strtoupper($companyName.' '.$suffix, 'UTF-8'));
|
$client->setCompanyName(mb_strtoupper($companyName.' '.$suffix, 'UTF-8'));
|
||||||
$client->setLastName('Complet');
|
|
||||||
$client->setPhonePrimary('0102030405');
|
|
||||||
$client->setEmail('complet'.$suffix.'@seed.test');
|
|
||||||
$client->addCategory($this->createCategory('SECTEUR'));
|
$client->addCategory($this->createCategory('SECTEUR'));
|
||||||
// Bloc comptable non nul (gating par omission cote Commerciale).
|
// Bloc comptable non nul (gating par omission cote Commerciale).
|
||||||
$client->setSiren('123456789');
|
$client->setSiren('123456789');
|
||||||
|
|||||||
@@ -12,41 +12,13 @@ use App\Module\Commercial\Domain\Entity\Client as ClientEntity;
|
|||||||
* RG-1.16 (doublon de companyName parmi les actifs -> 409) est DEJA couvert par
|
* RG-1.16 (doublon de companyName parmi les actifs -> 409) est DEJA couvert par
|
||||||
* ClientApiTest::testPostDuplicateCompanyNameReturns409 (ERP-55). Ce fichier
|
* ClientApiTest::testPostDuplicateCompanyNameReturns409 (ERP-55). Ce fichier
|
||||||
* verifie l'envers de la decision Q4 (29/05/2026) : le SIREN (RG-1.15 supprimee)
|
* verifie l'envers de la decision Q4 (29/05/2026) : le SIREN (RG-1.15 supprimee)
|
||||||
* et l'email (RG-1.17 supprimee) NE SONT PLUS contraints uniques.
|
* n'est PLUS contraint unique. (L'email — RG-1.17 — a disparu du Client avec la
|
||||||
|
* refonte contact, il vit desormais sur ClientContact.)
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class ClientUniquenessTest extends AbstractCommercialApiTestCase
|
final class ClientUniquenessTest extends AbstractCommercialApiTestCase
|
||||||
{
|
{
|
||||||
private const string LD = 'application/ld+json';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RG-1.16 / RG-1.17 (Q4) : deux clients actifs peuvent partager le meme
|
|
||||||
* email principal — aucune contrainte d'unicite (un email peut servir
|
|
||||||
* plusieurs clients).
|
|
||||||
*/
|
|
||||||
public function testDuplicateEmailIsAllowed(): void
|
|
||||||
{
|
|
||||||
$client = $this->createAdminClient();
|
|
||||||
$cat = $this->createCategory('SECTEUR');
|
|
||||||
$iri = '/api/categories/'.$cat->getId();
|
|
||||||
|
|
||||||
$payload = static fn (string $name): array => [
|
|
||||||
'companyName' => $name,
|
|
||||||
'firstName' => 'A',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 'partage@test.fr',
|
|
||||||
'categories' => [$iri],
|
|
||||||
];
|
|
||||||
|
|
||||||
$client->request('POST', '/api/clients', ['headers' => ['Content-Type' => self::LD], 'json' => $payload('Email Share One')]);
|
|
||||||
self::assertResponseStatusCodeSame(201);
|
|
||||||
|
|
||||||
// Meme email, nom different -> doit passer (pas d'index unique email).
|
|
||||||
$client->request('POST', '/api/clients', ['headers' => ['Content-Type' => self::LD], 'json' => $payload('Email Share Two')]);
|
|
||||||
self::assertResponseStatusCodeSame(201);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RG-1.15 (Q4) : deux clients peuvent partager le meme SIREN (etablissements
|
* RG-1.15 (Q4) : deux clients peuvent partager le meme SIREN (etablissements
|
||||||
* multiples). Le SIREN n'est pas ecrivable au POST (groupe accounting), on
|
* multiples). Le SIREN n'est pas ecrivable au POST (groupe accounting), on
|
||||||
|
|||||||
@@ -134,14 +134,11 @@ final class ClientProcessorTest extends TestCase
|
|||||||
'isArchived' => false,
|
'isArchived' => false,
|
||||||
],
|
],
|
||||||
managed: true,
|
managed: true,
|
||||||
// Etat persiste complet (valeurs normalisees) : sans les champs
|
// Etat persiste (valeurs normalisees) : sans companyName, guardManage
|
||||||
// metier, guardManage (ERP-74) les croirait modifies (companyName,
|
// (ERP-74) le croirait modifie (compare a null) et leverait un 403
|
||||||
// lastName... compares a null) et leverait un 403 parasite.
|
// parasite.
|
||||||
originalData: [
|
originalData: [
|
||||||
'companyName' => 'TEST CO',
|
'companyName' => 'TEST CO',
|
||||||
'lastName' => 'Dupont',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 't@test.fr',
|
|
||||||
'triageService' => false,
|
'triageService' => false,
|
||||||
'isArchived' => false,
|
'isArchived' => false,
|
||||||
],
|
],
|
||||||
@@ -164,13 +161,10 @@ final class ClientProcessorTest extends TestCase
|
|||||||
managed: true,
|
managed: true,
|
||||||
// getOriginalEntityData renvoie tous les champs mappes d'une entite
|
// getOriginalEntityData renvoie tous les champs mappes d'une entite
|
||||||
// geree : isArchived (non-null) y figure toujours, ainsi que les
|
// geree : isArchived (non-null) y figure toujours, ainsi que les
|
||||||
// champs metier (sinon guardManage les croirait modifies).
|
// champs metier (companyName) sinon guardManage les croirait modifies.
|
||||||
originalData: [
|
originalData: [
|
||||||
'siren' => '123456789',
|
'siren' => '123456789',
|
||||||
'companyName' => 'TEST CO',
|
'companyName' => 'TEST CO',
|
||||||
'lastName' => 'Dupont',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 't@test.fr',
|
|
||||||
'triageService' => false,
|
'triageService' => false,
|
||||||
'isArchived' => false,
|
'isArchived' => false,
|
||||||
],
|
],
|
||||||
@@ -193,9 +187,6 @@ final class ClientProcessorTest extends TestCase
|
|||||||
managed: true,
|
managed: true,
|
||||||
originalData: [
|
originalData: [
|
||||||
'companyName' => 'TEST CO',
|
'companyName' => 'TEST CO',
|
||||||
'lastName' => 'Dupont',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 't@test.fr',
|
|
||||||
'triageService' => false,
|
'triageService' => false,
|
||||||
'isArchived' => false,
|
'isArchived' => false,
|
||||||
],
|
],
|
||||||
@@ -220,9 +211,6 @@ final class ClientProcessorTest extends TestCase
|
|||||||
originalData: [
|
originalData: [
|
||||||
'siren' => '111111111',
|
'siren' => '111111111',
|
||||||
'companyName' => 'TEST CO',
|
'companyName' => 'TEST CO',
|
||||||
'lastName' => 'Dupont',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 't@test.fr',
|
|
||||||
'triageService' => false,
|
'triageService' => false,
|
||||||
'isArchived' => false,
|
'isArchived' => false,
|
||||||
],
|
],
|
||||||
@@ -324,9 +312,6 @@ final class ClientProcessorTest extends TestCase
|
|||||||
managed: true,
|
managed: true,
|
||||||
originalData: [
|
originalData: [
|
||||||
'companyName' => 'TEST CO',
|
'companyName' => 'TEST CO',
|
||||||
'lastName' => 'Dupont',
|
|
||||||
'phonePrimary' => '0102030405',
|
|
||||||
'email' => 't@test.fr',
|
|
||||||
'triageService' => false,
|
'triageService' => false,
|
||||||
'isArchived' => false,
|
'isArchived' => false,
|
||||||
],
|
],
|
||||||
@@ -401,16 +386,14 @@ final class ClientProcessorTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client minimal valide vis-a-vis de RG-1.01 (un nom de contact) — suffisant
|
* Client minimal — companyName seul depuis la suppression du contact inline.
|
||||||
* pour atteindre les validations testees.
|
* Suffisant pour atteindre les validations testees (le contact vit desormais
|
||||||
|
* dans ClientContact, hors scope du ClientProcessor).
|
||||||
*/
|
*/
|
||||||
private function minimalClient(): Client
|
private function minimalClient(): Client
|
||||||
{
|
{
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
$client->setCompanyName('Test Co');
|
$client->setCompanyName('Test Co');
|
||||||
$client->setLastName('Dupont');
|
|
||||||
$client->setPhonePrimary('0102030405');
|
|
||||||
$client->setEmail('t@test.fr');
|
|
||||||
|
|
||||||
return $client;
|
return $client;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user