Administrator — Shift Management Documentation

Version: 1.0
Last updated: December 10, 2025

Table of Contents


1. Overview

Shift Management is the core administrative module for creating, managing and assigning work shifts in the DTR System. Administrators can:

  • Create and configure shift templates (time slots, break times, days)
  • Assign shifts to individual employees or teams
  • Track shift assignments and changes
  • Integrate with DTR (timekeeping) and scheduling systems
  • Monitor shift coverage and conflicts
  • Generate shift schedules and reports

Key files involved:

  • Admin views: resources/views/users/admin/shift/ (index, create)
  • Controller: App\Http\Controllers\Web\Admin\ShiftController.php
  • Service: App\Services\ShiftServices.php
  • Model: App\Models\Shift, App\Models\UserShift

2. Purpose & Scope

Purpose:

  • Centralized shift creation and management
  • Flexible shift assignment to employees
  • Prevent scheduling conflicts and overlaps
  • Track shift history and audit trail
  • Integrate with DTR expectations and payroll calculations
  • Enable fair shift distribution and coverage planning

Scope:

  • Create / read / update / delete shifts
  • Assign shifts to users (single or bulk)
  • Remove user shift assignments
  • Search and filter shifts
  • Track shift assignment history
  • Generate shift rosters and reports
  • Validate shift coverage

Out of scope:

  • Employee self-service shift bidding (documented separately)
  • Advanced AI-based scheduling optimization (documented separately)
  • Real-time shift swap marketplace (documented separately)

3. Access & Permissions

Required permissions:

  • view_shifts — view shift list and details
  • create_shifts — create new shifts
  • edit_shifts — modify existing shifts
  • delete_shifts — delete shift records
  • assign_shifts — assign shifts to users
  • view_shift_assignments — view user shift history

Recommended roles:

  • Admin — full CRUD, bulk operations, all assignments
  • Scheduler / HR Manager — create/edit/assign shifts, view reports
  • Department Lead — view shifts, assign to own team members
  • Manager — view team shifts and assignments
  • Employee — view assigned shifts (read-only)

Implementation note:

  • Enforce RBAC server-side via middleware or gate checks
  • UI hides management actions (create, edit, delete, assign) for non-admin roles
  • Use authorize() in controller methods

4. How to Access

Admin URLs:

  • Shifts index: /admin/shift
  • Create shift: /admin/shift/create
  • Update shift: /admin/shift/{id} (via AJAX or inline edit)
  • Assign shift to user: /admin/shift/{id}/assign (POST)
  • Remove user shift: /admin/shift/{id}/remove (POST)
  • Search shifts: /admin/shift/search (GET with filter param)

Browser requirements: Modern Chromium-based browsers, recent Safari/Firefox. Mobile viewing supported; recommend desktop for full shift management.


5. Interface Layout & Views

5.1 Index (Shifts List)

Purpose: Display all shifts with filtering, search and bulk actions.

View file: resources/views/users/admin/shift/index.blade.php

UI elements:

  • Search bar: filter by shift name, code, time range
  • Filters:
    • Status (active, inactive, archived)
    • Department / Team
    • Shift type (morning, evening, night, flexible)
    • Days (Monday, Tuesday, etc. or All days)
  • Table columns:
    • Shift Code / Name
    • Time Range (start – end)
    • Break Duration
    • Days Applied (e.g., Mon-Fri)
    • Users Assigned (count)
    • Status (badge)
    • Actions (View | Edit | Assign Users | Delete)
  • Pagination: 25 items per page (configurable)
  • Quick actions: Create Shift, Export, Bulk Assign

Key features:

  • Inline status badge (green=active, gray=inactive)
  • Quick action dropdown per row
  • Export shifts to CSV/Excel with assignments
  • Real-time search via AJAX (/admin/shift/search)
  • Visual shift timeline (optional, if implemented)

Controller method: ShiftController::index(ShiftServices $shiftServices)

public function index(ShiftServices $shiftServices)
{
    $shifts = $shiftServices->lists();
    return view('users.admin.shift.index', compact('shifts'));
}

5.2 Create Shift

Purpose: Register a new shift template.

View file: resources/views/users/admin/shift/create.blade.php

Form fields (via ShiftStoreRequest):

  • Basic info:

    • Shift Name (required, string, max 255) — e.g., "Morning Shift", "Night Shift"
    • Shift Code (required, unique, string) — e.g., "MOR-001", "EVE-001"
    • Description (optional, text) — shift purpose and details
  • Time Configuration:

    • Start Time (required, time picker, HH:MM format)
    • End Time (required, time picker, HH:MM format)
    • Validation: End Time must be after Start Time
    • Timezone (optional, dropdown) — for global companies
  • Break Configuration:

    • Include Break (checkbox)
    • Break Start Time (if break included)
    • Break End Time (if break included)
    • Break Duration (calculated, read-only)
  • Days Configuration:

    • Apply to Days (multi-select checkboxes):
      • ☑ Monday
      • ☑ Tuesday
      • ☑ Wednesday
      • ☑ Thursday
      • ☑ Friday
      • ☐ Saturday
      • ☐ Sunday
    • Or: "All Days" radio (applies to all 7 days)
  • Department & Assignment:

    • Department (optional, dropdown) — if shift limited to department
    • Position (optional, dropdown) — if shift limited to position type
    • Default Assigned Users (optional, multi-select) — pre-assign on creation
  • Settings:

    • Status (Active / Inactive, radio, default Active)
    • Rotation Type (Fixed / Rotating, radio) — if rotating, specify rotation schedule
    • Max Users (optional, number) — capacity limit for this shift
    • Priority (optional, number) — for scheduling algorithms

On submit (store method):

  1. Validate all inputs via ShiftStoreRequest
  2. Calculate total shift hours (end_time - start_time - break_duration)
  3. Create Shift record in DB
  4. If default users specified, assign shift to those users
  5. Create audit log entry
  6. Redirect to index with success message

Controller method:

public function store(ShiftStoreRequest $request, ShiftServices $shiftServices)
{
    $shiftServices->create($request);
    return back()->with(['message' => 'Shift Added']);
}

ShiftStoreRequest validation (expected):

namespace App\Http\Requests;

class ShiftStoreRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'code' => 'required|string|unique:shifts|max:50',
            'description' => 'nullable|string',
            'start_time' => 'required|date_format:H:i',
            'end_time' => 'required|date_format:H:i|after:start_time',
            'include_break' => 'nullable|boolean',
            'break_start' => 'nullable|date_format:H:i|required_if:include_break,1',
            'break_end' => 'nullable|date_format:H:i|required_if:include_break,1',
            'days' => 'required|array|min:1',
            'days.*' => 'in:monday,tuesday,wednesday,thursday,friday,saturday,sunday',
            'department_id' => 'nullable|exists:departments,id',
            'position_id' => 'nullable|exists:positions,id',
            'status' => 'required|in:active,inactive',
            'rotation_type' => 'nullable|in:fixed,rotating',
            'max_users' => 'nullable|integer|min:1',
        ];
    }
}

5.3 Shift Details & Management

Purpose: View complete shift info and manage user assignments.

UI sections:

  • Header: Shift name, code, status badge, created date
  • Time info:
    • Start Time: 08:00 AM
    • End Time: 05:00 PM
    • Total Duration: 8 hours
    • Break: 12:00 PM – 1:00 PM (1 hour)
  • Configuration:
    • Days Applied: Monday – Friday
    • Department: Sales (if limited)
    • Position: Any (or specific role)
    • Rotation Type: Fixed
    • Max Users: 10
    • Current Assigned: 7 / 10
  • Assigned Users Table:
    • Employee Name
    • Employee ID
    • Department
    • Assigned Date
    • Status (active/inactive)
    • Actions (Remove assignment, View history)
  • Actions:
    • Edit Shift (button)
    • Assign Users (button)
    • Delete Shift (button, if no active assignments)
    • Generate Roster (button)
  • Assignment History:
    • List of past assignments with dates
    • User who made assignment
    • Reason (if recorded)

6. Key Features

  • Flexible shift creation: Support fixed, rotating, and custom shifts
  • Time management: Define start/end times, breaks, duration calculations
  • Multi-day scheduling: Apply shift to specific days or all days
  • User assignment: Assign single or bulk to employees
  • Conflict detection: Warn of overlapping shift assignments
  • Department/position filtering: Limit shifts to specific groups
  • Audit trail: Log all shift changes and assignments
  • Search & filter: Find shifts by name, code, time, department
  • Bulk operations: Create, import, assign multiple shifts at once
  • Integration with DTR: Shifts feed into timekeeping expectations
  • Reports: Generate shift rosters, coverage analysis, utilization

7. Data Model & Database

Shift model fields:

