Files
enterprise-addons/l10n_in_reports/tests/test_gstr2b_report.py
T

229 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import tagged
import logging
import json
from dateutil.relativedelta import relativedelta
from odoo.addons.l10n_in_reports.tests.common import L10nInTestAccountReportsCommon
_logger = logging.getLogger(__name__)
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestReports(L10nInTestAccountReportsCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_b.l10n_in_gst_treatment = "regular"
cls.partner_foreign.l10n_in_gst_treatment = "overseas"
cls.fully_matched_bill = cls._init_inv(move_type='in_invoice', ref='INV/001', taxes=cls.comp_igst_18, partner=cls.partner_b)
cls.fully_matched_bill_refund = cls._create_credit_note(inv=cls.fully_matched_bill, ref='CR/001')
cls.bill_with_conflict_date = cls._init_inv(move_type='in_invoice', ref='INV/002', taxes=cls.comp_igst_18, partner=cls.partner_b)
cls.bill_with_conflict_amount = cls._init_inv(move_type='in_invoice', ref='INV/003', taxes=cls.comp_igst_18, partner=cls.partner_b)
cls.bill_with_conflict_date_amount = cls._init_inv(move_type='in_invoice', ref='INV/004', taxes=cls.comp_igst_18, partner=cls.partner_b)
cls.bill_with_conflict_type = cls._init_inv(move_type='in_invoice', ref='INV/005', taxes=cls.comp_igst_18, partner=cls.partner_b)
cls.bill_with_conflict_type_date = cls._create_credit_note(inv=cls.bill_with_conflict_type, ref='CR/002')
cls.bill_with_conflict_type_amount = cls._init_inv(move_type='in_invoice', ref='INV/006', taxes=cls.comp_igst_18, partner=cls.partner_b)
cls.bill_with_conflict_type_date_amount = cls._create_credit_note(ref='CR/003', inv=cls.bill_with_conflict_type)
cls.bill_with_conflict_vat = cls._init_inv(move_type='in_invoice', ref='INV/007', taxes=cls.comp_igst_18, partner=cls.partner_a)
cls.bill_with_conflict_vat_date = cls._init_inv(move_type='in_invoice', ref='INV/008', taxes=cls.comp_igst_18, partner=cls.partner_a)
cls.bill_with_conflict_vat_amount = cls._init_inv(move_type='in_invoice', ref='INV/009', taxes=cls.comp_igst_18, partner=cls.partner_a)
cls.bill_with_conflict_vat_date_amount = cls._init_inv(move_type='in_invoice', ref='INV/010', taxes=cls.comp_igst_18, partner=cls.partner_a)
cls.bill_with_conflict_vat_type = cls._init_inv(move_type='in_invoice', ref='INV/011', taxes=cls.comp_igst_18, partner=cls.partner_a)
cls.bill_with_conflict_vat_type_date = cls._create_credit_note(ref='CR/004', inv=cls.bill_with_conflict_type)
cls.bill_with_conflict_vat_type_amount = cls._init_inv(move_type='in_invoice', ref='INV/012', taxes=cls.comp_igst_18, partner=cls.partner_a)
cls.bill_with_conflict_vat_type_date_amount = cls._create_credit_note(ref='CR/005', inv=cls.bill_with_conflict_type)
cls.bill_with_conflict_ref = cls._init_inv(move_type='in_invoice', partner=cls.partner_b, taxes=cls.comp_igst_18, line_vals={'price_unit': 2000})
cls.bill_not_in_gstr2b = cls.bill_with_conflict_ref = cls._init_inv(move_type='in_invoice', ref='INV/404', taxes=cls.comp_igst_18, partner=cls.partner_b)
cls.overseas_bill = cls.bill_with_conflict_ref = cls._init_inv(move_type='in_invoice', ref='BOE/123', taxes=cls.comp_igst_18, partner=cls.partner_foreign, line_vals={'price_unit': 100000})
cls.bill_with_conflict_pos = cls._init_inv(move_type='in_invoice', ref='INV/014', taxes=cls.comp_igst_18, partner=cls.partner_b)
cls.bill_matched_with_irn = cls._init_inv(move_type='in_invoice', ref='INV/015', taxes=cls.comp_igst_18, partner=cls.partner_b, irn="897adg56rty78956hyug90bnhhijk453gftd99845672fdhhhshgfh4567fg56kk")
cls.bill_with_no_tax = cls._init_inv(
"in_invoice",
products=cls.product_a,
partner=cls.partner_b,
ref='BILL/NO_TAX',
post=False,
)
cls.bill_with_no_tax.line_ids.tax_ids = False
cls.bill_with_no_tax.action_post()
account_return_type = cls.env.ref('l10n_in_reports.in_gstr2b_return_type')
start_date, end_date = account_return_type._get_period_boundaries(cls.default_company, cls.test_date)
cls.report = cls.gstr_report = cls.env['account.return'].create({
'name': 'IN Tax Return',
'type_id': account_return_type.id,
'company_id': cls.default_company.id,
'date_from': start_date,
'date_to': end_date
})
def test_gstr2b(self):
gstr2b_json = self._read_mock_json('gstr2b_response.json')
self.report.l10n_in_gstr2b_json_ids = self.env['ir.attachment'].create({
'name': 'gstr2b.json',
'mimetype': 'application/json',
'raw': json.dumps(gstr2b_json),
})
self.report.gstr2b_match_data()
self.assertEqual(self.report.l10n_in_gstr2b_status, "partially_matched")
self.assertEqual(self.fully_matched_bill.l10n_in_gstr2b_reconciliation_status, "matched")
self.assertEqual(bool(self.fully_matched_bill.l10n_in_exception), False)
self.assertEqual(self.fully_matched_bill_refund.l10n_in_gstr2b_reconciliation_status, "matched")
self.assertEqual(self.bill_with_conflict_date.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_amount.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_date_amount.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_type.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_type_date.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_type_amount.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_type_date_amount.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_vat.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_vat_date.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_vat_amount.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_vat_date_amount.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_vat_type.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_vat_type_date.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_vat_type_amount.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_with_conflict_vat_type_date_amount.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_not_in_gstr2b.l10n_in_gstr2b_reconciliation_status, "bills_not_in_gstr2")
self.assertEqual(self.overseas_bill.l10n_in_gstr2b_reconciliation_status, "matched")
self.assertEqual(self.overseas_bill.l10n_in_gst_treatment, "overseas")
bill_not_in_odoo = self.env['account.move'].search([('ref', '=', '533515'), ('company_id', '=', self.default_company.id)])
self.assertEqual(len(bill_not_in_odoo), 1)
self.assertEqual(bill_not_in_odoo.l10n_in_gstr2b_reconciliation_status, 'gstr2_bills_not_in_odoo')
self.assertEqual(bill_not_in_odoo.l10n_in_gst_treatment, 'regular')
sez_bill = self.env['account.move'].search([('ref', '=', 'SEZ/123'), ('company_id', '=', self.default_company.id)])
self.assertEqual(len(sez_bill), 1)
self.assertEqual(sez_bill.l10n_in_gstr2b_reconciliation_status, "gstr2_bills_not_in_odoo")
self.assertEqual(bool(sez_bill.l10n_in_exception), False)
self.assertEqual(sez_bill.l10n_in_gst_treatment, "special_economic_zone")
self.assertEqual(self.bill_with_conflict_pos.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(self.bill_matched_with_irn.l10n_in_gstr2b_reconciliation_status, "matched")
self.assertRecordValues(
self.bill_with_no_tax,
[{
'l10n_in_account_return_id': False,
'l10n_in_gstr2b_reconciliation_status': 'pending', # Default value
}]
)
def test_gstr2b_late_reconciliation(self):
"""
Test the GSTR-2B reconciliation process for late received invoices.
This test verifies that when an invoice arrives with a previous month's date
but differs from an existing invoice date, the original invoice remains unchanged.
"""
previous_invoice = self._init_inv(move_type='in_invoice', ref='BILL/001', taxes=self.comp_igst_18, partner=self.partner_b, invoice_date=self.test_date - relativedelta(months=1), line_vals={'price_unit': 100})
gstr2b_late_reconciliation_data = self._read_mock_json('gstr2b_late_reconciliation_data.json')
self.report.l10n_in_gstr2b_json_ids = self.env['ir.attachment'].create({
'name': 'gstr2b.json',
'mimetype': 'application/json',
'raw': json.dumps(gstr2b_late_reconciliation_data),
})
self.report.gstr2b_match_data()
self.assertEqual(previous_invoice.l10n_in_gstr2b_reconciliation_status, "matched")
self.assertFalse(previous_invoice.l10n_in_exception)
self.assertEqual(previous_invoice.l10n_in_account_return_id, self.report)
def test_gstr2b_reconciliation_different_partner(self):
"""
Test the GSTR-2B reconciliation process for invoices with different partners.
This test verifies that when multiple invoices with the same reference
but different partners are processed, they are correctly matched, and
a new bill is created for any additional invoice found in the GSTR-2B data.
The test checks that:
- Three invoices are matched (two existing, one new).
- Both bills with different partners are marked as 'matched'.
- A new bill is created and labeled as 'gstr2_bills_not_in_odoo' for
invoices found in the GSTR-2B data but not present in Odoo.
"""
bill_with_partner_b = self._init_inv(move_type='in_invoice', ref='BILL/001', taxes=self.comp_igst_18, partner=self.partner_b, invoice_date=self.test_date, line_vals={'price_unit': 800})
partner_c = self.partner_b.copy({'name': 'Partner_c', 'vat': '24WXYCM1234E1ZE'})
bill_with_partner_c = self._init_inv(move_type='in_invoice', ref='BILL/001', taxes=self.comp_igst_18, partner=partner_c, invoice_date=self.test_date, line_vals={'price_unit': 800})
gstr2b_reconciliation_different_partner = self._read_mock_json('gstr2b_reconciliation_different_partner.json')
self.report.l10n_in_gstr2b_json_ids = self.env['ir.attachment'].create({
'name': 'gstr2b.json',
'mimetype': 'application/json',
'raw': json.dumps(gstr2b_reconciliation_different_partner),
})
self.report.gstr2b_match_data()
matched_invoices = self.env['account.move'].search([('ref', '=', 'BILL/001'), ('company_id', '=', self.company_data['company'].id)])
self.assertEqual(len(matched_invoices), 3)
self.assertEqual(bill_with_partner_b.l10n_in_gstr2b_reconciliation_status, "matched")
self.assertEqual(bill_with_partner_c.l10n_in_gstr2b_reconciliation_status, "matched")
new_bill = matched_invoices - bill_with_partner_b - bill_with_partner_c
self.assertEqual(new_bill.l10n_in_gstr2b_reconciliation_status, "gstr2_bills_not_in_odoo")
def test_bill_status_and_exception_reset_on_draft(self):
gstr2b_draft_bill_response = self._read_mock_json('gstr2b_draft_bill_response.json')
self.report.l10n_in_gstr2b_json_ids = self.env['ir.attachment'].create({
'name': 'gstr2b.json',
'mimetype': 'application/json',
'raw': json.dumps(gstr2b_draft_bill_response),
})
self.report.gstr2b_match_data()
self.assertEqual(self.fully_matched_bill.l10n_in_gstr2b_reconciliation_status, "partially_matched")
self.assertEqual(bool(self.fully_matched_bill.l10n_in_exception), True)
self.fully_matched_bill.button_draft()
self.assertEqual(self.fully_matched_bill.l10n_in_gstr2b_reconciliation_status, "pending")
self.assertEqual(self.fully_matched_bill.l10n_in_account_return_id.id, False)
self.assertEqual(self.fully_matched_bill.l10n_in_exception, False)
def test_gstr2b_foreign_currency_bill(self):
""" Test GSTR-2B matching for a bill in foreign currency """
usd_currency = self.env.ref('base.USD')
self.env['res.currency.rate'].create({
'name': "2023-05-20",
'rate': 0.0125,
'currency_id': usd_currency.id,
'company_id': self.company_data['company'].id,
})
foreign_bill = self._init_inv(move_type='in_invoice', ref='FC-INV/001', taxes=self.comp_igst_18, partner=self.partner_b, invoice_date=self.test_date, line_vals={'price_unit': 100})
foreign_bill.button_draft()
foreign_bill.currency_id = usd_currency.id
foreign_bill.action_post()
gstr2b_foreign_currency = self._read_mock_json('gstr2b_foreign_currency.json')
self.report.l10n_in_gstr2b_json_ids = self.env['ir.attachment'].create({
'name': 'gstr2b_foreign.json',
'mimetype': 'application/json',
'raw': json.dumps(gstr2b_foreign_currency),
})
self.report.gstr2b_match_data()
self.assertEqual(foreign_bill.l10n_in_gstr2b_reconciliation_status, "matched")