JEI_Import_Documents-App/scripts/create_collections.py

147 lines
6.5 KiB
Python
Raw Normal View History

"""
Create the three JEI_* collections in PocketBase v0.39.8.
NOTE on PB v0.39.8 API quirk discovered 2026-09-17:
- Field options for select go at the FIELD ROOT, not nested inside `options`.
e.g. { name: "foo", type: "select", maxSelect: 1, values: [...] }
NOT { name: "foo", type: "select", options: { maxSelect: 1, values: [...] } }
- This is opposite to the docs for some earlier PB versions.
Idempotent: skips a collection if it already exists by name.
Run: python scripts/create_collections.py
"""
import os
import sys
import json
import urllib.request
from urllib.error import HTTPError
PB_URL = os.environ.get("PB_URL", "https://pb.agent.apphub.co.za")
PB_TOKEN = os.environ.get("PB_TOKEN", "")
if not PB_TOKEN:
env_path = os.path.join(os.path.dirname(__file__), "..", ".env.local")
if os.path.exists(env_path):
for line in open(env_path):
if line.startswith("PB_TOKEN="):
PB_TOKEN = line.strip().split("=", 1)[1]
if not PB_TOKEN:
sys.exit("ERROR: PB_TOKEN not set")
HEADERS = {"Authorization": PB_TOKEN, "Content-Type": "application/json"}
def pb_get(path):
req = urllib.request.Request(PB_URL + path, headers=HEADERS, method="GET")
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read())
def pb_post(path, body):
req = urllib.request.Request(PB_URL + path, data=json.dumps(body).encode("utf-8"),
headers=HEADERS, method="POST")
try:
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read()), None
except HTTPError as e:
return None, json.loads(e.read())
SUPERUSER_RULES = {"listRule": "", "viewRule": "", "createRule": "",
"updateRule": "", "deleteRule": ""}
def invoices_schema():
return {
"name": "JEI_invoices",
"type": "base",
"fields": [
{"name": "invoice_no", "type": "text", "required": True, "min": 1, "max": 50, "pattern": ""},
{"name": "invoice_date", "type": "date", "required": True, "min": "", "max": ""},
# Select options go at the field root (PB v0.39.8 quirk)
{"name": "document_type", "type": "select", "required": True, "maxSelect": 1, "values": ["invoice", "proforma", "quote"]},
{"name": "customer_order_no", "type": "text", "required": False, "min": 0, "max": 100, "pattern": ""},
{"name": "buyer_name_variant", "type": "select", "required": True, "maxSelect": 1, "values": ["t/a JEI South Africa", "JEI South Africa", "JEI South Africa Importers"]},
{"name": "supplier_name", "type": "text", "required": True, "min": 0, "max": 200, "pattern": ""},
{"name": "account_ref", "type": "text", "required": False, "min": 0, "max": 50, "pattern": ""},
{"name": "total_net", "type": "number", "required": True, "min": 0, "max": None, "noDecimal": False},
{"name": "carriage_net", "type": "number", "required": False, "min": 0, "max": None, "noDecimal": False},
{"name": "invoice_total", "type": "number", "required": True, "min": 0, "max": None, "noDecimal": False},
{"name": "deliver_to_name", "type": "text", "required": False, "min": 0, "max": 200, "pattern": ""},
{"name": "deliver_to_address", "type": "text", "required": False, "min": 0, "max": 1000, "pattern": ""},
{"name": "source_filename", "type": "text", "required": False, "min": 0, "max": 500, "pattern": ""},
{"name": "notes", "type": "text", "required": False, "min": 0, "max": 5000, "pattern": ""},
{"name": "pdf_file", "type": "file", "required": False, "maxSelect": 1, "maxSize": 0, "mimeTypes": ["application/pdf"], "thumbs": []},
],
**SUPERUSER_RULES,
}
def stock_codes_schema():
return {
"name": "JEI_stock_codes",
"type": "base",
"fields": [
{"name": "code", "type": "text", "required": True, "min": 1, "max": 100, "pattern": ""},
{"name": "description_goods", "type": "text", "required": True, "min": 1, "max": 500, "pattern": ""},
{"name": "first_seen_date", "type": "date", "required": False, "min": "", "max": ""},
{"name": "last_seen_date", "type": "date", "required": False, "min": "", "max": ""},
],
**SUPERUSER_RULES,
}
def invoice_lines_schema(invoices_id, stock_codes_id):
return {
"name": "JEI_invoice_lines",
"type": "base",
"fields": [
# relation: collectionId goes at field root in PB v0.39.8
{"name": "invoice", "type": "relation", "required": True, "collectionId": invoices_id, "cascadeDelete": True, "minSelect": 1, "maxSelect": 1},
{"name": "line_no", "type": "number", "required": True, "min": 0, "max": None, "noDecimal": True},
{"name": "stock_code", "type": "relation", "required": True, "collectionId": stock_codes_id, "cascadeDelete": True, "minSelect": 1, "maxSelect": 1},
{"name": "quantity", "type": "number", "required": True, "min": 0, "max": None, "noDecimal": False},
{"name": "unit_price", "type": "number", "required": True, "min": 0, "max": None, "noDecimal": False},
{"name": "net_amount", "type": "number", "required": True, "min": 0, "max": None, "noDecimal": False},
{"name": "commodity_code", "type": "text", "required": False, "min": 0, "max": 50, "pattern": ""},
{"name": "country_of_origin", "type": "text", "required": False, "min": 0, "max": 50, "pattern": ""},
],
**SUPERUSER_RULES,
}
def find_collection(name):
collections = pb_get("/api/collections?perPage=200")["items"]
for c in collections:
if c["name"] == name:
return c
return None
def ensure_collection(name, factory):
existing = find_collection(name)
if existing:
print(f"EXISTS: {name} (id={existing['id']}, fields={len(existing.get('fields') or [])})")
return existing
body = factory()
result, err = pb_post("/api/collections", body)
if err:
sys.exit(f"ERROR creating {name}: {err}")
print(f"CREATED: {name} (id={result['id']}, fields={len(result.get('fields') or [])})")
return result
def main():
print(f"PB_URL: {PB_URL}")
inv = ensure_collection("JEI_invoices", invoices_schema)
sc = ensure_collection("JEI_stock_codes", stock_codes_schema)
def lines_factory():
return invoice_lines_schema(inv["id"], sc["id"])
ensure_collection("JEI_invoice_lines", lines_factory)
print("DONE: 3 JEI_* collections ready.")
if __name__ == "__main__":
main()