Permalink
Browse files

[IMP] hr_payroll: improve general usability

This commit improves general usability by changing menus,
easing benefits and payslips generation (remove wizards), ...

+ Refactoring of benefit js to use "Odoo style" event binding.
  • Loading branch information...
Musvol committed Jan 8, 2019
1 parent 9a6d6ff commit 2f76a014d0d0fb124f23a4743e0d8433cf4e7077
@@ -32,7 +32,7 @@
'views/hr_leave_views.xml',
'views/resource_views.xml',
'views/hr_benefit_template.xml',
'wizard/hr_benefit_employee_views.xml',
'views/hr_payroll_menu.xml',
],
'demo': ['data/hr_payroll_demo.xml'],
'qweb': [
@@ -18,9 +18,9 @@ class HrBenefit(models.Model):
active = fields.Boolean(default=True)
employee_id = fields.Many2one('hr.employee', required=True,
domain=lambda self: [('contract_ids.state', 'in', ('open', 'pending')), ('company_id', '=', self.env.user.company_id.id)])
date_start = fields.Datetime(required=True, string='Start')
date_stop = fields.Datetime(string='End')
duration = fields.Float(compute='_compute_duration', inverse='_inverse_duration', store=True, string="Hours")
date_start = fields.Datetime(required=True, string='From')
date_stop = fields.Datetime(string='To')
duration = fields.Float(compute='_compute_duration', inverse='_inverse_duration', store=True, string="Period")
benefit_type_id = fields.Many2one('hr.benefit.type')
color = fields.Integer(related='benefit_type_id.color', readonly=True)
state = fields.Selection([
@@ -73,6 +73,7 @@ def write(self, vals):
vals['active'] = True
if vals['state'] == 'cancelled':
vals['active'] = False
self.mapped('leave_id').action_refuse()
return super(HrBenefit, self).write(vals)

@api.multi
@@ -283,3 +284,16 @@ class HrBenefitType(models.Model):
help="If the active field is set to false, it will allow you to hide the benefit type without removing it.")
is_leave = fields.Boolean(default=False, string="Leave")

class Contacts(models.Model):
""" Personnal calendar filter """

_name = 'hr.user.benefit.employee'
_description = 'Benefits Employees'

user_id = fields.Many2one('res.users', 'Me', required=True, default=lambda self: self.env.user)
employee_id = fields.Many2one('hr.employee', 'Employee', required=True)
active = fields.Boolean('Active', default=True)

_sql_constraints = [
('user_id_employee_id_unique', 'UNIQUE(user_id,employee_id)', 'You cannot have twice the same employee.')
]
@@ -25,15 +25,29 @@ def _compute_payslip_count(self):
for employee in self:
employee.payslip_count = len(employee.slip_ids)

def has_non_validated_benefits(self, date_from, date_to):
return bool(self.env['hr.benefit'].search_count([
('employee_id', 'in', self.ids),
('date_start', '<=', date_to),
('date_stop', '>=', date_from),
('state', 'in', ['draft', 'confirmed'])
]))

@api.multi



@api.model
def generate_benefit(self, date_start, date_stop):

date_start = date_start.replace(tzinfo=pytz.utc)
date_stop = date_stop.replace(tzinfo=pytz.utc)
def _format_datetime(date):
fmt = '%Y-%m-%d %H:%M:%S'
date = datetime.strptime(date, fmt) if isinstance(date, str) else date
return date.replace(tzinfo=pytz.utc) if not date.tzinfo else date

for employee in self:
date_start = _format_datetime(date_start)
date_stop = _format_datetime(date_stop)

for employee in self.env['hr.contract'].search([('state', 'in', ['open', 'pending', 'close'])]).mapped('employee_id'):
# Approved leaves
emp_leaves = employee.resource_calendar_id.leave_ids.filtered(
lambda r:
@@ -69,5 +69,5 @@ def action_validate(self):
def action_refuse(self):
super(HrLeave, self).action_refuse()
benefits = self.env['hr.benefit'].search([('leave_id', 'in', self.ids)])
benefits.write({'display_warning': False, 'leave_id': None})
benefits.write({'display_warning': False, 'active': False})
return True
@@ -24,16 +24,16 @@ class HrPayslip(models.Model):
'mandatory anymore and thus the rules applied will be all the rules set on the '
'structure of all contracts of the employee valid for the chosen period')
name = fields.Char(string='Payslip Name', readonly=True,
states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
number = fields.Char(string='Reference', readonly=True, copy=False,
states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
employee_id = fields.Many2one('hr.employee', string='Employee', required=True, readonly=True,
states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
date_from = fields.Date(string='Date From', readonly=True, required=True,
default=lambda self: fields.Date.to_string(date.today().replace(day=1)), states={'draft': [('readonly', False)]})
default=lambda self: fields.Date.to_string(date.today().replace(day=1)), states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
date_to = fields.Date(string='Date To', readonly=True, required=True,
default=lambda self: fields.Date.to_string((datetime.now() + relativedelta(months=+1, day=1, days=-1)).date()),
states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
# this is chaos: 4 states are defined, 3 are used ('verify' isn't) and 5 exist ('confirm' seems to have existed)
state = fields.Selection([
('draft', 'Draft'),
@@ -46,28 +46,42 @@ class HrPayslip(models.Model):
\n* If the payslip is confirmed then status is set to \'Done\'.
\n* When user cancel payslip the status is \'Rejected\'.""")
line_ids = fields.One2many('hr.payslip.line', 'slip_id', string='Payslip Lines', readonly=True,
states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
company_id = fields.Many2one('res.company', string='Company', readonly=True, copy=False,
default=lambda self: self.env['res.company']._company_default_get(),
states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
worked_days_line_ids = fields.One2many('hr.payslip.worked_days', 'payslip_id',
string='Payslip Worked Days', copy=True, readonly=True,
states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
input_line_ids = fields.One2many('hr.payslip.input', 'payslip_id', string='Payslip Inputs',
readonly=True, states={'draft': [('readonly', False)]})
readonly=True, states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
paid = fields.Boolean(string='Made Payment Order ? ', readonly=True, copy=False,
states={'draft': [('readonly', False)]})
note = fields.Text(string='Internal Note', readonly=True, states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
note = fields.Text(string='Internal Note', readonly=True, states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
contract_id = fields.Many2one('hr.contract', string='Contract', readonly=True,
states={'draft': [('readonly', False)]})
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
credit_note = fields.Boolean(string='Credit Note', readonly=True,
states={'draft': [('readonly', False)]},
states={'draft': [('readonly', False)], 'verify': [('readonly', False)]},
help="Indicates this payslip has a refund of another")
payslip_run_id = fields.Many2one('hr.payslip.run', string='Payslip Batches', readonly=True,
copy=False, states={'draft': [('readonly', False)]})
copy=False, states={'draft': [('readonly', False)], 'verify': [('readonly', False)]})
payslip_count = fields.Integer(compute='_compute_payslip_count', string="Payslip Computation Details")
compute_date = fields.Date('Computed On')

basic_wage = fields.Monetary(compute='_compute_basic_wage')
net_wage = fields.Monetary(compute='_compute_net_wage')
currency_id = fields.Many2one(related='contract_id.currency_id')

@api.multi
def _compute_basic_wage(self):
for payslip in self:
payslip.basic_wage = payslip.get_salary_line_total('BASIC')

@api.multi
def _compute_net_wage(self):
for payslip in self:
payslip.net_wage = payslip.get_salary_line_total('NET')

@api.multi
def _compute_payslip_count(self):
for payslip in self:
@@ -44,5 +44,16 @@
<field name="perm_read" eval="0"/>
</record>

<record id="hr_user_benefit_employee" model="ir.rule">
<field name="name">Benefits/Employee calendar filter: only self</field>
<field name="model_id" ref="model_hr_user_benefit_employee"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_create" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_unlink" eval="1"/>
<field name="perm_read" eval="0"/>
</record>

</data>
</odoo>
@@ -11,3 +11,4 @@ access_hr_payslip_run,hr.payslip.run,model_hr_payslip_run,hr_payroll.group_hr_pa
access_hr_salary_rule_user,hr.salary.rule.user,model_hr_salary_rule,hr_payroll.group_hr_payroll_user,1,1,1,1
access_hr_benefit,access_hr_benefit,model_hr_benefit,group_hr_payroll_user,1,1,1,1
access_hr_benefit_type,access_hr_benefit_type,model_hr_benefit_type,group_hr_payroll_user,1,1,1,1
access_hr_benefit_employee,hr.benefit.employee,model_hr_user_benefit_employee,group_hr_payroll_user,1,1,1,1
@@ -7,42 +7,22 @@ odoo.define('hr_payroll.benefit.view_custo', function(require) {
var _t = core._t;
CalendarController.include({

events: {
'click .btn-benefit-generate': '_onGenerateBenefits',
'click .btn-benefit-validate': '_onValidateBenefits',
'click .btn-payslip-generate': '_onGeneratePayslips',
},

update: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
self._renderBenefitButtons();
});
},

_renderGenerateButton: function(date_from, date_to, employee_ids, secondary) {
var self = this;
var primary = !secondary ? 'btn-primary' : 'btn-secondary';
var txt = _t("Generate Benefits");
_renderBenefitButton: function (text, event_class) {
this.$buttons.find('.o_calendar_button_month').after(
$('<button class="btn ' + primary + ' btn-benefit" type="button">'+ txt +'</button>')
.off('click')
.on('click', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
var date_fmt = 'YYYY-MM-DD HH:mm:ss';
var options = {
on_close: function () {
self.reload();
},
};
self.do_action({
type: 'ir.actions.act_window',
name: txt,
res_model: 'hr.benefit.employees',
view_type: 'form',
views: [[false,'form']],
target: 'new',
context: {
'start_benefits': date_from.format(date_fmt),
'stop_benefits':date_to.format(date_fmt),
},
}, options);
})
$('<button class="btn btn-primary btn-benefit ' + event_class + '" type="button">'+ _t(text) +'</button>')
);
},

@@ -51,60 +31,61 @@ odoo.define('hr_payroll.benefit.view_custo', function(require) {
return;
}

var firstDay = this.model.data.target_date.clone().startOf('month');
var lastDay = this.model.data.target_date.clone().endOf('month');
var events = this._checkDataInRange(firstDay, lastDay, this.model.data.data);
var is_validated = this._checkValidation(events);
this.firstDay = this.model.data.target_date.clone().startOf('month');
this.lastDay = this.model.data.target_date.clone().endOf('month');
this.events = this._checkDataInRange(this.firstDay, this.lastDay, this.model.data.data);
var is_validated = this._checkValidation(this.events);
this.$buttons.find('.btn-benefit').remove();
var employee_ids = _.map(events, function (event) { return event.record.employee_id[0]; });
employee_ids = _.uniq(employee_ids);
var self = this;
if (this.model.data.domain.length !== 0) { // select by default the employee in the domain
var employee_search_id = (this.model.data.domain[0][0] === 'employee_id' && this.model.data.domain[0][1] === '=')? [this.model.data.domain[0][2]]: null;
if (this.events.length === 0) {
this._renderBenefitButton("Generate Benefits", 'btn-benefit-generate');
}
if (events.length === 0) { // Generate button
this._renderGenerateButton(firstDay, lastDay, employee_search_id);
} else {
this._renderGenerateButton(firstDay, lastDay, employee_ids, true);
if (is_validated && this.events.length !== 0) {
this._renderBenefitButton("Generate Payslips", 'btn-payslip-generate');
}
if (is_validated && events.length !== 0) { // Generate Payslip button
this.$buttons.find('.o_calendar_button_month').after(
$('<button class="btn btn-primary btn-benefit" type="button">'+ _t('Generate Payslips') +'</button>')
.off('click')
// action_hr_payslip_by_employees
.on('click', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
var date_fmt = 'YYYY-MM-DD';
self.do_action('hr_payroll.action_hr_payslip_by_employees', {
additional_context: {
default_employee_ids: employee_ids || [],
default_date_start: firstDay.format(date_fmt),
default_date_end: lastDay.format(date_fmt),
},
});
})
);
}
else if (!is_validated) { // Validate button
this.$buttons.find('.o_calendar_button_month').after(
$('<button class="btn btn-primary btn-benefit" type="button">'+ _t('Validate Benefits') +'</button>')
.off('click')
.on('click', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
self._rpc({
model: 'hr.benefit',
method: 'action_validate',
args: [_.map(events, function (event) { return event.record.id; })],
}).then(function () {
return self.reload();
});
})
);
else if (!is_validated) {
this._renderBenefitButton("Validate Benefits", 'btn-benefit-validate');
}
},

_onGeneratePayslips: function (e) {
e.preventDefault();
e.stopImmediatePropagation();
var date_fmt = 'YYYY-MM-DD';
this.do_action('hr_payroll.action_generate_payslips_from_benefits', {
additional_context: {
default_date_start: this.firstDay.format(date_fmt),
default_date_end: this.lastDay.format(date_fmt),
},
});
},

_onValidateBenefits: function (e) {
e.preventDefault();
e.stopImmediatePropagation();
var self = this;
this._rpc({
model: 'hr.benefit',
method: 'action_validate',
args: [_.map(this.events, function (event) { return event.record.id; })],
}).then(function () {
return self.reload();
});
},

_onGenerateBenefits: function (e) {
e.preventDefault();
e.stopImmediatePropagation();
var date_fmt = 'YYYY-MM-DD HH:mm:ss';
var self = this;
this._rpc({
model: 'hr.employee',
method: 'generate_benefit',
args: [this.firstDay.format(date_fmt), this.lastDay.format(date_fmt)],
}).then(function () {
self.reload();
});
},

_checkDataInRange: function (firstDay, lastDay, events) {
var res = _.filter(events, function (event) {
// Filter records that are not inside the current month
@@ -27,8 +27,6 @@ def test_00_payslip_flow(self):
context = {
"lang": "en_US", "tz": False, "active_model": "ir.ui.menu",
"department_id": False, "section_id": False,
"active_ids": [self.ref("hr_payroll.menu_department_tree")],
"active_id": self.ref("hr_payroll.menu_department_tree")
}
# I click on 'Compute Sheet' button on payslip
richard_payslip.with_context(context).compute_sheet()
Oops, something went wrong.

0 comments on commit 2f76a01

Please sign in to comment.