Merge remote-tracking branch 'origin/feature/share_module' into feature/share_module

This commit is contained in:
Bhagya-K 2026-06-29 11:50:43 +05:30
commit 80e90ca88a
64 changed files with 3477 additions and 463 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,3 @@
# -*- coding: utf-8 -*-
from . import models

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Disciplinary',
'version': '1.0.0',
'category': 'Apps',
'summary': 'Disciplinary',
'description': 'Employee Disciplinary',
'sequence': '10',
'author': '',
'company': 'FTPROTECH',
'website': 'https://www.ftprotech.in',
'depends': ['mail', 'hr', 'base', 'website_hr_recruitment', 'contacts', 'point_of_sale'],
'demo': [],
'data': [
'data/sequence.xml',
'security/ir.model.access.csv',
'views/disciplinary_view.xml',
'views/employee_displance.xml',
'views/mistake_type_views.xml',
'views/incident_sub_type.xml',
'views/disciplinary_complaint_type.xml',
],
'installable': True,
'application': False,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data noupdate="1">
<record id="incident_report_sequence" model="ir.sequence">
<field name="name">Employee Disciplinary</field>
<field name="code">employee.disciplinary</field>
<field name="prefix">IR</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="manage_incident_report_sequence" model="ir.sequence">
<field name="name">Manage Incident</field>
<field name="code">manage.incident</field>
<field name="prefix">MI</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_employee_disciplinary_sequence" model="ir.sequence">
<field name="name">Disciplinary Sequence</field>
<field name="code">hr.employee.sequence</field>
<field name="prefix">ED</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,2 @@
from . import disciplinary
from . import employee_displane

View File

