From 7a48d9b4440ad6ae0105b0c6852ddb4dae6dafdf Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Fri, 3 Jul 2020 08:36:09 -0700 Subject: [PATCH] [MOV] hr_commission: from Hibou Suite Enterprise for 13.0 --- hr_commission/__init__.py | 3 + hr_commission/__manifest__.py | 26 ++ hr_commission/models/__init__.py | 8 + hr_commission/models/account.py | 50 +++ hr_commission/models/commission.py | 329 ++++++++++++++++++ hr_commission/models/hr.py | 11 + hr_commission/models/partner.py | 9 + hr_commission/models/res_company.py | 18 + hr_commission/models/res_config_settings.py | 12 + .../security/commission_security.xml | 34 ++ hr_commission/security/ir.model.access.csv | 9 + hr_commission/static/description/icon.png | Bin 0 -> 5113 bytes hr_commission/tests/__init__.py | 3 + hr_commission/tests/test_commission.py | 227 ++++++++++++ hr_commission/views/account_views.xml | 18 + hr_commission/views/commission_views.xml | 264 ++++++++++++++ hr_commission/views/hr_views.xml | 16 + hr_commission/views/partner_views.xml | 15 + .../views/res_config_settings_views.xml | 46 +++ 19 files changed, 1098 insertions(+) create mode 100644 hr_commission/__init__.py create mode 100644 hr_commission/__manifest__.py create mode 100755 hr_commission/models/__init__.py create mode 100644 hr_commission/models/account.py create mode 100644 hr_commission/models/commission.py create mode 100644 hr_commission/models/hr.py create mode 100644 hr_commission/models/partner.py create mode 100644 hr_commission/models/res_company.py create mode 100644 hr_commission/models/res_config_settings.py create mode 100644 hr_commission/security/commission_security.xml create mode 100644 hr_commission/security/ir.model.access.csv create mode 100644 hr_commission/static/description/icon.png create mode 100755 hr_commission/tests/__init__.py create mode 100644 hr_commission/tests/test_commission.py create mode 100644 hr_commission/views/account_views.xml create mode 100644 hr_commission/views/commission_views.xml create mode 100644 hr_commission/views/hr_views.xml create mode 100644 hr_commission/views/partner_views.xml create mode 100644 hr_commission/views/res_config_settings_views.xml diff --git a/hr_commission/__init__.py b/hr_commission/__init__.py new file mode 100644 index 00000000..09434554 --- /dev/null +++ b/hr_commission/__init__.py @@ -0,0 +1,3 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from . import models diff --git a/hr_commission/__manifest__.py b/hr_commission/__manifest__.py new file mode 100644 index 00000000..52e7d5d7 --- /dev/null +++ b/hr_commission/__manifest__.py @@ -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. ', + '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, + } diff --git a/hr_commission/models/__init__.py b/hr_commission/models/__init__.py new file mode 100755 index 00000000..d739b6ed --- /dev/null +++ b/hr_commission/models/__init__.py @@ -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 diff --git a/hr_commission/models/account.py b/hr_commission/models/account.py new file mode 100644 index 00000000..9fc6c166 --- /dev/null +++ b/hr_commission/models/account.py @@ -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 diff --git a/hr_commission/models/commission.py b/hr_commission/models/commission.py new file mode 100644 index 00000000..3a491080 --- /dev/null +++ b/hr_commission/models/commission.py @@ -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 diff --git a/hr_commission/models/hr.py b/hr_commission/models/hr.py new file mode 100644 index 00000000..4047ee34 --- /dev/null +++ b/hr_commission/models/hr.py @@ -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) + diff --git a/hr_commission/models/partner.py b/hr_commission/models/partner.py new file mode 100644 index 00000000..b88149ad --- /dev/null +++ b/hr_commission/models/partner.py @@ -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') diff --git a/hr_commission/models/res_company.py b/hr_commission/models/res_company.py new file mode 100644 index 00000000..26a2acb4 --- /dev/null +++ b/hr_commission/models/res_company.py @@ -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') diff --git a/hr_commission/models/res_config_settings.py b/hr_commission/models/res_config_settings.py new file mode 100644 index 00000000..d09b5b38 --- /dev/null +++ b/hr_commission/models/res_config_settings.py @@ -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) diff --git a/hr_commission/security/commission_security.xml b/hr_commission/security/commission_security.xml new file mode 100644 index 00000000..844471ff --- /dev/null +++ b/hr_commission/security/commission_security.xml @@ -0,0 +1,34 @@ + + + + + + Commission User + + [('user_id', '=', user.id)] + + + + + Commission Manager + + [(1, '=', 1)] + + + + + Commission Payment User + + [('user_id', '=', user.id)] + + + + + Commission Payment Manager + + [(1, '=', 1)] + + + + + diff --git a/hr_commission/security/ir.model.access.csv b/hr_commission/security/ir.model.access.csv new file mode 100644 index 00000000..59b621a1 --- /dev/null +++ b/hr_commission/security/ir.model.access.csv @@ -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 diff --git a/hr_commission/static/description/icon.png b/hr_commission/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f4d8b5019a241e217641c64ad41f37ddf6d59ec5 GIT binary patch literal 5113 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 + + + + account.move.form.inherit + account.move + + + + + + + + + \ No newline at end of file diff --git a/hr_commission/views/commission_views.xml b/hr_commission/views/commission_views.xml new file mode 100644 index 00000000..0e99a965 --- /dev/null +++ b/hr_commission/views/commission_views.xml @@ -0,0 +1,264 @@ + + + + hr.commission.form + hr.commission + +
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + hr.commission.tree + hr.commission + + + + + + + + + + + + + + + + + + hr.commission.pivot + hr.commission + + + + + + + + + + + + hr.commission.graph + hr.commission + + + + + + + + + + + hr.commission.search + hr.commission + + + + + + + + + + + + + + + + + + + + + Commissions + hr.commission + tree,form,pivot,graph + + + + + + + + + + + + Mark Paid + ir.actions.server + code + + + +action = records.action_mark_paid() + + + + + + hr.commission.payment.form + hr.commission.payment + +
+
+ +
+

+ + + + + + + + + + + + + + + + + + + + + + + + hr.commission.payment.tree + hr.commission.payment + + + + + + + + + + + + + hr.commission.payment.search + hr.commission.payment + + + + + + + + + + + + + Commission Payments + hr.commission.payment + tree,form + + + + + + + + + hr.commission.structure.form + hr.commission.structure + +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + diff --git a/hr_commission/views/hr_views.xml b/hr_commission/views/hr_views.xml new file mode 100644 index 00000000..6977afa2 --- /dev/null +++ b/hr_commission/views/hr_views.xml @@ -0,0 +1,16 @@ + + + + + hr.contract.form.inherit + hr.contract + + + + + + + + + + \ No newline at end of file diff --git a/hr_commission/views/partner_views.xml b/hr_commission/views/partner_views.xml new file mode 100644 index 00000000..23fcc43d --- /dev/null +++ b/hr_commission/views/partner_views.xml @@ -0,0 +1,15 @@ + + + + + res.parter.view.form.inherit + res.partner + + + + + + + + + \ No newline at end of file diff --git a/hr_commission/views/res_config_settings_views.xml b/hr_commission/views/res_config_settings_views.xml new file mode 100644 index 00000000..645c8f06 --- /dev/null +++ b/hr_commission/views/res_config_settings_views.xml @@ -0,0 +1,46 @@ + + + + + res.config.settings.view.form.inherit.account + res.config.settings + + + + +

Commissions

+
+
+
+
+ +
+ 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. +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file