tickets: distinguer par département + technicien assigné ; réponse courriel = Received

- TicketCard : badge Département (issue_type) + technicien (assigned_staff) → distingue les
  tickets quasi identiques (ex. 3 « Cancellation » = Support/Facturation/Rétention).
- migrate_tickets.py : importe assigned_staff (ticket.assign_to → nom du staff) — le rattrapage
  ne le posait pas. Backfill des ~7,5k tickets assignés en F mais sans technicien côté ERPNext.
- logToLinkedTicket : direction Received si l'expéditeur = adresse destinataire du ticket
  (une réponse depuis un domaine interne @targo n'est plus mal classée « Sent »).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-07 08:50:01 -04:00
parent 417ef08f58
commit 27263d7766
3 changed files with 17 additions and 4 deletions

View File

@ -23,6 +23,8 @@
</template>
</InlineField>
</span>
<span v-if="ticket.issue_type" class="ops-badge tk-dept"><q-icon name="apartment" size="11px" class="q-mr-xs" />{{ ticket.issue_type }}</span>
<span v-if="ticket.assigned_staff" class="tk-assignee"><q-icon name="person" size="12px" /> {{ ticket.assigned_staff }}</span>
<span class="tk-id">#{{ ticket.legacy_ticket_id || ticket.name }}</span>
<span v-if="ticket.opening_date" class="tk-date"><q-icon name="event" size="12px" /> {{ ticket.opening_date }}</span>
<q-space />
@ -64,6 +66,8 @@ defineEmits(['open'])
.tk-badge { font-size: 10px; padding: 1px 8px; }
.tk-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; margin-top: 6px; padding-left: 0; }
.tk-id { font-size: 11px; color: #94a3b8; font-variant-numeric: tabular-nums; }
.tk-dept { background: #eef2ff; color: #4338ca; font-size: 10px; padding: 1px 8px; display: inline-flex; align-items: center; }
.tk-assignee { font-size: 11px; color: #64748b; display: inline-flex; align-items: center; gap: 2px; white-space: nowrap; }
.tk-date { font-size: 11px; color: #94a3b8; white-space: nowrap; display: inline-flex; align-items: center; gap: 2px; }
.avatar-stack { display: flex; align-items: center; }
.avatar-stack .avatar-chip { color: #fff; font-size: 9px; font-weight: 600; border: 1.5px solid #fff; margin-left: -8px; }

View File

@ -115,10 +115,14 @@ def main():
user_by_email = {r[1]: r[0] for r in pgc.fetchall() if r[1]}
staff_to_user = {}
staff_name_by_id = {} # ticket.assign_to → « Prénom Nom » (custom field assigned_staff, cf update_assigned_staff.py)
for s in staff_list:
email = s.get("email", "")
if email and email in user_by_email:
staff_to_user[s["id"]] = user_by_email[email]
nm = ((s.get("first_name") or "") + " " + (s.get("last_name") or "")).strip() or (s.get("username") or "")
if nm:
staff_name_by_id[s["id"]] = nm
# Existing Issue Types
pgc.execute('SELECT name FROM "tabIssue Type"')
@ -208,6 +212,7 @@ def main():
dept_name = dept_map.get(t.get("dept_id"), None)
cust_name = cust_map.get(t.get("account_id"))
is_important = 1 if t.get("important") else 0
assigned_staff = staff_name_by_id.get(t.get("assign_to")) if t.get("assign_to") else None
opening_date = ts_to_dateonly(t.get("date_create"))
opening_time = None
@ -233,17 +238,17 @@ def main():
naming_series, subject, status, priority, issue_type,
customer, company, opening_date, opening_time,
legacy_ticket_id, is_incident, is_important,
parent_incident, service_location
parent_incident, service_location, assigned_staff
) VALUES (
%s, %s, %s, %s, %s, 0, 0,
'ISS-.YYYY.-', %s, %s, %s, %s,
%s, %s, %s, %s,
%s, %s, %s, %s, %s
%s, %s, %s, %s, %s, %s
)
""", (issue_name, ts, ts, ADMIN, ADMIN,
subject[:255], status, priority, dept_name,
cust_name, COMPANY, opening_date, opening_time,
tid, 0, is_important, None, None))
tid, 0, is_important, None, None, assigned_staff))
ticket_to_issue[tid] = issue_name
i_ok += 1

View File

@ -765,7 +765,11 @@ async function logToLinkedTicket (conv, msg, subject) {
if (!conv || !conv.linkedTickets || !conv.linkedTickets.length) return
const issue = conv.linkedTickets[conv.linkedTickets.length - 1] // le plus récent
if (!issue || !issue.name) return
const sent = msg.from === 'agent'
// Sortant seulement si NOUS écrivons (from=agent) ET pas depuis l'adresse destinataire du ticket
// (sinon une réponse depuis un domaine interne @targo — ex. test/employé destinataire — serait mal classée « Sent »).
const custEmail = String(conv.email || '').toLowerCase()
const fromEmail = String(msg.fromEmail || '').toLowerCase()
const sent = msg.from === 'agent' && (!custEmail || fromEmail !== custEmail)
try {
await createCommunication({
communication_type: 'Communication', communication_medium: 'Email',