TradeForce migration docs

#Time tracking and approvals

Module id: MOD-TIME-TRACKING
Doc profile: capability

Contractors record live hours and breaks, submit reports for company review, and companies approve filled time for payment send. Backend workers ensure tracking rows exist and mark approval notices when the shift window is due — replacing Bubble’s recursive send_tracking_approve with a queue/cursor job (LOG-RECURSIVE). End job popup is owned by MOD-COMPANY-SHIFTS; this module hosts it for report Submit only.

#Purpose

Give contractors a signed-in place to start and stop shift timers, edit logged duration, and submit completed entries; give companies a time-tracking tab to filter and approve those entries; and run server-side ensure/approve-notice jobs so rows and approval_notice_sent stay consistent without client service-role writes (MON-CLIENT-WRITES-ALL). Observable outcomes are time_entries rows (from Bubble time_tracking1) under RLS, with create/stop/update/approve going through JWT RPCs or service-role edge workers. Live Start time may set company_requested true or false from the begin-shift “Company request” checkbox; backend ensure-row create always leaves that flag false.

#Scope boundaries

In scope Out of scope
/contractor/time-tracking live timer + reports tabs Workspace Header / SideBar (MOD-SHELL)
Company tab body on /company/shifts?tab=time_tracking Company dashboard host / other tabs (MOD-COMPANY-SHIFTS)
time_entries schema, RLS, create/stop/update/approve RPCs Inventing reject UX (source has Approve → Approved only; not_approved deleted)
Queue redesign for create-list-tracking + send-tracking-approve Line-by-line Bubble ScheduleAPIEvent self-recursion
Fifteen-minute delay job (list visibility + clear delay marker) Attaching tracking-approve side effects to the delay job
Host EndJobPopup instance on report Submit Forking EndJobPopup / Postmark “New time-tracking report” implement tree
Break enum 10–60 min step 5; tracking mode tabs Stripe Connect / payouts (MOD-PAYMENTS / MON-MARKETPLACE)
Enqueue consumer for create-list-tracking Owning company shift publish that enqueues create-list (MOD-COMPANY-SHIFTS)

Module-map primary_journeys still say company “approve or reject” and that the fifteen-minute delay “resumes” tracking approve/create. Export chase supersedes both: company tab is Approve → Approved only, and the delay job only adjusts list visibility + clears its marker (J4 / J5). Refresh the map on the next map pass; do not reintroduce reject or delay→approve coupling from the stale map wording.

#Actors

Actor Behavior in this module
Contractor Opens time-tracking; starts/stops timers (optional Company request on begin); edits times; browses reports; Submit opens shared End job popup; Check opens notes.
Company operator Opens Time-tracking tab; filters Zero/Filled hours; approves one or many entries; edits contractors’ note (also marks sent-for-payment).
Session client Anon-key reads under RLS; mutations via user JWT RPCs only — never service role in the browser.
Tracking workers Service-role edge/cron: ensure time_entries for contractor+shift; mark approval notice when window due; re-queue without HTTP self-recursion.
Delay worker Service-role job for fifteen-minute delay: add user to contractor-list visibility set and clear delay marker.

#Capability composition

Contractor time-tracking is one cabinet route with two modes (live vs reports). Company approval lives as a tab body inside the company shifts host. Backend ensure/approve-notice and fifteen-minute delay are edge/cron surfaces, not SPA pages. Shared End job popup is imported from company-shifts.

flowchart TB subgraph contractor["Contractor time-tracking"] Live["Live timer"] Reports["Reports list"] Notes["Notes check popup"] end subgraph company["Company shifts host"] Tab["Time-tracking tab"] Approve["Approve / bulk approve"] end subgraph shared["Shared reusable"] EJ["End job popup"] end subgraph backend["Server workers"] Ensure["Ensure time entry"] Notice["Approval notice due"] Delay["Fifteen-minute delay"] end Live -->|"RPCs"| Entries["time_entries"] Reports --> EJ Reports --> Notes Tab --> Approve Approve -->|"set_company_approval"| Entries Ensure --> Entries Ensure --> Notice Notice --> Entries

