diff --git a/scripts/patch_mobile_ticket.py b/scripts/patch_mobile_ticket.py new file mode 100644 index 0000000..d6ec5f0 --- /dev/null +++ b/scripts/patch_mobile_ticket.py @@ -0,0 +1,215 @@ +#!/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')