Merge branch 'dev' of https://github.com/Oloodi/krow-workforce into feature/session-persistence-new

This commit is contained in:
2026-03-19 10:27:42 +05:30
190 changed files with 4827 additions and 196 deletions

View File

@@ -4,7 +4,8 @@
> **Status: LEGACY / ARCHIVED REFERENCE**
> This document is based on a historical export from the Base44 platform.
> It is maintained in this repository solely for reference purposes during the rebuild and is **not** to be considered the definitive or active API specification for the production system.
> The actual data schemas and operations are now defined directly within `backend/dataconnect/`.
> The historical V1 Data Connect schemas and operations now live in `legacy/dataconnect-v1/`.
> The active V2 backend source of truth is the unified/backend service stack under `backend/command-api`, `backend/query-api`, `backend/core-api`, and `backend/unified-api`.
**Original Version:** 3.0
**Original Date:** 2025-11-20
@@ -1423,4 +1424,4 @@ firebase functions:config:set \
---
© 2025 KROW Workforce / Oloodi Technologies Inc. All rights reserved.
© 2025 KROW Workforce / Oloodi Technologies Inc. All rights reserved.

View File

@@ -926,7 +926,7 @@ Via modular `profile_sections/` architecture:
| Legacy Client Mobile | `_legacy/apps/krow_client_context.md` | Flutter (context doc) |
| Legacy Worker (Legacy Staff) Mobile | `_legacy/apps/krow_staff_context.md` | Flutter (context doc) |
| Legacy Backend | `_legacy/apps/php_backend_context.md` | PHP/Laravel |
| New Backend | `backend/dataconnect/` | Firebase Data Connect |
| Legacy V1 Data Connect Backend | `legacy/dataconnect-v1/` | Firebase Data Connect |
---
@@ -974,7 +974,7 @@ For each feature in Sprint 3:
2. **Write Schema Incrementally**
```gql
# backend/dataconnect/schema/event.gql
# legacy/dataconnect-v1/schema/event.gql
type Event @table {
id: UUID! @default(expr: "uuidV4()")
name: String!
@@ -989,7 +989,7 @@ For each feature in Sprint 3:
3. **Define Queries/Mutations**
```gql
# backend/dataconnect/queries/events.gql
# legacy/dataconnect-v1/queries/events.gql
query ListEvents($status: EventStatus) @auth(level: USER) {
events(where: { status: { eq: $status } }) {
id, name, status, date
@@ -1308,7 +1308,7 @@ Business (1) ──┬── (1) BusinessSetting
| Legacy Client context | `_legacy/apps/krow_client_context.md` |
| Legacy Staff context | `_legacy/apps/krow_staff_context.md` |
| Architecture diagrams | `internal/launchpad/assets/diagrams/` |
| Data Connect schemas | `backend/dataconnect/` |
| Data Connect schemas | `legacy/dataconnect-v1/` |
---

View File

@@ -22,10 +22,13 @@ What was validated live against the deployed stack:
- staff auth bootstrap
- client dashboard, billing, coverage, hubs, vendors, managers, team members, orders, and reports
- client coverage incident feed for geofence and override review
- client blocked-staff review and invited shift-manager creation
- client hub, order, coverage review, device token, and late-worker cancellation flows
- client swap-request review, dispatch-team management, and dispatch-candidate ranking
- client invoice approve and dispute
- staff dashboard, availability, payments, shifts, profile sections, documents, certificates, attire, bank accounts, benefits, and time card
- staff availability, profile, tax form, bank account, shift apply, shift accept, push token registration, clock-in, clock-out, location stream upload, and swap request
- staff benefit history read model
- staff availability, profile, tax form, bank account, shift apply, shift accept, push token registration, clock-in, clock-out, location stream upload, swap request, and completed-shift submission
- direct file upload helpers and verification job creation through the unified host
- client and staff sign-out
@@ -107,6 +110,20 @@ Important operational rules:
- background location streams are stored as raw batch payloads in the private v2 bucket and summarized in SQL for query speed
- incident review lives on `GET /client/coverage/incidents`
- confirmed late-worker recovery is exposed on `POST /client/coverage/late-workers/:assignmentId/cancel`
- client swap review is exposed on:
- `GET /client/coverage/swap-requests`
- `POST /client/coverage/swap-requests/:swapRequestId/resolve`
- `POST /client/coverage/swap-requests/:swapRequestId/cancel`
- dispatch-team management is exposed on:
- `GET /client/coverage/dispatch-teams`
- `GET /client/coverage/dispatch-candidates`
- `POST /client/coverage/dispatch-teams/memberships`
- `DELETE /client/coverage/dispatch-teams/memberships/:membershipId`
- dispatch ranking order is:
1. `CORE`
2. `CERTIFIED_LOCATION`
3. `MARKETPLACE`
- expired swap requests are auto-cancelled by the notification worker and emit manager plus staff alerts
- queued alerts are written to `notification_outbox`, dispatched by the private Cloud Run worker service `krow-notification-worker-v2`, and recorded in `notification_deliveries`
## 5) Route model
@@ -143,6 +160,9 @@ Those routes still exist for backend/internal compatibility, but mobile/frontend
- [Authentication](./authentication.md)
- [Unified API](./unified-api.md)
- [Mobile Coding Agent Spec](./mobile-coding-agent-spec.md)
- [Mobile Frontend Implementation Spec](./mobile-frontend-implementation-spec.md)
- [Staff Shifts](./staff-shifts.md)
- [Core API](./core-api.md)
- [Command API](./command-api.md)
- [Query API](./query-api.md)

View File

@@ -0,0 +1,388 @@
# Mobile Coding Agent Spec
This document is the frontend handoff spec for an AI coding agent working on the mobile applications against the v2 backend.
Use this as the primary implementation brief.
Base URL:
- `https://krow-api-v2-933560802882.us-central1.run.app`
Supporting docs:
- `/Users/wiel/Development/krow-workforce/docs/BACKEND/API_GUIDES/V2/authentication.md`
- `/Users/wiel/Development/krow-workforce/docs/BACKEND/API_GUIDES/V2/unified-api.md`
- `/Users/wiel/Development/krow-workforce/docs/BACKEND/API_GUIDES/V2/mobile-frontend-implementation-spec.md`
- `/Users/wiel/Development/krow-workforce/docs/BACKEND/API_GUIDES/V2/staff-shifts.md`
## 1) Non-negotiable rules
- Use the unified base URL only.
- Do not call `/query/*`, `/commands/*`, or `/core/*` directly from frontend.
- Send `Authorization: Bearer <firebase-id-token>` on protected routes.
- Send `Idempotency-Key` on every write route.
- Treat `order`, `shift`, `shiftRole`, and `assignment` as different objects.
- For staff shift applications, `roleId` must come from the response of `GET /staff/shifts/open`.
## 2) What is implemented now
Safe to build against now:
- client auth/session
- client dashboard, billing, coverage, hubs, vendors, managers, team members, orders, reports
- client coverage reviews, worker rating, block/unblock, late-worker cancellation
- client swap review and dispatch-team management
- staff auth/session
- staff dashboard, availability, payments, shifts, profile, tax forms, bank accounts, benefits, documents, attire, certificates
- staff clock-in, clock-out, location streaming, swap request, completed-shift submission
- upload, signed URL, verification, and rapid-order processing
Not part of this implementation spec:
- backend `/auth/refresh`
- SMS or email notification fallback
- AI-driven report insights
- AI-driven personalized shift matching
- full NFC attestation enforcement
- chat backend
## 3) Auth implementation
### Client app
Use:
- `POST /auth/client/sign-in`
- `POST /auth/client/sign-up`
- `GET /auth/session`
- `POST /auth/client/sign-out`
Do not build a separate refresh route.
Token refresh remains on the Firebase client SDK side.
### Staff app
Use:
- `POST /auth/staff/phone/start`
- `POST /auth/staff/phone/verify`
- `GET /auth/session`
- `POST /auth/staff/sign-out`
Important:
- `POST /auth/staff/phone/start` can return `mode = CLIENT_FIREBASE_SDK`
- when that happens, the app must complete Firebase phone verification on-device
- after that, the app must call `POST /auth/staff/phone/verify` with the Firebase `idToken`
Do not assume staff auth is a fully backend-managed OTP flow.
## 4) Core data model assumptions
- `order`: client-facing staffing request
- `shift`: a scheduled work instance under an order
- `shiftRole`: a role slot inside a shift
- `application`: worker applies to a `shiftRole`
- `assignment`: worker is actually attached to a shift
Rules:
- `GET /staff/shifts/open` returns opportunities, not assignments
- `GET /staff/shifts/assigned` returns active assigned shifts
- `GET /client/orders/view` is the timeline/read model for client
- `POST /client/orders/:orderId/edit` and `POST /client/orders/:orderId/cancel` apply to future shifts only
## 5) Client app screen mapping
### Home
- `GET /client/session`
- `GET /client/dashboard`
- `GET /client/reorders`
### Billing
- `GET /client/billing/accounts`
- `GET /client/billing/invoices/pending`
- `GET /client/billing/invoices/history`
- `GET /client/billing/current-bill`
- `GET /client/billing/savings`
- `GET /client/billing/spend-breakdown`
- `POST /client/billing/invoices/:invoiceId/approve`
- `POST /client/billing/invoices/:invoiceId/dispute`
### Coverage
- `GET /client/coverage?date=YYYY-MM-DD`
- `GET /client/coverage/stats?date=YYYY-MM-DD`
- `GET /client/coverage/core-team?date=YYYY-MM-DD`
- `GET /client/coverage/incidents?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD`
- `GET /client/coverage/blocked-staff`
- `GET /client/coverage/swap-requests?status=OPEN`
- `GET /client/coverage/dispatch-teams`
- `GET /client/coverage/dispatch-candidates?shiftId=uuid&roleId=uuid`
- `POST /client/coverage/reviews`
- `POST /client/coverage/late-workers/:assignmentId/cancel`
- `POST /client/coverage/swap-requests/:swapRequestId/resolve`
- `POST /client/coverage/swap-requests/:swapRequestId/cancel`
- `POST /client/coverage/dispatch-teams/memberships`
- `DELETE /client/coverage/dispatch-teams/memberships/:membershipId`
Coverage review payload:
```json
{
"assignmentId": "uuid",
"rating": 4,
"comment": "Strong performance on the shift",
"markAsBlocked": false
}
```
Rules:
- worker rating happens through `POST /client/coverage/reviews`
- the same endpoint also supports `markAsFavorite` to add or remove a worker from business favorites
- blocking a worker is done through the same endpoint using `markAsBlocked`
- dispatch ranking order is:
1. `CORE`
2. `CERTIFIED_LOCATION`
3. `MARKETPLACE`
Swap management flow:
1. worker requests swap
2. backend moves original assignment to `SWAP_REQUESTED`
3. replacement workers see the shift in `GET /staff/shifts/open`
4. client/ops reads `GET /client/coverage/swap-requests`
5. client/ops reads `GET /client/coverage/dispatch-candidates`
6. client/ops resolves or cancels the swap request
7. if unresolved and expired, backend auto-cancels it
### Orders
- `GET /client/orders/view`
- `GET /client/orders/:orderId/reorder-preview`
- `POST /client/orders/one-time`
- `POST /client/orders/recurring`
- `POST /client/orders/permanent`
- `POST /client/orders/:orderId/edit`
- `POST /client/orders/:orderId/cancel`
- `POST /rapid-orders/process`
Rules:
- use `POST /rapid-orders/process` for the single-call rapid-order flow
- recent orders should expect total price and hourly rate fields
- order edit and cancel only affect future shifts
### Hubs and managers
- `GET /client/hubs`
- `GET /client/cost-centers`
- `GET /client/hubs/:hubId/managers`
- `GET /client/team-members`
- `POST /client/shift-managers`
- `POST /client/hubs`
- `PUT /client/hubs/:hubId`
- `DELETE /client/hubs/:hubId`
- `POST /client/hubs/:hubId/assign-nfc`
- `POST /client/hubs/:hubId/managers`
Rules:
- `POST /client/shift-managers` creates an invited manager identity
- if `hubId` is present, backend links the manager to that hub too
### Reports
- `GET /client/reports/summary?date=YYYY-MM-DD`
- `GET /client/reports/daily-ops?date=YYYY-MM-DD`
- `GET /client/reports/spend?date=YYYY-MM-DD`
- `GET /client/reports/coverage?date=YYYY-MM-DD`
- `GET /client/reports/forecast?date=YYYY-MM-DD`
- `GET /client/reports/performance?date=YYYY-MM-DD`
- `GET /client/reports/no-show?date=YYYY-MM-DD`
Important:
- these are operational reports
- this is not the same as the separate AI insights research issue
## 6) Staff app screen mapping
### Home
- `GET /staff/session`
- `GET /staff/dashboard`
- `GET /staff/profile-completion`
### Availability
- `GET /staff/availability`
- `PUT /staff/availability`
- `POST /staff/availability/quick-set`
### Find shifts
- `GET /staff/shifts/open`
- `POST /staff/shifts/:shiftId/apply`
Rule:
- use `roleId` from the open-shifts response
### My shifts
- `GET /staff/shifts/pending`
- `GET /staff/shifts/assigned`
- `GET /staff/shifts/cancelled`
- `GET /staff/shifts/completed`
- `GET /staff/shifts/:shiftId`
- `POST /staff/shifts/:shiftId/accept`
- `POST /staff/shifts/:shiftId/decline`
- `POST /staff/shifts/:shiftId/request-swap`
- `POST /staff/shifts/:shiftId/submit-for-approval`
Staff shift detail and list rules:
- assigned shifts include `clientName`, `hourlyRate`, `totalRate`, `startTime`, `endTime`
- shift detail includes `clientName`, `latitude`, `longitude`, `hourlyRate`, `totalRate`
- completed shifts include `date`, `clientName`, `startTime`, `endTime`, `hourlyRate`, `totalRate`
### Clock in / clock out
- `GET /staff/clock-in/shifts/today`
- `GET /staff/clock-in/status`
- `POST /staff/clock-in`
- `POST /staff/clock-out`
- `POST /staff/location-streams`
Clock-in payload rules:
- if using NFC, send `nfcTagId`
- if using geo, send `latitude`, `longitude`, `accuracyMeters`
- send `overrideReason` only when geo override is allowed
- send `proofNonce` and `proofTimestamp` on attendance writes
- send `attestationProvider` and `attestationToken` only if the device has them
Clock-in read rules:
`GET /staff/clock-in/shifts/today` returns fields including:
- `clientName`
- `hourlyRate`
- `totalRate`
- `latitude`
- `longitude`
- `clockInMode`
- `allowClockInOverride`
- `geofenceRadiusMeters`
- `nfcTagId`
Policy values:
- `NFC_REQUIRED`
- `GEO_REQUIRED`
- `EITHER`
### Payments
- `GET /staff/payments/summary`
- `GET /staff/payments/history`
- `GET /staff/payments/chart`
### Profile
- `GET /staff/profile/sections`
- `GET /staff/profile/personal-info`
- `GET /staff/profile/industries`
- `GET /staff/profile/skills`
- `GET /staff/profile/documents`
- `GET /staff/profile/attire`
- `GET /staff/profile/tax-forms`
- `GET /staff/profile/emergency-contacts`
- `GET /staff/profile/certificates`
- `GET /staff/profile/bank-accounts`
- `GET /staff/profile/benefits`
- `GET /staff/profile/benefits/history`
- `GET /staff/profile/time-card`
- `GET /staff/profile/privacy`
- `PUT /staff/profile/personal-info`
- `PUT /staff/profile/experience`
- `PUT /staff/profile/locations`
- `POST /staff/profile/emergency-contacts`
- `PUT /staff/profile/emergency-contacts/:contactId`
- `PUT /staff/profile/tax-forms/:formType`
- `POST /staff/profile/tax-forms/:formType/submit`
- `POST /staff/profile/bank-accounts`
- `PUT /staff/profile/privacy`
Profile data rules:
- `GET /staff/profile/documents` returns documents only
- `GET /staff/profile/attire` returns attire only
- `GET /staff/profile/tax-forms` returns tax forms only
- `GET /staff/profile/certificates` returns certificates only
- benefit summary and benefit history are separate endpoints
### FAQ
- `GET /staff/faqs`
- `GET /staff/faqs/search?q=...`
## 7) Upload flows
### General upload pattern
For documents, attire, and certificates:
1. `POST /upload-file`
2. `POST /create-signed-url`
3. upload bytes to storage
4. `POST /verifications`
5. finalize using the appropriate staff route
Staff upload routes:
- `POST /staff/profile/photo`
- `POST /staff/profile/documents/:documentId/upload`
- `PUT /staff/profile/documents/:documentId/upload`
- `POST /staff/profile/attire/:documentId/upload`
- `PUT /staff/profile/attire/:documentId/upload`
- `POST /staff/profile/certificates`
- `DELETE /staff/profile/certificates/:certificateId`
Rules:
- backend treats verification-linked file state as the source of truth
- frontend may still send `fileUri` or `photoUrl`, but verification linkage wins
## 8) What the coding agent should not assume
- do not invent a backend refresh route
- do not assume swap is staff-only; there is now a client/ops review side
- do not assume documents and attire share the same read endpoint
- do not assume backend direct CRUD on internal services
- do not assume AI reports, SMS fallback, or full NFC attestation are available
## 9) Suggested implementation order for the coding agent
1. auth/session flows
2. client home + orders + coverage
3. staff home + shifts + clock-in
4. profile sections and upload flows
5. reports and billing polish
6. swap review and dispatch-team management
## 10) Definition of done for frontend integration
Frontend implementation is aligned when:
- every screen calls the unified v2 routes only
- every write sends an `Idempotency-Key`
- staff shift apply uses `roleId` from open shifts
- clock-in respects `clockInMode`
- swap request uses the staff endpoint and swap review uses the client coverage endpoints
- documents, attire, certificates, and tax forms use their correct route families

View File

@@ -0,0 +1,305 @@
# Mobile Frontend Implementation Spec
This is the shortest path for frontend to implement the v2 mobile clients against the unified backend.
Base URL:
- `https://krow-api-v2-933560802882.us-central1.run.app`
Use this doc together with:
- [Authentication](./authentication.md)
- [Unified API](./unified-api.md)
- [Staff Shifts](./staff-shifts.md)
## 1) Global rules
- Use unified routes only.
- Send `Authorization: Bearer <firebase-id-token>` on protected routes.
- Send `Idempotency-Key` on all write routes.
- Do not call `/query/*`, `/commands/*`, or `/core/*` directly from frontend.
## 2) Core model frontend should assume
- `order` is the client-facing request for staffing.
- `shift` is the concrete scheduled unit of work under an order.
- `shiftRole` is the role slot inside a shift that staff apply to.
- `assignment` is the worker-to-shift record once a worker is attached.
Important consequences:
- `GET /staff/shifts/open` returns open shift-role opportunities.
- `POST /staff/shifts/:shiftId/apply` must send the `roleId` from that response.
- `GET /client/orders/view` is the timeline/read model for the client app.
- `POST /client/orders/:orderId/edit` and `POST /client/orders/:orderId/cancel` only affect future shifts.
## 3) Auth implementation
### Client app
- sign in with `POST /auth/client/sign-in`
- sign up with `POST /auth/client/sign-up`
- hydrate session with `GET /auth/session`
- sign out with `POST /auth/client/sign-out`
### Staff app
- start phone auth with `POST /auth/staff/phone/start`
- complete phone auth with `POST /auth/staff/phone/verify`
- hydrate session with `GET /auth/session`
- sign out with `POST /auth/staff/sign-out`
Token refresh:
- keep using Firebase client SDK refresh behavior
- there is no backend `/auth/refresh` route
## 4) Client app screen mapping
### Home / dashboard
- `GET /client/session`
- `GET /client/dashboard`
- `GET /client/reorders`
### Billing / payments
- `GET /client/billing/accounts`
- `GET /client/billing/invoices/pending`
- `GET /client/billing/invoices/history`
- `GET /client/billing/current-bill`
- `GET /client/billing/savings`
- `GET /client/billing/spend-breakdown`
- `POST /client/billing/invoices/:invoiceId/approve`
- `POST /client/billing/invoices/:invoiceId/dispute`
### Coverage
- `GET /client/coverage?date=YYYY-MM-DD`
- `GET /client/coverage/stats?date=YYYY-MM-DD`
- `GET /client/coverage/core-team?date=YYYY-MM-DD`
- `GET /client/coverage/incidents?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD`
- `GET /client/coverage/blocked-staff`
- `GET /client/coverage/swap-requests?status=OPEN`
- `GET /client/coverage/dispatch-teams`
- `GET /client/coverage/dispatch-candidates?shiftId=uuid&roleId=uuid`
- `POST /client/coverage/reviews`
- `POST /client/coverage/late-workers/:assignmentId/cancel`
- `POST /client/coverage/swap-requests/:swapRequestId/resolve`
- `POST /client/coverage/swap-requests/:swapRequestId/cancel`
- `POST /client/coverage/dispatch-teams/memberships`
- `DELETE /client/coverage/dispatch-teams/memberships/:membershipId`
Use `POST /client/coverage/reviews` when the business is rating a worker after coverage review.
Payload may include:
```json
{
"staffId": "uuid",
"assignmentId": "uuid",
"rating": 4,
"feedback": "Strong performance on the shift",
"markAsFavorite": true,
"markAsBlocked": false
}
```
If `markAsFavorite` is `true`, backend adds that worker to the business favorites list. If `markAsFavorite` is `false`, backend removes them from that list. If `markAsBlocked` is `true`, backend blocks that worker for that business and rejects future apply or assign attempts until a later review sets `markAsBlocked: false`.
Swap-management rule:
- use `GET /client/coverage/swap-requests` as the client review feed
- use `GET /client/coverage/dispatch-candidates` for the ranked replacement list
- use `POST /client/coverage/swap-requests/:swapRequestId/resolve` when ops selects a replacement
- use `POST /client/coverage/swap-requests/:swapRequestId/cancel` when ops wants to close the swap request without replacement
Dispatch-priority rule:
1. `CORE`
2. `CERTIFIED_LOCATION`
3. `MARKETPLACE`
### Orders
- `GET /client/orders/view`
- `GET /client/orders/:orderId/reorder-preview`
- `POST /client/orders/one-time`
- `POST /client/orders/recurring`
- `POST /client/orders/permanent`
- `POST /client/orders/:orderId/edit`
- `POST /client/orders/:orderId/cancel`
Rapid-order flow:
- use `POST /rapid-orders/process` for the single-call transcribe-and-parse flow
### Hubs and managers
- `GET /client/hubs`
- `GET /client/cost-centers`
- `GET /client/hubs/:hubId/managers`
- `GET /client/team-members`
- `POST /client/shift-managers`
- `POST /client/hubs`
- `PUT /client/hubs/:hubId`
- `DELETE /client/hubs/:hubId`
- `POST /client/hubs/:hubId/assign-nfc`
- `POST /client/hubs/:hubId/managers`
`POST /client/shift-managers` is the fastest path to create an invited manager identity for a business. If `hubId` is provided, backend also links that manager to the hub.
### Reports
- `GET /client/reports/summary?date=YYYY-MM-DD`
- `GET /client/reports/daily-ops?date=YYYY-MM-DD`
- `GET /client/reports/spend?date=YYYY-MM-DD`
- `GET /client/reports/coverage?date=YYYY-MM-DD`
- `GET /client/reports/forecast?date=YYYY-MM-DD`
- `GET /client/reports/performance?date=YYYY-MM-DD`
- `GET /client/reports/no-show?date=YYYY-MM-DD`
## 5) Staff app screen mapping
### Home / dashboard
- `GET /staff/session`
- `GET /staff/dashboard`
- `GET /staff/profile-completion`
### Availability
- `GET /staff/availability`
- `PUT /staff/availability`
- `POST /staff/availability/quick-set`
### Find shifts
- `GET /staff/shifts/open`
- `POST /staff/shifts/:shiftId/apply`
Rule:
- send the `roleId` from the open-shifts response
- this is the concrete `shift_roles.id`
### My shifts
- `GET /staff/shifts/pending`
- `GET /staff/shifts/assigned`
- `GET /staff/shifts/cancelled`
- `GET /staff/shifts/completed`
- `GET /staff/shifts/:shiftId`
- `POST /staff/shifts/:shiftId/accept`
- `POST /staff/shifts/:shiftId/decline`
- `POST /staff/shifts/:shiftId/request-swap`
- `POST /staff/shifts/:shiftId/submit-for-approval`
Current swap behavior:
- backend records the swap request
- assignment moves to `SWAP_REQUESTED`
- shift becomes visible in the replacement pool
- client/ops can review and resolve swap requests through the coverage endpoints
- if the swap request expires without coverage, backend auto-cancels it and alerts both the manager path and the original worker
### Clock in / clock out
- `GET /staff/clock-in/shifts/today`
- `GET /staff/clock-in/status`
- `POST /staff/clock-in`
- `POST /staff/clock-out`
- `POST /staff/location-streams`
Frontend should respect:
- `clockInMode`
- `allowClockInOverride`
- `latitude`
- `longitude`
- `geofenceRadiusMeters`
- `nfcTagId`
Clock-in proof rules:
- use `nfcTagId` for NFC clocking
- use `latitude`, `longitude`, and `accuracyMeters` for geolocation clocking
- send `overrideReason` only when a geofence override is allowed
- send `proofNonce` and `proofTimestamp` on attendance writes
### Payments
- `GET /staff/payments/summary`
- `GET /staff/payments/history`
- `GET /staff/payments/chart`
### Profile
- `GET /staff/profile/sections`
- `GET /staff/profile/personal-info`
- `GET /staff/profile/industries`
- `GET /staff/profile/skills`
- `GET /staff/profile/documents`
- `GET /staff/profile/attire`
- `GET /staff/profile/tax-forms`
- `GET /staff/profile/emergency-contacts`
- `GET /staff/profile/certificates`
- `GET /staff/profile/bank-accounts`
- `GET /staff/profile/benefits`
- `GET /staff/profile/benefits/history`
- `GET /staff/profile/time-card`
- `GET /staff/profile/privacy`
- `PUT /staff/profile/personal-info`
- `PUT /staff/profile/experience`
- `PUT /staff/profile/locations`
- `POST /staff/profile/emergency-contacts`
- `PUT /staff/profile/emergency-contacts/:contactId`
- `PUT /staff/profile/tax-forms/:formType`
- `POST /staff/profile/tax-forms/:formType/submit`
- `POST /staff/profile/bank-accounts`
- `PUT /staff/profile/privacy`
Document model rule:
- `GET /staff/profile/documents` returns only documents
- `GET /staff/profile/attire` returns attire items
- `GET /staff/profile/tax-forms` returns tax-form rows
- `GET /staff/profile/certificates` returns certificates
### FAQ
- `GET /staff/faqs`
- `GET /staff/faqs/search?q=...`
## 6) Upload implementation
For documents, attire, and certificates:
1. `POST /upload-file`
2. `POST /create-signed-url`
3. upload file bytes to storage with the signed URL
4. `POST /verifications`
5. finalize with:
- `PUT /staff/profile/documents/:documentId/upload`
- `PUT /staff/profile/attire/:documentId/upload`
- `POST /staff/profile/certificates`
Use the verification-linked file as the source of truth.
## 7) What frontend should not assume
- do not assume order edit mutates past shifts
- do not assume swap resolution is complete beyond the request step
- do not assume raw `/query/*` or `/commands/*` routes are stable for app integration
- do not assume blocked workers can still apply to future shifts for that business
## 8) Demo reset
To reset dev demo data:
```bash
source ~/.nvm/nvm.sh
nvm use 23.5.0
cd backend/command-api
npm run seed:v2-demo
```

View File

@@ -0,0 +1,183 @@
# 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-933560802882.us-central1.run.app`
## Read routes
- `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/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 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/*`

View File

@@ -44,6 +44,10 @@ Full auth behavior, including staff phone flow and refresh rules, is documented
- `GET /client/coverage/stats`
- `GET /client/coverage/core-team`
- `GET /client/coverage/incidents`
- `GET /client/coverage/blocked-staff`
- `GET /client/coverage/swap-requests`
- `GET /client/coverage/dispatch-teams`
- `GET /client/coverage/dispatch-candidates`
- `GET /client/hubs`
- `GET /client/cost-centers`
- `GET /client/vendors`
@@ -69,6 +73,7 @@ Full auth behavior, including staff phone flow and refresh rules, is documented
- `POST /client/orders/permanent`
- `POST /client/orders/:orderId/edit`
- `POST /client/orders/:orderId/cancel`
- `POST /client/shift-managers`
- `POST /client/hubs`
- `PUT /client/hubs/:hubId`
- `DELETE /client/hubs/:hubId`
@@ -78,6 +83,79 @@ Full auth behavior, including staff phone flow and refresh rules, is documented
- `POST /client/billing/invoices/:invoiceId/dispute`
- `POST /client/coverage/reviews`
- `POST /client/coverage/late-workers/:assignmentId/cancel`
- `POST /client/coverage/swap-requests/:swapRequestId/resolve`
- `POST /client/coverage/swap-requests/:swapRequestId/cancel`
- `POST /client/coverage/dispatch-teams/memberships`
- `DELETE /client/coverage/dispatch-teams/memberships/:membershipId`
Coverage-review request payload may also send:
```json
{
"staffId": "uuid",
"assignmentId": "uuid",
"rating": 2,
"feedback": "Worker left the shift early without approval",
"markAsFavorite": false,
"issueFlags": ["LEFT_EARLY"],
"markAsBlocked": true
}
```
If `markAsFavorite` is `true`, backend adds that worker to the business favorites list. If `markAsFavorite` is `false`, backend removes them from that list. If `markAsBlocked` is `true`, backend adds that staff member to the business-level blocked list and future apply or assign attempts are rejected until a later review sends `markAsBlocked: false`.
Swap-review routes:
- `GET /client/coverage/swap-requests?status=OPEN`
- `POST /client/coverage/swap-requests/:swapRequestId/resolve`
- `POST /client/coverage/swap-requests/:swapRequestId/cancel`
Resolve example:
```json
{
"applicationId": "uuid",
"note": "Dispatch selected the strongest replacement candidate"
}
```
Dispatch-team routes:
- `GET /client/coverage/dispatch-teams`
- `GET /client/coverage/dispatch-candidates?shiftId=uuid&roleId=uuid`
- `POST /client/coverage/dispatch-teams/memberships`
- `DELETE /client/coverage/dispatch-teams/memberships/:membershipId`
Dispatch-team membership example:
```json
{
"staffId": "uuid",
"hubId": "uuid",
"teamType": "CORE",
"notes": "Preferred lead barista for this location"
}
```
Dispatch priority order is:
1. `CORE`
2. `CERTIFIED_LOCATION`
3. `MARKETPLACE`
Shift-manager creation example:
```json
{
"firstName": "Nora",
"lastName": "Lead",
"email": "nora.lead@example.com",
"phone": "+15550001234",
"hubId": "uuid"
}
```
The manager is created as an invited business membership. If `hubId` is present, backend also links the manager to that hub.
## 3) Staff routes
@@ -109,6 +187,7 @@ Full auth behavior, including staff phone flow and refresh rules, is documented
- `GET /staff/profile/certificates`
- `GET /staff/profile/bank-accounts`
- `GET /staff/profile/benefits`
- `GET /staff/profile/benefits/history`
- `GET /staff/profile/time-card`
- `GET /staff/profile/privacy`
- `GET /staff/faqs`
@@ -153,6 +232,7 @@ Example `GET /staff/clock-in/shifts/today` item:
- `POST /staff/shifts/:shiftId/accept`
- `POST /staff/shifts/:shiftId/decline`
- `POST /staff/shifts/:shiftId/request-swap`
- `POST /staff/shifts/:shiftId/submit-for-approval`
- `PUT /staff/profile/personal-info`
- `PUT /staff/profile/experience`
- `PUT /staff/profile/locations`
@@ -174,6 +254,7 @@ These are exposed as direct unified aliases even though they are backed by `core
- `POST /invoke-llm`
- `POST /rapid-orders/transcribe`
- `POST /rapid-orders/parse`
- `POST /rapid-orders/process`
- `POST /verifications`
- `GET /verifications/:verificationId`
- `POST /verifications/:verificationId/review`
@@ -183,7 +264,9 @@ These are exposed as direct unified aliases even though they are backed by `core
- `POST /staff/profile/photo`
- `POST /staff/profile/documents/:documentId/upload`
- `PUT /staff/profile/documents/:documentId/upload`
- `POST /staff/profile/attire/:documentId/upload`
- `PUT /staff/profile/attire/:documentId/upload`
- `POST /staff/profile/certificates`
- `DELETE /staff/profile/certificates/:certificateId`
@@ -191,7 +274,22 @@ These are exposed as direct unified aliases even though they are backed by `core
- `roleId` on `POST /staff/shifts/:shiftId/apply` is the concrete `shift_roles.id` for that shift, not the catalog role definition id.
- `accountType` on `POST /staff/profile/bank-accounts` accepts either lowercase or uppercase and is normalized by the backend.
- Document routes now return only document rows. They do not mix in attire items anymore.
- Tax-form data should come from `GET /staff/profile/tax-forms`, not `GET /staff/profile/documents`.
- Staff benefit activity should come from `GET /staff/profile/benefits/history`; the summary card should keep using `GET /staff/profile/benefits`.
- File upload routes return a storage path plus a signed URL. Frontend uploads the file directly to storage using that URL.
- The frontend upload contract for documents, attire, and certificates is:
1. `POST /upload-file`
2. `POST /create-signed-url`
3. `POST /verifications`
4. finalize with:
- `PUT /staff/profile/documents/:documentId/upload`
- `PUT /staff/profile/attire/:documentId/upload`
- `POST /staff/profile/certificates`
- Finalization requires `verificationId`. Frontend may still send `fileUri` or `photoUrl`, but the backend treats the verification-linked file as the source of truth.
- `POST /rapid-orders/process` is the single-call route for "transcribe + parse".
- `POST /client/orders/:orderId/edit` builds a replacement order from future shifts only.
- `POST /client/orders/:orderId/cancel` cancels future shifts only on the mobile surface and leaves historical shifts intact.
- Verification upload and review routes are live and were validated through document, attire, and certificate flows. Do not rely on long-lived verification history durability until the dedicated persistence slice is landed in `core-api-v2`.
- Attendance policy is explicit. Reads now expose `clockInMode` and `allowClockInOverride`.
- `clockInMode` values are:
@@ -206,7 +304,11 @@ These are exposed as direct unified aliases even though they are backed by `core
- send `overrideReason` only when the worker is bypassing a geofence failure and the shift/hub allows overrides
- `POST /staff/location-streams` is for the background tracking loop after a worker is already clocked in.
- `GET /client/coverage/incidents` is the review feed for geofence breaches, missing-location batches, and clock-in overrides.
- `GET /client/coverage/blocked-staff` is the review feed for workers currently blocked by that business.
- `POST /client/coverage/late-workers/:assignmentId/cancel` is the client-side recovery action when lateness is confirmed by incident evidence or elapsed grace time.
- `GET /client/coverage/swap-requests` is the manager/ops review feed for swap requests, candidate applications, and status.
- `GET /client/coverage/dispatch-candidates` returns ranked candidates with the dispatch-team priority already applied.
- swap auto-cancellation is backend-driven. If a swap request expires without a replacement, backend cancels the original assignment, marks the swap request `AUTO_CANCELLED`, and alerts both the manager path and the original worker.
- Raw location stream payloads are stored in the private v2 bucket; SQL only stores the summary and incident index.
- Push delivery is backed by:
- SQL token registry in `device_push_tokens`

View File

@@ -1,5 +1,9 @@
# Data Connect Connectors Pattern
> [!WARNING]
> This document describes the legacy V1 Data Connect connector pattern.
> For current backend work, use the V2 unified API docs under `docs/BACKEND/API_GUIDES/V2/`.
## Overview
This document describes the **Data Connect Connectors** pattern implemented in the KROW mobile app. This pattern centralizes all backend query logic by mirroring backend connector structure in the mobile data layer.
@@ -45,9 +49,9 @@ apps/mobile/packages/data_connect/lib/src/connectors/
└── ...
```
**Maps to backend structure:**
**Maps to legacy backend structure:**
```
backend/dataconnect/connector/
legacy/dataconnect-v1/connector/
├── staff/
├── order/
├── user/
@@ -260,7 +264,7 @@ When backend adds new connector (e.g., `order`):
- `staff_main` - Guards bottom nav items requiring profile completion
**Backend Queries Used**:
- `backend/dataconnect/connector/staff/queries/profile_completion.gql`
- `legacy/dataconnect-v1/connector/staff/queries/profile_completion.gql`
### Shifts Connector
@@ -271,15 +275,15 @@ When backend adds new connector (e.g., `order`):
- `applyForShifts()` - Handles shift application with error tracking
**Backend Queries Used**:
- `backend/dataconnect/connector/shifts/queries/list_shift_roles_by_vendor.gql`
- `backend/dataconnect/connector/shifts/mutations/apply_for_shifts.gql`
- `legacy/dataconnect-v1/connector/shifts/queries/list_shift_roles_by_vendor.gql`
- `legacy/dataconnect-v1/connector/shifts/mutations/apply_for_shifts.gql`
## Future Expansion
As the app grows, additional connectors will be added:
- `order_connector_repository` (queries from `backend/dataconnect/connector/order/`)
- `user_connector_repository` (queries from `backend/dataconnect/connector/user/`)
- `emergency_contact_connector_repository` (queries from `backend/dataconnect/connector/emergencyContact/`)
- `order_connector_repository` (queries from `legacy/dataconnect-v1/connector/order/`)
- `user_connector_repository` (queries from `legacy/dataconnect-v1/connector/user/`)
- `emergency_contact_connector_repository` (queries from `legacy/dataconnect-v1/connector/emergencyContact/`)
- etc.
Each following the same Clean Architecture pattern implemented for Staff Connector.