#!/usr/bin/env python3 """ Patch MobileTicketAgent.vue to add BMG custom sections (Log Time, Items Used, Customer Email + CC) to the Details tab. """ TARGET = '/home/frappe/frappe-bench/frappe-bench/apps/helpdesk/desk/src/pages/ticket/MobileTicketAgent.vue' with open(TARGET, 'r') as f: content = f.read() # ── 1. Template: inject custom sections after TicketAgentFields ────────────── OLD_TMPL = ''' ''' NEW_TMPL = '''
{{ it.item_name || it.item || it.barcode || "\u2014" }} \u00d7 {{ it.qty }}
''' assert OLD_TMPL in content, 'ERROR: template anchor not found' content = content.replace(OLD_TMPL, NEW_TMPL, 1) # ── 2. Add Button + FeatherIcon to frappe-ui imports ──────────────────────── OLD_IMPORTS = '''import { Breadcrumbs, Dialog, Dropdown, FormControl, Tabs, call, createResource, toast, } from "frappe-ui";''' NEW_IMPORTS = '''import { Breadcrumbs, Button, Dialog, Dropdown, FeatherIcon, FormControl, Tabs, call, createResource, toast, } from "frappe-ui";''' assert OLD_IMPORTS in content, 'ERROR: imports anchor not found' content = content.replace(OLD_IMPORTS, NEW_IMPORTS, 1) # ── 3. Initialize mobile refs in onSuccess ─────────────────────────────────── OLD_ONSUCCESS = ''' onSuccess: (data) => { subjectInput.value = ticket.subject; setupCustomizations(ticket, {''' NEW_ONSUCCESS = ''' onSuccess: (data) => { subjectInput.value = ticket.subject; mobileRaisedBy.value = data.raised_by || ""; mobileCcCsv.value = data.cc_csv || ""; setupCustomizations(ticket, {''' if OLD_ONSUCCESS in content: content = content.replace(OLD_ONSUCCESS, NEW_ONSUCCESS, 1) else: print('WARNING: onSuccess anchor not found, skipping init') # ── 4. Inject BMG reactive state + functions before ──────────────── BMG_SCRIPT = ''' // ── BMG mobile additions ──────────────────────────────────────────────────── // Log Time async function promptLogTime() { const hoursStr = window.prompt( "Hours spent on this ticket:\\n\\nEnter a decimal like 0.5, 1, 2.5.", "" ); if (hoursStr === null) return; const trimmed = (hoursStr || "").trim(); if (!trimmed) return; const hoursNum = parseFloat(trimmed); if (isNaN(hoursNum) || hoursNum <= 0 || hoursNum > 24) { window.alert("Please enter a number between 0 and 24."); return; } const notes = window.prompt("Notes (optional):", "") || ""; try { await call("helpdesk_log_time_on_ticket", { ticket: ticket.data.name, hours: trimmed, notes, }); toast.success("Logged " + hoursNum + "h on ticket #" + ticket.data.name); } catch (e) { window.alert("Log time failed: " + ((e && e.message) || String(e))); } } // Items Used const mobileNewItem = ref(""); const mobileNewBarcode = ref(""); const mobileAddingItem = ref(false); const mobileItemsList = computed(() => ticket.data?.items_used || []); async function mobileAddItem() { if (!mobileNewItem.value && !mobileNewBarcode.value) return; mobileAddingItem.value = true; try { await call("helpdesk_add_ticket_item", { ticket: ticket.data.name, item: mobileNewItem.value, barcode: mobileNewBarcode.value, }); ticket.reload(); mobileNewItem.value = ""; mobileNewBarcode.value = ""; toast.success("Item added"); } catch (e) { toast.error("Add failed: " + ((e && e.message) || String(e))); } finally { mobileAddingItem.value = false; } } async function mobileRemoveItem(rowName) { try { await call("helpdesk_remove_ticket_item", { ticket: ticket.data.name, row: rowName, }); ticket.reload(); toast.success("Item removed"); } catch (e) { toast.error("Remove failed: " + ((e && e.message) || String(e))); } } // Recipients const mobileRaisedBy = ref(""); const mobileCcCsv = ref(""); const mobileSavingRecipients = ref(false); async function mobileSaveRecipients() { mobileSavingRecipients.value = true; try { const ccArray = (mobileCcCsv.value || "") .split(",") .map((s) => s.trim()) .filter((s) => s.length > 0) .map((email) => ({ email_id: email })); await call("helpdesk_update_ticket_recipients", { ticket: ticket.data.name, raised_by: (mobileRaisedBy.value || "").trim(), cc: JSON.stringify(ccArray), }); ticket.reload(); toast.success("Recipients saved"); } catch (e) { toast.error("Save failed: " + ((e && e.message) || String(e))); } finally { mobileSavingRecipients.value = false; } } ''' assert '' in content, 'ERROR: not found' content = content.replace('', BMG_SCRIPT + '', 1) with open(TARGET, 'w') as f: f.write(content) print('PATCH OK')