id                 — Primary key
code               — Unique identifier (string, max 50)
name               — Display name (string, max 255)
description        — Optional details (text)
start_time         — Shift start (time)
end_time           — Shift end (time)
include_break      — Has break (boolean)
break_start        — Break start time (time, nullable)
break_end          — Break end time (time, nullable)
shift_hours        — Total hours (calculated, decimal)
days               — JSON array of days (["monday", "tuesday", ...])
department_id      — Foreign key to departments (nullable)
position_id        — Foreign key to positions (nullable)
rotation_type      — 'fixed' or 'rotating' (enum/string)
max_users          — Capacity limit (integer, nullable)
priority           — Scheduling priority (integer, nullable)
status             — 'active' or 'inactive' (enum/string)
created_by         — User ID of creator
created_at         — Timestamp
updated_at         — Timestamp
deleted_at         — Soft delete timestamp (nullable)

UserShift model fields (pivot/join table):

id                 — Primary key
user_id            — Foreign key to users
shift_id           — Foreign key to shifts
assigned_at        — Assignment timestamp
assigned_by        — User ID of assigner
assigned_reason    — Optional reason (text)
removed_at         — Removal timestamp (nullable)
removed_by         — User ID of remover (nullable)
removed_reason     — Optional reason (text, nullable)
status             — 'active', 'inactive', 'suspended' (enum)
created_at         — Timestamp
updated_at         — Timestamp

Sample records:

Shift:

id: 1
code: "MOR-001"
name: "Morning Shift"
description: "Standard morning shift for customer support"
start_time: "08:00"
end_time: "17:00"
include_break: true
break_start: "12:00"
break_end: "13:00"
shift_hours: 8.0
days: ["monday", "tuesday", "wednesday", "thursday", "friday"]
department_id: 2 (Support)
status: "active"

UserShift:

id: 1
user_id: 42 (Jane Doe)
shift_id: 1 (Morning Shift)
assigned_at: "2025-01-01 09:00:00"
assigned_by: 1 (Admin)
assigned_reason: "Regular assignment"
status: "active"

8. Shift Types & Configurations

Standard Shift Types:

Morning Shift (06:00 – 14:00 or 08:00 – 17:00)
├─ Typical break: 30 min – 1 hour (mid-morning or lunch)
├─ Days: Usually Monday – Friday
├─ Departments: All (support, sales, operations, etc.)
└─ Use case: Standard business hours coverage

Evening Shift (14:00 – 22:00 or 17:00 – 01:00)
├─ Typical break: 30 min – 1 hour (mid-shift)
├─ Days: Usually Monday – Friday
├─ Departments: Customer support, operations
└─ Use case: Extended coverage, evening operations

Night Shift (22:00 – 06:00 or 23:00 – 07:00)
├─ Typical break: 30 min – 1 hour
├─ Days: Usually Monday – Friday or rotates
├─ Departments: Support, monitoring, security
└─ Use case: 24/7 coverage, emergency response

Flexible Shift (Customizable)
├─ No fixed start/end time
├─ Hours negotiated per employee
├─ Days: Varies per assignment
└─ Use case: Part-time, hybrid, freelance roles

Rotating Shift (Cycles through schedules)
├─ Week 1: Morning
├─ Week 2: Evening
├─ Week 3: Night
├─ Repeat: Every 3 weeks (configurable)
└─ Use case: Fair coverage distribution

Weekend/Holiday Shifts
├─ Saturday/Sunday only
├─ Higher pay (typically, via payroll)
├─ Days: Saturday, Sunday, holidays
└─ Use case: Retail, hospitality, healthcare

Custom Shift Configuration Example:

Name: "Flex Customer Support"
Code: FLEX-CUST-001
Start Time: 08:00
End Time: 18:00
Break: 12:00 – 13:00 (1 hour)
Days: Monday – Friday (Mon, Tue, Wed, Thu, Fri)
Department: Customer Support
Position: Support Agent
Rotation: Fixed
Max Users: 12
Priority: Medium
Status: Active

Notes:
"Flexible shift for customer support team.
 Hours can be adjusted daily based on ticket volume.
 Break timing flexible within 12:00-14:00 window."

9. User Shift Assignment

9.1 Assigning Shifts to Users

Scenario: Assign "Morning Shift" to Jane Doe for 30 days.

Steps:

  1. Navigate: Admin → Shifts → Morning Shift → "Assign Users" button
  2. Opens modal/page: User assignment form
  3. Select user(s):
    • Search for Jane Doe by name/email
    • Or select from department list
    • Can select multiple users (bulk assign)
  4. Set dates:
    • Start Date: 2025-01-01
    • End Date: 2025-01-31
    • Or: "Ongoing" (indefinite)
  5. Add reason (optional):
    • "Regular January assignment"
    • "Replacement for John (on leave)"
  6. Click "Assign"

