Files
3d-pricing/HANDOFF.md
T
2026-07-16 14:54:13 +02:00

208 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | — |