# Handoff — 3D Pricing App Ce document permet de reprendre le développement dans un nouveau contexte de conversation. Lire aussi `GUIDE.md` pour la documentation fonctionnelle complète. --- ## État du projet (07/07/2026) Application **en production** sur le réseau local. Tous les bugs critiques connus sont résolus. ### Stack - Python 3.12 / Flask / SQLite (WAL mode) - Bootstrap 5 + vis.js Timeline - Docker (port 5010 → 5000 interne) - Données persistantes dans volume Docker `/data/pricing.db` ### Dépôt ``` C:\Users\jbper\Claude\3d-pricing\ ├── app.py # ~2557 lignes — backend complet ├── templates/ # 27 templates Jinja2 ├── GUIDE.md # Documentation fonctionnelle ├── HANDOFF.md # Ce fichier ├── Dockerfile ├── docker-compose.yml └── requirements.txt # flask, authlib, requests ``` --- ## Règle absolue : ne jamais éditer app.py avec l'outil Edit Le fichier `app.py` est sur un montage NTFS (Windows) — l'outil `Edit` tronque le fichier silencieusement. **Méthode obligatoire pour modifier app.py :** ```bash # Toujours passer par un script Python et écrire directement via le chemin bash python3 - << 'EOF' with open('/sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py', 'r') as f: content = f.read() content = content.replace(old, new, 1) with open('/sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py', 'w') as f: f.write(content) EOF ``` La même règle s'applique aux templates si besoin (mais ils sont plus petits, moins risqués). --- ## Migrations DB Les migrations sont dans `init_db()` sous forme de liste `migrations` avec `try/except` par commande. **Ne jamais supprimer une migration existante** — l'ordre est cumulatif. Dernières migrations ajoutées (à la fin de la liste) : ```python 'ALTER TABLE printers ADD COLUMN ha_entity_prefix TEXT DEFAULT ""' 'ALTER TABLE print_slots ADD COLUMN pieces_ignored INTEGER DEFAULT 0' 'ALTER TABLE print_slots ADD COLUMN pieces_override INTEGER DEFAULT NULL' 'ALTER TABLE calendar_blocks ADD COLUMN date_end TEXT DEFAULT NULL' 'ALTER TABLE jobs ADD COLUMN marge_pct_on_ht REAL DEFAULT 0' 'ALTER TABLE jobs ADD COLUMN source_nc_path TEXT DEFAULT ""' 'ALTER TABLE jobs ADD COLUMN meshy_enabled INTEGER DEFAULT 0' 'ALTER TABLE jobs ADD COLUMN meshy_credits_used INTEGER DEFAULT 0' 'ALTER TABLE jobs ADD COLUMN meshy_cost REAL DEFAULT 0' 'ALTER TABLE jobs ADD COLUMN meshy_margin_pct REAL DEFAULT 0' ``` --- ## Fonctionnalités clés et leur implémentation ### Moteur de pricing (`calc()`, ligne ~458) Calcul par pièce. `weight_g` et `print_time_s` = valeurs du **plateau complet** Bambu, divisées par `pieces_per_plate`. Les charges fiscales (cotisations, VFL, autres taxes) sont incluses dans le prix via `prix_ht_net = prix_ht / (1 - total_charges)`. ### Thread Home Assistant (`_ha_sync_once()`, ligne ~2103) - Poll toutes les N secondes (setting `ha_poll_interval`, défaut 60s) - **Connexions courtes par imprimante avec `isolation_level=None`** (autocommit) pour éviter `database is locked` - Entités surveillées : `sensor.{prefix}_etat_de_l_impression`, `binary_sensor.{prefix}_erreur_d_impression`, `sensor.{prefix}_objets_ignores`, `sensor.{prefix}_avancement_de_l_impression` ### Multi-plateau - `pieces_per_plate` sur le job = pièces par plateau physique - `order_qty` = quantité totale commandée - `total_plates_needed = ceil(order_qty / pieces_per_plate)` (division entière ceiling) - "À planifier" sur le planning : affiche les jobs tant que `scheduled_plates < total_plates_needed` - Compensation : si HA signale `pieces_ignored > 0` sur un slot, les autres slots du job affichent un badge amber pour override le nb de pièces ### Déjà imprimé (`already_done`) Dans `POST /api/slots/new` : si `already_done=true`, les checks capacité/overlap sont **sautés entièrement**, slot inséré avec `status='done'`. L'ordre du check est important : `already_done` est testé **avant** `_check_overlap` et `_check_capacity`. ### Gantt (`planning.html`, ligne ~366) ```javascript start: new Date(now.getTime() - 12 * 3600000), // -12h pour voir les impressions en cours end: new Date(now.getTime() + 14 * 86400000), ``` ### Indisponibilités par plage (`calendar_blocks`) Colonne `date_end` optionnelle. Requêtes avec `COALESCE(date_end, date)` pour compatibilité ascendante. ### Duplication avec re-parse Nextcloud (`source_nc_path`) - Nouveau champ DB `source_nc_path` : chemin WebDAV complet du fichier `.3mf` - À la création d'un job depuis Nextcloud, le path est sauvegardé dans un hidden input - Au chargement de `new_job` avec `clone_from`, `autoParseOnClone()` appelle `/api/nextcloud/parse?path=...` et reconstitue le sélecteur de plateaux ### Intégration Meshy.ai - Abonnement annuel + crédits mensuels configurables dans Paramètres (`meshy_annual_cost`, `meshy_monthly_credits`, `meshy_default_margin_pct`) - Coût brut : `meshy_raw = credits × (annual_cost / 12 / monthly_credits)` → va dans **cout_fixe** (comme l'électricité) - Marge Meshy : `meshy_margin = meshy_raw × meshy_margin_pct / 100` → va dans **total_marge** (partie variable) - La `gross_margin_pct` s'applique aussi sur `cout_fixe` qui contient `meshy_raw` : double levier intentionnel - Champ `meshy_margin_pct` saisi par job (défaut = `meshy_default_margin_pct` dans settings) - Colonne `meshy_cost` en DB = coût brut ; `meshy_margin_amount` = calculé à la volée (`meshy_cost × meshy_margin_pct / 100`) - Dans `job_detail.html` : `meshy_cost` affiché dans section Cout fixe, `meshy_margin_amount` dans Partie variable --- ## Bugs résolus dans cette session | Bug | Fix | |---|---| | `database is locked` lors de la création de job | WAL mode + connexions autocommit par imprimante dans le thread HA | | Slot HA-synced invisible dans le Gantt | Gantt démarre 12h avant maintenant | | Vérification capacité bloquait "Déjà imprimé" | Check `already_done` déplacé avant `_check_capacity` | | Vérification capacité bloquait sur impressions en cours | Filtre `AND planned_end > datetime('now')` | | "À planifier" ne montrait qu'un seul plateau | Requête compare `scheduled_plates < total_plates_needed` | | `datetime.utcnow()` deprecated | Remplacé par `datetime.now(timezone.utc).replace(tzinfo=None)` | | INSERT jobs : 36 `?` pour 37 colonnes | Ajout du `?` manquant pour `marge_pct_on_ht` | | jobs.html tronqué après écriture Python | Toujours partir d'une version git saine avant modification | --- ## Points d'attention / dette technique ### Ce qui est propre - WAL mode SQLite : pas de blocage lecture/écriture - Thread HA : connexions courtes, autocommit, pas de transaction longue - Migrations cumulatives dans `init_db()` avec `try/except` par ligne - `login_required` décorateur + `require_login_global` pour protection globale si OIDC activé ### À surveiller - **jobs.html** : toujours utiliser le script Python pour modifier, vérifier la syntaxe Jinja2 après chaque modif avec `python3 -c "import jinja2; ..."` - **app.py** : après ajout de docstrings avec des accents, vérifier `python3 -m py_compile app.py` — les chaînes avec apostrophes dans les docstrings `"""` sont valides mais certaines substitutions de texte peuvent créer des conflits - **INSERT jobs** : 42 colonnes = 42 `?`. Si on ajoute une colonne, ajouter aussi le `?` ET la valeur dans le tuple ### Fonctionnalité pas encore implémentée - Recalcul de `marge_pct_on_ht` pour les jobs existants (valeur = 0 pour les anciens jobs) - Stats : le gaspillage filament (`total_wasted_g`) n'est pas encore affiché dans les stats globales - Page mobile : n'enregistre pas `source_nc_path` (pas critique) --- ## Commandes utiles ```bash # Vérifier la syntaxe Python python3 -m py_compile /sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py # Vérifier la syntaxe Jinja2 d'un template python3 -c " import jinja2 env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates')) env.parse(open('templates/jobs.html').read()) print('OK') " # Voir les routes Flask grep -n "^@app.route" /sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py # Chercher dans app.py sans tronquer grep -n "mot_clé" /sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py ``` --- ## Git Le dépôt est sur Windows — les commandes git **doivent être exécutées depuis un terminal Windows**, pas depuis le shell Linux (le fichier `git/index.lock` bloque les commits depuis Linux). ```powershell cd C:\Users\jbper\Claude\3d-pricing git add -A git commit -m "description" ``` Derniers commits : ``` (à committer) feat: Meshy.ai — raw cost in cout_fixe, margin in variable, per-job margin % 3da19f5 feat: auto-reparse Nextcloud 3mf on job clone 0069ab2 fix: INSERT jobs missing ? for marge_pct_on_ht 80307ac feat: plate_name and total_marge in jobs list f51a789 fix: HA sync per-printer autocommit connections 522a0c5 fix: WAL mode SQLite b55edce fix: skip capacity/overlap when already_done=true 7824124 fix: replace deprecated utcnow() 1899165 fix: gantt starts 12h before now ``` --- ## Chemins importants | Quoi | Chemin Windows | Chemin Linux (bash) | |---|---|---| | Dossier projet | `C:\Users\jbper\Claude\3d-pricing\` | `/sessions/festive-sharp-bardeen/mnt/3d-pricing/` | | app.py | `C:\Users\jbper\Claude\3d-pricing\app.py` | `/sessions/festive-sharp-bardeen/mnt/3d-pricing/app.py` | | Templates | `C:\Users\jbper\Claude\3d-pricing\templates\` | `/sessions/festive-sharp-bardeen/mnt/3d-pricing/templates/` | | Base de données | Dans le container Docker : `/data/pricing.db` | — |