Gig::Assignment
Purpose
An Assignment represents a confirmed work order — a specific talent committed to work a specific shift. It is created when both the employer and talent agree (the double handshake completes on Gig::Application).
Assignments belong to the operations phase of the gig lifecycle. They answer the question: "Who is working, when did they start/stop, and how much do we pay them?" While Applications handle recruiting (who wants to work?), Assignments handle execution (attendance, time tracking, verification, payment).
Each Assignment tracks time at two levels:
- Actual time: immutable timestamps from QR scans — the audit record of what the system observed
- Billable time: employer-confirmed hours used for payment calculation — defaults to actual, can be adjusted during the settlement window (until 9am the next day)
Model Context
| Context | Details |
|---|---|
| Entity | Gig::Assignment (child of Shift) |
| Layer | Operations |
| Upstream dependencies | Gig::Shift (which shift), Gig::Application (which application created this), Talent::Profile (who is working), Gig::QrCode (clock-in/out mechanism) |
| Downstream dependents | Gig::AssignmentAdjustment (billable time changes), Gig::Payment (wage disbursement), Billing (credit consumption) |
State Machine
| From | To | Trigger | Notes |
|---|---|---|---|
| (new) | confirmed | UC-1: Application confirmed | Created by system when Gig::Application reaches confirmed status. |
confirmed | clocked_in | UC-2: Talent scans clock-in QR | actual_clock_in recorded. Immutable. |
confirmed | cancelled | UC-5/UC-6: Talent or employer cancels | Before clock-in. Cancellation reason required. |
confirmed | no_show | UC-7: System detects no clock-in | Talent didn't show up. Triggers suspension discipline. |
clocked_in | clocked_out | UC-3: Talent scans clock-out QR | actual_clock_out recorded. billable_* fields set from employer form. Settlement window opens. |
clocked_in | cancelled | UC-6: Employer rejects mid-shift | Edge case — talent was working but employer cancels. Payment for hours worked is an open question. |
clocked_out | verified | UC-4: Billable times locked | Either 9am deadline passes or employer explicitly locks. Payment created. Credits consumed. |
Use Cases
| ID | Use Case | Trigger | Actor |
|---|---|---|---|
| UC-1 | Assignment created from confirmed Application | Both parties agreed (double handshake) | System |
| UC-2 | Talent clocks in | Talent scans clock-in QR code | Talent |
| UC-3 | Talent clocks out | Talent scans clock-out QR code + employer fills time form | Talent + Employer |
| UC-4 | Assignment verified — billable times locked | 9am deadline or employer explicit lock | System / Employer |
| UC-5 | Talent cancels assignment | Talent cancels before clock-in | Talent |
| UC-6 | Employer cancels assignment | Employer removes worker before or during shift | Employer |
| UC-7 | No-show detected | Talent didn't clock in within the configured window | System |
| UC-8 | Employer adjusts billable times | Employer corrects times during settlement window | Employer |
| UC-9 | Admin adjusts billable times on behalf of employer | Ops enters adjustment from WhatsApp/email request | Admin |
| UC-10 | View assignment detail | Employer reviewing a worker's attendance | Employer |
UC-1: Assignment created from confirmed Application
| Field | Details |
|---|---|
| Actor | System |
| Trigger | Gig::Application reaches confirmed status |
Preconditions:
- Application
status: confirmed(both employer accepted and talent confirmed) - Shift is
openoractive
System Behavior:
- System creates
Gig::Assignmentwith:gig_shift_idfrom the Application's shiftgig_application_idlinking back to the Applicationtalent_profile_idfrom the Applicationstatus: confirmed
- Shift's filled count increases
Business Rules:
- One Assignment per Application — if the Application is confirmed, exactly one Assignment is created
- The Assignment inherits the shift and talent references from the Application
- No manual creation of Assignments — they are always created by the system as a result of Application confirmation
Postconditions:
- Assignment exists with
status: confirmed - Talent is expected to show up for the shift
UC-2: Talent clocks in
| Field | Details |
|---|---|
| Actor | Talent::Profile (talent) |
| Trigger | Talent scans clock-in QR code at the shift |
Preconditions:
- Assignment
status: confirmed - Shift
status: active(shift start time has been reached) - A valid (non-expired)
Gig::QrCodewithqr_type: clock_inexists for this shift
System Behavior:
- Talent scans the clock-in QR code
- System validates the QR UUID and checks expiry
- System records
actual_clock_in = now()— this timestamp is immutable and never changes status→clocked_in
Business Rules:
actual_clock_inis set once and never modified — it is the system's record of when the QR was scanned- A talent can only clock in once per Assignment
- Clock-in is only allowed when the shift is
active - If the QR has expired, the talent sees an error and the employer must generate a new one
Postconditions:
- Assignment
status: clocked_in actual_clock_inis set
UC-3: Talent clocks out
| Field | Details |
|---|---|
| Actor | Talent::Profile (talent) + Org::UserProfile (employer) |
| Trigger | Talent scans clock-out QR code, employer fills time confirmation |
Preconditions:
- Assignment
status: clocked_in - A valid
Gig::QrCodewithqr_type: clock_outexists for this shift
System Behavior:
- Employer generates a clock-out QR code for the shift (see
Gig::QrCodespec) - Employer fills in the time confirmation form:
billable_clock_in,billable_clock_out,billable_break_minutes - Talent scans the clock-out QR code
- System records
actual_clock_out = now()— immutable - System sets
billable_clock_in,billable_clock_out,billable_break_minutesfrom the employer's form - If employer skips the form,
billable_*defaults to actual times with 0 break minutes status→clocked_out- The settlement window opens — employer can adjust billable times until 9am the next day
Business Rules:
actual_clock_outis set once and never modifiedbillable_*fields are mutable during the settlement window (see UC-8)- The time confirmation form is the employer's first opportunity to set billable times — they can adjust later during the settlement window
- A talent can only clock out once per Assignment
Postconditions:
- Assignment
status: clocked_out actual_clock_outis setbillable_*fields are set (from form or defaulted from actuals)- Settlement window is open
UC-4: Assignment verified — billable times locked
| Field | Details |
|---|---|
| Actor | System (deadline) or Org::UserProfile (explicit lock) |
| Trigger | 9am the next day arrives, or employer explicitly locks |
Preconditions:
- Assignment
status: clocked_out
System Behavior:
- System sets
billable_locked_at = now() status→verified- System calculates wage:
total_hours = (billable_clock_out - billable_clock_in) - billable_break_minutesgross_wage = total_hours × shift.hourly_ratenet_wage = gross_wage - deductions
- System creates
Gig::Paymentwith the calculated wage (status:pending) - System triggers Billing domain to consume gig credits from the employer's entitlement lots (consumed amount = gross wage)
Business Rules:
- After
billable_locked_atis set, no further changes to billable times are allowed (unless admin override) - The 9am deadline is system-enforced — a background job runs at 9am and locks all
clocked_outAssignments from the previous day - Employer can explicitly lock earlier by confirming "these times are final"
- If the shift had multiple Assignments, each is verified independently — the shift transitions to
pending_verification→completedwhen ALL Assignments reach a terminal state
Postconditions:
- Assignment
status: verified billable_locked_atis setGig::Paymentcreated- Billing credits consumed
UC-5: Talent cancels assignment
| Field | Details |
|---|---|
| Actor | Talent::Profile (talent) |
| Trigger | Talent cancels before clock-in |
Preconditions:
- Assignment
status: confirmed(not yet clocked in) - Shift has not started, OR shift started but talent hasn't clocked in yet
System Behavior:
- Talent requests cancellation with a mandatory reason (via
Taxonomy::GigStatusReason) - System evaluates cancellation timing (see Overview — Talent Cancellation Policy):
>48hours before shift: no penalty- 3–48 hours: enters progressive suspension discipline
- 0–3 hours: document required (urgent cancellation)
- After shift start: cancellation blocked (treated as no-show)
status→cancelled,cancelled_atset- The corresponding
Gig::Application.status→cancelled - Shift's filled count decreases — position reopens for new applicants if shift is still
open - Employer notified
Business Rules:
- Cancellation is blocked if the talent has already clocked in (
clocked_instatus) - Cancellation reason is mandatory
- Document required for urgent cancellations (0–3 hours before shift)
- Suspension discipline follows the progressive tiers: warning → 7d → 30d → 90d with 24-hour appeal window (see Overview — Suspension Policy)
Postconditions:
- Assignment
status: cancelled - Application
status: cancelled - Shift filled count decreased
- Suspension evaluation triggered if within 48-hour window
UC-6: Employer cancels assignment
| Field | Details |
|---|---|
| Actor | Org::UserProfile (employer) |
| Trigger | Employer removes worker before or during shift |
Preconditions:
- Assignment
status: confirmedorclocked_in
System Behavior:
- Employer selects the Assignment to cancel with a mandatory reason (via
Taxonomy::GigStatusReason) status→cancelled,cancelled_atset- The corresponding
Gig::Application.status→rejected - Shift's filled count decreases
- Talent notified: "You have been removed from this shift"
- Employer credit deduction applies based on timing (see Overview — Employer Cancellation Policy)
Business Rules:
- Rejection reason is mandatory
- Progressive credit deduction based on timing:
>48h(none) →24-48h(low) →12-24h(moderate) →3-12h(high) →0-3h(very high) → after start (full) - If the Assignment is
clocked_in(talent is already working), the employer can still cancel but payment for hours already worked is an open question (seeGig::Paymentspec)
Postconditions:
- Assignment
status: cancelled - Application
status: rejected - Credits deducted from employer if applicable
Open Questions:
- When an employer cancels a
clocked_inAssignment, should the talent be paid for hours already worked? If yes, should a partial Payment be created at cancellation time?
UC-7: No-show detected
| Field | Details |
|---|---|
| Actor | System |
| Trigger | Talent didn't clock in within the configured window after shift start |
Preconditions:
- Assignment
status: confirmed - Shift
status: active - Configured no-show detection window has passed (e.g., shift end time + grace period)
System Behavior:
- System detects that the Assignment is still
confirmedafter the no-show window status→no_show- No
Gig::Paymentis created - Talent enters progressive suspension discipline (same tiers as late cancellation)
- Employer notified: "Worker did not show up"
Business Rules:
- No-show detection is system-triggered via a background job
- The no-show window is configurable (e.g., shift end time + X hours)
- A no-show counts as an offense in the progressive suspension policy
- No payment is generated for no-shows
Postconditions:
- Assignment
status: no_show - No Payment created
- Suspension evaluation triggered
UC-8: Employer adjusts billable times
| Field | Details |
|---|---|
| Actor | Org::UserProfile (employer) |
| Trigger | Employer corrects times during settlement window |
Preconditions:
- Assignment
status: clocked_out billable_locked_atis NULL (settlement window still open)
System Behavior:
- Employer modifies one or more of:
billable_clock_in,billable_clock_out,billable_break_minutes - Employer provides a reason for the adjustment
- System creates an immutable
Gig::AssignmentAdjustmentrecord capturing:- Which fields changed (with old and new values, as JSON)
- Who made the change
- The reason
billable_*fields updated on the Assignment
Business Rules:
- Adjustments are only allowed while
billable_locked_at IS NULL(before verification deadline) - Each adjustment creates an immutable audit record — the Assignment's
billable_*fields are the current fact, the AssignmentAdjustment records are the event history - Reason is mandatory for each adjustment
- Multiple adjustments are allowed during the settlement window — each creates a separate audit record
Postconditions:
- Assignment
billable_*fields updated Gig::AssignmentAdjustmentrecord created
UC-9: Admin adjusts billable times on behalf of employer
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Ops enters adjustment from employer WhatsApp/email |
Preconditions:
- Assignment
status: clocked_out billable_locked_atis NULL (settlement window still open)
System Behavior:
- Admin modifies
billable_*fields with a reason (e.g., "Per employer WhatsApp request") - System creates
Gig::AssignmentAdjustmentwithadjusted_by_type: Identities::Admin billable_*fields updated
Business Rules:
- Same as UC-8, but
adjusted_byreferences an Admin instead of an Employer - The reason field captures the communication channel for audit
- Admin can also adjust AFTER
billable_locked_atis set (admin override) — this is the only way to change verified times
Postconditions:
- Same as UC-8
UC-10: View assignment detail
| Field | Details |
|---|---|
| Actor | Org::UserProfile (employer) |
| Trigger | Employer reviewing a worker's attendance |
Preconditions:
- Assignment exists
System Behavior:
- Employer views the Assignment detail
- System displays:
- Talent name and profile summary
- Status
- Scheduled time (from
Gig::Shift.starts_at / ends_at) - Actual time (
actual_clock_in,actual_clock_out) — if clocked in/out - Billable time (
billable_clock_in,billable_clock_out,billable_break_minutes) — if set - Adjustment history (from
Gig::AssignmentAdjustmentrecords) - Payment summary (from
Gig::Payment) — if verified
Business Rules:
- The view adapts based on Assignment status:
confirmed: shows scheduled time only, with "waiting for clock-in" indicatorclocked_in: shows actual clock-in timeclocked_out: shows all three time definitions + adjustment form (if settlement window open)verified: read-only summary with payment detailscancelled/no_show: shows reason and timestamp
Postconditions:
- Read-only operation — adjustments are a separate UC
Invariants
- An Assignment is always created by the system from a confirmed Application — no manual creation
- One Assignment per Application
actual_clock_inandactual_clock_outare immutable once set — they are the system's audit recordbillable_*fields can only be modified whilebillable_locked_at IS NULL(except by admin override)billable_locked_atis set either by the 9am deadline or explicit employer lock — once set, it triggers Payment creation- Every modification to
billable_*fields creates an immutableGig::AssignmentAdjustmentrecord Gig::Paymentis created only when the Assignment reachesverifiedstatus- A
clocked_inAssignment cannot be cancelled by the talent — only by the employer verified,cancelled, andno_showare terminal states- No-show detection only applies to
confirmedAssignments that never reachedclocked_in
Model Interactions
| Related Model | Relationship | Interaction |
|---|---|---|
Gig::Shift | Assignment belongs_to Shift | The shift provides the scheduled time window and hourly rate. |
Gig::Application | Assignment belongs_to Application | The Application that created this Assignment via the double handshake. |
Talent::Profile | Assignment belongs_to TalentProfile | The worker assigned to this shift. |
Gig::QrCode | QrCode scanned to update Assignment | Clock-in/out QR codes update actual_clock_in / actual_clock_out on the Assignment. |
Gig::AssignmentAdjustment | Assignment has_many Adjustments | Every change to billable_* fields creates an immutable audit record. |
Gig::Payment | Assignment has_one Payment | Created when Assignment reaches verified. Tracks wage calculation and disbursement. |
Billing | Assignment triggers credit consumption | When verified, the Billing domain consumes gig credits equal to the billable wage from the employer's entitlement lots. |
Taxonomy::GigStatusReason | Assignment references StatusReason | Required for cancellations and no-shows. Records why the Assignment ended. |
Org::UserProfile | Employer manages assignments | Employers generate QR codes, fill time forms, adjust billable times, and cancel assignments. |
Schema Gaps
Comparing the model spec against the current DBML in jodapp.dbml:
| Gap | Impact | Suggested Resolution |
|---|---|---|
No uuid on gig_assignments | Inconsistent with other models | Add uuid string [unique, not null] |
No gig_application_id on gig_assignments | Cannot trace which Application created this Assignment | Add gig_application_id bigint [ref: > gig_applications.id, not null] |
No status column on gig_assignments | Cannot track the Assignment lifecycle | Add status string [not null] with enum |
| No actual/billable time columns | Cannot track attendance or calculate payment | Add actual_clock_in, actual_clock_out, billable_clock_in, billable_clock_out, billable_break_minutes, billable_locked_at |
No cancelled_at column | Cannot track when cancellation happened | Add cancelled_at timestamp [null] |
gig_shift_id typed as integer instead of bigint | Inconsistent with other FK columns | Change to bigint |
Ref type on gig_shift_id is < instead of > | Incorrect relationship direction | Change to ref: > gig_shifts.id |
taxonomy_gig_status_reason_id typed as string | Should be bigint FK | Change to bigint [ref: > taxonomy_gig_status_reasons.id] |
cancel_reason, cancel_document, rejected_reason columns | Replaced by Taxonomy::GigStatusReason reference + AssignmentAdjustment audit | Remove these columns; use taxonomy_gig_status_reason_id instead |
The updated schema is already captured in jodapp-api/docs/db/gig.dbml. The gaps listed above reference the old schema in jodapp.dbml for migration planning.