Skip to content

Commit

Permalink
fix: incorrect status being set in Invoices (backport frappe#28019) (f…
Browse files Browse the repository at this point in the history
…rappe#28031)

* fix: incorrect status being set in Invoices (frappe#28019)

Co-authored-by: Pruthvi Patel <pruthvipatel145@gmail.com>
(cherry picked from commit 8d9d098)

# Conflicts:
#	erpnext/patches.txt

* fix: merge conflict

Co-authored-by: Sagar Vora <sagar@resilient.tech>
  • Loading branch information
mergify[bot] and sagarvora authored Oct 20, 2021
1 parent 2c50373 commit 6c0e1e9
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 28 deletions.
6 changes: 4 additions & 2 deletions erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
check_if_return_invoice_linked_with_payment_entry,
get_total_in_party_account_currency,
is_overdue,
unlink_inter_company_doc,
update_linked_doc,
Expand Down Expand Up @@ -1147,16 +1148,17 @@ def set_status(self, update=False, status=None, update_modified=True):
return

outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
total = get_total_in_party_account_currency(self)

if not status:
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif is_overdue(self):
elif is_overdue(self, total):
self.status = "Overdue"
elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")):
elif 0 < outstanding_amount < total:
self.status = "Partly Paid"
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
self.status = "Unpaid"
Expand Down
50 changes: 33 additions & 17 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1478,16 +1478,17 @@ def set_status(self, update=False, status=None, update_modified=True):
return

outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
total = get_total_in_party_account_currency(self)

if not status:
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif is_overdue(self):
elif is_overdue(self, total):
self.status = "Overdue"
elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")):
elif 0 < outstanding_amount < total:
self.status = "Partly Paid"
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
self.status = "Unpaid"
Expand All @@ -1514,27 +1515,42 @@ def set_status(self, update=False, status=None, update_modified=True):
if update:
self.db_set('status', self.status, update_modified = update_modified)

def is_overdue(doc):
outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))

def get_total_in_party_account_currency(doc):
total_fieldname = (
"grand_total"
if doc.disable_rounded_total
else "rounded_total"
)
if doc.party_account_currency != doc.currency:
total_fieldname = "base_" + total_fieldname

return flt(doc.get(total_fieldname), doc.precision(total_fieldname))

def is_overdue(doc, total):
outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))
if outstanding_amount <= 0:
return

grand_total = flt(doc.grand_total, doc.precision("grand_total"))
nowdate = getdate()
if doc.payment_schedule:
# calculate payable amount till date
payable_amount = sum(
payment.payment_amount
for payment in doc.payment_schedule
if getdate(payment.due_date) < nowdate
)
today = getdate()
if doc.get('is_pos') or not doc.get('payment_schedule'):
return getdate(doc.due_date) < today

# calculate payable amount till date
payment_amount_field = (
"base_payment_amount"
if doc.party_account_currency != doc.currency
else "payment_amount"
)

payable_amount = sum(
payment.get(payment_amount_field)
for payment in doc.payment_schedule
if getdate(payment.due_date) < today
)

if (grand_total - outstanding_amount) < payable_amount:
return True
return (total - outstanding_amount) < payable_amount

elif getdate(doc.due_date) < nowdate:
return True

def get_discounting_status(sales_invoice):
status = None
Expand Down
57 changes: 49 additions & 8 deletions erpnext/controllers/accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1691,17 +1691,58 @@ 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()

for doctype in ("Sales Invoice", "Purchase Invoice"):
frappe.db.sql("""
update `tab{}` as dt set dt.status = 'Overdue'
where dt.docstatus = 1
and dt.status != 'Overdue'
and dt.outstanding_amount > 0
and (dt.grand_total - dt.outstanding_amount) <
(select sum(payment_amount) from `tabPayment Schedule` as ps
where ps.parent = dt.name and ps.due_date < %s)
""".format(doctype), getdate())
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}
)

@frappe.whitelist()
def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
Expand Down
3 changes: 2 additions & 1 deletion erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ erpnext.patches.v13_0.create_website_items #30-09-2021
erpnext.patches.v13_0.populate_e_commerce_settings
erpnext.patches.v13_0.make_homepage_products_website_items
erpnext.patches.v13_0.update_dates_in_tax_withholding_category
erpnext.patches.v13_0.fix_invoice_statuses
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
erpnext.patches.v13_0.gst_fields_for_pos_invoice
erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes
Expand All @@ -327,4 +328,4 @@ erpnext.patches.v13_0.add_default_interview_notification_templates
erpnext.patches.v13_0.trim_sales_invoice_custom_field_length
erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting
erpnext.patches.v13_0.requeue_failed_reposts
erpnext.patches.v13_0.fetch_thumbnail_in_website_items
erpnext.patches.v13_0.fetch_thumbnail_in_website_items
113 changes: 113 additions & 0 deletions erpnext/patches/v13_0/fix_invoice_statuses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import frappe
from frappe.utils import flt, getdate

from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
get_total_in_party_account_currency,
is_overdue,
)

TODAY = getdate()

def execute():
# This fix is not related to Party Specific Item,
# but it is needed for code introduced after Party Specific Item was
# If your DB doesn't have this doctype yet, you should be fine
if not frappe.db.exists("DocType", "Party Specific Item"):
return

for doctype in ("Purchase Invoice", "Sales Invoice"):
fields = [
"name",
"status",
"due_date",
"outstanding_amount",
"grand_total",
"base_grand_total",
"rounded_total",
"base_rounded_total",
"disable_rounded_total",
]
if doctype == "Sales Invoice":
fields.append("is_pos")

invoices_to_update = frappe.get_all(
doctype,
fields=fields,
filters={
"docstatus": 1,
"status": ("in", (
"Overdue",
"Overdue and Discounted",
"Partly Paid",
"Partly Paid and Discounted"
)),
"outstanding_amount": (">", 0),
"modified": (">", "2021-01-01")
# an assumption is being made that only invoices modified
# after 2021 got affected as incorrectly overdue.
# required for performance reasons.
}
)

invoices_to_update = {
invoice.name: invoice for invoice in invoices_to_update
}

payment_schedule_items = frappe.get_all(
"Payment Schedule",
fields=(
"due_date",
"payment_amount",
"base_payment_amount",
"parent"
),
filters={"parent": ("in", invoices_to_update)}
)

for item in payment_schedule_items:
invoices_to_update[item.parent].setdefault(
"payment_schedule", []
).append(item)

status_map = {}

for invoice in invoices_to_update.values():
invoice.doctype = doctype
doc = frappe.get_doc(invoice)
correct_status = get_correct_status(doc)
if not correct_status or doc.status == correct_status:
continue

status_map.setdefault(correct_status, []).append(doc.name)

for status, docs in status_map.items():
frappe.db.set_value(
doctype, {"name": ("in", docs)},
"status",
status,
update_modified=False
)



def get_correct_status(doc):
outstanding_amount = flt(
doc.outstanding_amount, doc.precision("outstanding_amount")
)
total = get_total_in_party_account_currency(doc)

status = ""
if is_overdue(doc, total):
status = "Overdue"
elif 0 < outstanding_amount < total:
status = "Partly Paid"
elif outstanding_amount > 0 and getdate(doc.due_date) >= TODAY:
status = "Unpaid"

if not status:
return

if doc.status.endswith(" and Discounted"):
status += " and Discounted"

return status

0 comments on commit 6c0e1e9

Please sign in to comment.