fix: incorrect status being set in Invoices (#28019)
Co-authored-by: Pruthvi Patel <pruthvipatel145@gmail.com>
This commit is contained in:
parent
5ada11b1af
commit
8d9d0987fe
@ -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"
|
||||
|
@ -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
|
||||
|
||||
if (grand_total - outstanding_amount) < payable_amount:
|
||||
return True
|
||||
# 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
|
||||
)
|
||||
|
||||
return (total - outstanding_amount) < payable_amount
|
||||
|
||||
elif getdate(doc.due_date) < nowdate:
|
||||
return True
|
||||
|
||||
def get_discounting_status(sales_invoice):
|
||||
status = None
|
||||
|
@ -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):
|
||||
|
@ -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
Normal file
113
erpnext/patches/v13_0/fix_invoice_statuses.py
Normal file
@ -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…
x
Reference in New Issue
Block a user