Module sites (#8)
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s

| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [x] Pas de régression
- [x] TU/TI/TF rédigée
- [x] TU/TI/TF OK
- [ ] CHANGELOG modifié

Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: MALIO-DEV/Coltura#8
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #8.
This commit is contained in:
2026-04-20 15:31:58 +00:00
committed by Autin
parent 6b4868b261
commit 6cf5ef4cfc
77 changed files with 7739 additions and 80 deletions

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Module Sites - Ticket 1/4 : brique fondatrice de donnees.
*
* Cree la table `site` qui porte les etablissements physiques de l'instance
* Coltura. La table est creee inconditionnellement : meme si SitesModule est
* desactive dans `config/modules.php`, la structure DB existe (pas de
* dependance dure depuis Core, mais pas de coin d'ombre schema non plus).
*
* Note sur l'emplacement du fichier :
* Par convention projet les migrations vivent dans
* `src/Module/{Module}/Infrastructure/Doctrine/Migrations/`, sauf pour les
* initialisations critiques. Cf. CLAUDE.md (section "Regles d'architecture")
* qui documente le bug de tri alphabetique de Doctrine Migrations 3.x avec
* plusieurs `migrations_paths` : tant que ce n'est pas corrige, toute
* migration d'initialisation (creation de table sur base vide) reste au
* namespace racine `DoctrineMigrations` dans `migrations/`.
*/
final class Version20260417120000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Module Sites : creation de la table site (nom, ville, cp, couleur, adresse complete, timestamps).';
}
public function up(Schema $schema): void
{
// Creation de la table site. Toutes les colonnes sont NOT NULL :
// - le champ `color` est contraint cote applicatif au format #RRGGBB
// (7 caracteres), la longueur DB est dimensionnee en consequence ;
// - `postal_code` est limite a 10 caracteres pour laisser marge a
// d'eventuels formats etrangers plus tard, tout en le validant
// strictement en 5 chiffres cote applicatif (format FR).
//
// Note : `full_address` est restructure au ticket 2 (migration
// Version20260420130000) en `street` + `complement` (nullable). La
// structure d'origine est conservee ici pour ne pas casser les devs
// qui ont deja joue cette migration en local.
$this->addSql(<<<'SQL'
CREATE TABLE site (
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
name VARCHAR(100) NOT NULL,
city VARCHAR(100) NOT NULL,
postal_code VARCHAR(10) NOT NULL,
color VARCHAR(7) NOT NULL,
full_address TEXT NOT NULL,
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
PRIMARY KEY (id)
)
SQL);
// Index unique sur le nom : garantit l'invariant metier "un site porte
// un nom unique" et permet a la contrainte UniqueEntity cote Symfony
// de s'appuyer sur une erreur DB en cas de race condition.
$this->addSql('CREATE UNIQUE INDEX uniq_site_name ON site (name)');
}
public function down(Schema $schema): void
{
// Drop direct : aucune FK depuis/vers la table dans ce ticket.
$this->addSql('DROP TABLE site');
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Module Sites - Ticket 2/4 : rattachement User ↔ Site.
*
* Introduit deux nouvelles structures sur le schema existant :
* - la table de jointure `user_site` (M2M) : liste des sites autorises
* pour chaque utilisateur.
* - la colonne `"user".current_site_id` (M2O nullable) : site actuellement
* selectionne par l'utilisateur pour son contexte UX.
*
* Cascades choisies :
* - `user_site.user_id` → `ON DELETE CASCADE` : supprimer un user purge
* naturellement ses rattachements.
* - `user_site.site_id` → `ON DELETE CASCADE` : supprimer un site purge
* tous les rattachements a ce site.
* - `"user".current_site_id` → `ON DELETE SET NULL` : supprimer un site
* repasse le currentSite des users concernes a NULL (plutot que de
* detruire les users, ce qui serait catastrophique).
*
* Note sur l'emplacement du fichier (namespace racine `DoctrineMigrations`)
* Conforme a l'exception documentee dans `CLAUDE.md` : tant que le bug de
* tri alphabetique des MigrationsComparator Doctrine 3.x n'est pas resolu,
* toute migration touchant a la topologie des tables (creation, FKs
* cross-module) vit au namespace racine. La migration croise ici les tables
* `"user"` (module Core) et `site` (module Sites) — placement racine donc
* justifie pour garantir l'ordre d'execution deterministe vis-a-vis des
* deux migrations d'init deja presentes.
*/
final class Version20260417150000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Module Sites : table user_site (M2M) + colonne user.current_site_id (M2O SET NULL).';
}
public function up(Schema $schema): void
{
// 1) Creation de la table de jointure user_site.
$this->addSql(<<<'SQL'
CREATE TABLE user_site (
user_id INT NOT NULL,
site_id INT NOT NULL,
PRIMARY KEY (user_id, site_id)
)
SQL);
$this->addSql('CREATE INDEX IDX_user_site_user ON user_site (user_id)');
$this->addSql('CREATE INDEX IDX_user_site_site ON user_site (site_id)');
$this->addSql(<<<'SQL'
ALTER TABLE user_site
ADD CONSTRAINT FK_user_site_user
FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_site
ADD CONSTRAINT FK_user_site_site
FOREIGN KEY (site_id) REFERENCES site (id) ON DELETE CASCADE
SQL);
// 2) Ajout de la colonne nullable user.current_site_id + FK SET NULL.
$this->addSql('ALTER TABLE "user" ADD current_site_id INT DEFAULT NULL');
$this->addSql('CREATE INDEX IDX_user_current_site ON "user" (current_site_id)');
$this->addSql(<<<'SQL'
ALTER TABLE "user"
ADD CONSTRAINT FK_user_current_site
FOREIGN KEY (current_site_id) REFERENCES site (id) ON DELETE SET NULL
SQL);
}
public function down(Schema $schema): void
{
// Rollback en ordre inverse : enfants avant parents.
$this->addSql('ALTER TABLE "user" DROP CONSTRAINT FK_user_current_site');
$this->addSql('DROP INDEX IDX_user_current_site');
$this->addSql('ALTER TABLE "user" DROP current_site_id');
$this->addSql('ALTER TABLE user_site DROP CONSTRAINT FK_user_site_site');
$this->addSql('ALTER TABLE user_site DROP CONSTRAINT FK_user_site_user');
$this->addSql('DROP TABLE user_site');
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Module Sites - Ticket 2/4 : restructuration de l'adresse.
*
* Splitte la colonne `site.full_address` (TEXT NOT NULL, multi-lignes) en
* deux champs structures :
* - `street` (VARCHAR(255) NOT NULL) : numero + voie ;
* - `complement` (VARCHAR(255) DEFAULT NULL) : batiment, escalier, BP...
*
* L'adresse complete affichable est desormais reconstituee cote applicatif
* par Site::getFullAddress() (concatenation multi-lignes street\n[complement\n]CP ville)
* et exposee en lecture API via le groupe `site:read` + `me:read`. Plus de
* colonne DB redondante.
*
* Strategie de backfill (entre creation des nouvelles colonnes et drop de
* l'ancienne) :
* - `street` recoit la totalite de l'ancien `full_address` pour ne perdre
* aucune donnee. C'est imparfait pour les adresses multi-lignes mais
* safe : aucun risque de tronquage si l'ancienne adresse depasse 255
* chars (PostgreSQL leve une erreur explicite ; charge a l'admin de
* nettoyer manuellement si necessaire).
* - `complement` reste null : pas d'heuristique fiable pour decouper une
* adresse libre en street/complement.
*
* Cette migration evite un `make db-reset` force pour les developpeurs
* ayant deja joue Version20260417120000 dans son etat initial (table site
* avec full_address). Les fixtures sont mises a jour en parallele.
*/
final class Version20260420130000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Module Sites : split full_address en street + complement (getter computed cote applicatif).';
}
public function up(Schema $schema): void
{
// 1) Ajout des nouvelles colonnes en mode permissif :
// - `street` nullable temporairement pour permettre le backfill.
// - `complement` definitivement nullable.
$this->addSql('ALTER TABLE site ADD street VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE site ADD complement VARCHAR(255) DEFAULT NULL');
// 2) Backfill : recopier full_address dans street pour ne pas perdre
// les donnees existantes. Les retours a la ligne sont preserves
// (PostgreSQL VARCHAR accepte \n) ; un admin pourra reformater
// apres coup si besoin. Cas d'adresse > 255 chars : la migration
// echoue cleanly (pas de tronquage silencieux).
$this->addSql('UPDATE site SET street = full_address');
// 3) Bascule street en NOT NULL une fois le backfill applique.
$this->addSql('ALTER TABLE site ALTER COLUMN street SET NOT NULL');
// 4) Drop de l'ancienne colonne full_address.
$this->addSql('ALTER TABLE site DROP full_address');
}
public function down(Schema $schema): void
{
// Recreation de full_address (NOT NULL via DEFAULT '' pour eviter
// un crash si la table a deja des lignes), puis backfill inverse,
// puis drop des nouvelles colonnes.
$this->addSql("ALTER TABLE site ADD full_address TEXT NOT NULL DEFAULT ''");
$this->addSql("UPDATE site SET full_address = street || COALESCE(E'\\n' || complement, '')");
$this->addSql('ALTER TABLE site ALTER COLUMN full_address DROP DEFAULT');
$this->addSql('ALTER TABLE site DROP street');
$this->addSql('ALTER TABLE site DROP complement');
}
}