These round-2 portal fixes (white-border wrapper neutralisation in portal_clock.css, and the Payslips nav tab on the fusion_planning Schedule page) were briefly bundled into a concurrent NFC commit that a parallel session then rebased, dropping them from main. They are deployed and verified on entech (fusion_clock 3.12.3 / fusion_planning 1.3.0); re-committing so git matches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
3.7 KiB
Python
101 lines
3.7 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',
|
|
# Match the other portal pages so the Payslips nav tab appears
|
|
# consistently when payroll is installed.
|
|
'show_payslips': 'hr.payslip' in request.env,
|
|
}
|
|
return request.render('fusion_planning.portal_schedule_page', values)
|