fix: handling negative grand total

This commit is contained in:
nishkagosalia
2026-07-28 17:38:32 +05:30
parent c6a16495c0
commit 136f92db04
5 changed files with 56 additions and 3 deletions

View File

@@ -210,6 +210,23 @@ class AccountsController(TransactionBase):
)
frappe.msgprint(msg)
def is_negative_grand_total_allowed(self) -> bool:
"""Return True if this document may save with a negative grand total.
Sales Order and Purchase Order never post to the GL, so a negative
total is safe there whenever the user has explicitly opted into
negative rates via Selling/Buying Settings. Every other
AccountsController doctype (invoices, delivery notes, receipts,
quotations, ...) keeps relying on the `is_return` escape hatch only.
"""
if self.doctype == "Sales Order":
return bool(frappe.get_single_value("Selling Settings", "allow_negative_rates_for_items"))
if self.doctype == "Purchase Order":
return bool(frappe.get_single_value("Buying Settings", "allow_negative_rates_for_items"))
return False
def validate(self):
if not self.get("is_return") and not self.get("is_debit_note"):
self.validate_qty_is_not_zero()
@@ -260,7 +277,8 @@ class AccountsController(TransactionBase):
self.calculate_taxes_and_totals()
if not self.meta.get_field("is_return") or not self.is_return:
self.validate_value("base_grand_total", ">=", 0)
if not self.is_negative_grand_total_allowed():
self.validate_value("base_grand_total", ">=", 0)
validate_return(self)

View File

@@ -293,10 +293,11 @@ class StatusUpdater(Document):
frappe.throw(_("For an item {0}, quantity must be a negative number").format(d.item_code))
if (
not selling_negative_rate_allowed and self.doctype in ["Sales Invoice", "Delivery Note"]
not selling_negative_rate_allowed
and self.doctype in ["Sales Order", "Sales Invoice", "Delivery Note"]
) or (
not buying_negative_rate_allowed
and self.doctype in ["Purchase Invoice", "Purchase Receipt"]
and self.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
):
if hasattr(d, "item_code") and hasattr(d, "rate") and flt(d.rate) < 0:
frappe.throw(