Migration modular monolith DDD (0.1 → 3.3) (#17)
Auto Tag Develop / tag (push) Successful in 9s

## Migration modular monolith DDD — Lesstime (0.1 → 3.3)

Cette MR regroupe l'intégralité de la refonte en monolithe modulaire (strangler progressif, additif). Elle remplace les MR stackées de Phase 1 (#12–#16), désormais incluses ici.

**Ne pas merger avant validation fonctionnelle** : branche destinée à être testée telle quelle.

### Périmètre — 9 modules sous `src/Module/`
| Phase | Module | Contenu |
|------|--------|---------|
| 0.1 | (socle) | infrastructure modulaire, `ModuleInterface`, mapping Doctrine par module |
| 0.2 | (socle front) | auto-détection des layers Nuxt sous `frontend/modules/*` |
| 1.1 | **Core** | Identité (User/Auth), Notifications, Notifier |
| 1.2 | Core | RBAC fin (permissions `module.resource.action`, sidebar gated) |
| 1.3 | Core | Audit log (`#[Auditable]`, listener, provider DBAL) |
| 2.1 | **TimeTracking** | TimeEntry + MCP + export |
| 2.2 | **ProjectManagement** | cœur métier Projets/Tâches + 38 MCP tools |
| 2.3 | **Absence** | demandes, soldes, policies, justificatifs |
| 2.4 | **Directory** | Clients (migrés) + **Prospects** (nouveau, conversion → Client) |
| 2.5 | **Mail** | intégration IMAP OVH + liens tâches |
| 2.6 | **Integration** | Gitea / BookStack / Zimbra / Share |
| 3.1 | **Reporting** | rapports transverses (DBAL read-only, 0 import inter-module) |
| 3.2 | **ClientPortal** | portail client (ROLE_CLIENT cloisonné, tickets, notifications) |
| 3.3 | (finition) | nettoyage legacy — `src/Entity` vide, app 100% modulaire |

### Architecture
- Découplage inter-modules par **contrats** (`UserInterface`, `ProjectInterface`, `TaskInterface`, `TaskTagInterface`, `ClientInterface`, `ClientTicketInterface`, `LeaveProfileInterface`) + `resolve_target_entities` 100% modulaire (aucune cible legacy).
- Repositories : interface `Domain/Repository` + implémentation `Infrastructure/Doctrine`, bindées.
- Reporting en DBAL read-only pur (aucun import d'entité d'un autre module).
- Chaque migration de module : déplacement à comportement préservé (API publique et noms d'outils MCP inchangés), migrations **additives** uniquement (zéro destructif).

### Sécurité
- ROLE_CLIENT cloisonné : un utilisateur client n'accède qu'à `/portal` et à ses propres tickets (filtrés par `allowedProjects`), interdit sur toute l'API interne.
- Correctif : interdiction pour un client de créer un lien vers le partage SMB (upload uniquement).

### QA non-régression (branche reconstruite from scratch)
- Migrations from scratch + fixtures : OK.
- Compilation dev + prod : OK.
- **180 tests PHPUnit verts**, php-cs-fixer clean, ~96 routes, **66 outils MCP** tous sous `App\Module\*`.
- Smoke test runtime multi-rôles (admin / ROLE_USER / ROLE_CLIENT) : 44 vérifications HTTP, **0 écart**, cloisonnement client étanche.
- Build Nuxt OK, 9 layers, 0 import legacy résiduel.

### Points à arbitrer (hors périmètre de cette migration)
- Durcissement MCP/IDOR pré-existant (`userId` explicite sans scoping sur certains tools TimeTracking/Absence/TaskDocument) — ticket dédié recommandé.
- Validation fonctionnelle de **Prospect** et **ClientPortal** (conçus depuis les specs disque).
- **Harmonisation visuelle Malio finale** (3.3) — finition esthétique inter-modules laissée au PO.

---

## ⚠️ Déploiement / migration des données — à ne pas oublier

### 1. Resynchroniser les séquences PostgreSQL après tout import/restore de dump
Si la prod (ou tout environnement) est **montée depuis un dump** (`pg_restore` / `COPY`), les lignes sont chargées avec leurs `id` explicites **sans avancer les séquences** → au premier `INSERT` : `duplicate key value violates unique constraint "..._pkey"` (constaté en local sur `notification`, `task`, `time_entry`…).

À lancer **juste après chaque restore/import** :

```sql
DO $$
DECLARE r RECORD; maxid BIGINT; seq TEXT;
BEGIN
  FOR r IN SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='public'
  LOOP
    seq := pg_get_serial_sequence(quote_ident(r.table_name), r.column_name);
    IF seq IS NOT NULL THEN
      EXECUTE format('SELECT COALESCE(MAX(%I),0) FROM %I', r.column_name, r.table_name) INTO maxid;
      PERFORM setval(seq, GREATEST(maxid,1), maxid > 0);
    END IF;
  END LOOP;
END $$;
```

> Ne concerne **pas** une prod qui tourne déjà (séquences avancées organiquement) — uniquement le cas restore/import. Idempotent, sans risque.

### 2. Fix dénormalisation des collections typées-contrat (code, inclus dans la branche)
Les relations **to-many** typées par une interface `Shared\Domain\Contract\*` (`TimeEntry::tags` → `TaskTagInterface`, `Task::collaborators` → `UserInterface`) étaient **indénormalisables par API Platform** (mono-valué OK via IRI, collection KO) → **tout POST/PATCH portant une telle collection renvoyait 400/500**. Corrigé par un dénormaliseur générique `ContractRelationDenormalizer` (réutilise `resolve_target_entities`, zéro couplage par-entité) + test fonctionnel de non-régression.

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #17
This commit was merged in pull request #17.
This commit is contained in:
2026-06-23 13:50:42 +00:00
parent d0a49322e1
commit 8313c759c6
622 changed files with 24802 additions and 2864 deletions
+74
View File
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* RBAC fin (LST-57) : permission, role and their many-to-many relations with user.
* Additive only — no DROP/ALTER on existing tables.
*/
final class Version20260619145109 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add RBAC permission and role entities with user relations (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE permission (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, code VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL, module VARCHAR(100) NOT NULL, orphan BOOLEAN NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_E04992AA77153098 ON permission (code)');
$this->addSql('CREATE INDEX idx_permission_module ON permission (module)');
$this->addSql('CREATE INDEX idx_permission_orphan ON permission (orphan)');
$this->addSql('COMMENT ON COLUMN permission.code IS \'Permission code (module.resource[.sub].action)\'');
$this->addSql('COMMENT ON COLUMN permission.label IS \'Human-readable permission label\'');
$this->addSql('COMMENT ON COLUMN permission.module IS \'Owning module id (e.g. core)\'');
$this->addSql('COMMENT ON COLUMN permission.orphan IS \'True when the permission is no longer declared by any active module\'');
$this->addSql('CREATE TABLE "role" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, code VARCHAR(100) NOT NULL, label VARCHAR(255) NOT NULL, description TEXT DEFAULT NULL, is_system BOOLEAN NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_57698A6A77153098 ON "role" (code)');
$this->addSql('CREATE INDEX idx_role_is_system ON "role" (is_system)');
$this->addSql('COMMENT ON COLUMN "role".code IS \'Immutable role code (snake_case)\'');
$this->addSql('COMMENT ON COLUMN "role".label IS \'Human-readable role label\'');
$this->addSql('COMMENT ON COLUMN "role".description IS \'Optional role description\'');
$this->addSql('COMMENT ON COLUMN "role".is_system IS \'True for built-in roles that cannot be deleted\'');
$this->addSql('CREATE TABLE role_permission (role_id INT NOT NULL, permission_id INT NOT NULL, PRIMARY KEY (role_id, permission_id))');
$this->addSql('CREATE INDEX IDX_6F7DF886D60322AC ON role_permission (role_id)');
$this->addSql('CREATE INDEX IDX_6F7DF886FED90CCA ON role_permission (permission_id)');
$this->addSql('CREATE TABLE user_role (user_id INT NOT NULL, role_id INT NOT NULL, PRIMARY KEY (user_id, role_id))');
$this->addSql('CREATE INDEX IDX_2DE8C6A3A76ED395 ON user_role (user_id)');
$this->addSql('CREATE INDEX IDX_2DE8C6A3D60322AC ON user_role (role_id)');
$this->addSql('CREATE TABLE user_permission (user_id INT NOT NULL, permission_id INT NOT NULL, PRIMARY KEY (user_id, permission_id))');
$this->addSql('CREATE INDEX IDX_472E5446A76ED395 ON user_permission (user_id)');
$this->addSql('CREATE INDEX IDX_472E5446FED90CCA ON user_permission (permission_id)');
$this->addSql('ALTER TABLE role_permission ADD CONSTRAINT FK_6F7DF886D60322AC FOREIGN KEY (role_id) REFERENCES "role" (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE role_permission ADD CONSTRAINT FK_6F7DF886FED90CCA FOREIGN KEY (permission_id) REFERENCES permission (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE user_role ADD CONSTRAINT FK_2DE8C6A3A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE user_role ADD CONSTRAINT FK_2DE8C6A3D60322AC FOREIGN KEY (role_id) REFERENCES "role" (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE user_permission ADD CONSTRAINT FK_472E5446A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE user_permission ADD CONSTRAINT FK_472E5446FED90CCA FOREIGN KEY (permission_id) REFERENCES permission (id) ON DELETE CASCADE');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE role_permission DROP CONSTRAINT FK_6F7DF886D60322AC');
$this->addSql('ALTER TABLE role_permission DROP CONSTRAINT FK_6F7DF886FED90CCA');
$this->addSql('ALTER TABLE user_role DROP CONSTRAINT FK_2DE8C6A3A76ED395');
$this->addSql('ALTER TABLE user_role DROP CONSTRAINT FK_2DE8C6A3D60322AC');
$this->addSql('ALTER TABLE user_permission DROP CONSTRAINT FK_472E5446A76ED395');
$this->addSql('ALTER TABLE user_permission DROP CONSTRAINT FK_472E5446FED90CCA');
$this->addSql('DROP TABLE role_permission');
$this->addSql('DROP TABLE user_role');
$this->addSql('DROP TABLE user_permission');
$this->addSql('DROP TABLE permission');
$this->addSql('DROP TABLE "role"');
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Audit log (LST-61) : append-only `audit_log` table.
*
* Not managed by Doctrine ORM (no entity). Written via raw DBAL by the
* AuditLogWriter on a dedicated `audit` connection to avoid re-entrant
* flushes from the Doctrine listener. Columns are lowercase snake_case.
* Additive only — no DROP/ALTER on existing tables.
*/
final class Version20260619185448 extends AbstractMigration
{
public function getDescription(): string
{
return 'Audit log: create append-only audit_log table + indexes (additive)';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE audit_log (
id uuid NOT NULL,
entity_type VARCHAR(100) NOT NULL,
entity_id VARCHAR(64) NOT NULL,
action VARCHAR(10) NOT NULL,
changes JSONB NOT NULL DEFAULT '{}'::jsonb,
performed_by VARCHAR(100) NOT NULL,
performed_at TIMESTAMP(6) WITH TIME ZONE NOT NULL,
ip_address VARCHAR(45) DEFAULT NULL,
request_id VARCHAR(36) DEFAULT NULL,
PRIMARY KEY(id)
)
SQL);
$this->addSql('CREATE INDEX idx_audit_entity_time ON audit_log (entity_type, entity_id, performed_at)');
$this->addSql('CREATE INDEX idx_audit_performer ON audit_log (performed_by, performed_at)');
$this->addSql('CREATE INDEX idx_audit_time ON audit_log (performed_at)');
$this->addSql("COMMENT ON COLUMN audit_log.entity_type IS 'Audited entity type, format module.Entity (e.g. core.User)'");
$this->addSql("COMMENT ON COLUMN audit_log.entity_id IS 'Audited entity identifier (int or composite key serialized)'");
$this->addSql("COMMENT ON COLUMN audit_log.action IS 'create|update|delete'");
$this->addSql("COMMENT ON COLUMN audit_log.changes IS 'JSON diff: {field:{old,new}} for update, full snapshot for create/delete'");
$this->addSql("COMMENT ON COLUMN audit_log.performed_by IS 'User identifier or system'");
$this->addSql("COMMENT ON COLUMN audit_log.request_id IS 'UUID shared by all audit rows of a single HTTP request (null in CLI)'");
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE audit_log');
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* TimeTracking module (2.1): add Timestampable/Blamable columns to time_entry.
*
* TimeEntry is the first adopter of TimestampableBlamableTrait. This migration
* is purely additive — nullable columns + nullable FK to "user" with
* ON DELETE SET NULL. No DROP/ALTER on existing data. Columns are lowercase
* snake_case. Hand-written to guarantee zero destructive instruction.
*/
final class Version20260620161036 extends AbstractMigration
{
public function getDescription(): string
{
return 'TimeTracking: add timestampable/blamable columns to time_entry (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE time_entry ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE time_entry ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE time_entry ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE time_entry ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE time_entry ADD CONSTRAINT FK_6E537C0CDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE time_entry ADD CONSTRAINT FK_6E537C0C16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_6E537C0CDE12AB56 ON time_entry (created_by)');
$this->addSql('CREATE INDEX IDX_6E537C0C16FE72E1 ON time_entry (updated_by)');
$this->addSql("COMMENT ON COLUMN time_entry.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN time_entry.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN time_entry.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN time_entry.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE time_entry DROP CONSTRAINT FK_6E537C0CDE12AB56');
$this->addSql('ALTER TABLE time_entry DROP CONSTRAINT FK_6E537C0C16FE72E1');
$this->addSql('DROP INDEX IDX_6E537C0CDE12AB56');
$this->addSql('DROP INDEX IDX_6E537C0C16FE72E1');
$this->addSql('ALTER TABLE time_entry DROP created_at');
$this->addSql('ALTER TABLE time_entry DROP updated_at');
$this->addSql('ALTER TABLE time_entry DROP created_by');
$this->addSql('ALTER TABLE time_entry DROP updated_by');
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* ProjectManagement module (LST-65 slice 3): add Timestampable/Blamable columns
* to task and project.
*
* Task and Project adopt TimestampableBlamableTrait (after TimeEntry). This
* migration is purely additive — nullable columns + nullable FK to "user" with
* ON DELETE SET NULL. No DROP/ALTER on existing data. Columns are lowercase
* snake_case. Hand-written to guarantee zero destructive instruction.
*/
final class Version20260620161500 extends AbstractMigration
{
public function getDescription(): string
{
return 'ProjectManagement: add timestampable/blamable columns to task and project (additive)';
}
public function up(Schema $schema): void
{
// task
$this->addSql('ALTER TABLE task ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE task ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE task ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE task ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB2516FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_527EDB25DE12AB56 ON task (created_by)');
$this->addSql('CREATE INDEX IDX_527EDB2516FE72E1 ON task (updated_by)');
$this->addSql("COMMENT ON COLUMN task.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN task.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN task.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN task.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
// project
$this->addSql('ALTER TABLE project ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE project ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE project ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE project ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE project ADD CONSTRAINT FK_2FB3D0EEDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE project ADD CONSTRAINT FK_2FB3D0EE16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_2FB3D0EEDE12AB56 ON project (created_by)');
$this->addSql('CREATE INDEX IDX_2FB3D0EE16FE72E1 ON project (updated_by)');
$this->addSql("COMMENT ON COLUMN project.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN project.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN project.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN project.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
// task
$this->addSql('ALTER TABLE task DROP CONSTRAINT FK_527EDB25DE12AB56');
$this->addSql('ALTER TABLE task DROP CONSTRAINT FK_527EDB2516FE72E1');
$this->addSql('DROP INDEX IDX_527EDB25DE12AB56');
$this->addSql('DROP INDEX IDX_527EDB2516FE72E1');
$this->addSql('ALTER TABLE task DROP created_at');
$this->addSql('ALTER TABLE task DROP updated_at');
$this->addSql('ALTER TABLE task DROP created_by');
$this->addSql('ALTER TABLE task DROP updated_by');
// project
$this->addSql('ALTER TABLE project DROP CONSTRAINT FK_2FB3D0EEDE12AB56');
$this->addSql('ALTER TABLE project DROP CONSTRAINT FK_2FB3D0EE16FE72E1');
$this->addSql('DROP INDEX IDX_2FB3D0EEDE12AB56');
$this->addSql('DROP INDEX IDX_2FB3D0EE16FE72E1');
$this->addSql('ALTER TABLE project DROP created_at');
$this->addSql('ALTER TABLE project DROP updated_at');
$this->addSql('ALTER TABLE project DROP created_by');
$this->addSql('ALTER TABLE project DROP updated_by');
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Absence module: add Timestampable/Blamable columns to absence_balance and
* absence_policy.
*
* AbsenceBalance and AbsencePolicy adopt TimestampableBlamableTrait.
* AbsenceRequest is intentionally untouched (it already carries createdAt /
* reviewedAt). This migration is purely additive — nullable columns + nullable
* FK to "user" with ON DELETE SET NULL. No DROP/ALTER on existing data. Columns
* are lowercase snake_case. Hand-written to guarantee zero destructive
* instruction.
*/
final class Version20260620170000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Absence: add timestampable/blamable columns to absence_balance and absence_policy (additive)';
}
public function up(Schema $schema): void
{
// absence_balance
$this->addSql('ALTER TABLE absence_balance ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE absence_balance ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE absence_balance ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE absence_balance ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE absence_balance ADD CONSTRAINT FK_65723A76DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE absence_balance ADD CONSTRAINT FK_65723A7616FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_65723A76DE12AB56 ON absence_balance (created_by)');
$this->addSql('CREATE INDEX IDX_65723A7616FE72E1 ON absence_balance (updated_by)');
$this->addSql("COMMENT ON COLUMN absence_balance.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN absence_balance.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN absence_balance.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN absence_balance.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
// absence_policy
$this->addSql('ALTER TABLE absence_policy ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE absence_policy ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE absence_policy ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE absence_policy ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE absence_policy ADD CONSTRAINT FK_7A780B65DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE absence_policy ADD CONSTRAINT FK_7A780B6516FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_7A780B65DE12AB56 ON absence_policy (created_by)');
$this->addSql('CREATE INDEX IDX_7A780B6516FE72E1 ON absence_policy (updated_by)');
$this->addSql("COMMENT ON COLUMN absence_policy.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN absence_policy.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN absence_policy.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN absence_policy.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
// absence_balance
$this->addSql('ALTER TABLE absence_balance DROP CONSTRAINT FK_65723A76DE12AB56');
$this->addSql('ALTER TABLE absence_balance DROP CONSTRAINT FK_65723A7616FE72E1');
$this->addSql('DROP INDEX IDX_65723A76DE12AB56');
$this->addSql('DROP INDEX IDX_65723A7616FE72E1');
$this->addSql('ALTER TABLE absence_balance DROP created_at');
$this->addSql('ALTER TABLE absence_balance DROP updated_at');
$this->addSql('ALTER TABLE absence_balance DROP created_by');
$this->addSql('ALTER TABLE absence_balance DROP updated_by');
// absence_policy
$this->addSql('ALTER TABLE absence_policy DROP CONSTRAINT FK_7A780B65DE12AB56');
$this->addSql('ALTER TABLE absence_policy DROP CONSTRAINT FK_7A780B6516FE72E1');
$this->addSql('DROP INDEX IDX_7A780B65DE12AB56');
$this->addSql('DROP INDEX IDX_7A780B6516FE72E1');
$this->addSql('ALTER TABLE absence_policy DROP created_at');
$this->addSql('ALTER TABLE absence_policy DROP updated_at');
$this->addSql('ALTER TABLE absence_policy DROP created_by');
$this->addSql('ALTER TABLE absence_policy DROP updated_by');
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Directory module: add Timestampable/Blamable columns to client.
*
* Client (moved to App\Module\Directory\Domain\Entity) adopts
* TimestampableBlamableTrait. This migration is purely additive — nullable
* columns + nullable FK to "user" with ON DELETE SET NULL. No DROP/ALTER on
* existing data. Columns are lowercase snake_case. Hand-written to guarantee
* zero destructive instruction.
*/
final class Version20260620180000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Directory: add timestampable/blamable columns to client (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE client ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C744045516FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_C7440455DE12AB56 ON client (created_by)');
$this->addSql('CREATE INDEX IDX_C744045516FE72E1 ON client (updated_by)');
$this->addSql("COMMENT ON COLUMN client.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN client.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN client.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN client.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE client DROP CONSTRAINT FK_C7440455DE12AB56');
$this->addSql('ALTER TABLE client DROP CONSTRAINT FK_C744045516FE72E1');
$this->addSql('DROP INDEX IDX_C7440455DE12AB56');
$this->addSql('DROP INDEX IDX_C744045516FE72E1');
$this->addSql('ALTER TABLE client DROP created_at');
$this->addSql('ALTER TABLE client DROP updated_at');
$this->addSql('ALTER TABLE client DROP created_by');
$this->addSql('ALTER TABLE client DROP updated_by');
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Directory module: create the prospect table.
*
* Purely additive — creates a brand-new table with nullable FKs:
* converted_client_id -> client(id) ON DELETE SET NULL
* created_by/updated_by -> "user"(id) ON DELETE SET NULL (Blamable)
* No DROP/ALTER on existing data. Columns are lowercase snake_case.
* Hand-written to mirror the schema dump and guarantee zero destructive
* instruction. down() drops the new table.
*/
final class Version20260620190000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Directory: create prospect table (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE prospect (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, company VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, phone VARCHAR(50) DEFAULT NULL, street VARCHAR(255) DEFAULT NULL, city VARCHAR(255) DEFAULT NULL, postal_code VARCHAR(20) DEFAULT NULL, status VARCHAR(32) NOT NULL, source VARCHAR(255) DEFAULT NULL, notes TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, converted_client_id INT DEFAULT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_C9CE8C7D5AA408DD ON prospect (converted_client_id)');
$this->addSql('CREATE INDEX IDX_C9CE8C7DDE12AB56 ON prospect (created_by)');
$this->addSql('CREATE INDEX IDX_C9CE8C7D16FE72E1 ON prospect (updated_by)');
$this->addSql('ALTER TABLE prospect ADD CONSTRAINT FK_C9CE8C7D5AA408DD FOREIGN KEY (converted_client_id) REFERENCES client (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE prospect ADD CONSTRAINT FK_C9CE8C7DDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE prospect ADD CONSTRAINT FK_C9CE8C7D16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql("COMMENT ON COLUMN prospect.status IS 'Prospect pipeline status (ProspectStatus enum: new, contacted, qualified, won, lost)'");
$this->addSql("COMMENT ON COLUMN prospect.converted_client_id IS 'Client created when the prospect is converted (FK client.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN prospect.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN prospect.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN prospect.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN prospect.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE prospect');
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Mail module: add Timestampable + Blamable tracking on mail_configuration.
*
* Purely additive — adds nullable audit columns to an existing table:
* created_at / updated_at (Timestampable)
* created_by / updated_by -> "user"(id) ON DELETE SET NULL (Blamable)
* No DROP/ALTER on existing data. Columns are lowercase snake_case.
* Hand-written to mirror the schema dump and guarantee zero destructive
* instruction. down() drops the new columns and their FKs/indexes.
*/
final class Version20260620200000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Mail: add Timestampable/Blamable columns on mail_configuration (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE mail_configuration ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD CONSTRAINT FK_BFC0A7DBDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE mail_configuration ADD CONSTRAINT FK_BFC0A7DB16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_BFC0A7DBDE12AB56 ON mail_configuration (created_by)');
$this->addSql('CREATE INDEX IDX_BFC0A7DB16FE72E1 ON mail_configuration (updated_by)');
$this->addSql("COMMENT ON COLUMN mail_configuration.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE mail_configuration DROP CONSTRAINT FK_BFC0A7DBDE12AB56');
$this->addSql('ALTER TABLE mail_configuration DROP CONSTRAINT FK_BFC0A7DB16FE72E1');
$this->addSql('DROP INDEX IDX_BFC0A7DBDE12AB56');
$this->addSql('DROP INDEX IDX_BFC0A7DB16FE72E1');
$this->addSql('ALTER TABLE mail_configuration DROP created_at');
$this->addSql('ALTER TABLE mail_configuration DROP updated_at');
$this->addSql('ALTER TABLE mail_configuration DROP created_by');
$this->addSql('ALTER TABLE mail_configuration DROP updated_by');
}
}
+125
View File
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Integration module: add Timestampable + Blamable tracking on the four
* external-integration configuration tables (Gitea, BookStack, Zimbra, Share).
*
* Purely additive — adds nullable audit columns to existing tables:
* created_at / updated_at (Timestampable)
* created_by / updated_by -> "user"(id) ON DELETE SET NULL (Blamable)
* No DROP/ALTER on existing data. Columns are lowercase snake_case. FK/index
* names mirror Doctrine's generated identifiers so schema:validate stays clean.
* down() drops the new columns and their FKs/indexes.
*/
final class Version20260620201000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Integration: add Timestampable/Blamable columns on gitea/bookstack/zimbra/share configuration (additive)';
}
public function up(Schema $schema): void
{
// gitea_configuration
$this->addSql('ALTER TABLE gitea_configuration ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE gitea_configuration ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE gitea_configuration ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE gitea_configuration ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE gitea_configuration ADD CONSTRAINT FK_901AB3BDDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE gitea_configuration ADD CONSTRAINT FK_901AB3BD16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_901AB3BDDE12AB56 ON gitea_configuration (created_by)');
$this->addSql('CREATE INDEX IDX_901AB3BD16FE72E1 ON gitea_configuration (updated_by)');
$this->addSql("COMMENT ON COLUMN gitea_configuration.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN gitea_configuration.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN gitea_configuration.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN gitea_configuration.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
// book_stack_configuration
$this->addSql('ALTER TABLE book_stack_configuration ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE book_stack_configuration ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE book_stack_configuration ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE book_stack_configuration ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE book_stack_configuration ADD CONSTRAINT FK_63A143E0DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE book_stack_configuration ADD CONSTRAINT FK_63A143E016FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_63A143E0DE12AB56 ON book_stack_configuration (created_by)');
$this->addSql('CREATE INDEX IDX_63A143E016FE72E1 ON book_stack_configuration (updated_by)');
$this->addSql("COMMENT ON COLUMN book_stack_configuration.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN book_stack_configuration.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN book_stack_configuration.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN book_stack_configuration.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
// zimbra_configuration
$this->addSql('ALTER TABLE zimbra_configuration ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE zimbra_configuration ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE zimbra_configuration ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE zimbra_configuration ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE zimbra_configuration ADD CONSTRAINT FK_E97E3357DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE zimbra_configuration ADD CONSTRAINT FK_E97E335716FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_E97E3357DE12AB56 ON zimbra_configuration (created_by)');
$this->addSql('CREATE INDEX IDX_E97E335716FE72E1 ON zimbra_configuration (updated_by)');
$this->addSql("COMMENT ON COLUMN zimbra_configuration.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN zimbra_configuration.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN zimbra_configuration.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN zimbra_configuration.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
// share_configuration
$this->addSql('ALTER TABLE share_configuration ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE share_configuration ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE share_configuration ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE share_configuration ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE share_configuration ADD CONSTRAINT FK_F73FE5CDDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE share_configuration ADD CONSTRAINT FK_F73FE5CD16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_F73FE5CDDE12AB56 ON share_configuration (created_by)');
$this->addSql('CREATE INDEX IDX_F73FE5CD16FE72E1 ON share_configuration (updated_by)');
$this->addSql("COMMENT ON COLUMN share_configuration.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN share_configuration.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN share_configuration.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN share_configuration.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE gitea_configuration DROP CONSTRAINT FK_901AB3BDDE12AB56');
$this->addSql('ALTER TABLE gitea_configuration DROP CONSTRAINT FK_901AB3BD16FE72E1');
$this->addSql('DROP INDEX IDX_901AB3BDDE12AB56');
$this->addSql('DROP INDEX IDX_901AB3BD16FE72E1');
$this->addSql('ALTER TABLE gitea_configuration DROP created_at');
$this->addSql('ALTER TABLE gitea_configuration DROP updated_at');
$this->addSql('ALTER TABLE gitea_configuration DROP created_by');
$this->addSql('ALTER TABLE gitea_configuration DROP updated_by');
$this->addSql('ALTER TABLE book_stack_configuration DROP CONSTRAINT FK_63A143E0DE12AB56');
$this->addSql('ALTER TABLE book_stack_configuration DROP CONSTRAINT FK_63A143E016FE72E1');
$this->addSql('DROP INDEX IDX_63A143E0DE12AB56');
$this->addSql('DROP INDEX IDX_63A143E016FE72E1');
$this->addSql('ALTER TABLE book_stack_configuration DROP created_at');
$this->addSql('ALTER TABLE book_stack_configuration DROP updated_at');
$this->addSql('ALTER TABLE book_stack_configuration DROP created_by');
$this->addSql('ALTER TABLE book_stack_configuration DROP updated_by');
$this->addSql('ALTER TABLE zimbra_configuration DROP CONSTRAINT FK_E97E3357DE12AB56');
$this->addSql('ALTER TABLE zimbra_configuration DROP CONSTRAINT FK_E97E335716FE72E1');
$this->addSql('DROP INDEX IDX_E97E3357DE12AB56');
$this->addSql('DROP INDEX IDX_E97E335716FE72E1');
$this->addSql('ALTER TABLE zimbra_configuration DROP created_at');
$this->addSql('ALTER TABLE zimbra_configuration DROP updated_at');
$this->addSql('ALTER TABLE zimbra_configuration DROP created_by');
$this->addSql('ALTER TABLE zimbra_configuration DROP updated_by');
$this->addSql('ALTER TABLE share_configuration DROP CONSTRAINT FK_F73FE5CDDE12AB56');
$this->addSql('ALTER TABLE share_configuration DROP CONSTRAINT FK_F73FE5CD16FE72E1');
$this->addSql('DROP INDEX IDX_F73FE5CDDE12AB56');
$this->addSql('DROP INDEX IDX_F73FE5CD16FE72E1');
$this->addSql('ALTER TABLE share_configuration DROP created_at');
$this->addSql('ALTER TABLE share_configuration DROP updated_at');
$this->addSql('ALTER TABLE share_configuration DROP created_by');
$this->addSql('ALTER TABLE share_configuration DROP updated_by');
}
}
+115
View File
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Client portal — phase 1 foundations (additive).
*
* - Creates the client_ticket table (per-project numbering, FK project/submittedBy,
* Timestampable/Blamable columns, unique (project_id, number), filter indexes).
* - Adds user.client_id (FK client, SET NULL) and the user_allowed_projects join table.
* - Adds task.client_ticket_id (FK client_ticket, SET NULL).
* - Generalises task_document: task_id becomes nullable, adds client_ticket_id
* (FK client_ticket, CASCADE) and a CHECK enforcing that a document is bound
* to either a task or a client ticket.
*
* Lowercase SQL columns; "user" table is quoted. FK/index names mirror Doctrine's
* generated identifiers so doctrine:schema:validate stays clean. down() reverses.
*/
final class Version20260621120000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Client portal phase 1: client_ticket table, user.client_id, user_allowed_projects, task/task_document client ticket links (additive)';
}
public function up(Schema $schema): void
{
// --- client_ticket ---
$this->addSql('CREATE TABLE client_ticket (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, number INT NOT NULL, type VARCHAR(16) NOT NULL, title VARCHAR(255) NOT NULL, description TEXT NOT NULL, url VARCHAR(1024) DEFAULT NULL, status VARCHAR(16) NOT NULL, status_comment TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, project_id INT NOT NULL, submitted_by_id INT DEFAULT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE UNIQUE INDEX uniq_client_ticket_project_number ON client_ticket (project_id, number)');
$this->addSql('CREATE INDEX idx_client_ticket_project ON client_ticket (project_id)');
$this->addSql('CREATE INDEX idx_client_ticket_submitted_by ON client_ticket (submitted_by_id)');
$this->addSql('CREATE INDEX idx_client_ticket_status_project ON client_ticket (status, project_id)');
$this->addSql('CREATE INDEX IDX_C206E610DE12AB56 ON client_ticket (created_by)');
$this->addSql('CREATE INDEX IDX_C206E61016FE72E1 ON client_ticket (updated_by)');
$this->addSql('ALTER TABLE client_ticket ADD CONSTRAINT FK_C206E610166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE client_ticket ADD CONSTRAINT FK_C206E61079F7D87D FOREIGN KEY (submitted_by_id) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE client_ticket ADD CONSTRAINT FK_C206E610DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE client_ticket ADD CONSTRAINT FK_C206E61016FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql("COMMENT ON COLUMN client_ticket.number IS 'Numero incremental unique par projet (affiche CT-XXX).'");
$this->addSql("COMMENT ON COLUMN client_ticket.type IS 'Type de ticket : bug, improvement, other.'");
$this->addSql("COMMENT ON COLUMN client_ticket.status IS 'Statut : new, in_progress, done, rejected.'");
$this->addSql("COMMENT ON COLUMN client_ticket.status_comment IS 'Commentaire du manager lors d''un changement de statut (obligatoire si rejected).'");
$this->addSql("COMMENT ON COLUMN client_ticket.url IS 'URL de la page concernee, affichee uniquement pour les bugs.'");
$this->addSql("COMMENT ON COLUMN client_ticket.submitted_by_id IS 'Utilisateur-client ayant soumis le ticket (FK user, SET NULL pour conserver l''historique).'");
$this->addSql("COMMENT ON COLUMN client_ticket.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN client_ticket.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN client_ticket.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN client_ticket.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
// --- user.client_id ---
$this->addSql('ALTER TABLE "user" ADD client_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE "user" ADD CONSTRAINT FK_8D93D64919EB6921 FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_8D93D64919EB6921 ON "user" (client_id)');
$this->addSql('COMMENT ON COLUMN "user".client_id IS \'Client auquel appartient l\'\'utilisateur (FK client, SET NULL). null = utilisateur interne.\'');
// --- user_allowed_projects (ManyToMany) ---
$this->addSql('CREATE TABLE user_allowed_projects (user_id INT NOT NULL, project_id INT NOT NULL, PRIMARY KEY (user_id, project_id))');
$this->addSql('CREATE INDEX IDX_B3E0FC97A76ED395 ON user_allowed_projects (user_id)');
$this->addSql('CREATE INDEX IDX_B3E0FC97166D1F9C ON user_allowed_projects (project_id)');
$this->addSql('ALTER TABLE user_allowed_projects ADD CONSTRAINT FK_B3E0FC97A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE user_allowed_projects ADD CONSTRAINT FK_B3E0FC97166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE NOT DEFERRABLE');
// --- task.client_ticket_id ---
$this->addSql('ALTER TABLE task ADD client_ticket_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB259B2097DD FOREIGN KEY (client_ticket_id) REFERENCES client_ticket (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_527EDB259B2097DD ON task (client_ticket_id)');
$this->addSql("COMMENT ON COLUMN task.client_ticket_id IS 'Lien manuel optionnel vers un ticket client (FK client_ticket, SET NULL).'");
// --- task_document generalisation ---
$this->addSql('ALTER TABLE task_document ALTER task_id DROP NOT NULL');
$this->addSql('ALTER TABLE task_document ADD client_ticket_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE task_document ADD CONSTRAINT FK_98A9603A9B2097DD FOREIGN KEY (client_ticket_id) REFERENCES client_ticket (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_98A9603A9B2097DD ON task_document (client_ticket_id)');
$this->addSql('ALTER TABLE task_document ADD CONSTRAINT chk_task_document_target CHECK (task_id IS NOT NULL OR client_ticket_id IS NOT NULL)');
$this->addSql("COMMENT ON COLUMN task_document.client_ticket_id IS 'Ticket client auquel le document est rattache (FK client_ticket, CASCADE). Alternative a task_id.'");
}
public function down(Schema $schema): void
{
// task_document
$this->addSql('ALTER TABLE task_document DROP CONSTRAINT chk_task_document_target');
$this->addSql('ALTER TABLE task_document DROP CONSTRAINT FK_98A9603A9B2097DD');
$this->addSql('DROP INDEX IDX_98A9603A9B2097DD');
$this->addSql('ALTER TABLE task_document DROP client_ticket_id');
$this->addSql('ALTER TABLE task_document ALTER task_id SET NOT NULL');
// task
$this->addSql('ALTER TABLE task DROP CONSTRAINT FK_527EDB259B2097DD');
$this->addSql('DROP INDEX IDX_527EDB259B2097DD');
$this->addSql('ALTER TABLE task DROP client_ticket_id');
// user_allowed_projects
$this->addSql('ALTER TABLE user_allowed_projects DROP CONSTRAINT FK_B3E0FC97A76ED395');
$this->addSql('ALTER TABLE user_allowed_projects DROP CONSTRAINT FK_B3E0FC97166D1F9C');
$this->addSql('DROP TABLE user_allowed_projects');
// user.client_id
$this->addSql('ALTER TABLE "user" DROP CONSTRAINT FK_8D93D64919EB6921');
$this->addSql('DROP INDEX IDX_8D93D64919EB6921');
$this->addSql('ALTER TABLE "user" DROP client_id');
// client_ticket
$this->addSql('ALTER TABLE client_ticket DROP CONSTRAINT FK_C206E610166D1F9C');
$this->addSql('ALTER TABLE client_ticket DROP CONSTRAINT FK_C206E61079F7D87D');
$this->addSql('ALTER TABLE client_ticket DROP CONSTRAINT FK_C206E610DE12AB56');
$this->addSql('ALTER TABLE client_ticket DROP CONSTRAINT FK_C206E61016FE72E1');
$this->addSql('DROP TABLE client_ticket');
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Client portal — phase 3: link notifications to client tickets (additive).
*
* - Adds notification.related_ticket_id (FK client_ticket, SET NULL) plus its index,
* so a notification can deep-link to the ticket it concerns without breaking the
* existing task-based notifications (column is nullable).
*
* Lowercase SQL columns; FK/index names mirror Doctrine's generated identifiers.
* down() reverses.
*/
final class Version20260621130000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Client portal phase 3: notification.related_ticket_id link to client_ticket (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE notification ADD related_ticket_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE notification ADD CONSTRAINT FK_BF5476CA98F144DB FOREIGN KEY (related_ticket_id) REFERENCES client_ticket (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX idx_notification_related_ticket ON notification (related_ticket_id)');
$this->addSql("COMMENT ON COLUMN notification.related_ticket_id IS 'Ticket client lie a la notification (FK client_ticket, SET NULL). null = notification non liee a un ticket.'");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE notification DROP CONSTRAINT FK_BF5476CA98F144DB');
$this->addSql('DROP INDEX idx_notification_related_ticket');
$this->addSql('ALTER TABLE notification DROP related_ticket_id');
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Remove the client portal entirely (reverses phases 1 & 3).
*
* - Drops notification.related_ticket_id (FK/index/column).
* - Restores task_document to a task-only model: drops the CHECK constraint and
* client_ticket_id, removes orphan client-ticket-only documents, makes task_id
* NOT NULL again.
* - Drops task.client_ticket_id.
* - Drops the user_allowed_projects join table and user.client_id.
* - Drops the client_ticket table.
* - Deletes leftover client accounts (roles contains ROLE_CLIENT): with the portal
* gone every user now resolves to ROLE_USER, so external client accounts MUST be
* removed to avoid silently granting them internal access.
*
* Lowercase SQL columns; "user" table is quoted. down() recreates the schema
* (structure only — deleted client accounts/tickets are not restored).
*/
final class Version20260622090000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Remove client portal: drop client_ticket, related portal columns/links and external client accounts';
}
public function up(Schema $schema): void
{
// --- notification.related_ticket_id (phase 3) ---
$this->addSql('ALTER TABLE notification DROP CONSTRAINT FK_BF5476CA98F144DB');
$this->addSql('DROP INDEX idx_notification_related_ticket');
$this->addSql('ALTER TABLE notification DROP related_ticket_id');
// --- task_document: back to task-only ---
$this->addSql('ALTER TABLE task_document DROP CONSTRAINT chk_task_document_target');
$this->addSql('ALTER TABLE task_document DROP CONSTRAINT FK_98A9603A9B2097DD');
$this->addSql('DROP INDEX IDX_98A9603A9B2097DD');
// Remove documents attached only to a client ticket before re-enforcing NOT NULL.
$this->addSql('DELETE FROM task_document WHERE task_id IS NULL');
$this->addSql('ALTER TABLE task_document DROP client_ticket_id');
$this->addSql('ALTER TABLE task_document ALTER task_id SET NOT NULL');
// --- task.client_ticket_id ---
$this->addSql('ALTER TABLE task DROP CONSTRAINT FK_527EDB259B2097DD');
$this->addSql('DROP INDEX IDX_527EDB259B2097DD');
$this->addSql('ALTER TABLE task DROP client_ticket_id');
// --- user_allowed_projects ---
$this->addSql('ALTER TABLE user_allowed_projects DROP CONSTRAINT FK_B3E0FC97A76ED395');
$this->addSql('ALTER TABLE user_allowed_projects DROP CONSTRAINT FK_B3E0FC97166D1F9C');
$this->addSql('DROP TABLE user_allowed_projects');
// --- user.client_id ---
$this->addSql('ALTER TABLE "user" DROP CONSTRAINT FK_8D93D64919EB6921');
$this->addSql('DROP INDEX IDX_8D93D64919EB6921');
$this->addSql('ALTER TABLE "user" DROP client_id');
// --- client_ticket table ---
$this->addSql('ALTER TABLE client_ticket DROP CONSTRAINT FK_C206E610166D1F9C');
$this->addSql('ALTER TABLE client_ticket DROP CONSTRAINT FK_C206E61079F7D87D');
$this->addSql('ALTER TABLE client_ticket DROP CONSTRAINT FK_C206E610DE12AB56');
$this->addSql('ALTER TABLE client_ticket DROP CONSTRAINT FK_C206E61016FE72E1');
$this->addSql('DROP TABLE client_ticket');
// --- external client accounts ---
$this->addSql('DELETE FROM "user" WHERE roles::text LIKE \'%ROLE_CLIENT%\'');
}
public function down(Schema $schema): void
{
// --- client_ticket table ---
$this->addSql('CREATE TABLE client_ticket (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, number INT NOT NULL, type VARCHAR(16) NOT NULL, title VARCHAR(255) NOT NULL, description TEXT NOT NULL, url VARCHAR(1024) DEFAULT NULL, status VARCHAR(16) NOT NULL, status_comment TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, project_id INT NOT NULL, submitted_by_id INT DEFAULT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE UNIQUE INDEX uniq_client_ticket_project_number ON client_ticket (project_id, number)');
$this->addSql('CREATE INDEX idx_client_ticket_project ON client_ticket (project_id)');
$this->addSql('CREATE INDEX idx_client_ticket_submitted_by ON client_ticket (submitted_by_id)');
$this->addSql('CREATE INDEX idx_client_ticket_status_project ON client_ticket (status, project_id)');
$this->addSql('CREATE INDEX IDX_C206E610DE12AB56 ON client_ticket (created_by)');
$this->addSql('CREATE INDEX IDX_C206E61016FE72E1 ON client_ticket (updated_by)');
$this->addSql('ALTER TABLE client_ticket ADD CONSTRAINT FK_C206E610166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE client_ticket ADD CONSTRAINT FK_C206E61079F7D87D FOREIGN KEY (submitted_by_id) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE client_ticket ADD CONSTRAINT FK_C206E610DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE client_ticket ADD CONSTRAINT FK_C206E61016FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
// --- user.client_id ---
$this->addSql('ALTER TABLE "user" ADD client_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE "user" ADD CONSTRAINT FK_8D93D64919EB6921 FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_8D93D64919EB6921 ON "user" (client_id)');
// --- user_allowed_projects ---
$this->addSql('CREATE TABLE user_allowed_projects (user_id INT NOT NULL, project_id INT NOT NULL, PRIMARY KEY (user_id, project_id))');
$this->addSql('CREATE INDEX IDX_B3E0FC97A76ED395 ON user_allowed_projects (user_id)');
$this->addSql('CREATE INDEX IDX_B3E0FC97166D1F9C ON user_allowed_projects (project_id)');
$this->addSql('ALTER TABLE user_allowed_projects ADD CONSTRAINT FK_B3E0FC97A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE user_allowed_projects ADD CONSTRAINT FK_B3E0FC97166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE NOT DEFERRABLE');
// --- task.client_ticket_id ---
$this->addSql('ALTER TABLE task ADD client_ticket_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB259B2097DD FOREIGN KEY (client_ticket_id) REFERENCES client_ticket (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_527EDB259B2097DD ON task (client_ticket_id)');
// --- task_document generalisation ---
$this->addSql('ALTER TABLE task_document ALTER task_id DROP NOT NULL');
$this->addSql('ALTER TABLE task_document ADD client_ticket_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE task_document ADD CONSTRAINT FK_98A9603A9B2097DD FOREIGN KEY (client_ticket_id) REFERENCES client_ticket (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_98A9603A9B2097DD ON task_document (client_ticket_id)');
$this->addSql('ALTER TABLE task_document ADD CONSTRAINT chk_task_document_target CHECK (task_id IS NOT NULL OR client_ticket_id IS NOT NULL)');
// --- notification.related_ticket_id ---
$this->addSql('ALTER TABLE notification ADD related_ticket_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE notification ADD CONSTRAINT FK_BF5476CA98F144DB FOREIGN KEY (related_ticket_id) REFERENCES client_ticket (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX idx_notification_related_ticket ON notification (related_ticket_id)');
}
}
+222
View File
@@ -0,0 +1,222 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260622100916 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE commercial_report (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, subject VARCHAR(255) NOT NULL, body TEXT DEFAULT NULL, occurred_at DATE NOT NULL, type VARCHAR(32) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, client_id INT DEFAULT NULL, prospect_id INT DEFAULT NULL, author_id INT DEFAULT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_886919D819EB6921 ON commercial_report (client_id)');
$this->addSql('CREATE INDEX IDX_886919D8D182060A ON commercial_report (prospect_id)');
$this->addSql('CREATE INDEX IDX_886919D8F675F31B ON commercial_report (author_id)');
$this->addSql('CREATE INDEX IDX_886919D8DE12AB56 ON commercial_report (created_by)');
$this->addSql('CREATE INDEX IDX_886919D816FE72E1 ON commercial_report (updated_by)');
$this->addSql('CREATE TABLE directory_address (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(255) DEFAULT NULL, street VARCHAR(255) DEFAULT NULL, street_complement VARCHAR(255) DEFAULT NULL, postal_code VARCHAR(20) DEFAULT NULL, city VARCHAR(255) DEFAULT NULL, country VARCHAR(2) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, client_id INT DEFAULT NULL, prospect_id INT DEFAULT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_6E5D970719EB6921 ON directory_address (client_id)');
$this->addSql('CREATE INDEX IDX_6E5D9707D182060A ON directory_address (prospect_id)');
$this->addSql('CREATE INDEX IDX_6E5D9707DE12AB56 ON directory_address (created_by)');
$this->addSql('CREATE INDEX IDX_6E5D970716FE72E1 ON directory_address (updated_by)');
$this->addSql('CREATE TABLE directory_contact (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, first_name VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, job_title VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, phone_primary VARCHAR(50) DEFAULT NULL, phone_secondary VARCHAR(50) DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, client_id INT DEFAULT NULL, prospect_id INT DEFAULT NULL, created_by INT DEFAULT NULL, updated_by INT DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_2F711EBE19EB6921 ON directory_contact (client_id)');
$this->addSql('CREATE INDEX IDX_2F711EBED182060A ON directory_contact (prospect_id)');
$this->addSql('CREATE INDEX IDX_2F711EBEDE12AB56 ON directory_contact (created_by)');
$this->addSql('CREATE INDEX IDX_2F711EBE16FE72E1 ON directory_contact (updated_by)');
$this->addSql('CREATE TABLE report_document (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, original_name VARCHAR(255) NOT NULL, file_name VARCHAR(255) DEFAULT NULL, mime_type VARCHAR(100) NOT NULL, size INT NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, commercial_report_id INT NOT NULL, uploaded_by_id INT DEFAULT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_D809130B4AB5D1D4 ON report_document (commercial_report_id)');
$this->addSql('CREATE INDEX IDX_D809130BA2B28FE8 ON report_document (uploaded_by_id)');
$this->addSql('ALTER TABLE commercial_report ADD CONSTRAINT FK_886919D819EB6921 FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE commercial_report ADD CONSTRAINT FK_886919D8D182060A FOREIGN KEY (prospect_id) REFERENCES prospect (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE commercial_report ADD CONSTRAINT FK_886919D8F675F31B FOREIGN KEY (author_id) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE commercial_report ADD CONSTRAINT FK_886919D8DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE commercial_report ADD CONSTRAINT FK_886919D816FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE directory_address ADD CONSTRAINT FK_6E5D970719EB6921 FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE directory_address ADD CONSTRAINT FK_6E5D9707D182060A FOREIGN KEY (prospect_id) REFERENCES prospect (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE directory_address ADD CONSTRAINT FK_6E5D9707DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE directory_address ADD CONSTRAINT FK_6E5D970716FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE directory_contact ADD CONSTRAINT FK_2F711EBE19EB6921 FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE directory_contact ADD CONSTRAINT FK_2F711EBED182060A FOREIGN KEY (prospect_id) REFERENCES prospect (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE directory_contact ADD CONSTRAINT FK_2F711EBEDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE directory_contact ADD CONSTRAINT FK_2F711EBE16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE report_document ADD CONSTRAINT FK_D809130B4AB5D1D4 FOREIGN KEY (commercial_report_id) REFERENCES commercial_report (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE report_document ADD CONSTRAINT FK_D809130BA2B28FE8 FOREIGN KEY (uploaded_by_id) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
// Ownership CHECK constraints: each row must belong to a client or a prospect.
$this->addSql('ALTER TABLE directory_contact ADD CONSTRAINT chk_contact_owner CHECK (client_id IS NOT NULL OR prospect_id IS NOT NULL)');
$this->addSql('ALTER TABLE directory_address ADD CONSTRAINT chk_address_owner CHECK (client_id IS NOT NULL OR prospect_id IS NOT NULL)');
$this->addSql('ALTER TABLE commercial_report ADD CONSTRAINT chk_report_owner CHECK (client_id IS NOT NULL OR prospect_id IS NOT NULL)');
// Data migration: copy existing inline addresses into directory_address before dropping the columns.
$this->addSql("INSERT INTO directory_address (label, street, postal_code, city, country, client_id, created_at, updated_at)
SELECT 'Principale', street, postal_code, city, 'FR', id, NOW(), NOW()
FROM client
WHERE COALESCE(street, '') <> '' OR COALESCE(city, '') <> '' OR COALESCE(postal_code, '') <> ''");
$this->addSql("INSERT INTO directory_address (label, street, postal_code, city, country, prospect_id, created_at, updated_at)
SELECT 'Principale', street, postal_code, city, 'FR', id, NOW(), NOW()
FROM prospect
WHERE COALESCE(street, '') <> '' OR COALESCE(city, '') <> '' OR COALESCE(postal_code, '') <> ''");
$this->addSql('COMMENT ON COLUMN absence_balance.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN absence_balance.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN absence_balance.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN absence_balance.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN absence_policy.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN absence_policy.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN absence_policy.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN absence_policy.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN book_stack_configuration.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN book_stack_configuration.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN book_stack_configuration.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN book_stack_configuration.updated_by IS \'\'');
$this->addSql('ALTER TABLE client DROP street');
$this->addSql('ALTER TABLE client DROP city');
$this->addSql('ALTER TABLE client DROP postal_code');
$this->addSql('COMMENT ON COLUMN client.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN client.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN client.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN client.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN gitea_configuration.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN gitea_configuration.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN gitea_configuration.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN gitea_configuration.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN mail_configuration.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN mail_configuration.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN mail_configuration.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN mail_configuration.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN project.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN project.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN project.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN project.updated_by IS \'\'');
$this->addSql('ALTER TABLE prospect DROP street');
$this->addSql('ALTER TABLE prospect DROP city');
$this->addSql('ALTER TABLE prospect DROP postal_code');
$this->addSql('COMMENT ON COLUMN prospect.status IS \'\'');
$this->addSql('COMMENT ON COLUMN prospect.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN prospect.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN prospect.converted_client_id IS \'\'');
$this->addSql('COMMENT ON COLUMN prospect.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN prospect.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN share_configuration.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN share_configuration.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN share_configuration.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN share_configuration.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN task.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN task.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN task.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN task.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN time_entry.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN time_entry.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN time_entry.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN time_entry.updated_by IS \'\'');
$this->addSql('COMMENT ON COLUMN zimbra_configuration.created_at IS \'\'');
$this->addSql('COMMENT ON COLUMN zimbra_configuration.updated_at IS \'\'');
$this->addSql('COMMENT ON COLUMN zimbra_configuration.created_by IS \'\'');
$this->addSql('COMMENT ON COLUMN zimbra_configuration.updated_by IS \'\'');
$this->addSql('DROP INDEX idx_75ea56e016ba31db');
$this->addSql('DROP INDEX idx_75ea56e0fb7336f0');
$this->addSql('DROP INDEX idx_75ea56e0e3bd61ce');
$this->addSql('ALTER TABLE messenger_messages ALTER id DROP DEFAULT');
$this->addSql('ALTER TABLE messenger_messages ALTER id ADD GENERATED BY DEFAULT AS IDENTITY');
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE commercial_report DROP CONSTRAINT FK_886919D819EB6921');
$this->addSql('ALTER TABLE commercial_report DROP CONSTRAINT FK_886919D8D182060A');
$this->addSql('ALTER TABLE commercial_report DROP CONSTRAINT FK_886919D8F675F31B');
$this->addSql('ALTER TABLE commercial_report DROP CONSTRAINT FK_886919D8DE12AB56');
$this->addSql('ALTER TABLE commercial_report DROP CONSTRAINT FK_886919D816FE72E1');
$this->addSql('ALTER TABLE directory_address DROP CONSTRAINT FK_6E5D970719EB6921');
$this->addSql('ALTER TABLE directory_address DROP CONSTRAINT FK_6E5D9707D182060A');
$this->addSql('ALTER TABLE directory_address DROP CONSTRAINT FK_6E5D9707DE12AB56');
$this->addSql('ALTER TABLE directory_address DROP CONSTRAINT FK_6E5D970716FE72E1');
$this->addSql('ALTER TABLE directory_contact DROP CONSTRAINT FK_2F711EBE19EB6921');
$this->addSql('ALTER TABLE directory_contact DROP CONSTRAINT FK_2F711EBED182060A');
$this->addSql('ALTER TABLE directory_contact DROP CONSTRAINT FK_2F711EBEDE12AB56');
$this->addSql('ALTER TABLE directory_contact DROP CONSTRAINT FK_2F711EBE16FE72E1');
$this->addSql('ALTER TABLE report_document DROP CONSTRAINT FK_D809130B4AB5D1D4');
$this->addSql('ALTER TABLE report_document DROP CONSTRAINT FK_D809130BA2B28FE8');
$this->addSql('DROP TABLE commercial_report');
$this->addSql('DROP TABLE directory_address');
$this->addSql('DROP TABLE directory_contact');
$this->addSql('DROP TABLE report_document');
$this->addSql('COMMENT ON COLUMN absence_balance.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN absence_balance.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN absence_balance.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN absence_balance.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN absence_policy.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN absence_policy.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN absence_policy.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN absence_policy.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN book_stack_configuration.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN book_stack_configuration.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN book_stack_configuration.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN book_stack_configuration.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('ALTER TABLE client ADD street VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD city VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE client ADD postal_code VARCHAR(20) DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN client.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN client.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN client.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN client.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN gitea_configuration.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN gitea_configuration.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN gitea_configuration.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN gitea_configuration.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN mail_configuration.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN mail_configuration.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN mail_configuration.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN mail_configuration.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('DROP INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750');
$this->addSql('ALTER TABLE messenger_messages ALTER id SET DEFAULT nextval(\'messenger_messages_id_seq\'::regclass)');
$this->addSql('ALTER TABLE messenger_messages ALTER id DROP IDENTITY');
$this->addSql('CREATE INDEX idx_75ea56e016ba31db ON messenger_messages (delivered_at)');
$this->addSql('CREATE INDEX idx_75ea56e0fb7336f0 ON messenger_messages (queue_name)');
$this->addSql('CREATE INDEX idx_75ea56e0e3bd61ce ON messenger_messages (available_at)');
$this->addSql('COMMENT ON COLUMN project.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN project.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN project.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN project.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('ALTER TABLE prospect ADD street VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE prospect ADD city VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE prospect ADD postal_code VARCHAR(20) DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN prospect.status IS \'Prospect pipeline status (ProspectStatus enum: new, contacted, qualified, won, lost)\'');
$this->addSql('COMMENT ON COLUMN prospect.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN prospect.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN prospect.converted_client_id IS \'Client created when the prospect is converted (FK client.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN prospect.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN prospect.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN share_configuration.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN share_configuration.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN share_configuration.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN share_configuration.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN task.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN task.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN task.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN task.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN time_entry.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN time_entry.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN time_entry.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN time_entry.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN zimbra_configuration.created_at IS \'Creation timestamp (Timestampable, set on prePersist)\'');
$this->addSql('COMMENT ON COLUMN zimbra_configuration.updated_at IS \'Last update timestamp (Timestampable, set on prePersist/preUpdate)\'');
$this->addSql('COMMENT ON COLUMN zimbra_configuration.created_by IS \'User who created the entry (Blamable, FK user.id, SET NULL on delete)\'');
$this->addSql('COMMENT ON COLUMN zimbra_configuration.updated_by IS \'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)\'');
}
}