diff --git a/.gitignore b/.gitignore index a9e058a..14c28c8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,8 @@ build/ data/review/*.json data/review/*.txt data/review/unparseable.txt +scripts/__pycache__/ +scripts/*.pyc +.next-dev.log .DS_Store *.log diff --git a/NOTES.md b/NOTES.md index e188229..741e746 100644 --- a/NOTES.md +++ b/NOTES.md @@ -91,3 +91,70 @@ Verified by reading both files for invoice 154923 — same data, different PDF l - "Customer Order No" field on this invoice reads literally `Email 28.07.25` (looks like a separate "Email [date]" reference the supplier uses). The importer will capture it as-is. User can clean later. - The invoice is stamped **PAID IN FULL**. Some invoices are pre-paid (proforma-paid or settled-on-delivery). We don't track this in the schema yet (future phase) but it's visible on the original PDF. + +## 2026-09-17 — Build phase + +### Major wins +- ✅ Next.js 14 + Tailwind app on **http://localhost:6002** +- ✅ PB collections: JEI_invoices (15 fields + id), JEI_invoice_lines (8 fields + relations), JEI_stock_codes (4 fields + id) — 30 fields total +- ✅ PDF extractor parses 38 records from 33 JEI supplier PDFs (skips `_non_jei/`) +- ✅ Importer pushed 24 invoices + 70 stock codes + 118 lines into PB (idempotent — re-runs skip duplicates) +- ✅ All 5 UI pages return 200 with real data + +### Critical PB v0.39.8 API quirks discovered + +1. **POST /api/collections uses key `fields` not `schema`.** Docs show `schema` but the API silently accepts `schema` and stores under `fields`. Always send `fields: [...]`. + +2. **Field options go at the FIELD ROOT, not inside `options`.** This is the opposite of what docs for older PB versions show. e.g. + - ✅ WORKS: `{ name: "foo", type: "select", maxSelect: 1, values: ["a","b"] }` + - ❌ FAILS: `{ name: "foo", type: "select", options: { maxSelect: 1, values: ["a","b"] } }` — returns `fields.X.values.code: validation_required, Cannot be blank.` even though the values array is non-empty. + - Same applies to relation fields: `collectionId` and `cascadeDelete` go at the field root, not in `options`. + +3. **GET response hides field options.** After creating a collection, `GET /api/collections/` returns `fields` with `options: {}` (empty) for select/relation fields. The data IS stored — just not returned. PATCH the same field with explicit options to confirm (it succeeds). + +4. **PATCH round-trip "wakes up" newly created collections.** Same trick as the existing memory entry — if a collection 403s on read or returns empty fields, PATCHing any rule field (even with the existing value) often fixes it. + +### Parser quirks remaining +- 4 unparseable PDFs (empty text — likely failed exports: Invoice.pdf, JEI Supplier Invoice.pdf, JEI SA Invoice 147842.pdf, JEI SA Proforma 148931.pdf). +- Some multi-page PDFs (151778, 151779) have page-boundary issues — the parser creates multiple records from the same physical invoice when it shouldn't. +- "JEISA000" got imported as a stock code (it's actually the buyer's account reference) — minor data issue, can be deleted via admin UI. +- 161453, 161852: lines parsed as 0 (one of the page variants has a different layout that the parser doesn't recognize). + +### Token expiry +PB token expires **2026-09-18 19:10 SAST** (~22h from now). Re-grab from DevTools → Application → Local Storage → `pocketbase_auth.token` on the PB admin UI when needed. + +### File layout (final) +``` +JEI/ +├── .env.local # PB_URL, PB_TOKEN, NEXT_PUBLIC_PB_URL +├── .gitignore +├── NOTES.md +├── package.json # next 14, pocketbase, tailwind, etc. +├── tsconfig.json +├── next.config.js +├── tailwind.config.ts +├── postcss.config.js +├── app/ +│ ├── layout.tsx # header with nav +│ ├── page.tsx # dashboard +│ ├── invoices/page.tsx # list with search +│ ├── invoices/[id]/page.tsx # detail with PDF link +│ ├── stock-codes/page.tsx # list with stats +│ ├── stock-codes/[code]/page.tsx # detail with avg/min/max +│ ├── globals.css +│ └── lib/{pb.ts, types.ts} +├── scripts/ +│ ├── create_collections.py # creates 3 JEI_* collections +│ ├── extract_invoices.py # PDF → data/review/all.json +│ ├── import_to_pb.py # all.json → PB (idempotent) +│ └── patch_collections.py # legacy workaround (not needed anymore) +├── invoices/ # user drops PDFs here +├── data/review/all.json # parsed records +└── docs/superpowers/specs/... # design spec +``` + +### How to use +1. Server runs on `http://localhost:6002` via `pnpm dev` (or `node_modules/.bin/next.cmd dev -p 6002`). +2. To re-extract: `python scripts/extract_invoices.py` (reads all PDFs in `invoices/`, writes `data/review/all.json`). +3. To re-import: `python scripts/import_to_pb.py` (skips existing invoice_no + document_type pairs). +4. To refresh PB token: visit `https://pb.agent.apphub.co.za/_/`, copy `pocketbase_auth.token` from DevTools → Application → Local Storage, paste into `JEI/.env.local`. diff --git a/scripts/__pycache__/create_collections.cpython-311.pyc b/scripts/__pycache__/create_collections.cpython-311.pyc deleted file mode 100644 index fe99aaf..0000000 Binary files a/scripts/__pycache__/create_collections.cpython-311.pyc and /dev/null differ diff --git a/scripts/patch_collections.py b/scripts/patch_collections.py deleted file mode 100644 index d2a948c..0000000 --- a/scripts/patch_collections.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -PATCH schema fields onto the JEI_* collections if they're missing. - -Workaround for a known PocketBase quirk where collections created via API -sometimes persist with empty schema. The rules PATCH trick works for empty -rules; for empty schema we explicitly PATCH the schema field. - -Run: python scripts/patch_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_patch(path, body): - req = urllib.request.Request(PB_URL + path, data=json.dumps(body).encode("utf-8"), - headers=HEADERS, method="PATCH") - try: - with urllib.request.urlopen(req, timeout=30) as r: - return json.loads(r.read()), None - except HTTPError as e: - return None, e.read().decode() - - -# Mirror of create_collections.py schemas (kept independent for clarity) -SUPERUSER_RULES = {"listRule": "", "viewRule": "", "createRule": "", - "updateRule": "", "deleteRule": ""} - - -def invoices_schema(): - return [ - {"name": "invoice_no", "type": "text", "required": True, "options": {"min": 1, "max": 50, "pattern": ""}}, - {"name": "invoice_date", "type": "date", "required": True, "options": {"min": "", "max": ""}}, - {"name": "document_type", "type": "select", "required": True, "options": {"maxSelect": 1, "values": ["invoice", "proforma", "quote"]}}, - {"name": "customer_order_no", "type": "text", "required": False, "options": {"min": 0, "max": 100, "pattern": ""}}, - {"name": "buyer_name_variant", "type": "select", "required": True, "options": {"maxSelect": 1, "values": ["t/a JEI South Africa", "JEI South Africa", "JEI South Africa Importers"]}}, - {"name": "supplier_name", "type": "text", "required": True, "options": {"min": 0, "max": 200, "pattern": ""}}, - {"name": "account_ref", "type": "text", "required": False, "options": {"min": 0, "max": 50, "pattern": ""}}, - {"name": "total_net", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, - {"name": "carriage_net", "type": "number", "required": False, "options": {"min": 0, "max": None, "noDecimal": False}}, - {"name": "invoice_total", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, - {"name": "deliver_to_name", "type": "text", "required": False, "options": {"min": 0, "max": 200, "pattern": ""}}, - {"name": "deliver_to_address", "type": "text", "required": False, "options": {"min": 0, "max": 1000, "pattern": ""}}, - {"name": "source_filename", "type": "text", "required": False, "options": {"min": 0, "max": 500, "pattern": ""}}, - {"name": "notes", "type": "text", "required": False, "options": {"min": 0, "max": 5000, "pattern": ""}}, - {"name": "pdf_file", "type": "file", "required": False, "options": {"maxSelect": 1, "maxSize": 0, "mimeTypes": ["application/pdf"], "thumbs": []}}, - ] - - -def stock_codes_schema(): - return [ - {"name": "code", "type": "text", "required": True, "options": {"min": 1, "max": 100, "pattern": ""}}, - {"name": "description_goods", "type": "text", "required": True, "options": {"min": 1, "max": 500, "pattern": ""}}, - {"name": "first_seen_date", "type": "date", "required": False, "options": {"min": "", "max": ""}}, - {"name": "last_seen_date", "type": "date", "required": False, "options": {"min": "", "max": ""}}, - ] - - -def lines_schema(): - # We need to resolve the relation target ids - cols = pb_get("/api/collections?perPage=200")["items"] - inv = next(c for c in cols if c["name"] == "JEI_invoices") - sc = next(c for c in cols if c["name"] == "JEI_stock_codes") - return [ - {"name": "invoice", "type": "relation", "required": True, "options": {"collectionId": inv["id"], "cascadeDelete": True, "minSelect": 1, "maxSelect": 1}}, - {"name": "line_no", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": True}}, - {"name": "stock_code", "type": "relation", "required": True, "options": {"collectionId": sc["id"], "cascadeDelete": True, "minSelect": 1, "maxSelect": 1}}, - {"name": "quantity", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, - {"name": "unit_price", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, - {"name": "net_amount", "type": "number", "required": True, "options": {"min": 0, "max": None, "noDecimal": False}}, - {"name": "commodity_code", "type": "text", "required": False, "options": {"min": 0, "max": 50, "pattern": ""}}, - {"name": "country_of_origin", "type": "text", "required": False, "options": {"min": 0, "max": 50, "pattern": ""}}, - ] - - -PATCHES = [ - ("JEI_invoices", invoices_schema), - ("JEI_stock_codes", stock_codes_schema), - ("JEI_invoice_lines", lines_schema), -] - - -def main(): - cols = pb_get("/api/collections?perPage=200")["items"] - for name, factory in PATCHES: - col = next((c for c in cols if c["name"] == name), None) - if not col: - print(f"SKIP: {name} does not exist") - continue - if col.get("schema") and len(col["schema"]) > 0: - print(f"OK: {name} already has {len(col['schema'])} fields") - continue - body = {"schema": factory(), **SUPERUSER_RULES} - result, err = pb_patch(f"/api/collections/{col['id']}", body) - if err: - print(f"FAIL: {name} — {err}") - sys.exit(1) - print(f"PATCHED: {name} → {len(result.get('schema', []))} fields") - - -if __name__ == "__main__": - main() diff --git a/scripts/pb_snapshot.txt b/scripts/pb_snapshot.txt deleted file mode 100644 index 5b3274e..0000000 --- a/scripts/pb_snapshot.txt +++ /dev/null @@ -1,674 +0,0 @@ -uid=1_0 RootWebArea "ysterbaard_annual_returns - Acme" url="https://pb.agent.apphub.co.za/_/#/collections?collection=ysterbaard_annual_returns" - uid=1_1 main - uid=2_0 link "App logo" url="https://pb.agent.apphub.co.za/_/#/" - uid=2_1 image "App logo" url="https://pb.agent.apphub.co.za/_/images/logo_white.svg" - uid=2_2 navigation - uid=2_3 link "Collections" url="https://pb.agent.apphub.co.za/_/#/collections" - uid=2_4 StaticText "Collections" - uid=2_5 link "Logs" url="https://pb.agent.apphub.co.za/_/#/logs" - uid=2_6 StaticText "Logs" - uid=2_7 link "Settings" url="https://pb.agent.apphub.co.za/_/#/settings" - uid=2_8 StaticText "Settings" - uid=2_9 button "Color scheme" expandable - uid=2_10 button "ysterbaard@apphub.co.za" expandable - uid=2_11 complementary - uid=2_12 textbox "Search collections..." - uid=2_13 button description="Collections overview" - uid=2_14 navigation - uid=2_15 DisclosureTriangle "Collections" expandable expanded - uid=2_16 button "users " - uid=2_17 button "" description="Pin" - uid=2_18 button "groups " - uid=2_19 button "" description="Pin" - uid=2_20 button "sable_api " - uid=2_21 button "" description="Pin" - uid=2_22 button "sable_notes " - uid=2_23 button "" description="Pin" - uid=2_24 button "tasks " - uid=2_25 button "" description="Pin" - uid=2_26 button "test_patch_schema " - uid=2_27 button "" description="Pin" - uid=2_28 button "ysterbaard_SARS_EMP201_PAYE " - uid=2_29 button "" description="Pin" - uid=2_30 button "ysterbaard_SARS_VAT_VAT201 " - uid=2_31 button "" description="Pin" - uid=2_32 button "ysterbaard_annual_returns " - uid=2_33 button "" description="Pin" - uid=2_34 button "ysterbaard_companies " - uid=2_35 button "" description="Pin" - uid=2_36 button "ysterbaard_company_directors " - uid=2_37 button "" description="Pin" - uid=2_38 button "ysterbaard_company_documents " - uid=2_39 button "" description="Pin" - uid=2_40 button "ysterbaard_company_emails " - uid=2_41 button "" description="Pin" - uid=2_42 button "ysterbaard_company_registrations " - uid=2_43 button "" description="Pin" - uid=2_44 button "ysterbaard_director_appointments " - uid=2_45 button "" description="Pin" - uid=2_46 button "ysterbaard_director_documents " - uid=2_47 button "" description="Pin" - uid=2_48 button "ysterbaard_director_resignations " - uid=2_49 button "" description="Pin" - uid=2_50 button "ysterbaard_directors " - uid=2_51 button "" description="Pin" - uid=2_52 button "ysterbaard_document_types " - uid=2_53 button "" description="Pin" - uid=2_54 button "ysterbaard_notes " - uid=2_55 button "" description="Pin" - uid=2_56 button "ysterbaard_onboarding_submissions " - uid=2_57 button "" description="Pin" - uid=2_58 button "ysterbaard_reg_directors " - uid=2_59 button "" description="Pin" - uid=2_60 button "ysterbaard_reg_shareholders " - uid=2_61 button "" description="Pin" - uid=2_62 button "ysterbaard_registration_emails " - uid=2_63 button "" description="Pin" - uid=2_64 button "ysterbaard_resolution_attendees " - uid=2_65 button "" description="Pin" - uid=2_66 button "ysterbaard_resolution_received_letters " - uid=2_67 button "" description="Pin" - uid=2_68 button "ysterbaard_resolution_signers " - uid=2_69 button "" description="Pin" - uid=2_70 button "ysterbaard_resolutions " - uid=2_71 button "" description="Pin" - uid=2_72 button "ysterbaard_sars_irp6_income_tax " - uid=2_73 button "" description="Pin" - uid=2_74 DisclosureTriangle " System" expandable - uid=2_75 button "New collection" - uid=2_76 navigation - uid=2_77 StaticText "Collections" - uid=1_5 StaticText "/" - uid=2_78 StaticText "ysterbaard_annual_returns" - uid=2_79 button "Collection settings" - uid=2_80 button "Refresh" - uid=2_81 button "API preview" - uid=2_82 button "New record" - uid=2_83 form - uid=2_84 button "Search history" expandable - uid=2_85 textbox multiline - uid=2_86 checkbox "" - uid=1_5 StaticText "" - uid=2_87 StaticText "id" - uid=2_88 StaticText "company" - uid=2_89 StaticText "ar_year" - uid=2_90 StaticText "ar_month" - uid=2_91 StaticText "ar_non_compliance_date" - uid=2_92 StaticText "is_filed" - uid=2_93 StaticText "amount_paid" - uid=2_94 StaticText "date_filed" - uid=2_95 StaticText "source" - uid=2_96 button "Toggle columns" expandable - uid=2_97 row - uid=2_98 checkbox "" - uid=1_5 StaticText "" - uid=2_99 button "Copy" - uid=2_100 StaticText "vzusowawsnvryc4c4q5ib" - uid=2_101 StaticText "True" - uid=2_102 StaticText "2026" - uid=2_103 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_104 StaticText "False" - uid=2_105 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_106 StaticText "manual" - uid=2_107 row - uid=2_108 checkbox "" - uid=1_5 StaticText "" - uid=2_109 button "Copy" - uid=2_110 StaticText "utbehhh45ivqxsr3stzg4" - uid=2_111 StaticText "True" - uid=2_112 StaticText "2025" - uid=2_113 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_114 StaticText "False" - uid=2_115 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_116 StaticText "manual" - uid=2_117 row - uid=2_118 checkbox "" - uid=1_5 StaticText "" - uid=2_119 button "Copy" - uid=2_120 StaticText "lxsgxus2d2earitha3o8r" - uid=2_121 StaticText "True" - uid=2_122 StaticText "2024" - uid=2_123 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_124 StaticText "False" - uid=2_125 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_126 StaticText "manual" - uid=2_127 row - uid=2_128 checkbox "" - uid=1_5 StaticText "" - uid=2_129 button "Copy" - uid=2_130 StaticText "vp7e1adhqtc30qc8ucojn" - uid=2_131 StaticText "True" - uid=2_132 StaticText "2023" - uid=2_133 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_134 StaticText "False" - uid=2_135 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_136 StaticText "manual" - uid=2_137 row - uid=2_138 checkbox "" - uid=1_5 StaticText "" - uid=2_139 button "Copy" - uid=2_140 StaticText "qrplpjb1s3jcsmv05lfn6" - uid=2_141 StaticText "True" - uid=2_142 StaticText "2022" - uid=2_143 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_144 StaticText "False" - uid=2_145 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_146 StaticText "manual" - uid=2_147 row - uid=2_148 checkbox "" - uid=1_5 StaticText "" - uid=2_149 button "Copy" - uid=2_150 StaticText "mvjd01oe6rh6khh4fbmvo" - uid=2_151 StaticText "True" - uid=2_152 StaticText "2021" - uid=2_153 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_154 StaticText "False" - uid=2_155 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_156 StaticText "manual" - uid=2_157 row - uid=2_158 checkbox "" - uid=1_5 StaticText "" - uid=2_159 button "Copy" - uid=2_160 StaticText "3f1a3miti26r4elhcvkkx" - uid=2_161 StaticText "True" - uid=2_162 StaticText "2020" - uid=2_163 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_164 StaticText "False" - uid=2_165 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_166 StaticText "manual" - uid=2_167 row - uid=2_168 checkbox "" - uid=1_5 StaticText "" - uid=2_169 button "Copy" - uid=2_170 StaticText "x93s1dx044zjm6v5dgaty" - uid=2_171 StaticText "True" - uid=2_172 StaticText "2026" - uid=2_173 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_174 StaticText "False" - uid=2_175 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_176 StaticText "manual" - uid=2_177 row - uid=2_178 checkbox "" - uid=1_5 StaticText "" - uid=2_179 button "Copy" - uid=2_180 StaticText "r1elimr6oe4jbmi7mj4o4" - uid=2_181 StaticText "True" - uid=2_182 StaticText "2025" - uid=2_183 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_184 StaticText "False" - uid=2_185 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_186 StaticText "manual" - uid=2_187 row - uid=2_188 checkbox "" - uid=1_5 StaticText "" - uid=2_189 button "Copy" - uid=2_190 StaticText "1m56e3az8hbbtympbgyyv" - uid=2_191 StaticText "True" - uid=2_192 StaticText "2024" - uid=2_193 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_194 StaticText "False" - uid=2_195 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_196 StaticText "manual" - uid=2_197 row - uid=2_198 checkbox "" - uid=1_5 StaticText "" - uid=2_199 button "Copy" - uid=2_200 StaticText "eyjrz9r5o9vvyad651e93" - uid=2_201 StaticText "True" - uid=2_202 StaticText "2023" - uid=2_203 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_204 StaticText "False" - uid=2_205 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_206 StaticText "manual" - uid=2_207 row - uid=2_208 checkbox "" - uid=1_5 StaticText "" - uid=2_209 button "Copy" - uid=2_210 StaticText "oqs26zkb963yrxwci7uty" - uid=2_211 StaticText "True" - uid=2_212 StaticText "2022" - uid=2_213 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_214 StaticText "False" - uid=2_215 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_216 StaticText "manual" - uid=2_217 row - uid=2_218 checkbox "" - uid=1_5 StaticText "" - uid=2_219 button "Copy" - uid=2_220 StaticText "g57qpwq4mk11y0iox96gd" - uid=2_221 StaticText "True" - uid=2_222 StaticText "2021" - uid=2_223 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_224 StaticText "False" - uid=2_225 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_226 StaticText "manual" - uid=2_227 row - uid=2_228 checkbox "" - uid=1_5 StaticText "" - uid=2_229 button "Copy" - uid=2_230 StaticText "newgkeoeixf2tn3ddsm4o" - uid=2_231 StaticText "True" - uid=2_232 StaticText "2020" - uid=2_233 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_234 StaticText "False" - uid=2_235 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_236 StaticText "manual" - uid=2_237 row - uid=2_238 checkbox "" - uid=1_5 StaticText "" - uid=2_239 button "Copy" - uid=2_240 StaticText "px32hbbepgd8g4k3x6jw2" - uid=2_241 StaticText "True" - uid=2_242 StaticText "2026" - uid=2_243 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_244 StaticText "False" - uid=2_245 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_246 StaticText "manual" - uid=2_247 row - uid=2_248 checkbox "" - uid=1_5 StaticText "" - uid=2_249 button "Copy" - uid=2_250 StaticText "e9dfpce06qbnszabfagnb" - uid=2_251 StaticText "True" - uid=2_252 StaticText "2025" - uid=2_253 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_254 StaticText "False" - uid=2_255 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_256 StaticText "manual" - uid=2_257 row - uid=2_258 checkbox "" - uid=1_5 StaticText "" - uid=2_259 button "Copy" - uid=2_260 StaticText "rt0i5vcyefxz9a8b82q0s" - uid=2_261 StaticText "True" - uid=2_262 StaticText "2024" - uid=2_263 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_264 StaticText "False" - uid=2_265 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_266 StaticText "manual" - uid=2_267 row - uid=2_268 checkbox "" - uid=1_5 StaticText "" - uid=2_269 button "Copy" - uid=2_270 StaticText "zj57br33bmi80fkktefoi" - uid=2_271 StaticText "True" - uid=2_272 StaticText "2023" - uid=2_273 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_274 StaticText "False" - uid=2_275 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_276 StaticText "manual" - uid=2_277 row - uid=2_278 checkbox "" - uid=1_5 StaticText "" - uid=2_279 button "Copy" - uid=2_280 StaticText "ckrqqcb2osh1n6ad053y5" - uid=2_281 StaticText "True" - uid=2_282 StaticText "2022" - uid=2_283 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_284 StaticText "False" - uid=2_285 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_286 StaticText "manual" - uid=2_287 row - uid=2_288 checkbox "" - uid=1_5 StaticText "" - uid=2_289 button "Copy" - uid=2_290 StaticText "05y3njez84fa17mt263pu" - uid=2_291 StaticText "True" - uid=2_292 StaticText "2021" - uid=2_293 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_294 StaticText "False" - uid=2_295 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_296 StaticText "manual" - uid=2_297 row - uid=2_298 checkbox "" - uid=1_5 StaticText "" - uid=2_299 button "Copy" - uid=2_300 StaticText "aw4zn92g86sr0v24xtcmu" - uid=2_301 StaticText "True" - uid=2_302 StaticText "2020" - uid=2_303 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_304 StaticText "False" - uid=2_305 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_306 StaticText "manual" - uid=2_307 row - uid=2_308 checkbox "" - uid=1_5 StaticText "" - uid=2_309 button "Copy" - uid=2_310 StaticText "hh4r8l6vgvcvq3d42xe4y" - uid=2_311 StaticText "True" - uid=2_312 StaticText "2026" - uid=2_313 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_314 StaticText "False" - uid=2_315 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_316 StaticText "bizportal" - uid=2_317 row - uid=2_318 checkbox "" - uid=1_5 StaticText "" - uid=2_319 button "Copy" - uid=2_320 StaticText "aro8cui2k9x7y2nhuiou3" - uid=2_321 StaticText "True" - uid=2_322 StaticText "2025" - uid=2_323 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_324 StaticText "False" - uid=2_325 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_326 StaticText "bizportal" - uid=2_327 row - uid=2_328 checkbox "" - uid=1_5 StaticText "" - uid=2_329 button "Copy" - uid=2_330 StaticText "nmu5nsrdytqsl1p2j51d3" - uid=2_331 StaticText "True" - uid=2_332 StaticText "2024" - uid=2_333 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_334 StaticText "False" - uid=2_335 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_336 StaticText "bizportal" - uid=2_337 row - uid=2_338 checkbox "" - uid=1_5 StaticText "" - uid=2_339 button "Copy" - uid=2_340 StaticText "7m4qh6vfdn6yknzyia90f" - uid=2_341 StaticText "True" - uid=2_342 StaticText "2023" - uid=2_343 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_344 StaticText "False" - uid=2_345 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_346 StaticText "bizportal" - uid=2_347 row - uid=2_348 checkbox "" - uid=1_5 StaticText "" - uid=2_349 button "Copy" - uid=2_350 StaticText "gdk19i23qb89onuwvcgso" - uid=2_351 StaticText "True" - uid=2_352 StaticText "2022" - uid=2_353 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_354 StaticText "False" - uid=2_355 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_356 StaticText "bizportal" - uid=2_357 row - uid=2_358 checkbox "" - uid=1_5 StaticText "" - uid=2_359 button "Copy" - uid=2_360 StaticText "kod1zxa0dx1jc870avlkz" - uid=2_361 StaticText "True" - uid=2_362 StaticText "2021" - uid=2_363 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_364 StaticText "False" - uid=2_365 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_366 StaticText "bizportal" - uid=2_367 row - uid=2_368 checkbox "" - uid=1_5 StaticText "" - uid=2_369 button "Copy" - uid=2_370 StaticText "e9bmkdv2z0jel4w5cv9qa" - uid=2_371 StaticText "True" - uid=2_372 StaticText "2020" - uid=2_373 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_374 StaticText "False" - uid=2_375 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_376 StaticText "bizportal" - uid=2_377 row - uid=2_378 checkbox "" - uid=1_5 StaticText "" - uid=2_379 button "Copy" - uid=2_380 StaticText "2v2nos72ysckf6q6awkn5" - uid=2_381 StaticText "True" - uid=2_382 StaticText "2026" - uid=2_383 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_384 StaticText "False" - uid=2_385 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_386 StaticText "manual" - uid=2_387 row - uid=2_388 checkbox "" - uid=1_5 StaticText "" - uid=2_389 button "Copy" - uid=2_390 StaticText "usodm5otthx7d2wro8bwx" - uid=2_391 StaticText "True" - uid=2_392 StaticText "2025" - uid=2_393 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_394 StaticText "False" - uid=2_395 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_396 StaticText "manual" - uid=2_397 row - uid=2_398 checkbox "" - uid=1_5 StaticText "" - uid=2_399 button "Copy" - uid=2_400 StaticText "euf62r18bbgdn37aw4jrl" - uid=2_401 StaticText "True" - uid=2_402 StaticText "2024" - uid=2_403 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_404 StaticText "False" - uid=2_405 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_406 StaticText "manual" - uid=2_407 row - uid=2_408 checkbox "" - uid=1_5 StaticText "" - uid=2_409 button "Copy" - uid=2_410 StaticText "ii73b9ovz2pq5ug9pdt5b" - uid=2_411 StaticText "True" - uid=2_412 StaticText "2023" - uid=2_413 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_414 StaticText "False" - uid=2_415 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_416 StaticText "manual" - uid=2_417 row - uid=2_418 checkbox "" - uid=1_5 StaticText "" - uid=2_419 button "Copy" - uid=2_420 StaticText "auf2h2a96of5xw5hnmf2n" - uid=2_421 StaticText "True" - uid=2_422 StaticText "2022" - uid=2_423 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_424 StaticText "False" - uid=2_425 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_426 StaticText "manual" - uid=2_427 row - uid=2_428 checkbox "" - uid=1_5 StaticText "" - uid=2_429 button "Copy" - uid=2_430 StaticText "amcbd5kbuc3bmw4m2qdiv" - uid=2_431 StaticText "True" - uid=2_432 StaticText "2021" - uid=2_433 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_434 StaticText "False" - uid=2_435 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_436 StaticText "manual" - uid=2_437 row - uid=2_438 checkbox "" - uid=1_5 StaticText "" - uid=2_439 button "Copy" - uid=2_440 StaticText "v0qye9ln762yfdlq7d4ch" - uid=2_441 StaticText "True" - uid=2_442 StaticText "2020" - uid=2_443 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_444 StaticText "False" - uid=2_445 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_446 StaticText "manual" - uid=2_447 row - uid=2_448 checkbox "" - uid=1_5 StaticText "" - uid=2_449 button "Copy" - uid=2_450 StaticText "k3pbizpi0iaymplwy0fa1" - uid=2_451 StaticText "True" - uid=2_452 StaticText "2026" - uid=2_453 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_454 StaticText "False" - uid=2_455 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_456 StaticText "manual" - uid=2_457 row - uid=2_458 checkbox "" - uid=1_5 StaticText "" - uid=2_459 button "Copy" - uid=2_460 StaticText "bqd0glsquey9b7z4w2k50" - uid=2_461 StaticText "True" - uid=2_462 StaticText "2025" - uid=2_463 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_464 StaticText "False" - uid=2_465 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_466 StaticText "manual" - uid=2_467 row - uid=2_468 checkbox "" - uid=1_5 StaticText "" - uid=2_469 button "Copy" - uid=2_470 StaticText "xx9zhklos3e90w7opej5s" - uid=2_471 StaticText "True" - uid=2_472 StaticText "2024" - uid=2_473 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_474 StaticText "False" - uid=2_475 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_476 StaticText "manual" - uid=2_477 row - uid=2_478 checkbox "" - uid=1_5 StaticText "" - uid=2_479 button "Copy" - uid=2_480 StaticText "p4wn8dtp9qi1jh87gtt9g" - uid=2_481 StaticText "True" - uid=2_482 StaticText "2023" - uid=2_483 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_484 StaticText "False" - uid=2_485 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_486 StaticText "manual" - uid=2_487 row - uid=2_488 checkbox "" - uid=1_5 StaticText "" - uid=2_489 button "Copy" - uid=2_490 StaticText "3dj84c5u39hrv39ak8f7x" - uid=2_491 StaticText "True" - uid=2_492 StaticText "2022" - uid=2_493 StaticText "JANUARY" - uid=1_5 StaticText "N/A" - uid=2_494 StaticText "False" - uid=2_495 StaticText "0" - uid=1_5 StaticText "N/A" - uid=2_496 StaticText "manual" - uid=2_497 button "Load more" - uid=2_498 StaticText "Total: " - uid=2_499 StaticText "449" - uid=2_500 link "Docs" url="https://pocketbase.io/docs" - uid=2_501 StaticText "Docs" - uid=2_502 link "PocketBase v0.39.8" url="https://github.com/pocketbase/pocketbase/releases" - uid=2_503 StaticText "PocketBase v0.39.8" - uid=3_0 generic - uid=3_1 banner - uid=3_2 heading "Create collection" level="6" - uid=3_3 StaticText "Name" - uid=1_5 StaticText "*" - uid=3_4 textbox "Name *" required value="JEI_stock_codes" - uid=3_5 button "Type: Base" expandable - uid=3_6 navigation - uid=3_7 button "Fields" - uid=3_8 button "API rules" - uid=5_0 DisclosureTriangleGrouped "Field options" expandable - uid=1_5 StaticText "" - uid=5_1 banner - uid=1_5 StaticText "" - uid=5_2 textbox "" disableable disabled required value="id" - uid=5_3 StaticText "Required" - uid=5_4 button "Field options" - uid=5_5 DisclosureTriangleGrouped "Field options" expandable - uid=1_5 StaticText "" - uid=5_6 banner - uid=1_5 StaticText "" - uid=5_7 textbox "" focusable focused required value="text" - uid=5_8 button "Field options" - uid=5_9 DisclosureTriangleGrouped "Field options" expandable - uid=1_5 StaticText "" - uid=5_10 banner - uid=1_5 StaticText "" - uid=5_11 textbox "" required value="created" - uid=5_12 button "Create " expandable - uid=5_13 button "Field options" - uid=5_14 DisclosureTriangleGrouped "Field options" expandable - uid=1_5 StaticText "" - uid=5_15 banner - uid=1_5 StaticText "" - uid=5_16 textbox "" required value="updated" - uid=5_17 button "Create/Update " expandable - uid=5_18 button "Field options" - uid=3_24 button "New field" expandable - uid=3_25 StaticText "Unique constraints and indexes (" - uid=4_13 StaticText "0" - uid=3_27 StaticText ")" - uid=3_28 button "New index" - uid=3_29 contentinfo - uid=3_30 button "Close" - uid=3_31 button "Create" - uid=3_32 button "Save options" expandable diff --git a/scripts/test_pb_sdk.js b/scripts/test_pb_sdk.js deleted file mode 100644 index 99f36e1..0000000 --- a/scripts/test_pb_sdk.js +++ /dev/null @@ -1,26 +0,0 @@ -const { default: PocketBase } = require('pocketbase'); -const fs = require('fs'); -const envFile = fs.readFileSync('.env.local', 'utf8'); -const token = envFile.match(/PB_TOKEN=(.+)/)[1].trim(); -const url = envFile.match(/PB_URL=(.+)/)[1].trim(); -const pb = new PocketBase(url); -pb.authStore.save(token); - -(async () => { - try { - const col = await pb.collections.create({ - name: 'JEI_test_sdk', - type: 'base', - fields: [ - { name: 'foo', type: 'select', required: false, options: { maxSelect: 1, values: ['a','b'] } } - ], - listRule: '', viewRule: '', createRule: '', updateRule: '', deleteRule: '' - }); - console.log('Created id=' + col.id + ' fields=' + col.fields.length); - await pb.collections.delete(col.id); - console.log('deleted'); - } catch (e) { - console.log('ERR: ' + e.message); - if (e.data) console.log('data: ' + JSON.stringify(e.data)); - } -})(); diff --git a/scripts/test_pb_select.js b/scripts/test_pb_select.js deleted file mode 100644 index 89d1a38..0000000 --- a/scripts/test_pb_select.js +++ /dev/null @@ -1,60 +0,0 @@ -// Test creating a select field with various formats to find what works -const { default: PocketBase } = require('pocketbase'); -const fs = require('fs'); - -const envFile = fs.readFileSync('.env.local', 'utf8'); -const token = envFile.match(/PB_TOKEN=(.+)/)[1].trim(); -const url = envFile.match(/PB_URL=(.+)/)[1].trim(); -const pb = new PocketBase(url); -pb.authStore.save(token); - -async function test(name, fields) { - try { - const col = await pb.collections.create({ - name: 'JEI_t_' + Date.now() + '_' + name, - type: 'base', - fields, - listRule: '', viewRule: '', createRule: '', updateRule: '', deleteRule: '' - }); - console.log(`OK [${name}]: id=${col.id} fields=${col.fields.length}`); - // Get it back - const got = await pb.collections.getOne(col.id); - console.log(` fields detail:`); - got.fields.forEach(f => console.log(` - ${f.name} (${f.type}) opts=${JSON.stringify(f.options || {})}`)); - // Cleanup - await pb.collections.delete(col.id); - return true; - } catch (e) { - console.log(`FAIL [${name}]: ${e.message}`); - if (e.data) console.log(' data: ' + JSON.stringify(e.data)); - return false; - } -} - -(async () => { - // Test 1: select with explicit id + system:false + values only - await test('sel_basic', [ - { id: 'select_aaa', name: 'foo', type: 'select', system: false, required: true, options: { maxSelect: 1, values: ['a','b'] } } - ]); - - // Test 2: select with maxSelect as string - await test('sel_str', [ - { name: 'foo', type: 'select', options: { values: ['a','b'] } } - ]); - - // Test 3: multi-select - await test('sel_multi', [ - { name: 'foo', type: 'select', options: { maxSelect: 2, values: ['a','b','c'] } } - ]); - - // Test 4: text first then select - await test('text_then_sel', [ - { name: 'bar', type: 'text' }, - { name: 'foo', type: 'select', options: { maxSelect: 1, values: ['a','b'] } } - ]); - - // Test 5: select with no maxSelect at all - await test('sel_no_max', [ - { name: 'foo', type: 'select', required: false, options: { values: ['a','b'] } } - ]); -})();