Browse Source

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

Co-authored-by: Pruthvi Patel <pruthvipatel145@gmail.com>
develop
Sagar Vora 3 years ago
committed by GitHub
parent
commit
8d9d0987fe
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
  2. 50
      erpnext/accounts/doctype/sales_invoice/sales_invoice.py
  3. 57
      erpnext/controllers/accounts_controller.py
  4. 1
      erpnext/patches.txt
  5. 113
      erpnext/patches/v13_0/fix_invoice_statuses.py

6
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py

@ -15,6 +15,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
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,
@ -1183,6 +1184,7 @@ class PurchaseInvoice(BuyingController):
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:
@ -1190,9 +1192,9 @@ class PurchaseInvoice(BuyingController):
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"

50
erpnext/accounts/doctype/sales_invoice/sales_invoice.py

@ -1427,6 +1427,7 @@ class SalesInvoice(SellingController):
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:
@ -1434,9 +1435,9 @@ class SalesInvoice(SellingController):
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"
@ -1463,27 +1464,42 @@ class SalesInvoice(SellingController):
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

57
erpnext/controllers/accounts_controller.py

@ -1686,17 +1686,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):

1
erpnext/patches.txt

@ -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

113
erpnext/patches/v13_0/fix_invoice_statuses.py

@ -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
Loading…
Cancel
Save