fix: Provision to apply early payment discount if payment is recorded late
- Party could have paid on time but payment is recorded late
- Prompt for reference date so that discount is applied while mapping
- Prompt only if discount in payment schedule of valid doctypes
- test: Reference date and impact on PE
- `make_payment_entry` (JS) must be able to access `this`
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 8e47063..15fd0c6 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1686,7 +1686,14 @@
@frappe.whitelist()
def get_payment_entry(
- dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
+ dt,
+ dn,
+ party_amount=None,
+ bank_account=None,
+ bank_amount=None,
+ party_type=None,
+ payment_type=None,
+ reference_date=None,
):
reference_doc = None
doc = frappe.get_doc(dt, dn)
@@ -1713,8 +1720,9 @@
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
)
+ reference_date = getdate(reference_date)
paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount(
- paid_amount, received_amount, doc, party_account_currency
+ paid_amount, received_amount, doc, party_account_currency, reference_date
)
pe = frappe.new_doc("Payment Entry")
@@ -1722,6 +1730,7 @@
pe.company = doc.company
pe.cost_center = doc.get("cost_center")
pe.posting_date = nowdate()
+ pe.reference_date = reference_date
pe.mode_of_payment = doc.get("mode_of_payment")
pe.party_type = party_type
pe.party = doc.get(scrub(party_type))
@@ -1931,7 +1940,9 @@
return paid_amount, received_amount
-def apply_early_payment_discount(paid_amount, received_amount, doc, party_account_currency):
+def apply_early_payment_discount(
+ paid_amount, received_amount, doc, party_account_currency, reference_date
+):
total_discount = 0
valid_discounts = []
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
@@ -1940,7 +1951,7 @@
if doc.doctype in eligible_for_payments and has_payment_schedule:
for term in doc.payment_schedule:
- if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
+ if not term.discounted_amount and term.discount and reference_date <= term.discount_date:
if term.discount_type == "Percentage":
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 6e5c25e..ef57c99 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -302,6 +302,15 @@
si.save()
si.submit()
+ # Set reference date past discount cut off date
+ pe_1 = get_payment_entry(
+ "Sales Invoice",
+ si.name,
+ bank_account="_Test Cash - _TC",
+ reference_date=frappe.utils.add_days(si.posting_date, 2),
+ )
+ self.assertEqual(pe_1.paid_amount, 236.0) # discount not applied
+
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe.references[0].allocated_amount, 236.0)
self.assertEqual(pe.paid_amount, 186)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index e2b4a1a..5c9168b 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -82,7 +82,11 @@
if(doc.docstatus == 1 && doc.outstanding_amount != 0
&& !(doc.is_return && doc.return_against) && !doc.on_hold) {
- this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __('Create'));
+ this.frm.add_custom_button(
+ __('Payment'),
+ () => this.make_payment_entry(),
+ __('Create')
+ );
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
}
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 47e3f9b..56e412b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -93,9 +93,12 @@
if (doc.docstatus == 1 && doc.outstanding_amount!=0
&& !(cint(doc.is_return) && doc.return_against)) {
- cur_frm.add_custom_button(__('Payment'),
- this.make_payment_entry, __('Create'));
- cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
+ this.frm.add_custom_button(
+ __('Payment'),
+ () => this.make_payment_entry(),
+ __('Create')
+ );
+ this.frm.page.set_inner_btn_group_as_primary(__('Create'));
}
if(doc.docstatus==1 && !doc.is_return) {
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 47089f7..c6c9f1f 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -236,7 +236,11 @@
this.make_purchase_invoice, __('Create'));
if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
- cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
+ this.frm.add_custom_button(
+ __('Payment'),
+ () => this.make_payment_entry(),
+ __('Create')
+ );
}
if(flt(doc.per_billed) < 100) {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 8d69ea0..0bd4d91 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1897,20 +1897,60 @@
}
make_payment_entry() {
+ let via_journal_entry = this.frm.doc.__onload && this.frm.doc.__onload.make_payment_via_journal_entry;
+ if(this.has_discount_in_schedule() && !via_journal_entry) {
+ // If early payment discount is applied, ask user for reference date
+ this.prompt_user_for_reference_date();
+ } else {
+ this.make_mapped_payment_entry();
+ }
+ }
+
+ make_mapped_payment_entry(args) {
+ var me = this;
+ args = args || { "dt": this.frm.doc.doctype, "dn": this.frm.doc.name };
return frappe.call({
- method: cur_frm.cscript.get_method_for_payment(),
- args: {
- "dt": cur_frm.doc.doctype,
- "dn": cur_frm.doc.name
- },
+ method: me.get_method_for_payment(),
+ args: args,
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
- // cur_frm.refresh_fields()
}
});
}
+ prompt_user_for_reference_date(){
+ var me = this;
+ frappe.prompt({
+ label: __("Cheque/Reference Date"),
+ fieldname: "reference_date",
+ fieldtype: "Date",
+ reqd: 1,
+ }, (values) => {
+ let args = {
+ "dt": me.frm.doc.doctype,
+ "dn": me.frm.doc.name,
+ "reference_date": values.reference_date
+ }
+ me.make_mapped_payment_entry(args);
+ },
+ __("Reference Date for Early Payment Discount"),
+ __("Continue")
+ );
+ }
+
+ has_discount_in_schedule() {
+ let is_eligible = in_list(
+ ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"],
+ this.frm.doctype
+ );
+ let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length;
+ if(!is_eligible || !has_payment_schedule) return false;
+
+ let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date);
+ return has_discount;
+ }
+
make_quality_inspection() {
let data = [];
const fields = [