WFDASH-WEBAPP/scripts/smoke/cleanup-junk-data.py
SJCoetz 7f924ccc45 refactor: switch hardcoded UI colors to semantic theme tokens
Overhaul the app's color system by replacing all hardcoded Tailwind color classes with semantic theme variables to ensure consistent theming across all components, pages, and tests.

Additional changes:
- Add assigned date support for order item status updates: add `assignedDate` field to the order item status PATCH API, implement server-side normalization/validation, and add corresponding tests
- Refactor purchase order PDF workflows to use the consolidated `purchase_orders.documents` field instead of the legacy separate `purchase_order_pdfs` collection
- Fix quote number parsing and formatting to support both dashed (Q-YYYY-MM-NNN) and dashless (QYYYYMMNNN) formats
- Rename `driver_pool` item status to `collection_pool` across the codebase
- Add null handling for order item quantities and pricing to display "—" for missing values
- Improve filter tab styling: make mobile tabs scrollable, prevent text wrapping, hide scrollbar
- Add new utility scripts, API routes, migrations, and polish small UI/UX fixes including tsconfig updates and theme hook improvements
2026-07-07 17:22:03 +02:00

147 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""
WFDash PB data cleanup — removes junk placeholder/smoke-test records,
fixes two quote status anomalies, and clears {0,0} (null-island)
coordinates so the map doesn't render ocean pins.
Safe: checks for references before deleting. DRY-RUN by default; pass
--apply to actually mutate.
"""
import sys, json, urllib.request, urllib.error
PB = "https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io"
ADMIN_EMAIL = "david.els1@outlook.com"
ADMIN_PASS = "1234567890"
APPLY = "--apply" in sys.argv
def req(method, path, body=None, token=None):
url = f"{PB}{path}"
data = json.dumps(body).encode() if body is not None else None
r = urllib.request.Request(url, data=data, method=method)
r.add_header("Content-Type", "application/json")
if token:
r.add_header("Authorization", token)
try:
with urllib.request.urlopen(r) as resp:
return json.loads(resp.read()) if resp.status != 204 else {}
except urllib.error.HTTPError as e:
return {"__error": e.code, "__body": e.read().decode()[:300]}
# 1. Auth
auth = req("POST", "/api/collections/_superusers/auth-with-password",
{"identity": ADMIN_EMAIL, "password": ADMIN_PASS})
token = auth.get("token", "")
if not token:
print("AUTH FAILED:", auth); sys.exit(1)
print(f"[auth] admin token ok ({len(token)} chars)")
def list_all(collection, filter_str="", per_page=500):
out = []
page = 1
while True:
f = f"&filter={urllib.parse.quote(filter_str)}" if filter_str else ""
r = req("GET", f"/api/collections/{collection}/records?page={page}&perPage={per_page}{f}", token=token)
if "__error" in r:
print(f" [warn] list {collection} failed: {r['__error']}"); break
items = r.get("items", [])
out.extend(items)
if page >= r.get("totalPages", 1): break
page += 1
return out
import urllib.parse
# 2. Find junk records
print("\n=== Finding junk records ===")
companies = list_all("companies")
customers = list_all("customers")
junk_company_ids = [c["id"] for c in companies
if c.get("company","").startswith("SELECT/CREATE") or c.get("company","").startswith("SMOKE-CO")]
junk_customer_ids = [c["id"] for c in customers
if c.get("customer","").startswith("SELECT/CREATE")]
print(f" junk companies: {junk_company_ids}")
print(f" junk customers: {junk_customer_ids}")
# 3. Check references before deleting
print("\n=== Checking references ===")
# Quotes that reference junk customers or companies
quotes = list_all("quotes")
ref_customers = set(junk_customer_ids)
ref_companies = set(junk_company_ids)
quotes_blocked = [q for q in quotes if q.get("customer") in ref_customers or q.get("company") in ref_companies]
print(f" quotes referencing junk customer/company: {len(quotes_blocked)}")
if quotes_blocked:
for q in quotes_blocked[:5]:
print(f" {q.get('quote_number')} (customer={q.get('customer')}, company={q.get('company')})")
# Orders referencing junk
orders = list_all("orders")
orders_blocked = [o for o in orders if o.get("customer") in ref_customers or o.get("company") in ref_companies]
print(f" orders referencing junk customer/company: {len(orders_blocked)}")
# Customers referencing junk company
cust_blocked = [c for c in customers if c.get("company") in ref_companies and c["id"] not in junk_customer_ids]
print(f" non-junk customers referencing junk company: {len(cust_blocked)}")
# 4. Delete junk (customers first, then companies — avoids dangling customer→company ref)
print("\n=== Deleting junk ===")
deleted = 0
for cid in junk_customer_ids:
if quotes_blocked or orders_blocked or cust_blocked:
# only block if THIS customer is referenced
this_ref = any(q.get("customer")==cid for q in quotes_blocked) or any(o.get("customer")==cid for o in orders_blocked)
if this_ref:
print(f" SKIP customer {cid} — referenced by quotes/orders"); continue
if APPLY:
r = req("DELETE", f"/api/collections/customers/records/{cid}", token=token)
if "__error" in r: print(f" FAIL customer {cid}: {r['__error']} {r['__body']}")
else: print(f" DELETED customer {cid}"); deleted += 1
else:
print(f" [dry-run] would delete customer {cid}"); deleted += 1
for cid in junk_company_ids:
# check no remaining references
still_ref = any(q.get("company")==cid for q in quotes) or any(o.get("company")==cid for o in orders) or any(c.get("company")==cid for c in customers if c["id"] not in junk_customer_ids)
if still_ref:
print(f" SKIP company {cid} — still referenced"); continue
if APPLY:
r = req("DELETE", f"/api/collections/companies/records/{cid}", token=token)
if "__error" in r: print(f" FAIL company {cid}: {r['__error']} {r['__body']}")
else: print(f" DELETED company {cid}"); deleted += 1
else:
print(f" [dry-run] would delete company {cid}"); deleted += 1
# 5. Fix quote status anomalies
print("\n=== Fixing quote status anomalies ===")
fixed = 0
for q in quotes:
qid = q["id"]; qnum = q.get("quote_number",""); st = q.get("status",""); cb = q.get("claimed_by","")
if st == "":
print(f" {qnum}: empty status -> 'new'")
if APPLY:
req("PATCH", f"/api/collections/quotes/records/{qid}", {"status":"new"}, token=token)
fixed += 1
elif st == "claimed" and not cb:
print(f" {qnum}: claimed w/o claimer -> 'new'")
if APPLY:
req("PATCH", f"/api/collections/quotes/records/{qid}", {"status":"new","claimed_by":""}, token=token)
fixed += 1
# 6. Clear {0,0} coordinates on companies (null-island)
print("\n=== Clearing {0,0} coordinates ===")
cleared = 0
for c in companies:
co = c.get("coordinates")
if isinstance(co, dict) and co.get("lon")==0 and co.get("lat")==0:
if APPLY:
req("PATCH", f"/api/collections/companies/records/{c['id']}", {"coordinates":None}, token=token)
print(f" {c.get('company','?')}: cleared {{0,0}}")
cleared += 1
print(f"\n=== Summary ({'APPLIED' if APPLY else 'DRY-RUN'}) ===")
print(f" deleted: {deleted}")
print(f" quotes fixed: {fixed}")
print(f" coordinates cleared: {cleared}")
if not APPLY:
print("\n Re-run with --apply to make changes.")