# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import datetime import json import logging from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.exceptions import UserError from odoo.tests import tagged from odoo import Command, tools _logger = logging.getLogger(__name__) @tagged('post_install_l10n', 'post_install', '-at_install') class AccountTestFecImport(AccountTestInvoicingCommon): """ Main test class for Account FEC import testing """ # ---------------------------------------- # 1:: Test class body # ---------------------------------------- @classmethod @AccountTestInvoicingCommon.setup_country('fr') def setUpClass(cls): super().setUpClass() # Export company cls.company_data_2 = cls.setup_other_company(vat='FR15437982937') cls.company_export = cls.company_data_2['company'] # Export records cls.env['account.move'].with_company(cls.company_export).create([ { 'name': 'INV/001/123456', 'date': datetime.date(2010, 1, 1), 'invoice_date': datetime.date(2010, 1, 1), 'move_type': 'entry', 'partner_id': cls.partner_a.id, 'journal_id': cls.company_data_2['default_journal_sale'].id, 'line_ids': [ Command.create({ 'name': 'line-1', 'account_id': cls.company_data_2['default_account_receivable'].id, 'credit': 0.0, 'debit': 100.30, }), Command.create({ 'name': 'line-2', 'account_id': cls.company_data_2['default_account_tax_sale'].id, 'credit': 100.30, 'debit': 0.0, }), ], }, { 'name': 'INV/001/123457', 'move_type': 'entry', 'date': datetime.date(2010, 1, 1), 'invoice_date': datetime.date(2010, 1, 1), 'partner_id': cls.partner_b.id, 'journal_id': cls.company_data_2['default_journal_purchase'].id, 'line_ids': [ Command.create({ 'name': 'line-3', 'account_id': cls.company_data_2['default_account_payable'].id, 'credit': 65.15, 'debit': 0.0, }), Command.create({ 'name': 'line-4', 'account_id': cls.company_data_2['default_account_expense'].id, 'credit': 0.0, 'debit': 65.15, }), ], }, ]) def _import_fec_file(self, file_name, company=None, **kwargs): company = company or self.env.company wizard = self.env['account.fec.import.wizard'].with_company(company).create({**kwargs}) with tools.file_open(f'l10n_fr_fec_import/static/tests/fec_test_files/{file_name}', mode='rb') as raw_file: decoded_file = raw_file.read().decode() json_content = json.loads(decoded_file) header = json_content['header'] chunks = json_content['chunks'] for chunk in chunks: wizard.process_chunk(header, chunk) return wizard # ---------------------------------------- # 2:: Test methods # ---------------------------------------- def test_fec_import_ignores_empty_partner_refs(self): """ Ensure that FEC import does not assign a partner when no reference is provided. """ self.env['res.partner'].create({'name': 'Partner A', 'ref': '', 'company_id': self.company.id}) self._import_fec_file('fec_lf_tab_utf8.json') move = self.env['account.move'].search([('company_id', '=', self.company.id), ('name', '=', 'ACH000001')]) self.assertFalse(move.mapped('line_ids.partner_id')) self.assertFalse(move.partner_id) def test_import_fec_partners_no_duplicate(self): """ Test that the partners are not imported from the FEC file if already existing But that if two partners have the same name but a different ref, they are correctly created and linked """ partner_1 = self.env['res.partner'].create({ 'name': 'PARTNER 01', 'ref': 'PARTNER01' }) self._import_fec_file('fec_lf_tab_utf8.json') partners_1 = self.env['res.partner'].search_count([('ref', '=', partner_1.ref)]) self.assertEqual(partners_1, 1) partners_2 = self.env['res.partner'].search([('ref', '=', 'PARTNER02')]) self.assertEqual(len(partners_2), 1) domain = [('company_id', '=', self.company.id), ('move_name', '=', 'ACH000007')] move_lines = self.env['account.move.line'].search(domain, order='move_name, id') self.assertEqual(move_lines.partner_id, partners_2) def test_import_fec_big_file(self): """ Test that FEC import works fine with larger files """ last = self.env['account.move'].search([], order='id desc', limit=1) self._import_fec_file('fec_crlf_tab_utf8bom_large.json') new = self.env['account.move'].search([('id', '>', last.id)]) # We delay one move to be sure the FEC matching number is not lost # when performing the first posting/auto matching delayed_move = self.env['account.move.line'].search([('matching_number', '=', 'IAA')], limit=1).move_id (new - delayed_move).action_post() delayed_move.action_post() # Verify move_lines presence move_names = ('ACH000001', 'ACH000002', 'ACH000003') domain = [('company_id', '=', self.company.id), ('move_name', 'in', move_names)] move_lines = self.env['account.move.line'].search(domain, order='move_name, id') self.assertEqual(9, len(move_lines)) # Verify Reconciliation domain = [('company_id', '=', self.company.id), ('reconciled', '=', True)] move_lines = self.env['account.move.line'].search(domain) self.assertEqual(256, len(move_lines)) # Verify Full Reconciliation domain = [('company_id', '=', self.company.id), ('full_reconcile_id', '!=', False)] move_lines = self.env['account.move.line'].search(domain) self.assertEqual(256, len(move_lines)) # Verify Journal types domain = [('company_id', '=', self.company.id), ('name', '=', 'FEC-BQ 552')] journal = self.env['account.journal'].search(domain) self.assertEqual(journal.type, 'bank') def test_import_fec_export(self): """ Test that imports the results of a FEC export """ # Export Wizard ---------------------------------------- export_wizard = self.env['l10n_fr.fec.export.wizard'].with_company(self.company_export).create([{ 'date_from': datetime.date(1990, 1, 1), 'date_to': datetime.date.today(), 'test_file': True, 'export_type': 'nonofficial' }]) # Generate the FEC content with the export wizard def split_fields(csv_content): lines = csv_content.split('\r\n') fields = [line.split('|') for line in lines] # We remove the 'CompAuxNum' @6 because we use partner.id in it # and we don't care about comparing partner.id fields[1:] = [[*field[:6], '', *field[7:]] for field in fields[1:]] return fields fec_stream = export_wizard.with_context(fec_test_mode=True).generate_fec()['file_content'] content = b''.join(fec_stream).decode() exported_content = split_fields(content) with tools.file_open('l10n_fr_fec_import/static/tests/fec_test_files/fec_crlf_pipe_utf8_export.txt', mode='rb') as raw_file: expected_export = split_fields(raw_file.read().decode()) self.assertEqual(exported_content, expected_export) # Importing the fec file self._import_fec_file('fec_crlf_pipe_utf8_export.json') # Verify moves data new_moves = self.env['account.move'].search([ ('company_id', '=', self.company_export.id), ('closing_return_id', '=', False), # exclude automatic tax closing entries ], order="name") columns = ['company_id', 'name', 'journal_id', 'partner_id', 'date'] moves_data = [ (self.company_export.id, 'INV/001/123456', self.company_data_2['default_journal_sale'].id, self.partner_a.id, datetime.date(2010, 1, 1)), (self.company_export.id, 'INV/001/123457', self.company_data_2['default_journal_purchase'].id, self.partner_b.id, datetime.date(2010, 1, 1)), ] expected_values = [dict(zip(columns, move_data)) for move_data in moves_data] self.assertRecordValues(new_moves, expected_values) # Verify moves lines data columns = ['company_id', 'name', 'credit', 'debit', 'account_id'] lines_data = [ (self.company_export.id, 'line-1', 0.00, 100.30, self.company_data_2['default_account_receivable'].id), (self.company_export.id, 'line-2', 100.30, 0.00, self.company_data_2['default_account_tax_sale'].id), (self.company_export.id, 'line-3', 65.15, 0.00, self.company_data_2['default_account_payable'].id), (self.company_export.id, 'line-4', 0.00, 65.15, self.company_data_2['default_account_expense'].id), ] expected_values = [dict(zip(columns, line_data)) for line_data in lines_data] new_lines = new_moves.mapped("line_ids").sorted(key=lambda x: x.name) self.assertRecordValues(new_lines, expected_values) def test_balance_moves_by_day(self): """ Test that the imbalanced moves are correctly balanced with a grouping by day """ self._import_fec_file('fec_cr_tab_utf8_imbalanced_day.json') domain = [('company_id', '=', self.company.id), ('move_name', 'in', ('ACH/20180808', 'ACH/20180910'))] move_lines = self.env['account.move.line'].search(domain, order='move_name,name') self.assertEqual( move_lines.mapped(lambda line: (line.move_name, line.name)), [ ('ACH/20180808', 'ADVANCE PAYMENT COMPANY FORMALITIES'), ('ACH/20180808', 'ADVANCE PAYMENT COMPANY FORMALITIES'), ('ACH/20180808', 'ADVANCE PAYMENT COMPANY FORMALITIES'), ('ACH/20180808', 'DOMICILIATION'), ('ACH/20180808', 'DOMICILIATION'), ('ACH/20180808', 'DOMICILIATION'), ('ACH/20180910', 'ASSURANCE'), ('ACH/20180910', 'ASSURANCE'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ]) def test_balance_moves_by_month(self): """ Test that the imbalanced moves are correctly balanced with a grouping by month """ self._import_fec_file('fec_lf_tab_utf8_imbalanced_month.json') domain = [('company_id', '=', self.company.id), ('move_name', 'in', ('ACH/201808', 'ACH/20180910'))] move_lines = self.env['account.move.line'].search(domain, order='move_name,name') self.assertEqual( move_lines.mapped(lambda line: (line.move_name, line.name)), [ ('ACH/201808', 'ADVANCE PAYMENT COMPANY FORMALITIES'), ('ACH/201808', 'ADVANCE PAYMENT COMPANY FORMALITIES'), ('ACH/201808', 'ADVANCE PAYMENT COMPANY FORMALITIES'), ('ACH/201808', 'DOMICILIATION'), ('ACH/201808', 'DOMICILIATION'), ('ACH/201808', 'DOMICILIATION'), ('ACH/20180910', 'ASSURANCE'), ('ACH/20180910', 'ASSURANCE'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ('ACH/20180910', 'PARTNER 01'), ]) def test_unbalanceable_moves(self): """ Test that the imbalanced moves raise as they cannot be balanced by day/month """ with self.assertRaises(UserError): self._import_fec_file('fec_lf_tab_utf8_imbalanced.json') def test_positive_montant_devise(self): """ Test that it doesn't fail even when the MontantDevise is not signed, i.e. MontantDevise is positive even when the line is credited, or the opposite case: MontantDevise is negative while the line is debited. """ self._import_fec_file('fec_cr_tab_utf8bom.json') def test_fec_import_multicompany(self): self._import_fec_file('fec_lf_tab_utf8.json') fr_company2 = self.setup_other_company(name="Company FR 2")['company'] self._import_fec_file('fec_lf_tab_utf8.json', company=fr_company2) def test_fec_import_reconciliation(self): last = self.env['account.move'].search([], order='id desc', limit=1) self._import_fec_file('fec_crlf_tab_utf8.json') new = self.env['account.move'].search([('id', '>', last.id)]) self.assertEqual(len(new), 4) self.assertFalse(new.line_ids.full_reconcile_id, "Reconciliation is only temporary before posting") new.action_post() self.assertEqual(len(new.line_ids.full_reconcile_id), 2, "It is fully reconciled after posting") def test_key_is_empty(self): with self.assertRaisesRegex(UserError, "journal not found"): self._import_fec_file('fec_lf_tab_utf8-2.json') def test_no_update_journal(self): """Test that existing journal type is not updated when importing FEC file.""" self._import_fec_file('fec_lf_tab_utf8.json') purchase_journal = self.env['account.journal'].search([('code', '=', 'ACH')]) self.assertEqual(purchase_journal.type, 'general') purchase_journal.type = 'purchase' self._import_fec_file('fec_lf_tab_utf8.json') self.assertEqual(purchase_journal.type, 'purchase') def test_created_account_translation(self): self.env['res.lang']._activate_lang('fr_FR') self.env['account.account'].search([('name', '=', 'Subscribed capital - uncalled')]).unlink() self._import_fec_file('fec_lf_pipe_iso.json') account = self.env['account.account'].search([('name', '=', 'Capital')]).with_context(lang="fr_FR") self.assertEqual(account.name, 'Capital souscrit - non appelé') def test_currency_rounding_with_decimal_values(self): """ Test that it doesn't fail even when the currency has a rounding of 1, and the lines include values with decimal points. """ self.env["res.currency"].create({"name": "xxx", "symbol": "X", "rounding": 1}) self._import_fec_file('fec_lf_tab_utf8-3.json') def test_import_wizard_fields(self): last = self.env['account.move.line'].search([], order='id desc', limit=1) import_summary = self._import_fec_file('fec_lf_tab_utf8.json').import_summary_id self.assertEqual(import_summary.import_summary_have_data, True) self.assertNotEqual(last, self.env['account.move.line'].search([], order='id desc', limit=1)) last = self.env['account.move.line'].search([], order='id desc', limit=1) import_summary = self._import_fec_file('fec_lf_tab_utf8.json', duplicate_documents_handling='ignore').import_summary_id self.assertEqual(import_summary.import_summary_have_data, False) self.assertEqual(last, self.env['account.move.line'].search([], order='id desc', limit=1)) last = self.env['account.move.line'].search([], order='id desc', limit=1) import_summary = self._import_fec_file('fec_lf_tab_utf8.json').import_summary_id self.assertEqual(import_summary.import_summary_have_data, True) self.assertNotEqual(last, self.env['account.move.line'].search([], order='id desc', limit=1)) last = self.env['account.move.line'].search([], order='id desc', limit=1) import_summary = self._import_fec_file('fec_lf_tab_utf8.json', duplicate_documents_handling='ignore', document_prefix='PRE').import_summary_id self.assertEqual(import_summary.import_summary_have_data, True) self.assertNotEqual(last, self.env['account.move.line'].search([], order='id desc', limit=1)) last = self.env['account.move.line'].search([], order='id desc', limit=1) import_summary = self._import_fec_file('fec_lf_tab_utf8.json', duplicate_documents_handling='ignore', document_prefix='PRE').import_summary_id self.assertEqual(import_summary.import_summary_have_data, False) self.assertEqual(last, self.env['account.move.line'].search([], order='id desc', limit=1)) def test_account_name(self): """ Template should not override the account's name given by the user We defined ACH\tACHATS\tACH000001\t20180808\t45500000\tAssociate's current account\t\t\t1\t20180808\tADVANCE PAYMENT COMPANY FORMALITIES\t0,00\t600,00\t\t\t20190725\t\t We should retrieve the name "Associate's current account" """ self._import_fec_file('fec_lf_tab_utf8_imbalanced_month.json') account = self.env['account.account'].search([('code', '=', '455000')]) self.assertEqual(account.name, "Associate's current account")