Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions stems/stems/custom_scripts/sales_order/percentage_invoicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,15 @@ 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:
billed_qty, billed_amt = _get_billed_qty_and_amount(so.name, row.name)
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
Expand All @@ -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

Expand Down Expand Up @@ -169,23 +160,30 @@ 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
item.rate = total_rate
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
Expand Down
64 changes: 45 additions & 19 deletions stems/stems/custom_scripts/sales_order/sales_order.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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) {
Expand All @@ -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;
}
}
Expand All @@ -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();
}
});
Expand Down