Skip to content

Commit

Permalink
fix: incorrect status being set in Invoices
Browse files Browse the repository at this point in the history
  • Loading branch information
pruthvi145 committed Oct 20, 2021
1 parent 2ef4844 commit 785244a
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 27 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 @@ -1183,16 +1184,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
44 changes: 27 additions & 17 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1427,16 +1427,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 @@ -1463,27 +1464,36 @@ 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.company_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
payable_amount = sum(
payment.base_payment_amount
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
51 changes: 43 additions & 8 deletions erpnext/controllers/accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1686,17 +1686,52 @@ 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(ps.base_payment_amount)
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
1 change: 1 addition & 0 deletions erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
erpnext.patches.v13_0.validate_options_for_data_field
erpnext.patches.v13_0.create_gst_payment_entry_fields
erpnext.patches.v14_0.delete_shopify_doctypes
erpnext.patches.v13_0.fix_invoice_statuses
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
erpnext.patches.v13_0.update_dates_in_tax_withholding_category
erpnext.patches.v14_0.update_opportunity_currency_fields
Expand Down
106 changes: 106 additions & 0 deletions erpnext/patches/v13_0/fix_invoice_statuses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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",
"base_grand_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", "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 785244a

Please sign in to comment.