Bubble evidence for pages, APIs, and tab body is under Traceability / IR evidence.

#Primary journeys

Journeys are reconstructed from contractor page workflows, company dashboard time-tracking workflows, and API events create_list_tracking / send_tracking_approve / 15_minuts_delay. Where End job popup internals or company shift publish enqueue belong to another module, steps are marked out of scope.

#J1 — Contractor live-tracks a shift

  1. A signed-in contractor opens /contractor/time-tracking (default or tab=time_tracking).
  2. Signed-out visitors are redirected to /; the page stamps the contractor time-tracking root under the cabinet workspace.
  3. Mode shows “Tracking your time” (vs “Time tracking reports”); live mode shows “Start tracking your time”, today’s date, and “Begin shift”.
  4. Begin shift opens “New time entry for YYYY/MM/DD” with shift picker and Checkbox Company request; Cancel hides; Start time creates an in-progress time_entries row via RPC (self contractor, chosen shift, started_at now) with company_requested true when Company request is checked and false when it is not — do not treat backend ensure-row’s always-false create as this path.
  5. Stop ends the timer via RPC with logged hours/minutes/seconds; break (“If you had a break:”) applies only when elapsed ≥ 15 minutes — otherwise the less-than-15-minutes message blocks break selection.
  6. Editing start/end calls update RPC and recomputes logged fields; optional ?shift= focuses that shift or shows a clear empty/error state.
  7. RPC failures show inline/page errors without inventing client-only persistence.

#J2 — Contractor reviews reports and submits

  1. Contractor selects “Time tracking reports” and sees the reports list (Date, Job title, Company, Location, Start T, End T, Break T, Logged T, Notes, Approve, and Upcoming payment when bound) or “No result found”.
  2. Approve column is read-only on this surface (waiting vs company-approved labels).
  3. Check opens the notes popup for the row’s contractor notes.
  4. Submit opens the shared End job popup host for the selected entry — implement reuses MOD-COMPANY-SHIFTS End job popup; does not fork it.
  5. After a successful shared confirm, the list refreshes; Cancel/hide returns without orphan overlays.
  6. Postmark “New time-tracking report” and End job popup write paths remain owned by the shared reusable (out of this module’s implement tree).

#J3 — Backend ensures tracking rows and approval notices

  1. An enqueue for contractor + shift runs the create-list-tracking equivalent: if no time_entries row exists for that pair, insert one with company_requested false and approval_notice_sent false; if a row already exists, skip create (idempotent). This always-false create is only the ensure path — not the contractor live Start time branch in J1.
  2. After create (or when the row exists), enqueue send-tracking-approve work and persist an opaque schedule_send_approve_job_id when a follow-up is scheduled.
  3. When the approval window is due — shift date plus hours required, plus one day, is less than now — the worker sets approval_notice_sent true.
  4. When the window is not due, the worker re-queues with backoff/cursor and must not HTTP-schedule itself (LOG-RECURSIVE).
  5. Upstream company shift publish that enqueues this consumer stays MOD-COMPANY-SHIFTS; this module owns the consumer redesign only.

#J4 — Company reviews and approves time entries

  1. A company user opens /company/shifts?tab=time_tracking and sees filters “Select period”, “Select engagement”, “Approval status” plus “Zero hours” / “Filled hours”.
  2. The list shows Date, Job title, Worker, Location, Logged time, Approval, Contractors’ note — or “Nothing was found. Try again later!”.
  3. Row shows “Approve” when not company-approved and “Approved” when true; Approve opens confirm “Please, confirm your action” / “You are going to send this shift for payment.” with Save / Discard.
  4. Save calls set_company_approval setting company_approved and sent_for_payment true; Discard writes nothing.
  5. “Multipal approval” (source spelling) bulk-applies the same flags via one server round-trip.
  6. Contractors’ note popup Save persists company notes and sets sent_for_payment true (same paid flag as Approve Save); Close dismisses.
  7. There is no separate reject control on this surface — only Approve → Approved.

