147 lines
6.2 KiB
Python
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.")
|