# Briefing — Continuation du projet ContractFlow

> **Contexte** : Ce fichier te donne tout le contexte nécessaire pour continuer le développement sans avoir besoin de l'historique de la conversation précédente.

---

## 1. Présentation du projet

**ContractFlow** — Application SaaS de gestion pour une agence de création de sites web vitrine (freelance / TPE). Elle permet de gérer les contrats clients et d'avoir une vue sur l'activité financière.

**Stack technique :**
- Laravel 13 (PHP 8.3)
- Livewire 4 (composants réactifs)
- Alpine.js (interactions UI légères)
- Tailwind CSS v4 (via plugin Vite, **pas de tailwind.config.js**)
- SQLite (base locale `database/database.sqlite`)
- Vite pour les assets

**Répertoire de travail :** `/home/bapt/Desktop/perso_ent/perso_ent`

**Commandes utiles :**
```bash
composer dev          # Démarre PHP + queue + Vite en parallèle
npm run build         # Build assets prod
php artisan migrate:fresh --seed   # Reset DB + seed
php artisan route:list            # Voir les routes
php -l <fichier>      # Vérifier syntaxe PHP
```

---

## 2. État actuel du code

### Routes (`routes/web.php`)
```
GET  /                             → DashboardController          [dashboard]
GET  /contracts                    → Livewire\ContractList         [contracts.index]
GET  /contracts/create             → ContractController@create     [contracts.create]
POST /contracts                    → ContractController@store      [contracts.store]
GET  /contracts/{contract}         → ContractController@show       [contracts.show]
PATCH /contracts/{contract}/status → ContractController@updateStatus [contracts.status]
POST /contracts/{contract}/upload  → ContractController@uploadPdf  [contracts.upload]
GET  /contracts/{contract}/download/{type} → ContractController@download [contracts.download]
DELETE /contracts/{contract}       → ContractController@destroy    [contracts.destroy]
```

### Modèle Contract (`app/Models/Contract.php`)
Champs en base :
- `id`, `name`, `client_name`
- `status` : enum string parmi `['En cours', 'En attente', 'Terminé', 'Annulé']`
- `start_date`, `end_date` (castés en `date`)
- `pdf_contract_path` (nullable) — stocké dans `storage/app/private/contracts/`
- `pdf_quote_path` (nullable)
- `notes` (nullable, text)
- `timestamps`

⚠️ **Il n'y a PAS de champ `amount` sur contracts pour l'instant** — à ajouter.

### Contrôleurs
- `DashboardController` : KPI basiques (total, en cours, expirent bientôt), 5 derniers contrats
- `ContractController` : CRUD complet + upload PDF + download + destroy

### Composants Livewire
- `ContractList` : liste paginée avec search + filtre statut (`$search`, `$statusFilter`, `$statuses`, `$contracts`)

### Vues Blade (toutes dans `resources/views/`)
```
components/layouts/app.blade.php     — Layout avec sidebar + header + flash messages
components/contract-status-badge.blade.php — Badge coloré par statut
dashboard.blade.php                  — KPI cards + table derniers contrats
livewire/contract-list.blade.php     — Liste filtrée
contracts/show.blade.php             — Détail + upload PDF + suppression
contracts/create.blade.php           — Formulaire création (centré, max-w-2xl mx-auto)
```

### Design system (à respecter impérativement)
Font : **Plus Jakarta Sans** (Google Fonts, weights 400/500/600/700)
Couleurs définies dans `resources/css/app.css` via `@theme` Tailwind v4 :
```css
--color-brand:      #F43F5E;   /* rose-500 — boutons CTA, active states */
--color-brand-600:  #E11D48;   /* hover des boutons brand */
--color-brand-100:  #FFE4E6;
--color-brand-50:   #FFF1F2;   /* hover rows, badges actifs */
--color-surface:    #F8FAFC;   /* background de page */
--color-border:     #E5E7EB;   /* bordures */
--color-text:       #0F172A;   /* texte principal */
--color-muted:      #64748B;   /* texte secondaire */
```
Usage dans les classes Tailwind : `bg-brand`, `text-brand-600`, `bg-surface`, `text-muted`, etc.

Sidebar : **blanche**, `w-56`, `border-r border-border`. Active state : `bg-brand-50 text-brand-600 font-semibold`.
Cards : `bg-white rounded-xl border border-border shadow-sm`
Inputs : `rounded-lg border border-border bg-surface px-3.5 py-2.5 text-sm focus:border-brand focus:ring-2 focus:ring-brand/20 focus:outline-none transition-all`
Bouton primaire : `bg-brand text-white hover:bg-brand-600 rounded-lg px-4 py-2.5 text-sm font-semibold transition-colors shadow-sm`

