#fix: Employee Performance Management Module and few HRMS bugs

This commit is contained in:
seshikanth 2026-06-24 12:20:06 +05:30
parent a8570dea30
commit adc4733e15
33 changed files with 2503 additions and 377 deletions

View File

@ -5,7 +5,7 @@
<field name="name">bench.management.line.list</field>
<field name="model">bench.management.line</field>
<field name="arch" type="xml">
<list string="Bench Management">
<list create="0" string="Bench Management">
<field name="employee_id"/>
<field name="job_id"/>
<field name="status"/>
@ -41,7 +41,7 @@
<field name="name">bench.management.line.form</field>
<field name="model">bench.management.line</field>
<field name="arch" type="xml">
<form string="Bench Management">
<form create="0" string="Bench Management">
<sheet>
<group>
@ -73,9 +73,7 @@
<field name="name">bench.management.line.kanban</field>
<field name="model">bench.management.line</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<kanban create="0" class="o_kanban_mobile">
<field name="employee_id"/>
<field name="job_id"/>
<field name="status"/>
@ -85,17 +83,12 @@
<field name="future_project_count"/>
<field name="completed_project_count"/>
<field name="project_names_tooltip"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_card oe_kanban_global_click"
style="border-radius:16px;border:1px solid #dbe4ee;background:linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);padding:16px;min-height:260px;box-shadow:0 8px 24px rgba(15, 23, 42, 0.06);">
<!-- Header -->
<div class="d-flex align-items-center mb-3">
<img t-att-src="'/web/image/hr.employee/' + record.employee_id.raw_value + '/avatar_128'"
style="
width:42px;
@ -235,7 +228,6 @@
</field>
</record>
<record id="action_bench_management" model="ir.actions.act_window">
<field name="name">Bench Management</field>
<field name="res_model">bench.management.line</field>
<field name="view_mode">kanban,list,form</field>
@ -247,6 +239,7 @@
<menuitem id="menu_bench_management"
name="Employee Bench"
parent="hr.menu_hr_root"
groups="hr.group_hr_manager"
action="action_bench_management"
sequence="3"/>
</odoo>

View File

@ -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)

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_disciplinary_complaint_type_list" model="ir.ui.view">
<field name="name">disciplinary.complaint.type</field>
<field name="model">disciplinary.complaint.type</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
</list>
</field>
</record>
<record id="view_disciplinary_complaint_type_form" model="ir.ui.view">
<field name="name">disciplinary.complaint.type.form</field>
<field name="model">disciplinary.complaint.type</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" />
</group>
</sheet>
</form>
</field>
</record>
<record id="action_disciplinary_complaint_type" model="ir.actions.act_window">
<field name="name">Employee Disciplinary Complaint Type</field>
<field name="res_model">disciplinary.complaint.type</field>
<field name="view_mode">list,form</field>
</record>
<menuitem
id="menu_view_disciplinary_complaint"
name="Disciplinary Complaints"
action="action_disciplinary_complaint_type"
parent="menu_employee_disciplinary_root"
sequence="19"/>
<record id="view_employee_disciplinary_complaint_line_list" model="ir.ui.view">
<field name="name">hr.disciplinary.action.line</field>
<field name="model">hr.disciplinary.action.line</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
</list>
</field>
</record>
<record id="view_employee_disciplinary_complaint_line_form" model="ir.ui.view">
<field name="name">hr.disciplinary.action.line.form</field>
<field name="model">hr.disciplinary.action.line</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" />
<field name="disciplinary_id" />
<field name="action_taken_date" />
<field name="action_type_id" />
<field name="action" />
<field name="related_complaint_id" />
<field name="employee_id" />
</group>
</sheet>
</form>
</field>
</record>
<record id="action_employee_disciplinary_complaint_line" model="ir.actions.act_window">
<field name="name">Employee Disciplinary Action</field>
<field name="res_model">hr.disciplinary.action.line</field>
<field name="view_mode">list,form</field>
</record>
<menuitem
id="menu_view_employee__disciplinary_complaint"
name="Employee Disciplinary Complaints"
action="action_employee_disciplinary_complaint_line"
parent="menu_employee_disciplinary_root"
sequence="20"/>
<record id="view_disciplinary_action_type_list" model="ir.ui.view">
<field name="name">disciplinary.action.type</field>
<field name="model">disciplinary.action.type</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
</list>
</field>
</record>
<record id="view_disciplinary_action_type_form" model="ir.ui.view">
<field name="name">disciplinary.action.type.form</field>
<field name="model">disciplinary.action.type</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" />
</group>
</sheet>
</form>
</field>
</record>
<record id="action_disciplinary_action_type" model="ir.actions.act_window">
<field name="name">Employee Disciplinary Action Type</field>
<field name="res_model">disciplinary.action.type</field>
<field name="view_mode">list,form</field>
</record>
<menuitem
id="menu_view_disciplinary_action_type"
name="Disciplinary Action Type"
action="action_disciplinary_action_type"
parent="menu_employee_disciplinary_root"
sequence="21"/>
</odoo>

View File

@ -0,0 +1,169 @@
<odoo>
<!-- Disciplinary Form View -->
<record id="view_hr_employee_disciplinary_form" model="ir.ui.view">
<field name="name">employee.disciplinary.form</field>
<field name="model">hr.employee.disciplinary</field>
<field name="arch" type="xml">
<form string="Employee Disciplinary">
<header>
<button name="action_set_submitted"
type="object"
string="Submit"
class="btn-primary"
invisible="state != 'new'"/>
<button name="action_set_pending"
type="object"
string="Pending"
class="btn-warning"
invisible="state != 'submitted'"/>
<button name="action_set_closed"
type="object"
string="Closed"
class="btn-success"
invisible="state != 'pending'"/>
<button name="action_set_cancel" type="object" string="Cancel"
class="btn-danger" invisible="state not in ['new', 'submitted', 'pending']"/>
<button name="action_reset_to_new" type="object"
string="Reset to New" class="btn-secondary" invisible="state != 'cancel'"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_open_related_records"
type="object"
icon="fa-gavel"
class="oe_stat_button full-width-button"
string="Disciplinary">
<field name="related_record_count" widget="statinfo"/>
</button>
</div>
<div>
<h3>
<field name="name" readonly="1"/>
</h3>
</div>
<group>
<group>
<field name="employee_code"/>
<field name="employee_id"/>
<field name="designation_id"/>
<field name="doj"/>
<!-- <field name="general_cat"/>-->
<field name="referred_by_id"/>
<!-- <field name="occurrences"/>-->
</group>
<group>
<field name="company_id"/>
<!-- <field name="unit_id"/>-->
<field name="department_id"/>
<!-- <field name="employee_section_id"/>-->
<field name="loss_of_cost"/>
<!-- <field name="cat_id"/>-->
<field name="total_cost"/>
</group>
</group>
<group string="Complaints" colspan="2">
<group colspan="1">
<field name="complaint_date"/>
<field name="language_id"/>
<field name="complaint_type_id"/>
</group>
<group colspan="1">
<field name="mistake_type_id"/>
<field name="complaint"/>
</group>
</group>
<notebook>
<!-- <page name="'complaints" string = "Complaints">-->
<!-- <field name="disciplinary_complaint_line_ids">-->
<!-- <list string="complaints" editable="bottom">-->
<field name="name" column_invisible="1"/>
<field name="complaint_date"/>
<field name="language_id"/>
<field name="complaint_type_id"/>
<field name="mistake_type_id"/>
<field name="complaint"/>
<field name="disciplinary_id" column_invisible="1"/>
<field name="employee_id" column_invisible="1"/>
<!-- </list>-->
<!-- </field>-->
<!-- </page>-->
<page name="'actions" string="Actions">
<field name="disciplinary_action_line_ids">
<list string="Action Lines" editable="bottom">
<field name="name" column_invisible="1"/>
<field name="action_name"/>
<!-- <field name="action_taken_date"/>-->
<field name="action_taken_date"
context="{'max_date': time.strftime('%Y-%m-%d')}"/>
<field name="action_type_id"/>
<field name="action"/>
<field name="disciplinary_id" column_invisible="1"/>
<field name="employee_id" column_invisible="1"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- list View for Employee Disciplinary -->
<record id="view_hr_employee_disciplinary_list" model="ir.ui.view">
<field name="name">hr.employee.disciplinary.list</field>
<field name="model">hr.employee.disciplinary</field>
<field name="arch" type="xml">
<list string="Employee Disciplinary">
<field name="name"/>
<field name="employee_id" string="Employee"/>
<field name="designation_id" string="Designation"/>
<field name="doj" string="Date of Joining"/>
<field name="company_id" string="Company"/>
<!-- <field name="unit_id" string="Unit"/>-->
<field name="department_id" string="Department"/>
<field name="state"
widget="badge"
decoration-primary="state == 'new'"
decoration-warning="state == 'submitted'"
decoration-info="state == 'pending'"
decoration-success="state == 'closed'"
decoration-danger="state == 'cancel'"/>
</list>
</field>
</record>
<record id="view_hr_employee_disciplinary_search" model="ir.ui.view">
<field name="name">hr.employee.disciplinary.search</field>
<field name="model">hr.employee.disciplinary</field>
<field name="arch" type="xml">
<search string="Search Employee Disciplinary">
<field name="employee_id" string="Employee"/>
<field name="employee_code" string="Employee Code"/>
</search>
</field>
</record>
<record id="action_hr_employee_disciplinary" model="ir.actions.act_window">
<field name="name">Employee Disciplinary</field>
<field name="res_model">hr.employee.disciplinary</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_hr_employee_disciplinary_search"/>
</record>
<!-- <menuitem id="menu_employee_disciplinary_root" name="Employee Disciplinary" sequence="15" parent="hr.menu_hr_root"/>-->
<!-- <menuitem id="menu_employee_disciplinary" name="Employee Disciplinary"-->
<!-- parent="menu_employee_disciplinary_root"-->
<!-- action="action_hr_employee_disciplinary"-->
<!-- sequence="10"/>-->
</odoo>

