Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
4
l10n_ru_contract_account/models/__init__.py
Normal file
4
l10n_ru_contract_account/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import partner_contract_customer
|
||||
from . import contract_profile
|
||||
from . import account_move
|
||||
from . import account_payment
|
||||
127
l10n_ru_contract_account/models/account_move.py
Normal file
127
l10n_ru_contract_account/models/account_move.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from odoo import api, fields, models, exceptions, _
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
mt_contract_id = fields.Many2one(
|
||||
'partner.contract.customer', string=_('Номер договора'),
|
||||
compute='_compute_mt_contract_id', store=True, readonly=False)
|
||||
sf_number = fields.Char(string=_('Номер с/ф'))
|
||||
osnovanie = fields.Char(string=_('Основание'))
|
||||
sec_partner_id = fields.Many2one('res.partner', string=_('Контрагент'), store=True, compute='_compute_get_pid')
|
||||
stamp = fields.Boolean(string=_('Печать и подпись'), related='mt_contract_id.stamp')
|
||||
|
||||
@api.depends('partner_id')
|
||||
def _compute_get_pid(self):
|
||||
for s in self:
|
||||
s.sec_partner_id = s.partner_id.parent_id if s.partner_id.parent_id else s.partner_id
|
||||
|
||||
@api.depends('partner_id', 'company_id', 'move_type')
|
||||
def _compute_mt_contract_id(self):
|
||||
"""Подставить договор клиента по умолчанию.
|
||||
|
||||
Подстановка выполняется для документов продаж (счёт/возврат клиента):
|
||||
берём действующий (signed) клиентский договор партнёра в рамках
|
||||
компании. Для документов покупки договор не подставляется — профиль
|
||||
«Основной договор» ориентирован на журнал продаж. Уже выбранный
|
||||
вручную договор не перезаписываем.
|
||||
"""
|
||||
Contract = self.env['partner.contract.customer']
|
||||
for move in self:
|
||||
if move.mt_contract_id:
|
||||
continue
|
||||
if move.move_type not in ('out_invoice', 'out_refund'):
|
||||
move.mt_contract_id = False
|
||||
continue
|
||||
if not move.partner_id:
|
||||
move.mt_contract_id = False
|
||||
continue
|
||||
|
||||
partner = move.partner_id.commercial_partner_id or move.partner_id
|
||||
domain = [
|
||||
('partner_id', '=', partner.id),
|
||||
('type', '=', 'customer'),
|
||||
('is_template', '=', False),
|
||||
]
|
||||
if move.company_id:
|
||||
domain.append(('company_id', '=', move.company_id.id))
|
||||
contract = Contract.search(
|
||||
domain + [('state', '=', 'signed')],
|
||||
order='date_start desc', limit=1,
|
||||
) or Contract.search(domain, order='date_start desc', limit=1)
|
||||
move.mt_contract_id = contract.id if contract else False
|
||||
|
||||
@api.onchange('mt_contract_id')
|
||||
def set_ons(self):
|
||||
"""Синхронизировать поля инвойса с параметрами договора (в UI).
|
||||
|
||||
Заполняем основание, журнал и условие оплаты из профиля договора,
|
||||
чтобы пользователь сразу видел согласованные значения.
|
||||
"""
|
||||
for s in self:
|
||||
if s.mt_contract_id:
|
||||
s._apply_contract_fields()
|
||||
|
||||
def _apply_contract_fields(self):
|
||||
"""Привести журнал/условие оплаты/основание в соответствие договору.
|
||||
|
||||
Вызывается и в onchange (UI), и перед проведением (_post) — чтобы
|
||||
согласованность не зависела от способа создания инвойса
|
||||
(вручную, из заказа продаж или программно из демо-данных).
|
||||
"""
|
||||
for s in self:
|
||||
contract = s.mt_contract_id
|
||||
if not contract:
|
||||
continue
|
||||
s.osnovanie = 'Договор № ' + (contract.name or '') + ' от ' + fields.Datetime.from_string(
|
||||
contract.date_start).strftime("%d.%m.%Y")
|
||||
profile = contract.profile_id
|
||||
# Журнал/условие оплаты из профиля договора применяем только к
|
||||
# документам продаж; для покупок (in_invoice/in_refund) журнал
|
||||
# определяется типом документа, профиль клиентского договора его
|
||||
# не переопределяет.
|
||||
if s.move_type in ('out_invoice', 'out_refund'):
|
||||
if profile.journal_id and s.journal_id != profile.journal_id:
|
||||
s.journal_id = profile.journal_id
|
||||
if profile.payment_term_id and \
|
||||
s.invoice_payment_term_id != profile.payment_term_id:
|
||||
s.invoice_payment_term_id = profile.payment_term_id
|
||||
|
||||
def _post(self, soft=True):
|
||||
# Синхронизировать поля с договором до валидации invoice_fields_check.
|
||||
for move in self:
|
||||
if move.mt_contract_id and move.state == 'draft':
|
||||
move._apply_contract_fields()
|
||||
return super()._post(soft=soft)
|
||||
|
||||
@api.constrains('state')
|
||||
def invoice_fields_check(self):
|
||||
for s in self:
|
||||
if s.state == 'posted':
|
||||
# Сверку с параметрами договора выполняем только для продаж;
|
||||
# покупки не обязаны совпадать с профилем клиентского договора.
|
||||
if s.mt_contract_id and s.move_type in (
|
||||
'out_invoice', 'out_refund'):
|
||||
errors_list = []
|
||||
journal_in_contract = s.mt_contract_id.profile_id.journal_id
|
||||
payment_term_in_contract = s.mt_contract_id.profile_id.payment_term_id
|
||||
receivable_in_contract = s.mt_contract_id.profile_id.receivable_account_id
|
||||
|
||||
if journal_in_contract != s.journal_id:
|
||||
errors_list.append(f'Отличается Журнал - [{s.journal_id.name}] '
|
||||
f'и указанный в договоре №{s.mt_contract_id.name} '
|
||||
f'Журнал - [{journal_in_contract.name}]\n\n')
|
||||
|
||||
if payment_term_in_contract != s.invoice_payment_term_id:
|
||||
errors_list.append(f'Отличается поле "Условие оплаты" в инвойсе '
|
||||
f'[Условие оплаты - {s.invoice_payment_term_id.name}] '
|
||||
f'и указанный в договоре №{s.mt_contract_id.name} '
|
||||
f'[Условие оплаты - {payment_term_in_contract.name}]\n\n')
|
||||
|
||||
if receivable_in_contract not in s.line_ids.account_id:
|
||||
errors_list.append(f'Отличается поле "Счет дебиторской задолженности" в инвойсе '
|
||||
f'и указанный в договоре №{s.mt_contract_id.name}')
|
||||
|
||||
if errors_list:
|
||||
raise exceptions.ValidationError(''.join(errors_list))
|
||||
43
l10n_ru_contract_account/models/account_payment.py
Normal file
43
l10n_ru_contract_account/models/account_payment.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = 'account.payment'
|
||||
|
||||
mt_contract_id = fields.Many2one(
|
||||
'partner.contract.customer', string=_('Номер договора'),
|
||||
compute='_compute_mt_contract_id', store=True, readonly=False,
|
||||
help='Договор-основание платежа. Используется для ведения расчётов '
|
||||
'с контрагентом в разрезе договора (в т.ч. в РСБУ-слое).')
|
||||
|
||||
@api.depends('partner_id', 'company_id', 'partner_type')
|
||||
def _compute_mt_contract_id(self):
|
||||
"""Подставить договор контрагента по умолчанию.
|
||||
|
||||
Для клиентских платежей — действующий (signed) клиентский договор
|
||||
партнёра в рамках компании; для платежей поставщикам — договор с
|
||||
поставщиком. Выбранный вручную договор не перезаписываем.
|
||||
"""
|
||||
Contract = self.env['partner.contract.customer']
|
||||
for payment in self:
|
||||
if payment.mt_contract_id:
|
||||
continue
|
||||
if not payment.partner_id:
|
||||
payment.mt_contract_id = False
|
||||
continue
|
||||
partner = payment.partner_id.commercial_partner_id \
|
||||
or payment.partner_id
|
||||
contract_type = 'customer' if payment.partner_type == 'customer' \
|
||||
else 'supplier'
|
||||
domain = [
|
||||
('partner_id', '=', partner.id),
|
||||
('type', '=', contract_type),
|
||||
('is_template', '=', False),
|
||||
]
|
||||
if payment.company_id:
|
||||
domain.append(('company_id', '=', payment.company_id.id))
|
||||
contract = Contract.search(
|
||||
domain + [('state', '=', 'signed')],
|
||||
order='date_start desc', limit=1,
|
||||
) or Contract.search(domain, order='date_start desc', limit=1)
|
||||
payment.mt_contract_id = contract.id if contract else False
|
||||
11
l10n_ru_contract_account/models/contract_profile.py
Normal file
11
l10n_ru_contract_account/models/contract_profile.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class ContractProfile(models.Model):
|
||||
_inherit = 'contract.profile'
|
||||
|
||||
payable_account_id = fields.Many2one('account.account', string=_('Счет кредиторской задолженности'), required=True)
|
||||
receivable_account_id = fields.Many2one('account.account', string=_('Счет дебиторской задолженности'), required=True)
|
||||
max_receivable_id = fields.Float(string=_('Максимальная деб. задолженность'), required=True)
|
||||
payment_term_id = fields.Many2one('account.payment.term', string=_('Условие оплаты'), required=True)
|
||||
journal_id = fields.Many2one('account.journal', string=_('Журнал'), required=True)
|
||||
22
l10n_ru_contract_account/models/partner_contract_customer.py
Normal file
22
l10n_ru_contract_account/models/partner_contract_customer.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from odoo import fields, models, _
|
||||
|
||||
class PartnerContractCustomer(models.Model):
|
||||
_inherit = "partner.contract.customer"
|
||||
|
||||
sec_partner_id = fields.Many2one(
|
||||
"res.partner",
|
||||
string=_("Контрагент как в заказе"),
|
||||
)
|
||||
accountant_id = fields.Many2one(
|
||||
"res.users",
|
||||
string=_("Бухгалтер по взаиморасчетам"),
|
||||
)
|
||||
buh_code = fields.Char(
|
||||
string=_("Код договора из бухгалтерии"),
|
||||
)
|
||||
payment_term_id = fields.Many2one(
|
||||
"account.payment.term",
|
||||
string=_("Условие оплаты"),
|
||||
related="profile_id.payment_term_id",
|
||||
readonly=False,
|
||||
)
|
||||
Reference in New Issue
Block a user