Server-side processing (via ShiftController::assignShift):

  1. Validate request: shift param required
  2. Call ShiftServices::assignUserShift($userId, $shiftId)
  3. Create UserShift record with:
    • user_id, shift_id, assigned_at, assigned_by, status='active'
  4. Call ShiftServices::selectShift($shiftId, $userId) (mark as selected)
  5. Validate no overlapping shifts (if enabled)
  6. Create audit log: "User Jane Doe assigned to Morning Shift by Admin"
  7. Send notification to Jane (email/app): "You've been assigned to Morning Shift"
  8. Redirect back with success message: "Shift Assign Success"

Controller method:

public function assignShift(Request $request, string $id, ShiftServices $shiftServices)
{
    $request->validate([
        'shift' => 'required'
    ]);

    $userShift = $shiftServices->assignUserShift($id, $request->shift);
    $shiftServices->selectShift($userShift->shift->id, $id);

    return back()->with([
        'message' => 'Shift Assign Success'
    ]);
}

9.2 Removing User Shifts

Scenario: Remove Jane Doe from "Morning Shift" (e.g., due to leave).

Steps:

  1. Open shift details
  2. Find Jane Doe in "Assigned Users" table
  3. Click "Remove" action
  4. Optional: Add removal reason
    • "Approved leave (Feb 1-14)"
    • "Promotion to different shift"
  5. Confirm removal

Server-side processing (via ShiftController::removeShift):

  1. Validate request: userShift (shift assignment ID) required
  2. Call ShiftServices::removeUserShift($userId, $userShiftId)
  3. Update UserShift record:
    • removed_at = now()
    • removed_by = auth()->id()
    • status = 'inactive' (soft remove) or delete record
  4. Create audit log: "User Jane Doe removed from Morning Shift by Admin"
  5. Send notification to Jane: "Your Morning Shift assignment has been removed"
  6. DTR expectations updated (if Jane assigned future shifts conflicting)
  7. Redirect back with success message: "Shift Remove Success"

Controller method:

public function removeShift(Request $request, string $id, ShiftServices $shiftServices)
{
    $shiftServices->removeUserShift($id, $request->userShift);

    return back()->with([
        'message' => 'Shift Remove Success'
    ]);
}

10. API Endpoints

GET /api/admin/shifts

  • Returns paginated list of all shifts
  • Query params: page, search, department_id, status
  • Response:
    {
        "data": [
            {
                "id": 1,
                "code": "MOR-001",
                "name": "Morning Shift",
                "start_time": "08:00",
                "end_time": "17:00",
                "shift_hours": 8.0,
                "days": [
                    "monday",
                    "tuesday",
                    "wednesday",
                    "thursday",
                    "friday"
                ],
                "department": "Support",
                "users_assigned": 7,
                "status": "active"
            }
        ],
        "pagination": { "total": 12, "per_page": 25, "current_page": 1 }
    }

GET /api/admin/shifts/{id}

  • Returns single shift with full details and assigned users
  • Response:
    {
        "id": 1,
        "code": "MOR-001",
        "name": "Morning Shift",
        "description": "Standard morning shift",
        "start_time": "08:00",
        "end_time": "17:00",
        "break_start": "12:00",
        "break_end": "13:00",
        "shift_hours": 8.0,
        "days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
        "department": "Support",
        "status": "active",
        "assigned_users": [
            {
                "id": 42,
                "name": "Jane Doe",
                "email": "jane@example.com",
                "assigned_at": "2025-01-01T00:00:00Z"
            }
        ]
    }

POST /api/admin/shifts

  • Create new shift
  • Body:
    {
        "name": "New Shift",
        "code": "NEW-001",
        "start_time": "09:00",
        "end_time": "18:00",
        "include_break": true,
        "break_start": "12:00",
        "break_end": "13:00",
        "days": ["monday", "tuesday", "wednesday"],
        "status": "active"
    }
  • Response: 201 Created + shift record

PUT /api/admin/shifts/{id}

  • Update shift
  • Body: (any of create fields)
  • Response: 200 OK + updated shift

DELETE /api/admin/shifts/{id}

  • Delete shift (soft delete)
  • Response: 200 OK

POST /api/admin/shifts/{id}/assign

  • Assign shift to user(s)
  • Body:
    {
        "user_ids": [42, 43, 44],
        "start_date": "2025-01-01",
        "end_date": "2025-01-31",
        "reason": "Regular assignment"
    }
  • Response: 200 OK + list of assignments created

POST /api/admin/shifts/{id}/remove

  • Remove user from shift
  • Body:
    {
        "user_shift_id": 123,
        "reason": "Leave approved"
    }
  • Response: 200 OK

GET /admin/shift/search

  • Search shifts (AJAX endpoint)
  • Query params: filter (search term)
  • Response: JSON list of matching shifts
  • Controller method:
