19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:21 +01:00
parent 7dc55599c6
commit 7f43bbbfcc
650 changed files with 45260 additions and 33436 deletions

View file

@ -0,0 +1,8 @@
from . import common
from . import test_hsn_summary
from . import test_partner_details_on_invoice
from . import test_l10n_in_fiscal_position
from . import test_check_status
from . import test_tds_tcs_alert
from . import test_gstr_section
from . import test_invoice_label

View file

@ -0,0 +1,271 @@
from datetime import date
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo import Command
class L10nInTestInvoicingCommon(AccountTestInvoicingCommon):
@classmethod
@AccountTestInvoicingCommon.setup_country('in')
def setUpClass(cls):
super().setUpClass()
cls.maxDiff = None
cls.test_date = date(2023, 5, 20)
# === Countries === #
cls.country_in = cls.env.ref('base.in')
cls.country_us = cls.env.ref('base.us')
# === States === #
cls.state_in_gj = cls.env.ref('base.state_in_gj')
cls.state_in_mh = cls.env.ref('base.state_in_mh')
cls.state_in_hp = cls.env.ref('base.state_in_hp')
# === Companies === #
cls.default_company = cls.company_data['company']
cls.default_company.write({
'name': "Default Company",
'state_id': cls.state_in_gj.id,
'vat': "24AAGCC7144L6ZE",
'street': "Khodiyar Chowk",
'street2': "Sala Number 3",
'city': "Amreli",
'zip': "365220",
'l10n_in_is_gst_registered': True,
'l10n_in_tds_feature': True,
'l10n_in_tcs_feature': True,
})
cls.outside_in_company = cls.env['res.company'].create({
'name': 'Outside India Company',
'country_id': cls.country_us.id,
})
cls.user.write({
'company_ids': [cls.default_company.id, cls.outside_in_company.id],
'company_id': cls.default_company.id,
})
# === Partners === #
cls.partner_a.write({
'name': "Partner Intra State",
'vat': '24ABCPM8965E1ZE',
'state_id': cls.default_company.state_id.id,
'country_id': cls.country_in.id,
'street': "Karansinhji Rd",
'street2': "Karanpara",
'city': "Rajkot",
'zip': "360001",
})
cls.partner_b.write({
'vat': '27DJMPM8965E1ZE',
'state_id': cls.state_in_mh.id,
'country_id': cls.country_in.id,
'street': "Sangeet Samrat Naushad Ali Rd",
'city': "Mumbai",
'zip': "400052",
})
cls.partner_foreign = cls.env['res.partner'].create({
'name': "Foreign Partner",
'country_id': cls.country_us.id,
'state_id': cls.env.ref("base.state_us_1").id,
'street': "351 Horner Chapel Rd",
'city': "Peebles",
'zip': "45660",
})
cls.partner_foreign_no_state = cls.env['res.partner'].create({
'name': "Foreign Partner Without State",
'country_id': cls.country_us.id,
# No state_id defined
})
cls.sez_partner = cls.env['res.partner'].create({
'name': 'SEZ Partner',
'vat': '36AAAAA1234AAZA',
'l10n_in_gst_treatment': 'special_economic_zone',
'street': 'Block no. 402',
'city': 'Some city',
'zip': '500002',
'state_id': cls.env.ref('base.state_in_gj').id,
'country_id': cls.env.ref('base.in').id,
})
# === Taxes === #
AccountChartTemplate = cls.env['account.chart.template']
cls.sgst_sale_5 = AccountChartTemplate.ref('sgst_sale_5')
cls.sgst_purchase_5 = AccountChartTemplate.ref('sgst_purchase_5')
cls.igst_sale_5 = AccountChartTemplate.ref('igst_sale_5')
cls.igst_sale_18 = AccountChartTemplate.ref('igst_sale_18')
cls.sgst_sale_18 = AccountChartTemplate.ref('sgst_sale_18')
cls.igst_sale_18_rcm = AccountChartTemplate.ref('igst_sale_18_rc')
cls.igst_sale_18_sez_lut = AccountChartTemplate.ref('igst_sale_18_sez_lut')
cls.igst_sale_18_sez_exp_lut = AccountChartTemplate.ref('igst_sale_18_sez_exp_lut')
cls.igst_sale_18_sez_exp = AccountChartTemplate.ref('igst_sale_18_sez_exp')
cls.igst_sale_18_sez_exp_inc = cls.igst_sale_18_sez_exp.copy({'price_include_override': 'tax_included'})
cls.gst_with_cess = (
AccountChartTemplate.ref("sgst_sale_12")
+ AccountChartTemplate.ref("cess_5_plus_1591_sale")
)
cls.exempt = AccountChartTemplate.ref('exempt_sale')
# === Products === #
cls.product_a.write({
"l10n_in_hsn_code": "111111",
'taxes_id': cls.sgst_sale_5,
'supplier_taxes_id': cls.sgst_purchase_5,
})
cls.product_b.write({
"l10n_in_hsn_code": "111111",
'uom_id': cls.env.ref('uom.product_uom_unit').id,
'lst_price': 1000.0,
'standard_price': 1000.0,
'taxes_id': cls.sgst_sale_5.ids,
'supplier_taxes_id': cls.sgst_purchase_5.ids,
})
cls.product_with_cess = cls.env["product.product"].create({
"name": "product_with_cess",
"uom_id": cls.env.ref("uom.product_uom_unit").id,
"lst_price": 1000.0,
"standard_price": 800.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": [Command.set(cls.gst_with_cess.ids)],
"supplier_taxes_id": [Command.set(cls.sgst_purchase_5.ids)],
"l10n_in_hsn_code": "333333",
})
# === Fiscal Positions === #
cls.fp_in_intra_state = cls.env["account.chart.template"].ref('fiscal_position_in_intra_state')
cls.fp_in_inter_state = cls.env["account.chart.template"].ref('fiscal_position_in_inter_state')
cls.fp_in_export = cls.env["account.chart.template"].ref('fiscal_position_in_export_sez_in')
# === Invoices === #
cls.invoice_a = cls.init_invoice(
move_type='out_invoice',
partner=cls.partner_a,
amounts=[110, 500],
taxes=cls.igst_sale_18,
)
cls.invoice_b = cls.init_invoice(
move_type='out_invoice',
partner=cls.partner_b,
amounts=[250, 600],
taxes=cls.igst_sale_18,
)
cls.invoice_c = cls.init_invoice(
move_type='out_invoice',
partner=cls.partner_foreign,
amounts=[300, 740],
taxes=cls.igst_sale_18,
)
cls.invoice_d = cls.init_invoice(
move_type='out_invoice',
partner=cls.partner_foreign_no_state,
amounts=[100, 200],
taxes=cls.igst_sale_18,
)
cls.invoice_with_rcm = cls.init_invoice(
"out_invoice",
partner=cls.partner_b,
products=cls.product_a,
taxes=cls.igst_sale_18_rcm,
)
cls.invoice_with_sez_lut = cls.init_invoice(
"out_invoice",
partner=cls.sez_partner,
products=cls.product_a,
taxes=cls.igst_sale_18_sez_lut,
)
cls.invoice_with_sez_without_lut = cls.init_invoice(
"out_invoice",
partner=cls.sez_partner,
products=cls.product_a,
taxes=cls.igst_sale_18,
)
cls.invoice_with_export_lut = cls.init_invoice(
"out_invoice",
partner=cls.partner_foreign,
products=cls.product_a,
taxes=cls.igst_sale_18_sez_exp_lut,
)
cls.invoice_with_export_without_lut = cls.init_invoice(
"out_invoice",
partner=cls.partner_foreign,
products=cls.product_a,
taxes=cls.igst_sale_18_sez_exp,
)
cls.invoice_with_export_without_lut_inc = cls.init_invoice(
"out_invoice",
partner=cls.partner_foreign,
products=cls.product_a,
taxes=cls.igst_sale_18_sez_exp_inc,
)
@classmethod
def _set_vals_and_post(cls, move, ref=None, line_vals=None, post=True, irn=None):
if ref:
move.ref = ref
if irn:
move.l10n_in_irn_number = irn
if line_vals:
move.write({'invoice_line_ids': [Command.update(line.id, line_vals) for line in move.line_ids]})
if post:
move.action_post()
return move
@classmethod
def _init_inv(cls, move_type='out_invoice', company=None, ref=None, partner=None, taxes=None, invoice_date=None, products=None, line_vals=None, post=True, irn=None):
return cls._set_vals_and_post(
move=cls.init_invoice(
move_type,
products=products or cls.product_a,
invoice_date=invoice_date or cls.test_date,
taxes=taxes,
company=company or cls.default_company,
partner=partner,
),
ref=ref,
irn=irn,
line_vals=line_vals,
post=post
)
@classmethod
def _create_credit_note(cls, inv, ref=None, credit_note_date=None, line_vals=None, post=True):
move = inv._reverse_moves()
move.invoice_date = credit_note_date or cls.test_date
return cls._set_vals_and_post(
move=move,
ref=ref,
line_vals=line_vals,
post=post
)
@classmethod
def _create_debit_note(cls, inv, ref=None, debit_note_date=None, line_vals=None):
move_debit_note_wiz = cls.env['account.debit.note'].with_context(
active_model="account.move",
active_ids=inv.ids
).create({
'date': debit_note_date or cls.test_date,
'reason': 'no reason',
'copy_lines': True,
})
move_debit_note_wiz.create_debit()
return cls._set_vals_and_post(move=inv.debit_note_ids[0], ref=ref, line_vals=line_vals)

