6ceef62056
Cree le nouveau module Technique (pole distinct du Commercial) prerequis du M3 repertoire prestataires : - TechniqueModule (ID=technique, REQUIRED=false) + 5 permissions RBAC technique.providers.* (view / manage / accounting.view / accounting.manage / archive), declarees pour app:sync-permissions. - Activation dans config/modules.php + layer front frontend/modules/technique/. - Seed taxonomie : nouveau CategoryType PRESTATAIRE + 3 categories (Maintenance industrielle, Nettoyage, Transport) via migration idempotente (ON CONFLICT / NOT EXISTS, jonction M2M category_category_type) ET fixtures CategoryType/Category (survivent au purger db-reset). - Tests : structure du module (5 permissions figees) + filtre GET /api/categories?typeCode=PRESTATAIRE. Inclut la spec back/front M3 et le RETEX M1.
122 lines
5.6 KiB
PHP
122 lines
5.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use Doctrine\DBAL\ArrayParameterType;
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* M3 (ticket 1.1) — Taxonomie PRESTATAIRE (module Catalog, prerequis du module Technique).
|
|
*
|
|
* Contexte : le M3 (repertoire prestataires) a besoin d'une taxonomie distincte
|
|
* des types CLIENT (M1) et FOURNISSEUR (M2). Decision Matthieu (11/06) : types
|
|
* distincts CLIENT / FOURNISSEUR / PRESTATAIRE, chacun avec sa taxonomie. Le
|
|
* multi-select « Categorie » du prestataire (formulaire principal + adresse)
|
|
* ne reference que des `Category` rattachees au type PRESTATAIRE (RG-3.09).
|
|
*
|
|
* Cette migration :
|
|
* 1. cree le `category_type` PRESTATAIRE (code PRESTATAIRE, label « Prestataire ») ;
|
|
* 2. seede 3 `Category` de demonstration rattachees a ce type via la jonction
|
|
* ManyToMany `category_category_type` (modele courant depuis Version20260608120000 ;
|
|
* la colonne ManyToOne `category.category_type_id` n'existe plus).
|
|
*
|
|
* 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) et NON modulaire :
|
|
* avec plusieurs migrations_paths, Doctrine Migrations 3.x trie par FQCN
|
|
* alphabetique -> une migration `App\Module\...` passerait avant les
|
|
* `DoctrineMigrations\...` sur base vide, donc avant la creation des tables
|
|
* `category` / `category_type` / `category_category_type`. Le namespace racine
|
|
* garantit l'ordre par timestamp.
|
|
*
|
|
* Idempotence : `INSERT ... ON CONFLICT (code) DO NOTHING` pour le type,
|
|
* `INSERT ... SELECT ... WHERE NOT EXISTS` pour chaque categorie et chaque ligne
|
|
* de jonction (aligne sur le pattern ERP-84 / Version20260605120000). En prod la
|
|
* table `category` est vide (aucune fixture metier). En dev/test, le purger
|
|
* Doctrine vide `category` / `category_type` avant les fixtures qui reproduisent
|
|
* le meme etat final (CategoryTypeFixtures / CategoryFixtures etendus a PRESTATAIRE).
|
|
*/
|
|
final class Version20260612080000 extends AbstractMigration
|
|
{
|
|
/**
|
|
* Categories de demonstration du type PRESTATAIRE : nom => code stable. Le
|
|
* code est la cle metier (slug MAJUSCULE du nom, miroir du
|
|
* CategoryCodeGenerator) et reste unique parmi les actifs (uq_category_code,
|
|
* partage avec les codes CLIENT / FOURNISSEUR — aucune collision ici). Le nom
|
|
* est unique GLOBALEMENT parmi les actifs (uq_category_name_active) : les
|
|
* libelles ci-dessous n'entrent en collision avec aucune categorie seedee.
|
|
*/
|
|
private const array PROVIDER_CATEGORIES = [
|
|
'Maintenance industrielle' => 'MAINTENANCE_INDUSTRIELLE',
|
|
'Nettoyage' => 'NETTOYAGE',
|
|
'Transport' => 'TRANSPORT',
|
|
];
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return 'M3 1.1 : cree le CategoryType PRESTATAIRE + seed des categories prestataires (Maintenance industrielle, Nettoyage, Transport).';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
// 1. Type PRESTATAIRE (idempotent via l'index unique uq_category_type_code).
|
|
$this->addSql(<<<'SQL'
|
|
INSERT INTO category_type (code, label) VALUES ('PRESTATAIRE', 'Prestataire')
|
|
ON CONFLICT (code) DO NOTHING
|
|
SQL);
|
|
|
|
foreach (self::PROVIDER_CATEGORIES as $name => $code) {
|
|
// 2a. Categorie sous PRESTATAIRE (si le code est libre parmi les
|
|
// actifs). created_at/updated_at NOT NULL -> NOW() ; le blame
|
|
// reste null (seed hors contexte HTTP, libelle « Systeme » cote front).
|
|
$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]);
|
|
|
|
// 2b. Jonction M2M categorie <-> type PRESTATAIRE (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 = 'PRESTATAIRE'
|
|
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 : on retire d'abord les categories seedees (par code) — la FK
|
|
// category_category_type est ON DELETE CASCADE cote category, donc les
|
|
// lignes de jonction partent avec —, puis le type s'il n'est plus reference.
|
|
$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 = 'PRESTATAIRE')",
|
|
['codes' => array_values(self::PROVIDER_CATEGORIES)],
|
|
['codes' => ArrayParameterType::STRING],
|
|
);
|
|
|
|
$this->addSql(<<<'SQL'
|
|
DELETE FROM category_type
|
|
WHERE code = 'PRESTATAIRE'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM category_category_type cct WHERE cct.category_type_id = category_type.id
|
|
)
|
|
SQL);
|
|
}
|
|
}
|