odoo18/addons_extensions/grace_period/models/attendance_data.py

1003 lines
28 KiB
Python

from odoo import api, fields, models, tools
class AttendanceAnalytics(models.Model):
_name = 'attendance.analytics'
_description = 'Attendance Analytics'
_auto = False
_rec_name = 'employee_id'
_order = 'date desc'
employee_id = fields.Many2one(
'hr.employee',
string='Employee'
)
department_id = fields.Many2one(
'hr.department',
string='Department'
)
date = fields.Date()
date_end = fields.Date(
string="End Date"
)
min_check_in = fields.Datetime()
max_check_out = fields.Datetime()
worked_hours = fields.Float()
out_time = fields.Float()
expected_check_in = fields.Float()
expected_check_out = fields.Float()
required_checkout_time = fields.Float()
compensated_time = fields.Float()
department_grace_period = fields.Integer()
late_minutes = fields.Float()
early_out_minutes = fields.Float()
is_late = fields.Boolean(
string="Late"
)
is_early_out = fields.Boolean(
string="Early Out"
)
is_compensation_pending = fields.Boolean(
string="Compensation Pending"
)
late_time = fields.Char(
string="Late Time",
compute="_compute_late_time"
)
early_out_time = fields.Char(
string="Early Out Time",
compute="_compute_early_out_time"
)
holiday_name = fields.Char()
is_holiday = fields.Boolean()
is_week_off = fields.Boolean()
status_message = fields.Char(
string="Status"
)
display_label = fields.Char(
compute="_compute_display_label",
store=False
)
color = fields.Integer(
compute="_compute_color"
)
status = fields.Selection([
('present', 'Present'),
('leave', 'Leave'),
('no_info', 'No Information')
])
late_request_id = fields.Many2one(
'late.coming.request',
string="Late Request"
)
late_approved = fields.Boolean(
string="Late Approved"
)
@api.depends('late_minutes')
def _compute_late_time(self):
for rec in self:
total_minutes = int(rec.late_minutes or 0)
hours = total_minutes // 60
minutes = total_minutes % 60
rec.late_time = "%02d:%02d" % (
hours,
minutes
)
@api.depends('early_out_minutes')
def _compute_early_out_time(self):
for rec in self:
total_minutes = int(
rec.early_out_minutes or 0
)
hours = total_minutes // 60
minutes = total_minutes % 60
rec.early_out_time = "%02d:%02d" % (
hours,
minutes
)
@api.depends(
'status_message',
'late_time',
'early_out_time'
)
def _compute_display_label(self):
for rec in self:
lines = []
if rec.employee_id:
lines.append(rec.employee_id.name)
if rec.status_message:
lines.append(rec.status_message)
if rec.late_minutes:
lines.append(
f"Late: {rec.late_time}"
)
if rec.early_out_minutes:
lines.append(
f"Early Out: {rec.early_out_time}"
)
rec.display_label = "\n".join(lines)
@api.depends(
'is_holiday',
'is_week_off',
'status',
'is_compensation_pending',
'is_late'
)
def _compute_color(self):
for rec in self:
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
else:
rec.color = 8
def action_create_late_request(self):
self.ensure_one()
request = self.env['late.coming.request'].search([
('employee_id', '=', self.employee_id.id),
('attendance_date', '=', self.date)
], limit=1)
if not request:
request = self.env['late.coming.request'].create({
'employee_id': self.employee_id.id,
'attendance_date': self.date,
'check_in': self.min_check_in,
'late_minutes': self.late_minutes,
'department_grace_period':
self.department_grace_period,
'required_checkout_time':
self.required_checkout_time,
'compensation_pending':
self.is_compensation_pending,
'compensation_minutes':
self.early_out_minutes,
'status_message':
self.status_message,
})
return {
'type': 'ir.actions.act_window',
'name': 'Late Coming Request',
'res_model': 'late.coming.request',
'res_id': request.id,
'view_mode': 'form',
'target': 'current',
}
def init(self):
tools.drop_view_if_exists(
self.env.cr,
self._table
)
self.env.cr.execute("""
CREATE OR REPLACE VIEW attendance_analytics AS (
WITH employee_dates AS (
SELECT
emp.id AS employee_id,
emp.department_id,
emp.resource_calendar_id,
generate_series(
DATE(emp.create_date),
CURRENT_DATE,
interval '1 day'
)::date AS date
FROM hr_employee emp
WHERE emp.active = true
),
attendance_summary AS (
SELECT
att.employee_id,
DATE(att.check_in)
AS attendance_date,
MIN(att.check_in)
AS min_check_in,
MAX(att.check_out)
AS max_check_out,
SUM(att.worked_hours)
AS worked_hours
FROM hr_attendance att
GROUP BY
att.employee_id,
DATE(att.check_in)
),
department_grace AS (
SELECT
rcg.calendar_id,
rcg.department_id,
rcg.grace_period
FROM
resource_calendar_department_grace rcg
),
late_requests AS (
SELECT
id,
employee_id,
attendance_date,
state
FROM late_coming_request
WHERE state = 'approved'
),
holiday_data AS (
SELECT DISTINCT
rl.id,
rl.calendar_id,
rl.name AS holiday_name,
DATE(rl.date_from)
AS holiday_start,
DATE(rl.date_to)
AS holiday_end
FROM resource_calendar_leaves rl
WHERE rl.date_from IS NOT NULL
AND rl.date_to IS NOT NULL
AND rl.resource_id IS NULL
)
SELECT
row_number() OVER() AS id,
ed.employee_id,
ed.department_id,
ed.date,
ed.date AS date_end,
ats.min_check_in,
ats.max_check_out,
COALESCE(
ats.worked_hours,
0
) AS worked_hours,
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,
hd.holiday_name,
CASE
WHEN hd.id IS NOT NULL
THEN TRUE
ELSE FALSE
END AS is_holiday,
CASE
WHEN EXTRACT(
DOW FROM ed.date
) IN (0, 6)
THEN TRUE
ELSE FALSE
END AS is_week_off,
dg.grace_period
AS department_grace_period,
(
(
rc.shift_start_time * 60
)
+
COALESCE(
dg.grace_period,
rc.late_grace_period,
0
)
) AS expected_check_in,
(
rc.shift_end_time * 60
) AS expected_check_out,
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 AS late_minutes,
CASE
WHEN ats.min_check_in IS NOT NULL
AND
(
(
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
)
)
THEN TRUE
ELSE FALSE
END AS is_late,
(
(
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
) AS required_checkout_time,
CASE
WHEN ats.max_check_out
IS NOT NULL
THEN GREATEST(
(
(
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
)
),
0
)
ELSE 0
END AS early_out_minutes,
CASE
WHEN ats.max_check_out
IS NOT NULL
AND
(
(
EXTRACT(
HOUR
FROM ats.max_check_out
) * 60
)
+
EXTRACT(
MINUTE
FROM ats.max_check_out
)
)
<
(
(
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
)
THEN TRUE
ELSE FALSE
END AS is_early_out,
CASE
WHEN
(
(
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 TRUE
ELSE FALSE
END AS is_compensation_pending,
CASE
WHEN lr.state = 'approved'
THEN TRUE
ELSE FALSE
END AS late_approved,
CASE
WHEN hd.id IS NOT NULL
THEN 'Holiday'
WHEN EXTRACT(
DOW FROM ed.date
) IN (0,6)
THEN 'Week Off'
WHEN leave.id IS NOT NULL
THEN 'Leave'
WHEN ats.min_check_in IS NULL
THEN 'Absent'
WHEN
(
(
(
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
(
(
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
)
)
THEN 'Late'
ELSE 'Present'
END AS status_message,
CASE
WHEN leave.id IS NOT NULL
THEN 'leave'
WHEN ats.min_check_in IS NOT NULL
THEN 'present'
ELSE 'no_info'
END AS status
FROM employee_dates ed
LEFT JOIN attendance_summary ats
ON ats.employee_id = ed.employee_id
AND ats.attendance_date = ed.date
LEFT JOIN hr_leave leave
ON leave.employee_id = ed.employee_id
AND ed.date BETWEEN
leave.request_date_from
AND leave.request_date_to
AND leave.state = 'validate'
LEFT JOIN late_requests lr
ON lr.employee_id = ed.employee_id
AND lr.attendance_date = ed.date
LEFT JOIN holiday_data hd
ON ed.date BETWEEN
hd.holiday_start
AND hd.holiday_end
AND (
hd.calendar_id
= ed.resource_calendar_id
OR hd.calendar_id IS NULL
)
LEFT JOIN resource_calendar rc
ON rc.id = ed.resource_calendar_id
LEFT JOIN department_grace dg
ON dg.calendar_id
= ed.resource_calendar_id
AND dg.department_id
= ed.department_id
)
""")
def action_send_absent_mail(self):
today = fields.Date.today()
records = self.search([
('date', '=', today),
('status', '=', 'no_info'),
('is_holiday', '=', False),
('is_week_off', '=', False),
])
template = self.env.ref(
'grace_period.email_template_absent_alert'
)
for rec in records:
if rec.employee_id.work_email:
template.send_mail(
rec.id,
force_send=True
)