@ -0,0 +1,188 @@
from datetime import datetime, date
from odoo import fields, models, api
#
# class NameChangeHrEmployee(models.Model):
# _inherit = "hr.employee"
#
# employee_name_ids1 = fields.One2many('employee.disciplinary', 'disp_name')
# employee_self_service_line_ids = fields.One2many('manage.incident', 'emp_incident', domain=[('state', '=', 'closed')])
#
# def name_get(self):
# result = []
# for record in self:
# if self.env.context.get('new_custom_name', False):
# result.append((record.id, "{} - {}".format(record.name, record.identification_id)))
# else:
# return super(NameChangeHrEmployee, self).name_get()
# return result
class EmployeeDisciplinary(models.Model):
_name = 'employee.disciplinary'
_inherit = ['mail.thread', 'mail.activity.mixin']
_rec_name = 'incident_type'
incident_date = fields.Datetime(string='Incident Date & Time', tracking=True, default=datetime.now(), required=True)
incident_type = fields.Many2one('incident.employee', string='Incident Type', tracking=True, required=True)
incident_sub_type = fields.Many2many('incident.sub.employee', string='Incident Sub Type', tracking=True,
required=True)
incident_details = fields.Char(string='Incident Details', tracking=True, required=True)
seized_items = fields.Char(string='Seized Items', tracking=True)
incident_summary = fields.Text(string='Incident Summary', tracking=True, required=True)
attach = fields.Many2many('ir.attachment', string='Attachments', tracking=True)
emp_many_disp = fields.Many2many('hr.employee', 'new_custom_table', string='Employees Involved in the Incident',
tracking=True, required=True)
date_action = fields.Date(string='Date')
employee = fields.Many2one('manage.incident')
employee_code = fields.Many2one("hr.employee", string="Employee Name", required=True)
employee_name = fields.Char(related="employee_code.identification_id")
disp_name = fields.Many2one('hr.employee')
@api.onchange('incident_type')
def return_incident_sub_type(self):
print(self.incident_type.sub_type)
listed = []
for recs in self.incident_type.sub_type:
listed.append(recs.id)
return {'domain': {'incident_sub_type': [('id', 'in', listed)]}}
@api.constrains('incident_type')
def create_manage_incidents(self):
for rec in self:
print('created')
self.env['manage.incident'].create({
'employee_disciplinary_id': rec.id,
})
@api.constrains('employee')
def holds_hr_employee(self):
for rec in self:
rec.disp_name = rec.employee.employee_code_list1
class IncidentEmployee(models.Model):
_name = 'incident.employee'
name = fields.Char(string='Incident')
sub_type = fields.Many2many('incident.sub.employee', string='Sub type')
class IncidentSubEmployee(models.Model):
_name = 'incident.sub.employee'
name = fields.Char(string='Incident Sub')
class DisciplinaryMistakeType(models.Model):
_name = 'disciplinary.mistake.type'
_description = 'Disciplinary Mistake Type'
name = fields.Char(string="Mistake Type", required=True)
class IncidentSubEmployee(models.Model):
_name = 'incident.sub.employee'
_description = 'Incident Sub Type'
name = fields.Char(string="Incident Sub Type", required=True)
class EmployeeDisciplinaryLines(models.Model):
_name = 'employee.disciplinary.line'
_rec_name = 'hr_emp_many'
emp_many = fields.Many2one('employee.disciplinary', string='Employee Disp')
hr_emp_many = fields.Many2one('hr.employee', string='Employee Number')
hr_emp_many_name = fields.Char(related='hr_emp_many.name', string='Employee Name')
class ManageIncident(models.Model):
_name = 'manage.incident'
_inherit = ['mail.thread', 'mail.activity.mixin']
# employee_name_ids = fields.One2many('employee.disciplinary','employee',string="Employee Name")
employee_disciplinary_id = fields.Many2one("employee.disciplinary", string="Employee Disp")
employee_code_list1 = fields.Many2many("hr.employee", string="Employees Involved in the Incident",
related='employee_disciplinary_id.emp_many_disp', tracking=True)
incident_dat = fields.Datetime(related='employee_disciplinary_id.incident_date', string='Incident Date & Time',
tracking=True)
employee_by_code = fields.Many2one(related='employee_disciplinary_id.employee_code',
string="Reported By Employee Name")
incident_sum = fields.Text(related='employee_disciplinary_id.incident_summary', string='Incident Summary',
tracking=True)
incident_typ = fields.Many2one(related='employee_disciplinary_id.incident_type', string="Incident Type",
tracking=True)
incident_sub_typ = fields.Many2many(related='employee_disciplinary_id.incident_sub_type',
string="Incident Sub Type", tracking=True)
# corrective_action_emp_id = fields.Many2one(related='employee_disciplinary_id.corrective_action_id',
# string="Corrective Action", tracking=True)
state = fields.Selection(([
('pending_inquiry', 'Pending Inquiry'),
('in_progress', 'In Process'),
('closed', 'Closed')
]), string="Status", default='pending_inquiry', tracking=True)
emp_incident = fields.Many2one('hr.employee')
employee_inquiry = fields.One2many('manage.incident.line', 'employee_inquiry_state')
def button_in_progress(self):
self.state = 'in_progress'
# def button_closed(self):
# for rec in self:
# rec.state = 'closed'
def button_closed(self):
for rec in self:
rec.state = 'closed'
update_into_employee = rec.env['hr.employee'].search([('id', '=', rec.employee_code_list1.id)])
records = {
}
if records:
update_into_employee.write(records)
print('triggered 2')
class CorrectiveActions(models.Model):
_name = "corrective.actions"
name = fields.Char(string="Name")
class ManageIncidentLine(models.Model):
_name = 'manage.incident.line'
_inherit = ['mail.thread']
_description = 'Manage Incident Line'
corrective_action_id = fields.Many2one('corrective.actions', string="Corrective Action", tracking=True,
required=True)
internal_panel = fields.Many2many('hr.employee', string="Internal Panel Members", tracking=True,
required=True)
external_panel = fields.Char(string="External Panel Members")
due_date = fields.Date(string="Due Date")
last_action_date = fields.Datetime(string="Last Action Date", compute='_compute_last_action_date',
default=date.today())
recommendation = fields.Char(string="Recommendation", tracking=True, required=True)
venue = fields.Char(string='Venue')
inquiry_summary = fields.Char(string='Inquiry Summary', tracking=True, required=True)
is_guilty = fields.Selection(([
('yes', 'Yes'),
('no', 'No'),
]), string="Is the Employee Guilt of the Incident", default='no', tracking=True)
inquiry_date = fields.Datetime(string="Inquiry Date and Time", required=True)
employee_inquiry_state = fields.Many2one('manage.incident')
@api.depends('inquiry_date')
def _compute_last_action_date(self):
for line in self:
if not line.employee_inquiry_state or line == line.employee_inquiry_state.employee_inquiry[0]:
line.last_action_date = False
else:
previous_line = line.employee_inquiry_state.employee_inquiry.filtered(lambda l: l.inquiry_date < line.inquiry_date)
sorted_previous_line = previous_line.sorted(key=lambda l: l.inquiry_date, reverse=True)
if sorted_previous_line:
line.last_action_date = sorted_previous_line[0].inquiry_date
else:
line.last_action_date = False

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,22 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_employee_disciplinary,employee_disciplinary,model_employee_disciplinary,,1,1,1,1
access_incident_employee,incident_employee,model_incident_employee,,1,1,1,1
access_incident_sub_employee,incident_sub_employee,model_incident_sub_employee,,1,1,1,1
access_employee_disciplinary_line,employee_disciplinary_line,model_employee_disciplinary_line,,1,1,1,1
access_manage_incident,manage_incident,model_manage_incident,,1,1,1,1
access_manage_incident_line,manage_incident_line,model_manage_incident_line,,1,1,1,1
access_corrective_actions,corrective_actions,model_corrective_actions,,1,1,1,1
access_hr_employee_disciplinary,hr.employee.disciplinary,model_hr_employee_disciplinary,,1,1,1,1
access_hr_disciplinary_complaint_line,hr.disciplinary.complaint.line,model_hr_disciplinary_complaint_line,,1,1,1,1
access_hr_disciplinary_action_line,hr.disciplinary.action.line,model_hr_disciplinary_action_line,,1,1,1,1
access_disciplinary_action_type,disciplinary.action.type,model_disciplinary_action_type,,1,1,1,1
access_disciplinary_complaint_type,disciplinary.complaint.type,model_disciplinary_complaint_type,,1,1,1,1
access_disciplinary_mistake_type,disciplinary.mistake.type,model_disciplinary_mistake_type,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_employee_disciplinary employee_disciplinary model_employee_disciplinary 1 1 1 1
3 access_incident_employee incident_employee model_incident_employee 1 1 1 1
4 access_incident_sub_employee incident_sub_employee model_incident_sub_employee 1 1 1 1
5 access_employee_disciplinary_line employee_disciplinary_line model_employee_disciplinary_line 1 1 1 1
6 access_manage_incident manage_incident model_manage_incident 1 1 1 1
7 access_manage_incident_line manage_incident_line model_manage_incident_line 1 1 1 1
8 access_corrective_actions corrective_actions model_corrective_actions 1 1 1 1
9 access_hr_employee_disciplinary hr.employee.disciplinary model_hr_employee_disciplinary 1 1 1 1
10 access_hr_disciplinary_complaint_line hr.disciplinary.complaint.line model_hr_disciplinary_complaint_line 1 1 1 1
11 access_hr_disciplinary_action_line hr.disciplinary.action.line model_hr_disciplinary_action_line 1 1 1 1
12 access_disciplinary_action_type disciplinary.action.type model_disciplinary_action_type 1 1 1 1
13 access_disciplinary_complaint_type disciplinary.complaint.type model_disciplinary_complaint_type 1 1 1 1
14 access_disciplinary_mistake_type disciplinary.mistake.type model_disciplinary_mistake_type 1 1 1 1

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,264 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="employee_disciplinary_list" model="ir.ui.view">
<field name="name">Employee Disciplinary list</field>
<field name="model">employee.disciplinary</field>
<field name="arch" type="xml">
<list>
<field name="incident_date"/>
<field name="incident_type"/>
<field name="incident_sub_type" widget="many2many_tags"/>
</list>
</field>
</record>
<record id="employee_disciplinary_form" model="ir.ui.view">
<field name="name">Employee Disciplinary form</field>
<field name="model">employee.disciplinary</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="incident_date"/>
<field name="incident_type" options="{'no_open': True,}"/>
<field name="incident_sub_type" widget="many2many_tags" readonly="0"
options="{'no_open': True}"/>
<label for="employee_code" string="Reported By Employee Code"/>
<div class="address_format">
<field name="employee_code" style="width: 50%" options="{'no_open': True,}"/>
<field name="employee_name" style="width: 50%"/>
</div>
<field name="incident_details"/>
<field name="seized_items"/>
<field name="incident_summary"/>
<field name="attach" widget="many2many_binary" options="{'preview_image': True}"/>
<field name="emp_many_disp" widget="many2many_tags" context="{'new_custom_name': True}"/>
</group>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="manage_incident_list" model="ir.ui.view">
<field name="name">Manage Incident list</field>
<field name="model">manage.incident</field>
<field name="arch" type="xml">
<list create="0">
<field name="employee_code_list1" widget="many2many_tags" context="{'new_custom_name': True}"/>
<!-- <field name="incident_dat"/>-->
<field name="incident_typ"/>
<field name="incident_sub_typ" widget="many2many_tags"/>
<field name="incident_sum"/>
<field name="state"/>
</list>
</field>
</record>
<record id="manage_incident_form" model="ir.ui.view">
<field name="name">Manage Incident form</field>
<field name="model">manage.incident</field>
<field name="arch" type="xml">
<form create="0">
<header>
<button name="button_in_progress" string="In Progress" class="oe_highlight" type="object"/>
<!-- states="pending_inquiry"/>-->
<!-- attrs="{'invisible' : ('state','!=','pending_inquiry')}"/>-->
<button name="button_closed" string="Closed" class="oe_highlight" type="object"/>
<!-- states="in_progress"/>-->
<!-- attrs="{'invisible' : ('state','!=','in_progress')}"/>-->
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<group>
<field name="employee_code_list1" widget="many2many_tags"
context="{'new_custom_name': True}"/>
<field name="employee_by_code" options="{'no_open': True,}"/>
<!-- <field name="incident_dat"/>-->
</group>
<group>
<field name="incident_typ" options="{'no_open': True,}"/>
<field name="incident_sub_typ" widget="many2many_tags"/>
<field name="incident_sum"/>
</group>
</group>
<field name="employee_inquiry" string="Manage Incident">
<!-- attrs="{'readonly': [('state', '=','closed')]}">-->
<list>
<field name="inquiry_date"/>
<field name="corrective_action_id"/>
<field name="due_date"/>
<field name="last_action_date"/>
</list>
<form>
<group>
<field name="inquiry_date"/>
<field name="venue"/>
<field name="internal_panel" widget="many2many_tags"
context="{'new_custom_name': True}"/>
<field name="external_panel"/>
<field name="inquiry_summary"/>
<field name="is_guilty" widget="radio" options="{'horizontal':true}"/>
<field name="corrective_action_id" options="{'no_open': True,}"/>
<field name="recommendation"/>
<field name="due_date"/>
<field name="last_action_date"/>
</group>
</form>
</field>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- <record id="career_history_tab_sub_menu" model="ir.ui.view">-->
<!-- <field name="name">Career History Tab Sub Menu</field>-->
<!-- <field name="model">hr.employee</field>-->
<!-- <field name="inherit_id" ref="employee_life_cycle.career_history_tab_menu"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="//page/field[@name='career_history_field']" position="after">-->
<!-- &lt;!&ndash; <group name="career_hist_sub_menu" string="Disciplinary Actions">&ndash;&gt;-->
<!-- &lt;!&ndash; <field name="employee_name_ids1" string="Manage Incident">&ndash;&gt;-->
<!-- &lt;!&ndash; <list editable="0" create="0">&ndash;&gt;-->
<!-- &lt;!&ndash; <field name="incident_date"/>&ndash;&gt;-->
<!-- &lt;!&ndash; <field name="incident_type"/>&ndash;&gt;-->
<!-- &lt;!&ndash; <field name="incident_sub_type"/>&ndash;&gt;-->
<!-- &lt;!&ndash;&lt;!&ndash; <field name="corrective_action_id"/>&ndash;&gt;&ndash;&gt;-->
<!-- &lt;!&ndash; </list>&ndash;&gt;-->
<!-- &lt;!&ndash; </field>&ndash;&gt;-->
<!-- &lt;!&ndash; </group>&ndash;&gt;-->
<!-- <field name="employee_self_service_line_ids" string="Manage Incident" readonly="1">-->
<!-- <list>-->
<!-- <field name="incident_dat"/>-->
<!-- <field name="incident_typ"/>-->
<!-- <field name="incident_sub_typ"/>-->
<!-- </list>-->
<!-- </field>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<!-- <record id="career_history_tab_sub_menu_self" model="ir.ui.view">-->
<!-- <field name="name">Career History Tab Sub Menu Self Service</field>-->
<!-- <field name="model">hr.employee</field>-->
<!-- <field name="inherit_id" ref="employee_self_service.view_employee_form_self_service"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="//page/field[@name='career_history_field']" position="after">-->
<!-- &lt;!&ndash; <group name="career_hist_sub_menu" string="Disciplinary Actions">&ndash;&gt;-->
<!-- &lt;!&ndash; <field name="employee_name_ids1" string="Manage Incident">&ndash;&gt;-->
<!-- &lt;!&ndash; <list editable="0" create="0">&ndash;&gt;-->
<!-- &lt;!&ndash; <field name="incident_date"/>&ndash;&gt;-->
<!-- &lt;!&ndash; <field name="incident_type"/>&ndash;&gt;-->
<!-- &lt;!&ndash; <field name="incident_sub_type"/>&ndash;&gt;-->
<!-- &lt;!&ndash; &lt;!&ndash; <field name="corrective_action_id"/>&ndash;&gt;&ndash;&gt;-->
<!-- &lt;!&ndash; </list>&ndash;&gt;-->
<!-- &lt;!&ndash; </field>&ndash;&gt;-->
<!-- &lt;!&ndash; </group>&ndash;&gt;-->
<!-- <field name="employee_self_service_line_ids" string="Manage Incident" readonly="1">-->
<!-- <list>-->
<!-- <field name="incident_dat"/>-->
<!-- <field name="incident_typ"/>-->
<!-- <field name="incident_sub_typ"/>-->
<!-- </list>-->
<!-- </field>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<record id="incident_employee_list" model="ir.ui.view">
<field name="name">incident Employee list</field>
<field name="model">incident.employee</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="sub_type" widget="many2many_tags"/>
</list>
</field>
</record>
<record id="incident_employee_form" model="ir.ui.view">
<field name="name">incident Employee form</field>
<field name="model">incident.employee</field>
<field name="arch" type="xml">
<form>
<sheet>
<form>
<group>
<field name="name"/>
<field name="sub_type" widget="many2many_tags"/>
</group>
</form>
</sheet>
</form>
</field>
</record>
<record id="employee_disciplinary_action" model="ir.actions.act_window">
<field name="name">Incident Reporting</field>
<field name="res_model">employee.disciplinary</field>
<field name="view_mode">list,form</field>
</record>
<record id="manage_incident_action" model="ir.actions.act_window">
<field name="name">Manage Incident</field>
<field name="res_model">manage.incident</field>
<field name="view_mode">list,form</field>
</record>
<record id="incident_employee_action" model="ir.actions.act_window">
<field name="name">Incident Type</field>
<field name="res_model">incident.employee</field>
<field name="view_mode">list,form</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>
</record>
<menuitem id="employee_disciplinary_menu"
name="Employee Disciplinary Management"
parent="hr.menu_hr_root"
action="employee_disciplinary_action"
sequence="105"/>
<!-- Child Menu (moved inside) -->
<menuitem id="menu_employee_disciplinary_root"
name="Employee Disciplinary Configuration"
parent="employee_disciplinary_menu"
sequence="10"/>
<!-- Sub Menu -->
<menuitem id="menu_employee_disciplinary"
name="Employee Disciplinary"
parent="employee_disciplinary_menu"
action="action_hr_employee_disciplinary"
sequence="01"/>
<!-- <menuitem id="manage_incident_employee"-->
<!-- name="Incident Type"-->
<!-- parent="employee_disciplinary_menu"-->
<!-- action="incident_employee_action"-->
<!-- sequence="3"/>-->
<!-- <menuitem id="manage_incident_sub_menu"-->
<!-- name="Manage Incident"-->
<!-- parent="employee_disciplinary_menu"-->
<!-- action="manage_incident_action"-->
<!-- sequence="2"/>-->
<!-- <menuitem id="employee_disciplinary_sub_menu"-->
<!-- name="Incident Reporting"-->
<!-- parent="employee_disciplinary_menu"-->
<!-- action="employee_disciplinary_action"-->
<!-- sequence="1"/>-->
</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

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- list View -->
<record id="view_incident_sub_employee_list" model="ir.ui.view">
<field name="name">incident.sub.employee.list</field>
<field name="model">incident.sub.employee</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
</list>
</field>
</record>
<!-- Form View -->
<record id="view_incident_sub_employee_form" model="ir.ui.view">
<field name="name">incident.sub.employee.form</field>
<field name="model">incident.sub.employee</field>
<field name="arch" type="xml">
<form string="Incident Sub Type">
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- Action -->
<record id="action_incident_sub_employee" model="ir.actions.act_window">
<field name="name">Incident Sub Type</field>
<field name="res_model">incident.sub.employee</field>
<field name="view_mode">list,form</field>
</record>
<!-- Menu -->
<!-- <menuitem id="menu_incident_sub_employee"-->
<!-- name="Incident Sub Type"-->
<!-- parent="employee_disciplinary_menu"-->
<!-- action="action_incident_sub_employee"/>-->
</data>
</odoo>