View File

@ -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}"

View File

@ -41,6 +41,11 @@
<field name="payslip_count" readonly="1"/>
</group>
</sheet>
<footer>
<button string="Close"
special="cancel"
class="btn-secondary"/>
</footer>
</form>
</field>
</record>

View File

@ -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',

View File

@ -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):

View File

@ -0,0 +1,15 @@
<odoo>
<record model="ir.module.category" id="module_resignation_management_group">
<field name="name">Resignation Management</field>
<field name="sequence">41</field>
</record>
<record id="group_resignation_user" model="res.groups">
<field name="name">Resignation User</field>
<field name="category_id" ref="module_resignation_management_group"/>
</record>
<record id="group_resignation_manager" model="res.groups">
<field name="name">Resignation Manager</field>
<field name="category_id" ref="module_resignation_management_group"/>
</record>
</odoo>

View File

@ -150,6 +150,7 @@
<field name="employee_id"
readonly="change_employee == False or state != 'draft'"/>
<field name="department_id"/>
<field name="applied_date"/>
<field name="employee_contract" invisible="state == 'draft'"/>
<field name="emp_responded" readonly="1" force_save="1" invisible="resignation_type != 'abosconded'"/>
<field name="show_withdraw" invisible="not is_hr"/>
@ -173,12 +174,30 @@
<field name="reason"
readonly="state != 'draft'"/>
</group>
<!-- <group string="Checklist Submission Status">-->
<!-- <field name="manager_checklist_submitted" string="Manager" readonly="1" force_save="1"/>-->
<!-- <field name="it_checklist_submitted" string="IT Manager" readonly="1" force_save="1"/>-->
<!-- <field name="finance_checklist_submitted" string="Finance" readonly="1" force_save="1"/>-->
<!-- <field name="admin_checklist_submitted" string="Admin" readonly="1" force_save="1"/>-->
<!-- <field name="hr_checklist_submitted" string="HR" readonly="1" force_save="1" invisible="not is_hr or normal_resignation_status not in ['process_relieving_documents','final_clearance']"/>-->
<!-- </group>-->
<group string="Checklist Submission Status">
<field name="manager_checklist_submitted" string="Manager" readonly="1" force_save="1"/>
<field name="it_checklist_submitted" string="IT Manager" readonly="1" force_save="1"/>
<field name="finance_checklist_submitted" string="Finance" readonly="1" force_save="1"/>
<field name="admin_checklist_submitted" string="Admin" readonly="1" force_save="1"/>
<field name="hr_checklist_submitted" string="HR" readonly="1" force_save="1" invisible="not is_hr or normal_resignation_status not in ['process_relieving_documents','final_clearance']"/>
<field name="manager_checklist_status" string="Manager" widget="badge"
decoration-success="manager_checklist_status == 'completed'"
decoration-warning="manager_checklist_status == 'pending'"/>
<field name="it_checklist_status" string="IT Manager" widget="badge"
decoration-success="it_checklist_status == 'completed'"
decoration-warning="it_checklist_status == 'pending'"/>
<field name="finance_checklist_status" string="Finance" widget="badge"
decoration-success="finance_checklist_status == 'completed'"
decoration-warning="finance_checklist_status == 'pending'"/>
<field name="admin_checklist_status" string="Admin" widget="badge"
decoration-success="admin_checklist_status == 'completed'"
decoration-warning="admin_checklist_status == 'pending'"/>
<field name="hr_checklist_status" string="HR" widget="badge"
decoration-success="hr_checklist_status == 'completed'"
decoration-warning="hr_checklist_status == 'pending'"
invisible="not is_hr or normal_resignation_status not in ['process_relieving_documents','final_clearance']"/>
</group>
</group>
<notebook>
@ -186,7 +205,8 @@
<group>
<field name="emp_comments" readonly="not is_emp" string="Employee"/>
<field name="manager_comments" readonly="not is_manager" string="Manager"/>
<field name="finance_manager_comments" readonly="not is_finance_manager" string="Finance Manager"/>
<field name="finance_manager_comments" readonly="not is_finance_manager"
string="Finance Manager"/>
<field name="it_manager_comments" readonly="not is_it_manager" string="IT Manager"/>
<field name="admin_comments" readonly="not is_admin" string="Admin"/>
<field name="hr_comments" readonly="not is_hr" string="HR"/>
@ -387,7 +407,7 @@
<!-- Menu item for Approved Resignation -->
<menuitem id="hr_resignation_menu_approved_request"
parent="hr_resignation_menu_root"
name="Approved Resignation"
name="Resignation Status"
action="hr_resignation_approved_action"
groups="hr.group_hr_user"
sequence="4"/>

View File

@ -24,7 +24,7 @@
</field>
</record>
<menuitem id="menu_hr_resignation_configurations" name="Configurations" parent="hr_resignation_menu_root" sequence="100"/>
<menuitem id="menu_hr_resignation_configurations" name="Configurations" groups="hr_resignation.group_resignation_manager" parent="hr_resignation_menu_root" sequence="100"/>
<menuitem id="menu_pre_resignation_root" name="Pre-Resignation" parent="menu_hr_resignation_configurations" sequence="100"/>
<menuitem id="menu_pre_resignation_requirements" name="Requirements Proceedings"

View File

@ -109,9 +109,27 @@
margin-bottom: 4px;
}
.hrms-icon-button.primary {
/*.hrms-icon-button.primary {*/
/* background: #16a34a;*/
/* border-color: #22c55e;*/
/*}*/
.hrms-icon-button.checkin {
background: #16a34a;
border-color: #22c55e;
color: #fff;
}
.hrms-icon-button.checkout {
background: #dc2626;
color: #fff;
}
.hrms-icon-button.checkout:hover {
background: #b91c1c;
}
.hrms-icon-button.checkin:hover {
background: #15803d;
}
.hrms-filter-box {

View File

@ -292,12 +292,15 @@ class HrmsEmployeeDashboard extends Component {
}
// raiseHelpdeskTicket() {
// window.open(
// '/helpdesk/new',
// 'helpdesk',
// 'width=1200,height=800,resizable=yes,scrollbars=yes'
// );
// }
raiseHelpdeskTicket() {
window.open(
'/helpdesk/new',
'helpdesk',
'width=1200,height=800,resizable=yes,scrollbars=yes'
);
window.open('/helpdesk/new', '_blank');
}
async toggleAttendance() {
@ -309,6 +312,9 @@ class HrmsEmployeeDashboard extends Component {
}
this.notification.add(response.message, { type: "success" });
await this.loadData();
setTimeout(() =>{
window.location.reload();
}, 500);
} catch (error) {
console.error(error);
this.notification.add("Unable to update attendance", { type: "danger" });

View File

@ -27,10 +27,21 @@
</div>
<div class="hrms-employee-actions">
<div class="hrms-action-buttons">
<button class="hrms-icon-button primary" t-on-click="toggleAttendance" t-att-title="statusText">
<i t-att-class="state.data.attendance_state === 'checked_in' ? 'fa fa-sign-out' : 'fa fa-sign-in'"/>
<!-- <button class="hrms-icon-button primary" t-on-click="toggleAttendance" t-att-title="statusText">-->
<!-- <i t-att-class="state.data.attendance_state === 'checked_in' ? 'fa fa-sign-out' : 'fa fa-sign-in'"/>-->
<!-- <span t-esc="statusText"/>-->
<!-- </button>-->
<button
t-att-class="'hrms-icon-button ' + (state.data.attendance_state === 'checked_in' ? 'checkout' : 'checkin')"
t-on-click="toggleAttendance"
t-att-title="statusText">
<i t-att-class="state.data.attendance_state === 'checked_in'
? 'fa fa-sign-out'
: 'fa fa-sign-in'"/>
<span t-esc="statusText"/>
</button>
</button>
<button class="hrms-icon-button" t-on-click="downloadPayslip" title="Download Payslip">
<i class="fa fa-download"/>
<span>Payslip</span>

View File

@ -13,12 +13,16 @@
'depends': ['base', 'hr','hr_employee_extended'],
'data': [
'data/reminder_corn.xml',
'security/ir.model.access.csv',
'security/security_groups.xml',
'security/performace_record_rules.xml',
'views/employee_appraisal.xml',
'views/employee_evalutor.xml',
'views/employee_template_appraisal.xml',
'views/hr_notice_appraisal.xml',
'views/stage_config.xml',
'views/employee_pip.xml',
'Wizard/cancel_wizard_hr.xml',
'Wizard/postpone_hr_appraisal.xml',
],

View File

@ -2,3 +2,6 @@ from . import employee_appraisal
from . import apprasial_conf
from . import kpi_kra
from . import hr_notice_appraisal
from . import employee_pip
from . import setting_config
from . import hr_head_nofication

View File

@ -1,4 +1,8 @@
from odoo import api, fields, models
from odoo import api, fields, models,_
from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
class AppraisalYear(models.Model):
_name = 'employee.appraisal.year'
@ -9,8 +13,8 @@ class AppraisalYear(models.Model):
year = fields.Integer(string="Performance Period")
appraisal_name_id = fields.Char(string="Appraisal Type")
appraisal_name = fields.Char(string="Appraisal Name")
start_month = fields.Datetime('Starting Month')
end_month = fields.Datetime('End Month')
start_month = fields.Date('Starting Month')
end_month = fields.Date('End Month')
active = fields.Boolean(default=True)
year_seq = fields.Integer(string="Sequence")
appraisal_type_id = fields.Many2one(
@ -18,6 +22,55 @@ class AppraisalYear(models.Model):
string="Appraisal Type"
)
@api.constrains('appraisal_type_id', 'start_month', 'end_month')
def _check_appraisal_type(self):
for rec in self:
if not rec.start_month or not rec.end_month:
continue
if rec.start_month > rec.end_month:
raise ValidationError(
_("Start Month cannot be greater than End Month.")
)
expected_end = False
if rec.appraisal_type_id.name == 'Monthly':
expected_end = rec.start_month + relativedelta(months=1, days=-1)
elif rec.appraisal_type_id.name == 'Quarterly':
expected_end = rec.start_month + relativedelta(months=3, days=-1)
elif rec.appraisal_type_id.name == 'Half-yearly':
expected_end = rec.start_month + relativedelta(months=6, days=-1)
elif rec.appraisal_type_id.name == 'Annual':
expected_end = rec.start_month + relativedelta(months=12, days=-1)
if expected_end and rec.end_month != expected_end:
raise ValidationError(_(
"Invalid period for %s.\nExpected End Date: %s"
) % (
rec.appraisal_type_id.name,
expected_end.strftime('%d/%m/%Y')
))
duplicate = self.search([
('id', '!=', rec.id),
('appraisal_type_id', '=', rec.appraisal_type_id.id),
('start_month', '<=', rec.end_month),
('end_month', '>=', rec.start_month),
], limit=1)
if duplicate:
raise ValidationError(
_("This appraisal period overlaps with existing period: %s")
% duplicate.appraisal_name
)
class EmployeeAppraisalType(models.Model):
_name = 'employee.appraisal.type'
_description = 'Employee Appraisal Type'
@ -36,7 +89,7 @@ class AppraisalTemplate(models.Model):
name = fields.Char(string="Name")
employee_evaluator_name_id = fields.Many2one('employee.appraisal.evaluator', string="Employee Appraisal Evaluator")
employee_eva_id = fields.Many2one('hr.employee',string="Employee Appraisal Evaluator")
employee_eva_id = fields.Many2one('hr.employee',string="Manager")
hr_employee_id = fields.Many2one('hr.employee',string="Employee HR Employee")
employee_department_id = fields.Many2one('hr.department',string="Department")
company_id = fields.Many2one('res.company', string="Company",default=lambda self: self.env.company)
@ -69,39 +122,40 @@ class AppraisalTemplate(models.Model):
rec.kra_ids.mapped('kra_weightage'))
def action_sent_employee(self):
appraisal_config_obj = self.env['employee.appraisal.template.config']
for rec in self:
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,
# 'state': 'self_evaluation',
})
appraisal._onchange_template_id()
# appraisal_config_obj = self.env['employee.appraisal.template.config']
# for rec in self:
# 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,
# # 'state': 'self_evaluation',
# })
# appraisal._onchange_template_id()
employee_emails = rec.employee_ids.mapped('work_email')
manager_emails = rec.manager_ids.mapped('work_email')
all_emails = employee_emails + manager_emails

