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>
49 lines
2.1 KiB
JavaScript
49 lines
2.1 KiB
JavaScript
'use strict'
|
|
// Journal d'audit d'assignation des Dispatch Jobs : CHAQUE changement horodaté (assigné / réassigné / reflété de F /
|
|
// retour au pool / complété / annulé), avec l'ACTEUR (courriel Authentik, ou « F » pour le legacy) et la SOURCE.
|
|
// Stockage append-only JSONL dans /app/data (bind-monté → persiste, comme user_prefs.json). Zéro schéma ERPNext.
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const { log } = require('./helpers')
|
|
|
|
const DIR = '/app/data'
|
|
const FILE = path.join(DIR, 'dispatch_audit.jsonl')
|
|
|
|
// Enregistre un événement d'assignation. Fire-and-forget : ne bloque JAMAIS la synchro même si l'écriture échoue.
|
|
function record (entry) {
|
|
try {
|
|
if (!entry || !entry.job) return
|
|
const line = JSON.stringify({
|
|
ts: new Date().toISOString(),
|
|
job: String(entry.job),
|
|
ticket: String(entry.ticket || ''),
|
|
event: entry.event || 'change', // assigned | reassigned | mirror-assigned | returned-to-pool | completed | cancelled | backfill
|
|
from: entry.from || '',
|
|
to: entry.to || '',
|
|
actor: entry.actor || 'system', // courriel Authentik · 'F' (legacy) · 'system'
|
|
source: entry.source || '', // ops-push | F-watch | ops-return | backfill
|
|
}) + '\n'
|
|
try { fs.mkdirSync(DIR, { recursive: true }) } catch (e) {}
|
|
fs.appendFile(FILE, line, () => {})
|
|
} catch (e) { try { log('dispatch-audit record error: ' + e.message) } catch (x) {} }
|
|
}
|
|
|
|
// Lecture filtrée (par job et/ou tech et/ou plage ISO), du plus RÉCENT au plus ancien, plafonnée à `limit`.
|
|
function read ({ job, tech, from, to, limit = 500 } = {}) {
|
|
let raw = ''
|
|
try { raw = fs.readFileSync(FILE, 'utf8') } catch (e) { return [] }
|
|
const lines = raw.split('\n'); const out = []
|
|
for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {
|
|
const l = lines[i].trim(); if (!l) continue
|
|
let e; try { e = JSON.parse(l) } catch (x) { continue }
|
|
if (job && e.job !== job) continue
|
|
if (tech && e.to !== tech && e.from !== tech) continue
|
|
if (from && e.ts < from) continue
|
|
if (to && e.ts > to) continue
|
|
out.push(e)
|
|
}
|
|
return out
|
|
}
|
|
|
|
module.exports = { record, read }
|