From b5f643fb18c0fbeec39028e50ee5eceec52a9eb2 Mon Sep 17 00:00:00 2001 From: pranaysaidurga Date: Fri, 5 Jun 2026 16:42:23 +0530 Subject: [PATCH] attendance, leaves, weekly timesheets enhancements --- addons_extensions/grace_period/__init__.py | 1 + .../grace_period/__manifest__.py | 31 + .../data/resource_calendar_weekday_data.xml | 47 + .../grace_period/data/sequence.xml | 24 + .../grace_period/models/__init__.py | 4 + .../grace_period/models/attendance_data.py | 1002 +++++++++++++++++ .../models/hr_employee_inherit.py | 34 + .../models/late_coming_request.py | 182 +++ .../models/resource_calendar_period.py | 112 ++ .../grace_period/security/ir.model.access.csv | 7 + .../grace_period/security/record_rules.xml | 154 +++ .../grace_period/views/attendance_data.xml | 127 +++ .../views/hr_employee_inherit.xml | 37 + .../views/late_coming_mail_template.xml | 447 ++++++++ .../views/late_coming_request.xml | 134 +++ .../views/resource_calendar_period.xml | 85 ++ .../__manifest__.py | 3 + .../data/project_task_data.xml | 2 + .../models/__init__.py | 3 +- .../models/hr_leave.py | 69 +- .../models/hr_leave_allocation.py | 106 ++ .../views/hr_leave_allocation.xml | 32 + .../hr_leave_allocation_mail_template.xml | 92 ++ .../views/hr_leave_type.xml | 35 + .../views/hr_leave_views.xml | 5 +- .../weekly_timesheets/__manifest__.py | 1 + .../models/weekly_period_timesheets.py | 9 + .../weekly_timesheets/views/mail_template.xml | 59 + 28 files changed, 2827 insertions(+), 17 deletions(-) create mode 100644 addons_extensions/grace_period/__init__.py create mode 100644 addons_extensions/grace_period/__manifest__.py create mode 100644 addons_extensions/grace_period/data/resource_calendar_weekday_data.xml create mode 100644 addons_extensions/grace_period/data/sequence.xml create mode 100644 addons_extensions/grace_period/models/__init__.py create mode 100644 addons_extensions/grace_period/models/attendance_data.py create mode 100644 addons_extensions/grace_period/models/hr_employee_inherit.py create mode 100644 addons_extensions/grace_period/models/late_coming_request.py create mode 100644 addons_extensions/grace_period/models/resource_calendar_period.py create mode 100644 addons_extensions/grace_period/security/ir.model.access.csv create mode 100644 addons_extensions/grace_period/security/record_rules.xml create mode 100644 addons_extensions/grace_period/views/attendance_data.xml create mode 100644 addons_extensions/grace_period/views/hr_employee_inherit.xml create mode 100644 addons_extensions/grace_period/views/late_coming_mail_template.xml create mode 100644 addons_extensions/grace_period/views/late_coming_request.xml create mode 100644 addons_extensions/grace_period/views/resource_calendar_period.xml create mode 100644 addons_extensions/leaves_timesheets_extended/models/hr_leave_allocation.py create mode 100644 addons_extensions/leaves_timesheets_extended/views/hr_leave_allocation.xml create mode 100644 addons_extensions/leaves_timesheets_extended/views/hr_leave_allocation_mail_template.xml create mode 100644 addons_extensions/leaves_timesheets_extended/views/hr_leave_type.xml create mode 100644 addons_extensions/weekly_timesheets/views/mail_template.xml diff --git a/addons_extensions/grace_period/__init__.py b/addons_extensions/grace_period/__init__.py new file mode 100644 index 000000000..9a7e03ede --- /dev/null +++ b/addons_extensions/grace_period/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/addons_extensions/grace_period/__manifest__.py b/addons_extensions/grace_period/__manifest__.py new file mode 100644 index 000000000..7251fbc6d --- /dev/null +++ b/addons_extensions/grace_period/__manifest__.py @@ -0,0 +1,31 @@ +{ + 'name': 'Grace period Attendance', + 'version': '18.0.1.0.0', + 'category': 'Human Resources', + 'summary': 'Attendances grace Period', + 'author': 'Srivyn Platforms', + 'license': 'LGPL-3', + + 'depends': [ + 'hr', + 'hr_attendance', + 'hr_holidays', + 'resource', + 'hr_attendance_extended', + ], + + 'data': [ + 'security/ir.model.access.csv', + 'data/resource_calendar_weekday_data.xml', + 'data/sequence.xml', + 'views/resource_calendar_period.xml', + 'views/attendance_data.xml', + 'views/late_coming_request.xml', + 'views/hr_employee_inherit.xml', + 'views/late_coming_mail_template.xml', + # 'security/record_rules.xml', + ], + + 'installable': True, + 'application': False, +} \ No newline at end of file diff --git a/addons_extensions/grace_period/data/resource_calendar_weekday_data.xml b/addons_extensions/grace_period/data/resource_calendar_weekday_data.xml new file mode 100644 index 000000000..c6fb2efb5 --- /dev/null +++ b/addons_extensions/grace_period/data/resource_calendar_weekday_data.xml @@ -0,0 +1,47 @@ + + + + + + Monday + 0 + + + + Tuesday + 1 + + + + Wednesday + 2 + + + + Thursday + 3 + + + + Friday + 4 + + + + Saturday + 5 + + + + Sunday + 6 + + + \ No newline at end of file diff --git a/addons_extensions/grace_period/data/sequence.xml b/addons_extensions/grace_period/data/sequence.xml new file mode 100644 index 000000000..955adb7f2 --- /dev/null +++ b/addons_extensions/grace_period/data/sequence.xml @@ -0,0 +1,24 @@ + + + + + + Late Coming Request + + + + late.coming.request + + + + LCR/ + + + + 5 + + + + + \ No newline at end of file diff --git a/addons_extensions/grace_period/models/__init__.py b/addons_extensions/grace_period/models/__init__.py new file mode 100644 index 000000000..c212941c1 --- /dev/null +++ b/addons_extensions/grace_period/models/__init__.py @@ -0,0 +1,4 @@ +from . import resource_calendar_period +from . import late_coming_request +from . import attendance_data +from. import hr_employee_inherit \ No newline at end of file diff --git a/addons_extensions/grace_period/models/attendance_data.py b/addons_extensions/grace_period/models/attendance_data.py new file mode 100644 index 000000000..c7a4d6372 --- /dev/null +++ b/addons_extensions/grace_period/models/attendance_data.py @@ -0,0 +1,1002 @@ +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 + ) diff --git a/addons_extensions/grace_period/models/hr_employee_inherit.py b/addons_extensions/grace_period/models/hr_employee_inherit.py new file mode 100644 index 000000000..6915b7e8b --- /dev/null +++ b/addons_extensions/grace_period/models/hr_employee_inherit.py @@ -0,0 +1,34 @@ +from odoo import fields, models + + +class HREmployee(models.Model): + _inherit = 'hr.employee' + + attendance_analytics_count = fields.Integer( + string="Attendance Count", + compute="_compute_attendance_analytics_count" + ) + + def _compute_attendance_analytics_count(self): + + analytics = self.env['attendance.analytics'] + + for rec in self: + rec.attendance_analytics_count = analytics.search_count([ + ('employee_id', '=', rec.id) + ]) + + def action_open_attendance_analytics(self): + + self.ensure_one() + + return { + 'type': 'ir.actions.act_window', + 'name': 'Attendance Sheet', + 'res_model': 'attendance.analytics', + 'view_mode': 'list,pivot,graph', + 'domain': [('employee_id', '=', self.id)], + 'context': { + 'search_default_employee_id': self.id + } + } \ No newline at end of file diff --git a/addons_extensions/grace_period/models/late_coming_request.py b/addons_extensions/grace_period/models/late_coming_request.py new file mode 100644 index 000000000..3ff72109f --- /dev/null +++ b/addons_extensions/grace_period/models/late_coming_request.py @@ -0,0 +1,182 @@ +from odoo import fields, models, _ +from odoo.exceptions import UserError + + +class LateComingRequest(models.Model): + _name = 'late.coming.request' + _description = 'Late Coming Request' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _rec_name = 'employee_id' + + + employee_id = fields.Many2one( + 'hr.employee', + string="Employee", + required=True + ) + + manager_id = fields.Many2one( + 'hr.employee', + related='employee_id.parent_id', + store=True, + string="Manager" + ) + + department_id = fields.Many2one( + 'hr.department', + related='employee_id.department_id', + store=True, + string="Department" + ) + + attendance_date = fields.Date( + string="Attendance Date" + ) + + check_in = fields.Datetime( + string="Check In" + ) + + worked_hours = fields.Float( + string="Worked Hours" + ) + + expected_check_in = fields.Float( + string="Expected Check In" + ) + + late_minutes = fields.Float( + string="Late Minutes" + ) + + department_grace_period = fields.Integer( + string="Department Grace Period" + ) + + required_checkout_time = fields.Float( + string="Required Checkout Time" + ) + + compensation_pending = fields.Boolean( + string="Compensation Pending" + ) + + compensation_minutes = fields.Float( + string="Compensation Minutes" + ) + + reason = fields.Text( + string="Reason" + ) + + manager_comment = fields.Text( + string="Manager Comment" + ) + + status_message = fields.Char( + string="Status" + ) + + state = fields.Selection([ + + ('draft', 'Draft'), + + ('submitted', 'Submitted'), + + ('approved', 'Approved'), + + ('rejected', 'Rejected') + + ], + string="Status", + default='draft' + ) + + 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 work email not configured.") + ) + + rec.state = 'submitted' + + template = self.env.ref( + 'grace_period.email_template_late_coming_request' + ) + + template.send_mail( + rec.id, + force_send=True + ) + rec.message_post( + body=_("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' + + 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.") + ) + + if not rec.employee_id.work_email: + + raise UserError( + _("Employee work email not configured.") + ) + + rec.state = 'rejected' + + template = self.env.ref( + 'grace_period.email_template_late_coming_rejected' + ) + + template.send_mail( + rec.id, + force_send=True + ) + rec.message_post( + body=_("Request Rejected.") + ) + + def action_reset_to_draft(self): + + self.state = 'draft' \ No newline at end of file diff --git a/addons_extensions/grace_period/models/resource_calendar_period.py b/addons_extensions/grace_period/models/resource_calendar_period.py new file mode 100644 index 000000000..c3b5c10d9 --- /dev/null +++ b/addons_extensions/grace_period/models/resource_calendar_period.py @@ -0,0 +1,112 @@ +from odoo import api, fields, models, tools + + +class ResourceCalendar(models.Model): + _inherit = 'resource.calendar' + + weekday_ids = fields.Many2many( + 'resource.calendar.weekday', + 'resource_calendar_weekday_rel', + 'calendar_id', + 'weekday_id', + string="Weekdays" + ) + + weekend_ids = fields.Many2many( + 'resource.calendar.weekday', + 'resource_calendar_weekend_rel', + 'calendar_id', + 'weekday_id', + string="Weekends" + ) + + shift_start_time = fields.Float(string="Start Time") + shift_end_time = fields.Float(string="End Time") + late_grace_period = fields.Integer(default=15) + early_out_grace_period = fields.Integer(default=0) + department_grace_ids = fields.One2many( + 'resource.calendar.department.grace', + 'calendar_id', + string="Department Grace Periods" + ) + + def action_generate_schedule(self): + for rec in self: + + attendance_commands = [(5, 0, 0)] + + added_days = [] + + for day in rec.weekday_ids: + + # Avoid duplicate weekdays + if day.code in added_days: + continue + + added_days.append(day.code) + + attendance_commands.append((0, 0, { + 'name': day.name, + 'dayofweek': day.code, + 'hour_from': rec.shift_start_time, + 'hour_to': rec.shift_end_time, + })) + + # Single write operation + rec.write({ + 'attendance_ids': attendance_commands + }) + + # ------------------------------------ + # Department Grace Period Generation + # ------------------------------------ + + department_commands = [(5, 0, 0)] + + departments = self.env['hr.department'].search([]) + + for dept in departments: + department_commands.append((0, 0, { + 'department_id': dept.id, + 'grace_period': 0, + })) + + rec.write({ + 'department_grace_ids': department_commands + }) +class ResourceCalendarWeekday(models.Model): + _name = 'resource.calendar.weekday' + _description = 'Weekday Master' + + name = fields.Char(required=True) + + code = fields.Selection([ + ('0', 'Monday'), + ('1', 'Tuesday'), + ('2', 'Wednesday'), + ('3', 'Thursday'), + ('4', 'Friday'), + ('5', 'Saturday'), + ('6', 'Sunday'), + ], required=True) + +class ResourceCalendarDepartmentGrace(models.Model): + _name = 'resource.calendar.department.grace' + _description = 'Department Wise Grace Period' + + calendar_id = fields.Many2one( + 'resource.calendar', + string="Working Schedule", + ondelete='cascade' + ) + + department_id = fields.Many2one( + 'hr.department', + string="Department", + required=True + ) + + grace_period = fields.Integer( + string="Grace Period (Minutes)", + required=True + ) \ No newline at end of file diff --git a/addons_extensions/grace_period/security/ir.model.access.csv b/addons_extensions/grace_period/security/ir.model.access.csv new file mode 100644 index 000000000..b72a540a6 --- /dev/null +++ b/addons_extensions/grace_period/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_resource_calendar_weekday, resource_calendar_weekday,model_resource_calendar_weekday,base.group_user,1,1,1,1 +access_attendance_analytics_user,attendance.analytics.user,model_attendance_analytics,base.group_user,1,0,0,0 +access_attendance_analytics_manager,attendance.analytics.manager,model_attendance_analytics,hr.group_hr_manager,1,1,1,0 +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_resource_calendar_department_grace,resource_calendar_department_grace,model_resource_calendar_department_grace,base.group_user,1,1,1,1 diff --git a/addons_extensions/grace_period/security/record_rules.xml b/addons_extensions/grace_period/security/record_rules.xml new file mode 100644 index 000000000..c6c98454c --- /dev/null +++ b/addons_extensions/grace_period/security/record_rules.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + Attendance Analytics - Employee Own Records + + + + + + [('employee_id.user_id', '=', user.id)] + + + + + + + + + + + + + + + Attendance Analytics - Manager Records + + + + + + + [ + '|', + + ('employee_id.user_id', '=', user.id), + + ('employee_id.parent_id.user_id', '=', user.id) + ] + + + + + + + + + + + + + + + + Attendance Analytics - Admin Full Access + + + + + + [(1, '=', 1)] + + + + + + + + + + + + + + + Late Coming Request Employee Rule + + + + + + [('employee_id.user_id', '=', user.id)] + + + + + + + + + Late Coming Request Manager Rule + + + + + + + [ + '|', + ('employee_id.user_id', '=', user.id), + ('employee_id.parent_id.user_id', '=', user.id) + ] + + + + + + + + + + + + + + + Late Coming Request HR Rule + + + + + + [(1, '=', 1)] + + + + + + + \ No newline at end of file diff --git a/addons_extensions/grace_period/views/attendance_data.xml b/addons_extensions/grace_period/views/attendance_data.xml new file mode 100644 index 000000000..2146a5ba8 --- /dev/null +++ b/addons_extensions/grace_period/views/attendance_data.xml @@ -0,0 +1,127 @@ + + + + + + + + + + attendance.analytics.list + attendance.analytics + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/addons_extensions/grace_period/views/late_coming_mail_template.xml b/addons_extensions/grace_period/views/late_coming_mail_template.xml new file mode 100644 index 000000000..f55ed61a6 --- /dev/null +++ b/addons_extensions/grace_period/views/late_coming_mail_template.xml @@ -0,0 +1,447 @@ + + + + + + Late Coming Request Submitted + + + + + + Late Coming Request - {{ object.employee_id.name }} + + + + {{ user.email }} + + + + {{ object.manager_id.work_email }} + + + + +
+ +

+ Hello + , +

+ +

+ Employee + + has submitted a Late Coming / Compensation Request. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Employee + + +
+ Department + + +
+ Date + + +
+ Check In + + +
+ Late Minutes + + +
+ Department Grace + + + Minutes +
+ Compensation Required + + Yes +
+ Pending Minutes + + +
+ Required Checkout Time + + +
+ Status + + +
+ +
+ +

+ Reason: +

+ +

+ +

+ +

+ No reason provided. +

+ +
+ +

+ Please review and take appropriate action. +

+ +
+ +

+ Thank You +

+ +
+ +
+ +
+ + + + Late Coming Request Rejected + + + + + + Your Late Coming Request was Rejected + + + + {{ user.email }} + + + + {{ object.employee_id.work_email }} + + + + +
+ +

+ Hello + , +

+ +

+ Your Late Coming / Compensation Request has been + + Rejected + . +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Employee + + +
+ Department + + +
+ Attendance Date + + +
+ Manager + + +
+ Late Minutes + + +
+ Department Grace + + + Minutes +
+ Compensation Required + + Yes +
+ Pending Minutes + + +
+ Required Checkout Time + + +
+ Status + + +
+ +
+ +

+ Reason Submitted: +

+ +

+ +

+ +

+ No reason provided. +

+ +
+ +

+ Please contact your manager or HR for further clarification. +

+ +
+ +

+ Thank You +

+ +
+ +
+ +
+ + + + + + Attendance Missing Alert + + + + + + Attendance Punch Missing + + + + {{ user.email }} + + + + {{ object.employee_id.work_email }} + + + + +
+ +

+ Hello + , +

+ +

+ Your attendance punch has not been recorded. +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ Employee + + +
+ Department + + +
+ Date + + +
+ Status + + Absent / No Attendance Recorded +
+ +
+ +

+ Please login your attendance immediately + or contact HR/Admin if already marked. +

+ +
+ +

+ Thank You +

+ +
+ +
+ +
+ + + + Send Absent Attendance Alert + + code + model.action_send_absent_mail() + 1 + days + True + + +
\ No newline at end of file diff --git a/addons_extensions/grace_period/views/late_coming_request.xml b/addons_extensions/grace_period/views/late_coming_request.xml new file mode 100644 index 000000000..f29cc537b --- /dev/null +++ b/addons_extensions/grace_period/views/late_coming_request.xml @@ -0,0 +1,134 @@ + + + + + + + + late.coming.request.list + late.coming.request + + + + + + + + + + + + + + + + + + late.coming.request.form + late.coming.request + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + Permission Requests + late.coming.request + list,form + + [ + '|', + ('employee_id.user_id', '=', uid), + ('employee_id.parent_id.user_id', '=', uid) + ] + + + + +
\ No newline at end of file diff --git a/addons_extensions/grace_period/views/resource_calendar_period.xml b/addons_extensions/grace_period/views/resource_calendar_period.xml new file mode 100644 index 000000000..b7618786e --- /dev/null +++ b/addons_extensions/grace_period/views/resource_calendar_period.xml @@ -0,0 +1,85 @@ + + + + + + + + resource.calendar.form.inherit.grace.period + + + resource.calendar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +