diff --git a/lib/screens/workforce/workforce_screen.dart b/lib/screens/workforce/workforce_screen.dart index 5e9d2f80..9453efcf 100644 --- a/lib/screens/workforce/workforce_screen.dart +++ b/lib/screens/workforce/workforce_screen.dart @@ -1529,6 +1529,18 @@ class _ScheduleGeneratorPanelState startMinute: 0, duration: const Duration(hours: 8), ); + // Weekend Saturday on_call: 5PM (17:00) to 8AM (08:00) next day = 15 hours + templates['on_call_saturday'] = _ShiftTemplate( + startHour: 17, + startMinute: 0, + duration: const Duration(hours: 15), + ); + // Weekend Sunday on_call: 5PM (17:00) to 7AM (07:00) next day = 14 hours + templates['on_call_sunday'] = _ShiftTemplate( + startHour: 17, + startMinute: 0, + duration: const Duration(hours: 14), + ); // Default normal shift (8am-5pm = 9 hours) templates['normal'] = _ShiftTemplate( startHour: 8, @@ -1684,14 +1696,7 @@ class _ScheduleGeneratorPanelState final pmUserId = itStaff.isEmpty ? null : itStaff[pmBaseIndex % itStaff.length].id; - final nextWeekPmUserId = itStaff.isEmpty - ? null - : itStaff[(pmBaseIndex + 1) % itStaff.length].id; final pmRelievers = _buildRelievers(pmBaseIndex, itStaff); - final nextWeekRelievers = itStaff.isEmpty - ? [] - : _buildRelievers((pmBaseIndex + 1) % itStaff.length, itStaff); - var weekendNormalOffset = 0; for ( var day = weekStart; @@ -1706,32 +1711,53 @@ class _ScheduleGeneratorPanelState final dayIsRamadan = isApproximateRamadan(day); if (isWeekend) { - // Weekend: only IT Staff get normal + on_call (rotating) - if (itStaff.isNotEmpty) { - final normalIndex = - (amBaseIndex + pmBaseIndex + weekendNormalOffset) % - itStaff.length; - _tryAddDraft( - draft, - existing, - templates, - 'normal', - itStaff[normalIndex].id, - day, - const [], - ); - weekendNormalOffset += 1; - } - if (nextWeekPmUserId != null) { - _tryAddDraft( - draft, - existing, - templates, - 'on_call', - nextWeekPmUserId, - day, - nextWeekRelievers, - ); + final isSaturday = day.weekday == DateTime.saturday; + if (isSaturday) { + // Saturday: AM person gets normal shift, PM person gets weekend on_call + if (amUserId != null) { + _tryAddDraft( + draft, + existing, + templates, + 'normal', + amUserId, + day, + const [], + ); + } + if (pmUserId != null) { + _tryAddDraft( + draft, + existing, + templates, + 'on_call_saturday', + pmUserId, + day, + pmRelievers, + ); + } + } else { + // Sunday: PM person gets both normal and weekend on_call + if (pmUserId != null) { + _tryAddDraft( + draft, + existing, + templates, + 'normal', + pmUserId, + day, + const [], + ); + _tryAddDraft( + draft, + existing, + templates, + 'on_call_sunday', + pmUserId, + day, + pmRelievers, + ); + } } } else { // Weekday: IT Staff rotate AM/PM/on_call diff --git a/supabase/migrations/20260309100000_add_weekend_oncall_shift_types.sql b/supabase/migrations/20260309100000_add_weekend_oncall_shift_types.sql new file mode 100644 index 00000000..4213a2ef --- /dev/null +++ b/supabase/migrations/20260309100000_add_weekend_oncall_shift_types.sql @@ -0,0 +1,115 @@ +-- Add weekend-specific on_call shift types for Saturday and Sunday +-- Saturday on_call: 5PM to 8AM (15 hours) +-- Sunday on_call: 5PM to 7AM (14 hours) + +-- Update duty_schedules.shift_type. +-- If enum-backed, add new enum values. +-- If CHECK-backed, replace the existing CHECK constraint with expanded values. +DO $$ +DECLARE + _is_enum boolean := false; + _con text; +BEGIN + SELECT (t.typtype = 'e') INTO _is_enum + FROM pg_attribute a + JOIN pg_type t ON t.oid = a.atttypid + WHERE a.attrelid = 'duty_schedules'::regclass + AND a.attname = 'shift_type' + AND NOT a.attisdropped; + + IF _is_enum THEN + BEGIN + ALTER TYPE shift_type ADD VALUE IF NOT EXISTS 'on_call_saturday'; + EXCEPTION WHEN undefined_object THEN + NULL; + END; + BEGIN + ALTER TYPE shift_type ADD VALUE IF NOT EXISTS 'on_call_sunday'; + EXCEPTION WHEN undefined_object THEN + NULL; + END; + ELSE + FOR _con IN + SELECT con.conname + FROM pg_constraint con + JOIN pg_attribute att + ON att.attrelid = con.conrelid + AND att.attnum = ANY(con.conkey) + WHERE con.conrelid = 'duty_schedules'::regclass + AND con.contype = 'c' + AND att.attname = 'shift_type' + LOOP + EXECUTE format('ALTER TABLE duty_schedules DROP CONSTRAINT %I', _con); + END LOOP; + + ALTER TABLE duty_schedules + ADD CONSTRAINT duty_schedules_shift_type_check + CHECK ( + shift_type IN ( + 'normal', + 'am', + 'pm', + 'on_call', + 'weekend', + 'overtime', + 'on_call_saturday', + 'on_call_sunday' + ) + ); + END IF; +END $$; + +-- Replace attendance_logs shift_type CHECK constraint regardless of current name. +DO $$ +DECLARE + _is_enum boolean := false; + _con text; +BEGIN + SELECT (t.typtype = 'e') INTO _is_enum + FROM pg_attribute a + JOIN pg_type t ON t.oid = a.atttypid + WHERE a.attrelid = 'attendance_logs'::regclass + AND a.attname = 'shift_type' + AND NOT a.attisdropped; + + IF _is_enum THEN + BEGIN + ALTER TYPE shift_type ADD VALUE IF NOT EXISTS 'on_call_saturday'; + EXCEPTION WHEN undefined_object THEN + NULL; + END; + BEGIN + ALTER TYPE shift_type ADD VALUE IF NOT EXISTS 'on_call_sunday'; + EXCEPTION WHEN undefined_object THEN + NULL; + END; + ELSE + FOR _con IN + SELECT con.conname + FROM pg_constraint con + JOIN pg_attribute att + ON att.attrelid = con.conrelid + AND att.attnum = ANY(con.conkey) + WHERE con.conrelid = 'attendance_logs'::regclass + AND con.contype = 'c' + AND att.attname = 'shift_type' + LOOP + EXECUTE format('ALTER TABLE attendance_logs DROP CONSTRAINT %I', _con); + END LOOP; + + ALTER TABLE attendance_logs + ADD CONSTRAINT attendance_logs_shift_type_check + CHECK ( + shift_type IN ( + 'normal', + 'am', + 'pm', + 'on_call', + 'weekend', + 'overtime', + 'on_call_saturday', + 'on_call_sunday' + ) + ); + END IF; +END $$;