175 lines
5.8 KiB
Python
175 lines
5.8 KiB
Python
"""M5 货品视图只读查询。账号密码来自环境变量或 connection.local.json。"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from .config import settings
|
||
|
||
FIXTURE_DIR = Path(__file__).resolve().parents[1] / "fixtures"
|
||
M5_DIR = FIXTURE_DIR / "m5"
|
||
ORDER_PATH = FIXTURE_DIR / "orders" / "yyc-fujian-20260804.json"
|
||
FUJIAN_PATH = M5_DIR / "fujian.json"
|
||
LOCAL_CONN_PATH = M5_DIR / "connection.local.json"
|
||
|
||
|
||
def _load_json(path: Path) -> dict[str, Any]:
|
||
return json.loads(path.read_text(encoding="utf-8"))
|
||
|
||
|
||
def load_connection() -> dict[str, Any]:
|
||
local: dict[str, Any] = {}
|
||
if LOCAL_CONN_PATH.is_file():
|
||
local = _load_json(LOCAL_CONN_PATH)
|
||
host = os.getenv("M5_HOST") or local.get("host") or settings.M5_HOST
|
||
port = int(os.getenv("M5_PORT") or local.get("port") or settings.M5_PORT)
|
||
service = os.getenv("M5_SERVICE") or local.get("serviceName") or settings.M5_SERVICE
|
||
user = os.getenv("M5_USER") or local.get("user") or settings.M5_USER
|
||
password = os.getenv("M5_PASSWORD") or local.get("password") or settings.M5_PASSWORD
|
||
schema = os.getenv("M5_SCHEMA") or local.get("schema") or settings.M5_SCHEMA
|
||
return {
|
||
"host": host,
|
||
"port": port,
|
||
"serviceName": service,
|
||
"user": user,
|
||
"password": password,
|
||
"schema": schema,
|
||
}
|
||
|
||
|
||
def recorded_source_rows(goods_id: str) -> list[dict[str, Any]] | None:
|
||
"""离线夹具:哈药牌这条 SQL 的 P1 结果,不连 M5。"""
|
||
params = lookup_params()
|
||
if str(goods_id) != params["expectedGoodsId"]:
|
||
return None
|
||
return [
|
||
{
|
||
"ENTRYID": params["entryId"],
|
||
"PRIORITY": 1,
|
||
"GOODSTYPE": params["spec"],
|
||
"FACTORYNAME": params["manufacturer"],
|
||
}
|
||
]
|
||
|
||
|
||
def evaluate_priority_source(
|
||
rows: list[dict[str, Any]],
|
||
*,
|
||
entry_id: int,
|
||
spec: str,
|
||
manufacturer: str,
|
||
) -> dict[str, Any]:
|
||
"""PRIORITY=1 且货主/规格/厂家对上,才算货源齐全。"""
|
||
priority_rows = [row for row in rows if str(row.get("PRIORITY") or "") == "1"]
|
||
hit = next(
|
||
(
|
||
row
|
||
for row in priority_rows
|
||
if str(row.get("ENTRYID") or "") == str(entry_id)
|
||
and str(row.get("GOODSTYPE") or "").strip() == spec.strip()
|
||
and str(row.get("FACTORYNAME") or "").strip() == manufacturer.strip()
|
||
),
|
||
None,
|
||
)
|
||
return {
|
||
"source_add_needed": hit is None,
|
||
"hit": hit,
|
||
"priorityRows": priority_rows,
|
||
}
|
||
|
||
|
||
def lookup_params(order: dict[str, Any] | None = None, fujian: dict[str, Any] | None = None) -> dict[str, Any]:
|
||
order = order or _load_json(ORDER_PATH)
|
||
fujian = fujian or _load_json(FUJIAN_PATH)
|
||
row = order["row"]
|
||
return {
|
||
"goodsNameLike": f"%{row['goodsNameLike']}%",
|
||
"approvalNo": row["approvalNo"],
|
||
"spec": row["spec"],
|
||
"manufacturer": row["manufacturer"],
|
||
"entryId": int(order["m5"]["entryId"] or fujian["entryId"]),
|
||
"expectedGoodsId": str(row["expectedGoodsId"]),
|
||
"sql": fujian["sql"],
|
||
"sourceSql": fujian["sourceSql"],
|
||
"expectedSourceAddNeeded": bool(row.get("expectedSourceAddNeeded", True)),
|
||
}
|
||
|
||
|
||
def connect(conn_cfg: dict[str, Any] | None = None):
|
||
cfg = conn_cfg or load_connection()
|
||
missing = [key for key in ("user", "password") if not cfg.get(key)]
|
||
if missing:
|
||
raise RuntimeError(
|
||
"M5 连接缺少 " + ", ".join(missing) + "。设 M5_USER/M5_PASSWORD,或写 fixtures/m5/connection.local.json"
|
||
)
|
||
import oracledb
|
||
|
||
dsn = f"{cfg['host']}:{cfg['port']}/{cfg['serviceName']}"
|
||
return oracledb.connect(user=cfg["user"], password=cfg["password"], dsn=dsn)
|
||
|
||
|
||
def lookup_one(conn_cfg: dict[str, Any] | None = None) -> dict[str, Any]:
|
||
params = lookup_params()
|
||
conn = connect(conn_cfg)
|
||
try:
|
||
cur = conn.cursor()
|
||
cur.execute(
|
||
params["sql"],
|
||
goodsNameLike=params["goodsNameLike"],
|
||
approvalNo=params["approvalNo"],
|
||
spec=params["spec"],
|
||
manufacturer=params["manufacturer"],
|
||
entryId=params["entryId"],
|
||
)
|
||
rows = _rows_from_cursor(cur)
|
||
finally:
|
||
conn.close()
|
||
|
||
goods_ids = [str(row.get("GOODSID") or "") for row in rows]
|
||
return {
|
||
"ok": len(rows) == 1 and goods_ids[0] == params["expectedGoodsId"],
|
||
"expectedGoodsId": params["expectedGoodsId"],
|
||
"hits": len(rows),
|
||
"goodsIds": goods_ids,
|
||
"rows": rows,
|
||
}
|
||
|
||
|
||
def _rows_from_cursor(cur) -> list[dict[str, Any]]:
|
||
columns = [col[0] for col in cur.description]
|
||
return [dict(zip(columns, row)) for row in cur.fetchall()]
|
||
|
||
|
||
def query_priority_source(goods_id: str, conn_cfg: dict[str, Any] | None = None) -> list[dict[str, Any]]:
|
||
fujian = _load_json(FUJIAN_PATH)
|
||
conn = connect(conn_cfg)
|
||
try:
|
||
cur = conn.cursor()
|
||
cur.execute(fujian["sourceSql"], goodsId=int(goods_id))
|
||
return _rows_from_cursor(cur)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
def check_priority_source(conn_cfg: dict[str, Any] | None = None) -> dict[str, Any]:
|
||
params = lookup_params()
|
||
rows = query_priority_source(params["expectedGoodsId"], conn_cfg)
|
||
judged = evaluate_priority_source(
|
||
rows,
|
||
entry_id=params["entryId"],
|
||
spec=params["spec"],
|
||
manufacturer=params["manufacturer"],
|
||
)
|
||
judged["ok"] = judged["source_add_needed"] == params["expectedSourceAddNeeded"]
|
||
judged["expectedSourceAddNeeded"] = params["expectedSourceAddNeeded"]
|
||
judged["rows"] = rows
|
||
return judged
|
||
|
||
|
||
if __name__ == "__main__":
|
||
match = lookup_one()
|
||
source = check_priority_source() if match.get("ok") else {"skipped": True, "reason": "对码未命中,不查货源"}
|
||
print(json.dumps({"match": match, "source": source}, ensure_ascii=False, default=str, indent=2))
|