Files
public/ruodoo_commission/models/sale_order.py

186 lines
8.1 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 import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare
class SaleOrder(models.Model):
_inherit = 'sale.order'
amount_commission = fields.Monetary(
string='ИТОГО КОМИССИЙ', compute='_compute_amount_commission',
store=True, currency_field='currency_id',
help='Суммарные агентские комиссии по заказу. '
'Не входит в итог заказа — это забалансовый агентский расход.',
)
commission_bill_ids = fields.One2many(
'account.move', 'commission_sale_order_id',
string='Счета комиссий',
)
commission_bill_count = fields.Integer(
string='Кол-во счетов комиссий',
compute='_compute_commission_bill_count',
)
@api.depends('order_line.commission_amount')
def _compute_amount_commission(self):
for order in self:
order.amount_commission = sum(
order.order_line.mapped('commission_amount')
)
@api.depends('commission_bill_ids')
def _compute_commission_bill_count(self):
for order in self:
order.commission_bill_count = len(order.commission_bill_ids)
# ------------------------------------------------------------------
# Начисление комиссии → счета поставщика (Vendor Bill)
# ------------------------------------------------------------------
def action_accrue_commission(self):
"""Сформировать и провести вендор-биллы по комиссиям заказа.
Обходим строки → привязанные комиссии, группируем строки будущих
биллов по (партнёр-получатель, журнал), создаём один или несколько
account.move (in_invoice) и проводим их.
"""
AccountMove = self.env['account.move']
created = AccountMove
for order in self:
if order.state != 'sale':
raise UserError(_(
"Начислять комиссию можно только по подтверждённому заказу."
))
# Дедупликация: не начисляем повторно при наличии активных биллов.
existing = order.commission_bill_ids.filtered(
lambda m: m.state != 'cancel'
)
if existing:
raise UserError(_(
"По заказу %(name)s комиссии уже начислены. "
"Отмените или удалите существующие счета комиссий.",
name=order.name,
))
buckets = order._prepare_commission_bill_buckets()
for (partner, journal), line_vals_list in buckets.items():
move = AccountMove.with_company(order.company_id).create({
'move_type': 'in_invoice',
'partner_id': partner.id,
'journal_id': journal.id,
'invoice_date': fields.Date.context_today(order),
'invoice_origin': order.name,
'commission_sale_order_id': order.id,
'invoice_line_ids': [
(0, 0, vals) for vals in line_vals_list
],
})
move.action_post()
created |= move
if not created:
raise UserError(_(
"По заказу нет комиссий с положительной суммой для начисления."
))
return self._action_open_commission_bills(created)
def _prepare_commission_bill_buckets(self):
"""Справочник строк биллов, сгруппированный по (получатель, журнал).
:return: dict {(partner, journal): [invoice_line_vals, ...]}
"""
self.ensure_one()
buckets = {}
precision = self.currency_id.rounding or 0.01
for line in self.order_line:
for commission in line.commission_ids:
amount = commission._compute_amount(line)
if float_compare(amount, 0.0, precision_rounding=precision) <= 0:
continue
key = (commission.partner_id, commission.journal_id)
buckets.setdefault(key, []).append(
commission._prepare_bill_line_vals(line, amount)
)
return buckets
def _action_open_commission_bills(self, moves):
"""Действие: открыть сформированные счета комиссий."""
self.ensure_one()
action = {
'name': _('Счета комиссий'),
'type': 'ir.actions.act_window',
'res_model': 'account.move',
'context': {'create': False},
}
if len(moves) == 1:
action.update({
'view_mode': 'form',
'res_id': moves.id,
})
else:
action.update({
'view_mode': 'list,form',
'domain': [('id', 'in', moves.ids)],
})
return action
def action_view_commission_bills(self):
"""Смарт-кнопка «Комиссии»: связанные вендор-биллы заказа."""
self.ensure_one()
return {
'name': _('Комиссии'),
'type': 'ir.actions.act_window',
'res_model': 'account.move',
'view_mode': 'list,form',
'domain': [('commission_sale_order_id', '=', self.id)],
'context': {
'create': False,
'default_move_type': 'in_invoice',
},
}
# ------------------------------------------------------------------
# Отмена заказа → каскад на счета комиссий
# ------------------------------------------------------------------
def action_cancel(self):
res = super().action_cancel()
for order in self:
bills = order.commission_bill_ids.filtered(
lambda m: m.state != 'cancel'
)
if bills:
order._cancel_commission_bills(bills)
return res
def _cancel_commission_bills(self, bills):
"""Отменить счета комиссий: отвязать платежи → черновик → удалить."""
self.ensure_one()
for bill in bills:
self._detach_bill_payments(bill)
if bill.state == 'posted':
bill.button_draft()
# Из черновика документ можно удалить штатно.
bill.unlink()
def _detach_bill_payments(self, bill):
"""Отвязать платежи от счёта комиссии штатными средствами.
Если ядро не позволяет отвязать платёж — сообщаем понятной ошибкой,
деструктивных обходов ядра не выполняем.
"""
self.ensure_one()
# Строки счёта, участвующие в сопоставлении с платежами.
reconciled_lines = bill.line_ids.filtered(
lambda ln: ln.matched_debit_ids or ln.matched_credit_ids
)
if not reconciled_lines:
return
try:
reconciled_lines.remove_move_reconcile()
except Exception as ex:
raise UserError(_(
"Не удалось отвязать платежи от счёта комиссии %(name)s. "
"Сначала отмените сопоставление платежей вручную.\n%(err)s",
name=bill.name or bill.display_name, err=ex,
)) from ex