mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Merge branch 'imp/15.0/H11044_hr_commission__margin_threshold_no_commission_product' into 15.0
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
'views/commission_views.xml',
|
'views/commission_views.xml',
|
||||||
'views/hr_views.xml',
|
'views/hr_views.xml',
|
||||||
'views/partner_views.xml',
|
'views/partner_views.xml',
|
||||||
|
'views/product_views.xml',
|
||||||
'views/res_config_settings_views.xml',
|
'views/res_config_settings_views.xml',
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ from . import account
|
|||||||
from . import commission
|
from . import commission
|
||||||
from . import hr
|
from . import hr
|
||||||
from . import partner
|
from . import partner
|
||||||
|
from . import product_template
|
||||||
from . import res_company
|
from . import res_company
|
||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
|
|||||||
@@ -37,15 +37,32 @@ class AccountMove(models.Model):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def amount_for_commission(self, commission=None):
|
def amount_for_commission(self, commission=None):
|
||||||
|
# Override to exclude ineligible products
|
||||||
|
amount = 0.0
|
||||||
|
invoice_lines = self.invoice_line_ids.filtered(lambda l: not l.product_id.is_commission_exempt)
|
||||||
|
sign = -1 if self.move_type in ['in_refund', 'out_refund'] else 1
|
||||||
if hasattr(self, 'margin') and self.company_id.commission_amount_type == 'on_invoice_margin':
|
if hasattr(self, 'margin') and self.company_id.commission_amount_type == 'on_invoice_margin':
|
||||||
sign = -1 if self.move_type in ['in_refund', 'out_refund'] else 1
|
margin_threshold = float(self.env['ir.config_parameter'].sudo().get_param('commission.margin.threshold', 0.0))
|
||||||
return self.margin * sign
|
if margin_threshold:
|
||||||
|
invoice_lines = invoice_lines.filtered(lambda l: l.get_margin_percent() > margin_threshold)
|
||||||
|
amount = sum(invoice_lines.mapped('margin'))
|
||||||
elif self.company_id.commission_amount_type == 'on_invoice_untaxed':
|
elif self.company_id.commission_amount_type == 'on_invoice_untaxed':
|
||||||
return self.amount_untaxed_signed
|
amount = sum(invoice_lines.mapped('price_subtotal'))
|
||||||
return self.amount_total_signed
|
else:
|
||||||
|
amount = sum(invoice_lines.mapped('price_total'))
|
||||||
|
return amount * sign
|
||||||
|
|
||||||
def action_cancel(self):
|
def action_cancel(self):
|
||||||
res = super(AccountMove, self).action_cancel()
|
res = super(AccountMove, self).action_cancel()
|
||||||
for move in self:
|
for move in self:
|
||||||
move.sudo().commission_ids.unlink()
|
move.sudo().commission_ids.unlink()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class AccountMoveLine(models.Model):
|
||||||
|
_inherit = 'account.move.line'
|
||||||
|
|
||||||
|
def get_margin_percent(self):
|
||||||
|
if not self.price_subtotal:
|
||||||
|
return 0.0
|
||||||
|
return ((self.margin or 0.0) / self.price_subtotal) * 100.0
|
||||||
|
|||||||
7
hr_commission/models/product_template.py
Normal file
7
hr_commission/models/product_template.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ProductTemplate(models.Model):
|
||||||
|
_inherit = 'product.template'
|
||||||
|
|
||||||
|
is_commission_exempt = fields.Boolean('Exclude from Commissions')
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
from odoo import fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class ResConfigSettings(models.TransientModel):
|
class ResConfigSettings(models.TransientModel):
|
||||||
@@ -10,3 +10,15 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
commission_liability_id = fields.Many2one(related='company_id.commission_liability_id', readonly=False)
|
commission_liability_id = fields.Many2one(related='company_id.commission_liability_id', readonly=False)
|
||||||
commission_type = fields.Selection(related='company_id.commission_type', readonly=False)
|
commission_type = fields.Selection(related='company_id.commission_type', readonly=False)
|
||||||
commission_amount_type = fields.Selection(related='company_id.commission_amount_type', readonly=False)
|
commission_amount_type = fields.Selection(related='company_id.commission_amount_type', readonly=False)
|
||||||
|
commission_margin_threshold = fields.Float()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_values(self):
|
||||||
|
res = super(ResConfigSettings, self).get_values()
|
||||||
|
margin_threshold = float(self.env['ir.config_parameter'].sudo().get_param('commission.margin.threshold', default=0.0))
|
||||||
|
res.update(commission_margin_threshold=margin_threshold)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def set_values(self):
|
||||||
|
super(ResConfigSettings, self).set_values()
|
||||||
|
self.env['ir.config_parameter'].sudo().set_param('commission.margin.threshold', self.commission_margin_threshold)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
from . import test_commission
|
from . import test_commission
|
||||||
|
from . import test_is_commission_exempt
|
||||||
|
|||||||
156
hr_commission/tests/test_is_commission_exempt.py
Normal file
156
hr_commission/tests/test_is_commission_exempt.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
from odoo.tests import common
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TestIsCommissionExempt(common.TransactionCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
# find and configure company commissions journal
|
||||||
|
expense_user_type = self.env['account.account.type'].search([('name', '=', 'Expenses')], limit=1)
|
||||||
|
self.assertTrue(expense_user_type)
|
||||||
|
expense_account = self.env['account.account'].search([('user_type_id', '=', expense_user_type.id),
|
||||||
|
('company_id', '=', self.env.user.company_id.id)], limit=1)
|
||||||
|
self.assertTrue(expense_account)
|
||||||
|
commission_journal = self.env['account.journal'].search([
|
||||||
|
('type', '=', 'general'),
|
||||||
|
('company_id', '=', expense_account.company_id.id),
|
||||||
|
], limit=1)
|
||||||
|
self.assertTrue(commission_journal)
|
||||||
|
commission_journal.default_account_id = expense_account
|
||||||
|
commission_journal.default_account_id = expense_account
|
||||||
|
self.env.user.company_id.commission_journal_id = commission_journal
|
||||||
|
self.env.user.company_id.commission_type = 'on_invoice'
|
||||||
|
|
||||||
|
self.sales_user = self.browse_ref('base.user_demo')
|
||||||
|
self.customer_partner = self.browse_ref('base.res_partner_12')
|
||||||
|
|
||||||
|
self.sales_employee = self.sales_user.employee_id
|
||||||
|
self.sales_employee.write({
|
||||||
|
'address_home_id': self.sales_user.partner_id,
|
||||||
|
'contract_ids': [(0, 0, {
|
||||||
|
'date_start': '2016-01-01',
|
||||||
|
'date_end': '2030-12-31',
|
||||||
|
'name': 'Contract for tests',
|
||||||
|
'wage': 1000.0,
|
||||||
|
# 'type_id': self.ref('hr_contract.hr_contract_type_emp'),
|
||||||
|
'employee_id': self.sales_employee.id,
|
||||||
|
'resource_calendar_id': self.ref('resource.resource_calendar_std'),
|
||||||
|
'commission_rate': 10.0,
|
||||||
|
'state': 'open', # if not "Running" then no automatic selection when Payslip is created in 11.0
|
||||||
|
})],
|
||||||
|
})
|
||||||
|
|
||||||
|
self.product = self.env['product.product'].create({
|
||||||
|
'name': 'Test Product',
|
||||||
|
'invoice_policy': 'order',
|
||||||
|
'taxes_id': [],
|
||||||
|
})
|
||||||
|
self.product_is_commission_exempt = self.env['product.product'].create({
|
||||||
|
'name': 'Test Product No Commission',
|
||||||
|
'invoice_policy': 'order',
|
||||||
|
'is_commission_exempt': True,
|
||||||
|
'taxes_id': [],
|
||||||
|
})
|
||||||
|
|
||||||
|
def _createSaleOrder(self):
|
||||||
|
order = self.env['sale.order'].create({
|
||||||
|
'partner_id': self.customer_partner.id,
|
||||||
|
'user_id': self.sales_user.id,
|
||||||
|
'order_line': [(0, 0, {
|
||||||
|
'name': 'test product',
|
||||||
|
'product_id': self.product.id,
|
||||||
|
'product_uom_qty': 1.0,
|
||||||
|
'product_uom': self.product.uom_id.id,
|
||||||
|
'price_unit': 100.0,
|
||||||
|
'tax_id': False,
|
||||||
|
}), (0, 0, {
|
||||||
|
'name': 'test product no commission',
|
||||||
|
'product_id': self.product_is_commission_exempt.id,
|
||||||
|
'product_uom_qty': 1.0,
|
||||||
|
'product_uom': self.product_is_commission_exempt.uom_id.id,
|
||||||
|
'price_unit': 20.0,
|
||||||
|
'tax_id': False,
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
self.assertEqual(order.amount_total, 120.0)
|
||||||
|
return order
|
||||||
|
|
||||||
|
def test_00_is_commission_exempt_total(self):
|
||||||
|
# TODO: test refunds
|
||||||
|
|
||||||
|
# New attribute
|
||||||
|
self.assertFalse(self.product.is_commission_exempt)
|
||||||
|
self.assertTrue(self.product_is_commission_exempt.is_commission_exempt)
|
||||||
|
|
||||||
|
# Calculate commission based on invoice total
|
||||||
|
self.env.user.company_id.commission_amount_type = 'on_invoice_total'
|
||||||
|
|
||||||
|
sale = self._createSaleOrder()
|
||||||
|
sale.action_confirm()
|
||||||
|
self.assertIn(sale.state, ('sale', 'done'), 'Could not confirm, maybe archive exception rules.')
|
||||||
|
inv = sale._create_invoices()
|
||||||
|
self.assertFalse(inv.commission_ids, 'Commissions exist when invoice is created.')
|
||||||
|
inv.action_post()
|
||||||
|
self.assertTrue(inv.commission_ids, 'Commissions not created when invoice is validated.')
|
||||||
|
self.assertEqual(inv.amount_total, 120.0)
|
||||||
|
|
||||||
|
user_commission = inv.commission_ids.filtered(lambda c: c.employee_id.id == self.sales_employee.id)
|
||||||
|
self.assertEqual(len(user_commission), 1)
|
||||||
|
|
||||||
|
# rate = 10.0, total = 120.0, commission total = 100.0
|
||||||
|
# commmission should be 10.0
|
||||||
|
self.assertEqual(user_commission.amount, 10.0)
|
||||||
|
|
||||||
|
def test_10_is_commission_exempt_margin(self):
|
||||||
|
self.env['ir.config_parameter'].set_param('commission.margin.threshold', '51.0')
|
||||||
|
low_margin_product = self.env['product.product'].create({
|
||||||
|
'name': 'Test Low Margin Product',
|
||||||
|
'standard_price': 100.0,
|
||||||
|
'invoice_policy': 'order',
|
||||||
|
})
|
||||||
|
self.env.user.company_id.commission_amount_type = 'on_invoice_margin'
|
||||||
|
self.product.standard_price = 50.0 # margin is 100%, margin = $50.0
|
||||||
|
self.product_is_commission_exempt.standard_price = 10.0 # margin is 100%
|
||||||
|
|
||||||
|
sale = self._createSaleOrder()
|
||||||
|
sale.write({
|
||||||
|
'order_line': [(0, 0, {
|
||||||
|
'name': 'test low margin product',
|
||||||
|
'product_id': low_margin_product.id,
|
||||||
|
'product_uom_qty': 1.0,
|
||||||
|
'product_uom': low_margin_product.uom_id.id,
|
||||||
|
'price_unit': 101.0, # margin is 1.0 %
|
||||||
|
'tax_id': False,
|
||||||
|
})],
|
||||||
|
})
|
||||||
|
|
||||||
|
# Total margin is now $61.0, but eligible margin should still be $50.0
|
||||||
|
sale.action_confirm()
|
||||||
|
self.assertIn(sale.state, ('sale', 'done'), 'Could not confirm, maybe archive exception rules.')
|
||||||
|
inv = sale._create_invoices()
|
||||||
|
self.assertEqual(inv.invoice_line_ids.mapped(lambda l: l.get_margin_percent()), [100.0, 100.0, 1.0])
|
||||||
|
self.assertFalse(inv.commission_ids, 'Commissions exist when invoice is created.')
|
||||||
|
inv.action_post()
|
||||||
|
self.assertTrue(inv.commission_ids, 'Commissions not created when invoice is validated.')
|
||||||
|
|
||||||
|
user_commission = inv.commission_ids.filtered(lambda c: c.employee_id.id == self.sales_employee.id)
|
||||||
|
self.assertEqual(len(user_commission), 1)
|
||||||
|
|
||||||
|
# rate = 10.0, total margin = 60.0, commission margin = 50.0
|
||||||
|
# commission should be 5.0
|
||||||
|
self.assertEqual(user_commission.amount, 5.0)
|
||||||
|
|
||||||
|
def test_20_test_zero_price(self):
|
||||||
|
self.env.user.company_id.commission_amount_type = 'on_invoice_margin'
|
||||||
|
self.product.standard_price = 0.0 # margin_percent is NaN
|
||||||
|
self.product_is_commission_exempt.standard_price = 10.0 # margin is 100%
|
||||||
|
|
||||||
|
sale = self._createSaleOrder()
|
||||||
|
sale.action_confirm()
|
||||||
|
self.assertIn(sale.state, ('sale', 'done'), 'Could not confirm, maybe archive exception rules.')
|
||||||
|
inv = sale._create_invoices()
|
||||||
|
self.assertEqual(inv.invoice_line_ids.mapped(lambda l: l.get_margin_percent()), [-1.0, 100.0])
|
||||||
30
hr_commission/views/product_views.xml
Normal file
30
hr_commission/views/product_views.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="product_template_form_view" model="ir.ui.view">
|
||||||
|
<field name="name">product.template.common.form.inherit</field>
|
||||||
|
<field name="model">product.template</field>
|
||||||
|
<field name="inherit_id" ref="product.product_template_form_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[@name='options']" position="inside">
|
||||||
|
<span class="d-inline-block">
|
||||||
|
<field name="is_commission_exempt" readonly="1"/>
|
||||||
|
<label for="is_commission_exempt"/>
|
||||||
|
</span>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="product_template_form_view_manager" model="ir.ui.view">
|
||||||
|
<field name="name">product.template.common.form.inherit.manager</field>
|
||||||
|
<field name="model">product.template</field>
|
||||||
|
<field name="inherit_id" ref="product.product_template_form_view"/>
|
||||||
|
<field name="groups_id" eval="[(4, ref('account.group_account_user'))]"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='is_commission_exempt']" position="attributes">
|
||||||
|
<attribute name="readonly">0</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -35,6 +35,10 @@
|
|||||||
<label string="Commission Base" for="commission_amount_type" class="col-md-3 o_light_label"/>
|
<label string="Commission Base" for="commission_amount_type" class="col-md-3 o_light_label"/>
|
||||||
<field name="commission_amount_type"/>
|
<field name="commission_amount_type"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label string="Commission Margin Threshold" for="commission_margin_threshold" class="col-md-3 o_light_label"/>
|
||||||
|
<field name="commission_margin_threshold"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user