Skip to main content

Gig::QrCode

Purpose

A QrCode is a time-limited, scannable code that the employer generates for talent to clock in or clock out of a shift. Each QR encodes a unique UUID that the talent's mobile app scans to record attendance on their Gig::Assignment.

QR codes are the primary attendance mechanism in the Gig domain. They solve two problems:

  1. Verification: the talent must be physically present at the workplace to scan the QR (the employer has the screen)
  2. Simplicity: no GPS, no biometrics — the employer shows a code, the worker scans it. Simple enough for non-tech-savvy blue-collar workers and busy hiring managers.

QR codes are generated per shift (not per assignment). All workers on the same shift scan the same QR. This avoids the employer needing to generate individual codes for each worker.

Model Context

ContextDetails
EntityGig::QrCode (child of Shift)
LayerOperations — Attendance
Upstream dependenciesGig::Shift (which shift this QR is for)
Downstream dependentsGig::Assignment (scanning updates actual_clock_in / actual_clock_out on the Assignment)

State Machine

QrCode has no status column. A QR is either valid (current time is before expires_at) or expired (current time is at or after expires_at). This is a derived state, not stored.

Use Cases

IDUse CaseTriggerActor
UC-1Generate a clock-in QR codeShift is active, employer ready for workers to arriveEmployer
UC-2Generate a clock-out QR codeWorkers finishing, employer ready for clock-outEmployer
UC-3Talent scans a clock-in QR codeTalent arrives at the shift and scans the QRTalent
UC-4Talent scans a clock-out QR codeTalent finishes work and scans the QRTalent
UC-5QR code expires and employer regeneratesQR expired before all workers scannedEmployer

UC-1: Generate a clock-in QR code

FieldDetails
ActorOrg::UserProfile (employer)
TriggerShift is active, employer ready for workers to arrive

Preconditions:

  • Gig::Shift status: active
  • At least one Assignment in confirmed status (workers expected)

System Behavior:

  1. Employer requests a clock-in QR for the shift
  2. System checks: does a valid (non-expired) clock-in QR already exist for this shift?
    • If yes: return the existing QR (reuse)
    • If no: generate a new QR
  3. To generate: system creates a Gig::QrCode with:
    • gig_shift_id: the shift
    • qr_type: clock_in
    • qr_uuid: a unique UUID (e.g., SecureRandom.uuid)
    • expires_at: now + configured_duration (e.g., 15 minutes)
    • generated_by_id: the employer
  4. System renders the UUID as a QR image and displays it to the employer

Business Rules:

  • One valid clock-in QR per shift at a time — if a valid one exists, reuse it instead of creating a new one
  • Expiry duration is configurable (system-wide or per-company, default: 15 minutes)
  • The QR image is generated client-side from the UUID — the backend only stores and validates the UUID
  • Multiple expired QRs can exist for the same shift (each regeneration creates a new record)

Postconditions:

  • Gig::QrCode exists with qr_type: clock_in
  • Employer has the QR displayed for workers to scan

UC-2: Generate a clock-out QR code

FieldDetails
ActorOrg::UserProfile (employer)
TriggerWorkers finishing, employer ready for clock-out

Preconditions:

  • Gig::Shift status: active
  • At least one Assignment in clocked_in status

System Behavior:

  1. Employer requests a clock-out QR for the shift
  2. Same reuse logic as UC-1 (check for existing valid clock-out QR)
  3. If generating new: same as UC-1 but with qr_type: clock_out
  4. Alongside QR generation, employer is presented with the time confirmation form for each clocked_in Assignment:
    • billable_clock_in, billable_clock_out, billable_break_minutes
    • Pre-filled from actual clock-in times and shift end time

Business Rules:

  • Same reuse and expiry rules as UC-1
  • The time confirmation form is presented at clock-out QR generation time — this is the employer's first opportunity to set billable times
  • The form is optional — if skipped, billable times default to actual times with 0 break

Postconditions:

  • Gig::QrCode exists with qr_type: clock_out
  • Time confirmation form presented to employer

UC-3: Talent scans a clock-in QR code

FieldDetails
ActorTalent::Profile (talent)
TriggerTalent arrives at the shift and scans QR

Preconditions:

  • Talent has an Assignment for this shift in confirmed status
  • QR code is valid (not expired)

