""" Tests for Parser_1C (account_bank_statement_1c_import). Validates: Requirements 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.9 """ import base64 from odoo.exceptions import UserError from odoo.tests.common import TransactionCase # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- COMPANY_INN = "7700000001" COMPANY_KPP = "770001001" PARTNER_INN = "7700000002" PARTNER_KPP = "770001002" VALID_TXT_TEMPLATE = """\ 1CClientBankExchange ВерсияФормата=1.03 Кодировка=Windows Отправитель=Бухгалтерия предприятия ДатаСоздания=01.01.2024 ВремяСоздания=12:00:00 ДатаНачала=01.01.2024 ДатаКонца=31.01.2024 РасчСчет=40702810000000000001 СекцияРасчСчет ДатаНачала=01.01.2024 ДатаКонца=31.01.2024 РасчСчет=40702810000000000001 НачальныйОстаток=10000.00 ВсегоПоступило=5000.00 ВсегоСписано=3000.00 КонечныйОстаток=12000.00 КонецРасчСчет {transactions}КонецФайла """ TRANSACTION_PAYER = """\ СекцияДокумент=Платежное поручение Номер=1 Дата=15.01.2024 Сумма=1000.00 ПлательщикСчет=40702810000000000001 ДатаСписано=15.01.2024 ПлательщикИНН={company_inn} ПлательщикКПП={company_kpp} Плательщик=ООО Наша Компания Плательщик1=ООО Наша Компания ПлательщикРасчСчет=40702810000000000001 ПлательщикБанк1=Банк Плательщика ПлательщикБИК=044525001 ПлательщикКорсчет=30101810400000000001 ПолучательСчет=40702810000000000002 ПолучательИНН={partner_inn} ПолучательКПП={partner_kpp} Получатель=ООО Контрагент Получатель1=ООО Контрагент ПолучательРасчСчет=40702810000000000002 ПолучательБанк1=Банк Получателя ПолучательБИК=044525002 ПолучательКорсчет=30101810400000000002 НазначениеПлатежа=Оплата по договору №1 КонецДокумента """ TRANSACTION_RECIPIENT = """\ СекцияДокумент=Платежное поручение Номер=2 Дата=16.01.2024 Сумма=2000.00 ПлательщикСчет=40702810000000000002 ДатаПоступило=16.01.2024 ПлательщикИНН={partner_inn} ПлательщикКПП={partner_kpp} Плательщик=ООО Контрагент Плательщик1=ООО Контрагент ПлательщикРасчСчет=40702810000000000002 ПлательщикБанк1=Банк Плательщика ПлательщикБИК=044525002 ПлательщикКорсчет=30101810400000000002 ПолучательСчет=40702810000000000001 ПолучательИНН={company_inn} ПолучательКПП={company_kpp} Получатель=ООО Наша Компания Получатель1=ООО Наша Компания ПолучательРасчСчет=40702810000000000001 ПолучательБанк1=Банк Получателя ПолучательБИК=044525001 ПолучательКорсчет=30101810400000000001 НазначениеПлатежа=Поступление по договору №2 КонецДокумента """ TRANSACTION_UNKNOWN_INN = """\ СекцияДокумент=Платежное поручение Номер=3 Дата=17.01.2024 Сумма=500.00 ПлательщикСчет=40702810000000000003 ДатаСписано=17.01.2024 ПлательщикИНН=9999999999 ПлательщикКПП=999999999 Плательщик=ООО Чужая Компания Плательщик1=ООО Чужая Компания ПлательщикРасчСчет=40702810000000000003 ПлательщикБанк1=Чужой Банк ПлательщикБИК=044525003 ПлательщикКорсчет=30101810400000000003 ПолучательСчет=40702810000000000004 ПолучательИНН=8888888888 ПолучательКПП=888888888 Получатель=ООО Другая Компания Получатель1=ООО Другая Компания ПолучательРасчСчет=40702810000000000004 ПолучательБанк1=Другой Банк ПолучательБИК=044525004 ПолучательКорсчет=30101810400000000004 НазначениеПлатежа=Транзакция без нашей компании КонецДокумента """ def _encode(text): """Encode text to base64 as cp1251 (as the wizard expects).""" return base64.b64encode(text.encode('cp1251')) def _make_txt(transactions=""): return VALID_TXT_TEMPLATE.format(transactions=transactions) def _payer_tx(): return TRANSACTION_PAYER.format( company_inn=COMPANY_INN, company_kpp=COMPANY_KPP, partner_inn=PARTNER_INN, partner_kpp=PARTNER_KPP, ) def _recipient_tx(): return TRANSACTION_RECIPIENT.format( company_inn=COMPANY_INN, company_kpp=COMPANY_KPP, partner_inn=PARTNER_INN, partner_kpp=PARTNER_KPP, ) def _unknown_inn_tx(): return TRANSACTION_UNKNOWN_INN # --------------------------------------------------------------------------- # Fixtures / setUp helpers # --------------------------------------------------------------------------- class _Base(TransactionCase): """Common setUp: company VAT, bank journal with use_in_bank_statement.""" def setUp(self): super().setUp() # Set company VAT so the parser can match INN self.env.company.write({'vat': COMPANY_INN}) # Create (or find) a bank journal with use_in_bank_statement=True self.journal = self.env['account.journal'].search( [('type', '=', 'bank'), ('use_in_bank_statement', '=', True)], limit=1, ) if not self.journal: self.journal = self.env['account.journal'].create({ 'name': 'Test Bank Journal', 'type': 'bank', 'code': 'TBNK', 'use_in_bank_statement': True, }) def _wizard(self, file_content_bytes, file_name='statement.txt'): """Create an InvoiceImportWizard record.""" return self.env['invoice.import.wizard'].create({ 'file': base64.b64encode(file_content_bytes), 'file_name': file_name, }) # --------------------------------------------------------------------------- # TestParser1CBasic # --------------------------------------------------------------------------- class TestParser1CBasic(_Base): """Validates: Requirements 2.1, 2.2, 2.3""" def test_valid_txt_parses_without_exception(self): """Req 2.1 — correct TXT file is parsed without exceptions.""" content = _make_txt().encode('cp1251') wizard = self._wizard(content, 'statement.txt') # action_import_invoice should not raise wizard.action_import_invoice() def test_wrong_extension_raises_user_error(self): """Req 2.2 — non-.txt extension raises UserError.""" content = _make_txt().encode('cp1251') wizard = self._wizard(content, 'statement.csv') with self.assertRaises(UserError): wizard.action_import_invoice() def test_no_file_raises_user_error(self): """Req 2.3 — missing file raises UserError.""" wizard = self.env['invoice.import.wizard'].create({ 'file': False, 'file_name': 'statement.txt', }) with self.assertRaises(UserError): wizard.action_import_invoice() # --------------------------------------------------------------------------- # TestParser1CLines # --------------------------------------------------------------------------- class TestParser1CLines(_Base): """Validates: Requirements 2.4, 2.5, 2.6""" def _import_and_get_lines(self, transactions_text): content = _make_txt(transactions_text).encode('cp1251') wizard = self._wizard(content, 'statement.txt') wizard.action_import_invoice() return self.env['account.bank.statement.line'].search([], order='id desc') def test_company_as_payer_creates_negative_amount(self): """Req 2.4 — company INN as payer → negative amount line.""" lines = self._import_and_get_lines(_payer_tx()) payer_lines = lines.filtered(lambda l: l.payment_ref == '1') self.assertTrue(payer_lines, "Expected a statement line with payment_ref='1'") self.assertLess(payer_lines[0].amount, 0, "Amount should be negative when company is payer") def test_company_as_recipient_creates_positive_amount(self): """Req 2.5 — company INN as recipient → positive amount line.""" lines = self._import_and_get_lines(_recipient_tx()) recipient_lines = lines.filtered(lambda l: l.payment_ref == '2') self.assertTrue(recipient_lines, "Expected a statement line with payment_ref='2'") self.assertGreater(recipient_lines[0].amount, 0, "Amount should be positive when company is recipient") def test_duplicate_line_is_skipped(self): """Req 2.6 — importing the same transaction twice does not create a duplicate.""" tx = _payer_tx() content = _make_txt(tx).encode('cp1251') # First import self._wizard(content, 'statement.txt').action_import_invoice() count_after_first = self.env['account.bank.statement.line'].search_count( [('payment_ref', '=', '1')] ) # Second import of the same file self._wizard(content, 'statement.txt').action_import_invoice() count_after_second = self.env['account.bank.statement.line'].search_count( [('payment_ref', '=', '1')] ) self.assertEqual(count_after_first, count_after_second, "Duplicate transaction should be skipped on second import") # --------------------------------------------------------------------------- # TestParser1CPartner # --------------------------------------------------------------------------- class TestParser1CPartner(_Base): """Validates: Requirements 2.7, 2.9""" def test_unknown_inn_kpp_creates_new_partner(self): """Req 2.7 — unknown INN/KPP → new partner of type 'company' is created.""" # Ensure partner does not exist self.env['res.partner'].search([('vat', '=', PARTNER_INN)]).unlink() content = _make_txt(_payer_tx()).encode('cp1251') self._wizard(content, 'statement.txt').action_import_invoice() partner = self.env['res.partner'].search([('vat', '=', PARTNER_INN)], limit=1) self.assertTrue(partner, "A new partner should have been created") self.assertEqual(partner.company_type, 'company', "New partner should have company_type='company'") def test_unknown_company_inn_in_transaction_raises_user_error(self): """Req 2.9 — company INN not found as payer or recipient → UserError.""" content = _make_txt(_unknown_inn_tx()).encode('cp1251') wizard = self._wizard(content, 'statement.txt') with self.assertRaises(UserError): wizard.action_import_invoice()