@@ -643,6 +644,8 @@ function renderEquipOverlay () {
// ═════════════════════════════════════════════════════════════════════════════
const CLIENT_SCRIPT = `
+${types.CLIENT_TYPES_JS}
+
// Current detail-view job, customer, location (set by openDetail)
var CJ='',CC='',CL='',CMODEL='',CTYPE='';
// Equipment overlay scanner (separate from field-scan)
@@ -703,9 +706,9 @@ function applyHistFilter(){
var j=JOBS[jidEl.textContent]; if(!j){c.style.display='none';continue}
var okQ = !q || txt.indexOf(q)>=0;
var okF = true;
- if(f==='done') okF = j.status==='Completed';
+ if(f==='done') okF = isDone(j.status);
else if(f==='cancelled') okF = j.status==='Cancelled';
- else if(f==='overdue') okF = j.status!=='Completed' && j.status!=='Cancelled' && j.scheduled_date && j.scheduled_date= 0;
- var canFinish= j.status==='In Progress' || j.status==='in_progress';
+ var canFinish= isInProgress(j.status);
var sMeta = {Scheduled:['Planifié','#818cf8'], assigned:['Assigné','#818cf8'], open:['Ouvert','#818cf8'],
'In Progress':['En cours','#f59e0b'], in_progress:['En cours','#f59e0b'],
- Completed:['Terminé','#22c55e'], Cancelled:['Annulé','#94a3b8']};
+ Completed:['Terminé','#22c55e'], done:['Terminé','#22c55e'], Cancelled:['Annulé','#94a3b8']};
var sm = sMeta[j.status] || [j.status||'—','#94a3b8'];
- var urgent = j.priority==='urgent' || j.priority==='high';
+ var urgent = isUrgent(j.priority);
var addr = j.address || j.service_location_name || '';
var gps = j.service_location_name ? 'https://www.google.com/maps/dir/?api=1&destination='+encodeURIComponent(j.service_location_name) : '';
diff --git a/services/targo-hub/lib/types.js b/services/targo-hub/lib/types.js
new file mode 100644
index 0000000..ed58c2e
--- /dev/null
+++ b/services/targo-hub/lib/types.js
@@ -0,0 +1,95 @@
+'use strict'
+// ─────────────────────────────────────────────────────────────────────────────
+// Shared enums + predicates for Dispatch Job and related doctypes.
+//
+// Background: the v16 Dispatch Job doctype evolved and ended up with both
+// snake_case and Title Case variants in its status Select field:
+// open, assigned, in_progress, In Progress, On Hold, Scheduled,
+// Completed, Cancelled, done
+//
+// Code all over the codebase had ad-hoc checks like:
+// if (j.status === 'In Progress' || j.status === 'in_progress')
+// if (!['Completed', 'Cancelled', 'In Progress', 'in_progress'].includes(…))
+//
+// This module collects them in one place so:
+// 1. Any future status rename (or cleanup) touches one file.
+// 2. Client-side JS embedded in tech-mobile can import the same spellings.
+// 3. New callers have a semantic helper instead of memorizing aliases.
+// ─────────────────────────────────────────────────────────────────────────────
+
+// ── Canonical Dispatch Job statuses ──────────────────────────────────────────
+// Grouped by logical phase. We keep every spelling ERPNext accepts so filters
+// catch legacy rows too.
+const JOB_STATUS = {
+ // New, not yet assigned
+ OPEN: 'open',
+ // Assigned to a tech but not yet scheduled/started
+ ASSIGNED: 'assigned',
+ // Date/time set, tech hasn't started
+ SCHEDULED: 'Scheduled',
+ // Blocked by a dependency (parent chain, parts, …)
+ ON_HOLD: 'On Hold',
+ // Tech started (we emit "In Progress" on new starts; "in_progress" is legacy)
+ IN_PROGRESS: 'In Progress',
+ IN_PROGRESS_LEGACY: 'in_progress',
+ // Finished (we emit "Completed"; "done" is legacy)
+ COMPLETED: 'Completed',
+ COMPLETED_LEGACY: 'done',
+ // Aborted
+ CANCELLED: 'Cancelled',
+}
+
+// Logical groupings — use these in filters so legacy spellings don't slip through.
+const JOB_IN_PROGRESS_STATUSES = [JOB_STATUS.IN_PROGRESS, JOB_STATUS.IN_PROGRESS_LEGACY]
+const JOB_DONE_STATUSES = [JOB_STATUS.COMPLETED, JOB_STATUS.COMPLETED_LEGACY]
+const JOB_TERMINAL_STATUSES = [...JOB_DONE_STATUSES, JOB_STATUS.CANCELLED]
+const JOB_PENDING_STATUSES = [JOB_STATUS.OPEN, JOB_STATUS.ASSIGNED, JOB_STATUS.SCHEDULED, JOB_STATUS.ON_HOLD]
+const JOB_ACTIVE_STATUSES = [...JOB_PENDING_STATUSES, ...JOB_IN_PROGRESS_STATUSES]
+
+// ── Predicates ───────────────────────────────────────────────────────────────
+// Use these in branching logic; use the arrays above in list filters.
+const isInProgress = s => JOB_IN_PROGRESS_STATUSES.includes(s)
+const isDone = s => JOB_DONE_STATUSES.includes(s)
+const isCancelled = s => s === JOB_STATUS.CANCELLED
+const isTerminal = s => JOB_TERMINAL_STATUSES.includes(s)
+const isPending = s => JOB_PENDING_STATUSES.includes(s)
+
+// ── Priority ─────────────────────────────────────────────────────────────────
+// Doctype Select options: low | medium | high.
+// 'urgent' shows up in older LLM prompts and client filters — treated as high.
+const JOB_PRIORITY = {
+ LOW: 'low',
+ MEDIUM: 'medium',
+ HIGH: 'high',
+ URGENT: 'urgent', // alias — code treats as "high or above"
+}
+
+const URGENT_PRIORITIES = [JOB_PRIORITY.HIGH, JOB_PRIORITY.URGENT]
+const isUrgent = p => URGENT_PRIORITIES.includes(p)
+
+// ── Client-side snippet ──────────────────────────────────────────────────────
+// For embedding in template-literal