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:
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
|
||||
Reference in New Issue
Block a user