Files
Odoo-Modules/fusion_clock/models/clock_leave_request.py
gsinghpal 6a9c7c74ea feat(fusion_clock): multi-day leave requests (date range)
Request Leave now takes a From/To date range instead of a single day (the To
field is optional -> single-day). Added date_to to fusion.clock.leave.request
(start kept as leave_date), with overlap detection on submit and a date_to >=
leave_date constraint. The absence check and reports now treat a leave as
covering its whole span. The form shows two date inputs; the controller accepts
date_from/date_to (the old single leave_date payload is still honoured). A
migration backfills date_to = leave_date for existing rows.

Live and verified on entech 19.0.3.13.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 23:01:19 -04:00

138 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
import logging
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class FusionClockLeaveRequest(models.Model):
_name = 'fusion.clock.leave.request'
_description = 'Clock Leave Request'
_order = 'leave_date desc, id desc'
_rec_name = 'display_name'
_inherit = ['mail.thread']
employee_id = fields.Many2one(
'hr.employee',
string='Employee',
required=True,
index=True,
ondelete='cascade',
)
leave_date = fields.Date(
string='From Date',
required=True,
index=True,
)
date_to = fields.Date(
string='To Date',
index=True,
help="Last day of the leave (inclusive); equals the start date for a "
"single-day request.",
)
reason = fields.Text(
string='Reason',
required=True,
)
state = fields.Selection(
[
('auto_approved', 'Auto-Approved'),
('reviewed', 'Reviewed'),
],
string='Status',
default='auto_approved',
tracking=True,
)
created_from = fields.Selection(
[
('portal', 'Portal'),
('backend', 'Backend'),
],
string='Created From',
default='portal',
)
company_id = fields.Many2one(
'res.company',
string='Company',
related='employee_id.company_id',
store=True,
)
display_name = fields.Char(
compute='_compute_display_name',
store=True,
)
@api.depends('employee_id', 'leave_date', 'date_to')
def _compute_display_name(self):
for rec in self:
emp = rec.employee_id.name or ''
rec.display_name = f"{emp} - Leave ({rec._fclk_date_label()})"
def _fclk_date_label(self):
"""Human label for the leave period: a single date, or 'from to to'."""
self.ensure_one()
if not self.leave_date:
return ''
if self.date_to and self.date_to != self.leave_date:
return f"{self.leave_date} to {self.date_to}"
return str(self.leave_date)
@api.constrains('leave_date', 'date_to')
def _check_leave_dates(self):
for rec in self:
if rec.date_to and rec.leave_date and rec.date_to < rec.leave_date:
raise ValidationError(_("The end date cannot be before the start date."))
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get('date_to') and vals.get('leave_date'):
vals['date_to'] = vals['leave_date']
records = super().create(vals_list)
for rec in records:
rec._notify_office_user()
rec._create_activity_log()
return records
def _notify_office_user(self):
"""Schedule a mail.activity for the office user."""
ICP = self.env['ir.config_parameter'].sudo()
office_user_id = int(ICP.get_param('fusion_clock.office_user_id', '0'))
if not office_user_id:
return
office_user = self.env['res.users'].sudo().browse(office_user_id)
if not office_user.exists():
return
try:
self.env['mail.activity'].sudo().create({
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
'summary': f"Leave Request: {self.employee_id.name} ({self._fclk_date_label()})",
'note': f"Reason: {self.reason}",
'user_id': office_user.id,
'res_model_id': self.env['ir.model']._get_id('fusion.clock.leave.request'),
'res_id': self.id,
'date_deadline': self.leave_date,
})
except Exception as e:
_logger.error("Fusion Clock: Failed to create leave activity: %s", e)
def _create_activity_log(self):
"""Log the leave request in the activity log."""
try:
self.env['fusion.clock.activity.log'].sudo().create({
'employee_id': self.employee_id.id,
'log_type': 'leave_request',
'description': f"Leave requested for {self._fclk_date_label()}: {self.reason}",
'source': 'portal' if self.created_from == 'portal' else 'system',
})
except Exception as e:
_logger.error("Fusion Clock: Failed to create leave activity log: %s", e)
def action_mark_reviewed(self):
"""Mark the leave request as reviewed by the office user."""
self.write({'state': 'reviewed'})