refactor(commercial) : suppression du contact principal inline du Client (M1 · back 1/3) (#55)
Auto Tag Develop / tag (push) Successful in 9s
Auto Tag Develop / tag (push) Successful in 9s
## Contexte M1 · Ticket 1/3 (Backend) de la refonte contact. Le contact principal inline du `Client` (firstName, lastName, phonePrimary, phoneSecondary, email) faisait doublon avec la sous-entité `ClientContact` (onglet Contact). Il est supprimé : les contacts vivent désormais **uniquement** dans `client_contact`. RG-1.01 (firstName OU lastName sur Client) et RG-1.02 (max 2 téléphones sur Client) sont **supprimées** du Client — leur équivalent vit déjà sur `ClientContact` (RG-1.05 / RG-1.14). ## Changements - **Migration** `Version20260603120000` (namespace racine `DoctrineMigrations` — tri par timestamp, cf. AlphabeticalComparator) : **backfill** des clients sans contact vers `client_contact` (position 0) **avant** le `DROP` des 5 colonnes. `down()` best-effort documenté. - **Entité `Client`** : retrait des 5 propriétés + getters/setters + groupes de sérialisation. - **`ClientProcessor`** : `MAIN_FIELDS` / `changedBusinessFields()` / `normalize()` allégés ; `validateMainContact()` (RG-1.01) supprimée. - **Recherche répertoire (D1)** : sur `companyName` seul (les anciens critères lastName/email vivaient sur les colonnes supprimées). - **Export XLSX (D2)** : colonnes de contact retirées (Nom entreprise / Catégories / Sites / [SIREN] / Date). - **Fixtures** + **catalogue de commentaires SQL** (`ColumnCommentsCatalog`) alignés. - **Tests** fonctionnels et unitaires mis à jour. ## Décisions actées - **Migration** au namespace racine (et non modulaire Commercial) : une migration `App\Module\Commercial\…` trierait avant le `CREATE TABLE client` sur base fraîche → casse. Conforme à la règle ABSOLUE n°11. - **D1** = recherche `companyName` seul. **D2** = retrait des colonnes contact de l'export. ## Vérifications - ✅ `make db-reset && make migration-migrate` : migration rejouable sur base fraîche (backfill no-op si contacts déjà présents). - ✅ `make test` : 466 tests verts. - ✅ `make php-cs-fixer-allow-risky` : clean. - ✅ Contrat réel `GET /api/clients/{id}` : les 5 champs ont disparu de la racine, `contacts[]` porte l'info. --------- Co-authored-by: admin malio <malio@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #55 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
This commit was merged in pull request #55.
This commit is contained in:
@@ -151,31 +151,9 @@ class Client implements TimestampableInterface, BlamableInterface
|
||||
#[Groups(['client:read', 'client:write:main'])]
|
||||
private ?string $companyName = null;
|
||||
|
||||
// RG-1.01 : firstName OU lastName obligatoire (validation au futur Processor).
|
||||
#[ORM\Column(length: 120, nullable: true)]
|
||||
#[Assert\Length(max: 120, normalizer: 'trim')]
|
||||
#[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;
|
||||
// Le contact principal n'est plus porte inline par le Client : les contacts
|
||||
// vivent uniquement dans ClientContact (onglet Contact). RG-1.01 / RG-1.02
|
||||
// supprimees du Client (equivalent RG-1.05 / RG-1.14 sur ClientContact).
|
||||
|
||||
// RG-1.03 : distributor / broker auto-references mutuellement exclusives
|
||||
// (CHECK chk_client_distrib_or_broker en base).
|
||||
@@ -326,66 +304,6 @@ class Client implements TimestampableInterface, BlamableInterface
|
||||
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
|
||||
{
|
||||
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 /
|
||||
* § 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) :
|
||||
* 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
|
||||
* interdit toute autre modification dans la meme requete (RG-1.22, 422).
|
||||
* 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),
|
||||
* RG-1.13 (LCR -> >= 1 RIB), RG-1.04 (completude Information exigee sur POST
|
||||
* 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). */
|
||||
private const array MAIN_FIELDS = [
|
||||
'companyName', 'firstName', 'lastName', 'phonePrimary', 'phoneSecondary',
|
||||
'email', 'distributor', 'broker', 'triageService', 'categories',
|
||||
'companyName', 'distributor', 'broker', 'triageService', 'categories',
|
||||
];
|
||||
|
||||
/** 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).
|
||||
$this->guardManage($data);
|
||||
|
||||
$this->validateMainContact($data);
|
||||
$this->validateDistributorBroker($data);
|
||||
$this->validateAccountingConsistency($data);
|
||||
$this->validateInformationCompleteness($data);
|
||||
@@ -274,11 +272,6 @@ final class ClientProcessor implements ProcessorInterface
|
||||
{
|
||||
$newValues = [
|
||||
'companyName' => $data->getCompanyName(),
|
||||
'firstName' => $data->getFirstName(),
|
||||
'lastName' => $data->getLastName(),
|
||||
'phonePrimary' => $data->getPhonePrimary(),
|
||||
'phoneSecondary' => $data->getPhoneSecondary(),
|
||||
'email' => $data->getEmail(),
|
||||
'distributor' => $data->getDistributor(),
|
||||
'broker' => $data->getBroker(),
|
||||
'triageService' => $data->isTriageService(),
|
||||
@@ -420,39 +413,17 @@ final class ClientProcessor implements ProcessorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalisation serveur (RG-1.18 a 1.21). Les setters non-nullables
|
||||
* (companyName, email, phonePrimary) ne sont touches que si une valeur est
|
||||
* presente, pour ne jamais ecraser l'existant lors d'un PATCH partiel.
|
||||
* Normalisation serveur du formulaire principal (RG-1.18). Seul companyName
|
||||
* subsiste cote Client depuis la suppression du contact inline (les champs de
|
||||
* 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
|
||||
{
|
||||
if (null !== $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.
|
||||
*
|
||||
* @return list<string>
|
||||
@@ -95,11 +97,6 @@ final class ClientExportController
|
||||
{
|
||||
$headers = [
|
||||
'Nom entreprise',
|
||||
'Nom contact principal',
|
||||
'Prénom',
|
||||
'Téléphone principal',
|
||||
'Téléphone secondaire',
|
||||
'Email',
|
||||
'Catégories',
|
||||
'Sites',
|
||||
];
|
||||
@@ -123,11 +120,6 @@ final class ClientExportController
|
||||
foreach ($clients as $client) {
|
||||
$row = [
|
||||
$client->getCompanyName(),
|
||||
$client->getLastName(),
|
||||
$client->getFirstName(),
|
||||
$client->getPhonePrimary(),
|
||||
$client->getPhoneSecondary(),
|
||||
$client->getEmail(),
|
||||
$this->formatCategories($client),
|
||||
$this->formatSites($client),
|
||||
];
|
||||
|
||||
@@ -112,10 +112,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$gso, $gsoIsNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Distrib Grand Sud-Ouest',
|
||||
firstName: 'Paul',
|
||||
lastName: 'Garnier',
|
||||
phonePrimary: '05 56 10 20 30',
|
||||
email: 'contact@distrib-gso.fr',
|
||||
categoryNames: ['Distributeur'],
|
||||
);
|
||||
if ($gsoIsNew) {
|
||||
@@ -127,10 +123,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$leonard, $leonardIsNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Cabinet Léonard Assurances',
|
||||
firstName: 'Sophie',
|
||||
lastName: 'Léonard',
|
||||
phonePrimary: '05 49 11 22 33',
|
||||
email: 'contact@cabinet-leonard.fr',
|
||||
categoryNames: ['Courtier'],
|
||||
);
|
||||
if ($leonardIsNew) {
|
||||
@@ -142,10 +134,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$dubois, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Menuiserie Dubois',
|
||||
firstName: 'Jean',
|
||||
lastName: 'Dubois',
|
||||
phonePrimary: '05 49 00 00 01',
|
||||
email: 'contact@menuiserie-dubois.fr',
|
||||
categoryNames: ['BTP'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -159,10 +147,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$garage, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Garage Martin',
|
||||
firstName: 'Luc',
|
||||
lastName: 'Martin',
|
||||
phonePrimary: '05 56 44 55 66',
|
||||
email: 'accueil@garage-martin.fr',
|
||||
categoryNames: ['Services'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -175,10 +159,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$boulangerie, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Boulangerie Lemoine',
|
||||
firstName: 'Marie',
|
||||
lastName: 'Lemoine',
|
||||
phonePrimary: '05 49 77 88 99',
|
||||
email: 'bonjour@boulangerie-lemoine.fr',
|
||||
categoryNames: ['Agro-alimentaire'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -191,10 +171,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$transports, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Transports Rapides',
|
||||
firstName: null,
|
||||
lastName: 'Bernard',
|
||||
phonePrimary: '05 56 12 13 14',
|
||||
email: 'exploitation@transports-rapides.fr',
|
||||
categoryNames: ['Transport/Logistique'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -209,10 +185,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$industries, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Industries Vertes',
|
||||
firstName: 'Claire',
|
||||
lastName: 'Moreau',
|
||||
phonePrimary: '05 49 21 22 23',
|
||||
email: 'contact@industries-vertes.fr',
|
||||
categoryNames: ['Industrie'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -229,12 +201,7 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$agro, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Agro Distribution Sud',
|
||||
firstName: 'Thomas',
|
||||
lastName: 'Petit',
|
||||
phonePrimary: '05 56 31 32 33',
|
||||
email: 'contact@agro-sud.fr',
|
||||
categoryNames: ['Agro-alimentaire'],
|
||||
phoneSecondary: '06 01 02 03 04',
|
||||
);
|
||||
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);
|
||||
@@ -247,10 +214,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$ancienne, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Ancienne Société Oubliée',
|
||||
firstName: null,
|
||||
lastName: 'Durand',
|
||||
phonePrimary: '05 49 99 99 99',
|
||||
email: 'contact@ancienne-societe.fr',
|
||||
categoryNames: ['Association'],
|
||||
isArchived: true,
|
||||
);
|
||||
@@ -263,10 +226,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$services, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Services Pro Conseil',
|
||||
firstName: 'Nadia',
|
||||
lastName: 'Benali',
|
||||
phonePrimary: '05 49 41 42 43',
|
||||
email: 'contact@services-pro.fr',
|
||||
categoryNames: ['Services'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -279,10 +238,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$holding, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Holding Premium Invest',
|
||||
firstName: 'Antoine',
|
||||
lastName: 'Lefèvre',
|
||||
phonePrimary: '05 56 51 52 53',
|
||||
email: 'direction@holding-premium.fr',
|
||||
categoryNames: ['Industrie'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -301,10 +256,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$conglo, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
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'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -316,10 +267,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$prospect, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Prospect Futur Client',
|
||||
firstName: 'Olivier',
|
||||
lastName: 'Renard',
|
||||
phonePrimary: '05 56 71 72 73',
|
||||
email: 'olivier.renard@prospect-futur.fr',
|
||||
categoryNames: ['BTP'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -331,10 +278,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
[$association, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Association des Riverains',
|
||||
firstName: null,
|
||||
lastName: 'Caron',
|
||||
phonePrimary: '05 49 81 82 83',
|
||||
email: 'contact@asso-riverains.fr',
|
||||
categoryNames: ['Association'],
|
||||
);
|
||||
if ($isNew) {
|
||||
@@ -350,6 +293,9 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
* sinon retourne l'existant. Retourne [Client, isNew] : isNew=false bloque la
|
||||
* 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
|
||||
*
|
||||
* @return array{0: Client, 1: bool}
|
||||
@@ -357,12 +303,7 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
private function ensureClient(
|
||||
ObjectManager $manager,
|
||||
string $companyName,
|
||||
?string $firstName,
|
||||
?string $lastName,
|
||||
string $phonePrimary,
|
||||
string $email,
|
||||
array $categoryNames,
|
||||
?string $phoneSecondary = null,
|
||||
bool $isArchived = false,
|
||||
): array {
|
||||
$normalizedName = (string) $this->normalizer->normalizeCompanyName($companyName);
|
||||
@@ -374,11 +315,6 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
|
||||
$client = new Client();
|
||||
$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) {
|
||||
$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.
|
||||
* Les metacaracteres LIKE (%, _, \) saisis sont echappes pour rester
|
||||
* litteraux.
|
||||
* Recherche fuzzy insensible a la casse sur companyName (D1, refonte contact).
|
||||
* Depuis la suppression du contact inline du Client, la recherche ne porte
|
||||
* 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
|
||||
{
|
||||
@@ -116,11 +118,9 @@ class DoctrineClientRepository extends ServiceEntityRepository implements Client
|
||||
$escaped = str_replace(['\\', '%', '_'], ['\\\\', '\%', '\_'], trim($search));
|
||||
$pattern = '%'.mb_strtolower($escaped, 'UTF-8').'%';
|
||||
|
||||
$qb->andWhere(
|
||||
'LOWER(c.companyName) LIKE :search '
|
||||
.'OR LOWER(c.lastName) LIKE :search '
|
||||
.'OR LOWER(c.email) LIKE :search',
|
||||
)->setParameter('search', $pattern);
|
||||
$qb->andWhere('LOWER(c.companyName) LIKE :search')
|
||||
->setParameter('search', $pattern)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -166,14 +166,12 @@ final class ColumnCommentsCatalog
|
||||
],
|
||||
|
||||
'client' => [
|
||||
'_table' => 'Repertoire clients (M1 Commercial) — entites archivables (is_archived) et soft-deletables (deleted_at, HP M2).',
|
||||
'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).',
|
||||
'first_name' => 'Prenom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).',
|
||||
'last_name' => 'Nom du contact principal (capitalise serveur, RG-1.19). first_name OU last_name obligatoire (RG-1.01).',
|
||||
'phone_primary' => 'Telephone principal — stocke en chiffres uniquement (RG-1.20). Obligatoire.',
|
||||
'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).',
|
||||
'_table' => 'Repertoire clients (M1 Commercial) — entites archivables (is_archived) et soft-deletables (deleted_at, HP M2).',
|
||||
'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).',
|
||||
// Contact principal inline supprime (refonte contact) : first_name,
|
||||
// last_name, phone_primary, phone_secondary, email vivent desormais
|
||||
// uniquement sur client_contact.
|
||||
'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.',
|
||||
'triage_service' => 'Drapeau service triage active pour le client. Faux par defaut.',
|
||||
|
||||
Reference in New Issue
Block a user