Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC

This commit is contained in:
CI Publish Bot
2026-11-01 22:54:30 +00:00
commit 26250aa580
2492 changed files with 664277 additions and 0 deletions

View File

@@ -0,0 +1,305 @@
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.АВ платежом не затронут).