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))