feat(sites): add work_month_start_day field
This commit is contained in:
parent
d570352b76
commit
daf2344d99
2 changed files with 118 additions and 0 deletions
113
scripts/migrations/2026-09-11-add-site-work-month-field.py
Normal file
113
scripts/migrations/2026-09-11-add-site-work-month-field.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
One-shot PocketBase migration applied via:
|
||||
|
||||
PB_URL=https://pb.mega.infra.apphub.co.za \\
|
||||
PB_ADMIN_EMAIL=admin@mega.co.za \\
|
||||
PB_ADMIN_PASSWORD=... \\
|
||||
python3 scripts/migrations/2026-09-11-add-site-work-month-field.py
|
||||
|
||||
Adds sites.work_month_start_day (see
|
||||
docs/superpowers/specs/2026-09-11-project-month-week-design.md for why:
|
||||
some sites' "work months" don't run 1st-30th, they run e.g. 21st-21st).
|
||||
|
||||
Strictly additive, and the field is not required — existing sites keep
|
||||
whatever value they have (empty means "ordinary calendar months").
|
||||
|
||||
Idempotent: re-running skips the field if it already exists.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
NEW_FIELD = {
|
||||
"name": "work_month_start_day",
|
||||
"type": "number",
|
||||
"required": False,
|
||||
"min": 1,
|
||||
"max": 31,
|
||||
}
|
||||
|
||||
|
||||
def admin_auth(base_url: str) -> str:
|
||||
body = json.dumps(
|
||||
{
|
||||
"identity": os.environ["PB_ADMIN_EMAIL"],
|
||||
"password": os.environ["PB_ADMIN_PASSWORD"],
|
||||
}
|
||||
).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{base_url}/api/collections/_superusers/auth-with-password",
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
return json.loads(resp.read())["token"]
|
||||
|
||||
|
||||
def get_collection(base_url: str, token: str, name: str):
|
||||
req = urllib.request.Request(
|
||||
f"{base_url}/api/collections/{name}", headers={"Authorization": token}
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
return json.loads(resp.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
return None
|
||||
raise
|
||||
|
||||
|
||||
def patch_collection(base_url: str, token: str, name: str, body: dict):
|
||||
req = urllib.request.Request(
|
||||
f"{base_url}/api/collections/{name}",
|
||||
data=json.dumps(body).encode(),
|
||||
headers={"Authorization": token, "Content-Type": "application/json"},
|
||||
method="PATCH",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
return json.loads(resp.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
raise RuntimeError(
|
||||
f"PATCH {name} failed: {e.code} {e.read().decode() if e.fp else ''}"
|
||||
) from e
|
||||
|
||||
|
||||
def main() -> int:
|
||||
base_url = os.environ.get("PB_URL", "http://127.0.0.1:8090").rstrip("/")
|
||||
token = admin_auth(base_url)
|
||||
|
||||
coll = get_collection(base_url, token, "sites")
|
||||
if not coll:
|
||||
print(f"ERROR: collection 'sites' not found on {base_url}")
|
||||
return 1
|
||||
|
||||
existing = coll.get("fields", [])
|
||||
have = {f["name"] for f in existing}
|
||||
if NEW_FIELD["name"] in have:
|
||||
print("sites: no-op, work_month_start_day already present")
|
||||
else:
|
||||
print("sites: adding work_month_start_day")
|
||||
patch_collection(base_url, token, "sites", {"fields": existing + [NEW_FIELD]})
|
||||
|
||||
final = get_collection(base_url, token, "sites")
|
||||
print("\nFinal sites field list:")
|
||||
print(f" {sorted(f['name'] for f in final.get('fields', []))}")
|
||||
print("\nMigration complete.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except KeyError as e:
|
||||
print(
|
||||
f"ERROR: missing environment variable {e}. "
|
||||
f"Set PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
|
|
@ -115,6 +115,11 @@ DEFS["sites"] = {
|
|||
field("latitude", "number", required=False),
|
||||
field("longitude", "number", required=False),
|
||||
field("job_start_date", "date", required=False),
|
||||
# How the site counts its own "work months" — e.g. 21 means each
|
||||
# work-month runs the 21st through the 20th of the next calendar
|
||||
# month, not the 1st–30th. Unset (or 1) means ordinary calendar
|
||||
# months. See src/lib/project-calendar.ts.
|
||||
field("work_month_start_day", "number", required=False, min=1, max=31),
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX `idx_sites_project_code` ON `sites` (`project_code`)",
|
||||
|
|
|
|||
Loading…
Reference in a new issue