From 1efce21ba4ed7b9d65d4d9829f23ebda58c7773b Mon Sep 17 00:00:00 2001 From: Pruthvi Patel Date: Sat, 30 Oct 2021 16:44:15 +0530 Subject: [PATCH 1/4] refactor: update_invoice_status with query builder (cherry picked from commit 6c96ed4e11b17acf46d5ea9bba4f1df60cd3238a) --- erpnext/controllers/accounts_controller.py | 104 +++++++++++---------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 28cded2e671..ac78a524c5f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -7,6 +7,7 @@ import json import frappe from frappe import _, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied +from frappe.query_builder.functions import Sum from frappe.utils import ( add_days, add_months, @@ -1692,58 +1693,63 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, def update_invoice_status(): """Updates status as Overdue for applicable invoices. Runs daily.""" today = getdate() - + payment_schedule = frappe.qb.DocType("Payment Schedule") for doctype in ("Sales Invoice", "Purchase Invoice"): - frappe.db.sql(""" - UPDATE `tab{doctype}` invoice SET invoice.status = 'Overdue' - WHERE invoice.docstatus = 1 - AND invoice.status REGEXP '^Unpaid|^Partly Paid' - AND invoice.outstanding_amount > 0 - AND ( - {or_condition} - ( - ( - CASE - WHEN invoice.party_account_currency = invoice.currency - THEN ( - CASE - WHEN invoice.disable_rounded_total - THEN invoice.grand_total - ELSE invoice.rounded_total - END - ) - ELSE ( - CASE - WHEN invoice.disable_rounded_total - THEN invoice.base_grand_total - ELSE invoice.base_rounded_total - END - ) - END - ) - invoice.outstanding_amount - ) < ( - SELECT SUM( - CASE - WHEN invoice.party_account_currency = invoice.currency - THEN ps.payment_amount - ELSE ps.base_payment_amount - END - ) - FROM `tabPayment Schedule` ps - WHERE ps.parent = invoice.name - AND ps.due_date < %(today)s - ) - ) - """.format( - doctype=doctype, - or_condition=( - "invoice.is_pos AND invoice.due_date < %(today)s OR" - if doctype == "Sales Invoice" - else "" - ) - ), {"today": today} + invoice = frappe.qb.DocType(doctype) + + consider_base_amount = invoice.party_account_currency != invoice.currency + payment_amount = ( + frappe.qb.terms.Case() + .when(consider_base_amount, payment_schedule.base_payment_amount) + .else_(payment_schedule.payment_amount) ) + payable_amount = ( + frappe.qb.from_(payment_schedule) + .select(Sum(payment_amount)) + .where( + (payment_schedule.parent == invoice.name) + & (payment_schedule.due_date < today) + ) + ) + + total = ( + frappe.qb.terms.Case() + .when(invoice.disable_rounded_total, invoice.grand_total) + .else_(invoice.rounded_total) + ) + + base_total = ( + frappe.qb.terms.Case() + .when(invoice.disable_rounded_total, invoice.base_grand_total) + .else_(invoice.base_rounded_total) + ) + + total_amount = ( + frappe.qb.terms.Case() + .when(consider_base_amount, base_total) + .else_(total) + ) + + is_overdue = total_amount - invoice.outstanding_amount < payable_amount + + conditions = ( + (invoice.docstatus == 1) + & (invoice.outstanding_amount > 0) + & ( + invoice.status.like('Unpaid%') + | invoice.status.like('Partly Paid%') + ) + & ( + (invoice.is_pos & invoice.due_date < today) | is_overdue + if doctype == "Sales Invoice" + else is_overdue + ) + ) + + frappe.qb.update(invoice).set("status", "Overdue").where(conditions).run() + + @frappe.whitelist() def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): if not terms_template: From e648a2ccde106f18e66f4bb680c49a47ca4664d1 Mon Sep 17 00:00:00 2001 From: Pruthvi Patel Date: Fri, 12 Nov 2021 12:56:29 +0530 Subject: [PATCH 2/4] fix: consider `Discounted` status (cherry picked from commit 0799f378b4e29d376b1e3cdbe3edc132cf15009e) --- erpnext/controllers/accounts_controller.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ac78a524c5f..3cc73402a85 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1737,8 +1737,8 @@ def update_invoice_status(): (invoice.docstatus == 1) & (invoice.outstanding_amount > 0) & ( - invoice.status.like('Unpaid%') - | invoice.status.like('Partly Paid%') + invoice.status.like("Unpaid%") + | invoice.status.like("Partly Paid%") ) & ( (invoice.is_pos & invoice.due_date < today) | is_overdue @@ -1747,7 +1747,13 @@ def update_invoice_status(): ) ) - frappe.qb.update(invoice).set("status", "Overdue").where(conditions).run() + status = ( + frappe.qb.terms.Case() + .when(invoice.status.like("%Discounted"), "Overdue and Discounted") + .else_("Overdue") + ) + + frappe.qb.update(invoice).set("status", status).where(conditions).run() @frappe.whitelist() From 50628d416ce29511b8acb9d72fa4c940fa93dc9f Mon Sep 17 00:00:00 2001 From: Pruthvi Patel Date: Mon, 20 Dec 2021 13:23:14 +0530 Subject: [PATCH 3/4] test: update_invoice_status (cherry picked from commit 4d48ca42282c28ab682dbcec0391b9770630df3a) --- .../sales_invoice/test_sales_invoice.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6696d4ad2a8..e2c1f79bd2e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -19,6 +19,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_comp from erpnext.accounts.utils import PaymentEntryUnlinkError from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data +from erpnext.controllers.accounts_controller import update_invoice_status from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.regional.india.utils import get_ewb_data @@ -2358,6 +2359,41 @@ class TestSalesInvoice(unittest.TestCase): si.reload() self.assertEqual(si.status, "Paid") + def test_update_invoice_status(self): + today = nowdate() + + # Sales Invoice without Payment Schedule + si = create_sales_invoice(posting_date=add_days(today, -5)) + + # Sales Invoice with Payment Schedule + si_with_payment_schedule = create_sales_invoice(do_not_submit=True) + si_with_payment_schedule.extend("payment_schedule", [ + { + "due_date": add_days(today, -5), + "invoice_portion": 50, + "payment_amount": si_with_payment_schedule.grand_total / 2 + }, + { + "due_date": add_days(today, 5), + "invoice_portion": 50, + "payment_amount": si_with_payment_schedule.grand_total / 2 + } + ]) + si_with_payment_schedule.submit() + + + for invoice in (si, si_with_payment_schedule): + invoice.db_set("status", "Unpaid") + update_invoice_status() + invoice.reload() + self.assertEqual(invoice.status, "Overdue") + + invoice.db_set("status", "Unpaid and Discounted") + update_invoice_status() + invoice.reload() + self.assertEqual(invoice.status, "Overdue and Discounted") + + def test_sales_commission(self): si = frappe.copy_doc(test_records[0]) item = copy.deepcopy(si.get('items')[0]) From 84f3f34821a2dcc1fbecc7f3e63c294abf2ca1b9 Mon Sep 17 00:00:00 2001 From: Pruthvi Patel Date: Mon, 20 Dec 2021 13:24:19 +0530 Subject: [PATCH 4/4] Update erpnext/controllers/accounts_controller.py Co-authored-by: Saqib (cherry picked from commit 16a90d3e606cfed1e0ce499ece6b223162451dc6) --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3cc73402a85..6e57a5bfac6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1741,7 +1741,7 @@ def update_invoice_status(): | invoice.status.like("Partly Paid%") ) & ( - (invoice.is_pos & invoice.due_date < today) | is_overdue + ((invoice.is_pos & invoice.due_date < today) | is_overdue) if doctype == "Sales Invoice" else is_overdue )