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:
@@ -66,6 +66,13 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
|
||||
if (doc.docstatus == 1 && !doc.is_return) {
|
||||
this.frm.add_custom_button(__("Return"), this.make_sales_return.bind(this), __("Create"));
|
||||
if (["Partly Paid", "Overdue", "Unpaid"].includes(doc.status)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment"),
|
||||
this.collect_outstanding_payment.bind(this),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
this.frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||
}
|
||||
|
||||
@@ -210,6 +217,138 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
frm: this.frm,
|
||||
});
|
||||
}
|
||||
|
||||
async collect_outstanding_payment() {
|
||||
const total_amount = flt(this.frm.doc.rounded_total) | flt(this.frm.doc.grand_total);
|
||||
const paid_amount = flt(this.frm.doc.paid_amount);
|
||||
const outstanding_amount = flt(this.frm.doc.outstanding_amount);
|
||||
const me = this;
|
||||
|
||||
const table_fields = [
|
||||
{
|
||||
fieldname: "mode_of_payment",
|
||||
fieldtype: "Link",
|
||||
in_list_view: 1,
|
||||
label: __("Mode of Payment"),
|
||||
options: "Mode of Payment",
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "amount",
|
||||
fieldtype: "Currency",
|
||||
in_list_view: 1,
|
||||
label: __("Amount"),
|
||||
options: this.frm.doc.currency,
|
||||
reqd: 1,
|
||||
onchange: function () {
|
||||
dialog.fields_dict.payments.df.data.some((d) => {
|
||||
if (d.idx == this.doc.idx) {
|
||||
d.amount = this.value === null ? 0 : this.value;
|
||||
dialog.fields_dict.payments.grid.refresh();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
let amount = 0;
|
||||
for (let d of dialog.fields_dict.payments.df.data) {
|
||||
amount += d.amount;
|
||||
}
|
||||
|
||||
let change_amount = total_amount - (paid_amount + amount);
|
||||
|
||||
dialog.fields_dict.outstanding_amount.set_value(
|
||||
outstanding_amount - amount < 0 ? 0 : outstanding_amount - amount
|
||||
);
|
||||
dialog.fields_dict.paid_amount.set_value(paid_amount + amount);
|
||||
dialog.fields_dict.change_amount.set_value(change_amount < 0 ? change_amount * -1 : 0);
|
||||
},
|
||||
},
|
||||
];
|
||||
const payment_method_data = await this.fetch_pos_payment_methods();
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Collect Outstanding Amount"),
|
||||
fields: [
|
||||
{
|
||||
fieldname: "payments",
|
||||
fieldtype: "Table",
|
||||
label: __("Payments"),
|
||||
cannot_add_rows: false,
|
||||
in_place_edit: true,
|
||||
reqd: 1,
|
||||
data: payment_method_data,
|
||||
fields: table_fields,
|
||||
},
|
||||
{
|
||||
fieldname: "section_break_1",
|
||||
fieldtype: "Section Break",
|
||||
},
|
||||
{
|
||||
fieldname: "outstanding_amount",
|
||||
fieldtype: "Currency",
|
||||
label: __("Outstanding Amount"),
|
||||
read_only: 1,
|
||||
default: outstanding_amount,
|
||||
},
|
||||
{
|
||||
fieldname: "column_break_1",
|
||||
fieldtype: "Column Break",
|
||||
},
|
||||
{
|
||||
fieldname: "paid_amount",
|
||||
fieldtype: "Currency",
|
||||
label: __("Paid Amount"),
|
||||
read_only: 1,
|
||||
default: paid_amount,
|
||||
},
|
||||
{
|
||||
fieldname: "change_amount",
|
||||
fieldtype: "Currency",
|
||||
label: __("Change Amount"),
|
||||
read_only: 1,
|
||||
default: 0,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Submit"),
|
||||
primary_action(values) {
|
||||
dialog.hide();
|
||||
me.frm.call({
|
||||
doc: me.frm.doc,
|
||||
method: "update_payments",
|
||||
args: {
|
||||
payments: values.payments.filter((d) => d.amount != 0),
|
||||
},
|
||||
freeze: true,
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frappe.show_alert({
|
||||
message: __("Payments updated."),
|
||||
indicator: "green",
|
||||
});
|
||||
me.frm.reload_doc();
|
||||
} else {
|
||||
frappe.show_alert({
|
||||
message: __("Payments could not be updated."),
|
||||
indicator: "red",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
async fetch_pos_payment_methods() {
|
||||
const pos_profile = this.frm.doc.pos_profile;
|
||||
if (!pos_profile) return;
|
||||
const pos_profile_doc = await frappe.db.get_doc("POS Profile", pos_profile);
|
||||
const data = [];
|
||||
pos_profile_doc.payments.forEach((pay) => {
|
||||
const { mode_of_payment } = pay;
|
||||
data.push({ mode_of_payment, amount: 0 });
|
||||
});
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
extend_cscript(cur_frm.cscript, new erpnext.selling.POSInvoiceController({ frm: cur_frm }));
|
||||
|
||||
@@ -1330,7 +1330,7 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "\nDraft\nReturn\nCredit Note Issued\nConsolidated\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
|
||||
"options": "\nDraft\nReturn\nCredit Note Issued\nConsolidated\nSubmitted\nPaid\nPartly Paid\nUnpaid\nPartly Paid and Discounted\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1573,7 +1573,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-06 15:03:19.957277",
|
||||
"modified": "2025-06-24 12:13:28.242649",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
@@ -1618,6 +1618,7 @@
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "creation",
|
||||
|
||||
@@ -149,7 +149,9 @@ class POSInvoice(SalesInvoice):
|
||||
"Consolidated",
|
||||
"Submitted",
|
||||
"Paid",
|
||||
"Partly Paid",
|
||||
"Unpaid",
|
||||
"Partly Paid and Discounted",
|
||||
"Unpaid and Discounted",
|
||||
"Overdue and Discounted",
|
||||
"Overdue",
|
||||
@@ -220,6 +222,9 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
validate_coupon_code(self.coupon_code)
|
||||
|
||||
def before_submit(self):
|
||||
self.set_outstanding_amount()
|
||||
|
||||
def on_submit(self):
|
||||
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
|
||||
if not self.is_return and self.loyalty_program:
|
||||
@@ -525,6 +530,10 @@ class POSInvoice(SalesInvoice):
|
||||
)
|
||||
)
|
||||
|
||||
def set_outstanding_amount(self):
|
||||
total = flt(self.rounded_total) or flt(self.grand_total)
|
||||
self.outstanding_amount = total - flt(self.paid_amount) if total > flt(self.paid_amount) else 0
|
||||
|
||||
def validate_loyalty_transaction(self):
|
||||
if self.redeem_loyalty_points and (
|
||||
not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center
|
||||
@@ -546,6 +555,8 @@ class POSInvoice(SalesInvoice):
|
||||
self.status = "Draft"
|
||||
return
|
||||
|
||||
total = flt(self.rounded_total) or flt(self.grand_total)
|
||||
|
||||
if not status:
|
||||
if self.docstatus == 2:
|
||||
status = "Cancelled"
|
||||
@@ -561,6 +572,14 @@ class POSInvoice(SalesInvoice):
|
||||
self.status = "Overdue and Discounted"
|
||||
elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()):
|
||||
self.status = "Overdue"
|
||||
elif (
|
||||
0 < flt(self.outstanding_amount) < total
|
||||
and self.is_discounted
|
||||
and self.get_discounting_status() == "Disbursed"
|
||||
):
|
||||
self.status = "Partly Paid and Discounted"
|
||||
elif 0 < flt(self.outstanding_amount) < total:
|
||||
self.status = "Partly Paid"
|
||||
elif (
|
||||
flt(self.outstanding_amount) > 0
|
||||
and getdate(self.due_date) >= getdate(nowdate())
|
||||
@@ -781,6 +800,48 @@ class POSInvoice(SalesInvoice):
|
||||
if pr:
|
||||
return frappe.get_doc("Payment Request", pr)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_payments(self, payments):
|
||||
if self.status == "Consolidated":
|
||||
frappe.throw(_("Create Payment Entry for Consolidated POS Invoices."))
|
||||
|
||||
paid_amount = flt(self.paid_amount)
|
||||
total = flt(self.rounded_total) or flt(self.grand_total)
|
||||
|
||||
if paid_amount >= total:
|
||||
frappe.throw(title=_("Invoice Paid"), msg=_("This invoice has already been paid."))
|
||||
|
||||
idx = self.payments[-1].idx if self.payments else -1
|
||||
|
||||
for d in payments:
|
||||
idx += 1
|
||||
payment = create_payments_on_invoice(self, idx, frappe._dict(d))
|
||||
paid_amount += flt(payment.amount)
|
||||
payment.submit()
|
||||
|
||||
paid_amount = flt(flt(paid_amount), self.precision("paid_amount"))
|
||||
base_paid_amount = flt(flt(paid_amount * self.conversion_rate), self.precision("base_paid_amount"))
|
||||
outstanding_amount = (
|
||||
flt(flt(total - paid_amount), self.precision("outstanding_amount")) if total > paid_amount else 0
|
||||
)
|
||||
change_amount = (
|
||||
flt(flt(paid_amount - total), self.precision("change_amount")) if paid_amount > total else 0
|
||||
)
|
||||
|
||||
pi = frappe.qb.DocType("POS Invoice")
|
||||
query = (
|
||||
frappe.qb.update(pi)
|
||||
.set(pi.paid_amount, paid_amount)
|
||||
.set(pi.base_paid_amount, base_paid_amount)
|
||||
.set(pi.outstanding_amount, outstanding_amount)
|
||||
.set(pi.change_amount, change_amount)
|
||||
.where(pi.name == self.name)
|
||||
)
|
||||
query.run()
|
||||
self.reload()
|
||||
|
||||
self.set_status(update=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
@@ -932,3 +993,19 @@ def get_item_group(pos_profile):
|
||||
item_groups.extend(get_descendants_of("Item Group", row.item_group))
|
||||
|
||||
return list(set(item_groups))
|
||||
|
||||
|
||||
def create_payments_on_invoice(doc, idx, payment_details):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
||||
|
||||
payment = frappe.new_doc("Sales Invoice Payment")
|
||||
payment.idx = idx
|
||||
payment.mode_of_payment = payment_details.mode_of_payment
|
||||
payment.amount = payment_details.amount
|
||||
payment.base_amount = payment.amount * doc.conversion_rate
|
||||
payment.parent = doc.name
|
||||
payment.parentfield = "payments"
|
||||
payment.parenttype = doc.doctype
|
||||
payment.account = get_bank_cash_account(payment.mode_of_payment, doc.company).get("account")
|
||||
|
||||
return payment
|
||||
|
||||
@@ -18,11 +18,13 @@ frappe.listview_settings["POS Invoice"] = {
|
||||
Draft: "red",
|
||||
Unpaid: "orange",
|
||||
Paid: "green",
|
||||
"Partly Paid": "yellow",
|
||||
Submitted: "blue",
|
||||
Consolidated: "green",
|
||||
Return: "darkgrey",
|
||||
"Unpaid and Discounted": "orange",
|
||||
"Overdue and Discounted": "red",
|
||||
"Partly Paid and Discounted": "yellow",
|
||||
Overdue: "red",
|
||||
};
|
||||
return [__(doc.status), status_color[doc.status], "status,=," + doc.status];
|
||||
|
||||
@@ -401,6 +401,50 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
pos_inv.insert()
|
||||
self.assertRaises(PartialPaymentValidationError, pos_inv.submit)
|
||||
|
||||
def test_partly_paid_invoices(self):
|
||||
set_allow_partial_payment(self.pos_profile, 1)
|
||||
|
||||
pos_inv = create_pos_invoice(pos_profile=self.pos_profile.name, rate=100, do_not_save=1)
|
||||
pos_inv.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 90},
|
||||
)
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
self.assertEqual(pos_inv.paid_amount, 90)
|
||||
self.assertEqual(pos_inv.status, "Partly Paid")
|
||||
|
||||
pos_inv.update_payments(payments=[{"mode_of_payment": "Cash", "amount": 10}])
|
||||
self.assertEqual(pos_inv.paid_amount, 100)
|
||||
self.assertEqual(pos_inv.status, "Paid")
|
||||
|
||||
set_allow_partial_payment(self.pos_profile, 0)
|
||||
|
||||
def test_multi_payment_for_partly_paid_invoices(self):
|
||||
set_allow_partial_payment(self.pos_profile, 1)
|
||||
|
||||
pos_inv = create_pos_invoice(pos_profile=self.pos_profile.name, rate=100, do_not_save=1)
|
||||
pos_inv.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 90},
|
||||
)
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
self.assertEqual(pos_inv.paid_amount, 90)
|
||||
self.assertEqual(pos_inv.status, "Partly Paid")
|
||||
|
||||
pos_inv.update_payments(payments=[{"mode_of_payment": "Cash", "amount": 5}])
|
||||
self.assertEqual(pos_inv.paid_amount, 95)
|
||||
self.assertEqual(pos_inv.status, "Partly Paid")
|
||||
|
||||
pos_inv.update_payments(payments=[{"mode_of_payment": "Cash", "amount": 5}])
|
||||
self.assertEqual(pos_inv.paid_amount, 100)
|
||||
self.assertEqual(pos_inv.status, "Paid")
|
||||
|
||||
set_allow_partial_payment(self.pos_profile, 0)
|
||||
|
||||
def test_serialized_item_transaction(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
@@ -1094,3 +1138,9 @@ def make_batch_item(item_name):
|
||||
|
||||
if not frappe.db.exists(item_name):
|
||||
return make_item(item_name, dict(has_batch_no=1, create_new_batch=1, is_stock_item=1))
|
||||
|
||||
|
||||
def set_allow_partial_payment(pos_profile, value):
|
||||
pos_profile.reload()
|
||||
pos_profile.allow_partial_payment = value
|
||||
pos_profile.save()
|
||||
|
||||
@@ -26,13 +26,14 @@
|
||||
"auto_add_item_to_cart",
|
||||
"validate_stock_on_save",
|
||||
"print_receipt_on_order_complete",
|
||||
"action_on_new_invoice",
|
||||
"column_break_16",
|
||||
"update_stock",
|
||||
"ignore_pricing_rule",
|
||||
"allow_rate_change",
|
||||
"allow_discount_change",
|
||||
"set_grand_total_to_default_mop",
|
||||
"action_on_new_invoice",
|
||||
"allow_partial_payment",
|
||||
"section_break_23",
|
||||
"item_groups",
|
||||
"column_break_25",
|
||||
@@ -423,6 +424,12 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Action on New Invoice",
|
||||
"options": "Always Ask\nSave Changes and Load New Invoice\nDiscard Changes and Load New Invoice"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_partial_payment",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Partial Payment"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -451,7 +458,7 @@
|
||||
"link_fieldname": "pos_profile"
|
||||
}
|
||||
],
|
||||
"modified": "2025-05-23 12:12:32.247652",
|
||||
"modified": "2025-06-24 11:19:19.834905",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
||||
@@ -32,6 +32,7 @@ class POSProfile(Document):
|
||||
"Always Ask", "Save Changes and Load New Invoice", "Discard Changes and Load New Invoice"
|
||||
]
|
||||
allow_discount_change: DF.Check
|
||||
allow_partial_payment: DF.Check
|
||||
allow_rate_change: DF.Check
|
||||
applicable_for_users: DF.Table[POSProfileUser]
|
||||
apply_discount_on: DF.Literal["Grand Total", "Net Total"]
|
||||
|
||||
@@ -1104,20 +1104,19 @@ class SalesInvoice(SellingController):
|
||||
self.validate_pos_opening_entry()
|
||||
|
||||
def validate_full_payment(self):
|
||||
allow_partial_payment = frappe.db.get_value("POS Profile", self.pos_profile, "allow_partial_payment")
|
||||
invoice_total = flt(self.rounded_total) or flt(self.grand_total)
|
||||
|
||||
if self.docstatus == 1:
|
||||
if self.is_return and self.paid_amount != invoice_total:
|
||||
frappe.throw(
|
||||
msg=_("Partial Payment in POS Transactions are not allowed."),
|
||||
exc=PartialPaymentValidationError,
|
||||
)
|
||||
|
||||
if self.paid_amount < invoice_total:
|
||||
frappe.throw(
|
||||
msg=_("Partial Payment in POS Transactions are not allowed."),
|
||||
exc=PartialPaymentValidationError,
|
||||
)
|
||||
if (
|
||||
self.docstatus == 1
|
||||
and not self.is_return
|
||||
and not allow_partial_payment
|
||||
and self.paid_amount < invoice_total
|
||||
):
|
||||
frappe.throw(
|
||||
msg=_("Partial Payment in POS Transactions are not allowed."),
|
||||
exc=PartialPaymentValidationError,
|
||||
)
|
||||
|
||||
def validate_pos_opening_entry(self):
|
||||
opening_entries = frappe.get_all(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(),
|
||||
]);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1042,6 +1042,7 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
"Credit Note Issued": "gray",
|
||||
"Partly Paid": "yellow",
|
||||
Overdue: "yellow",
|
||||
Unpaid: "red",
|
||||
};
|
||||
|
||||
transaction_container.append(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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.");
|
||||
|
||||
Reference in New Issue
Block a user