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