Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions rmax_custom/api/delivery_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,12 +695,63 @@ def clear_consolidated_si_links(si_name):
)


def mark_consolidated_dns_billed(si_name):
"""Mark every DN consolidated into this SI as fully billed → status Completed.

The net-off consolidated SI omits ``SI Item.delivery_note``, so ERPNext's
``update_billing_status`` sums zero linked rows and would leave each source
DN stuck on "To Bill". Set ``per_billed``/``status`` explicitly instead.
Flat 100% — a clubbed DN is treated as fully invoiced regardless of net-off.
No-op for a normal (non-consolidated) Sales Invoice.
"""
for dn_name in frappe.get_all(
"Delivery Note",
filters={"custom_consolidated_si": si_name},
pluck="name",
):
frappe.db.set_value(
"Delivery Note", dn_name,
{"per_billed": 100, "status": "Completed"},
update_modified=False,
)


def unmark_consolidated_dns_billed(si_name):
"""Revert the billing flag on DNs consolidated into this SI when it is
cancelled or deleted. Re-runs ERPNext's own ``update_billing_status`` so the
DN reflects any OTHER real invoices (a pure net-off DN has none → "To Bill").
Must be called BEFORE clearing ``custom_consolidated_si`` — it needs the link
to find the DNs.
"""
for dn_name in frappe.get_all(
"Delivery Note",
filters={"custom_consolidated_si": si_name},
pluck="name",
):
try:
dn = frappe.get_doc("Delivery Note", dn_name)
dn.update_billing_status(update_modified=False)
except Exception:
frappe.db.set_value(
"Delivery Note", dn_name,
{"per_billed": 0, "status": "To Bill"},
update_modified=False,
)


def sales_invoice_on_submit_mark_consolidated(doc, method=None):
"""Sales Invoice ``on_submit``: flip every clubbed DN (linked via
``custom_consolidated_si``) from "To Bill" to "Completed"."""
mark_consolidated_dns_billed(doc.name)


def sales_invoice_on_trash_clear_consolidated(doc, method=None):
"""Sales Invoice ``on_trash``: free the netted-off DN stamps so a DRAFT
consolidated SI can be deleted. ``on_trash`` runs before
``check_if_doc_is_linked`` in ``frappe.delete_doc`` (verified against v15),
so clearing here lets the link-integrity check pass. The submitted-SI case
is handled separately on ``on_cancel``. No-op for non-consolidated SIs."""
"""Sales Invoice ``on_trash``: revert the clubbed DNs' billing flag, then
free the netted-off DN stamps so a DRAFT consolidated SI can be deleted.
``on_trash`` runs before ``check_if_doc_is_linked`` in ``frappe.delete_doc``
(verified against v15), so clearing here lets the link-integrity check pass.
No-op for non-consolidated SIs."""
unmark_consolidated_dns_billed(doc.name)
clear_consolidated_si_links(doc.name)


Expand Down
1 change: 1 addition & 0 deletions rmax_custom/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@
"on_submit": [
"rmax_custom.inter_company.sales_invoice_on_submit",
"rmax_custom.inter_company_dn.sales_invoice_on_submit",
"rmax_custom.api.delivery_note.sales_invoice_on_submit_mark_consolidated",
],
"on_cancel": [
"rmax_custom.inter_company_dn.sales_invoice_on_cancel",
Expand Down
13 changes: 9 additions & 4 deletions rmax_custom/inter_company_dn.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,15 @@ def sales_invoice_on_cancel(doc, method=None):
if linked:
frappe.db.commit()

# Clear the net-off consolidation stamp on every DN linked via
# custom_consolidated_si. (Set by api.delivery_note.consolidate_dns_to_si.)
# Shared with the on_trash path. Local import avoids a circular import.
from rmax_custom.api.delivery_note import clear_consolidated_si_links
# Revert the billing flag on the clubbed DNs, then clear the net-off
# consolidation stamp. (Set by api.delivery_note.consolidate_dns_to_si.)
# Order matters — unmark needs the link before it is cleared. Shared with the
# on_trash path. Local import avoids a circular import.
from rmax_custom.api.delivery_note import (
clear_consolidated_si_links,
unmark_consolidated_dns_billed,
)
unmark_consolidated_dns_billed(doc.name)
clear_consolidated_si_links(doc.name)


Expand Down
18 changes: 18 additions & 0 deletions rmax_custom/tests/test_dn_consolidation.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ def test_si_cancel_clears_stamp(self):
frappe.db.get_value("Delivery Note", dn.name, "custom_consolidated_si")
)

def test_clubbed_dn_billing_status_flips_on_submit_and_reverts(self):
# The net-off SI omits SI Item.delivery_note, so ERPNext's billing calc
# can't flip the clubbed DNs. on_submit must mark them Completed; cancel
# must revert to To Bill.
dn = _make_submitted_dn(self.customer, self.item, self.company,
self.warehouse, qty=5, rate=10)
self.assertEqual(
frappe.db.get_value("Delivery Note", dn.name, "status"), "To Bill"
)
si = frappe.get_doc("Sales Invoice", consolidate_dns_to_si([dn.name]))
si.submit()
dn.reload()
self.assertEqual(flt(dn.per_billed), 100)
self.assertEqual(dn.status, "Completed")
si.cancel()
dn.reload()
self.assertEqual(dn.status, "To Bill")

def test_draft_si_delete_clears_stamp_and_succeeds(self):
# Reproduces the bug: a DRAFT consolidated SI stamps the DN via
# custom_consolidated_si; deleting it must clear the stamp (on_trash)
Expand Down
Loading