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>
77 lines
3.9 KiB
JavaScript
77 lines
3.9 KiB
JavaScript
import { ref } from 'vue'
|
|
import { HUB_URL } from 'src/config/hub'
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// SLA côté Ops : charge les politiques (par FILE=issue_type + PRIORITÉ) depuis le hub,
|
|
// et calcule l'état d'un ticket (réponse d'abord tant que non répondu, puis résolution).
|
|
// const { slaFor, savePolicies, policies, defaults } = useSla()
|
|
// slaFor(ticket) → { state:'ok'|'at_risk'|'breached'|'met'|'none', kind, label, remainMins }
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
let _policies = null
|
|
let _defaults = {}
|
|
let _loadP = null
|
|
const policies = ref(null)
|
|
const defaults = ref({})
|
|
|
|
function loadPolicies () {
|
|
if (_policies) return Promise.resolve(_policies)
|
|
if (!_loadP) {
|
|
_loadP = fetch(`${HUB_URL}/sla/policies`).then(r => r.ok ? r.json() : {})
|
|
.then(d => { _policies = d.policies || {}; _defaults = d.defaults || {}; policies.value = _policies; defaults.value = _defaults; return _policies })
|
|
.catch(() => { _policies = {}; policies.value = {}; return {} })
|
|
}
|
|
return _loadP
|
|
}
|
|
|
|
async function savePolicies (pol) {
|
|
_policies = pol; policies.value = pol
|
|
const r = await fetch(`${HUB_URL}/sla/policies`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ policies: pol }) })
|
|
return r.ok
|
|
}
|
|
|
|
function policyFor (issueType, priority) {
|
|
const p = policies.value || _policies || {} // policies.value = ref réactif → recalcule quand les politiques se chargent
|
|
return (p[issueType] && p[issueType][priority]) || (p._default && p._default[priority]) || null
|
|
}
|
|
|
|
function fmt (mins) {
|
|
const a = Math.abs(Math.round(mins))
|
|
if (a < 60) return a + ' min'
|
|
if (a < 1440) { const h = Math.floor(a / 60); const m = a % 60; return h + ' h' + (m ? ' ' + m : '') }
|
|
const j = Math.floor(a / 1440); const h = Math.floor((a % 1440) / 60); return j + ' j' + (h ? ' ' + h + ' h' : '')
|
|
}
|
|
|
|
export function useSla () {
|
|
loadPolicies()
|
|
function slaFor (t) {
|
|
if (!t) return { state: 'none' }
|
|
const pol = policyFor(t.issue_type, t.priority)
|
|
if (!pol) return { state: 'none' }
|
|
const od = t.opening_date || t.creation
|
|
if (!od) return { state: 'none' }
|
|
const opening = new Date(String(od).length <= 10 ? od + 'T00:00:00' : od)
|
|
if (isNaN(opening.getTime())) return { state: 'none' }
|
|
if (['Resolved', 'Closed'].includes(t.status)) return { state: 'met', label: 'SLA clos' }
|
|
const now = Date.now()
|
|
const band = (remain, target, kind, verb) => {
|
|
if (remain < 0) return { state: 'breached', kind, label: verb + ' en retard ' + fmt(remain), remainMins: remain }
|
|
if (remain < target * 0.25) return { state: 'at_risk', kind, label: verb + ' dans ' + fmt(remain), remainMins: remain }
|
|
return { state: 'ok', kind, label: verb + ' dans ' + fmt(remain), remainMins: remain }
|
|
}
|
|
// Réponse (1ʳᵉ réponse) prioritaire tant que pas encore répondu
|
|
if (!t.first_responded_on && pol.response) return band((opening.getTime() + pol.response * 60000 - now) / 60000, pol.response, 'response', 'Réponse')
|
|
// Sinon résolution
|
|
if (pol.resolution) return band((opening.getTime() + pol.resolution * 60000 - now) / 60000, pol.resolution, 'resolution', 'Résolution')
|
|
return { state: 'none' }
|
|
}
|
|
return { slaFor, loadPolicies, savePolicies, policies, defaults }
|
|
}
|
|
|
|
// Couleurs de pastille par état (réutilisable).
|
|
export const SLA_BADGE = {
|
|
breached: { color: 'red-2', text: 'red-9', icon: 'error' },
|
|
at_risk: { color: 'orange-2', text: 'orange-9', icon: 'schedule' },
|
|
ok: { color: 'green-2', text: 'green-9', icon: 'schedule' },
|
|
met: { color: 'grey-3', text: 'grey-7', icon: 'check' },
|
|
}
|