Files

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'))