feat(acte): barème sous-traitant — transport/jour + FTTH 250m+ (base + $/m)

- Ajout acte 'transport' (Tarif transport, 100$/jour, unité qte) ; retrait de
  l'ancien 'tarif_horaire + transport' combiné (remplacé par transport + perte_temps).
- amountOf : palier « FTTH 250 m + » = base 150-250 m (120$) + 1,20$/m au-delà de 250 m
  (l'input mètres = distance TOTALE).
- Prix du barème global saisis via /acte/rates (35/26/26/14/29/19/21/60/80/120/…).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-02 09:04:11 -04:00
parent 453bef006c
commit 83338ca57b

View File

@ -30,8 +30,8 @@ const SEED = [
['conduit', 'Conduit (m)', 'metres', null, null], ['conduit', 'Conduit (m)', 'metres', null, null],
['demarcation', 'Démarcation Fibre', 'qte', null, null], ['demarcation', 'Démarcation Fibre', 'qte', null, null],
['connecteur', 'Connecteur Fibre', 'qte', null, null], ['connecteur', 'Connecteur Fibre', 'qte', null, null],
['tarif_horaire', 'Tarif horaire + transport (h)', 'heures', null, null], ['transport', 'Tarif transport (jour)', 'qte', null, null], // 100$/jour — remplace l'ancien « Tarif horaire + transport »
['perte_temps', 'Tarif horaire perte de temps (h)', 'heures', null, null], ['perte_temps', 'Tarif horaire perte de temps (h)', 'heures', null, null], // 70$/h — À VALIDER/APPROUVER par TARGO (flux d'approbation existant)
] ]
const DEFAULT_CHECKED = ['accueil'] // « Accueil » presque toujours coché par défaut (temps client + déplacement) const DEFAULT_CHECKED = ['accueil'] // « Accueil » presque toujours coché par défaut (temps client + déplacement)
// Groupes d'options (config code, PAS de colonne DB) : 'seg' = segmenté mutuellement exclusif (FTTH, on en choisit 1) ; // Groupes d'options (config code, PAS de colonne DB) : 'seg' = segmenté mutuellement exclusif (FTTH, on en choisit 1) ;
@ -78,6 +78,7 @@ async function ensure () {
// ON CONFLICT DO UPDATE : met à jour grp/implies/sort (structure) SANS écraser price/label/unit (édités par le staff) // ON CONFLICT DO UPDATE : met à jour grp/implies/sort (structure) SANS écraser price/label/unit (édités par le staff)
for (let i = 0; i < SEED.length; i++) { const code = SEED[i][0], label = SEED[i][1], unit = SEED[i][2], grp = SEED[i][3], implies = SEED[i][4], sort = (i + 1) * 10; await p.query('INSERT INTO subcontractor.rate_card(code,label,unit,price,sort,grp,implies) VALUES($1,$2,$3,0,$4,$5,$6) ON CONFLICT (code) DO UPDATE SET grp=EXCLUDED.grp, implies=EXCLUDED.implies, sort=EXCLUDED.sort', [code, label, unit, sort, grp, implies]) } for (let i = 0; i < SEED.length; i++) { const code = SEED[i][0], label = SEED[i][1], unit = SEED[i][2], grp = SEED[i][3], implies = SEED[i][4], sort = (i + 1) * 10; await p.query('INSERT INTO subcontractor.rate_card(code,label,unit,price,sort,grp,implies) VALUES($1,$2,$3,0,$4,$5,$6) ON CONFLICT (code) DO UPDATE SET grp=EXCLUDED.grp, implies=EXCLUDED.implies, sort=EXCLUDED.sort', [code, label, unit, sort, grp, implies]) }
await p.query("UPDATE subcontractor.rate_card SET label='FTTH Demie' WHERE code='demi_drop' AND label='Demi Drop Fibre'") // renommage ponctuel (le DO UPDATE ne touche pas au libellé ; conditionnel = ne clobbe pas une édition admin) await p.query("UPDATE subcontractor.rate_card SET label='FTTH Demie' WHERE code='demi_drop' AND label='Demi Drop Fibre'") // renommage ponctuel (le DO UPDATE ne touche pas au libellé ; conditionnel = ne clobbe pas une édition admin)
await p.query("DELETE FROM subcontractor.rate_card WHERE code='tarif_horaire'") // remplacé par 'transport' (jour) + 'perte_temps' (h) — n'affecte pas les soumissions passées (montant figé à la soumission)
return true return true
})().catch(e => { log('subcontractor.ensure:', e.message); _ready = null; throw e }) })().catch(e => { log('subcontractor.ensure:', e.message); _ready = null; throw e })
return _ready return _ready
@ -108,7 +109,13 @@ async function getRatesForSub (subName) {
} }
function amountOf (acts, rates) { function amountOf (acts, rates) {
let amt = 0 let amt = 0
for (const r of rates) { const v = acts ? acts[r.code] : null; if (r.unit === 'case') { if (v === true || v === 1 || v === '1') amt += +r.price } else { const q = Number(v) || 0; if (q > 0) amt += +r.price * q } } const base250 = +((rates.find(x => x.code === 'ftth_150_250') || {}).price) || 0 // base du palier « 250 m + » = tarif 150-250 m
for (const r of rates) {
const v = acts ? acts[r.code] : null
if (r.code === 'ftth_250_plus') { const q = Number(v) || 0; if (q > 0) amt += base250 + (+r.price) * Math.max(0, q - 250) } // 120$ + 1,20$/m au-delà de 250 m (q = distance TOTALE en m)
else if (r.unit === 'case') { if (v === true || v === 1 || v === '1') amt += +r.price }
else { const q = Number(v) || 0; if (q > 0) amt += +r.price * q }
}
return Math.round(amt * 100) / 100 return Math.round(amt * 100) / 100
} }
async function context (ticket) { // libellé du ticket pour la page : DJ lié → sujet/client/adresse async function context (ticket) { // libellé du ticket pour la page : DJ lié → sujet/client/adresse