Merge dev

This commit is contained in:
2026-02-25 13:34:52 +05:30
87 changed files with 5153 additions and 1534 deletions

View File

@@ -0,0 +1,367 @@
# KROW Workforce API Contracts
This document captures all API contracts used by the Staff and Client mobile applications. The application backend is powered by **Firebase Data Connect (GraphQL)**, so traditional REST endpoints do not exist natively. For clarity and ease of reading for all engineering team members, the tables below formulate these GraphQL Data Connect queries and mutations into their **Conceptual REST Endpoints** alongside the actual **Data Connect Operation Name**.
---
## Staff Application
### Authentication / Onboarding Pages
*(Pages: get_started_page.dart, intro_page.dart, phone_verification_page.dart, profile_setup_page.dart)*
#### Setup / User Validation API
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /users/{id}` |
| **Data Connect OP** | `getUserById` |
| **Purpose** | Retrieves the base user profile to determine authentication status and role access (e.g., if the user is STAFF). |
| **Operation** | Query |
| **Inputs** | `id: UUID!` (Firebase UID) |
| **Outputs** | `User { id, email, phone, role }` |
| **Notes** | Required after OTP verification to route users appropriately. |
#### Create Default User API
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `POST /users` |
| **Data Connect OP** | `createUser` |
| **Purpose** | Inserts a base user record into the system during initial signup. |
| **Operation** | Mutation |
| **Inputs** | `id: UUID!`, `role: UserBaseRole` |
| **Outputs** | `id` of newly created User |
| **Notes** | Used explicitly during the "Sign Up" flow if the user doesn't physically exist in the database. |
#### Get Staff Profile API
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /staff/user/{userId}` |
| **Data Connect OP** | `getStaffByUserId` |
| **Purpose** | Finds the specific Staff record associated with the base user ID. |
| **Operation** | Query |
| **Inputs** | `userId: UUID!` |
| **Outputs** | `Staffs { id, userId, fullName, email, phone, photoUrl, status }` |
| **Notes** | Needed to verify if a complete staff profile exists before allowing navigation to the main app dashboard. |
#### Update Staff Profile API
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `PUT /staff/{id}` |
| **Data Connect OP** | `updateStaff` |
| **Purpose** | Saves onboarding data across Personal Info, Experience, and Preferred Locations pages. |
| **Operation** | Mutation |
| **Inputs** | `id: UUID!`, `fullName`, `email`, `phone`, `address`, etc. |
| **Outputs** | `id` |
| **Notes** | Called incrementally during the profile setup wizard as the user fills out step-by-step information. |
### Home Page & Benefits Overview
*(Pages: worker_home_page.dart, benefits_overview_page.dart)*
#### Load Today/Tomorrow Shifts
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /staff/{staffId}/applications` |
| **Data Connect OP** | `getApplicationsByStaffId` |
| **Purpose** | Retrieves applications (shifts) assigned to the current staff member within a specific date range. |
| **Operation** | Query |
| **Inputs** | `staffId: UUID!`, `dayStart: Timestamp`, `dayEnd: Timestamp` |
| **Outputs** | `Applications { shift, shiftRole, status, createdAt }` |
| **Notes** | The frontend filters the query response for `CONFIRMED` applications to successfully display "Today's" and "Tomorrow's" shifts. |
#### List Recommended Shifts
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /shifts/recommended` |
| **Data Connect OP** | `listShifts` |
| **Purpose** | Fetches open shifts that are available for the staff to apply to. |
| **Operation** | Query |
| **Inputs** | None directly mapped on load, but fetches available items logically. |
| **Outputs** | `Shifts { id, title, orderId, cost, location, startTime, endTime, status }` |
| **Notes** | Limits output to 10 on the frontend. Should ideally rely on an active backend `$status: OPEN` parameter. |
#### Benefits Summary API
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /staff/{staffId}/benefits` |
| **Data Connect OP** | `listBenefitsDataByStaffId` |
| **Purpose** | Retrieves accrued benefits (e.g., Sick time, Vacation) to display gracefully on the home screen. |
| **Operation** | Query |
| **Inputs** | `staffId: UUID!` |
| **Outputs** | `BenefitsDatas { vendorBenefitPlan { title, total }, current }` |
| **Notes** | Used by `benefits_overview_page.dart`. Derives available metrics via `usedHours = total - current`. |
### Find Shifts / Shift Details Pages
*(Pages: shifts_page.dart, shift_details_page.dart)*
#### List Available Shifts Filtered
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /shifts` |
| **Data Connect OP** | `filterShifts` |
| **Purpose** | Used to fetch Open Shifts in specific regions when the worker searches in the "Find Shifts" tab. |
| **Operation** | Query |
| **Inputs** | `$status: ShiftStatus`, `$dateFrom: Timestamp`, `$dateTo: Timestamp` |
| **Outputs** | `Shifts { id, title, location, cost, durationDays, order { business, vendor } }` |
| **Notes** | Main driver for discovering available work. |
#### Get Shift Details
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /shifts/{id}` |
| **Data Connect OP** | `getShiftById` |
| **Purpose** | Gets deeper details for a single shift including exact uniform requirements and managers. |
| **Operation** | Query |
| **Inputs** | `id: UUID!` |
| **Outputs** | `Shift { id, title, hours, cost, locationAddress, workersNeeded ... }` |
| **Notes** | Invoked when users click into a full `shift_details_page.dart`. |
#### Apply To Shift
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `POST /applications` |
| **Data Connect OP** | `createApplication` |
| **Purpose** | Worker submits an intent to take an open shift (creates an application record). |
| **Operation** | Mutation |
| **Inputs** | `shiftId: UUID!`, `staffId: UUID!`, `roleId: UUID!`, `status: ApplicationStatus!` (e.g. `PENDING` or `CONFIRMED`), `origin: ApplicationOrigin!` (e.g. `STAFF`); optional: `checkInTime`, `checkOutTime` |
| **Outputs** | `application_insert.id` (Application ID) |
| **Notes** | The app uses `status: CONFIRMED` and `origin: STAFF` when claiming; backend also supports `PENDING` for admin review flows. After creation, shift-role assigned count and shift filled count are updated. |
### Availability Page
*(Pages: availability_page.dart)*
#### Get Default Availability
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /staff/{staffId}/availabilities` |
| **Data Connect OP** | `listStaffAvailabilitiesByStaffId` |
| **Purpose** | Fetches the standard Mon-Sun recurring availability for a staff member. |
| **Operation** | Query |
| **Inputs** | `staffId: UUID!` |
| **Outputs** | `StaffAvailabilities { dayOfWeek, isAvailable, startTime, endTime }` |
| **Notes** | Bound to Monday through Sunday configuration. |
#### Update Availability
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `PUT /staff/availabilities/{id}` |
| **Data Connect OP** | `updateStaffAvailability` (or `createStaffAvailability` for new entries) |
| **Purpose** | Upserts availability preferences. |
| **Operation** | Mutation |
| **Inputs** | `staffId`, `dayOfWeek`, `isAvailable`, `startTime`, `endTime` |
| **Outputs** | `id` |
| **Notes** | Called individually per day edited. |
### Payments Page
*(Pages: payments_page.dart, early_pay_page.dart)*
#### Get Recent Payments
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /staff/{staffId}/payments` |
| **Data Connect OP** | `listRecentPaymentsByStaffId` |
| **Purpose** | Loads the history of earnings and timesheets completed by the staff. |
| **Operation** | Query |
| **Inputs** | `staffId: UUID!` |
| **Outputs** | `Payments { amount, processDate, shiftId, status }` |
| **Notes** | Displays historical metrics under the comprehensive Earnings tab. |
### Compliance / Profiles
*(Pages: certificates_page.dart, documents_page.dart, tax_forms_page.dart, form_i9_page.dart, form_w4_page.dart)*
#### Get Tax Forms
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /staff/{staffId}/tax-forms` |
| **Data Connect OP** | `getTaxFormsByStaffId` |
| **Purpose** | Check the filing status and detailed inputs of I9 and W4 forms. |
| **Operation** | Query |
| **Inputs** | `staffId: UUID!` |
| **Outputs** | `TaxForms { formType, isCompleted, updatedDate }` |
| **Notes** | Crucial requirement for staff to be eligible to apply for highly regulated shifts. |
#### Update Tax Forms
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `PUT /tax-forms/{id}` |
| **Data Connect OP** | `updateTaxForm` |
| **Purpose** | Submits state and filing for the given tax form type (W4/I9). |
| **Operation** | Mutation |
| **Inputs** | `id`, `dataPoints...` |
| **Outputs** | `id` |
| **Notes** | Modifies the core compliance state variables directly. |
---
## Client Application
### Authentication / Intro
*(Pages: client_sign_in_page.dart, client_get_started_page.dart)*
#### Client User Validation API
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /users/{id}` |
| **Data Connect OP** | `getUserById` |
| **Purpose** | Retrieves the base user profile to determine authentication status and role access (ensuring user is BUSINESS). |
| **Operation** | Query |
| **Inputs** | `id: UUID!` (Firebase UID) |
| **Outputs** | `User { id, email, phone, userRole }` |
| **Notes** | Validates against conditional statements checking `userRole == BUSINESS` or `BOTH`. |
#### Get Businesses By User API
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/user/{userId}` |
| **Data Connect OP** | `getBusinessesByUserId` |
| **Purpose** | Maps the authenticated user to their client business context. |
| **Operation** | Query |
| **Inputs** | `userId: String!` |
| **Outputs** | `Businesses { id, businessName, email, contactName }` |
| **Notes** | Dictates the working scopes (Business ID) across the entire application lifecycle and binds the user. |
### Hubs Page
*(Pages: client_hubs_page.dart, edit_hub_page.dart, hub_details_page.dart)*
#### List Hubs by Team
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /teams/{teamId}/hubs` |
| **Data Connect OP** | `getTeamHubsByTeamId` |
| **Purpose** | Fetches the primary working sites (Hubs) for a client context by using Team mapping. |
| **Operation** | Query |
| **Inputs** | `teamId: UUID!` |
| **Outputs** | `TeamHubs { id, hubName, address, managerName, isActive }` |
| **Notes** | `teamId` is derived first from `getTeamsByOwnerId(ownerId: businessId)`. |
#### Create / Update / Delete Hub
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `POST /team-hubs` / `PUT /team-hubs/{id}` / `DELETE /team-hubs/{id}` |
| **Data Connect OP** | `createTeamHub` / `updateTeamHub` / `deleteTeamHub` |
| **Purpose** | Provisions, Edits details directly, or Removes a Team Hub location. |
| **Operation** | Mutation |
| **Inputs** | `id: UUID!`, optionally `hubName`, `address`, etc. |
| **Outputs** | `id` |
| **Notes** | Fired from `edit_hub_page.dart` mutations. |
### Orders Page
*(Pages: create_order_page.dart, view_orders_page.dart, recurring_order_page.dart)*
#### Create Order
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `POST /orders` |
| **Data Connect OP** | `createOrder` |
| **Purpose** | Submits a new request for temporary staff requirements. |
| **Operation** | Mutation |
| **Inputs** | `businessId`, `eventName`, `orderType`, `status` |
| **Outputs** | `id` (Order ID) |
| **Notes** | This explicitly invokes an order pipeline, meaning Shift instances are subsequently created through secondary mutations triggered after order instantiation. |
#### List Orders
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/{businessId}/orders` |
| **Data Connect OP** | `listOrdersByBusinessId` |
| **Purpose** | Retrieves all ongoing and past staff requests from the client. |
| **Operation** | Query |
| **Inputs** | `businessId: UUID!` |
| **Outputs** | `Orders { id, eventName }` |
| **Notes** | Populates the `view_orders_page.dart`. |
### Billing Pages
*(Pages: billing_page.dart, pending_invoices_page.dart, completion_review_page.dart)*
#### List Invoices
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/{businessId}/invoices` |
| **Data Connect OP** | `listInvoicesByBusinessId` |
| **Purpose** | Fetches all invoices bound directly to the active business context (mapped directly in Firebase Schema). |
| **Operation** | Query |
| **Inputs** | `businessId: UUID!` |
| **Outputs** | `Invoices { id, amount, issueDate, status }` |
| **Notes** | Used massively across all Billing view tabs. |
#### Mark / Dispute Invoice
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `PUT /invoices/{id}` |
| **Data Connect OP** | `updateInvoice` |
| **Purpose** | Actively marks an invoice as disputed or pays it directly (altering status). |
| **Operation** | Mutation |
| **Inputs** | `id: UUID!`, `status: InvoiceStatus` |
| **Outputs** | `id` |
| **Notes** | Disputing usually involves setting a `disputeReason` flag state dynamically via builder pattern. |
### Reports Page
*(Pages: reports_page.dart, coverage_report_page.dart, performance_report_page.dart)*
#### Get Coverage Stats
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/{businessId}/coverage` |
| **Data Connect OP** | `listShiftsForCoverage` |
| **Purpose** | Provides data on Shifts grouped by Date for fulfillment calculations. |
| **Operation** | Query |
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
| **Outputs** | `Shifts { id, date, workersNeeded, filled, status }` |
| **Notes** | The frontend aggregates the raw backend rows to compose Coverage percentage natively. |
#### Get Daily Ops Stats
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/{businessId}/dailyops` |
| **Data Connect OP** | `listShiftsForDailyOpsByBusiness` |
| **Purpose** | Supplies current day operations and shift tracking progress. |
| **Operation** | Query |
| **Inputs** | `businessId: UUID!`, `date: Timestamp!` |
| **Outputs** | `Shifts { id, title, location, workersNeeded, filled }` |
| **Notes** | - |
#### Get Forecast Stats
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/{businessId}/forecast` |
| **Data Connect OP** | `listShiftsForForecastByBusiness` |
| **Purpose** | Retrieves scheduled future shifts to calculate financial run-rates. |
| **Operation** | Query |
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
| **Outputs** | `Shifts { id, date, workersNeeded, hours, cost }` |
| **Notes** | The App maps hours `x` cost to deliver Financial Dashboards. |
#### Get Performance KPIs
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/{businessId}/performance` |
| **Data Connect OP** | `listShiftsForPerformanceByBusiness` |
| **Purpose** | Fetches historical data allowing time-to-fill and completion-rate calculations. |
| **Operation** | Query |
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
| **Outputs** | `Shifts { id, workersNeeded, filled, createdAt, filledAt }` |
| **Notes** | Data Connect exposes timestamps so the App calculates `avgFillTimeHours`. |
#### Get No-Show Metrics
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/{businessId}/noshows` |
| **Data Connect OP** | `listShiftsForNoShowRangeByBusiness` |
| **Purpose** | Retrieves shifts where workers historically ghosted the platform. |
| **Operation** | Query |
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
| **Outputs** | `Shifts { id, date }` |
| **Notes** | Accompanies `listApplicationsForNoShowRange` cascading querying to generate full report. |
#### Get Spend Analytics
| Field | Description |
|---|---|
| **Conceptual Endpoint** | `GET /business/{businessId}/spend` |
| **Data Connect OP** | `listInvoicesForSpendByBusiness` |
| **Purpose** | Detailed invoice aggregates for Spend metrics filtering. |
| **Operation** | Query |
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
| **Outputs** | `Invoices { id, issueDate, dueDate, amount, status }` |
| **Notes** | Used explicitly under the "Spend Report" graphings. |
---
*This document meticulously abstracts the underlying Data Connect Data-layer definitions implemented natively across the frontend. It maps the queries/mutations to recognizable REST equivalents for comprehensive and top-notch readability by external and internal developers alike.*

