gigafibre-fsm/services/targo-hub/lib/billing-approvals.js
louispaulb 512c4a5f1b feat(ops): dispatch auto complet + perf Boîte/rapports + fix session
Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré /
juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis
par compétence + par job, carte des tournées (1 couleur/tech, domicile→arrêts,
sélecteur de jour), fenêtre de dispatch auj.+demain (dates sélectionnées), règle
week-end + placeholder « en attente du quart », clustering + lasso + filtre-date
sur la carte, accès rapide « À assigner » (badge).

Boîte : liste /conversations allégée (45 Mo → ~1 Mo, 17×) + messages chargés à
l'ouverture. Rapports : cache SWR sur revenue-explorer (22×). Session : keep-alive
+ timeout fetch global + authFetch durci → fin des rechargements manuels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:49:35 -04:00

62 lines
2.7 KiB
JavaScript

// P1 — Porte d'APPROBATION de facturation (chaîne installation → approbation → facturation).
// Quand le job terminal est complété (et que BILLING_APPROVAL_GATE=on), l'activation est placée
// « à approuver » au lieu d'activer/facturer directement. Un dispatcher/admin révise l'aperçu
// prorata (lecture seule) puis approuve → on ACTIVE les abonnements. La FACTURATION reste gérée
// par activateSubscriptionForJob (gatée PRORATION_WRITE) : en P1 (gate off) → active SANS facturer.
//
// Store : data/billing-approvals.json (même répertoire persistant que les autres données du hub).
const fs = require('fs')
const path = require('path')
const STORE = path.join(__dirname, '..', 'data', 'billing-approvals.json')
function _read () { try { return JSON.parse(fs.readFileSync(STORE, 'utf8')) } catch (_) { return {} } }
function _write (o) { fs.mkdirSync(path.dirname(STORE), { recursive: true }); fs.writeFileSync(STORE, JSON.stringify(o, null, 2)) }
// Appelé à la complétion du job terminal (gaté). Marque « à approuver ». N'ACTIVE PAS, NE FACTURE PAS.
// Idempotent : ne rétrograde jamais un job déjà approuvé.
function markPending (job, completed_at) {
if (!job) return null
const all = _read()
if (all[job] && all[job].status === 'approved') return all[job]
all[job] = {
job,
completed_at: completed_at || (all[job] && all[job].completed_at) || new Date().toISOString(),
status: 'pending',
approved_by: null,
approved_at: null,
}
_write(all)
return all[job]
}
function listPending () { return Object.values(_read()).filter(a => a.status === 'pending') }
function get (job) { return _read()[job] || null }
// Approbation : enregistre la décision PUIS active les abonnements via dispatch.activateSubscriptionForJob.
// La facturation (création de la facture consolidée) est gérée DANS activateSubscriptionForJob et reste
// DOUBLE-GATÉE (PRORATION_WRITE) → en P1 (gate off) : active SANS écrire de facture.
async function approve (job, approver) {
if (!job) throw new Error('job requis')
const all = _read()
const rec = all[job]
if (!rec) throw new Error('aucune approbation en attente pour ' + job)
if (rec.status === 'approved') return { ...rec, already: true }
const result = await require('./dispatch').activateSubscriptionForJob(job)
rec.status = 'approved'
rec.approved_by = approver || 'inconnu'
rec.approved_at = new Date().toISOString()
rec.activation = {
activated: result.activated || [],
invoice: result.invoice || null,
written: !!result.written,
prorated_total: result.prorated_total != null ? result.prorated_total : null,
}
_write(all)
return rec
}
module.exports = { markPending, listPending, get, approve }