diff --git a/fusion_planning/__manifest__.py b/fusion_planning/__manifest__.py index 25f6c9cb..d1af8e54 100644 --- a/fusion_planning/__manifest__.py +++ b/fusion_planning/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Planning', - 'version': '19.0.1.4.0', + 'version': '19.0.1.5.0', 'category': 'Human Resources/Planning', 'summary': 'Fusion Clock bridge to Odoo Planning - employee schedule on the portal', 'description': """ diff --git a/fusion_planning/controllers/portal_schedule.py b/fusion_planning/controllers/portal_schedule.py index 2f257414..a15487d0 100644 --- a/fusion_planning/controllers/portal_schedule.py +++ b/fusion_planning/controllers/portal_schedule.py @@ -31,66 +31,103 @@ class FusionPlanningPortal(http.Controller): now_utc = fields.Datetime.now() horizon_utc = now_utc + timedelta(days=60) + today_local = fields.Datetime.context_timestamp(request.env.user, now_utc).date() + horizon_local = today_local + timedelta(days=60) + # Upcoming shifts come from BOTH sources: published Odoo Planning slots + # AND posted Fusion Clock shift-planner entries (the planner the team + # leads use day-to-day). Each is normalised to a + # (sort_key, date, display_dict) tuple so they merge + sort together. + entries = [] + + # 1) Published Odoo Planning slots. Slot = request.env['planning.slot'].sudo() - domain = [ + slot_domain = [ ('state', '=', 'published'), ('end_datetime', '>=', now_utc), ('start_datetime', '<=', horizon_utc), ] if employee.resource_id: - domain.append(('resource_id', '=', employee.resource_id.id)) + slot_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: + slot_domain.append(('resource_id', '=', -1)) + for slot in Slot.search(slot_domain, order='start_datetime asc', limit=200): 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() + entries.append(( + (local_start.date(), local_start.hour * 60 + local_start.minute), + local_start.date(), + { + '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 '', + }, + )) + + # 2) Posted Fusion Clock shift-planner schedule (local clock times). + Schedule = request.env['fusion.clock.schedule'].sudo() + for sch in Schedule.search([ + ('employee_id', '=', employee.id), + ('state', '=', 'posted'), + ('is_off', '=', False), + ('schedule_date', '>=', today_local), + ('schedule_date', '<=', horizon_local), + ], order='schedule_date asc', limit=200): + day = sch.schedule_date + entries.append(( + (day, int(round((sch.start_time or 0.0) * 60))), + day, + { + 'day_label': day.strftime('%a').upper(), + 'day_num': day.strftime('%d'), + 'date_full': day.strftime('%b %d, %Y'), + 'time_range': '%s - %s' % ( + Schedule.fclk_float_to_display(sch.start_time), + Schedule.fclk_float_to_display(sch.end_time), + ), + 'duration_hours': round(sch.planned_hours or 0.0, 1), + 'role_name': sch.shift_id.name if sch.shift_id else '', + 'role_color': 0, + 'note': sch.note or '', + }, + )) + + entries.sort(key=lambda e: e[0]) + + groups = OrderedDict() + for _key, day, item in entries: 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') + bucket_key = day.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 '', - }) + bucket_key = day.strftime('%b %d') + groups.setdefault(bucket_key, []).append(item) next_slot_data = None - if slots: - next_slot = slots[0] - local_start = pytz.UTC.localize(next_slot.start_datetime).astimezone(local_tz) + if entries: + first = entries[0][2] 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 '', + 'date': entries[0][1].strftime('%a, %b %d'), + 'time': first['time_range'].split(' - ')[0], + 'role': first['role_name'], } values = { 'employee': employee, 'groups': groups, - 'slot_count': len(slots), + 'slot_count': len(entries), 'next_slot': next_slot_data, 'page_name': 'fusion_clock_schedule', # Match the other portal pages so the Payslips nav tab appears