Roster management and grace period changes
This commit is contained in:
parent
5c6341d8b7
commit
adfe801d8e
|
|
@ -12,6 +12,7 @@
|
||||||
'hr_holidays',
|
'hr_holidays',
|
||||||
'resource',
|
'resource',
|
||||||
'hr_attendance_extended',
|
'hr_attendance_extended',
|
||||||
|
'roster_management',
|
||||||
],
|
],
|
||||||
|
|
||||||
'data': [
|
'data': [
|
||||||
|
|
@ -23,7 +24,8 @@
|
||||||
'views/late_coming_request.xml',
|
'views/late_coming_request.xml',
|
||||||
'views/hr_employee_inherit.xml',
|
'views/hr_employee_inherit.xml',
|
||||||
'views/late_coming_mail_template.xml',
|
'views/late_coming_mail_template.xml',
|
||||||
# 'security/record_rules.xml',
|
'views/ot_request.xml',
|
||||||
|
'views/ot_mail_template.xml',
|
||||||
],
|
],
|
||||||
|
|
||||||
'installable': True,
|
'installable': True,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from odoo import api, fields, models, tools
|
from odoo import api, fields, models, tools, _
|
||||||
|
|
||||||
|
|
||||||
class AttendanceAnalytics(models.Model):
|
class AttendanceAnalytics(models.Model):
|
||||||
|
|
@ -12,86 +12,73 @@ class AttendanceAnalytics(models.Model):
|
||||||
'hr.employee',
|
'hr.employee',
|
||||||
string='Employee'
|
string='Employee'
|
||||||
)
|
)
|
||||||
|
|
||||||
department_id = fields.Many2one(
|
department_id = fields.Many2one(
|
||||||
'hr.department',
|
'hr.department',
|
||||||
string='Department'
|
string='Department'
|
||||||
)
|
)
|
||||||
|
|
||||||
date = fields.Date()
|
date = fields.Date()
|
||||||
|
|
||||||
date_end = fields.Date(
|
date_end = fields.Date(
|
||||||
string="End Date"
|
string="End Date"
|
||||||
)
|
)
|
||||||
|
|
||||||
min_check_in = fields.Datetime()
|
min_check_in = fields.Datetime()
|
||||||
|
|
||||||
max_check_out = fields.Datetime()
|
max_check_out = fields.Datetime()
|
||||||
|
|
||||||
worked_hours = fields.Float()
|
worked_hours = fields.Float()
|
||||||
|
|
||||||
out_time = fields.Float()
|
out_time = fields.Float()
|
||||||
|
|
||||||
expected_check_in = fields.Float()
|
expected_check_in = fields.Float()
|
||||||
|
|
||||||
expected_check_out = fields.Float()
|
expected_check_out = fields.Float()
|
||||||
|
|
||||||
required_checkout_time = fields.Float()
|
required_checkout_time = fields.Float()
|
||||||
|
|
||||||
compensated_time = fields.Float()
|
compensated_time = fields.Float()
|
||||||
|
|
||||||
department_grace_period = fields.Integer()
|
department_grace_period = fields.Integer()
|
||||||
|
|
||||||
late_minutes = fields.Float()
|
late_minutes = fields.Float()
|
||||||
|
|
||||||
early_out_minutes = fields.Float()
|
early_out_minutes = fields.Float()
|
||||||
|
|
||||||
is_late = fields.Boolean(
|
is_late = fields.Boolean(
|
||||||
string="Late"
|
string="Late"
|
||||||
)
|
)
|
||||||
|
|
||||||
is_early_out = fields.Boolean(
|
is_early_out = fields.Boolean(
|
||||||
string="Early Out"
|
string="Early Out"
|
||||||
)
|
)
|
||||||
|
|
||||||
is_compensation_pending = fields.Boolean(
|
is_compensation_pending = fields.Boolean(
|
||||||
string="Compensation Pending"
|
string="Compensation Pending"
|
||||||
)
|
)
|
||||||
|
|
||||||
late_time = fields.Char(
|
late_time = fields.Char(
|
||||||
string="Late Time",
|
string="Late Time",
|
||||||
compute="_compute_late_time"
|
compute="_compute_late_time"
|
||||||
)
|
)
|
||||||
|
|
||||||
early_out_time = fields.Char(
|
early_out_time = fields.Char(
|
||||||
string="Early Out Time",
|
string="Early Out Time",
|
||||||
compute="_compute_early_out_time"
|
compute="_compute_early_out_time"
|
||||||
)
|
)
|
||||||
|
|
||||||
holiday_name = fields.Char()
|
holiday_name = fields.Char()
|
||||||
|
|
||||||
is_holiday = fields.Boolean()
|
is_holiday = fields.Boolean()
|
||||||
|
|
||||||
is_week_off = fields.Boolean()
|
is_week_off = fields.Boolean()
|
||||||
|
|
||||||
status_message = fields.Char(
|
|
||||||
string="Status"
|
|
||||||
)
|
|
||||||
|
|
||||||
display_label = fields.Char(
|
display_label = fields.Char(
|
||||||
compute="_compute_display_label",
|
compute="_compute_display_label",
|
||||||
store=False
|
store=False
|
||||||
)
|
)
|
||||||
|
|
||||||
color = fields.Integer(
|
color = fields.Integer(
|
||||||
compute="_compute_color"
|
compute="_compute_color"
|
||||||
)
|
)
|
||||||
|
|
||||||
status = fields.Selection([
|
status = fields.Selection([
|
||||||
|
|
||||||
('present', 'Present'),
|
('present', 'Present'),
|
||||||
('leave', 'Leave'),
|
|
||||||
('no_info', 'No Information')
|
('absent', 'Absent'),
|
||||||
])
|
|
||||||
|
('half_day', 'Half Day'),
|
||||||
|
|
||||||
|
('late_in', 'Late In'),
|
||||||
|
|
||||||
|
('early_out', 'Early Out'),
|
||||||
|
|
||||||
|
('on_duty', 'On Duty'),
|
||||||
|
|
||||||
|
('work_from_home', 'Work From Home'),
|
||||||
|
|
||||||
|
('holiday', 'Holiday'),
|
||||||
|
|
||||||
|
('week_off', 'Week Off'),
|
||||||
|
|
||||||
|
], string="Attendance Status")
|
||||||
|
|
||||||
late_request_id = fields.Many2one(
|
late_request_id = fields.Many2one(
|
||||||
'late.coming.request',
|
'late.coming.request',
|
||||||
|
|
@ -102,11 +89,26 @@ class AttendanceAnalytics(models.Model):
|
||||||
string="Late Approved"
|
string="Late Approved"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hours_per_day = fields.Float()
|
||||||
|
|
||||||
|
allowed_ot_limit = fields.Float()
|
||||||
|
|
||||||
|
ot_eligible = fields.Boolean()
|
||||||
|
|
||||||
|
overtime_hours = fields.Float()
|
||||||
|
shift_id = fields.Many2one(
|
||||||
|
'resource.calendar',
|
||||||
|
string='Shift'
|
||||||
|
)
|
||||||
|
|
||||||
|
shift_name = fields.Char(
|
||||||
|
string='Shift'
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('late_minutes')
|
@api.depends('late_minutes')
|
||||||
def _compute_late_time(self):
|
def _compute_late_time(self):
|
||||||
|
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
|
||||||
total_minutes = int(rec.late_minutes or 0)
|
total_minutes = int(rec.late_minutes or 0)
|
||||||
|
|
||||||
hours = total_minutes // 60
|
hours = total_minutes // 60
|
||||||
|
|
@ -121,7 +123,6 @@ class AttendanceAnalytics(models.Model):
|
||||||
def _compute_early_out_time(self):
|
def _compute_early_out_time(self):
|
||||||
|
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
|
||||||
total_minutes = int(
|
total_minutes = int(
|
||||||
rec.early_out_minutes or 0
|
rec.early_out_minutes or 0
|
||||||
)
|
)
|
||||||
|
|
@ -135,12 +136,16 @@ class AttendanceAnalytics(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends(
|
@api.depends(
|
||||||
'status_message',
|
'status',
|
||||||
'late_time',
|
'late_time',
|
||||||
'early_out_time'
|
'early_out_time'
|
||||||
)
|
)
|
||||||
def _compute_display_label(self):
|
def _compute_display_label(self):
|
||||||
|
|
||||||
|
status_dict = dict(
|
||||||
|
self._fields['status'].selection
|
||||||
|
)
|
||||||
|
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
|
|
@ -148,8 +153,13 @@ class AttendanceAnalytics(models.Model):
|
||||||
if rec.employee_id:
|
if rec.employee_id:
|
||||||
lines.append(rec.employee_id.name)
|
lines.append(rec.employee_id.name)
|
||||||
|
|
||||||
if rec.status_message:
|
if rec.status:
|
||||||
lines.append(rec.status_message)
|
lines.append(
|
||||||
|
status_dict.get(
|
||||||
|
rec.status,
|
||||||
|
rec.status
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if rec.late_minutes:
|
if rec.late_minutes:
|
||||||
lines.append(
|
lines.append(
|
||||||
|
|
@ -163,36 +173,30 @@ class AttendanceAnalytics(models.Model):
|
||||||
|
|
||||||
rec.display_label = "\n".join(lines)
|
rec.display_label = "\n".join(lines)
|
||||||
|
|
||||||
@api.depends(
|
|
||||||
'is_holiday',
|
@api.depends('status')
|
||||||
'is_week_off',
|
|
||||||
'status',
|
|
||||||
'is_compensation_pending',
|
|
||||||
'is_late'
|
|
||||||
)
|
|
||||||
def _compute_color(self):
|
def _compute_color(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
if rec.status == 'present':
|
||||||
if rec.is_holiday:
|
|
||||||
rec.color = 4
|
|
||||||
|
|
||||||
elif rec.is_week_off:
|
|
||||||
rec.color = 7
|
|
||||||
|
|
||||||
elif rec.status == 'leave':
|
|
||||||
rec.color = 3
|
|
||||||
|
|
||||||
elif rec.is_compensation_pending:
|
|
||||||
rec.color = 2
|
|
||||||
|
|
||||||
elif rec.is_late:
|
|
||||||
rec.color = 1
|
|
||||||
|
|
||||||
elif rec.status == 'present':
|
|
||||||
rec.color = 10
|
rec.color = 10
|
||||||
|
elif rec.status == 'absent':
|
||||||
else:
|
rec.color = 1
|
||||||
|
elif rec.status == 'half_day':
|
||||||
|
rec.color = 2
|
||||||
|
elif rec.status == 'late_in':
|
||||||
|
rec.color = 3
|
||||||
|
elif rec.status == 'early_out':
|
||||||
|
rec.color = 4
|
||||||
|
elif rec.status == 'holiday':
|
||||||
|
rec.color = 7
|
||||||
|
elif rec.status == 'week_off':
|
||||||
rec.color = 8
|
rec.color = 8
|
||||||
|
elif rec.status == 'work_from_home':
|
||||||
|
rec.color = 5
|
||||||
|
elif rec.status == 'on_duty':
|
||||||
|
rec.color = 6
|
||||||
|
else:
|
||||||
|
rec.color = 0
|
||||||
|
|
||||||
def action_create_late_request(self):
|
def action_create_late_request(self):
|
||||||
|
|
||||||
|
|
@ -216,8 +220,6 @@ class AttendanceAnalytics(models.Model):
|
||||||
self.is_compensation_pending,
|
self.is_compensation_pending,
|
||||||
'compensation_minutes':
|
'compensation_minutes':
|
||||||
self.early_out_minutes,
|
self.early_out_minutes,
|
||||||
'status_message':
|
|
||||||
self.status_message,
|
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
|
|
@ -228,6 +230,71 @@ class AttendanceAnalytics(models.Model):
|
||||||
'target': 'current',
|
'target': 'current',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def action_create_ot_request(self):
|
||||||
|
self.ensure_one()
|
||||||
|
request = self.env['overtime.request'].search([
|
||||||
|
('employee_id', '=', self.employee_id.id),
|
||||||
|
('attendance_date', '=', self.date)
|
||||||
|
], limit=1)
|
||||||
|
if not request:
|
||||||
|
request = self.env['overtime.request'].create({
|
||||||
|
'employee_id':
|
||||||
|
self.employee_id.id,
|
||||||
|
'attendance_date':
|
||||||
|
self.date,
|
||||||
|
'check_in':
|
||||||
|
self.min_check_in,
|
||||||
|
'check_out':
|
||||||
|
self.max_check_out,
|
||||||
|
'worked_hours':
|
||||||
|
self.worked_hours,
|
||||||
|
'hours_per_day':
|
||||||
|
self.hours_per_day,
|
||||||
|
'allowed_ot_limit':
|
||||||
|
self.allowed_ot_limit,
|
||||||
|
'overtime_hours':
|
||||||
|
self.overtime_hours,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': 'OT Request',
|
||||||
|
'res_model': 'overtime.request',
|
||||||
|
'res_id': request.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'current',
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_create_shiftswap_request(self):
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
request = self.env['shift.swap.request'].search([
|
||||||
|
('employee_id', '=', self.employee_id.id),
|
||||||
|
('roster_date', '=', self.date),
|
||||||
|
('state', '!=', 'approved')
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
|
||||||
|
self.ensure_one()
|
||||||
|
roster_form = self.env.ref('roster_management.view_shift_swap_form')
|
||||||
|
if not request:
|
||||||
|
request = self.env['shift.swap.request'].create({
|
||||||
|
'employee_id': self.employee_id.id,
|
||||||
|
'roster_date': self.date,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': 'Shift Swap Request',
|
||||||
|
'res_model': 'shift.swap.request',
|
||||||
|
'res_id': request.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'view_id': roster_form.id,
|
||||||
|
'target': 'current',
|
||||||
|
'context': {
|
||||||
|
'edit': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
|
|
||||||
tools.drop_view_if_exists(
|
tools.drop_view_if_exists(
|
||||||
|
|
@ -318,6 +385,23 @@ class AttendanceAnalytics(models.Model):
|
||||||
|
|
||||||
WHERE state = 'approved'
|
WHERE state = 'approved'
|
||||||
),
|
),
|
||||||
|
|
||||||
|
ot_requests AS (
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
|
||||||
|
id,
|
||||||
|
|
||||||
|
employee_id,
|
||||||
|
|
||||||
|
attendance_date,
|
||||||
|
|
||||||
|
state
|
||||||
|
|
||||||
|
FROM overtime_request
|
||||||
|
|
||||||
|
WHERE state = 'approved'
|
||||||
|
),
|
||||||
|
|
||||||
holiday_data AS (
|
holiday_data AS (
|
||||||
|
|
||||||
|
|
@ -343,45 +427,73 @@ class AttendanceAnalytics(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
|
|
||||||
row_number() OVER() AS id,
|
row_number() OVER() AS id,
|
||||||
|
|
||||||
ed.employee_id,
|
ed.employee_id,
|
||||||
|
|
||||||
ed.department_id,
|
ed.department_id,
|
||||||
|
rc.id AS shift_id,
|
||||||
|
rc.name AS shift_name,
|
||||||
ed.date,
|
ed.date,
|
||||||
|
|
||||||
ed.date AS date_end,
|
ed.date AS date_end,
|
||||||
|
|
||||||
ats.min_check_in,
|
ats.min_check_in,
|
||||||
|
|
||||||
ats.max_check_out,
|
ats.max_check_out,
|
||||||
|
|
||||||
COALESCE(
|
COALESCE(
|
||||||
ats.worked_hours,
|
ats.worked_hours,
|
||||||
0
|
0
|
||||||
) AS worked_hours,
|
) AS worked_hours,
|
||||||
CASE
|
rc.hours_per_day AS hours_per_day,
|
||||||
WHEN ats.min_check_in IS NOT NULL
|
|
||||||
AND ats.max_check_out IS NOT NULL
|
|
||||||
THEN (
|
|
||||||
EXTRACT(
|
|
||||||
EPOCH FROM (
|
|
||||||
ats.max_check_out -
|
|
||||||
ats.min_check_in
|
|
||||||
)
|
|
||||||
) / 3600
|
|
||||||
) - ats.worked_hours
|
|
||||||
ELSE 0
|
|
||||||
END AS out_time,
|
|
||||||
|
|
||||||
CASE
|
|
||||||
WHEN ats.worked_hours > 9
|
|
||||||
THEN ats.worked_hours - 9
|
|
||||||
ELSE 0
|
|
||||||
END AS compensated_time,
|
|
||||||
|
|
||||||
|
(
|
||||||
|
rc.hours_per_day
|
||||||
|
+
|
||||||
|
COALESCE(rc.over_time_hrs, 0)
|
||||||
|
) AS allowed_ot_limit,
|
||||||
|
|
||||||
|
CASE
|
||||||
|
|
||||||
|
WHEN ats.worked_hours > rc.hours_per_day
|
||||||
|
|
||||||
|
THEN
|
||||||
|
ats.worked_hours - rc.hours_per_day
|
||||||
|
|
||||||
|
ELSE 0
|
||||||
|
|
||||||
|
END AS overtime_hours,
|
||||||
|
|
||||||
|
CASE
|
||||||
|
|
||||||
|
WHEN ats.worked_hours >
|
||||||
|
|
||||||
|
(
|
||||||
|
rc.hours_per_day
|
||||||
|
+
|
||||||
|
COALESCE(rc.over_time_hrs, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
THEN TRUE
|
||||||
|
|
||||||
|
ELSE FALSE
|
||||||
|
|
||||||
|
END AS ot_eligible,
|
||||||
|
CASE
|
||||||
|
WHEN ats.min_check_in IS NOT NULL
|
||||||
|
AND ats.max_check_out IS NOT NULL
|
||||||
|
THEN (
|
||||||
|
EXTRACT(
|
||||||
|
EPOCH FROM (
|
||||||
|
ats.max_check_out -
|
||||||
|
ats.min_check_in
|
||||||
|
)
|
||||||
|
) / 3600
|
||||||
|
) - ats.worked_hours
|
||||||
|
ELSE 0
|
||||||
|
END AS out_time,
|
||||||
|
|
||||||
|
CASE
|
||||||
|
WHEN ats.worked_hours > 9
|
||||||
|
THEN ats.worked_hours - 9
|
||||||
|
ELSE 0
|
||||||
|
END AS compensated_time,
|
||||||
|
|
||||||
lr.id AS late_request_id,
|
lr.id AS late_request_id,
|
||||||
|
|
||||||
hd.holiday_name,
|
hd.holiday_name,
|
||||||
|
|
@ -796,104 +908,28 @@ END AS compensated_time,
|
||||||
THEN TRUE
|
THEN TRUE
|
||||||
ELSE FALSE
|
ELSE FALSE
|
||||||
END AS late_approved,
|
END AS late_approved,
|
||||||
|
|
||||||
CASE
|
CASE
|
||||||
|
|
||||||
WHEN hd.id IS NOT NULL
|
WHEN hd.id IS NOT NULL
|
||||||
THEN 'Holiday'
|
THEN 'holiday'
|
||||||
|
|
||||||
WHEN EXTRACT(
|
WHEN EXTRACT(
|
||||||
DOW FROM ed.date
|
DOW FROM ed.date
|
||||||
) IN (0,6)
|
) IN (0,6)
|
||||||
|
THEN 'week_off'
|
||||||
THEN 'Week Off'
|
|
||||||
|
|
||||||
WHEN leave.id IS NOT NULL
|
|
||||||
THEN 'Leave'
|
|
||||||
|
|
||||||
WHEN ats.min_check_in IS NULL
|
WHEN ats.min_check_in IS NULL
|
||||||
THEN 'Absent'
|
THEN 'absent'
|
||||||
|
|
||||||
WHEN
|
WHEN ats.worked_hours < (rc.hours_per_day / 2.0)
|
||||||
(
|
THEN 'half_day'
|
||||||
(
|
|
||||||
|
|
||||||
(
|
|
||||||
rc.shift_end_time * 60
|
|
||||||
)
|
|
||||||
|
|
||||||
+
|
|
||||||
|
|
||||||
CASE
|
|
||||||
|
|
||||||
WHEN ats.min_check_in
|
|
||||||
IS NOT NULL
|
|
||||||
|
|
||||||
THEN GREATEST(
|
|
||||||
|
|
||||||
(
|
|
||||||
(
|
|
||||||
EXTRACT(
|
|
||||||
HOUR
|
|
||||||
FROM ats.min_check_in
|
|
||||||
) * 60
|
|
||||||
)
|
|
||||||
+
|
|
||||||
EXTRACT(
|
|
||||||
MINUTE
|
|
||||||
FROM ats.min_check_in
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
-
|
|
||||||
|
|
||||||
(
|
|
||||||
(
|
|
||||||
rc.shift_start_time
|
|
||||||
* 60
|
|
||||||
)
|
|
||||||
+
|
|
||||||
COALESCE(
|
|
||||||
dg.grace_period,
|
|
||||||
rc.late_grace_period,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
ELSE 0
|
|
||||||
|
|
||||||
END
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
>
|
|
||||||
|
|
||||||
(
|
|
||||||
(
|
|
||||||
EXTRACT(
|
|
||||||
HOUR
|
|
||||||
FROM ats.max_check_out
|
|
||||||
) * 60
|
|
||||||
)
|
|
||||||
+
|
|
||||||
EXTRACT(
|
|
||||||
MINUTE
|
|
||||||
FROM ats.max_check_out
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
THEN 'Compensation Pending'
|
|
||||||
|
|
||||||
WHEN
|
WHEN
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
EXTRACT(
|
EXTRACT(
|
||||||
HOUR
|
HOUR FROM ats.min_check_in
|
||||||
FROM ats.min_check_in
|
|
||||||
) * 60
|
) * 60
|
||||||
)
|
)
|
||||||
+
|
+
|
||||||
|
|
@ -902,9 +938,9 @@ END AS compensated_time,
|
||||||
FROM ats.min_check_in
|
FROM ats.min_check_in
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
>
|
>
|
||||||
|
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
rc.shift_start_time * 60
|
rc.shift_start_time * 60
|
||||||
|
|
@ -916,23 +952,36 @@ END AS compensated_time,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
THEN 'Late'
|
THEN 'late_in'
|
||||||
|
|
||||||
ELSE 'Present'
|
WHEN
|
||||||
|
(
|
||||||
END AS status_message,
|
(
|
||||||
|
EXTRACT(
|
||||||
CASE
|
HOUR
|
||||||
WHEN leave.id IS NOT NULL
|
FROM ats.max_check_out
|
||||||
THEN 'leave'
|
) * 60
|
||||||
|
)
|
||||||
WHEN ats.min_check_in IS NOT NULL
|
+
|
||||||
THEN 'present'
|
EXTRACT(
|
||||||
|
MINUTE
|
||||||
ELSE 'no_info'
|
FROM ats.max_check_out
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
<
|
||||||
|
|
||||||
|
(
|
||||||
|
rc.shift_end_time * 60
|
||||||
|
)
|
||||||
|
|
||||||
|
THEN 'early_out'
|
||||||
|
|
||||||
|
ELSE 'present'
|
||||||
|
|
||||||
END AS status
|
END AS status
|
||||||
|
|
||||||
FROM employee_dates ed
|
FROM employee_dates ed
|
||||||
|
|
||||||
LEFT JOIN attendance_summary ats
|
LEFT JOIN attendance_summary ats
|
||||||
|
|
@ -949,6 +998,10 @@ END AS compensated_time,
|
||||||
LEFT JOIN late_requests lr
|
LEFT JOIN late_requests lr
|
||||||
ON lr.employee_id = ed.employee_id
|
ON lr.employee_id = ed.employee_id
|
||||||
AND lr.attendance_date = ed.date
|
AND lr.attendance_date = ed.date
|
||||||
|
|
||||||
|
LEFT JOIN ot_requests otr
|
||||||
|
ON otr.employee_id = ed.employee_id
|
||||||
|
AND otr.attendance_date = ed.date
|
||||||
|
|
||||||
LEFT JOIN holiday_data hd
|
LEFT JOIN holiday_data hd
|
||||||
ON ed.date BETWEEN
|
ON ed.date BETWEEN
|
||||||
|
|
@ -981,7 +1034,7 @@ END AS compensated_time,
|
||||||
|
|
||||||
('date', '=', today),
|
('date', '=', today),
|
||||||
|
|
||||||
('status', '=', 'no_info'),
|
('status', '=', 'absent'),
|
||||||
|
|
||||||
('is_holiday', '=', False),
|
('is_holiday', '=', False),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,17 @@ class HREmployee(models.Model):
|
||||||
string="Attendance Count",
|
string="Attendance Count",
|
||||||
compute="_compute_attendance_analytics_count"
|
compute="_compute_attendance_analytics_count"
|
||||||
)
|
)
|
||||||
|
attendance_mode = fields.Selection(
|
||||||
|
[
|
||||||
|
('office', 'Office Based'),
|
||||||
|
('remote', 'Remote'),
|
||||||
|
('hybrid', 'Hybrid'),
|
||||||
|
('shift', 'Shift Based'),
|
||||||
|
],
|
||||||
|
string='Attendance Mode',
|
||||||
|
default='office',
|
||||||
|
tracking=True,
|
||||||
|
)
|
||||||
|
|
||||||
def _compute_attendance_analytics_count(self):
|
def _compute_attendance_analytics_count(self):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
from odoo import fields, models, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class OvertimeRequest(models.Model):
|
||||||
|
_name = 'overtime.request'
|
||||||
|
_description = 'Overtime Request'
|
||||||
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
|
_rec_name = 'employee_id'
|
||||||
|
|
||||||
|
employee_id = fields.Many2one(
|
||||||
|
'hr.employee',
|
||||||
|
required=True,
|
||||||
|
tracking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
department_id = fields.Many2one(
|
||||||
|
'hr.department',
|
||||||
|
related='employee_id.department_id',
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
manager_id = fields.Many2one(
|
||||||
|
'hr.employee',
|
||||||
|
related='employee_id.parent_id',
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
attendance_date = fields.Date(
|
||||||
|
tracking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
check_in = fields.Datetime()
|
||||||
|
|
||||||
|
check_out = fields.Datetime()
|
||||||
|
|
||||||
|
worked_hours = fields.Float()
|
||||||
|
|
||||||
|
hours_per_day = fields.Float()
|
||||||
|
|
||||||
|
allowed_ot_limit = fields.Float()
|
||||||
|
|
||||||
|
overtime_hours = fields.Float(
|
||||||
|
tracking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
reason = fields.Text(
|
||||||
|
tracking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
state = fields.Selection([
|
||||||
|
|
||||||
|
('draft', 'Draft'),
|
||||||
|
|
||||||
|
('submitted', 'Submitted'),
|
||||||
|
|
||||||
|
('approved', 'Approved'),
|
||||||
|
|
||||||
|
('rejected', 'Rejected')
|
||||||
|
|
||||||
|
], default='draft', tracking=True)
|
||||||
|
|
||||||
|
def action_submit(self):
|
||||||
|
|
||||||
|
for rec in self:
|
||||||
|
|
||||||
|
if not rec.reason:
|
||||||
|
raise UserError(
|
||||||
|
_("Please enter reason.")
|
||||||
|
)
|
||||||
|
|
||||||
|
if not rec.manager_id:
|
||||||
|
raise UserError(
|
||||||
|
_("Manager not configured.")
|
||||||
|
)
|
||||||
|
|
||||||
|
if not rec.manager_id.work_email:
|
||||||
|
raise UserError(
|
||||||
|
_("Manager email not configured.")
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.state = 'submitted'
|
||||||
|
|
||||||
|
template = self.env.ref(
|
||||||
|
'grace_period.email_template_ot_request'
|
||||||
|
)
|
||||||
|
|
||||||
|
template.send_mail(
|
||||||
|
rec.id,
|
||||||
|
force_send=True
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.message_post(
|
||||||
|
body=_(
|
||||||
|
"OT Request Submitted"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_approve(self):
|
||||||
|
|
||||||
|
for rec in self:
|
||||||
|
|
||||||
|
if (
|
||||||
|
rec.manager_id.user_id != self.env.user
|
||||||
|
and not self.env.user.has_group(
|
||||||
|
'hr.group_hr_manager'
|
||||||
|
)
|
||||||
|
):
|
||||||
|
|
||||||
|
raise UserError(
|
||||||
|
_("Only Manager or HR can approve.")
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.state = 'approved'
|
||||||
|
|
||||||
|
rec.message_post(
|
||||||
|
body=_("Overtime Request Approved.")
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_reject(self):
|
||||||
|
|
||||||
|
for rec in self:
|
||||||
|
|
||||||
|
if (
|
||||||
|
rec.manager_id.user_id != self.env.user
|
||||||
|
and not self.env.user.has_group(
|
||||||
|
'hr.group_hr_manager'
|
||||||
|
)
|
||||||
|
):
|
||||||
|
|
||||||
|
raise UserError(
|
||||||
|
_("Only Manager or HR can reject.")
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.state = 'rejected'
|
||||||
|
|
||||||
|
template = self.env.ref(
|
||||||
|
'grace_period.email_template_ot_rejected'
|
||||||
|
)
|
||||||
|
|
||||||
|
template.send_mail(
|
||||||
|
rec.id,
|
||||||
|
force_send=True
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.message_post(
|
||||||
|
body=_("Overtime Request Rejected.")
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_reset_to_draft(self):
|
||||||
|
|
||||||
|
self.state = 'draft'
|
||||||
|
|
||||||
|
self.message_post(
|
||||||
|
body=_(
|
||||||
|
"Reset to Draft"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -24,6 +24,7 @@ class ResourceCalendar(models.Model):
|
||||||
shift_end_time = fields.Float(string="End Time")
|
shift_end_time = fields.Float(string="End Time")
|
||||||
late_grace_period = fields.Integer(default=15)
|
late_grace_period = fields.Integer(default=15)
|
||||||
early_out_grace_period = fields.Integer(default=0)
|
early_out_grace_period = fields.Integer(default=0)
|
||||||
|
over_time_hrs = fields.Float(default=2)
|
||||||
department_grace_ids = fields.One2many(
|
department_grace_ids = fields.One2many(
|
||||||
'resource.calendar.department.grace',
|
'resource.calendar.department.grace',
|
||||||
'calendar_id',
|
'calendar_id',
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,4 @@ access_attendance_analytics_manager,attendance.analytics.manager,model_attendanc
|
||||||
access_attendance_analytics_admin,attendance.analytics.admin,model_attendance_analytics,base.group_system,1,1,1,1
|
access_attendance_analytics_admin,attendance.analytics.admin,model_attendance_analytics,base.group_system,1,1,1,1
|
||||||
access_late_coming_request,attendance_late_coming_request,model_late_coming_request,base.group_user,1,1,1,1
|
access_late_coming_request,attendance_late_coming_request,model_late_coming_request,base.group_user,1,1,1,1
|
||||||
access_resource_calendar_department_grace,resource_calendar_department_grace,model_resource_calendar_department_grace,base.group_user,1,1,1,1
|
access_resource_calendar_department_grace,resource_calendar_department_grace,model_resource_calendar_department_grace,base.group_user,1,1,1,1
|
||||||
|
access_overtime_request,overtime_request,model_overtime_request,base.group_user,1,1,1,1
|
||||||
|
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<!-- =============================================== -->
|
<!-- =============================================== -->
|
||||||
<!-- TREE VIEW -->
|
<!-- TREE VIEW -->
|
||||||
<!-- =============================================== -->
|
<!-- =============================================== -->
|
||||||
|
|
||||||
<record id="view_attendance_analytics_list" model="ir.ui.view">
|
<record id="view_attendance_analytics_list" model="ir.ui.view">
|
||||||
<field name="name">attendance.analytics.list</field>
|
<field name="name">attendance.analytics.list</field>
|
||||||
<field name="model">attendance.analytics</field>
|
<field name="model">attendance.analytics</field>
|
||||||
|
|
@ -25,7 +22,15 @@
|
||||||
<field name="is_early_out"/>
|
<field name="is_early_out"/>
|
||||||
<field name="is_compensation_pending"/>
|
<field name="is_compensation_pending"/>
|
||||||
<field name="late_approved"/>
|
<field name="late_approved"/>
|
||||||
<field name="status_message"/>
|
<field name="shift_id"/>
|
||||||
|
<!-- <field name="shift_name"/>-->
|
||||||
|
<field name="status"
|
||||||
|
widget="badge"
|
||||||
|
decoration-success="status == 'present'"
|
||||||
|
decoration-danger="status == 'absent'"
|
||||||
|
decoration-warning="status == 'late_in'"
|
||||||
|
decoration-info="status == 'half_day'"
|
||||||
|
decoration-primary="status == 'holiday'"/>
|
||||||
<button name="action_create_late_request"
|
<button name="action_create_late_request"
|
||||||
type="object"
|
type="object"
|
||||||
string="Request Approval"
|
string="Request Approval"
|
||||||
|
|
@ -39,6 +44,15 @@
|
||||||
and early_out_minutes == 0
|
and early_out_minutes == 0
|
||||||
)
|
)
|
||||||
"/>
|
"/>
|
||||||
|
<button name="action_create_ot_request"
|
||||||
|
string="Apply OT"
|
||||||
|
type="object"
|
||||||
|
class="btn-warning"
|
||||||
|
invisible="ot_eligible == False"/>
|
||||||
|
<button name="action_create_shiftswap_request"
|
||||||
|
string="Swift Swap Request"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -60,7 +74,6 @@
|
||||||
event_open_popup="true">
|
event_open_popup="true">
|
||||||
<field name="display_label"/>
|
<field name="display_label"/>
|
||||||
<field name="employee_id"/>
|
<field name="employee_id"/>
|
||||||
<field name="status_message"/>
|
|
||||||
</calendar>
|
</calendar>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -104,6 +117,9 @@
|
||||||
<filter string="Date"
|
<filter string="Date"
|
||||||
name="group_date"
|
name="group_date"
|
||||||
context="{'group_by':'date'}"/>
|
context="{'group_by':'date'}"/>
|
||||||
|
<filter string="Status"
|
||||||
|
name="group_status"
|
||||||
|
context="{'group_by':'status'}"/>
|
||||||
</group>
|
</group>
|
||||||
</search>
|
</search>
|
||||||
</field>
|
</field>
|
||||||
|
|
@ -114,12 +130,13 @@
|
||||||
<field name="name">Attendance Analytics</field>
|
<field name="name">Attendance Analytics</field>
|
||||||
<field name="res_model">attendance.analytics</field>
|
<field name="res_model">attendance.analytics</field>
|
||||||
<field name="view_mode">list,calendar</field>
|
<field name="view_mode">list,calendar</field>
|
||||||
<field name="domain"> [ '|', ('employee_id.user_id', '=', uid), ('employee_id.parent_id.user_id', '=', uid) ] </field>
|
<field name="domain">[ '|', ('employee_id.user_id', '=', uid), ('employee_id.parent_id.user_id', '=', uid) ]
|
||||||
|
</field>
|
||||||
<field name="search_view_id" ref="view_attendance_analytics_search"/>
|
<field name="search_view_id" ref="view_attendance_analytics_search"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem id="menu_attendance_analytics"
|
<menuitem id="menu_attendance_analytics"
|
||||||
name="Attendance Report"
|
name="Attendance Data"
|
||||||
parent="hr_attendance.menu_hr_attendance_root"
|
parent="hr_attendance.menu_hr_attendance_root"
|
||||||
action="action_attendance_analytics"
|
action="action_attendance_analytics"
|
||||||
sequence="50"/>
|
sequence="50"/>
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,54 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
</field>
|
</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
|
||||||
|
<xpath expr="//field[@name='department_id']"
|
||||||
|
position="after">
|
||||||
|
|
||||||
|
<field name="attendance_mode"/>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
|
||||||
|
|
||||||
|
</record>
|
||||||
|
<record id="view_employee_filter_inherit_attendance_mode"
|
||||||
|
model="ir.ui.view">
|
||||||
|
|
||||||
|
<field name="name">
|
||||||
|
hr.employee.search.attendance.mode
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field name="model">hr.employee</field>
|
||||||
|
|
||||||
|
<field name="inherit_id"
|
||||||
|
ref="hr.view_employee_filter"/>
|
||||||
|
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
|
||||||
|
<xpath expr="//search" position="inside">
|
||||||
|
|
||||||
|
<filter string="Office"
|
||||||
|
name="office_employees"
|
||||||
|
domain="[('attendance_mode','=','office')]"/>
|
||||||
|
|
||||||
|
<filter string="Remote"
|
||||||
|
name="remote_employees"
|
||||||
|
domain="[('attendance_mode','=','remote')]"/>
|
||||||
|
|
||||||
|
<filter string="Hybrid"
|
||||||
|
name="hybrid_employees"
|
||||||
|
domain="[('attendance_mode','=','hybrid')]"/>
|
||||||
|
|
||||||
|
<filter string="Shift"
|
||||||
|
name="shift_employees"
|
||||||
|
domain="[('attendance_mode','=','shift')]"/>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,57 +54,29 @@
|
||||||
type="object"
|
type="object"
|
||||||
invisible="state != 'submitted'"
|
invisible="state != 'submitted'"
|
||||||
class="btn-danger"/>
|
class="btn-danger"/>
|
||||||
|
|
||||||
<field name="state"
|
<field name="state"
|
||||||
widget="statusbar"/>
|
widget="statusbar"/>
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<sheet>
|
<sheet>
|
||||||
|
|
||||||
<group>
|
<group>
|
||||||
|
|
||||||
<group>
|
<group>
|
||||||
|
<field name="employee_id"/>
|
||||||
<field name="employee_id"
|
<field name="manager_id"/>
|
||||||
readonly="1"/>
|
<field name="attendance_date"/>
|
||||||
|
<field name="worked_hours"/>
|
||||||
<field name="manager_id"
|
|
||||||
readonly="1"/>
|
|
||||||
|
|
||||||
<field name="attendance_date"
|
|
||||||
readonly="1"/>
|
|
||||||
|
|
||||||
<field name="worked_hours"
|
|
||||||
readonly="1"/>
|
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group>
|
<group>
|
||||||
|
<field name="late_minutes"/>
|
||||||
<field name="late_minutes"
|
<field name="compensation_minutes"/>
|
||||||
readonly="1"/>
|
<field name="department_grace_period"/>
|
||||||
|
<field name="required_checkout_time"/>
|
||||||
<field name="compensation_minutes"
|
|
||||||
readonly="1"/>
|
|
||||||
|
|
||||||
<field name="department_grace_period"
|
|
||||||
readonly="1"/>
|
|
||||||
|
|
||||||
<field name="required_checkout_time"
|
|
||||||
readonly="1"/>
|
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group>
|
<group>
|
||||||
|
|
||||||
<field name="status_message"
|
<field name="status_message"
|
||||||
readonly="1"/>
|
readonly="1"/>
|
||||||
|
|
||||||
<field name="reason"
|
<field name="reason"
|
||||||
placeholder="Enter reason here..."
|
placeholder="Enter reason here...Forgot punch/Client meeting/System issue/Travel/on-duty"
|
||||||
readonly="state in ('approved','rejected')"/>
|
readonly="state in ('approved','rejected')"/>
|
||||||
</group>
|
</group>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="email_template_ot_request" model="mail.template">
|
||||||
|
<field name="name">Overtime Request Submitted</field>
|
||||||
|
<field name="model_id" ref="model_overtime_request"/>
|
||||||
|
<field name="subject">Overtime Request - {{ object.employee_id.name }}</field>
|
||||||
|
<field name="email_from">{{ user.email }}</field>
|
||||||
|
<field name="email_to">{{ object.manager_id.work_email }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello,
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Employee
|
||||||
|
<strong>
|
||||||
|
<t t-out="object.employee_id.name"/>
|
||||||
|
</strong>
|
||||||
|
submitted an Overtime Request.
|
||||||
|
</p>
|
||||||
|
<table border="1"
|
||||||
|
cellpadding="5"
|
||||||
|
cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Date</strong>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<t t-out="object.attendance_date"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Worked Hours</strong>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<t t-out="object.worked_hours"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>OT Hours</strong>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<t t-out="object.overtime_hours"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
<strong>Reason:</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<t t-out="object.reason"/>
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
Please review the request.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="email_template_ot_rejected" model="mail.template">
|
||||||
|
<field name="name">Overtime Request Rejected</field>
|
||||||
|
<field name="model_id" ref="model_overtime_request"/>
|
||||||
|
<field name="subject">Overtime Request Rejected</field>
|
||||||
|
<field name="email_from">{{ user.email }}</field>
|
||||||
|
<field name="email_to">{{ object.employee_id.work_email }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
<strong>
|
||||||
|
<t t-out="object.employee_id.name"/>
|
||||||
|
</strong>
|
||||||
|
,
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Your Overtime Request for
|
||||||
|
<strong>
|
||||||
|
<t t-out="object.attendance_date"/>
|
||||||
|
</strong>
|
||||||
|
has been
|
||||||
|
<strong style="color:red;">
|
||||||
|
Rejected
|
||||||
|
</strong>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Manager:</strong>
|
||||||
|
<t t-out="object.manager_id.name"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>OT Hours:</strong>
|
||||||
|
|
||||||
|
<t t-out="object.overtime_hours"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Reason Submitted:</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<t t-out="object.reason"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Please contact your manager.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Thank You
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_overtime_request_form" model="ir.ui.view">
|
||||||
|
<field name="name">overtime.request.form</field>
|
||||||
|
<field name="model">overtime.request</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="OT Request">
|
||||||
|
<header>
|
||||||
|
<button name="action_submit"
|
||||||
|
string="Submit"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"
|
||||||
|
invisible="state != 'draft'"/>
|
||||||
|
|
||||||
|
<button name="action_approve"
|
||||||
|
string="Approve"
|
||||||
|
type="object"
|
||||||
|
class="btn-success"
|
||||||
|
invisible="state != 'submitted'"/>
|
||||||
|
|
||||||
|
<button name="action_reject"
|
||||||
|
string="Reject"
|
||||||
|
type="object"
|
||||||
|
class="btn-danger"
|
||||||
|
invisible="state != 'submitted'"/>
|
||||||
|
|
||||||
|
<button name="action_reset_to_draft"
|
||||||
|
string="Reset to Draft"
|
||||||
|
type="object"
|
||||||
|
invisible="state == 'draft'"/>
|
||||||
|
|
||||||
|
<field name="state"
|
||||||
|
widget="statusbar"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="department_id"/>
|
||||||
|
<field name="attendance_date"/>
|
||||||
|
<field name="worked_hours"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="hours_per_day"/>
|
||||||
|
<field name="allowed_ot_limit"/>
|
||||||
|
<field name="overtime_hours"/>
|
||||||
|
<field name="check_in"/>
|
||||||
|
<field name="check_out"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="reason"
|
||||||
|
placeholder="Enter OT Reason..."
|
||||||
|
readonly="state in ('approved','rejected')"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
|
||||||
|
<chatter/>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</record>
|
||||||
|
<record id="view_overtime_request_list" model="ir.ui.view">
|
||||||
|
<field name="name">overtime.request.list</field>
|
||||||
|
<field name="model">overtime.request</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="OT Request">
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="department_id"/>
|
||||||
|
<field name="attendance_date"/>
|
||||||
|
<field name="worked_hours"/>
|
||||||
|
<field name="hours_per_day"/>
|
||||||
|
<field name="overtime_hours"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_overtime_request" model="ir.actions.act_window">
|
||||||
|
<field name="name">Overtime Requests</field>
|
||||||
|
<field name="res_model">overtime.request</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="domain">
|
||||||
|
[
|
||||||
|
'|',
|
||||||
|
('employee_id.user_id', '=', uid),
|
||||||
|
('employee_id.parent_id.user_id', '=', uid)
|
||||||
|
]
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_overtime_request"
|
||||||
|
name="Overtime Requests"
|
||||||
|
parent="hr_attendance.menu_hr_attendance_root"
|
||||||
|
action="action_overtime_request"
|
||||||
|
sequence="35"/>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
<field name="late_grace_period"/>
|
<field name="late_grace_period"/>
|
||||||
|
|
||||||
<field name="early_out_grace_period"/>
|
<field name="early_out_grace_period"/>
|
||||||
|
<field name="over_time_hrs"/>
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
'name': 'Roster Management',
|
||||||
|
'version': '18.0.1.0.0',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'summary': 'Employee Shift and Roster Management',
|
||||||
|
'author': 'Srivyn Platforms',
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
'depends': [
|
||||||
|
'hr',
|
||||||
|
'mail',
|
||||||
|
'resource',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/shift_swap.xml',
|
||||||
|
'views/team_roster.xml',
|
||||||
|
'views/shift_swap_mail_template.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import shift_swap
|
||||||
|
from . import team_roster
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
from odoo import fields, models, api, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class ShiftSwapRequest(models.Model):
|
||||||
|
_name = 'shift.swap.request'
|
||||||
|
_inherit = [
|
||||||
|
'mail.thread',
|
||||||
|
'mail.activity.mixin'
|
||||||
|
]
|
||||||
|
|
||||||
|
employee_id = fields.Many2one('hr.employee')
|
||||||
|
shift_id = fields.Many2one(
|
||||||
|
'resource.calendar',
|
||||||
|
related='employee_id.resource_calendar_id',
|
||||||
|
string='Assigned Shift',
|
||||||
|
store=True,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
swap_employee_id = fields.Many2one(
|
||||||
|
'hr.employee'
|
||||||
|
)
|
||||||
|
roster_date = fields.Date()
|
||||||
|
reason = fields.Text()
|
||||||
|
state = fields.Selection([
|
||||||
|
('draft', 'Draft'),
|
||||||
|
('submitted', 'Submitted'),
|
||||||
|
('approved', 'Approved'),
|
||||||
|
('rejected', 'Rejected')
|
||||||
|
], default='draft')
|
||||||
|
manager_id = fields.Many2one(
|
||||||
|
'hr.employee',
|
||||||
|
related='employee_id.parent_id',
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_submit(self):
|
||||||
|
for rec in self:
|
||||||
|
|
||||||
|
if not rec.reason:
|
||||||
|
raise UserError(
|
||||||
|
_("Please enter reason.")
|
||||||
|
)
|
||||||
|
|
||||||
|
if not rec.manager_id:
|
||||||
|
raise UserError(
|
||||||
|
_("Manager not configured.")
|
||||||
|
)
|
||||||
|
|
||||||
|
if not rec.manager_id.work_email:
|
||||||
|
raise UserError(
|
||||||
|
_("Manager email not configured.")
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.state = 'submitted'
|
||||||
|
|
||||||
|
template = self.env.ref(
|
||||||
|
'roster_management.email_template_shift_swap_request'
|
||||||
|
)
|
||||||
|
|
||||||
|
template.send_mail(
|
||||||
|
rec.id,
|
||||||
|
force_send=True
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.message_post(
|
||||||
|
body=_(
|
||||||
|
"Shift swapping Request Submitted"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_reject(self):
|
||||||
|
for rec in self:
|
||||||
|
|
||||||
|
if (
|
||||||
|
rec.manager_id.user_id != self.env.user
|
||||||
|
and not self.env.user.has_group(
|
||||||
|
'hr.group_hr_manager'
|
||||||
|
)
|
||||||
|
):
|
||||||
|
raise UserError(
|
||||||
|
_("Only Manager or HR can reject.")
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.state = 'rejected'
|
||||||
|
|
||||||
|
template = self.env.ref(
|
||||||
|
'roster_management.email_template_shiftswap_rejected'
|
||||||
|
)
|
||||||
|
|
||||||
|
template.send_mail(
|
||||||
|
rec.id,
|
||||||
|
force_send=True
|
||||||
|
)
|
||||||
|
|
||||||
|
rec.message_post(
|
||||||
|
body=_("ShiftSwap Request Rejected.")
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_approve(self):
|
||||||
|
|
||||||
|
for rec in self:
|
||||||
|
|
||||||
|
if (
|
||||||
|
rec.manager_id.user_id != self.env.user
|
||||||
|
and not self.env.user.has_group(
|
||||||
|
'hr.group_hr_manager'
|
||||||
|
)
|
||||||
|
):
|
||||||
|
raise UserError(
|
||||||
|
_("Only Manager or HR can approve.")
|
||||||
|
)
|
||||||
|
|
||||||
|
roster1 = self.env[
|
||||||
|
'team.roster.line'
|
||||||
|
].search([
|
||||||
|
('employee_id', '=', rec.employee_id.id),
|
||||||
|
('roster_date', '=', rec.roster_date)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
roster2 = self.env[
|
||||||
|
'team.roster.line'
|
||||||
|
].search([
|
||||||
|
('employee_id', '=', rec.swap_employee_id.id),
|
||||||
|
('roster_date', '=', rec.roster_date)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if not roster1 or not roster2:
|
||||||
|
raise UserError(
|
||||||
|
_("Roster records not found for the selected date.")
|
||||||
|
)
|
||||||
|
|
||||||
|
shift = roster1.shift_id
|
||||||
|
|
||||||
|
roster1.shift_id = roster2.shift_id
|
||||||
|
|
||||||
|
roster2.shift_id = shift
|
||||||
|
|
||||||
|
rec.state = 'approved'
|
||||||
|
|
||||||
|
rec.message_post(
|
||||||
|
body=_("Shift Swap Request Approved.")
|
||||||
|
)
|
||||||
|
def action_reset_to_draft(self):
|
||||||
|
|
||||||
|
self.state = 'draft'
|
||||||
|
|
||||||
|
self.message_post(
|
||||||
|
body=_(
|
||||||
|
"Reset to Draft"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
from odoo import api, fields, models
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
|
class TeamRoster(models.Model):
|
||||||
|
_name = 'team.roster'
|
||||||
|
_description = 'Team Roster'
|
||||||
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = fields.Date(
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
end_date = fields.Date(
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
state = fields.Selection([
|
||||||
|
('draft', 'Draft'),
|
||||||
|
('generated', 'Generated'),
|
||||||
|
('approved', 'Approved')
|
||||||
|
], default='draft')
|
||||||
|
|
||||||
|
line_ids = fields.One2many(
|
||||||
|
'team.roster.line',
|
||||||
|
'roster_id'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def action_generate_roster(self):
|
||||||
|
|
||||||
|
shifts = self.env[
|
||||||
|
'resource.calendar'
|
||||||
|
].search([])
|
||||||
|
|
||||||
|
employees = self.env[
|
||||||
|
'hr.employee'
|
||||||
|
].search([
|
||||||
|
('active', '=', True)
|
||||||
|
])
|
||||||
|
|
||||||
|
if not shifts:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.line_ids.unlink()
|
||||||
|
|
||||||
|
shift_count = len(shifts)
|
||||||
|
|
||||||
|
current_date = self.start_date
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
while current_date <= self.end_date:
|
||||||
|
|
||||||
|
for emp in employees:
|
||||||
|
|
||||||
|
self.env[
|
||||||
|
'team.roster.line'
|
||||||
|
].create({
|
||||||
|
|
||||||
|
'roster_id': self.id,
|
||||||
|
|
||||||
|
'employee_id': emp.id,
|
||||||
|
|
||||||
|
'shift_id':
|
||||||
|
shifts[
|
||||||
|
counter %
|
||||||
|
shift_count
|
||||||
|
].id,
|
||||||
|
|
||||||
|
'roster_date':
|
||||||
|
current_date
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
current_date += timedelta(days=1)
|
||||||
|
|
||||||
|
self.state = 'generated'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TeamRosterLine(models.Model):
|
||||||
|
_name = 'team.roster.line'
|
||||||
|
_description = 'Team Roster Line'
|
||||||
|
|
||||||
|
roster_id = fields.Many2one(
|
||||||
|
'team.roster'
|
||||||
|
)
|
||||||
|
|
||||||
|
employee_id = fields.Many2one(
|
||||||
|
'hr.employee',
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
shift_id = fields.Many2one(
|
||||||
|
'resource.calendar',
|
||||||
|
required=True,
|
||||||
|
string="Shift"
|
||||||
|
)
|
||||||
|
|
||||||
|
roster_date = fields.Date(
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
department_id = fields.Many2one(
|
||||||
|
related='employee_id.department_id',
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.constrains(
|
||||||
|
'employee_id',
|
||||||
|
'roster_date'
|
||||||
|
)
|
||||||
|
def _check_duplicate(self):
|
||||||
|
|
||||||
|
for rec in self:
|
||||||
|
|
||||||
|
duplicate = self.search([
|
||||||
|
('employee_id', '=', rec.employee_id.id),
|
||||||
|
('roster_date', '=', rec.roster_date),
|
||||||
|
('id', '!=', rec.id)
|
||||||
|
])
|
||||||
|
|
||||||
|
if duplicate:
|
||||||
|
|
||||||
|
raise ValidationError(
|
||||||
|
"Employee already has a shift assigned."
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_team_roster,team.roster,model_team_roster,,1,1,1,1
|
||||||
|
access_team_roster_line,team.roster.line,model_team_roster_line,,1,1,1,1
|
||||||
|
access_shift_swap_request,shift.swap.request,model_shift_swap_request,,1,1,1,1
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_shift_swap_tree" model="ir.ui.view">
|
||||||
|
<field name="name">shift.swap.request.tree</field>
|
||||||
|
<field name="model">shift.swap.request</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="swap_employee_id"/>
|
||||||
|
<field name="roster_date"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="view_shift_swap_form" model="ir.ui.view">
|
||||||
|
<field name="name">shift.swap.request.form</field>
|
||||||
|
<field name="model">shift.swap.request</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<header>
|
||||||
|
<button name="action_submit"
|
||||||
|
string="Submit"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"
|
||||||
|
invisible="state != 'draft'"/>
|
||||||
|
<button name="action_approve"
|
||||||
|
string="Approve"
|
||||||
|
type="object"
|
||||||
|
class="btn-success"
|
||||||
|
invisible="state != 'submitted'"/>
|
||||||
|
<button name="action_reject"
|
||||||
|
string="Reject"
|
||||||
|
type="object"
|
||||||
|
class="btn-danger"
|
||||||
|
invisible="state != 'submitted'"/>
|
||||||
|
<field name="state"
|
||||||
|
widget="statusbar" force_save="1"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="shift_id" readonly="1"/>
|
||||||
|
<field name="swap_employee_id"
|
||||||
|
required="1" force_save="1"/>
|
||||||
|
<field name="roster_date"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="reason"
|
||||||
|
readonly="state in ('approved','rejected')"
|
||||||
|
required="1"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_shift_swap" model="ir.actions.act_window">
|
||||||
|
<field name="name">Shift Swap Request</field>
|
||||||
|
<field name="res_model">shift.swap.request</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="domain">
|
||||||
|
[
|
||||||
|
'|',
|
||||||
|
('employee_id.user_id', '=', uid),
|
||||||
|
('employee_id.parent_id.user_id', '=', uid)
|
||||||
|
]
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="email_template_shift_swap_request" model="mail.template">
|
||||||
|
<field name="name">ShiftSwap Request Submitted</field>
|
||||||
|
<field name="model_id" ref="model_shift_swap_request"/>
|
||||||
|
<field name="subject">ShiftSwap Request - {{ object.employee_id.name }}</field>
|
||||||
|
<field name="email_from">{{ user.email }}</field>
|
||||||
|
<field name="email_to">{{ object.manager_id.work_email }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello,
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Employee
|
||||||
|
<strong>
|
||||||
|
<t t-out="object.employee_id.name"/>
|
||||||
|
</strong>
|
||||||
|
submitted an ShiftSwap Request.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The Swap Employee is
|
||||||
|
<strong>
|
||||||
|
<t t-out="object.swap_employee_id.name"/>
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<table border="1"
|
||||||
|
cellpadding="5"
|
||||||
|
cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Date</strong>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<t t-out="object.roster_date"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
<strong>Reason:</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<t t-out="object.reason"/>
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
Please review the request.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="email_template_shiftswap_rejected" model="mail.template">
|
||||||
|
<field name="name">ShiftSwap Request Rejected</field>
|
||||||
|
<field name="model_id" ref="model_shift_swap_request"/>
|
||||||
|
<field name="subject">ShiftSwap Request Rejected</field>
|
||||||
|
<field name="email_from">{{ user.email }}</field>
|
||||||
|
<field name="email_to">{{ object.employee_id.work_email }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
<strong>
|
||||||
|
<t t-out="object.employee_id.name"/>
|
||||||
|
</strong>
|
||||||
|
,
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Your ShiftSwap Request for
|
||||||
|
<strong>
|
||||||
|
<t t-out="object.roster_date"/>
|
||||||
|
</strong>
|
||||||
|
has been
|
||||||
|
<strong style="color:red;">
|
||||||
|
Rejected
|
||||||
|
</strong>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Manager:</strong>
|
||||||
|
<t t-out="object.manager_id.name"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Reason Submitted:</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<t t-out="object.reason"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Please contact your manager.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Thank You
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_team_roster_tree" model="ir.ui.view">
|
||||||
|
<field name="name">team.roster.tree</field>
|
||||||
|
<field name="model">team.roster</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="start_date"/>
|
||||||
|
<field name="end_date"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="view_team_roster_form" model="ir.ui.view">
|
||||||
|
<field name="name">team.roster.form</field>
|
||||||
|
<field name="model">team.roster</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<header>
|
||||||
|
<button name="action_generate_roster"
|
||||||
|
string="Generate Roster"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"
|
||||||
|
invisible="state != 'draft'"/>
|
||||||
|
<!-- <button name="action_approve"-->
|
||||||
|
<!-- string="Approve"-->
|
||||||
|
<!-- type="object"-->
|
||||||
|
<!-- class="btn-success"-->
|
||||||
|
<!-- invisible="state != 'generated'"/>-->
|
||||||
|
<field name="state"
|
||||||
|
widget="statusbar"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="start_date"/>
|
||||||
|
<field name="end_date"/>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Roster Lines">
|
||||||
|
<field name="line_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="roster_date"/>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="shift_id"/>
|
||||||
|
<field name="department_id"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="view_team_roster_calendar" model="ir.ui.view">
|
||||||
|
<field name="name">team.roster.line.calendar</field>
|
||||||
|
<field name="model">team.roster.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<calendar
|
||||||
|
string="Roster Calendar"
|
||||||
|
date_start="roster_date"
|
||||||
|
color="employee_id">
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="shift_id"/>
|
||||||
|
</calendar>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_team_roster" model="ir.actions.act_window">
|
||||||
|
<field name="name">Team Rosters</field>
|
||||||
|
<field name="res_model">team.roster</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
<menuitem
|
||||||
|
id="menu_roster_root"
|
||||||
|
name="Roster Management"
|
||||||
|
sequence="50"/>
|
||||||
|
<menuitem
|
||||||
|
id="menu_team_roster"
|
||||||
|
name="Team Rosters"
|
||||||
|
parent="menu_roster_root"
|
||||||
|
action="action_team_roster"/>
|
||||||
|
<menuitem
|
||||||
|
id="menu_shift_swap"
|
||||||
|
name="Shift Swap Requests"
|
||||||
|
parent="menu_roster_root"
|
||||||
|
action="action_shift_swap"/>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue