09a4b9d464
Auto Tag Develop / tag (push) Successful in 9s
## ERP-132 — Migrer le schéma BDD M3 (provider + sous-collections) > ⚠️ **MR stackée** sur `feat/erp-m3-technique-module-taxonomie` (ERP-131, module Technique + type PRESTATAIRE). À merger **après** ERP-131. Base volontairement ≠ develop tant qu'ERP-131 n'est pas mergé. ### Contenu Crée tout le schéma Postgres du répertoire prestataires (1 migration, namespace racine `DoctrineMigrations` — FK cross-module user/category/site + référentiels comptables M1). **Tables (9)** : - `provider` : company_name + bloc Comptabilité (siren/account_number/n_tva + FK tva_mode/payment_delay/payment_type/bank ON DELETE RESTRICT) + is_archived/archived_at/deleted_at + Timestampable/Blamable. **Pas d'onglet Information** (≠ supplier). - M2M formulaire principal : `provider_category` (RG-3.09), `provider_site` (sites du prestataire — RG-3.03, **nouveau vs supplier** + `idx_provider_site_site` pour le cloisonnement par site). - Sous-collections : `provider_contact` (CHECK `chk_provider_contact_name` : ≥1 champ parmi first_name/last_name/phone_primary/email), `provider_address` (**sans** address_type/bennes/triage), `provider_rib`. - Jointures adresse : `provider_address_site` (RG-3.05), `provider_address_contact`, `provider_address_category`. - Index partiel unique `uq_provider_company_name_active` (LOWER(company_name) WHERE non archivé/non supprimé — RG-3.10) + index FK. - `COMMENT ON COLUMN/TABLE` inline sur **toutes** les colonnes (règle ABSOLUE n°12). ### Décisions - **CategoryType PRESTATAIRE non re-seedé** : déjà créé par ERP-131. Migration purement structurelle. - **COMMENT inline (pas via ColumnCommentsCatalog)** : tant que les entités Provider* n'existent pas (ERP-133), `schema:update --force` du setup test droppe les tables non mappées → les référencer dans le catalogue ferait planter `app:apply-column-comments`. Catalogue + ligne `dbal:run-sql uq_provider` différés à ERP-133, exactement comme supplier (ERP-86 après ERP-85). ### Tests - ✅ `make db-reset` (dev + test-db-setup) - ✅ `make test` — 589 tests, `ColumnsHaveSqlCommentTest` vert - ✅ Index partiel vérifié partiel (clause WHERE), `idx_provider_site_site` présent, 0 colonne sans COMMENT - ✅ Cycle `down()`/`up()` OK - ✅ `make php-cs-fixer-allow-risky` (0 fichier) --------- Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #90
452 lines
26 KiB
PHP
452 lines
26 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use App\Shared\Infrastructure\Database\ColumnCommentsCatalog;
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* M3 — Repertoire prestataires (ERP-132) : creation de toute la structure BDD
|
|
* des prestataires sous le nouveau module Technique (jumeau du M2 fournisseur).
|
|
*
|
|
* Tables creees :
|
|
* - Table principale : provider (formulaire principal + Comptabilite + archive
|
|
* + soft-delete + Timestampable/Blamable). PAS d onglet Information.
|
|
* - M2M du formulaire principal : provider_category (RG-3.09),
|
|
* provider_site (sites du prestataire, RG-3.03 — NOUVEAU vs supplier).
|
|
* - Sous-collections : provider_contact (1:n), provider_address (1:n),
|
|
* provider_rib (1:n).
|
|
* - Jointures de provider_address : provider_address_site (RG-3.05),
|
|
* provider_address_contact, provider_address_category.
|
|
*
|
|
* Differences vs le M2 `supplier` (cf. spec M3 § 3.1) :
|
|
* - PAS d onglet Information : aucun champ description / competitors /
|
|
* founded_at / employees_count / revenue_amount / director_name /
|
|
* profit_amount / volume_forecast. Le provider est minimal : nom + compta.
|
|
* - AJOUT de provider_site (M2M) : sites rattaches au prestataire directement
|
|
* sur le formulaire principal (RG-3.03, >= 1). Sert aussi le cloisonnement
|
|
* par site (idx_provider_site_site, § 2.13).
|
|
* - provider_address SIMPLIFIEE : pas de address_type / bennes /
|
|
* triage_provider (specifiques fournisseur). Champs : country / postal_code
|
|
* / city / street / street_complement / position + M2M sites/contacts/categories.
|
|
*
|
|
* Referentiels comptables NON recrees : tva_mode / payment_delay / payment_type
|
|
* / bank sont ceux du M1 (FK partagees, zero duplication — spec § 2.3).
|
|
*
|
|
* CategoryType PRESTATAIRE NON re-seede : il est cree par ERP-131
|
|
* (Version20260612080000) avec ses categories de demonstration. Le M2M
|
|
* provider_category / provider_address_category s appuie sur ce type existant.
|
|
*
|
|
* Namespace racine `DoctrineMigrations` (regle ABSOLUE Starseed n°11) et NON
|
|
* `App\Module\Technique\...` : la migration cree un schema avec FK cross-module
|
|
* (user, category, site, et les referentiels comptables M1). Avec plusieurs
|
|
* migrations_paths, Doctrine Migrations 3.x trie par FQCN alphabetique — un
|
|
* namespace modulaire s executerait avant la creation de user/category/site sur
|
|
* base vide -> echec des FK. Le namespace racine garantit l ordre par timestamp.
|
|
*
|
|
* Style DDL aligne sur le M1/M2 (Version20260605130000) : `INT GENERATED BY
|
|
* DEFAULT AS IDENTITY` (et non SERIAL), `TIMESTAMP(0) WITHOUT TIME ZONE` (et non
|
|
* TIMESTAMPTZ, car le TimestampableBlamableTrait mappe `datetime_immutable`).
|
|
* Garantit que `schema:update` restera un no-op quand les entites arriveront
|
|
* (ticket ERP-133).
|
|
*
|
|
* Decision unicite (alignee Q4 M1 / § 2.6 M2) : unicite metier sur le NOM DE
|
|
* SOCIETE uniquement (uq_provider_company_name_active, partiel). Pas d index
|
|
* unique sur siren ni email.
|
|
*
|
|
* COMMENT ON COLUMN inline (regle ABSOLUE n°12) : chaque colonne metier porte sa
|
|
* description ici-meme. Volontairement NON ajoutees a `ColumnCommentsCatalog` /
|
|
* `makefile test-db-setup` a ce stade : tant que les entites Provider* n existent
|
|
* pas (ERP-133), `schema:update --force` du setup de test droppe ces tables non
|
|
* mappees — les referencer dans le catalogue ferait planter
|
|
* `app:apply-column-comments`. Le catalogue + la ligne `dbal:run-sql`
|
|
* (uq_provider_company_name_active) seront ajoutes au ticket entites (ERP-133),
|
|
* exactement comme supplier (ERP-86) apres sa migration (ERP-85). Les 4 colonnes
|
|
* Timestampable/Blamable reutilisent les textes standardises du catalogue
|
|
* (`timestampableBlamableComments()`, simple tableau statique sans dependance DB).
|
|
*/
|
|
final class Version20260612100000 extends AbstractMigration
|
|
{
|
|
public function getDescription(): string
|
|
{
|
|
return 'ERP-132 (M3) : tables provider + sous-collections + jointures M2M (referentiels comptables et CategoryType PRESTATAIRE reutilises).';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
$this->createProviderTable();
|
|
$this->createProviderCategory();
|
|
$this->createProviderSite();
|
|
$this->createProviderContact();
|
|
$this->createProviderAddress();
|
|
$this->createProviderAddressJoinTables();
|
|
$this->createProviderRib();
|
|
}
|
|
|
|
public function down(Schema $schema): void
|
|
{
|
|
// Ordre inverse des dependances FK : jointures et sous-collections
|
|
// d abord, puis provider. Les referentiels comptables et le
|
|
// CategoryType PRESTATAIRE ne sont pas touches (crees ailleurs).
|
|
$this->addSql('DROP TABLE IF EXISTS provider_address_category');
|
|
$this->addSql('DROP TABLE IF EXISTS provider_address_contact');
|
|
$this->addSql('DROP TABLE IF EXISTS provider_address_site');
|
|
$this->addSql('DROP TABLE IF EXISTS provider_rib');
|
|
$this->addSql('DROP TABLE IF EXISTS provider_address');
|
|
$this->addSql('DROP TABLE IF EXISTS provider_contact');
|
|
$this->addSql('DROP TABLE IF EXISTS provider_site');
|
|
$this->addSql('DROP TABLE IF EXISTS provider_category');
|
|
$this->addSql('DROP TABLE IF EXISTS provider');
|
|
}
|
|
|
|
// =================================================================
|
|
// Table principale `provider`
|
|
// =================================================================
|
|
|
|
private function createProviderTable(): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider (
|
|
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
company_name VARCHAR(180) NOT NULL,
|
|
siren VARCHAR(20) DEFAULT NULL,
|
|
account_number VARCHAR(40) DEFAULT NULL,
|
|
tva_mode_id INT DEFAULT NULL,
|
|
n_tva VARCHAR(40) DEFAULT NULL,
|
|
payment_delay_id INT DEFAULT NULL,
|
|
payment_type_id INT DEFAULT NULL,
|
|
bank_id INT DEFAULT NULL,
|
|
is_archived BOOLEAN DEFAULT FALSE NOT NULL,
|
|
archived_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
|
deleted_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
|
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
created_by INT DEFAULT NULL,
|
|
updated_by INT DEFAULT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT fk_provider_tva_mode
|
|
FOREIGN KEY (tva_mode_id) REFERENCES tva_mode (id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_provider_payment_delay
|
|
FOREIGN KEY (payment_delay_id) REFERENCES payment_delay (id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_provider_payment_type
|
|
FOREIGN KEY (payment_type_id) REFERENCES payment_type (id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_provider_bank
|
|
FOREIGN KEY (bank_id) REFERENCES bank (id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_provider_created_by
|
|
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_provider_updated_by
|
|
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
|
|
)
|
|
SQL);
|
|
|
|
$this->addSql('CREATE INDEX idx_provider_is_archived ON provider (is_archived)');
|
|
$this->addSql('CREATE INDEX idx_provider_deleted_at ON provider (deleted_at)');
|
|
$this->addSql('CREATE INDEX idx_provider_created_by ON provider (created_by)');
|
|
$this->addSql('CREATE INDEX idx_provider_updated_by ON provider (updated_by)');
|
|
|
|
// Index sur les FK des referentiels comptables (Postgres n indexe pas
|
|
// automatiquement les colonnes portant une FOREIGN KEY).
|
|
$this->addSql('CREATE INDEX idx_provider_tva_mode_id ON provider (tva_mode_id)');
|
|
$this->addSql('CREATE INDEX idx_provider_payment_delay_id ON provider (payment_delay_id)');
|
|
$this->addSql('CREATE INDEX idx_provider_payment_type_id ON provider (payment_type_id)');
|
|
$this->addSql('CREATE INDEX idx_provider_bank_id ON provider (bank_id)');
|
|
|
|
// Unicite metier partielle : nom de societe insensible a la casse, parmi
|
|
// les non-archives ET non soft-deletes uniquement (RG-3.10). Pas d index
|
|
// unique sur siren ni email.
|
|
$this->addSql(<<<'SQL'
|
|
CREATE UNIQUE INDEX uq_provider_company_name_active
|
|
ON provider (LOWER(company_name))
|
|
WHERE is_archived = FALSE AND deleted_at IS NULL
|
|
SQL);
|
|
|
|
$this->comment('provider', '_table', 'Repertoire prestataires (M3 Technique) — entites archivables (is_archived) et soft-deletables (deleted_at, HP M4). Pas d onglet Information (≠ supplier).');
|
|
$this->comment('provider', 'id', 'Identifiant interne auto-incremente.');
|
|
$this->comment('provider', 'company_name', 'Raison sociale du prestataire (stockee en MAJUSCULES). Unique case-insensitive parmi les actifs non archives/non supprimes (uq_provider_company_name_active, RG-3.10).');
|
|
$this->comment('provider', 'siren', 'Onglet Comptabilite : SIREN (9 chiffres attendus). NON unique — peut etre partage entre etablissements (RG-3.10).');
|
|
$this->comment('provider', 'account_number', 'Onglet Comptabilite : numero de compte comptable du prestataire.');
|
|
$this->comment('provider', 'tva_mode_id', 'Onglet Comptabilite : mode de TVA applique — FK -> tva_mode.id (referentiel partage M1), ON DELETE RESTRICT.');
|
|
$this->comment('provider', 'n_tva', 'Onglet Comptabilite : numero de TVA intracommunautaire.');
|
|
$this->comment('provider', 'payment_delay_id', 'Onglet Comptabilite : delai de reglement — FK -> payment_delay.id (M1), ON DELETE RESTRICT.');
|
|
$this->comment('provider', 'payment_type_id', 'Onglet Comptabilite : type de reglement — FK -> payment_type.id (M1), ON DELETE RESTRICT. Pilote RG-3.07 (Banque si VIREMENT) et RG-3.08 (RIB).');
|
|
$this->comment('provider', 'bank_id', 'Onglet Comptabilite : banque — FK -> bank.id (M1), ON DELETE RESTRICT. Obligatoire ssi payment_type = VIREMENT (RG-3.07), null sinon.');
|
|
$this->comment('provider', 'is_archived', 'Drapeau fonctionnel d archivage — masque par defaut dans la liste. Bascule via permission technique.providers.archive.');
|
|
$this->comment('provider', 'archived_at', 'Horodatage de l archivage — pose quand is_archived passe a vrai, remis a null a la restauration.');
|
|
$this->comment('provider', 'deleted_at', 'Horodatage du soft-delete technique (HP M4) — non expose par l API au M3. Null = ligne active.');
|
|
$this->addTimestampableBlamableComments('provider');
|
|
}
|
|
|
|
// =================================================================
|
|
// M2M provider <-> category (type PRESTATAIRE — RG-3.09)
|
|
// =================================================================
|
|
|
|
private function createProviderCategory(): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider_category (
|
|
provider_id INT NOT NULL,
|
|
category_id INT NOT NULL,
|
|
PRIMARY KEY (provider_id, category_id),
|
|
CONSTRAINT fk_provider_category_provider
|
|
FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_provider_category_category
|
|
FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE RESTRICT
|
|
)
|
|
SQL);
|
|
$this->addSql('CREATE INDEX idx_provider_category_category ON provider_category (category_id)');
|
|
|
|
$this->comment('provider_category', '_table', 'Jointure M2M provider <-> category (Catalog) — categories de type PRESTATAIRE du prestataire, au moins une obligatoire (RG-3.09).');
|
|
$this->comment('provider_category', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire porteur de la categorie.');
|
|
$this->comment('provider_category', 'category_id', 'FK -> category.id, ON DELETE RESTRICT — categorie de type PRESTATAIRE rattachee au prestataire (RG-3.09).');
|
|
}
|
|
|
|
// =================================================================
|
|
// M2M provider <-> site (formulaire principal — RG-3.03)
|
|
// =================================================================
|
|
|
|
private function createProviderSite(): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider_site (
|
|
provider_id INT NOT NULL,
|
|
site_id INT NOT NULL,
|
|
PRIMARY KEY (provider_id, site_id),
|
|
CONSTRAINT fk_provider_site_provider
|
|
FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_provider_site_site
|
|
FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE RESTRICT
|
|
)
|
|
SQL);
|
|
// Index sur site_id : sert le filtre de cloisonnement par site
|
|
// (WHERE site = :currentSite, § 2.13).
|
|
$this->addSql('CREATE INDEX idx_provider_site_site ON provider_site (site_id)');
|
|
|
|
$this->comment('provider_site', '_table', 'Jointure M2M provider <-> site (Sites) — sites du prestataire, selecteur du formulaire principal, au moins un obligatoire (RG-3.03). Sert le cloisonnement par site (§ 2.13).');
|
|
$this->comment('provider_site', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire porteur du site.');
|
|
$this->comment('provider_site', 'site_id', 'FK -> site.id, ON DELETE RESTRICT — site rattache au prestataire (RG-3.03, idx_provider_site_site).');
|
|
}
|
|
|
|
// =================================================================
|
|
// Sous-collection : contacts (1:n)
|
|
// =================================================================
|
|
|
|
private function createProviderContact(): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider_contact (
|
|
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
provider_id INT NOT NULL,
|
|
first_name VARCHAR(120) DEFAULT NULL,
|
|
last_name VARCHAR(120) DEFAULT NULL,
|
|
job_title VARCHAR(120) DEFAULT NULL,
|
|
phone_primary VARCHAR(20) DEFAULT NULL,
|
|
phone_secondary VARCHAR(20) DEFAULT NULL,
|
|
email VARCHAR(180) DEFAULT NULL,
|
|
position INT DEFAULT 0 NOT NULL,
|
|
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
created_by INT DEFAULT NULL,
|
|
updated_by INT DEFAULT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT chk_provider_contact_name
|
|
CHECK (first_name IS NOT NULL OR last_name IS NOT NULL OR phone_primary IS NOT NULL OR email IS NOT NULL),
|
|
CONSTRAINT fk_provider_contact_provider
|
|
FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_provider_contact_created_by
|
|
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_provider_contact_updated_by
|
|
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
|
|
)
|
|
SQL);
|
|
$this->addSql('CREATE INDEX idx_provider_contact_provider ON provider_contact (provider_id)');
|
|
|
|
$this->comment('provider_contact', '_table', 'Contacts d un prestataire (1:n) — au moins un champ rempli parmi prenom/nom/telephone/email (RG-3.04, chk_provider_contact_name).');
|
|
$this->comment('provider_contact', 'id', 'Identifiant interne auto-incremente.');
|
|
$this->comment('provider_contact', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire proprietaire du contact.');
|
|
$this->comment('provider_contact', 'first_name', 'Prenom du contact (capitalise serveur). Au moins un champ du contact requis (RG-3.04, chk_provider_contact_name).');
|
|
$this->comment('provider_contact', 'last_name', 'Nom du contact (capitalise serveur). Au moins un champ du contact requis (RG-3.04, chk_provider_contact_name).');
|
|
$this->comment('provider_contact', 'job_title', 'Fonction / intitule de poste du contact (≤ 120 caracteres).');
|
|
$this->comment('provider_contact', 'phone_primary', 'Telephone principal du contact — chiffres uniquement (normalisation serveur).');
|
|
$this->comment('provider_contact', 'phone_secondary', 'Telephone secondaire du contact — chiffres uniquement (normalisation serveur).');
|
|
$this->comment('provider_contact', 'email', 'Email du contact (lowercase serveur).');
|
|
$this->comment('provider_contact', 'position', 'Ordre d affichage du contact dans la liste du prestataire (croissant).');
|
|
$this->addTimestampableBlamableComments('provider_contact');
|
|
}
|
|
|
|
// =================================================================
|
|
// Sous-collection : adresses (1:n) — SANS address_type / bennes / triage
|
|
// =================================================================
|
|
|
|
private function createProviderAddress(): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider_address (
|
|
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
provider_id INT NOT NULL,
|
|
country VARCHAR(80) DEFAULT 'France' NOT NULL,
|
|
postal_code VARCHAR(20) NOT NULL,
|
|
city VARCHAR(120) NOT NULL,
|
|
street VARCHAR(255) NOT NULL,
|
|
street_complement VARCHAR(255) DEFAULT NULL,
|
|
position INT DEFAULT 0 NOT NULL,
|
|
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
created_by INT DEFAULT NULL,
|
|
updated_by INT DEFAULT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT fk_provider_address_provider
|
|
FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_provider_address_created_by
|
|
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_provider_address_updated_by
|
|
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
|
|
)
|
|
SQL);
|
|
$this->addSql('CREATE INDEX idx_provider_address_provider ON provider_address (provider_id)');
|
|
|
|
$this->comment('provider_address', '_table', 'Adresses d un prestataire (1:n) — >= 1 site rattache (RG-3.05). SANS address_type / bennes / triage_provider (specifiques fournisseur).');
|
|
$this->comment('provider_address', 'id', 'Identifiant interne auto-incremente.');
|
|
$this->comment('provider_address', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire proprietaire de l adresse.');
|
|
$this->comment('provider_address', 'country', 'Pays de l adresse — defaut France.');
|
|
$this->comment('provider_address', 'postal_code', 'Code postal (4-5 chiffres attendus) — declenche l autocompletion ville via l API BAN cote front (RG-3.06).');
|
|
$this->comment('provider_address', 'city', 'Ville — preremplie depuis le code postal via API BAN cote front.');
|
|
$this->comment('provider_address', 'street', 'Numero et voie de l adresse.');
|
|
$this->comment('provider_address', 'street_complement', 'Complement d adresse (etage, batiment...) — optionnel.');
|
|
$this->comment('provider_address', 'position', 'Ordre d affichage de l adresse dans la liste du prestataire (croissant).');
|
|
$this->addTimestampableBlamableComments('provider_address');
|
|
}
|
|
|
|
// =================================================================
|
|
// Jointures de provider_address (M2M)
|
|
// =================================================================
|
|
|
|
private function createProviderAddressJoinTables(): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider_address_site (
|
|
provider_address_id INT NOT NULL,
|
|
site_id INT NOT NULL,
|
|
PRIMARY KEY (provider_address_id, site_id),
|
|
CONSTRAINT fk_provider_address_site_address
|
|
FOREIGN KEY (provider_address_id) REFERENCES provider_address (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_provider_address_site_site
|
|
FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE RESTRICT
|
|
)
|
|
SQL);
|
|
$this->comment('provider_address_site', '_table', 'Jointure M2M provider_address <-> site (Sites) — sites rattaches a l adresse (>= 1 obligatoire, RG-3.05).');
|
|
$this->comment('provider_address_site', 'provider_address_id', 'FK -> provider_address.id, ON DELETE CASCADE — adresse concernee.');
|
|
$this->comment('provider_address_site', 'site_id', 'FK -> site.id, ON DELETE RESTRICT — site rattache a l adresse.');
|
|
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider_address_contact (
|
|
provider_address_id INT NOT NULL,
|
|
provider_contact_id INT NOT NULL,
|
|
PRIMARY KEY (provider_address_id, provider_contact_id),
|
|
CONSTRAINT fk_provider_address_contact_address
|
|
FOREIGN KEY (provider_address_id) REFERENCES provider_address (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_provider_address_contact_contact
|
|
FOREIGN KEY (provider_contact_id) REFERENCES provider_contact (id) ON DELETE CASCADE
|
|
)
|
|
SQL);
|
|
$this->comment('provider_address_contact', '_table', 'Jointure M2M provider_address <-> provider_contact — contacts associes a une adresse.');
|
|
$this->comment('provider_address_contact', 'provider_address_id', 'FK -> provider_address.id, ON DELETE CASCADE — adresse concernee.');
|
|
$this->comment('provider_address_contact', 'provider_contact_id', 'FK -> provider_contact.id, ON DELETE CASCADE — contact associe a l adresse.');
|
|
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider_address_category (
|
|
provider_address_id INT NOT NULL,
|
|
category_id INT NOT NULL,
|
|
PRIMARY KEY (provider_address_id, category_id),
|
|
CONSTRAINT fk_provider_address_category_address
|
|
FOREIGN KEY (provider_address_id) REFERENCES provider_address (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_provider_address_category_category
|
|
FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE RESTRICT
|
|
)
|
|
SQL);
|
|
$this->comment('provider_address_category', '_table', 'Jointure M2M provider_address <-> category — categories d adresse de type PRESTATAIRE (RG-3.09).');
|
|
$this->comment('provider_address_category', 'provider_address_id', 'FK -> provider_address.id, ON DELETE CASCADE — adresse concernee.');
|
|
$this->comment('provider_address_category', 'category_id', 'FK -> category.id, ON DELETE RESTRICT — categorie d adresse de type PRESTATAIRE (RG-3.09).');
|
|
}
|
|
|
|
// =================================================================
|
|
// Sous-collection : RIB (1:n)
|
|
// =================================================================
|
|
|
|
private function createProviderRib(): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE provider_rib (
|
|
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
provider_id INT NOT NULL,
|
|
label VARCHAR(120) NOT NULL,
|
|
bic VARCHAR(20) NOT NULL,
|
|
iban VARCHAR(34) NOT NULL,
|
|
position INT DEFAULT 0 NOT NULL,
|
|
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
created_by INT DEFAULT NULL,
|
|
updated_by INT DEFAULT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT fk_provider_rib_provider
|
|
FOREIGN KEY (provider_id) REFERENCES provider (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_provider_rib_created_by
|
|
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_provider_rib_updated_by
|
|
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
|
|
)
|
|
SQL);
|
|
$this->addSql('CREATE INDEX idx_provider_rib_provider ON provider_rib (provider_id)');
|
|
|
|
$this->comment('provider_rib', '_table', 'Coordonnees bancaires d un prestataire (1:n) — >= 1 RIB attendu selon le type de reglement (RG-3.08). Tous les champs audites (pas d AuditIgnore).');
|
|
$this->comment('provider_rib', 'id', 'Identifiant interne auto-incremente.');
|
|
$this->comment('provider_rib', 'provider_id', 'FK -> provider.id, ON DELETE CASCADE — prestataire proprietaire du RIB.');
|
|
$this->comment('provider_rib', 'label', 'Libelle du RIB (ex: compte principal).');
|
|
$this->comment('provider_rib', 'bic', 'Code BIC/SWIFT de la banque (8 ou 11 caracteres).');
|
|
$this->comment('provider_rib', 'iban', 'IBAN du compte (≤ 34 caracteres).');
|
|
$this->comment('provider_rib', 'position', 'Ordre d affichage du RIB dans la liste du prestataire (croissant).');
|
|
$this->addTimestampableBlamableComments('provider_rib');
|
|
}
|
|
|
|
// =================================================================
|
|
// Helpers
|
|
// =================================================================
|
|
|
|
/**
|
|
* Pose les 4 commentaires standardises Timestampable/Blamable sur une table,
|
|
* en reutilisant le catalogue partage (source unique, cf. ERP-67). Seul le
|
|
* tableau statique des textes est reutilise — aucune dependance a l etat DB.
|
|
*/
|
|
private function addTimestampableBlamableComments(string $table): void
|
|
{
|
|
foreach (ColumnCommentsCatalog::timestampableBlamableComments() as $column => $description) {
|
|
$this->comment($table, $column, $description);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Emet un `COMMENT ON TABLE` (colonne speciale `_table`) ou
|
|
* `COMMENT ON COLUMN` en dollar-quoting Postgres ($_$...$_$) pour eviter
|
|
* tout echappement d apostrophe.
|
|
*/
|
|
private function comment(string $table, string $column, string $description): void
|
|
{
|
|
$quotedTable = '"'.str_replace('"', '""', $table).'"';
|
|
|
|
if ('_table' === $column) {
|
|
$this->addSql(sprintf('COMMENT ON TABLE %s IS $_$%s$_$', $quotedTable, $description));
|
|
|
|
return;
|
|
}
|
|
|
|
$this->addSql(sprintf(
|
|
'COMMENT ON COLUMN %s.%s IS $_$%s$_$',
|
|
$quotedTable,
|
|
'"'.str_replace('"', '""', $column).'"',
|
|
$description,
|
|
));
|
|
}
|
|
}
|