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`
This commit is contained in:
parent
e1d28063b0
commit
d6d0163514
@ -1686,7 +1686,14 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_payment_entry(
|
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
|
reference_doc = None
|
||||||
doc = frappe.get_doc(dt, dn)
|
doc = frappe.get_doc(dt, dn)
|
||||||
@ -1713,8 +1720,9 @@ def get_payment_entry(
|
|||||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
|
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, 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 = frappe.new_doc("Payment Entry")
|
||||||
@ -1722,6 +1730,7 @@ def get_payment_entry(
|
|||||||
pe.company = doc.company
|
pe.company = doc.company
|
||||||
pe.cost_center = doc.get("cost_center")
|
pe.cost_center = doc.get("cost_center")
|
||||||
pe.posting_date = nowdate()
|
pe.posting_date = nowdate()
|
||||||
|
pe.reference_date = reference_date
|
||||||
pe.mode_of_payment = doc.get("mode_of_payment")
|
pe.mode_of_payment = doc.get("mode_of_payment")
|
||||||
pe.party_type = party_type
|
pe.party_type = party_type
|
||||||
pe.party = doc.get(scrub(party_type))
|
pe.party = doc.get(scrub(party_type))
|
||||||
@ -1931,7 +1940,9 @@ def set_paid_amount_and_received_amount(
|
|||||||
return paid_amount, 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
|
total_discount = 0
|
||||||
valid_discounts = []
|
valid_discounts = []
|
||||||
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
|
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
|
||||||
@ -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:
|
if doc.doctype in eligible_for_payments and has_payment_schedule:
|
||||||
for term in doc.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":
|
if term.discount_type == "Percentage":
|
||||||
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
|
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
|
||||||
|
@ -302,6 +302,15 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
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")
|
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.references[0].allocated_amount, 236.0)
|
||||||
self.assertEqual(pe.paid_amount, 186)
|
self.assertEqual(pe.paid_amount, 186)
|
||||||
|
@ -82,7 +82,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
|
|
||||||
if(doc.docstatus == 1 && doc.outstanding_amount != 0
|
if(doc.docstatus == 1 && doc.outstanding_amount != 0
|
||||||
&& !(doc.is_return && doc.return_against) && !doc.on_hold) {
|
&& !(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'));
|
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,9 +93,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
|
|
||||||
if (doc.docstatus == 1 && doc.outstanding_amount!=0
|
if (doc.docstatus == 1 && doc.outstanding_amount!=0
|
||||||
&& !(cint(doc.is_return) && doc.return_against)) {
|
&& !(cint(doc.is_return) && doc.return_against)) {
|
||||||
cur_frm.add_custom_button(__('Payment'),
|
this.frm.add_custom_button(
|
||||||
this.make_payment_entry, __('Create'));
|
__('Payment'),
|
||||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
() => this.make_payment_entry(),
|
||||||
|
__('Create')
|
||||||
|
);
|
||||||
|
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(doc.docstatus==1 && !doc.is_return) {
|
if(doc.docstatus==1 && !doc.is_return) {
|
||||||
|
@ -236,7 +236,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
this.make_purchase_invoice, __('Create'));
|
this.make_purchase_invoice, __('Create'));
|
||||||
|
|
||||||
if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
|
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) {
|
if(flt(doc.per_billed) < 100) {
|
||||||
|
@ -1897,20 +1897,60 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_payment_entry() {
|
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({
|
return frappe.call({
|
||||||
method: cur_frm.cscript.get_method_for_payment(),
|
method: me.get_method_for_payment(),
|
||||||
args: {
|
args: args,
|
||||||
"dt": cur_frm.doc.doctype,
|
|
||||||
"dn": cur_frm.doc.name
|
|
||||||
},
|
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
var doclist = frappe.model.sync(r.message);
|
var doclist = frappe.model.sync(r.message);
|
||||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
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() {
|
make_quality_inspection() {
|
||||||
let data = [];
|
let data = [];
|
||||||
const fields = [
|
const fields = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user