Compare commits
3 Commits
42766d792e
...
74358c2cda
| Author | SHA1 | Date |
|---|---|---|
|
|
74358c2cda | |
|
|
804a6ec626 | |
|
|
ec42e2736c |
|
|
@ -0,0 +1,2 @@
|
|||
from . import models
|
||||
from . import controllers
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "Odoo Flutter",
|
||||
|
||||
'summary': 'Integrating flutter with odoo',
|
||||
|
||||
'description': """
|
||||
Creating the required functions according to our needs to add it in the flutter;
|
||||
""",
|
||||
|
||||
'author': "Pranay",
|
||||
'website': "https://www.ftprotech.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Flutter',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','hr','hr_attendance','hr_employee_extended'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import hr_employee
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class HrEmployeeBase(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
|
||||
def fetch_all_employees_info(self, user_id=None):
|
||||
if user_id:
|
||||
employee = self.env['hr.employee'].sudo().search([('user_id','=',user_id)], limit=1)
|
||||
|
||||
if employee:
|
||||
|
||||
data = {
|
||||
'id': str(employee.id),
|
||||
'employee_code': str(employee.employee_id),
|
||||
'name': str(employee.name),
|
||||
'work_email': str(employee.work_email),
|
||||
'work_phone': str(employee.work_phone),
|
||||
'job_title': str(employee.job_title),
|
||||
'department_name': str(employee.department_id.name),
|
||||
'work_location_name': str(employee.work_location_id.name),
|
||||
'manager': str(employee.parent_id.name),
|
||||
'address': str(", ".join(
|
||||
filter(None, [
|
||||
employee.private_street,
|
||||
employee.private_street2,
|
||||
employee.private_city,
|
||||
employee.private_state_id.name if employee.private_state_id else False,
|
||||
employee.private_zip,
|
||||
employee.private_country_id.name if employee.private_country_id else False
|
||||
])
|
||||
) or 'N/A'),
|
||||
'bank_account_id': str(employee.bank_account_id.acc_number if employee.bank_account_id else 'N/A'),
|
||||
'bank_name': str(employee.bank_account_id.bank_id.name if employee.bank_account_id and employee.bank_account_id.bank_id else 'N/A'),
|
||||
'bank_ifsc': str(employee.bank_account_id.bank_id.bic if employee.bank_account_id and employee.bank_account_id.bank_id else 'N/A'),
|
||||
'doj': str(fields.Date.from_string(employee.doj).strftime('%d-%m-%Y') if employee.doj else False),
|
||||
'birthday': str(fields.Date.from_string(employee.birthday).strftime('%d-%m-%Y') if employee.birthday else False),
|
||||
'gender': str(employee.gender).upper(),
|
||||
'marital': str(employee.marital).upper(),
|
||||
'aadhar_no': str(employee.identification_id),
|
||||
'pan_no': str(employee.pan_no),
|
||||
'profile_pic': employee.image_1920,
|
||||
}
|
||||
return data
|
||||
|
||||
def mobile_check_in_out(self,is_check_in,longitude=None,latitude=None,in_ip_address=None):
|
||||
"""
|
||||
Check in or check out logic.
|
||||
:param latitude: Latitude from the mobile app.
|
||||
:param longitude: Longitude from the mobile app.
|
||||
:param is_check_in: Boolean to check whether it is check-in or check-out.
|
||||
"""
|
||||
employee = self.env.user.employee_id
|
||||
attendance_obj = self.env['hr.attendance']
|
||||
|
||||
# Check if there is an existing attendance record with no checkout
|
||||
attendance = attendance_obj.search([
|
||||
('employee_id', '=', employee.id),
|
||||
('check_out', '=', False)
|
||||
], limit=1)
|
||||
|
||||
if not is_check_in:
|
||||
# If it's check-in, create a new attendance record
|
||||
if not attendance:
|
||||
record = attendance_obj.create({
|
||||
'employee_id': employee.id,
|
||||
'check_in': fields.Datetime.now(),
|
||||
'in_latitude': latitude if latitude and latitude!=0 else False,
|
||||
'in_longitude': longitude if longitude and longitude!=0 else False,
|
||||
'in_mode': 'systray' if latitude != 0 else 'manual',
|
||||
'in_ip_address': in_ip_address if in_ip_address else False,
|
||||
'in_browser': 'Mobile'
|
||||
|
||||
})
|
||||
return {
|
||||
'message': 'Checked in successfully',
|
||||
'attendance_id': record.id
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'message': 'Already checked in',
|
||||
}
|
||||
else:
|
||||
# If it's check-out, update the existing attendance record
|
||||
if attendance:
|
||||
attendance.write({
|
||||
'check_out': fields.Datetime.now(),
|
||||
'out_latitude': latitude if latitude and latitude != 0 else False,
|
||||
'out_longitude': longitude if longitude and longitude != 0 else False,
|
||||
'out_mode': 'systray' if latitude != 0 else 'manual',
|
||||
'out_ip_address': in_ip_address if in_ip_address else False,
|
||||
'out_browser': 'Mobile'
|
||||
})
|
||||
return {
|
||||
'message': 'Checked out successfully',
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'message': 'No active check-in found',
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "Attendance Extended",
|
||||
|
||||
'summary': 'Customized Attendance',
|
||||
|
||||
'description': """
|
||||
Customized Attendance data, reports
|
||||
""",
|
||||
|
||||
'author': "Pranay",
|
||||
'website': "https://www.ftprotech.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Human Resources/Attendances',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','hr','hr_attendance','hr_holidays','hr_employee_extended'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/cron.xml',
|
||||
'views/hr_attendance.xml'
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="ir.cron" id="hr_attendance_check_out_update">
|
||||
<field name="name">Attendance: Check Out Update</field>
|
||||
<field name="model_id" ref="hr_attendance_extended.model_attendance_attendance"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.update_hr_attendance_check_out()</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="nextcall" eval="(DateTime.now().replace(hour=18, minute=15, second=0, microsecond=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import hr_attendance
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
from odoo import fields, api, models
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class AttendanceAttendance(models.Model):
|
||||
_name = "attendance.attendance"
|
||||
|
||||
date = fields.Date(string='Date', default=fields.Date.today, required=True)
|
||||
work_location_id = fields.Many2one('hr.work.location', default=lambda self: self.env.company.id)
|
||||
attendance_data = fields.One2many('attendance.data','attendance_id', string="Attendance Data")
|
||||
|
||||
_sql_constraints = [
|
||||
('name_date_work_location_id_unique', 'unique(date, work_location_id)', 'The combination of date and work_location_id should be unique.')
|
||||
]
|
||||
|
||||
def get_attendance_for_date(self, rec_date, employee_id):
|
||||
# Convert rec.date to a datetime object for filtering
|
||||
start_of_day = fields.Datetime.to_string(
|
||||
fields.Datetime.from_string(rec_date).replace(hour=0, minute=0, second=0))
|
||||
end_of_day = fields.Datetime.to_string(
|
||||
fields.Datetime.from_string(rec_date).replace(hour=23, minute=59, second=59))
|
||||
|
||||
# Filter attendance records for the given employee and date range
|
||||
attendance_records = self.env['hr.attendance'].sudo().search([
|
||||
('employee_id', '=', employee_id),
|
||||
('check_in', '>=', start_of_day),
|
||||
('check_in', '<=', end_of_day)
|
||||
],order='check_in asc')
|
||||
if not attendance_records:
|
||||
return False, False, False
|
||||
|
||||
# Initialize variables to track min check_in and max check_out
|
||||
min_check_in = None
|
||||
max_check_out = None
|
||||
|
||||
for record in attendance_records:
|
||||
if min_check_in is None or record.check_in < min_check_in:
|
||||
min_check_in = record.check_in
|
||||
if max_check_out is None or (record.check_out and record.check_out > max_check_out):
|
||||
max_check_out = record.check_out
|
||||
|
||||
# If no check_out found, set max_check_out to current time
|
||||
if not max_check_out:
|
||||
max_check_out = False
|
||||
total_worked_hours = sum(record.worked_hours for record in attendance_records)
|
||||
|
||||
# Return the min_check_in and max_check_out
|
||||
return min_check_in, max_check_out, total_worked_hours
|
||||
|
||||
def update_attendance_data(self):
|
||||
for rec in self:
|
||||
if rec.date and rec.work_location_id:
|
||||
rec.attendance_data.unlink()
|
||||
emp_work_location_history = self.env['emp.work.location.history'].sudo().search([('employee_id.company_id','=',self.env.company.id),('employee_id.work_location_id','=',rec.work_location_id.id),('start_date','<=',rec.date),'|',('end_date','=',False),('end_date','>=',rec.date)]).employee_id.ids
|
||||
employees = self.env['hr.employee'].sudo().search([('id','not in',emp_work_location_history),('work_location_id','=',rec.work_location_id.id)]).ids
|
||||
all_employees = self.env['hr.employee'].sudo().search([('id','in',list(set(emp_work_location_history) | set(employees)))])
|
||||
attendance_data = list()
|
||||
for emp in all_employees:
|
||||
leaves = self.env['hr.leave'].search([('employee_id','=',emp.id),('request_date_from', '<=', rec.date),('request_date_to', '>=', rec.date),('state','=','validate')])
|
||||
check_in, check_out, worked_hours = self.get_attendance_for_date(rec.date,emp.id)
|
||||
if leaves:
|
||||
status = 'leave'
|
||||
elif check_in or check_out:
|
||||
status = 'present'
|
||||
else:
|
||||
status = 'no_info'
|
||||
if not leaves and (check_in and check_out):
|
||||
# Calculate total hours between check_in and check_out in float format
|
||||
total_hours = (check_out - check_in).total_seconds() / 3600 # Convert seconds to hours
|
||||
|
||||
# Calculate out_time as the difference between total_hours and worked_hours
|
||||
out_time = total_hours - (worked_hours if worked_hours else 0)
|
||||
else:
|
||||
total_hours = 0
|
||||
out_time = 0
|
||||
|
||||
data = self.env['attendance.data'].sudo().create({
|
||||
'employee_id': emp.id,
|
||||
'min_check_in': check_in if check_in else False,
|
||||
'max_check_out': check_out if check_out else False,
|
||||
'worked_hours': worked_hours if worked_hours else False,
|
||||
'out_time': out_time if out_time else False,
|
||||
'status': status,
|
||||
'attendance_id': rec.id
|
||||
})
|
||||
attendance_data.append(data.id)
|
||||
rec.attendance_data = [(6, 0, attendance_data)]
|
||||
|
||||
|
||||
def update_hr_attendance_check_out(self):
|
||||
attendance = self.env['hr.attendance'].sudo().search([('check_in','!=', False),('check_out','=',False)])
|
||||
for rec in attendance:
|
||||
rec.check_out = fields.Datetime.to_string(
|
||||
fields.Datetime.from_string(rec.check_in).replace(hour=21, minute=0, second=59))
|
||||
|
||||
class AttendanceData(models.Model):
|
||||
_name = 'attendance.data'
|
||||
|
||||
employee_id = fields.Many2one('hr.employee')
|
||||
min_check_in = fields.Datetime(string="Check In")
|
||||
max_check_out = fields.Datetime(string="Check Out")
|
||||
out_time = fields.Float()
|
||||
worked_hours = fields.Float()
|
||||
# extra_hours = fields.Float()
|
||||
status = fields.Selection([('leave','On Leave'),('present','Present'),('no_info','No Information')])
|
||||
attendance_id = fields.Many2one('attendance.attendance')
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_attendance_attendance_user,attendance.attendance.access,model_attendance_attendance,base.group_user,1,1,1,1
|
||||
access_attendance_data_user,attendance.data.access,model_attendance_data,base.group_user,1,1,1,1
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Attendance Attendance Views -->
|
||||
<record id="view_attendance_attendance_list" model="ir.ui.view">
|
||||
<field name="name">attendance.attendance.list</field>
|
||||
<field name="model">attendance.attendance</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Attendance">
|
||||
<field name="date"/>
|
||||
<field name="work_location_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_attendance_attendance_form" model="ir.ui.view">
|
||||
<field name="name">attendance.attendance.form</field>
|
||||
<field name="model">attendance.attendance</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Attendance">
|
||||
<header>
|
||||
<button name="update_attendance_data" type="object" string="Update Data" class="btn-primary"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
<field name="work_location_id"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Attendance Data">
|
||||
<field name="attendance_data">
|
||||
<list string="Attendance Data" editable="bottom">
|
||||
<field name="employee_id"/>
|
||||
<field name="min_check_in"/>
|
||||
<field name="max_check_out"/>
|
||||
<field name="out_time" widget="float_time"/>
|
||||
<field name="worked_hours" widget="float_time"/>
|
||||
<!-- <field name="extra_hours"/>-->
|
||||
<field name="status"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Attendance Data Views -->
|
||||
<record id="view_attendance_data_list" model="ir.ui.view">
|
||||
<field name="name">attendance.data.list</field>
|
||||
<field name="model">attendance.data</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Attendance Data">
|
||||
<field name="employee_id"/>
|
||||
<field name="min_check_in"/>
|
||||
<field name="max_check_out"/>
|
||||
<field name="out_time"/>
|
||||
<field name="worked_hours"/>
|
||||
<!-- <field name="extra_hours"/>-->
|
||||
<field name="status"/>
|
||||
<field name="attendance_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Action for Attendance Attendance -->
|
||||
<record id="action_attendance_attendance" model="ir.actions.act_window">
|
||||
<field name="name">Attendance</field>
|
||||
<field name="res_model">attendance.attendance</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Create your first attendance record
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_attendance_attendance" name="Management" parent="hr_attendance.menu_hr_attendance_root"
|
||||
sequence="6" groups="hr_attendance.group_hr_attendance_officer"/>
|
||||
|
||||
<!-- Menu for Attendance -->
|
||||
<menuitem id="hr_attendance.menu_hr_attendance_view_attendances_management" name="Attendance" parent="hr_attendance_extended.menu_attendance_attendance" sequence="6" groups="hr_attendance.group_hr_attendance_officer" action="action_attendance_attendance"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
'name': 'Employee Appraisal',
|
||||
'version': '1.0',
|
||||
'category': 'Human Resources',
|
||||
'summary': 'Module for managing employee appraisals and performance reviews.',
|
||||
'description': """
|
||||
This module allows you to manage employee appraisals and performance reviews. It provides functionality to create and track appraisals, assign reviewers, and evaluate employee performance based on defined criteria.
|
||||
""",
|
||||
'author': 'Pranay',
|
||||
'website': 'https://www.ftprotech.com',
|
||||
'depends': ['hr','hr_employee_extended'], # Assuming 'hr' and 'hr_appraisal' modules are required
|
||||
'data': [
|
||||
'security/security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
# 'data/default_data.xml',
|
||||
'data/mail_template_data.xml',
|
||||
'views/appraisal_period_master.xml',
|
||||
'views/kpi_kra_master.xml',
|
||||
'views/employee_appraisal.xml',
|
||||
'views/res_config_settings.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'/hr_employee_appraisal/static/description/*',
|
||||
],
|
||||
},
|
||||
'images': ['static/description/icon.png'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="ir.cron" id="hr_appraisal_activation_date_update">
|
||||
<field name="name">APPRAISAL: Activation date</field>
|
||||
<field name="model_id" ref="hr.model_employee_appraisal"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.employee_appraisal_activation_date_update()</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="nextcall" eval="(DateTime.now().replace(hour=1, minute=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<odoo>
|
||||
<data>
|
||||
|
||||
|
||||
<record id="first_course_item" model="rating.name">
|
||||
<field name="name">Productivity</field>
|
||||
</record>
|
||||
|
||||
<record id="second_course_item" model="rating.name">
|
||||
<field name="name">Dependability</field>
|
||||
|
||||
</record>
|
||||
<record id="third_course_item" model="rating.name">
|
||||
<field name="name">Interpersonal Relationships</field>
|
||||
</record>
|
||||
|
||||
<record id="fourth_course_item" model="rating.name">
|
||||
<field name="name">Initiative</field>
|
||||
</record>
|
||||
|
||||
<record id="fifth_course_item" model="rating.name">
|
||||
<field name="name">Knowledge of Job</field>
|
||||
</record>
|
||||
|
||||
<record id="sixth_course_item" model="rating.name">
|
||||
<field name="name">Self Organizing and Time Management</field>
|
||||
</record>
|
||||
|
||||
<record id="seventh_course_item" model="rating.name">
|
||||
<field name="name">Punctuality/Leave Management</field>
|
||||
</record>
|
||||
|
||||
<record id="eighth_course_item" model="rating.name">
|
||||
<field name="name">Quality of Deliverables</field>
|
||||
</record>
|
||||
<record id="ningth_course_item" model="rating.name">
|
||||
<field name="name">Active participation in Company Events/Programs</field>
|
||||
</record>
|
||||
<record id="ningth_course_item" model="rating.name">
|
||||
<field name="name">Adherence to the Policies</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
<!-- Employee Email Template -->
|
||||
<record id="employee_email_template" model="mail.template">
|
||||
<field name="name">PEP-Employee</field>
|
||||
<field name="email_from">{{ object.name.sudo().parent_id.work_email }}</field>
|
||||
<field name="subject">Goal Setting Notification</field>
|
||||
<field name="email_to">{{ object.name.sudo().work_email }}</field>
|
||||
<field name="email_cc">{{ object.appraisal_hr_id.sudo().email }}</field>
|
||||
<field name="model_id" ref="hr_employee_appraisal.model_employee_appraisal"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html" type="html">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Dear <t t-out="object.name.sudo().name or 'Employee'">Employee</t>,
|
||||
|
||||
<br /><br />
|
||||
You have received a notification from
|
||||
<t t-if="object.name.sudo().parent_id.name">
|
||||
your manager, <strong t-out="object.name.sudo().parent_id.name or ''">Manager</strong>,
|
||||
</t>
|
||||
<t t-else="">
|
||||
your manager
|
||||
</t>
|
||||
regarding goal setting for FY
|
||||
<t t-if="object.appraisal_period_id.name">
|
||||
<t t-out="object.appraisal_period_id.name or ''">Period</t>.
|
||||
</t>
|
||||
|
||||
<br /><br />
|
||||
Please <a href="{{ redirect_url }}">click here</a> to review and respond.
|
||||
|
||||
<br /><br />
|
||||
Thank you,
|
||||
<br />
|
||||
<t t-if="object.name.sudo().parent_id.name">
|
||||
<t t-out="object.name.sudo().parent_id.name or ''">Manager</t>
|
||||
</t>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="employee_email_submit_rating_template" model="mail.template">
|
||||
<field name="name">PEP-Employee</field>
|
||||
<field name="email_from">{{ object.name.sudo().parent_id.work_email }}</field>
|
||||
<field name="subject">Goal Setting Notification</field>
|
||||
<field name="email_to">{{ object.name.sudo().work_email }}</field>
|
||||
<field name="email_cc">{{ object.appraisal_hr_id.sudo().email }}</field>
|
||||
<field name="model_id" ref="hr_employee_appraisal.model_employee_appraisal"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html" type="html">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Dear <t t-out="object.name.sudo().name or 'Employee'">Employee</t>,
|
||||
|
||||
<br /><br />
|
||||
You have received a notification from
|
||||
<t t-if="object.name.sudo().parent_id.name">
|
||||
your manager, <strong t-out="object.name.sudo().parent_id.name or ''">Manager</strong>,
|
||||
</t>
|
||||
<t t-else="">
|
||||
your manager
|
||||
</t>
|
||||
regarding submitting the rating for FY
|
||||
<t t-if="object.appraisal_period_id.name">
|
||||
<t t-out="object.appraisal_period_id.name or ''">Period</t>.
|
||||
</t>
|
||||
|
||||
<br /><br />
|
||||
Please <a href="{{ redirect_url }}">click here</a> to review and respond.
|
||||
|
||||
<br /><br />
|
||||
Thank you,
|
||||
<br />
|
||||
<t t-if="object.name.sudo().parent_id.name">
|
||||
<t t-out="object.name.sudo().parent_id.name or ''">Manager</t>
|
||||
</t>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Manager Email Template -->
|
||||
|
||||
<record id="manager_targets_submitted_email_template" model="mail.template">
|
||||
<field name="name">PEP-Manager</field>
|
||||
<field name="email_from">{{ object.name.sudo().work_email }}</field>
|
||||
<field name="subject">Response to Goal Setting</field>
|
||||
<field name="email_to">{{ object.name.sudo().parent_id.work_email }}</field>
|
||||
<field name="email_cc">{{ object.appraisal_hr_id.sudo().email }}</field>
|
||||
<field name="model_id" ref="hr_employee_appraisal.model_employee_appraisal"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html" type="html">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Dear <t t-out="object.name.sudo().parent_id.name or 'Manager'">Manager</t>,
|
||||
|
||||
<br /><br />
|
||||
You have received a response from
|
||||
<t t-if="object.name.sudo().name">
|
||||
<strong t-out="object.name.sudo().name or ''">Employee</strong>
|
||||
</t>
|
||||
<t t-else="">
|
||||
the employee
|
||||
</t>
|
||||
regarding goal setting.
|
||||
|
||||
<br /><br />
|
||||
Please <a href="{{ redirect_url }}">click here</a> to review and take the necessary action.
|
||||
|
||||
<br /><br />
|
||||
Thank you,
|
||||
<br />
|
||||
<t t-out="object.name.sudo().name or 'Employee'">Employee</t>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="manager_email_template" model="mail.template">
|
||||
<field name="name">PEP-Manager</field>
|
||||
<field name="email_from">{{ object.name.sudo().work_email }}</field>
|
||||
<field name="subject">Response to rating Submission</field>
|
||||
<field name="email_to">{{ object.name.sudo().parent_id.work_email }}</field>
|
||||
<field name="email_cc">{{ object.appraisal_hr_id.sudo().email }}</field>
|
||||
<field name="model_id" ref="hr_employee_appraisal.model_employee_appraisal"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html" type="html">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Dear <t t-out="object.name.sudo().parent_id.name or 'Manager'">Manager</t>,
|
||||
|
||||
<br /><br />
|
||||
You have received a response from
|
||||
<t t-if="object.name.sudo().name">
|
||||
<strong t-out="object.name.sudo().name or ''">Employee</strong>
|
||||
</t>
|
||||
<t t-else="">
|
||||
the employee
|
||||
</t>
|
||||
regarding Rating Submission.
|
||||
|
||||
<br /><br />
|
||||
Please <a href="{{ redirect_url }}">click here</a> to review and take the necessary action.
|
||||
|
||||
<br /><br />
|
||||
Thank you,
|
||||
<br />
|
||||
<t t-out="object.name.sudo().name or 'Employee'">Employee</t>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- HR Email Template -->
|
||||
<record id="hr_email_template" model="mail.template">
|
||||
<field name="name">PEP-HR</field>
|
||||
<field name="email_from">{{ object.name.sudo().parent_id.work_email }}</field>
|
||||
<field name="subject">PEP Review Submission</field>
|
||||
<field name="email_to">{{ object.appraisal_hr_id.sudo().email }}</field>
|
||||
<field name="email_cc">{{ object.name.sudo().work_email }}</field>
|
||||
<field name="model_id" ref="hr_employee_appraisal.model_employee_appraisal"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html" type="html">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Dear <t t-out="object.appraisal_hr_id.sudo().name or 'HR'">HR</t>,
|
||||
|
||||
<br /><br />
|
||||
The PEP review for
|
||||
<t t-if="object.name.sudo().name">
|
||||
<strong t-out="object.name.sudo().name or ''">Employee</strong>
|
||||
</t>
|
||||
has been finalized and submitted for your review.
|
||||
|
||||
<br /><br />
|
||||
Please <a href="{{ redirect_url }}">click here</a> to proceed.
|
||||
|
||||
<br /><br />
|
||||
Thank you,
|
||||
<br />
|
||||
<t t-out="object.name.sudo().parent_id.name or 'Manager'">Manager</t>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- HR Reject Email Template -->
|
||||
<record id="hr_reject_email_template" model="mail.template">
|
||||
<field name="name">PEP-HR-Manager</field>
|
||||
<field name="email_from">{{ object.appraisal_hr_id.sudo().email }}</field>
|
||||
<field name="subject">Goal Setting Feedback</field>
|
||||
<field name="email_to">{{ object.name.sudo().parent_id.work_email }}</field>
|
||||
<field name="model_id" ref="hr_employee_appraisal.model_employee_appraisal"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html" type="html">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Dear <t t-out="object.name.sudo().parent_id.name or 'Manager'">Manager</t>,
|
||||
|
||||
<br /><br />
|
||||
You have received feedback from HR regarding goal setting.
|
||||
|
||||
<br /><br />
|
||||
Please <a href="{{ redirect_url }}">click here</a> to review and respond.
|
||||
|
||||
<br /><br />
|
||||
Thank you,
|
||||
<br />
|
||||
<t t-out="object.appraisal_hr_id.sudo().name or 'HR'">HR</t>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<!-- MD Email Template -->
|
||||
<record id="md_email_template" model="mail.template">
|
||||
<field name="name">PEP-MD</field>
|
||||
<field name="email_from">{{ object.appraisal_hr_id.sudo().email }}</field>
|
||||
<field name="subject">PEP Review Submitted</field>
|
||||
<field name="email_to">{{ object.appraisal_md_id.sudo().email }}</field>
|
||||
<field name="model_id" ref="hr_employee_appraisal.model_employee_appraisal"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html" type="html">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Dear <t t-out="object.appraisal_md_id.sudo().name or 'MD'">MD</t>,
|
||||
|
||||
<br /><br />
|
||||
The PEP review for
|
||||
<t t-if="object.name.sudo().name">
|
||||
<strong t-out="object.name.sudo().name or ''">Employee</strong>
|
||||
</t>
|
||||
has been finalized and submitted for your review.
|
||||
|
||||
<br /><br />
|
||||
Please <a href="{{ redirect_url }}">click here</a> to review and take action.
|
||||
|
||||
<br /><br />
|
||||
Thank you,
|
||||
<br />
|
||||
<t t-out="object.appraisal_hr_id.sudo().name or 'HR'">HR</t>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from . import apprisal_period_master
|
||||
from . import kpi_kra_master
|
||||
from . import employee_appraisal
|
||||
from . import res_config_settings
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# models.py
|
||||
from odoo import models, fields, api
|
||||
from datetime import datetime, timedelta
|
||||
import datetime
|
||||
|
||||
class AppraisalPeriod(models.Model):
|
||||
_name = 'appraisal.period'
|
||||
_description = 'Appraisal Period'
|
||||
_rec_name = 'name'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char('Appraisal Period', required=True, track_visibility='onchange', copy=True)
|
||||
date_from = fields.Date('Date From', track_visibility='onchange', copy=True)
|
||||
date_to = fields.Date('Date To', track_visibility='onchange', copy=True)
|
||||
active = fields.Boolean('Active', track_visibility='onchange', copy=True)
|
||||
activation_date = fields.Date('Activation Date', required=True)
|
||||
|
||||
@api.onchange('date_to')
|
||||
def onchange_activation_date(self):
|
||||
for rec in self:
|
||||
rec.activation_date = rec.date_to + timedelta(days=1)
|
||||
|
||||
_defaults = {
|
||||
'color_name': 'red',
|
||||
'active': True,
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,512 @@
|
|||
|
||||
from odoo import api, fields, models, _, tools
|
||||
from odoo.exceptions import UserError, AccessError, ValidationError
|
||||
|
||||
import datetime
|
||||
|
||||
from dateutil import relativedelta
|
||||
|
||||
class employee_appraisal(models.Model):
|
||||
_name = "employee.appraisal"
|
||||
_description = "Employee"
|
||||
|
||||
_inherit = ['mail.thread', ]
|
||||
|
||||
|
||||
def employee_appraisal_activation_date_update(self):
|
||||
appraisal_periods = self.env['appraisal.period'].sudo().search([('activation_date','<=',fields.Date.today())])
|
||||
employee_appraisals = self.env['employee.appraisal'].sudo().search([('appraisal_period_id','in',appraisal_periods.ids)])
|
||||
for appraisal in employee_appraisals:
|
||||
if appraisal.state == 'sent':
|
||||
appraisal.button_send_employee()
|
||||
# @api.depends('employee_rating_id.self_rating', 'employee_rating_id.manager_rating')
|
||||
# def _avg_jr_rating(self):
|
||||
# """
|
||||
# Compute the total amounts of the SO.
|
||||
# """
|
||||
# for rec in self:
|
||||
# leng_rating = len(rec.employee_rating_id)
|
||||
# if leng_rating != 0:
|
||||
# for rating in rec:
|
||||
# emp_tot_rating = self_rating = manager_rating = 0
|
||||
# for line in rating.employee_rating_id:
|
||||
# self_rating += int(line.self_rating)
|
||||
# manager_rating += int(line.manager_rating)
|
||||
# # return self_rating
|
||||
# rating.update({
|
||||
# # 'self_rating':rating.round(self_rating),
|
||||
# # 'manager_rating':rating.round(manager_rating),
|
||||
# 'emp_avg_jr_rating': float(self_rating) / leng_rating,
|
||||
# 'mng_avg_jr_rating': float(manager_rating) / leng_rating
|
||||
# })
|
||||
# else:
|
||||
# rec.emp_avg_jr_rating = 0.0
|
||||
# rec.mng_avg_jr_rating = 0.0
|
||||
#
|
||||
@api.depends('employee_job_requirement_id.emp_rating', 'employee_job_requirement_id.man_rating',
|
||||
'employee_job_requirement_id.weightage')
|
||||
def _avg_kra_rating(self):
|
||||
"""
|
||||
Compute the weighted average ratings for Employee and Manager based on the weightage of each KRA.
|
||||
"""
|
||||
for rec in self:
|
||||
total_emp_rating = 0
|
||||
total_man_rating = 0
|
||||
total_weightage = 0
|
||||
|
||||
# Loop through each KRA
|
||||
for line in rec.employee_job_requirement_id:
|
||||
if line.weightage > 0: # Check if weightage is greater than 0
|
||||
total_emp_rating += int(line.emp_rating) * line.weightage
|
||||
total_man_rating += int(line.man_rating) * line.weightage
|
||||
total_weightage += line.weightage
|
||||
|
||||
# Avoid division by zero if the total weightage is zero
|
||||
if total_weightage != 0:
|
||||
rec.emp_avg_kra_rating = total_emp_rating / total_weightage
|
||||
rec.mng_avg_kra_rating = total_man_rating / total_weightage
|
||||
else:
|
||||
rec.emp_avg_kra_rating = 0.0
|
||||
rec.mng_avg_kra_rating = 0.0
|
||||
|
||||
# def _get_total(self, cr, uid, ids, field_names, args, context=None):
|
||||
# res = {}
|
||||
# for line in self.browse(cr, uid, ids, context=context):
|
||||
# res[line.id] = (line.mng_avg_jr_rating + line.mng_avg_kra_rating) / 2
|
||||
# return res
|
||||
#
|
||||
@api.depends('employee_job_requirement_id.weightage')
|
||||
def _tot_weightage(self):
|
||||
"""
|
||||
Compute the total amounts of the SO.
|
||||
"""
|
||||
for rec in self:
|
||||
weightage = 0
|
||||
for line in rec.employee_job_requirement_id:
|
||||
weightage += line.weightage
|
||||
rec.update({
|
||||
'total_weightage': weightage
|
||||
})
|
||||
|
||||
# we need a related field in order to be able to sort the employee by name
|
||||
name = fields.Many2one('hr.employee', 'Employee Name', track_visibility='onchange', copy=False)
|
||||
state = fields.Selection([('draft', 'Draft Appraisal'), ('to_emp', 'Send to Employee'),
|
||||
('sent', 'Sent to Manager'),
|
||||
('emp_rating','Sent for Employee Rating'),
|
||||
('manager_rating','Sent for Manager Rating'),
|
||||
('to_approve', 'Sent to HR'),
|
||||
('to_md', 'Sent to MD'),
|
||||
], default='draft', string='Status', readonly=True, index=True, copy=False,
|
||||
track_visibility='onchange')
|
||||
appraisal_period_id = fields.Many2one('appraisal.period', 'Appraisal Period',
|
||||
track_visibility='onchange', copy=False)
|
||||
location = fields.Char('Location', track_visibility='onchange', copy=True)
|
||||
designation = fields.Many2one('hr.job', 'Designation', track_visibility='onchange', copy=False, required=True)
|
||||
user_id = fields.Many2one('res.users', related='name.user_id', string='Users', store=True,
|
||||
track_visibility='onchange', copy=False)
|
||||
department_id = fields.Many2one('hr.department', 'Department', track_visibility='onchange', copy=False)
|
||||
reviewers_name = fields.Many2one('hr.employee', 'Reviewers Name', related="name.parent_id",store=True, track_visibility='onchange', copy=False)
|
||||
todays_date = fields.Date(string='Date', default=fields.Date.today(), track_visibility='onchange', copy=False)
|
||||
appraisal_active = fields.Boolean('Appraisal Active', copy=True)
|
||||
# emp_tot_rating = fields.Integer('Emp Tot Rating', track_visibility='onchange', copy=False)
|
||||
# emp_avg_jr_rating = fields.Float(string='Emp JR Average Rating', compute='_avg_jr_rating',
|
||||
# track_visibility='onchange', copy=False)
|
||||
# mng_avg_jr_rating = fields.Float(string='Manager JR Average Rating', compute='_avg_jr_rating',
|
||||
# track_visibility='onchange', copy=False)
|
||||
emp_avg_kra_rating = fields.Float(string='Employee KRA Average Rating', compute='_avg_kra_rating',
|
||||
track_visibility='onchange', copy=False)
|
||||
mng_avg_kra_rating = fields.Float(string='Manager KRA Average Rating', compute='_avg_kra_rating',
|
||||
track_visibility='onchange', copy=False)
|
||||
total_weightage = fields.Float(string='Total Weightage', compute='_tot_weightage', track_visibility='onchange',
|
||||
copy=False)
|
||||
comments_by_hr = fields.Char('Comments By HR', track_visibility='onchange', copy=False)
|
||||
comments_by_md = fields.Char('Comments By MD', track_visibility='onchange', copy=False)
|
||||
employee_comment_set = fields.Boolean('Employee comment set', track_visibility='onchange', copy=False)
|
||||
employee_rating_id = fields.One2many('employee.rating', 'appraisal_id', 'Appraisal', track_visibility='onchange',
|
||||
copy=True)
|
||||
# 'overall_rating':fields.function(_get_total, string='Overall Manager Rating', type='float', track_visibility='onchange'),
|
||||
overall_evaluation_of_performance = fields.Text('Overall Evaluation of Performance', track_visibility='onchange',
|
||||
copy=False)
|
||||
time_in_current_position = fields.Char('Time in current position', track_visibility='onchange', copy=False)
|
||||
time_with_company = fields.Char('Time with Company', track_visibility='onchange', copy=False)
|
||||
date = fields.Date('Date', track_visibility='onchange', copy=False)
|
||||
evaluation_list = fields.Selection([
|
||||
('far_exceeds_expectation', 'Far Exceeds Expectations'),
|
||||
('exceed_expectation', 'Exceed Expectations'),
|
||||
('met_expectation', 'Met Expectations'),
|
||||
('below_expectation', 'Below Expectations'),
|
||||
('never_met', 'Never Met'),
|
||||
], string='Overall Evaluation', index=True, copy=False, track_visibility='onchange')
|
||||
current_salary = fields.Float('Current Salary', copy=False)
|
||||
proposed_merit_increase = fields.Integer('Proposed Merit Increase (%)', copy=False)
|
||||
proposed_salary = fields.Float('Proposed Salary', copy=False)
|
||||
proposed_promotion_list = fields.Selection([
|
||||
('yes', 'Yes'),
|
||||
('no', 'No'),
|
||||
], string='Proposed Promotion', index=True, copy=False)
|
||||
proposed_designation = fields.Char('Proposed Designation', copy=False)
|
||||
employee_job_requirement_id = fields.One2many('employee.job.requirement', 'appraisal_id', 'Job Requirement',
|
||||
track_visibility='onchange', copy=True)
|
||||
appraisal_hr_id = fields.Many2one('res.users', string="HR Manager", compute='_compute_appraisal_hr_md')
|
||||
appraisal_md_id = fields.Many2one('res.users', string="Managing Director", compute='_compute_appraisal_hr_md')
|
||||
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_appraisal_hr_md(self):
|
||||
hr_id = self.env['ir.config_parameter'].sudo().get_param('hr_employee_appraisal.appraisal_hr_id')
|
||||
self.appraisal_hr_id = self.env['res.users'].sudo().browse(int(hr_id)) if hr_id else False
|
||||
md_id = self.env['ir.config_parameter'].sudo().get_param('hr_employee_appraisal.appraisal_md_id')
|
||||
self.appraisal_md_id = self.env['res.users'].sudo().browse(int(md_id)) if md_id else False
|
||||
|
||||
@api.constrains('total_weightage')
|
||||
def constrain_total_weightage(self):
|
||||
for record in self:
|
||||
if record.total_weightage == 100:
|
||||
return True
|
||||
else:
|
||||
raise UserError(_("Error: Total Weightage should be equal to 100"))
|
||||
return True
|
||||
|
||||
# @api.model
|
||||
# def default_get(self, default_fields):
|
||||
# res = {}
|
||||
# # srd = self.env['employee.rating']
|
||||
# # ids = []
|
||||
# # active_id = self._context.get('active_ids', [])
|
||||
# # items_list = self.env['kpi.kra'].search([('kpi_kra_type','in',['kpi','both'])])
|
||||
# # for item in items_list:
|
||||
# # result = {'appraisal_id': active_id, 'name': item.id, 'state': 'draft'}
|
||||
# # sr = srd.create(result)
|
||||
# # ids.append(sr.id)
|
||||
# # res['employee_rating_id'] = [(6, 0, ids)]
|
||||
# res['state'] = 'draft'
|
||||
# return res
|
||||
|
||||
@api.onchange('name')
|
||||
def onchange_employee_name(self):
|
||||
if self.name:
|
||||
# self.reviewers_name = self.name.parent_id.id
|
||||
self.department_id = self.name.department_id.id
|
||||
self.designation = self.name.job_id.id
|
||||
self.location = self.name.work_location_id.name
|
||||
|
||||
@api.onchange('designation')
|
||||
def onchange_designation_id(self):
|
||||
for rec in self:
|
||||
# Clear existing records
|
||||
rec.sudo().employee_job_requirement_id.unlink()
|
||||
|
||||
if rec.designation and rec.designation.kpi_kra_ids:
|
||||
employee_job_requirement_ids = []
|
||||
for kra in rec.designation.kpi_kra_ids:
|
||||
# Create employee.job.requirement record
|
||||
kra_data = {'appraisal_id': rec.id, 'kra_1': kra.id}
|
||||
kra_create = self.env['employee.job.requirement'].create(kra_data)
|
||||
|
||||
# Create employee.rating records linked to the created employee.job.requirement
|
||||
kpi_ids = []
|
||||
for kpi in kra.kpi_question_ids:
|
||||
kpi_data = {
|
||||
'job_requirement_id': kra_create.id, # Link to the created employee.job.requirement
|
||||
'appraisal_id': rec.id,
|
||||
'name': kpi.id,
|
||||
}
|
||||
kpi_create = self.env['employee.rating'].create(kpi_data)
|
||||
kpi_ids.append(kpi_create.id)
|
||||
|
||||
kra_create.write({'employee_rating_id': [(6, 0, kpi_ids)]})
|
||||
employee_job_requirement_ids.append(kra_create.id)
|
||||
|
||||
rec.employee_job_requirement_id = [(6, 0, employee_job_requirement_ids)]
|
||||
|
||||
def button_send_employee(self):
|
||||
for rec in self:
|
||||
if rec.state == 'draft' and self.env.user.id != rec.appraisal_hr_id.id:
|
||||
raise ValidationError(_("Only HR can send Appraisal to the employee"))
|
||||
if rec.state == 'sent' and self.env.user.id != rec.reviewers_name.user_id.id:
|
||||
raise ValidationError(_("Only Manager can send Appraisal to the employee in this stage"))
|
||||
|
||||
if rec.state=='draft':
|
||||
if self.total_weightage == 100:
|
||||
self.state = 'to_emp'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'to_emp'
|
||||
template = self.env.ref('hr_employee_appraisal.employee_email_template')
|
||||
self.env['mail.template'].browse(template.id).send_mail(self.id)
|
||||
return True
|
||||
else:
|
||||
raise UserError(_('Error: Total Weightage should be equal to 100'))
|
||||
elif rec.state == 'sent':
|
||||
rec.state = 'emp_rating'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'emp_rating'
|
||||
template = self.env.ref('hr_employee_appraisal.employee_email_submit_rating_template')
|
||||
self.env['mail.template'].browse(template.id).send_mail(self.id)
|
||||
return True
|
||||
|
||||
def button_refuse(self):
|
||||
if self.state == 'sent':
|
||||
self.state = 'to_emp'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'to_emp'
|
||||
elif self.state == 'manager_rating':
|
||||
self.state = 'emp_rating'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'emp_rating'
|
||||
elif self.state == 'to_approve':
|
||||
self.state = 'manager_rating'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'manager_rating'
|
||||
elif self.state == 'to_md':
|
||||
self.state = 'to_approve'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'to_approve'
|
||||
|
||||
# elif self.state=='to_approve':
|
||||
# self.state='to_emp'
|
||||
# for line in self.employee_rating_id:
|
||||
# line.state='to_emp'
|
||||
# for line in self.employee_job_requirement_id:
|
||||
# line.status='to_emp'
|
||||
|
||||
# if self.state ='to_emp':
|
||||
# state=manager_rating
|
||||
# refuse = manager_rating
|
||||
|
||||
# if self.state ='mgr':
|
||||
# statr=hr
|
||||
# refuse=hr
|
||||
|
||||
def button_send_manager(self):
|
||||
if self.env.user.id != self.name.user_id.id:
|
||||
raise ValidationError(_("Only the Employee can submit to manager"))
|
||||
for record in self.employee_job_requirement_id:
|
||||
for rec in record.employee_rating_id:
|
||||
if not rec.emp_kri_target:
|
||||
raise ValidationError(_(f"Please provide your targets before submitting to Manager ({record.kra_1.name})"))
|
||||
if not record.emp_kra_target:
|
||||
raise ValidationError(_("Please provide your targets before submitting to Manager"))
|
||||
for rec in self:
|
||||
if rec.state == 'to_emp':
|
||||
rec.state = 'sent'
|
||||
template = self.env.ref('hr_employee_appraisal.manager_targets_submitted_email_template')
|
||||
self.env['mail.template'].browse(template.id).send_mail(self.id)
|
||||
# When the state is 'emp_rating', we need to ensure all fields are filled
|
||||
if rec.state == 'emp_rating':
|
||||
for record in rec.employee_job_requirement_id:
|
||||
# Check if employee has filled the rating and comments in the job requirement (KRA)
|
||||
if not record.emp_rating:
|
||||
raise UserError(_('Please fill Employee Rating in the KRA: {}').format(record.kra_1.name))
|
||||
if not record.sop:
|
||||
raise UserError(_('Please fill Standards of Performance (SOP) in the KRA: {}').format(
|
||||
record.kra_1.name))
|
||||
|
||||
for rating in record.employee_rating_id: # Iterate through related employee ratings
|
||||
# Check if self-rating and employee comments are filled in the ratings
|
||||
if not rating.self_rating:
|
||||
raise UserError(_(f'Please fill your Self Rating for KPI: {rating.name.name}'))
|
||||
if not rating.employee_comments:
|
||||
raise UserError(_('Please fill your Employee Comments for KPI: {}').format(rating.name.name))
|
||||
|
||||
# After all checks, change the state to 'manager_rating'
|
||||
self.state = 'manager_rating'
|
||||
|
||||
# Update the status for all job requirements to 'manager_rating'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'manager_rating'
|
||||
|
||||
# Send email to manager using the template
|
||||
template = self.env.ref('hr_employee_appraisal.manager_email_template')
|
||||
self.env['mail.template'].browse(template.id).send_mail(self.id)
|
||||
|
||||
return True
|
||||
|
||||
def button_send_hr(self):
|
||||
# Ensure only the manager can send the appraisal to HR
|
||||
if self.env.user.id != self.reviewers_name.user_id.id:
|
||||
raise ValidationError(_("Only Manager can send to HR"))
|
||||
|
||||
# Check if all manager comments and ratings are filled in the employee ratings and job requirements
|
||||
for record in self.employee_job_requirement_id:
|
||||
for rating in record.employee_rating_id:
|
||||
if not rating.manager_comments:
|
||||
raise UserError(_(f'Please fill Manager Comments for KPI: {rating.name.name}'))
|
||||
if not rating.manager_rating:
|
||||
raise UserError(_('Please fill Manager Rating for KPI: {}').format(rating.name.name))
|
||||
|
||||
if not record.man_rating:
|
||||
raise UserError(_('Please fill Manager Rating in the KRA: {}').format(record.kra_1.name))
|
||||
if not record.results:
|
||||
raise UserError(_('Please fill Results Achieved (Manager) for KRA: {}').format(record.kra_1.name))
|
||||
|
||||
# Change the state to 'to_approve' and update status for all ratings and job requirements
|
||||
self.state = 'to_approve'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'to_approve'
|
||||
|
||||
# Send an email to HR
|
||||
template = self.env.ref('hr_employee_appraisal.hr_email_template')
|
||||
self.env['mail.template'].browse(template.id).send_mail(self.id)
|
||||
|
||||
return True
|
||||
|
||||
def button_send_md(self):
|
||||
self.state = 'to_md'
|
||||
for line in self.employee_rating_id:
|
||||
line.state = 'to_md'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'to_md'
|
||||
|
||||
def button_back(self):
|
||||
if self.state == 'to_approve':
|
||||
self.state = 'sent'
|
||||
for line in self.employee_rating_id:
|
||||
line.state = 'sent'
|
||||
for line in self.employee_job_requirement_id:
|
||||
line.status = 'sent'
|
||||
template = self.env.ref('hr_employee_appraisal.hr_reject_email_template')
|
||||
self.env['mail.template'].browse(template.id).send_mail(self.id)
|
||||
|
||||
@api.onchange('current_salary', 'proposed_merit_increase')
|
||||
def onchange_proposed_salary(self):
|
||||
pro_merit = float(self.proposed_merit_increase)
|
||||
if self.current_salary or pro_merit:
|
||||
self.proposed_salary = self.current_salary + (self.current_salary * (pro_merit / 100))
|
||||
|
||||
|
||||
@api.onchange('date')
|
||||
def onchange_time_in_current_position(self):
|
||||
# pdb.set_trace()
|
||||
if self.date:
|
||||
|
||||
fmt = '%Y-%m-%d'
|
||||
if self.name.doj:
|
||||
d1 = self.name.doj
|
||||
d2 = self.date
|
||||
Diff = relativedelta.relativedelta(d2, d1)
|
||||
self.time_in_current_position = str(Diff.years) + " Years " + str(Diff.months) + " Months " + str(
|
||||
Diff.days) + " Days "
|
||||
|
||||
|
||||
class employee_rating(models.Model):
|
||||
_name = "employee.rating"
|
||||
_description = "Part A"
|
||||
_inherit = ['mail.thread', ]
|
||||
|
||||
|
||||
|
||||
state = fields.Selection([('draft', 'Draft Appraisal'), ('to_emp', 'Send to Employee'),
|
||||
('sent', 'Sent to Manager'),
|
||||
('emp_rating','Sent for Employee Rating'),
|
||||
('manager_rating','Sent for Manager Rating'),
|
||||
('to_approve', 'Sent to HR'),
|
||||
('to_md', 'Sent to MD'), ],
|
||||
string='Status', related='job_requirement_id.status', track_visibility='onchange', copy=False)
|
||||
appr_active_rel = fields.Boolean(related='appraisal_id.appraisal_active', string='Appraisal Active Related',
|
||||
store=True, copy=False)
|
||||
name = fields.Many2one('kpi.question', 'KPIs', track_visibility='onchange', copy=True)
|
||||
emp_kri_target = fields.Text('Employee Target')
|
||||
self_rating = fields.Selection([(str(num), str(num)) for num in range(1, 6)],'Self Rating', track_visibility='onchange', copy=False)
|
||||
employee_comments = fields.Char('Employee Comments', track_visibility='onchange', copy=False)
|
||||
manager_rating = fields.Selection([(str(num), str(num)) for num in range(1, 6)],'Manager Rating', track_visibility='onchange', copy=False)
|
||||
manager_comments = fields.Char('Manager Comments', track_visibility='onchange', copy=False)
|
||||
appraisal_id = fields.Many2one('employee.appraisal', 'Appraisal Id', related='job_requirement_id.appraisal_id',track_visibility='onchange', copy=True)
|
||||
job_requirement_id = fields.Many2one('employee.job.requirement','Requirement Id', track_visibility='onchange', copy=True)
|
||||
|
||||
|
||||
@api.constrains('self_rating')
|
||||
def _self_rating(self):
|
||||
record = self
|
||||
if int(record.self_rating) <= 5 and int(record.self_rating) >= 1:
|
||||
return True
|
||||
else:
|
||||
raise UserError(_('Error: Invalid JR Employee Rating'))
|
||||
return True
|
||||
|
||||
@api.constrains('manager_rating')
|
||||
def _manager_rating(self):
|
||||
record = self
|
||||
|
||||
if int(record.manager_rating) <= 5 and int(record.manager_rating) >= 1:
|
||||
return True
|
||||
else:
|
||||
raise UserError(_('Error: Invalid JR Manager Rating'))
|
||||
return True
|
||||
|
||||
|
||||
|
||||
class employee_job_requirement(models.Model):
|
||||
_name = "employee.job.requirement"
|
||||
_description = "Part B"
|
||||
|
||||
@api.depends('employee_rating_id.self_rating', 'employee_rating_id.manager_rating')
|
||||
def _avg_jr_rating(self):
|
||||
"""
|
||||
Compute the total amounts of the SO.
|
||||
"""
|
||||
for rec in self:
|
||||
leng_rating = len(rec.employee_rating_id)
|
||||
if leng_rating != 0:
|
||||
for rating in rec:
|
||||
emp_tot_rating = self_rating = manager_rating = 0
|
||||
for line in rating.employee_rating_id:
|
||||
self_rating += int(line.self_rating)
|
||||
manager_rating += int(line.manager_rating)
|
||||
# return self_rating
|
||||
rating.update({
|
||||
# 'self_rating':rating.round(self_rating),
|
||||
# 'manager_rating':rating.round(manager_rating),
|
||||
'emp_avg_jr_rating': float(self_rating) / leng_rating,
|
||||
'mng_avg_jr_rating': float(manager_rating) / leng_rating
|
||||
})
|
||||
else:
|
||||
rec.emp_avg_jr_rating = 0.0
|
||||
rec.mng_avg_jr_rating = 0.0
|
||||
|
||||
status = fields.Selection(
|
||||
[('draft', 'Draft Appraisal'), ('to_emp', 'Send to Employee'),
|
||||
('sent', 'Sent to Manager'),
|
||||
('emp_rating','Sent for Employee Rating'),
|
||||
('manager_rating','Sent for Manager Rating'),
|
||||
('to_approve', 'Sent to HR'),
|
||||
('to_md', 'Sent to MD'),],
|
||||
string='Status',related='appraisal_id.state',track_visibility='onchange', copy=False)
|
||||
|
||||
appr_active_kra_rel = fields.Boolean(related='appraisal_id.appraisal_active',
|
||||
string='Appraisal Active KRA Related', store=True, copy=False)
|
||||
kra_type = fields.Many2one('kra.types',related='kra_1.kra_type', string='KRA Type', index=True, track_visibility='onchange', copy=True)
|
||||
kra_1 = fields.Many2one('kpi.kra','Job Requirements(KRA)',
|
||||
help="(List each major job requirement and describe the key responsibilities of the function)",
|
||||
track_visibility='onchange', copy=True)
|
||||
emp_kra_target = fields.Text('Target', track_visibility='onchange', copy=True)
|
||||
weightage = fields.Integer('Weightage', track_visibility='onchange', copy=False)
|
||||
emp_rating = fields.Selection([(str(num), str(num)) for num in range(1, 6)],string='Employee Rating (Rating between 1-5)', help="(Rating should be between 1 and 5)",
|
||||
track_visibility='onchange', copy=False)
|
||||
sop = fields.Text('Standards of Performance (Employee)',
|
||||
help="(Indicate the quality of work that you have exhibited against the Key Roles that are delegated to you)",
|
||||
track_visibility='onchange', copy=False)
|
||||
man_rating = fields.Selection([(str(num), str(num)) for num in range(1, 6)],'Manager Rating (Rating between 1-5)', help="(Rating should be between 1 and 5)",
|
||||
track_visibility='onchange', copy=False)
|
||||
results = fields.Text('Results Achieved (Manager)',
|
||||
help="(Describe the extent to which the employee has met the standards of performance expected for each major job function.)",
|
||||
track_visibility='onchange', copy=False)
|
||||
appraisal_id = fields.Many2one('employee.appraisal', 'Job Requirement Id', track_visibility='onchange',
|
||||
copy=True)
|
||||
employee_rating_id = fields.One2many('employee.rating', 'job_requirement_id', 'KPIs', track_visibility='onchange',
|
||||
copy=True)
|
||||
|
||||
|
||||
emp_avg_jr_rating = fields.Float(string='Emp JR Average Rating', compute='_avg_jr_rating',
|
||||
track_visibility='onchange', copy=False)
|
||||
mng_avg_jr_rating = fields.Float(string='Manager JR Average Rating', compute='_avg_jr_rating',
|
||||
track_visibility='onchange', copy=False)
|
||||
|
||||
@api.constrains('man_rating')
|
||||
def _weightage(self):
|
||||
# pdb.set_trace()
|
||||
for record in self:
|
||||
if record.weightage != 0:
|
||||
return True
|
||||
else:
|
||||
raise UserError(_('Error: Weightage should not be 0'))
|
||||
return {}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
from odoo import models, fields
|
||||
|
||||
|
||||
class kraTypes(models.Model):
|
||||
_name = 'kra.types'
|
||||
_description = 'KRA Types'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char('Name')
|
||||
|
||||
|
||||
class KpiQuestion(models.Model):
|
||||
_name = 'kpi.question'
|
||||
_description = 'KPI Questions'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char(string='Question', required=True)
|
||||
kra_id = fields.Many2one('kpi.kra', string='KRA', ondelete='cascade', required=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('name_kra_unique', 'unique(name, kra_id)', 'The combination of question name and KRA should be unique.')
|
||||
]
|
||||
|
||||
|
||||
class KpiKra(models.Model):
|
||||
_name = 'kpi.kra'
|
||||
_description = 'Master Record for KPIs and KRAs'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char(string='KRA Name', required=True)
|
||||
kra_type = fields.Many2one('kra.types',string="Type")
|
||||
# One2many field to link multiple KPI questions to each KRA
|
||||
kpi_question_ids = fields.One2many(
|
||||
'kpi.question', # Related model
|
||||
'kra_id', # Field in kpi.question model
|
||||
string='KPI Questions'
|
||||
)
|
||||
job_id = fields.Many2one('hr.job')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_job_id_unique', 'unique(name, job_id)', 'The combination of question name and Job Position should be unique.')
|
||||
]
|
||||
|
||||
|
||||
class HrJob(models.Model):
|
||||
_inherit = 'hr.job' # Inherit the hr.job model
|
||||
|
||||
# One2many field to link multiple KRAs to each job role
|
||||
kpi_kra_ids = fields.One2many(
|
||||
'kpi.kra', # Related model (KpiKra)
|
||||
'job_id', # Field in kpi.kra model (you'll add this field to the KpiKra model)
|
||||
string='KRAs'
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api, _
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
appraisal_hr_id = fields.Many2one('res.users',config_parameter='hr_employee_appraisal.appraisal_hr_id', string='Appraisal HR',
|
||||
domain=lambda self: [
|
||||
('groups_id', 'in', self.env.ref('hr_employee_appraisal.group_appraisal_manager').id)])
|
||||
|
||||
appraisal_md_id = fields.Many2one('res.users', config_parameter='hr_employee_appraisal.appraisal_md_id',
|
||||
string='Appraisal MD',
|
||||
domain=lambda self: [
|
||||
('groups_id', 'in',
|
||||
self.env.ref('hr_employee_appraisal.group_appraisal_administrator').id)])
|
||||
|
||||
# requisition_md_id = fields.Many2one('res.users', string='Requisition MD')
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
model_appraisal_period_manager_access,appraisal.period manager access,model_appraisal_period,group_appraisal_manager,1,1,1,1
|
||||
model_appraisal_period_user_access,appraisal.period user access,model_appraisal_period,base.group_user,1,0,0,0
|
||||
|
||||
access_kra_types_user,kra.types.user,model_kra_types,base.group_user,1,0,0,0
|
||||
access_kra_types_manager,kra.types.manager,model_kra_types,group_appraisal_manager,1,1,1,1
|
||||
|
||||
|
||||
access_kpi_kra_user,kpi.kra user,model_kpi_kra,base.group_user,1,0,0,0
|
||||
access_kpi_kra_manager,kpi.kra manager,model_kpi_kra,group_appraisal_manager,1,1,1,1
|
||||
|
||||
access_kpi_question_user,kpi.question.user,model_kpi_question,base.group_user,1,0,0,0
|
||||
access_kpi_question_manager,kpi.question.manager,model_kpi_question,group_appraisal_manager,1,1,1,1
|
||||
|
||||
access_employee_appraisal_user,employee.appraisal user,model_employee_appraisal,base.group_user,1,1,0,0
|
||||
access_employee_appraisal_manager,employee.appraisal manager,model_employee_appraisal,group_appraisal_manager,1,1,1,1
|
||||
access_employee_appraisal_officer,employee.appraisal officer,model_employee_appraisal,group_appraisal_officer,1,1,0,0
|
||||
|
||||
access_rating_user,employee.rating,model_employee_rating,base.group_user,1,1,0,0
|
||||
access_rating_hr_user,employee.rating.hr,model_employee_rating,group_appraisal_manager,1,1,1,1
|
||||
access_rating_officer,employee.rating.officer,model_employee_rating,group_appraisal_officer,1,1,0,0
|
||||
|
||||
access_job_req_user,employee.job.requirement,model_employee_job_requirement,base.group_user,1,1,0,0
|
||||
access_job_req_hr_user,employee.job.requirement.hr,model_employee_job_requirement,group_appraisal_manager,1,1,1,1
|
||||
access_job_req_officer,employee.job.requirement.officer,model_employee_job_requirement,group_appraisal_officer,1,1,0,0
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<!-- security.xml -->
|
||||
<odoo>
|
||||
<!-- security.xml -->
|
||||
<odoo>
|
||||
|
||||
<record id="category_employee_appraisal" model="ir.module.category">
|
||||
<field name="name">Appraisal</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- Define the user groups -->
|
||||
<record id="group_appraisal_officer" model="res.groups">
|
||||
<field name="name">Appraisal Officer</field>
|
||||
<field name="category_id" ref="category_employee_appraisal"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="group_appraisal_manager" model="res.groups">
|
||||
<field name="name">Appraisal HR Manager</field>
|
||||
<field name="category_id" ref="category_employee_appraisal"/>
|
||||
<field name="implied_ids" eval="[(4, ref('group_appraisal_officer'))]"/> <!-- Inherit Appraisal User permissions -->
|
||||
</record>
|
||||
|
||||
<record id="group_appraisal_administrator" model="res.groups">
|
||||
<field name="name">Appraisal Administrator</field>
|
||||
<field name="category_id" ref="category_employee_appraisal"/>
|
||||
<field name="implied_ids" eval="[(4, ref('group_appraisal_officer'))]"/> <!-- Inherit Appraisal User permissions -->
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<record id="employee_appraisal_base_user_rule" model="ir.rule">
|
||||
<field name="name">User can only see his/her own appraisals</field>
|
||||
<field name="model_id" ref="model_employee_appraisal"/>
|
||||
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="domain_force">[('user_id','=',user.id),('state','!=','draft')]</field>
|
||||
</record>
|
||||
<record id="employee_appraisal_officer_rule" model="ir.rule">
|
||||
<field name="name">User can only see the records of people under him/her</field>
|
||||
<field name="model_id" ref="model_employee_appraisal"/>
|
||||
<field name="groups" eval="[(4, ref('group_appraisal_officer'))]"/>
|
||||
<field name="domain_force">[('reviewers_name.user_id','=',user.id),('state','!=','draft')]</field>
|
||||
</record>
|
||||
|
||||
<record id="employee_appraisal_manager_rule" model="ir.rule">
|
||||
<field name="name">User can only see the all the appraisal records where he/she is set as HR</field>
|
||||
<field name="model_id" ref="model_employee_appraisal"/>
|
||||
<field name="groups" eval="[(4, ref('group_appraisal_manager'))]"/>
|
||||
<field name="domain_force">[('appraisal_hr_id','=',user.id)]</field>
|
||||
</record>
|
||||
<record id="employee_appraisal_md_rule" model="ir.rule">
|
||||
<field name="name">User can only see the all the appraisal records where he/she is set as MD</field>
|
||||
<field name="model_id" ref="model_employee_appraisal"/>
|
||||
<field name="groups" eval="[(4, ref('group_appraisal_administrator'))]"/>
|
||||
<field name="domain_force">[('appraisal_md_id','=',user.id)]</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
</odoo>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- views.xml -->
|
||||
<odoo>
|
||||
<record id="view_appraisal_period_form" model="ir.ui.view">
|
||||
<field name="name">appraisal.period.form</field>
|
||||
<field name="model">appraisal.period</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Period Type">
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" required="True"/>
|
||||
<field name="date_from" required="True"/>
|
||||
<field name="date_to" required="True"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="active"/>
|
||||
<field name="activation_date"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- <div class="oe_chatter">-->
|
||||
<!-- <field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>-->
|
||||
<!-- <field name="message_ids" widget="mail_thread"/>-->
|
||||
<!-- </div>-->
|
||||
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_appraisal_period_list" model="ir.ui.view">
|
||||
<field name="name">appraisal.period.list</field>
|
||||
<field name="model">appraisal.period</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
<field name="activation_date"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_appraisal_period" model="ir.actions.act_window">
|
||||
<field name="name">Appraisal Period</field>
|
||||
<field name="res_model">appraisal.period</field>
|
||||
<field name="view_mode">list,form</field> <!-- The view modes: list view and form view -->
|
||||
</record>
|
||||
|
||||
<!-- Create the parent menu (Appraisal) -->
|
||||
<menuitem id="menu_appraisal" name="Appraisal" sequence="10" web_icon="hr_employee_appraisal,static/description/icon.png"/>
|
||||
|
||||
<!-- Create the 'Appraisal' sub-menu under 'Configurations' -->
|
||||
<menuitem id="menu_configurations" name="Configurations" parent="hr_employee_appraisal.menu_appraisal" sequence="20" />
|
||||
|
||||
<!-- Create the 'Appraisal Period' menu under the 'Appraisal' menu -->
|
||||
<menuitem id="menu_config_appraisal_period" name="Appraisal Period"
|
||||
parent="hr_employee_appraisal.menu_configurations" action="hr_employee_appraisal.action_appraisal_period"
|
||||
groups="hr_employee_appraisal.group_appraisal_manager"/>
|
||||
</odoo>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_employee_appraisal_form" model="ir.ui.view">
|
||||
<field name="name">employee.appraisal.form</field>
|
||||
<field name="model">employee.appraisal</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee">
|
||||
<header>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,empsent,to_approve,to_md"/>
|
||||
<button name="button_send_employee" type="object" string="Send to Employee"
|
||||
invisible="state not in ['draft','sent']" groups="hr_employee_appraisal.group_appraisal_manager,hr_employee_appraisal.group_appraisal_officer"/>
|
||||
<button name="button_send_manager" type="object" invisible="state not in ['to_emp','emp_rating']" string="Send to Manager"/>
|
||||
<button name="button_send_hr" type="object" invisible="state != 'manager_rating'" string="Send to HR"
|
||||
groups="hr_employee_appraisal.group_appraisal_officer" />
|
||||
<button name="button_send_md" type="object" invisible="state != 'to_approve'" string="Send to MD"
|
||||
groups="hr.group_hr_manager"/>
|
||||
|
||||
<button name="button_refuse" type="object" string="Refuse" groups="hr_employee_appraisal.group_appraisal_officer"
|
||||
invisible="state not in ['sent', 'manager_rating']"/>
|
||||
|
||||
<button name="button_refuse" type="object" string="Refuse"
|
||||
groups="hr_employee_appraisal.group_appraisal_manager,hr_employee_appraisal.group_appraisal_administrator"
|
||||
invisible="state != 'to_approve'"/>
|
||||
</header>
|
||||
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="appraisal_period_id" required="state == 'draft'" readonly="state != 'draft'"/>
|
||||
<field name="name" invisible="1"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" required="state == 'draft'" readonly="state != 'draft'"/>
|
||||
<field name="location" required="state == 'draft'" readonly="state != 'draft'"/>
|
||||
<field name="designation" required="state == 'draft'" readonly="state != 'draft'"/>
|
||||
<field name="department_id" required="state == 'draft'" readonly="state != 'draft'"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="reviewers_name" required="state == 'draft'" readonly="state != 'draft'"/>
|
||||
<field name="todays_date" required="state == 'draft'" readonly="state != 'draft'"/>
|
||||
<field name="appraisal_active" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<table style="width: 100%; height: 100%; margin-left: auto; margin-right: auto;" border="1"
|
||||
cellpadding="2">
|
||||
<caption>
|
||||
<B>
|
||||
<U>Rating Guide</U>
|
||||
</B>
|
||||
</caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align: center;">5</td>
|
||||
<td>Far Exceeds Expectations</td>
|
||||
<td>Consistently exceeds all requirements; superior performance</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: center;">4</td>
|
||||
<td>Exceeds Expectations</td>
|
||||
<td>Generally exceeds requirements with a minimum of guidance; Well Above Average
|
||||
Performance
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: center;">3</td>
|
||||
<td>Met Expectations</td>
|
||||
<td>Responsibilities met in a wholly satisfactory manner; normal guidance and
|
||||
supervision required
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: center;">2</td>
|
||||
<td>Below Expectations</td>
|
||||
<td>Improvement needed in some key job areas; considerable guidance and supervision
|
||||
required
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: center;">1</td>
|
||||
<td>Never Met</td>
|
||||
<td>Major shortcomings in performance; performance improvement plan required to
|
||||
improve performance within a set time frame
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page name="part_a" string="Part A">
|
||||
<field name="employee_job_requirement_id">
|
||||
<form>
|
||||
<group>
|
||||
<field name="status" invisible="1"/>
|
||||
<field name="appr_active_kra_rel" invisible="1"/>
|
||||
<field name="kra_type" required="status == 'draft'"
|
||||
readonly="status != 'draft'"/>
|
||||
<field name="kra_1" required="status == 'draft'" readonly="status != 'draft'"/>
|
||||
<field name="weightage" readonly="status != 'draft'"/>
|
||||
<field name="emp_kra_target" required="status != 'draft'"
|
||||
readonly="status != 'to_emp'"/>
|
||||
|
||||
<field name="emp_rating" required="status == 'emp_rating'"
|
||||
readonly="status != 'emp_rating'"/>
|
||||
<field name="sop" required="status == 'emp_rating'" readonly="status != 'emp_rating'"/>
|
||||
<field name="man_rating" required="status == 'manager_rating'"
|
||||
readonly="status != 'manager_rating'"/>
|
||||
<field name="results" required="status == 'manager_rating'" readonly="status != 'manager_rating'"/>
|
||||
|
||||
<field name="employee_rating_id" nolabel="1">
|
||||
<list editable="bottom">
|
||||
<field name="state" column_invisible="True"/>
|
||||
<field name="name"
|
||||
readonly="state != 'draft'"/>
|
||||
<field name="emp_kri_target" readonly="state != 'to_emp'"/>
|
||||
<field name="appr_active_rel" column_invisible="True"/>
|
||||
<field name="self_rating" style="text-align:center;"
|
||||
required="state == 'emp_rating'"
|
||||
readonly="state != 'emp_rating'" column_invisible="parent.status not in ['emp_rating','manager_rating','to_approve','to_md']"/>
|
||||
<field name="employee_comments" required="state == 'emp_rating'"
|
||||
readonly="state != 'emp_rating'" column_invisible="parent.status not in ['emp_rating','manager_rating','to_approve','to_md']"/>
|
||||
<field name="manager_rating" required="state == 'manager_rating'"
|
||||
readonly="state != 'manager_rating'" column_invisible="parent.status not in ['emp_rating','manager_rating','to_approve','to_md']"/>
|
||||
<field name="manager_comments" required="state == 'manager_rating'"
|
||||
readonly="state != 'manager_rating'" column_invisible="parent.status not in ['emp_rating','manager_rating','to_approve','to_md']"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
<group>
|
||||
<field name="emp_avg_jr_rating" readonly="1"/>
|
||||
<field name="mng_avg_jr_rating" readonly="1"/>
|
||||
</group>
|
||||
</form>
|
||||
|
||||
<list>
|
||||
<field name="kra_type"/>
|
||||
<field name="kra_1"/>
|
||||
<field name="weightage"/>
|
||||
<field name="emp_kra_target"/>
|
||||
<field name="emp_rating" column_invisible="parent.state not in ['emp_rating','manager_rating','to_approve','to_md']"/>
|
||||
<field name="man_rating" column_invisible="parent.state not in ['emp_rating','manager_rating','to_approve','to_md']"/>
|
||||
<field name="sop" column_invisible="parent.state not in ['emp_rating','manager_rating','to_approve','to_md']"/>
|
||||
<field name="results" column_invisible="parent.state not in ['emp_rating','manager_rating','to_approve','to_md']"/>
|
||||
</list>
|
||||
</field>
|
||||
<group>
|
||||
<field name="total_weightage" readonly="True"/>
|
||||
<field name="emp_avg_kra_rating" readonly="True"/>
|
||||
<field name="mng_avg_kra_rating" readonly="True"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<!-- <field name="overall_rating" attrs="{'required':[('state','in',['sent'])],'invisible': ['|', ('state','in', ('draft','to_emp',))], 'readonly':[('state','not in',('sent','to_approve','to_md'))]}"/> -->
|
||||
<field name="overall_evaluation_of_performance" required="state == 'manager_rating'" invisible="state in ['draft','to_emp','sent','emp_rating']" readonly="state in ['to_approve','to_md']"/>
|
||||
<field name="comments_by_hr" invisible="state not in ['to_approve']" readonly="state == 'to_md'" required="state == 'to_approve'"/>
|
||||
<field name="comments_by_md" invisible="state not in ['to_md']"/>
|
||||
<field name="employee_comment_set" invisible="True"/>
|
||||
</group>
|
||||
</page>
|
||||
<page name="salary_adjustment_request" string="Salary Adjustment Request" groups="hr_employee_appraisal.group_appraisal_manager, hr_employee_appraisal.group_appraisal_administrator">
|
||||
<group>
|
||||
<field name="date" required = "state == 'to_approve'"/>
|
||||
<field name="time_in_current_position"/>
|
||||
<field name="time_with_company" readonly="1"/>
|
||||
<field name="evaluation_list" required = "state == 'to_approve'"/>
|
||||
</group>
|
||||
<group string="SALARY INFORMATION">
|
||||
<field name="proposed_merit_increase"/>
|
||||
<field name="current_salary" />
|
||||
</group>
|
||||
<group string="SALARY ADJUSTMENT PROPOSAL">
|
||||
|
||||
<field name="proposed_salary" />
|
||||
<field name="proposed_promotion_list"/>
|
||||
<field name="proposed_designation"/>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_appraisal_search" model="ir.ui.view">
|
||||
<field name="name">employee.appraisal.search</field>
|
||||
<field name="model">employee.appraisal</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Employee Appraisals">
|
||||
<filter string="Group by State" name="group_by_state" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Group by Department" name="group_by_department" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Group by Period" name="group_by_period" domain="[]" context="{'group_by':'appraisal_period_id'}"/>
|
||||
<filter string="Group by Employee" name="group_by_employee" domain="[]" context="{'group_by':'name'}"/>
|
||||
<!-- Add other filters if necessary -->
|
||||
<filter string="My Appraisals" name="my_appraisals" domain="[('user_id', '=', uid)]"/>
|
||||
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_appraisal_list" model="ir.ui.view">
|
||||
<field name="name">employee.appraisal.list</field>
|
||||
<field name="model">employee.appraisal</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Employees Appraisal">
|
||||
<field name="appraisal_period_id"/>
|
||||
<field name="name"/>
|
||||
<field name="location"/>
|
||||
<field name="department_id"/>
|
||||
<field name="reviewers_name"/>
|
||||
<field name="todays_date"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_view_employee_appraisal_list_my" model="ir.actions.act_window">
|
||||
<field name="name">Employees Appraisal</field>
|
||||
<field name="res_model">employee.appraisal</field>
|
||||
<!-- <field name="view_type">form</field>-->
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="groups_id" eval="[(4, ref('hr.group_hr_user'))]"/>
|
||||
<field name="search_view_id" ref="view_employee_appraisal_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a new Employee Appraisal.
|
||||
</p>
|
||||
<p>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Create the 'Appraisal' sub-menu under 'Configurations' -->
|
||||
<menuitem id="menu_appraisal_menu" name="Appraisal" parent="hr_employee_appraisal.menu_appraisal" sequence="1"/>
|
||||
|
||||
<!-- Create the 'Appraisal Period' menu under the 'Appraisal' menu -->
|
||||
<menuitem id="menu_employee_appraisal" name="Appraisal"
|
||||
parent="hr_employee_appraisal.menu_appraisal_menu" action="open_view_employee_appraisal_list_my"/>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Inherit the hr.job form view and add kpi_kra_ids inside a new tab -->
|
||||
<record id="view_hr_job_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.job.form.inherit</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_hr_job_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Add a new page to the notebook -->
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="KRAs" name="kras_page" groups="hr_employee_appraisal.group_appraisal_manager">
|
||||
<!-- One2many field to link KRAs to this job -->
|
||||
<field name="kpi_kra_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="kra_type"/>
|
||||
<field name="kpi_question_ids" widget="many2many_tags"/>
|
||||
</list>
|
||||
<form string="KRA Form">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name" required="True"/>
|
||||
<field name="kra_type"/>
|
||||
<!-- One2many field to add KPI questions inside the KRA form -->
|
||||
<field name="kpi_question_ids">
|
||||
<list editable="bottom">
|
||||
<field name="name" required="True"/>
|
||||
<!-- Add other fields you want to show inside the KPI questions -->
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="action_hr_job_kpi_kra" model="ir.actions.act_window">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_hr_job_appraisal" name="Job Positions" parent="hr_employee_appraisal.menu_configurations"
|
||||
action="action_hr_job_kpi_kra" groups="hr_employee_appraisal.group_appraisal_manager" sequence="14"/>
|
||||
|
||||
<record id="view_kra_types_list" model="ir.ui.view">
|
||||
<field name="name">kra.types.list</field>
|
||||
<field name="model">kra.types</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="KPIs and KRAs" editable="bottom">
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="view_kpi_kra_form" model="ir.ui.view">-->
|
||||
<!-- <field name="name">kpi.kra.form</field>-->
|
||||
<!-- <field name="model">kpi.kra</field>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <form string="KPI/KRA">-->
|
||||
<!-- <sheet>-->
|
||||
<!-- <group>-->
|
||||
<!-- <field name="name"/>-->
|
||||
<!-- <field name="is_active"/>-->
|
||||
<!-- <field name="kpi_kra_type"/>-->
|
||||
<!-- </group>-->
|
||||
<!-- </sheet>-->
|
||||
<!-- </form>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
|
||||
<!-- <!– Action –>-->
|
||||
<record id="action_kra_types" model="ir.actions.act_window">
|
||||
<field name="name">KRA Types</field>
|
||||
<field name="res_model">kra.types</field>
|
||||
<field name="view_mode">list</field>
|
||||
<field name="target">current</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_kra_type_appraisal" name="KRA Types" parent="hr_employee_appraisal.menu_configurations"
|
||||
action="hr_employee_appraisal.action_kra_types" groups="hr_employee_appraisal.group_appraisal_manager" sequence="12"/>
|
||||
|
||||
|
||||
<!-- <!– Menu Item –>-->
|
||||
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.appraisal</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="75"/>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form" position="inside">
|
||||
<app data-string="Appraisal" string="Appraisal" name="hr_appraisal"
|
||||
groups="hr_employee_appraisal.group_appraisal_manager" logo="/hr_employee_appraisal/static/description/icon.png">
|
||||
<block title="Appraisal Access Control" name="appraisal_access_block">
|
||||
<setting string="Appraisal HR Approval Access"
|
||||
help="Select the HR responsible for appraisals"
|
||||
id="appraisal_hr_access_control">
|
||||
<field name="appraisal_hr_id"
|
||||
options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||
</setting>
|
||||
<setting string="Appraisal MD Approval Access"
|
||||
help="Select the MD responsible for appraisals."
|
||||
id="appraisal_md_access_control">
|
||||
<field name="appraisal_md_id"
|
||||
options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||
</setting>
|
||||
|
||||
</block>
|
||||
</app>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="action_hr_employee_appraisal_configuration" 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' : 'hr_appraisal', 'bin_size': False}</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_hr_appraisal_global_settings"
|
||||
name="Settings"
|
||||
parent="hr_employee_appraisal.menu_configurations"
|
||||
sequence="0"
|
||||
action="hr_employee_appraisal.action_hr_employee_appraisal_configuration"
|
||||
groups="base.group_system"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import models,wizards
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "Employee Extended",
|
||||
|
||||
'summary': 'Customized employee information',
|
||||
|
||||
'description': """
|
||||
Customized Employee Info
|
||||
""",
|
||||
|
||||
'author': "Pranay",
|
||||
'website': "https://www.ftprotech.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Human Resources/Employees',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','hr'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/hr_employee.xml',
|
||||
'wizards/work_location_wizard.xml'
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from . import hr_employee
|
||||
from . import work_location_history
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class HrEmployeeBase(models.AbstractModel):
|
||||
_inherit = "hr.employee.base"
|
||||
|
||||
employee_id = fields.Char(string='Employee Code')
|
||||
doj = fields.Date(string='Date of Joining')
|
||||
pan_no = fields.Char(string='PAN No')
|
||||
# Add computed field for experience
|
||||
previous_exp = fields.Integer(string='Previous Experience (Months)', default=0)
|
||||
|
||||
current_company_exp = fields.Char(string='Current Experience', compute='_compute_experience', store=True)
|
||||
|
||||
total_exp = fields.Char(string='Total Experience', compute='_compute_total_experience', store=True)
|
||||
|
||||
@api.constrains('identification_id')
|
||||
def _check_identification_id(self):
|
||||
for record in self:
|
||||
if not record.identification_id.isdigit():
|
||||
raise ValidationError("Identification ID should only contain numbers.")
|
||||
|
||||
@api.depends('doj')
|
||||
def _compute_experience(self):
|
||||
for record in self:
|
||||
if record.doj:
|
||||
# Get the current date
|
||||
current_date = fields.Date.today()
|
||||
|
||||
# Calculate the difference between current date and doj
|
||||
delta = relativedelta(current_date, record.doj)
|
||||
|
||||
# Format the experience as 'X years Y months Z days'
|
||||
experience_str = f"{delta.years} years {delta.months} months {delta.days} days"
|
||||
record.current_company_exp = experience_str
|
||||
else:
|
||||
record.current_company_exp = '0 years 0 months 0 days'
|
||||
|
||||
|
||||
@api.depends('doj', 'previous_exp')
|
||||
def _compute_total_experience(self):
|
||||
for record in self:
|
||||
if record.doj:
|
||||
# Get the current date
|
||||
current_date = fields.Date.today()
|
||||
|
||||
# Calculate the current experience in years, months, and days
|
||||
delta = relativedelta(current_date, record.doj)
|
||||
current_years = delta.years
|
||||
current_months = delta.months
|
||||
current_days = delta.days
|
||||
|
||||
# Convert previous experience (in months) to years, months, and days
|
||||
previous_years = record.previous_exp // 12
|
||||
previous_months = record.previous_exp % 12
|
||||
previous_days = 0 # No days in previous_exp, it's already in months
|
||||
|
||||
# Add the previous experience to the current experience
|
||||
total_years = current_years + previous_years
|
||||
total_months = current_months + previous_months
|
||||
total_days = current_days + previous_days
|
||||
|
||||
# Adjust if months > 12 or days > 30
|
||||
if total_months >= 12:
|
||||
total_years += total_months // 12
|
||||
total_months = total_months % 12
|
||||
if total_days >= 30:
|
||||
total_months += total_days // 30
|
||||
total_days = total_days % 30
|
||||
|
||||
# Format the total experience as 'X years Y months Z days'
|
||||
total_experience_str = f"{total_years} years {total_months} months {total_days} days"
|
||||
record.total_exp = total_experience_str
|
||||
else:
|
||||
# If there's no DOJ, total experience is just the previous experience
|
||||
total_years = record.previous_exp // 12
|
||||
total_months = record.previous_exp % 12
|
||||
record.total_exp = f"{total_years} years {total_months} months 0 days"
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class HREmployee(models.Model):
|
||||
_inherit='hr.employee'
|
||||
|
||||
work_loc_history = fields.One2many('emp.work.location.history','employee_id',string="Work Location History")
|
||||
|
||||
def open_work_location_wizard(self):
|
||||
"""Open the wizard to manage work location history."""
|
||||
return {
|
||||
'name': 'Work Location Update',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'work.location.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_work_location_id': self.work_location_id.id,
|
||||
'default_employee_id': self.id,
|
||||
'default_start_date': fields.Date.today()
|
||||
},
|
||||
}
|
||||
class EmpWorkLocationHistory(models.Model):
|
||||
_name = 'emp.work.location.history'
|
||||
|
||||
|
||||
work_location_type = fields.Many2one('hr.work.location')
|
||||
start_date = fields.Date(string="Start Date")
|
||||
end_date = fields.Date(string="End Date")
|
||||
note = fields.Text(string="Notes")
|
||||
employee_id = fields.Many2one('hr.employee')
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_emp_work_location_history,emp.work.location.history,model_emp_work_location_history,base.group_user,1,1,1,1
|
||||
access_work_location_wizard,work.location.wizard,model_work_location_wizard,base.group_user,1,1,1,1
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_employee_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form.inherit</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='work_email']" position="before">
|
||||
<field name="employee_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='work_location_id']" position="after">
|
||||
<field name="doj"/>
|
||||
<field name="current_company_exp"/>
|
||||
<field name="previous_exp"/>
|
||||
<field name="total_exp"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='identification_id']" position="attributes">
|
||||
<attribute name="string">AADHAR No</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='identification_id']" position="after">
|
||||
<field name="pan_no"/>
|
||||
</xpath>
|
||||
<xpath expr="//header" position="inside">
|
||||
<button name="open_work_location_wizard" type="object" string="Update Work Location" class="btn-primary" groups="hr.group_hr_manager"/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page name="work_location_history_page" string="Work Location History" groups="hr.group_hr_manager">
|
||||
<field name="work_loc_history">
|
||||
<list editable="bottom">
|
||||
<field name="employee_id" column_invisible="1"/>
|
||||
<field name="work_location_type" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="note"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_public_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.employee.public.form.inherit</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="inherit_id" ref="hr.hr_employee_public_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='mobile_phone']" position="before">
|
||||
<field name="employee_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='work_location_id']" position="after">
|
||||
<field name="doj"/>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//field[@name='identification_id']" position="attributes">-->
|
||||
<!-- <attribute name="string">AADHAR No</attribute>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//field[@name='identification_id']" position="after">-->
|
||||
<!-- <field name="pan_no"/>-->
|
||||
<!-- </xpath>-->
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import work_location_wizard
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
from odoo import models, api, fields
|
||||
from datetime import timedelta
|
||||
|
||||
class WorkLocationWizard(models.TransientModel):
|
||||
_name = 'work.location.wizard'
|
||||
|
||||
work_location_id = fields.Many2one('hr.work.location', string="Work Location", required=True)
|
||||
start_date = fields.Date(string="Start Date", required=True)
|
||||
end_date = fields.Date(string="End Date", required=False)
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee")
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(WorkLocationWizard, self).default_get(fields)
|
||||
employee_id = self.env.context.get('default_employee_id')
|
||||
if employee_id:
|
||||
res['employee_id'] = employee_id
|
||||
return res
|
||||
|
||||
def apply_work_location_update(self):
|
||||
"""Update work location history."""
|
||||
self.ensure_one()
|
||||
EmpWorkLocationHistory = self.env['emp.work.location.history']
|
||||
previous_records = EmpWorkLocationHistory.search([
|
||||
('employee_id', '=', self.employee_id.id),
|
||||
('end_date', '=', False)
|
||||
])
|
||||
if previous_records:
|
||||
for record in previous_records:
|
||||
record.end_date = self.start_date - timedelta(days=1)
|
||||
|
||||
EmpWorkLocationHistory.create({
|
||||
'work_location_type': self.work_location_id.id,
|
||||
'start_date': self.start_date,
|
||||
'end_date': self.end_date,
|
||||
'employee_id': self.employee_id.id,
|
||||
})
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_work_location_wizard_form" model="ir.ui.view">
|
||||
<field name="name">work.location.wizard.form</field>
|
||||
<field name="model">work.location.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Work Location Update">
|
||||
<group>
|
||||
<field name="work_location_id"/>
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Apply" type="object" name="apply_work_location_update" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "Timeoff Extended",
|
||||
|
||||
'summary': 'Customized timeoff information',
|
||||
|
||||
'description': """
|
||||
Customized timeoff data
|
||||
""",
|
||||
|
||||
'author': "Pranay",
|
||||
'website': "https://www.ftprotech.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Human Resources/Time Off',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','hr','hr_holidays','hr_employee_extended'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'views/hr_employee.xml',
|
||||
'views/hr_timeoff.xml'
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="hr_holidays.hr_leave_allocation_cron_accrual" model="ir.cron">
|
||||
<field name="code">model._update_accrual()</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from . import hr_timeoff
|
||||
from . import hr_employee
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from odoo import fields, models, api, _
|
||||
|
||||
class HREmployeeBase(models.AbstractModel):
|
||||
_inherit = 'hr.employee.base'
|
||||
|
||||
run_auto_allocations = fields.Boolean(string="Allocation")
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
from calendar import month
|
||||
|
||||
from dateutil.utils import today
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from datetime import datetime, date, time, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
class hrLeaveAccrualLevel(models.Model):
|
||||
_inherit = 'hr.leave.accrual.level'
|
||||
|
||||
level_frequency = fields.Selection([
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
('monthly', 'Monthly'),
|
||||
('yearly', 'Yearly'),
|
||||
], default='daily', required=True, string="Frequency")
|
||||
|
||||
max_start_count = fields.Integer(
|
||||
"Start after",
|
||||
help="The accrual will not proceed if the employee experience is grater that the count and type ",
|
||||
default="1")
|
||||
max_start_type = fields.Selection(
|
||||
[('day', 'Days'),
|
||||
('month', 'Months'),
|
||||
('year', 'Years')],
|
||||
default='day', string=" ", required=True,
|
||||
help="This accrual will not proceed if the employee experience is grater that the count and type")
|
||||
|
||||
@api.constrains('start_count', 'start_type', 'max_start_count', 'max_start_type')
|
||||
def _check_start_count_and_max(self):
|
||||
for record in self:
|
||||
# Define a function to convert the start_count and max_start_count to a base unit (e.g., days)
|
||||
def convert_to_days(count, type):
|
||||
if type == 'day':
|
||||
return count
|
||||
elif type == 'month':
|
||||
return count * 30 # Approximate value for months (30 days)
|
||||
elif type == 'year':
|
||||
return count * 365 # Approximate value for years (365 days)
|
||||
|
||||
start_days = convert_to_days(record.start_count, record.start_type)
|
||||
max_days = convert_to_days(record.max_start_count, record.max_start_type)
|
||||
|
||||
if start_days >= max_days:
|
||||
raise ValidationError("Start count and type must be smaller than the max start count and type.")
|
||||
class hrLeaveAccrualPlan(models.Model):
|
||||
_inherit = 'hr.leave.accrual.plan'
|
||||
|
||||
accrual_start_count = fields.Integer(
|
||||
"Start after",
|
||||
help="The accrual starts after a defined period from the employee joining date. This field defines the number of days, months or years after which accrual is used.",
|
||||
default="1")
|
||||
accrual_start_type = fields.Selection(
|
||||
[('day', 'Days'),
|
||||
('month', 'Months'),
|
||||
('year', 'Years')],
|
||||
default='day', string=" ", required=True,
|
||||
help="This field defines the unit of time after which the employee joining date.")
|
||||
|
||||
time_off_type_id = fields.Many2one('hr.leave.type', domain=[('requires_allocation','=','yes')])
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_time_off_type_id', 'unique(time_off_type_id)', 'You can not create multiple plans with same leave type.')
|
||||
]
|
||||
|
||||
class hrTimeoffAllocation(models.Model):
|
||||
_inherit = "hr.leave.allocation"
|
||||
|
||||
allocation_type = fields.Selection(selection_add =[('auto_allocation','Auto Allocation')],ondelete={
|
||||
'auto_allocation': 'cascade',
|
||||
},)
|
||||
|
||||
def _process_accrual_plans(self, date_to=False, force_period=False, log=True):
|
||||
pass
|
||||
|
||||
@api.model
|
||||
def _update_accrual(self):
|
||||
"""
|
||||
Method called by the cron task in order to increment the number_of_days when
|
||||
necessary, based on the frequency selected in the accrual level.
|
||||
"""
|
||||
accrual_plans = self.env['hr.leave.accrual.plan'].sudo().search([])
|
||||
for accrual in accrual_plans:
|
||||
employees = self.env['hr.employee'].sudo().search([('run_auto_allocations', '=', True)])
|
||||
|
||||
if accrual.accrual_start_count and accrual.accrual_start_type:
|
||||
if accrual.accrual_start_count > 0:
|
||||
employees = employees.filtered(lambda emp: self._is_accrual_applicable(emp, accrual))
|
||||
|
||||
level_ids = accrual.level_ids.sorted('sequence')
|
||||
if not level_ids:
|
||||
continue
|
||||
|
||||
for level in level_ids:
|
||||
# Calculate the current frequency
|
||||
run_allocation = self._handel_weekly_frequency(level)
|
||||
if run_allocation:
|
||||
qualified_employees = employees.filtered(lambda emp: self._is_experience_in_range(emp, level))
|
||||
|
||||
# After filtering, we create the leave allocation for each employee
|
||||
for emp in qualified_employees:
|
||||
allocations = self.env['hr.leave.allocation'].sudo().search([('employee_id','=',emp.id),('holiday_status_id','=',accrual.time_off_type_id.id),('state','=','validate')])
|
||||
leaves = self.env['hr.leave'].sudo().search([('employee_id','=',emp.id),('holiday_status_id','=',accrual.time_off_type_id.id),('state','not in',['draft','refuse','cancel'])])
|
||||
emp_leave_balance = sum(allocation.number_of_days for allocation in allocations) - sum(leave.number_of_days for leave in leaves)
|
||||
if level.cap_accrued_time and level.maximum_leave < emp_leave_balance:
|
||||
continue
|
||||
self._create_leave_allocation(emp, level, accrual)
|
||||
|
||||
def _handel_weekly_frequency(self,level):
|
||||
today_date = datetime.today().date()
|
||||
if level.level_frequency == 'weekly':
|
||||
weekday_map = {
|
||||
'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3, 'fri': 4, 'sat': 5, 'sun': 6
|
||||
}
|
||||
return True if today_date.weekday() == weekday_map.get(level.week_day) else False
|
||||
elif level.level_frequency == 'daily':
|
||||
return True
|
||||
elif level.level_frequency == 'monthly':
|
||||
return True if level.first_day_display == str(today_date.day) else False
|
||||
elif level.level_frequency == 'yearly':
|
||||
month_map = {
|
||||
'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
|
||||
'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12
|
||||
}
|
||||
return True if level.first_day_display == str(today_date.day) and today_date.month == month_map.get(level.yearly_month) else False
|
||||
|
||||
else:
|
||||
return True
|
||||
|
||||
def _create_leave_allocation(self, employee, level, accrual):
|
||||
"""
|
||||
Create leave allocation for a qualified employee based on the accrual level and added value.
|
||||
"""
|
||||
self.env['hr.leave.allocation'].sudo().create({
|
||||
'employee_id': employee.id,
|
||||
'holiday_status_id': accrual.time_off_type_id.id,
|
||||
'date_from': fields.Date.today(),
|
||||
'number_of_days': level.added_value,
|
||||
'allocation_type': 'auto_allocation'
|
||||
}).action_approve()
|
||||
|
||||
def _is_accrual_applicable(self, employee, accrual):
|
||||
"""
|
||||
Helper method to check if the accrual is applicable to the employee
|
||||
based on joining date and the accrual plan.
|
||||
"""
|
||||
if employee.doj:
|
||||
doj = employee.doj
|
||||
start_date = False
|
||||
|
||||
# Calculate the start date based on the accrual type and count
|
||||
if accrual.accrual_start_type == 'day':
|
||||
start_date = doj + relativedelta(days=accrual.accrual_start_count)
|
||||
elif accrual.accrual_start_type == 'month':
|
||||
start_date = doj + relativedelta(months=accrual.accrual_start_count)
|
||||
elif accrual.accrual_start_type == 'year':
|
||||
start_date = doj + relativedelta(years=accrual.accrual_start_count)
|
||||
|
||||
# Return if the employee's today date is greater than or equal to start_date
|
||||
return fields.date.today() >= start_date
|
||||
return False
|
||||
|
||||
def _is_experience_in_range(self, employee, level):
|
||||
"""
|
||||
Helper method to check if the employee's total experience (including previous experience) is within the
|
||||
range defined by the accrual's start and max start counts and types.
|
||||
"""
|
||||
if employee.doj:
|
||||
# Calculate the employee's total experience based on DOJ and previous_exp
|
||||
total_experience = self._calculate_total_experience(employee.doj, employee.previous_exp)
|
||||
|
||||
# Calculate the minimum experience based on accrual_start_count and accrual_start_type
|
||||
min_experience = self._calculate_experience_threshold(level.start_count,
|
||||
level.start_type)
|
||||
|
||||
# Calculate the maximum experience based on max_start_count and max_start_type
|
||||
max_experience = self._calculate_experience_threshold(level.max_start_count, level.max_start_type)
|
||||
|
||||
# Return whether the total experience is within the min and max range
|
||||
return min_experience <= total_experience <= max_experience
|
||||
return False
|
||||
|
||||
def _calculate_total_experience(self, doj, previous_exp):
|
||||
"""
|
||||
Helper method to calculate total experience of an employee in months, combining the previous experience
|
||||
and experience since the DOJ.
|
||||
"""
|
||||
today = datetime.today()
|
||||
# Calculate current experience from the date of joining
|
||||
delta = relativedelta(today, doj)
|
||||
current_experience_months = delta.years * 12 + delta.months
|
||||
|
||||
# Add previous experience (already in months)
|
||||
total_experience_months = current_experience_months + previous_exp
|
||||
return total_experience_months
|
||||
|
||||
def _calculate_experience_threshold(self, count, experience_type):
|
||||
"""
|
||||
Helper method to calculate the experience threshold in months based on a given count and type.
|
||||
"""
|
||||
today = datetime.today()
|
||||
start_date = today
|
||||
|
||||
if experience_type == 'day':
|
||||
start_date = today - relativedelta(days=count)
|
||||
elif experience_type == 'month':
|
||||
start_date = today - relativedelta(months=count)
|
||||
elif experience_type == 'year':
|
||||
start_date = today - relativedelta(years=count)
|
||||
|
||||
# Calculate the experience in months for the threshold
|
||||
return self._calculate_total_experience(start_date, 0)
|
||||
|
||||
class HRLeave(models.Model):
|
||||
_inherit = 'hr.leave'
|
||||
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('confirm', 'To Approve'),
|
||||
('refuse', 'Refused'),
|
||||
('validate1', 'Second Approval'),
|
||||
('validate', 'Approved'),
|
||||
('cancel', 'Cancelled'),
|
||||
], string='Status', store=True, tracking=True, copy=False, readonly=False, default='draft',
|
||||
help="The status is set to 'Draft' when the leave request is created." +
|
||||
"\nThe status is 'To Approve', when time off request is confirmed by user." +
|
||||
"\nThe status is 'Refused', when time off request is refused by manager." +
|
||||
"\nThe status is 'Second Approval', when time off request is awaiting further validation." +
|
||||
"\nThe status is 'Approved', when time off request is approved by manager." +
|
||||
"\nThe status is 'Cancelled', when time off request is cancelled.")
|
||||
|
||||
def action_draft(self):
|
||||
for rec in self:
|
||||
if rec.employee_id.user_id.id != self.env.user.id:
|
||||
raise ValidationError(_("Only employee can submit his own leave"))
|
||||
self._check_validity()
|
||||
rec.state = 'confirm'
|
||||
|
||||
def action_reset_confirm(self):
|
||||
if any(holiday.state not in ['cancel', 'refuse'] for holiday in self):
|
||||
raise UserError(_('Time off request state must be "Refused" or "Cancelled" in order to be reset to "Confirmed".'))
|
||||
self.write({
|
||||
'state': 'draft',
|
||||
'first_approver_id': False,
|
||||
'second_approver_id': False,
|
||||
})
|
||||
self.activity_update()
|
||||
return True
|
||||
|
||||
def action_approve(self):
|
||||
for rec in self:
|
||||
if rec.employee_id.leave_manager_id.id != self.env.user.id:
|
||||
raise ValidationError(_("Only Employees Time Off Approver can approve this "))
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_employee_form_inherit_allocation" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form.inherit.allocation</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='hr_settings']" position="inside">
|
||||
<group>
|
||||
<group string="Leaves" name="hr_leaves_group">
|
||||
<field name="run_auto_allocations"/>
|
||||
</group>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="hr_accrual_plan_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.leave.accrual.plan.inherit</field>
|
||||
<field name="model">hr.leave.accrual.plan</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_accrual_plan_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='carryover']" position="after">
|
||||
<field name="time_off_type_id" required="1"/>
|
||||
<div>
|
||||
<strong>Allocation Starts</strong>
|
||||
<field name="accrual_start_count" style="width: 2rem"/>
|
||||
<field name="accrual_start_type" style="width: 4.75rem"/>
|
||||
<strong>after employee joining date</strong>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='level_ids']/kanban" position="replace">
|
||||
<kanban default_order="sequence">
|
||||
<field name="sequence"/>
|
||||
<field name="action_with_unused_accruals"/>
|
||||
<field name="accrual_validity_count"/>
|
||||
<field name="accrual_validity_type"/>
|
||||
<field name="cap_accrued_time"/>
|
||||
<field name="accrual_validity"/>
|
||||
<templates>
|
||||
<div t-name="card" class="bg-transparent border-0">
|
||||
<div class="o_hr_holidays_body">
|
||||
<div class="o_hr_holidays_timeline text-center">
|
||||
<t t-if="record.start_count.raw_value > 0">
|
||||
Experience between <field name="start_count"/> <field name="start_type"/> and <field name="max_start_count"/> <field name="max_start_type"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
initially
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="!read_only_mode">
|
||||
<a type="edit" t-attf-class="oe_kanban_action text-black">
|
||||
<t t-call="level_content"/>
|
||||
</a>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="level_content"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<t t-name="level_content">
|
||||
<div class="o_hr_holidays_card">
|
||||
<div class="content container" style="width: 560px;">
|
||||
<div class="row w-100">
|
||||
<div class="pe-0 me-0" style="width: 6rem;">
|
||||
<field name="added_value" invisible="1"/>
|
||||
<span t-out="record.added_value.raw_value"/> <field name="added_value_type"/>,
|
||||
</div>
|
||||
<div class="col-auto m-0 p-0">
|
||||
<field name="level_frequency" class="ms-1"/>
|
||||
<t t-if="record.level_frequency.raw_value === 'weekly'">
|
||||
on <field name="week_day"/>
|
||||
</t>
|
||||
<t t-elif="record.level_frequency.raw_value === 'monthly'">
|
||||
on the <field name="first_day"/> day of the month
|
||||
</t>
|
||||
<t t-elif="record.level_frequency.raw_value === 'bimonthly'">
|
||||
on the <field name="first_day"/> and on the <field name="second_day"/> days of the months
|
||||
</t>
|
||||
<t t-elif="record.level_frequency.raw_value === 'biyearly'">
|
||||
on the <field name="first_month_day"/> <field name="first_month"/> and on the <field name="second_month_day"/> <field name="second_month"/>
|
||||
</t>
|
||||
<t t-elif="record.level_frequency.raw_value === 'yearly'">
|
||||
on <field name="yearly_day"/> <field name="yearly_month"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-muted">
|
||||
<div class="pe-0 me-0" style="width: 6rem;">
|
||||
Cap:
|
||||
</div>
|
||||
<div class="col-3 m-0 ps-1 d-flex">
|
||||
<t t-if="record.cap_accrued_time.raw_value and record.maximum_leave.raw_value > 0">
|
||||
<field name="maximum_leave" widget="float_without_trailing_zeros"/> <field class="ms-1" name="added_value_type"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
Unlimited
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-muted" invisible="1">
|
||||
<div class="pe-0 me-0" style="width: 6rem;">
|
||||
Carry over:
|
||||
</div>
|
||||
<div class="col-6 m-0 ps-1">
|
||||
<t t-if="record.action_with_unused_accruals.raw_value === 'all'">all
|
||||
<span invisible="not accrual_validity">
|
||||
- Valid for <field name="accrual_validity_count"/> <field name="accrual_validity_type"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-elif="record.action_with_unused_accruals.raw_value === 'maximum'">up to <field name="postpone_max_days"/> <t t-esc="record.added_value_type.raw_value"/>
|
||||
<span invisible="not accrual_validity">
|
||||
- Valid for <field name="accrual_validity_count"/> <field name="accrual_validity_type"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-else="">no</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="hr_leave_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.leave.view.form.inherit</field>
|
||||
<field name="model">hr.leave</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_leave_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='state']" position="attributes">
|
||||
<attribute name="statusbar_visible">draft,confirm,validate,cancel</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_approve']" position="before">
|
||||
<button string="Submit" name="action_draft" type="object" class="oe_highlight"
|
||||
invisible="state != 'draft'"/>
|
||||
</xpath>
|
||||
<xpath expr="//div/field[@name='request_date_from']" position="attributes">
|
||||
<attribute name="readonly">state != 'draft'</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div/field[@name='request_date_from_period']" position="attributes">
|
||||
<attribute name="readonly">state != 'draft'</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[2]/field[@name='request_date_from']" position="attributes">
|
||||
<attribute name="readonly">state != 'draft'</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[3]/field[@name='request_unit_half']" position="attributes">
|
||||
<attribute name="readonly">state != 'draft'</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[3]/field[@name='request_unit_hours']" position="attributes">
|
||||
<attribute name="readonly">state != 'draft'</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='name']" position="attributes">
|
||||
<attribute name="readonly">state != 'draft'</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//label[@for='supported_attachment_ids']" position="attributes">
|
||||
<attribute name="invisible">not leave_type_support_document or state not in ('draft', 'confirm',
|
||||
'validate1')
|
||||
</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='supported_attachment_ids']" position="attributes">
|
||||
<attribute name="invisible">not leave_type_support_document or state not in ('draft', 'confirm',
|
||||
'validate1')
|
||||
</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="hr_accrual_level_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.leave.accrual.level.form.inherit</field>
|
||||
<field name="model">hr.leave.accrual.level</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_accrual_level_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='accrue']" position="replace">
|
||||
<group name="accrue" col="1" width="800px">
|
||||
<div class="o_td_label">
|
||||
<label for="added_value" string="Employee accrue"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="accrued_gain_time" invisible="1"/>
|
||||
<field name="can_modify_value_type" invisible="1"/>
|
||||
<field name="added_value" widget="float_without_trailing_zeros" style="width: 4rem" class="me-1"/>
|
||||
<field name="added_value_type" style="width: 3.4rem" nolabel="1" readonly="not can_modify_value_type"/>
|
||||
</div>
|
||||
<div style="width: 5rem"/>
|
||||
<div name="daily" invisible="level_frequency != 'daily'">
|
||||
<field name="level_frequency" style="width: 5rem"/>
|
||||
</div>
|
||||
<div name="weekly" invisible="level_frequency != 'weekly'">
|
||||
<field name="level_frequency" style="width: 4.5rem;"/>
|
||||
<label for="week_day" string="on" class="me-1"/><field name="week_day" style="width: 6.6rem"/>
|
||||
</div>
|
||||
<div name="monthly" invisible="level_frequency != 'monthly'">
|
||||
<field name="level_frequency" style="width: 4.5rem"/>
|
||||
<label for="first_day_display" string="on the" class="me-1"/>
|
||||
<field name="first_day_display" required="1" style="width: 4rem"/>
|
||||
of the month
|
||||
</div>
|
||||
<div name="yearly" invisible="level_frequency != 'yearly'">
|
||||
<field name="level_frequency" style="width: 4rem"/>
|
||||
<label for="yearly_day_display" string="on the" class="me-1"/>
|
||||
<field name="yearly_day_display" required="1" style="width: 4rem"/> of <field name="yearly_month" required="1" style="width: 5.4rem"/>
|
||||
</div>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='milestone']" position="replace">
|
||||
<group name="milestone">
|
||||
<div class="o_td_label">
|
||||
<label for="start_count" string="Employee total Experience"/>
|
||||
</div>
|
||||
<div>Min
|
||||
<field name="start_count" style="width: 2rem" required="max_start_count > 0 or max_start_type != False"/>
|
||||
<field name="start_type" style="width: 4.75rem" required="max_start_count > 0 or max_start_type != False"/>
|
||||
&
|
||||
Max of
|
||||
<field name="max_start_count" style="width: 2rem" required="start_count > 0 or start_type != False"/>
|
||||
<field name="max_start_type" style="width: 4.75rem" required="start_count > 0 or start_type != False"/>
|
||||
|
||||
Experience is required
|
||||
</div>
|
||||
</group>
|
||||
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="hr_holidays_hr_leave_allocation_view_form_manager_inherit">
|
||||
<field name="name">hr.leave.allocation.view.form.manager.inherit</field>
|
||||
<field name="model">hr.leave.allocation</field>
|
||||
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='allocation_type']" position="attributes">
|
||||
<attribute name="readonly">1</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue