From adc4733e15fa0a6643df4131ec73a827b4ab9707 Mon Sep 17 00:00:00 2001 From: seshikanth Date: Wed, 24 Jun 2026 12:20:06 +0530 Subject: [PATCH] #fix: Employee Performance Management Module and few HRMS bugs --- .../views/bench_management_view.xml | 15 +- .../disciplinary/models/employee_displane.py | 179 ++++ .../views/disciplinary_complaint_type.xml | 122 +++ .../disciplinary/views/employee_displance.xml | 169 ++++ .../models/payroll_periods.py | 41 +- ...employee_payslip_download_wizard_views.xml | 5 + .../hr_resignation/__manifest__.py | 1 + .../hr_resignation/models/hr_resignation.py | 25 + .../security/resignation_groups.xml | 15 + .../views/hr_resignation_views.xml | 34 +- ...e_resignation_requirements_proceedings.xml | 2 +- .../static/src/css/hrms_emp_dashboard.css | 22 +- .../static/src/js/hrms_emp_dashboard.js | 16 +- .../static/src/xml/hrms_emp_dashboard.xml | 17 +- .../hrms_employee_appraisal/__manifest__.py | 4 + .../models/__init__.py | 3 + .../models/apprasial_conf.py | 126 ++- .../models/employee_appraisal.py | 800 +++++++++++++++--- .../models/employee_pip.py | 89 ++ .../models/hr_head_nofication.py | 121 +++ .../models/hr_notice_appraisal.py | 301 +++++-- .../hrms_employee_appraisal/models/kpi_kra.py | 15 + .../models/setting_config.py | 76 ++ .../security/ir.model.access.csv | 7 + .../security/performace_record_rules.xml | 111 +++ .../security/security_groups.xml | 45 + .../views/employee_appraisal.xml | 196 +++-- .../views/employee_evalutor.xml | 17 +- .../views/employee_pip.xml | 84 ++ .../views/employee_template_appraisal.xml | 38 +- .../views/hr_notice_appraisal.xml | 129 ++- .../views/stage_config.xml | 41 +- .../requisitions/views/hr_requisition.xml | 14 +- 33 files changed, 2503 insertions(+), 377 deletions(-) create mode 100644 addons_extensions/disciplinary/models/employee_displane.py create mode 100644 addons_extensions/disciplinary/views/disciplinary_complaint_type.xml create mode 100644 addons_extensions/disciplinary/views/employee_displance.xml create mode 100644 addons_extensions/hr_resignation/security/resignation_groups.xml create mode 100644 addons_extensions/hrms_employee_appraisal/models/employee_pip.py create mode 100644 addons_extensions/hrms_employee_appraisal/models/hr_head_nofication.py create mode 100644 addons_extensions/hrms_employee_appraisal/models/setting_config.py create mode 100644 addons_extensions/hrms_employee_appraisal/security/performace_record_rules.xml create mode 100644 addons_extensions/hrms_employee_appraisal/security/security_groups.xml create mode 100644 addons_extensions/hrms_employee_appraisal/views/employee_pip.xml diff --git a/addons_extensions/bench_management_system/views/bench_management_view.xml b/addons_extensions/bench_management_system/views/bench_management_view.xml index 606632e12..e75526621 100644 --- a/addons_extensions/bench_management_system/views/bench_management_view.xml +++ b/addons_extensions/bench_management_system/views/bench_management_view.xml @@ -5,7 +5,7 @@ bench.management.line.list bench.management.line - + @@ -41,7 +41,7 @@ bench.management.line.form bench.management.line -
+ @@ -73,9 +73,7 @@ bench.management.line.kanban bench.management.line - - - + @@ -85,17 +83,12 @@ - - -
-
- - Bench Management bench.management.line kanban,list,form @@ -247,6 +239,7 @@ diff --git a/addons_extensions/disciplinary/models/employee_displane.py b/addons_extensions/disciplinary/models/employee_displane.py new file mode 100644 index 000000000..0abcba19e --- /dev/null +++ b/addons_extensions/disciplinary/models/employee_displane.py @@ -0,0 +1,179 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError +from datetime import date + + +class HRDisciplinaryAction(models.Model): + _name = 'hr.employee.disciplinary' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _description = 'Employee Disciplinary Management' + + active = fields.Boolean(default=True) + name = fields.Char('Reference', copy=False, readonly=True, default=lambda x: _('New')) + employee_id = fields.Many2one('hr.employee', string="Employee", required=True) + company_id = fields.Many2one('res.company', string="Company", required=True, default=lambda self: self.env.company) + employee_code = fields.Char(string='Employee Code', related='employee_id.employee_id',tracking=True,required=True) + # unit_id = fields.Many2one('unit.master', string="Unit",tracking=True) + department_id = fields.Many2one('hr.department', string="Department",tracking=True) + designation_id = fields.Many2one('hr.job', string="Designation",tracking=True) + doj = fields.Date(string="Date of Joining",tracking=True) + referred_by_id = fields.Many2one('res.users', string="Referred By",tracking=True) + loss_of_cost = fields.Float(string="Loss of Cost") + # employee_section_id = fields.Many2one('section.master',string='Section') + disciplinary_complaint_line_ids = fields.One2many('hr.disciplinary.complaint.line','disciplinary_id',string = 'Complaint Lines') + disciplinary_action_line_ids = fields.One2many('hr.disciplinary.action.line','disciplinary_id',string = 'Action Lines') + state = fields.Selection([ + ('new', 'New'), + ('submitted', 'Submitted'), + ('pending', 'Pending'), + ('closed', 'Closed'), + ('cancel', 'Cancel') + ], default='new',tracking=True,string='State') + complaint_name = fields.Text('Complaint', compute='_compute_complaint_name', store=True) + name_1 = fields.Char('Name') + disciplinary_id = fields.Many2one('hr.employee.disciplinary', string="Disciplinary") + complaint_date = fields.Date('Complaint Date') + language_id = fields.Many2one('res.lang', 'Language') + complaint_type_id = fields.Many2one('disciplinary.complaint.type', string="Complaint Type") + mistake_type_id = fields.Many2one('disciplinary.mistake.type', string="Mistake Type", required=True) + complaint = fields.Char(string='Complaints') + employee_id_2 = fields.Many2one('hr.employee', string='Employee') + related_record_count = fields.Integer(string="Disciplinary Action Records Count", compute="_compute_related_record_count") + # general_cat = fields.Many2one('general.category', string="General Category", tracking=True) + # cat_id = fields.Many2one('hr.category','Category') + occurrences = fields.Integer('Occurrences', store=True) + severe = fields.Char('Severe') + major = fields.Char('Major') + less_major = fields.Char('Less Major') + negligible = fields.Char('Negligible') + normal = fields.Char('Normal') + total_mistakes = fields.Char('Total Mistakes') + memo = fields.Char('Memo') + explanation = fields.Char('Explanation') + show_cause = fields.Char('Show Cause') + charge_sheet = fields.Char('Charge Sheet') + warning = fields.Char('Warning') + enquiry_notice = fields.Char('Enquiry Notice') + recovery_order = fields.Char('Recovery_ Order') + stoppage_of_increment = fields.Char('Stoppage Of Increment') + demotion = fields.Char('Demotion') + total_actions = fields.Char('Total Actions') + normal_action = fields.Char('Normal Actions') + suspension = fields.Char('Suspension') + total_cost = fields.Float('Total Cost') + + @api.depends('employee_id') + def _compute_related_record_count(self): + for record in self: + record.related_record_count = self.env['hr.employee.disciplinary'].search_count([('employee_id', '=', record.employee_id.id)]) + + def action_open_related_records(self): + return { + 'name': 'Disciplinary Action Records', + 'type': 'ir.actions.act_window', + 'res_model': 'hr.employee.disciplinary', + 'view_mode': 'list', + 'domain': [('employee_id', '=', self.employee_id.id)], + 'context': {'default_employee_id': self.employee_id.id}, + } + + @api.depends('disciplinary_complaint_line_ids.complaint') + def _compute_complaint_name(self): + for record in self: + complaints = record.disciplinary_complaint_line_ids.mapped('complaint') + record.complaint_name = "\n".join(filter(None, complaints)) + + def action_set_submitted(self): + self.state = 'submitted' + + def action_set_pending(self): + self.state = 'pending' + + def action_set_closed(self): + self.state = 'closed' + + def action_set_cancel(self): + self.state = 'cancel' + + def action_reset_to_new(self): + self.state = 'new' + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if not vals.get('name') or vals['name'] == _('New'): + vals['name'] = self.env['ir.sequence'].next_by_code('hr.employee.sequence') or _('New') + return super().create(vals_list) + + @api.onchange('employee_id') + def _onchange_employee_id(self): + for rec in self: + if rec.employee_id: + rec.employee_code = rec.employee_id.employee_id or '' + rec.department_id = rec.employee_id.department_id.id + rec.designation_id = rec.employee_id.job_id.id + rec.doj = rec.employee_id.doj + rec.company_id = rec.employee_id.company_id.id + # rec.unit_id = rec.employee_id.unit_name_hr.id if rec.employee_id.unit_name_hr else False + # rec.employee_section_id = rec.employee_id.section_name_hr.id if rec.employee_id.section_name_hr else False + else: + rec.employee_code = False + rec.department_id = False + rec.designation_id = False + rec.doj = False + + +class DisciplinaryComplaintLine(models.Model): + _name = 'hr.disciplinary.complaint.line' + _description = 'Disciplinary Complaint Line' + + name = fields.Char('Name') + disciplinary_id = fields.Many2one('hr.employee.disciplinary',string="Disciplinary") + complaint_date = fields.Date('Complaint Date') + language_id = fields.Many2one('res.lang','Language') + complaint_type_id = fields.Many2one('disciplinary.complaint.type',string="Complaint Type") + mistake_type_id = fields.Many2one('disciplinary.mistake.type',string="Mistake Type") + complaint = fields.Char(string='Complaints') + employee_id = fields.Many2one('hr.employee', string='Employee') + + + + +class DisciplinaryActionLine(models.Model): + _name = 'hr.disciplinary.action.line' + _description = 'Disciplinary Action Line' + + name = fields.Char('Name') + disciplinary_id = fields.Many2one('hr.employee.disciplinary',string="Disciplinary") + action_taken_date = fields.Date('Action On') + action_type_id = fields.Many2one('disciplinary.action.type',string="Action Type") + action = fields.Char(string='Description') + action_name = fields.Char('ActionName') + related_complaint_id = fields.Many2one('hr.disciplinary.complaint.line', string="Related Complaint", + domain="[('disciplinary_id', '=', disciplinary_id)]") + employee_id = fields.Many2one('hr.employee', string='Employee') + + @api.constrains('action_taken_date') + def _check_action_taken_date(self): + for record in self: + if record.action_taken_date and record.action_taken_date > date.today(): + raise ValidationError("The Action On date cannot be in the future.") + + +class DisciplinaryActionType(models.Model): + _name = 'disciplinary.action.type' + _description = 'Action Type' + + name = fields.Char('Name', required=True) + +class DisciplinaryComplaintType(models.Model): + _name = 'disciplinary.complaint.type' + _description = 'Complaint Type' + + name = fields.Char('Name', required=True) + +class DisciplinaryMistakeType(models.Model): + _name = 'disciplinary.mistake.type' + _description = 'Mistake Type' + + name = fields.Char('Name', required=True) diff --git a/addons_extensions/disciplinary/views/disciplinary_complaint_type.xml b/addons_extensions/disciplinary/views/disciplinary_complaint_type.xml new file mode 100644 index 000000000..569a0caf9 --- /dev/null +++ b/addons_extensions/disciplinary/views/disciplinary_complaint_type.xml @@ -0,0 +1,122 @@ + + + + disciplinary.complaint.type + disciplinary.complaint.type + + + + + + + + + disciplinary.complaint.type.form + disciplinary.complaint.type + + + + + + + + + + + + + Employee Disciplinary Complaint Type + disciplinary.complaint.type + list,form + + + + + + + hr.disciplinary.action.line + hr.disciplinary.action.line + + + + + + + + + hr.disciplinary.action.line.form + hr.disciplinary.action.line + +
+ + + + + + + + + + + +
+
+
+ + + Employee Disciplinary Action + hr.disciplinary.action.line + list,form + + + + + + + + disciplinary.action.type + disciplinary.action.type + + + + + + + + + disciplinary.action.type.form + disciplinary.action.type + +
+ + + + + +
+
+
+ + + Employee Disciplinary Action Type + disciplinary.action.type + list,form + + + +
\ No newline at end of file diff --git a/addons_extensions/disciplinary/views/employee_displance.xml b/addons_extensions/disciplinary/views/employee_displance.xml new file mode 100644 index 000000000..f762c695b --- /dev/null +++ b/addons_extensions/disciplinary/views/employee_displance.xml @@ -0,0 +1,169 @@ + + + + employee.disciplinary.form + hr.employee.disciplinary + +
+
+ +
+ +
+ +
+
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + hr.employee.disciplinary.list + hr.employee.disciplinary + + + + + + + + + + + + + + + + hr.employee.disciplinary.search + hr.employee.disciplinary + + + + + + + + + + Employee Disciplinary + hr.employee.disciplinary + list,form + + + + + + + + + +
\ No newline at end of file diff --git a/addons_extensions/employee_it_declaration/models/payroll_periods.py b/addons_extensions/employee_it_declaration/models/payroll_periods.py index ed9bc86c8..5cc558492 100644 --- a/addons_extensions/employee_it_declaration/models/payroll_periods.py +++ b/addons_extensions/employee_it_declaration/models/payroll_periods.py @@ -1,12 +1,13 @@ -from odoo import models, fields, api -from odoo.exceptions import ValidationError -import calendar +from odoo import models, fields, api +from odoo.exceptions import ValidationError +import calendar -class PayrollPeriod(models.Model): +class PayrollPeriod(models.Model): _name = 'payroll.period' _description = 'Payroll Period' _rec_name = 'name' + _order = 'id desc' _sql_constraints = [ ('unique_name', 'unique(name)', 'The name must be unique.') ] @@ -14,22 +15,22 @@ class PayrollPeriod(models.Model): from_date = fields.Date(string="From Date", required=True) to_date = fields.Date(string="To Date", required=True) name = fields.Char(string="Name", required=True) - period_line_ids = fields.One2many('payroll.period.line', 'period_id', string="Monthly Periods") - - @api.model_create_multi - def create(self, vals_list): - periods = super().create(vals_list) - active_investment_types = self.env['it.investment.type'].search([('active', '=', True)]) - if active_investment_types: - active_investment_types.write({ - 'period_ids': [(4, period.id) for period in periods], - }) - return periods - - @api.onchange('from_date', 'to_date') - def onchange_from_to_date(self): - for rec in self: - if rec.from_date and rec.to_date: + period_line_ids = fields.One2many('payroll.period.line', 'period_id', string="Monthly Periods") + + @api.model_create_multi + def create(self, vals_list): + periods = super().create(vals_list) + active_investment_types = self.env['it.investment.type'].search([('active', '=', True)]) + if active_investment_types: + active_investment_types.write({ + 'period_ids': [(4, period.id) for period in periods], + }) + return periods + + @api.onchange('from_date', 'to_date') + def onchange_from_to_date(self): + for rec in self: + if rec.from_date and rec.to_date: rec.name = f"{rec.from_date.year}-{rec.to_date.year}" diff --git a/addons_extensions/employee_it_declaration/views/employee_payslip_download_wizard_views.xml b/addons_extensions/employee_it_declaration/views/employee_payslip_download_wizard_views.xml index cf0b7b0cf..d0ce96181 100644 --- a/addons_extensions/employee_it_declaration/views/employee_payslip_download_wizard_views.xml +++ b/addons_extensions/employee_it_declaration/views/employee_payslip_download_wizard_views.xml @@ -41,6 +41,11 @@ +
+
diff --git a/addons_extensions/hr_resignation/__manifest__.py b/addons_extensions/hr_resignation/__manifest__.py index 8ab0426f3..06b508c07 100644 --- a/addons_extensions/hr_resignation/__manifest__.py +++ b/addons_extensions/hr_resignation/__manifest__.py @@ -34,6 +34,7 @@ 'data': [ 'security/hr_resignation_security.xml', 'security/ir.model.access.csv', + 'security/resignation_groups.xml', 'data/data.xml', 'data/ir_sequence_data.xml', 'data/ir_cron_data.xml', diff --git a/addons_extensions/hr_resignation/models/hr_resignation.py b/addons_extensions/hr_resignation/models/hr_resignation.py index cf6e9cbdb..cd39f8e11 100644 --- a/addons_extensions/hr_resignation/models/hr_resignation.py +++ b/addons_extensions/hr_resignation/models/hr_resignation.py @@ -167,8 +167,33 @@ class HrResignation(models.Model): admin_checklist_submitted = fields.Boolean(tracking=True) hr_checklist_submitted = fields.Boolean(tracking=True) + manager_checklist_status = fields.Selection([('pending', 'Pending'),('completed', 'Completed')], compute='_compute_checklist_status') + it_checklist_status = fields.Selection([('pending', 'Pending'),('completed', 'Completed')], compute='_compute_checklist_status') + finance_checklist_status = fields.Selection([('pending', 'Pending'),('completed', 'Completed') ], compute='_compute_checklist_status') + admin_checklist_status = fields.Selection([('pending', 'Pending'),('completed', 'Completed')], compute='_compute_checklist_status') + hr_checklist_status = fields.Selection([('pending', 'Pending'),('completed', 'Completed')], compute='_compute_checklist_status') relieving_documents = fields.Many2many('ir.attachment') + applied_date = fields.Date(string="Applied Date",default=fields.Date.context_today,readonly=True,tracking=True) + + @api.depends('manager_checklist_submitted','it_checklist_submitted','finance_checklist_submitted','admin_checklist_submitted', 'hr_checklist_submitted') + def _compute_checklist_status(self): + for rec in self: + rec.manager_checklist_status = ( + 'completed' if rec.manager_checklist_submitted else 'pending' + ) + rec.it_checklist_status = ( + 'completed' if rec.it_checklist_submitted else 'pending' + ) + rec.finance_checklist_status = ( + 'completed' if rec.finance_checklist_submitted else 'pending' + ) + rec.admin_checklist_status = ( + 'completed' if rec.admin_checklist_submitted else 'pending' + ) + rec.hr_checklist_status = ( + 'completed' if rec.hr_checklist_submitted else 'pending' + ) @api.depends('employee_id') def _compute_user_rights(self): diff --git a/addons_extensions/hr_resignation/security/resignation_groups.xml b/addons_extensions/hr_resignation/security/resignation_groups.xml new file mode 100644 index 000000000..822086f4b --- /dev/null +++ b/addons_extensions/hr_resignation/security/resignation_groups.xml @@ -0,0 +1,15 @@ + + + Resignation Management + 41 + + + Resignation User + + + + + Resignation Manager + + + \ No newline at end of file diff --git a/addons_extensions/hr_resignation/views/hr_resignation_views.xml b/addons_extensions/hr_resignation/views/hr_resignation_views.xml index 1363c4746..d1f3a1dc1 100644 --- a/addons_extensions/hr_resignation/views/hr_resignation_views.xml +++ b/addons_extensions/hr_resignation/views/hr_resignation_views.xml @@ -150,6 +150,7 @@ + @@ -173,12 +174,30 @@ + + + + + + + - - - - - + + + + + @@ -186,7 +205,8 @@ - + @@ -387,7 +407,7 @@ diff --git a/addons_extensions/hr_resignation/views/pre_resignation_requirements_proceedings.xml b/addons_extensions/hr_resignation/views/pre_resignation_requirements_proceedings.xml index 6589247d8..00277d65f 100644 --- a/addons_extensions/hr_resignation/views/pre_resignation_requirements_proceedings.xml +++ b/addons_extensions/hr_resignation/views/pre_resignation_requirements_proceedings.xml @@ -24,7 +24,7 @@ - + { + window.location.reload(); + }, 500); } catch (error) { console.error(error); this.notification.add("Unable to update attendance", { type: "danger" }); diff --git a/addons_extensions/hrms_emp_dashboard/static/src/xml/hrms_emp_dashboard.xml b/addons_extensions/hrms_emp_dashboard/static/src/xml/hrms_emp_dashboard.xml index ab06a9e15..4f3481021 100644 --- a/addons_extensions/hrms_emp_dashboard/static/src/xml/hrms_emp_dashboard.xml +++ b/addons_extensions/hrms_emp_dashboard/static/src/xml/hrms_emp_dashboard.xml @@ -27,10 +27,21 @@
- +
""" ctx = { 'default_model': 'employee.appraisal.template.config', @@ -549,17 +894,132 @@ class EmployeeAppraisal(models.Model): 'context': ctx, } - def action_confirm(self): - for rec in self: - rec.state = 'self_evaluation' - - def action_confirm_manager(self): - for rec in self: - rec.state = 'hr_evaluation' - def action_confirm_hr(self): - for rec in self: - rec.state = 'finance_team' + self.ensure_one() + + if not self.hr_remarks: + raise ValidationError ('Please Provide the Remarks') + + email_to = self.managerapp_id.work_email or '' + + body_html = f""" +
+

Hello,

+ +

+ HR evaluation has been completed for the appraisal of + {self.employee_appraisal_id.name}. +

+ +

+ Kindly review the appraisal and take the necessary action. +

+ +
+ + + + + + + + + + + + + + +
Employee{self.employee_appraisal_id.name or ''}
Department{self.department_appraisal_id.name or ''}
Template{self.template_id.name or ''}
+ +
+ +

Regards,

+

HR Team

+ +
+ """ + + ctx = { + 'default_model': 'employee.appraisal.template.config', + 'default_res_ids': [self.id], + 'default_composition_mode': 'comment', + 'default_email_to': email_to, + 'default_subject': f'HR Evaluation Completed - {self.employee_appraisal_id.name}', + 'default_body': body_html, + 'move_hr_next_stage': True, + } + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'mail.compose.message', + 'view_mode': 'form', + 'target': 'new', + 'context': ctx, + } + + def action_head_hr(self): + self.ensure_one() + + if not self.hr_head_remarks: + raise ValidationError( + _('Please provide HR Head Remarks.') + ) + + email_to = self.created_by_id.work_email or '' + + body_html = f""" +
+

Hello,

+ +

+ HR Head review has been completed for the appraisal of + {self.employee_appraisal_id.name}. +

+ +

+ Kindly proceed with the next level approval. +

+ + + + + + + + + + + + + + +
Employee{self.employee_appraisal_id.name or ''}
Department{self.department_appraisal_id.name or ''}
HR Head Remarks{self.hr_head_remarks or ''}
+ +
+ +

Regards,

+

HR Head

+
+ """ + + ctx = { + 'default_model': 'employee.appraisal.template.config', + 'default_res_ids': [self.id], + 'default_composition_mode': 'comment', + 'default_email_to': email_to, + 'default_subject': f'HR Head Approval - {self.employee_appraisal_id.name}', + 'default_body': body_html, + 'move_hr_head_next_stage': True, + } + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'mail.compose.message', + 'view_mode': 'form', + 'target': 'new', + 'context': ctx, + } def action_send_colleague_feedback(self): for rec in self: @@ -568,6 +1028,7 @@ class EmployeeAppraisal(models.Model): ('id', '!=', rec.employee_appraisal_id.id) ]) vals = [] + email_list = [] for emp in employees: already_exists = self.env['colleague.feedback'].search([ ('employee_appraisal_feed_id', '=', rec.id), @@ -578,7 +1039,123 @@ class EmployeeAppraisal(models.Model): 'colleague_feed_id': emp.id, })) rec.colleague_feed_ids = vals - rec.state = 'colleague_manager' + if self.managerapp_id and self.managerapp_id.work_email: + email_list.append(self.managerapp_id.work_email) + email_to = ",".join(filter(None, email_list)) + + body_html = f""" +
+

Hello Team,

+ +

+ {self.employee_appraisal_id.name} has completed the self-assessment. + Please provide your feedback as part of the appraisal process. +

+ +

+ Your feedback will help in evaluating the employee's overall performance. +

+ +
+ +

Regards,

+

{self.employee_appraisal_id.name}

+
+ """ + + ctx = { + 'default_model': 'employee.appraisal.template.config', + 'default_res_ids': [self.id], + 'default_composition_mode': 'comment', + 'default_email_to': email_to, + 'default_subject': f'Colleague Feedback Request - {self.employee_appraisal_id.name}', + 'default_body': body_html, + 'mark_colleague_feedback_sent': True, + } + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'mail.compose.message', + 'view_mode': 'form', + 'target': 'new', + 'context': ctx, + } + + def action_initiate_pip(self): + self.ensure_one() + self.write({'invite_pip': True}) + + employee_email = self.employee_appraisal_id.work_email or '' + + body_html = f""" +
+

Dear {self.employee_appraisal_id.name},

+ +

+ Based on the recent performance appraisal review, your overall performance + rating indicates that improvement is required in certain areas. +

+ +

+ Therefore, you are requested to attend a Performance Improvement Plan (PIP) + discussion meeting with Management and HR. +

+ + + + + + + + + + + + + + + + + + +
Employee{self.employee_appraisal_id.name or ''}
Department{self.department_appraisal_id.name or ''}
Performance Period{self.appraisal_period_id.appraisal_name or ''}
Overall Rating{self.overall_rating or ''}
+ +
+ +

+ During this meeting, we will discuss performance concerns, + expectations, improvement objectives, and the Performance + Improvement Plan (PIP) timeline. +

+ +

+ Kindly acknowledge and attend the meeting as scheduled. +

+ +
+ +

Regards,

+

Management Team

+
+ """ + + ctx = { + 'default_model': 'employee.appraisal.template.config', + 'default_res_ids': [self.id], + 'default_composition_mode': 'comment', + 'default_email_to': employee_email, + 'default_subject': 'Performance Improvement Plan (PIP) Meeting Notification', + 'default_body': body_html, + 'mark_invite_pip': True, + } + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'mail.compose.message', + 'view_mode': 'form', + 'target': 'new', + 'context': ctx, + } @api.onchange('template_id') def _onchange_template_id(self): @@ -624,7 +1201,6 @@ class ColleagueFeedBack(models.Model): submitted_date = fields.Datetime() def action_submit_feedback(self): - for rec in self: rec.write({ 'state': 'submitted', diff --git a/addons_extensions/hrms_employee_appraisal/models/employee_pip.py b/addons_extensions/hrms_employee_appraisal/models/employee_pip.py new file mode 100644 index 000000000..905d9c21a --- /dev/null +++ b/addons_extensions/hrms_employee_appraisal/models/employee_pip.py @@ -0,0 +1,89 @@ +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class EmployeePIP(models.Model): + _name = 'employee.pip' + _description = 'Performance Improvement Plan' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _rec_name = 'employee_id' + + name = fields.Char(string="PIP Reference",default=lambda self: _('New'),readonly=True ) + employee_id = fields.Many2one( 'hr.employee',required=True) + manager_id = fields.Many2one('hr.employee',string="Manager") + appraisal_id = fields.Many2one('employee.appraisal.template.config',string="Appraisal") + objective = fields.Text(string="Improvement Objective",required=True) + timeline = fields.Selection([ + ('30', '30 Days'), + ('60', '60 Days'), + ('90', '90 Days') + ], default='30', required=True) + start_date = fields.Date(default=fields.Date.today) + end_date = fields.Date() + review_date = fields.Date() + employee_acknowledged = fields.Boolean(string="Employee Acknowledged") + state = fields.Selection([ + ('draft', 'Draft'), + ('running', 'In Progress'), + ('review', 'Under Review'), + ('completed', 'Completed'), + ('failed', 'Failed') + ], default='draft', tracking=True) + task_ids = fields.One2many('employee.pip.task','pip_id',string='Improvement Tasks') + progress_percentage = fields.Float(compute='_compute_progress',store=True) + remarks = fields.Text() + + @api.depends('task_ids.state') + def _compute_progress(self): + for rec in self: + + total = len(rec.task_ids) + + completed = len( + rec.task_ids.filtered( + lambda l: l.state == 'done' + ) + ) + + rec.progress_percentage = ( + (completed / total) * 100 + ) if total else 0 + + @api.onchange('timeline', 'start_date') + def _onchange_timeline(self): + for rec in self: + if rec.start_date and rec.timeline: + rec.end_date = fields.Date.add( + rec.start_date, + days=int(rec.timeline) + ) + + def action_start(self): + self.state = 'running' + + def action_review(self): + self.state = 'review' + + def action_complete(self): + self.state = 'completed' + + def action_fail(self): + self.state = 'failed' + + + + +class EmployeePIPTask(models.Model): + _name = 'employee.pip.task' + _description = 'PIP Task' + + pip_id = fields.Many2one('employee.pip',ondelete='cascade') + name = fields.Char(required=True) + description = fields.Text() + target_date = fields.Date() + training_course = fields.Char(string="Suggested Training") + state = fields.Selection([ + ('pending', 'Pending'), + ('progress', 'In Progress'), + ('done', 'Completed') + ], default='pending') \ No newline at end of file diff --git a/addons_extensions/hrms_employee_appraisal/models/hr_head_nofication.py b/addons_extensions/hrms_employee_appraisal/models/hr_head_nofication.py new file mode 100644 index 000000000..38366da68 --- /dev/null +++ b/addons_extensions/hrms_employee_appraisal/models/hr_head_nofication.py @@ -0,0 +1,121 @@ +from odoo import api, fields, models + + +class HrHeadNofication(models.Model): + _name = 'hr.head.notification' + _description = 'HeadNofication' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + @api.returns('self') + def _default_employee_get(self): + return self.env.user.employee_id + + hr_employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee_get) + name = fields.Char("Subject") + appraisal_type_id = fields.Many2one('employee.appraisal.type') + appraisal_period_id = fields.Many2one('employee.appraisal.year', + domain="[('appraisal_type_id', '=', appraisal_type_id)]") + body = fields.Html(string="Notice Body", required=True) + start_date = fields.Date() + end_date = fields.Date() + hr_ids = fields.Many2many('hr.employee', string="HR Team") + # hr_employee_domain_ids = fields.Many2many('hr.employee',compute='_compute_hr_employee_domain') + stage_config_ids = fields.Many2many('employee.stage.config', string="Stages") + state = fields.Selection([ + ('draft', 'Draft'), + ('sent', 'Sent') + ], default='draft') + seq = fields.Char(string="Reference", readonly=True, copy=False, default="New") + + @api.model + def _get_hr_users_domain(self): + group = self.env.ref('hrms_employee_appraisal.group_appraisal_hr') + if group: + return [('groups_id', 'in', [group.id])] + return [('id', '=', False)] + + hr_users_ids = fields.Many2many('res.users', string="HR Team", copy=False, domain=_get_hr_users_domain) + + @api.model + def create(self, vals): + if vals.get('seq', 'New') == 'New': + company = self.env.company + company_code = company.short_code or 'CMP' + today = fields.Datetime.now() + month = str(today.month).zfill(2) + year = str(today.year)[-2:] + prefix = f"{company_code}/{month}/{year}" + last_record = self.search([ + ('seq', '=like', f'{prefix}%') + ], order='id desc', limit=1) + number = 1 + if last_record and last_record.seq: + try: + number = int( + last_record.seq.split('/')[-1] + ) + 1 + except Exception: + number = 1 + vals['seq'] = ( + f"{prefix}/{str(number).zfill(3)}" + ) + return super().create(vals) + + + def action_sent_hr(self): + self.ensure_one() + + hr_emails = self.hr_users_ids.mapped('email') + email_to = ",".join(filter(None, hr_emails)) + + body_html = f""" +
+

Hello HR Team,

+ +

{self.body or ''}

+ + + + + + + + + + + + + + + + + + +
Appraisal Type{self.appraisal_type_id.name or ''}
Appraisal Period{self.appraisal_period_id.appraisal_name or ''}
Start Date{self.start_date or ''}
End Date{self.end_date or ''}
+ +
+ +

Please initiate the appraisal process.

+ +

Regards,

+

HR Head

+
+ """ + + ctx = { + 'default_model': 'hr.head.notification', + 'default_res_ids': [self.id], + 'default_composition_mode': 'comment', + 'default_email_to': email_to, + 'default_subject': self.name, + 'default_body': body_html, + 'mark_hr_notification_sent': True, + } + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'mail.compose.message', + 'view_mode': 'form', + 'target': 'new', + 'context': ctx, + } \ No newline at end of file diff --git a/addons_extensions/hrms_employee_appraisal/models/hr_notice_appraisal.py b/addons_extensions/hrms_employee_appraisal/models/hr_notice_appraisal.py index 4432a9b6c..546a6c78d 100644 --- a/addons_extensions/hrms_employee_appraisal/models/hr_notice_appraisal.py +++ b/addons_extensions/hrms_employee_appraisal/models/hr_notice_appraisal.py @@ -2,6 +2,8 @@ from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from random import randint +import logging +_logger = logging.getLogger(__name__) class HrNoticeAppraisal(models.Model): _name = 'hr.notice.appraisal' @@ -15,10 +17,11 @@ class HrNoticeAppraisal(models.Model): return self.env.user.employee_id hr_employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee_get) + notification_id = fields.Many2one('hr.head.notification','HR') subject = fields.Char(string="Subject", required=True, tracking=True) body = fields.Html(string="Notice Body", required=True) - start_date = fields.Datetime(string="Start Date", required=True) - end_date = fields.Datetime(string="End Date", required=True) + start_date = fields.Date(string="Start Date") + end_date = fields.Date(string="End Date") employee_ids = fields.Many2many('hr.employee', 'notice_employee_rel', 'notice_id', 'employee_id',string="Employees") manager_ids = fields.Many2many('hr.employee', 'notice_manager_rel', 'notice_id', 'manager_id', string="Managers") state = fields.Selection([ @@ -45,36 +48,81 @@ class HrNoticeAppraisal(models.Model): stage_config = fields.Many2many('employee.stage.config',string='Stages') hr_department_ids = fields.Many2many('hr.department', string="Departments") - @api.model - def create(self, vals): - if vals.get('seq', 'New') == 'New': - company = self.env.company - company_code = company.short_code or 'CMP' - today = fields.Datetime.now() - month = str(today.month).zfill(2) - year = str(today.year)[-2:] - prefix = f"{company_code}/{month}/{year}" - last_record = self.search([ - ('seq', '=like', f'{prefix}%') - ], order='id desc', limit=1) - number = 1 - if last_record and last_record.seq: - try: - number = int( - last_record.seq.split('/')[-1] - ) + 1 - except Exception: - number = 1 - vals['seq'] = ( - f"{prefix}/{str(number).zfill(3)}" - ) - return super().create(vals) + # @api.model + # def create(self, vals): + # if vals.get('seq', 'New') == 'New': + # company = self.env.company + # company_code = company.short_code or 'CMP' + # today = fields.Datetime.now() + # month = str(today.month).zfill(2) + # year = str(today.year)[-2:] + # prefix = f"{company_code}/{month}/{year}" + # last_record = self.search([ + # ('seq', '=like', f'{prefix}%') + # ], order='id desc', limit=1) + # number = 1 + # if last_record and last_record.seq: + # try: + # number = int( + # last_record.seq.split('/')[-1] + # ) + 1 + # except Exception: + # number = 1 + # vals['seq'] = ( + # f"{prefix}/{str(number).zfill(3)}" + # ) + # return super().create(vals) - @api.constrains('start_date', 'end_date') - def _check_dates(self): + @api.constrains('start_date', 'end_date','employee_ids','manager_ids','appraisal_notice_id','state') + def _check_appraisal_validations(self): for rec in self: - if rec.end_date < rec.start_date: - raise ValidationError(_("End Date must be greater than Start Date.")) + if rec.start_date and rec.end_date: + if rec.end_date <= rec.start_date: + raise ValidationError( + _("End Date must be greater than Start Date.") + ) + if rec.state == 'cancelled': + continue + duplicate_period = self.search([ + ('id', '!=', rec.id), + ('appraisal_notice_id', '=', rec.appraisal_notice_id.id), + ('hr_employee_id', '=', rec.hr_employee_id.id), + ('state', '!=', 'cancelled'), + ], limit=1) + if duplicate_period: + raise ValidationError(_( + "An appraisal notification already exists for appraisal period '%s'." + ) % rec.appraisal_notice_id.display_name) + + for employee in rec.employee_ids: + duplicate_employee = self.search([ + ('id', '!=', rec.id), + ('employee_ids', 'in', employee.id), + ('state', '!=', 'cancelled'), + ('start_date', '<=', rec.end_date), + ('end_date', '>=', rec.start_date), + ], limit=1) + _logger.info( + "Duplicate Period Records: %s", + duplicate_period.ids + ) + if duplicate_employee: + raise ValidationError(_( + "Employee '%s' is already assigned in another appraisal notification for the selected period." + ) % employee.name) + + for manager in rec.manager_ids: + duplicate_manager = self.search([ + ('id', '!=', rec.id), + ('manager_ids', 'in', manager.id), + ('state', '!=', 'cancelled'), + ('start_date', '<=', rec.end_date), + ('end_date', '>=', rec.start_date), + ], limit=1) + if duplicate_manager: + raise ValidationError(_( + "Manager '%s' is already assigned in another appraisal notification for the selected period." + ) % manager.name) @api.onchange('employee_ids') def _onchange_employee_ids(self): @@ -89,45 +137,6 @@ class HrNoticeAppraisal(models.Model): manager_emails = self.manager_ids.mapped('work_email') all_emails = employee_emails + manager_emails email_to = ",".join(filter(None, all_emails)) - template_obj = self.env['employee.appraisal.template'] - grouped_employees = {} - for employee in self.employee_ids: - manager = employee.parent_id - department = employee.department_id - key = ( - manager.id if manager else False, - department.id if department else False - ) - if key not in grouped_employees: - grouped_employees[key] = self.env['hr.employee'] - grouped_employees[key] |= employee - for (manager_id, department_id), employees in grouped_employees.items(): - already_exists = template_obj.search([ - ('notice_id', '=', self.id), - # ('employee_eva_id', '=', manager_id), - ('employee_department_id', '=', department_id), - ], limit=1) - if already_exists: - continue - template_obj.create({ - 'name': self.subject, - 'seq': self.seq, - 'employee_eva_id': manager_id, - 'employee_department_id': department_id, - 'employee_ids': [(6, 0, employees.ids)], - 'manager_ids': [(6, 0, [manager_id])] if manager_id else [(5, 0, 0)], - 'notice_id': self.id, - 'start_date': self.start_date, - 'end_date': self.end_date, - 'appraisal_period_id': self.appraisal_notice_id.id, - 'appraisal_period_type_id': self.appraisal_type_id.id, - 'template_rating_bool': self.employee_rating, - 'template_point_bool': self.employee_points, - 'hr_employee_id': self.hr_employee_id.id, - 'stage_config_ids': [(6, 0, self.stage_config.ids)], - }) - # print('1234',template_obj.create({})) - body_html = f"""

Hello,

@@ -226,6 +235,7 @@ class StageConfig(models.Model): name = fields.Char(required=True) seq = fields.Integer(required=True) + colour_seq = fields.Integer(required=True) active = fields.Boolean(default=True) color = fields.Integer('Color', default=_get_default_color_stage) @@ -240,26 +250,157 @@ class MailComposeMessage(models.TransientModel): model = self.env.context.get('default_model') res_ids = self.env.context.get('default_res_ids') if self.env.context.get('mark_notice_sent'): - if model == 'hr.notice.appraisal' and res_ids: records = self.env[model].browse(res_ids) - - records.write({ - 'state': 'sent' - }) - if self.env.context.get('mark_appraisal_sent'): + template_obj = self.env['employee.appraisal.template'] + for rec in records: + grouped_employees = {} + for employee in rec.employee_ids: + manager = employee.parent_id + department = employee.department_id + key = ( + manager.id if manager else False, + department.id if department else False + ) + if key not in grouped_employees: + grouped_employees[key] = self.env['hr.employee'] + grouped_employees[key] |= employee + for (manager_id, department_id), employees in grouped_employees.items(): + already_exists = template_obj.search([ + ('notice_id', '=', rec.id), + ('employee_department_id', '=', department_id), + ], limit=1) + if already_exists: + continue + template_obj.create({ + 'name': rec.subject, + 'seq': rec.seq, + 'employee_eva_id': manager_id, + 'employee_department_id': department_id, + 'employee_ids': [(6, 0, employees.ids)], + 'manager_ids': [(6, 0, [manager_id])] if manager_id else [(5, 0, 0)], + 'notice_id': rec.id, + 'start_date': rec.start_date, + 'end_date': rec.end_date, + 'appraisal_period_id': rec.appraisal_notice_id.id, + 'appraisal_period_type_id': rec.appraisal_type_id.id, + 'template_rating_bool': rec.employee_rating, + 'template_point_bool': rec.employee_points, + 'hr_employee_id': rec.hr_employee_id.id, + 'stage_config_ids': [(6, 0, rec.stage_config.ids)], + }) + rec.write({ + 'state': 'sent' + }) + if ( + self.env.context.get('mark_appraisal_sent') + or self.env.context.get('mark_colleague_feedback_sent') + or self.env.context.get('move_hr_next_stage') + or self.env.context.get('move_hr_head_next_stage') + or self.env.context.get('mark_finance_approved') + or self.env.context.get('mark_finance_head_approved') + or self.env.context.get('mark_invite_pip') + ): if model == 'employee.appraisal.template.config' and res_ids: records = self.env[model].browse(res_ids) for record in records: record._move_to_next_stage() + + # if self.env.context.get('mark_appraisal_sent'): + # if model == 'employee.appraisal.template.config' and res_ids: + # records = self.env[model].browse(res_ids) + # for record in records: + # record._move_to_next_stage() + # + # if self.env.context.get('mark_colleague_feedback_sent'): + # if model == 'employee.appraisal.template.config' and res_ids: + # records = self.env[model].browse(res_ids) + # for record in records: + # record._move_to_next_stage() + if self.env.context.get('mark_appraisal_sent_appraisal'): - if model == 'employee.appraisal.template' and res_ids: - records = self.env[model].browse(res_ids) - records.write({ - 'employee_state': 'sent' + templates = self.env[model].browse(res_ids) + appraisal_config_obj = self.env[ + 'employee.appraisal.template.config' + ] + for rec in templates: + first_stage = rec.stage_config_ids.sorted( + key=lambda s: s.seq + )[:1] + for employee in rec.employee_ids: + already_exists = appraisal_config_obj.search([ + ('template_id', '=', rec.id), + ('employee_appraisal_id', '=', employee.id) + ], limit=1) + if already_exists: + continue + appraisal = appraisal_config_obj.create({ + 'template_id': rec.id, + 'seq': rec.seq, + 'employee_appraisal_id': employee.id, + 'employee_ids': [(6, 0, rec.employee_ids.ids)], + 'manager_ids': [(6, 0, rec.manager_ids.ids)], + 'notice_id': rec.notice_id.id, + 'start_date': rec.start_date, + 'end_date': rec.end_date, + 'appraisal_period_id': rec.appraisal_period_id.id, + 'hr_apprai_id': rec.hr_employee_id.id, + 'managerapp_id': rec.employee_eva_id.id, + 'template_empl_rating_bool': rec.template_rating_bool, + 'template_empl_point_bool': rec.template_point_bool, + 'available_stage_ids': [ + (6, 0, rec.stage_config_ids.ids) + ], + 'stage_id': first_stage.id if first_stage else False, + }) + + appraisal._onchange_template_id() + + rec.write({ + 'employee_state': 'sent' + }) + + if self.env.context.get('mark_hr_notification_sent'): + records = self.env[model].browse(res_ids) + for record in records: + for user in record.hr_users_ids: + employee = self.env['hr.employee'].search([ + ('user_id', '=', user.id) + ], limit=1) + if not employee: + continue + already_exists = self.env[ + 'hr.notice.appraisal' + ].search([ + ('notification_id', '=', record.id), + ('hr_employee_id', '=', employee.id) + ], limit=1) + if already_exists: + continue + self.env['hr.notice.appraisal'].create({ + 'notification_id': record.id, + 'hr_employee_id': employee.id, + 'subject': record.name, + 'appraisal_type_id': + record.appraisal_type_id.id, + 'appraisal_notice_id': + record.appraisal_period_id.id, + 'start_date': record.start_date, + 'end_date': record.end_date, + 'seq': record.seq, + 'body': record.body, + 'stage_config': [ + (6, 0, + record.stage_config_ids.ids) + ], + }) + + record.write({ + 'state': 'sent' }) - return res + + return res \ No newline at end of file diff --git a/addons_extensions/hrms_employee_appraisal/models/kpi_kra.py b/addons_extensions/hrms_employee_appraisal/models/kpi_kra.py index 20edde453..aaa370f3a 100644 --- a/addons_extensions/hrms_employee_appraisal/models/kpi_kra.py +++ b/addons_extensions/hrms_employee_appraisal/models/kpi_kra.py @@ -373,6 +373,21 @@ class EmployeeAppraisalKPILine(models.Model): ('4', '4'), ('5', '5'), ], string="Stars", copy=False) + is_employee_reviewer = fields.Boolean(compute="_compute_user_roles", store=False) + is_manager_reviewer = fields.Boolean(compute="_compute_user_roles", store=False) + is_hr_reviewer = fields.Boolean(compute="_compute_user_roles", store=False) + + def _compute_user_roles(self): + current_user = self.env.user + for rec in self: + rec.is_employee_reviewer = ( + rec.kra_line_id.config_id.employee_appraisal_id.user_id.id == current_user.id) + rec.is_manager_reviewer= ( + rec.kra_line_id.config_id.managerapp_id.user_id.id == current_user.id) + rec.is_hr_reviewer= ( + rec.kra_line_id.config_id.hr_apprai_id.user_id.id == current_user.id + ) + # self_rating = fields.Selection([ # ('0', '0'), # ('1', '1'), diff --git a/addons_extensions/hrms_employee_appraisal/models/setting_config.py b/addons_extensions/hrms_employee_appraisal/models/setting_config.py new file mode 100644 index 000000000..4d7075129 --- /dev/null +++ b/addons_extensions/hrms_employee_appraisal/models/setting_config.py @@ -0,0 +1,76 @@ +from odoo import api, fields, models +from datetime import date + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + + appraisal_reminder_days = fields.Integer( + string="Appraisal Reminder Before (Days)", + config_parameter='hrms_employee_appraisal.appraisal_reminder_days', + default=7 + ) + + appraisal_reminder_enabled = fields.Boolean( + string="Enable Appraisal Reminders", + config_parameter='hrms_employee_appraisal.appraisal_reminder_enabled', + default=True + ) + + + + + + +class EmployeeAppraisal(models.Model): + _inherit = 'employee.appraisal.template.config' + + def cron_send_appraisal_reminder(self): + + enabled = self.env['ir.config_parameter'].sudo().get_param( + 'hrms_employee_appraisal.appraisal_reminder_enabled' + ) + + if not enabled: + return + + reminder_days = int( + self.env['ir.config_parameter'].sudo().get_param( + 'hrms_employee_appraisal.appraisal_reminder_days', + 7 + ) + ) + + today = date.today() + + records = self.search([ + ('end_date', '!=', False) + ]) + + for rec in records: + + days_left = (rec.end_date - today).days + + if days_left == reminder_days: + + if rec.employee_appraisal_id.work_email: + + self.env['mail.mail'].sudo().create({ + 'subject': 'Performance Appraisal Reminder', + 'email_to': rec.employee_appraisal_id.work_email, + 'body_html': f""" +

Dear {rec.employee_appraisal_id.name},

+ +

+ Your appraisal period is ending in + {reminder_days} days. +

+ +

+ Please complete your self appraisal. +

+ +
+

Regards,
HR Team

+ """ + }).send() \ No newline at end of file diff --git a/addons_extensions/hrms_employee_appraisal/security/ir.model.access.csv b/addons_extensions/hrms_employee_appraisal/security/ir.model.access.csv index 247be6304..f6abb9a48 100644 --- a/addons_extensions/hrms_employee_appraisal/security/ir.model.access.csv +++ b/addons_extensions/hrms_employee_appraisal/security/ir.model.access.csv @@ -23,7 +23,14 @@ access_appraisal_postpone_wizard,appraisal_postpone_wizard,model_appraisal_postp access_appraisal_cancel_wizard,appraisal.cancel.wizard,model_appraisal_cancel_wizard,base.group_user,1,1,1,1 +access_hr_head_notification,hr.head.notification,model_hr_head_notification,base.group_user,1,1,1,1 + + access_employee_stage_config,employee.stage.config,model_employee_stage_config,base.group_user,1,1,1,1 +access_employee_pip,employee.pip,model_employee_pip,base.group_user,1,1,1,1 +access_employee_pip_task,employee.pip.task,model_employee_pip_task,base.group_user,1,1,1,1 + + diff --git a/addons_extensions/hrms_employee_appraisal/security/performace_record_rules.xml b/addons_extensions/hrms_employee_appraisal/security/performace_record_rules.xml new file mode 100644 index 000000000..fa17c46bc --- /dev/null +++ b/addons_extensions/hrms_employee_appraisal/security/performace_record_rules.xml @@ -0,0 +1,111 @@ + + + + HR Notice - HR Access + + + [('hr_employee_id.user_id', '=', user.id)] + + + + + + HR Notice - Management Access + + [(1,'=',1)] + + + + + Appraisal Template Manager + + + [('employee_eva_id.user_id', '=', user.id)] + + + + + + Appraisal Template HR + + + [('hr_employee_id.user_id', '=', user.id)] + + + + + Appraisal Template - Management Access + + [(1,'=',1)] + + + + + Employee Appraisal - employee access + + + [('employee_appraisal_id.user_id','=', user.id)] + + + + + + Manager Access + + + [('managerapp_id.user_id','=',user.id)] + + + + + + + + + + Employee Appraisal - HR Access + + + [('hr_apprai_id.user_id', '=', user.id)] + + + + + + Employee Appraisal - HR Head Access + + [(1,'=',1)] + + + + + Employee Appraisal - Finance Access + + [(1,'=',1)] + + + + + Employee Appraisal - Finance Head Access + + [(1,'=',1)] + + + + + Employee Appraisal - Management Access + + [(1,'=',1)] + + + + + Employee Appraisal - Management Access + + [(1,'=',1)] + + + + + + \ No newline at end of file diff --git a/addons_extensions/hrms_employee_appraisal/security/security_groups.xml b/addons_extensions/hrms_employee_appraisal/security/security_groups.xml new file mode 100644 index 000000000..d9fdcda58 --- /dev/null +++ b/addons_extensions/hrms_employee_appraisal/security/security_groups.xml @@ -0,0 +1,45 @@ + + + + + Performance Management + 40 + + + + Employee + + + + + Appraisal Manager + + + + + Appraisal HR + + + + + Appraisal HR Head + + + + + Appraisal Finance + + + + + Appraisal Finance Head + + + + + Appraisal Management + + + + + \ No newline at end of file diff --git a/addons_extensions/hrms_employee_appraisal/views/employee_appraisal.xml b/addons_extensions/hrms_employee_appraisal/views/employee_appraisal.xml index 7e6ce572a..9d75ce7d2 100644 --- a/addons_extensions/hrms_employee_appraisal/views/employee_appraisal.xml +++ b/addons_extensions/hrms_employee_appraisal/views/employee_appraisal.xml @@ -13,10 +13,31 @@ - + + + + + employee.app.search + employee.appraisal.template.config + + + + + + + + + + + employee.appraisal.template.config.form employee.appraisal.template.config @@ -32,23 +53,42 @@ domain="[('id', 'in', available_stage_ids)]"/> +
- - - - - - - - - - - - + + + + + + + + + + + + + + - + + - - + + +
@@ -146,27 +206,64 @@ - - + + + - + + - - + + + + + + + + + + + + + + + + + + + + + + @@ -174,47 +271,35 @@ - -