feat(dispatch): coloriage cartes par type legacy + n° ticket dans le détail
- pont : stocke legacy_dept (dept osTicket) sur le Dispatch Job + backfill des 70 existants - useHelpers.jobColor/legacyDeptColor : palette comme legacy (Installation vert, Réparation or, Télé rose, Téléphonie vert pâle, Désinstallation rouge foncé) ; tech en pause = rouge vif prioritaire - store _mapJob : expose legacyDept + legacyTicketId - RightPanel : champ « Ticket legacy » (#id · dept) — le client est déjà un lien cliquable vers la fiche - doc mise à jour Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
70bf25ea84
commit
dadda9fd49
|
|
@ -81,12 +81,28 @@ export function jobSvcCode (job) {
|
|||
return 'WO'
|
||||
}
|
||||
|
||||
// Couleur par TYPE de ticket, calquée sur le board legacy (osTicket). Téléphonie = vert
|
||||
// PLUS PÂLE que l'installation. Ordre des tests important (téléph avant télé ; télé avant install).
|
||||
export function legacyDeptColor (dept) {
|
||||
if (!dept) return null
|
||||
const d = String(dept).toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '')
|
||||
if (d.includes('teleph')) return '#8fce93' // Téléphonie → vert pâle
|
||||
if (d.includes('tele') || d.includes('televis')) return '#ec5fb0' // Télé (install/réparation) → rose
|
||||
if (d.includes('desinstall') || d.includes('retrait')) return '#c0392b' // Désinstallation → rouge foncé
|
||||
if (d.includes('repar')) return '#f1c84b' // Réparation (Fibre) → jaune/or
|
||||
if (d.includes('installation') || d.includes('monteur') || d.includes('fusionneur')) return '#46992f' // Installation (Fibre) → vert
|
||||
return null
|
||||
}
|
||||
|
||||
export function jobColor (job, techColors, store) {
|
||||
// Tech en pause/absent (statut interne 'off') → ses jobs en ROUGE (à réassigner)
|
||||
// Tech en pause/absent (statut interne 'off') → ses jobs en ROUGE vif (à réassigner) — priorité opérationnelle
|
||||
if (job.assignedTech && store) {
|
||||
const at = store.technicians.find(x => x.id === job.assignedTech)
|
||||
if (at && at.status === 'off') return '#e53935'
|
||||
}
|
||||
// Type legacy (pont osTicket) → couleur « comme legacy »
|
||||
const ld = legacyDeptColor(job.legacyDept)
|
||||
if (ld) return ld
|
||||
if (SVC_COLORS[job.service_type]) return SVC_COLORS[job.service_type]
|
||||
const s = (job.subject||'').toLowerCase()
|
||||
if (s.includes('internet')) return '#3b82f6'
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ const onDeleteTag = inject('onDeleteTag')
|
|||
@change="emit('set-end-date', panel.data.job, $event.target.value)" style="margin-top:2px" />
|
||||
</div>
|
||||
<div class="sb-rp-field"><span class="sb-rp-lbl">Statut</span>{{ panel.data?.job?.status }}</div>
|
||||
<div v-if="panel.data?.job?.legacyTicketId" class="sb-rp-field">
|
||||
<span class="sb-rp-lbl">Ticket legacy</span>
|
||||
<span>#{{ panel.data.job.legacyTicketId }}<span v-if="panel.data?.job?.legacyDept"> · {{ panel.data.job.legacyDept }}</span></span>
|
||||
</div>
|
||||
<div v-if="panel.data?.job?.note" class="sb-rp-field"><span class="sb-rp-lbl">Note</span>{{ panel.data.job.note }}</div>
|
||||
<div class="sb-rp-field">
|
||||
<span class="sb-rp-lbl">Tags</span>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ export const useDispatchStore = defineStore('dispatch', () => {
|
|||
sourceIssue: j.source_issue || null,
|
||||
dependsOn: j.depends_on || null,
|
||||
jobType: j.job_type || null,
|
||||
legacyDept: j.legacy_dept || null, // département osTicket legacy → coloriage par type
|
||||
legacyTicketId: j.legacy_ticket_id || null, // n° ticket legacy (affiché dans le panneau détail)
|
||||
parentJob: j.parent_job || null,
|
||||
stepOrder: j.step_order || 0,
|
||||
onOpenWebhook: j.on_open_webhook || null,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ Override possible via `LEGACY_TARGO_STAFF_ID`.
|
|||
## Mapping ticket legacy → Dispatch Job
|
||||
| Dispatch Job | Source legacy | Notes |
|
||||
|---|---|---|
|
||||
| `legacy_ticket_id` | `ticket.id` | **clé d'idempotence** (lookup avant create) |
|
||||
| `legacy_ticket_id` | `ticket.id` | **clé d'idempotence** (lookup avant create) ; affiché dans le panneau détail |
|
||||
| `legacy_dept` | `ticket_dept.name` | département granulaire → **coloriage des cartes « comme legacy »** |
|
||||
| `ticket_id` | `'LEG-' + ticket.id` | nom lisible du DJ |
|
||||
| `subject` | `ticket.subject` | + adresse ajoutée si pas de Service Location |
|
||||
| `customer` | `Customer` où `legacy_account_id = ticket.account_id` | 61/70 matchés ; sinon laissé vide |
|
||||
|
|
@ -40,6 +41,13 @@ Override possible via `LEGACY_TARGO_STAFF_ID`.
|
|||
seulement tant qu'il est encore `open` + non assigné.
|
||||
- **SÉQUENTIEL** (frappe_pg ne supporte pas la concurrence) — pas de `Promise.all`.
|
||||
|
||||
## Coloriage des cartes (comme legacy)
|
||||
`jobColor()` (`apps/ops/src/composables/useHelpers.js` → `legacyDeptColor`) colore par `legacyDept` :
|
||||
Installation(Fibre)/Monteur/Fusionneur → **vert** `#46992f` · Réparation(Fibre) → **or** `#f1c84b` ·
|
||||
Télé (install/réparation) → **rose** `#ec5fb0` · Téléphonie → **vert pâle** `#8fce93` ·
|
||||
Désinstallation → **rouge foncé** `#c0392b`. (Tech en pause → rouge vif, priorité.) Le store
|
||||
(`_mapJob`) expose `legacyDept`/`legacyTicketId` ; le pool « non-assigné » est déjà trié par `scheduledDate`.
|
||||
|
||||
## Endpoints
|
||||
- `GET /dispatch/legacy-sync/preview` — **dry-run, 0 écriture** : ce qui serait créé + matching client/SL + non-matchés.
|
||||
- `POST /dispatch/legacy-sync/run` — exécute la synchro (création/maj). Retourne `{tickets, created, updated, skipped, errors, unmatched_customer}`.
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ async function buildJob (t) {
|
|||
status: 'open',
|
||||
order_source: 'Manual',
|
||||
legacy_ticket_id: String(t.id),
|
||||
legacy_dept: t.dept || '', // département legacy granulaire → coloriage « comme legacy » (Installation Fibre / Réparation Fibre / Télé / Téléphonie…)
|
||||
}
|
||||
const sd = tzDate(t.due_date); if (sd) payload.scheduled_date = sd
|
||||
const st = startTime(t.due_time); if (st) payload.start_time = st
|
||||
|
|
@ -137,7 +138,7 @@ async function buildJob (t) {
|
|||
}
|
||||
|
||||
async function findExisting (legacyId) {
|
||||
const r = await erp.list('Dispatch Job', { filters: [['legacy_ticket_id', '=', legacyId]], fields: ['name', 'status', 'assigned_tech', 'scheduled_date'], limit: 1 })
|
||||
const r = await erp.list('Dispatch Job', { filters: [['legacy_ticket_id', '=', legacyId]], fields: ['name', 'status', 'assigned_tech', 'scheduled_date', 'legacy_dept'], limit: 1 })
|
||||
return (r && r[0]) || null
|
||||
}
|
||||
|
||||
|
|
@ -153,11 +154,13 @@ async function sync ({ dryRun = false } = {}) {
|
|||
if (!b.matched.customer) unmatched++
|
||||
const ex = await findExisting(b.legacy_id)
|
||||
if (ex) {
|
||||
// Déjà importé : on ne touche QUE s'il est encore au pool (open + non assigné) et que la date a changé.
|
||||
if (!dryRun && ex.status === 'open' && !ex.assigned_tech && b.payload.scheduled_date && b.payload.scheduled_date !== ex.scheduled_date) {
|
||||
await erp.update('Dispatch Job', ex.name, { scheduled_date: b.payload.scheduled_date })
|
||||
updated++; details.push({ legacy_id: b.legacy_id, action: 'reschedule', job: ex.name, date: b.payload.scheduled_date })
|
||||
} else { skipped++ }
|
||||
// Déjà importé. Backfill du département (métadonnée couleur, sans risque) + maj date SEULEMENT
|
||||
// s'il est encore au pool (open + non assigné) → on ne clobbe jamais le travail du répartiteur.
|
||||
const patch = {}
|
||||
if (!ex.legacy_dept && b.payload.legacy_dept) patch.legacy_dept = b.payload.legacy_dept
|
||||
if (ex.status === 'open' && !ex.assigned_tech && b.payload.scheduled_date && b.payload.scheduled_date !== ex.scheduled_date) patch.scheduled_date = b.payload.scheduled_date
|
||||
if (!dryRun && Object.keys(patch).length) { await erp.update('Dispatch Job', ex.name, patch); updated++; details.push({ legacy_id: b.legacy_id, action: 'update', job: ex.name, patch }) }
|
||||
else skipped++
|
||||
} else if (dryRun) {
|
||||
created++; details.push({ legacy_id: b.legacy_id, action: 'would-create', subject: b.payload.subject, job_type: b.payload.job_type, dept: b.dept, scheduled_date: b.payload.scheduled_date || null, start_time: b.payload.start_time || null, customer: b.matched.customer_name, customer_matched: b.matched.customer, sl_matched: b.matched.service_location, addr: b.addr })
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user