public function search(Request $request, ShiftServices $shiftServices)
{
    $filter = $request->filter;
    $shifts = $shiftServices->get();

    if($filter) {
        $shifts = $shiftServices->search($filter);
    }

    return response([
        'shifts' => $shifts,
    ]);
}

11. Controller Implementation Details

11.1 ShiftController Methods

Location: app/Http/Controllers/Web/Admin/ShiftController.php

index() — List all shifts

public function index(ShiftServices $shiftServices)
{
    $shifts = $shiftServices->lists();
    return view('users.admin.shift.index', compact('shifts'));
}

create() — Show create form

public function create()
{
    return view('users.admin.shift.create');
}

store() — Save new shift

public function store(ShiftStoreRequest $request, ShiftServices $shiftServices)
{
    $shiftServices->create($request);
    return back()->with(['message' => 'Shift Added']);
}

update() — Modify shift

public function update(Request $request, string $id, ShiftServices $shiftServices)
{
    $shiftServices->update($request, $id);
    return back()->with(['message' => 'Shift is Updated']);
}

destroy() — Delete shift

public function destroy(string $id, ShiftServices $shiftServices)
{
    $shiftServices->delete($id);
    return back()->with(['message' => 'Shift Deleted!']);
}

search() — Find shifts by filter

public function search(Request $request, ShiftServices $shiftServices)
{
    $filter = $request->filter;
    $shifts = $shiftServices->get();

    if($filter) {
        $shifts = $shiftServices->search($filter);
    }

    return response(['shifts' => $shifts]);
}

assignShift() — Assign shift to user

public function assignShift(Request $request, string $id, ShiftServices $shiftServices)
{
    $request->validate(['shift' => 'required']);

    $userShift = $shiftServices->assignUserShift($id, $request->shift);
    $shiftServices->selectShift($userShift->shift->id, $id);

    return back()->with(['message' => 'Shift Assign Success']);
}

removeShift() — Remove user from shift

public function removeShift(Request $request, string $id, ShiftServices $shiftServices)
{
    $shiftServices->removeUserShift($id, $request->userShift);
    return back()->with(['message' => 'Shift Remove Success']);
}

11.2 ShiftServices Methods

Location: app/Services/ShiftServices.php (expected)

lists() — Retrieve all shifts with pagination

public function lists()
{
    return Shift::with('assignedUsers')
        ->paginate(25);
}

get() — Retrieve all shifts (no pagination)

public function get()
{
    return Shift::all();
}

search($filter) — Search shifts by name, code, or time

public function search($filter)
{
    return Shift::where('name', 'like', "%{$filter}%")
        ->orWhere('code', 'like', "%{$filter}%")
        ->orWhere('description', 'like', "%{$filter}%")
        ->get();
}

create($request) — Create new shift

public function create($request)
{
    $validated = $request->validated();

    // Calculate shift hours
    $startTime = Carbon::parse($validated['start_time']);
    $endTime = Carbon::parse($validated['end_time']);
    $shiftHours = $startTime->diffInHours($endTime);

    if ($validated['include_break'] ?? false) {
        $breakStart = Carbon::parse($validated['break_start']);
        $breakEnd = Carbon::parse($validated['break_end']);
        $breakDuration = $breakStart->diffInHours($breakEnd);
        $shiftHours -= $breakDuration;
    }

    return Shift::create([
        'code' => $validated['code'],
        'name' => $validated['name'],
        'description' => $validated['description'] ?? null,
        'start_time' => $validated['start_time'],
        'end_time' => $validated['end_time'],
        'include_break' => $validated['include_break'] ?? false,
        'break_start' => $validated['break_start'] ?? null,
        'break_end' => $validated['break_end'] ?? null,
        'shift_hours' => $shiftHours,
        'days' => json_encode($validated['days']),
        'department_id' => $validated['department_id'] ?? null,
        'status' => $validated['status'] ?? 'active',
        'created_by' => auth()->id(),
    ]);
}

update($request, $id) — Modify shift

public function update($request, $id)
{
    $shift = Shift::findOrFail($id);

    $validated = $request->validated();

    // Recalculate hours
    $startTime = Carbon::parse($validated['start_time']);
    $endTime = Carbon::parse($validated['end_time']);
    $shiftHours = $startTime->diffInHours($endTime);

    if ($validated['include_break'] ?? false) {
        $breakStart = Carbon::parse($validated['break_start']);
        $breakEnd = Carbon::parse($validated['break_end']);
        $breakDuration = $breakStart->diffInHours($breakEnd);
        $shiftHours -= $breakDuration;
    }

    $shift->update([
        'name' => $validated['name'],
        'start_time' => $validated['start_time'],
        'end_time' => $validated['end_time'],
        'include_break' => $validated['include_break'] ?? false,
        'break_start' => $validated['break_start'] ?? null,
        'break_end' => $validated['break_end'] ?? null,
        'shift_hours' => $shiftHours,
        'days' => json_encode($validated['days']),
        'status' => $validated['status'] ?? 'active',
    ]);

    return $shift;
}