View File

@@ -0,0 +1,74 @@
flowchart LR
subgraph C1["Login"]
S_client_sign_in["client_sign_in_screen.dart"]
S_client_sign_in --> S_client_sign_in_Q["Queries<br/>* user - getUserById<br/>* business - getBusinessesByUserId"]
S_client_sign_in --> S_client_sign_in_F["Firebase<br/>* user - auth"]
end
subgraph C2["Create account"]
S_client_sign_up["client_sign_up_screen.dart"]
S_client_sign_up --> S_client_sign_up_Q["Queries<br/>* business - getBusinessesByUserId"]
S_client_sign_up --> S_client_sign_up_M["Mutations<br/>* user - createUser<br/>* business - createBusiness"]
end
subgraph C3["Edit account"]
S_edit_account_na["manual_or_unknown_screen.dart"]
S_edit_account_na --> S_edit_account_na_M["Mutations<br/>* business - updateBusiness"]
end
subgraph C4["Profile"]
S_client_settings["client_settings_screen.dart"]
S_client_settings --> S_client_settings_Q["Queries<br/>* user - getUserById<br/>* business - getBusinessesByUserId"]
end
subgraph C5["Hubs"]
S_client_hubs["client_hubs_screen.dart"]
S_client_hubs --> S_client_hubs_Q["Queries<br/>* TeamHub - listTeamHubsByOwnerId"]
S_client_hubs --> S_client_hubs_M["Mutations<br/>* TeamHub - createTeamHub<br/>* TeamHub - updateTeamHub<br/>* TeamHub - deleteTeamHub"]
end
subgraph C6["Orders"]
S_client_shifts["client_shifts_screen.dart"]
S_client_shifts --> S_client_shifts_Q["Queries<br/>* order - getOrdersByBusinessId"]
end
subgraph C7["RAPID Order"]
S_rapid_order["rapid_order_flow_page.dart"]
S_rapid_order --> S_rapid_order_M["Mutations<br/>* order - createOrder"]
end
subgraph C8["One-time Order"]
S_one_time["one_time_order_flow_page.dart"]
S_one_time --> S_one_time_Q["Queries<br/>* role - listRolesByOwnerId"]
S_one_time --> S_one_time_M["Mutations<br/>* ShiftRole - createShiftRole<br/>* order - createOrder"]
end
subgraph C9["Permanent Placement"]
S_permanent["permanent_order_flow_page.dart"]
S_permanent --> S_permanent_Q["Queries<br/>* role - listRolesByOwnerId"]
S_permanent --> S_permanent_M["Mutations<br/>* ShiftRole - createShiftRole<br/>* order - createOrder"]
end
subgraph C10["Recurring Order"]
S_recurring["recurring_order_flow_page.dart"]
S_recurring --> S_recurring_Q["Queries<br/>* role - listRolesByOwnerId"]
S_recurring --> S_recurring_M["Mutations<br/>* ShiftRole - createShiftRole<br/>* order - createOrder"]
end
subgraph C11["Billing"]
S_billing["client_billing_screen.dart"]
S_billing --> S_billing_Q["Queries<br/>* account - getAccountsByOwnerId<br/>* invoice - listInvoicesByBusinessId<br/>* recentPayment - listRecentPaymentsByBusinessId"]
S_billing --> S_billing_M["Mutations<br/>* account - createAccount<br/>* account - updateAccount<br/>* account - deleteAccount"]
end
subgraph C12["Coverage"]
S_coverage["coverage_dashboard.dart"]
S_coverage --> S_coverage_Q["Queries<br/>* order - getOrdersByBusinessId<br/>* shift - getShiftsByBusinessId<br/>* application - getApplicationsByShiftId"]
end
subgraph C13["Home"]
S_client_home["client_home_screen.dart"]
S_client_home --> S_client_home_Q["Queries<br/>* order - getOrdersByBusinessId<br/>* shift - getShiftsByBusinessId<br/>* application - getApplicationsByShiftId<br/>* recentPayment - listRecentPaymentsByBusinessId"]
S_client_home --> S_client_home_M["Mutations<br/>* order - createOrder"]
end

