# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) import logging from collections import OrderedDict from datetime import timedelta import pytz from odoo import http, fields from odoo.http import request _logger = logging.getLogger(__name__) class FusionPlanningPortal(http.Controller): """Portal controller exposing the employee's published Planning shifts.""" @http.route('/my/clock/schedule', type='http', auth='user', website=True) def portal_schedule(self, **kw): employee = request.env.user.employee_id if not employee: return request.redirect('/my') tz_name = employee.tz or request.env.user.tz or 'UTC' try: local_tz = pytz.timezone(tz_name) except pytz.UnknownTimeZoneError: local_tz = pytz.UTC now_utc = fields.Datetime.now() horizon_utc = now_utc + timedelta(days=60) Slot = request.env['planning.slot'].sudo() domain = [ ('state', '=', 'published'), ('end_datetime', '>=', now_utc), ('start_datetime', '<=', horizon_utc), ] if employee.resource_id: domain.append(('resource_id', '=', employee.resource_id.id)) else: domain.append(('resource_id', '=', -1)) slots = Slot.search(domain, order='start_datetime asc', limit=200) groups = OrderedDict() today_local = fields.Datetime.context_timestamp( request.env.user, now_utc ).date() for slot in slots: local_start = pytz.UTC.localize(slot.start_datetime).astimezone(local_tz) local_end = pytz.UTC.localize(slot.end_datetime).astimezone(local_tz) day = local_start.date() delta_days = (day - today_local).days if delta_days == 0: bucket_key = 'Today' elif delta_days == 1: bucket_key = 'Tomorrow' elif 0 <= delta_days <= 6: bucket_key = local_start.strftime('%A') else: bucket_key = local_start.strftime('%b %d') groups.setdefault(bucket_key, []).append({ 'slot': slot, 'day_label': local_start.strftime('%a').upper(), 'day_num': local_start.strftime('%d'), 'date_full': local_start.strftime('%b %d, %Y'), 'time_range': '%s - %s' % ( local_start.strftime('%I:%M %p').lstrip('0'), local_end.strftime('%I:%M %p').lstrip('0'), ), 'duration_hours': round(slot.allocated_hours or 0.0, 1), 'role_name': slot.role_id.name if slot.role_id else '', 'role_color': slot.role_id.color if slot.role_id else 0, 'note': slot.name or '', }) next_slot_data = None if slots: next_slot = slots[0] local_start = pytz.UTC.localize(next_slot.start_datetime).astimezone(local_tz) next_slot_data = { 'date': local_start.strftime('%a, %b %d'), 'time': local_start.strftime('%I:%M %p').lstrip('0'), 'role': next_slot.role_id.name if next_slot.role_id else '', } values = { 'employee': employee, 'groups': groups, 'slot_count': len(slots), 'next_slot': next_slot_data, 'page_name': 'fusion_clock_schedule', } return request.render('fusion_planning.portal_schedule_page', values)