delete($id) — Soft delete shift

public function delete($id)
{
    $shift = Shift::findOrFail($id);

    // Check if shift has active assignments
    if ($shift->assignedUsers()->where('status', 'active')->exists()) {
        throw new \Exception('Cannot delete shift with active assignments');
    }

    $shift->delete();
    return true;
}

assignUserShift($userId, $shiftId) — Assign user to shift

public function assignUserShift($userId, $shiftId)
{
    // Check for overlapping shifts
    $user = User::findOrFail($userId);
    $shift = Shift::findOrFail($shiftId);

    // Validate no overlap with existing shifts for same days
    // (Implementation depends on overlap logic)

    return UserShift::create([
        'user_id' => $userId,
        'shift_id' => $shiftId,
        'assigned_at' => now(),
        'assigned_by' => auth()->id(),
        'status' => 'active',
    ]);
}

selectShift($shiftId, $userId) — Mark shift as selected (after assignment)

public function selectShift($shiftId, $userId)
{
    // Update pivot/join table or trigger related action
    $userShift = UserShift::where('user_id', $userId)
        ->where('shift_id', $shiftId)
        ->first();

    if ($userShift) {
        $userShift->update(['status' => 'active']);
    }

    return $userShift;
}

removeUserShift($userId, $userShiftId) — Remove user from shift

public function removeUserShift($userId, $userShiftId)
{
    $userShift = UserShift::findOrFail($userShiftId);

    // Verify user matches
    if ($userShift->user_id != $userId) {
        throw new \Exception('User mismatch');
    }

    $userShift->update([
        'status' => 'inactive',
        'removed_at' => now(),
        'removed_by' => auth()->id(),
    ]);

    return $userShift;
}

12. Filters, Search & Reports

Index filters:

  • Status: active, inactive, archived
  • Department / Team
  • Shift type (morning, evening, night, flexible)
  • Days (Monday, Tuesday, ... or All)
  • Assigned users (filter by count)
  • Search: shift name, code, description

Search functionality:

  • Real-time AJAX search via /admin/shift/search?filter={term}
  • Searches: name, code, description
  • Returns JSON list of matching shifts

Reports:

  • Shift roster: all users assigned to each shift (Excel export)
  • Coverage analysis: users per shift, capacity vs assigned
  • Utilization: shift usage per department/week
  • Assignment history: who assigned/removed shifts when
  • Conflict report: overlapping or double-booked shifts

Export formats: CSV, Excel, PDF (roster)


13. Integrations (DTR / Scheduling / Payroll)

DTR Integration:

  • Shifts feed into DTR expectations (when user should clock in/out)
  • Assigned shift determines DTR clock-in/out times
  • User cannot clock in outside assigned shift times (or logged as overtime)
  • Shift absence tracked against DTR

Scheduling Integration:

  • Shifts visible on scheduling calendar
  • Department-specific shifts pre-filter employee lists
  • Shift assignment triggers calendar update
  • Scheduling conflicts flagged if user assigned to overlapping shifts

Payroll Integration:

  • Shift assignment feeds payroll system
  • Shift hours used for salary/wage calculations
  • Overtime (hours beyond shift) flagged for supervisor review
  • Shift type may trigger pay multipliers (e.g., night shift premium)

Sync behavior:

  • Sync events triggered by background jobs on assignment/removal
  • Idempotent payloads; include shift code and user ID
  • Retry on failure; reconciliation UI to manually re-sync

14. Common Tasks & Workflows

Task 1: Create Morning Shift

  1. Navigate: Admin → Shifts → Create
  2. Fill form:
    • Name: "Morning Shift"
    • Code: "MOR-001"
    • Start: 08:00, End: 17:00
    • Break: 12:00–13:00
    • Days: Mon–Fri
    • Department: Support
    • Status: Active
  3. Click "Create"
  4. Shift appears in index ✓

Task 2: Assign Shift to Employee

  1. Navigate: Admin → Shifts → Morning Shift
  2. Click "Assign Users"
  3. Select Jane Doe
  4. Set dates: Jan 1–31, 2025
  5. Add reason: "Regular assignment"
  6. Click "Assign"
  7. Jane assigned to shift; notification sent ✓