View File

@@ -0,0 +1,121 @@
flowchart LR
subgraph L1["login/create user"]
S_auth_phone["phone_verification_screen.dart"]
S_auth_phone --> S_auth_phone_Q["Queries<br/>* user - getUserById<br/>* staff - getStaffByUserId"]
S_auth_phone --> S_auth_phone_M["Mutations<br/>* user - createUser"]
S_auth_phone --> S_auth_phone_F["Firebase<br/>* user - auth"]
end
subgraph L2["Profile"]
S_worker_profile["worker_profile_screen.dart"]
S_worker_profile --> S_worker_profile_Q["Queries<br/>* user - getUserById<br/>* staff - getStaffByUserId"]
end
subgraph L3["Personal info"]
S_personal_info["personal_info_screen.dart"]
S_personal_info --> S_personal_info_Q["Queries<br/>* staff - getStaffByUserId"]
S_personal_info --> S_personal_info_M["Mutations<br/>* staff - UpdateStaff"]
end
subgraph L4["Emergency Contact"]
S_emergency["emergency_contact_screen.dart"]
S_emergency --> S_emergency_Q["Queries<br/>* emergencyContact - getEmergencyContactsByStaffId"]
S_emergency --> S_emergency_M["Mutations<br/>* conemergencyContacttact - updateEmergencyContact<br/>* emergencyContact - createEmergencyContact<br/>* contemergencyContactact - deleteEmergencyContact"]
end
subgraph L5["Experience & skills"]
S_experience["experience_screen.dart"]
S_experience --> S_experience_Q["Queries<br/>* staff - getStaffByUserId"]
S_experience --> S_experience_M["Mutations<br/>* staff - UpdateStaff"]
end
subgraph L6["Attire"]
S_attire["attire_screen.dart"]
S_attire --> S_attire_Q["Queries<br/>* attireOption - filterAttireOptions<br/>* staff - getStaffByUserId"]
S_attire --> S_attire_M["Mutations<br/>* staff - UpdateStaff"]
end
subgraph L7["Documents"]
S_documents["documents_screen.dart"]
S_documents --> S_documents_Q["Queries<br/>* document - listDocuments<br/>* staffDocument - listStaffDocumentsByStaffId"]
S_documents --> S_documents_M["Mutations<br/>* staffDocument - updateStaffDocument<br/>* staffDocument - createStaffDocument"]
end
subgraph L8["Certificates"]
S_certificates["certificates_screen.dart"]
S_certificates --> S_certificates_Q["Queries<br/>* certificate - listCertificatesByStaffId"]
S_certificates --> S_certificates_M["Mutations<br/>* certificate - UpdateCertificate<br/>* certificate - CreateCertificate<br/>* certificate - DeleteCertificate"]
end
subgraph L9["Tax Documents"]
S_tax_forms["tax_forms_screen.dart"]
S_tax_forms --> S_tax_forms_Q["Queries<br/>* taxForm - getTaxFormsBystaffId"]
S_tax_forms --> S_tax_forms_M["Mutations<br/>* taxForm - createTaxForm<br/>* taxForm - updateTaxForm"]
end
subgraph L10["KROW University"]
S_uni["krow_university_screen.dart"]
S_uni --> S_uni_Q["Queries<br/>* course - listCourses<br/>* staffCourse - listStaffCoursesByStaffId<br/>* staff - getStaffByUserId<br/>* level - listLevels<br/>* certificate - listCertificatesByStaffId"]
end
subgraph L11["Trainings"]
S_trainings["trainings_screen.dart"]
S_trainings --> S_trainings_Q["Queries<br/>* course - listCourses<br/>* staffCourse - listStaffCoursesByStaffId"]
end
subgraph L12["Leaderboard"]
S_leaderboard["leaderboard_screen.dart"]
S_leaderboard --> S_leaderboard_Q["Queries<br/>* staffCourse - missing"]
end
subgraph L13["Bank Account"]
S_bank["bank_account_screen.dart"]
S_bank --> S_bank_Q["Queries<br/>* account - getAccountsByOwnerId"]
S_bank --> S_bank_M["Mutations<br/>* account - createAccount<br/>* account - updateAccount<br/>* account - deleteAccount"]
end
subgraph L14["Earnings/Payments"]
S_payments["payments_screen.dart"]
S_payments --> S_payments_Q["Queries<br/>* recentPayment - listRecentPaymentsByStaffId"]
end
subgraph L15["Timecard"]
S_timecard["time_card_screen.dart"]
S_timecard --> S_timecard_Q["Queries<br/>* application - getApplicationsByStaffId"]
end
subgraph L16["Clock in"]
S_clockin["clock_in_screen.dart"]
S_clockin --> S_clockin_Q["Queries<br/>* application - getApplicationsByStaffId"]
S_clockin --> S_clockin_M["Mutations<br/>* application - createApplication<br/>* application - updateApplicationStatus"]
end
subgraph L17["Shifts"]
S_shifts["shifts_screen.dart"]
S_shifts --> S_shifts_Q["Queries<br/>* application - getApplicationsByStaffId<br/>* shiftRole - listShiftRolesByVendorId/listShiftRolesByRoleId<br/>* application - getApplicationsByStaffId"]
S_shifts --> S_shifts_M["Mutations<br/>* application - updateApplicationStatus<br/>* application - createApplication"]
end
subgraph L18["My availability"]
S_availability["availability_screen.dart"]
S_availability --> S_availability_Q["Queries<br/>* staffAvailability - listStaffAvailabilitiesByStaffId/getStaffAvailabilityByKey"]
S_availability --> S_availability_M["Mutations<br/>* staffAvailability - updateStaffAvailability<br/>* staffAvailability - createStaffAvailability<br/>* staffAvailability - deleteStaffAvailability"]
end
subgraph L19["Your Benefits Overview"]
S_benefits["benefits_screen.dart"]
S_benefits --> S_benefits_Q["Queries<br/>* benefitsData - listBenefitsDataByStaffId"]
S_benefits --> S_benefits_M["Mutations<br/>* benefitsData - updateBenefitsData<br/>* benefitsData - createBenefitsData"]
end
subgraph L20["Home"]
S_home["worker_home_screen.dart"]
S_home --> S_home_Q["Queries<br/>* application - getApplicationsByStaffId<br/>* shiftRole - listShiftRolesByVendorId/listShiftRolesByRoleId<br/>* benefitsData - getBenefitsDataByStaffId"]
end
subgraph L21["Shift detail"]
S_shift_detail["shift_details_screen.dart"]
S_shift_detail --> S_shift_detail_Q["Queries<br/>* application - getApplicationsByStaffId"]
S_shift_detail --> S_shift_detail_M["Mutations<br/>* application - updateApplicationStatus"]
end

View File

@@ -0,0 +1,130 @@
---
config:
theme: mc
layout: dagre
---
classDiagram
direction TB
class User {
id: String
email: String
}
class Business {
id: UUID
userId: String
businessName: String
status: BusinessStatus
}
class Vendor {
id: UUID
userId: String
companyName: String
}
class Order {
id: UUID
businessId: UUID
vendorId: UUID
status: OrderStatus
}
class Shift {
id: UUID
orderId: UUID
status: ShiftStatus
}
class ShiftRole {
shiftId: UUID
roleId: UUID
}
class Role {
id: UUID
name: String
vendorId: UUID
}
class Application {
id: UUID
shiftId: UUID
staffId: UUID
roleId: UUID
}
class Invoice {
id: UUID
businessId: UUID
vendorId: UUID
orderId: UUID
status: InvoiceStatus
}
class InvoiceTemplate {
id: UUID
name: String
ownerId: UUID
businessId: UUID
vendorId: UUID
}
class RecentPayment {
id: UUID
invoiceId: UUID
applicationId: UUID
staffId: UUID
}
class ClientFeedback {
id: UUID
businessId: UUID
vendorId: UUID
rating: Int
}
class Team {
id: UUID
teamName: String
ownerId: String
}
class TeamMember {
id: UUID
teamId: UUID
userId: String
role: TeamMemberRole
}
class TeamHub {
id: UUID
teamId: UUID
hubName: String
}
class Staff {
id: UUID
userId: String
}
note for Staff "business can create a staff too"
Business "1" -- "*" Order : places
Business "1" -- "*" Invoice : receives
Business "1" -- "*" ClientFeedback : gives
Business "1" --o "1" Team : (ownerId)
Business "1" --o "*" InvoiceTemplate : (ownerId)
User "1" -- "1" Business : owns
Vendor "1" -- "*" Order : fulfills
Vendor "1" -- "*" Invoice : issues
Order "1" -- "*" Shift : contains
Order "1" -- "1" Invoice : billed via
Shift "1" -- "*" ShiftRole : requires
ShiftRole "1" -- "1" Role
ShiftRole "1" -- "*" Application : target for
Application "1" -- "1" Staff
Application "1" -- "1" RecentPayment
Invoice "1" -- "*" RecentPayment : paid through
Team "1" -- "*" TeamHub : contains
Team "1" -- "*" TeamMember : has

View File

@@ -0,0 +1,181 @@
classDiagram
direction TB
class User {
id: String
email: String
fullName: String
role: UserBaseRole
}
class Staff {
id: UUID
userId: String
fullName: String
ownerId: UUID
hubId: UUID
rollId: UUID
status: BackgroundCheckStatus
}
class Account{
id: UUID
ownerId:UUID
type:AccountType
}
class Workforce {
id: UUID
vendorId: UUID
staffId: UUID
status: WorkforceStatus
}
class Application {
id: UUID
shiftId: UUID
staffId: UUID
roleId: UUID
status: ApplicationStatus
}
class Assignment {
id: UUID
workforceId: UUID
shiftId: UUID
roleId: UUID
status: AssignmentStatus
}
class ShiftRole {
id: UUID
shiftId: UUID
roleId: UUID
}
class Shift {
id: UUID
orderId: UUID
status: ShiftStatus
}
class Order {
id: UUID
vendorId: UUID
businessId: UUID
status: OrderStatus
}
class Vendor {
id: UUID
userId: String
companyName: String
}
class Business {
id: UUID
userId: String
businessName: String
}
class Role {
id: UUID
name: String
vendorId: UUID
roleCategoryId: UUID
}
class StaffDocument {
staffId: UUID
documentId: UUID
status: DocumentStatus
}
class Document {
id: UUID
name: String
documentType: DocumentType
}
class StaffCourse {
staffId: UUID
courseId: UUID
}
class Course {
id: UUID
title: String
categoryId: UUID
}
class Category {
id: UUID
label: String
}
class StaffAvailability {
staffId: UUID
day: DayOfWeek
slot: AvailabilitySlot
}
class Certificate {
staffId: UUID
certificationType: ComplianceType
status: CertificateStatus
}
class BenefitData {
staffId: UUID
vendorBenefitPlanId: UUID
current: int
}
class Contact {
staffId: UUID
relationship: RelationshipType
name: string
}
class Invoice {
orderId: UUID
}
class RecentPayment {
staffId: UUID
applicationId: UUID
status: RecentPaymentStatus
}
class TaxForm {
staffId: UUID
formType: TaxFormType
status: TaxFormStatus
}
User "1" -- "1" Staff : has
Staff "1" -- "*" Application : applies to
Staff "1" -- "*" Workforce : part of
Staff "1" -- "*" StaffDocument : has
Staff "1" -- "*" Certificate : has
Staff "1" -- "*" BenefitData : has
Staff "1" -- "*" Contact : has
Staff "1" -- "*" TaxForm : has
Staff "1" -- "*" Account : has
Staff "1" -- "*" StaffCourse : takes
Staff "1" -- "*" StaffAvailability : sets
Workforce "1" -- "*" Assignment : receives
Assignment -- ShiftRole
Vendor "1" -- "*" Order : receives
Business "1" -- "*" Order : places
Order "1" -- "1" Invoice : has
Invoice "1" -- "*" RecentPayment : has
RecentPayment "1" -- "1" Application : has
Staff "1" --o "1" Vendor : (ownerId)
Staff "1" --o "1" Business : (ownerId)
Application -- ShiftRole
ShiftRole "*" -- "1" Shift : belongs to
ShiftRole "*" -- "1" Role : defines
Shift "*" -- "1" Order : belongs to
StaffDocument "1" -- "1" Document : references
StaffCourse "1" -- "1" Course : references
Course "1" -- "1" Category : belongs to

View File

@@ -0,0 +1,79 @@
---
config:
layout: elk
theme: mc
---
classDiagram
class User {
id: String
email: String
fullName: String
}
class TeamMember {
id: UUID
teamId: UUID
userId: String
teamHubId: UUID
role: TeamMemberRole
inviteStatus: TeamMemberInviteStatus
inviteCode: UUID
}
class Team {
id: UUID
teamName: String
ownerId: String
}
class TeamHub {
id: UUID
teamId: UUID
hubName: String
}
class TeamHudDepartment {
id: UUID
name: String
teamHubId: UUID
}
class MemberTask {
teamMemberId: UUID
taskId: UUID
}
class Task {
id: UUID
taskName: String
status: TaskStatus
priority: TaskPriority
ownerId: UUID
}
class Vendor {
id: UUID
companyName: String
}
class Business {
id: UUID
businessName: String
}
User "1" -- "1" TeamMember : has
Team "1" -- "*" TeamHub : contains
Team "1" --o "1" Vendor : (ownerId)
Team "1" --o "1" Business : (ownerId)
TeamHub "1" -- "*" TeamHudDepartment : has
TeamHub "1" -- "*" TeamMember : is assigned to
TeamMember "*" -- "1" Team
TeamMember "1" -- "*" MemberTask : has assigned
Task "1" -- "*" MemberTask : is assigned to
Task --o Vendor : (ownerId)
Task --o Business : (ownerId)

View File

@@ -0,0 +1,71 @@
classDiagram
direction TB
class User {
id: String
email: String
fullName: String
role: UserBaseRole
}
class Staff {
id: UUID
userId: String
fullName: String
ownerId: UUID
hubId: UUID
rollId: UUID
status: BackgroundCheckStatus
}
class Vendor {
id: UUID
userId: String
companyName: String
}
class Business {
id: UUID
userId: String
businessName: String
}
class TeamMember {
id: UUID
teamId: UUID
userId: String
role: TeamMemberRole
}
class ActivityLog {
id: UUID
userId: String
activityType: ActivityType
}
class UserConversation {
conversationId: UUID
userId: String
}
class Conversation {
id: UUID
conversationType: ConversationType
}
class Message{
id: UUID
conversationId: UUID
content: String
}
User <|-- Staff
User <|-- Business
User <|-- Vendor
User <|-- TeamMember
User "1" -- "*" ActivityLog : logs
User "1" -- "*" UserConversation : participates in
UserConversation "*" -- "1" Conversation : is part of
Conversation "1" -- "*" Message : has

View File

@@ -0,0 +1,164 @@
---
config:
layout: dagre
---
classDiagram
direction TB
class User {
id: String
email: String
}
class Vendor {
id: UUID
userId: String
companyName: String
tier: VendorTier
}
class Staff {
id: UUID
userId: String
fullName: String
}
class VendorBenefitPlan {
id: UUID
vendorId: UUID
title: String
}
class InvoiceTemplate {
id: UUID
name: String
ownerId: UUID
}
class VendorRate {
id: UUID
vendorId: UUID
roleName: String
}
class AttireOption{
id: UUID
vendorId: UUID
}
class Team {
id: UUID
teamName: String
ownerId: String
}
class TeamMember {
id: UUID
teamId: UUID
userId: String
role: TeamMemberRole
}
class TeamHub {
id: UUID
teamId: UUID
hubName: String
}
class Business {
id: UUID
userId: String
businessName: String
}
class ClientFeedback {
id: UUID
businessId: UUID
vendorId: UUID
rating: Int
}
class Order {
id: UUID
vendorId: UUID
businessId: UUID
status: OrderStatus
}
class Shift {
id: UUID
orderId: UUID
status: ShiftStatus
}
class Role {
id: UUID
name: String
vendorId: UUID
}
class ShiftRole {
shiftId: UUID
roleId: UUID
}
class Workforce {
id: UUID
vendorId: UUID
staffId: UUID
status: WorkforceStatus
}
class Application {
id: UUID
shiftId: UUID
staffId: UUID
roleId: UUID
status: ApplicationStatus
}
class Assignment {
id: UUID
workforceId: UUID
shiftId: UUID
roleId: UUID
status: AssignmentStatus
}
class Invoice {
id: UUID
vendorId: UUID
businessId: UUID
orderId: UUID
status: InvoiceStatus
}
class RecentPayment {
id: UUID
staffId: UUID
applicationId: UUID
invoiceId: UUID
status: RecentPaymentStatus
}
note for Vendor "All tables has relationship with vendor"
User "1" -- "1" Vendor : has
Vendor "1" o-- "1" Staff : (ownerId)
Vendor "1" -- "*" VendorBenefitPlan : offers
Vendor "1" -- "*" AttireOption
Vendor o-- InvoiceTemplate : (ownerId)
Vendor "1" -- "*" VendorRate : has
Vendor "1" -- "*" ClientFeedback : receives
Vendor "1" -- "*" Order : receives
Business "1" -- "*" Order : places
Order "1" -- "*" Shift : contains
Shift "1" -- "*" ShiftRole : requires
Role "1" -- "*" ShiftRole : fills
ShiftRole "1" -- "*" Application : receives
Assignment "1" -- "1" ShiftRole
Assignment "1" -- "1" Workforce
Invoice "1" -- "1" Order
Invoice "1" -- "*" RecentPayment : details
Vendor "1" o-- "1" Team
Team "1" -- "*" TeamHub : contains
Team "1" -- "*" TeamMember : has

