mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-07 15:12:51 +00:00
refactor: discount on early payments
This commit is contained in:
committed by
Deepesh Garg
parent
88da5f9073
commit
666af17cfa
@@ -343,13 +343,13 @@ class PaymentEntry(AccountsController):
|
|||||||
payment_schedule = frappe.get_all(
|
payment_schedule = frappe.get_all(
|
||||||
'Payment Schedule',
|
'Payment Schedule',
|
||||||
filters={'parent': ref.reference_name},
|
filters={'parent': ref.reference_name},
|
||||||
fields=['paid_amount', 'payment_amount', 'payment_term', 'discount_percentage', 'outstanding']
|
fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding']
|
||||||
)
|
)
|
||||||
for term in payment_schedule:
|
for term in payment_schedule:
|
||||||
invoice_key = (term.payment_term, ref.reference_name)
|
invoice_key = (term.payment_term, ref.reference_name)
|
||||||
invoice_paid_amount_map.setdefault(invoice_key, {})
|
invoice_paid_amount_map.setdefault(invoice_key, {})
|
||||||
invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
|
invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
|
||||||
invoice_paid_amount_map[invoice_key]['discounted_amt'] = term.payment_amount * (term.discount_percentage / 100)
|
invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
|
||||||
|
|
||||||
for key, allocated_amount in iteritems(invoice_payment_amount_map):
|
for key, allocated_amount in iteritems(invoice_payment_amount_map):
|
||||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
||||||
@@ -1337,8 +1337,12 @@ def apply_early_payment_discount(paid_amount, received_amount, doc):
|
|||||||
difference_amount = 0
|
difference_amount = 0
|
||||||
if doc.doctype in ["Sales Invoice", "Purchase Invoice"] and doc.payment_schedule:
|
if doc.doctype in ["Sales Invoice", "Purchase Invoice"] and doc.payment_schedule:
|
||||||
for term in doc.payment_schedule:
|
for term in doc.payment_schedule:
|
||||||
if not term.paid_amount and term.discount_percentage and getdate(nowdate()) <= term.due_date:
|
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
|
||||||
discount_amount = term.payment_amount * (term.discount_percentage / 100)
|
if term.discount_type == 'Percentage':
|
||||||
|
discount_amount = flt(doc.get('grand_total')) * (term.discount / 100)
|
||||||
|
else:
|
||||||
|
discount_amount = term.discount
|
||||||
|
|
||||||
discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1)
|
discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1)
|
||||||
|
|
||||||
if doc.doctype == "Sales Invoice":
|
if doc.doctype == "Sales Invoice":
|
||||||
@@ -1351,7 +1355,8 @@ def apply_early_payment_discount(paid_amount, received_amount, doc):
|
|||||||
difference_amount += discount_amount
|
difference_amount += discount_amount
|
||||||
|
|
||||||
if difference_amount:
|
if difference_amount:
|
||||||
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(difference_amount), alert=1)
|
money = frappe.utils.fmt_money(difference_amount, currency=doc.get('currency'))
|
||||||
|
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
|
||||||
|
|
||||||
return paid_amount, received_amount, difference_amount
|
return paid_amount, received_amount, difference_amount
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,24 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"payment_term",
|
"payment_term",
|
||||||
"discount_percentage",
|
"section_break_15",
|
||||||
"discounted_amount",
|
|
||||||
"outstanding",
|
|
||||||
"description",
|
"description",
|
||||||
"column_break_3",
|
"section_break_4",
|
||||||
"due_date",
|
"due_date",
|
||||||
|
"mode_of_payment",
|
||||||
|
"column_break_5",
|
||||||
"invoice_portion",
|
"invoice_portion",
|
||||||
|
"section_break_6",
|
||||||
|
"discount_type",
|
||||||
|
"discount_date",
|
||||||
|
"column_break_9",
|
||||||
|
"discount",
|
||||||
|
"section_break_9",
|
||||||
"payment_amount",
|
"payment_amount",
|
||||||
"outstanding",
|
|
||||||
"paid_amount",
|
|
||||||
"discounted_amount",
|
"discounted_amount",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"base_payment_amount",
|
"outstanding",
|
||||||
"base_outstanding",
|
"paid_amount"
|
||||||
"base_paid_amount"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -34,6 +37,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
|
"fetch_from": "payment_term.description",
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -80,11 +84,6 @@
|
|||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "discount_percentage",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"label": "Discount (%)"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "discounted_amount",
|
"depends_on": "discounted_amount",
|
||||||
@@ -94,18 +93,64 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "outstanding || paid_amount",
|
|
||||||
"fetch_from": "payment_amount",
|
"fetch_from": "payment_amount",
|
||||||
"fieldname": "outstanding",
|
"fieldname": "outstanding",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Outstanding",
|
"label": "Outstanding",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_5",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "discount",
|
||||||
|
"fieldname": "discount_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Discount Date",
|
||||||
|
"mandatory_depends_on": "discount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Percentage",
|
||||||
|
"fetch_from": "payment_term.discount_type",
|
||||||
|
"fieldname": "discount_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Discount Type",
|
||||||
|
"options": "Percentage\nAmount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "payment_term.discount",
|
||||||
|
"fieldname": "discount",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Discount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_9",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "section_break_15",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_6",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_9",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-11 20:11:42.058393",
|
"modified": "2021-02-15 21:03:12.540546",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Schedule",
|
"name": "Payment Schedule",
|
||||||
|
|||||||
@@ -1,2 +1,19 @@
|
|||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
frappe.ui.form.on('Payment Term', {
|
||||||
|
discount(frm) {
|
||||||
|
frm.trigger('set_dynamic_description');
|
||||||
|
},
|
||||||
|
discount_type(frm) {
|
||||||
|
frm.trigger('set_dynamic_description');
|
||||||
|
},
|
||||||
|
set_dynamic_description(frm) {
|
||||||
|
if (frm.doc.discount) {
|
||||||
|
let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
|
||||||
|
if (frm.doc.discount_type == 'Amount') {
|
||||||
|
description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)])
|
||||||
|
}
|
||||||
|
frm.set_df_property("discount", "description", description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -10,12 +10,17 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"payment_term_name",
|
"payment_term_name",
|
||||||
"invoice_portion",
|
"invoice_portion",
|
||||||
"discount_percentage",
|
"mode_of_payment",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"due_date_based_on",
|
"due_date_based_on",
|
||||||
"credit_days",
|
"credit_days",
|
||||||
"credit_months",
|
"credit_months",
|
||||||
"mode_of_payment",
|
"section_break_8",
|
||||||
|
"discount_type",
|
||||||
|
"discount",
|
||||||
|
"column_break_11",
|
||||||
|
"discount_validity_based_on",
|
||||||
|
"discount_validity",
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"description"
|
"description"
|
||||||
],
|
],
|
||||||
@@ -74,13 +79,44 @@
|
|||||||
"label": "Description"
|
"label": "Description"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "discount_percentage",
|
"fieldname": "section_break_8",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Discount Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Percentage",
|
||||||
|
"fieldname": "discount_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Discount Type",
|
||||||
|
"options": "Percentage\nAmount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "discount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Discount (%)"
|
"label": "Discount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Day(s) after invoice date",
|
||||||
|
"depends_on": "discount",
|
||||||
|
"fieldname": "discount_validity_based_on",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Discount Validity Based On",
|
||||||
|
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "discount",
|
||||||
|
"fieldname": "discount_validity",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Discount Validity",
|
||||||
|
"mandatory_depends_on": "discount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_11",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-11 20:09:42.559344",
|
"modified": "2021-02-15 20:30:56.256403",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Term",
|
"name": "Payment Term",
|
||||||
|
|||||||
@@ -12,8 +12,17 @@ from frappe import _
|
|||||||
|
|
||||||
class PaymentTermsTemplate(Document):
|
class PaymentTermsTemplate(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_invoice_portion()
|
||||||
self.check_duplicate_terms()
|
self.check_duplicate_terms()
|
||||||
|
|
||||||
|
def validate_invoice_portion(self):
|
||||||
|
total_portion = 0
|
||||||
|
for term in self.terms:
|
||||||
|
total_portion += flt(term.get('invoice_portion', 0))
|
||||||
|
|
||||||
|
if flt(total_portion, 2) != 100.00:
|
||||||
|
frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red')
|
||||||
|
|
||||||
def check_duplicate_terms(self):
|
def check_duplicate_terms(self):
|
||||||
terms = []
|
terms = []
|
||||||
for term in self.terms:
|
for term in self.terms:
|
||||||
|
|||||||
@@ -6,14 +6,21 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"payment_term",
|
"payment_term",
|
||||||
"discount_percentage",
|
"section_break_13",
|
||||||
"description",
|
"description",
|
||||||
"column_break_3",
|
"section_break_4",
|
||||||
"invoice_portion",
|
"invoice_portion",
|
||||||
|
"mode_of_payment",
|
||||||
|
"column_break_3",
|
||||||
"due_date_based_on",
|
"due_date_based_on",
|
||||||
"credit_days",
|
"credit_days",
|
||||||
"credit_months",
|
"credit_months",
|
||||||
"mode_of_payment"
|
"section_break_8",
|
||||||
|
"discount_type",
|
||||||
|
"discount",
|
||||||
|
"column_break_11",
|
||||||
|
"discount_validity_based_on",
|
||||||
|
"discount_validity"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -84,16 +91,60 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "payment_term.discount_percentage",
|
"fieldname": "section_break_8",
|
||||||
"fieldname": "discount_percentage",
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Discount Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Percentage",
|
||||||
|
"fetch_from": "payment_term.discount_type",
|
||||||
|
"fieldname": "discount_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Discount Type",
|
||||||
|
"options": "Percentage\nAmount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "payment_term.discount",
|
||||||
|
"fieldname": "discount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Discount (%)"
|
"label": "Discount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_11",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Day(s) after invoice date",
|
||||||
|
"depends_on": "discount",
|
||||||
|
"fetch_from": "payment_term.discount_validity_based_on",
|
||||||
|
"fieldname": "discount_validity_based_on",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Discount Validity Based On",
|
||||||
|
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "section_break_13",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "discount",
|
||||||
|
"fetch_from": "payment_term.discount_validity",
|
||||||
|
"fieldname": "discount_validity",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Discount Validity",
|
||||||
|
"mandatory_depends_on": "discount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-11 20:10:29.233745",
|
"modified": "2021-02-15 21:03:38.211696",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Terms Template Detail",
|
"name": "Payment Terms Template Detail",
|
||||||
|
|||||||
@@ -911,7 +911,6 @@ class AccountsController(TransactionBase):
|
|||||||
for d in self.get("payment_schedule"):
|
for d in self.get("payment_schedule"):
|
||||||
if d.invoice_portion:
|
if d.invoice_portion:
|
||||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||||
d.discounted_amount = flt(d.payment_amount * flt(d.discount_percentage / 100), d.precision('payment_amount'))
|
|
||||||
d.outstanding = d.payment_amount
|
d.outstanding = d.payment_amount
|
||||||
|
|
||||||
def set_due_date(self):
|
def set_due_date(self):
|
||||||
@@ -950,7 +949,7 @@ class AccountsController(TransactionBase):
|
|||||||
total = 0
|
total = 0
|
||||||
base_total = 0
|
base_total = 0
|
||||||
for d in self.get("payment_schedule"):
|
for d in self.get("payment_schedule"):
|
||||||
total += flt(d.payment_amount) if not d.discount_percentage else 0
|
total += flt(d.payment_amount)
|
||||||
|
|
||||||
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||||
grand_total = self.get("rounded_total") or self.grand_total
|
grand_total = self.get("rounded_total") or self.grand_total
|
||||||
@@ -1232,16 +1231,18 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, base_gra
|
|||||||
term_details.description = term.description
|
term_details.description = term.description
|
||||||
term_details.invoice_portion = term.invoice_portion
|
term_details.invoice_portion = term.invoice_portion
|
||||||
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
|
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
|
||||||
term_details.discounted_amount = term_details.payment_amount * (term.discount_percentage / 100)
|
term_details.discount_type = term.discount_type
|
||||||
|
term_details.discount = term.discount
|
||||||
|
# term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount
|
||||||
term_details.outstanding = term_details.payment_amount
|
term_details.outstanding = term_details.payment_amount
|
||||||
|
|
||||||
term_details.mode_of_payment = term.mode_of_payment
|
term_details.mode_of_payment = term.mode_of_payment
|
||||||
term_details.discount_percentage = term.discount_percentage
|
|
||||||
|
|
||||||
if bill_date:
|
if bill_date:
|
||||||
term_details.due_date = get_due_date(term, bill_date)
|
term_details.due_date = get_due_date(term, bill_date)
|
||||||
|
term_details.discount_date = get_discount_date(term, bill_date)
|
||||||
elif posting_date:
|
elif posting_date:
|
||||||
term_details.due_date = get_due_date(term, posting_date)
|
term_details.due_date = get_due_date(term, posting_date)
|
||||||
|
term_details.discount_date = get_discount_date(term, posting_date)
|
||||||
|
|
||||||
if getdate(term_details.due_date) < getdate(posting_date):
|
if getdate(term_details.due_date) < getdate(posting_date):
|
||||||
term_details.due_date = posting_date
|
term_details.due_date = posting_date
|
||||||
@@ -1259,6 +1260,17 @@ def get_due_date(term, posting_date=None, bill_date=None):
|
|||||||
due_date = add_months(get_last_day(date), term.credit_months)
|
due_date = add_months(get_last_day(date), term.credit_months)
|
||||||
return due_date
|
return due_date
|
||||||
|
|
||||||
|
def get_discount_date(term, posting_date=None, bill_date=None):
|
||||||
|
discount_validity = None
|
||||||
|
date = bill_date or posting_date
|
||||||
|
if term.discount_validity_based_on == "Day(s) after invoice date":
|
||||||
|
discount_validity = add_days(date, term.discount_validity)
|
||||||
|
elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
|
||||||
|
discount_validity = add_days(get_last_day(date), term.discount_validity)
|
||||||
|
elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
|
||||||
|
discount_validity = add_months(get_last_day(date), term.discount_validity)
|
||||||
|
return discount_validity
|
||||||
|
|
||||||
def get_supplier_block_status(party_name):
|
def get_supplier_block_status(party_name):
|
||||||
"""
|
"""
|
||||||
Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of
|
Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of
|
||||||
|
|||||||
Reference in New Issue
Block a user