[IMP] l10n_in: add hsn summary to invoice report
task_id: 3196107 closes odoo/odoo#155084 Signed-off-by: Laurent Smet (las) <las@odoo.com> Co-authored-by: Harsh Modi <hamo@odoo.com>
This commit is contained in:
committed by
Harsh Modi
co-authored by
Harsh Modi
parent
7f4e421bcb
commit
a404654c18
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import test_invoice_hsn_summary
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -58,6 +58,59 @@
|
||||
</h2>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[@id='payment_term']" position="before">
|
||||
<t t-if="o.company_id.account_fiscal_country_id.code == 'IN'">
|
||||
<t t-set="hsn_summary" t-value="o._l10n_in_get_hsn_summary_table()"/>
|
||||
<t t-if="hsn_summary">
|
||||
<div name="l10n_in_hsn_summary" class="mt-3">
|
||||
<table class="table table-sm table-borderless col-6" style="page-break-inside: avoid;">
|
||||
<thead>
|
||||
<th t-att-colspan="hsn_summary['nb_columns']"><h3>HSN Summary</h3></th>
|
||||
</thead>
|
||||
<thead>
|
||||
<th>HSN/SAC</th>
|
||||
<th class="text-end">Quantity</th>
|
||||
<th class="text-end">Rate %</th>
|
||||
<th class="text-end">Taxable Value</th>
|
||||
<th class="text-end" t-if="hsn_summary['has_gst']">SGST</th>
|
||||
<th class="text-end" t-if="hsn_summary['has_gst']">CGST</th>
|
||||
<th class="text-end" t-if="hsn_summary['has_igst']">IGST</th>
|
||||
<th class="text-end" t-if="hsn_summary['has_cess']">CESS</th>
|
||||
</thead>
|
||||
<tr t-foreach="hsn_summary['items']" t-as="item">
|
||||
<td t-esc="item['l10n_in_hsn_code']"/>
|
||||
<td class="text-end">
|
||||
<span t-esc="item['quantity']"/>
|
||||
<span t-if="hsn_summary['display_uom']">(<t t-esc="item['uom'].name"/>)</span>
|
||||
</td>
|
||||
<td class="text-end" t-esc="item['rate']"/>
|
||||
<td class="text-end">
|
||||
<span t-esc="item['amount_untaxed']"
|
||||
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end" t-if="hsn_summary['has_gst']">
|
||||
<span t-esc="item['tax_amount_sgst']"
|
||||
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end" t-if="hsn_summary['has_gst']">
|
||||
<span t-esc="item['tax_amount_cgst']"
|
||||
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end" t-if="hsn_summary['has_igst']">
|
||||
<span t-esc="item['tax_amount_igst']"
|
||||
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
<td class="text-end" t-if="hsn_summary['has_cess']">
|
||||
<span t-esc="item['tax_amount_cess']"
|
||||
t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
</template>
|
||||
|
||||
<!-- Workaround for Studio reports, see odoo/odoo#60660 -->
|
||||
|
||||
Reference in New Issue
Block a user