1. 新增SSE看板广播机制,业务操作后主动推送刷新事件 2. 新增产线工序横道图与工位状态展示面板 3. 移除dashboard无用的three.js依赖 4. 重构WMS客户端布局与WMS前端资源哈希 5. 新增工位终端时钟图标与大屏自适应布局 6. 新增系统截图脚本与说明书生成工具 7. 修复多处代码细节与空值处理逻辑
538 lines
26 KiB
Python
538 lines
26 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""演示数据落库脚本(北京电力 WMS + MES)
|
||
|
||
用途:为交付甲方的《系统页面截图说明书》准备成体系的业务数据。
|
||
数据真实落库,可长期保留。脚本可重复执行(幂等:已存在则跳过)。
|
||
|
||
用法:
|
||
python demo_seed.py # 全部执行
|
||
python demo_seed.py inspect # 只探测库存/基线,不写库
|
||
"""
|
||
import json
|
||
import sys
|
||
import time
|
||
import urllib.request
|
||
import urllib.error
|
||
|
||
sys.stdout.reconfigure(encoding="utf-8")
|
||
|
||
WMS = "http://127.0.0.1:8890"
|
||
MES = "http://127.0.0.1:8888"
|
||
XAPI = "Hardman_2026"
|
||
|
||
OK, FAIL, SKIP = [], [], []
|
||
|
||
|
||
def req(base, method, path, body=None, token=None, xapi=None, timeout=20):
|
||
url = base + path
|
||
data = None
|
||
headers = {"Content-Type": "application/json"}
|
||
if token:
|
||
headers["Authorization"] = "Bearer " + token
|
||
if xapi:
|
||
headers["X-API-TOKEN"] = xapi
|
||
if body is not None:
|
||
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
|
||
r = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||
try:
|
||
with urllib.request.urlopen(r, timeout=timeout) as resp:
|
||
return resp.status, resp.read().decode("utf-8", "replace")
|
||
except urllib.error.HTTPError as e:
|
||
return e.code, e.read().decode("utf-8", "replace")
|
||
except Exception as e:
|
||
return 0, str(e)
|
||
|
||
|
||
def j(txt):
|
||
try:
|
||
return json.loads(txt)
|
||
except Exception:
|
||
return {"_raw": txt}
|
||
|
||
|
||
def d_of(txt):
|
||
d = j(txt)
|
||
return d.get("data") if isinstance(d, dict) else None
|
||
|
||
|
||
def step(label, st, txt, ok_cond=None, note=""):
|
||
body = j(txt)
|
||
ok = body.get("code") == 0 if isinstance(body, dict) else False
|
||
if ok_cond is not None:
|
||
ok = ok_cond
|
||
tag = "OK " if ok else "FAIL"
|
||
msg = body.get("message", "") if isinstance(body, dict) else txt[:120]
|
||
print(" [%s] %-38s %s %s" % (tag, label, msg, note))
|
||
(OK if ok else FAIL).append(label)
|
||
return body
|
||
|
||
|
||
def skip(label, why):
|
||
print(" [SKIP] %-38s %s" % (label, why))
|
||
SKIP.append(label)
|
||
|
||
|
||
# ============================== 登录 ==============================
|
||
def login_wms():
|
||
st, txt = req(WMS, "POST", "/api/auth/login", {"username": "admin", "password": "123456"})
|
||
return (d_of(txt) or {}).get("token", "")
|
||
|
||
|
||
def login_mes():
|
||
st, txt = req(MES, "POST", "/api/v1/login", {"username": "admin", "password": "123456"})
|
||
return (d_of(txt) or {}).get("accessToken", "")
|
||
|
||
|
||
def today(offset=0):
|
||
return time.strftime("%Y-%m-%d", time.localtime(time.time() + offset * 86400))
|
||
|
||
|
||
# ============================== 探测 ==============================
|
||
def fetch_details(wms_tok, manage_mode=0, page_size=200):
|
||
"""库存明细(原始行,含 batch_no / sn_code)——出库必须用它,聚合接口不返批次/SN"""
|
||
url = "/api/stock/details?page=1&pageSize=%d" % page_size
|
||
if manage_mode:
|
||
url += "&manageMode=%d" % manage_mode
|
||
st, txt = req(WMS, "GET", url, token=wms_tok)
|
||
d = d_of(txt) or {}
|
||
return d.get("list", [])
|
||
|
||
|
||
def inspect(wms_tok, mes_tok):
|
||
print("\n===== 库存探测(明细)=====")
|
||
rows = fetch_details(wms_tok)
|
||
print(" 库存明细行数=%d" % len(rows))
|
||
if rows:
|
||
print(" 样例字段:%s" % sorted(rows[0].keys()))
|
||
for r in rows[:12]:
|
||
print(" %s mode=%s qty=%s zone=%s batch=%s sn=%s qual=%s" % (
|
||
r.get("materialCode"), r.get("manageMode"), r.get("quantity"),
|
||
r.get("zoneCode"), r.get("batchNo"), r.get("snCode"), r.get("qualityStatus")))
|
||
print("\n===== MES 基线 =====")
|
||
st, txt = req(MES, "GET", "/api/v1/product-types", token=mes_tok)
|
||
print(" 产品类型:", json.dumps(d_of(txt), ensure_ascii=False)[:200])
|
||
st, txt = req(MES, "GET", "/api/v1/stations", token=mes_tok)
|
||
sts = d_of(txt) or []
|
||
print(" 工位:%s" % [(s.get("stationNo"), s.get("name"), s.get("flowId")) for s in sts][:12])
|
||
|
||
|
||
# ============================== MES 造数 ==============================
|
||
def mes_seed(mes_tok, wms_tok):
|
||
print("\n===== MES 演示数据 =====")
|
||
st, txt = req(MES, "GET", "/api/v1/product-types", token=mes_tok)
|
||
ptypes = d_of(txt) or []
|
||
ptype = ptypes[0]
|
||
ptype_id = ptype["id"]
|
||
prod_code = ptype.get("code")
|
||
prod_name = ptype.get("name")
|
||
print(" 产品类型 id=%s code=%s name=%s" % (ptype_id, prod_code, prod_name))
|
||
|
||
# ---- BOM(用 WMS 真实物料)----
|
||
# 说明:前三项库内有现货(可走通备料出库全链路),第四项为长周期采购件
|
||
bom_items = [
|
||
{"materialCode": "ST-A100", "materialName": "标准结构件A100", "spec": "A100", "unit": "件", "manageMode": "1", "unitQty": 2, "lossRate": 5},
|
||
{"materialCode": "GJ-GANGGUAN-002", "materialName": "无缝钢管114x8", "spec": "114x8", "unit": "米", "manageMode": "1", "unitQty": 4, "lossRate": 3},
|
||
{"materialCode": "PR-S100", "materialName": "精密传感器S100", "spec": "S100", "unit": "个", "manageMode": "2", "unitQty": 1, "lossRate": 0},
|
||
{"materialCode": "JM-ZHOUCHENG-6020", "materialName": "精密轴承6020", "spec": "6020", "unit": "个", "manageMode": "2", "unitQty": 2, "lossRate": 0},
|
||
]
|
||
step("BOM 保存(4 项物料)", *req(MES, "PUT", "/api/v1/bom", {"productCode": prod_code, "items": bom_items}, token=mes_tok))
|
||
|
||
# ---- 第二个产品类型(仅 1 项物料,用于演示台账"领料完结"状态)----
|
||
prod2_code = "PROD-20260904-002"
|
||
st, txt = req(MES, "GET", "/api/v1/product-types", token=mes_tok)
|
||
hit = [p for p in (d_of(txt) or []) if p.get("code") == prod2_code]
|
||
if hit:
|
||
ptype2_id = hit[0]["id"]
|
||
print(" [SKIP] 产品类型2 已存在 id=%s" % ptype2_id)
|
||
else:
|
||
st, txt = req(MES, "POST", "/api/v1/product-types",
|
||
{"code": prod2_code, "name": "接驳体总成", "category": "总成类",
|
||
"remark": "单物料演示产品", "isActive": True}, token=mes_tok)
|
||
b = j(txt)
|
||
print(" [%s] 创建产品类型2 %s" % ("OK " if b.get("code") == 0 else "FAIL", b.get("message", "")))
|
||
# 创建接口不返回 id,需回查
|
||
st, txt = req(MES, "GET", "/api/v1/product-types", token=mes_tok)
|
||
hit = [p for p in (d_of(txt) or []) if p.get("code") == prod2_code]
|
||
ptype2_id = hit[0]["id"] if hit else None
|
||
if ptype2_id:
|
||
step("BOM2 保存(单物料 PR-S100)", *req(MES, "PUT", "/api/v1/bom", {
|
||
"productCode": prod2_code,
|
||
"items": [{"materialCode": "PR-S100", "materialName": "精密传感器S100", "spec": "S100",
|
||
"unit": "个", "manageMode": "2", "unitQty": 1, "lossRate": 0}]}, token=mes_tok))
|
||
|
||
# ---- 工单:覆盖各状态 ----
|
||
tag = time.strftime("%m%d")
|
||
orders = {}
|
||
|
||
def create_wo(no, qty, status_flow, p_id=None, p_code=None, p_name=None):
|
||
body = {"workOrderNo": no, "productTypeId": p_id or ptype_id, "productCode": p_code or prod_code,
|
||
"productName": p_name or prod_name, "quantity": qty, "status": "CREATED", "processSeq": ""}
|
||
st, txt = req(MES, "POST", "/api/v1/work-orders", body, token=mes_tok)
|
||
b = j(txt)
|
||
if b.get("code") != 0 and "已存在" not in b.get("message", ""):
|
||
step("工单创建 %s" % no, st, txt)
|
||
return None
|
||
st, txt = req(MES, "GET", "/api/v1/work-orders?orderNo=" + no, token=mes_tok)
|
||
rows = d_of(txt) or []
|
||
if not rows:
|
||
step("工单创建 %s" % no, st, txt)
|
||
return None
|
||
wo = rows[0]
|
||
print(" [OK ] 工单 %-30s id=%s qty=%s" % (no, wo.get("id"), qty))
|
||
OK.append("工单 " + no)
|
||
for s, reason in status_flow:
|
||
st2, txt2 = req(MES, "POST", "/api/v1/work-orders/status",
|
||
{"id": wo["id"], "status": s, "reason": reason}, token=mes_tok)
|
||
b2 = j(txt2)
|
||
if b2.get("code") == 0:
|
||
print(" -> %s" % s)
|
||
else:
|
||
print(" -> %s 失败: %s" % (s, b2.get("message")))
|
||
orders[no] = wo
|
||
return wo
|
||
|
||
# WO-01 执行中(主力演示)
|
||
create_wo("WO-2026%s-01" % tag, 30, [("RELEASED", ""), ("IN_PROGRESS", "按计划投产")])
|
||
# WO-02 已发布
|
||
create_wo("WO-2026%s-02" % tag, 20, [("RELEASED", "")])
|
||
# WO-03 暂停
|
||
create_wo("WO-2026%s-03" % tag, 15, [("RELEASED", ""), ("IN_PROGRESS", ""), ("PAUSED", "待料:轴承未到货")])
|
||
# WO-04 已完工
|
||
create_wo("WO-2026%s-04" % tag, 10, [("RELEASED", ""), ("IN_PROGRESS", ""), ("DONE", "按计划完工")])
|
||
# WO-05 草稿
|
||
create_wo("WO-2026%s-05" % tag, 25, [])
|
||
# WO-06 单物料小工单(用于演示台账"领料完结")
|
||
if ptype2_id:
|
||
create_wo("WO-2026%s-06" % tag, 2, [("RELEASED", ""), ("IN_PROGRESS", "小批量试产")],
|
||
p_id=ptype2_id, p_code=prod2_code, p_name="接驳体总成")
|
||
|
||
# ---- 日排产 ----
|
||
print(" --- 日排产 ---")
|
||
plans = [
|
||
("WO-2026%s-01" % tag, today(-1), 10),
|
||
("WO-2026%s-01" % tag, today(0), 12),
|
||
("WO-2026%s-01" % tag, today(1), 8),
|
||
("WO-2026%s-02" % tag, today(0), 10),
|
||
("WO-2026%s-02" % tag, today(2), 10),
|
||
("WO-2026%s-05" % tag, today(3), 15),
|
||
]
|
||
if ptype2_id:
|
||
plans.append(("WO-2026%s-06" % tag, today(4), 2))
|
||
for no, d, qty in plans:
|
||
st, txt = req(MES, "POST", "/api/v1/daily-plans",
|
||
{"orderNo": no, "planDate": d, "planQty": qty, "status": ""}, token=mes_tok)
|
||
b = j(txt)
|
||
okk = b.get("code") == 0 or "已存在" in b.get("message", "")
|
||
print(" [%s] 排产 %s %s qty=%s %s" % ("OK " if okk else "FAIL", no, d, qty, b.get("message", "")))
|
||
(OK if okk else FAIL).append("排产 %s %s" % (no, d))
|
||
|
||
# ---- 备料单(按日生成,幂等:已有该日记录则跳过)----
|
||
print(" --- 备料单生成 ---")
|
||
st, txt = req(MES, "GET", "/api/v1/material-requests", token=mes_tok)
|
||
exist_dates = {m.get("planDate") for m in (d_of(txt) or []) if m.get("planDate")}
|
||
for d in [today(-1), today(0), today(1)] + ([today(4)] if ptype2_id else []):
|
||
if d in exist_dates:
|
||
skip("备料单 %s" % d, "该日已生成过")
|
||
continue
|
||
st, txt = req(MES, "POST", "/api/v1/material-requests/generate", {"planDate": d}, token=mes_tok)
|
||
b = j(txt)
|
||
cnt = (b.get("data") or {}).get("count")
|
||
print(" [%s] 生成 %s 备料单 %s 条 %s" % ("OK " if b.get("code") == 0 else "FAIL", d, cnt, b.get("message", "")))
|
||
(OK if b.get("code") == 0 else FAIL).append("备料单 %s" % d)
|
||
|
||
# ---- 工件:进线 / 工序报工 / 拧紧 / 完工 ----
|
||
print(" --- 工件与工序 ---")
|
||
main_no = "WO-2026%s-01" % tag
|
||
sns = ["SN2026%s-0001" % tag, "SN2026%s-0002" % tag, "SN2026%s-0003" % tag,
|
||
"SN2026%s-0004" % tag, "SN2026%s-0005" % tag]
|
||
for i, sn in enumerate(sns):
|
||
st, txt = req(MES, "POST", "/api/v1/workpiece/online", {"sn": sn, "orderNo": main_no}, token=mes_tok)
|
||
b = j(txt)
|
||
print(" [%s] 进线 %s %s" % ("OK " if b.get("code") == 0 else "FAIL", sn, b.get("message", "")))
|
||
# 前 3 件走完工序 1~6,后 2 件只到工序 2(在制)
|
||
upto = 6 if i < 3 else 2
|
||
for p in range(1, upto + 1):
|
||
req(MES, "POST", "/api/v1/workpiece/process/report",
|
||
{"sn": sn, "processCode": p, "stationNo": p, "steps": []}, token=mes_tok)
|
||
print(" 工序 1~%d 已报工" % upto)
|
||
# 拧紧数据
|
||
for i, sn in enumerate(sns[:3]):
|
||
st, txt = req(MES, "POST", "/api/v1/torque/report",
|
||
{"sn": sn, "workOrder": main_no, "stationNo": "7", "screwNo": "S-%03d" % (i + 1),
|
||
"torque": 48.5 + i, "angle": 88.0 + i * 2, "result": "OK"}, token=mes_tok)
|
||
b = j(txt)
|
||
print(" [%s] 拧紧 %s torque=%.1f" % ("OK " if b.get("code") == 0 else "FAIL", sn, 48.5 + i))
|
||
# 完工 3 件
|
||
for sn in sns[:3]:
|
||
st, txt = req(MES, "POST", "/api/v1/workpiece/done",
|
||
{"sn": sn, "batchItems": [], "serialItems": []}, token=mes_tok)
|
||
b = j(txt)
|
||
print(" [%s] 完工 %s %s" % ("OK " if b.get("code") == 0 else "FAIL", sn, b.get("message", "")))
|
||
|
||
# ---- 扫码报工 ----
|
||
print(" --- 扫码报工 ---")
|
||
for i, sn in enumerate(sns[3:]):
|
||
st, txt = req(MES, "POST", "/api/v1/scan/report",
|
||
{"station": "装配工位%d" % (i + 3), "sn": sn, "orderNo": main_no,
|
||
"type": "PROCESS", "processCode": i + 1, "operator": "张装配"}, token=mes_tok)
|
||
b = j(txt)
|
||
print(" [%s] 报工 %s %s" % ("OK " if b.get("code") == 0 else "FAIL", sn, b.get("message", "")))
|
||
|
||
# ---- PLC 下发(握手:每次间隔 7s)----
|
||
print(" --- PLC 工位组合下发 ---")
|
||
for sn in sns[3:5]:
|
||
st, txt = req(MES, "POST", "/api/v1/plc/send-process",
|
||
{"orderNo": main_no, "sn": sn, "stationNo": 3, "processCombination": "1"}, token=mes_tok)
|
||
b = j(txt)
|
||
print(" [%s] 下发 %s %s" % ("OK " if b.get("code") == 0 else "FAIL", sn, b.get("message", "")))
|
||
time.sleep(7)
|
||
|
||
# ---- 巡检记录 ----
|
||
print(" --- 巡检终端 ---")
|
||
insp = [
|
||
{"category": "CHECKIN", "stationNo": "1", "shift": "早", "orderNo": main_no, "sn": "",
|
||
"result": "OK", "items": ["劳保穿戴齐全", "设备点检完成"], "photo": "", "remark": "到岗签到", "operator": "李巡检"},
|
||
{"category": "POINT", "stationNo": "7", "shift": "早", "orderNo": main_no, "sn": "",
|
||
"result": "OK", "items": ["扭矩枪校准", "气压正常"], "photo": "", "remark": "班前点检", "operator": "李巡检"},
|
||
{"category": "PROCESS", "stationNo": "3", "shift": "中", "orderNo": main_no, "sn": sns[3],
|
||
"result": "OK", "items": ["装配到位", "标识清晰"], "photo": "", "remark": "过程巡检", "operator": "王质检"},
|
||
{"category": "DONE", "stationNo": "1", "shift": "晚", "orderNo": main_no, "sn": "",
|
||
"result": "OK", "items": ["现场清理", "设备断电"], "photo": "", "remark": "收工巡检", "operator": "李巡检"},
|
||
]
|
||
for it in insp:
|
||
st, txt = req(MES, "POST", "/api/v1/inspections", it, token=mes_tok)
|
||
b = j(txt)
|
||
print(" [%s] 巡检 %s/%s %s" % ("OK " if b.get("code") == 0 else "FAIL", it["category"], it["stationNo"], b.get("message", "")))
|
||
|
||
return {"ptype_id": ptype_id, "prod_code": prod_code, "main_no": main_no, "sns": sns, "tag": tag}
|
||
|
||
|
||
# ============================== WMS 造数 ==============================
|
||
def wms_seed(wms_tok, mes_info):
|
||
print("\n===== WMS 演示数据 =====")
|
||
tag = mes_info["tag"]
|
||
main_no = mes_info["main_no"]
|
||
|
||
# ---- 库存探测:取可用批次/SN(明细接口,聚合行无批次/SN)----
|
||
allrows = fetch_details(wms_tok)
|
||
batches = [r for r in allrows if r.get("manageMode") == 1 and (r.get("quantity") or 0) > 0]
|
||
snrows = [r for r in allrows if r.get("manageMode") == 2 and (r.get("quantity") or 0) > 0]
|
||
print(" 可用批次行=%d SN行=%d" % (len(batches), len(snrows)))
|
||
for r in batches[:6]:
|
||
print(" 批次 %s %s qty=%s zone=%s" % (r.get("materialCode"), r.get("batchNo"), r.get("quantity"), r.get("zoneCode")))
|
||
for r in snrows[:6]:
|
||
print(" SN %s %s zone=%s" % (r.get("materialCode"), r.get("snCode"), r.get("zoneCode")))
|
||
|
||
# ---- 补充入库:精密件 SN(演示出库会持续消耗,先保证后续链路有货)----
|
||
print(" --- 补充入库 ---")
|
||
for code, want in [("PR-S100", 12), ("ST-A100", 2)]:
|
||
have = sum(1 for r in allrows if r.get("materialCode") == code and (r.get("quantity") or 0) > 0)
|
||
if have >= want:
|
||
skip("补入库 %s" % code, "现有 %d 已充足" % have)
|
||
continue
|
||
need = want - have
|
||
if code == "PR-S100":
|
||
body = {"inboundType": "purchase", "materialCode": code,
|
||
"snList": ["PR-S100-DEMO%03d" % i for i in range(have, have + need)],
|
||
"zoneCode": "Z02", "operator": "孙采购"}
|
||
else:
|
||
body = {"inboundType": "purchase", "materialCode": code,
|
||
"batchNo": "B-DEMO-%s" % time.strftime("%H%M%S"), "quantity": 30 * need,
|
||
"zoneCode": "Z01", "operator": "孙采购"}
|
||
st, txt = req(WMS, "POST", "/api/inbound/create", body, token=wms_tok)
|
||
step("补入库 %s x%d" % (code, need), st, txt)
|
||
allrows = fetch_details(wms_tok)
|
||
|
||
# ---- 台账同步(MES 工单 → WMS 工单备料台账)----
|
||
print(" --- 工单备料台账同步 ---")
|
||
st, txt = req(MES, "GET", "/api/v1/material-requests", token=mes_info["mes_tok"])
|
||
mrs = d_of(txt) or []
|
||
by_order = {}
|
||
for m in mrs:
|
||
by_order.setdefault(m.get("orderNo"), []).append(m)
|
||
# 幂等:已同步过的工单不再同步(sync 会把 status 重置为"进行中",会抹掉"领料完结")
|
||
st, txt = req(WMS, "GET", "/api/ledger/query?page=1&pageSize=200", token=wms_tok)
|
||
synced = {x.get("orderNo") for x in ((d_of(txt) or {}).get("list") or [])}
|
||
for no, items in by_order.items():
|
||
if no in synced:
|
||
skip("台账同步 %s" % no, "已同步过")
|
||
continue
|
||
payload = {
|
||
"orderNo": no,
|
||
"productCode": mes_info["prod_code"],
|
||
"operator": "系统同步",
|
||
"items": [{"materialCode": i.get("materialCode"), "materialName": i.get("materialName"),
|
||
"totalQty": int(i.get("reqQty") or 0)} for i in items],
|
||
}
|
||
st, txt = req(WMS, "POST", "/api/internal/ledger/sync", payload, xapi=XAPI)
|
||
b = j(txt)
|
||
print(" [%s] 台账同步 %s (%d 项) %s" % ("OK " if b.get("code") == 0 else "FAIL", no, len(items), b.get("message", "")))
|
||
(OK if b.get("code") == 0 else FAIL).append("台账同步 " + no)
|
||
|
||
# ---- 备料出库(受台账约束:累计出库 ≤ BOM 需求)----
|
||
print(" --- 备料出库(按台账)---")
|
||
st, txt = req(WMS, "GET", "/api/ledger/query?page=1&pageSize=200", token=wms_tok)
|
||
lg = (d_of(txt) or {}).get("list") or []
|
||
print(" 台账共 %d 项" % len(lg))
|
||
for item in lg:
|
||
code, total = item.get("materialCode"), item.get("totalQty") or 0
|
||
out = item.get("outQty") or 0
|
||
need = total - out
|
||
if need <= 0:
|
||
skip("备料出库 %s/%s" % (item.get("orderNo"), code), "已领料完结")
|
||
continue
|
||
cand = [r for r in allrows if r.get("materialCode") == code and (r.get("quantity") or 0) > 0]
|
||
if not cand:
|
||
skip("备料出库 %s/%s" % (item.get("orderNo"), code), "库内无可用库存")
|
||
continue
|
||
r0 = cand[0]
|
||
# 小单(需求≤6)一次领足 → 演示"领料完结";大单只领一部分 → 演示"部分领料"
|
||
want = need if need <= 6 else min(need, 3)
|
||
if r0.get("manageMode") == 2:
|
||
take = cand[:min(want, len(cand))]
|
||
body = {"orderNo": item.get("orderNo"), "materialCode": code,
|
||
"snList": [x.get("snCode") for x in take], "qty": len(take),
|
||
"operator": "刘库管", "targetDock": "DOCK03",
|
||
"zoneCode": r0.get("zoneCode"), "remark": "工单备料"}
|
||
else:
|
||
qty = min(want, r0.get("quantity") or 1)
|
||
body = {"orderNo": item.get("orderNo"), "materialCode": code, "batchNo": r0.get("batchNo"),
|
||
"qty": qty, "operator": "刘库管", "targetDock": "DOCK03",
|
||
"zoneCode": r0.get("zoneCode"), "remark": "工单备料"}
|
||
st, txt = req(WMS, "POST", "/api/outbound/create", body, token=wms_tok)
|
||
step("备料出库 %s/%s x%s" % (item.get("orderNo"), code, body["qty"]), st, txt)
|
||
# 出库后刷新库存视图,避免同一行被重复分配
|
||
allrows = fetch_details(wms_tok)
|
||
|
||
# ---- 通用出库(含装箱/合同号;用最新库存,避免与备料出库抢同一行)----
|
||
print(" --- 通用出库 ---")
|
||
allrows = fetch_details(wms_tok)
|
||
fresh_batch = [r for r in allrows if r.get("manageMode") == 1 and (r.get("quantity") or 0) > 0]
|
||
fresh_sn = [r for r in allrows if r.get("manageMode") == 2 and (r.get("quantity") or 0) > 0]
|
||
for i, (r, box, contract) in enumerate([(fresh_batch[0], "BOX-2026-001", "HT2026-0117"),
|
||
(fresh_batch[1] if len(fresh_batch) > 1 else None, "BOX-2026-002", "HT2026-0118")]):
|
||
if not r:
|
||
continue
|
||
st, txt = req(WMS, "POST", "/api/outbound/general", {
|
||
"materialCode": r.get("materialCode"), "batchNo": r.get("batchNo"),
|
||
"qty": min(3, r.get("quantity") or 1), "operator": "刘库管",
|
||
"zoneCode": r.get("zoneCode"), "boxNo": box, "contractNo": contract,
|
||
"remark": "销售发货",
|
||
}, token=wms_tok)
|
||
step("通用出库 %s/%s" % (r.get("materialCode"), box), st, txt)
|
||
|
||
# SN 出库(挑库内仍可用的 SN)
|
||
s0 = next((r for r in fresh_sn if r.get("materialCode") == "PR-S100"), fresh_sn[0] if fresh_sn else None)
|
||
if s0:
|
||
st, txt = req(WMS, "POST", "/api/outbound/general", {
|
||
"materialCode": s0.get("materialCode"), "snList": [s0.get("snCode")],
|
||
"operator": "刘库管", "zoneCode": s0.get("zoneCode"),
|
||
"boxNo": "BOX-2026-003", "contractNo": "HT2026-0119", "remark": "精密件领用",
|
||
}, token=wms_tok)
|
||
step("通用出库 SN %s" % s0.get("snCode"), st, txt)
|
||
|
||
# ---- 半成品入库 ----
|
||
print(" --- 半成品管理 ---")
|
||
semi_plan = [
|
||
("BP-2026%s-001" % tag, "BCP-JIEKOUTI", [1, 2, 3], "装配三", "Z02", main_no),
|
||
("BP-2026%s-002" % tag, "BCP-JIEKOUTI", [1, 2], "装配二", "Z02", main_no),
|
||
("BP-2026%s-003" % tag, "BCP-JIEKOUTI", [1, 2, 3, 4, 5, 6], "装配六", "Z02", main_no),
|
||
("BP-2026%s-004" % tag, "BCP-JIEKOUTI", [1], "装配一", "Z02", main_no),
|
||
]
|
||
for sn, mat, done, comp, zone, order in semi_plan:
|
||
st, txt = req(WMS, "POST", "/api/semi/inbound", {
|
||
"sn": sn, "materialCode": mat, "doneProcessCodes": done,
|
||
"completedProcess": comp, "zoneCode": zone, "orderNo": order, "operator": "陈产线",
|
||
}, token=wms_tok)
|
||
step("半成品入库 %s" % sn, st, txt)
|
||
|
||
# ---- 盘点 ----
|
||
print(" --- 库存盘点 ---")
|
||
st, txt = req(WMS, "GET", "/api/stocktake/query", token=wms_tok)
|
||
existing = (d_of(txt) or {}).get("list") or []
|
||
if existing:
|
||
skip("发起盘点", "已存在 %d 张盘点单" % len(existing))
|
||
else:
|
||
zone_pick = []
|
||
for r in allrows[:60]:
|
||
z = r.get("zoneCode")
|
||
if z and z not in zone_pick:
|
||
zone_pick.append(z)
|
||
if len(zone_pick) >= 2:
|
||
break
|
||
st, txt = req(WMS, "POST", "/api/stocktake/start", {"operator": "赵盘点", "zones": zone_pick}, token=wms_tok)
|
||
b = j(txt)
|
||
st_no = (b.get("data") or {}).get("stocktakeNo") if b.get("code") == 0 else None
|
||
print(" [%s] 发起盘点 zones=%s no=%s %s" % ("OK " if st_no else "FAIL", zone_pick, st_no, b.get("message", "")))
|
||
if st_no:
|
||
OK.append("发起盘点")
|
||
st, txt = req(WMS, "GET", "/api/stocktake/query?stocktakeNo=" + st_no + "&pageSize=200", token=wms_tok)
|
||
bb = j(txt)
|
||
items = (bb.get("data") or {}).get("list") or []
|
||
print(" 盘点项 %d 条" % len(items))
|
||
# 录入前 8 条实盘:前 6 条账实相符,后 2 条制造差异
|
||
for i, it in enumerate(items[:8]):
|
||
tid = it.get("target_id")
|
||
if not tid:
|
||
continue
|
||
sysq = it.get("book_qty") or 1
|
||
scan = sysq if i < 6 else max(0, sysq - 1)
|
||
req(WMS, "POST", "/api/stocktake/record", {
|
||
"stocktakeNo": st_no, "targetType": it.get("target_type") or "",
|
||
"targetId": tid, "scanQty": scan}, token=wms_tok)
|
||
print(" 已录入 8 条实盘(含 2 条差异)")
|
||
st, txt = req(WMS, "POST", "/api/stocktake/finish", {"stocktakeNo": st_no, "adjust": True}, token=wms_tok)
|
||
step("完成盘点 %s" % st_no, st, txt)
|
||
|
||
# ---- AGV 配送 ----
|
||
print(" --- AGV 配送 ---")
|
||
rows_payload = []
|
||
for i, m in enumerate((d_of(req(MES, "GET", "/api/v1/material-requests", token=mes_info["mes_tok"])[1]) or [])[:4]):
|
||
rows_payload.append({
|
||
"requestNo": m.get("requestNo"), "materialCode": m.get("materialCode"),
|
||
"materialName": m.get("materialName"), "unit": m.get("unit") or "件",
|
||
"qty": float(m.get("reqQty") or 1), "targetDock": "DOCK%02d" % (i + 1),
|
||
})
|
||
if rows_payload:
|
||
st, txt = req(WMS, "POST", "/api/agv/submit", {"rows": rows_payload, "sourceDock": "DOCK21"}, token=wms_tok)
|
||
step("AGV 下发 %d 行" % len(rows_payload), st, txt)
|
||
|
||
|
||
# ============================== main ==============================
|
||
def main():
|
||
mode = sys.argv[1] if len(sys.argv) > 1 else "all"
|
||
print("=" * 70)
|
||
print("演示数据落库 WMS=%s MES=%s" % (WMS, MES))
|
||
print("=" * 70)
|
||
|
||
wms_tok = login_wms()
|
||
mes_tok = login_mes()
|
||
if not wms_tok:
|
||
print("WMS 登录失败")
|
||
sys.exit(1)
|
||
if not mes_tok:
|
||
print("MES 登录失败")
|
||
sys.exit(1)
|
||
print("登录成功")
|
||
|
||
if mode == "inspect":
|
||
inspect(wms_tok, mes_tok)
|
||
return
|
||
|
||
mes_info = mes_seed(mes_tok, wms_tok)
|
||
mes_info["mes_tok"] = mes_tok
|
||
wms_seed(wms_tok, mes_info)
|
||
|
||
print("\n" + "=" * 70)
|
||
print("成功 %d / 失败 %d / 跳过 %d" % (len(OK), len(FAIL), len(SKIP)))
|
||
if FAIL:
|
||
print("失败项:")
|
||
for f in FAIL:
|
||
print(" - " + f)
|
||
print("=" * 70)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|