View File

@ -0,0 +1,42 @@
<odoo>
<!-- Tree View -->
<record id="view_mistake_type_list" model="ir.ui.view">
<field name="name">mistake.type.list</field>
<field name="model">disciplinary.mistake.type</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
</list>
</field>
</record>
<!-- Form View -->
<record id="view_mistake_type_form" model="ir.ui.view">
<field name="name">mistake.type.form</field>
<field name="model">disciplinary.mistake.type</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- Action -->
<record id="action_mistake_type" model="ir.actions.act_window">
<field name="name">Mistake Type</field>
<field name="res_model">disciplinary.mistake.type</field>
<field name="view_mode">list,form</field>
</record>
<menuitem
id="menu_mistake_type"
name="Mistake Type"
parent="menu_employee_disciplinary_root"
action="action_mistake_type"
sequence="02"/>
</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

@ -179,9 +179,8 @@ class CustomerPortal(portal.CustomerPortal):
('use_website_helpdesk_form', '=', True),
'|',
('company_id', '=', False),
('company_id', 'in', [request.env.company.id])
('company_id', 'in', [request.env.user.company_id.id])
])
selection_dict = dict(
request.env['helpdesk.team']._fields['portal_ticket_type'].selection
)

View File

@ -483,6 +483,10 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
}
def _get_jd_required_fields(self):
category_dict = {
str(category['id']): category['category_name']
for category in self.env['job.category'].sudo().search_read([], ['id', 'category_name'])
}
return {
"request_id": {"type": "string", "description": "Request or requisition identifier"},
"start_date": {"type": "string", "description": "Requested start date"},
@ -495,6 +499,7 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
"secondary_skills": {"type": "list", "description": "Secondary or nice to have skills"},
"budget": {"type": "string", "description": "Budget or salary range"},
"experience_years": {"type": "float", "description": "Expected total years of experience"},
"job_category": {"type": "selection", "description": "Based on over all Job description which one matches more accurately give me the key: %s"%(category_dict)}
}
def _get_resume_prompt(self):
@ -1766,8 +1771,11 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
"secondary_skill_ids": [(6, 0, secondary_skills.ids)] if secondary_skills else False,
}
job_category = self._match_job_category(parsed_data, parsed_payload)
if job_category:
write_vals["job_category"] = job_category.id
if job_request and job_request.job_category:
if job_request.job_id and job_request.job_id.job_category != job_request.job_category:
job_request.job_id.job_category = job_request.job_category.id
elif job_category:
if job_request.job_id and job_request.job_id.job_category != job_category:
job_request.job_id.job_category = job_category.id
write_vals = {key: value for key, value in write_vals.items() if value not in (False, None, "")}
@ -1805,7 +1813,14 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
job_category = self._match_job_category(parsed_data, parsed_payload)
if job_category and job.job_category != job_category:
job.job_category = job_category.id
category_dict = {
str(category['id']): category['category_name']
for category in self.env['job.category'].search_read([], ['id', 'category_name'])
}
reverse_category = {v: k for k, v in category_dict.items()}
job_category = parsed_data.get("job_category")
job_category_id = reverse_category.get(job_category, job_category)
create_vals = {
"job_id": job.id,
"company_id": self.env.company.id,
@ -1813,12 +1828,17 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
"requirements": parsed_data.get("requirements"),
"target_from": self._parse_date_value(parsed_data.get("start_date")),
"target_to": self._parse_date_value(parsed_data.get("end_date")),
"job_category": job_category.id if job_category else False,
"job_category": int(job_category_id) if job_category_id and job_category_id.isdigit() else False,
"address_id":False,
"recruitment_type": 'external'
}
if request_id:
create_vals["recruitment_sequence"] = request_id
create_vals = {key: value for key, value in create_vals.items() if value not in (False, None, "")}
return job_request_model.create(create_vals), "created"
job_request = job_request_model.create(create_vals)
job_request.sudo().write({"address_id" : False})
return job_request, "created"
def _match_existing_job_position(self, job_title):
hr_job_model = self.env["hr.job"]

