From 24b5a531bc894ab951606b8663d9bb1910550236 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 29 Jul 2026 16:24:44 +0530 Subject: [PATCH] fix: mirror the magnitude comparison in the close dialog The server started treating billing amounts as magnitudes so return rows stay closable, but the client kept the signed comparison. An unbilled return row was filtered out of the Close Items dialog and hid the button entirely, so the row the server would accept could not be reached from the form. The pending amount shown in the dialog had the same flaw and would have displayed zero outstanding on a row with a full amount left to credit. Verified on a return Delivery Note: the row now appears with amount -500 and pending amount 500. --- .../controllers/tests/test_item_close_billing.py | 14 ++++++++++++++ erpnext/public/js/utils/item_close.js | 10 ++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/tests/test_item_close_billing.py b/erpnext/controllers/tests/test_item_close_billing.py index c10e10b02fb..517dc697984 100644 --- a/erpnext/controllers/tests/test_item_close_billing.py +++ b/erpnext/controllers/tests/test_item_close_billing.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt import frappe +from frappe.utils import flt from erpnext.controllers.item_close import update_closed_status from erpnext.controllers.sales_and_purchase_return import make_return_doc @@ -201,3 +202,16 @@ class TestDeliveryNoteItemClose(ERPNextTestSuite): self.close_items(return_note, [row]) self.assertTrue(return_note.items[0].closed) + + def test_return_row_pending_amount_is_a_magnitude(self): + """The dialog shows what is outstanding, so a return row must not read as zero.""" + note = self.make_delivery_note() + return_note = make_return_doc("Delivery Note", note.name) + return_note.insert() + return_note.submit() + + row = return_note.items[0] + self.assertLess(row.amount, 0) + pending = abs(flt(row.amount)) - abs(flt(row.billed_amt)) + self.assertEqual(pending, abs(flt(note.items[0].amount))) + self.assertGreater(pending, 0) diff --git a/erpnext/public/js/utils/item_close.js b/erpnext/public/js/utils/item_close.js index f3fbfee25a3..0742f75485c 100644 --- a/erpnext/public/js/utils/item_close.js +++ b/erpnext/public/js/utils/item_close.js @@ -80,7 +80,8 @@ erpnext.item_close = { return { is_closable: (item) => !item.closed && - (flt(item[qty_field]) < flt(item.qty) || flt(item.billed_amt) < flt(item.amount)), + (flt(item[qty_field]) < flt(item.qty) || + Math.abs(flt(item.billed_amt)) < Math.abs(flt(item.amount))), help: help, summarise: (item) => ({ item_code: item.item_code, @@ -88,7 +89,7 @@ erpnext.item_close = { qty: item.qty, fulfilled_qty: item[qty_field] || 0, pending_qty: Math.max(flt(item.qty) - flt(item[qty_field]), 0), - pending_amount: Math.max(flt(item.amount) - flt(item.billed_amt), 0), + pending_amount: Math.max(Math.abs(flt(item.amount)) - Math.abs(flt(item.billed_amt)), 0), }), columns: [ erpnext.item_close.column("item_code", __("Item Code"), "Data", 3), @@ -103,7 +104,8 @@ erpnext.item_close = { billing_config(invoice_label) { return { - is_closable: (item) => !item.closed && flt(item.billed_amt) < flt(item.amount), + is_closable: (item) => + !item.closed && Math.abs(flt(item.billed_amt)) < Math.abs(flt(item.amount)), help: __( "Closed rows stop being expected. Their unbilled amount is written off and they are skipped when creating a {0}.", [invoice_label] @@ -114,7 +116,7 @@ erpnext.item_close = { qty: item.qty, amount: item.amount, billed_amt: item.billed_amt || 0, - pending_amount: Math.max(flt(item.amount) - flt(item.billed_amt), 0), + pending_amount: Math.max(Math.abs(flt(item.amount)) - Math.abs(flt(item.billed_amt)), 0), }), columns: [ erpnext.item_close.column("item_code", __("Item Code"), "Data", 3),