diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 81a0395b8b0..e949d5d07e4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -343,13 +343,13 @@ class PaymentEntry(AccountsController): payment_schedule = frappe.get_all( 'Payment Schedule', 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: invoice_key = (term.payment_term, ref.reference_name) invoice_paid_amount_map.setdefault(invoice_key, {}) 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): 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 if doc.doctype in ["Sales Invoice", "Purchase Invoice"] and doc.payment_schedule: for term in doc.payment_schedule: - if not term.paid_amount and term.discount_percentage and getdate(nowdate()) <= term.due_date: - discount_amount = term.payment_amount * (term.discount_percentage / 100) + if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date: + 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) if doc.doctype == "Sales Invoice": @@ -1351,7 +1355,8 @@ def apply_early_payment_discount(paid_amount, received_amount, doc): difference_amount += discount_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 diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e33b86b222a..e362566af08 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -6,21 +6,24 @@ "engine": "InnoDB", "field_order": [ "payment_term", - "discount_percentage", - "discounted_amount", - "outstanding", + "section_break_15", "description", - "column_break_3", + "section_break_4", "due_date", + "mode_of_payment", + "column_break_5", "invoice_portion", + "section_break_6", + "discount_type", + "discount_date", + "column_break_9", + "discount", + "section_break_9", "payment_amount", - "outstanding", - "paid_amount", "discounted_amount", "column_break_3", - "base_payment_amount", - "base_outstanding", - "base_paid_amount" + "outstanding", + "paid_amount" ], "fields": [ { @@ -34,6 +37,7 @@ }, { "columns": 2, + "fetch_from": "payment_term.description", "fieldname": "description", "fieldtype": "Small Text", "in_list_view": 1, @@ -80,11 +84,6 @@ "fieldname": "column_break_3", "fieldtype": "Column Break" }, - { - "fieldname": "discount_percentage", - "fieldtype": "Float", - "label": "Discount (%)" - }, { "default": "0", "depends_on": "discounted_amount", @@ -94,18 +93,64 @@ "read_only": 1 }, { - "depends_on": "outstanding || paid_amount", "fetch_from": "payment_amount", "fieldname": "outstanding", "fieldtype": "Currency", "label": "Outstanding", "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, "istable": 1, "links": [], - "modified": "2021-02-11 20:11:42.058393", + "modified": "2021-02-15 21:03:12.540546", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", diff --git a/erpnext/accounts/doctype/payment_term/payment_term.js b/erpnext/accounts/doctype/payment_term/payment_term.js index 054c2d11917..25835298a48 100644 --- a/erpnext/accounts/doctype/payment_term/payment_term.js +++ b/erpnext/accounts/doctype/payment_term/payment_term.js @@ -1,2 +1,19 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // 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); + } + } +}) \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_term/payment_term.json b/erpnext/accounts/doctype/payment_term/payment_term.json index 9bbc4e66bcd..aec4965d79a 100644 --- a/erpnext/accounts/doctype/payment_term/payment_term.json +++ b/erpnext/accounts/doctype/payment_term/payment_term.json @@ -10,12 +10,17 @@ "field_order": [ "payment_term_name", "invoice_portion", - "discount_percentage", + "mode_of_payment", "column_break_3", "due_date_based_on", "credit_days", "credit_months", - "mode_of_payment", + "section_break_8", + "discount_type", + "discount", + "column_break_11", + "discount_validity_based_on", + "discount_validity", "section_break_6", "description" ], @@ -74,13 +79,44 @@ "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", - "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": [], - "modified": "2021-02-11 20:09:42.559344", + "modified": "2021-02-15 20:30:56.256403", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Term", diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py index 52ed85a279d..80e3348d817 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py @@ -12,8 +12,17 @@ from frappe import _ class PaymentTermsTemplate(Document): def validate(self): + self.validate_invoice_portion() 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): terms = [] for term in self.terms: diff --git a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json index 0c99b0cfbbf..66468588aa3 100644 --- a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json +++ b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json @@ -6,14 +6,21 @@ "engine": "InnoDB", "field_order": [ "payment_term", - "discount_percentage", + "section_break_13", "description", - "column_break_3", + "section_break_4", "invoice_portion", + "mode_of_payment", + "column_break_3", "due_date_based_on", "credit_days", "credit_months", - "mode_of_payment" + "section_break_8", + "discount_type", + "discount", + "column_break_11", + "discount_validity_based_on", + "discount_validity" ], "fields": [ { @@ -84,16 +91,60 @@ "fieldtype": "Column Break" }, { - "fetch_from": "payment_term.discount_percentage", - "fieldname": "discount_percentage", + "fieldname": "section_break_8", + "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", - "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, "istable": 1, "links": [], - "modified": "2021-02-11 20:10:29.233745", + "modified": "2021-02-15 21:03:38.211696", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Terms Template Detail", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3565b72bb18..3d507ee0b49 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -911,7 +911,6 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: 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 def set_due_date(self): @@ -950,7 +949,7 @@ class AccountsController(TransactionBase): total = 0 base_total = 0 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 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.invoice_portion = term.invoice_portion 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.mode_of_payment = term.mode_of_payment - term_details.discount_percentage = term.discount_percentage if 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: 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): 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) 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): """ Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of