System Behavior:

  1. Talent scans the QR code with their mobile app
  2. App sends the qr_uuid to the backend
  3. System looks up the QR by qr_uuid
  4. System validates:
    • QR exists and qr_type = clock_in
    • QR has not expired (expires_at > now)
    • Talent has a confirmed Assignment for this QR's shift
    • Talent has not already clocked in
  5. System updates the Assignment (see Assignment UC-2):
    • actual_clock_in = now()
    • statusclocked_in

Business Rules:

  • The QR UUID is the only input from the talent — the system resolves the shift and assignment from it
  • If the QR is expired: error "This QR code has expired. Ask your manager to generate a new one."
  • If the talent has no Assignment for this shift: error "You are not assigned to this shift."
  • If already clocked in: error "You have already clocked in for this shift."

Postconditions:

  • Assignment updated (see Assignment UC-2 postconditions)

UC-4: Talent scans a clock-out QR code

FieldDetails
ActorTalent::Profile (talent)
TriggerTalent finishes work and scans QR

Preconditions:

  • Talent has an Assignment for this shift in clocked_in status
  • QR code is valid (not expired)

System Behavior:

  1. Talent scans the QR code
  2. App sends the qr_uuid to the backend
  3. System validates:
    • QR exists and qr_type = clock_out
    • QR has not expired
    • Talent has a clocked_in Assignment for this QR's shift
    • Talent has not already clocked out
  4. System updates the Assignment (see Assignment UC-3):
    • actual_clock_out = now()
    • billable_* fields set from the employer's time confirmation form (or defaulted from actuals)
    • statusclocked_out

Business Rules:

  • Same validation rules as UC-3 but for clock-out
  • If the employer filled in the time confirmation form (during UC-2), those values are applied to this Assignment's billable_* fields
  • If not, billable_clock_in defaults to actual_clock_in, billable_clock_out defaults to actual_clock_out, billable_break_minutes defaults to 0

Postconditions:

  • Assignment updated (see Assignment UC-3 postconditions)

UC-5: QR code expires and employer regenerates

FieldDetails
ActorOrg::UserProfile (employer)
TriggerQR expired before all workers scanned

Preconditions:

  • Previous QR for this shift + type has expired
  • Workers still need to clock in or out

System Behavior:

  1. Employer requests a new QR (same as UC-1 or UC-2)
  2. System sees no valid QR exists (previous one expired)
  3. System generates a new QR with a new UUID and new expiry
  4. The expired QR remains in the database for audit (not deleted)

Business Rules:

  • Expired QRs are never deleted — they are audit records showing that a QR was generated at a specific time
  • Each regeneration creates a new record with a new UUID
  • Workers who scanned the old QR before it expired are already clocked in — they are not affected by regeneration
  • Workers who missed the window must scan the new QR

Postconditions:

  • New Gig::QrCode exists with fresh UUID and expiry
  • Previous QR remains in database (expired, for audit)

Invariants

  1. qr_uuid is globally unique across all QR codes
  2. At most one valid (non-expired) QR per (shift, qr_type) at any time
  3. QR codes are never updated or deleted — they are append-only records
  4. Expiry is the only mechanism for invalidation — there is no manual "revoke" action
  5. QR codes belong to a Shift, not an Assignment — all workers on the same shift scan the same QR
  6. expires_at must be in the future at creation time
  7. A QR can only be scanned by talent who have an Assignment for the QR's shift in the correct status (confirmed for clock-in, clocked_in for clock-out)

Model Interactions

Related ModelRelationshipInteraction
Gig::ShiftQrCode belongs_to ShiftQR codes are generated for a specific shift. The shift must be active.
Gig::AssignmentQR scan updates AssignmentWhen talent scans, the system finds their Assignment for the QR's shift and updates clock times.
Org::UserProfileEmployer generates QR codesThe employer initiates QR generation from the shift management screen.
Talent::ProfileTalent scans QR codesThe talent's app scans the QR and sends the UUID to the backend for validation.

Schema Gaps

The gig_qr_codes table is new — it does not exist in the current DBML for jodapp. The schema is defined in jodapp-api/docs/db/gig.dbml.

Open Questions:

  • Should QR codes support a "scan radius" (GPS check to ensure talent is at the outlet)? This would reduce fraud but adds complexity and may not work well in indoor environments.
  • Should the system support manual clock-in (employer enters it without QR) as a fallback for situations where the talent's phone can't scan? The legacy system supports this for the "forgotten QR code" scenario.