142 lines
6.3 KiB
Python
142 lines
6.3 KiB
Python
|
||
from odoo import fields, models, _
|
||
from odoo.exceptions import UserError
|
||
from odoo.tools.safe_eval import safe_eval
|
||
|
||
# Подсказка для поля формулы: доступные переменные и обязательный результат.
|
||
CODE_HELP = (
|
||
"Python-код расчёта комиссии. Результат присвоить переменной `result`.\n"
|
||
"Доступные переменные:\n"
|
||
" line — строка заказа (sale.order.line)\n"
|
||
" order — заказ (sale.order)\n"
|
||
" product — товар строки (product.product)\n"
|
||
" qty — количество (product_uom_qty)\n"
|
||
" price_unit — цена за единицу\n"
|
||
" price_subtotal — сумма строки без налогов\n"
|
||
" partner — контрагент заказа (res.partner)\n"
|
||
" commission — текущий вид комиссии (commission.commission)\n"
|
||
"Пример: result = price_subtotal * 0.05"
|
||
)
|
||
|
||
DEFAULT_CODE = "result = price_subtotal * 0.0"
|
||
|
||
|
||
class CommissionCommission(models.Model):
|
||
_name = 'commission.commission'
|
||
_description = 'Вид комиссии агенту'
|
||
_order = 'name'
|
||
|
||
name = fields.Char(string='Наименование', required=True, translate=True)
|
||
active = fields.Boolean(string='Активно', default=True)
|
||
company_id = fields.Many2one(
|
||
'res.company', string='Компания', required=True,
|
||
default=lambda self: self.env.company,
|
||
)
|
||
|
||
calc_type = fields.Selection(
|
||
selection=[
|
||
('percentage', 'Процент'),
|
||
('code', 'Формула'),
|
||
],
|
||
string='Способ расчёта', required=True, default='percentage',
|
||
)
|
||
percentage = fields.Float(
|
||
string='Процент, %', digits=(16, 4),
|
||
help='Процент от суммы строки без налогов (для способа «Процент»).',
|
||
)
|
||
amount_python_compute = fields.Text(
|
||
string='Формула (Python)', default=DEFAULT_CODE, help=CODE_HELP,
|
||
)
|
||
|
||
# --- Настройки будущих проводок вендор-билла ---
|
||
journal_id = fields.Many2one(
|
||
'account.journal', string='Журнал', required=True,
|
||
domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]",
|
||
help='Журнал для счетов поставщика (Vendor Bill) по комиссии.',
|
||
)
|
||
account_id = fields.Many2one(
|
||
'account.account', string='Счёт расходов', required=True,
|
||
domain="[('account_type', 'in', ('expense', 'expense_depreciation', 'expense_direct_cost'))]",
|
||
help='Счёт расходов (тип «Расходы»), на котором копятся комиссии.',
|
||
)
|
||
partner_id = fields.Many2one(
|
||
'res.partner', string='Получатель', required=True,
|
||
help='Партнёр-агент, на кого выставляется вендор-билл по комиссии.',
|
||
)
|
||
analytic_account_id = fields.Many2one(
|
||
'account.analytic.account', string='Аналитический счёт',
|
||
help='Опциональный аналитический счёт для строки вендор-билла.',
|
||
)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Расчёт суммы комиссии
|
||
# ------------------------------------------------------------------
|
||
def _get_eval_context(self, line):
|
||
"""Ограниченный контекст выполнения формулы комиссии."""
|
||
self.ensure_one()
|
||
return {
|
||
'line': line,
|
||
'order': line.order_id,
|
||
'product': line.product_id,
|
||
'qty': line.product_uom_qty,
|
||
'price_unit': line.price_unit,
|
||
'price_subtotal': line.price_subtotal,
|
||
'partner': line.order_id.partner_id,
|
||
'commission': self,
|
||
'result': 0.0,
|
||
}
|
||
|
||
def _compute_amount(self, line):
|
||
"""Сумма комиссии для конкретной строки заказа.
|
||
|
||
:param line: sale.order.line
|
||
:return: float — сумма комиссии в валюте заказа
|
||
"""
|
||
self.ensure_one()
|
||
if self.calc_type == 'percentage':
|
||
return line.price_subtotal * (self.percentage or 0.0) / 100.0
|
||
|
||
# calc_type == 'code' — формула result = ...
|
||
localdict = self._get_eval_context(line)
|
||
try:
|
||
safe_eval(
|
||
self.amount_python_compute or '',
|
||
localdict, mode='exec',
|
||
)
|
||
except Exception as ex:
|
||
raise UserError(_(
|
||
"Ошибка в формуле комиссии «%(name)s»:\n%(err)s",
|
||
name=self.name, err=ex,
|
||
)) from ex
|
||
try:
|
||
return float(localdict.get('result', 0.0))
|
||
except (TypeError, ValueError):
|
||
raise UserError(_(
|
||
"Формула комиссии «%(name)s» вернула нечисловой результат.",
|
||
name=self.name,
|
||
)) from None
|
||
|
||
def _prepare_bill_line_vals(self, line, amount):
|
||
"""Значения строки вендор-билла для этой комиссии по строке заказа.
|
||
|
||
:param line: sale.order.line — исходная строка заказа
|
||
:param amount: float — рассчитанная сумма комиссии
|
||
:return: dict значений для account.move.line (invoice_line_ids)
|
||
"""
|
||
self.ensure_one()
|
||
vals = {
|
||
'name': _(
|
||
"Комиссия «%(commission)s» по %(order)s / %(product)s",
|
||
commission=self.name,
|
||
order=line.order_id.name,
|
||
product=line.product_id.display_name or line.name,
|
||
),
|
||
'account_id': self.account_id.id,
|
||
'quantity': 1.0,
|
||
'price_unit': amount,
|
||
}
|
||
if self.analytic_account_id:
|
||
# analytic_distribution — JSON {id_счёта: процент}, сумма = 100%.
|
||
vals['analytic_distribution'] = {str(self.analytic_account_id.id): 100.0}
|
||
return vals
|