Files
Odoo-Modules/fusion_planning/controllers/portal_schedule.py
gsinghpal 19c1cbdf15 feat(fusion_planning): new module bridging fusion_clock with Odoo Planning
Adds a 'My Schedule' tab to the Fusion Clock portal that lists the current
employee's published planning.slot records, grouped by day. Reuses the
fusion_clock dark theme and reuses Odoo Planning's stock backend UI
(Gantt, send wizard, recurrence) unchanged.

- Controller /my/clock/schedule: pulls published slots in next 60 days
- Portal template with next-shift hero card, summary stats, grouped list
- Bottom-nav xpath inherits target the nav bar specifically (not the
  Recent Activity 'View All' link, which also linked to /my/clock/timesheets)
- 4-tab nav fits via reduced padding and flex sizing

Module depends on stock 'planning' (Enterprise) + fusion_clock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:16:02 -04:00

98 lines
3.5 KiB
Python

# -*- 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)