View file

@ -0,0 +1,87 @@
from unittest.mock import patch
from freezegun import freeze_time
from odoo.addons.l10n_in.models.iap_account import IapAccount
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import UserError
from odoo.tools import mute_logger
from datetime import date
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestGSTStatusFeature(TransactionCase):
def setUp(self):
self.partner1 = self.env["res.partner"].create(
{"name": "Active GSTIN", "vat": "36AAACM4154G1ZO"}
)
self.partner2 = self.env["res.partner"].create(
{"name": "Cancelled GSTIN", "vat": "19AABCT1332L2ZD"}
)
self.partner3 = self.env["res.partner"].create(
{"name": "Invalid GSTIN", "vat": "19AACCT6304M1ZB"}
)
self.partner4 = self.env["res.partner"].create(
{"name": "No Records GSTIN", "vat": "19AACCT6304M1DB"}
)
self.partner5 = self.env["res.partner"].create(
{
"name": "Partner Vat Reset",
"vat": "36AAACM4154G1ZO",
"country_id": self.env.ref('base.in').id,
"l10n_in_gstin_verified_status": "active",
"l10n_in_gstin_verified_date": "2024-06-01",
}
)
self.mock_responses = {
"active": {
"data": {"sts": "Active"}
},
"cancelled": {
"data": {"sts": "Cancelled"}
},
"invalid": {
"error": [{"code": "SWEB_9035", "message": "Invalid GSTIN / UID"}],
},
"no_records": {
"error": [{"code": "FO8000", "message": "No records found for the provided GSTIN."}],
},
}
self.env.company.l10n_in_gstin_status_feature = True
self.env.company.account_fiscal_country_id = self.env.ref("base.in")
@freeze_time('2024-05-20')
@mute_logger('odoo.addons.l10n_in.models.res_partner')
def check_gstin_status(self, partner, expected_status, mock_response, raises_exception=False):
with patch.object(IapAccount, "_l10n_in_connect_to_server", return_value=mock_response):
if raises_exception:
with self.assertRaises(UserError):
partner.action_l10n_in_verify_gstin_status()
else:
partner.action_l10n_in_verify_gstin_status()
self.assertEqual(partner.l10n_in_gstin_verified_status, expected_status)
self.assertEqual(partner.l10n_in_gstin_verified_date, date(2024, 5, 20))
def test_gstin_status(self):
"""Test GSTIN status for various cases"""
self.check_gstin_status(
self.partner1,
expected_status=True,
mock_response=self.mock_responses["active"]
)
self.check_gstin_status(
self.partner2,
expected_status=False,
mock_response=self.mock_responses["cancelled"]
)
self.check_gstin_status(
self.partner3,
expected_status=False,
raises_exception=True,
mock_response=self.mock_responses["invalid"],
)
self.check_gstin_status(
self.partner4,
expected_status=False,
raises_exception=True,
mock_response=self.mock_responses["no_records"],
)

View file

