Exposition de Site via API Platform (5 operations RBAC sites.view/sites.manage), relation User.sites (M2M user_site EAGER) + User.currentSite (M2O nullable, ON DELETE SET NULL). Endpoint PATCH /api/me/current-site via ressource virtuelle + processor (SiteNotAuthorizedException → 403). UserRbacProcessor etendu avec gardes post-persist : auto-reset si currentSite retire, auto-select premier site si null + sites non vide. Page /admin/sites (DataTable + drawer creation/edition + modale suppression). UserRbacDrawer etendu avec section "Sites autorises". Colonne "Sites" ajoutee dans la table /admin/users (liste des noms separes par virgule). Sidebar entree Sites (module: sites, permission: sites.view). Refactor adresse : split full_address en street + complement (nullable) + getter computed Site::getFullAddress() multi-lignes. Migration ALTER dediee pour compat devs ayant deja joue le ticket 1. Fixtures avec vraies adresses (Chatellerault/Fontenet/Pommevic). Doctrine : inversedBy synchrone User.sites <-> Site.users pour maintenir la collection inverse en memoire. User::switchCurrentSite() porte la garde domaine (throw SiteNotAuthorizedException), aligne sur Role::ensureDeletable. Helper skipIfSitesModuleDisabled centralise dans AbstractApiTestCase. Tests : 182/182 (182/182 aussi module desactive, 2 skipped). 29 nouveaux tests PHPUnit (CRUD API, switch currentSite, cascade DB, /api/me enrichi, extension /rbac, gardes structurelles fullAddress/currentSite ignores, anti-cycle Site.users). 11 tests Vitest sur la validation hex couleur. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
3.1 KiB
PHP
73 lines
3.1 KiB
PHP
<?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');
|
|
}
|
|
}
|