feat: partly paid pos invoices (#48246)

* fix: partial payment in pos

* fix: show alerts for update failure

* fix: partial payment validation

* fix: remove setting clearance date

* fix: partly paid invoices in pos

* fix: throw error if user tries to make payment for consolidated invoice

* fix: include unpaid invoices in partly paid invoice filter

* refactor: function rename

* feat: button to open form view for partly paid invoices in pos order summary

* fix: payment menu item visible for unpaid invoices

* refactor: update_payments function

* fix: set outstanding amount for pos invoice

* test: partly paid pos invoices

* test: removed frappe.db.commit

* refactor: using before_submit to set outstanding amount
This commit is contained in:
Diptanil Saha
2025-06-28 00:48:23 +05:30
committed by GitHub
parent 1cb7d5126c
commit c742a1dbe9
14 changed files with 338 additions and 30 deletions

View File

@@ -477,22 +477,28 @@ def get_invoice_filters(doctype, status, name=None, customer=None):
if doctype == "POS Invoice":
filters["status"] = status
if status == "Partly Paid":
filters["status"] = ["in", ["Partly Paid", "Overdue", "Unpaid"]]
return filters
if doctype == "Sales Invoice":
filters["is_created_using_pos"] = 1
filters["is_consolidated"] = 0
if status == "Draft":
filters["docstatus"] = 0
if status == "Consolidated":
filters["pos_closing_entry"] = ["is", "set"]
else:
filters["docstatus"] = 1
if status == "Paid":
filters["is_return"] = 0
if status == "Return":
filters["is_return"] = 1
filters["pos_closing_entry"] = ["is", "set"] if status == "Consolidated" else ["is", "not set"]
filters["pos_closing_entry"] = ["is", "not set"]
if status == "Draft":
filters["docstatus"] = 0
elif status == "Partly Paid":
filters["status"] = ["in", ["Partly Paid", "Overdue", "Unpaid"]]
else:
filters["docstatus"] = 1
if status == "Paid":
filters["is_return"] = 0
if status == "Return":
filters["is_return"] = 1
return filters

View File

@@ -561,6 +561,13 @@ erpnext.PointOfSale.Controller = class {
() => frappe.dom.unfreeze(),
]);
},
open_in_form_view: (doctype, name) => {
frappe.run_serially([
() => frappe.dom.freeze(),
() => frappe.set_route("Form", doctype, name),
() => frappe.dom.unfreeze(),
]);
},
},
});
}

View File

@@ -1042,6 +1042,7 @@ erpnext.PointOfSale.ItemCart = class {
"Credit Note Issued": "gray",
"Partly Paid": "yellow",
Overdue: "yellow",
Unpaid: "red",
};
transaction_container.append(

View File

@@ -66,7 +66,7 @@ erpnext.PointOfSale.PastOrderList = class {
df: {
label: __("Invoice Status"),
fieldtype: "Select",
options: `Draft\nPaid\nConsolidated\nReturn`,
options: ["Draft", "Paid", "Consolidated", "Return", "Partly Paid"].join("\n"),
placeholder: __("Filter by invoice status"),
onchange: function () {
if (me.$component.is(":visible")) me.refresh_list();

View File

@@ -75,8 +75,9 @@ erpnext.PointOfSale.PastOrderSummary = class {
let indicator_color = "";
["Paid", "Consolidated"].includes(status) && (indicator_color = "green");
status === "Draft" && (indicator_color = "red");
status === "Return" && (indicator_color = "grey");
["Partly Paid", "Overdue"].includes(status) && (indicator_color = "yellow");
["Draft", "Unpaid"].includes(status) && (indicator_color = "red");
["Credit Note Issued", "Return"].includes(status) && (indicator_color = "grey");
return `<div class="left-section">
<div class="customer-name">${doc.customer}</div>
@@ -243,6 +244,10 @@ erpnext.PointOfSale.PastOrderSummary = class {
this.$summary_container.on("click", ".print-btn", () => {
this.print_receipt();
});
this.$summary_container.on("click", ".open-btn", () => {
this.events.open_in_form_view(this.doc.doctype, this.doc.name);
});
}
print_receipt() {
@@ -361,7 +366,14 @@ erpnext.PointOfSale.PastOrderSummary = class {
return [
{ condition: this.doc.docstatus === 0, visible_btns: ["Edit Order", "Delete Order"] },
{
condition: !this.doc.is_return && this.doc.docstatus === 1,
condition: ["Partly Paid", "Overdue", "Unpaid"].includes(this.doc.status),
visible_btns: ["Print Receipt", "Email Receipt", "Open in Form View"],
},
{
condition:
!this.doc.is_return &&
this.doc.docstatus === 1 &&
!["Partly Paid", "Overdue", "Unpaid"].includes(this.doc.status),
visible_btns: ["Print Receipt", "Email Receipt", "Return"],
},
{

View File

@@ -5,6 +5,7 @@ erpnext.PointOfSale.Payment = class {
this.events = events;
this.set_gt_to_default_mop = settings.set_grand_total_to_default_mop;
this.invoice_fields = settings.invoice_fields;
this.allow_partial_payment = settings.allow_partial_payment;
this.init_component();
}
@@ -224,7 +225,12 @@ erpnext.PointOfSale.Payment = class {
const paid_amount = doc.paid_amount;
const items = doc.items;
if (!items.length || (paid_amount == 0 && doc.additional_discount_percentage != 100)) {
if (
!items.length ||
(paid_amount == 0 &&
doc.additional_discount_percentage != 100 &&
this.allow_partial_payment === 0)
) {
const message = items.length
? __("You cannot submit the order without payment.")
: __("You cannot submit empty order.");