View File

@ -5,10 +5,10 @@
<field name="model">hr.recruitment.auto.doc.wizard</field>
<field name="arch" type="xml">
<form string="Parse Recruitment Documents" create="0" edit="1">
<header>
<button name="action_parse_documents" string="Parse Documents" type="object" class="btn-primary" invisible="not attachment_ids and not resume_file"/>
<!-- <button string="Close" special="cancel" class="btn-secondary"/>-->
</header>
<!-- <header>-->
<!-- <button name="action_parse_documents" string="Parse Documents" type="object" class="btn-primary" invisible="not attachment_ids and not resume_file"/>-->
<!--&lt;!&ndash; <button string="Close" special="cancel" class="btn-secondary"/>&ndash;&gt;-->
<!-- </header>-->
<sheet>
<group>
<group>
@ -39,7 +39,15 @@
invisible="1"/>
</group>
<group string="Uploaded Files">
<div class="text-end">
<button name="action_parse_documents"
string="Parse Documents"
type="object"
class="btn-primary"
invisible="not attachment_ids and not resume_file"/>
</div>
<group string="Uploaded Files" invisible="not line_ids">
<field name="line_ids" nolabel="1" readonly="1">
<kanban class="o_kanban_small_column">
<templates>

View File

@ -29,6 +29,7 @@
'data/sequence.xml',
'data/mail_template.xml',
'data/templates.xml',
'views/submission_share_history.xml',
'views/job_category.xml',
'views/hr_location.xml',
'views/stages.xml',

View File

