# Staff Shifts V2

This document is the frontend handoff for the `staff/shifts/*` routes on the unified v2 API.

Base URL:

- `https://krow-api-v2-e3g6witsvq-uc.a.run.app`

## Read routes

- `GET /staff/orders/available`
- `GET /staff/shifts/assigned`
- `GET /staff/shifts/open`
- `GET /staff/shifts/pending`
- `GET /staff/shifts/cancelled`
- `GET /staff/shifts/completed`
- `GET /staff/shifts/:shiftId`

## Write routes

- `POST /staff/orders/:orderId/book`
- `POST /staff/shifts/:shiftId/apply`
- `POST /staff/shifts/:shiftId/accept`
- `POST /staff/shifts/:shiftId/decline`
- `POST /staff/shifts/:shiftId/request-swap`
- `POST /staff/shifts/:shiftId/submit-for-approval`

All write routes require:

- `Authorization: Bearer <firebase-id-token>`
- `Idempotency-Key: <unique-per-action>`

## Shift lifecycle

### Find work by order

`GET /staff/orders/available`

- use this for grouped recurring or permanent work cards
- each item represents one order plus one role
- this feed is already filtered to the current worker context
- `schedule` gives the preview for the whole booking window

Example response:

```json
{
  "orderId": "uuid",
  "orderType": "RECURRING",
  "roleId": "uuid",
  "roleCode": "BARISTA",
  "roleName": "Barista",
  "clientName": "Google Mountain View Cafes",
  "location": "Google MV Cafe Clock Point",
  "locationAddress": "1600 Amphitheatre Pkwy, Mountain View, CA",
  "hourlyRateCents": 2300,
  "hourlyRate": 23,
  "requiredWorkerCount": 1,
  "filledCount": 0,
  "instantBook": false,
  "dispatchTeam": "CORE",
  "dispatchPriority": 1,
  "schedule": {
    "totalShifts": 3,
    "startDate": "2026-03-24",
    "endDate": "2026-03-28",
    "daysOfWeek": ["WED", "FRI"],
    "startTime": "09:00",
    "endTime": "15:00",
    "timezone": "America/Los_Angeles",
    "firstShiftStartsAt": "2026-03-25T16:00:00.000Z",
    "lastShiftEndsAt": "2026-03-27T22:00:00.000Z"
  }
}
```

`POST /staff/orders/:orderId/book`

- use this when the worker books the full order instead of one shift
- booking is atomic across the future shifts in that order for the selected role
- backend returns `PENDING` when the booking is reserved but not instant-booked
- backend returns `CONFIRMED` when every future shift in that booking path is instant-booked

Example request:

```json
{
  "roleId": "uuid"
}
```

Important:

- `roleId` for the order-booking flow is the role catalog id returned by `GET /staff/orders/available`
- it is not the same thing as the per-shift `shift_roles.id`

### Find shifts

`GET /staff/shifts/open`

- use this for the worker marketplace feed
- the worker applies to a concrete shift role
- send the `roleId` returned by the open-shifts response
- `roleId` here means `shift_roles.id`, not the role catalog id

Apply request example:

```json
{
  "roleId": "uuid",
  "instantBook": false
}
```

### Pending shifts

`GET /staff/shifts/pending`

- use `POST /staff/shifts/:shiftId/accept` to accept
- use `POST /staff/shifts/:shiftId/decline` to decline

### Assigned shifts

`GET /staff/shifts/assigned`

Each item now includes:

- `clientName`
- `hourlyRate`
- `totalRate`
- `startTime`
- `endTime`

### Shift detail

`GET /staff/shifts/:shiftId`

Each detail response now includes:

- `clientName`
- `latitude`
- `longitude`
- `hourlyRate`
- `totalRate`

Use this as the source of truth for the shift detail screen.

### Request swap

`POST /staff/shifts/:shiftId/request-swap`

Example:

```json
{
  "reason": "Need coverage for a family emergency"
}
```

Current backend behavior:

- marks the assignment as `SWAP_REQUESTED`
- stores the reason
- emits `SHIFT_SWAP_REQUESTED`
- exposes the shift in the replacement pool
- starts the swap-expiry window used by backend auto-cancellation

Manager/ops review happens through:

- `GET /client/coverage/swap-requests`
- `GET /client/coverage/dispatch-candidates`
- `POST /client/coverage/swap-requests/:swapRequestId/resolve`
- `POST /client/coverage/swap-requests/:swapRequestId/cancel`

If the swap request expires without coverage, backend auto-cancels it and alerts the manager path plus the original worker.

### Submit completed shift for approval

`POST /staff/shifts/:shiftId/submit-for-approval`

Use this after the worker has clocked out.

Example:

```json
{
  "note": "Worked full shift and all tasks were completed"
}
```

Current backend behavior:

- only allows shifts in `CHECKED_OUT` or `COMPLETED`
- creates or updates the assignment timesheet
- sets the timesheet to `SUBMITTED` unless it is already `APPROVED` or `PAID`
- emits `TIMESHEET_SUBMITTED_FOR_APPROVAL`

Example response:

```json
{
  "assignmentId": "uuid",
  "shiftId": "uuid",
  "timesheetId": "uuid",
  "status": "SUBMITTED",
  "submitted": true
}
```

## Completed shifts

`GET /staff/shifts/completed`

Each item now includes:

- `date`
- `clientName`
- `startTime`
- `endTime`
- `hourlyRate`
- `totalRate`
- `timesheetStatus`
- `paymentStatus`

## Clock-in support fields

`GET /staff/clock-in/shifts/today`

Each item now includes:

- `clientName`
- `hourlyRate`
- `totalRate`
- `latitude`
- `longitude`
- `clockInMode`
- `allowClockInOverride`

## Frontend rule

Use the unified routes only.

Do not build new mobile work on:

- `/query/*`
- `/commands/*`
- `/core/*`
