294 lines
11 KiB
Python
294 lines
11 KiB
Python
"""
|
||
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()
|