Files
public/rsbu_mapping/tests/test_mapping.py

306 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestRsbuMapping(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company = cls.env.company
cls.partner = cls.env['res.partner'].create({'name': 'Map Клиент'})
cls.journal_bank = cls.env['account.journal'].search(
[('type', '=', 'bank'), ('company_id', '=', cls.company.id)],
limit=1)
cls.Template = cls.env['rsbu.operation.template']
cls.Matrix = cls.env['rsbu.operation.matrix']
# Роли счетов из типового плана.
cls.acc = lambda self, code: self.env['rsbu.account'].search([
('code', '=', code),
('company_id', '=', self.env.company.id)], limit=1)
def _acc(self, code):
return self.env['rsbu.account'].search([
('code', '=', code),
('company_id', '=', self.env.company.id)], limit=1)
def _advance_template(self):
return self.Template.create({
'code': 'ADV_IN',
'name': 'Получение аванса',
'resolver_key': 'advance_received',
'role_ids': [
(0, 0, {'role': 'bank', 'account_id': self._acc('51').id}),
(0, 0, {'role': 'advance_received',
'account_id': self._acc('62.АВ').id}),
(0, 0, {'role': 'vat_advance',
'account_id': self._acc('76.АВ').id}),
(0, 0, {'role': 'vat_output',
'account_id': self._acc('68.НДС').id}),
],
})
def _matrix_advance(self, template):
return self.Matrix.create({
'name': 'Входящий платёж → аванс',
'doc_kind': 'payment_inbound',
'has_advance': 'any',
'template_id': template.id,
'priority': 10,
})
def _payment(self, amount=1050.0, vat=50.0):
return self.env['account.payment'].create({
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': self.partner.id,
'amount': amount,
'rsbu_vat_amount': vat,
'journal_id': self.journal_bank.id,
})
def test_detect_template_by_matrix(self):
tmpl = self._advance_template()
self._matrix_advance(tmpl)
pay = self._payment()
self.assertEqual(pay._rsbu_detect_template(), tmpl)
self.assertTrue(pay._rsbu_is_applicable())
def test_no_matrix_not_applicable(self):
# Без матрицы документ не в РСБУ-периметре.
pay = self._payment()
self.assertFalse(pay._rsbu_detect_template())
self.assertFalse(pay._rsbu_is_applicable())
def test_advance_resolver_lines_balanced(self):
tmpl = self._advance_template()
self._matrix_advance(tmpl)
pay = self._payment(amount=1050.0, vat=50.0)
lines = pay._rsbu_build_lines()
total_debit = sum(l.get('debit', 0.0) for l in lines)
total_credit = sum(l.get('credit', 0.0) for l in lines)
self.assertEqual(total_debit, total_credit)
# Дт 51 = 1050, авансовый НДС 50
self.assertEqual(total_debit, 1050.0 + 50.0)
def test_full_flow_generate_rsbu_move(self):
tmpl = self._advance_template()
self._matrix_advance(tmpl)
pay = self._payment(amount=1050.0, vat=50.0)
pay.action_post()
move = pay.rsbu_move_id
self.assertTrue(move)
self.assertEqual(move.state, 'posted')
# Проверим сальдо: 51 дебет 1050
acc51 = self._acc('51')
bal = acc51.get_balance(company_id=self.company.id)
self.assertEqual(bal['debit'], 1050.0)
# 68.НДС кредит 50 (начислен авансовый НДС)
acc68 = self._acc('68.НДС')
bal68 = acc68.get_balance(company_id=self.company.id)
self.assertEqual(bal68['credit'], 50.0)
def _sale_template(self, with_advance_roles=False):
roles = [
(0, 0, {'role': 'receivable', 'account_id': self._acc('62.01').id}),
(0, 0, {'role': 'revenue', 'account_id': self._acc('90.01').id}),
(0, 0, {'role': 'revenue_vat', 'account_id': self._acc('90.03').id}),
(0, 0, {'role': 'vat_output', 'account_id': self._acc('68.НДС').id}),
]
if with_advance_roles:
roles += [
(0, 0, {'role': 'advance_received',
'account_id': self._acc('62.АВ').id}),
(0, 0, {'role': 'vat_advance',
'account_id': self._acc('76.АВ').id}),
]
return self.Template.create({
'code': 'SALE_T',
'name': 'Продажа',
'resolver_key': 'shipment_upd',
'role_ids': roles,
})
def _sale_invoice_with_vat(self):
"""Инвойс с НДС: untaxed 147.00, tax 22.05, total 169.05."""
tax = self.env['account.tax'].create({
'name': 'НДС 15% demo',
'amount': 15.0,
'amount_type': 'percent',
'type_tax_use': 'sale',
})
product = self.env['product.product'].create({'name': 'Demo P'})
return self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner.id,
'invoice_line_ids': [(0, 0, {
'product_id': product.id,
'name': 'Demo',
'quantity': 1,
'price_unit': 147.0,
'tax_ids': [(6, 0, tax.ids)],
})],
})
def test_sale_with_vat_balanced(self):
# Регрессия: реализация с НДС должна давать сбалансированную проводку.
tmpl = self._sale_template()
inv = self._sale_invoice_with_vat()
inv.rsbu_template_id = tmpl.id
lines = inv._rsbu_build_lines()
total_debit = sum(l.get('debit', 0.0) for l in lines)
total_credit = sum(l.get('credit', 0.0) for l in lines)
self.assertAlmostEqual(total_debit, total_credit, places=2)
# Дт 62.01 = total 169.05, Дт 90.03 = НДС -> дебет = total + tax.
self.assertAlmostEqual(total_debit, 169.05 + 22.05, places=2)
def test_advance_offset_on_shipment(self):
"""Р1: при отгрузке после аванса зачёт 62.АВ и вычет авансового НДС."""
# 1) Аванс клиента 105 (НДС 5) через резолвер advance_received.
adv_tmpl = self._advance_template()
self._matrix_advance(adv_tmpl)
pay = self._payment(amount=105.0, vat=5.0)
pay.action_post()
self.assertTrue(pay.rsbu_move_id)
# Остатки после аванса: 62.АВ кредит 105, 76.АВ дебет 5.
acc_av = self._acc('62.АВ')
acc_vatav = self._acc('76.АВ')
bal_av = acc_av.get_balance(
company_id=self.company.id,
subconto={'partner': self.partner.id})
self.assertEqual(bal_av['balance'], 105.0)
self.assertEqual(bal_av['side'], 'credit')
# 2) Отгрузка тому же клиенту, шаблон с ролями аванса.
sale_tmpl = self._sale_template(with_advance_roles=True)
inv = self._sale_invoice_with_vat() # total 169.05
inv.rsbu_template_id = sale_tmpl.id
lines = inv._rsbu_build_lines()
# Проводка должна содержать зачёт аванса и вычет авансового НДС.
d = sum(l.get('debit', 0.0) for l in lines)
c = sum(l.get('credit', 0.0) for l in lines)
self.assertAlmostEqual(d, c, places=2)
inv.action_post()
# 3) После отгрузки зачёт аванса: 62.АВ по партнёру закрыт (=0).
# Вычет авансового НДС (76.АВ) при отгрузке НЕ делается — он вынесен
# в регламент конца периода (rsbu.period.close), поэтому 76.АВ здесь
# остаётся ненулевым (закрывается регламентом «книга покупок»).
bal_av2 = acc_av.get_balance(
company_id=self.company.id,
subconto={'partner': self.partner.id})
self.assertAlmostEqual(bal_av2['balance'], 0.0, places=2)
# ------------------------------------------------------------------
# customer_payment: аванс / постоплата / переплата
# ------------------------------------------------------------------
def _customer_payment_template(self):
tmpl = self.Template.create({
'code': 'CUST_PAY',
'name': 'Оплата от клиента',
'resolver_key': 'customer_payment',
'company_id': self.company.id,
'role_ids': [
(0, 0, {'role': 'bank', 'account_id': self._acc('51').id}),
(0, 0, {'role': 'receivable',
'account_id': self._acc('62.01').id}),
(0, 0, {'role': 'advance_received',
'account_id': self._acc('62.АВ').id}),
(0, 0, {'role': 'vat_advance',
'account_id': self._acc('76.АВ').id}),
(0, 0, {'role': 'vat_output',
'account_id': self._acc('68.НДС').id}),
],
})
self.Matrix.create({
'name': 'Оплата клиента',
'doc_kind': 'payment_inbound',
'partner_kind': 'customer',
'has_advance': 'any',
'template_id': tmpl.id,
'company_id': self.company.id,
})
return tmpl
def _debt(self, partner, amount, date):
"""Создать дебиторку покупателя (реализация) для теста постоплаты."""
move = self.env['rsbu.move'].create({
'date': date, 'company_id': self.company.id,
'line_ids': [
(0, 0, {'account_id': self._acc('62.01').id, 'debit': amount,
'partner_id': partner.id}),
(0, 0, {'account_id': self._acc('90.01').id, 'credit': amount}),
],
})
move.action_post()
return move
def _inbound(self, partner, amount, date):
pay = self.env['account.payment'].create({
'payment_type': 'inbound', 'partner_type': 'customer',
'partner_id': partner.id, 'amount': amount,
'journal_id': self.journal_bank.id, 'company_id': self.company.id,
'date': date,
})
pay.action_post()
return pay
def test_customer_payment_advance(self):
"""Платёж без долга → аванс (Кт 62.АВ), только деньги.
Авансовый НДС в платеже НЕ начисляется: он живёт в отдельном
авансовом счёте-фактуре (rsbu.invoice, invoice_type='advance'),
рождаемом из платежа и не связанном с документом реализации
(realization_id пуст). Модель-источник авансового НДС — rsbu.invoice,
без изменений. Платёж двигает только деньги.
"""
self.company.rsbu_vat_mode = 'vat_5'
self._customer_payment_template()
p = self.env['res.partner'].create({'name': 'Аванс-клиент'})
pay = self._inbound(p, 105000.0, '2026-03-01')
acc_av = self._acc('62.АВ')
bal = acc_av.get_balance(company_id=self.company.id,
subconto={'partner': p.id})
self.assertAlmostEqual(bal['balance'], 105000.0, places=2)
# Авансовый НДС в платеже не начисляется (68.НДС не затронут платежом).
# дебиторка 62.01 не затронута
bal01 = self._acc('62.01').get_balance(
company_id=self.company.id, subconto={'partner': p.id})
self.assertAlmostEqual(bal01['balance'], 0.0, places=2)
def test_customer_payment_postpaid(self):
"""Платёж при наличии долга → погашение 62.01, без аванса и НДС."""
self.company.rsbu_vat_mode = 'vat_5'
self._customer_payment_template()
p = self.env['res.partner'].create({'name': 'Постоплата-клиент'})
self._debt(p, 50000.0, '2026-03-05')
pay = self._inbound(p, 50000.0, '2026-03-10')
# 62.01 закрыт (0), 62.АВ не создавался, авансовый НДС не начислен
bal01 = self._acc('62.01').get_balance(
company_id=self.company.id, subconto={'partner': p.id})
self.assertAlmostEqual(bal01['balance'], 0.0, places=2)
bal_av = self._acc('62.АВ').get_balance(
company_id=self.company.id, subconto={'partner': p.id})
self.assertAlmostEqual(bal_av['balance'], 0.0, places=2)
def test_customer_payment_overpaid(self):
"""Переплата → долг гасится (62.01), остаток в аванс (62.АВ).
Авансовый НДС в платеже не начисляется (вынесен в СФ на аванс).
"""
self.company.rsbu_vat_mode = 'vat_5'
self._customer_payment_template()
p = self.env['res.partner'].create({'name': 'Переплата-клиент'})
self._debt(p, 30000.0, '2026-03-06')
pay = self._inbound(p, 100000.0, '2026-03-11')
# долг 62.01 закрыт
bal01 = self._acc('62.01').get_balance(
company_id=self.company.id, subconto={'partner': p.id})
self.assertAlmostEqual(bal01['balance'], 0.0, places=2)
# аванс на остаток 70000
bal_av = self._acc('62.АВ').get_balance(
company_id=self.company.id, subconto={'partner': p.id})
self.assertAlmostEqual(bal_av['balance'], 70000.0, places=2)
# Авансовый НДС в платеже не начисляется (76.АВ платежом не затронут).