# Note — Génération dynamique des fiches de suivi terrain (PDF)

## Décision
Fiche PDF imprimable (ex. C-006) générée **à la volée** par le serveur PHP, pas pré-générée en
statique. Bibliothèque retenue : **Dompdf** (pure PHP, aucun binaire système requis — compatible
hébergement mutualisé o2switch, contrairement à WeasyPrint qui dépend de Pango/Cairo).

## Prérequis : Composer n'existe pas encore dans `uluana_server`
C'est la première dépendance externe du projet back. À ajouter :
```bash
composer require dompdf/dompdf
```
Implique un `composer.json` + `vendor/` (à ajouter au `.gitignore`, install via cron/déploiement
comme n'importe quel projet Composer standard).

## CSS : déjà adapté pour compatibilité Dompdf
J'ai retiré les seuls usages de `display: flex` et `display: grid` dans
`claude/pipeline_cartes_pdf/card-guide.css` et `worksheet.css` (Dompdf ne les supporte pas
fiablement) :
- `.section-title` / `.section-num` : flex → inline-block (vérifié : rendu identique sous WeasyPrint)
- `.worksheet-header` : flex → float + clearfix
- `.field-grid` (CSS Grid) → `.field-table` (table HTML classique)

Le HTML (`worksheet_template.html`) a été mis à jour en conséquence (la grille de champs bilan
est maintenant une `<table>`). **Un seul jeu de CSS partagé entre WeasyPrint (pipeline fiche-guide
existant) et Dompdf (nouvelle fiche terrain)** — pas de duplication.

## Ce qui reste variable par carte : `checklist_json`
Chaque item de la checklist (`card_guide.checklist_json`) porte :
```json
{ "id": "chk2", "label": "...", "detail": "...", "printable_worksheet": true, "worksheet_section": "observation" }
```
`worksheet_section` détermine quel bloc afficher sur la fiche PDF (`"observation"` → tableau
d'observation, `"bilan"` → grille de champs chiffrés). Pas de contenu de fiche codé en dur par
carte : le PHP lit `checklist_json`, ne génère que les sections dont `printable_worksheet: true`.

## Premier jet de code (non testé — pas de PHP dans mon environnement)

`api/card_worksheet.php` (nouveau fichier, routé depuis `_core.php` comme les autres `api/*.php`) :

```php
<?php
/**
 * card_worksheet.php — Génère la fiche de suivi terrain imprimable (PDF) d'une carte.
 * GET /api/cards/{id}/worksheet.pdf
 *
 * Ne génère que les sections dont card_guide.checklist_json a printable_worksheet=true.
 * Le mapping worksheet_section -> bloc HTML est en dur ici (observation, bilan) ;
 * ajouter un nouveau bloc = ajouter un cas dans render_section().
 */
require_once '_core.php';
require_once __DIR__ . '/../vendor/autoload.php';

use Dompdf\Dompdf;
use Dompdf\Options;

$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
preg_match('#(?:/api)?/cards/([^/]+)/worksheet\.pdf#', $path, $m);
$card_id = $m[1] ?? null;
if (!$card_id) error('card_id requis', 400);

if (method() !== 'GET') error('Méthode non supportée', 405);

$pdo = db();

$stmt = $pdo->prepare("
    SELECT c.id, c.title, cg.titre AS guide_titre, cg.checklist_json
    FROM cards c
    LEFT JOIN card_guide cg ON cg.card_id = c.id
    WHERE c.id = :id LIMIT 1
");
$stmt->execute([':id' => $card_id]);
$card = $stmt->fetch();
if (!$card) error('Carte non trouvée', 404);

$checklist = json_decode($card['checklist_json'] ?? '[]', true) ?: [];
$printableItems = array_filter($checklist, fn($item) => !empty($item['printable_worksheet']));

if (!$printableItems) error('Cette carte n\'a pas de fiche imprimable', 404);

// ── Construction du HTML à partir du template partagé ──────────────────────
$templateDir = __DIR__ . '/../claude/pipeline_cartes_pdf';
$css1 = file_get_contents("$templateDir/card-guide.css");
$css2 = file_get_contents("$templateDir/worksheet.css");

function render_observation_section(): string {
    $rows = str_repeat(
        '<tr><td></td><td class="checkbox-cell"><span class="checkbox-box"></span></td>'
        . '<td class="checkbox-cell"><span class="checkbox-box"></span></td><td></td>'
        . '<td class="checkbox-cell"><span class="checkbox-box"></span></td></tr>',
        6
    );
    return <<<HTML
    <div class="section">
      <div class="section-title"><span class="section-num">1</span>Pendant le marché — observation</div>
      <table class="worksheet-table">
        <thead><tr><th>Pièce</th><th class="checkbox-cell">Regardée</th><th class="checkbox-cell">Touchée</th><th>Prix demandé ?</th><th class="checkbox-cell">Achetée</th></tr></thead>
        <tbody>{$rows}</tbody>
      </table>
    </div>
    HTML;
}

function render_bilan_section(): string {
    // Champs génériques — à terme, faire lire les labels de champs depuis
    // checklist_json plutôt qu'en dur, si d'autres cartes ont un bilan différent.
    return <<<HTML
    <div class="section">
      <div class="section-title"><span class="section-num">2</span>Après le marché — bilan chiffré</div>
      <table class="field-table">
        <tr>
          <td><div class="field-box"><div class="field-box-label">CA total <span class="field-box-unit">€</span></div><div class="field-box-line"></div></div></td>
          <td><div class="field-box"><div class="field-box-label">Nombre de clients</div><div class="field-box-line"></div></div></td>
        </tr>
        <tr>
          <td><div class="field-box"><div class="field-box-label">Panier moyen <span class="field-box-unit">€</span></div><div class="field-box-line"></div></div></td>
          <td><div class="field-box"><div class="field-box-label">Contacts collectés</div><div class="field-box-line"></div></div></td>
        </tr>
      </table>
      <div class="formula-strip">
        Rentabilité nette = CA total − coûts − (heures × ton taux horaire) = <span class="fill-line" style="min-width:90px;"></span>
      </div>
    </div>
    HTML;
}

$sectionsHtml = '';
$sectionNum = 0;
foreach ($printableItems as $item) {
    $sectionNum++;
    $sectionsHtml .= match ($item['worksheet_section'] ?? null) {
        'observation' => render_observation_section(),
        'bilan'       => render_bilan_section(),
        default       => '',
    };
}

$title = $card['guide_titre'] ?? $card['title'];
$html = <<<HTML
<!DOCTYPE html>
<html lang="fr">
<head><meta charset="UTF-8"><style>{$css1}\n{$css2}</style></head>
<body>
<div class="page">
  <div class="worksheet-header">
    <div class="worksheet-header-left">
      <div class="worksheet-header-title">Fiche de suivi — {$card['id']}</div>
      <div class="worksheet-header-sub">{$title}</div>
    </div>
    <div class="worksheet-fields">
      <div class="field-inline">Marché <span class="fill-line"></span></div>
      <div class="field-inline">Date <span class="fill-line" style="min-width:80px;"></span></div>
    </div>
  </div>
  {$sectionsHtml}
  <div class="worksheet-footer-note">
    Garde cette fiche avec toi pendant le marché. Ressaisis ensuite ces chiffres dans ton suivi
    d'impact sur l'appli — ça alimente ton évolution et, une fois anonymisé, la moyenne partagée
    avec les autres artisans.
  </div>
  <div class="brandmark">Uluana — la cohérence sans effort</div>
</div>
</body>
</html>
HTML;

$options = new Options();
$options->set('isRemoteEnabled', false); // pas de ressources externes, tout est inline
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();

header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="fiche-suivi-' . $card['id'] . '.pdf"');
echo $dompdf->output();
```

## Points à vérifier / compléter côté Claude Code
1. **Router** : ajouter la route `/api/cards/{id}/worksheet.pdf` dans `_core.php` ou l'équivalent
   (je ne connais pas le mécanisme de routage exact, à adapter à l'existant de `cards.php`).
2. **`render_bilan_section()` a des champs codés en dur** (CA, clients, panier moyen, contacts).
   Si le bilan diffère d'une carte à l'autre au-delà de C-006, il faudra faire porter la liste des
   champs par `checklist_json` plutôt qu'en dur dans cette fonction — pas fait ici pour rester
   simple sur le premier jet.
3. **Tester le rendu Dompdf réel** — je n'ai pas Dompdf ni PHP dans mon environnement, ce code n'a
   pas été exécuté. Le CSS a été validé visuellement sous WeasyPrint uniquement, qui est plus
   permissif que Dompdf sur certaines propriétés (`border-radius`, `box-shadow` notamment —
   Dompdf les supporte partiellement, à vérifier au premier rendu réel).
4. **Bouton front** : sur les items de checklist avec `printable_worksheet: true`, ajouter un
   lien/bouton "Imprimer la fiche" vers `GET /api/cards/{id}/worksheet.pdf`.