View File

@ -20,11 +20,13 @@ class EmployeeAppraisal(models.Model):
name = fields.Char(string="Reference", copy=False)
employee_evaluator_name_id = fields.Many2one('employee.appraisal.evaluator', string="Employee Appraisal Evaluator")
hr_apprai_id = fields.Many2one('hr.employee')
managerapp_id = fields.Many2one('hr.employee')
managerapp_id = fields.Many2one('hr.employee', string='Manager')
# managerapp_id = fields.Many2one('hr.employee', compute='_compute_managerapp_id', string='Manager')
performance_evaluator = fields.Selection([('manager', 'Manager'), ('colleague', 'Colleague'), ('own', 'Own')])
template_id = fields.Many2one('employee.appraisal.template', string="Template")
stage_id = fields.Many2one('employee.stage.config',string='Stage')
available_stage_ids = fields.Many2many('employee.stage.config',compute='_compute_available_stages')
stage_id = fields.Many2one('employee.stage.config', string='Stage')
stage_name = fields.Char(related='stage_id.name', store=True)
available_stage_ids = fields.Many2many('employee.stage.config', compute='_compute_available_stages')
tracking_date = fields.Datetime(default=fields.Datetime.now, readonly=True, index=True)
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company)
state = fields.Selection([
@ -43,13 +45,18 @@ class EmployeeAppraisal(models.Model):
employee_appraisal_id = fields.Many2one('hr.employee')
image_1920 = fields.Image(related='employee_appraisal_id.image_1920')
employee_code = fields.Char(string='Employee Code', related='employee_appraisal_id.employee_id', store=True)
department_appraisal_id = fields.Many2one("hr.department", string="Department",related="employee_appraisal_id.department_id", store=True)
job_appraisal_id = fields.Many2one("hr.job", string="Job Position", related="employee_appraisal_id.job_id",store=True)
department_appraisal_id = fields.Many2one("hr.department", string="Department",
related="employee_appraisal_id.department_id", store=True)
job_appraisal_id = fields.Many2one("hr.job", string="Job Position", related="employee_appraisal_id.job_id",
store=True)
kra_line_ids = fields.One2many('employee.appraisal.kra.line', 'config_id', string='Kra')
manager_remarks = fields.Char(string="Manager Remarks")
hr_remarks = fields.Char(string="HR Remarks")
hr_head_remarks = fields.Text(string="HR Head Remarks")
finance_head_remarks = fields.Text(string="Finance Head Remarks")
colleague_feed_ids = fields.One2many('colleague.feedback', 'employee_appraisal_feed_id', 'Colleague Feed Back')
created_by_id = fields.Many2one('hr.employee', string="Created By", default=lambda self: self.env.user.employee_id,readonly=True)
created_by_id = fields.Many2one('hr.employee', string="Created By", default=lambda self: self.env.user.employee_id,
readonly=True)
created_user_id = fields.Many2one('res.users', default=lambda self: self.env.user, readonly=True)
creator_email = fields.Char(related='created_by_id.work_email', string="Creator Email", readonly=True)
notice_id = fields.Many2one('hr.notice.appraisal', string="Notice")
@ -60,13 +67,16 @@ class EmployeeAppraisal(models.Model):
is_readonly = fields.Boolean(compute="_compute_is_readonly")
mail_sent_employee_ids = fields.Many2many('hr.employee', string="Mail Sent Employees")
seq = fields.Char(string="Sequence", readonly=True, copy=False)
employee_ids = fields.Many2many('hr.employee', 'appraisal_config_employee_rel', 'config_id', 'employee_id',string="Employees")
manager_ids = fields.Many2many('hr.employee', 'appraisal_config_manager_rel', 'config_id', 'manager_id',string="Managers")
employee_ids = fields.Many2many('hr.employee', 'appraisal_config_employee_rel', 'config_id', 'employee_id',
string="Employees")
manager_ids = fields.Many2many('hr.employee', 'appraisal_config_manager_rel', 'config_id', 'manager_id',
string="Managers")
total_employee_score = fields.Float(string="Employee Total Points", compute="_compute_total_scores", store=True)
total_manager_score = fields.Float(string="Manager Total Points", compute="_compute_total_scores", store=True)
total_hr_score = fields.Float(string="HR Total Points", compute="_compute_total_scores", store=True)
manager_email = fields.Char(compute="_compute_manager_email", string="Manager Email")
Note_appraisal = fields.Char(string="User Note", default="Please click KRA's Name, To open the KPI's",Readonly=True)
Note_appraisal = fields.Char(string="User Note", default="Please click KRA's Name, To open the KPI's",
Readonly=True)
overall_score = fields.Float(compute="_compute_overall_scores", store=True)
overall_rating = fields.Selection([
('0', '0'),
@ -91,9 +101,9 @@ class EmployeeAppraisal(models.Model):
('4', '4'),
('5', '5'),
], string="Overall Stars")
employee_overall_score = fields.Float(compute='_compute_overall_scores',store=True)
manager_overall_score = fields.Float(compute='_compute_overall_scores',store=True)
hr_overall_score = fields.Float(compute='_compute_overall_scores',store=True)
employee_overall_score = fields.Float(compute='_compute_overall_scores', store=True)
manager_overall_score = fields.Float(compute='_compute_overall_scores', store=True)
hr_overall_score = fields.Float(compute='_compute_overall_scores', store=True)
employee_overall_star = fields.Selection([
('0', '0'),
('1', '1'),
@ -119,14 +129,73 @@ class EmployeeAppraisal(models.Model):
('5', '5'),
], compute='_compute_overall_scores', store=True)
finance_user_id = fields.Many2one('res.users', string="Finance Approved By", readonly=True)
current_salary = fields.Float(string="Current Salary")
currency_id = fields.Many2one('res.currency',string='Currency',default=lambda self: self.env.company.currency_id.id)
current_salary = fields.Monetary(string="Current Salary",related="employee_appraisal_id.contract_id.wage",currency_field='currency_id',store=True,readonly=True,)
appraisal_percentage = fields.Float(string="Appraisal %")
appraisal_amount = fields.Float(string="Appraisal Amount")
new_salary = fields.Float(string="Revised Salary", compute="_compute_new_salary", store=True)
finance_remarks = fields.Text(string="Finance Remarks")
finance_date = fields.Datetime(string="Finance Approval Date", readonly=True)
salary_update = fields.Boolean(string="Salary Update",default=False,readonly=True)
contract_count = fields.Integer(string="Contracts",compute="_compute_contract_count")
template_empl_rating_bool = fields.Boolean('Star Rating')
template_empl_point_bool = fields.Boolean('Point Rating')
disciplinary_ids = fields.Many2many(
'hr.employee.disciplinary',string='Disciplinary Actions',compute='_compute_disciplinary_ids')
achievement_summary = fields.Html(string="Achievement Summary")
employee_comments = fields.Text(string="Employee Comments")
attachment_ids = fields.Many2many(
'ir.attachment',
'employee_appraisal_attachment_rel',
'appraisal_id',
'attachment_id',
string='Supporting Documents'
)
stage_color_class = fields.Char(compute="_compute_stage_color_class")
invite_pip = fields.Boolean('Invite Pip',default=False)
is_manager_reviewer = fields.Boolean(compute="_compute_user_roles",store=False)
is_hr_reviewer = fields.Boolean(compute="_compute_user_roles",store=False)
is_current_employee = fields.Boolean(compute='_compute_is_current_employee')
@api.depends('employee_appraisal_id')
def _compute_is_current_employee(self):
current_employee = self.env.user.employee_id
for rec in self:
rec.is_current_employee = (
rec.employee_appraisal_id == current_employee
)
def _compute_user_roles(self):
current_user = self.env.user
for rec in self:
rec.is_manager_reviewer = (
rec.managerapp_id.user_id.id == current_user.id
)
rec.is_hr_reviewer = (
rec.hr_apprai_id.user_id.id == current_user.id
)
@api.depends('stage_id')
def _compute_stage_color_class(self):
for rec in self:
if rec.stage_id.colour_seq == 1:
rec.stage_color_class = 'success'
elif rec.stage_id.colour_seq == 2:
rec.stage_color_class = 'info'
elif rec.stage_id.colour_seq == 3:
rec.stage_color_class = 'warning'
elif rec.stage_id.colour_seq == 4:
rec.stage_color_class = 'primary'
else:
rec.stage_color_class = 'danger'
@api.depends('employee_appraisal_id')
def _compute_disciplinary_ids(self):
for rec in self:
rec.disciplinary_ids = self.env['hr.employee.disciplinary'].search([
('employee_id', '=', rec.employee_appraisal_id.id)
])
@api.depends('template_id')
def _compute_available_stages(self):
@ -322,11 +391,20 @@ class EmployeeAppraisal(models.Model):
else:
rec.appraisal_percentage = 0
@api.depends('manager_ids')
def _compute_manager_email(self):
# @api.depends('manager_ids')
# def _compute_manager_email(self):
# for rec in self:
# emails = rec.manager_ids.mapped('work_email')
# rec.manager_email = ",".join(filter(None, emails))
@api.depends('managerapp_id')
def _compute_managerapp_id(self):
for rec in self:
emails = rec.manager_ids.mapped('work_email')
rec.manager_email = ",".join(filter(None, emails))
rec.manager_email = rec.managerapp_id.work_email or ''
@api.onchange('employee_appraisal_id')
def _onchange_employee_appraisal_id(self):
self.managerapp_id = self.employee_appraisal_id.parent_id
@api.depends('end_date')
def _compute_is_readonly(self):
@ -373,44 +451,298 @@ class EmployeeAppraisal(models.Model):
@api.depends('current_salary', 'appraisal_amount')
def _compute_new_salary(self):
for rec in self:
rec.new_salary = (
rec.current_salary +
rec.appraisal_amount
)
rec.new_salary = rec.current_salary + rec.appraisal_amount
@api.onchange('appraisal_percentage')
def _onchange_appraisal_percentage(self):
for rec in self:
if rec.current_salary:
rec.appraisal_amount = (
rec.current_salary *
rec.appraisal_percentage
rec.current_salary * rec.appraisal_percentage
) / 100
def _move_to_next_stage(self):
self.ensure_one()
next_stage = self.env['employee.stage.config'].search(
[('seq', '>', self.stage_id.seq)],
[('id', 'in', self.available_stage_ids.ids),
('seq', '>', self.stage_id.seq)],
order='seq asc',
limit=1
)
if next_stage:
self.stage_id = next_stage.id
def action_finance_approve(self):
for rec in self:
rec.write({
'state': 'management_team',
'finance_user_id': self.env.user.id,
'finance_date': fields.Datetime.now()
})
def action_create_pip(self):
self.ensure_one()
rec.message_post(
body=_(
"Finance appraisal approved."
)
pip = self.env['employee.pip'].create({
'employee_id': self.employee_appraisal_id.id,
'manager_id': self.managerapp_id.id,
'appraisal_id': self.id,
'objective':
'Improve performance and achieve expected goals.',
'timeline': '90',
})
return {
'type': 'ir.actions.act_window',
'res_model': 'employee.pip',
'res_id': pip.id,
'view_mode': 'form',
'target': 'current',
}
def _compute_contract_count(self):
for rec in self:
rec.contract_count = len(rec.employee_appraisal_id.contract_ids)
def action_view_current_contract(self):
self.ensure_one()
contract = self.employee_appraisal_id.contract_id
if not contract:
return False
return {
'type': 'ir.actions.act_window',
'name': 'Contract',
'res_model': 'hr.contract',
'view_mode': 'form',
'res_id': contract.id,
'target': 'current',
}
def action_update_contract_salary(self):
for rec in self:
contract = rec.employee_appraisal_id.contract_id
if not contract:
raise ValidationError( "No active contract found for employee.")
if rec.new_salary <= 0:
raise ValidationError("Revised Salary must be greater than zero.")
contract.write({
'wage': rec.new_salary
})
rec.salary_update = True
def action_finance_approve(self):
self.ensure_one()
if not self.finance_remarks:
raise ValidationError(
_('Please provide Finance Remarks.')
)
if not self.current_salary:
raise ValidationError(
_('Please enter Current Salary.')
)
if not self.appraisal_percentage:
raise ValidationError(
_('Please enter Appraisal Percentage.')
)
email_to = ",".join(
filter(None, [
self.employee_appraisal_id.work_email,
self.managerapp_id.work_email,
self.creator_email
])
)
body_html = f"""
<div>
<p>Hello,</p>
<p>
Finance review has been completed for the appraisal of
<b>{self.employee_appraisal_id.name}</b>.
</p>
<p>
The salary revision details are given below for further approval.
</p>
<br/>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td><b>Employee</b></td>
<td>{self.employee_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Department</b></td>
<td>{self.department_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Current Salary</b></td>
<td>{self.current_salary or 0}</td>
</tr>
<tr>
<td><b>Appraisal Percentage</b></td>
<td>{self.appraisal_percentage or 0}%</td>
</tr>
<tr>
<td><b>Appraisal Amount</b></td>
<td>{self.appraisal_amount or 0}</td>
</tr>
<tr>
<td><b>Revised Salary</b></td>
<td>{self.new_salary or 0}</td>
</tr>
<tr>
<td><b>Finance Remarks</b></td>
<td>{self.finance_remarks or ''}</td>
</tr>
</table>
<br/>
<p>
Kindly review and proceed with the next level approval.
</p>
<br/>
<p>Regards,</p>
<p>Finance Team</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': email_to,
'default_subject': f'Finance Approval - {self.employee_appraisal_id.name}',
'default_body': body_html,
'move_next_stage': True,
'mark_finance_approved': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
def action_finance_head(self):
self.ensure_one()
if not self.finance_head_remarks:
raise ValidationError(
_('Please provide Finance Head Remarks.')
)
email_to = ",".join(
filter(None, [
self.employee_appraisal_id.work_email,
self.managerapp_id.work_email,
self.creator_email
])
)
body_html = f"""
<div>
<p>Hello,</p>
<p>
Finance Head has completed the appraisal review for
<b>{self.employee_appraisal_id.name}</b>.
</p>
<p>
The compensation revision details have been reviewed and approved.
</p>
<br/>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td><b>Employee</b></td>
<td>{self.employee_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Department</b></td>
<td>{self.department_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Current Salary</b></td>
<td>{self.current_salary or 0}</td>
</tr>
<tr>
<td><b>Appraisal %</b></td>
<td>{self.appraisal_percentage or 0}%</td>
</tr>
<tr>
<td><b>Increment Amount</b></td>
<td>{self.appraisal_amount or 0}</td>
</tr>
<tr>
<td><b>Revised Salary</b></td>
<td>{self.new_salary or 0}</td>
</tr>
<tr>
<td><b>Finance Head Remarks</b></td>
<td>{self.finance_head_remarks or ''}</td>
</tr>
</table>
<br/>
<p>
Kindly proceed with the next level approval.
</p>
<br/>
<p>Regards,</p>
<p>Finance Head</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': email_to,
'default_subject': f'Finance Head Approval - {self.employee_appraisal_id.name}',
'default_body': body_html,
'move_next_stage': True,
'mark_finance_head_approved': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
def _send_manager_notification_mail(self):
self.ensure_one()
email_to = self.manager_email
@ -434,7 +766,7 @@ class EmployeeAppraisal(models.Model):
</tr>
<tr>
<td><b>Performance Period</b></td>
<td>{self.appraisal_period_id.appraisal_type_id.id or ''}</td>
<td>{self.appraisal_period_id.appraisal_name or ''}</td>
</tr>
</table>
<br/>
@ -455,81 +787,94 @@ class EmployeeAppraisal(models.Model):
}
self.env['mail.mail'].create(mail_values).send()
def check_colleague_feedback_deadline(self):
now = fields.Datetime.now()
records = self.search([
('college_end_date_time', '!=', False),
('college_end_date_time', '<=', now),
('state', '=', 'colleague_feedback')
])
for rec in records:
rec.write({
'state': 'in_progress'
})
rec.message_post(
body=_(
"Colleague feedback deadline completed automatically. "
"Stage moved to Manager Evaluation."
)
)
rec._send_manager_notification_mail()
return True
# def check_colleague_feedback_deadline(self):
# now = fields.Datetime.now()
# records = self.search([
# ('college_end_date_time', '!=', False),
# ('college_end_date_time', '<=', now),
# ('state', '=', 'colleague_feedback')
# ])
# for rec in records:
# rec.write({
# 'state': 'in_progress'
# })
# rec.message_post(
# body=_(
# "Colleague feedback deadline completed automatically. "
# "Stage moved to Manager Evaluation."
# )
# )
# rec._send_manager_notification_mail()
# return True
def action_sent_employee_appraisal(self):
self.ensure_one()
if not self.manager_remarks:
raise ValidationError("Please give the Manager Remarks")
manager_email = self.managerapp_id.work_email or ''
employee_email = self.employee_appraisal_id.work_email or ''
creator_email = self.creator_email or ''
emails = ",".join(
filter(None, [employee_email, creator_email])
filter(None, [
manager_email,
employee_email,
creator_email
])
)
body_html = f"""
<div>
<p>Hello,Team</p>
<p>
Your appraisal evaluation has been initiated.
</p>
<br/>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td>
<b>Employee</b>
</td>
<div>
<p>Hello Team,</p>
<td>
{self.employee_appraisal_id.name or ''}
</td>
</tr>
<tr>
<td>
<b>Template</b>
</td>
<p>
The employee has completed the self-assessment as part of the performance appraisal process.
Your feedback and evaluation are now requested.
</p>
<td>
{self.template_id.name or ''}
</td>
</tr>
<tr>
<td>
<b>Performance Period</b>
</td>
<br/>
<td>
{self.appraisal_period_id.appraisal_type_id.id or ''}
</td>
</tr>
</table>
<br/>
<p>
Please complete the self evaluation before deadline.
</p>
<br/>
<p>
Regards,
</p>
<p>
HR Team
</p>
</div>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td><b>Employee</b></td>
<td>{self.employee_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Department</b></td>
<td>{self.department_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Appraisal Template</b></td>
<td>{self.template_id.name or ''}</td>
</tr>
<tr>
<td><b>Performance Period</b></td>
<td>{self.appraisal_period_id.appraisal_name or ''}</td>
</tr>
</table>
<br/>
<p>
Kindly review the employee's self-assessment and provide your feedback,
evaluation, and recommendations within the appraisal timeline.
</p>
<p>
Your valuable input will contribute to the employee's overall performance review.
</p>
<br/>
<p>
Regards,
</p>
<p>
Manager Team
</p>
</div>
"""
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"""
<div>
<p>Hello,</p>
<p>
HR evaluation has been completed for the appraisal of
<b>{self.employee_appraisal_id.name}</b>.
</p>
<p>
Kindly review the appraisal and take the necessary action.
</p>
<br/>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td><b>Employee</b></td>
<td>{self.employee_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Department</b></td>
<td>{self.department_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Template</b></td>
<td>{self.template_id.name or ''}</td>
</tr>
</table>
<br/>
<p>Regards,</p>
<p>HR Team</p>
</div>
"""
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"""
<div>
<p>Hello,</p>
<p>
HR Head review has been completed for the appraisal of
<b>{self.employee_appraisal_id.name}</b>.
</p>
<p>
Kindly proceed with the next level approval.
</p>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td><b>Employee</b></td>
<td>{self.employee_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Department</b></td>
<td>{self.department_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>HR Head Remarks</b></td>
<td>{self.hr_head_remarks or ''}</td>
</tr>
</table>
<br/>
<p>Regards,</p>
<p>HR Head</p>
</div>
"""
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"""
<div>
<p>Hello Team,</p>
<p>
{self.employee_appraisal_id.name} has completed the self-assessment.
Please provide your feedback as part of the appraisal process.
</p>
<p>
Your feedback will help in evaluating the employee's overall performance.
</p>
<br/>
<p>Regards,</p>
<p>{self.employee_appraisal_id.name}</p>
</div>
"""
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"""
<div>
<p>Dear {self.employee_appraisal_id.name},</p>
<p>
Based on the recent performance appraisal review, your overall performance
rating indicates that improvement is required in certain areas.
</p>
<p>
Therefore, you are requested to attend a Performance Improvement Plan (PIP)
discussion meeting with Management and HR.
</p>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td><b>Employee</b></td>
<td>{self.employee_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Department</b></td>
<td>{self.department_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Performance Period</b></td>
<td>{self.appraisal_period_id.appraisal_name or ''}</td>
</tr>
<tr>
<td><b>Overall Rating</b></td>
<td>{self.overall_rating or ''}</td>
</tr>
</table>
<br/>
<p>
During this meeting, we will discuss performance concerns,
expectations, improvement objectives, and the Performance
Improvement Plan (PIP) timeline.
</p>
<p>
Kindly acknowledge and attend the meeting as scheduled.
</p>
<br/>
<p>Regards,</p>
<p>Management Team</p>
</div>
"""
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',

View File

@ -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')

View File

@ -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"""
<div>
<p>Hello HR Team,</p>
<p>{self.body or ''}</p>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td><b>Appraisal Type</b></td>
<td>{self.appraisal_type_id.name or ''}</td>
</tr>
<tr>
<td><b>Appraisal Period</b></td>
<td>{self.appraisal_period_id.appraisal_name or ''}</td>
</tr>
<tr>
<td><b>Start Date</b></td>
<td>{self.start_date or ''}</td>
</tr>
<tr>
<td><b>End Date</b></td>
<td>{self.end_date or ''}</td>
</tr>
</table>
<br/>
<p>Please initiate the appraisal process.</p>
<p>Regards,</p>
<p>HR Head</p>
</div>
"""
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,
}

View File

@ -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"""
<div>
<p>Hello,</p>
@ -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

View File

@ -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'),

View File

@ -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"""
<p>Dear {rec.employee_appraisal_id.name},</p>
<p>
Your appraisal period is ending in
<b>{reminder_days}</b> days.
</p>
<p>
Please complete your self appraisal.
</p>
<br/>
<p>Regards,<br/>HR Team</p>
"""
}).send()

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
23
24
25
26
27
28
29
30
31
32
33
34
35
36

View File

@ -0,0 +1,111 @@
<odoo>
<data>
<record id="hr_notice_hr_rule" model="ir.rule">
<field name="name">HR Notice - HR Access</field>
<field name="model_id" ref="model_hr_notice_appraisal"/>
<field name="domain_force">
[('hr_employee_id.user_id', '=', user.id)]
</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_hr'))]"/>
</record>
<!-- Management Access - HR Notice -->
<record id="hr_notice_management_rule" model="ir.rule">
<field name="name">HR Notice - Management Access</field>
<field name="model_id" ref="model_hr_notice_appraisal"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_management'))]"/>
</record>
<record id="appraisal_template_manager_rule" model="ir.rule">
<field name="name">Appraisal Template Manager</field>
<field name="model_id" ref="model_employee_appraisal_template"/>
<field name="domain_force">
[('employee_eva_id.user_id', '=', user.id)]
</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_manager'))]"/>
</record>
<record id="appraisal_template_hr_rule" model="ir.rule">
<field name="name">Appraisal Template HR</field>
<field name="model_id" ref="model_employee_appraisal_template"/>
<field name="domain_force">
[('hr_employee_id.user_id', '=', user.id)]
</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_hr'))]"/>
</record>
<record id="appraisal_template_management_rule" model="ir.rule">
<field name="name">Appraisal Template - Management Access</field>
<field name="model_id" ref="model_employee_appraisal_template"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_management'))]"/>
</record>
<record id="record_rule_employee" model="ir.rule">
<field name="name">Employee Appraisal - employee access</field>
<field name="model_id" ref="model_employee_appraisal_template_config"/>
<field name="domain_force">
[('employee_appraisal_id.user_id','=', user.id)]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="record_rule_manager" model="ir.rule">
<field name="name">Manager Access</field>
<field name="model_id" ref="model_employee_appraisal_template_config"/>
<field name="domain_force">
[('managerapp_id.user_id','=',user.id)]
</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_manager'))]"/>
</record>
<record id="employee_appraisal_hr_rule" model="ir.rule">
<field name="name">Employee Appraisal - HR Access</field>
<field name="model_id" ref="model_employee_appraisal_template_config"/>
<field name="domain_force">
[('hr_apprai_id.user_id', '=', user.id)]
</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_hr'))]"/>
</record>
<record id="employee_appraisal_hr_head_rule" model="ir.rule">
<field name="name">Employee Appraisal - HR Head Access</field>
<field name="model_id" ref="model_employee_appraisal_template_config"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_hr_head'))]"/>
</record>
<record id="employee_appraisal_finance_rule" model="ir.rule">
<field name="name">Employee Appraisal - Finance Access</field>
<field name="model_id" ref="model_employee_appraisal_template_config"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_finance'))]"/>
</record>
<record id="employee_appraisal_finance_head_rule" model="ir.rule">
<field name="name">Employee Appraisal - Finance Head Access</field>
<field name="model_id" ref="model_employee_appraisal_template_config"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_finance_head'))]"/>
</record>
<record id="employee_appraisal_management_rule" model="ir.rule">
<field name="name">Employee Appraisal - Management Access</field>
<field name="model_id" ref="model_employee_appraisal_template_config"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_management'))]"/>
</record>
<record id="employee_appraisal_management_rule" model="ir.rule">
<field name="name">Employee Appraisal - Management Access</field>
<field name="model_id" ref="model_employee_appraisal_template_config"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('hrms_employee_appraisal.group_appraisal_management'))]"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,45 @@
<odoo>
<data noupdate="1">
<record model="ir.module.category" id="module_performance_management_group">
<field name="name">Performance Management</field>
<field name="sequence">40</field>
</record>
<record id="group_appraisal_employee" model="res.groups">
<field name="name">Employee</field>
<field name="category_id" ref="module_performance_management_group"/>
</record>
<record id="group_appraisal_manager" model="res.groups">
<field name="name">Appraisal Manager</field>
<field name="category_id" ref="module_performance_management_group"/>
</record>
<record id="group_appraisal_hr" model="res.groups">
<field name="name">Appraisal HR</field>
<field name="category_id" ref="module_performance_management_group"/>
</record>
<record id="group_appraisal_hr_head" model="res.groups">
<field name="name">Appraisal HR Head</field>
<field name="category_id" ref="module_performance_management_group"/>
</record>
<record id="group_appraisal_finance" model="res.groups">
<field name="name">Appraisal Finance</field>
<field name="category_id" ref="module_performance_management_group"/>
</record>
<record id="group_appraisal_finance_head" model="res.groups">
<field name="name">Appraisal Finance Head</field>
<field name="category_id" ref="module_performance_management_group"/>
</record>
<record id="group_appraisal_management" model="res.groups">
<field name="name">Appraisal Management</field>
<field name="category_id" ref="module_performance_management_group"/>
</record>
</data>
</odoo>

View File

@ -13,10 +13,31 @@
<!-- <field name="performance_evaluator"/>-->
<field name="template_id" string="Template"/>
<field name="appraisal_period_id" string="Performance Period"/>
<!-- <field name="state" widget="badge"/>-->
<field name="appraisal_period_id" string="Performance Period"/>
<field name="stage_id" widget="badge"
decoration-success="stage_color_class == 'success'"
decoration-info="stage_color_class == 'info'"
decoration-warning="stage_color_class == 'warning'"
decoration-primary="stage_color_class == 'primary'"
decoration-danger="stage_color_class == 'danger'"/>
</list>
</field>
</record>
<record id="view_employee_app_search" model="ir.ui.view">
<field name="name">employee.app.search</field>
<field name="model">employee.appraisal.template.config</field>
<field name="arch" type="xml">
<search>
<searchpanel>
<field name="company_id" icon="fa-building"/>
<field name="department_appraisal_id" icon="fa-users"/>
<field name="stage_id" icon="fa-tasks" select="multi" enable_counters="1"/>
</searchpanel>
</search>
</field>
</record>
<record id="view_employee_appraisal_form" model="ir.ui.view">
<field name="name">employee.appraisal.template.config.form</field>
<field name="model">employee.appraisal.template.config</field>
@ -32,23 +53,42 @@
domain="[('id', 'in', available_stage_ids)]"/>
<button name="action_sent_employee_appraisal" string="Send" type="object"
confirm="Do you want to move to next stage?"
class="btn-primary" invisible="stage_id.name != 'NEW'"/>
<!-- class="btn-primary" invisible="state != 'new'"/>-->
groups="hrms_employee_appraisal.group_appraisal_manager"
class="btn-primary" invisible="stage_name != 'COLLEAGUES&amp;MANAGER'"/>
<button name="action_send_colleague_feedback"
string="Send Colleague Feedback &amp; Manager"
type="object"
class="btn-primary" invisible="stage_id.name != 'COLLEAGE&amp;MANAGER'"/>
<!-- invisible="state != 'self_evaluation'"/>-->
<button name="action_confirm_manager" string="Manager" type="object"
confirm="Do you want to move to next stage?"
class="btn-primary" invisible="stage_id.name != 'HR'"/>
<!-- invisible="state != 'colleague_manager'"/>-->
type="object"
class="btn-primary"
invisible="stage_name != 'NEW' or not is_current_employee"/>
<!-- groups="group_appraisal_employee,group_appraisal_management"-->
<!-- -->
<!-- class="btn-primary" invisible="stage_name != 'COLLEAGE&amp;MANAGER'"/>-->
<button name="action_confirm_hr" string="HR Confirm" type="object"
confirm="Do you want to move to next stage?"
class="btn-primary" invisible="stage_id.name != 'HR'"/>
groups="hrms_employee_appraisal.group_appraisal_hr"
class="btn-primary" invisible="stage_name != 'HR'"/>
<button name="action_head_hr"
string="HR Head Approval" type="object" class="btn-primary"
invisible="stage_name != 'HR HEAD'"/>
<!-- invisible="state != 'hr_evaluation'"/>-->
<button name="action_finance_approve" string="Finance Appraisal"
type="object" class="btn-primary" invisible="stage_id.name != 'FINANCE TEAM'"/>
type="object" class="btn-primary" invisible="stage_name != 'FINANCE'"/>
<button name="action_finance_head" string="Finance Head Approval" type="object"
class="btn-primary" invisible="stage_name != 'FINANCE HEAD'"/>
<button name="action_create_pip"
string="Create PIP"
type="object"
class="btn-danger"
invisible="invite_pip == False or overall_rating not in ('0','1','2')"/>
<button name="action_initiate_pip"
string="Initiate PIP"
type="object"
class="btn-danger"
invisible="stage_name != 'MANAGEMENT'
or overall_rating not in ('0', '1', '2')
or invite_pip"/>
<!-- invisible="state != 'finance_team'"/>-->
<!-- <button name="action_confirm" string="Confirm" type="object"-->
<!-- confirm="Do you want to move to next stage?"-->
@ -60,21 +100,38 @@
<!-- invisible="state != 'colleague_feedback'"/>-->
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_current_contract"
type="object"
class="oe_stat_button"
icon="fa-file-text-o">
<field name="contract_count"
string="Contracts"
widget="statinfo"/>
</button>
</div>
<group col="2">
<group>
<field name="seq" string="Performance Id"/>
<field name="employee_appraisal_id" string="Employee"/>
<field name="job_appraisal_id" string="Current Job Title" readonly="1"/>
<field name="department_appraisal_id" string="Department" readonly="1"/>
<field name="template_id" string="Template"/>
<!-- <field name="available_stage_ids" invisible="0" widget="many2many_tags"/>-->
<field name="appraisal_period_id" string="Performance Period"/>
<!-- <field name="manager_ids" widget="many2many_tags"/>-->
<field name="managerapp_id"/>
<field name="manager_remarks"/>
<field name="hr_apprai_id" string="HR"/>
<field name="hr_remarks"/>
<field name="start_date" readonly="1"/>
<field name="employee_appraisal_id" string="Employee" readonly="1"
options="{'no_open': True}"/>
<field name="job_appraisal_id" string="Current Job Title" readonly="1"
options="{'no_open': True}"/>
<field name="department_appraisal_id" string="Department" readonly="1"
options="{'no_open': True}"/>
<!-- <field name="template_id" string="Template"/>-->
<!-- <field name="available_stage_ids" invisible="0" widget="many2many_tags"/>-->
<field name="appraisal_period_id" string="Performance Period" readonly="1"
options="{'no_open': True}"/>
<!-- <field name="manager_ids" widget="many2many_tags"/>-->
<field name="managerapp_id" readonly="1" options="{'no_open': True}"/>
<field name="is_manager_reviewer" invisible="1"/>
<field name="manager_remarks" readonly="not is_manager_reviewer"/>
<field name="hr_apprai_id" string="HR" readonly="1" options="{'no_open': True}"/>
<field name="is_hr_reviewer" invisible="1"/>
<field name="hr_remarks" readonly="not is_hr_reviewer"/>
</group>
<group>
<field name="image_1920" widget="image" class="oe_avatar" nolabel="1"
@ -82,10 +139,13 @@
<field name="total_employee_score" readonly="1"/>
<field name="total_manager_score" readonly="1"/>
<field name="total_hr_score" readonly="1"/>
<field name="company_id" string="Company"/>
<field name="company_id" string="Company" readonly="1" options="{'no_open': True}"/>
<field name="start_date" readonly="1"/>
<field name="end_date" readonly="1"/>
<field name="template_empl_rating_bool"/>
<field name="template_empl_point_bool"/>
<field name="template_empl_rating_bool" readonly="1" invisible="1"/>
<field name="template_empl_point_bool" readonly="1" invisible="1"/>
<field name="hr_head_remarks" placeholder="Enter HR Head Remarks..."
invisible="stage_name != 'HR HEAD'"/>
</group>
<group string="Performance Standards">
<div>
@ -146,27 +206,64 @@
<field name="kpi_line_ids" nolabel="1" string="KPI">
<list editable="bottom" delete="0">
<field name="sequence" widget="handle"/>
<field name="question"/>
<field name="description"/>
<field name="question" readonly="1"/>
<field name="description" readonly="1"/>
<!-- <field name="kpi_line_weightage" sum="Total Weight Age"/>-->
<field name="template_empl_rating_bool" column_invisible="1"/>
<field name="template_empl_point_bool" column_invisible="1"/>
<field name="is_employee_reviewer" column_invisible="1"/>
<field name="employee_score" string="self Rating" sum="Total score"
readonly="not is_employee_reviewer"
column_invisible="parent.template_empl_point_bool == False"/>
<field name="rating_star" widget="priority" string="Self Rating"
readonly="not is_employee_reviewer"
column_invisible="parent.template_empl_rating_bool == False or parent.template_empl_point_bool == True"/>
<field name="manager_rating_star" widget="priority" string="Manager Rating"
<field name="is_manager_reviewer" column_invisible="1"/>
<field name="is_hr_reviewer" column_invisible="1"/>
<field name="manager_rating_star" widget="priority"
string="Manager Rating" readonly="not is_manager_reviewer"
column_invisible="parent.template_empl_rating_bool == False or parent.template_empl_point_bool == True"/>
<field name="hr_rating_star" widget="priority" string="HR Rating"
readonly="not is_hr_reviewer"
column_invisible="parent.template_empl_rating_bool == False or parent.template_empl_point_bool == True"/>
<field name="manager_score" sum="Total Points" column_invisible="parent.template_empl_point_bool == False"/>
<field name="hr_score" sum="Total Points" column_invisible="parent.template_empl_point_bool == False"/>
<field name="manager_score" sum="Total Points"
readonly="not is_manager_reviewer"
column_invisible="parent.template_empl_point_bool == False"/>
<field name="hr_score" sum="Total Points"
readonly="not is_hr_reviewer"
column_invisible="parent.template_empl_point_bool == False"/>
</list>
</field>
</sheet>
</form>
</field>
</page>
<page string="Disciplinary Actions">
<field name="disciplinary_ids" readonly="1">
<list create="0" delete="0" open_form_view="1">
<field name="complaint_date"/>
<field name="complaint_type_id"/>
<field name="mistake_type_id"/>
<field name="complaint"/>
<field name="state"
widget="badge"
decoration-success="state == 'closed'"
decoration-warning="state == 'submitted'"
decoration-info="state == 'pending'"
decoration-danger="state == 'cancel'"/>
</list>
</field>
</page>
<page string="Achievements &amp; Documents">
<group>
<field name="achievement_summary" widget="html"
placeholder="Describe your achievements during this appraisal period..."/>
<field name="employee_comments" placeholder="Additional comments..."/>
</group>
<group string="Supporting Documents">
<field name="attachment_ids" widget="many2many_binary"/>
</group>
</page>
<page string="Team Feed Back">
<!-- <field name="colleague_feed_ids" readonly="state != 'colleague_feedback'">-->
<field name="colleague_feed_ids">
@ -174,47 +271,35 @@
<field name="colleague_feed_id"/>
<field name="feedback"/>
<!-- <field name="feedback" readonly="state == 'submitted'"/>-->
<!-- <field name="state"/>-->
<button name="action_submit_feedback" type="object" string="Submit"/>
<button name="action_submit_feedback" type="object" string="Submit"
invisible="state == 'submitted'"/>
<field name="state"/>
<!-- invisible="state == 'submitted'"/>-->
</list>
</field>
</page>
<page string="Finance Review">
<group>
<button name="action_update_contract_salary"
string="Update Contract Salary"
type="object"
class="btn-primary"
invisible="salary_update"/>
<group string="Salary Details">
<field name="current_salary"/>
<field name="appraisal_percentage"/>
<field name="appraisal_amount"/>
<field name="new_salary" readonly="1"/>
</group>
<group string="Finance Remarks">
<field name="salary_update" readonly="1"/>
<field name="finance_remarks"/>
<field name="finance_user_id"
readonly="1"/>
<field name="finance_date"
readonly="1"/>
<field name="finance_head_remarks"/>
</group>
</group>
<footer>
</footer>
</page>
</notebook>
</sheet>
@ -228,6 +313,7 @@
<field name="name">Employee Performance Review</field>
<field name="res_model">employee.appraisal.template.config</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_employee_app_search"/>
</record>
<!-- ROOT MENU -->
@ -241,7 +327,7 @@
name="Employee Performance Review"
parent="menu_employee_appraisal_root"
action="action_employee_appraisal_template"
sequence="03"/>
sequence="04"/>
</data>
</odoo>

View File

@ -2,8 +2,23 @@
<menuitem id="configuration_id_employee"
name="Configuration"
groups="group_appraisal_hr_head,group_appraisal_hr,group_appraisal_management"
parent="menu_employee_appraisal_root"
sequence="04"/>
sequence="06"/>
<record id="action_appraisal_settings" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module':'hrms_employee_appraisal'}</field>
</record>
<menuitem id="menu_appraisal_settings"
name="Settings"
parent="configuration_id_employee"
action="action_appraisal_settings"
sequence="100"/>
<record id="view_employee_appraisal_yea_form" model="ir.ui.view">
<field name="name">employee.appraisal.year.form</field>

View File

@ -0,0 +1,84 @@
<odoo>
<data>
<record id="view_employee_stage_conf_form" model="ir.ui.view">
<field name="name">employee.pip.form</field>
<field name="model">employee.pip</field>
<field name="arch" type="xml">
<form string="Performance Improvement Plan">
<header>
<button name="action_start" string="Start" type="object" class="btn-primary" invisible="state != 'draft'"/>
<button name="action_review" string="Review" type="object" class="btn-info" invisible="state != 'running'"/>
<button name="action_complete" string="Complete" type="object" class="btn-success" invisible="state != 'review'"/>
<button name="action_fail" string="Fail" type="object" class="btn-danger" invisible="state != 'review'"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<group>
<field name="employee_id"/>
<field name="manager_id"/>
<field name="timeline"/>
<field name="progress_percentage" widget="progressbar"/>
<field name="objective"/>
</group>
<group>
<field name="start_date"/>
<field name="end_date"/>
<field name="review_date"/>
<field name="employee_acknowledged"/>
</group>
</group>
<notebook>
<page string="Improvement Tasks">
<field name="task_ids">
<list editable="bottom">
<field name="name"/>
<field name="description"/>
<field name="target_date"/>
<field name="training_course"/>
<field name="state"/>
</list>
</field>
</page>
<page string="Manager Remarks">
<field name="remarks"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_employee_pip_tree" model="ir.ui.view">
<field name="name">employee.pip.tree</field>
<field name="model">employee.pip</field>
<field name="arch" type="xml">
<list string="Performance Improvement Plans">
<field name="employee_id"/>
<field name="manager_id"/>
<field name="timeline"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="progress_percentage" widget="progressbar"/>
<field name="review_date"/>
<field name="employee_acknowledged"/>
<field name="state"
widget="badge"
decoration-success="state == 'completed'"
decoration-warning="state == 'review'"
decoration-info="state == 'running'"
decoration-danger="state == 'failed'"/>
</list>
</field>
</record>
<record id="action_employe_pip" model="ir.actions.act_window">
<field name="name">Performance Improvement Plans</field>
<field name="res_model">employee.pip</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_employe_pip"
name="Performance Improvement Plans"
parent="menu_employee_appraisal_root"
action="action_employe_pip"
sequence="05"/>
</data>
</odoo>

View File

@ -10,6 +10,7 @@
type="object"
string="Send To Employee"
class="oe_highlight"
groups="hrms_employee_appraisal.group_appraisal_manager,hrms_employee_appraisal.group_appraisal_management"
invisible="employee_state != 'new'"/>
<field name="employee_state" widget="statusbar"/>
</header>
@ -18,7 +19,7 @@
<group>
<field name="seq" string="Performance Id"/>
<field name="name" string="Reference" placeholder="Administration Appraisal Template"/>
<field name="manager_ids" widget="many2many_tags" string="Performance Evaluator"/>
<field name="manager_ids" widget="many2many_tags" string="Performance Evaluator" invisible="1"/>
<field name="employee_eva_id"/>
<field name="employee_department_id"/>
<field name="appraisal_period_type_id" string="Performance Type"/>
@ -48,8 +49,10 @@
<!-- <field name="kra_weightage" sum="Total Weightage"/>-->
<field name="kra_template_rating_bool" column_invisible="1"/>
<field name="kra_template_point_bool" column_invisible="1"/>
<field name="max_star_rating" widget="priority" column_invisible="parent.template_rating_bool == False"/>
<field name="max_points" column_invisible="parent.template_point_bool == False"/>
<field name="max_star_rating" widget="priority"
column_invisible="parent.template_rating_bool == False"/>
<field name="max_points"
column_invisible="parent.template_point_bool == False"/>
<field name="kpi_count" readonly="1"/>
<button name="action_open_questions"
type="object"
@ -80,7 +83,10 @@
<field name="appraisal_period_type_id" string="Performance Type"/>
<field name="appraisal_period_id" string="Performance Period"/>
<field name="company_id"/>
<field name="hr_email_notify"/>
<field name="employee_state"
widget="badge"
decoration-muted="employee_state == 'new'"
decoration-success="employee_state == 'sent'"/>
</list>
</field>
</record>
@ -96,9 +102,9 @@
name="Employee Appraisal Templates"
parent="menu_employee_appraisal_root"
action="action_employee_appraisal_template_conf"
sequence="02"/>
groups="group_appraisal_management,group_appraisal_finance_head,group_appraisal_finance,group_appraisal_hr_head,group_appraisal_hr,group_appraisal_manager"
sequence="03"/>
<!-- KPI TREE VIEW -->
<record id="view_employee_appraisal_kpi_list" model="ir.ui.view">
<field name="name">employee.appraisal.kpi.list</field>
@ -119,8 +125,6 @@
</record>
<!-- KPI FORM VIEW -->
<record id="view_employee_appraisal_kpi_form" model="ir.ui.view">
<field name="name">employee.appraisal.kpi.form</field>
<field name="model">employee.appraisal.kpi</field>
@ -132,22 +136,8 @@
</group>
</sheet>
<footer>
<!-- SAVE -->
<button string="Save"
type="object"
special="save"
class="btn-primary"/>
<!-- DELETE -->
<button name="action_delete_record"
string="Delete"
type="object"
class="btn-danger"/>
<!-- CLOSE -->
<button string="Save" type="object" special="save" class="btn-primary"/>
<button name="action_delete_record" string="Delete" type="object" class="btn-danger"/>
</footer>
</form>
</field>

View File

@ -6,7 +6,8 @@
<form string="HR Notice">
<header>
<button name="action_send_notice" string="Send Appraisal Initiation" type="object"
class="btn btn-success" invisible="state != 'draft'"/>
class="btn btn-success" groups="hrms_employee_appraisal.group_appraisal_hr,hrms_employee_appraisal.group_appraisal_management"
invisible="state != 'draft'"/>
<button name="action_open_postpone_wizard" string="Postpone" type="object"
class="btn-warning" invisible="state != 'sent'"/>
<button name="action_open_cancel_wizard" string="Cancel" type="object"
@ -17,30 +18,36 @@
<group col="2">
<group>
<field name="seq"/>
<field name="hr_employee_id" string="Created BY"/>
<field name="appraisal_type_id"/>
<field name="appraisal_notice_id"/>
<field name="subject"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="hr_employee_id" string="Created BY" readonly="1" options="{'no_open': True}"/>
<field name="appraisal_type_id"
options="{'no_edit': True, 'no_create': True, 'no_open': True}"
readonly="state != 'draft'"/>
<field name="appraisal_notice_id"
options="{'no_edit': True, 'no_create': True, 'no_open': True}"
readonly="state != 'draft'"/>
<field name="subject" readonly="state != 'draft'"/>
<field name="start_date" readonly="state != 'draft'" force_save="1"/>
<field name="end_date" readonly="state != 'draft'" force_save="1"/>
</group>
<group>
<field name="employee_ids" widget="many2many_tags"/>
<field name="manager_ids" widget="many2many_tags"/>
<field name="hr_department_ids" widget="many2many_tags"/>
<field name="stage_config" widget="many2many_tags"/>
<field name="employee_rating"/>
<field name="employee_points"/>
<field name="employee_ids" widget="many2many_tags" required="1"
readonly="state != 'draft'"/>
<field name="manager_ids" widget="many2many_tags" readonly="state != 'draft'"/>
<field name="hr_department_ids" widget="many2many_tags" readonly="state != 'draft'"/>
<field name="stage_config" widget="many2many_tags" required="1"
readonly="state != 'draft'"/>
<field name="employee_rating" readonly="state != 'draft'"/>
<field name="employee_points" readonly="state != 'draft'"/>
</group>
</group>
<group>
<field name="body" widget="html"/>
<field name="body" widget="html" readonly="state != 'draft'"/>
</group>
<notebook>
<page string="Postponed Details">
<group>
<group>
<field name="postponed_by_id" readonly="1"/>
<field name="postponed_by_id" readonly="1" options="{'no_open': True}"/>
<field name="postponed_date" readonly="1"/>
<field name="new_start_date" readonly="1"/>
<field name="new_end_date" readonly="1"/>
@ -53,7 +60,7 @@
<page string="Cancelled Details">
<group>
<group>
<field name="cancelled_by_id" readonly="1"/>
<field name="cancelled_by_id" readonly="1" options="{'no_open': True}"/>
<field name="cancelled_date" readonly="1"/>
</group>
<group>
@ -74,6 +81,7 @@
<field name="arch" type="xml">
<list>
<field name="seq"/>
<field name="hr_employee_id"/>
<field name="subject"/>
<field name="start_date"/>
<field name="end_date"/>
@ -97,6 +105,95 @@
name="Performance Cycle Notification"
parent="menu_employee_appraisal_root"
action="action_hr_notice_appraisal"
groups="group_appraisal_hr,group_appraisal_hr_head,group_appraisal_management"
sequence="02"/>
<record id="view_hr_appraisal_notification_form" model="ir.ui.view">
<field name="name">hr.head.notification.form</field>
<field name="model">hr.head.notification</field>
<field name="arch" type="xml">
<form string="Appraisal Notification">
<header>
<button name="action_sent_hr" string="Send To HR Team" type="object" class="btn-primary"
invisible="state != 'draft'"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="Performance Appraisal Notification"/>
</h1>
</div>
<group col="2">
<group string="Appraisal Details">
<field name="seq"/>
<field name="hr_employee_id"/>
<field name="appraisal_type_id" options="{'no_create': True,'no_open': True}"/>
<field name="appraisal_period_id" options="{'no_create': True,'no_open': True}"/>
<field name="start_date"/>
<field name="end_date"/>
</group>
<group string="Assignment">
<!-- <field name="hr_employee_domain_ids" invisible="1"/>-->
<!-- <field name="hr_ids"-->
<!-- widget="many2many_tags"/>-->
<!-- <field name="hr_user_domain_ids" invisible="1"/>-->
<field name="hr_users_ids"
widget="many2many_tags"/>
<field name="stage_config_ids" widget="many2many_tags" required="1"/>
</group>
</group>
<notebook>
<page string="Notification Message">
<field name="body" widget="html"/>
</page>
<page string="Audit">
<group>
<field name="create_uid" readonly="1"/>
<field name="create_date" readonly="1"/>
<field name="write_uid" readonly="1"/>
<field name="write_date" readonly="1"/>
</group>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_hr_appraisal_notification_tree" model="ir.ui.view">
<field name="name">hr.head.notification.tree</field>
<field name="model">hr.head.notification</field>
<field name="arch" type="xml">
<list>
<field name="seq"/>
<field name="hr_employee_id"/>
<field name="name"/>
<field name="appraisal_type_id"/>
<field name="appraisal_period_id"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="state"
widget="badge"
decoration-success="state == 'sent'"
decoration-muted="state == 'draft'"/>
</list>
</field>
</record>
<record id="action_hr_head_appraisal" model="ir.actions.act_window">
<field name="name">Appraisal Notifications</field>
<field name="res_model">hr.head.notification</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_hr_head_appraisal"
name="Appraisal Notifications"
parent="menu_employee_appraisal_root"
action="action_hr_head_appraisal"
groups="group_appraisal_hr_head,group_appraisal_management"
sequence="01"/>
</odoo>

View File

@ -9,6 +9,7 @@
<group>
<field name="seq" invisible="0"/>
<field name="name"/>
<field name="colour_seq"/>
<field name="color" widget="color_picker"/>
</group>
</sheet>
@ -20,7 +21,7 @@
<field name="model">employee.stage.config</field>
<field name="arch" type="xml">
<list string="Employee Appraisal Period">
<field name="seq" widget="handle"/>
<field name="seq" widget="handle"/>
<field name="name"/>
<field name="color" widget="color_picker"/>
</list>
@ -36,5 +37,43 @@
parent="configuration_id_employee"
action="action_employee_stage_confr"
sequence="20"/>
<record id="res_config_settings_view_form_appraisal" model="ir.ui.view">
<field name="name">res.config.settings.appraisal.form</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app string="Employee Appraisal" name="employee_appraisal">
<block title="Appraisal Settings">
<setting string="Enable Appraisal Reminders"><field name="appraisal_reminder_enabled"/></setting>
<setting string="Reminder Days"><field name="appraisal_reminder_days"/>
<div class="text-muted">
Reminder email will be sent before appraisal end date.
</div>
</setting>
</block>
</app>
</xpath>
</field>
</record>
<record id="action_employee_appraisal_settings" model="ir.actions.act_window">
<field name="name">Appraisal Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module': 'hrms_employee_appraisal'}</field>
</record>
<menuitem id="menu_employee_appraisal_settings"
name="Settings"
parent="configuration_id_employee"
action="action_employee_appraisal_settings"
sequence="10"/>
<!-- groups="hrms_employee_appraisal.group_appraisal_hr_head,-->
<!-- hrms_employee_appraisal.group_appraisal_hr,-->
<!-- hrms_employee_appraisal.group_appraisal_management"-->
</data>
</odoo>

View File

@ -34,7 +34,7 @@
<field name="jd_file" filename="jd_file_name"
widget="binary" force_save="1"/>
<field name="jd_file_name" invisible="1" force_save="1"/>
</group>
</group>
<group>
<field name="job_id" invisible="job_id == False" readonly="1" force_save="1" options="{'no_quick_create':True,'no_open':True}"/>
<field name="number_of_positions" readonly="(state != 'draft' and not is_hr) or state == 'jd_created'"/>
@ -46,7 +46,7 @@
<field name="assign_to" invisible="state not in ['final','jd_created']" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
<field name="target_startdate" invisible="1" readonly="state in ['jd_created']"/>
<field name="target_deadline" widget="daterange" string="Target Deadline" options="{'start_date_field': 'target_startdate'}" on_change="1" readonly="state in ['jd_created']"/>
</group>
</group>
<field name="notes" placeholder="Remarks" readonly="state == 'jd_created'" invisible="not notes"/>
</group>
<notebook>
@ -80,10 +80,18 @@
</list>
</field>
</record>
<record id="action_recruitment_requisition" model="ir.actions.act_window">
<record id="action_recruitment_requisition" model="ir.actions.act_window">
<field name="name">Recruitment Requisitions</field>
<field name="res_model">recruitment.requisition</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No Recruitment Requisitions Found
</p>
<p>
Click Create to raise a new recruitment requisition.
</p>
</field>
</record>
<menuitem