---

## 3. Nouvelles features à implémenter

### Vue d'ensemble

L'agence crée des **sites web vitrine** pour ses clients. Le workflow est :
1. Créer un **devis** (proposer services + tarifs)
2. Si accepté → générer un **contrat** lié au devis
3. **Générer des PDFs** pro pour devis et contrats
4. Suivre les **revenus** dans le dashboard

---

### Feature A — Modèle Quote (Devis)

#### Migration à créer
```php
Schema::create('quotes', function (Blueprint $table) {
    $table->id();
    $table->string('client_name');
    $table->string('client_email')->nullable();
    $table->string('client_address')->nullable();
    $table->string('quote_number')->unique(); // Ex: DEV-2026-001
    $table->json('line_items'); // voir structure ci-dessous
    $table->decimal('total_ht', 10, 2)->default(0);
    $table->decimal('tva_rate', 5, 2)->default(20.00); // % TVA (0 si micro-entrepreneur)
    $table->decimal('total_ttc', 10, 2)->default(0);
    $table->string('status')->default('Brouillon'); // Brouillon | Envoyé | Accepté | Refusé
    $table->date('valid_until')->nullable(); // Validité du devis (ex: 30 jours)
    $table->text('notes')->nullable();
    $table->foreignId('contract_id')->nullable()->constrained()->nullOnDelete(); // lié à un contrat si accepté
    $table->timestamps();
});
```

Structure d'un `line_item` (JSON) :
```json
{
  "description": "Création site vitrine 5 pages",
  "quantity": 1,
  "unit_price": 1500.00,
  "total": 1500.00
}
```

Prédéfinir des prestations typiques pour l'agence (utiliser comme suggestions dans le formulaire) :
- Création site vitrine (5 pages) — ~1 500 €
- Design / maquettes Figma — ~400 €
- Intégration CMS (WordPress/autre) — ~600 €
- SEO de base (balises, sitemap) — ~300 €
- Formation utilisation CMS (2h) — ~200 €
- Hébergement 1 an — ~120 €
- Maintenance mensuelle — ~80 €/mois
- Nom de domaine 1 an — ~15 €
- Rédaction contenu (par page) — ~150 €

#### Modèle Quote (`app/Models/Quote.php`)
```php
const STATUSES = ['Brouillon', 'Envoyé', 'Accepté', 'Refusé'];

protected $casts = [
    'line_items'  => 'array',
    'valid_until' => 'date',
    'total_ht'    => 'decimal:2',
    'tva_rate'    => 'decimal:2',
    'total_ttc'   => 'decimal:2',
];

// Relation avec le contrat associé
public function contract(): BelongsTo { ... }

// Calcule et met à jour les totaux depuis les line_items
public function recalculateTotals(): void {
    $ht = collect($this->line_items)->sum('total');
    $this->total_ht  = $ht;
    $this->total_ttc = $ht * (1 + $this->tva_rate / 100);
}

// Génère un numéro de devis unique
public static function generateNumber(): string {
    $year  = now()->year;
    $count = static::whereYear('created_at', $year)->count() + 1;
    return 'DEV-' . $year . '-' . str_pad($count, 3, '0', STR_PAD_LEFT);
}
```

#### Migration Contract — ajouter `amount`
Créer une migration additive sur la table `contracts` :
```php
$table->decimal('amount', 10, 2)->nullable(); // Montant TTC du contrat
$table->foreignId('quote_id')->nullable()->constrained('quotes')->nullOnDelete();
```

---

### Feature B — Génération PDF

#### Package à installer
```bash
composer require barryvdh/laravel-dompdf
```
Pas besoin de publier la config pour un usage basique.

#### Deux types de PDF à générer

**1. PDF Devis** (`/quotes/{quote}/pdf`)
Vue Blade : `resources/views/pdf/quote.blade.php`
Contenu :
- En-tête : logo/nom de l'agence à gauche, numéro de devis + date à droite
- Infos client (nom, email, adresse)
- Tableau des prestations : Description | Qté | Prix unitaire HT | Total HT
- Totaux : Total HT, TVA (X%), Total TTC
- Conditions : validité du devis, mentions légales (ex: "Devis valable 30 jours")
- Pied de page : coordonnées de l'agence

