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 imported —
opsImportedis false. Use/seamless-ops/admin-import.htmlto re-import manually. - Orphan reference —
opsImported=truebut theops_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
archivedis true. The Backfill button above only writesarchived:falseon 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-in
→ ordered; on-hold →
not-ordered; done →
ready. 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.