feat(ops): pickups = ~5min jobs + generic visit icon (handyman, not bolt)
Retours terrain : 1. « Ramassage d'équipement » = simple collecte chez le client → job court. Détection (isPickup : titre/type ~ /ramm?ass|récupération/) → durée plafonnée à 5 min : jobDur(pickup)=5/60 (maths d'occupation/Suggérer) + capPickups() sur les 3 chargements du pool (est_min affiché=5). Évite de surallouer le temps même si l'est_min du ticket dit plus long. 2. Icône de visite GÉNÉRIQUE (type non reconnu) : `bolt` (éclair, peu parlant) → `handyman` (technicien + outils). Fallback de skillIcon → s'applique partout (carte markerIcon + listes skillSym). Vérifié live (données réelles) : ticket « Rammassage Équipements #253602 » (St-Anicet) affiche ≈ 5min ; carrousel : plus de bolt, handyman présent pour le job générique ; 0 nouvelle erreur. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cfc721e598
commit
c05a6272b7
|
|
@ -2093,7 +2093,7 @@ async function fetchKbMatrix (techId) {
|
||||||
}
|
}
|
||||||
let _kbMatT = null // le watch déclencheur est enregistré PLUS BAS (après la déclaration de occByTechDay) pour éviter la zone morte temporelle
|
let _kbMatT = null // le watch déclencheur est enregistré PLUS BAS (après la déclaration de occByTechDay) pour éviter la zone morte temporelle
|
||||||
const kbColor = (j) => j.skill ? getTagColor(j.skill) : (j.required_skill ? getTagColor(j.required_skill) : (legacyDeptColor(j.legacy_dept) || '#90a4ae'))
|
const kbColor = (j) => j.skill ? getTagColor(j.skill) : (j.required_skill ? getTagColor(j.required_skill) : (legacyDeptColor(j.legacy_dept) || '#90a4ae'))
|
||||||
async function reloadPool () { try { assignPanel.jobs = (await roster.unassignedJobs()).jobs || [] } catch (e) { /* non bloquant */ } }
|
async function reloadPool () { try { assignPanel.jobs = capPickups((await roster.unassignedJobs()).jobs) } catch (e) { /* non bloquant */ } }
|
||||||
// ── Création de job NATIVE OPS (#17) — Dispatch déprécié → tout depuis Planification. Réutilise UnifiedCreateModal (work-order). ──
|
// ── Création de job NATIVE OPS (#17) — Dispatch déprécié → tout depuis Planification. Réutilise UnifiedCreateModal (work-order). ──
|
||||||
// Job OPS-natif = PAS de legacy_ticket_id → coexiste avec les jobs synchronisés de F (la sync F ne touche que les jobs à legacy_ticket_id). « Sync F transitoire » = coexistence, pas de push F pour l'instant.
|
// Job OPS-natif = PAS de legacy_ticket_id → coexiste avec les jobs synchronisés de F (la sync F ne touche que les jobs à legacy_ticket_id). « Sync F transitoire » = coexistence, pas de push F pour l'instant.
|
||||||
const createJobOpen = ref(false)
|
const createJobOpen = ref(false)
|
||||||
|
|
@ -2517,7 +2517,7 @@ function skillIcon (sk) {
|
||||||
if (/info|ordinateur|\bpc\b|cam[ée]ra/.test(s)) return 'computer'
|
if (/info|ordinateur|\bpc\b|cam[ée]ra/.test(s)) return 'computer'
|
||||||
if (/vente|sales|soumission/.test(s)) return 'sell'
|
if (/vente|sales|soumission/.test(s)) return 'sell'
|
||||||
if (/factur|paiement|compta/.test(s)) return 'receipt_long'
|
if (/factur|paiement|compta/.test(s)) return 'receipt_long'
|
||||||
return 'bolt'
|
return 'handyman' // visite générique (type non reconnu) : technicien avec outils, plutôt que l'éclair peu parlant
|
||||||
}
|
}
|
||||||
// Icône de compétence pour un MARQUEUR carte (DOM, police material-icons) : UNIQUEMENT des ligatures material (pas les SVG
|
// Icône de compétence pour un MARQUEUR carte (DOM, police material-icons) : UNIQUEMENT des ligatures material (pas les SVG
|
||||||
// custom de skillSym) — sinon le glyphe ne s'affiche pas dans un <span class="material-icons"> hors q-icon.
|
// custom de skillSym) — sinon le glyphe ne s'affiche pas dans un <span class="material-icons"> hors q-icon.
|
||||||
|
|
@ -2587,7 +2587,7 @@ const draggingJobName = ref(null); const dropCell = ref(null); const dragHours =
|
||||||
const selectedJobs = reactive({}) // jobName → true
|
const selectedJobs = reactive({}) // jobName → true
|
||||||
const dropPreview = reactive({ key: null, addH: 0 })
|
const dropPreview = reactive({ key: null, addH: 0 })
|
||||||
const draggingSet = reactive(new Set()); let _dragGhost = null // jobs en cours de glissé (source estompée) + fantôme custom
|
const draggingSet = reactive(new Set()); let _dragGhost = null // jobs en cours de glissé (source estompée) + fantôme custom
|
||||||
async function openAssignPanel () { assignPanel.open = true; assignPanel.loading = true; for (const k in selectedJobs) delete selectedJobs[k]; try { assignPanel.jobs = (await roster.unassignedJobs()).jobs || [] } catch (e) { err(e) } finally { assignPanel.loading = false } }
|
async function openAssignPanel () { assignPanel.open = true; assignPanel.loading = true; for (const k in selectedJobs) delete selectedJobs[k]; try { assignPanel.jobs = capPickups((await roster.unassignedJobs()).jobs) } catch (e) { err(e) } finally { assignPanel.loading = false } }
|
||||||
|
|
||||||
// Write-back legacy : aperçu (dryRun, 0 écriture) → confirmation → écrit ticket.assign_to dans osTicket.
|
// Write-back legacy : aperçu (dryRun, 0 écriture) → confirmation → écrit ticket.assign_to dans osTicket.
|
||||||
const legacyPush = reactive({ open: false, loading: false, applying: false, preview: null, notify: true })
|
const legacyPush = reactive({ open: false, loading: false, applying: false, preview: null, notify: true })
|
||||||
|
|
@ -4324,7 +4324,14 @@ function setEntryPriority (name, lvl) { // écrit la priorité ERPNext du job (l
|
||||||
const solverOpts = reactive({ rankWeight: 2, overtimeCoef: 20, speedKmh: 45, maxSeconds: 8 }) // un COÛT par critère (pas de slider)
|
const solverOpts = reactive({ rankWeight: 2, overtimeCoef: 20, speedKmh: 45, maxSeconds: 8 }) // un COÛT par critère (pas de slider)
|
||||||
try { const s = JSON.parse(localStorage.getItem('ops.solverOpts') || 'null'); if (s && typeof s === 'object') Object.assign(solverOpts, s) } catch (e) { /* défauts */ }
|
try { const s = JSON.parse(localStorage.getItem('ops.solverOpts') || 'null'); if (s && typeof s === 'object') Object.assign(solverOpts, s) } catch (e) { /* défauts */ }
|
||||||
watch(solverOpts, v => { try { localStorage.setItem('ops.solverOpts', JSON.stringify(v)) } catch (e) {} }, { deep: true })
|
watch(solverOpts, v => { try { localStorage.setItem('ops.solverOpts', JSON.stringify(v)) } catch (e) {} }, { deep: true })
|
||||||
function jobDur (j) { return (j && (j.est_min ? j.est_min / 60 : Number(j.duration_h || 1))) || 1 }
|
// « Ramassage d'équipement » (récupération chez le client) = simple collecte → job court (~5 min).
|
||||||
|
// On ne surallocue pas le temps même si la classification/est_min du ticket dit plus long.
|
||||||
|
const PICKUP_RE = /ramm?ass|r[ée]cup[ée]ration/i
|
||||||
|
const PICKUP_MIN = 5
|
||||||
|
function isPickup (j) { return !!j && PICKUP_RE.test(String(j.subject || j.service_type || j.job_type || '')) }
|
||||||
|
function jobDur (j) { if (isPickup(j)) return PICKUP_MIN / 60; return (j && (j.est_min ? j.est_min / 60 : Number(j.duration_h || 1))) || 1 }
|
||||||
|
// Cap la durée AFFICHÉE des ramassages à 5 min (le temps affiché suit alors jobDur partout : pool, carte, occupation).
|
||||||
|
function capPickups (jobs) { return (jobs || []).map(j => isPickup(j) ? { ...j, est_min: PICKUP_MIN } : j) }
|
||||||
// Construit un plan job→tech×jour SANS rien écrire (proposition). Chaque job garde sa date si elle est dans la fenêtre.
|
// Construit un plan job→tech×jour SANS rien écrire (proposition). Chaque job garde sa date si elle est dans la fenêtre.
|
||||||
function buildSuggestion () {
|
function buildSuggestion () {
|
||||||
const useSel = selectedNames.value.length > 0
|
const useSel = selectedNames.value.length > 0
|
||||||
|
|
@ -5628,7 +5635,7 @@ let _legacyRefreshT = null
|
||||||
function scheduleLegacyRefresh () {
|
function scheduleLegacyRefresh () {
|
||||||
clearTimeout(_legacyRefreshT)
|
clearTimeout(_legacyRefreshT)
|
||||||
_legacyRefreshT = setTimeout(async () => {
|
_legacyRefreshT = setTimeout(async () => {
|
||||||
try { await reloadOccupancy(); if (assignPanel.open) { assignPanel.jobs = (await roster.unassignedJobs()).jobs || [] } } catch (e) { /* non bloquant */ }
|
try { await reloadOccupancy(); if (assignPanel.open) { assignPanel.jobs = capPickups((await roster.unassignedJobs()).jobs) } } catch (e) { /* non bloquant */ }
|
||||||
}, 700)
|
}, 700)
|
||||||
}
|
}
|
||||||
function onLegacyUpdate (data) {
|
function onLegacyUpdate (data) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user