diff --git a/addons/l10n_in_edi/models/account_edi_format.py b/addons/l10n_in_edi/models/account_edi_format.py index fe963a33c0a..12768ae2ca3 100644 --- a/addons/l10n_in_edi/models/account_edi_format.py +++ b/addons/l10n_in_edi/models/account_edi_format.py @@ -6,9 +6,11 @@ import json import pytz import markupsafe +from collections import defaultdict + from odoo import models, fields, api, _ -from odoo.tools import html_escape, float_is_zero -from odoo.exceptions import AccessError +from odoo.tools import html_escape, float_is_zero, float_compare +from odoo.exceptions import AccessError, ValidationError from odoo.addons.iap import jsonrpc import logging @@ -69,6 +71,11 @@ class AccountEdiFormat(models.Model): if not re.match("^.{1,16}$", move.name): error_message.append(_("Invoice number should not be more than 16 characters")) for line in move.invoice_line_ids.filtered(lambda line: line.display_type not in ('line_note', 'line_section', 'rounding')): + if line.price_subtotal < 0: + # Line having a negative amount is not allowed. + if not move._l10n_in_edi_is_managing_invoice_negative_lines_allowed(): + raise ValidationError(_("Invoice lines having a negative amount are not allowed to generate the IRN. " + "Please create a credit note instead.")) if line.product_id: hsn_code = self._l10n_in_edi_extract_digits(line.product_id.l10n_in_hsn_code) if not hsn_code: @@ -299,7 +306,8 @@ class AccountEdiFormat(models.Model): """ sign = line.move_id.is_inbound() and -1 or 1 tax_details_by_code = self._get_l10n_in_tax_details_by_line_code(line_tax_details.get("tax_details", {})) - full_discount_or_zero_quantity = line.discount == 100.00 or float_is_zero(line.quantity, 3) + quantity = line.quantity + full_discount_or_zero_quantity = line.discount == 100.00 or float_is_zero(quantity, 3) if full_discount_or_zero_quantity: unit_price_in_inr = line.currency_id._convert( line.price_unit, @@ -308,19 +316,26 @@ class AccountEdiFormat(models.Model): line.date or fields.Date.context_today(self) ) else: - unit_price_in_inr = ((sign * line.balance) / (1 - (line.discount / 100))) / line.quantity + unit_price_in_inr = ((sign * line.balance) / (1 - (line.discount / 100))) / quantity + + if unit_price_in_inr < 0 and quantity < 0: + # If unit price and quantity both is negative then + # We set unit price and quantity as positive because + # government does not accept negative in qty or unit price + unit_price_in_inr = unit_price_in_inr * -1 + quantity = quantity * -1 return { "SlNo": str(index), "PrdDesc": line.name.replace("\n", ""), "IsServc": line.product_id.type == "service" and "Y" or "N", "HsnCd": self._l10n_in_edi_extract_digits(line.product_id.l10n_in_hsn_code), - "Qty": self._l10n_in_round_value(line.quantity or 0.0, 3), + "Qty": self._l10n_in_round_value(quantity or 0.0, 3), "Unit": line.product_uom_id.l10n_in_code and line.product_uom_id.l10n_in_code.split("-")[0] or "OTH", # Unit price in company currency and tax excluded so its different then price_unit "UnitPrice": self._l10n_in_round_value(unit_price_in_inr, 3), # total amount is before discount - "TotAmt": self._l10n_in_round_value(unit_price_in_inr * line.quantity), - "Discount": self._l10n_in_round_value((unit_price_in_inr * line.quantity) * (line.discount / 100)), + "TotAmt": self._l10n_in_round_value(unit_price_in_inr * quantity), + "Discount": self._l10n_in_round_value((unit_price_in_inr * quantity) * (line.discount / 100)), "AssAmt": self._l10n_in_round_value((sign * line.balance)), "GstRt": self._l10n_in_round_value(tax_details_by_code.get("igst_rate", 0.00) or ( tax_details_by_code.get("cgst_rate", 0.00) + tax_details_by_code.get("sgst_rate", 0.00)), 3), @@ -339,6 +354,94 @@ class AccountEdiFormat(models.Model): "TotItemVal": self._l10n_in_round_value(((sign * line.balance) + line_tax_details.get("tax_amount", 0.00))), } + def _l10n_in_edi_generate_invoice_json_managing_negative_lines(self, invoice, json_payload): + """Set negative lines against positive lines as discount with same HSN code and tax rate + + With negative lines + + product name | hsn code | unit price | qty | discount | total + ============================================================= + product A | 123456 | 1000 | 1 | 100 | 900 + product B | 123456 | 1500 | 2 | 0 | 3000 + Discount | 123456 | -300 | 1 | 0 | -300 + + Converted to without negative lines + + product name | hsn code | unit price | qty | discount | total + ============================================================= + product A | 123456 | 1000 | 1 | 100 | 900 + product B | 123456 | 1500 | 2 | 300 | 2700 + + totally discounted lines are kept as 0, though + """ + def discount_group_key(line_vals): + return "%s-%s"%(line_vals['HsnCd'], line_vals['GstRt']) + + def put_discount_on(discount_line_vals, other_line_vals): + discount = discount_line_vals['AssAmt'] * -1 + discount_to_allow = other_line_vals['AssAmt'] + if float_compare(discount_to_allow, discount, precision_rounding=invoice.currency_id.rounding) < 0: + # Update discount line, needed when discount is more then max line, in short remaining_discount is not zero + discount_line_vals.update({ + 'AssAmt': self._l10n_in_round_value(discount_line_vals['AssAmt'] + other_line_vals['AssAmt']), + 'IgstAmt': self._l10n_in_round_value(discount_line_vals['IgstAmt'] + other_line_vals['IgstAmt']), + 'CgstAmt': self._l10n_in_round_value(discount_line_vals['CgstAmt'] + other_line_vals['CgstAmt']), + 'SgstAmt': self._l10n_in_round_value(discount_line_vals['SgstAmt'] + other_line_vals['SgstAmt']), + 'CesAmt': self._l10n_in_round_value(discount_line_vals['CesAmt'] + other_line_vals['CesAmt']), + 'CesNonAdvlAmt': self._l10n_in_round_value(discount_line_vals['CesNonAdvlAmt'] + other_line_vals['CesNonAdvlAmt']), + 'StateCesAmt': self._l10n_in_round_value(discount_line_vals['StateCesAmt'] + other_line_vals['StateCesAmt']), + 'StateCesNonAdvlAmt': self._l10n_in_round_value(discount_line_vals['StateCesNonAdvlAmt'] + other_line_vals['StateCesNonAdvlAmt']), + 'OthChrg': self._l10n_in_round_value(discount_line_vals['OthChrg'] + other_line_vals['OthChrg']), + 'TotItemVal': self._l10n_in_round_value(discount_line_vals['TotItemVal'] + other_line_vals['TotItemVal']), + }) + other_line_vals.update({ + 'Discount': self._l10n_in_round_value(other_line_vals['Discount'] + discount_to_allow), + 'AssAmt': 0.00, + 'IgstAmt': 0.00, + 'CgstAmt': 0.00, + 'SgstAmt': 0.00, + 'CesAmt': 0.00, + 'CesNonAdvlAmt': 0.00, + 'StateCesAmt': 0.00, + 'StateCesNonAdvlAmt': 0.00, + 'OthChrg': 0.00, + 'TotItemVal': 0.00, + }) + return False + other_line_vals.update({ + 'Discount': self._l10n_in_round_value(other_line_vals['Discount'] + discount), + 'AssAmt': self._l10n_in_round_value(other_line_vals['AssAmt'] + discount_line_vals['AssAmt']), + 'IgstAmt': self._l10n_in_round_value(other_line_vals['IgstAmt'] + discount_line_vals['IgstAmt']), + 'CgstAmt': self._l10n_in_round_value(other_line_vals['CgstAmt'] + discount_line_vals['CgstAmt']), + 'SgstAmt': self._l10n_in_round_value(other_line_vals['SgstAmt'] + discount_line_vals['SgstAmt']), + 'CesAmt': self._l10n_in_round_value(other_line_vals['CesAmt'] + discount_line_vals['CesAmt']), + 'CesNonAdvlAmt': self._l10n_in_round_value(other_line_vals['CesNonAdvlAmt'] + discount_line_vals['CesNonAdvlAmt']), + 'StateCesAmt': self._l10n_in_round_value(other_line_vals['StateCesAmt'] + discount_line_vals['StateCesAmt']), + 'StateCesNonAdvlAmt': self._l10n_in_round_value(other_line_vals['StateCesNonAdvlAmt'] + discount_line_vals['StateCesNonAdvlAmt']), + 'OthChrg': self._l10n_in_round_value(other_line_vals['OthChrg'] + discount_line_vals['OthChrg']), + 'TotItemVal': self._l10n_in_round_value(other_line_vals['TotItemVal'] + discount_line_vals['TotItemVal']), + }) + return True + + discount_lines = [] + for discount_line in json_payload['ItemList'].copy(): #to be sure to not skip in the loop: + if discount_line['AssAmt'] < 0: + discount_lines.append(discount_line) + json_payload['ItemList'].remove(discount_line) + if not discount_lines: + return json_payload + + lines_grouped_and_sorted = defaultdict(list) + for line in sorted(json_payload['ItemList'], key=lambda i: i['AssAmt'], reverse=True): + lines_grouped_and_sorted[discount_group_key(line)].append(line) + + for discount_line in discount_lines: + apply_discount_on_lines = lines_grouped_and_sorted.get(discount_group_key(discount_line), []) + for apply_discount_on in apply_discount_on_lines: + if put_discount_on(discount_line, apply_discount_on): + break + return json_payload + def _l10n_in_edi_generate_invoice_json(self, invoice): tax_details = self._l10n_in_prepare_edi_tax_details(invoice) saler_buyer = self._get_l10n_in_edi_saler_buyer_party(invoice) @@ -418,7 +521,9 @@ class AccountEdiFormat(models.Model): json_payload["ExpDtls"].update({ "Port": invoice.l10n_in_shipping_port_code_id.code }) - return json_payload + if not invoice._l10n_in_edi_is_managing_invoice_negative_lines_allowed(): + return json_payload + return self._l10n_in_edi_generate_invoice_json_managing_negative_lines(invoice, json_payload) @api.model def _l10n_in_prepare_edi_tax_details(self, move, in_foreign=False): diff --git a/addons/l10n_in_edi/models/account_move.py b/addons/l10n_in_edi/models/account_move.py index 65fefc5daa4..4bdc68c20cb 100644 --- a/addons/l10n_in_edi/models/account_move.py +++ b/addons/l10n_in_edi/models/account_move.py @@ -51,3 +51,13 @@ class AccountMove(models.Model): return json.loads(l10n_in_edi.attachment_id.raw.decode("utf-8")) else: return {} + + @api.model + def _l10n_in_edi_is_managing_invoice_negative_lines_allowed(self): + """ Negative lines are not allowed by the Indian government making some features unavailable like sale_coupon + or global discounts. This method allows odoo to distribute the negative discount lines to each others lines + with same HSN code making such features available even for Indian people. + :return: True if odoo needs to distribute the negative discount lines, False otherwise. + """ + param_name = 'l10n_in_edi.manage_invoice_negative_lines' + return bool(self.env['ir.config_parameter'].sudo().get_param(param_name)) diff --git a/addons/l10n_in_edi/tests/test_edi_json.py b/addons/l10n_in_edi/tests/test_edi_json.py index c238a319ad5..c48e73e6ab7 100644 --- a/addons/l10n_in_edi/tests/test_edi_json.py +++ b/addons/l10n_in_edi/tests/test_edi_json.py @@ -10,6 +10,7 @@ class TestEdiJson(AccountTestInvoicingCommon): @classmethod def setUpClass(cls, chart_template_ref="l10n_in.indian_chart_template_standard"): super().setUpClass(chart_template_ref=chart_template_ref) + cls.env['ir.config_parameter'].set_param('l10n_in_edi.manage_invoice_negative_lines', True) cls.maxDiff = None cls.company_data["company"].write({ "street": "Block no. 401", @@ -31,6 +32,28 @@ class TestEdiJson(AccountTestInvoicingCommon): "l10n_in_gst_treatment": "regular", }) cls.product_a.write({"l10n_in_hsn_code": "01111"}) + cls.product_a2 = cls.env['product.product'].create({ + 'name': 'product_a2', + 'uom_id': cls.env.ref('uom.product_uom_unit').id, + 'lst_price': 1000.0, + 'standard_price': 1000.0, + 'property_account_income_id': cls.company_data['default_account_revenue'].id, + 'property_account_expense_id': cls.company_data['default_account_expense'].id, + 'taxes_id': [(6, 0, cls.tax_sale_a.ids)], + 'supplier_taxes_id': [(6, 0, cls.tax_purchase_a.ids)], + "l10n_in_hsn_code": "01111", + }) + cls.product_a_discount = cls.env['product.product'].create({ + 'name': 'product_a discount', + 'uom_id': cls.env.ref('uom.product_uom_unit').id, + 'lst_price': 400.0, + 'standard_price': 400.0, + 'property_account_income_id': cls.company_data['default_account_revenue'].id, + 'property_account_expense_id': cls.company_data['default_account_expense'].id, + 'taxes_id': [(6, 0, cls.tax_sale_a.ids)], + 'supplier_taxes_id': [(6, 0, cls.tax_purchase_a.ids)], + "l10n_in_hsn_code": "01111", + }) gst_with_cess = cls.env.ref("l10n_in.%s_sgst_sale_12" % (cls.company_data["company"].id) ) + cls.env.ref("l10n_in.%s_cess_5_plus_1591_sale" % (cls.company_data["company"].id)) product_with_cess = cls.env["product.product"].create({ @@ -56,13 +79,42 @@ class TestEdiJson(AccountTestInvoicingCommon): cls.invoice_zero_qty.write({ "invoice_line_ids": [(1, l_id, {"quantity": 0}) for l_id in cls.invoice_zero_qty.invoice_line_ids.ids]}) cls.invoice_zero_qty.action_post() - cls.invoice_negative_unit_price = cls.init_invoice("out_invoice", post=False, products=cls.product_a + product_with_cess) + cls.invoice_negative_unit_price = cls.init_invoice("out_invoice", post=False, products=cls.product_a + cls.product_a_discount + product_with_cess) cls.invoice_negative_unit_price.write({ "invoice_line_ids": [ - (1, cls.invoice_negative_unit_price.invoice_line_ids[0].id, {"price_unit": -400}), - (1, cls.invoice_negative_unit_price.invoice_line_ids[1].id, {"price_unit": 1000}), + (1, cls.invoice_negative_unit_price.invoice_line_ids[0].id, {"price_unit": 1000}), + (1, cls.invoice_negative_unit_price.invoice_line_ids[1].id, {"price_unit": -400}), ]}) cls.invoice_negative_unit_price.action_post() + cls.invoice_negative_qty = cls.init_invoice("out_invoice", post=False, products=cls.product_a + cls.product_a_discount + product_with_cess) + cls.invoice_negative_qty.write({ + "invoice_line_ids": [ + (1, cls.invoice_negative_qty.invoice_line_ids[0].id, {"price_unit": 1000}), + (1, cls.invoice_negative_qty.invoice_line_ids[1].id, {"price_unit": 400, 'quantity': -1}), + ]}) + cls.invoice_negative_qty.action_post() + cls.invoice_negative_unit_price_and_qty = cls.init_invoice("out_invoice", post=False, products=cls.product_a + cls.product_a_discount + product_with_cess) + cls.invoice_negative_unit_price_and_qty.write({ + "invoice_line_ids": [ + (1, cls.invoice_negative_unit_price_and_qty.invoice_line_ids[0].id, {"price_unit": -1000, 'quantity': -1}), + (1, cls.invoice_negative_unit_price_and_qty.invoice_line_ids[1].id, {"price_unit": -400}), + ]}) + cls.invoice_negative_unit_price_and_qty.action_post() + cls.invoice_negative_with_discount = cls.init_invoice("out_invoice", post=False, products=cls.product_a + cls.product_a_discount) + cls.invoice_negative_with_discount.write({ + "invoice_line_ids": [ + (1, cls.invoice_negative_with_discount.invoice_line_ids[0].id, {"price_unit": 2000, 'discount': 50}), + (1, cls.invoice_negative_with_discount.invoice_line_ids[1].id, {"price_unit": -400}), + ]}) + cls.invoice_negative_with_discount.action_post() + cls.invoice_negative_more_than_max_line = cls.init_invoice("out_invoice", post=False, products=cls.product_a + cls.product_a2 + cls.product_a_discount) + cls.invoice_negative_more_than_max_line.write({ + "invoice_line_ids": [ + (1, cls.invoice_negative_more_than_max_line.invoice_line_ids[0].id, {"price_unit": 2000, 'discount': 50}), + (1, cls.invoice_negative_more_than_max_line.invoice_line_ids[1].id, {"price_unit": 1000}), + (1, cls.invoice_negative_more_than_max_line.invoice_line_ids[2].id, {"price_unit": -1100}), + ]}) + cls.invoice_negative_more_than_max_line.action_post() def test_edi_json(self): json_value = self.env["account.edi.format"]._l10n_in_edi_generate_invoice_json(self.invoice) @@ -142,22 +194,74 @@ class TestEdiJson(AccountTestInvoicingCommon): json_value = self.env["account.edi.format"]._l10n_in_edi_generate_invoice_json(self.invoice_negative_unit_price) expected.update({ "DocDtls": {"Typ": "INV", "No": "INV/2019/00004", "Dt": "01/01/2019"}, - "ItemList": [{ - "SlNo": "1", "PrdDesc": "product_a", "IsServc": "N", "HsnCd": "01111", "Qty": 1.0, - "Unit": "UNT", "UnitPrice": -400.0, "TotAmt": -400.0, "Discount": 0.0, "AssAmt": -400.0, - "GstRt": 5.0, "IgstAmt": 0.0, "CgstAmt": -10.0, "SgstAmt": -10.0, "CesRt": 0.0, "CesAmt": 0.0, - "CesNonAdvlAmt": 0.0, "StateCesRt": 0.0, "StateCesAmt": 0.0, "StateCesNonAdvlAmt": 0.0, - "OthChrg": 0.0, "TotItemVal": -420.0 - }, { - "SlNo": "2", "PrdDesc": "product_with_cess", "IsServc": "N", "HsnCd": "02222", "Qty": 1.0, - "Unit": "UNT", "UnitPrice": 1000.0, "TotAmt": 1000.0, "Discount": 0.0, "AssAmt": 1000.0, - "GstRt": 12.0, "IgstAmt": 0.0, "CgstAmt": 60.0, "SgstAmt": 60.0, "CesRt": 5.0, "CesAmt": 50.0, - "CesNonAdvlAmt": 1.59, "StateCesRt": 0.0, "StateCesAmt": 0.0, "StateCesNonAdvlAmt": 0.0, - "OthChrg": 0.0, "TotItemVal": 1171.59 - }], + "ItemList": [ + { + "SlNo": "1", "PrdDesc": "product_a", "IsServc": "N", "HsnCd": "01111", "Qty": 1.0, + "Unit": "UNT", "UnitPrice": 1000.0, "TotAmt": 1000.0, "Discount": 400.0, "AssAmt": 600.0, + "GstRt": 5.0, "IgstAmt": 0.0, "CgstAmt": 15.0, "SgstAmt": 15.0, "CesRt": 0.0, "CesAmt": 0.0, + "CesNonAdvlAmt": 0.0, "StateCesRt": 0.0, "StateCesAmt": 0.0, "StateCesNonAdvlAmt": 0.0, + "OthChrg": 0.0, "TotItemVal": 630.0 + }, + { + "SlNo": "3", "PrdDesc": "product_with_cess", "IsServc": "N", "HsnCd": "02222", "Qty": 1.0, + "Unit": "UNT", "UnitPrice": 1000.0, "TotAmt": 1000.0, "Discount": 0.0, "AssAmt": 1000.0, + "GstRt": 12.0, "IgstAmt": 0.0, "CgstAmt": 60.0, "SgstAmt": 60.0, "CesRt": 5.0, "CesAmt": 50.0, + "CesNonAdvlAmt": 1.59, "StateCesRt": 0.0, "StateCesAmt": 0.0, "StateCesNonAdvlAmt": 0.0, + "OthChrg": 0.0, "TotItemVal": 1171.59 + } + ], "ValDtls": { - "AssVal": 600.0, "CgstVal": 50.0, "SgstVal": 50.0, "IgstVal": 0.0, "CesVal": 51.59, - "StCesVal": 0.0, "RndOffAmt": 0.0, "TotInvVal": 751.59 + "AssVal": 1600.0, "CgstVal": 75.0, "SgstVal": 75.0, "IgstVal": 0.0, "CesVal": 51.59, + "StCesVal": 0.0, "RndOffAmt": 0.0, "TotInvVal": 1801.59 }, }) - self.assertDictEqual(json_value, expected, "Indian EDI with Negative unit price sent json value is not matched") + self.assertDictEqual(json_value, expected, "Indian EDI with negative unit price sent json value is not matched") + + expected.update({"DocDtls": {"Typ": "INV", "No": "INV/2019/00005", "Dt": "01/01/2019"}}) + json_value = self.env["account.edi.format"]._l10n_in_edi_generate_invoice_json(self.invoice_negative_qty) + self.assertDictEqual(json_value, expected, "Indian EDI with negative quantity sent json value is not matched") + + expected.update({"DocDtls": {"Typ": "INV", "No": "INV/2019/00006", "Dt": "01/01/2019"}}) + json_value = self.env["account.edi.format"]._l10n_in_edi_generate_invoice_json(self.invoice_negative_unit_price_and_qty) + self.assertDictEqual(json_value, expected, "Indian EDI with negative unit price and quantity sent json value is not matched") + + expected.update({ + "DocDtls": {"Typ": "INV", "No": "INV/2019/00007", "Dt": "01/01/2019"}, + "ItemList": [{ + "SlNo": "1", "PrdDesc": "product_a", "IsServc": "N", "HsnCd": "01111", "Qty": 1.0, + "Unit": "UNT", "UnitPrice": 2000.0, "TotAmt": 2000.0, "Discount": 1400.0, "AssAmt": 600.0, + "GstRt": 5.0, "IgstAmt": 0.0, "CgstAmt": 15.0, "SgstAmt": 15.0, "CesRt": 0.0, "CesAmt": 0.0, + "CesNonAdvlAmt": 0.0, "StateCesRt": 0.0, "StateCesAmt": 0.0, "StateCesNonAdvlAmt": 0.0, + "OthChrg": 0.0, "TotItemVal": 630.0 + }], + "ValDtls": { + "AssVal": 600.0, "CgstVal": 15.0, "SgstVal": 15.0, "IgstVal": 0.0, "CesVal": 0.0, + "StCesVal": 0.0, "RndOffAmt": 0.0, "TotInvVal": 630.0 + }, + }) + json_value = self.env["account.edi.format"]._l10n_in_edi_generate_invoice_json(self.invoice_negative_with_discount) + self.assertDictEqual(json_value, expected, "Indian EDI with negative unit price and quantity sent json value is not matched") + + expected.update({ + "DocDtls": {"Typ": "INV", "No": "INV/2019/00008", "Dt": "01/01/2019"}, + "ItemList": [{ + "SlNo": "1", "PrdDesc": "product_a", "IsServc": "N", "HsnCd": "01111", "Qty": 1.0, + "Unit": "UNT", "UnitPrice": 2000.0, "TotAmt": 2000.0, "Discount": 2000.0, "AssAmt": 0.0, + "GstRt": 5.0, "IgstAmt": 0.0, "CgstAmt": 0.0, "SgstAmt": 0.0, "CesRt": 0.0, "CesAmt": 0.0, + "CesNonAdvlAmt": 0.0, "StateCesRt": 0.0, "StateCesAmt": 0.0, "StateCesNonAdvlAmt": 0.0, + "OthChrg": 0.0, "TotItemVal": 0.0 + }, + { + "SlNo": "2", "PrdDesc": "product_a2", "IsServc": "N", "HsnCd": "01111", "Qty": 1.0, + "Unit": "UNT", "UnitPrice": 1000.0, "TotAmt": 1000.0, "Discount": 100.0, "AssAmt": 900.0, + "GstRt": 5.0, "IgstAmt": 0.0, "CgstAmt": 22.5, "SgstAmt": 22.5, "CesRt": 0.0, "CesAmt": 0.0, + "CesNonAdvlAmt": 0.0, "StateCesRt": 0.0, "StateCesAmt": 0.0, "StateCesNonAdvlAmt": 0.0, + "OthChrg": 0.0, "TotItemVal": 945.0 + }], + "ValDtls": { + "AssVal": 900.0, "CgstVal": 22.5, "SgstVal": 22.5, "IgstVal": 0.0, "CesVal": 0.0, + "StCesVal": 0.0, "RndOffAmt": 0.0, "TotInvVal": 945.0 + }, + }) + json_value = self.env['account.edi.format']._l10n_in_edi_generate_invoice_json(self.invoice_negative_more_than_max_line) + self.assertDictEqual(json_value, expected, "Indian EDI with negative value more than max line sent json value is not matched")