# Fix: Aramex waybill COD amount does not match the BL total ## Context The user reports that the **BL (Bon de Livraison)** shows the **correct** montant, but the **Aramex waybill** (the COD / `CashOnDeliveryAmount` value printed on the shipment label) shows the **wrong** montant for the same online order. The goal is to make the amount the courier collects (COD) equal the amount shown on the BL. ## Root cause The BL total and the Aramex COD are computed independently, from different quantity fields, via different code paths, with different rounding. They are therefore not guaranteed to agree. **BL total** — `frontend-POS/src/utils/printBLEnligne.js:326-414` ``` BL = Σ (PUAVR × (1 − promo/100) × qteValide) + fraisLivraison → toFixed(3) ``` - source quantity: **qteValide** **Aramex COD** — `Backend-Caisse-POS/controllers/commandes.js:1155-1295` (genererVenteEnligneCore) ``` sommes.mntdt = Σ (PUAVR × (1 − promo/100) × qtePointe) COD = fraisLivraison + sommes.mntdt → toFixed(2) ``` - source quantity: **qtePointe**, read as `parseFloat(ligne.qtePointe) || 0` ### Divergence vectors 1. **Quantity field mismatch** — BL = `qteValide`, Aramex = `qtePointe`. When pointage hasn't run / was partial / the frontend `orderDetails` snapshot still has `qtePointe = 0|null`, `sommes.mntdt` drops (down to 0), so the COD collapses toward **only the delivery fee**. 2. **Missing fallback on the direct generation path** — the auto-trigger path `generateVenteIfConfigured` (`commandes.js:1392-1401`) deliberately uses `COALESCE(NULLIF(qtePointe,0), NULLIF(qteValide,''), qteCommande)`, but the HTTP handler `genererVenteEnligne` (`commandes.js:1414-1425`, called by the "Générer la vente" button in `frontend-POS/src/pages/commandes/components/Lignes.jsx`) forwards the frontend `lignes` **as-is** with no fallback. Same order → different COD depending on the generation path. 3. **Rounding** — BL `toFixed(3)` vs Aramex `toFixed(2)` truncates/rounds millimes (TND has 3 decimals). Minor but real. 4. **Merged-orders path** — `unifierCommandes` (`commandes.js:2017-2028`) uses a THIRD formula: `SUM(montant)` from the `commandes` table + `MAX(fraisLivraison)`. The stored `montant` is written by the frontend (`Lignes.jsx` handleSaveArticles / confirmerCommande) **without** the delivery fee and using `qteValide`. Different basis again. ## Critical files - `Backend-Caisse-POS/controllers/commandes.js` — COD assembly (lines 1155-1171, 1287-1295), direct-path handler (1414-1425), auto path (1392-1410), merge path (2017-2028). - `frontend-POS/src/utils/printBLEnligne.js` — BL total (lines 312-414); the reference figure. - `frontend-POS/src/pages/commandes/components/Lignes.jsx` — calls `genererVenteEnligne` with in-memory `orderDetails` (carries possibly-stale `qtePointe`); also stores `montant`. - `Backend-Caisse-POS/utils/shipmentProviders/aramex.js:255-261` — maps COD value into payload. ## Confirmed decisions (from user) - **Waybill is generated automatically on a status change** → path = `applyConfiguredStockAction` → `generateVenteIfConfigured` → `genererVenteEnligneCore`. - **The COD must equal the BL "Total TTC" exactly** → it must be driven off **qteValide** (price after promo) + delivery fee, NOT off qtePointe. ### Why it diverges on this path `generateVenteIfConfigured` (`commandes.js:1392-1401`) feeds the line quantity as `COALESCE(NULLIF(qtePointe,0), NULLIF(qteValide,''), qteCommande)`. That **prefers qtePointe whenever it is > 0** — so a partial pointage, or a qtePointe left stale after the order's quantities were later edited, makes the COD reflect the picked/old quantity while the BL reflects the current qteValide. Result: BL correct, Aramex COD wrong. ## Proposed fix Compute the COD as a **separate figure from `qteValide`** that mirrors the BL summary exactly, and leave the fiscal invoice + stock movement on `qtePointe` (they legitimately reflect what physically shipped — do NOT repurpose `sommes.mntdt`). 1. **`genererVenteEnligneCore` (`commandes.js` ~1155-1295)** — after the existing `sommes` reduce (keep it untouched for the invoice/entete/stock), add a dedicated COD computation using the BL formula: ```js // COD mirrors the BL "Total TTC": validated quantities × price after promo + delivery fee. const codGoods = lignes.reduce((acc, ligne) => { const qteFacturee = parseFloat( ligne.qteValide != null && ligne.qteValide !== "" ? ligne.qteValide : ligne.qteCommande, ) || 0; return acc + ligne.PUAVR * (1 - (ligne.promo || 0) / 100) * qteFacturee; }, 0); const codValue = ((parseFloat(commande.fraisLivraison) || 0) + codGoods).toFixed(3); ``` Then set `cashOnDeliveryAmount.value: codValue` (replaces the `fraisLivraison + sommes.mntdt` / `.toFixed(2)` expression at lines 1289-1292). Use `.toFixed(3)` to match the BL (TND is a 3-decimal/millime currency). 2. **Auto path SELECT (`commandes.js:1392-1401`)** — the query currently exposes only the coalesced quantity aliased as `qtePointe`. Add the raw columns so the core can read them: `... , qteValide, qteCommande, COALESCE(NULLIF(qtePointe,0), NULLIF(qteValide,''), qteCommande) AS qtePointe, ...` (The direct HTTP path already carries `qteValide`/`qteCommande` on the frontend `orderDetails`.) 3. **Merged-orders path `unifierCommandes` (`commandes.js:2017-2028`)** — secondary (user's path is the auto one). For consistency, recompute the COD from each order's lines with the same qteValide formula + delivery fee instead of `SUM(montant)`. Confirm before changing, since it sums multiple orders. ### Scope note / consequence to confirm This makes the **COD = validated total (BL)** while the **fiscal invoice/entete stays on the picked total (qtePointe)**. In the normal case (picked == validated) they're identical. In a partial-pointage case they will intentionally differ — the customer is charged the full BL. If the invoice should also follow the BL, that's a follow-up (out of current scope). ## Verification 1. **Confirm the active vector on a reported order** — run: `SELECT codeProduit, qteCommande, qteValide, qtePointe, PUAVR, promo, PUAPR FROM lignesCommandes WHERE id = ?;` and `SELECT montant, fraisLivraison, numExpedition FROM commandes WHERE id = ?;` Expect to see at least one line where `qtePointe` ≠ `qteValide` (partial pointage, or a qtePointe left stale after a later quantity edit). Hand-compute: - BL total = `Σ PUAVR×(1−promo/100)×qteValide + fraisLivraison` - Old COD = `Σ PUAVR×(1−promo/100)×COALESCE(qtePointe,qteValide,qteCommande) + fraisLivraison` and verify Old COD matches the wrong figure on the Aramex waybill. 2. **After the fix**, regenerate a fresh test order through the normal status-change flow and confirm the Aramex `CashOnDeliveryAmount.Value` equals the BL "Total TTC" to the millime, for: (a) order where picked == validated, (b) order with a partial/edited pointage so `qtePointe ≠ qteValide` (the regression case), (c) merged orders if path 3 is applied. 3. Confirm the fiscal invoice (`entete.mntdt`) and stock movement are unchanged (still based on `qtePointe`) — only the COD value should change.