186 lines
8.1 KiB
Python
186 lines
8.1 KiB
Python
|
||
from odoo.tests import tagged
|
||
from odoo.exceptions import UserError
|
||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||
|
||
|
||
@tagged('post_install', '-at_install')
|
||
class TestCommission(AccountTestInvoicingCommon):
|
||
|
||
@classmethod
|
||
def setUpClass(cls):
|
||
super().setUpClass()
|
||
# AccountTestInvoicingCommon работает под пользователем бухгалтерии
|
||
# в контексте тестовой компании, но без прав на продажи и комиссии.
|
||
# Добавляем нужные группы этому пользователю (компания сохраняется).
|
||
cls.env.user.group_ids |= (
|
||
cls.env.ref('sales_team.group_sale_salesman_all_leads')
|
||
| cls.env.ref('ruodoo_commission.group_commission_manager')
|
||
)
|
||
cls.company = cls.company_data['company']
|
||
cls.purchase_journal = cls.company_data['default_journal_purchase']
|
||
cls.expense_account = cls.company_data['default_account_expense']
|
||
|
||
cls.agent_a = cls.env['res.partner'].create({'name': 'Агент А'})
|
||
cls.agent_b = cls.env['res.partner'].create({'name': 'Агент Б'})
|
||
cls.customer = cls.env['res.partner'].create({'name': 'Покупатель ООО'})
|
||
cls.product = cls.env['product.product'].create({
|
||
'name': 'Товар А', 'type': 'consu', 'lst_price': 100.0,
|
||
})
|
||
|
||
cls.commission_pct = cls.env['commission.commission'].create({
|
||
'name': 'Комиссия 10%',
|
||
'calc_type': 'percentage',
|
||
'percentage': 10.0,
|
||
'partner_id': cls.agent_a.id,
|
||
'journal_id': cls.purchase_journal.id,
|
||
'account_id': cls.expense_account.id,
|
||
})
|
||
cls.commission_code = cls.env['commission.commission'].create({
|
||
'name': 'Комиссия по формуле',
|
||
'calc_type': 'code',
|
||
'amount_python_compute': 'result = price_subtotal * 0.05',
|
||
'partner_id': cls.agent_b.id,
|
||
'journal_id': cls.purchase_journal.id,
|
||
'account_id': cls.expense_account.id,
|
||
})
|
||
|
||
def _make_order(self, commissions=None, price_unit=1000.0, qty=1):
|
||
line_vals = {
|
||
'product_id': self.product.id,
|
||
'product_uom_qty': qty,
|
||
'price_unit': price_unit,
|
||
}
|
||
if commissions:
|
||
line_vals['commission_ids'] = [(6, 0, [c.id for c in commissions])]
|
||
return self.env['sale.order'].create({
|
||
'partner_id': self.customer.id,
|
||
'order_line': [(0, 0, line_vals)],
|
||
})
|
||
|
||
# ---------------- Расчёт ----------------
|
||
def test_percentage_amount(self):
|
||
order = self._make_order([self.commission_pct], price_unit=1000.0)
|
||
line = order.order_line
|
||
# 10% от price_subtotal (1000) = 100
|
||
self.assertAlmostEqual(line.commission_amount, 100.0)
|
||
|
||
def test_code_formula_amount(self):
|
||
order = self._make_order([self.commission_code], price_unit=1000.0)
|
||
# 5% от 1000 = 50
|
||
self.assertAlmostEqual(order.order_line.commission_amount, 50.0)
|
||
|
||
def test_multiple_commissions_on_line(self):
|
||
order = self._make_order(
|
||
[self.commission_pct, self.commission_code], price_unit=1000.0,
|
||
)
|
||
# 100 + 50 = 150
|
||
self.assertAlmostEqual(order.order_line.commission_amount, 150.0)
|
||
self.assertAlmostEqual(order.amount_commission, 150.0)
|
||
|
||
def test_commission_not_in_amount_total(self):
|
||
order = self._make_order([self.commission_pct], price_unit=1000.0)
|
||
# amount_total не должен включать комиссию
|
||
self.assertNotEqual(order.amount_total, 0.0)
|
||
self.assertAlmostEqual(order.amount_total, order.amount_untaxed + order.amount_tax)
|
||
|
||
def test_bad_formula_raises(self):
|
||
bad = self.env['commission.commission'].create({
|
||
'name': 'Плохая формула',
|
||
'calc_type': 'code',
|
||
'amount_python_compute': 'result = undefined_var + 1',
|
||
'partner_id': self.agent_a.id,
|
||
'journal_id': self.purchase_journal.id,
|
||
'account_id': self.expense_account.id,
|
||
})
|
||
# Сумма комиссии — хранимое вычисляемое поле, ошибка формулы
|
||
# проявляется при вычислении (создании строки заказа).
|
||
with self.assertRaises(UserError):
|
||
order = self._make_order([bad], price_unit=1000.0)
|
||
order.order_line._compute_commission_amount()
|
||
|
||
# ---------------- Начисление ----------------
|
||
def test_accrue_creates_posted_bills_by_partner(self):
|
||
order = self._make_order(
|
||
[self.commission_pct, self.commission_code], price_unit=1000.0,
|
||
)
|
||
order.action_confirm()
|
||
self.assertEqual(order.state, 'sale')
|
||
order.action_accrue_commission()
|
||
|
||
bills = order.commission_bill_ids
|
||
# Два разных получателя -> два билла
|
||
self.assertEqual(len(bills), 2)
|
||
self.assertEqual(set(bills.mapped('move_type')), {'in_invoice'})
|
||
self.assertEqual(set(bills.mapped('state')), {'posted'})
|
||
self.assertEqual(bills.mapped('commission_sale_order_id'), order)
|
||
|
||
bill_a = bills.filtered(lambda m: m.partner_id == self.agent_a)
|
||
bill_b = bills.filtered(lambda m: m.partner_id == self.agent_b)
|
||
self.assertAlmostEqual(bill_a.amount_total, 100.0)
|
||
self.assertAlmostEqual(bill_b.amount_total, 50.0)
|
||
# Счёт расходов проставлен на строках
|
||
self.assertEqual(
|
||
set(bill_a.invoice_line_ids.mapped('account_id')),
|
||
{self.expense_account},
|
||
)
|
||
|
||
def test_accrue_requires_confirmed(self):
|
||
order = self._make_order([self.commission_pct])
|
||
with self.assertRaises(UserError):
|
||
order.action_accrue_commission()
|
||
|
||
def test_accrue_dedup(self):
|
||
order = self._make_order([self.commission_pct])
|
||
order.action_confirm()
|
||
order.action_accrue_commission()
|
||
with self.assertRaises(UserError):
|
||
order.action_accrue_commission()
|
||
|
||
def test_accrue_skips_zero(self):
|
||
zero = self.env['commission.commission'].create({
|
||
'name': 'Комиссия 0%',
|
||
'calc_type': 'percentage', 'percentage': 0.0,
|
||
'partner_id': self.agent_a.id,
|
||
'journal_id': self.purchase_journal.id,
|
||
'account_id': self.expense_account.id,
|
||
})
|
||
order = self._make_order([zero])
|
||
order.action_confirm()
|
||
with self.assertRaises(UserError):
|
||
order.action_accrue_commission()
|
||
self.assertFalse(order.commission_bill_ids)
|
||
|
||
# ---------------- Отмена заказа ----------------
|
||
def test_cancel_order_removes_bills(self):
|
||
order = self._make_order([self.commission_pct])
|
||
order.action_confirm()
|
||
order.action_accrue_commission()
|
||
self.assertTrue(order.commission_bill_ids)
|
||
bill_ids = order.commission_bill_ids.ids
|
||
|
||
order.action_cancel()
|
||
self.assertEqual(order.state, 'cancel')
|
||
# Биллы удалены
|
||
remaining = self.env['account.move'].search([('id', 'in', bill_ids)])
|
||
self.assertFalse(remaining)
|
||
|
||
def test_cancel_order_no_bills_ok(self):
|
||
order = self._make_order([self.commission_pct])
|
||
order.action_confirm()
|
||
# Без начисления отмена не должна падать
|
||
order.action_cancel()
|
||
self.assertEqual(order.state, 'cancel')
|
||
|
||
# ---------------- Смарт-кнопка ----------------
|
||
def test_bill_count_and_action(self):
|
||
order = self._make_order(
|
||
[self.commission_pct, self.commission_code],
|
||
)
|
||
order.action_confirm()
|
||
order.action_accrue_commission()
|
||
self.assertEqual(order.commission_bill_count, 2)
|
||
action = order.action_view_commission_bills()
|
||
self.assertEqual(action['res_model'], 'account.move')
|
||
self.assertIn('commission_sale_order_id', str(action['domain']))
|