**2. PDF Contrat** (`/contracts/{contract}/pdf`)
Vue Blade : `resources/views/pdf/contract.blade.php`
Contenu :
- En-tête : "CONTRAT DE PRESTATION DE SERVICES"
- Parties : L'agence (prestataire) + client (nom, infos)
- Objet du contrat (name du contrat)
- Durée : start_date → end_date
- Montant : si lié à un devis, reprend le total TTC
- Clauses types :
  1. Objet
  2. Prestations incluses (si lié à un devis, liste les line_items)
  3. Modalités de paiement (ex: 30% acompte, 70% à la livraison)
  4. Propriété intellectuelle
  5. Confidentialité
  6. Résiliation
- Signatures : zone "Le prestataire" + "Le client" avec date

#### Méthode dans les controllers
Pour QuoteController :
```php
public function pdf(Quote $quote): Response
{
    $pdf = Pdf::loadView('pdf.quote', compact('quote'));
    return $pdf->download("devis-{$quote->quote_number}.pdf");
}
```
Pour ContractController (ajouter) :
```php
public function pdf(Contract $contract): Response
{
    $pdf = Pdf::loadView('pdf.contract', compact('contract'));
    return $pdf->download("contrat-{$contract->id}.pdf");
}
```

#### Style des PDFs
Les PDFs doivent être dans un style professionnel mais simple (DomPDF ne supporte pas Tailwind). Utiliser du CSS inline ou une balise `<style>` dans la vue Blade. Couleur d'accentuation : `#F43F5E` (brand rose) pour les titres/en-têtes de tableau. Police : Arial ou sans-serif (DomPDF a une liste limitée).

---

### Feature C — Interface de gestion des devis

#### Routes à ajouter
```
GET  /quotes                    → QuoteList (Livewire)      [quotes.index]
GET  /quotes/create             → QuoteController@create    [quotes.create]
POST /quotes                    → QuoteController@store     [quotes.store]
GET  /quotes/{quote}            → QuoteController@show      [quotes.show]
GET  /quotes/{quote}/edit       → QuoteController@edit      [quotes.edit]
PUT  /quotes/{quote}            → QuoteController@update    [quotes.update]
PATCH /quotes/{quote}/status    → QuoteController@updateStatus [quotes.status]
GET  /quotes/{quote}/pdf        → QuoteController@pdf       [quotes.pdf]
POST /quotes/{quote}/convert    → QuoteController@convertToContract [quotes.convert]
DELETE /quotes/{quote}          → QuoteController@destroy   [quotes.destroy]
```

#### Action clé : Convertir un devis en contrat
`QuoteController@convertToContract` :
- Crée un `Contract` avec : name = "Contrat — {quote->client_name}", client_name, status = 'En attente', start_date = today, end_date = today + 12 mois, amount = quote->total_ttc, quote_id = quote->id
- Met le devis en statut 'Accepté' et lie le contract_id
- Redirige vers le contrat créé avec flash 'success'

#### Formulaire de création de devis (create.blade.php)
Interface dynamique avec Alpine.js pour gérer les lignes de prestations :
- Champs en-tête : client_name, client_email, client_address, valid_until, tva_rate, notes
- Section line items : liste de lignes (description, quantité, prix unitaire)
  - Bouton "Ajouter une prestation"
  - Bouton "Supprimer" par ligne
  - Calcul automatique des totaux en temps réel avec Alpine
- Suggestions rapides : boutons cliquables pour les prestations types de l'agence (voir liste ci-dessus)
- Résumé : Total HT / TVA / Total TTC calculés dynamiquement

Exemple Alpine pour les line items :
```javascript
// x-data sur le formulaire
{
    items: [{ description: '', quantity: 1, unit_price: 0, total: 0 }],
    tva_rate: 20,
    get totalHT() { return this.items.reduce((s, i) => s + i.total, 0) },
    get totalTTC() { return this.totalHT * (1 + this.tva_rate / 100) },
    addItem(description = '', price = 0) {
        this.items.push({ description, quantity: 1, unit_price: price, total: price })
    },
    removeItem(index) { this.items.splice(index, 1) },
    updateTotal(item) { item.total = item.quantity * item.unit_price }
}
```

#### Ajouter "Devis" dans la sidebar
Dans `resources/views/components/layouts/app.blade.php`, ajouter un lien nav vers `quotes.index` entre "Tableau de bord" et "Contrats" (ou après), avec une icône de document chiffré.

---

### Feature D — Dashboard : revenus

#### DashboardController — nouvelles métriques
Ajouter ces calculs (utiliser la colonne `amount` des contracts) :

