Planification: drapeau « longue durée » sur absence (maternité/invalidité = à remplacer)

Tech Availability gagne long_term (Check). Case à cocher « Longue durée » dans le dialogue Congés.
absencesByTechDay encode « (longue durée) » → applyTemplate force « à remplacer » (pas juste sauter
comme des vacances), même si l'absence ne couvre pas toute la semaine. Complète l'intelligence
permanent vs vacances (avant: déduit de « absent toute la semaine »).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-06-04 20:31:39 -04:00
parent 8d946daf8d
commit fe60eeb485
2 changed files with 7 additions and 5 deletions

View File

@ -224,6 +224,7 @@
<q-input dense outlined type="date" v-model="newLeave.from_date" label="Du" style="width:140px" /> <q-input dense outlined type="date" v-model="newLeave.from_date" label="Du" style="width:140px" />
<q-input dense outlined type="date" v-model="newLeave.to_date" label="Au" style="width:140px" /> <q-input dense outlined type="date" v-model="newLeave.to_date" label="Au" style="width:140px" />
<q-input dense outlined v-model="newLeave.reason" label="Motif" style="width:150px" /> <q-input dense outlined v-model="newLeave.reason" label="Motif" style="width:150px" />
<q-toggle dense v-model="newLeave.long_term" :true-value="1" :false-value="0" label="Longue durée"><q-tooltip>Maternité, invalidité à remplacer (pas juste sauter comme des vacances)</q-tooltip></q-toggle>
<q-btn dense unelevated color="primary" icon="add" label="Créer" @click="createLeave" /> <q-btn dense unelevated color="primary" icon="add" label="Créer" @click="createLeave" />
</div> </div>
<div class="text-caption text-grey-7 q-mt-sm">Une demande <b>approuvée</b> rend le tech indisponible pour le solveur sur ces dates.</div> <div class="text-caption text-grey-7 q-mt-sm">Une demande <b>approuvée</b> rend le tech indisponible pour le solveur sur ces dates.</div>
@ -367,7 +368,7 @@ const showShiftEditor = ref(false); const editTpls = ref([])
const showTeamEditor = ref(false); const editTechs = ref([]) const showTeamEditor = ref(false); const editTechs = ref([])
const notifySms = ref(false) const notifySms = ref(false)
const showLeave = ref(false); const leaveRows = ref([]); const leaveFilter = ref('Demandé') const showLeave = ref(false); const leaveRows = ref([]); const leaveFilter = ref('Demandé')
const newLeave = reactive({ technician: '', availability_type: 'Congé', from_date: '', to_date: '', reason: '' }) const newLeave = reactive({ technician: '', availability_type: 'Congé', from_date: '', to_date: '', reason: '', long_term: 0 })
const newTpl = reactive({ template_name: '', start: '08:00', end: '16:00', color: '#1976d2', on_call: 0 }) const newTpl = reactive({ template_name: '', start: '08:00', end: '16:00', color: '#1976d2', on_call: 0 })
function numToTime (h) { const hh = Math.floor(h); const mm = Math.round((h - hh) * 60); return String(hh).padStart(2, '0') + ':' + String(mm).padStart(2, '0') } function numToTime (h) { const hh = Math.floor(h); const mm = Math.round((h - hh) * 60); return String(hh).padStart(2, '0') + ':' + String(mm).padStart(2, '0') }
// Slider à 2 poignées pour le nouveau modèle (heures custom) newTpl.start/end // Slider à 2 poignées pour le nouveau modèle (heures custom) newTpl.start/end
@ -532,7 +533,7 @@ async function approveLeave (l, reject) { try { await roster.approveAvailability
async function createLeave () { async function createLeave () {
if (!newLeave.technician || !newLeave.from_date || !newLeave.to_date) { $q.notify({ type: 'warning', message: 'Technicien + dates requis' }); return } if (!newLeave.technician || !newLeave.from_date || !newLeave.to_date) { $q.notify({ type: 'warning', message: 'Technicien + dates requis' }); return }
const t = techs.value.find(x => x.id === newLeave.technician) const t = techs.value.find(x => x.id === newLeave.technician)
try { await roster.requestAvailability({ technician: newLeave.technician, technician_name: t ? t.name : '', availability_type: newLeave.availability_type, from_date: newLeave.from_date, to_date: newLeave.to_date, reason: newLeave.reason }); $q.notify({ type: 'positive', message: 'Demande créée' }); newLeave.from_date = ''; newLeave.to_date = ''; newLeave.reason = ''; await loadLeave() } catch (e) { err(e) } try { await roster.requestAvailability({ technician: newLeave.technician, technician_name: t ? t.name : '', availability_type: newLeave.availability_type, from_date: newLeave.from_date, to_date: newLeave.to_date, reason: newLeave.reason, long_term: newLeave.long_term }); $q.notify({ type: 'positive', message: 'Demande créée' }); newLeave.from_date = ''; newLeave.to_date = ''; newLeave.reason = ''; newLeave.long_term = 0; await loadLeave() } catch (e) { err(e) }
} }
async function saveEff (t) { const eff = Number(t.efficiency) || 1; try { await roster.setTechEfficiency(t.id, eff); const tt = techs.value.find(x => x.id === t.id); if (tt) tt.efficiency = eff; $q.notify({ type: 'positive', message: t.name + ' : cadence ' + eff }) } catch (e) { err(e) } } async function saveEff (t) { const eff = Number(t.efficiency) || 1; try { await roster.setTechEfficiency(t.id, eff); const tt = techs.value.find(x => x.id === t.id); if (tt) tt.efficiency = eff; $q.notify({ type: 'positive', message: t.name + ' : cadence ' + eff }) } catch (e) { err(e) } }
function loadedCost (t) { return Math.round(((Number(t.salary) || 0) * (1 + (Number(t.charges) || 0) / 100) + (Number(t.other) || 0)) * 100) / 100 } function loadedCost (t) { return Math.round(((Number(t.salary) || 0) * (1 + (Number(t.charges) || 0) / 100) + (Number(t.other) || 0)) * 100) / 100 }
@ -680,7 +681,7 @@ function applyTemplate (tm) {
const t = techs.value.find(x => x.id === techId); const name = (t ? t.name : techId) const t = techs.value.find(x => x.id === techId); const name = (t ? t.name : techId)
const type = absByTechDay.value[techId + '|' + (dayList.value.find(d => isAbsent(techId, d.iso)) || {}).iso] || '' const type = absByTechDay.value[techId + '|' + (dayList.value.find(d => isAbsent(techId, d.iso)) || {}).iso] || ''
const lbl = name + (type ? ' (' + type + ')' : '') const lbl = name + (type ? ' (' + type + ')' : '')
if (skipped[techId] >= countPatternDays(tm, techId)) fullOut.push(lbl); else partial.push(lbl) if (/longue durée/i.test(type) || skipped[techId] >= countPatternDays(tm, techId)) fullOut.push(lbl); else partial.push(lbl)
} }
let msg = 'Modèle « ' + tm.name + ' » appliqué (' + applied + ' assignations)' let msg = 'Modèle « ' + tm.name + ' » appliqué (' + applied + ' assignations)'
if (partial.length) msg += ' · absence partielle ignorée : ' + partial.join(', ') if (partial.length) msg += ' · absence partielle ignorée : ' + partial.join(', ')

View File

@ -499,9 +499,9 @@ async function absencesByTechDay (start, days) {
for (const t of techs) if (t.status === PAUSE_STATUS) for (const d of dates) m[t.id + '|' + d] = 'En pause' for (const t of techs) if (t.status === PAUSE_STATUS) for (const d of dates) m[t.id + '|' + d] = 'En pause'
const avs = await erp.list('Tech Availability', { const avs = await erp.list('Tech Availability', {
filters: [['status', '=', 'Approuvé'], ['from_date', '<=', hi], ['to_date', '>=', lo]], filters: [['status', '=', 'Approuvé'], ['from_date', '<=', hi], ['to_date', '>=', lo]],
fields: ['technician', 'from_date', 'to_date', 'availability_type'], limit: 1000, fields: ['technician', 'from_date', 'to_date', 'availability_type', 'long_term'], limit: 1000,
}) })
for (const a of avs) for (const d of dates) if (d >= a.from_date && d <= a.to_date) m[a.technician + '|' + d] = a.availability_type || 'Absent' for (const a of avs) for (const d of dates) if (d >= a.from_date && d <= a.to_date) m[a.technician + '|' + d] = (a.availability_type || 'Absent') + (a.long_term ? ' (longue durée)' : '')
return m return m
} }
@ -734,6 +734,7 @@ async function handle (req, res, method, path, url) {
technician: b.technician, technician_name: b.technician_name || '', technician: b.technician, technician_name: b.technician_name || '',
availability_type: b.availability_type || 'Congé', status: 'Demandé', availability_type: b.availability_type || 'Congé', status: 'Demandé',
from_date: b.from_date, to_date: b.to_date, reason: b.reason || '', from_date: b.from_date, to_date: b.to_date, reason: b.reason || '',
long_term: b.long_term ? 1 : 0,
}) })
return json(res, r.ok ? 200 : 500, r) return json(res, r.ok ? 200 : 500, r)
} }