#J5 — Fifteen-minute delay clears list visibility marker

  1. A company (or system) caller schedules the fifteen-minute delay job for a target user.
  2. When the job runs as service role, it adds the user to the show-contractor-list visibility set and clears the schedule-15-min-delay marker.
  3. A second run with the marker already clear is a successful no-op.
  4. The job does not run create-list-tracking or send-tracking-approve — those stay on J3.

#Vertical slices (decompose)

Item Product outcome
TT-SCHEMA time_entries migration, enums, RLS, and create/stop/update/approve RPCs (infrastructure).
TT-LIVE-TRACK Contractor live timer start/stop/edit with Company request checkbox and break rules on /contractor/time-tracking.
TT-REPORTS-SUBMIT Reports list, notes check, Submit hosting End job popup.
TT-APPROVE-QUEUE Queue/cursor redesign for ensure-row + approval-notice (LOG-RECURSIVE).
TT-COMPANY-REVIEW Company time-tracking tab filters, approve, bulk approve, notes (notes Save also sets sent-for-payment).
TT-DELAY-JOB Fifteen-minute delay worker: list visibility + clear delay marker.

Decompose TT-LIVE-TRACK still echoes an AC that Start time always passes company_requested=false; implement must follow the checkbox branch documented in J1 / IR evidence, not that oversimplification.

#Key UI states

State When User sees
Live mode Tracking your time selected Begin shift, today label, active Stop / break
Reports mode Time tracking reports selected Reports table or “No result found”
New entry popup Begin shift Shift picker, Checkbox Company request, Start time / Cancel
Company request checked Checkbox has data on Start time New row with company_requested true
Company request unchecked Checkbox has no data on Start time New row with company_requested false
Break blocked Stop with elapsed < 15 minutes Less-than-15-minutes break message
Company Zero / Filled hours Mode chips on company tab Filtered list or empty copy
Approve vs Approved company_approved false / true Approve control or Approved label
Approve confirm open Approve clicked Payment-send confirm Save / Discard
End job popup host open Reports Submit Shared End job popup for selected entry

#Style and fidelity

Cabinet workspace outlet for contractor route and company tab body. Transcribe visible copy and stamp data-bubble-id on migrated roots: contractor page, mode toggle, live/report regions, begin-shift popup (including Company request checkbox), reports RG, company time-tracking group and RG, approve and notes popups. Preserve source spelling “Multipal approval”. Shared tokens come from the app-level stylesheet that SHELL-LAYOUT (MOD-SHELL) delivers out of IR styles (ir/slices/styles____part_all.json — 49 styles, 8 color tokens, 1 font token); this cycle's module map has no separate design-system module, so do not defer a token to one. Do not restyle reports into a card grid — keep table/list hierarchy.

#Failure modes

Case Expected behavior
Signed-out on contractor time-tracking Redirect to /; no timer UI.
Start / stop / update RPC failure Inline or page error; no silent client-only row.
Break selected under 15 minutes Block break; show less-than-15-minutes message.
Invalid / empty ?shift= Clear empty or error state; do not invent shifts.
Reports load empty “No result found”.
End job popup failure (shared) Surface error to contractor; list unchanged until success.
Company list / filter failure Plain-language error; no fake empty success.
Approve / bulk / notes RPC failure Visible error; prior approval and sent-for-payment flags unchanged.
Ensure-row enqueue duplicate Still one time_entries row (idempotent).
Approval window not due Re-queue; never recursive HTTP self-schedule.
Delay job unauthorized invoke Fail closed; no profile mutation.
Anon mutate time_entries RLS / RPC deny.

#Traceability