@ -14,7 +14,7 @@
<t t-set="user_names"
t-value="', '.join(object.user_id.mapped('name')) if object.user_id else 'Recruiter'"/>
<t t-set="location_names"
t-value="', '.join(object.locations.mapped('name')) if object.locations else 'N/A'"/>
t-value="', '.join(object.locations.mapped('location_name')) if object.locations else 'N/A'"/>
<div style="font-family: Arial, sans-serif; font-size: 14px; color: #333; line-height: 1.5; padding: 20px;">
@ -669,14 +669,14 @@
<t t-set="locations">
<t t-if="object.hr_job_recruitment.locations">
<t t-set="locations_str"
t-value="', '.join(object.hr_job_recruitment.locations.mapped('name'))"/>
t-value="', '.join(object.hr_job_recruitment.locations.mapped('location_name'))"/>
<t t-set="locations" t-value="locations_str"/>
</t>
<t t-else="">
<t t-set="locations" t-value="''"/>
</t>
</t>
<div style="margin: 0; padding: 0; font-size: 13px; line-height: 1.6;">
<div style="margin: 0; padding: 0; font-size: 13px; line-height: 1.6; background:#ffffff; color:#000000;">
<p>Dear Sir/Madam,</p>
<p>Please find the applicant details below for your review.</p>
<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">
@ -784,10 +784,10 @@
<field name="body_html" type="html">
<t t-set="job_name" t-value="object.job_id.name or object.name or object.display_name or ''"/>
<t t-set="recruitment_name" t-value="object.recruitment_sequence or object.display_name or ''"/>
<t t-set="locations" t-value="', '.join(object.locations.mapped('name')) if object.locations else ''"/>
<t t-set="locations" t-value="', '.join(object.locations.mapped('location_name')) if object.locations else ''"/>
<t t-set="primary_skills" t-value="', '.join(object.skill_ids.mapped('name')) if object.skill_ids else ''"/>
<t t-set="secondary_skills" t-value="', '.join(object.secondary_skill_ids.mapped('name')) if object.secondary_skill_ids else ''"/>
<div style="margin: 0; padding: 0; font-size: 13px; line-height: 1.6;">
<div style="margin: 0; padding: 0; font-size: 13px; line-height: 1.6; background:#ffffff; color:#000000;">
<p>Dear Sir/Madam,</p>
<p>Please find the job description and hiring details below for your review and sourcing support.</p>
<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">

View File

@ -1,9 +1,10 @@
from . import submission_share_history
from . import hr_recruitment
from . import hr_job_recruitment
from . import stages
from . import applicant_request_forms
from . import hr_applicant_stage_comment
from . import hr_applicant
from . import applicant_request_forms
from . import hr_applicant_stage_comment
from . import hr_applicant
from . import hr_job
from . import res_partner
from . import candidate_experience

View File

@ -61,6 +61,30 @@ class HRApplicant(models.Model):
)
stage_comment_count = fields.Integer(compute='_compute_stage_comment_count')
stage_comment_tooltips = fields.Json(compute='_compute_stage_comment_tooltips')
submission_tracker = fields.One2many('recruitment.share.tracker','applicant_id')
submission_count = fields.Integer(
string="Submission Count",
compute="_compute_submission_count",
)
def _compute_submission_count(self):
for rec in self:
rec.submission_count = len(rec.submission_tracker)
def action_open_submissions_wizard(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"name": "Submission History",
"res_model": "recruitment.share.tracker",
"view_mode": "list",
"views": [
(self.env.ref("hr_recruitment_extended.view_recruitment_share_tracker_list_popup").id, "list"),
],
"domain": [("applicant_id", "=", self.id)],
"target": "new",
}
@api.depends('is_on_hold')
def _compute_hold_state(self):

View File

@ -175,6 +175,7 @@ class HRJobRecruitment(models.Model):
requested_by = fields.Many2one('res.partner', string="Requested By",
default=lambda self: self.env.user.partner_id, domain="[('contact_type','=',recruitment_type)]", tracking=True)
submission_tracker = fields.One2many('recruitment.share.tracker','job_recruitment_id')
def action_toggle_chatter_visibility(self):
for record in self:
record.hide_chatter_suggestion = not record.hide_chatter_suggestion
@ -405,6 +406,7 @@ class HRJobRecruitment(models.Model):
'default_template_id': self.env.ref(
'hr_recruitment_extended.job_recruitment_share_email_template'
).id,
'default_is_job_recruitment': True,
},
}

View File

@ -0,0 +1,51 @@
from odoo import api, fields, models
class RecruitmentShareTracker(models.Model):
_name = "recruitment.share.tracker"
_description = "Recruitment Share Tracker"
_order = "date desc, id desc"
date = fields.Datetime(
string="Date",
required=True,
default=fields.Datetime.now,
)
share_type = fields.Selection(
[
("job", "Job"),
("applicant", "Applicant"),
],
string="Share Type",
required=True,
)
reference = fields.Char(
string="Reference",
)
applicant_id = fields.Many2one(
"hr.applicant",
string="Applicant",
ondelete="set null",
)
job_recruitment_id = fields.Many2one(
"hr.job.recruitment",
string="Job Position",
ondelete="set null",
)
email_from = fields.Char(
string="Email From",
)
email_to = fields.Char(
string="Email To",
)
is_client_submission = fields.Boolean(
string="Client Submission",
default=False,
)

View File

@ -1,6 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_location,hr.location,model_hr_location,base.group_user,1,1,1,1
access_recruitment_share_tracker_user,recruitment.share.tracker.user,model_recruitment_share_tracker,hr_recruitment.group_hr_recruitment_user,1,1,1,1
access_job_category_user,job.category.user,model_job_category,base.group_user,1,0,0,0
access_job_category_manager,job.category.manager,model_job_category,hr_recruitment.group_hr_recruitment_user,1,1,1,1
@ -35,4 +36,5 @@ access_applicant_stage_comment_wizard,applicant.stage.comment.wizard.user,model_
access_hr_application_public,hr.applicant.public.access,hr_recruitment.model_hr_applicant,base.group_public,1,0,0,0
access_hr_application_group_hr,hr.applicant.hr.access,hr_recruitment.model_hr_applicant,hr.group_hr_manager,1,1,0,0
access_applicant_request_forms_hr_user,access.applicant.request.forms.hr.user,model_applicant_request_forms,hr.group_hr_user,1,1,1,1
access_hr_skill,access.hr.skill.user,hr_skills.model_hr_skill,base.group_public,1,0,0,0
access_hr_skill,access.hr.skill.user,hr_skills.model_hr_skill,base.group_public,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_location hr.location model_hr_location base.group_user 1 1 1 1
3 access_job_category_user access_recruitment_share_tracker_user job.category.user recruitment.share.tracker.user model_job_category model_recruitment_share_tracker base.group_user hr_recruitment.group_hr_recruitment_user 1 0 1 0 1 0 1
4 access_job_category_user job.category.user model_job_category base.group_user 1 0 0 0
5 access_job_category_manager job.category.manager model_job_category hr_recruitment.group_hr_recruitment_user 1 1 1 1
6 access_hr_job_recruitment_user access.hr.job.recruitment.user model_hr_job_recruitment base.group_user 1 1 0 0
7 access_hr_job_recruitment_manager access.hr.job.recruitment.manager model_hr_job_recruitment hr_recruitment.group_hr_recruitment_user 1 1 1 1
36
37
38
39
40

