Skip to content

QBTime Manager — Project Plan & Status

Single source of truth for what we set out to build and what actually got built. Consolidates the former TODO.md (the plan), REMAINING-WORK.md (verified status), and CHANGELOG.md (build log). Last reconciled 2026-06-03.

This doc stays at the plan / status level. It does not repeat the deploy, testing, backup, or user-facing guides — see the other ops docs and the public User/Administrator guides for the "how." For system design, see Architecture.

What QBTime Manager is

A multi-tenant web app that extends QuickBooks Time (QBT) with user/schedule management and automated attendance reporting (scheduled emails listing who clocked in and who was scheduled but didn't). It manages multiple QBT accounts (companies) under a three-tier hierarchy (Platform/DevOps → Customer → Company) and is built to be resold. Audience is admins/managers only — no employee logins; QBT access is via stored per-company service tokens.

Stack: React + Vite + TypeScript + Tailwind (client); Cloudflare Pages Functions/Workers (API); D1 (SQLite); KV (cache/sessions); a separate cron Worker (Pages can't run cron); Microsoft Graph / SendGrid email; Stripe billing.

Confirmed product decisions (locked)

  • Audience: admins/managers only; no per-user QBT OAuth; per-company service tokens.
  • Multi-tenant from day one: every record partitioned by customer_id / company_id.
  • Login: local salted+hashed (PBKDF2) accounts in D1 + mandatory MFA (TOTP).
  • Tiers/roles: Platform DevOps (+ Global Admin) → Customer Admin + Manager (per company).
  • Impersonation: DevOps "view-as," fully audited, at a separate /platform/* route.
  • Onboarding: self-service (Model 1) — admins enter their QBT client_id/secret and run the Connect wizard.
  • Hosting: Cloudflare. Email: Microsoft 365 / Graph sendMail (SendGrid fallback). Source control: GitHub.
  • Billing: Stripe; usage-based with a next-cycle model (see Billing section).

Build phases — planned vs. built

Status legend: ✅ built & verified · ⚠️ partial / read-only · ⏳ deferred · 👤 owner/infra step (not code).

Phase Planned Status
0 — Setup / scaffold Monorepo, Cloudflare config, D1 schema, design system, CI, docs. ✅ Built. (Some account/infra steps are owner tasks — see below.)
1 — Auth (all tiers) PBKDF2 login, KV sessions, mandatory MFA/TOTP + recovery, lockout, password policy + HIBP, CSRF, role/tier guards, first-run bootstrap, change-password. ✅ Built. + email-based password reset added this round.
2 — Read & display QBT client wrapper; dashboard (today), users list, schedule view, company switcher. ✅ Built.
3 — Management (write) Users/schedule/jobcode writes, idempotent + audited; add/edit/archive UIs. ✅ Built. + drag-to-move shifts, bulk actions + undo added this round.
4 — Reporting engine Attendance diff, tz/DST, HTML template, Graph send, manual run + preview, snapshotting. ✅ Built. + PDF export, combined per-company email, saved views added.
5 — Scheduling Cron orchestrator, report-config CRUD, internal cron endpoint. ✅ Built. Combined-email + config edit/delete done.
6 — Hardening & polish Activity log + viewer + chain-verify, health/status page, dark mode, a11y, pagination. ✅ Built. + component-test harness, a11y pass, pagination added.
7 — Operations & reliability Idempotent sends, per-company isolation, self-throttle, monitoring/alerts, token watchdog, backups, runbook. ✅ Built. + notification delivery, daily token-expiry sweep, proactive attendance alerts, D1 backup doc, incident runbook added.
8 — Onboarding & admin tooling Add/connect/disconnect company, account invite/manage, company settings. ✅ Built. + QBT client-secret rotation added.
9 — Platform / DevOps console /platform/* guards, customers, DevOps mgmt, audited impersonation. ✅ Built. + cross-customer fleet-health dashboard added.

Competitive-gap features (this round — beating QBT)

Built from researched QBT complaints (see Product Roadmap (QBT gaps) for the analysis):

  • Proactive attendance alerts — no-show / late / missing-punch / coverage-gap to the bell, per-company prefs (migration 0006).
  • Multi-level timesheet approvals — sequential 1–2 level chain; writes approved_to back to QBT on final approval (needs QBT Approvals Add-On; graceful no-op without it). Migration 0007.
  • ⚠️ Time off (PTO)read-only view shipped; field-mapping is defensive and unverified against live QBT; approve/deny + balances deferred until a QBT trial confirms the payload.
  • Saved report views — name a run, re-run in one click.
  • Billing usage model — next-cycle tier reconciliation + onboarding true-up (see Billing).
  • Branding/chargeback — CowboyMSP disclosure on signup/billing, email sender name, Stripe statement-descriptor guidance, promo-code support.

Report catalog & QBT capabilities — built vs. planned

The schema defines 27 report types, all wired + emailable. ✅ = verified compute; ⚠️ = built but on assumed QBT fields (verify at the QBT trial).

Report type Status
Daily attendance / late-early / no-show / missing-punch / coverage-gaps ✅ Attendance family (flagship template). Missing-punch also feeds the alert.
Hours (per employee) ✅ From timesheets.
Overtime (>40h) ✅ From timesheets.
Weekly timesheet (grid) ✅ Employee × 7-day matrix from timesheets.
Detailed timesheet ✅ Line-item entries (in/out/hours/jobcode/notes).
Pay-period summary ✅ Per-user totals + entries.
Hours by jobcode ⚠️ assumed jobcode_id + /jobcodes fetch.
Hours by group/department ⚠️ assumed user.group_id/group_name.
Payroll summary (reg/OT/PTO/holiday) ⚠️ assumed type field for PTO/holiday split.
Wage / gross pay (OT @1.5x) ⚠️ assumed user.pay_rate.
Labor cost ⚠️ assumed user.pay_rate.
Job / project costing (hours + cost) ⚠️ assumed pay_rate + jobcode_id.
PTO balances ⚠️ assumed PTO type; accrual passed in (QBT balance payload unconfirmed).
Schedule vs actual ⚠️ schedule events vs timesheets; all-day = 8h assumed.
On-the-clock-now ⚠️ open punches.
GPS anomalies ⚠️ assumed /geolocations shape.
Edited timesheets ⚠️ assumed created/last_modified.
Approvals status ✅ current state from our tables.
Approvals history ⚠️ from our approval tables (period/step trail).
Audit / edit log ⚠️ from our audit_log table.
Meal/rest break compliance ⚠️ assumed break_seconds/paid.
Certified payroll (WH-347) ⚠️ assumed classification + pay_rate; no prevailing-wage feed.
ACA / ALE hours ⚠️ avg weekly hours; ≥30 = full-time.

Wiring note (updated): All catalog types now flow through runReport, which dispatches by report_type: the attendance family uses the rich attendance template; the rest use the generic table template (report-template.ts) via report-dispatch.ts. Scheduled configs and manual runs both honor the selected type and email the matching report. Trial-gated types compute on ASSUMED QBT fields (flagged in code, in the config UI, and via an in-email banner) — verify the field mappings at the QBT trial before trusting their numbers.

Outcome features (beyond reports):

  • Payroll export — Generic/Gusto/ADP/Paychex CSV (payroll-export.ts + GET /reports/payroll-export). Built from payroll-summary; rates ASSUMED.
  • Live ops dashboard/reports/ops-dashboard: on-clock-now, 7-day labor cost vs. settable weekly budget, exceptions (anomalies + break-premium $ + missing punches). Budget via /companies/labor-budget (migration 0008).
  • Insights engine (insights.ts) — break-premium $ owed, anomaly detection (impossible-hours / overlapping-punch / schedule-drift / GPS-teleport), labor-vs-budget. ASSUMED fields, flagged provisional.
  • Daily approvals nudgesweepPendingApprovals in the cron notifies admins once a day when approvals are pending. (Approvals page already mobile-friendly.)

QBT API endpoints we call today: /users, /schedule_events, /schedule_calendars, /timesheets, and /jobcodes (new). Documented but not yet wired: /jobcode_assignments, /geolocations (GPS), /reports current_totals (live "on the clock now"), /notifications (push reminders to QBT employees). These are deferred to the QBT-trial phase, where their real field shapes can be verified — building them on guessed shapes (especially payroll-cost and GPS) was deliberately avoided.

Billing model (as built)

Stripe, usage-based, next-cycle (no mid-cycle surprise charges):

  • Tiers in billing_plans with max_companies / max_employees; onboarding fee per tier.
  • Over-limit → pending tier: the new monthly rate applies at the next cycle; a dashboard banner warns in advance. No proration mid-cycle.
  • Onboarding true-up: charged once, to the highest tier ever reached (highest_onboarding_tier); delta on the next invoice; never re-charged on later growth. Catches "sign up cheap, add 20 companies" whenever it happens.
  • Adding a company beyond the largest plan is hard-blocked ("contact us").
  • "Active employees" counted over a rolling 7-day window (excludes churned staff).
  • Discounts: Stripe Checkout promo codes enabled; coupons created in the Stripe Dashboard (repeating / forever / free / onboarding-waiver).
  • Migrations 0003 (billing), 0005 (usage columns: pending_tier, highest_onboarding_tier).
  • Coupons + tier changes: a Stripe coupon lives on the customer/subscription, not the price, so changing a customer's tier (our price swap) never disturbs the discount — a lifetime-free customer stays free at the new tier; a repeating discount keeps its remaining duration applied to the new tier's price, then reverts. The one-time onboarding-delta is skipped for any customer with an active discount (hasActiveDiscount), so "free/discounted" means no surprise onboarding charge.

What's still open

Codeable (deferred by choice):

  • PTO write-back + balances — finish once a QBT trial confirms the time-off payload.
  • ⏳ True pixel drag-and-drop niceties, bulk-undo extras, deeper a11y audit, comparative reports (the larger reporting-depth items).

Owner / infra (not code — 👤 you):

  • 👤 Enable the QBT Approvals Add-On on connected companies (for approval write-back).
  • 👤 Create the QBT trial/sandbox account (unblocks PTO finish + live verification).
  • ✅ Stripe statement descriptor set: account prefix COWBOYMSP + per-product suffix QBTIME on the QBTime prices → statement reads COWBOYMSP* QBTIME; other products keep their own labels. (Confirm the suffix is on every QBTime tier, test + live.)
  • ✅ Prod cron Worker + secret deployed; migrations run per env; coupons created in Stripe as needed.

Original open questions — and how they were resolved

From the former TODO.md §8 ("confirm before building"):

  • "Scheduled" definition → a user counts as scheduled only if a published (non-draft) event exists that day. Resolved, implemented.
  • Late threshold → default 15 min, configurable per report config. Resolved.
  • Report delivery across companiesseparate or combined email, customer's choice (delivery_mode). Resolved, built.
  • Failure alerts destination → in-app bell (notification delivery), with per-company alert prefs. Resolved, built.
  • Repo visibility → private. Confirmed.
  • Onboarding fee vs monthly → two Stripe objects (one-time onboarding + recurring), onboarding trued-up to highest tier. Resolved, built.
  • Self-serve vs assisted → converged on one customer model: everyone signs up self-serve; "we'll set it up" files a support request. Resolved.
  • Customer timezone(s) / GPS retention / branding → per-company timezone setting; GPS read-only/optional; CowboyMSP branding disclosure added. Resolved / noted.

Items that still genuinely depend on the customer/QBT trial (real recipient lists, exact sender mailbox, the two real company names) are captured in the End-to-End Test Walkthrough Step 0, not here.

Condensed build log

  • Phase 0–9 application + client built and verified (typecheck/lint/test/build green); 186+ unit/component tests.
  • This round (2026-06-03): password reset, secret rotation, combined email, PDF export, fleet-health dashboard, notification delivery, drag-drop, bulk actions, pagination, a11y + component-test harness, usage-based billing rework, branding/chargeback, promo codes, proactive alerts, multi-level approvals, read-only PTO, saved views.
  • Docs: split into public (User/Administrator guides) + private ops site (Deploy App/Docs/Sales, Backup & Restore, Runbook, E2E Test Walkthrough, Architecture, Product Roadmap, and this doc).

Full per-commit history lives in git; this section is the human-readable summary.