Task 3: Bulk Assign Shift

  1. Navigate: Admin → Shifts → Morning Shift
  2. Click "Bulk Assign"
  3. Upload CSV with user IDs and dates
  4. System validates and creates assignments
  5. Confirmation: "50 users assigned to Morning Shift" ✓

Task 4: Remove User from Shift

  1. Navigate: Admin → Shifts → Morning Shift
  2. Find Jane Doe in assigned users table
  3. Click "Remove"
  4. Add reason: "Approved leave Feb 1–14"
  5. Confirm
  6. Jane removed; DTR updated; notification sent ✓

Task 5: Generate Shift Roster

  1. Navigate: Admin → Shifts
  2. Filter: Department = Support, Status = Active
  3. Click "Export Roster"
  4. Select columns: Employee, Shift, Days, Hours
  5. Download Excel file
  6. Print or distribute to department ✓

15. Audit, Notifications & Compliance

Audit logging:

  • Log create/update/delete shifts with user, timestamp, changes
  • Log assign/remove user from shift with reason
  • Maintain immutable audit trail (1 year retention minimum)
  • Include IP address for assignment/removal actions

Notifications:

  • Shift created → notify department heads
  • User assigned → notify user + manager
  • User removed → notify user + manager
  • Shift status change → notify affected users
  • Assignment conflict detected → notify admin

Compliance:

  • Shifts comply with labor laws (max hours, break requirements)
  • Assignment conflicts flagged and prevent save
  • Audit trail supports legal discovery
  • Status change workflows require justification

16. Practical Examples

Example 1: Support Department Scheduling

Shifts Created:
- MOR-001: Morning (08:00–17:00) Mon–Fri, Break 12:00–13:00, 8 hrs
- EVE-001: Evening (17:00–01:00) Mon–Fri, Break 21:00–22:00, 8 hrs
- NIG-001: Night (01:00–08:00) Mon–Fri, Break 04:00–05:00, 8 hrs

Assigned Users:
- Morning: Jane, John, Sarah, Mike, Anna (5 users)
- Evening: Robert, Lisa, Tom, Diana (4 users)
- Night: Kevin, Maya (2 users)

Total Coverage: 24/7 support for 5 business days
Capacity: 5 users per shift (average)
Utilization: 11 users / 3 shifts = 73% efficiency

Example 2: Rotating Shift Schedule

Shift: ROT-SUPP (Rotating Support)
- Schedule: 3-week rotation
- Week 1: Morning (MOR-001) Mon–Fri
- Week 2: Evening (EVE-001) Mon–Fri
- Week 3: Night (NIG-001) Mon–Fri
- Then repeat

Assigned: 15 users (5 per shift rotation)
- Users 1–5: Week 1 = Morning, Week 2 = Evening, Week 3 = Night
- Users 6–10: Week 2 = Morning, Week 3 = Evening, Week 1 = Night (offset)
- Users 11–15: Week 3 = Morning, Week 1 = Evening, Week 2 = Night (offset)

Fair distribution: Each user works all shifts equally

17. Testing & QA Checklist

Unit tests:

  • ✓ Shift::create generates correct shift_hours (including break deduction)
  • ✓ Shift::search filters by name/code correctly
  • ✓ UserShift::create creates assignment record
  • ✓ Overlap detection prevents double-booking (if implemented)

Integration tests:

  • ✓ Create shift via form → Shift record created in DB
  • ✓ Assign user → UserShift record created + notification sent
  • ✓ Remove user → UserShift marked inactive + notification sent
  • ✓ Delete shift with no assignments → succeeds
  • ✓ Delete shift with active assignments → fails with error

E2E tests (manual):

  • ✓ Admin creates shift → appears in index
  • ✓ Admin assigns user → user sees shift on dashboard
  • ✓ Admin removes user → user no longer sees shift
  • ✓ Search returns matching shifts
  • ✓ Export roster generates Excel file

Performance tests:

  • ✓ Index page (100 shifts) loads < 500ms
  • ✓ Search filters < 200ms
  • ✓ Bulk assign (50 users) completes < 5 sec
  • ✓ Export (500 users) generates < 10 sec

18. Troubleshooting & FAQs

Q: "Shift hours calculated incorrectly"

  • A:
    • Verify start/end times are in HH:MM format
    • Confirm break times are within shift hours
    • Check timezone (if multi-timezone system)
    • Example: 08:00–17:00 - 1 hour break = 8 hours ✓

Q: "Cannot delete shift — says it has active assignments"

  • A:
    • Remove all users from shift first (via "Remove" action)
    • Or deactivate shift instead of deleting (set Status = Inactive)
    • Check UserShift table for orphaned records

