← back to Dw Five Field Step0

scripts/set-lec-min5.py

85 lines

#!/usr/bin/env python3
"""Set 5-yard minimum on all ACTIVE LEC- (Le Embossed Croc) products.

Steve 2026-07-02: "update all lec- patterns to 5 yard minimum".
Root cause found on LEC-6000: min=5 was set on the LEGACY no-s product key
(global.v_prod_quantity_order_min) which the live theme IGNORES, and variant
metafields were absent entirely — so the storefront never enforced the minimum.

Per the update-product-min skill (verified against theme 2026-06-30), BOTH
levels must be set:
  product : global.v_prods_quantity_order_min = 5   (with-s — the theme key)
            global.v_prods_quantity_order_units = 1 (only if missing)
  variant : non-Sample -> custom.v_prod_quantity_order_min = 5
                          custom.v_prods_quantity_order_units = 1
            Sample     -> 1 / 1 (skill hard rule)

Idempotent (upsert: PUT existing metafield id, else POST). REST rate-limited
at ~2/s via 0.55s sleeps. Audit JSON written next to this script's out/.
"""
import json, subprocess, time, urllib.request

TOKEN = os.environ.get('SHOPIFY_ADMIN_TOKEN','')
API = "https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-01"
OUT = "/Users/macstudio3/Projects/dw-five-field-step0/out/lec-min5-result.json"
MIN_YD, STEP = "5", "1"

def req(method, path, body=None):
    r = urllib.request.Request(API + path, method=method,
        headers={"X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"},
        data=json.dumps(body).encode() if body else None)
    time.sleep(0.55)
    with urllib.request.urlopen(r, timeout=30) as resp:
        return json.loads(resp.read())

def upsert(owner_path, namespace, key, value, existing):
    cur = next((m for m in existing if m["namespace"] == namespace and m["key"] == key), None)
    if cur:
        if str(cur["value"]) == value:
            return "ok"
        req("PUT", f"/metafields/{cur['id']}.json",
            {"metafield": {"id": cur["id"], "value": value, "type": "single_line_text_field"}})
        return f"updated {cur['value']}->{value}"
    req("POST", owner_path + "/metafields.json",
        {"metafield": {"namespace": namespace, "key": key, "value": value,
                       "type": "single_line_text_field"}})
    return f"created ={value}"

ids = subprocess.run(["psql", "-d", "dw_unified", "-tAc",
    "select replace(shopify_id,'gid://shopify/Product/','') from shopify_products "
    "where (sku ilike 'LEC%' or handle ilike 'lec-%') and status='ACTIVE' order by sku"],
    capture_output=True, text=True).stdout.split()

print(f"{len(ids)} active LEC products")
audit = []
for i, pid in enumerate(ids, 1):
    try:
        p = req("GET", f"/products/{pid}.json")["product"]
        pmf = req("GET", f"/products/{pid}/metafields.json?limit=250")["metafields"]
        entry = {"product_id": pid, "title": p["title"], "actions": {}}
        entry["actions"]["product.min"] = upsert(f"/products/{pid}", "global",
            "v_prods_quantity_order_min", MIN_YD, pmf)
        # units: only fill if missing — don't clobber an existing step
        if not any(m["namespace"] == "global" and m["key"] == "v_prods_quantity_order_units" for m in pmf):
            entry["actions"]["product.units"] = upsert(f"/products/{pid}", "global",
                "v_prods_quantity_order_units", STEP, pmf)
        for v in p["variants"]:
            vmf = req("GET", f"/variants/{v['id']}/metafields.json?limit=250")["metafields"]
            is_sample = "sample" in (v["title"] or "").lower() or (v["sku"] or "").lower().endswith("-sample")
            mn, st = ("1", "1") if is_sample else (MIN_YD, STEP)
            tag = "sample" if is_sample else "yard"
            entry["actions"][f"variant.{v['sku']}.min({tag})"] = upsert(
                f"/variants/{v['id']}", "custom", "v_prod_quantity_order_min", mn, vmf)
            entry["actions"][f"variant.{v['sku']}.units({tag})"] = upsert(
                f"/variants/{v['id']}", "custom", "v_prods_quantity_order_units", st, vmf)
        entry["status"] = "done"
    except Exception as e:
        entry = {"product_id": pid, "status": "error", "error": str(e)[:200]}
    audit.append(entry)
    print(f"[{i}/{len(ids)}] {pid} {entry.get('title','')[:50]} -> {entry['status']}")

with open(OUT, "w") as f:
    json.dump(audit, f, indent=1)
done = sum(1 for a in audit if a["status"] == "done")
print(f"\nDONE {done}/{len(audit)} — audit: {OUT}")