Files
Starseed/migrations/Version20260626110000.php
T
tristan ec648ff2ff feat(catalog) : M6 — seed prod-safe des catégories de type PRODUIT
Les Category de type PRODUIT (Céréales, Oléagineux, Aliments du bétail, Engrais)
ne vivaient que dans CategoryFixtures (dev/test) → table category vide en prod,
select « Catégorie » du formulaire produit vide. On aligne sur les autres
taxonomies (CLIENT/FOURNISSEUR/PRESTATAIRE/ADRESSE déjà seedées en migration) :
INSERT idempotent (NOT EXISTS) + jonction category_category_type, miroir du
pattern Version20260612080000. Re-seed dev/test conservé via les fixtures.
2026-06-26 16:12:44 +02:00

102 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* M6 Catalog — seed prod-safe des `Category` de type PRODUIT.
*
* Contexte : le `CategoryType` PRODUIT est seede en migration (Version20260625110000),
* mais ses `Category` (Cereales, Oleagineux, Aliments du betail, Engrais) ne vivaient
* que dans `CategoryFixtures` (dev/test) — table `category` VIDE en prod, donc le
* select « Categorie » du formulaire produit serait vide. On aligne sur les autres
* taxonomies (CLIENT / FOURNISSEUR / PRESTATAIRE / ADRESSE, deja seedees en migration)
* : seed idempotent ici (prod), re-seed dev/test par les fixtures apres purge.
*
* Aucune colonne creee/modifiee -> 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],
);
}
}