```php
// Chiffre d'affaires total (contrats "Terminé" ou "En cours" avec amount)
$caTotal = Contract::whereIn('status', ['En cours', 'Terminé'])
    ->whereNotNull('amount')
    ->sum('amount');

// CA du mois en cours
$caMois = Contract::whereIn('status', ['En cours', 'Terminé'])
    ->whereNotNull('amount')
    ->whereMonth('created_at', now()->month)
    ->whereYear('created_at', now()->year)
    ->sum('amount');

// Devis en attente (envoyés, pas encore acceptés)
$devisEnAttente = Quote::where('status', 'Envoyé')->count();
$montantEnAttente = Quote::where('status', 'Envoyé')->sum('total_ttc');
```

#### Nouveaux KPI cards sur le dashboard
Ajouter une deuxième rangée de cards sous les 3 existantes :

Card 1 — **CA Total** (contrats actifs/terminés) : nombre en vert, suffixe "€"
Card 2 — **CA ce mois** : nombre en brand rose
Card 3 — **Devis en attente** : nombre + montant total en petit en dessous

#### Section "Devis récents" sur le dashboard
Ajouter sous la table des contrats une petite table des 5 derniers devis avec : numéro, client, montant TTC, statut, bouton "Voir".

---

## 4. Fichiers à créer / modifier

### À créer
```
database/migrations/xxxx_create_quotes_table.php
database/migrations/xxxx_add_amount_quote_id_to_contracts_table.php
app/Models/Quote.php
app/Http/Controllers/QuoteController.php
app/Livewire/QuoteList.php
resources/views/quotes/create.blade.php
resources/views/quotes/show.blade.php
resources/views/quotes/edit.blade.php
resources/views/livewire/quote-list.blade.php
resources/views/pdf/quote.blade.php
resources/views/pdf/contract.blade.php
database/factories/QuoteFactory.php
database/seeders/QuoteSeeder.php
```

### À modifier
```
routes/web.php                          — Ajouter routes quotes + /contracts/{contract}/pdf
app/Models/Contract.php                 — Ajouter fillable amount + quote_id, relation quote()
app/Http/Controllers/ContractController.php — Ajouter méthode pdf()
app/Http/Controllers/DashboardController.php — Ajouter métriques CA
resources/views/dashboard.blade.php     — Nouveaux KPI + table devis récents
resources/views/components/layouts/app.blade.php — Lien Devis dans sidebar
database/seeders/DatabaseSeeder.php     — Appeler QuoteSeeder
```

---

## 5. Ordre d'implémentation recommandé

1. **Migrations** (quotes table + alter contracts)
2. **Modèles** (Quote + mise à jour Contract)
3. **QuoteController + routes**
4. **Vues quotes** (list, create, show) — respecter le design system rose/blanc
5. **PDF generation** (installer dompdf, vues pdf/quote.blade.php + pdf/contract.blade.php)
6. **Dashboard** (nouvelles métriques + KPI cards CA)
7. **Seeders** (générer des devis de test réalistes pour l'agence web)

---

## 6. Contexte métier important

L'utilisateur est un freelance / TPE qui crée des **sites web vitrine** pour des PME et artisans. Il est probablement en micro-entreprise (TVA non applicable selon seuil, mais prévoir le champ `tva_rate` avec 0% possible). Les montants typiques d'un projet :
- Site vitrine simple : 800 € – 2 000 €
- Site vitrine premium : 2 000 € – 5 000 €
- Maintenance annuelle : 500 € – 1 500 €

Les seeders doivent refléter ça (pas des contrats à 100 000 €).

---

## 7. Notes techniques importantes

- **Tailwind CSS v4** : pas de `tailwind.config.js`. Les classes custom (`bg-brand`, `text-muted`, etc.) sont définies dans `resources/css/app.css` via `@theme {}`. Pour les nouvelles vues, utiliser ces classes.
- **Livewire v4** : utiliser `#[Layout(...)]` et `#[Title(...)]` attributes sur les composants. `wire:model.live` pour la réactivité.
- **Alpine.js** : disponible via Livewire (inclus automatiquement). Pas besoin de l'importer séparément.
- **DomPDF** : les vues PDF doivent utiliser CSS inline (pas de Tailwind). Taille de page A4, orientation portrait.
- **SQLite** : base dans `database/database.sqlite`. Aucun service externe nécessaire.
- **Stockage PDFs uploadés** : `Storage::disk('local')` → `storage/app/private/contracts/`. Les PDFs générés sont streamés directement sans stockage.
- **Flash messages** : le layout gère `session('success')` et `session('error')`. Utiliser `->with('success', '...')` dans les redirections.
- Toujours valider PHP avec `php -l <fichier>` avant de déclarer une tâche terminée.
- Toujours rebuilder les assets avec `npm run build` après modification du CSS ou ajout de nouvelles classes.

---

*Fichier généré le 2026-04-27 — Projet ContractFlow*
