243 lines
13 KiB
Python
243 lines
13 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
from odoo import fields, Command
|
|
from odoo.tests import tagged
|
|
import logging
|
|
from datetime import date
|
|
|
|
from odoo.addons.l10n_in_reports.tests.common import L10nInTestAccountReportsCommon
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
TEST_DATE = date(2023, 5, 20)
|
|
HSN_CHANGE_TEST_DATE = date(2025, 5, 11)
|
|
|
|
|
|
@tagged('post_install_l10n', 'post_install', '-at_install')
|
|
class TestReports(L10nInTestAccountReportsCommon):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
cls.init_invoice(
|
|
move_type='out_invoice',
|
|
products=cls.product_a,
|
|
post=True,
|
|
)
|
|
|
|
cls.partner_b.l10n_in_gst_treatment = "regular"
|
|
|
|
cls.consumer_partner = cls.partner_a.copy({
|
|
'vat': None,
|
|
'l10n_in_gst_treatment': "consumer",
|
|
})
|
|
|
|
cls.deemed_export_partner = cls.partner_a.copy({"l10n_in_gst_treatment": "deemed_export"})
|
|
cls.composition_partner = cls.partner_a.copy({"l10n_in_gst_treatment": "composition"})
|
|
cls.uin_holders_partner = cls.partner_a.copy({"l10n_in_gst_treatment": "uin_holders"})
|
|
cls.large_unregistered_partner = cls.consumer_partner.copy({"state_id": cls.state_in_mh.id, "l10n_in_gst_treatment": "unregistered"})
|
|
|
|
ChartTemplate = cls.env['account.chart.template']
|
|
cls.partner_foreign.l10n_in_gst_treatment = "overseas"
|
|
cls.comp_sgst_18 = ChartTemplate.ref('sgst_sale_18')
|
|
cls.comp_export_wp = ChartTemplate.ref('igst_sale_18_sez_exp')
|
|
cls.comp_export_wop = ChartTemplate.ref('igst_sale_18_sez_lut')
|
|
cls.exempt_tax = ChartTemplate.ref('exempt_sale')
|
|
cls.nil_rated_tax = ChartTemplate.ref('nil_rated_sale')
|
|
cls.non_gst_supplies = ChartTemplate.ref('non_gst_supplies_sale')
|
|
|
|
@classmethod
|
|
def setup_armageddon_tax(cls, tax_name, company_data):
|
|
# TODO: default_account_tax_sale is not set when default_tax is group of tax
|
|
# so when this method is called it's raise error so by overwrite this and stop call super.
|
|
return cls.env["account.tax"]
|
|
|
|
def _setup_moves(self, reverse_inv_func, invoice_date=TEST_DATE):
|
|
b2b_invoice = self._init_inv(partner=self.partner_b, taxes=self.comp_igst_18, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2b_invoice, line_vals={'quantity': 1})
|
|
|
|
b2b_intrastate_invoice = self._init_inv(partner=self.partner_a, taxes=self.comp_sgst_18, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2b_intrastate_invoice, line_vals={'quantity': 1})
|
|
|
|
b2c_intrastate_invoice = self._init_inv(partner=self.consumer_partner, taxes=self.comp_sgst_18, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2c_intrastate_invoice, line_vals={'quantity': 1})
|
|
|
|
b2cl_invoice = self._init_inv(partner=self.large_unregistered_partner, taxes=self.comp_igst_18, line_vals={'price_unit': 250000, 'quantity': 1}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2cl_invoice, line_vals={'quantity': 0.5})
|
|
|
|
export_invoice = self._init_inv(partner=self.partner_foreign, taxes=self.comp_igst_18, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=export_invoice, line_vals={'quantity': 1})
|
|
|
|
b2b_invoice_nilratedtax = self._init_inv(partner=self.partner_b, taxes=self.nil_rated_tax, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2b_invoice_nilratedtax, line_vals={'quantity': 1})
|
|
|
|
b2b_invoice_exemptedtax = self._init_inv(partner=self.partner_b, taxes=self.exempt_tax, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2b_invoice_exemptedtax, line_vals={'quantity': 1})
|
|
|
|
b2b_invoice_nongsttax = self._init_inv(partner=self.partner_b, taxes=self.non_gst_supplies, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2b_invoice_nongsttax, line_vals={'quantity': 1})
|
|
|
|
b2b_invoice_deemed_export = self._init_inv(partner=self.deemed_export_partner, taxes=self.comp_igst_18, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2b_invoice_deemed_export, line_vals={'quantity': 1}) # Creates and posts credit note for the above invoice
|
|
|
|
b2b_invoice_composition = self._init_inv(partner=self.composition_partner, taxes=self.comp_igst_18, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2b_invoice_composition, line_vals={'quantity': 1})
|
|
|
|
b2b_invoice_uin_holders = self._init_inv(partner=self.uin_holders_partner, taxes=self.comp_igst_18, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=b2b_invoice_uin_holders, line_vals={'quantity': 1})
|
|
|
|
# if no tax is applied then it will be out of scope and not considered in GSTR1
|
|
self._init_inv(partner=self.partner_b, taxes=[], line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
|
|
# for b2b invoice with 2 invoice_line_ids having different taxes
|
|
b2b_invoice_gst_and_nil_rated_tax = self._init_inv(partner=self.partner_b, taxes=self.nil_rated_tax, line_vals={'price_unit': 700, 'quantity': 2}, post=False, invoice_date=invoice_date)
|
|
existing_line_vals = b2b_invoice.invoice_line_ids[0].read(['product_id', 'account_id', 'price_unit', 'quantity', 'tax_ids'])[0]
|
|
b2b_invoice_gst_and_nil_rated_tax.write({
|
|
'invoice_line_ids': [
|
|
Command.create({
|
|
'product_id': existing_line_vals['product_id'][0],
|
|
'account_id': existing_line_vals['account_id'][0],
|
|
'price_unit': existing_line_vals['price_unit'],
|
|
'quantity': existing_line_vals['quantity'],
|
|
'tax_ids': [(6, 0, existing_line_vals['tax_ids'])],
|
|
})
|
|
]
|
|
})
|
|
b2b_invoice_gst_and_nil_rated_tax.action_post()
|
|
|
|
# b2b invoice with special economic zone
|
|
b2b_sez_invoice_gst_and_nil_rated_tax = b2b_invoice_gst_and_nil_rated_tax.copy(default={'l10n_in_gst_treatment': 'special_economic_zone', 'invoice_date': invoice_date})
|
|
b2b_sez_invoice_gst_and_nil_rated_tax.action_post()
|
|
# Export invoices with and without payment of IGST
|
|
export_invoice_wp = self._init_inv(partner=self.partner_foreign, taxes=self.comp_export_wp, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=export_invoice_wp, line_vals={'quantity': 1})
|
|
|
|
export_invoice_wop = self._init_inv(partner=self.partner_foreign, taxes=self.comp_export_wop, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=invoice_date)
|
|
reverse_inv_func(inv=export_invoice_wop, line_vals={'quantity': 1})
|
|
|
|
def _create_gstr_report(self, company=None):
|
|
account_return_type = self.env.ref('l10n_in_reports.in_gstr1_return_type')
|
|
return_company = company or self.default_company
|
|
start_date, end_date = account_return_type._get_period_boundaries(return_company, self.test_date)
|
|
gstr_report = self.env['account.return'].create({
|
|
'name': 'IN Tax Return',
|
|
'type_id': account_return_type.id,
|
|
'company_id': return_company.id,
|
|
'date_from': start_date,
|
|
'date_to': end_date
|
|
})
|
|
# Generate Document Summary for the following gstr report.
|
|
gstr_report.action_generate_document_summary()
|
|
return gstr_report
|
|
|
|
def test_gstr1_b2b_summary(self):
|
|
report = self.env.ref('l10n_in_reports.account_report_gstr1')
|
|
options = self._generate_options(report, fields.Date.from_string("2019-01-01"), fields.Date.from_string("2019-12-31"))
|
|
b2b_line = report._get_lines(options)[0]
|
|
columns = {col.get('expression_label'): col.get('no_format') for col in b2b_line.get('columns')}
|
|
# For B2B Invoice - 4A, AB, 4C, 6B, 6C
|
|
expected = {
|
|
'name': '4. Taxable outward supplies made to Registered persons',
|
|
'tax_base': 1000.0,
|
|
'tax_cgst': 25.0,
|
|
'tax_sgst': 25.0,
|
|
'tax_igst': 0.0,
|
|
'tax_cess': 0.0
|
|
}
|
|
self.assertDictEqual(expected, {'name': b2b_line.get('name'), **columns}, "Wrong values for Indian GSTR-1 B2B summary report.")
|
|
|
|
def test_gstr1_json(self):
|
|
self._setup_moves(self._create_credit_note)
|
|
gstr_report = self._create_gstr_report()
|
|
gstr1_expected_json = self._read_mock_json('gstr1_expected_response.json')
|
|
self.assertDictEqual(gstr_report._get_l10n_in_gstr1_json(), gstr1_expected_json)
|
|
|
|
def test_gstr1_debit_note_json(self):
|
|
self._setup_moves(self._create_debit_note)
|
|
gstr_report = self._create_gstr_report()
|
|
gstr1_debit_note_expected_json = self._read_mock_json('gstr1_debit_note_expected_response.json')
|
|
self.assertDictEqual(gstr_report._get_l10n_in_gstr1_json(), gstr1_debit_note_expected_json)
|
|
|
|
def test_gstr1_credit_note_warning_pre_and_post_november(self):
|
|
invoice_1 = self._init_inv(partner=self.partner_a, taxes=self.comp_igst_18, line_vals={'price_unit': 500, 'quantity': 2}, invoice_date=date(2022, 8, 1))
|
|
|
|
# Case 1: Credit note created after 30th November (December 20, 2023)
|
|
# Expected: The warning should be displayed since the credit note is created after the November 30th of invoice financial year.
|
|
reversed_move_1 = self._create_credit_note(inv=invoice_1, line_vals={'quantity': 1}, credit_note_date=date(2023, 12, 20), post=False)
|
|
self.assertTrue(reversed_move_1.l10n_in_reversed_entry_warning)
|
|
|
|
# Case 2: Credit note created before 30th November (August 1, 2023)
|
|
# Expected: The warning should not be displayed since the credit note is created before the November 30th of invoice financial year.
|
|
reversed_move_2 = self._create_credit_note(inv=invoice_1, line_vals={'quantity': 1}, credit_note_date=date(2023, 8, 1), post=False)
|
|
self.assertFalse(reversed_move_2.l10n_in_reversed_entry_warning)
|
|
|
|
def test_gstr1_sez_zero_rated_tax(self):
|
|
igst_0 = self.env['account.chart.template'].ref('igst_sale_0_sez_exp')
|
|
igst_lut_0 = self.env['account.chart.template'].ref('igst_sale_0_sez_exp_lut')
|
|
|
|
b2b_invoice = self._init_inv(
|
|
partner=self.partner_a.copy({'l10n_in_gst_treatment': 'special_economic_zone'}),
|
|
taxes=igst_0,
|
|
line_vals={'price_unit': 500, 'quantity': 2}
|
|
)
|
|
self._init_inv(
|
|
partner=self.partner_foreign,
|
|
taxes=igst_lut_0,
|
|
line_vals={'price_unit': 500, 'quantity': 2}
|
|
)
|
|
self._create_credit_note(inv=b2b_invoice, line_vals={'quantity': 1}) # Creates and posts credit note for the above invoice
|
|
gstr1_report = self._create_gstr_report()
|
|
gstr1_json = gstr1_report._get_l10n_in_gstr1_json()
|
|
self.assertDictEqual(gstr1_json, self._read_mock_json('gstr1_sez_zero_rated_expected_response.json'))
|
|
|
|
def test_hsn_schema_change_gstr1_json(self):
|
|
self._setup_moves(self._create_credit_note, invoice_date=HSN_CHANGE_TEST_DATE)
|
|
account_return_type = self.env.ref('l10n_in_reports.in_gstr1_return_type')
|
|
start_date, end_date = account_return_type._get_period_boundaries(self.company_data["company"], HSN_CHANGE_TEST_DATE)
|
|
gstr1_report = self.env['account.return'].create({
|
|
'name': 'IN Tax Return',
|
|
'type_id': account_return_type.id,
|
|
'company_id': self.company_data["company"].id,
|
|
'date_from': start_date,
|
|
'date_to': end_date
|
|
})
|
|
gstr1_json = gstr1_report._get_l10n_in_gstr1_json()
|
|
self.assertDictEqual(gstr1_json, self._read_mock_json('gstr1_new_hsn_schema_response.json'))
|
|
|
|
def test_taxes_with_sez_exp_lut_and_rcm(self):
|
|
self.invoice_with_sez_lut.invoice_date = self.test_date
|
|
self.invoice_with_sez_lut.action_post()
|
|
self.invoice_with_rcm.invoice_date = self.test_date
|
|
self.invoice_with_rcm.action_post()
|
|
gstr1_report = self._create_gstr_report()
|
|
gstr1_json = gstr1_report._get_l10n_in_gstr1_json()
|
|
self.assertDictEqual(gstr1_json, self._read_mock_json('gstr1_sez_lut_and_rcm_response.json'))
|
|
|
|
def test_gstr1_foreign_currency_invoice_value(self):
|
|
"""Test that invoice Value in GSTR1 B2B is exported in company currency (INR), not invoice currency."""
|
|
foreign_currency = self.other_currency
|
|
self.env['res.currency.rate'].create({
|
|
'currency_id': foreign_currency.id,
|
|
'name': self.test_date,
|
|
'rate': 0.01,
|
|
'company_id': self.company_data['company'].id,
|
|
})
|
|
|
|
inv = self.init_invoice(
|
|
'out_invoice',
|
|
products=self.product_a,
|
|
invoice_date=self.test_date,
|
|
taxes=self.comp_igst_18,
|
|
company=self.company_data['company'],
|
|
partner=self.partner_b,
|
|
currency=foreign_currency,
|
|
post=False,
|
|
)
|
|
inv.write({
|
|
'l10n_in_gst_treatment': 'special_economic_zone',
|
|
})
|
|
inv.action_post()
|
|
|
|
gstr1_report = self._create_gstr_report()
|
|
gstr1_json = gstr1_report._get_l10n_in_gstr1_json()
|
|
self.assertDictEqual(gstr1_json, self._read_mock_json('gstr1_sez_foreign_currency_test.json'))
|