View File

@ -90,6 +90,10 @@
<button name="action_open_stage_comment_wizard" type="object" class="oe_stat_button" icon="fa-comments">
<field name="stage_comment_count" widget="statinfo" string="Stage Notes"/>
</button>
<button name="action_open_submissions_wizard" type="object" class="oe_stat_button" icon="fa-file-text-o" invisible="not submission_tracker">
<field name="submission_count" widget="statinfo" string="Share History"/>
</button>
</xpath>
<xpath expr="//page[@name='application_details']/group[1]" position="after">
<group string="Client Submission">

View File

@ -110,10 +110,10 @@
</group>
<group>
<group>
<field name="job_category" force_save="1"/>
<field name="job_category" force_save="1" placeholder="Select Category"/>
</group>
<group>
<field name="job_priority"/>
<field name="job_priority" placeholder="Select Priority"/>
</group>
</group>
@ -201,6 +201,20 @@
<separator string="Process Details"/>
<field name="job_details" nolabel="1"/>
</page>
<page string="Submission History" name="submission_tracker" invisible="not submission_tracker">
<field name="submission_tracker">
<list create="0" edit="0" delete="0">
<field name="date"/>
<field name="share_type" column_invisible="1"/>
<field name="reference"/>
<field name="applicant_id" column_invisible="1"/>
<field name="job_recruitment_id" column_invisible="1"/>
<field name="email_from"/>
<field name="email_to"/>
<field name="is_client_submission" column_invisible="1"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter open_attachments="True" invisible="hide_chatter_suggestion"/>
@ -429,6 +443,7 @@
<field name="name">Job Positions Recruitment</field>
<field name="res_model">hr.job.recruitment</field>
<field name="view_mode">kanban,list,form</field>
<field name="domain"></field>
<field name="search_view_id" ref="view_job_recruitment_filter"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@ -446,6 +461,7 @@
<field name="res_model">hr.job.recruitment</field>
<field name="view_mode">kanban,list,form,search</field>
<field name="search_view_id" ref="view_job_recruitment_filter"/>
<field name="domain"></field>
<field name="context">{"search_default_open_status":1,"search_default_my_assignments":1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@ -482,7 +498,7 @@
active="0"
sequence="2"/>
<menuitem name="JP"
<menuitem name="Job Position"
id="menu_hr_job_descriptions"
parent="hr_recruitment.menu_hr_recruitment_root"
action="action_hr_job_recruitment_awaiting_published"

View File

@ -117,7 +117,7 @@
<xpath expr="//notebook" position="before">
<group>
<field name="department_id"/>
<field name="job_category"/>
<field name="job_category" placeholder="Select Category"/>
</group>
</xpath>
<xpath expr="//notebook" position="inside">
@ -338,6 +338,7 @@
<record id="hr_recruitment.action_hr_candidate" model="ir.actions.act_window">
<field name="search_view_id" ref="hr_recruitment.hr_candidate_view_search"/>
<field name="context">{'search_default_my_candidates': 1,'active_test': False}</field>
<field name="view_mode">kanban,list,form,activity</field>
</record>
<menuitem

View File

@ -12,7 +12,7 @@
<attribute name="required">1</attribute>
</xpath>
<xpath expr="//field[@name='job_id']" position="after">
<field name="job_category" required="1" readonly="state not in ['draft']"/>
<field name="job_category" placeholder="Select Category" required="1" readonly="state not in ['draft']"/>
<field name="hr_job_recruitment" string="Job Recruitment ID" readonly="not is_hr or state == 'jd_created'" force_save="1" invisible="state not in ['final','jd_created']" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
<field name="recruitment_status" widget="statusbar" readonly="1" force_save="1" invisible="not hr_job_recruitment" options="{'clickable': '1', 'fold_field': 'fold'}"/>
</xpath>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_recruitment_share_tracker_list_popup" model="ir.ui.view">
<field name="name">recruitment.share.tracker.popup.list</field>
<field name="model">recruitment.share.tracker</field>
<field name="arch" type="xml">
<list create="0"
edit="0"
delete="0"
open_form_view="0">
<field name="date"/>
<field name="share_type"/>
<field name="reference"/>
<field name="email_from"/>
<field name="email_to"/>
<field name="is_client_submission"/>
</list>
</field>
</record>
</odoo>

View File

@ -1,65 +1,128 @@
from odoo import models, fields, api
from odoo.exceptions import UserError
class ClientSubmissionsMailTemplateWizard(models.TransientModel):
_name = 'client.submission.mails.template.wizard'
_description = 'Client Submission Mails Template Wizard'
template_id = fields.Many2one('mail.template', string='Email Template')
submit_date = fields.Date(string='Submission Date', required=True, default=fields.Date.today())
send_email_from_odoo = fields.Boolean(string="Send Email From Odoo", default=False)
is_job_recruitment = fields.Boolean(string='Is Job Recruitment', default=False)
is_client_submission = fields.Boolean(string='Is Client Submission?', default=False)
send_email_from_odoo = fields.Boolean(string="Send Email", default=True)
email_from = fields.Char('Email From')
email_to = fields.Char('Email To')
email_cc = fields.Text('Email CC')
email_subject = fields.Char()
email_body = fields.Html(
'Body', render_engine='qweb', render_options={'post_process': True},
prefetch=True, translate=True, sanitize='email_outgoing',
'Body',
render_engine='qweb',
render_options={'post_process': True},
prefetch=True,
translate=True,
sanitize='email_outgoing',
)
@api.onchange('template_id')
def _onchange_template_id(self):
""" Update the email body and recipients based on the selected template. """
if self.template_id:
record_id = self.env.context.get('active_id')
if record_id:
record = self.env[self.template_id.model].browse(record_id)
if not self.template_id:
return
if not record.exists():
raise UserError("The record does not exist or is not accessible.")
record_id = self.env.context.get('active_id')
active_model = self.env.context.get('active_model')
# Fetch email template
email_template = self.env['mail.template'].browse(self.template_id.id)
if not record_id:
return
if not email_template:
raise UserError("Email template not found.")
record = self.env[active_model].browse(record_id)
self.email_from = record.user_id.partner_id.email
self.email_to = record.requested_by.email
self.email_body = email_template.body_html # Assign the rendered email bodyc
self.email_subject = email_template.subject
self.email_from = record.user_id.partner_id.email
if active_model == 'hr.applicant':
self.email_to = record.hr_job_recruitment.requested_by.email
else:
self.email_to = record.requested_by.email
# Render subject
self.email_subject = self.template_id._render_field(
'subject',
[record.id],
)[record.id]
# Render body
self.email_body = self.template_id._render_field(
'body_html',
[record.id],
)[record.id]
def action_send_email(self):
""" Send email to the selected partners """
"""Send email and create recruitment share tracker."""
self.ensure_one()
record_id = self.env.context.get('active_id')
for rec in self:
record = self.env[self.template_id.model].browse(record_id)
if rec.send_email_from_odoo:
template = self.env.ref('hr_recruitment_extended.application_client_submission_email_template')
values = {
'email_from': rec.email_from,
'email_to': rec.email_to,
'email_cc': rec.email_cc,
'subject' : rec.email_subject,
'body_html': rec.email_body,
}
render_ctx = dict(client_name=record.hr_job_recruitment.requested_by.name)
# Use 'with_context' to override the email template fields dynamically
template.sudo().with_context(**render_ctx).send_mail(self.env.context.get('active_id'),email_values=values, force_send=True)
model_name = self.env.context.get('active_model')
record = self.env[self.template_id.model].browse(record_id)
tracker_values = {
'date': fields.Datetime.now(),
'is_client_submission': self.is_client_submission,
}
if model_name == 'hr.applicant':
tracker_values.update({
'share_type': 'applicant',
'reference': "%s/%s" % (
record.hr_job_recruitment.recruitment_sequence,
record.candidate_id.candidate_sequence,
),
'applicant_id': record.id,
})
else:
tracker_values.update({
'share_type': 'job',
'reference': record.recruitment_sequence,
'job_recruitment_id': record.id,
})
if self.send_email_from_odoo:
# Use the selected template
template = self.template_id
values = {
'email_from': self.email_from,
'email_to': self.email_to,
'email_cc': self.email_cc,
'subject': self.email_subject,
'body_html': self.email_body,
}
if model_name == 'hr.applicant':
client_name = record.hr_job_recruitment.requested_by.name
else:
client_name = record.requested_by.name
render_ctx = {
'client_name': client_name,
}
tracker_values['email_from'] = self.email_from
tracker_values['email_to'] = self.email_to
template.sudo().with_context(**render_ctx).send_mail(
record.id,
email_values=values,
force_send=True,
)
if self.is_client_submission and model_name == 'hr.applicant':
record.sudo().write({
'submitted_to_client': True,
'client_submission_date': rec.submit_date,
'client_submission_date': self.submit_date,
'submitted_stage': record.recruitment_stage_id.id,
})
return {'type': 'ir.actions.act_window_close'} # Close wizard after sending
self.env['recruitment.share.tracker'].sudo().create(tracker_values)
return {'type': 'ir.actions.act_window_close'}

View File

@ -9,6 +9,8 @@
<form>
<group>
<field name="submit_date"/>
<field name="is_job_recruitment" invisible="1"/>
<field name="is_client_submission" invisible="is_job_recruitment"/>
<field name="send_email_from_odoo"/>
<separator string="Section Title"/>
<field name="template_id" options="{'no_create': True}" invisible="not send_email_from_odoo" required="send_email_from_odoo" readonly="1" force_save="1"/>

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

@ -33,9 +33,9 @@ class HrContract(models.Model):
"""Get the default notice period from the configuration.
:return: The default notice period in days.
:rtype: int """
return self.env['ir.config_parameter'].get_param(
return self.env['ir.config_parameter'].sudo().get_param(
'hr_employee_updation.no_of_days') if self.env[
'ir.config_parameter'].get_param(
'ir.config_parameter'].sudo().get_param(
'hr_employee_updation.notice_period') else 0
notice_days = fields.Integer(string="Notice Period",

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,8 @@ 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")
image_template = fields.Image(related='employee_eva_id.image_1920', string="Image")
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 +123,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,122 @@
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)
image_1920 = fields.Image(related='hr_employee_id.image_1920')
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([
@ -44,37 +47,83 @@ class HrNoticeAppraisal(models.Model):
new_end_date = fields.Datetime(string="New End Date")
stage_config = fields.Many2many('employee.stage.config',string='Stages')
hr_department_ids = fields.Many2many('hr.department', string="Departments")
image_1920 = fields.Image(related='hr_employee_id.image_1920',string='Employee Image')
@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 +138,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 +236,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 +251,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,21 +10,27 @@
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>
<sheet>
<field name="image_template"
widget="image"
class="oe_avatar"
options="{'preview_image': 'image_template'}"/>
<group string="Details" col="2">
<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="employee_eva_id"/>
<field name="employee_department_id"/>
<field name="appraisal_period_type_id" string="Performance Type"/>
<field name="appraisal_period_id" string="Performance Period"/>
<field name="hr_employee_id" string="HR"/>
<field name="stage_config_ids" widget="many2many_tags"/>
<field name="seq" string="Reference"/>
<field name="name" string="Subject" readonly="1" placeholder="Administration Appraisal Template"/>
<field name="manager_ids" widget="many2many_tags" string="Performance Evaluator"
invisible="1"/>
<field name="employee_eva_id" readonly="1" options="{'no_open': True}"/>
<field name="employee_department_id" readonly="1" options="{'no_open': True}"/>
<field name="appraisal_period_type_id" string="Appraisal Type" readonly="1" options="{'no_open' : True}"/>
<field name="appraisal_period_id" string="Appraisal Period" readonly="1" options="{'no_open' : True}"/>
<field name="hr_employee_id" string="HR" readonly="1" options="{'no_open' : True}"/>
<field name="stage_config_ids" widget="many2many_tags" readonly="1"/>
</group>
<group>
<field name="company_id" string="Company"/>
@ -33,8 +39,8 @@
<!-- <field name="hr_email_notify" string="Send Email if HR APPROVE or REJECT Evaluation?"/>-->
<field name="start_date" readonly="1"/>
<field name="end_date" readonly="1"/>
<field name="template_rating_bool"/>
<field name="template_point_bool"/>
<field name="template_rating_bool" string="Employee Rating"/>
<field name="template_point_bool" string="Employee points"/>
<field name="kra_weightage"/>
</group>
</group>
@ -48,8 +54,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 +88,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 +107,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 +130,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 +141,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,9 @@
<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"
@ -14,33 +16,53 @@
<field name="state" widget="statusbar"/>
</header>
<sheet>
<!-- <div class="oe_title" style="display:flex; align-items:center; gap:20px;">-->
<!-- <field name="image_1920"-->
<!-- widget="image"-->
<!-- class="oe_avatar"-->
<!-- readonly="1"/>-->
<!-- <h1>-->
<!-- <field name="seq" readonly="1"/>-->
<!-- </h1>-->
<!-- </div>-->
<field name="image_1920"
widget="image"
class="oe_avatar"
readonly="1"/>
<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="Employee" readonly="1" options="{'no_open': True}"/>
<field name="appraisal_type_id"
options="{'no_edit': True, 'no_create': True, 'no_open': True}"
readonly="1"/>
<field name="appraisal_notice_id"
options="{'no_edit': True, 'no_create': True, 'no_open': True}"
readonly="1"/>
<field name="subject" readonly="1"/>
<field name="start_date" readonly="1" force_save="1"/>
<field name="end_date" readonly="1" 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="1"/>
<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="1"/>
</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 +75,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 +96,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 +120,105 @@
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_button_box" name="button_box"/>
<field name="image_1920"
widget="image"
class="oe_avatar"
options="{'preview_image': 'image_1920'}"/>
<div class="oe_title">
<h1>
<field name="name" placeholder="Performance Appraisal Notification" required="1"
readonly="state != 'draft'"/>
</h1>
</div>
<group col="2">
<group string="Appraisal Details">
<field name="seq"/>
<field name="hr_employee_id" readonly="1" options="{'no_open':True}"/>
<field name="appraisal_type_id" options="{'no_create': True,'no_open': True}"
readonly="state != 'draft'"/>
<field name="appraisal_period_id" options="{'no_create': True,'no_open': True}"
readonly="state != 'draft'"/>
<field name="start_date" readonly="state != 'draft'"/>
<field name="end_date" readonly="state != 'draft'"/>
</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" readonly="state != 'draft'"/>
<field name="stage_config_ids" widget="many2many_tags" required="1"
readonly="state != 'draft'"/>
</group>
</group>
<notebook>
<page string="Notification Message">
<field name="body" widget="html" readonly="state != 'draft'"/>
</page>
<page string="Audit">
<group>
<field name="create_uid" readonly="1" options="{'no_open':True}"/>
<field name="create_date" readonly="1"/>
<field name="write_uid" readonly="1" options="{'no_open':True}"/>
<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

@ -5,14 +5,14 @@
<field name="name">Applicant Offer Email Template</field>
<field name="model_id" ref="offer_letters.model_offer_letter"/>
<field name="email_from">{{ user.email_formatted }}</field>
<field name="email_to">{{ object.candidate_id.email_from or '' }}</field>
<field name="subject">Offer Letter - {{ object.position or object.candidate_id.job_id.name or '' }}</field>
<field name="email_to">{{ object.main_candidate_id.email_from or '' }}</field>
<field name="subject">Offer Letter - {{ object.position or object.main_candidate_id.job_id.name or '' }}</field>
<field name="description">
Send applicant offer mail with offer letter attachment.
</field>
<field name="body_html" type="html">
<div style="margin: 0; padding: 0; font-size: 13px; line-height: 1.7;">
<p>Dear <t t-esc="object.candidate_id.partner_name or ''"/>,</p>
<p>Dear <t t-esc="object.main_candidate_id.partner_name or ''"/>,</p>
<p>
With reference to the interview and subsequent discussions you had with us, we are pleased to select
you for the position of "<t t-esc="object.position or ''"/>" in our organization with the following

View File

@ -1,4 +1,5 @@
from odoo import models, fields, api, _
from odoo.api import readonly
from odoo.exceptions import UserError
from collections import defaultdict
from datetime import timedelta, datetime
@ -26,8 +27,24 @@ class OfferLetter(models.Model):
default=lambda self: _('New'),
copy=False
)
candidate_id = fields.Many2one( 'hr.applicant', string='Candidate', required=True,
candidate_id = fields.Many2one( 'hr.applicant', string='Applicant', required=False,
)
main_candidate_id = fields.Many2one('hr.candidate',string='Candidate', readonly=False, required=True)
@api.onchange('candidate_id')
def _onchange_candidate_id(self):
if self.candidate_id:
self.main_candidate_id = self.candidate_id.candidate_id
main_candidate_name = fields.Char(compute="_compute_main_candidate_name", readonly=False)
@api.depends('candidate_id','main_candidate_id')
def _compute_main_candidate_name(self):
for rec in self:
if rec.candidate_id:
rec.main_candidate_name = rec.candidate_id.partner_name
elif rec.main_candidate_id:
rec.main_candidate_name = rec.main_candidate_id.partner_name
requested_by_id = fields.Many2one('res.users', string='Requested By', readonly=True, tracking=True)
request_date = fields.Datetime(string='Requested On', readonly=True, tracking=True)

View File

@ -62,9 +62,9 @@
<div style="margin-bottom: 25px;">
<strong>To,</strong>
<br/>
<strong t-esc="o.candidate_id.partner_name"/>
<strong t-esc="o.main_candidate_id.partner_name"/>
<br/>
<div t-if="o.candidate_id.private_street and o.candidate_id.private_city">
<div t-if="o.candidate_id and o.candidate_id.private_street and o.candidate_id.private_city">
<t t-esc="o.candidate_id.private_street"/>
<br/>
<t t-esc="o.candidate_id.private_street2" t-if="o.candidate_id.private_street2"/>
@ -81,7 +81,7 @@
<!-- DEAR LINE -->
<div style="margin-bottom: 20px;">
<strong>Dear
<t t-esc="o.candidate_id.partner_name"/>,
<t t-esc="o.main_candidate_id.partner_name"/>,
</strong>
</div>
@ -368,7 +368,7 @@
<br/>
<br/>
<br/>
<strong t-esc="o.candidate_id.partner_name"/>
<strong t-esc="o.main_candidate_id.partner_name"/>
<br/>
<strong t-esc="o.position"/>
<br/>
@ -486,7 +486,7 @@
<br/>
<br/>
<strong>Employee Name :
<t t-esc="o.candidate_id.partner_name"/>
<t t-esc="o.main_candidate_id.partner_name"/>
</strong>
<br/>
<br/>
@ -595,7 +595,7 @@
<br/>
<br/>
<br/>
<strong t-esc="o.candidate_id.partner_name"/>
<strong t-esc="o.main_candidate_id.partner_name"/>
<br/>
<strong t-esc="o.position"/>
<br/>
@ -621,7 +621,7 @@
Company incorporated under Indian Companies Act 1956, having registered office in
Hyderabad,
India ("Company")
<strong t-esc="o.candidate_id.partner_name"/>
<strong t-esc="o.main_candidate_id.partner_name"/>
(Recipient)
</p>
<p>Whereas "Company" wishes to explore the possibility of entering into an employment
@ -875,7 +875,7 @@
<strong t-esc="o.joining_date"/>
</td>
<td style="padding-top: 20px;">
<strong t-esc="o.candidate_id.partner_name"/>
<strong t-esc="o.main_candidate_id.partner_name"/>
<br/>
<strong t-esc="o.position"/>
<br/>

View File

@ -6,7 +6,7 @@
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="candidate_id"/>
<field name="main_candidate_name"/>
<field name="position"/>
<field name="state"/>
<field name="sent_date"/>
@ -32,7 +32,9 @@
<group>
<group>
<field name="name" readonly="state != 'requested'"/>
<field name="candidate_id"/>
<field name="main_candidate_name" invisible="1"/>
<field name="candidate_id" invisible="not candidate_id" readonly="1" force_save="1"/>
<field name="main_candidate_id" invisible="candidate_id" force_save="1"/>
<field name="requested_by_id" readonly="1"/>
<field name="request_date" readonly="1"/>
<field name="manager_id"/>

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