@ -0,0 +1,124 @@
from datetime import date
from odoo import Command
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
from odoo.tests import tagged
TEST_DATE = date(2025, 6, 8)
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestGstrSection(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_b.l10n_in_gst_treatment = "regular"
cls.partner_foreign.l10n_in_gst_treatment = 'overseas'
cls.sez_partner = cls.partner_a.copy({"l10n_in_gst_treatment": "special_economic_zone"})
cls.large_unregistered_partner = cls.partner_a.copy({"state_id": cls.state_in_mh.id, "vat": None, "l10n_in_gst_treatment": "unregistered"})
ChartTemplate = cls.env['account.chart.template']
cls.nil_rated_tax = ChartTemplate.ref('nil_rated_sale')
cls.igst_lut_sale_28_sez = ChartTemplate.ref('igst_sale_28_sez_lut')
cls.igst_rc_sale_18 = ChartTemplate.ref('igst_sale_18_rc')
cls.igst_exp_sale_18 = ChartTemplate.ref('igst_sale_18_sez_exp')
def test_gstr_sections(self):
def assert_line_sections(lines, expected_sections):
for line in lines.filtered(lambda l: l.display_type in ('product', 'tax')):
if line.display_type == 'product':
tax_types = {tax.l10n_in_tax_type for tax in line.tax_ids}
matched = tax_types & expected_sections.keys()
if matched:
expected = expected_sections[matched.pop()]
else:
expected = expected_sections.get('with_gst_tag')
else: # tax line
expected = expected_sections['with_gst_tag'] if line.tax_tag_ids else expected_sections['no_tag']
self.assertEqual(line.l10n_in_gstr_section, expected)
# SEZ without payment with Nil Rated
sez_invoice = self._init_inv(
partner=self.sez_partner,
taxes=self.igst_lut_sale_28_sez,
line_vals={'price_unit': 1000, 'quantity': 1},
post=False,
invoice_date=TEST_DATE,
)
sez_invoice.write({
'invoice_line_ids': [Command.create({
'product_id': self.product_b.id,
'account_id': sez_invoice.invoice_line_ids[0].account_id.id,
'price_unit': 500,
'quantity': 1,
'tax_ids': [(6, 0, [self.nil_rated_tax.id])],
})],
})
sez_invoice.action_post()
assert_line_sections(sez_invoice.line_ids, {
'with_gst_tag': 'sale_sez_wop',
'nil_rated': 'sale_nil_rated',
'no_tag': 'sale_out_of_scope',
})
sez_credit_note = self._create_credit_note(inv=sez_invoice)
assert_line_sections(sez_credit_note.line_ids, {
'with_gst_tag': 'sale_cdnr_sez_wop',
'nil_rated': 'sale_nil_rated',
'no_tag': 'sale_out_of_scope',
})
# Export with payment
exp_invoice = self._init_inv(
partner=self.partner_foreign,
taxes=self.igst_exp_sale_18,
line_vals={'price_unit': 3000, 'quantity': 1},
invoice_date=TEST_DATE,
)
assert_line_sections(exp_invoice.line_ids, {
'with_gst_tag': 'sale_exp_wp',
'no_tag': 'sale_out_of_scope',
})
exp_credit_note = self._create_credit_note(inv=exp_invoice)
assert_line_sections(exp_credit_note.line_ids, {
'with_gst_tag': 'sale_cdnur_exp_wp',
'no_tag': 'sale_out_of_scope',
})
# B2B RCM
b2b_rcm_invoice = self._init_inv(
partner=self.partner_b,
taxes=self.igst_rc_sale_18,
line_vals={'price_unit': 1000, 'quantity': 1},
invoice_date=TEST_DATE,
)
assert_line_sections(b2b_rcm_invoice.line_ids, {
'with_gst_tag': 'sale_b2b_rcm',
'no_tag': 'sale_out_of_scope',
})
b2b_rcm_credit_note = self._create_credit_note(inv=b2b_rcm_invoice)
assert_line_sections(b2b_rcm_credit_note.line_ids, {
'with_gst_tag': 'sale_cdnr_rcm',
'no_tag': 'sale_out_of_scope',
})
# B2CL
b2cl_invoice = self._init_inv(
partner=self.large_unregistered_partner,
taxes=self.igst_sale_18,
line_vals={'price_unit': 220000, 'quantity': 1},
invoice_date=TEST_DATE,
)
assert_line_sections(b2cl_invoice.line_ids, {
'with_gst_tag': 'sale_b2cl',
'no_tag': 'sale_out_of_scope',
})
b2cl_credit_note = self._create_credit_note(inv=b2cl_invoice, line_vals={'quantity': 0.5})
assert_line_sections(b2cl_credit_note.line_ids, {
'with_gst_tag': 'sale_cdnur_b2cl',
'no_tag': 'sale_out_of_scope',
})

View file

@ -0,0 +1,770 @@
from odoo import Command
from odoo.addons.account.tests.common import TestTaxCommon
from odoo.tests import tagged
@tagged('post_install', '-at_install', 'post_install_l10n')
class TestL10nInHSNSummary(TestTaxCommon):
@classmethod
@TestTaxCommon.setup_country('in')
def setUpClass(cls):
super().setUpClass()
cls.company_data['company'].write({
'l10n_in_is_gst_registered': True,
})
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,
})
ChartTemplate = cls.env['account.chart.template']
cls.gst_5 = ChartTemplate.ref('sgst_sale_5')
cls.gst_18 = ChartTemplate.ref('sgst_sale_18')
cls.igst_5 = ChartTemplate.ref('igst_sale_5')
cls.igst_18 = ChartTemplate.ref('igst_sale_18')
cls.cess_5_plus_1591 = ChartTemplate.ref('cess_5_plus_1591_sale')
cls.nil_rated = ChartTemplate.ref('nil_rated_sale')
cls.exempt_0 = ChartTemplate.ref('exempt_sale')
cls.igst_18_rc = ChartTemplate.ref('igst_sale_18_rc')
def _jsonify_tax(self, tax):
# EXTENDS 'account.
values = super()._jsonify_tax(tax)
values['l10n_in_gst_tax_type'] = tax.l10n_in_gst_tax_type
return values
def _jsonify_document_line(self, document, index, line):
# EXTENDS 'account.
values = super()._jsonify_document_line(document, index, line)
values['l10n_in_hsn_code'] = line['l10n_in_hsn_code']
return values
def convert_base_line_to_invoice_line(self, document, base_line):
# EXTENDS 'account.
values = super().convert_base_line_to_invoice_line(document, base_line)
values['l10n_in_hsn_code'] = base_line['l10n_in_hsn_code']
return values
# -------------------------------------------------------------------------
# l10n_in_hsn_summary
# -------------------------------------------------------------------------
def _assert_sub_test_l10n_in_hsn_summary(self, results, expected_values):
self.assertEqual(
{k: len(v) if k == 'items' else v for k, v in results['hsn'].items()},
{k: len(v) if k == 'items' else v for k, v in expected_values.items()},
)
self.assertEqual(len(results['hsn']['items']), len(expected_values['items']))
for item, expected_item in zip(results['hsn']['items'], expected_values['items']):
self.assertDictEqual(item, expected_item)
def _create_py_sub_test_l10n_in_hsn_summary(self, document, display_uom):
return {
'hsn': self.env['account.tax']._l10n_in_get_hsn_summary_table(document['lines'], display_uom),
}
def _create_js_sub_test_l10n_in_hsn_summary(self, document, display_uom):
return {
'test': 'l10n_in_hsn_summary',
'document': self._jsonify_document(document),
'display_uom': display_uom,
}
def assert_l10n_in_hsn_summary(
self,
document,
expected_values,
display_uom=False,
):
self._create_assert_test(
expected_values,
self._create_py_sub_test_l10n_in_hsn_summary,
self._create_js_sub_test_l10n_in_hsn_summary,
self._assert_sub_test_l10n_in_hsn_summary,
document,
display_uom,
)
# -------------------------------------------------------------------------
# invoice l10n_in_hsn_summary
# -------------------------------------------------------------------------
def assert_invoice_l10n_in_hsn_summary(self, invoice, expected_values):
results = {'hsn': {
**invoice._l10n_in_get_hsn_summary_table(),
# 'display_uom' is just checking if the user has the uom group. It's irrelevant to test it.
'display_uom': expected_values['display_uom'],
}}
self._assert_sub_test_l10n_in_hsn_summary(results, expected_values)
# -------------------------------------------------------------------------
# Tests
# -------------------------------------------------------------------------
def _test_l10n_in_hsn_summary_1(self):
""" Test GST/IGST taxes. """
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': False,
'has_gst': True,
'has_cess': False,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'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_name': self.uom_unit.name,
'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,
},
],
}
yield 1, document, expected_values
# Another UOM on the second line.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 12000.0, 'product_uom_id': self.uom_dozen, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': False,
'has_gst': True,
'has_cess': False,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 7.0,
'uom_name': self.uom_unit.name,
'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_name': self.uom_dozen.name,
'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_name': self.uom_unit.name,
'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,
}
]
}
yield 2, document, expected_values
# Change GST 5% taxes to IGST.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 12000.0, 'product_uom_id': self.uom_dozen, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': True,
'has_gst': True,
'has_cess': False,
'nb_columns': 8,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 7.0,
'uom_name': self.uom_unit.name,
'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_name': self.uom_dozen.name,
'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_name': self.uom_unit.name,
'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,
},
],
}
yield 3, document, expected_values
# Put back the UOM of the second line to unit.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': True,
'has_gst': True,
'has_cess': False,
'nb_columns': 8,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'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_name': self.uom_unit.name,
'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,
},
],
}
yield 4, document, expected_values
# Change GST 18% taxes to IGST.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_5},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 600.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 5.0, 'price_unit': 300.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': False,
'nb_columns': 6,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 8.0,
'uom_name': self.uom_unit.name,
'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_name': self.uom_unit.name,
'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,
},
],
}
yield 5, document, expected_values
def test_l10n_in_hsn_summary_1_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_1():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_1_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_1():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_2(self):
""" Test CESS taxes in combination with GST/IGST. """
# Need the tax to be evaluated at the end.
self.cess_5_plus_1591.sequence = 100
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 15.80, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18 + self.cess_5_plus_1591},
]))
expected_values = {
'has_igst': False,
'has_gst': True,
'has_cess': True,
'nb_columns': 8,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'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,
},
],
}
yield 1, document, expected_values
# Change GST 18% taxes to IGST.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 15.80, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18 + self.cess_5_plus_1591},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': True,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'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,
},
],
}
yield 2, document, expected_values
def test_l10n_in_hsn_summary_2_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_2():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_2_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_2():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_3(self):
""" Test with mixed HSN codes. """
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 50.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_2, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
{'l10n_in_hsn_code': self.test_hsn_code_2, 'quantity': 2.0, 'price_unit': 50.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.gst_18},
]))
expected_values = {
'has_igst': False,
'has_gst': True,
'has_cess': False,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 3.0,
'uom_name': self.uom_unit.name,
'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_name': self.uom_unit.name,
'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,
},
],
}
yield 1, document, expected_values
# Change GST 18% taxes to IGST.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 2.0, 'price_unit': 50.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_2, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_2, 'quantity': 2.0, 'price_unit': 50.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': False,
'nb_columns': 6,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 3.0,
'uom_name': self.uom_unit.name,
'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_name': self.uom_unit.name,
'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,
},
],
}
yield 2, document, expected_values
def test_l10n_in_hsn_summary_3_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_3():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_3_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_3():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_4(self):
""" Zero rated GST or no taxes at all."""
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit},
]))
expected_values = {
'has_igst': False,
'has_gst': False,
'has_cess': False,
'nb_columns': 5,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 2.0,
'uom_name': self.uom_unit.name,
'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,
},
],
}
yield 1, document, expected_values
# No tax to Nil Rated/exempt.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.nil_rated},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.exempt_0},
]))
expected_values = {
'has_igst': False,
'has_gst': False,
'has_cess': False,
'nb_columns': 5,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 2.0,
'uom_name': self.uom_unit.name,
'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,
},
],
}
yield 2, document, expected_values
# Put one IGST 18% to get a value on the IGST column.
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18},
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 350.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.exempt_0},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': False,
'nb_columns': 6,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'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_name': self.uom_unit.name,
'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,
},
],
}
yield 3, document, expected_values
def test_l10n_in_hsn_summary_4_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_4():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_4_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_4():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_5(self):
""" Test with discount. """
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 100.0, 'discount': 10.0, 'product_uom_id': self.uom_unit},
]))
expected_values = {
'has_igst': False,
'has_gst': False,
'has_cess': False,
'nb_columns': 5,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 0.0,
'amount_untaxed': 90.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 1, document, expected_values
def test_l10n_in_hsn_summary_5_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_5():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_5_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_5():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def _test_l10n_in_hsn_summary_6(self):
""" Test with Sale RC tax. """
document = self.populate_document(self.init_document([
{'l10n_in_hsn_code': self.test_hsn_code_1, 'quantity': 1.0, 'price_unit': 100.0, 'product_uom_id': self.uom_unit, 'tax_ids': self.igst_18_rc},
]))
expected_values = {
'has_igst': True,
'has_gst': False,
'has_cess': False,
'nb_columns': 6,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 18.0,
'amount_untaxed': 100.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 0.0,
'tax_amount_sgst': 0.0,
'tax_amount_cess': 0.0,
},
],
}
yield 1, document, expected_values
def test_l10n_in_hsn_summary_6_generic_helpers(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_6():
with self.subTest(test_index=test_index):
self.assert_l10n_in_hsn_summary(document, expected_values)
self._run_js_tests()
def test_l10n_in_hsn_summary_6_invoices(self):
for test_index, document, expected_values in self._test_l10n_in_hsn_summary_6():
with self.subTest(test_index=test_index):
invoice = self.convert_document_to_invoice(document)
self.assert_invoice_l10n_in_hsn_summary(invoice, expected_values)
def test_l10n_in_hsn_summary_manual_edit_invoice_taxes(self):
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': '2017-01-01',
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'product_id': self.product_a.id,
'l10n_in_hsn_code': self.test_hsn_code_1,
'price_unit': 1000.0,
'tax_ids': [Command.set(self.gst_5.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'l10n_in_hsn_code': self.test_hsn_code_2,
'price_unit': 1000.0,
'tax_ids': [Command.set(self.gst_5.ids)],
}),
],
})
# Manual edition of the tax.
sgst_tax = self.gst_5.children_tax_ids.filtered(lambda tax: tax.l10n_in_gst_tax_type == 'sgst')
cgst_tax = self.gst_5.children_tax_ids.filtered(lambda tax: tax.l10n_in_gst_tax_type == 'cgst')
tax_line_sgst = invoice.line_ids.filtered(lambda aml: aml.tax_line_id == sgst_tax)
tax_line_cgst = invoice.line_ids.filtered(lambda aml: aml.tax_line_id == cgst_tax)
payment_term = invoice.line_ids.filtered(lambda aml: aml.display_type == 'payment_term')
invoice.line_ids = [
Command.update(tax_line_sgst.id, {'amount_currency': tax_line_sgst.amount_currency + 1.0}),
Command.update(tax_line_cgst.id, {'amount_currency': tax_line_cgst.amount_currency + 1.0}),
Command.update(payment_term.id, {'amount_currency': payment_term.amount_currency - 2.0}),
]
self.assert_invoice_l10n_in_hsn_summary(invoice, {
'has_igst': False,
'has_gst': True,
'has_cess': False,
'nb_columns': 7,
'display_uom': False,
'items': [
{
'l10n_in_hsn_code': self.test_hsn_code_1,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 1000.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 24.5,
'tax_amount_sgst': 24.5,
'tax_amount_cess': 0.0,
},
{
'l10n_in_hsn_code': self.test_hsn_code_2,
'quantity': 1.0,
'uom_name': self.uom_unit.name,
'rate': 5.0,
'amount_untaxed': 1000.0,
'tax_amount_igst': 0.0,
'tax_amount_cgst': 24.5,
'tax_amount_sgst': 24.5,
'tax_amount_cess': 0.0,
},
],
})

