# Modèle de données MVP — Coffra Document de référence pour le développement (Laravel : modèles, migrations, relations, validations ; React : écrans et appels API). **Ce fichier ne remplace pas les migrations** : il les prescrit. Le code actuel du dépôt peut encore être en retard sur ce schéma cible. --- ## 1. Introduction et conventions ### Contexte produit - Application **mobile-first** de gestion opérationnelle de chantier. - **Backend** : Laravel. **Frontend** : React, TypeScript, Tailwind. - **Multi-organisation** : toute donnée métier est isolée par `organization_id`. - **Comptes connectés** : uniquement des **User** avec l’un des rôles MVP (`entrepreneur_admin`, `chef_chantier`, `contremaitre_technicien`, `financier_tresorier`) — voir `ROLE_PERMISSIONS.md`. - **Ouvriers terrain** : entité **Worker**, **sans login** dans le MVP. ### Conventions de nommage (code et base) | Élément | Convention | |---------|------------| | Tables | `snake_case`, pluriel anglais (`organizations`, `projects`, `user_project_access`, …) | | Colonnes | `snake_case` | | Clés primaires | `id` (bigint unsigned, auto-incrément) sauf mention contraire | | FK | `{table_singulier}_id` (ex. `organization_id`, `project_id`) | | Horodatage Laravel | `created_at`, `updated_at` (`timestamps()`) | | Booléens | `is_active`, etc. (tinyint/boolean) | | Montants | décimal en base (ex. `decimal(12,2)`), jamais `float` brut pour la monnaie | | Dates seules | type `date` (présence, périodes) | | Datetimes | `datetime` ou `timestamp` selon besoin timezone | ### Types de champs (indicatif Laravel) - Texte court : `string` / `varchar(255)` sauf précision. - Texte long : `text`. - Enum métier : `string` avec valeurs contrôlées (ou colonnes enum MySQL si politique d’équipe le impose). - JSON : réservé au **hors MVP** sauf besoin explicite futur. ### Alignement abilities / scope Les **abilities** globales (`projects.view`, `attendances.manage`, `production_entries.manage`, …) ouvrent des **familles d’actions**. Elles **ne remplacent pas** : 1. le filtre **`organization_id` = organisation du User authentifié** ; 2. le filtre **projet** : un User ne voit / n’agit sur un `Project` que s’il y est autorisé (**`UserProjectAccess`**, rôle applicatif, ou règles dérivées pour `entrepreneur_admin` — à figer en policy). Voir aussi `ARCHITECTURE.md` et `ROLE_PERMISSIONS.md`. --- ## 2. Décision de vocabulaire : Project = Chantier | Couche | Terme à utiliser | |--------|------------------| | **Code, base de données, API** (tables, modèles, routes, JSON) | **`Project` / `projects` / `project_id`** | | **Interface utilisateur** (libellés, aide, messages) | **« Chantier »** (ou équivalent métier local) | **Exemple** : la ressource API peut s’appeler `/projects` ou `/projects/{id}` ; l’écran affiche « Chantiers » ou « Chantier actif ». Éviter dans le code un mélange `chantier_id` / `project_id` : **standard unique `project_id`**. --- ## 3. Vue d’ensemble des entités ``` Organization ├── User (compte connecté, role MVP) ├── Project (chantier) │ ├── UserProjectAccess (qui accède à quel chantier) │ ├── Team (équipe sur le chantier) │ ├── WorkerProjectAssignment (ouvrier affecté au chantier) │ ├── Attendance (présence Worker, saisie par User) │ ├── Need (besoin) │ ├── WorkItem (ouvrage suivi) │ ├── WorkProgress (avancement chantier / suivi — lecture métier) │ ├── ProductionEntry (production validée, base de calcul paie « au rendu ») │ └── PayrollEntry (ligne de paie / rémunération) └── Worker (fiche ouvrier, sans compte) WorkItem └── WorkProgress (historique d’avancement, non source directe de paie au rendu) ``` **Distinction produit (officielle)** : | Entité | Rôle | |--------|------| | **WorkProgress** | Mesure d’**avancement chantier** sur un **WorkItem** (pilotage, reporting chantier). **Ne sert pas** de source directe pour le mode de paie **`per_completed_work`**. | | **ProductionEntry** | **Production attribuable** à un **Worker** et/ou une **Team**, rattachée à un **WorkItem**, soumise puis **validée** ; seules les lignes au statut adapté alimentent le calcul de paie « au travail réalisé / au rendu ». | **Résumé des tables MVP** : `organizations`, `users`, `projects`, `user_project_access`, `teams`, `workers`, `worker_project_assignments`, `attendances`, `needs`, `work_items`, `work_progress`, `production_entries`, `payroll_entries`. --- ## 4. Détail de chaque entité ### 4.1 Organization | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `name` | oui | string | Raison sociale ou nom d’usage | | `slug` | oui | string | Unique **global** ; URL / référence stable | | `status` | oui | string | Voir §6 (ex. `active`, `suspended` — préciser enums org. en §6 si besoin) | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **FK** : aucune. **Index / unicité** : `UNIQUE(slug)`. Index sur `status` si filtrage fréquent. **MVP** : une organisation active en seed ; statut permet de désactiver un tenant sans supprimer les données. --- ### 4.2 User Compte **connecté** ; distinct de **Worker**. | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | Cascade **interdite** en général si données existent ; préférer `restrict` ou soft-delete org. | | `full_name` | oui | string | **Cible MVP** ; le code actuel peut encore utiliser `name` — migration de renommage prévue. | | `email` | oui | string | Unique **par organisation** : `UNIQUE(organization_id, email)` (recommandé) plutôt que global, pour le multi-tenant. | | `phone` | non | string, nullable | | | `password` | oui | string | Hash Laravel | | `role` | oui | string | Une des valeurs MVP : `entrepreneur_admin`, `chef_chantier`, `contremaitre_technicien`, `financier_tresorier` | | `is_active` | oui | boolean | Défaut `true` ; false = ne peut plus se connecter | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **FK** : `organization_id` → `organizations.id`. **Index** : `(organization_id, email)` unique ; `(organization_id, is_active)`. --- ### 4.3 Project Représente le **chantier** côté métier. | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `name` | oui | string | Libellé chantier (UI : « Nom du chantier ») | | `code` | non | string, nullable | Référence interne ; unique **recommandé** par org. : `UNIQUE(organization_id, code)` si `code` non null (traiter les nulls selon moteur SQL). | | `description` | non | text, nullable | | | `location` | non | string, nullable | Adresse ou libellé lieu | | `start_date` | non | date, nullable | | | `expected_end_date` | non | date, nullable | | | `status` | oui | string | Voir §6 — `preparation`, `active`, `suspended`, `completed` | | `site_manager_user_id` | non | bigint nullable FK → `users.id` | Chef de chantier désigné sur le **papier métier** ; accès réel reste gouverné par `UserProjectAccess` + policies. `null` = non renseigné. | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **FK** : `organization_id` ; `site_manager_user_id` → `users.id` (nullable, `nullOnDelete` ou `restrict` selon règle métier). **Index** : `(organization_id, status)` ; `(organization_id, name)` pour recherche. --- ### 4.4 UserProjectAccess Liaison **User ↔ Project** pour le **périmètre d’accès** au chantier (en complément des abilities globales). | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | Clé surrogate **recommandée** (simplicité Laravel, audits). | | `organization_id` | oui | bigint FK → `organizations.id` | Redondant avec org du user / du project mais **utile pour contraintes et requêtes** ; doit rester **cohérent** avec `users.organization_id` et `projects.organization_id`. | | `user_id` | oui | bigint FK → `users.id` | | | `project_id` | oui | bigint FK → `projects.id` | | | `access_role` | non | string, nullable | Rôle **contextuel chantier** (ex. `viewer`, `editor`, `lead`) — **optionnel MVP** ; si null, l’accès est binaire (présence = accès) et les capacités viennent du `User.role` + policy. | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **Unicité métier** : **`UNIQUE(organization_id, user_id, project_id)`** — un seul enregistrement par triplet. **Index** : `(user_id, project_id)` ; `(project_id)` pour lister les membres d’un chantier. **Règle** : `organization_id` doit égaler l’organisation du `user` et du `project` (validation applicative / trigger logique). --- ### 4.5 Team Équipe opérationnelle **au sein d’un projet**. | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | Cohérent avec `project.organization_id` | | `project_id` | oui | bigint FK → `projects.id` | | | `name` | oui | string | Ex. « Gros œuvre », « Second œuvre » | | `description` | non | text, nullable | | | `leader_worker_id` | non | bigint nullable FK → `workers.id` | Chef d’équipe **métier** (Worker) ; nullable si non désigné. | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **FK** : `organization_id`, `project_id`, `leader_worker_id` → `workers.id` (nullable). **Index** : `(project_id)` ; `(organization_id, project_id)`. **Contrainte** : le `leader_worker_id`, s’il est renseigné, doit désigner un worker **affecté au même projet** (via `WorkerProjectAssignment` actif — logique applicative). --- ### 4.6 Worker Ouvrier **sans compte** MVP. | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `display_name` | oui | string | Nom affiché | | `external_reference` | non | string, nullable | Matricule / code interne ; unique recommandé par org. si utilisé : `UNIQUE(organization_id, external_reference)` où non null. | | `phone` | non | string, nullable | | | `specialty` | non | string, nullable | Métier / spécialité libre ou enum léger plus tard | | `worker_type` | non | string, nullable | Catégorisation (ex. interne / sous-traitant) — valeurs à figer en prod | | `payment_mode` | non | string, nullable | **Valeurs officielles MVP** : `daily`, `hourly`, `per_completed_work`. Détermine quelle **source** alimente le calcul des `PayrollEntry` pour cet ouvrier (voir §4.13). | | `base_rate` | non | decimal, nullable | Taux / prix de base indicatif (ex. taux horaire, prix unitaire au rendu) ; montants **officiels** et périodes en `PayrollEntry` | | `is_active` | oui | boolean | Défaut `true` | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **FK** : `organization_id`. **Index** : `(organization_id, is_active)` ; `(organization_id, display_name)`. --- ### 4.7 WorkerProjectAssignment #### Nécessité dans le MVP : **oui** Sans cette table, on ne sait pas **formellement** quels ouvriers sont **rattachés** à quel chantier pour : - valider qu’une **Attendance** a lieu sur un couple worker/projet légitime ; - filtrer les listes « ouvriers du chantier » ; - préparer **PayrollEntry** par chantier. Les **Team** seules ne suffisent pas : un worker peut être sur le chantier sans être dans une équipe, ou changer d’équipe. | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `worker_id` | oui | bigint FK → `workers.id` | | | `project_id` | oui | bigint FK → `projects.id` | | | `team_id` | non | bigint nullable FK → `teams.id` | Équipe principale sur la période ; nullable | | `start_date` | non | date, nullable | Début d’affectation ; null = pas de borne ou « depuis toujours » selon règle produit | | `end_date` | non | date, nullable | null = affectation **ouverte** (en cours) | | `status` | oui | string | Ex. `planned`, `active`, `ended`, `cancelled` — détail §6 | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **Index** : `(worker_id, project_id)` ; `(project_id)` ; `(organization_id, project_id)`. **Unicité / chevauchement** : pas d’unicité brute sur `(worker_id, project_id)` si plusieurs périodes successives sont autorisées. Règle applicative : **au plus une affectation `active` à la même date** pour un même couple worker/projet (voir §7). --- ### 4.8 Attendance (présence) | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `project_id` | oui | bigint FK → `projects.id` | | | `worker_id` | oui | bigint FK → `workers.id` | **La présence concerne un Worker**, pas un User connecté | | `team_id` | non | bigint nullable FK → `teams.id` | | | `date` | oui | date | Jour de présence (timezone org. à documenter en prod) | | `status` | oui | string | Voir §6 — `present`, `absent`, `half_day`, `validated` | | `hours_worked` | non | decimal, nullable | Heures si pertinent (ex. status `present` / `half_day`) | | `comment` | non | text, nullable | | | `created_by_user_id` | oui | bigint FK → `users.id` | **Saisie par un User connecté** | | `validated_by_user_id` | non | bigint nullable FK → `users.id` | Rempli quand le statut passe en contrôle validé côté métier | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **Unicité métier** : **`UNIQUE(organization_id, worker_id, project_id, date)`** — une seule ligne de présence par ouvrier, chantier et jour. **Index** : `(project_id, date)` ; `(worker_id, date)` ; `(created_by_user_id)` ; `(project_id, worker_id, date)` pour jointures liste. **Règles d’intégrité** : 1. **`organization_id`** : identique à celui du `project` et du `worker` (contrôle applicatif). 2. **Affectation** : à la création / mise à jour, le **worker** doit avoir un **`WorkerProjectAssignment`** considéré **actif** pour ce **`project_id`** à la **`date`** de la présence : `status` ∈ (`planned`, `active` selon règle produit — **recommandation MVP** : `active` uniquement) et `start_date` ≤ `date` ≤ `end_date` **ou** `end_date` null (affectation ouverte). Sinon **422** côté API. 3. **`team_id`** : si renseigné, la **Team** doit appartenir au **même** `project_id` ; idéalement le worker est rattaché à cette équipe sur la période (souplesse MVP possible en policy). **Lien avec la paie (`Worker.payment_mode`)** : - **`Attendance` n’alimente pas** le mode **`per_completed_work`** (réservé aux **`ProductionEntry`** validées). - **`daily`** : en préparation de **`PayrollEntry`**, compter les jours « ouvrés » ou « payants » à partir des lignes dont le statut et la sémantique métier le permettent (ex. `present` = 1 jour, `half_day` = 0,5 jour, `absent` = 0 sauf règle contractuelle — **à figer en règle métier** au moment du module Payroll). Les lignes **`validated`** peuvent être seules prises en compte si le produit impose une **clôture** avant paie. - **`hourly`** : même principe avec agrégation de **`hours_worked`** sur les lignes pertinentes (`present`, `half_day` avec heures saisies). Si `hours_worked` est null pour un ouvrier **hourly** en statut `present`, la policy peut refuser la validation ou appliquer une **durée par défaut** documentée (hors scope DATA_MODEL — trancher à l’implémentation Payroll). **Champ `hours_worked`** : - Pertinent surtout pour **`hourly`** ; peut rester `null` pour **`daily`** si la journée est traitée comme unité entière via **`status`** (`present` / `half_day` / `absent`). - Pour **`half_day`**, saisie d’**heures** possible (ex. 4 h) ou déduction fixe selon produit. --- ### 4.9 Need (besoin) | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `project_id` | oui | bigint FK → `projects.id` | | | `type` | oui | string | Catégorie de besoin (matériel, sous-traitance, etc.) — enum à lister en prod | | `title` | oui | string | | | `description` | oui | text | Détail du besoin ; chaîne vide autorisée en base si le produit le permet | | `priority` | oui | string | Voir §6 — `low`, `medium`, `high`, `critical` | | `status` | oui | string | Voir §6 — `submitted`, `in_progress`, `resolved`, `rejected` | | `created_by_user_id` | oui | bigint FK → `users.id` | | | `assigned_to_user_id` | non | bigint nullable FK → `users.id` | Responsable traitement | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **Index** : `(project_id, status)` ; `(assigned_to_user_id)` ; `(organization_id, project_id)`. --- ### 4.10 WorkItem (ouvrage suivi) | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `project_id` | oui | bigint FK → `projects.id` | | | `name` | oui | string | Libellé ouvrage (UI : « Ouvrage ») | | `description` | non | text, nullable | | | `category` | oui | string | Lot / discipline (enum ou libre selon produit) | | `status` | oui | string | Voir §6 | | `start_date` | non | date, nullable | | | `expected_end_date` | non | date, nullable | | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **Index** : `(project_id, status)` ; `(organization_id, project_id)`. **Hors MVP court** : hiérarchie parent/enfant entre ouvrages (lots) — peut s’ajouter via `parent_work_item_id` plus tard. --- ### 4.11 WorkProgress Historique d’**avancement chantier** sur un ouvrage (**suivi opérationnel**). | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `work_item_id` | oui | bigint FK → `work_items.id` | | | `date` | oui | date | Date de la mesure / saisie | | `progress_value` | oui | decimal | Valeur numérique (ex. % ou quantité) | | `unit` | oui | string | Ex. `percent`, `m2`, `units` — cohérent avec `progress_value` | | `comment` | non | text, nullable | | | `author_user_id` | oui | bigint FK → `users.id` | **User connecté** auteur de la saisie | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **Index** : `(work_item_id, date)` ; `(author_user_id)`. **Règle produit** : **`WorkProgress` n’est pas la source directe** des calculs de paie pour le mode **`per_completed_work`**. Pour la rémunération au rendu, utiliser **`ProductionEntry`** validée (§4.12, statuts §6.7). **MVP** : plusieurs lignes le **même jour** autorisées (ex. deux relevés) sauf décision produit contraire ; si une seule saisie/jour est requise, ajouter `UNIQUE(work_item_id, date)` en V2. --- ### 4.12 ProductionEntry Ligne de **production** rattachée à un **WorkItem**, saisie par un **User** et soumise à un **workflow de validation** distinct de l’avancement **`WorkProgress`**. Sert de **base de calcul** pour les ouvriers en **`payment_mode` = `per_completed_work`** une fois la ligne **validée** (voir §6.8). | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `project_id` | oui | bigint FK → `projects.id` | | | `work_item_id` | oui | bigint FK → `work_items.id` | Ouvrage / poste de coût concerné | | `worker_id` | non | bigint nullable FK → `workers.id` | Attribution directe à un ouvrier ; **au moins un** de `worker_id` ou `team_id` devra être renseigné (règle applicative MVP). | | `team_id` | non | bigint nullable FK → `teams.id` | Attribution à une équipe (répartition ou agrégat selon règle paie — à préciser à l’implémentation). | | `date` | oui | date | Date de la production (jour concerné) | | `quantity` | oui | decimal | Quantité réalisée | | `unit` | oui | string | Unité métier (ex. `m2`, `units`, `task`) — cohérente avec `quantity` | | `unit_rate` | non | decimal, nullable | Taux unitaire appliqué ou snapshot pour audit ; peut être dérivé de `Worker.base_rate` ou grille chantier | | `status` | oui | string | Workflow : voir §6.7 (`draft`, `submitted`, `validated`, `rejected`) | | `comment` | non | text, nullable | | | `created_by_user_id` | oui | bigint FK → `users.id` | Auteur de la création | | `validated_by_user_id` | non | bigint nullable FK → `users.id` | Validateur **métier / chantier** (pas confondre avec la validation **financière** des `PayrollEntry`) | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **Index** : `(project_id, date)` ; `(work_item_id, date)` ; `(worker_id, date)` ; `(organization_id, project_id)` ; `(status)`. **Règles** : cohérence `organization_id` avec `project`, `work_item`, `worker`, `team` ; le **worker** doit être **affecté** au **project** pour la période concernée (via `WorkerProjectAssignment`, sauf dérogation explicite produit). --- ### 4.13 PayrollEntry Ligne de **paie / rémunération** liée à un chantier et un ouvrier (données sensibles). | Champ | Obligatoire | Type | Notes | |-------|-------------|------|--------| | `id` | oui | bigint PK | | | `organization_id` | oui | bigint FK → `organizations.id` | | | `project_id` | oui | bigint FK → `projects.id` | | | `worker_id` | oui | bigint FK → `workers.id` | | | `period_start` | oui | date | | | `period_end` | oui | date | >= `period_start` | | `payment_mode` | oui | string | Aligné sur **`Worker.payment_mode`** : `daily`, `hourly`, `per_completed_work` | | `calculation_base` | oui | decimal | Base utilisée pour le calcul (ex. jours, heures, quantité au rendu agrégée) | | `gross_amount` | oui | decimal | | | `adjustment_amount` | non | decimal, nullable | Ajustement (+/-) | | `adjustment_reason` | non | text, nullable | Obligatoire en UI si `adjustment_amount` non null | | `final_amount` | oui | decimal | Montant final | | `status` | oui | string | Voir §6.6 | | `prepared_by_user_id` | oui | bigint FK → `users.id` | | | `validated_by_user_id` | non | bigint nullable FK → `users.id` | Validateur **financier** (transitions `validated` / `paid` — voir `ROLE_PERMISSIONS.md`) | | `paid_at` | non | datetime nullable | Date / heure effective du paiement | | `created_at` | oui | timestamp | | | `updated_at` | oui | timestamp | | **Source de calcul (MVP)** — selon `Worker.payment_mode` : | `payment_mode` | Source métier pour préparer / expliquer la ligne | |----------------|--------------------------------------------------| | `daily` | Agrégation des **`Attendance`** (jours présents / équivalents) sur la période. | | `hourly` | Agrégation des **`Attendance`** avec **heures** (`hours_worked`) sur la période. | | `per_completed_work` | Agrégation des **`ProductionEntry`** au statut **`validated`** (quantités × taux), **sans** lire directement **`WorkProgress`**. | **Évolution** : un champ optionnel futur **`source_type`** (ex. `attendance`, `production_entry`, `manual`) sur `payroll_entries` pourrait tracer la **provenance** principale de la ligne et faciliter les audits ; non obligatoire au premier jet MVP. **Index** : `(project_id, worker_id, period_start)` ; `(worker_id, status)` ; `(organization_id, status)`. **Unicité recommandée** : une entrée **non annulée** par couple `(organization_id, project_id, worker_id, period_start, period_end)` — à implémenter en validation ou index partiel si le SGBD le permet ; sinon contrôle applicatif strict. --- ## 5. Relations entre entités (récapitulatif) | De | Vers | Cardinalité | Nom de relation (indicatif) | |----|------|-------------|------------------------------| | Organization | User | 1 — n | `users()` | | Organization | Project | 1 — n | `projects()` | | Organization | Worker | 1 — n | `workers()` | | Project | User | n — n via `user_project_access` | membres autorisés | | Project | User | n — 1 optionnel | `siteManager` (`site_manager_user_id`) | | Project | Team | 1 — n | `teams()` | | Project | Worker | n — n via `worker_project_assignments` | affectations | | Team | Worker | n — 1 optionnel | `leader` (`leader_worker_id`) | | Worker | Project | n — n via `worker_project_assignments` | | | Team | Worker | n — n implicite via assignment `team_id` ou logique métier | | | Project | Attendance | 1 — n | | | Worker | Attendance | 1 — n | | | User | Attendance | 1 — n (création / validation) | `createdBy`, `validatedBy` | | Project | Need | 1 — n | | | User | Need | 1 — n | créateur, assigné | | Project | WorkItem | 1 — n | | | WorkItem | WorkProgress | 1 — n | | | User | WorkProgress | 1 — n | auteur | | Project | ProductionEntry | 1 — n | | | WorkItem | ProductionEntry | 1 — n | | | Worker | ProductionEntry | 1 — n | optionnel (attribution) | | Team | ProductionEntry | 1 — n | optionnel (attribution) | | User | ProductionEntry | 1 — n | créateur, validateur métier | | Project | PayrollEntry | 1 — n | | | Worker | PayrollEntry | 1 — n | | --- ## 6. Statuts métier (valeurs stockées) ### 6.1 `projects.status` | Valeur | Signification indicatif | |--------|-------------------------| | `preparation` | Chantier en préparation, pas encore opérationnel | | `active` | En cours | | `suspended` | Arrêt temporaire | | `completed` | Chantier terminé (lecture / archivage selon policy) | ### 6.2 `attendances.status` Les quatre valeurs coexistent **dans la même colonne** en MVP ; **`validated`** joue à la fois un rôle de **type de journée** affiché et de **marqueur de workflow** (voir ci-dessous). | Valeur | Sémantique MVP (type de journée) | Usage indicatif paie `daily` / `hourly` | |--------|-----------------------------------|----------------------------------------| | `present` | Journée travaillée complète | 1 unité jour ou somme des **`hours_worked`** | | `absent` | Absence | 0 (sauf règle RH contractuelle hors MVP logiciel) | | `half_day` | Demi-journée | 0,5 jour ou **`hours_worked`** si renseigné | | `validated` | **Journée validée par le management** (chef / entrepreneur) — la ligne est considérée **figée** pour le contrôle et, selon règle produit, **seule base** retenue pour l’agrégat paie | Même logique de calcul que les autres statuts **une fois** la règle d’inclusion Payroll définie (souvent : n’agréger que les lignes `validated`, ou agréger tout sauf brouillon — **à trancher avec `API_SPEC.md` / module Payroll**) | **Sémantique détaillée de `validated`** : - Indique que **`chef_chantier`** ou **`entrepreneur_admin`** a **validé** la fiche (champ **`validated_by_user_id`**, horodatage implicite via `updated_at` ou colonne dédiée V2). - **`contremaitre_technicien`** **ne peut pas** appliquer ce statut en MVP (`ROLE_PERMISSIONS.md`). - Après validation, la **modification** peut être **interdite** ou **réservée** aux mêmes rôles que la validation (policy Laravel) pour éviter les écarts post-paie. **Évolution V2 recommandée** : séparer **`day_type`** (`present` \| `absent` \| `half_day`) et **`validation_status`** (`pending` \| `validated` \| `rejected`) pour éviter l’ambiguïté entre « type de journée » et « étape workflow ». Tant que le MVP garde un seul champ, l’UI et l’API doivent **documenter** clairement qu’une ligne `validated` est une **journée validée**, pas une simple présence partielle. **Rappel** : **`Attendance`** ne sert **pas** au mode de paie **`per_completed_work`**. ### 6.3 `needs.priority` `low` · `medium` · `high` · `critical` ### 6.4 `needs.status` | Valeur | Signification | |--------|----------------| | `submitted` | Soumis | | `in_progress` | En traitement | | `resolved` | Résolu | | `rejected` | Rejeté | ### 6.5 `work_items.status` | Valeur | Signification | |--------|----------------| | `not_started` | Pas démarré | | `in_progress` | En cours | | `completed` | Terminé | | `validated` | Validé | | `suspended` | Suspendu | ### 6.6 `payroll_entries.status` | Valeur | Signification | |--------|----------------| | `draft` | Brouillon | | `ready_for_validation` | Prêt pour validation **financière** | | `validated` | Validé **financièrement** | | `paid` | Payé | | `cancelled` | Annulé | **Note** : la validation d’une **`ProductionEntry`** (§6.7) est un **circuit chantier / technique** ; la validation d’une **`PayrollEntry`** est un **circuit financier** — à ne pas fusionner dans l’UI ni dans les policies. ### 6.7 `production_entries.status` | Valeur | Signification | |--------|---------------| | `draft` | Brouillon — saisie / correction par les rôles autorisés (`production_entries.manage`). | | `submitted` | Soumis — en attente de **validation chantier** (ex. chef de chantier). | | `validated` | Validé métier — **éligible** pour l’agrégation vers la paie **`per_completed_work`**. | | `rejected` | Rejeté — **exclu** du calcul paie ; peut être corrigé et resoumis selon règles produit. | ### 6.8 Autres enums à figer (MVP) | Entité / champ | Valeurs suggérées (à valider produit) | |----------------|----------------------------------------| | `organizations.status` | ex. `active`, `suspended` | | `worker_project_assignments.status` | ex. `planned`, `active`, `ended`, `cancelled` | | `needs.type` | selon métiers Coffra | | `work_items.category` | selon métiers Coffra | --- ## 7. Contraintes et règles de cohérence ### 7.1 Isolation tenant - Toute requête métier : **`WHERE organization_id = :current_user_org`** (ou équivalent scope Eloquent). - Toute création : `organization_id` **forcé** depuis le User authentifié, jamais depuis un champ client non contrôlé. ### 7.2 Isolation projet - Lecture / écriture sur une ressource rattachée à un `project_id` : le User doit avoir une ligne **`UserProjectAccess`** correspondante, **sauf** règle explicite pour `entrepreneur_admin` (ex. accès à tous les projets de l’org. — **à implémenter clairement dans les policies**). ### 7.3 Cohérence `organization_id` sur tables liées Pour `user_project_access`, `teams`, `worker_project_assignments`, `attendances`, `needs`, `work_items`, `work_progress`, `production_entries`, `payroll_entries` : - `organization_id` doit correspondre à l’organisation du **projet** et du **user** / **worker** concernés (contrôles en couche service ou événements Eloquent). ### 7.4 User vs Worker - Pas de `password` sur `workers`. - Pas de login Worker dans le MVP. - Lier un jour un `User` à un `Worker` (même personne physique) : **hors MVP** (colonne `user_id` nullable sur `workers` ou table de lien — évolution future). ### 7.5 Traçabilité - Présences, besoins, avancement ouvrage, paie : conserver un **User** auteur / préparateur / validateur lorsque le champ existe. - Ne pas supprimer en dur les User ayant des références : préférer **désactivation** (`is_active = false`) ou anonymisation encadrée plus tard. ### 7.6 Unicités et index clés | Table | Contrainte | |-------|------------| | `organizations` | `UNIQUE(slug)` | | `users` | `UNIQUE(organization_id, email)` (recommandé) | | `user_project_access` | `UNIQUE(organization_id, user_id, project_id)` | | `attendances` | `UNIQUE(organization_id, worker_id, project_id, date)` | | `worker_project_assignments` | Pas d’unicité simple si historique de périodes ; contrôle « une seule active » en code | ### 7.7 Abilities vs données Les abilities (`attendances.manage`, `production_entries.manage`, etc.) indiquent **quel type d’action** le rôle peut entreprendre. Chaque endpoint doit quand même vérifier **org + projet + statut de la ressource** (ex. ne pas modifier une `PayrollEntry` `paid` sans règle métier explicite ; ne pas traiter une `ProductionEntry` `rejected` comme base paie). --- ## 8. Éléments hors MVP / évolutions futures **Hors MVP (non décrit en détail ici)** : - Compte **ouvrier connecté** ou application terrain dédiée. - **`platform_admin`** multi-tenant global. - Hiérarchie **lots / sous-lots** sur `work_items` (`parent_id`). - **Pièces jointes** (photos, PDF) sur besoins, présences, paie. - **Notifications** push / e-mail, files d’attente. - **Multi-devises**, **export comptable** structuré. - **Soft delete** généralisé et corbeille — à décider entité par entité. - **Historisation** fine (tables d’audit) pour la paie et les validations. - Lien **`workers.user_id`** pour même personne physique avec compte + fiche. **Écarts possibles avec le code actuel du dépôt** : - Table `users` peut encore avoir `name` au lieu de `full_name`, sans `phone` / `is_active` / unicité e-mail par org. : **migration de convergence** prévue vers ce document. - Tables métier (`projects`, `attendances`, …) : **à créer** selon ce modèle. **Rappel** : ce document est la **référence cible MVP** ; il doit rester aligné avec `ARCHITECTURE.md`, `ROLE_PERMISSIONS.md` et `API_SPEC.md` lors des évolutions.