View File

@@ -0,0 +1,294 @@
# Krow Workforce Backend Manual
Firebase Data Connect + Cloud SQL (PostgreSQL)
---
## 1. Backend Overview
This project uses Firebase Data Connect with Cloud SQL (PostgreSQL) as the main backend system.
The architecture is based on:
- GraphQL Schemas → Define database tables
- Connectors (Queries & Mutations) → Data access layer
- Cloud SQL → Real database
- Auto-generated SDK → Used by Web & Mobile apps
- Makefile → Automates backend workflows
The goal is to keep the backend scalable, structured, and aligned with Web and Mobile applications.
---
## 2. Project Structure
```
dataconnect/
├── dataconnect.yaml
├── schema/
│ ├── Staff.gql
│ ├── Vendor.gql
│ ├── Business.gql
│ └── ...
├── connector/
│ ├── staff/
│ │ ├── queries.gql
│ │ └── mutations.gql
│ ├── invoice/
│ └── ...
├── connector/connector.yaml
docs/backend-diagrams/
│ ├── business_uml_diagram.mmd
│ ├── staff_uml_diagram.mmd
│ ├── team_uml_diagram.mmd
│ ├── user_uml_diagram.mmd
│ └── vendor_uml_diagram_simplify.mmd
```
---
## 3. dataconnect.yaml (Main Configuration)
```yaml
specVersion: "v1"
serviceId: "krow-workforce-db"
location: "us-central1"
schema:
source: "./schema"
datasource:
postgresql:
database: "krow_db"
cloudSql:
instanceId: "krow-sql"
connectorDirs: ["./connector"]
```
### Purpose
| Field | Description |
|------|------------|
| serviceId | Data Connect service name |
| schema.source | Where GraphQL schemas live |
| datasource | Cloud SQL connection |
| connectorDirs | Where queries/mutations are |
---
## 4. Database Schemas
All database schemas are located in:
```
dataconnect/schema/
```
Each `.gql` file represents a table:
- Staff.gql
- Invoice.gql
- ShiftRole.gql
- Application.gql
- etc.
Schemas define:
- Fields
- Enums
- Relationships (`@ref`)
- Composite keys (`key: []`)
---
## 5. Queries & Mutations (Connectors)
Located in:
```
dataconnect/connector/<entity>/
```
Example:
```
dataconnect/connector/staff/queries.gql
dataconnect/connector/staff/mutations.gql
```
Each folder represents one entity.
This layer defines:
- listStaff
- getStaffById
- createStaff
- updateStaff
- deleteStaff
- etc.
---
## 6. connector.yaml (SDK Generator)
```yaml
connectorId: example
generate:
dartSdk:
- outputDir: ../../mobile/staff/staff_app_mvp/lib/dataconnect_generated
package: dataconnect_generated/generated.dart
- outputDir: ../../mobile/client/client_app_mvp/lib/dataconnect_generated
package: dataconnect_generated/generated.dart
```
This file generates the SDK for:
- Staff Mobile App
- Client Mobile App
---
## 7. What is the SDK?
The SDK is generated using:
```bash
firebase dataconnect:sdk:generate
```
It allows the apps to:
- Call queries/mutations
- Use strong typing
- Avoid manual GraphQL
- Reduce runtime errors
Example in Flutter:
```dart
client.listStaff();
client.createInvoice();
```
---
## 8. Makefile Automation Commands
### Main Commands
| Command | Purpose |
|--------|---------|
| dataconnect-enable-apis | Enable required APIs |
| dataconnect-init | Initialize Data Connect |
| dataconnect-deploy | Deploy schemas |
| dataconnect-sql-migrate | Apply DB migrations |
| dataconnect-generate-sdk | Generate SDK |
| dataconnect-sync | Full backend update |
| dataconnect-test | Test without breaking |
| dataconnect-seed | Insert seed data |
| dataconnect-bootstrap-db | Create Cloud SQL |
---
## 9. Correct Backend Workflow
### Production Flow
```bash
make dataconnect-sync
```
Steps:
1. Deploy schema
2. Run SQL migrations
3. Generate SDK
---
### Safe Test Flow
```bash
make dataconnect-test
```
This runs:
- Deploy dry-run
- SQL diff
- Shows errors without changing DB
---
## 10. Seed Data
Current command:
```make
dataconnect-seed:
@firebase dataconnect:execute seeds/seed_min.graphql --project=$(FIREBASE_ALIAS)
```
Purpose:
- Validate schema
- Detect missing tables
- Prevent bad inserts
---
## 11. UML Diagrams
Located in:
```
docs/backend-diagrams/
```
Divided by role:
| File | Scope |
|------|-------|
| user_uml_diagram.mmd | User |
| staff_uml_diagram.mmd | Staff |
| vendor_uml_diagram_simplify.mmd | Vendor |
| business_uml_diagram.mmd | Business |
| team_uml_diagram.mmd | Teams |
Used with Mermaid to visualize relationships.
---
## 12. Core Business Workflow
```text
Order
→ Shift
→ ShiftRole
→ Application
→ Workforce
→ Assignment
→ Invoice
→ RecentPayment
```
This represents the full work & payment lifecycle.
---
## 13. Final Notes
This backend is designed to:
- Scale efficiently
- Maintain data consistency
- Align Web & Mobile models
- Support reporting and billing
- Avoid duplicated data
---
END OF MANUAL

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
# Backend Cloud Run / Functions Guide
## 1) Validate Shift Acceptance (Worker)
**Best fit:** Cloud Run
**Why backend logic is required**
- Shift acceptance must be enforced serverside to prevent bypassing the client.
- It must be racecondition safe (two accepts at the same time).
- It needs to be extensible for future eligibility rules.
**Proposed backend solution**
Add a single command endpoint:
- `POST /shifts/:shiftId/accept`
**Backend flow**
- Verify Firebase Auth token + permissions (worker identity).
- Run an extensible validation pipeline (pluggable rules):
- `NoOverlapRule` (M4)
- Future rules can be added without changing core logic.
- Apply acceptance in a DB transaction (atomic).
- Return a clear error payload on rejection:
- `409 CONFLICT` (overlap) with `{ code, message, conflictingShiftIds }`
---
## 2) Validate Shift Creation by a Client (Minimum Hours — soft check)
**Best fit:** Cloud Run
**Why backend logic is required**
- Creation rules must be enforced serverside so clients cant bypass validations by skipping the UI or calling APIs directly.
- We want a scalable rule system so new creation checks can be added without rewriting core logic.
**Proposed backend solution**
Add/route creation through a backend validation layer (Cloud Run endpoint or a dedicated “create order” command).
**On create**
- Compute shift duration and compare against vendor minimum (current: **5 hours**).
- Return a consistent validation response when below minimum, e.g.:
- `200 OK` with `{ valid: false, severity: "SOFT", code: "MIN_HOURS", message, minHours: 5 }`
- (or `400` only if we decide it should block creation; for now its a soft check)
**FE note**
- Show the same message before submission (UX feedback), but backend remains the source of truth.
---
## 3) Enforce Cancellation Policy (no cancellations within 24 hours)
**Best fit:** Cloud Run
**Why backend logic is required**
- Cancellation restrictions must be enforced serverside to prevent policy bypass.
- Ensures consistent behavior across web/mobile and future clients.
**Proposed backend solution**
Add a backend command endpoint for cancel:
- `POST /shifts/:shiftId/cancel` (or `/orders/:id/cancel` depending on ownership model)
**Backend checks**
- If `now >= shiftStart - 24h`, reject cancellation.
**Error response**
- `403 FORBIDDEN` (or `409 CONFLICT`) with `{ code: "CANCEL_WINDOW_LOCKED", message, windowHours: 24, penalty: <TBD> }`
- Once penalty is finalized, include it in the response and logs/audit trail.
---
## 4) Implement Worker Documentation Upload Process
**Best fit:** Cloud Functions v2 + Cloud Storage
**Why backend logic is required**
- Uploads must be stored securely and reliably linked to the correct worker profile.
- Requires serverside auth and auditing.
**Proposed backend solution**
- HTTP/Callable Function: `uploadInit(workerId, docType)` → returns signed upload URL + `documentId`.
- Client uploads directly to Cloud Storage.
- Storage trigger (`onFinalize`) or `uploadComplete(documentId)`:
- Validate uploader identity/ownership.
- Store metadata in DB (type, path, status, timestamps).
- Link document to worker profile.
- Enforce access control (worker/self, admins, authorized client reviewers).
---
## 5) Parse Uploaded Documentation for Verification
**Best fit:** Cloud Functions (eventdriven) or Cloud Run worker (async)
**Why backend logic is required**
- Parsing should run asynchronously.
- Store structured results for review while keeping manual verification as the final authority.
**Proposed backend solution**
- Trigger on Storage upload finalize:
- OCR/AI extract key fields → store structured output (`parsedFields`, `confidence`, `aiStatus`).
- Keep manual review:
- Client can approve/override AI results.
- Persist reviewer decision + audit trail.
---
## 6) Support Attire Upload for Workers
**Best fit:** Cloud Functions v2 + Cloud Storage
**Why backend logic is required**
- Attire images must be securely stored and linked to the correct worker profile.
- Requires serverside authorization.
**Proposed backend solution**
- HTTP/Callable Function: `attireUploadInit(workerId)` → signed upload URL + `attireAssetId`.
- Client uploads to Cloud Storage.
- Storage trigger (`onFinalize`) or `attireUploadComplete(attireAssetId)`:
- Validate identity/ownership.
- Store metadata and link to worker profile.
---
## 7) Verify Attire Images Against Shift Dress Code
**Best fit:** Cloud Functions (trigger) or Cloud Run worker (async)
**Why backend logic is required**
- Verification must be enforced serverside.
- Must remain reviewable/overrideable by the client even if AI passes.
**Proposed backend solution**
- Async verification triggered after upload or when tied to a shift:
- Evaluate dress code rules (and optional AI).
- Store results `{ status, reasons, evidence, confidence }`.
- Client can manually approve/override; audit every decision.
---
## 8) Support Shifts Requiring “Awaiting Confirmation” Status
**Best fit:** Cloud Run (domain state transitions)
**Why backend logic is required**
- State transitions must be enforced serverside.
- Prevent invalid bookings and support future workflow rules.
**Proposed backend solution**
- Add status flow: `AWAITING_CONFIRMATION → BOOKED/ACTIVE` (per lifecycle).
- Command endpoint: `POST /shifts/:id/confirm`.
**Backend validates**
- Caller is the assigned worker.
- Shift is still eligible (not started/canceled/overlapped, etc.).
- Persist transition + audit event.
---
## 9) Enable NFCBased ClockIn and ClockOut
**Best fit:** Cloud Run (secure API) + optional Cloud Functions for downstream events
**Why backend logic is required**
- Clockin/out is securitysensitive and must be validated serverside.
- Requires strong auditing and antifraud checks.
**Proposed backend solution**
API endpoints:
- `POST /attendance/clock-in`
- `POST /attendance/clock-out`
**Validate**
- Firebase identity.
- NFC tag legitimacy (mapped to hub/location).
- Time window rules + prevent duplicates/inconsistent sequences.
**Persist**
- Store immutable events + derived attendance record.
- Emit audit logs/alerts if needed.
---
## 10) Update Recurring & Permanent Orders (Backend)
**Best fit:** Cloud Run
**Why backend logic is required**
Updating a recurring or permanent order is not a single update. It may affect **N shifts** and **M shift roles**, and requires extra validations, such as:
- Prevent editing shifts that already started.
- Prevent removing or reducing roles with assigned staff.
- Control whether changes apply to future only, from a given date, or all.
- Ensure data consistency (allornothing updates).
These operations can take time and must be enforced serverside, even if the client is bypassed.