diff --git a/stems/stems/custom_scripts/sales_order/percentage_invoicing.py b/stems/stems/custom_scripts/sales_order/percentage_invoicing.py index cc7c08f..2f46e69 100644 --- a/stems/stems/custom_scripts/sales_order/percentage_invoicing.py +++ b/stems/stems/custom_scripts/sales_order/percentage_invoicing.py @@ -86,7 +86,6 @@ def set_initial_so_item_amounts(sales_order, method=None): @frappe.whitelist() def get_so_items(sales_order): - """ Retrieves the sales order items along with their billed and remaining quantities and amounts for percentage invoicing.""" so = frappe.get_doc("Sales Order", sales_order) data = [] for row in so.items: @@ -94,16 +93,8 @@ def get_so_items(sales_order): total_qty = flt(row.qty) total_rate = flt(row.rate) total_amt = total_qty * total_rate - stored_invoiced = row.get("invoiced_amount") - stored_remaining = row.get("remaining_amount") - if stored_invoiced is not None or stored_remaining is not None: - invoiced_amt = flt(stored_invoiced) - remaining_amt = flt(stored_remaining) - if remaining_amt <= 0 and total_amt > 0: - remaining_amt = max(0, total_amt - invoiced_amt) - else: - invoiced_amt = billed_amt - remaining_amt = max(0, total_amt - billed_amt) + invoiced_amt = billed_amt + remaining_amt = max(0, total_amt - billed_amt) remaining_qty = max(0, total_qty - billed_qty) remaining_qty_pct = (remaining_qty / total_qty * 100) if total_qty else 0 remaining_amt_pct = (remaining_amt / total_amt * 100) if total_amt else 0 @@ -124,7 +115,7 @@ def get_so_items(sales_order): "invoiced_amount": invoiced_amt, "remaining_amt_percentage": round(remaining_amt_pct, 2), "item_type": item_type, - "include": 1 if has_remaining else 0, + "include": 0, }) return data @@ -169,15 +160,18 @@ def make_sales_invoice_by_percentage(source_name, target_doc=None): if apply_to_all: fraction = (percentage / remaining_overall_pct) if remaining_overall_pct else 0 else: - item_percentage = flt(sel.get("percentage")) - if not item_percentage or item_percentage <= 0: + if not flt(sel.get("percentage")) or flt(sel.get("percentage")) <= 0: continue - fraction = min(1.0, item_percentage / 100.0) if item_type == "Stock": if remaining_qty <= 0: continue - qty_to_bill = flt(remaining_qty * fraction, item.precision("qty")) - qty_to_bill = min(qty_to_bill, remaining_qty) + if apply_to_all: + qty_to_bill = flt(remaining_qty * fraction, item.precision("qty")) + else: + item_pct = flt(sel.get("percentage")) / 100.0 + amt_to_bill = min(remaining_amt, flt(total_amt * item_pct, item.precision("amount"))) + qty_to_bill = (amt_to_bill / total_rate) if total_rate else 0 + qty_to_bill = min(flt(qty_to_bill, item.precision("qty")), remaining_qty) if qty_to_bill <= 0: continue item.qty = qty_to_bill @@ -185,7 +179,11 @@ def make_sales_invoice_by_percentage(source_name, target_doc=None): else: if remaining_amt <= 0: continue - amt_to_bill = flt(remaining_amt * fraction, item.precision("amount")) + if apply_to_all: + amt_to_bill = flt(remaining_amt * fraction, item.precision("amount")) + else: + item_pct = flt(sel.get("percentage")) / 100.0 + amt_to_bill = min(remaining_amt, flt(total_amt * item_pct, item.precision("amount"))) amt_to_bill = min(amt_to_bill, remaining_amt) if amt_to_bill <= 0: continue diff --git a/stems/stems/custom_scripts/sales_order/sales_order.js b/stems/stems/custom_scripts/sales_order/sales_order.js index 0a79e18..66c732b 100644 --- a/stems/stems/custom_scripts/sales_order/sales_order.js +++ b/stems/stems/custom_scripts/sales_order/sales_order.js @@ -144,38 +144,60 @@ function open_percentage_dialog(frm) { callback: function (r) { let items = r.message || []; + items.forEach(function (row) { + let pct = row.remaining_amt_percentage; + if (pct == null || pct === undefined) { + pct = (row.total_amt && flt(row.total_amt) > 0 && row.remaining_amt != null) + ? (flt(row.remaining_amt) / flt(row.total_amt) * 100) : remaining; + } + row.percentage = pct; + }); let item_fields = [ { fieldname: "include", fieldtype: "Check", label: __("Include"), in_list_view: 1 }, { fieldname: "so_detail", fieldtype: "Data", hidden: 1 }, { fieldname: "item_code", fieldtype: "Data", label: __("Item"), in_list_view: 1, read_only: 1 }, { fieldname: "item_type", fieldtype: "Data", label: __("Type"), in_list_view: 1, read_only: 1 }, + { fieldname: "remaining_amt_percentage", fieldtype: "Float", label: __("Remaining %"), in_list_view: 1, read_only: 1 }, + { fieldname: "percentage", fieldtype: "Float", label: __("Invoice %"), in_list_view: 1, reqd: 0 }, { fieldname: "qty", fieldtype: "Float", label: __("Total Qty"), in_list_view: 1, read_only: 1 }, { fieldname: "billed_qty", fieldtype: "Float", label: __("Billed Qty"), in_list_view: 1, read_only: 1 }, { fieldname: "remaining_qty", fieldtype: "Float", label: __("Remaining Qty"), in_list_view: 1, read_only: 1 }, { fieldname: "rate", fieldtype: "Currency", label: __("Rate"), in_list_view: 1, read_only: 1 }, { fieldname: "total_amt", fieldtype: "Currency", label: __("Total Amt"), in_list_view: 1, read_only: 1 }, - { fieldname: "invoiced_amount", fieldtype: "Currency", label: __("Invoiced Amount"), in_list_view: 1, read_only: 1 }, - { fieldname: "remaining_amt", fieldtype: "Currency", label: __("Remaining Amount"), in_list_view: 1, read_only: 1 }, - { fieldname: "percentage", fieldtype: "Float", label: __("Percentage"), in_list_view: 1 } + { fieldname: "invoiced_amount", fieldtype: "Currency", label: __("Invoiced Amt"), in_list_view: 1, read_only: 1 }, + { fieldname: "remaining_amt", fieldtype: "Currency", label: __("Remaining Amt"), in_list_view: 1, read_only: 1 } ]; let d = new frappe.ui.Dialog({ title: __("Create Percentage Invoice"), - size: "large", + size: "extra-large", fields: [ + { + fieldname: "options_section", + fieldtype: "Section Break", + label: __("Options") + }, { fieldname: "apply_to_all", fieldtype: "Check", - label: __("Apply percentage to all items"), - default: 1, - description: __("If unchecked, set percentage per item in the table.") + label: __("Same % for all items"), + default: 0 }, { - fieldname: "percentage", + fieldname: "col_break_options", + fieldtype: "Column Break" + }, + { + fieldname: "invoice_percentage", fieldtype: "Float", - label: __("Invoice Percentage"), - default: remaining, - description: __("Percentage of the order to bill in this invoice (when applying to all).") + label: __("Invoice % (for all)"), + default: remaining + }, + { + fieldname: "items_section", + fieldtype: "Section Break", + label: __("Items"), + description: __("Check Include and set Invoice % per row when using different % per item.") }, { fieldname: "items", @@ -186,13 +208,12 @@ function open_percentage_dialog(frm) { fields: item_fields } ], - primary_action_label: __("Create Invoice"), primary_action(values) { let apply_to_all = values.apply_to_all; - let pct = flt(values.percentage); + let pct = flt(values.invoice_percentage); let selected = (values.items || []).filter(i => i.include); if (!selected.length) { @@ -212,7 +233,7 @@ function open_percentage_dialog(frm) { } else { let invalid = selected.some(i => !(flt(i.percentage) > 0 && flt(i.percentage) <= 100)); if (invalid) { - frappe.msgprint(__("Enter a percentage (1–100) for each selected item when not applying to all.")); + frappe.msgprint(__("Set Invoice % (1–100) for each selected item in the table.")); return; } } @@ -231,12 +252,17 @@ function open_percentage_dialog(frm) { } }); - // Show/hide single percentage based on "Apply to all" - d.fields_dict.apply_to_all.$input.on("change", function () { + function toggle_invoice_pct_field() { let apply = d.get_value("apply_to_all"); - d.fields_dict.percentage.df.hidden = !apply; - d.fields_dict.percentage.refresh(); - }); + let inv_pct = d.fields_dict.invoice_percentage; + if (inv_pct && inv_pct.df) { + inv_pct.df.hidden = !apply; + inv_pct.refresh(); + } + } + d.fields_dict.apply_to_all.$input.on("change", toggle_invoice_pct_field); + toggle_invoice_pct_field(); + d.show(); } });