Skip to content

Commit

Permalink
fix: Provision to apply early payment discount if payment is recorded…
Browse files Browse the repository at this point in the history
… 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`
  • Loading branch information
marination committed Mar 14, 2023
1 parent e1d2806 commit d6d0163
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 15 deletions.
19 changes: 15 additions & 4 deletions erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1686,7 +1686,14 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre

@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)
Expand All @@ -1713,15 +1720,17 @@ def get_payment_entry(
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")
pe.payment_type = payment_type
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))
Expand Down Expand Up @@ -1931,7 +1940,9 @@ def set_paid_amount_and_received_amount(
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"]
Expand All @@ -1940,7 +1951,7 @@ def apply_early_payment_discount(paid_amount, received_amount, doc, party_accoun

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")
Expand Down
9 changes: 9 additions & 0 deletions erpnext/accounts/doctype/payment_entry/test_payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ def test_payment_entry_against_payment_terms_with_discount_amount(self):
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.

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'));
}

Expand Down
9 changes: 6 additions & 3 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e

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) {
Expand Down
6 changes: 5 additions & 1 deletion erpnext/buying/doctype/purchase_order/purchase_order.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
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) {
Expand Down
52 changes: 46 additions & 6 deletions erpnext/public/js/controllers/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -1897,20 +1897,60 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}

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 = [
Expand Down

0 comments on commit d6d0163

Please sign in to comment.