[IMP] hr_commission: margin threshold and no commission products

H11044
This commit is contained in:
galeshibou
2022-09-29 02:06:14 +00:00
parent d957c043bb
commit a0924cc036
9 changed files with 242 additions and 8 deletions

View File

@@ -8,7 +8,7 @@
'license': 'OPL-1', 'license': 'OPL-1',
'website': 'https://hibou.io/', 'website': 'https://hibou.io/',
'depends': [ 'depends': [
# 'account_invoice_margin', # optional 'account_invoice_margin',
'account', 'account',
'hr_contract', 'hr_contract',
], ],
@@ -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,

View File

@@ -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

View File

@@ -37,15 +37,45 @@ class AccountMove(models.Model):
return res return res
def amount_for_commission(self, commission=None): def amount_for_commission(self, commission=None):
if hasattr(self, 'margin') and self.company_id.commission_amount_type == 'on_invoice_margin': # Override to exclude ineligible products
amount = 0.0
if self.is_invoice():
invoice_lines = self.invoice_line_ids.filtered(lambda l: not l.product_id.no_commission)
if self.company_id.commission_amount_type == 'on_invoice_margin':
margin_threshold = float(self.env['ir.config_parameter'].sudo().get_param('commission.margin.threshold', default=0.0))
if margin_threshold:
invoice_lines = invoice_lines.filtered(lambda l: l.margin_percent > margin_threshold)
sign = -1 if self.move_type in ['in_refund', 'out_refund'] else 1 sign = -1 if self.move_type in ['in_refund', 'out_refund'] else 1
return self.margin * sign margin = sum(invoice_lines.mapped('margin'))
elif self.company_id.commission_amount_type == 'on_invoice_untaxed': amount = margin * sign
return self.amount_untaxed_signed else:
return self.amount_total_signed amount = sum(invoice_lines.mapped('balance'))
amount = abs(amount) if self.move_type == 'entry' else -amount
return amount
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'
margin_percent = fields.Float(string='Margin percent (%)', compute='_compute_margin_percent', digits=(3, 2))
@api.depends('margin', 'product_id', 'purchase_price', 'quantity', 'price_unit', 'price_subtotal')
def _compute_margin_percent(self):
for line in self:
currency = line.move_id.currency_id
price = line.purchase_price
if line.product_id and not price:
date = line.move_id.date if line.move_id.date else fields.Date.context_today(line.move_id)
from_cur = line.move_id.company_currency_id.with_context(date=date)
price = from_cur._convert(line.product_id.standard_price, currency, line.company_id, date, round=False)
total_price = price * line.quantity
if total_price == 0.0:
line.margin_percent = -1.0
else:
line.margin_percent = (line.margin / total_price) * 100.0

View File

@@ -0,0 +1,13 @@
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
no_commission = fields.Boolean('Exclude from Commissions')
can_edit_no_commission = fields.Boolean(compute='_compute_can_edit_no_commission')
def _compute_can_edit_no_commission(self):
can_edit = self.env.user.has_group('account.group_account_user')
for template in self:
template.can_edit_no_commission = can_edit

View File

@@ -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)

View File

@@ -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_no_commission

View File

@@ -0,0 +1,156 @@
from odoo.tests import common
import logging
_logger = logging.getLogger(__name__)
class TestNoCommission(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_no_commission = self.env['product.product'].create({
'name': 'Test Product No Commission',
'invoice_policy': 'order',
'no_commission': 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_no_commission.id,
'product_uom_qty': 1.0,
'product_uom': self.product_no_commission.uom_id.id,
'price_unit': 20.0,
'tax_id': False,
})]
})
self.assertEqual(order.amount_total, 120.0)
return order
def test_00_no_commission_total(self):
# TODO: test refunds
# New attribute
self.assertFalse(self.product.no_commission)
self.assertTrue(self.product_no_commission.no_commission)
# 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_no_commission_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_no_commission.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('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_no_commission.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('margin_percent'), [-1.0, 100.0])

View File

@@ -0,0 +1,16 @@
<?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="//group[@name='group_general']" position="inside">
<field name="can_edit_no_commission" invisible="1"/>
<field name="no_commission" attrs="{'readonly': [('can_edit_no_commission', '=', False)]}"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -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>