fix(stock): correct delivery note credit exposure

This commit is contained in:
pandiyan
2026-10-22 12:19:53 +05:30
parent 55a5b9d2af
commit 9597a16156
2 changed files with 58 additions and 39 deletions
+52 -37
View File
@@ -14,6 +14,8 @@ from frappe.contacts.address_and_contact import (
from frappe.model.mapper import get_mapped_doc
from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
from frappe.model.utils.rename_doc import update_linked_doctypes
from frappe.query_builder import Criterion
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import cint, cstr, flt, get_formatted_email, get_link_to_form, today
from frappe.utils.deprecations import deprecated
from frappe.utils.user import get_users_with_role
@@ -696,49 +698,62 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F
outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0
# Outstanding based on Delivery Note, which are not created against Sales Order
outstanding_based_on_dn = 0
DeliveryNote = frappe.qb.DocType("Delivery Note")
DeliveryNoteItem = frappe.qb.DocType("Delivery Note Item")
SalesInvoice = frappe.qb.DocType("Sales Invoice")
SalesInvoiceItem = frappe.qb.DocType("Sales Invoice Item")
unmarked_delivery_note_items = frappe.db.sql(
"""select
dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
where
dn.name = dn_item.parent
and dn.customer=%s and dn.company=%s
and dn.docstatus = 1 and dn.status not in ('Closed', 'Stopped')
and ifnull(dn_item.against_sales_order, '') = ''
and ifnull(dn_item.against_sales_invoice, '') = ''
""",
(customer, company),
as_dict=True,
si_subquery = (
frappe.qb.from_(SalesInvoiceItem)
.join(SalesInvoice)
.on(SalesInvoice.name == SalesInvoiceItem.parent)
.select(SalesInvoiceItem.dn_detail, Sum(SalesInvoiceItem.stock_qty).as_("billed_qty"))
.where(SalesInvoiceItem.docstatus == 1)
.where((SalesInvoice.is_return == 0) | (SalesInvoice.update_billed_amount_in_delivery_note == 1))
.groupby(SalesInvoiceItem.dn_detail)
)
if not unmarked_delivery_note_items:
return outstanding_based_on_gle + outstanding_based_on_so
si_amounts = frappe.db.sql(
"""
SELECT
dn_detail, sum(amount) from `tabSales Invoice Item`
WHERE
docstatus = 1
and dn_detail in ({})
GROUP BY dn_detail""".format(
", ".join(frappe.db.escape(dn_item.name) for dn_item in unmarked_delivery_note_items)
dn_query = (
frappe.qb.from_(DeliveryNote)
.join(DeliveryNoteItem)
.on(DeliveryNote.name == DeliveryNoteItem.parent)
.left_join(si_subquery)
.on(DeliveryNoteItem.name == si_subquery.dn_detail)
.select(
Sum(
(
DeliveryNoteItem.base_net_amount
* (DeliveryNoteItem.stock_qty - IfNull(si_subquery.billed_qty, 0.0))
/ DeliveryNoteItem.stock_qty
/ DeliveryNote.base_net_total
)
* DeliveryNote.base_grand_total
)
)
.where(DeliveryNote.customer == customer)
.where(DeliveryNote.company == company)
.where(DeliveryNote.docstatus == 1)
.where(DeliveryNote.base_net_total > 0)
.where(DeliveryNote.status.notin(["Closed", "Stopped"]))
.where(DeliveryNoteItem.stock_qty > 0)
.where(DeliveryNoteItem.stock_qty > IfNull(si_subquery.billed_qty, 0.0))
.where(
Criterion.any(
[DeliveryNoteItem.against_sales_order.isnull(), DeliveryNoteItem.against_sales_order == ""]
)
)
.where(
Criterion.any(
[
DeliveryNoteItem.against_sales_invoice.isnull(),
DeliveryNoteItem.against_sales_invoice == "",
]
)
)
)
si_amounts = {si_item[0]: si_item[1] for si_item in si_amounts}
for dn_item in unmarked_delivery_note_items:
dn_amount = flt(dn_item.amount)
si_amount = flt(si_amounts.get(dn_item.name))
if dn_amount > si_amount and dn_item.base_net_total:
outstanding_based_on_dn += (
(dn_amount - si_amount) / dn_item.base_net_total
) * dn_item.base_grand_total
dn_res = dn_query.run()
outstanding_based_on_dn = flt(dn_res[0][0]) if dn_res and dn_res[0][0] is not None else 0.0
return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn
@@ -669,8 +669,12 @@ class DeliveryNote(SellingController):
for d in self.get("items"):
if not d.against_sales_invoice:
validate_against_credit_limit = True
extra_amount = self.base_grand_total
break
# Only submitted standalone rows are already included in customer outstanding.
if (d.against_sales_order or self.docstatus == 0) and self.base_net_total:
extra_amount += d.base_net_amount / self.base_net_total * self.base_grand_total
if validate_against_credit_limit and not self.base_net_total:
# Customer outstanding excludes zero-net deliveries, including their freight charges.
extra_amount = self.base_grand_total
else:
for d in self.get("items"):
if not (d.against_sales_order or d.against_sales_invoice):