sku and item fix

This commit is contained in:
2026-08-11 21:55:04 +05:30
parent 15ddac178c
commit a7ba3d3e04
15 changed files with 557 additions and 96 deletions
+1 -1
View File
@@ -1 +1 @@
{"runId": 64, "templateId": 2, "warehouseId": 4, "assembleStageId": 118, "finishedItemId": 13, "rawItemId": 18, "packItemId": 14}
{"runId": 20, "templateId": 6, "warehouseId": 4, "assembleStageId": 30, "finishedItemId": 1, "rawItemId": 4, "packItemId": 3}
+52 -8
View File
@@ -35,6 +35,13 @@ SEED_BOTTLES = 100
UNIT_COST = 3.0
def production_reason(c, code):
for r in c.get("/reason-codes?context=Production&pageSize=50").body["items"]:
if r["code"] == code:
return r["reasonCodeId"]
sys.exit(f"FATAL: Production reason {code} not seeded.")
def ensure_warehouse(c):
for w in c.get(f"/warehouses?q={WAREHOUSE_CODE}&pageSize=50").body["items"]:
if w["code"] == WAREHOUSE_CODE:
@@ -72,9 +79,17 @@ def ensure_item(c, sku, name, content_qty, content_unit):
return created.body
def save_template(c, raw_item, finished_item, qty_per_batch, qty_unit):
def save_template(c, raw_item, finished_item, qty_per_batch, qty_unit, suffix):
"""
One template per section, never a shared one.
Each section starts a run and leaves it InProgress, and a template with a live run is
edit-locked (FR-MFG-06, 409 TEMPLATE_IN_USE) — so re-saving a single shared code would
fail from the second section onward for reasons that have nothing to do with content units.
"""
code = f"{TEMPLATE_CODE}-{suffix}"
payload = {
"code": TEMPLATE_CODE, "name": "Content-unit smoke line",
"code": code, "name": f"Content-unit smoke line ({suffix})",
"stages": [{
"key": "tmp-mix", "name": "Mix", "estimatedMinutes": 5, "posX": 0, "posY": 0,
"fieldDefs": [],
@@ -85,8 +100,8 @@ def save_template(c, raw_item, finished_item, qty_per_batch, qty_unit):
}],
"edges": [],
}
existing = next((t for t in c.get(f"/production-templates?q={TEMPLATE_CODE}").body["items"]
if t["code"] == TEMPLATE_CODE), None)
existing = next((t for t in c.get(f"/production-templates?q={code}").body["items"]
if t["code"] == code), None)
if existing:
head = c.get(f"/production-templates/{existing['templateId']}")
return c.put(f"/production-templates/{existing['templateId']}", payload, if_match=head.etag)
@@ -134,10 +149,39 @@ def consume_once(c, chk, tid, wh, target_qty, label, expected_packs):
chk.check(f"{label}: ledger qtyBase matches the consumption",
round(abs(float(rows[0]["qtyBase"])), 4), expected_packs)
# Cancel so the script is re-runnable. A run left InProgress edit-locks its template
# (FR-MFG-06), so the next execution could not re-save it and would fail with a 409 that
# says nothing about content units. Cancelling also returns the consumed stock (FR-MFG-17),
# which keeps the seeded on-hand stable across runs.
c.post(f"/production-runs/{run['runId']}/cancel",
{"reasonCodeId": production_reason(c, "PRD-CANCEL"), "note": "m4c cleanup"})
def cancel_stale_runs(c):
"""
Cancel any InProgress run this script left behind previously.
Self-healing rather than merely tidy: a live run edit-locks its template, so without this
a re-run (or an earlier interrupted run) fails at template save with 409 TEMPLATE_IN_USE —
a failure that looks like a content-unit bug and is not one.
"""
ours = {t["templateId"] for t in c.get(f"/production-templates?q={TEMPLATE_CODE}&pageSize=50").body["items"]
if t["code"].startswith(TEMPLATE_CODE)}
if not ours:
return
stale = [r for r in c.get("/production-runs?status=InProgress&pageSize=200").body["items"]
if r["templateId"] in ours]
for r in stale:
c.post(f"/production-runs/{r['runId']}/cancel",
{"reasonCodeId": production_reason(c, "PRD-CANCEL"), "note": "m4c stale cleanup"})
if stale:
print(f"cancelled {len(stale)} stale run(s) from a previous execution")
def main():
c, chk, args = bootstrap(__doc__)
print(f"API {args.api}")
cancel_stale_runs(c)
bottle = ensure_item(c, SKU, "Smoke syrup 500ml bottle", CONTENT_QTY, "Ml")
plain = ensure_item(c, PLAIN_SKU, "Smoke item with no content", None, None)
@@ -155,23 +199,23 @@ def main():
# ------------------------------------------------- whole packs out of content units
chk.section("1. A content quantity resolves to whole packs (2000 ml / 500 ml = 4)")
saved = save_template(c, bottle["itemId"], finished["itemId"], 2000, "Content")
saved = save_template(c, bottle["itemId"], finished["itemId"], 2000, "Content", "whole")
if saved_ok(chk, "save the Content template", saved):
consume_once(c, chk, saved.body["templateId"], wh, 1, "2000 ml", 4.0)
# --------------------------------------------------------------- fractional packs
chk.section("2. A content quantity below one pack consumes a FRACTION of one (300 ml = 0.6)")
saved = save_template(c, bottle["itemId"], finished["itemId"], 300, "Content")
saved = save_template(c, bottle["itemId"], finished["itemId"], 300, "Content", "frac")
if saved_ok(chk, "save the fractional Content template", saved):
consume_once(c, chk, saved.body["templateId"], wh, 1, "300 ml", 0.6)
# ------------------------------------------------------------------ the guard rail
chk.section("3. Content units are refused on an item that has no content size")
refused = save_template(c, plain["itemId"], finished["itemId"], 100, "Content")
refused = save_template(c, plain["itemId"], finished["itemId"], 100, "Content", "nocontent")
chk.status("Content input on a contentless item", refused, 422)
chk.section("4. The same item still works when the formula is written in packs")
saved = save_template(c, bottle["itemId"], finished["itemId"], 3, "Pack")
saved = save_template(c, bottle["itemId"], finished["itemId"], 3, "Pack", "pack")
if saved_ok(chk, "save the Pack template", saved):
consume_once(c, chk, saved.body["templateId"], wh, 1, "3 bottles", 3.0)