Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
69
rsbu_demo_templates/README.rst
Normal file
69
rsbu_demo_templates/README.rst
Normal file
@@ -0,0 +1,69 @@
|
||||
============================
|
||||
РСБУ — Демо-шаблоны операций
|
||||
============================
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Beta
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
|
||||
|badge1| |badge2|
|
||||
|
||||
Создаёт готовый набор шаблонов типовых операций и правил их подбора для
|
||||
базовых хозяйственных сценариев. Позволяет быстро запустить формирование
|
||||
проводок РСБУ без ручной настройки шаблонов. Данные создаются для каждой
|
||||
компании в базе.
|
||||
|
||||
**Содержание**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Возможности
|
||||
===========
|
||||
|
||||
Готовые шаблоны для типовых операций.
|
||||
|
||||
Счета (документы реализации и поступления):
|
||||
|
||||
* Продажа клиенту
|
||||
* Возврат от клиента (кредит-нота)
|
||||
* Счёт поставщика
|
||||
* Возврат поставщику (кредит-нота)
|
||||
|
||||
Платежи:
|
||||
|
||||
* Оплата от клиента
|
||||
* Возврат клиенту
|
||||
* Оплата поставщику
|
||||
* Возврат от поставщика
|
||||
|
||||
Счета для проводок подбираются из типового плана счетов 94н по коду
|
||||
для каждой компании.
|
||||
|
||||
Использование
|
||||
=============
|
||||
|
||||
#. Установите модуль — шаблоны и правила подбора создадутся автоматически
|
||||
для каждой компании.
|
||||
#. Проверьте их в *РСБУ > Настройка > Шаблоны операций*.
|
||||
#. Проводите документы обычным образом — проводки РСБУ будут формироваться
|
||||
по готовым шаблонам.
|
||||
|
||||
Модуль предназначен для быстрого старта и демонстрации возможностей
|
||||
РСБУ-слоя. Шаблоны можно дорабатывать под свои задачи.
|
||||
|
||||
Авторы
|
||||
======
|
||||
|
||||
* MK.Lab, RuOdoo
|
||||
|
||||
Модуль сопровождается MK.Lab, RuOdoo.
|
||||
|
||||
.. image:: https://ruodoo.ru/logo.png
|
||||
:alt: MK.Lab, RuOdoo
|
||||
:target: https://ruodoo.ru
|
||||
|
||||
Модуль является частью РСБУ-слоя проекта РуОду.
|
||||
7
rsbu_demo_templates/__init__.py
Normal file
7
rsbu_demo_templates/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from . import models
|
||||
|
||||
|
||||
def post_init_load_demo_templates(env):
|
||||
"""Создать демо-шаблоны и матрицу для всех компаний."""
|
||||
for company in env['res.company'].search([]):
|
||||
env['rsbu.demo.templates.loader'].load_for_company(company)
|
||||
37
rsbu_demo_templates/__manifest__.py
Normal file
37
rsbu_demo_templates/__manifest__.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'РСБУ — Демо-шаблоны операций',
|
||||
'version': '19.0.2026.09.17',
|
||||
'summary': 'Готовые шаблоны и матрица связности для типовых операций',
|
||||
'description': """
|
||||
РСБУ — Демо-шаблоны операций
|
||||
============================
|
||||
|
||||
Создаёт готовые шаблоны типовых операций и правила матрицы связности
|
||||
для базовых сценариев (в разрезе каждой компании):
|
||||
|
||||
Инвойсы (account.move):
|
||||
* Продажа клиенту (out_invoice)
|
||||
* Возврат от клиента / кредит-нота (out_refund)
|
||||
* Счёт поставщика (in_invoice)
|
||||
* Возврат поставщику / кредит-нота (in_refund)
|
||||
|
||||
Платежи (account.payment):
|
||||
* Оплата от клиента (inbound, customer)
|
||||
* Возврат клиенту (outbound, customer)
|
||||
* Оплата поставщику (outbound, supplier)
|
||||
* Возврат от поставщика (inbound, supplier)
|
||||
|
||||
Счета берутся из типового плана 94н по коду для каждой компании.
|
||||
""",
|
||||
'author': 'MK.Lab, RuOdoo',
|
||||
'website': 'https://ruodoo.ru',
|
||||
'category': 'Accounting/Localizations',
|
||||
'depends': ['rsbu_mapping', 'rsbu_kudir'],
|
||||
'data': [],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'license': 'LGPL-3',
|
||||
'post_init_hook': 'post_init_load_demo_templates',
|
||||
'maintainers': ['mklab', 'ruodoo'],
|
||||
}
|
||||
1
rsbu_demo_templates/models/__init__.py
Normal file
1
rsbu_demo_templates/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import rsbu_demo_templates_loader
|
||||
186
rsbu_demo_templates/models/rsbu_demo_templates_loader.py
Normal file
186
rsbu_demo_templates/models/rsbu_demo_templates_loader.py
Normal file
@@ -0,0 +1,186 @@
|
||||
from odoo import api, models
|
||||
|
||||
# Определения демо-шаблонов.
|
||||
# Формат: code -> dict(name, resolver_key, usn_recognition, is_refund,
|
||||
# roles={role: account_code})
|
||||
TEMPLATES = {
|
||||
# --- Инвойсы ---
|
||||
'DEMO_SALE': {
|
||||
'name': 'Продажа клиенту',
|
||||
'resolver_key': 'shipment_upd',
|
||||
'usn_recognition': 'none', # доход по УСН признаётся по оплате
|
||||
'is_refund': False,
|
||||
'roles': {
|
||||
'receivable': '62.01',
|
||||
'revenue': '90.01',
|
||||
'revenue_vat': '90.03',
|
||||
'vat_output': '68.НДС',
|
||||
# роли для зачёта аванса и вычета авансового НДС при отгрузке
|
||||
'advance_received': '62.АВ',
|
||||
'vat_advance': '76.АВ',
|
||||
},
|
||||
},
|
||||
'DEMO_SALE_REFUND': {
|
||||
'name': 'Возврат от клиента (кредит-нота)',
|
||||
'resolver_key': 'sale_credit_note',
|
||||
'usn_recognition': 'none',
|
||||
'is_refund': True,
|
||||
'roles': {
|
||||
'receivable': '62.01',
|
||||
'revenue': '90.01',
|
||||
'revenue_vat': '90.03',
|
||||
'vat_output': '68.НДС',
|
||||
},
|
||||
},
|
||||
'DEMO_PURCHASE': {
|
||||
'name': 'Счёт поставщика',
|
||||
'resolver_key': 'purchase_bill',
|
||||
'usn_recognition': 'none', # расход по УСН признаётся по оплате
|
||||
'is_refund': False,
|
||||
'roles': {
|
||||
'expense': '44',
|
||||
'payable': '60',
|
||||
},
|
||||
},
|
||||
'DEMO_PURCHASE_REFUND': {
|
||||
'name': 'Возврат поставщику (кредит-нота)',
|
||||
'resolver_key': 'purchase_credit_note',
|
||||
'usn_recognition': 'none',
|
||||
'is_refund': True,
|
||||
'roles': {
|
||||
'expense': '44',
|
||||
'payable': '60',
|
||||
},
|
||||
},
|
||||
# --- Платежи ---
|
||||
'DEMO_PAY_CUSTOMER': {
|
||||
'name': 'Оплата от клиента',
|
||||
'resolver_key': 'customer_payment',
|
||||
'usn_recognition': 'income',
|
||||
'is_refund': False,
|
||||
'roles': {
|
||||
'bank': '51',
|
||||
'receivable': '62.01',
|
||||
'advance_received': '62.АВ',
|
||||
'vat_advance': '76.АВ',
|
||||
'vat_output': '68.НДС',
|
||||
},
|
||||
},
|
||||
'DEMO_REFUND_CUSTOMER': {
|
||||
'name': 'Возврат клиенту',
|
||||
'resolver_key': 'advance_refund',
|
||||
'usn_recognition': 'refund_customer',
|
||||
'is_refund': True,
|
||||
'roles': {
|
||||
'bank': '51',
|
||||
'advance_received': '62.АВ',
|
||||
'vat_advance': '76.АВ',
|
||||
'vat_output': '68.НДС',
|
||||
},
|
||||
},
|
||||
'DEMO_PAY_SUPPLIER': {
|
||||
'name': 'Оплата поставщику',
|
||||
'resolver_key': 'supplier_payment',
|
||||
'usn_recognition': 'expense',
|
||||
'is_refund': False,
|
||||
'roles': {
|
||||
'bank': '51',
|
||||
'payable': '60',
|
||||
},
|
||||
},
|
||||
'DEMO_REFUND_SUPPLIER': {
|
||||
'name': 'Возврат от поставщика',
|
||||
'resolver_key': 'supplier_refund',
|
||||
'usn_recognition': 'refund_supplier',
|
||||
'is_refund': True,
|
||||
'roles': {
|
||||
'bank': '51',
|
||||
'payable': '60',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Правила матрицы: (name, doc_kind, partner_kind, has_advance, template_code, priority)
|
||||
MATRIX = [
|
||||
('Продажа клиенту', 'out_invoice', 'any', 'any', 'DEMO_SALE', 10),
|
||||
('Возврат от клиента', 'out_refund', 'any', 'any', 'DEMO_SALE_REFUND', 10),
|
||||
('Счёт поставщика', 'in_invoice', 'any', 'any', 'DEMO_PURCHASE', 10),
|
||||
('Возврат поставщику', 'in_refund', 'any', 'any', 'DEMO_PURCHASE_REFUND', 10),
|
||||
('Оплата от клиента', 'payment_inbound', 'customer', 'any',
|
||||
'DEMO_PAY_CUSTOMER', 10),
|
||||
('Возврат клиенту', 'payment_outbound', 'customer', 'any',
|
||||
'DEMO_REFUND_CUSTOMER', 10),
|
||||
('Оплата поставщику', 'payment_outbound', 'supplier', 'any',
|
||||
'DEMO_PAY_SUPPLIER', 10),
|
||||
('Возврат от поставщика', 'payment_inbound', 'supplier', 'any',
|
||||
'DEMO_REFUND_SUPPLIER', 10),
|
||||
]
|
||||
|
||||
|
||||
class RsbuDemoTemplatesLoader(models.AbstractModel):
|
||||
"""Загрузчик демо-шаблонов и матрицы связности по компаниям."""
|
||||
_name = 'rsbu.demo.templates.loader'
|
||||
_description = 'РСБУ — Загрузчик демо-шаблонов'
|
||||
|
||||
@api.model
|
||||
def _account(self, company, code):
|
||||
return self.env['rsbu.account'].search([
|
||||
('company_id', '=', company.id),
|
||||
('code', '=', code)], limit=1)
|
||||
|
||||
@api.model
|
||||
def load_for_company(self, company):
|
||||
Template = self.env['rsbu.operation.template']
|
||||
Matrix = self.env['rsbu.operation.matrix']
|
||||
code_to_tmpl = {}
|
||||
|
||||
for code, spec in TEMPLATES.items():
|
||||
existing = Template.search([
|
||||
('code', '=', code),
|
||||
('company_id', '=', company.id)], limit=1)
|
||||
if existing:
|
||||
code_to_tmpl[code] = existing
|
||||
continue
|
||||
role_cmds = []
|
||||
skip = False
|
||||
for role, acc_code in spec['roles'].items():
|
||||
account = self._account(company, acc_code)
|
||||
if not account:
|
||||
skip = True
|
||||
break
|
||||
role_cmds.append((0, 0, {
|
||||
'role': role, 'account_id': account.id}))
|
||||
if skip:
|
||||
continue
|
||||
tmpl = Template.create({
|
||||
'code': code,
|
||||
'name': spec['name'],
|
||||
'resolver_key': spec['resolver_key'],
|
||||
'usn_recognition': spec['usn_recognition'],
|
||||
'is_refund': spec['is_refund'],
|
||||
'company_id': company.id,
|
||||
'role_ids': role_cmds,
|
||||
})
|
||||
code_to_tmpl[code] = tmpl
|
||||
|
||||
for name, doc_kind, partner_kind, has_advance, tmpl_code, prio \
|
||||
in MATRIX:
|
||||
tmpl = code_to_tmpl.get(tmpl_code)
|
||||
if not tmpl:
|
||||
continue
|
||||
exists = Matrix.search([
|
||||
('company_id', '=', company.id),
|
||||
('doc_kind', '=', doc_kind),
|
||||
('partner_kind', '=', partner_kind),
|
||||
('template_id', '=', tmpl.id)], limit=1)
|
||||
if exists:
|
||||
continue
|
||||
Matrix.create({
|
||||
'name': name,
|
||||
'doc_kind': doc_kind,
|
||||
'partner_kind': partner_kind,
|
||||
'has_advance': has_advance,
|
||||
'template_id': tmpl.id,
|
||||
'priority': prio,
|
||||
'company_id': company.id,
|
||||
})
|
||||
1
rsbu_demo_templates/tests/__init__.py
Normal file
1
rsbu_demo_templates/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_demo_templates
|
||||
77
rsbu_demo_templates/tests/test_demo_templates.py
Normal file
77
rsbu_demo_templates/tests/test_demo_templates.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestDemoTemplates(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.company = cls.env.company
|
||||
cls.partner = cls.env['res.partner'].create({'name': 'Demo Клиент'})
|
||||
cls.journal_bank = cls.env['account.journal'].search(
|
||||
[('type', '=', 'bank'), ('company_id', '=', cls.company.id)],
|
||||
limit=1)
|
||||
|
||||
def _tmpl(self, code):
|
||||
return self.env['rsbu.operation.template'].search([
|
||||
('code', '=', code),
|
||||
('company_id', '=', self.company.id)], limit=1)
|
||||
|
||||
def test_all_templates_loaded(self):
|
||||
codes = ['DEMO_SALE', 'DEMO_SALE_REFUND', 'DEMO_PURCHASE',
|
||||
'DEMO_PURCHASE_REFUND', 'DEMO_PAY_CUSTOMER',
|
||||
'DEMO_REFUND_CUSTOMER', 'DEMO_PAY_SUPPLIER',
|
||||
'DEMO_REFUND_SUPPLIER']
|
||||
for code in codes:
|
||||
self.assertTrue(self._tmpl(code), 'Нет шаблона %s' % code)
|
||||
|
||||
def test_customer_payment_detects_template(self):
|
||||
pay = self.env['account.payment'].create({
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner.id,
|
||||
'amount': 1000.0,
|
||||
'journal_id': self.journal_bank.id,
|
||||
})
|
||||
self.assertEqual(pay._rsbu_detect_template().code, 'DEMO_PAY_CUSTOMER')
|
||||
|
||||
def test_supplier_payment_detects_template(self):
|
||||
pay = self.env['account.payment'].create({
|
||||
'payment_type': 'outbound',
|
||||
'partner_type': 'supplier',
|
||||
'partner_id': self.partner.id,
|
||||
'amount': 500.0,
|
||||
'journal_id': self.journal_bank.id,
|
||||
})
|
||||
self.assertEqual(pay._rsbu_detect_template().code, 'DEMO_PAY_SUPPLIER')
|
||||
|
||||
def test_supplier_refund_detects_template(self):
|
||||
# Входящий платёж от поставщика -> возврат от поставщика.
|
||||
pay = self.env['account.payment'].create({
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'supplier',
|
||||
'partner_id': self.partner.id,
|
||||
'amount': 200.0,
|
||||
'journal_id': self.journal_bank.id,
|
||||
})
|
||||
self.assertEqual(pay._rsbu_detect_template().code,
|
||||
'DEMO_REFUND_SUPPLIER')
|
||||
|
||||
def test_supplier_payment_full_flow(self):
|
||||
pay = self.env['account.payment'].create({
|
||||
'payment_type': 'outbound',
|
||||
'partner_type': 'supplier',
|
||||
'partner_id': self.partner.id,
|
||||
'amount': 500.0,
|
||||
'journal_id': self.journal_bank.id,
|
||||
})
|
||||
pay.action_post()
|
||||
move = pay.rsbu_move_id
|
||||
self.assertTrue(move)
|
||||
self.assertEqual(move.state, 'posted')
|
||||
# Дт 60 — Кт 51 на 500
|
||||
acc60 = self.env['rsbu.account'].search([
|
||||
('code', '=', '60'), ('company_id', '=', self.company.id)])
|
||||
bal = acc60.get_balance(company_id=self.company.id)
|
||||
self.assertEqual(bal['debit'], 500.0)
|
||||
Reference in New Issue
Block a user