mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[MOV] hr_commission: from Hibou Suite Enterprise for 13.0
This commit is contained in:
3
hr_commission/__init__.py
Normal file
3
hr_commission/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import models
|
||||||
26
hr_commission/__manifest__.py
Normal file
26
hr_commission/__manifest__.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Hibou Commissions',
|
||||||
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
|
'version': '13.0.1.0.1',
|
||||||
|
'category': 'Accounting/Commissions',
|
||||||
|
'license': 'OPL-1',
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'depends': [
|
||||||
|
# 'account_invoice_margin', # optional
|
||||||
|
'hr_contract',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'security/commission_security.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/account_views.xml',
|
||||||
|
'views/commission_views.xml',
|
||||||
|
'views/hr_views.xml',
|
||||||
|
'views/partner_views.xml',
|
||||||
|
'views/res_config_settings_views.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
||||||
8
hr_commission/models/__init__.py
Executable file
8
hr_commission/models/__init__.py
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import account
|
||||||
|
from . import commission
|
||||||
|
from . import hr
|
||||||
|
from . import partner
|
||||||
|
from . import res_company
|
||||||
|
from . import res_config_settings
|
||||||
50
hr_commission/models/account.py
Normal file
50
hr_commission/models/account.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class AccountMove(models.Model):
|
||||||
|
_inherit = 'account.move'
|
||||||
|
|
||||||
|
commission_ids = fields.One2many(comodel_name='hr.commission', inverse_name='source_move_id', string='Commissions')
|
||||||
|
commission_count = fields.Integer(string='Number of Commissions', compute='_compute_commission_count')
|
||||||
|
|
||||||
|
@api.depends('state', 'commission_ids')
|
||||||
|
def _compute_commission_count(self):
|
||||||
|
for move in self:
|
||||||
|
move.commission_count = len(move.commission_ids)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def open_commissions(self):
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': 'Invoice Commissions',
|
||||||
|
'res_model': 'hr.commission',
|
||||||
|
'view_mode': 'tree,form',
|
||||||
|
'context': {'search_default_source_move_id': self[0].id}
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_post(self):
|
||||||
|
res = super(AccountMove, self).action_post()
|
||||||
|
invoices = self.filtered(lambda m: m.is_invoice())
|
||||||
|
if invoices:
|
||||||
|
self.env['hr.commission'].invoice_validated(invoices)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def action_invoice_paid(self):
|
||||||
|
res = super(AccountMove, self).action_invoice_paid()
|
||||||
|
self.env['hr.commission'].invoice_paid(self)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def amount_for_commission(self):
|
||||||
|
# TODO Should toggle in Config Params
|
||||||
|
if hasattr(self, 'margin') and self.company_id.commission_amount_type == 'on_invoice_margin':
|
||||||
|
sign = -1 if self.type in ['in_refund', 'out_refund'] else 1
|
||||||
|
return self.margin * sign
|
||||||
|
return self.amount_total_signed
|
||||||
|
|
||||||
|
def action_cancel(self):
|
||||||
|
res = super(AccountMove, self).action_cancel()
|
||||||
|
for move in self:
|
||||||
|
move.sudo().commission_ids.unlink()
|
||||||
|
return res
|
||||||
329
hr_commission/models/commission.py
Normal file
329
hr_commission/models/commission.py
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
from odoo.tools import float_is_zero
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class Commission(models.Model):
|
||||||
|
_name = 'hr.commission'
|
||||||
|
_description = 'Commission'
|
||||||
|
_order = 'id desc'
|
||||||
|
|
||||||
|
state = fields.Selection([
|
||||||
|
('draft', 'New'),
|
||||||
|
('done', 'Confirmed'),
|
||||||
|
('paid', 'Paid'),
|
||||||
|
('cancel', 'Cancelled'),
|
||||||
|
], 'Status', default='draft')
|
||||||
|
employee_id = fields.Many2one('hr.employee', required=1)
|
||||||
|
user_id = fields.Many2one('res.users', related='employee_id.user_id')
|
||||||
|
source_move_id = fields.Many2one('account.move')
|
||||||
|
contract_id = fields.Many2one('hr.contract')
|
||||||
|
structure_id = fields.Many2one('hr.commission.structure')
|
||||||
|
rate_type = fields.Selection([
|
||||||
|
('normal', 'Normal'),
|
||||||
|
('structure', 'Structure'),
|
||||||
|
('admin', 'Admin'),
|
||||||
|
('manual', 'Manual'),
|
||||||
|
], 'Rate Type', default='normal')
|
||||||
|
rate = fields.Float('Rate')
|
||||||
|
base_total = fields.Float('Base Total')
|
||||||
|
base_amount = fields.Float(string='Base Amount')
|
||||||
|
amount = fields.Float(string='Amount')
|
||||||
|
move_id = fields.Many2one('account.move', ondelete='set null')
|
||||||
|
move_date = fields.Date(related='move_id.date', store=True)
|
||||||
|
company_id = fields.Many2one('res.company', 'Company', required=True,
|
||||||
|
default=lambda s: s.env['res.company']._company_default_get('hr.commission'))
|
||||||
|
memo = fields.Char(string='Memo')
|
||||||
|
accounting_date = fields.Date('Force Accounting Date',
|
||||||
|
help="Choose the accounting date at which you want to value the commission "
|
||||||
|
"moves created by the commission instead of the default one.")
|
||||||
|
payment_id = fields.Many2one('hr.commission.payment', string='Commission Payment', ondelete='set null')
|
||||||
|
|
||||||
|
@api.depends('employee_id', 'source_move_id')
|
||||||
|
def name_get(self):
|
||||||
|
res = []
|
||||||
|
for commission in self:
|
||||||
|
name = ''
|
||||||
|
if commission.source_move_id:
|
||||||
|
name += commission.source_move_id.name
|
||||||
|
if commission.employee_id:
|
||||||
|
if name:
|
||||||
|
name += ' - ' + commission.employee_id.name
|
||||||
|
else:
|
||||||
|
name += commission.employee_id.name
|
||||||
|
res.append((commission.id, name))
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.onchange('rate_type')
|
||||||
|
def _onchange_rate_type(self):
|
||||||
|
for commission in self.filtered(lambda c: c.rate_type == 'manual'):
|
||||||
|
commission.rate = 100.0
|
||||||
|
|
||||||
|
@api.onchange('source_move_id', 'contract_id', 'rate_type', 'base_amount', 'rate')
|
||||||
|
def _compute_amount(self):
|
||||||
|
for commission in self:
|
||||||
|
# Determine rate (if needed)
|
||||||
|
if commission.structure_id and commission.rate_type == 'structure':
|
||||||
|
line = commission.structure_id.line_ids.filtered(lambda l: l.employee_id == commission.employee_id)
|
||||||
|
commission.rate = line.get_rate()
|
||||||
|
elif commission.contract_id and commission.rate_type != 'manual':
|
||||||
|
if commission.rate_type == 'normal':
|
||||||
|
commission.rate = commission.contract_id.commission_rate
|
||||||
|
else:
|
||||||
|
commission.rate = commission.contract_id.admin_commission_rate
|
||||||
|
|
||||||
|
rounding = 2
|
||||||
|
if commission.source_move_id:
|
||||||
|
rounding = commission.source_move_id.company_currency_id.rounding
|
||||||
|
commission.base_total = commission.source_move_id.amount_total_signed
|
||||||
|
commission.base_amount = commission.source_move_id.amount_for_commission()
|
||||||
|
|
||||||
|
amount = (commission.base_amount * commission.rate) / 100.0
|
||||||
|
if float_is_zero(amount, precision_rounding=rounding):
|
||||||
|
amount = 0.0
|
||||||
|
commission.amount = amount
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, values):
|
||||||
|
res = super(Commission, self).create(values)
|
||||||
|
res._compute_amount()
|
||||||
|
if res.amount == 0.0 and res.state == 'draft':
|
||||||
|
res.state = 'done'
|
||||||
|
return res
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
if self.filtered(lambda c: c.move_id):
|
||||||
|
raise UserError('You cannot delete a commission when it has an accounting entry.')
|
||||||
|
return super(Commission, self).unlink()
|
||||||
|
|
||||||
|
def _filter_source_moves_for_creation(self, moves):
|
||||||
|
return moves.filtered(lambda i: i.user_id and not i.commission_ids)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _commissions_to_confirm(self, moves):
|
||||||
|
commissions = moves.mapped('commission_ids')
|
||||||
|
return commissions.filtered(lambda c: c.state != 'cancel' and not c.move_id)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def invoice_validated(self, moves):
|
||||||
|
employee_obj = self.env['hr.employee'].sudo()
|
||||||
|
commission_obj = self.sudo()
|
||||||
|
for move in self._filter_source_moves_for_creation(moves):
|
||||||
|
move_amount = move.amount_for_commission()
|
||||||
|
|
||||||
|
# Does the invoice have a commission structure?
|
||||||
|
partner = move.partner_id
|
||||||
|
commission_structure = partner.commission_structure_id
|
||||||
|
while not commission_structure and partner:
|
||||||
|
partner = partner.parent_id
|
||||||
|
commission_structure = partner.commission_structure_id
|
||||||
|
|
||||||
|
if commission_structure:
|
||||||
|
commission_structure.create_for_source_move(move, move_amount)
|
||||||
|
else:
|
||||||
|
employee = employee_obj.search([('user_id', '=', move.user_id.id)], limit=1)
|
||||||
|
contract = employee.contract_id
|
||||||
|
if all((employee, contract)):
|
||||||
|
move.commission_ids += commission_obj.create({
|
||||||
|
'employee_id': employee.id,
|
||||||
|
'contract_id': contract.id,
|
||||||
|
'source_move_id': move.id,
|
||||||
|
'base_amount': move_amount,
|
||||||
|
'rate_type': 'normal',
|
||||||
|
'company_id': move.company_id.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Admin/Coach commission.
|
||||||
|
employee = employee.coach_id
|
||||||
|
contract = employee.contract_id
|
||||||
|
if all((employee, contract)):
|
||||||
|
move.commission_ids += commission_obj.create({
|
||||||
|
'employee_id': employee.id,
|
||||||
|
'contract_id': contract.id,
|
||||||
|
'source_move_id': move.id,
|
||||||
|
'base_amount': move_amount,
|
||||||
|
'rate_type': 'admin',
|
||||||
|
'company_id': move.company_id.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if move.commission_ids and move.company_id.commission_type == 'on_invoice':
|
||||||
|
commissions = self._commissions_to_confirm(move)
|
||||||
|
commissions.sudo().action_confirm()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def invoice_paid(self, moves):
|
||||||
|
commissions = self._commissions_to_confirm(moves)
|
||||||
|
commissions.sudo().action_confirm()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def action_confirm(self):
|
||||||
|
move_obj = self.env['account.move'].sudo()
|
||||||
|
|
||||||
|
for commission in self:
|
||||||
|
if commission.state == 'cancel':
|
||||||
|
continue
|
||||||
|
if commission.move_id or commission.amount == 0.0:
|
||||||
|
commission.write({'state': 'done'})
|
||||||
|
continue
|
||||||
|
|
||||||
|
journal = commission.company_id.commission_journal_id
|
||||||
|
if not journal or not journal.default_debit_account_id or not journal.default_credit_account_id:
|
||||||
|
raise UserError('Commission Journal not configured.')
|
||||||
|
|
||||||
|
liability_account = commission.company_id.commission_liability_id
|
||||||
|
if not liability_account:
|
||||||
|
liability_account = commission.employee_id.address_home_id.property_account_payable_id
|
||||||
|
if not liability_account:
|
||||||
|
raise UserError('Commission liability account must be configured if employee\'s don\'t have AP setup.')
|
||||||
|
|
||||||
|
date = commission.source_move_id.date if commission.source_move_id else fields.Date.context_today(commission)
|
||||||
|
|
||||||
|
# Already paid.
|
||||||
|
payments = commission.source_move_id._get_reconciled_payments()
|
||||||
|
if payments:
|
||||||
|
date = max(payments.mapped('payment_date'))
|
||||||
|
if commission.accounting_date:
|
||||||
|
date = commission.accounting_date
|
||||||
|
|
||||||
|
ref = 'Commission for ' + commission.name_get()[0][1]
|
||||||
|
if commission.memo:
|
||||||
|
ref += ' :: ' + commission.memo
|
||||||
|
|
||||||
|
move = move_obj.create({
|
||||||
|
'date': date,
|
||||||
|
'ref': ref,
|
||||||
|
'journal_id': journal.id,
|
||||||
|
'type': 'entry',
|
||||||
|
'line_ids': [
|
||||||
|
(0, 0, {
|
||||||
|
'name': ref,
|
||||||
|
'partner_id': commission.employee_id.address_home_id.id,
|
||||||
|
'account_id': liability_account.id,
|
||||||
|
'credit': commission.amount if commission.amount > 0.0 else 0.0,
|
||||||
|
'debit': 0.0 if commission.amount > 0.0 else -commission.amount,
|
||||||
|
}),
|
||||||
|
(0, 0, {
|
||||||
|
'name': ref,
|
||||||
|
'partner_id': commission.employee_id.address_home_id.id,
|
||||||
|
'account_id': journal.default_credit_account_id.id if commission.amount > 0.0 else journal.default_debit_account_id.id,
|
||||||
|
'credit': 0.0 if commission.amount > 0.0 else -commission.amount,
|
||||||
|
'debit': commission.amount if commission.amount > 0.0 else 0.0,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
move.post()
|
||||||
|
commission.write({'state': 'done', 'move_id': move.id})
|
||||||
|
return True
|
||||||
|
|
||||||
|
def action_mark_paid(self):
|
||||||
|
if self.filtered(lambda c: c.state != 'done'):
|
||||||
|
raise UserError('You cannot mark a commission "paid" if it is not already "done".')
|
||||||
|
if not self:
|
||||||
|
raise UserError('You must have at least one "done" commission.')
|
||||||
|
payments = self._mark_paid()
|
||||||
|
action = self.env.ref('hr_commission.action_hr_commission_payment').read()[0]
|
||||||
|
action['res_ids'] = payments.ids
|
||||||
|
return action
|
||||||
|
|
||||||
|
def _mark_paid(self):
|
||||||
|
employees = self.mapped('employee_id')
|
||||||
|
payments = self.env['hr.commission.payment']
|
||||||
|
for employee in employees:
|
||||||
|
commissions = self.filtered(lambda c: c.employee_id == employee)
|
||||||
|
min_date = False
|
||||||
|
max_date = False
|
||||||
|
for commission in commissions:
|
||||||
|
if not min_date or (commission.move_date and min_date > commission.move_date):
|
||||||
|
min_date = commission.move_date
|
||||||
|
if not max_date or (commission.move_date and max_date < commission.move_date):
|
||||||
|
max_date = commission.move_date
|
||||||
|
payment = payments.create({
|
||||||
|
'employee_id': employee.id,
|
||||||
|
'name': ('Commissions %s - %s' % (min_date, max_date)),
|
||||||
|
'date': fields.Date.today(),
|
||||||
|
})
|
||||||
|
payments += payment
|
||||||
|
commissions.write({'state': 'paid', 'payment_id': payment.id})
|
||||||
|
return payments
|
||||||
|
|
||||||
|
def action_cancel(self):
|
||||||
|
for commission in self:
|
||||||
|
if commission.move_id:
|
||||||
|
commission.move_id.write({'state': 'draft'})
|
||||||
|
commission.move_id.unlink()
|
||||||
|
commission.write({'state': 'cancel'})
|
||||||
|
return True
|
||||||
|
|
||||||
|
def action_draft(self):
|
||||||
|
for commission in self.filtered(lambda c: c.state == 'cancel'):
|
||||||
|
commission.write({'state': 'draft'})
|
||||||
|
|
||||||
|
|
||||||
|
class CommissionPayment(models.Model):
|
||||||
|
_name = 'hr.commission.payment'
|
||||||
|
_description = 'Commission Payment'
|
||||||
|
_order = 'id desc'
|
||||||
|
|
||||||
|
name = fields.Char(string='Name')
|
||||||
|
employee_id = fields.Many2one('hr.employee', required=1)
|
||||||
|
user_id = fields.Many2one('res.users', related='employee_id.user_id')
|
||||||
|
date = fields.Date(string='Date')
|
||||||
|
commission_ids = fields.One2many('hr.commission', 'payment_id', string='Paid Commissions', readonly=True)
|
||||||
|
commission_count = fields.Integer(string='Commission Count', compute='_compute_commission_stats', store=True)
|
||||||
|
commission_amount = fields.Float(string='Commission Amount', compute='_compute_commission_stats', store=True)
|
||||||
|
|
||||||
|
@api.depends('commission_ids')
|
||||||
|
def _compute_commission_stats(self):
|
||||||
|
for payment in self:
|
||||||
|
payment.commission_count = len(payment.commission_ids)
|
||||||
|
payment.commission_amount = sum(payment.commission_ids.mapped('amount'))
|
||||||
|
|
||||||
|
|
||||||
|
class CommissionStructure(models.Model):
|
||||||
|
_name = 'hr.commission.structure'
|
||||||
|
_description = 'Commission Structure'
|
||||||
|
_order = 'id desc'
|
||||||
|
|
||||||
|
name = fields.Char(string='Name')
|
||||||
|
line_ids = fields.One2many('hr.commission.structure.line', 'structure_id', string='Lines')
|
||||||
|
|
||||||
|
def create_for_source_move(self, move, amount):
|
||||||
|
self.ensure_one()
|
||||||
|
commission_obj = self.env['hr.commission'].sudo()
|
||||||
|
|
||||||
|
for line in self.line_ids:
|
||||||
|
employee = line.employee_id
|
||||||
|
rate = line.get_rate()
|
||||||
|
if all((employee, rate)):
|
||||||
|
contract = False
|
||||||
|
if not line.rate:
|
||||||
|
# The rate must have come from the contract.
|
||||||
|
contract = employee.contract_id
|
||||||
|
move.commission_ids += commission_obj.create({
|
||||||
|
'employee_id': employee.id,
|
||||||
|
'structure_id': self.id,
|
||||||
|
'source_move_id': move.id,
|
||||||
|
'base_amount': amount,
|
||||||
|
'rate_type': 'structure',
|
||||||
|
'contract_id': contract.id if contract else False,
|
||||||
|
'company_id': move.company_id.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class CommissionStructureLine(models.Model):
|
||||||
|
_name = 'hr.commission.structure.line'
|
||||||
|
_description = 'Commission Structure Line'
|
||||||
|
|
||||||
|
structure_id = fields.Many2one('hr.commission.structure', string='Structure', required=True)
|
||||||
|
employee_id = fields.Many2one('hr.employee', string='Employee', required=True)
|
||||||
|
rate = fields.Float(string='Commission %', default=0.0, help='Leave 0.0 to use the employee\'s current contract rate.')
|
||||||
|
|
||||||
|
def get_rate(self):
|
||||||
|
if not self.rate:
|
||||||
|
return self.employee_id.contract_id.commission_rate
|
||||||
|
return self.rate
|
||||||
11
hr_commission/models/hr.py
Normal file
11
hr_commission/models/hr.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class Contract(models.Model):
|
||||||
|
_inherit = 'hr.contract'
|
||||||
|
|
||||||
|
commission_rate = fields.Float(string='Commission %', default=0.0)
|
||||||
|
admin_commission_rate = fields.Float(string='Admin Commission %', default=0.0)
|
||||||
|
|
||||||
9
hr_commission/models/partner.py
Normal file
9
hr_commission/models/partner.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class Partner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
commission_structure_id = fields.Many2one('hr.commission.structure', string='Commission Structure')
|
||||||
18
hr_commission/models/res_company.py
Normal file
18
hr_commission/models/res_company.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
commission_journal_id = fields.Many2one('account.journal', string='Commission Journal')
|
||||||
|
commission_liability_id = fields.Many2one('account.account', string='Commission Liability Account')
|
||||||
|
commission_type = fields.Selection([
|
||||||
|
('on_invoice', 'On Invoice Validation'),
|
||||||
|
('on_invoice_paid', 'On Invoice Paid'),
|
||||||
|
], string='Pay Commission', default='on_invoice_paid')
|
||||||
|
commission_amount_type = fields.Selection([
|
||||||
|
('on_invoice_margin', 'On Invoice Margin'),
|
||||||
|
('on_invoice_total', 'On Invoice Total'),
|
||||||
|
], string='Commission Base', default='on_invoice_margin')
|
||||||
12
hr_commission/models/res_config_settings.py
Normal file
12
hr_commission/models/res_config_settings.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
_inherit = 'res.config.settings'
|
||||||
|
|
||||||
|
commission_journal_id = fields.Many2one(related='company_id.commission_journal_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_amount_type = fields.Selection(related='company_id.commission_amount_type', readonly=False)
|
||||||
34
hr_commission/security/commission_security.xml
Normal file
34
hr_commission/security/commission_security.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="0">
|
||||||
|
|
||||||
|
<record id="hr_commission_rule" model="ir.rule">
|
||||||
|
<field name="name">Commission User</field>
|
||||||
|
<field name="model_id" ref="model_hr_commission"/>
|
||||||
|
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_commission_rule_manager" model="ir.rule">
|
||||||
|
<field name="name">Commission Manager</field>
|
||||||
|
<field name="model_id" ref="model_hr_commission"/>
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('account.group_account_manager'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_commission_payment_rule" model="ir.rule">
|
||||||
|
<field name="name">Commission Payment User</field>
|
||||||
|
<field name="model_id" ref="model_hr_commission_payment"/>
|
||||||
|
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="hr_commission_payment_rule_manager" model="ir.rule">
|
||||||
|
<field name="name">Commission Payment Manager</field>
|
||||||
|
<field name="model_id" ref="model_hr_commission_payment"/>
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('account.group_account_manager'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
9
hr_commission/security/ir.model.access.csv
Normal file
9
hr_commission/security/ir.model.access.csv
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_commission_user,commission user,model_hr_commission,base.group_user,1,0,0,0
|
||||||
|
access_commission_manager,commission manager,model_hr_commission,account.group_account_manager,1,1,1,1
|
||||||
|
access_commission_payment_user,commission payment user,model_hr_commission_payment,base.group_user,1,0,0,0
|
||||||
|
access_commission_payment_manager,commission payment manager,model_hr_commission_payment,account.group_account_manager,1,1,1,1
|
||||||
|
access_commission_structure_user,commission structure user,model_hr_commission_structure,base.group_user,1,0,0,0
|
||||||
|
access_commission_structure_manager,commission structure manager,model_hr_commission_structure,account.group_account_manager,1,1,1,1
|
||||||
|
access_commission_structure_line_user,commission structure line user,model_hr_commission_structure_line,base.group_user,1,0,0,0
|
||||||
|
access_commission_structure_line_manager,commission structure line manager,model_hr_commission_structure_line,account.group_account_manager,1,1,1,1
|
||||||
|
BIN
hr_commission/static/description/icon.png
Normal file
BIN
hr_commission/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
3
hr_commission/tests/__init__.py
Executable file
3
hr_commission/tests/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import test_commission
|
||||||
227
hr_commission/tests/test_commission.py
Normal file
227
hr_commission/tests/test_commission.py
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo.tests import common
|
||||||
|
|
||||||
|
|
||||||
|
class TestCommission(common.TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.user = self.browse_ref('base.user_demo')
|
||||||
|
self.employee = self.browse_ref('hr.employee_qdp') # This is the employee associated with above user.
|
||||||
|
|
||||||
|
def _createUser(self):
|
||||||
|
return self.env['res.users'].create({
|
||||||
|
'name': 'Coach',
|
||||||
|
'email': 'coach',
|
||||||
|
})
|
||||||
|
|
||||||
|
def _createEmployee(self, user):
|
||||||
|
return self.env['hr.employee'].create({
|
||||||
|
'birthday': '1985-03-14',
|
||||||
|
'country_id': self.ref('base.us'),
|
||||||
|
'department_id': self.ref('hr.dep_rd'),
|
||||||
|
'gender': 'male',
|
||||||
|
'name': 'Jared',
|
||||||
|
'address_home_id': user.partner_id.id,
|
||||||
|
'user_id': user.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _createContract(self, employee, commission_rate, admin_commission_rate=0.0):
|
||||||
|
return self.env['hr.contract'].create({
|
||||||
|
'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': employee.id,
|
||||||
|
'resource_calendar_id': self.ref('resource.resource_calendar_std'),
|
||||||
|
'commission_rate': commission_rate,
|
||||||
|
'admin_commission_rate': admin_commission_rate,
|
||||||
|
'state': 'open', # if not "Running" then no automatic selection when Payslip is created in 11.0
|
||||||
|
})
|
||||||
|
|
||||||
|
def _createInvoiceableSaleOrder(self, user):
|
||||||
|
product = self.env.ref('sale.advance_product_0')
|
||||||
|
partner = self.env.ref('base.res_partner_12')
|
||||||
|
sale = self.env['sale.order'].create({
|
||||||
|
'partner_id': partner.id,
|
||||||
|
'user_id': user.id,
|
||||||
|
'order_line': [(0, 0, {
|
||||||
|
'name': 'test deposit',
|
||||||
|
'product_id': product.id,
|
||||||
|
'product_uom_qty': 1.0,
|
||||||
|
'product_uom': product.uom_id.id,
|
||||||
|
'price_unit': 5.0,
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
self.assertEqual(sale.user_id, user)
|
||||||
|
sale.action_confirm()
|
||||||
|
self.assertTrue(sale.state in ('sale', 'done'))
|
||||||
|
self.assertEqual(sale.invoice_status, 'to invoice')
|
||||||
|
return sale
|
||||||
|
|
||||||
|
def test_commission(self):
|
||||||
|
# find and configure company commissions journal
|
||||||
|
commission_journal = self.env['account.journal'].search([('type', '=', 'general')], limit=1)
|
||||||
|
self.assertTrue(commission_journal)
|
||||||
|
expense_account = self.env.ref('l10n_generic_coa.1_expense')
|
||||||
|
commission_journal.default_debit_account_id = expense_account
|
||||||
|
commission_journal.default_credit_account_id = expense_account
|
||||||
|
self.env.user.company_id.commission_journal_id = commission_journal
|
||||||
|
|
||||||
|
coach = self._createEmployee(self.browse_ref('base.user_root'))
|
||||||
|
coach_contract = self._createContract(coach, 12.0, admin_commission_rate=2.0)
|
||||||
|
user = self.user
|
||||||
|
emp = self.employee
|
||||||
|
emp.address_home_id = user.partner_id # Important field for payables.
|
||||||
|
emp.coach_id = coach
|
||||||
|
|
||||||
|
contract = self._createContract(emp, 5.0)
|
||||||
|
|
||||||
|
so = self._createInvoiceableSaleOrder(user)
|
||||||
|
inv = so._create_invoices()
|
||||||
|
self.assertFalse(inv.commission_ids, 'Commissions exist when invoice is created.')
|
||||||
|
inv.action_post() # validate
|
||||||
|
self.assertEqual(inv.state, 'posted')
|
||||||
|
self.assertEqual(inv.invoice_payment_state, 'not_paid')
|
||||||
|
self.assertTrue(inv.commission_ids, 'Commissions not created when invoice is validated.')
|
||||||
|
|
||||||
|
user_commission = inv.commission_ids.filtered(lambda c: c.employee_id.id == emp.id)
|
||||||
|
self.assertEqual(len(user_commission), 1, 'Incorrect commission count %d (expect 1)' % len(user_commission))
|
||||||
|
self.assertEqual(user_commission.state, 'draft', 'Commission is not draft.')
|
||||||
|
self.assertFalse(user_commission.move_id, 'Commission has existing journal entry.')
|
||||||
|
|
||||||
|
# Amounts
|
||||||
|
commission_rate = contract.commission_rate
|
||||||
|
self.assertEqual(commission_rate, 5.0)
|
||||||
|
expected = (inv.amount_for_commission() * commission_rate) / 100.0
|
||||||
|
actual = user_commission.amount
|
||||||
|
self.assertAlmostEqual(actual, expected, int(inv.company_currency_id.rounding))
|
||||||
|
|
||||||
|
# Pay.
|
||||||
|
pay_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
|
||||||
|
payment = self.env['account.payment'].create({
|
||||||
|
'payment_type': 'inbound',
|
||||||
|
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||||
|
'partner_type': 'customer',
|
||||||
|
'partner_id': inv.partner_id.id,
|
||||||
|
'amount': inv.amount_residual,
|
||||||
|
'currency_id': inv.currency_id.id,
|
||||||
|
'journal_id': pay_journal.id,
|
||||||
|
})
|
||||||
|
payment.post()
|
||||||
|
|
||||||
|
receivable_line = payment.move_line_ids.filtered('credit')
|
||||||
|
inv.js_assign_outstanding_line(receivable_line.id)
|
||||||
|
self.assertEqual(inv.invoice_payment_state, 'paid', 'Invoice is not paid.')
|
||||||
|
|
||||||
|
user_commission = inv.commission_ids.filtered(lambda c: c.employee_id.id == emp.id)
|
||||||
|
self.assertEqual(user_commission.state, 'done', 'Commission is not done.')
|
||||||
|
self.assertTrue(user_commission.move_id, 'Commission didn\'t create a journal entry.')
|
||||||
|
inv.company_currency_id.rounding
|
||||||
|
|
||||||
|
# Coach/Admin commissions
|
||||||
|
coach_commission = inv.commission_ids.filtered(lambda c: c.employee_id.id == coach.id)
|
||||||
|
self.assertEqual(len(coach_commission), 1, 'Incorrect commission count %d (expect 1)' % len(coach_commission))
|
||||||
|
|
||||||
|
commission_rate = coach_contract.admin_commission_rate
|
||||||
|
expected = (inv.amount_for_commission() * commission_rate) / 100.0
|
||||||
|
actual = coach_commission.amount
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
int(inv.company_currency_id.rounding))
|
||||||
|
|
||||||
|
# Use the "Mark Paid" button
|
||||||
|
result_action = user_commission.action_mark_paid()
|
||||||
|
self.assertEqual(user_commission.state, 'paid')
|
||||||
|
self.assertTrue(user_commission.payment_id)
|
||||||
|
|
||||||
|
def test_commission_on_invoice(self):
|
||||||
|
# Set to be On Invoice instead of On Invoice Paid
|
||||||
|
self.env.user.company_id.commission_type = 'on_invoice'
|
||||||
|
|
||||||
|
# find and configure company commissions journal
|
||||||
|
commission_journal = self.env['account.journal'].search([('type', '=', 'general')], limit=1)
|
||||||
|
self.assertTrue(commission_journal)
|
||||||
|
expense_account = self.env.ref('l10n_generic_coa.1_expense')
|
||||||
|
commission_journal.default_debit_account_id = expense_account
|
||||||
|
commission_journal.default_credit_account_id = expense_account
|
||||||
|
self.env.user.company_id.commission_journal_id = commission_journal
|
||||||
|
|
||||||
|
|
||||||
|
coach = self._createEmployee(self.browse_ref('base.user_root'))
|
||||||
|
coach_contract = self._createContract(coach, 12.0, admin_commission_rate=2.0)
|
||||||
|
user = self.user
|
||||||
|
emp = self.employee
|
||||||
|
emp.address_home_id = user.partner_id # Important field for payables.
|
||||||
|
emp.coach_id = coach
|
||||||
|
|
||||||
|
contract = self._createContract(emp, 5.0)
|
||||||
|
|
||||||
|
so = self._createInvoiceableSaleOrder(user)
|
||||||
|
inv = so._create_invoices()
|
||||||
|
self.assertFalse(inv.commission_ids, 'Commissions exist when invoice is created.')
|
||||||
|
inv.action_post() # validate
|
||||||
|
self.assertEqual(inv.state, 'posted')
|
||||||
|
self.assertTrue(inv.commission_ids, 'Commissions not created when invoice is validated.')
|
||||||
|
|
||||||
|
user_commission = inv.commission_ids.filtered(lambda c: c.employee_id.id == emp.id)
|
||||||
|
self.assertEqual(len(user_commission), 1, 'Incorrect commission count %d (expect 1)' % len(user_commission))
|
||||||
|
self.assertEqual(user_commission.state, 'done', 'Commission is not done.')
|
||||||
|
self.assertTrue(user_commission.move_id, 'Commission missing journal entry.')
|
||||||
|
|
||||||
|
# Use the "Mark Paid" button
|
||||||
|
user_commission.action_mark_paid()
|
||||||
|
self.assertEqual(user_commission.state, 'paid')
|
||||||
|
|
||||||
|
def test_commission_structure(self):
|
||||||
|
# Set to be On Invoice instead of On Invoice Paid
|
||||||
|
self.env.user.company_id.commission_type = 'on_invoice'
|
||||||
|
|
||||||
|
# find and configure company commissions journal
|
||||||
|
commission_journal = self.env['account.journal'].search([('type', '=', 'general')], limit=1)
|
||||||
|
self.assertTrue(commission_journal)
|
||||||
|
expense_account = self.env.ref('l10n_generic_coa.1_expense')
|
||||||
|
commission_journal.default_debit_account_id = expense_account
|
||||||
|
commission_journal.default_credit_account_id = expense_account
|
||||||
|
self.env.user.company_id.commission_journal_id = commission_journal
|
||||||
|
|
||||||
|
|
||||||
|
coach = self._createEmployee(self.browse_ref('base.user_root'))
|
||||||
|
coach_contract = self._createContract(coach, 12.0, admin_commission_rate=2.0)
|
||||||
|
user = self.user
|
||||||
|
emp = self.employee
|
||||||
|
emp.address_home_id = user.partner_id # Important field for payables.
|
||||||
|
emp.coach_id = coach
|
||||||
|
|
||||||
|
contract = self._createContract(emp, 5.0)
|
||||||
|
|
||||||
|
so = self._createInvoiceableSaleOrder(user)
|
||||||
|
|
||||||
|
# Create and set commission structure
|
||||||
|
commission_structure = self.env['hr.commission.structure'].create({
|
||||||
|
'name': 'Test Structure',
|
||||||
|
'line_ids': [
|
||||||
|
(0, 0, {'employee_id': emp.id, 'rate': 13.0}),
|
||||||
|
(0, 0, {'employee_id': coach.id, 'rate': 0.0}), # This means it will use the coach's contract normal rate
|
||||||
|
],
|
||||||
|
})
|
||||||
|
so.partner_id.commission_structure_id = commission_structure
|
||||||
|
|
||||||
|
inv = so._create_invoices()
|
||||||
|
self.assertFalse(inv.commission_ids, 'Commissions exist when invoice is created.')
|
||||||
|
inv.action_post() # validate
|
||||||
|
self.assertEqual(inv.state, 'posted')
|
||||||
|
self.assertTrue(inv.commission_ids, 'Commissions not created when invoice is validated.')
|
||||||
|
|
||||||
|
user_commission = inv.commission_ids.filtered(lambda c: c.employee_id.id == emp.id)
|
||||||
|
self.assertEqual(len(user_commission), 1, 'Incorrect commission count %d (expect 1)' % len(user_commission))
|
||||||
|
self.assertEqual(user_commission.state, 'done', 'Commission is not done.')
|
||||||
|
self.assertEqual(user_commission.rate, 13.0)
|
||||||
|
|
||||||
|
coach_commission = inv.commission_ids.filtered(lambda c: c.employee_id.id == coach.id)
|
||||||
|
self.assertEqual(len(coach_commission), 1, 'Incorrect commission count %d (expect 1)' % len(coach_commission))
|
||||||
|
self.assertEqual(coach_commission.state, 'done', 'Commission is not done.')
|
||||||
|
self.assertEqual(coach_commission.rate, 12.0, 'Commission rate should be the contract rate.')
|
||||||
18
hr_commission/views/account_views.xml
Normal file
18
hr_commission/views/account_views.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_move_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">account.move.form.inherit</field>
|
||||||
|
<field name="model">account.move</field>
|
||||||
|
<field name="inherit_id" ref="account.view_move_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[@name='button_box']" position="inside">
|
||||||
|
<button class="oe_stat_button" name="open_commissions" icon="fa-money" type="object"
|
||||||
|
attrs="{'invisible': [('commission_count', '=', 0)]}">
|
||||||
|
<field name="commission_count" string="Commissions" widget="statinfo"/>
|
||||||
|
</button>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
264
hr_commission/views/commission_views.xml
Normal file
264
hr_commission/views/commission_views.xml
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_hr_commission_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.form</field>
|
||||||
|
<field name="model">hr.commission</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Commission" class="oe_form_nomargin">
|
||||||
|
<header>
|
||||||
|
<button name="action_confirm" string="Confirm" class="btn-primary" type="object" attrs="{'invisible': [('state', '!=', 'draft')]}"/>
|
||||||
|
<button name="action_draft" string="Set Draft" class="btn-default" type="object" attrs="{'invisible': [('state', '!=', 'cancel')]}"/>
|
||||||
|
<button name="action_mark_paid" string="Mark Paid" class="btn-default" type="object" attrs="{'invisible': [('state', '!=', 'done')]}"/>
|
||||||
|
<button name="action_cancel" string="Cancel" class="btn-default" type="object" attrs="{'invisible': [('state', '=', 'cancel')]}"/>
|
||||||
|
<field name="state" widget="statusbar" on_change="1" modifiers="{'readonly': true}"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
</div>
|
||||||
|
<div class="oe_title">
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="employee_id" modifiers="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="contract_id" domain="[('employee_id', '=', employee_id)]" modifiers="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="structure_id" modifiers="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="source_move_id" modifiers="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="company_id" groups="base.group_multi_company" modifiers="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="memo" modifiers="{'readonly': [('state', 'not in', ('draft', 'done'))]}"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="rate_type" modifiers="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="rate" attrs="{'readonly': [('contract_id', '!=', False), ('rate_type', '!=', 'manual')]}"/>
|
||||||
|
<field name="base_total" modifiers="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="base_amount" modifiers="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="amount" readonly="1" force_save="1"/>
|
||||||
|
<field name="move_date" readonly="1" attrs="{'invisible': [('move_date', '=', False)]}"/>
|
||||||
|
<field name="accounting_date"
|
||||||
|
attrs="{'invisible': ['|', ('rate_type', '!=', 'manual'), ('move_date', '!=', False)], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="move_id" attrs="{'invisible': [('move_id', '=', False)], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
<field name="payment_id" attrs="{'invisible': [('payment_id', '=', False)]}" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_commission_tree" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.tree</field>
|
||||||
|
<field name="model">hr.commission</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree decoration-info="state == 'draft'" decoration-muted="state == 'cancel'">
|
||||||
|
<field name="create_date"/>
|
||||||
|
<field name="source_move_id"/>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="contract_id"/>
|
||||||
|
<field name="base_total" string="Invoice Total" sum="Invoice Total"/>
|
||||||
|
<field name="base_amount" string="Margin" sum="Margin Total"/>
|
||||||
|
<field name="amount" string="Commission" sum="Commission Total"/>
|
||||||
|
<field name="state"/>
|
||||||
|
<field name="move_date"/>
|
||||||
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_commission_pivot" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.pivot</field>
|
||||||
|
<field name="model">hr.commission</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<pivot string="Commissions">
|
||||||
|
<field name="create_date" type="row" interval="week"/>
|
||||||
|
<field name="state" type="row"/>
|
||||||
|
<field name="employee_id" type="col"/>
|
||||||
|
<field name="amount" type="measure"/>
|
||||||
|
</pivot>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_commission_pivot_graph" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.graph</field>
|
||||||
|
<field name="model">hr.commission</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<graph string="Commissions" stacked="True">
|
||||||
|
<field name="create_date" type="row" interval="week"/>
|
||||||
|
<field name="state" type="row"/>
|
||||||
|
<field name="amount" type="measure"/>
|
||||||
|
</graph>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_commission_search" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.search</field>
|
||||||
|
<field name="model">hr.commission</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Commission">
|
||||||
|
<field name="source_move_id"/>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="contract_id"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="New" name="new" domain="[('state', '=', 'draft')]"/>
|
||||||
|
<filter string="Confirmed" name="confirmed" domain="[('state', '=', 'done')]"/>
|
||||||
|
<filter string="Paid" name="paid" domain="[('state', '=', 'paid')]"/>
|
||||||
|
<filter string="Cancelled" name="cancelled" domain="[('state', '=', 'cancel')]"/>
|
||||||
|
<group expand="0" name="group_by" string="Group By">
|
||||||
|
<filter name="group_state" string="Status" domain="[]" context="{'group_by': 'state'}"/>
|
||||||
|
<filter name="group_employee" string="Employee" domain="[]" context="{'group_by': 'employee_id'}"/>
|
||||||
|
<filter name="group_invoice" string="Invoice" domain="[]" context="{'group_by': 'source_move_id'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_hr_commission" model="ir.actions.act_window">
|
||||||
|
<field name="name">Commissions</field>
|
||||||
|
<field name="res_model">hr.commission</field>
|
||||||
|
<field name="view_mode">tree,form,pivot,graph</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
action="action_hr_commission"
|
||||||
|
id="menu_action_sales_commission_form"
|
||||||
|
parent="sale.sale_menu_root"
|
||||||
|
sequence="5"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
action="action_hr_commission"
|
||||||
|
id="menu_action_sales_commission_form2"
|
||||||
|
parent="menu_action_sales_commission_form"
|
||||||
|
sequence="5"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
action="action_hr_commission"
|
||||||
|
id="menu_action_account_commission_form"
|
||||||
|
parent="account.menu_finance_entries"
|
||||||
|
sequence="90"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
action="action_hr_commission"
|
||||||
|
id="menu_action_account_commission_form2"
|
||||||
|
parent="menu_action_account_commission_form"
|
||||||
|
sequence="90"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<record id="action_commission_mark_paid" model="ir.actions.server">
|
||||||
|
<field name="name">Mark Paid</field>
|
||||||
|
<field name="type">ir.actions.server</field>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="model_id" ref="hr_commission.model_hr_commission"/>
|
||||||
|
<field name="binding_model_id" ref="hr_commission.model_hr_commission"/>
|
||||||
|
<field name="code">
|
||||||
|
action = records.action_mark_paid()
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Commission Payments -->
|
||||||
|
<record id="view_hr_commission_payment_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.payment.form</field>
|
||||||
|
<field name="model">hr.commission.payment</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Commission Payment">
|
||||||
|
<header/>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box"/>
|
||||||
|
<h1><field name="name" placeholder="Commission Payment Description"/></h1>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="date"/>
|
||||||
|
<field name="employee_id" readonly="1"/>
|
||||||
|
<field name="commission_count"/>
|
||||||
|
<field name="commission_amount"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="commission_ids" nolabel="1" readonly="1">
|
||||||
|
<tree>
|
||||||
|
<field name="source_move_id"/>
|
||||||
|
<field name="amount"/>
|
||||||
|
<field name="move_date"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_commission_payment_tree" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.payment.tree</field>
|
||||||
|
<field name="model">hr.commission.payment</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree>
|
||||||
|
<field name="date"/>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="commission_count" sum="Commission Count Total"/>
|
||||||
|
<field name="commission_amount" sum="Commission Amount Total"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_commission_payment_search" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.payment.search</field>
|
||||||
|
<field name="model">hr.commission.payment</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Commission Payment">
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<group expand="0" name="group_by" string="Group By">
|
||||||
|
<filter name="group_employee" string="Employee" domain="[]" context="{'group_by': 'employee_id'}"/>
|
||||||
|
<filter name="group_date" string="Date" domain="[]" context="{'group_by': 'date'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_hr_commission_payment" model="ir.actions.act_window">
|
||||||
|
<field name="name">Commission Payments</field>
|
||||||
|
<field name="res_model">hr.commission.payment</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
action="action_hr_commission_payment"
|
||||||
|
id="menu_action_sales_commission_payment_form"
|
||||||
|
parent="menu_action_sales_commission_form"
|
||||||
|
sequence="10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
action="action_hr_commission_payment"
|
||||||
|
id="menu_action_account_commission_payment_form"
|
||||||
|
parent="menu_action_account_commission_form"
|
||||||
|
sequence="100"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Commission Structure -->
|
||||||
|
<record id="view_hr_commission_structure_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.commission.structure.form</field>
|
||||||
|
<field name="model">hr.commission.structure</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Commission Structure" class="oe_form_nomargin">
|
||||||
|
<header/>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box"/>
|
||||||
|
<div class="oe_title">
|
||||||
|
<field name="name"/>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<field name="line_ids" nolabel="1">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field name="structure_id" invisible="1"/>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="rate"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
16
hr_commission/views/hr_views.xml
Normal file
16
hr_commission/views/hr_views.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="hr_contract_view_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">hr.contract.form.inherit</field>
|
||||||
|
<field name="model">hr.contract</field>
|
||||||
|
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//group/group" position="inside">
|
||||||
|
<field name="commission_rate"/>
|
||||||
|
<field name="admin_commission_rate"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
15
hr_commission/views/partner_views.xml
Normal file
15
hr_commission/views/partner_views.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="res_partner_view_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">res.parter.view.form.inherit</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='property_account_payable_id']" position="after">
|
||||||
|
<field name="commission_structure_id"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
46
hr_commission/views/res_config_settings_views.xml
Normal file
46
hr_commission/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.config.settings.view.form.inherit.account</field>
|
||||||
|
<field name="model">res.config.settings</field>
|
||||||
|
<field name="priority" eval="50"/>
|
||||||
|
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[@id='invoicing_settings']" position="after">
|
||||||
|
<h2>Commissions</h2>
|
||||||
|
<div class="row mt16 o_settings_container" id="commission_settings" groups="account.group_account_user">
|
||||||
|
<div class="col-xs-12 col-md-6 o_setting_box" title="These taxes are set in any new product created.">
|
||||||
|
<div class="o_setting_left_pane"/>
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." groups="base.group_multi_company"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Commission journal default accounts can be thought of as the 'expense' side of the commission. If a Liability account
|
||||||
|
is not chosen, then the employee's home address partner's Account Payable will be used instead.
|
||||||
|
</div>
|
||||||
|
<div class="content-group">
|
||||||
|
<div class="row mt16">
|
||||||
|
<label string="Journal" for="commission_journal_id" class="col-md-3 o_light_label"/>
|
||||||
|
<field name="commission_journal_id" domain="[('company_id', '=', company_id)]"/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label string="Liability Account" for="commission_liability_id" class="col-md-3 o_light_label"/>
|
||||||
|
<field name="commission_liability_id" domain="[('company_id', '=', company_id)]"/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label string="Pay Commission" for="commission_type" class="col-md-3 o_light_label"/>
|
||||||
|
<field name="commission_type"/>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label string="Commission Base" for="commission_amount_type" class="col-md-3 o_light_label"/>
|
||||||
|
<field name="commission_amount_type"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user