diff --git a/services/targo-hub/lib/contracts.js b/services/targo-hub/lib/contracts.js index c19cec7..5affb5f 100644 --- a/services/targo-hub/lib/contracts.js +++ b/services/targo-hub/lib/contracts.js @@ -62,15 +62,21 @@ function calculateTerminationFee (contract) { termination_fee_total: fee, } } else { - // Résidentiel: only benefit residual value (+ current month max) - fee = totalBenefitRemaining + // Résidentiel (CRTC 2026-43) : AUCUN clawback de promotion. On ne facture + // que le SOLDE de l'installation financée — un service déjà rendu, donc + // facturable. Les promotions/carte-cadeau ne sont JAMAIS récupérées. + const installFee = contract.install_fee || 0 + const installRemaining = round2(installFee * monthsRemaining / (contract.duration_months || 24)) + fee = installRemaining feeBreakdown = { type: 'residential', months_remaining: monthsRemaining, monthly_rate: monthlyRate, - termination_fee_benefits: totalBenefitRemaining, - termination_fee_remaining: 0, - termination_fee_total: totalBenefitRemaining, + install_fee: installFee, + install_remaining: installRemaining, + termination_fee_benefits: 0, + termination_fee_remaining: installRemaining, + termination_fee_total: installRemaining, } } @@ -180,6 +186,7 @@ async function handle (req, res, method, path) { end_date: endDate, duration_months: duration, monthly_rate: body.monthly_rate || 0, + install_fee: body.install_fee || 0, service_location: body.service_location || '', quotation: body.quotation || '', subscription: body.subscription || '', @@ -878,15 +885,15 @@ async function createTerminationInvoice (contract, calc, reason) { income_account: '', // will use default }) } else { - // Residential: charge benefit residual - for (const b of calc.benefits) { - if (b.remaining_value <= 0) continue + // Résidentiel : solde de l'installation financée (service rendu) — PAS une pénalité. + if (calc.install_remaining > 0) { items.push({ - item_name: `Résiliation — ${b.description}`, - description: `Valeur résiduelle: ${b.benefit_value}$ - ${b.months_recognized} mois reconnus ` + - `(${b.monthly_recognition}$/mois). Restant: ${b.remaining_value}$`, + item_name: "Solde de l'installation (financement restant)", + description: `Installation ${calc.install_fee}$ financée sur ${contract.duration_months || 24} mois. ` + + `Solde restant après ${calc.months_elapsed} mois : ${calc.install_remaining}$. ` + + `Service déjà réalisé — aucune pénalité de résiliation, aucune promotion récupérée.`, qty: 1, - rate: b.remaining_value, + rate: calc.install_remaining, }) } } @@ -931,9 +938,8 @@ function renderAcceptancePage (contract, token) { const TARGO_LOGO_SVG = `` const isCommercial = contract.contract_type === 'Commercial' - const penaltyText = isCommercial - ? `la totalité des mensualités restantes au terme (${contract.monthly_rate || 0} $/mois × mois restants)` - : `la portion non étalée de la promotion, au prorata des mois restants sur ${duration} mois` + const installFee = contract.install_fee || 0 + const installMonthly = installFee ? round2(installFee / duration) : 0 return `
@@ -992,6 +998,7 @@ tfoot td{font-weight:700;color:#00733a;background:#f4fff6;border-top:1px solid #| Valeur totale de la promotion | ${round2(totalBenefit)} $ |
| Étalée sur ${duration} mois | |
| Valeur des promotions (non récupérables) | ${round2(totalBenefit)} $ |