Files
suite/hr_commission/models/commission.py

330 lines
14 KiB
Python

# 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