Q: "User assigned to two overlapping shifts"

  • A:
    • System should prevent overlap (if validation enabled)
    • If allowed, verify with manager first
    • Remove conflicting shift if error

Q: "Shift not appearing on /apps or DTR"

  • A:
    • Confirm shift Status = Active
    • Check if shift limited to specific department (user must be in dept)
    • Verify sync job ran successfully (check job logs)
    • Manually re-sync if needed

Q: "Bulk assign not working"

  • A:
    • Verify CSV format: user_id, shift_id, [start_date, end_date]
    • Check for duplicate user_ids in CSV
    • Ensure users exist in DB (validate user_ids)
    • Check file size limit (if set)

Q: "User received no notification of shift assignment"

  • A:
    • Confirm notification setting is enabled for user
    • Check email configuration (MAIL_* env vars)
    • Verify mail queue is running (php artisan queue:work)
    • Check spam folder

19. Appendix: SQL & Code Examples

SQL: Find active shifts for a department

SELECT s.* FROM shifts s
WHERE s.status = 'active'
  AND s.department_id = 2
  AND s.deleted_at IS NULL
ORDER BY s.start_time;

SQL: Find all users assigned to a shift

SELECT u.* FROM users u
JOIN user_shifts us ON u.id = us.user_id
WHERE us.shift_id = 1 AND us.status = 'active'
ORDER BY u.last_name;

SQL: Find overlapping shift assignments for a user

SELECT us1.*, us2.* FROM user_shifts us1
JOIN user_shifts us2 ON us1.user_id = us2.user_id
  AND us1.id <> us2.id
  AND us1.status = 'active'
  AND us2.status = 'active'
WHERE us1.user_id = 42
  AND (us1.shift_id <> us2.shift_id);

SQL: Count users per shift

SELECT s.name, s.code, COUNT(us.id) as assigned_users
FROM shifts s
LEFT JOIN user_shifts us ON s.id = us.shift_id AND us.status = 'active'
WHERE s.status = 'active'
GROUP BY s.id
ORDER BY assigned_users DESC;

Laravel Model: Shift with users relationship

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Shift extends Model
{
    protected $fillable = [
        'code', 'name', 'description', 'start_time', 'end_time',
        'include_break', 'break_start', 'break_end', 'shift_hours',
        'days', 'department_id', 'status', 'created_by'
    ];

    protected $casts = [
        'days' => 'array',
        'include_break' => 'boolean',
    ];

    public function assignedUsers(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'user_shifts')
            ->withPivot('assigned_at', 'assigned_by', 'status')
            ->where('user_shifts.status', 'active');
    }

    public function userShifts(): HasMany
    {
        return $this->hasMany(UserShift::class);
    }
}

Laravel Model: UserShift

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class UserShift extends Model
{
    protected $fillable = [
        'user_id', 'shift_id', 'assigned_at', 'assigned_by',
        'assigned_reason', 'removed_at', 'removed_by', 'removed_reason', 'status'
    ];

    protected $casts = [
        'assigned_at' => 'datetime',
        'removed_at' => 'datetime',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function shift(): BelongsTo
    {
        return $this->belongsTo(Shift::class);
    }

    public function assignedByUser(): BelongsTo
    {
        return $this->belongsTo(User::class, 'assigned_by');
    }
}

Blade template: Shift index table row

@foreach($shifts as $shift)
    <tr>
        <td>{{ $shift->code }}</td>
        <td>{{ $shift->name }}</td>
        <td>{{ $shift->start_time }} – {{ $shift->end_time }}</td>
        <td>{{ implode(', ', $shift->days) }}</td>
        <td>{{ $shift->assignedUsers()->count() }}</td>
        <td>
            <span class="badge {{ $shift->status === 'active' ? 'bg-green' : 'bg-gray' }}">
                {{ ucfirst($shift->status) }}
            </span>
        </td>
        <td>
            <a href="{{ route('admin.shift.show', $shift->id) }}" class="btn btn-sm btn-info">View</a>
            <button @click="assignModal($shift)" class="btn btn-sm btn-success">Assign</button>
            <a href="{{ route('admin.shift.edit', $shift->id) }}" class="btn btn-sm btn-warning">Edit</a>
            <form action="{{ route('admin.shift.destroy', $shift->id) }}" method="POST" style="display:inline;">
                @csrf @method('DELETE')
                <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Delete?')">Delete</button>
            </form>
        </td>
    </tr>
@endforeach

Document version: 1.0
Maintainers: HR / Scheduler / Engineering / Operations
Last updated: December 10, 2025