diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py index 4e9397b7506..ad7d9ad324d 100644 --- a/addons/account/models/account_tax.py +++ b/addons/account/models/account_tax.py @@ -1132,6 +1132,7 @@ class AccountTax(models.Model): return {'tax': tax_values['tax_repartition_line'].tax_id} global_tax_details = { + 'to_process': to_process, 'base_amount_currency': 0.0, 'base_amount': 0.0, 'tax_amount_currency': 0.0, diff --git a/addons/l10n_in/models/account_invoice.py b/addons/l10n_in/models/account_invoice.py index c65d2c54fd9..6533e2fd320 100644 --- a/addons/l10n_in/models/account_invoice.py +++ b/addons/l10n_in/models/account_invoice.py @@ -3,8 +3,11 @@ import base64 import logging +from collections import defaultdict + from odoo import api, fields, models, _ from odoo.exceptions import ValidationError, RedirectWarning, UserError +from odoo.tools import frozendict from odoo.tools.image import image_data_uri _logger = logging.getLogger(__name__) @@ -142,3 +145,117 @@ class AccountMove(models.Model): barcode = self.env['ir.actions.report'].barcode(barcode_type="QR", value=payment_url, width=120, height=120) return image_data_uri(base64.b64encode(barcode)) return super()._generate_qr_code(silent_errors) + + def _l10n_in_get_hsn_summary_table(self): + self.ensure_one() + display_uom = self.env.user.user_has_groups('uom.group_uom') + tag_igst = self.env.ref('l10n_in.tax_tag_igst') + tag_cgst = self.env.ref('l10n_in.tax_tag_cgst') + tag_sgst = self.env.ref('l10n_in.tax_tag_sgst') + tag_cess = self.env.ref('l10n_in.tax_tag_cess') + + def filter_invl_to_apply(invoice_line): + return bool(invoice_line.product_id.l10n_in_hsn_code) + + def grouping_key_generator(base_line, _tax_values): + # The rate is only for SGST/CGST. + if base_line['is_refund']: + tax_rep_field = 'refund_repartition_line_ids' + else: + tax_rep_field = 'invoice_repartition_line_ids' + gst_taxes = base_line['taxes'].flatten_taxes_hierarchy()[tax_rep_field]\ + .filtered(lambda tax_rep: ( + tax_rep.repartition_type == 'tax' + and any(tag in tax_rep.tag_ids for tag in tag_sgst + tag_cgst + tag_igst) + ))\ + .tax_id + + return { + 'l10n_in_hsn_code': base_line['record'].product_id.l10n_in_hsn_code, + 'rate': sum(gst_taxes.mapped('amount')), + 'uom': base_line['record'].product_uom_id, + } + + aggregated_values = self._prepare_invoice_aggregated_taxes( + filter_invl_to_apply=filter_invl_to_apply, + grouping_key_generator=grouping_key_generator, + ) + + results_map = {} + has_igst = False + has_gst = False + has_cess = False + for grouping_key, tax_details in aggregated_values['tax_details'].items(): + values = results_map.setdefault(grouping_key, { + 'quantity': 0.0, + 'amount_untaxed': tax_details['base_amount_currency'], + 'tax_amounts': defaultdict(lambda: 0.0), + }) + + # Quantity. + invoice_line_ids = set() + for invoice_line in tax_details['records']: + if invoice_line.id not in invoice_line_ids: + values['quantity'] += invoice_line.quantity + invoice_line_ids.add(invoice_line.id) + + # Tax amounts. + for tax_details in tax_details['group_tax_details']: + tax_rep = tax_details['tax_repartition_line'] + if tag_igst in tax_rep.tag_ids: + has_igst = True + values['tax_amounts'][tag_igst] += tax_details['tax_amount_currency'] + if tag_cgst in tax_rep.tag_ids: + has_gst = True + values['tax_amounts'][tag_cgst] += tax_details['tax_amount_currency'] + if tag_sgst in tax_rep.tag_ids: + has_gst = True + values['tax_amounts'][tag_sgst] += tax_details['tax_amount_currency'] + if tag_cess in tax_rep.tag_ids: + has_cess = True + values['tax_amounts'][tag_cess] += tax_details['tax_amount_currency'] + + # In case of base_line with HSN code but no taxes, an entry in results_map should be created. + for base_line, _to_update_vals, _tax_values_list in aggregated_values['to_process']: + if base_line['taxes']: + continue + + grouping_key = frozendict(grouping_key_generator(base_line, None)) + results = results_map.setdefault(grouping_key, { + 'quantity': 0.0, + 'amount_untaxed': 0.0, + 'tax_amounts': defaultdict(lambda: 0.0), + }) + results['quantity'] += base_line['quantity'] + results['amount_untaxed'] += base_line['price_subtotal'] + + nb_columns = 5 + if has_igst: + nb_columns += 1 + if has_gst: + nb_columns += 2 + if has_cess: + nb_columns += 1 + + items = [] + for grouping_key, values in results_map.items(): + items.append({ + 'l10n_in_hsn_code': grouping_key['l10n_in_hsn_code'], + 'quantity': values['quantity'], + 'uom': grouping_key['uom'], + 'rate': grouping_key['rate'], + 'amount_untaxed': values['amount_untaxed'], + 'tax_amount_igst': values['tax_amounts'].get(tag_igst, 0.0), + 'tax_amount_cgst': values['tax_amounts'].get(tag_cgst, 0.0), + 'tax_amount_sgst': values['tax_amounts'].get(tag_sgst, 0.0), + 'tax_amount_cess': values['tax_amounts'].get(tag_cess, 0.0), + }) + + return { + 'has_igst': has_igst, + 'has_gst': has_gst, + 'has_cess': has_cess, + 'nb_columns': nb_columns, + 'display_uom': display_uom, + 'items': items, + } diff --git a/addons/l10n_in/tests/__init__.py b/addons/l10n_in/tests/__init__.py new file mode 100644 index 00000000000..fe6cde896ca --- /dev/null +++ b/addons/l10n_in/tests/__init__.py @@ -0,0 +1 @@ +from . import test_invoice_hsn_summary diff --git a/addons/l10n_in/tests/test_invoice_hsn_summary.py b/addons/l10n_in/tests/test_invoice_hsn_summary.py new file mode 100644 index 00000000000..c8122504fe7 --- /dev/null +++ b/addons/l10n_in/tests/test_invoice_hsn_summary.py @@ -0,0 +1,624 @@ +# -*- coding: utf-8 -*- +from freezegun import freeze_time + +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.fields import Command +from odoo.tests import tagged + + +@tagged('post_install', '-at_install', 'post_install_l10n') +class TestInvoiceHSNsummary(AccountTestInvoicingCommon): + + @classmethod + def setUpClass(cls): + super().setUpClass(chart_template_ref='in') + + cls.test_hsn_code_1 = '1234' + cls.test_hsn_code_2 = '4321' + + cls.uom_unit = cls.env.ref('uom.product_uom_unit') + cls.uom_dozen = cls.env.ref('uom.product_uom_dozen') + + cls.product_a.l10n_in_hsn_code = cls.test_hsn_code_1 + cls.product_b.l10n_in_hsn_code = cls.test_hsn_code_2 + cls.product_c = cls.env['product.product'].create({ + 'name': 'product_c', + 'l10n_in_hsn_code': cls.test_hsn_code_1, + 'uom_id': cls.env.ref('uom.product_uom_unit').id, + 'lst_price': 1000.0, + 'property_account_income_id': cls.company_data['default_account_revenue'].id, + }) + + cls.gst_5 = cls.env['account.chart.template'].ref('sgst_sale_5') + cls.gst_18 = cls.env['account.chart.template'].ref('sgst_sale_18') + + cls.igst_0 = cls.env['account.chart.template'].ref('igst_sale_0') + cls.igst_5 = cls.env['account.chart.template'].ref('igst_sale_5') + cls.igst_18 = cls.env['account.chart.template'].ref('igst_sale_18') + cls.cess_5_plus_1591 = cls.env['account.chart.template'].ref('cess_5_plus_1591_sale') + cls.exempt_0 = cls.env['account.chart.template'].ref('exempt_sale') + + cls.display_uom = cls.env.user.user_has_groups('uom.group_uom') + + def assert_hsn_summary(self, invoice, expected_values): + hsn_summary = invoice._l10n_in_get_hsn_summary_table() + self.assertEqual( + {k: len(v) if k == 'items' else v for k, v in hsn_summary.items()}, + {k: len(v) if k == 'items' else v for k, v in expected_values.items()}, + ) + self.assertEqual(len(hsn_summary['items']), len(expected_values['items'])) + for item, expected_item in zip(hsn_summary['items'], expected_values['items']): + self.assertDictEqual(item, expected_item) + + @freeze_time('2019-01-01') + def create_invoice(self, invoice_line_ids, **kwargs): + return self.env['account.move'].create({ + 'move_type': 'out_invoice', + 'partner_id': self.partner_a.id, + **kwargs, + 'invoice_line_ids': invoice_line_ids, + }) + + def test_l10n_in_hsn_summary_1(self): + """ Test GST/IGST taxes. """ + invoice = self.create_invoice(invoice_line_ids=[ + Command.create({ + 'product_id': self.product_a.id, + 'quantity': 2.0, + 'price_unit': 100, + 'tax_ids': [Command.set(self.gst_5.ids)], #Tax: 10 + }), + Command.create({ + 'product_id': self.product_c.id, + 'quantity': 1.0, + 'price_unit': 600, + 'tax_ids': [Command.set(self.gst_5.ids)], #Tax: 30 + }), + Command.create({ + 'product_id': self.product_a.id, + # 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 5.0, + 'price_unit': 300, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [Command.set(self.gst_5.ids)], #Tax: 75 + }), + + Command.create({ + 'product_id': self.product_a.id, + 'quantity': 2.0, + 'price_unit': 100, + 'tax_ids': [Command.set(self.gst_18.ids)], #Tax: 36 + }), + Command.create({ + 'product_id': self.product_c.id, + 'quantity': 1.0, + 'price_unit': 600, + 'tax_ids': [Command.set(self.gst_18.ids)], #Tax: 108 + }), + Command.create({ + 'product_id': self.product_a.id, + # 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 5.0, + 'price_unit': 300, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [Command.set(self.gst_18.ids)], #Tax: 270 + }), + ]) + + self.assertRecordValues(invoice, [{ + 'amount_untaxed': 4600.0, + 'amount_tax': 529.0, + 'amount_total': 5129.0, + }]) + + self.assert_hsn_summary(invoice, { + 'has_igst': False, + 'has_gst': True, + 'has_cess': False, + 'nb_columns': 7, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 8.0, + 'uom': self.uom_unit, + 'rate': 5.0, + 'amount_untaxed': 2300.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 57.5, + 'tax_amount_sgst': 57.5, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 8.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 2300.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 207.0, + 'tax_amount_sgst': 207.0, + 'tax_amount_cess': 0.0, + }, + ], + }) + + # Change the UOM of the second line. + invoice.invoice_line_ids = [ + Command.update(invoice.invoice_line_ids[1].id, {'product_uom_id': self.uom_dozen.id}), + ] + + self.assertRecordValues(invoice, [{ + 'amount_untaxed': 16000.0, + 'amount_tax': 1099.0, + 'amount_total': 17099.0, + }]) + + self.assert_hsn_summary(invoice, { + 'has_igst': False, + 'has_gst': True, + 'has_cess': False, + 'nb_columns': 7, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 7.0, + 'uom': self.uom_unit, + 'rate': 5.0, + 'amount_untaxed': 1700.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 42.5, + 'tax_amount_sgst': 42.5, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 1.0, + 'uom': self.uom_dozen, + 'rate': 5.0, + 'amount_untaxed': 12000.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 300.0, + 'tax_amount_sgst': 300.0, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 8.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 2300.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 207.0, + 'tax_amount_sgst': 207.0, + 'tax_amount_cess': 0.0, + } + ] + }) + + # Change GST 5% taxes to IGST. + invoice.invoice_line_ids = [ + Command.update(invoice.invoice_line_ids[0].id, {'tax_ids': [Command.set(self.igst_5.ids)]}), + Command.update(invoice.invoice_line_ids[1].id, {'tax_ids': [Command.set(self.igst_5.ids)]}), + Command.update(invoice.invoice_line_ids[2].id, {'tax_ids': [Command.set(self.igst_5.ids)]}), + ] + + self.assertRecordValues(invoice, [{ + 'amount_untaxed': 16000.0, + 'amount_tax': 1099.0, + 'amount_total': 17099.0, + }]) + + self.assert_hsn_summary(invoice, { + 'has_igst': True, + 'has_gst': True, + 'has_cess': False, + 'nb_columns': 8, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 7.0, + 'uom': self.uom_unit, + 'rate': 5.0, + 'amount_untaxed': 1700.0, + 'tax_amount_igst': 85.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 1.0, + 'uom': self.uom_dozen, + 'rate': 5.0, + 'amount_untaxed': 12000.0, + 'tax_amount_igst': 600.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 8.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 2300.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 207.0, + 'tax_amount_sgst': 207.0, + 'tax_amount_cess': 0.0, + }, + ], + }) + + # Put back the UOM of the second line to unit. + invoice.invoice_line_ids = [ + Command.update(invoice.invoice_line_ids[1].id, { + 'product_uom_id': self.uom_unit.id, + 'price_unit': 600, + 'tax_ids': [Command.set(self.igst_5.ids)], + }), + ] + + self.assertRecordValues(invoice, [{ + 'amount_untaxed': 4600.0, + 'amount_tax': 529.0, + 'amount_total': 5129.0, + }]) + + self.assert_hsn_summary(invoice, { + 'has_igst': True, + 'has_gst': True, + 'has_cess': False, + 'nb_columns': 8, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 8.0, + 'uom': self.uom_unit, + 'rate': 5.0, + 'amount_untaxed': 2300.0, + 'tax_amount_igst': 115.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 8.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 2300.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 207.0, + 'tax_amount_sgst': 207.0, + 'tax_amount_cess': 0.0, + }, + ], + }) + + # Change GST 18% taxes to IGST. + invoice.invoice_line_ids = [ + Command.update(invoice.invoice_line_ids[3].id, {'tax_ids': [Command.set(self.igst_18.ids)]}), + Command.update(invoice.invoice_line_ids[4].id, {'tax_ids': [Command.set(self.igst_18.ids)]}), + Command.update(invoice.invoice_line_ids[5].id, {'tax_ids': [Command.set(self.igst_18.ids)]}), + ] + + self.assert_hsn_summary(invoice, { + 'has_igst': True, + 'has_gst': False, + 'has_cess': False, + 'nb_columns': 6, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 8.0, + 'uom': self.uom_unit, + 'rate': 5.0, + 'amount_untaxed': 2300.0, + 'tax_amount_igst': 115.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 8.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 2300.0, + 'tax_amount_igst': 414.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + ], + }) + + def test_l10n_in_hsn_summary_2(self): + """ Test CESS taxes in combination with GST/IGST. """ + invoice = self.create_invoice(invoice_line_ids=[ + Command.create({ + 'product_id': self.product_a.id, + 'quantity': 1.0, + 'price_unit': 15.80, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [Command.set((self.gst_18 + self.cess_5_plus_1591).ids)], + }) + ]) + + self.assertRecordValues(invoice, [{ + 'amount_untaxed': 15.8, + 'amount_tax': 5.22, + 'amount_total': 21.02, + }]) + + self.assert_hsn_summary(invoice, { + 'has_igst': False, + 'has_gst': True, + 'has_cess': True, + 'nb_columns': 8, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 1.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 15.8, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 1.42, + 'tax_amount_sgst': 1.42, + 'tax_amount_cess': 2.38, + }, + ], + }) + + # Change GST 18% taxes to IGST. + invoice.invoice_line_ids = [ + Command.update(invoice.invoice_line_ids.id, { + 'tax_ids': [Command.set((self.igst_18 + self.cess_5_plus_1591).ids)], + }), + ] + + self.assert_hsn_summary(invoice, { + 'has_igst': True, + 'has_gst': False, + 'has_cess': True, + 'nb_columns': 7, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 1.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 15.8, + 'tax_amount_igst': 2.84, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 2.38, + }, + ], + }) + + def test_l10n_in_hsn_summary_3(self): + """ Test with mixed HSN codes. """ + invoice = self.create_invoice(invoice_line_ids=[ + Command.create({ + 'product_id': self.product_a.id, + 'quantity': 1.0, + 'price_unit': 100, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [Command.set(self.gst_18.ids)], + }), + Command.create({ + # 'l10n_in_hsn_code': self.test_hsn_code_1, + 'product_id': self.product_a.id, + 'quantity': 2.0, + 'price_unit': 50, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [Command.set(self.gst_18.ids)], + }), + Command.create({ + 'product_id': self.product_b.id, + 'quantity': 1.0, + 'price_unit': 100, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [Command.set(self.gst_18.ids)], + }), + Command.create({ + 'product_id': self.product_b.id, + # 'l10n_in_hsn_code': self.test_hsn_code_2, + 'quantity': 2.0, + 'price_unit': 50, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [Command.set(self.gst_18.ids)], + }) + ]) + + self.assertRecordValues(invoice, [{ + 'amount_untaxed': 400.0, + 'amount_tax': 72.0, + 'amount_total': 472.0, + }]) + + self.assert_hsn_summary(invoice, { + 'has_igst': False, + 'has_gst': True, + 'has_cess': False, + 'nb_columns': 7, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 3.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 200.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 18.0, + 'tax_amount_sgst': 18.0, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_2, + 'quantity': 3.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 200.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 18.0, + 'tax_amount_sgst': 18.0, + 'tax_amount_cess': 0.0, + }, + ], + }) + + # Change GST 18% taxes to IGST. + invoice.invoice_line_ids = [ + Command.update(invoice.invoice_line_ids[0].id, {'tax_ids': [Command.set(self.igst_18.ids)]}), + Command.update(invoice.invoice_line_ids[1].id, {'tax_ids': [Command.set(self.igst_18.ids)]}), + Command.update(invoice.invoice_line_ids[2].id, {'tax_ids': [Command.set(self.igst_18.ids)]}), + Command.update(invoice.invoice_line_ids[3].id, {'tax_ids': [Command.set(self.igst_18.ids)]}), + ] + + self.assert_hsn_summary(invoice, { + 'has_igst': True, + 'has_gst': False, + 'has_cess': False, + 'nb_columns': 6, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 3.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 200.0, + 'tax_amount_igst': 36.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_2, + 'quantity': 3.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 200.0, + 'tax_amount_igst': 36.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + ], + }) + + def test_l10n_in_hsn_summary_4(self): + """ Zero rated GST or no taxes at all.""" + invoice = self.create_invoice(invoice_line_ids=[ + Command.create({ + 'product_id': self.product_a.id, + 'quantity': 1.0, + 'price_unit': 350.0, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [], + }), + Command.create({ + 'product_id': self.product_a.id, + 'quantity': 1.0, + 'price_unit': 350.0, + 'product_uom_id': self.uom_unit.id, + 'tax_ids': [], + }), + ]) + + self.assert_hsn_summary(invoice, { + 'has_igst': False, + 'has_gst': False, + 'has_cess': False, + 'nb_columns': 5, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 2.0, + 'uom': self.uom_unit, + 'rate': 0.0, + 'amount_untaxed': 700.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + ], + }) + + # No tax to IGST 0%/exempt. + invoice.invoice_line_ids = [ + Command.update(invoice.invoice_line_ids[0].id, {'tax_ids': [Command.set(self.igst_0.ids)]}), + Command.update(invoice.invoice_line_ids[1].id, {'tax_ids': [Command.set(self.exempt_0.ids)]}), + ] + + self.assert_hsn_summary(invoice, { + 'has_igst': False, + 'has_gst': False, + 'has_cess': False, + 'nb_columns': 5, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 2.0, + 'uom': self.uom_unit, + 'rate': 0.0, + 'amount_untaxed': 700.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + ], + }) + + # Put one IGST 18% to get a value on the IGST column. + invoice.invoice_line_ids = [ + Command.update(invoice.invoice_line_ids[0].id, {'tax_ids': [Command.set(self.igst_18.ids)]}), + ] + + self.assert_hsn_summary(invoice, { + 'has_igst': True, + 'has_gst': False, + 'has_cess': False, + 'nb_columns': 6, + 'display_uom': self.display_uom, + 'items': [ + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 1.0, + 'uom': self.uom_unit, + 'rate': 18.0, + 'amount_untaxed': 350.0, + 'tax_amount_igst': 63.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + { + 'l10n_in_hsn_code': self.test_hsn_code_1, + 'quantity': 1.0, + 'uom': self.uom_unit, + 'rate': 0.0, + 'amount_untaxed': 350.0, + 'tax_amount_igst': 0.0, + 'tax_amount_cgst': 0.0, + 'tax_amount_sgst': 0.0, + 'tax_amount_cess': 0.0, + }, + ], + }) diff --git a/addons/l10n_in/views/report_invoice.xml b/addons/l10n_in/views/report_invoice.xml index 3f7847b1ae3..9a4d84dc237 100644 --- a/addons/l10n_in/views/report_invoice.xml +++ b/addons/l10n_in/views/report_invoice.xml @@ -58,6 +58,59 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +

HSN Summary

HSN/SACQuantityRate %Taxable ValueSGSTCGSTIGSTCESS
+ + + () + + + + + + + + + + + +
+
+
+
+
+