pas de `COMMENT ON COLUMN` (regle ABSOLUE n°12) : * la migration ne fait que des INSERT de donnees de reference. * * Namespace racine `DoctrineMigrations` (regle ABSOLUE n°11) : depend de * `category` / `category_type` / `category_category_type` (creees au namespace racine) * et du type PRODUIT (Version20260625110000) ; le tri par timestamp garantit l'ordre. * * Idempotence : `INSERT ... SELECT ... WHERE NOT EXISTS` pour chaque categorie et * chaque ligne de jonction (miroir Version20260612080000 / ERP-84). Codes = slug * MAJUSCULE deterministe (meme sortie que CategoryCodeGenerator), provisoires — a * affiner avec le metier (ERP-201). */ final class Version20260626110000 extends AbstractMigration { /** * Categories produit (provisoires, Figma/metier) : nom => code stable. Le code * reste unique parmi les actifs (uq_category_code) et le nom unique globalement * (uq_category_name_active) — aucune collision avec les taxonomies existantes. */ private const array PRODUCT_CATEGORIES = [ 'Céréales' => 'CEREALES', 'Oléagineux' => 'OLEAGINEUX', 'Aliments du bétail' => 'ALIMENTS_DU_BETAIL', 'Engrais' => 'ENGRAIS', ]; public function getDescription(): string { return 'M6 Catalog : seed prod-safe des categories de type PRODUIT (Cereales, Oleagineux, Aliments du betail, Engrais).'; } public function up(Schema $schema): void { // Le type PRODUIT existe deja (Version20260625110000) ; re-assert defensif // et idempotent pour rendre cette migration auto-portante. $this->addSql(<<<'SQL' INSERT INTO category_type (code, label) VALUES ('PRODUIT', 'Produit') ON CONFLICT (code) DO NOTHING SQL); foreach (self::PRODUCT_CATEGORIES as $name => $code) { // 1. Categorie (si le code est libre parmi les actifs). created_at/updated_at // NOT NULL -> NOW() ; le blame reste null (seed hors contexte HTTP). $this->addSql(<<<'SQL' INSERT INTO category (name, code, created_at, updated_at) SELECT :name, :code, NOW(), NOW() WHERE NOT EXISTS ( SELECT 1 FROM category c WHERE c.code = :code AND c.deleted_at IS NULL ) SQL, ['name' => $name, 'code' => $code]); // 2. Jonction M2M categorie <-> type PRODUIT (modele courant). $this->addSql(<<<'SQL' INSERT INTO category_category_type (category_id, category_type_id) SELECT c.id, ct.id FROM category c CROSS JOIN category_type ct WHERE c.code = :code AND c.deleted_at IS NULL AND ct.code = 'PRODUIT' AND NOT EXISTS ( SELECT 1 FROM category_category_type cct WHERE cct.category_id = c.id AND cct.category_type_id = ct.id ) SQL, ['code' => $code]); } } public function down(Schema $schema): void { // Best-effort : retire les categories seedees (par code) rattachees au type // PRODUIT — la jonction part en CASCADE cote category. Echoue si un produit // reference encore l'une d'elles (FK RESTRICT product.category_id), attendu. $this->addSql( 'DELETE FROM category WHERE code IN (:codes) ' .'AND id IN (SELECT category_id FROM category_category_type cct ' ."JOIN category_type ct ON ct.id = cct.category_type_id WHERE ct.code = 'PRODUIT')", ['codes' => array_values(self::PRODUCT_CATEGORIES)], ['codes' => ArrayParameterType::STRING], ); } }