Compare commits

...

2 Commits

Author SHA1 Message Date
administrator 804a6ec626 Initial commit 2025-01-08 15:04:24 +05:30
administrator ec42e2736c Pranay : addons extra 2025-01-08 15:01:54 +05:30
48 changed files with 2698 additions and 0 deletions

View File

@ -0,0 +1,2 @@
from . import models
from . import controllers

View File

@ -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': [
],
}

View File

@ -0,0 +1 @@
from . import hr_employee

View File

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

View File

@ -0,0 +1 @@
from . import models

View File

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

View File

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

View File

@ -0,0 +1 @@
from . import hr_attendance

View File

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

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_attendance_attendance_user attendance.attendance.access model_attendance_attendance base.group_user 1 1 1 1
3 access_attendance_data_user attendance.data.access model_attendance_data base.group_user 1 1 1 1

View File

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

View File

@ -0,0 +1 @@
from . import models

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
from . import apprisal_period_master
from . import kpi_kra_master
from . import employee_appraisal
from . import res_config_settings

View File

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

View File

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

View File

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

View File

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

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 model_appraisal_period_manager_access appraisal.period manager access model_appraisal_period group_appraisal_manager 1 1 1 1
3 model_appraisal_period_user_access appraisal.period user access model_appraisal_period base.group_user 1 0 0 0
4 access_kra_types_user kra.types.user model_kra_types base.group_user 1 0 0 0
5 access_kra_types_manager kra.types.manager model_kra_types group_appraisal_manager 1 1 1 1
6 access_kpi_kra_user kpi.kra user model_kpi_kra base.group_user 1 0 0 0
7 access_kpi_kra_manager kpi.kra manager model_kpi_kra group_appraisal_manager 1 1 1 1
8 access_kpi_question_user kpi.question.user model_kpi_question base.group_user 1 0 0 0
9 access_kpi_question_manager kpi.question.manager model_kpi_question group_appraisal_manager 1 1 1 1
10 access_employee_appraisal_user employee.appraisal user model_employee_appraisal base.group_user 1 1 0 0
11 access_employee_appraisal_manager employee.appraisal manager model_employee_appraisal group_appraisal_manager 1 1 1 1
12 access_employee_appraisal_officer employee.appraisal officer model_employee_appraisal group_appraisal_officer 1 1 0 0
13 access_rating_user employee.rating model_employee_rating base.group_user 1 1 0 0
14 access_rating_hr_user employee.rating.hr model_employee_rating group_appraisal_manager 1 1 1 1
15 access_rating_officer employee.rating.officer model_employee_rating group_appraisal_officer 1 1 0 0
16 access_job_req_user employee.job.requirement model_employee_job_requirement base.group_user 1 1 0 0
17 access_job_req_hr_user employee.job.requirement.hr model_employee_job_requirement group_appraisal_manager 1 1 1 1
18 access_job_req_officer employee.job.requirement.officer model_employee_job_requirement group_appraisal_officer 1 1 0 0

View File

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

View File

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

View File

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

View File

@ -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>-->
<!-- &lt;!&ndash; Action &ndash;&gt;-->
<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"/>
<!-- &lt;!&ndash; Menu Item &ndash;&gt;-->
</data>
</odoo>

View File

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

View File

@ -0,0 +1 @@
from . import models,wizards

View File

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

View File

@ -0,0 +1,2 @@
from . import hr_employee
from . import work_location_history

View File

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

View File

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

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_emp_work_location_history emp.work.location.history model_emp_work_location_history base.group_user 1 1 1 1
3 access_work_location_wizard work.location.wizard model_work_location_wizard base.group_user 1 1 1 1

View File

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

View File

@ -0,0 +1 @@
from . import work_location_wizard

View File

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

View File

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

View File

@ -0,0 +1 @@
from . import models

View File

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

View File

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

View File

@ -0,0 +1,2 @@
from . import hr_timeoff
from . import hr_employee

View File

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

View File

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

View File

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

View File

@ -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 &gt; 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 &gt; 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"/>
&amp;
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>