Admin tools

Admin  ›  Tools

One-shot maintenance jobs. These are safe to run multiple times — each tool is idempotent (it skips records that are already up to date) and won't overwrite data the admin has explicitly set.

Seed profile chips on the Job Board pipeline

Adds the default colored panel-profile chips (Snap 1.0", Snap 1.5", Lock variants, Standing Seam, G-Rib) to the ops_pipelines/main document so the kanban + job-detail pages can render the second chip on each card. Skips silently if profiles are already configured. Run this once after upgrading to v0.1.3.

Upgrade profiles to siding + work-type scoping

For pipelines created before profile chips became work-type-aware (v0.1.5 and earlier). This:
(1) Adds workTypes: ['re-roof','new-construction'] to the existing roofing profiles (Snap, Lock, Standing Seam, G-Rib) so they only show on roofing jobs.
(2) Adds three new siding profiles — Board and Batten, Cottage Panel, G-Rib Siding — scoped to the siding work type.
Existing custom profiles are left exactly as-is. Idempotent — safe to click again.

Backfill panel profile on imported jobs

Scans every job, and for any job missing panelProfile and roofType, copies those values from the yutzybid snapshot stored in the job's private/main sub-document. After this runs, the colored profile chip will appear on every imported job that had a recognizable profile in yutzybid. Jobs without a snapshot (manually added, or older imports that didn't capture one) are left untouched.

Re-pull attachments from yutzybid

Fixes two problems in one pass:
(a) Jobs with copyFailed attachment records (auto-import couldn't copy the PDFs server-side because of an IAM gap on the Cloud Functions runtime). This tool re-copies them from the browser using your admin credentials, then replaces the failure record with a successful one.
(b) Jobs that came over with no PDFs at all because the source estimate hadn't generated them yet at import time (e.g. Holcomb). For each yutzybid-imported job, looks up the live source estimate and copies any estimatePdfStoragePath / financialReportStoragePath / attachments[] that aren't already on the ops job.
Idempotent — files already present (matched by source path) are skipped.

Canonicalize ops_jobs.scheduledWeekStart

One-shot data migration. The v5.6.153 fix made the kanban robust to viewer timezone differences by storing scheduledWeekStart as a noon-UTC instant (instead of local-midnight Monday). Existing jobs were written under the old format. This tool rewrites each one to the new canonical value so the kanban can use exact matching everywhere. Safe to re-run — jobs already in canonical form are skipped.

Seed estimate aggregates

Reads every estimates and siding-estimates doc (excluding contractor-deleted ones), sums counts and totals, and writes the result to meta/estimateAggregates. Run this once after the Cloud Function trigger is deployed (firebase deploy --only functions from firebase-functions/). After this seeds, the aggregate doc is maintained automatically by the per-doc trigger — no further manual runs needed. Re-running this tool re-computes from scratch and overwrites whatever the trigger has accumulated, so only run again if you suspect drift.

Backfill ops_jobs.archived

Scans every ops_jobs doc and writes archived: false on any that don't have the field set. Pre-requisite for narrowing the active-jobs and kanban listeners from full-collection reads to where('archived','==',false) — which currently can't ship without this backfill because legacy imports don't have the field and the strict where-clause would silently drop them. Idempotent — safe to click twice.

Audit sold → kanban pairing

Scans every estimate (both roofing and siding) where sold AND soldAcknowledged are true and reports any that aren't currently rendering on the kanban. Three failure categories surfaced separately:

  • Never importedopsImported is false. Use /seamless-ops/admin-import.html to re-import manually.
  • Orphan referenceopsImported=true but the ops_jobs/{opsJobId} doc no longer exists. Auto-import claim succeeded but the doc was later deleted or never finished writing. Re-import via admin-import.html.
  • Ghost (archived) — ops_jobs doc exists but archived is true. The Backfill button above only writes archived:false on docs missing the field; manually-archived docs need to be unarchived in Firestore or via job-detail.

Backfill ops_jobs.expectedDays from estimates

v5.6.214 added auto-fill of expectedDays at auto-import time (ceil((laborCost + tearoffCost) ÷ 3000)). This tool runs the same calculation against every existing ops_job that doesn't yet have a value — driving the new multi-day calendar bars without you typing every job. For legacy estimates without the laborCost + tearoffCost fields (pre-v5.6.214), it falls back to totalPrice × 0.30 ÷ 3000 as a rough labor+tearoff approximation. Skips jobs that already have expectedDays set, so it's safe to click twice.

Migrate order statuses to 3 levels

v5.6.200 collapsed the order-status list from six entries down to three: Not Ordered Yet / Ordered / Ready. The kanban and detail-page UI already render legacy values mapped on the fly, but until this runs the underlying field on each ops_jobs doc still carries the old id (e.g. material-ordered). This tool walks every job and rewrites: material-ordered & material-inordered; on-holdnot-ordered; doneready. Already-new values are skipped.

Migrate PIN material to subcollection (Path B)

v5.6.161 moved contractor auth material off the parent contractor doc into contractors/{id}/private/auth (admin/server- only). This tool walks every existing contractor doc and copies pinHash / pinSalt / legacy pin into the subcollection, then sets pinSet: true on the parent. Does NOT delete the parent fields yet — run the Audit tool below first to verify completeness, then the Purge tool to clear them. Idempotent.

Audit PIN migration (Path B)

Read-only audit. Walks every contractor doc and reports any that either (a) have PIN material on the parent doc but no subcollection doc, or (b) have a subcollection doc that's missing a hash. Should return clean before running the Purge tool. Safe to re-run.

Purge legacy PIN fields from parent (Path B)

Final step. Deletes pin, pinHash, and pinSalt from every contractor doc whose subcollection auth material exists. After this runs, the parent contractor doc carries no PIN material at all — only the pinSet: true flag. Run only after the Audit returns clean. Idempotent.

Audit unauthorized admin grants

Lists every contractors doc with isAdmin: true that is NOT in the hardcoded admin email allowlist (currently just aaron@seamlessarthur.com plus whatever's in admin-contractors.html's intended list). Use this BEFORE the Phase 1 contractor-rule lockdown ships so you can confirm no one has self-promoted. If anything shows up here that you don't recognize, investigate the audit log for what they did, then flip the field off via admin-contractors.html. Read-only.

Seed leaderboard aggregates

Reads every estimates + siding-estimates doc, tallies per-company counts, and writes them to meta/leaderboardAggregates. Run once after deploying the new Cloud Functions in v5.6.184; subsequent estimate writes are maintained automatically. Idempotent — re-running just rebuilds the doc from current data.

Backfill hasPendingCorrection flag

For every ops_timeEntries doc that still carries a correctionRequest object but no hasPendingCorrection: true flag, set the flag so the daily checkPendingCorrections Cloud Function can find them via a where() query instead of a 90-day full scan. Idempotent; safe to re-run.

Failed attachment diagnostic

Scans every job for attachments whose copy from yutzybid failed. Groups them by job and shows the exact error message + the original yutzybid storage path each one was trying to copy from. Use this to figure out WHY a PDF didn't come over — common causes are the source file having been deleted in yutzybid, a malformed legacy URL, or a bucket-permission hiccup. Read-only.