From f3d337a0df08a1f7fa6097ab83833509feb8d07f Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Fri, 3 Jul 2020 08:36:09 -0700 Subject: [PATCH 01/15] [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 From 126101bba04145be2ed6230381eda79a2af4a215 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 9 Jul 2020 18:46:37 -0700 Subject: [PATCH 02/15] [IMP] hr_commission: remove menus in Sales because we don't depend on `sale` --- hr_commission/tests/test_commission.py | 4 ++++ hr_commission/views/commission_views.xml | 21 --------------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/hr_commission/tests/test_commission.py b/hr_commission/tests/test_commission.py index 688513d1..f921f30a 100644 --- a/hr_commission/tests/test_commission.py +++ b/hr_commission/tests/test_commission.py @@ -2,6 +2,10 @@ from odoo.tests import common +# TODO Tests won't pass without `sale` +# Tests should be refactored to not build sale orders +# to invoice, just create invoices directly. + class TestCommission(common.TransactionCase): diff --git a/hr_commission/views/commission_views.xml b/hr_commission/views/commission_views.xml index 0e99a965..62a695ca 100644 --- a/hr_commission/views/commission_views.xml +++ b/hr_commission/views/commission_views.xml @@ -116,20 +116,6 @@ tree,form,pivot,graph - - - - tree,form - - Date: Tue, 17 Nov 2020 15:06:01 -0500 Subject: [PATCH 03/15] [MIG] hr_commission: migrate to Odoo 14.0 --- hr_commission/__manifest__.py | 3 +- hr_commission/models/account.py | 2 +- hr_commission/models/commission.py | 12 ++--- hr_commission/tests/test_commission.py | 72 +++++++++++--------------- 4 files changed, 38 insertions(+), 51 deletions(-) diff --git a/hr_commission/__manifest__.py b/hr_commission/__manifest__.py index 52e7d5d7..83eea70e 100644 --- a/hr_commission/__manifest__.py +++ b/hr_commission/__manifest__.py @@ -3,12 +3,13 @@ { 'name': 'Hibou Commissions', 'author': 'Hibou Corp. ', - 'version': '13.0.1.0.1', + 'version': '14.0.1.0.0', 'category': 'Accounting/Commissions', 'license': 'OPL-1', 'website': 'https://hibou.io/', 'depends': [ # 'account_invoice_margin', # optional + 'account', 'hr_contract', ], 'data': [ diff --git a/hr_commission/models/account.py b/hr_commission/models/account.py index 9fc6c166..eb4d8350 100644 --- a/hr_commission/models/account.py +++ b/hr_commission/models/account.py @@ -39,7 +39,7 @@ class AccountMove(models.Model): 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 + sign = -1 if self.move_type in ['in_refund', 'out_refund'] else 1 return self.margin * sign return self.amount_total_signed diff --git a/hr_commission/models/commission.py b/hr_commission/models/commission.py index 3a491080..c6f3b25c 100644 --- a/hr_commission/models/commission.py +++ b/hr_commission/models/commission.py @@ -86,7 +86,6 @@ class Commission(models.Model): commission.amount = amount - @api.model def create(self, values): res = super(Commission, self).create(values) res._compute_amount() @@ -102,12 +101,10 @@ class Commission(models.Model): 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() @@ -155,7 +152,6 @@ class Commission(models.Model): return True - @api.model def invoice_paid(self, moves): commissions = self._commissions_to_confirm(moves) commissions.sudo().action_confirm() @@ -172,7 +168,7 @@ class Commission(models.Model): 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: + if not journal or not journal.default_account_id: raise UserError('Commission Journal not configured.') liability_account = commission.company_id.commission_liability_id @@ -186,7 +182,7 @@ class Commission(models.Model): # Already paid. payments = commission.source_move_id._get_reconciled_payments() if payments: - date = max(payments.mapped('payment_date')) + date = max(payments.mapped('date')) if commission.accounting_date: date = commission.accounting_date @@ -198,7 +194,7 @@ class Commission(models.Model): 'date': date, 'ref': ref, 'journal_id': journal.id, - 'type': 'entry', + 'move_type': 'entry', 'line_ids': [ (0, 0, { 'name': ref, @@ -210,7 +206,7 @@ class Commission(models.Model): (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, + 'account_id': journal.default_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, }), diff --git a/hr_commission/tests/test_commission.py b/hr_commission/tests/test_commission.py index f921f30a..86a84ebe 100644 --- a/hr_commission/tests/test_commission.py +++ b/hr_commission/tests/test_commission.py @@ -2,10 +2,6 @@ from odoo.tests import common -# TODO Tests won't pass without `sale` -# Tests should be refactored to not build sale orders -# to invoice, just create invoices directly. - class TestCommission(common.TransactionCase): @@ -44,34 +40,32 @@ class TestCommission(common.TransactionCase): '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, + + def _create_invoice(self, user): + # Create invoice + invoice = self.env['account.move'].create({ + 'move_type': 'out_invoice', + 'partner_id': self.env.ref("base.res_partner_2").id, + 'currency_id': self.env.ref('base.USD').id, + 'invoice_date': '2020-12-11', + 'invoice_user_id': user.id, + 'invoice_line_ids': [(0, 0, { + 'product_id': self.env.ref("product.product_product_4").id, + 'quantity': 1, '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 - + + self.assertEqual(invoice.invoice_user_id.id, user.id) + self.assertEqual(invoice.payment_state, 'not_paid') + return invoice + 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 + commission_journal.default_account_id = expense_account self.env.user.company_id.commission_journal_id = commission_journal coach = self._createEmployee(self.browse_ref('base.user_root')) @@ -83,12 +77,12 @@ class TestCommission(common.TransactionCase): contract = self._createContract(emp, 5.0) - so = self._createInvoiceableSaleOrder(user) - inv = so._create_invoices() + inv = self._create_invoice(user) + 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.assertEqual(inv.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) @@ -114,11 +108,12 @@ class TestCommission(common.TransactionCase): 'currency_id': inv.currency_id.id, 'journal_id': pay_journal.id, }) - payment.post() + payment.action_post() + + receivable_line = payment.move_id.line_ids.filtered('credit') - 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.') + self.assertEqual(inv.payment_state, 'in_payment', '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.') @@ -150,8 +145,7 @@ class TestCommission(common.TransactionCase): 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 + commission_journal.default_account_id = expense_account self.env.user.company_id.commission_journal_id = commission_journal @@ -164,8 +158,7 @@ class TestCommission(common.TransactionCase): contract = self._createContract(emp, 5.0) - so = self._createInvoiceableSaleOrder(user) - inv = so._create_invoices() + inv = self._create_invoice(user) self.assertFalse(inv.commission_ids, 'Commissions exist when invoice is created.') inv.action_post() # validate self.assertEqual(inv.state, 'posted') @@ -188,8 +181,7 @@ class TestCommission(common.TransactionCase): 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 + commission_journal.default_account_id = expense_account self.env.user.company_id.commission_journal_id = commission_journal @@ -202,8 +194,6 @@ class TestCommission(common.TransactionCase): 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', @@ -212,9 +202,9 @@ class TestCommission(common.TransactionCase): (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() + inv = self._create_invoice(user) + inv.partner_id.commission_structure_id = commission_structure self.assertFalse(inv.commission_ids, 'Commissions exist when invoice is created.') inv.action_post() # validate self.assertEqual(inv.state, 'posted') From e274f5a7f6e5fa5253691918eb32bf3e35932564 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 24 May 2021 11:05:10 -0700 Subject: [PATCH 04/15] [FIX] hr_commission: creating in webclient --- hr_commission/models/commission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hr_commission/models/commission.py b/hr_commission/models/commission.py index c6f3b25c..f6b891f0 100644 --- a/hr_commission/models/commission.py +++ b/hr_commission/models/commission.py @@ -85,7 +85,7 @@ class Commission(models.Model): amount = 0.0 commission.amount = amount - + @api.model def create(self, values): res = super(Commission, self).create(values) res._compute_amount() From 8ee491a9da2a0365a7b28813431c679e825a2efa Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 24 May 2021 11:53:14 -0700 Subject: [PATCH 05/15] [IMP] hr_commission: add option for tax excluded invoice total --- hr_commission/models/account.py | 3 ++- hr_commission/models/res_company.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/hr_commission/models/account.py b/hr_commission/models/account.py index eb4d8350..6180d014 100644 --- a/hr_commission/models/account.py +++ b/hr_commission/models/account.py @@ -37,10 +37,11 @@ class AccountMove(models.Model): 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.move_type in ['in_refund', 'out_refund'] else 1 return self.margin * sign + elif self.company_id.commission_amount_type == 'on_invoice_untaxed': + return self.amount_untaxed_signed return self.amount_total_signed def action_cancel(self): diff --git a/hr_commission/models/res_company.py b/hr_commission/models/res_company.py index 26a2acb4..febdcaf9 100644 --- a/hr_commission/models/res_company.py +++ b/hr_commission/models/res_company.py @@ -15,4 +15,5 @@ class ResCompany(models.Model): commission_amount_type = fields.Selection([ ('on_invoice_margin', 'On Invoice Margin'), ('on_invoice_total', 'On Invoice Total'), + ('on_invoice_untaxed', 'On Invoice Total Tax Excluded'), ], string='Commission Base', default='on_invoice_margin') From 17f517586190735448bcb4e4db8a6fe7ca9a45ce Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Fri, 24 Sep 2021 16:11:55 -0700 Subject: [PATCH 06/15] [FIX] hr_commission: `account.move` soft deprecated method post --- hr_commission/models/commission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hr_commission/models/commission.py b/hr_commission/models/commission.py index f6b891f0..2744dff4 100644 --- a/hr_commission/models/commission.py +++ b/hr_commission/models/commission.py @@ -212,7 +212,7 @@ class Commission(models.Model): }), ], }) - move.post() + move._post() commission.write({'state': 'done', 'move_id': move.id}) return True From 04cd1cbda01a23646319b2b66bf2b60b182a4e1e Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Thu, 7 Oct 2021 11:50:35 -0700 Subject: [PATCH 07/15] [MIG] hr_commission: to Odoo 15.0 --- hr_commission/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hr_commission/__manifest__.py b/hr_commission/__manifest__.py index 83eea70e..491981fc 100644 --- a/hr_commission/__manifest__.py +++ b/hr_commission/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'Hibou Commissions', 'author': 'Hibou Corp. ', - 'version': '14.0.1.0.0', + 'version': '15.0.1.0.0', 'category': 'Accounting/Commissions', 'license': 'OPL-1', 'website': 'https://hibou.io/', From aacd794fe5d98c2ecdb7da50ecd6a5337bbc2533 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 1 Nov 2021 14:52:26 -0700 Subject: [PATCH 08/15] [IMP] hr_commission: display default account on misc journal, force delete journal entry --- hr_commission/__manifest__.py | 2 +- hr_commission/tests/test_commission.py | 69 ++++++++++++++++++++++++ hr_commission/views/account_views.xml | 11 ++++ hr_commission/views/commission_views.xml | 2 +- 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/hr_commission/__manifest__.py b/hr_commission/__manifest__.py index 491981fc..422f75ef 100644 --- a/hr_commission/__manifest__.py +++ b/hr_commission/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'Hibou Commissions', 'author': 'Hibou Corp. ', - 'version': '15.0.1.0.0', + 'version': '15.0.1.1.0', 'category': 'Accounting/Commissions', 'license': 'OPL-1', 'website': 'https://hibou.io/', diff --git a/hr_commission/tests/test_commission.py b/hr_commission/tests/test_commission.py index 86a84ebe..3ee6192e 100644 --- a/hr_commission/tests/test_commission.py +++ b/hr_commission/tests/test_commission.py @@ -1,6 +1,7 @@ # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. from odoo.tests import common +from odoo.exceptions import UserError class TestCommission(common.TransactionCase): @@ -219,3 +220,71 @@ class TestCommission(common.TransactionCase): 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.') + + def test_commission_cancel_post_journal_entry(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_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) + + inv = self._create_invoice(user) + + self.assertFalse(inv.commission_ids, 'Commissions exist when invoice is created.') + inv.action_post() # validate + self.assertEqual(inv.state, 'posted') + self.assertEqual(inv.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.action_post() + + receivable_line = payment.move_id.line_ids.filtered('credit') + + inv.js_assign_outstanding_line(receivable_line.id) + self.assertEqual(inv.payment_state, 'in_payment', '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.') + + # By Default, Odoo does NOT allow you to cancel/unlink a journal entry. + with self.assertRaises(UserError): + user_commission.action_cancel() + + # Our form has the needed context to allow cancel/unlink of a journal entry. + user_commission.with_context(force_delete=True).action_cancel() + self.assertEqual(user_commission.state, 'cancel', 'Commission is not cancelled.') + self.assertFalse(user_commission.move_id, 'Commission didn\'t remove journal entry.') diff --git a/hr_commission/views/account_views.xml b/hr_commission/views/account_views.xml index 5caf5cd3..ec9e4836 100644 --- a/hr_commission/views/account_views.xml +++ b/hr_commission/views/account_views.xml @@ -15,4 +15,15 @@ + + account.journal.form.inherit + account.journal + + + + {'required': [('type', '=', 'purchase')], 'invisible': [('type', 'not in', ('purchase', 'general'))]} + + + + \ No newline at end of file diff --git a/hr_commission/views/commission_views.xml b/hr_commission/views/commission_views.xml index 62a695ca..abfb7e67 100644 --- a/hr_commission/views/commission_views.xml +++ b/hr_commission/views/commission_views.xml @@ -9,7 +9,7 @@
From d547edac05e0501b1205fc4642aa92b1b086c9e2 Mon Sep 17 00:00:00 2001 From: Mishael De La Cruz Date: Tue, 2 Nov 2021 09:58:15 -0500 Subject: [PATCH 09/15] [I18N] multi: Initial Translations 15.0 --- hr_commission/i18n/es.po | 530 +++++++++++++++++++++++++++++ hr_commission/models/commission.py | 10 +- 2 files changed, 535 insertions(+), 5 deletions(-) create mode 100644 hr_commission/i18n/es.po diff --git a/hr_commission/i18n/es.po b/hr_commission/i18n/es.po new file mode 100644 index 00000000..36b58c6d --- /dev/null +++ b/hr_commission/i18n/es.po @@ -0,0 +1,530 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_commission +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-10-30 01:21+0000\n" +"PO-Revision-Date: 2021-10-30 01:21+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.res_config_settings_view_form +msgid "" +"" +msgstr "" +"" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__hr_commission__rate_type__admin +msgid "Admin" +msgstr "Admin" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_contract__admin_commission_rate +msgid "Admin Commission %" +msgstr "& de Comisión de Admin" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__amount +msgid "Amount" +msgstr "Monto" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__base_amount +msgid "Base Amount" +msgstr "Monto Base" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__base_total +msgid "Base Total" +msgstr "Base Total" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__hr_commission__state__cancel +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "Cancelled" +msgstr "Cancelado" + +#. module: hr_commission +#: model:ir.model.fields,help:hr_commission.field_hr_commission__accounting_date +msgid "" +"Choose the accounting date at which you want to value the commission moves " +"created by the commission instead of the default one." +msgstr "" +"Elija la fecha contable en la que desea valorar los movimientos de comisión " +"creados por la comisión en lugar de la predeterminada" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_form +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_tree +msgid "Commission" +msgstr "Comisión" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__rate +#: model:ir.model.fields,field_description:hr_commission.field_hr_contract__commission_rate +msgid "Commission %" +msgstr "% de Comisión" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__commission_amount +msgid "Commission Amount" +msgstr "Monto de Comisión" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_payment_tree +msgid "Commission Amount Total" +msgstr "Monto Total de Comisión" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_res_company__commission_amount_type +#: model:ir.model.fields,field_description:hr_commission.field_res_config_settings__commission_amount_type +#: model_terms:ir.ui.view,arch_db:hr_commission.res_config_settings_view_form +msgid "Commission Base" +msgstr "Comisión Base" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__commission_count +msgid "Commission Count" +msgstr "Recuento de comisiones" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_payment_tree +msgid "Commission Count Total" +msgstr "Total del Recuento de Comisiones" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_res_company__commission_journal_id +#: model:ir.model.fields,field_description:hr_commission.field_res_config_settings__commission_journal_id +msgid "Commission Journal" +msgstr "Libro Diario de Comisiones" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_res_company__commission_liability_id +#: model:ir.model.fields,field_description:hr_commission.field_res_config_settings__commission_liability_id +msgid "Commission Liability Account" +msgstr "Cuenta de Pasivo de Comisión" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_hr_commission_payment +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__payment_id +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_payment_form +msgid "Commission Payment" +msgstr "Pago de comisiones" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_payment_form +msgid "Commission Payment Description" +msgstr "Descripción del Pago de Comisión" + +#. module: hr_commission +#: model:ir.actions.act_window,name:hr_commission.action_hr_commission_payment +#: model:ir.ui.menu,name:hr_commission.menu_action_account_commission_payment_form +msgid "Commission Payments" +msgstr "Pagos de Comisión" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_hr_commission_structure +#: model:ir.model.fields,field_description:hr_commission.field_res_partner__commission_structure_id +#: model:ir.model.fields,field_description:hr_commission.field_res_users__commission_structure_id +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_structure_form +msgid "Commission Structure" +msgstr "Estructura de Comisión" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_hr_commission_structure_line +msgid "Commission Structure Line" +msgstr "Línea de estructura de comisión" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_tree +msgid "Commission Total" +msgstr "Total de comisión" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.res_config_settings_view_form +msgid "" +"Commission journal default accounts can be thought of as the 'expense' side of the commission. If a Liability account\n" +" is not chosen, then the employee's home address partner's Account Payable will be used instead." +msgstr "" +"Las cuentas predeterminado del libro diario de la comision se puede " +"considerar como el 'coste' de la comision. Si no se ha definido la cuenta " +"pasiva, entonces se utilizará la cuenta a pagar del socio del la direccion " +"de casa del empleado" + +#. module: hr_commission +#: code:addons/hr_commission/models/commission.py:0 +#, python-format +msgid "" +"Commission liability account must be configured if employee's don't have AP " +"setup." +msgstr "" +"Se debe configurar la cuenta pasiva de la comisión si los empleados no tienen" +" la cuenta a pagar configurada" + +#. module: hr_commission +#: model:ir.actions.act_window,name:hr_commission.action_hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_account_bank_statement_line__commission_ids +#: model:ir.model.fields,field_description:hr_commission.field_account_move__commission_ids +#: model:ir.model.fields,field_description:hr_commission.field_account_payment__commission_ids +#: model:ir.ui.menu,name:hr_commission.menu_action_account_commission_form +#: model:ir.ui.menu,name:hr_commission.menu_action_account_commission_form2 +#: model_terms:ir.ui.view,arch_db:hr_commission.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_pivot +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_pivot_graph +#: model_terms:ir.ui.view,arch_db:hr_commission.view_move_form_inherit +msgid "Commissions" +msgstr "Comisiones" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__company_id +msgid "Company" +msgstr "Compañía" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_res_config_settings +msgid "Config Settings" +msgstr "Opciones de configuración" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_form +msgid "Confirm" +msgstr "Confirmar" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__hr_commission__state__done +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "Confirmed" +msgstr "Confirmado" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__contract_id +msgid "Contract" +msgstr "Contrato" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__create_uid +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__create_uid +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure__create_uid +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__create_date +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__create_date +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure__create_date +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__create_date +msgid "Created on" +msgstr "Creado en" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__move_date +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__date +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_payment_search +msgid "Date" +msgstr "Fecha" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__display_name +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__display_name +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure__display_name +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__display_name +msgid "Display Name" +msgstr "Nombre para mostrar" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__employee_id +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__employee_id +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__employee_id +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_payment_search +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "Employee" +msgstr "Empleado" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_hr_contract +msgid "Employee Contract" +msgstr "Contrato del empleado" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__accounting_date +msgid "Force Accounting Date" +msgstr "Forzar Fecha de Contabilidad" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_payment_search +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "Group By" +msgstr "Agrupar Por" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__id +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__id +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure__id +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__id +msgid "ID" +msgstr "ID" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "Invoice" +msgstr "Factura" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_tree +msgid "Invoice Total" +msgstr "Total de la Factura" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.res_config_settings_view_form +msgid "Journal" +msgstr "Libro Diario" + +#. module: hr_commission +#: model:ir.model,name:hr_commission.model_account_move +msgid "Journal Entry" +msgstr "Asiento contable" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission____last_update +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment____last_update +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure____last_update +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line____last_update +msgid "Last Modified on" +msgstr "Última Modificación en" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__write_uid +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__write_uid +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure__write_uid +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__write_uid +msgid "Last Updated by" +msgstr "Última Actualización por" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__write_date +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__write_date +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure__write_date +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__write_date +msgid "Last Updated on" +msgstr "Última Actualización en" + +#. module: hr_commission +#: model:ir.model.fields,help:hr_commission.field_hr_commission_structure_line__rate +msgid "Leave 0.0 to use the employee's current contract rate." +msgstr "" +"Déjelo como 0.0 para utilizar la tarifa actual del contrato del empleado" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.res_config_settings_view_form +msgid "Liability Account" +msgstr "Cuenta Pasiva" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure__line_ids +msgid "Lines" +msgstr "Líneas" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__hr_commission__rate_type__manual +msgid "Manual" +msgstr "Manual" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_tree +msgid "Margin" +msgstr "Margen" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_tree +msgid "Margin Total" +msgstr "Margen Total" + +#. module: hr_commission +#: model:ir.actions.server,name:hr_commission.action_commission_mark_paid +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_form +msgid "Mark Paid" +msgstr "Marcar como Pagado" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__memo +msgid "Memo" +msgstr "Nota" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__move_id +msgid "Move" +msgstr "Movimiento" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__name +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure__name +msgid "Name" +msgstr "Nombre" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__hr_commission__state__draft +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "New" +msgstr "Nuevo" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__hr_commission__rate_type__normal +msgid "Normal" +msgstr "Normal" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_account_bank_statement_line__commission_count +#: model:ir.model.fields,field_description:hr_commission.field_account_move__commission_count +#: model:ir.model.fields,field_description:hr_commission.field_account_payment__commission_count +msgid "Number of Commissions" +msgstr "Número de Comisiones" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__res_company__commission_amount_type__on_invoice_margin +msgid "On Invoice Margin" +msgstr "En el margen de factura" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__res_company__commission_type__on_invoice_paid +msgid "On Invoice Paid" +msgstr "En factura pagada" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__res_company__commission_amount_type__on_invoice_total +msgid "On Invoice Total" +msgstr "En el total de factura" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__res_company__commission_amount_type__on_invoice_untaxed +msgid "On Invoice Total Tax Excluded" +msgstr "En total de factura con impuestos excluidos" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__res_company__commission_type__on_invoice +msgid "On Invoice Validation" +msgstr "En la validación de factura" + +#. module: hr_commission +#: model:ir.model.fields.selection,name:hr_commission.selection__hr_commission__state__paid +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "Paid" +msgstr "Pagado" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__commission_ids +msgid "Paid Commissions" +msgstr "Comisión Pagada" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_res_company__commission_type +#: model:ir.model.fields,field_description:hr_commission.field_res_config_settings__commission_type +#: model_terms:ir.ui.view,arch_db:hr_commission.res_config_settings_view_form +msgid "Pay Commission" +msgstr "Pagar Comisión" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__rate +msgid "Rate" +msgstr "Tarifa" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__rate_type +msgid "Rate Type" +msgstr "Tipo de Tarifa" + +#. module: hr_commission +#: model:ir.model.fields,help:hr_commission.field_hr_commission__user_id +#: model:ir.model.fields,help:hr_commission.field_hr_commission_payment__user_id +msgid "Related user name for the resource to manage its access." +msgstr "" +"Nombre del usuario relacionada para que el recurso administre su acceso." + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "Search Commission" +msgstr "Buscar Comisiones" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_payment_search +msgid "Search Commission Payment" +msgstr "Buscar Pago de Comisiones" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_form +msgid "Set Draft" +msgstr "Establecer Borrador" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__source_move_id +msgid "Source Move" +msgstr "Movimiento de Origen" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__state +#: model_terms:ir.ui.view,arch_db:hr_commission.view_hr_commission_search +msgid "Status" +msgstr "Estado" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__structure_id +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_structure_line__structure_id +#: model:ir.model.fields.selection,name:hr_commission.selection__hr_commission__rate_type__structure +msgid "Structure" +msgstr "Estructura" + +#. module: hr_commission +#: model_terms:ir.ui.view,arch_db:hr_commission.res_config_settings_view_form +msgid "These taxes are set in any new product created." +msgstr "Estos impuestos se establecen en cualquier producto nuevo creado" + +#. module: hr_commission +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission__user_id +#: model:ir.model.fields,field_description:hr_commission.field_hr_commission_payment__user_id +msgid "User" +msgstr "Usuario" + +#. module: hr_commission +#: code:addons/hr_commission/models/commission.py:0 +#, python-format +msgid "You cannot delete a commission when it has an accounting entry." +msgstr "No puede eliminar una comisión cuando tiene un asiento contable" + +#. module: hr_commission +#: code:addons/hr_commission/models/commission.py:0 +#, python-format +msgid "You cannot mark a commission \"paid\" if it is not already \"done\"." +msgstr "Usted no puede marcar un comisión \"pagada\" si no está ya \"completada\". + +#. module: hr_commission +#: code:addons/hr_commission/models/commission.py:0 +#, python-format +msgid "You must have at least one \"done\" commission." +msgstr "Usted debe tener al menos una comisión \"completada\"." diff --git a/hr_commission/models/commission.py b/hr_commission/models/commission.py index 2744dff4..f427db14 100644 --- a/hr_commission/models/commission.py +++ b/hr_commission/models/commission.py @@ -1,6 +1,6 @@ # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. -from odoo import api, fields, models +from odoo import api, fields, models, _ from odoo.tools import float_is_zero from odoo.exceptions import UserError @@ -95,7 +95,7 @@ class Commission(models.Model): def unlink(self): if self.filtered(lambda c: c.move_id): - raise UserError('You cannot delete a commission when it has an accounting entry.') + 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): @@ -175,7 +175,7 @@ class Commission(models.Model): 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.') + 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) @@ -218,9 +218,9 @@ class Commission(models.Model): 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".') + 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.') + 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 From fe72c4705e210007a2eb134df01b03cd688d30d5 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 18 Jan 2022 10:55:41 -0800 Subject: [PATCH 10/15] [FIX] hr_commission: tests can now be 'paid', use invoicing manager group --- hr_commission/tests/test_commission.py | 4 ++-- hr_commission/views/res_config_settings_views.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hr_commission/tests/test_commission.py b/hr_commission/tests/test_commission.py index 3ee6192e..1d15ef70 100644 --- a/hr_commission/tests/test_commission.py +++ b/hr_commission/tests/test_commission.py @@ -114,7 +114,7 @@ class TestCommission(common.TransactionCase): receivable_line = payment.move_id.line_ids.filtered('credit') inv.js_assign_outstanding_line(receivable_line.id) - self.assertEqual(inv.payment_state, 'in_payment', 'Invoice is not paid.') + self.assertEqual(inv.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.') @@ -274,7 +274,7 @@ class TestCommission(common.TransactionCase): receivable_line = payment.move_id.line_ids.filtered('credit') inv.js_assign_outstanding_line(receivable_line.id) - self.assertEqual(inv.payment_state, 'in_payment', 'Invoice is not paid.') + self.assertEqual(inv.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.') diff --git a/hr_commission/views/res_config_settings_views.xml b/hr_commission/views/res_config_settings_views.xml index 645c8f06..ccb41ddd 100644 --- a/hr_commission/views/res_config_settings_views.xml +++ b/hr_commission/views/res_config_settings_views.xml @@ -7,9 +7,9 @@ - +

Commissions

-
+
From 9190c2330b5039ee67ab11f24e4932387efdf230 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Mon, 31 Jan 2022 16:16:34 -0800 Subject: [PATCH 11/15] [FIX] hr_commission: use new invoice level salesperson field --- hr_commission/models/commission.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hr_commission/models/commission.py b/hr_commission/models/commission.py index f427db14..ccb4c0a8 100644 --- a/hr_commission/models/commission.py +++ b/hr_commission/models/commission.py @@ -99,7 +99,7 @@ class Commission(models.Model): 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) + return moves.filtered(lambda i: i.invoice_user_id and not i.commission_ids) def _commissions_to_confirm(self, moves): commissions = moves.mapped('commission_ids') @@ -121,7 +121,7 @@ class Commission(models.Model): 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) + employee = employee_obj.search([('user_id', '=', move.invoice_user_id.id)], limit=1) contract = employee.contract_id if all((employee, contract)): move.commission_ids += commission_obj.create({ From 63fd2c9d2a33202b106972063a097d9f80f9c46a Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 1 Feb 2022 14:08:06 -0800 Subject: [PATCH 12/15] [IMP] hr_commission: extended ahead of timesheet based commission * Allow invoice to see commission it is deriving amount for. This allows for basing on amounts that may depend on the employee or contract config. * Prevent making commissions if the employee rate or admin rates are not set. * Updated views to work in both CE and EE --- hr_commission/models/account.py | 2 +- hr_commission/models/commission.py | 8 +++--- hr_commission/views/commission_views.xml | 31 +++++++++++++++++------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/hr_commission/models/account.py b/hr_commission/models/account.py index 6180d014..1048faa1 100644 --- a/hr_commission/models/account.py +++ b/hr_commission/models/account.py @@ -36,7 +36,7 @@ class AccountMove(models.Model): self.env['hr.commission'].invoice_paid(self) return res - def amount_for_commission(self): + def amount_for_commission(self, commission=None): if hasattr(self, 'margin') and self.company_id.commission_amount_type == 'on_invoice_margin': sign = -1 if self.move_type in ['in_refund', 'out_refund'] else 1 return self.margin * sign diff --git a/hr_commission/models/commission.py b/hr_commission/models/commission.py index ccb4c0a8..59219090 100644 --- a/hr_commission/models/commission.py +++ b/hr_commission/models/commission.py @@ -71,14 +71,14 @@ class Commission(models.Model): elif commission.contract_id and commission.rate_type != 'manual': if commission.rate_type == 'normal': commission.rate = commission.contract_id.commission_rate - else: + elif commission.rate_type == 'admin': 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() + commission.base_amount = commission.source_move_id.amount_for_commission(commission) amount = (commission.base_amount * commission.rate) / 100.0 if float_is_zero(amount, precision_rounding=rounding): @@ -123,7 +123,7 @@ class Commission(models.Model): else: employee = employee_obj.search([('user_id', '=', move.invoice_user_id.id)], limit=1) contract = employee.contract_id - if all((employee, contract)): + if all((employee, contract, contract.commission_rate)): move.commission_ids += commission_obj.create({ 'employee_id': employee.id, 'contract_id': contract.id, @@ -136,7 +136,7 @@ class Commission(models.Model): # Admin/Coach commission. employee = employee.coach_id contract = employee.contract_id - if all((employee, contract)): + if all((employee, contract, contract.admin_commission_rate)): move.commission_ids += commission_obj.create({ 'employee_id': employee.id, 'contract_id': contract.id, diff --git a/hr_commission/views/commission_views.xml b/hr_commission/views/commission_views.xml index abfb7e67..e5864b9e 100644 --- a/hr_commission/views/commission_views.xml +++ b/hr_commission/views/commission_views.xml @@ -54,7 +54,7 @@ - + @@ -118,16 +118,16 @@ @@ -210,8 +210,8 @@ action = records.action_mark_paid() @@ -240,4 +240,17 @@ action = records.action_mark_paid() + + Commission Structures + hr.commission.structure + tree,form + + + + From 8dfd287c4af21478cc81854c6c11874e119c3d9f Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 1 Feb 2022 14:03:44 -0800 Subject: [PATCH 13/15] [FIX] hr_commission: filter to sales invoices Additionally, don't require salesperson (e.g. commission structure or timesheets) --- hr_commission/models/commission.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hr_commission/models/commission.py b/hr_commission/models/commission.py index 59219090..4a0d9580 100644 --- a/hr_commission/models/commission.py +++ b/hr_commission/models/commission.py @@ -99,7 +99,7 @@ class Commission(models.Model): return super(Commission, self).unlink() def _filter_source_moves_for_creation(self, moves): - return moves.filtered(lambda i: i.invoice_user_id and not i.commission_ids) + return moves.filtered(lambda i: i.is_sale_document() and not i.commission_ids) def _commissions_to_confirm(self, moves): commissions = moves.mapped('commission_ids') @@ -120,7 +120,7 @@ class Commission(models.Model): if commission_structure: commission_structure.create_for_source_move(move, move_amount) - else: + elif move.invoice_user_id: employee = employee_obj.search([('user_id', '=', move.invoice_user_id.id)], limit=1) contract = employee.contract_id if all((employee, contract, contract.commission_rate)): From aaa31092ee8684f6e8cab4519c58b0152689b7e1 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 22 Mar 2022 17:01:12 +0000 Subject: [PATCH 14/15] [FIX] hr_commission,hr_payroll_commission: fix linking commission payment Additionally `hr_commission`: test archive contracts in case someone moves one to running --- hr_commission/tests/test_commission.py | 2 ++ hr_commission/views/account_views.xml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hr_commission/tests/test_commission.py b/hr_commission/tests/test_commission.py index 1d15ef70..021652e7 100644 --- a/hr_commission/tests/test_commission.py +++ b/hr_commission/tests/test_commission.py @@ -10,6 +10,8 @@ class TestCommission(common.TransactionCase): 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. + # arcive all current contracts + self.employee.contract_ids.write({'active': False}) def _createUser(self): return self.env['res.users'].create({ diff --git a/hr_commission/views/account_views.xml b/hr_commission/views/account_views.xml index ec9e4836..a0a100d9 100644 --- a/hr_commission/views/account_views.xml +++ b/hr_commission/views/account_views.xml @@ -20,8 +20,8 @@ account.journal - - {'required': [('type', '=', 'purchase')], 'invisible': [('type', 'not in', ('purchase', 'general'))]} + + From d3d3a02168c338376d8e62fcf75a42ccd6ba6a1f Mon Sep 17 00:00:00 2001 From: Leo Pinedo Date: Mon, 24 Oct 2022 19:51:11 +0000 Subject: [PATCH 15/15] [MIG] hr_commission: to 16 test error on state field of commission --- hr_commission/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hr_commission/__manifest__.py b/hr_commission/__manifest__.py index 422f75ef..7154b588 100644 --- a/hr_commission/__manifest__.py +++ b/hr_commission/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'Hibou Commissions', 'author': 'Hibou Corp. ', - 'version': '15.0.1.1.0', + 'version': '16.0.1.1.0', 'category': 'Accounting/Commissions', 'license': 'OPL-1', 'website': 'https://hibou.io/',