// helper: format currency (Indonesia style) - untuk fallback client-side rendering function fmtMoney(n) { n = Number(n) || 0; var parts = n.toFixed(2).split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, "."); return parts[0] + ',' + parts[1]; } // Function: build HTML client-side (same logic as template) - dipakai ketika doc belum disimpan function build_message_html_client(doc, ref_doc) { var amount = Number(doc.grand_total || doc.amount || 0); var netto = amount ? (amount / 1.11) : 0; var ppn = amount - netto; var qty = 0; if (ref_doc) { qty = Number(ref_doc.total_qty || 0); if (!qty && ref_doc.items) { for (var i = 0; i < ref_doc.items.length; i++) { qty += Number(ref_doc.items[i].qty || 0); } } } if (!qty) qty = 1; var per_unit = netto / qty; var html = ''; html += '
'; html += ''; html += ''; html += ''; html += ''; html += ''; var name = (ref_doc && ref_doc.items && ref_doc.items.length>0) ? (ref_doc.items[0].item_name || "Nama Barang / Uraian") : "Nama Barang / Uraian"; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += '
NoNama BarangBanyaknyaHarga SatuanJumlah Satuan
1' + name + '' + (qty.toFixed(2)) + 'Rp ' + fmtMoney(per_unit) + 'Rp ' + fmtMoney(netto) + '
DiskonRp ' + fmtMoney(0) + '
PPN 11%Rp ' + fmtMoney(ppn) + '
TotalRp ' + fmtMoney(amount) + '
'; html += '

Terbilang : ' + ( (typeof frappe !== 'undefined' && frappe.utils && frappe.utils.money_in_words) ? frappe.utils.money_in_words(amount, (doc.currency || "IDR")) : "") + '

'; html += '

Jakarta, ' + ((doc.transaction_date) ? doc.transaction_date : ( (typeof frappe !== 'undefined' && frappe.utils && frappe.utils.nowdate) ? frappe.utils.nowdate() : "" )) + '
Hormat kami,

'; html += '
'; return html; } // Function: update message - uses server render if docname exists, otherwise client-side render function update_message(frm) { // If there's a reference document, fetch it (for client-side rendering) var ref_doctype = frm.doc.reference_doctype; var ref_name = frm.doc.reference_name; var do_server_render = !!frm.doc.name; // prefer server render when doc saved (has name) if (ref_doctype && ref_name) { frappe.call({ method: "frappe.client.get", args: { doctype: ref_doctype, name: ref_name }, callback: function(r) { var ref_doc = r.message || null; if (do_server_render) { // server render (uses api.get_payment_message) frappe.call({ method: "editor_override.api.get_payment_message", args: { docname: frm.doc.name }, callback: function(res) { if (res.message) { frm.set_value("message", res.message); frm.refresh_field("message"); } } }); } else { // client-side render for unsaved doc var html = build_message_html_client(frm.doc, ref_doc); frm.set_value("message", html); frm.refresh_field("message"); } } }); } else { // no reference doc if (do_server_render) { frappe.call({ method: "editor_override.api.get_payment_message", args: { docname: frm.doc.name }, callback: function(res) { if (res.message) { frm.set_value("message", res.message); frm.refresh_field("message"); } } }); } else { var html = build_message_html_client(frm.doc, null); frm.set_value("message", html); frm.refresh_field("message"); } } } // =========================== // Payment Term logic (existing) + tie to update_message // =========================== frappe.ui.form.on("Payment Request", { onload: function(frm) { // set default or initial behaviours if needed // e.g. df property toggles, default percent, etc. (adapt from your original code) if (!frm.doc.custom_payment_term_percentage) { frm.set_value("custom_payment_term_percentage", 100); } }, refresh: function(frm) { // always update message UI on refresh (safe) update_message(frm); }, reference_name: function(frm) { // when reference changes, fetch base amount and refresh message if (frm.doc.reference_doctype === "Sales Order" && frm.doc.reference_name) { // fetch SO grand_total and set as amount (default 100% portion) frappe.call({ method: "frappe.client.get", args: { doctype: "Sales Order", name: frm.doc.reference_name }, callback: function(r) { if (r.message && r.message.grand_total) { // if no custom percent set, set amount to full SO var portion = frm.doc.custom_payment_term_percentage || 100; var final_amount = (r.message.grand_total * portion) / 100; frm.set_value("grand_total", final_amount); frm.refresh_field("grand_total"); // now update message update_message(frm); } } }); } else { update_message(frm); } }, custom_payment_term: function(frm) { var value = frm.doc.custom_payment_term; if (!value) { frm.set_value("custom_payment_term_percentage", 100); // get original amount from SO and refresh message if (frm.doc.reference_doctype === "Sales Order" && frm.doc.reference_name) { frappe.call({ method: "frappe.client.get", args: { doctype: "Sales Order", name: frm.doc.reference_name }, callback: function(r) { if (r.message && r.message.grand_total) { frm.set_value("grand_total", r.message.grand_total); frm.refresh_field("grand_total"); update_message(frm); } } }); } else { update_message(frm); } } else { // fetch selected Payment Term invoice_portion frappe.call({ method: "frappe.client.get", args: { doctype: "Payment Term", name: value }, callback: function(r) { if (r.message && r.message.invoice_portion !== undefined) { var portion = r.message.invoice_portion; frm.set_value("custom_payment_term_percentage", portion); // calc final amount based on sales order base if (frm.doc.reference_doctype === "Sales Order" && frm.doc.reference_name) { frappe.call({ method: "frappe.client.get", args: { doctype: "Sales Order", name: frm.doc.reference_name }, callback: function(rr) { if (rr.message && rr.message.grand_total) { var final_amount = (rr.message.grand_total * portion) / 100; frm.set_value("grand_total", final_amount); frm.refresh_field("grand_total"); // Immediately update message to reflect new grand_total update_message(frm); } } }); } else { update_message(frm); } } } }); } }, // in case grand_total is edited manually grand_total: function(frm) { update_message(frm); } });