From 3a9eec2e92086c6b437cdcdf700b57b658051040 Mon Sep 17 00:00:00 2001 From: Shreya Shah Date: Fri, 16 Feb 2018 13:05:21 +0530 Subject: [PATCH] Calculate due date in Purchase Invoice on the basis of Supplier invoice date (#12913) * pass bill_date as parameter and calculate due_date on that basis * calculate payment_schedule on the basis of bill_date if present else posting_date * add bill_date as an argument to get_party_details * pass bill_date on supplier trigger in purchase invoice --- .../purchase_invoice/purchase_invoice.js | 3 +- .../purchase_invoice/purchase_invoice.py | 2 +- erpnext/accounts/party.py | 45 +++++++++---------- erpnext/controllers/accounts_controller.py | 28 +++++++----- erpnext/public/js/controllers/transaction.js | 13 ++++-- erpnext/public/js/utils/party.js | 1 + 6 files changed, 50 insertions(+), 42 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 26cc59892f..39f80396e3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -103,10 +103,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details", { posting_date: this.frm.doc.posting_date, + bill_date: this.frm.doc.bill_date, party: this.frm.doc.supplier, party_type: "Supplier", account: this.frm.doc.credit_to, - price_list: this.frm.doc.buying_price_list, + price_list: this.frm.doc.buying_price_list }, function() { me.apply_pricing_rule(); }) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 21b71ff37c..d7e14e1f6c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -96,7 +96,7 @@ class PurchaseInvoice(BuyingController): if not self.credit_to: self.credit_to = get_party_account("Supplier", self.supplier, self.company) if not self.due_date: - self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company) + self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company, self.bill_date) super(PurchaseInvoice, self).set_missing_values(for_validate) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 55d2c21f72..90bb0bb807 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -23,22 +23,19 @@ class DuplicatePartyAccountError(frappe.ValidationError): pass @frappe.whitelist() def get_party_details(party=None, account=None, party_type="Customer", company=None, - posting_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False): + posting_date=None, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False): if not party: return {} - if not frappe.db.exists(party_type, party): frappe.throw(_("{0}: {1} does not exists").format(party_type, party)) - return _get_party_details(party, account, party_type, - company, posting_date, price_list, currency, doctype, ignore_permissions) + company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions) def _get_party_details(party=None, account=None, party_type="Customer", company=None, - posting_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False): - - out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, doctype)) + posting_date=None, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False): + out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)) party = out[party_type.lower()] if not ignore_permissions and not frappe.has_permission(party_type, "read", party): @@ -150,7 +147,7 @@ def set_price_list(out, party, party_type, given_price_list): out["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list -def set_account_and_due_date(party, account, party_type, company, posting_date, doctype): +def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype): if doctype not in ["Sales Invoice", "Purchase Invoice"]: # not an invoice return { @@ -161,12 +158,12 @@ def set_account_and_due_date(party, account, party_type, company, posting_date, account = get_party_account(party_type, party, company) account_fieldname = "debit_to" if party_type=="Customer" else "credit_to" - out = { party_type.lower(): party, account_fieldname : account, - "due_date": get_due_date(posting_date, party_type, party, company) + "due_date": get_due_date(posting_date, party_type, party, company, bill_date) } + return out @frappe.whitelist() @@ -268,32 +265,34 @@ def validate_party_accounts(doc): @frappe.whitelist() -def get_due_date(posting_date, party_type, party, company=None): +def get_due_date(posting_date, party_type, party, company=None, bill_date=None): """Get due date from `Payment Terms Template`""" due_date = None - if posting_date and party: - due_date = posting_date + if (bill_date or posting_date) and party: + due_date = bill_date or posting_date template_name = get_pyt_term_template(party, party_type, company) if template_name: - due_date = get_due_date_from_template(template_name, posting_date).strftime("%Y-%m-%d") + due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d") else: if party_type == "Supplier": supplier_type = frappe.db.get_value(party_type, party, fieldname="supplier_type") template_name = frappe.db.get_value("Supplier Type", supplier_type, fieldname="payment_terms") if template_name: - due_date = get_due_date_from_template(template_name, posting_date).strftime("%Y-%m-%d") - + due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d") + # If due date is calculated from bill_date, check this condition + if getdate(due_date) < getdate(posting_date): + due_date = posting_date return due_date - -def get_due_date_from_template(template_name, posting_date): +def get_due_date_from_template(template_name, posting_date, bill_date): """ Inspects all `Payment Term`s from the a `Payment Terms Template` and returns the due date after considering all the `Payment Term`s requirements. :param template_name: Name of the `Payment Terms Template` :return: String representing the calculated due date """ - due_date = getdate(posting_date) + due_date = getdate(bill_date or posting_date) + template = frappe.get_doc('Payment Terms Template', template_name) for term in template.terms: @@ -303,14 +302,13 @@ def get_due_date_from_template(template_name, posting_date): due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days)) else: due_date = max(due_date, add_months(get_last_day(due_date), term.credit_months)) - return due_date -def validate_due_date(posting_date, due_date, party_type, party, company=None): +def validate_due_date(posting_date, due_date, party_type, party, company=None, bill_date=None): if getdate(due_date) < getdate(posting_date): frappe.throw(_("Due Date cannot be before Posting Date")) else: - default_due_date = get_due_date(posting_date, party_type, party, company) + default_due_date = get_due_date(posting_date, party_type, party, company, bill_date) if not default_due_date: return @@ -363,7 +361,6 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup def get_pyt_term_template(party_name, party_type, company=None): if party_type not in ("Customer", "Supplier"): return - template = None if party_type == 'Customer': customer = frappe.db.get_value("Customer", party_name, @@ -377,13 +374,11 @@ def get_pyt_term_template(party_name, party_type, company=None): supplier = frappe.db.get_value("Supplier", party_name, fieldname=['payment_terms', "supplier_type"], as_dict=1) template = supplier.payment_terms - if not template and supplier.supplier_type: template = frappe.db.get_value("Supplier Type", supplier.supplier_type, fieldname='payment_terms') if not template and company: template = frappe.db.get_value("Company", company, fieldname='payment_terms') - return template def validate_party_frozen_disabled(party_type, party_name): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b7017c1d84..1425160629 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -137,7 +137,7 @@ class AccountsController(TransactionBase): validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company) elif self.doctype == "Purchase Invoice": - validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company) + validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company, self.bill_date) def set_price_list_currency(self, buying_or_selling): if self.meta.get_field("posting_date"): @@ -908,7 +908,7 @@ def update_invoice_status(): where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""") @frappe.whitelist() -def get_payment_terms(terms_template, posting_date=None, grand_total=None): +def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None): if not terms_template: return @@ -916,13 +916,13 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None): schedule = [] for d in terms_doc.get("terms"): - term_details = get_payment_term_details(d, posting_date, grand_total) + term_details = get_payment_term_details(d, posting_date, grand_total, bill_date) schedule.append(term_details) return schedule @frappe.whitelist() -def get_payment_term_details(term, posting_date=None, grand_total=None): +def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None): term_details = frappe._dict() if isinstance(term, unicode): term = frappe.get_doc("Payment Term", term) @@ -931,17 +931,23 @@ def get_payment_term_details(term, posting_date=None, grand_total=None): term_details.description = term.description term_details.invoice_portion = term.invoice_portion term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 - if posting_date: - term_details.due_date = get_due_date(posting_date, term) + if bill_date: + term_details.due_date = get_due_date(term, bill_date) + elif posting_date: + term_details.due_date = get_due_date(term, posting_date) + + if getdate(term_details.due_date) < getdate(posting_date): + term_details.due_date = posting_date + return term_details -def get_due_date(posting_date, term): +def get_due_date(term, posting_date=None, bill_date=None): due_date = None + date = bill_date or posting_date if term.due_date_based_on == "Day(s) after invoice date": - due_date = add_days(posting_date, term.credit_days) + due_date = add_days(date, term.credit_days) elif term.due_date_based_on == "Day(s) after the end of the invoice month": - due_date = add_days(get_last_day(posting_date), term.credit_days) + due_date = add_days(get_last_day(date), term.credit_days) elif term.due_date_based_on == "Month(s) after the end of the invoice month": - due_date = add_months(get_last_day(posting_date), term.credit_months) - + due_date = add_months(get_last_day(date), term.credit_months) return due_date diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index e8c3262e1d..274bb3e9ff 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -518,6 +518,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ args: { "posting_date": me.frm.doc.posting_date, "party_type": me.frm.doc.doctype == "Sales Invoice" ? "Customer" : "Supplier", + "bill_date": me.frm.doc.bill_date, "party": me.frm.doc.doctype == "Sales Invoice" ? me.frm.doc.customer : me.frm.doc.supplier, "company": me.frm.doc.company }, @@ -561,9 +562,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, + bill_date: function() { + this.posting_date(); + }, + recalculate_terms: function() { const doc = this.frm.doc; - if (doc.payment_terms_template) { this.payment_terms_template(); } else if (doc.payment_schedule) { @@ -1272,14 +1276,14 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var me = this; const doc = this.frm.doc; if(doc.payment_terms_template && doc.doctype !== 'Delivery Note') { - var posting_date = doc.bill_date || doc.posting_date || doc.transaction_date; - + var posting_date = doc.posting_date || doc.transaction_date; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_terms", args: { terms_template: doc.payment_terms_template, posting_date: posting_date, - grand_total: doc.rounded_total || doc.grand_total + grand_total: doc.rounded_total || doc.grand_total, + bill_date: doc.bill_date }, callback: function(r) { if(r.message && !r.exc) { @@ -1297,6 +1301,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { term: row.payment_term, + bill_date: this.frm.doc.bill_date, posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date, grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total }, diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 75d5ce9e21..5480aed559 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -18,6 +18,7 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { args = { party: frm.doc.supplier, party_type: "Supplier", + bill_date: frm.doc.bill_date, price_list: frm.doc.buying_price_list }; }