View file

@ -0,0 +1,72 @@
from odoo import Command
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
from odoo.tests import tagged
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestInvoiceLabel(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_b.l10n_in_gst_treatment = "regular"
cls.unregistered_partner = cls.partner_a.copy({"state_id": cls.state_in_mh.id, "vat": None, "l10n_in_gst_treatment": "unregistered"})
def test_invoice_label(self):
# Regular with taxable items
regular_taxable_invoice = self._init_inv(
partner=self.partner_b,
taxes=self.igst_sale_18,
line_vals={'price_unit': 1000, 'quantity': 1},
)
invoice_label = regular_taxable_invoice._get_l10n_in_invoice_label()
self.assertEqual(invoice_label, 'Tax Invoice')
# Regular with exempt items
regular_exempt_invoice = self._init_inv(
partner=self.partner_b,
taxes=self.exempt,
line_vals={'price_unit': 1000, 'quantity': 1},
)
invoice_label = regular_exempt_invoice._get_l10n_in_invoice_label()
self.assertEqual(invoice_label, 'Bill of Supply')
# Regular with taxable and exempt items
regular_mix_invoice = self._init_inv(
partner=self.partner_b,
taxes=self.igst_sale_18,
line_vals={'price_unit': 1000, 'quantity': 1},
post=False,
)
regular_mix_invoice.write({
'invoice_line_ids': [Command.create({
'product_id': self.product_b.id,
'account_id': regular_mix_invoice.invoice_line_ids[0].account_id.id,
'price_unit': 500,
'quantity': 1,
'tax_ids': [(6, 0, [self.exempt.id])],
})],
})
regular_mix_invoice.action_post()
invoice_label = regular_mix_invoice._get_l10n_in_invoice_label()
self.assertEqual(invoice_label, 'Invoice')
# unregistered with taxable and exempt items
unregistered_invoice = self._init_inv(
partner=self.unregistered_partner,
taxes=self.igst_sale_18,
line_vals={'price_unit': 220000, 'quantity': 1},
post=False,
)
unregistered_invoice.write({
'invoice_line_ids': [Command.create({
'product_id': self.product_b.id,
'account_id': unregistered_invoice.invoice_line_ids[0].account_id.id,
'price_unit': 500,
'quantity': 1,
'tax_ids': [(6, 0, [self.exempt.id])],
})],
})
unregistered_invoice.action_post()
invoice_label = unregistered_invoice._get_l10n_in_invoice_label()
self.assertEqual(invoice_label, 'Invoice-cum-Bill of Supply')

View file

@ -0,0 +1,242 @@
from contextlib import contextmanager
from unittest.mock import patch
from odoo.tests import tagged
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
@tagged('post_install', '-at_install', 'post_install_l10n')
class TestFiscal(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Remove fiscal position 'fiscal_pos_a' so it does not show up on invoice
cls.partner_b.property_account_position_id = None
@contextmanager
def _set_env_company(self, companies):
cls = self.__class__
env = cls.env(context=dict(cls.env.context, allowed_company_ids=companies.ids))
with patch.object(cls, "env", env):
yield
def _assert_in_intra_state_fiscal_with_company(self, companies):
for company in companies:
state = company.state_id
name = state and 'Within %s' % state.name or 'Intra State'
self.assertRecordValues(
self.env['account.chart.template'].with_company(company).ref('fiscal_position_in_intra_state'),
[{
'name': name,
'state_ids': state.ids,
'company_id': company.id, # To make sure for branches don't get parent fiscal position
'auto_apply': True
}]
)
def _assert_invoice_fiscal_position(self, fiscal_position_ref, partner, taxes=None, move_type='out_invoice', post=True):
test_invoice = self.init_invoice(
move_type=move_type,
partner=partner,
post=post,
amounts=[110, 500],
taxes=taxes
)
self.assertEqual(test_invoice.fiscal_position_id, self.env['account.chart.template'].ref(fiscal_position_ref))
return test_invoice
def test_l10n_in_setting_up_company(self):
company = self._create_company(name='Fiscal Setup Test Company')
# Test with no state but country india
self._assert_in_intra_state_fiscal_with_company(company)
# Change state
company.write({'state_id': self.default_company.state_id.id})
self._assert_in_intra_state_fiscal_with_company(company)
# Change State Again
company.write({'state_id': self.env.ref('base.state_in_ap')})
self._assert_in_intra_state_fiscal_with_company(company)
def test_l10n_in_auto_apply_fiscal_invoices(self):
self._assert_in_intra_state_fiscal_with_company(self.default_company)
# Intra State
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_intra_state',
partner=self.partner_a,
taxes=self.sgst_sale_18,
)
# Inter State
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_inter_state',
partner=self.partner_b,
taxes=self.igst_sale_18,
)
# Outside India
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_export_sez_in',
partner=self.partner_foreign,
taxes=self.igst_sale_18,
)
def test_l10n_in_fiscal_for_branch(self):
branch_1 = self._create_company(
name='Branch 1',
parent_id=self.default_company.id,
state_id=self.partner_b.state_id.id, # Setting Partner B state will be now Intra State for branch 1
account_fiscal_country_id=self.country_in.id,
)
with self._set_env_company(self.outside_in_company):
branch_2 = self.env['res.company'].create({
'name': 'Branch 2',
'parent_id': self.default_company.id,
'account_fiscal_country_id': self.country_in.id,
'country_id': self.country_in.id,
})
# Check Branch with country india and no state
self._assert_in_intra_state_fiscal_with_company(branch_2)
# Set state after creating branch
branch_2.write({'state_id': self.env.ref('base.state_in_mp').id})
self._assert_in_intra_state_fiscal_with_company(branch_1 + branch_2)
# Invoice fiscal test with branch
with self._set_env_company(branch_1):
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_intra_state',
partner=self.partner_b,
taxes=self.sgst_sale_18,
)
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_inter_state',
partner=self.partner_a,
taxes=self.igst_sale_18,
)
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_export_sez_in',
partner=self.partner_foreign,
taxes=self.igst_sale_18,
)
def test_l10n_in_fiscal_in_bill_to_ship_to(self):
with self._set_env_company(self.default_company):
# Inter State
out_invoice = self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_inter_state',
partner=self.partner_b,
taxes=self.igst_sale_18,
post=False,
)
# Intra State
out_invoice.write({
'l10n_in_state_id': self.env.ref('base.state_in_gj').id,
})
self.assertEqual(
out_invoice.fiscal_position_id,
self.env['account.chart.template'].ref('fiscal_position_in_intra_state')
)
# Outside India (Export/SEZ)
out_invoice.write({
'l10n_in_state_id': self.env.ref('l10n_in.state_in_oc').id, # Other Country State
})
self.assertEqual(
out_invoice.fiscal_position_id,
self.env['account.chart.template'].ref('fiscal_position_in_export_sez_in')
)
def test_l10n_in_fiscal_in_vendor_bills(self):
'''
In Purchase Document: Compare place of supply with vendor state
'''
with self._set_env_company(self.default_company):
template = self.env['account.chart.template']
company_state = self.env.company.state_id
other_state = self.env['res.country.state'].search([
('id', '!=', company_state.id),
('country_id', '=', company_state.country_id.id)
], limit=1)
# Sub-test: Intra-State
with self.subTest(scenario="Intra-State"):
self.partner_a.write({'state_id': company_state.id})
vendor_bill = self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_intra_state',
partner=self.partner_a,
move_type='in_invoice',
post=False,
)
self.partner_a.write({'state_id': other_state.id})
vendor_bill.write({'l10n_in_state_id': other_state.id})
self.assertEqual(
vendor_bill.fiscal_position_id,
template.ref('fiscal_position_in_intra_state')
)
# Sub-test: Inter-State
with self.subTest(scenario="Inter-State"):
self.partner_a.write({'state_id': other_state.id})
vendor_bill = self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_inter_state',
partner=self.partner_a,
move_type='in_invoice',
post=False,
)
self.partner_a.write({'state_id': company_state.id})
vendor_bill.write({'l10n_in_state_id': other_state.id})
self.assertEqual(
vendor_bill.fiscal_position_id,
template.ref('fiscal_position_in_inter_state')
)
# Sub-test: Export/SEZ (Outside India)
with self.subTest(scenario="Export/SEZ"):
vendor_bill = self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_export_sez_in',
partner=self.partner_foreign,
move_type='in_invoice',
post=False,
)
vendor_bill.write({'l10n_in_state_id': self.env.ref('l10n_in.state_in_oc').id}) # Other Country State
self.assertEqual(
vendor_bill.fiscal_position_id,
template.ref('fiscal_position_in_export_sez_in')
)
# Here fpos should Inter-State. But due to `l10n_in_gst_treatment` it will be Export/SEZ
self.partner_a.write({'state_id': other_state.id})
vendor_bill.write({
'partner_id': self.partner_a.id, # Inter-State Partner
'l10n_in_state_id': company_state.id, # Company State
'l10n_in_gst_treatment': 'special_economic_zone',
})
self.assertEqual(
vendor_bill.fiscal_position_id,
template.ref('fiscal_position_in_sez')
)
# Sub-test: Manual Partner Fiscal Check
with self.subTest(scenario="Manual Partner Fiscal Check"):
# Here fpos should be Intra-State. But due to `property_account_position_id` it will be Export/SEZ
self.partner_a.write({
'state_id': company_state.id, # Intra-State Partner
'property_account_position_id': template.ref('fiscal_position_in_export_sez_in').id
})
self._assert_invoice_fiscal_position(
fiscal_position_ref='fiscal_position_in_export_sez_in',
partner=self.partner_a,
move_type='in_invoice',
)
def test_l10n_in_company_with_no_vat(self):
"""
Test the company with no VAT and update the partner and company states as per the GSTIN number
"""
company = self.default_company
company.write({'vat': False})
self.assertFalse(company.vat)
company.action_update_state_as_per_gstin()
self.assertEqual(company.partner_id.state_id, self.env.ref('base.state_in_gj'))
company.write({'vat': '36AABCT1332L011'})
company.action_update_state_as_per_gstin()
self.assertEqual(company.state_id, self.env.ref('base.state_in_ts'))

