diff --git a/docs/reference/file-map.md b/docs/reference/file-map.md index 1ec523b..7202408 100644 --- a/docs/reference/file-map.md +++ b/docs/reference/file-map.md @@ -290,7 +290,7 @@ python tests/creator_dialogs_check.py # S1 creator - 0 natives, errors at fields Wave 8 adds these: ```bash -python tests/kitting_check.py # CR-009/CR-010 - statuses, owner, filter 21 checks +python tests/kitting_check.py # CR-009/010/012 - statuses, owner, delivery 26 checks python tests/kitting_notify_check.py # CR-011 - kitting mail, coalesced, gated 17 checks ``` diff --git a/html/wp-creation-app.js b/html/wp-creation-app.js index 81e0d71..dd13d57 100644 --- a/html/wp-creation-app.js +++ b/html/wp-creation-app.js @@ -1605,7 +1605,7 @@ function collectPackage(){ gateOverride:pkgGateOverride || undefined, assets:pkgAssets.filter(a=>a.tag||a.link||a.desc), materials:pkgMaterials.filter(m=>m.qty||m.desc), attachments:pkgAttach.filter(a=>a.doc), - kitStatus:gv('wp_kit_status'), kitOwner:gv('wp_kit_owner'), kitOwnerId:pkgKitOwnerId||'', kitDate:gv('wp_kit_date'), + kitStatus:gv('wp_kit_status'), kitOwner:gv('wp_kit_owner'), kitOwnerId:pkgKitOwnerId||'', delivBuilding:gv('wp_deliv_building'), delivFloor:gv('wp_deliv_floor'), delivSector:gv('wp_deliv_sector'), delivDetail:gv('wp_deliv_detail'), deliveryLoc:deliveryLocOf({delivBuilding:gv('wp_deliv_building'), delivFloor:gv('wp_deliv_floor'), delivSector:gv('wp_deliv_sector'), delivDetail:gv('wp_deliv_detail')}), kitDate:gv('wp_kit_date'), mimoTime:gv('wp_mimo_time'), mimoLoc:gv('wp_mimo_loc'), constraints:pkgConstraints.map(c=>({name:c.name,status:c.status,comment:c.comment})), qc:gv('wp_qc'), photo:gv('wp_photo'), hold:gv('wp_hold'), @@ -1759,6 +1759,7 @@ function renderPackage(pkg){ Warehouse Owner${cell(pkg.kitOwner)} Kitting Need Date${cell(pkg.kitDate)} MIMO Sch. Time / Location${cell(pkg.mimoTime)} ${pkg.mimoLoc?'· '+esc(pkg.mimoLoc):''} + Delivery Location${cell(pkg.deliveryLoc || deliveryLocOf(pkg))} `); let ct=``; @@ -2193,7 +2194,7 @@ function locLabelFull(path){ } async function loadLocations(){ - if(!activeProjectId){ wpLocations = []; wpLocationsLoaded = true; buildLocationPickers(); return; } + if(!activeProjectId){ wpLocations = []; wpLocationsLoaded = true; buildLocationPickers(); buildDeliveryPickers(); return; } try { const r = await fetch('/api/projects/' + encodeURIComponent(activeProjectId) + '/locations?include_inactive=true', @@ -2209,6 +2210,7 @@ async function loadLocations(){ } wpLocationsLoaded = true; buildLocationPickers(); + buildDeliveryPickers(); } /* Fill one level's
scheduled material-move date & time
+ +
+
+
+
diff --git a/tests/kitting_check.py b/tests/kitting_check.py index a3ffaab..2246751 100644 --- a/tests/kitting_check.py +++ b/tests/kitting_check.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Kitting: statuses, the owner, the filter — CR-009 (T8.1) + CR-010 (T8.2). +"""Kitting: statuses, owner, filter, delivery — CR-009/CR-010/CR-012 (T8.1/2/4). The statuses become an explicit five (Not Started / Picking / Staged / In Transit / Delivered) - fulfillment states, not free text. A value stored @@ -25,6 +25,7 @@ import cdp # noqa: E40 from browser_check import seed, start_server, chk, _PASS, _FAIL # noqa: E402 from sections_check import set_sop # noqa: E402 from stepper_check import dismiss_dialogs # noqa: E402 +from qa_gate_check import api # noqa: E402 KIT = ["Not Started", "Picking", "Staged", "In Transit", "Delivered"] @@ -186,6 +187,50 @@ def main(): page.eval("!!document.querySelector('[aria-label=%s]')" % json.dumps("Filter by warehouse owner"))) + # ── 5. CR-012: the delivery location (T8.4) ────────────────────────── + print(chr(10) + "5. CR-012: delivery on the shared lists") + code, b1 = api(base, "/api/projects/projA/locations", tok["root"], "POST", + {"level": "building", "name": "B-100"}) + code, f1 = api(base, "/api/projects/projA/locations", tok["root"], "POST", + {"level": "floor", "parent_id": b1["id"], "name": "Level 3"}) + code, s1 = api(base, "/api/projects/projA/locations", tok["root"], "POST", + {"level": "sector", "parent_id": f1["id"], "name": "Sector East"}) + page.goto(base + "/wp-creation-index.html?project=projA") + dismiss_dialogs(page) + wait_creator(page) + settle(1.8) + page.eval("window.alert=()=>{}; window.confirm=()=>false; window.prompt=()=>null;") + opts = json.loads(page.eval( + "JSON.stringify([...document.getElementById('wp_deliv_building').options].map(o=>o.value))")) + wp_opts = json.loads(page.eval( + "JSON.stringify([...document.getElementById('wp_building').options].map(o=>o.value))")) + chk("the delivery building select is fed by the SAME shared list as CR-004's", + opts == wp_opts and len(opts) > 1, ascii_((opts, wp_opts))) + page.eval("document.getElementById('wp_deliv_building').value=%s; onDeliveryLocChange('building')" + % json.dumps(b1["path"])) + settle(0.3) + page.eval("document.getElementById('wp_deliv_floor').value=%s; onDeliveryLocChange('floor')" + % json.dumps(f1["path"])) + settle(0.3) + page.eval("document.getElementById('wp_deliv_sector').value=%s; onDeliveryLocChange('sector')" + % json.dumps(s1["path"])) + page.eval("document.getElementById('wp_deliv_detail').value='Shark cage 7, conduit tree C'") + got = json.loads(page.eval("""JSON.stringify((() => { const p = collectPackage(); + return {b: p.delivBuilding, f: p.delivFloor, s: p.delivSector, + d: p.delivDetail, disp: p.deliveryLoc}; })())""")) + chk("the picked values persist as PATHS off the shared lists", + got["b"] == b1["path"] and got["f"] == f1["path"] and got["s"] == s1["path"], + ascii_(got)) + chk("...the detail field persists", got["d"] == "Shark cage 7, conduit tree C") + chk("...and the composed display carries labels AND the detail", + "B-100" in got["disp"] and "Shark cage 7" in got["disp"], ascii_(got["disp"])) + page.eval("document.getElementById('wp_subject').value='deliv print'") + page.eval("document.getElementById('wp_type').value='Conduit Install'") + doc = page.eval("(() => { renderPackage(collectPackage()); " + "return document.getElementById('pkg-doc').innerHTML; })()") + chk("the delivery location prints on the package output", + "Delivery Location" in doc and "Shark cage 7" in doc, ascii_(doc, 120)) + js_errors = [e for e in page.js_errors() if "beforeunload" not in e] chk("no JavaScript errors anywhere in this run", not js_errors, ascii_(js_errors[:2])) diff --git a/tests/kitting_notify_check.py b/tests/kitting_notify_check.py index 7fd33b3..77eb850 100644 --- a/tests/kitting_notify_check.py +++ b/tests/kitting_notify_check.py @@ -38,7 +38,9 @@ def ascii_(v, n=300): def save_wp(base, tok, kit_status, extra=None): data = {"constraints": [{"name": "Materials", "status": "cleared", "comment": ""}], "kitStatus": kit_status, "kitOwner": "Sue", "kitOwnerId": "user_sue", - "distributionIds": ["user_pat"], "mimoLoc": "Staging 04, dock B"} + "distributionIds": ["user_pat"], "mimoLoc": "Staging 04, dock B", + # CR-012 (T8.4): the composed delivery location wins over mimoLoc + "deliveryLoc": "B-100 / Level 3 / Sector East — Shark cage 7"} data.update(extra or {}) return api(base, "/api/wps", tok, "POST", { "id": "wpKN1", "project_id": "projA", "number": "KN-1", @@ -108,7 +110,8 @@ def main(): chk("the mail says old status, new status and who", "In Transit" in body and "Delivered" in body and "Root" in body, ascii_(body, 260)) - chk("...and the delivery location", "Staging 04, dock B" in body) + chk("...and the delivery location - CR-012's composed value, not the " + "mimoLoc fallback", "Shark cage 7" in body and "Staging 04" not in body) chk("...and a deep link to THAT package, not the app root", "/wp-creation-index.html?project=projA&wp=wpKN1" in body) chk("...in the house convention (greeting + automated-message footer)",
ConstraintStatusComment