Product concept Target (route / component / table) Bubble evidence
Contractor time-tracking page /contractor/time-tracking, ContractorTimeTrackingPage bTJFi0
Signed-out redirect ChangePage → index bTLsP
Live / reports mode tracking option → tab state tracking, bTLcl, bTLcp, bTLcq
Begin shift / new entry BeginShiftPopup bTJIn0, bTJIt0, bTLXf
Company request checkbox begin-shift popup control bTJJZ0
Start time create (requested) create_time_entry RPC, company_requested true bTLYK, bTLar
Start time create (not requested) create_time_entry RPC, company_requested false bTLYK, bTLat
Stop + break stop_time_entry RPC, BreakSelect bTJMp0, break, bTLZL
Edit start/end inputs update_time_entry_times RPC bTMEa
Shift deep-link ?shift= focus / empty bTLen
Reports list ReportsPanel bTJLz0, bTJMd0
Empty reports “No result found” bTJMv0
Submit → End job host EndJobPopupHost → shared EndJobPopup bTJNl0, bTJPv0, bTHkx
Notes check NotesCheckPopup bTJOP0, bTJOc0
Postmark new report (shared) owned by End job popup external_call:bTNMZ:bTNMs, external_ns:bTNMZ
Ensure tracking row API edge create-list-tracking bTLsx, bTNLv1
Schedule approve after create enqueue send-tracking-approve bTMCF, bTMCP
Approval notice + recursion edge send-tracking-approve bTMBg, bTMCE, bTMCQ
Company time-tracking tab CompanyTimeTrackingTab bTKSh0, bTKbH4
Single approve set_company_approval bTLPX1, bTMSH
Bulk approve set_company_approval_bulk bTMTN, bTMTZ
Contractors’ note save company notes + sent-for-payment bTLhF, bTLhL
Fifteen-minute delay API edge fifteen-min-delay bTKPV, bTKPa, bTKVs1
Delay schedule caller company dashboard ScheduleAPIEvent bTKPf
Time entries entity public.time_entries time_tracking1
Approval display option labels only; boolean is source of truth approve_type, bTLhj, bTLho
Deleted not-approved value omit bTLhn (residual dead)
Empty option set time_tracking omit time_tracking (residual dead)
Deleted feedback field omit column feedback_text (residual dead)

#IR evidence

#Module evidence roots

Supporting segments: ir/slices/bTJIF0____root.json, ir/slices/edef_bTHky____root.json, bTHkx (deferred EndJobPopup), time_tracking1, approve_type, break, external_ns:bTNMZ.

#Path-key ↔ inventory id aliases

Inventory id Export path key
bTJFi0 /pages/bTJIF0
bTLsx /api/bTLsy
bTMBg /api/bTMBh
bTKPV /api/bTKPZ
bTHkx /element_definitions/bTHky
bTKSh0 /pages/bTIbd/elements/bTHQp/elements/bTKSm0

#Start time / company_requested (chased)

Workflow bTLYK (ButtonClicked Start time on begin-shift popup) has two NewThing actions on custom.time_tracking1:

Do not conflate with create_list_tracking action bTNLv1, which always initializes company_request_boolean false on ensure-row create.

#Approval window (chased)

On send_tracking_approve action bTMCE, send_boolean is set true when:

track.shift.date_date + hours_required_number (plus_hours) + 1 day (plus_days) less_than Current Date/Time.

If that ChangeThing does not apply (PreviousStep empty), action bTMCQ schedules bTMBg again — rewrite as re-queue, not HTTP self-schedule.

#create_list_tracking (chased)

#Company approve and notes (chased)

#Fifteen-minute delay (chased)

#Privacy roles (time_tracking1)

Target: RLS + SECURITY DEFINER RPCs; Bubble ignore_privacy_rules on APIs must not become SPA bypasses.

#Option sets

Option db_value / notes UI
tracking tracking_your_time, time_tracking_reports Mode tabs
break 10_min60_min (attrs.number 10…60) Break dropdown
approve_type approved, waiting_for_approval; not_approved deleted Labels only

#Residuals (decompose)

#Decompose refs

tooling/cursor-impl-cycle/output/decompose/MOD-TIME-TRACKING.json — items TT-SCHEMA through TT-DELAY-JOB.

Generated from the repository at 2026-09-22 15:55 UTC · source of truth is docs/ in the workspace, not this site.