Clic sur une case → touche « A » (bascule) ou menu « Marquer absent ce jour / Retirer l'absence » →
crée/supprime une Tech Availability d'1 jour APPROUVÉE (hachurée tout de suite). Multi-sélection
supportée (A marque toutes, re-A retire). Endpoint POST /roster/absence/set {tech,date,type,remove}
(remove ne touche que les absences d'un jour, pas les vacances multi-jours). Type défaut Congé.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
5.1 KiB
JavaScript
82 lines
5.1 KiB
JavaScript
/**
|
|
* Roster (Planification) API — appelle targo-hub /roster/*.
|
|
* Le backend lit les techs/modèles/besoins dans ERPNext facturation et appelle
|
|
* le solveur OR-Tools. Voir services/targo-hub/lib/roster.js.
|
|
*/
|
|
import { HUB_URL as HUB } from 'src/config/hub'
|
|
|
|
async function jget (path) {
|
|
const r = await fetch(HUB + path)
|
|
if (!r.ok) throw new Error('Roster API ' + r.status)
|
|
return r.json()
|
|
}
|
|
async function jpost (path, body) {
|
|
const r = await fetch(HUB + path, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body || {}),
|
|
})
|
|
if (!r.ok) throw new Error('Roster API ' + r.status)
|
|
return r.json()
|
|
}
|
|
async function jput (path, body) {
|
|
const r = await fetch(HUB + path, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) })
|
|
if (!r.ok) throw new Error('Roster API ' + r.status)
|
|
return r.json()
|
|
}
|
|
|
|
export const listTechnicians = () => jget('/roster/technicians')
|
|
export const listTemplates = () => jget('/roster/templates')
|
|
export const createTemplate = (t) => jpost('/roster/templates', t)
|
|
export const listRequirements = (start, days = 7) => jget(`/roster/requirements?start=${start}&days=${days}`)
|
|
export const createRequirement = (r) => jpost('/roster/requirements', r)
|
|
export const listAssignments = (start, days = 7) => jget(`/roster/assignments?start=${start}&days=${days}`)
|
|
export const getCoverage = (start, days = 7) => jget(`/roster/coverage?start=${start}&days=${days}`)
|
|
export const getStats = (start, days = 7) => jget(`/roster/stats?start=${start}&days=${days}`)
|
|
export const getOccupancy = (start, days = 7) => jget(`/roster/occupancy?start=${start}&days=${days}`)
|
|
export const getAbsences = (start, days = 7) => jget(`/roster/absences?start=${start}&days=${days}`)
|
|
export const applyGardeHorizon = (start, weeks, assignments, shifts) => jpost('/roster/garde/apply', { start, weeks, assignments, shifts })
|
|
export const setAbsence = (tech, date, type, remove) => jpost('/roster/absence/set', { tech, date, type, remove })
|
|
export const generate = (start, days = 7, weights) => jpost('/roster/generate', { start, days, weights })
|
|
export const publish = (assignments) => jpost('/roster/publish', { assignments })
|
|
export const publishWeek = (start, days, assignments, notify) => jpost('/roster/publish-week', { start, days, assignments, notify })
|
|
export const updateTemplate = (name, patch) => jput('/roster/template/' + encodeURIComponent(name), patch)
|
|
|
|
// ── Copilote (Gemini Flash) + politique de reprise ──
|
|
export const askAssistant = (message, history) => jpost('/roster/assistant', { message, history })
|
|
export const getPolicy = () => jget('/roster/policy')
|
|
export const savePolicy = (policy) => jpost('/roster/policy', policy)
|
|
export async function deleteShiftTemplate (name) {
|
|
const r = await fetch(HUB + '/roster/template/' + encodeURIComponent(name), { method: 'DELETE' })
|
|
if (!r.ok) throw new Error('Suppression modèle: ' + r.status)
|
|
return r.json()
|
|
}
|
|
export const bulkRequirements = (requirements) => jpost('/roster/requirements/bulk', { requirements })
|
|
export const clearRequirements = (start, days = 7) => jpost('/roster/requirements/clear', { start, days })
|
|
export async function deleteAssignment (name) {
|
|
const r = await fetch(HUB + '/roster/assignment/' + encodeURIComponent(name), { method: 'DELETE' })
|
|
if (!r.ok) throw new Error('Suppression échouée: ' + r.status)
|
|
return r.json()
|
|
}
|
|
export const listAvailability = (status) => jget('/roster/availability' + (status ? '?status=' + encodeURIComponent(status) : ''))
|
|
export const requestAvailability = (a) => jpost('/roster/availability', a)
|
|
export const approveAvailability = (name, body) => jpost('/roster/availability/' + encodeURIComponent(name) + '/approve', body || {})
|
|
export const pauseTechnician = (id, paused, reason) => jpost(`/roster/technician/${encodeURIComponent(id)}/pause`, { paused, reason })
|
|
export const setTechEfficiency = (id, efficiency) => jpost(`/roster/technician/${encodeURIComponent(id)}/efficiency`, { efficiency })
|
|
export const setTechCost = (id, body) => jpost(`/roster/technician/${encodeURIComponent(id)}/cost`, body)
|
|
export const setTechSkills = (id, skills) => jpost(`/roster/technician/${encodeURIComponent(id)}/skills`, { skills })
|
|
|
|
// ── Prise de RDV ──
|
|
export const bookJobs = () => jget('/roster/book/jobs')
|
|
export const bookSlots = (p) => jget('/roster/book/slots?' + new URLSearchParams(p).toString())
|
|
export const bookFit = (body) => jpost('/roster/book/fit', body)
|
|
export const bookConfirm = (body) => jpost('/roster/book/confirm', body)
|
|
// Hold temporaire d'une fenêtre (agent qui sélectionne) — { date, start, minutes } ou { date, start, release:true }
|
|
export const bookHold = (body) => jpost('/roster/book/hold', body)
|
|
// Lien client (token) pour un job → { token, url }
|
|
export const bookLink = (job) => jpost('/roster/book/link', { job })
|
|
// File « À recontacter » (jobs À reporter)
|
|
export const jobsToReschedule = () => jget('/roster/jobs-to-reschedule')
|
|
// Aviser le client d'un report : désassigne + SMS lien /book — { job, phone?, message? }
|
|
export const notifyReschedule = (body) => jpost('/roster/job/notify-reschedule', body)
|