# Status — 2026-04-18 (session 2) — ARCHIVED > **⚠️ ARCHIVED snapshot.** For current state, see [../../roadmap.md](../../roadmap.md). > Follow-up to [2026-04-18.md](2026-04-18.md). This session was the sales-flow sprint. ## What changed this session ### 1. Field scanner — offline resilience for weak-signal zones Photos that can't reach Gemini Vision within 8s (weak LTE / no service) are now queued in IndexedDB and retried in the background; late results are merged back into the scanner UI when they arrive. - `apps/field/src/stores/offline.js` — new `visionQueue`, `scanResults`, `enqueueVisionScan`, `syncVisionQueue`, `consumeScanResult`. Driven off queue length (not `navigator.onLine`, which lies in basements). - `apps/field/src/composables/useScanner.js` — `Promise.race` with 8s timeout, retryable errors enqueue, late arrivals dispatched through `onNewCode`. - `apps/field/src/pages/ScanPage.vue` — pending chip "N scan(s) en attente". ### 2. Sales flow — residential contract wiring The ProjectWizard now knows how to turn a quotation into the Service Contract (promotion-framing récap). The contract *is* the shopping-cart recap per your brief: "c'est un peu le récapitulatif d'un panier d'achat." **Entry from customer page:** ClientDetailPage → Soumissions section now has a "**+ Nouvelle soumission**" button that opens the wizard pre-filled with customer phone/email/primary service location. Wizard skips to `quotation` mode + `requireAcceptance=true` by default when launched from a customer. **Residential presets (one-click):** - Installation standard (Internet 500 + Routeur offert + Frais offerts, 24 mois) - Duo 300 + Tél (79.99$/mois, 24 mois, installation offerte) - Trio complet (119.99$/mois, 24 mois, routeur + installation offerts) - Internet seul (89.99$/mois, 12 mois) Presets live in `apps/ops/src/data/wizard-constants.js` → `RESIDENTIAL_PRESETS` — edit there to tune prices / add bundles. **Promo framing on one-time items:** each one-time line has a "Prix régulier (si promo)" field. When `regular_price > rate`, the line becomes a **benefit** on the Service Contract (e.g. Installation 75$ → 0$ = 75$ de promotion étalée sur 24 mois). Shown with celebration chip + line-through regular price in review. **What `publish()` now does on a residential quotation:** 1. Creates Quotation (as before). 2. Creates Service Contract via hub `/contract/create` with `duration_months = max(contract_months)`, `monthly_rate = sum(recurring)`, `benefits[] = onetime items where regular_price > rate`, `contract_type = 'Résidentiel'` (or `Commercial` when DocuSeal is selected). 3. Sends acceptance via `/contract/send` (promotion-framed récap, not the old Quotation `/accept/generate`) — résidentiel gets the "J'accepte ce récapitulatif" page; commercial gets DocuSeal. If no recurring commitment items exist, the old `/accept/generate` path is still used (unchanged). Direct-order / prepaid modes are untouched. **Success screen** shows the Service Contract name when one was created. ## Files touched this session ``` apps/field/src/stores/offline.js +vision queue apps/field/src/composables/useScanner.js +timeout/retry apps/field/src/pages/ScanPage.vue +pending chip apps/ops/src/composables/useWizardPublish.js +contract creation branch apps/ops/src/composables/useWizardCatalog.js +applyPreset apps/ops/src/data/wizard-constants.js +RESIDENTIAL_PRESETS apps/ops/src/components/shared/ProjectWizard.vue +presets, promo UI, customer prop apps/ops/src/pages/ClientDetailPage.vue +Nouvelle soumission button + wizard dialog docs/STATUS_2026-04-18.md scanner stack corrected (Gemini, not html5-qrcode) ``` ## Deployed - Ops app: `dist/pwa/*` → `/opt/ops-app/` at 2026-04-18 ~08:05 EDT. New bundle: `index.caa91ade.js`. Hard-reload in browser to pick up. - Field app: **not yet deployed**. Build + deploy when you want it live: `cd apps/field && quasar build && scp -r dist/spa/* root@96.125.196.67:/opt/field-app/` ## End-to-end smoke test done - `CTR-00001` for C-LPB4 (Louis-Paul Bourdon) created via manual hub call during the Service Contract prod-doctype setup. See `memory/project_contract_acceptance.md` for creation gotchas (Dispatch User role). - The wizard integration has **not been exercised end-to-end** yet — it compiles, UI renders, but please run one manual test on a real customer before announcing it. ## Suggested test path when you're back 1. Open ops → any customer → Soumissions → "+ Nouvelle soumission". 2. Pick "Duo 300 + Tél" preset. Set Frais d'installation prix régulier = 75$ (rate stays 0 → benefit of 75$). 3. Keep acceptation = JWT (Résidentiel). 4. Publish. Confirm: Quotation created + Service Contract created + SMS sent. 5. Open the SMS link on phone, click "J'accepte ce récapitulatif", verify contract goes Actif + signature_proof stored. ## Known follow-ups (deliberately deferred) - Wizard doesn't yet let the agent pick `Résidentiel` vs `Commercial` independently of the acceptance method. Today: JWT = Résidentiel, DocuSeal = Commercial. If you want a large commercial client on JWT (or vice-versa), add a `contract_type` toggle in the expansion panel. - Presets use hard-coded prices. Eventually these should come from the live catalog (ERPNext Item price list) — same place `loadCatalog()` reads from. - `/accept/send` (old) and `/contract/send` (new) are two parallel paths. When the residential flow is proven, consider deprecating `/accept/generate` for quotations with recurring commitments. - Two-column visual split (recurring | one-time) was mentioned — current design keeps them in one list with swap button + promo chip, which seemed to stay readable. Flag if you want the split.