From e3224df8c047e17e90ea6c09bfdd07936439885d Mon Sep 17 00:00:00 2001 From: patsh-odoo Date: Tue, 2 Jun 2026 10:20:49 +0530 Subject: [PATCH 1/2] [ADD] accounting_update: allow zero quantity on invoice lines for tax reporting Added a checkbox called 'Is Zero Quantity' on the invoice line items. When a user checks this box, the official Indian E-invoicing (l10n_in_edi) system will force the item's quantity to be sent as zero to the government portal, while keeping the monetary value intact. This is needed for commercial edge cases like customer rejections. For example, if a customer rejects a delivery of low-quality coal, the company issues a Credit Note. Because the rejected scrap coal is left with the customer and not returned to inventory, the net physical quantity transferred is zero, but the financial adjustment must still be reported to the government. project - [PSIM] INTERNSHIP ONBOARDING task - [accounting/l10n_in] Add zero quantity on move line --- accounting_update/__init__.py | 1 + accounting_update/__manifest__.py | 9 +++++++++ accounting_update/models/__init__.py | 2 ++ accounting_update/models/account_move.py | 13 +++++++++++++ accounting_update/models/account_move_line.py | 7 +++++++ accounting_update/views/accounting_move_views.xml | 15 +++++++++++++++ 6 files changed, 47 insertions(+) create mode 100644 accounting_update/__init__.py create mode 100644 accounting_update/__manifest__.py create mode 100644 accounting_update/models/__init__.py create mode 100644 accounting_update/models/account_move.py create mode 100644 accounting_update/models/account_move_line.py create mode 100644 accounting_update/views/accounting_move_views.xml diff --git a/accounting_update/__init__.py b/accounting_update/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/accounting_update/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/accounting_update/__manifest__.py b/accounting_update/__manifest__.py new file mode 100644 index 00000000000..7263f18288c --- /dev/null +++ b/accounting_update/__manifest__.py @@ -0,0 +1,9 @@ +{ + "name": "Account Update", + "author": "Shrey Patel", + "depends": ["l10n_in_edi"], + "license": "LGPL-3", + "data": [ + "views/accounting_move_views.xml", + ], +} diff --git a/accounting_update/models/__init__.py b/accounting_update/models/__init__.py new file mode 100644 index 00000000000..0d5ab6a2fc6 --- /dev/null +++ b/accounting_update/models/__init__.py @@ -0,0 +1,2 @@ +from . import account_move +from . import account_move_line diff --git a/accounting_update/models/account_move.py b/accounting_update/models/account_move.py new file mode 100644 index 00000000000..1d2a5b7c240 --- /dev/null +++ b/accounting_update/models/account_move.py @@ -0,0 +1,13 @@ +from odoo import models + + +class AccountMove(models.Model): + _inherit = "account.move" + + def _get_l10n_in_edi_line_details(self, index, line, line_tax_details): + line_details = super()._get_l10n_in_edi_line_details( + index, line, line_tax_details + ) + if line.is_zero_qty: + line_details["Qty"] = 0.0 + return line_details diff --git a/accounting_update/models/account_move_line.py b/accounting_update/models/account_move_line.py new file mode 100644 index 00000000000..391b265311b --- /dev/null +++ b/accounting_update/models/account_move_line.py @@ -0,0 +1,7 @@ +from odoo import models, fields + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + is_zero_qty = fields.Boolean() diff --git a/accounting_update/views/accounting_move_views.xml b/accounting_update/views/accounting_move_views.xml new file mode 100644 index 00000000000..5c23ad22551 --- /dev/null +++ b/accounting_update/views/accounting_move_views.xml @@ -0,0 +1,15 @@ + + + account.move.form.inherit.accounting_update + account.move + + + + + + + + + + + From 7203592b9ece806ef233daa3d47a5b1f4420563a Mon Sep 17 00:00:00 2001 From: patsh-odoo Date: Thu, 4 Jun 2026 00:53:03 +0530 Subject: [PATCH 2/2] [IMP] accounting_update: added test cases for zero quantity payloads Added test cases to test the new zero quantity checkbox feature on invoice lines. It tests four distinct scenarios covering regular customer invoices and credit notes both with and without the zero quantity setting turned on. It ensures added feature works correctly. project - [PSIM] INTERNSHIP ONBOARDING task - [accounting/l10n_in] Add zero quantity on move line --- accounting_update/__init__.py | 1 + accounting_update/tests/__init__.py | 1 + .../tests/test_edi_json_zero_qty.py | 195 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 accounting_update/tests/__init__.py create mode 100644 accounting_update/tests/test_edi_json_zero_qty.py diff --git a/accounting_update/__init__.py b/accounting_update/__init__.py index 0650744f6bc..0ee8b5073e2 100644 --- a/accounting_update/__init__.py +++ b/accounting_update/__init__.py @@ -1 +1,2 @@ from . import models +from . import tests diff --git a/accounting_update/tests/__init__.py b/accounting_update/tests/__init__.py new file mode 100644 index 00000000000..2e1abf98b0d --- /dev/null +++ b/accounting_update/tests/__init__.py @@ -0,0 +1 @@ +from . import test_edi_json_zero_qty diff --git a/accounting_update/tests/test_edi_json_zero_qty.py b/accounting_update/tests/test_edi_json_zero_qty.py new file mode 100644 index 00000000000..bf953e933b0 --- /dev/null +++ b/accounting_update/tests/test_edi_json_zero_qty.py @@ -0,0 +1,195 @@ +from freezegun import freeze_time + +from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon +from odoo.tests import tagged +from odoo import fields + + +@tagged("post_install", "-at_install") +class TestEdiJsonZeroQty(L10nInTestInvoicingCommon): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner_a.l10n_in_gst_treatment = "regular" + cls.invoice_with_zero_qty = cls._create_invoice_one_line( + product_id=cls.product_a, invoice_date=fields.Date.from_string("2019-01-01") + ) + cls.invoice_with_zero_qty.write( + { + "invoice_line_ids": [ + (1, l_id, {"is_zero_qty": True}) + for l_id in cls.invoice_with_zero_qty.invoice_line_ids.ids + ] + } + ) + cls.invoice_with_zero_qty.action_post() + with freeze_time("2023-12-25"): + cls.credit_note_with_zero_qty = cls.invoice_with_zero_qty._reverse_moves() + cls.credit_note_with_zero_qty.action_post() + + cls.invoice_without_zero_qty = cls._create_invoice_one_line( + product_id=cls.product_a, invoice_date=fields.Date.from_string("2019-01-01") + ) + cls.invoice_without_zero_qty.action_post() + with freeze_time("2023-12-25"): + cls.credit_note_without_zero_qty = ( + cls.invoice_without_zero_qty._reverse_moves() + ) + cls.credit_note_without_zero_qty.action_post() + + def test_edi_json(self): + # line1: 1000 and a tax of 5% + # 1000 * 1.05 = 1050 + # total tax: 50 + expected = { + "Version": "1.1", + "TranDtls": { + "TaxSch": "GST", + "SupTyp": "B2B", + "RegRev": "N", + "IgstOnIntra": "N", + }, + "DocDtls": {"Typ": "INV", "No": "INV/18-19/0001", "Dt": "01/01/2019"}, + "SellerDtls": { + "Addr1": "Khodiyar Chowk", + "Loc": "Amreli", + "Pin": 365220, + "Stcd": "24", + "Addr2": "Sala Number 3", + "LglNm": "Default Company", + "GSTIN": "24AAGCC7144L6ZE", + }, + "BuyerDtls": { + "Addr1": "Karansinhji Rd", + "Loc": "Rajkot", + "Pin": 360001, + "Stcd": "24", + "Addr2": "Karanpara", + "POS": "24", + "LglNm": "Partner Intra State", + "GSTIN": "24ABCPM8965E1ZE", + }, + "ItemList": [ + { + "SlNo": "1", + "PrdDesc": "product_a", + "IsServc": "N", + "HsnCd": "111111", + "Qty": 0.0, + "Unit": "UNT", + "UnitPrice": 1000.0, + "TotAmt": 1000.0, + "Discount": 0.0, + "AssAmt": 1000.0, + "GstRt": 5.0, + "IgstAmt": 0.0, + "CgstAmt": 25.0, + "SgstAmt": 25.0, + "CesRt": 0.0, + "CesAmt": 0.0, + "CesNonAdvlAmt": 0.0, + "StateCesRt": 0.0, + "StateCesAmt": 0.0, + "StateCesNonAdvlAmt": 0.0, + "OthChrg": 0.0, + "TotItemVal": 1050.0, + }, + ], + "ValDtls": { + "AssVal": 1000.0, + "CgstVal": 25.0, + "SgstVal": 25.0, + "IgstVal": 0.0, + "CesVal": 0.0, + "StCesVal": 0.0, + "Discount": 0.0, + "RndOffAmt": 0.0, + "TotInvVal": 1050.0, + }, + } + + with self.subTest(scenario="Invoice with zero qty"): + json_value = self.invoice_with_zero_qty._l10n_in_edi_generate_invoice_json() + self.assertDictEqual( + json_value, + expected, + "Indian EDI with zero qty json value is not matched", + ) + + with self.subTest(scenario="Credit Note with zero qty"): + expected.update( + { + "DocDtls": { + "Typ": "CRN", + "No": "RINV/23-24/0001", + "Dt": "25/12/2023", + } + } + ) + self.assertDictEqual( + self.credit_note_with_zero_qty._l10n_in_edi_generate_invoice_json(), + expected, + "Indian E-invoice Credit note with zero qty json value is not matched", + ) + + with self.subTest(scenario="Invoice without zero qty"): + expected.update( + { + "DocDtls": { + "Typ": "INV", + "No": "INV/18-19/0002", + "Dt": "01/01/2019", + }, + "ItemList": [ + { + "SlNo": "1", + "PrdDesc": "product_a", + "IsServc": "N", + "HsnCd": "111111", + "Qty": 1.0, + "Unit": "UNT", + "UnitPrice": 1000.0, + "TotAmt": 1000.0, + "Discount": 0.0, + "AssAmt": 1000.0, + "GstRt": 5.0, + "IgstAmt": 0.0, + "CgstAmt": 25.0, + "SgstAmt": 25.0, + "CesRt": 0.0, + "CesAmt": 0.0, + "CesNonAdvlAmt": 0.0, + "StateCesRt": 0.0, + "StateCesAmt": 0.0, + "StateCesNonAdvlAmt": 0.0, + "OthChrg": 0.0, + "TotItemVal": 1050.0, + }, + ], + } + ) + json_value = ( + self.invoice_without_zero_qty._l10n_in_edi_generate_invoice_json() + ) + self.assertDictEqual( + json_value, + expected, + "Indian E-invoice without zero qty json value is not matched", + ) + + with self.subTest(scenario="Credit Note without zero qty"): + expected.update( + { + "DocDtls": { + "Typ": "CRN", + "No": "RINV/23-24/0002", + "Dt": "25/12/2023", + } + } + ) + self.assertDictEqual( + self.credit_note_without_zero_qty._l10n_in_edi_generate_invoice_json(), + expected, + "Indian E-invoice Credit note without zero qty json value is not matched", + )