View file

@ -0,0 +1,135 @@
from odoo.tests import tagged
import logging
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
_logger = logging.getLogger(__name__)
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestReports(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_b.l10n_in_gst_treatment = 'regular'
cls.partner_a.l10n_in_gst_treatment = 'composition'
cls.partner_foreign.l10n_in_gst_treatment = 'overseas'
cls.partner_foreign_no_state.l10n_in_gst_treatment = 'overseas'
cls.igst_sale_18 = cls.env['account.chart.template'].ref('igst_sale_18')
def test_partner_details_change_with_invoice(self):
invoice_b_2 = self.init_invoice(
move_type='out_invoice',
partner=self.partner_b,
amounts=[250, 600],
taxes=[self.igst_sale_18],
post=True
)
# Place of Supply (pos) is same as of state of partner (for journal type sale)
expected_pos_id = self.state_in_mh.id
self.assertRecordValues(
self.invoice_b,
[{
'state': 'draft',
'l10n_in_gst_treatment': self.partner_b.l10n_in_gst_treatment,
'l10n_in_state_id': expected_pos_id,
}]
)
self.assertRecordValues(
invoice_b_2,
[{
'state': 'posted',
'l10n_in_gst_treatment': self.partner_b.l10n_in_gst_treatment,
'l10n_in_state_id': expected_pos_id,
}]
)
self.assertRecordValues(
invoice_b_2,
[{ # check gst treatment and pos doesn't change on posted invoice
'state': 'posted',
'l10n_in_gst_treatment': 'regular',
}]
)
def test_partner_change_with_invoice(self):
in_invoice = self.init_invoice(
move_type='in_invoice',
partner=self.partner_b,
amounts=[452, 58, 110],
taxes=[self.igst_sale_18],
)
self.invoice_a.partner_id = self.partner_foreign
self.assertRecordValues(
self.invoice_a,
[{
'state': 'draft',
'l10n_in_gst_treatment': self.partner_foreign.l10n_in_gst_treatment,
'l10n_in_state_id': self.env.ref("l10n_in.state_in_oc").id,
}]
)
self.assertRecordValues(
in_invoice,
[{
'state': 'draft',
'l10n_in_gst_treatment': self.partner_b.l10n_in_gst_treatment,
'l10n_in_state_id': self.env.company.state_id.id,
}]
)
def test_place_of_supply(self):
child_partner = self.env['res.partner'].create({
'name': "Child Contact",
'type': "delivery",
'parent_id': self.partner_a.id,
'state_id': self.state_in_hp.id
})
self.assertRecordValues(
self.invoice_a,
[{
'partner_shipping_id': self.partner_a.id,
'l10n_in_state_id': self.partner_a.state_id.id,
}]
)
self.invoice_a.partner_shipping_id = child_partner
self.assertRecordValues(
self.invoice_a,
[{
'partner_shipping_id': child_partner.id,
'l10n_in_state_id': child_partner.state_id.id,
}]
)
self.invoice_a.partner_shipping_id = self.partner_b
self.assertRecordValues(
self.invoice_a,
[{
'l10n_in_state_id': self.partner_a.state_id.id,
}]
)
def test_foreign_customer_without_state(self):
""" Verify foreign customer without state_id gets foreign state reference """
self.assertRecordValues(
self.invoice_d,
[{
'l10n_in_gst_treatment': 'overseas',
'l10n_in_state_id': self.env.ref("l10n_in.state_in_oc").id,
}]
)
def test_government_gstin_extraction_tan(self):
""" Verify that a GSTIN based on a TAN (Government entity) correctly populates the TAN field and leaves the PAN field empty. """
gov_partner = self.env['res.partner'].create({
'name': "Gov Partner",
'country_id': self.env.ref('base.in').id,
'state_id': self.env.ref('base.state_in_dl').id,
'vat': '07DELN10357E1DH',
})
self.assertRecordValues(gov_partner, [{
'l10n_in_pan_entity_id': False,
'l10n_in_tan': 'DELN10357E',
}])

View file

@ -0,0 +1,596 @@
from odoo import Command
from odoo.addons.l10n_in.tests.common import L10nInTestInvoicingCommon
from odoo.tests import tagged
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestTdsTcsAlert(L10nInTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
ChartTemplate = cls.env['account.chart.template']
# ==== Chart of Accounts ====
cls.purchase_account = ChartTemplate.ref('p2107')
cls.purchase_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tds_section_194c').id
})
cls.house_expense_account = ChartTemplate.ref('p2103')
cls.house_expense_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tds_section_194c').id
})
cls.internet_account = ChartTemplate.ref('p2105')
cls.internet_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tds_section_194j').id
})
cls.rent_account = ChartTemplate.ref('p2111')
cls.rent_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tds_section_194ib').id
})
cls.sale_account = ChartTemplate.ref('p20011')
cls.sale_account.write({
'l10n_in_tds_tcs_section_id': cls.env.ref('l10n_in.tcs_section_206c1g_r').id
})
cls.service_account = ChartTemplate.ref('p20021')
cls.creditors_account = ChartTemplate.ref('p11211')
# ==== Taxes ====
cls.tax_194c = ChartTemplate.ref('tds_20_us_194c')
cls.tax_194c.write({'l10n_in_section_id': cls.env.ref('l10n_in.tds_section_194c').id})
cls.tax_194j = ChartTemplate.ref('tds_10_us_194j')
cls.tax_194j.write({'l10n_in_section_id': cls.env.ref('l10n_in.tds_section_194j').id})
cls.tax_194ib = ChartTemplate.ref('tds_20_us_194ib')
cls.tax_194ib.write({'l10n_in_section_id': cls.env.ref('l10n_in.tds_section_194ib').id})
cls.tax_206c1g_r = ChartTemplate.ref('tcs_5_us_206c_1g_som')
cls.tax_206c1g_r.write({'l10n_in_section_id': cls.env.ref('l10n_in.tcs_section_206c1g_r').id})
country_in_id = cls.env.ref("base.in").id
cls.partner_b.write({
'vat': '27ABCPM8965E1ZE',
})
cls.partner_foreign_2 = cls.partner_foreign.copy()
# ==== Company ====
cls.env.company.write({
'child_ids': [
Command.create({
'name': 'Branch A',
"state_id": cls.env.ref("base.state_in_gj").id,
'account_fiscal_country_id': country_in_id,
'country_id': country_in_id,
}),
Command.create({
'name': 'Branch B',
"state_id": cls.env.ref("base.state_in_mh").id,
'account_fiscal_country_id': country_in_id,
'country_id': country_in_id,
}),
Command.create({
'name': 'Branch C',
"state_id": cls.env.ref("base.state_in_mp").id,
'account_fiscal_country_id': country_in_id,
'country_id': country_in_id,
}),
],
})
cls.cr.precommit.run() # load the CoA
cls.branch_a, cls.branch_b, cls.branch_c = cls.env.company.child_ids
def create_invoice(self, move_type=None, partner=None, invoice_date=None, amounts=None, taxes=[], company=None, accounts=[], quantities=[]):
invoice = self.init_invoice(
move_type=move_type or 'in_invoice',
partner=partner,
invoice_date=invoice_date,
post=False,
amounts=amounts,
company=company
)
for i, account in enumerate(accounts):
invoice.invoice_line_ids[i].account_id = account
for i, quantity in enumerate(quantities):
invoice.invoice_line_ids[i].quantity = quantity
for i, tax in enumerate(taxes):
invoice.invoice_line_ids[i].tax_ids = tax
invoice.action_post()
return invoice
def tds_wizard_entry(self, move, lines):
journal_id = self.env['account.journal'].search([('company_id', '=', self.env.company.id),('type', '=', 'general')], limit=1)
for tax, amount in lines:
self.env['l10n_in.withhold.wizard'].with_context(active_model='account.move', active_ids=move.ids).create({
'journal_id': journal_id.id,
'tax_id': tax.id,
'base': amount,
'date': move.invoice_date,
}).action_create_and_post_withhold()
def reverse_move(self, move, date):
move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=move.ids).create({
'date': date,
'reason': 'no reason',
'journal_id': move.journal_id.id,
})
return move_reversal.refund_moves()
def test_tcs_tds_warning(self):
'''
Test that if any of the limit is not exceeded.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[29000],
company=self.branch_a,
accounts=[self.internet_account],
quantities=[1]
)
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_on_exceeded_per_transaction_limit(self):
'''
Test that if the per transaction limit is exceeded.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[31000],
company=self.branch_a,
quantities=[1]
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
move_1 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-06-05',
amounts=[31000],
company=self.branch_b,
quantities=[1]
)
self.assertEqual(move_1.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
self.create_invoice(
partner=self.partner_b,
invoice_date='2024-06-05',
amounts=[31000],
company=self.branch_b,
quantities=[1]
)
move_3 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-06-05',
amounts=[31000],
company=self.branch_b,
quantities=[1]
)
self.assertEqual(move_3.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_on_monthly_aggregate_limit(self):
'''
Test the monthly aggregate limit, the warning
message should be set accordingly.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_a,
accounts=[self.rent_account]
)
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-07-06',
amounts=[20000],
company=self.branch_b,
accounts=[self.rent_account]
)
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-07-16',
amounts=[31000],
company=self.branch_c,
accounts=[self.rent_account]
)
self.assertEqual(move_2.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194IB on this transaction.")
move_3 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-09-06',
amounts=[50000],
company=self.branch_c,
accounts=[self.rent_account]
)
self.assertEqual(move_3.l10n_in_warning, False)
move_4 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-09-16',
amounts=[50000],
company=self.branch_c,
accounts=[self.rent_account]
)
self.assertEqual(move_4.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194IB on this transaction.")
def test_tcs_tds_warning_partner_wiht_pan(self):
'''
Test the aggregate limit when partner don't have
pan number and having pan number.
'''
# no pan number
move = self.create_invoice(
partner=self.partner_foreign,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_foreign_2,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_b,
accounts=[self.internet_account]
)
self.assertEqual(move_1.l10n_in_warning, False)
# same pan number
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.assertEqual(move_2.l10n_in_warning, False)
move_3 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-06-05',
amounts=[30000],
company=self.branch_b,
accounts=[self.internet_account]
)
self.assertEqual(move_3.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194J on this transaction.")
def test_tcs_tds_warning_on_exceeded_aggregate_limit(self):
'''
Test that if the aggregate limit is exceeded.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-06-05',
amounts=[20000],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-07-06',
amounts=[20000],
company=self.branch_b,
)
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-08-06',
amounts=[31000],
company=self.branch_c,
)
self.assertEqual(move_2.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
move_3 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-09-06',
amounts=[5000],
company=self.branch_a,
)
self.assertEqual(move_3.l10n_in_warning, False)
move_4 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-10-07',
amounts=[20000],
company=self.branch_b,
)
self.assertEqual(move_4.l10n_in_warning, False)
move_5 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-11-08',
amounts=[25000],
company=self.branch_c,
)
self.assertEqual(move_5.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_on_case_of_credit_note(self):
'''
Test that the aggregate limit in case of debit/credit note.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-09-01',
amounts=[2000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_b,
invoice_date='2024-09-01',
amounts=[3000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.reverse_move(move, '2024-09-01')
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-09-01',
amounts=[2000],
company=self.branch_a,
accounts=[self.internet_account]
)
self.assertEqual(move_2.l10n_in_warning, False)
def test_tcs_tds_warning_cleared_on_available_tax(self):
'''
Test when a tax is added to the move line with a similar tax group
as the account.
'''
move = self.create_invoice(
partner=self.partner_a,
move_type='out_invoice',
invoice_date='2022-12-12',
amounts=[710000],
taxes=[self.tax_206c1g_r],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_for_multiple_accounts_in_lines(self):
'''
Test when there are multiple products in the move line and some of them
have different accounts which have the different tax group as the account.
'''
move = self.create_invoice(
partner=self.partner_a,
move_type='in_invoice',
invoice_date='2022-12-12',
amounts=[100000, 1100000, 710000],
company=self.branch_a,
accounts=[self.rent_account, self.internet_account, self.purchase_account],
quantities=[15, 16, 10]
)
self.assertTrue(move.l10n_in_warning)
move_1 = self.create_invoice(
partner=self.partner_a,
move_type='in_invoice',
invoice_date='2022-12-12',
amounts=[1000000.0, 1100000.0, 710000],
company=self.branch_a,
accounts=[self.rent_account, self.internet_account, self.purchase_account],
)
self.tds_wizard_entry(move=move_1, lines=[(self.tax_194ib, 100000), (self.tax_194j, 100000), (self.tax_194c, 100000)])
move_1.button_draft()
move_1.action_post()
self.assertEqual(move_1.l10n_in_warning, False)
def test_tcs_tds_warning_for_if_line_has_price_zero(self):
'''
Test when any invoice line has Zero
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[101000, 0],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
move_1 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[0],
company=self.branch_a,
)
self.assertEqual(move_1.l10n_in_warning, False)
def test_tcs_tds_warning_for_all_lines_do_not_have_taxes(self):
'''
Test when tds entry created and warning will removed
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[1000, 6000],
company=self.branch_a,
accounts=[],
quantities=[15, 16]
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
self.tds_wizard_entry(move=move, lines=[(self.tax_194c, 100000)])
move.line_ids.remove_move_reconcile()
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_for_company_branches(self):
'''
Test when the aggregate limit is exceeded in case of multiple branches
of the company,the warning message should be set accordingly.
'''
self.create_invoice(
partner=self.partner_a,
invoice_date='2024-05-14',
amounts=[25000],
company=self.branch_a,
)
self.create_invoice(
partner=self.partner_b,
invoice_date='2024-05-14',
amounts=[25000],
company=self.branch_b,
)
self.create_invoice(
partner=self.partner_b,
invoice_date='2024-05-14',
amounts=[25000],
company=self.branch_c,
quantities=[25]
)
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-05-14',
amounts=[28000],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_tcs_use_in_bill(self):
'''
Test when tcs section is used in the bill creation.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2024-05-29',
amounts=[1100000],
company=self.branch_a,
accounts=[self.sale_account]
)
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_tds_use_in_invoice(self):
'''
Test when tcs section is used in the bill creation.
'''
move = self.create_invoice(
move_type='out_invoice',
partner=self.partner_a,
invoice_date='2024-05-29',
amounts=[110000],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning, False)
def test_tcs_tds_warning_for_multiple_accounts_same_section_in_lines(self):
'''
Test when there are multiple products in the move line and some of them
have different accounts which have the same tax group as the account.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[17000, 14000],
company=self.branch_a,
accounts=[self.house_expense_account, self.purchase_account],
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
move_1 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[17000, 13000],
company=self.branch_a,
accounts=[self.house_expense_account, self.purchase_account],
)
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[30000],
company=self.branch_a,
accounts=[self.house_expense_account],
)
self.assertEqual(move_2.l10n_in_warning, False)
move_3 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[10000],
company=self.branch_a,
)
self.assertEqual(move_3.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_for_not_consider_draft_cancel_invoices_for_aggregate(self):
'''
Test to exclude draft and canceled invoices from aggregate
total calculation.
'''
move = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[16000],
company=self.branch_a,
accounts=[self.purchase_account],
)
move.button_cancel()
self.assertEqual(move.l10n_in_warning, False)
move_1 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[25000],
company=self.branch_a,
accounts=[self.purchase_account],
)
self.assertEqual(move_1.l10n_in_warning, False)
move_2 = self.create_invoice(
partner=self.partner_a,
invoice_date='2022-12-12',
amounts=[85000],
company=self.branch_a,
accounts=[self.purchase_account],
)
self.assertEqual(move_2.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to deduct TDS u/s 194C on this transaction.")
def test_tcs_tds_warning_if_some_lines_has_tax(self):
'''
Test when a tax is added to the some of the move line
'''
move = self.create_invoice(
partner=self.partner_a,
move_type='out_invoice',
invoice_date='2022-12-12',
amounts=[710000, 710000],
taxes=[self.tax_206c1g_r],
company=self.branch_a,
)
self.assertEqual(move.l10n_in_warning['tds_tcs_threshold_alert']['message'], "It's advisable to collect TCS u/s 206C(1G) Remittance on this transaction.")