diff --git a/apps/ops/src/components/shared/WeeklyScheduleEditor.vue b/apps/ops/src/components/shared/WeeklyScheduleEditor.vue new file mode 100644 index 0000000..0d73624 --- /dev/null +++ b/apps/ops/src/components/shared/WeeklyScheduleEditor.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index 2cbc997..aa4ec0f 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -790,11 +790,10 @@
Score ★ = maîtrise · Efficacité : + plus vite / plus lent pour CETTE compétence (ex. +80), vide = globale · × sur le chip = retirer.
-
🗓 Horaire — semaine affichée
-
- - - +
🗓 Horaire
+
+ Modèles (5×8h · 4×10h · 3×12h) + heures par jour + N semaines → publie les quarts + Applique 5×8h à la semaine affichée (à publier)
🏠 Domicile (départ de tournée si plus proche que le dépôt)
@@ -1734,6 +1733,8 @@ :technicians="techs" :external-tags="tagCatalog" :external-get-color="getTagColor" @created="onJobCreated" /> + + @@ -1777,6 +1778,7 @@ import TagEditor from 'src/components/shared/TagEditor.vue' // module de tags pa import RouteMap from 'src/components/shared/RouteMap.vue' // carte de tournées réutilisable (revue dispatch auto + onglet Tournées) : OSRM réel, arrêts numérotés, fitTo import UnifiedCreateModal from 'src/components/shared/UnifiedCreateModal.vue' // création de job NATIVE OPS (work-order) — Dispatch déprécié, tout ici import SuggestSlotsDialog from 'src/modules/dispatch/components/SuggestSlotsDialog.vue' // « Trouver un créneau » → pré-remplit la création (tech+date+heure+adresse) +import WeeklyScheduleEditor from 'src/components/shared/WeeklyScheduleEditor.vue' // génération de quarts hebdo par tech (modèles) — repris/amélioré depuis Dispatch const $q = useQuasar() const router = useRouter() @@ -2333,6 +2335,29 @@ async function applyWeekPreset (t, dows, min, max) { skillMenuShown.value = false $q.notify({ type: 'positive', message: t.name + ' : horaire appliqué (semaine affichée) — pense à Publier', timeout: 2500 }) } +// ── Génération de quarts HEBDO par tech (modèles + N semaines) — écrit DIRECTEMENT les Shift Assignment (Publié). ── +const schedGenOpen = ref(false); const schedGenTech = ref(null) +function openSchedGen (t) { schedGenTech.value = { id: t.id, name: t.name }; skillMenuShown.value = false; schedGenOpen.value = true } +const _hmNum = (s) => { const [h, m] = String(s || '').split(':').map(Number); return (h || 0) + (m || 0) / 60 } +async function onScheduleApply ({ schedule, weeks }) { + const t = schedGenTech.value; if (!t) return + let created = 0, failed = 0; const tplByKey = {} + const base = start.value // lundi de la semaine affichée + for (let w = 0; w < weeks; w++) { + for (let i = 0; i < 7; i++) { + const day = schedule[i]; if (!day || !day.on) continue + const sh = _hmNum(day.start), eh = _hmNum(day.end); if (!(eh > sh)) { failed++; continue } + const key = sh + '-' + eh; let tpl = tplByKey[key] + if (!tpl) { tpl = await ensureWindowTpl(sh, eh); tplByKey[key] = tpl } + if (!tpl) { failed++; continue } + const iso = addDaysISO(base, w * 7 + i) + try { const r = await roster.createShift({ tech: t.id, tech_name: t.name, date: iso, shift: tpl.name, hours: tpl.hours || calcHours(day.start, day.end) }); if (r && (r.ok || r.existed)) created++; else failed++ } catch (e) { failed++ } + } + } + try { await loadWeek() } catch (e) {} // rafraîchit la grille (semaine affichée) + $q.notify({ type: created ? 'positive' : 'warning', icon: 'event_available', message: created + ' quart(s) publié(s) pour ' + t.name + (weeks > 1 ? ' (' + weeks + ' sem.)' : '') + (failed ? ' · ' + failed + ' ignoré(s)' : ''), timeout: 3500 }) + schedGenTech.value = null +} function skillEffOf (t, sk) { const e = t.skill_eff && t.skill_eff[sk]; return (e != null && e !== '') ? Number(e) : (Number(t.efficiency) || 1) } // facteur (pour le calcul de priorité) // Efficacité saisie en % de PERFORMANCE : + = plus VITE (moins de temps) · − = plus LENT. Conversion ↔ facteur. function effPctOf (factor) { return Math.round((1 - (Number(factor) || 1)) * 100) }