_prep_close_evidence.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #!/usr/bin/env python3
  2. """CLOSE 证据准备:S1 MDP 刷新 + S3 净需求样例(幂等,可重复跑)。"""
  3. import json
  4. import subprocess
  5. import sys
  6. import time
  7. import urllib.error
  8. import urllib.request
  9. from datetime import datetime, timedelta
  10. import pymysql
  11. BASE = "http://127.0.0.1:5005"
  12. TENANT = 797403760988229
  13. BILL_NO = "MPO482024102300001"
  14. DEMAND_MARKER = "CLOSE_UAT_S3_DEMAND"
  15. DEMAND_ID = 9106000999000001
  16. CONN = dict(
  17. host="123.60.180.165",
  18. port=3306,
  19. user="aidopremote",
  20. password="1234567890aiDOP#",
  21. database="aidopdev",
  22. charset="utf8mb4",
  23. connect_timeout=20,
  24. cursorclass=pymysql.cursors.DictCursor,
  25. )
  26. def http_json(method, path, body=None, token=None, timeout=300):
  27. headers = {"Content-Type": "application/json", "Accept": "application/json"}
  28. if token:
  29. headers["Authorization"] = f"Bearer {token}"
  30. data = None if body is None else json.dumps(body).encode("utf-8")
  31. req = urllib.request.Request(f"{BASE}{path}", data=data, headers=headers, method=method)
  32. try:
  33. with urllib.request.urlopen(req, timeout=timeout) as resp:
  34. raw = resp.read().decode("utf-8")
  35. try:
  36. return resp.status, json.loads(raw)
  37. except json.JSONDecodeError:
  38. return resp.status, {"raw": raw[:2000]}
  39. except urllib.error.HTTPError as e:
  40. raw = e.read().decode("utf-8", errors="replace")
  41. try:
  42. return e.code, json.loads(raw)
  43. except json.JSONDecodeError:
  44. return e.code, {"raw": raw[:2000]}
  45. def login():
  46. node_script = r"""
  47. const { sm2 } = require('sm-crypto-v2');
  48. const PK = '0484C7466D950E120E5ECE5DD85D0C90EAA85081A3A2BD7C57AE6DC822EFCCBD66620C67B0103FC8DD280E36C3B282977B722AAEC3C56518EDCEBAFB72C5A05312';
  49. console.log(JSON.stringify({ account: 'AIDOPDemo', password: sm2.doEncrypt('1234567890dop', PK, 1), tenantId: '797403760988229' }));
  50. """
  51. proc = subprocess.run(
  52. ["node", "-e", node_script],
  53. cwd=r"d:\Projects\Ai-DOP\SourceCode\ZZYDOP\Web",
  54. capture_output=True,
  55. text=True,
  56. timeout=30,
  57. )
  58. if proc.returncode != 0:
  59. raise RuntimeError(proc.stderr or proc.stdout)
  60. _, body = http_json("POST", "/api/sysAuth/login", json.loads(proc.stdout.strip()))
  61. token = (body.get("result") or {}).get("accessToken") or body.get("accessToken")
  62. if not token:
  63. raise RuntimeError(f"login failed: {body}")
  64. return token
  65. def unwrap(body):
  66. if isinstance(body, dict) and body.get("code") == 200 and "result" in body:
  67. return body["result"]
  68. if isinstance(body, dict) and body.get("ok") is True:
  69. return body
  70. return body
  71. def count_dwd(conn):
  72. with conn.cursor() as c:
  73. c.execute(
  74. "SELECT COUNT(*) AS c FROM dwd_ship_trans WHERE tenant_id=%s AND order_no=%s",
  75. (TENANT, BILL_NO),
  76. )
  77. return c.fetchone()["c"]
  78. def count_demand(conn):
  79. with conn.cursor() as c:
  80. c.execute(
  81. """
  82. SELECT COUNT(*) AS c FROM ic_demandschedule
  83. WHERE tenant_id=%s AND IFNULL(status,'')='P' AND IFNULL(tosechedqty,0)>0
  84. AND (IFNULL(ishistoryversion,'')='' OR ishistoryversion='N') AND IFNULL(IsDeleted,0)=0
  85. """,
  86. (TENANT,),
  87. )
  88. return c.fetchone()["c"]
  89. def find_seed_item(conn):
  90. with conn.cursor() as c:
  91. c.execute(
  92. """
  93. SELECT sp.number AS itemnum, sp.supplier_number, im.PurMfg
  94. FROM srm_purchase sp
  95. INNER JOIN ItemMaster im ON im.ItemNum = sp.number
  96. WHERE sp.tenant_id=%s AND IFNULL(sp.IsDeleted,0)=0
  97. AND IFNULL(sp.quota_rate,0)>0
  98. AND IFNULL(sp.supplier_number,'')<>''
  99. AND IFNULL(im.PurMfg,'')='P'
  100. ORDER BY sp.number
  101. LIMIT 1
  102. """,
  103. (TENANT,),
  104. )
  105. return c.fetchone()
  106. def ensure_demand_seed(conn):
  107. existing = count_demand(conn)
  108. if existing > 0:
  109. return {"action": "skip", "reason": f"demand_published={existing}"}
  110. item = find_seed_item(conn)
  111. if not item:
  112. return {"action": "failed", "reason": "no item with source+PO open qty"}
  113. need_date = (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d %H:%M:%S")
  114. qty = 10.0
  115. with conn.cursor() as c:
  116. c.execute("SELECT Id FROM ic_demandschedule WHERE Id=%s", (DEMAND_ID,))
  117. if c.fetchone():
  118. c.execute(
  119. """
  120. UPDATE ic_demandschedule
  121. SET itemnum=%s, status='P', toschedqty=%s, sechedqty=%s,
  122. requestdate=%s, arrivaldate=%s, ishistoryversion='N',
  123. IsDeleted=0, remarks=%s, update_time=NOW()
  124. WHERE Id=%s AND tenant_id=%s
  125. """,
  126. (item["itemnum"], qty, qty, need_date, need_date, DEMAND_MARKER, DEMAND_ID, TENANT),
  127. )
  128. else:
  129. c.execute(
  130. """
  131. INSERT INTO ic_demandschedule
  132. (Id, itemnum, status, toschedqty, sechedqty, requestdate, arrivaldate,
  133. ishistoryversion, remarks, tenant_id, IsDeleted, create_time, update_time)
  134. VALUES (%s,%s,'P',%s,%s,%s,%s,'N',%s,%s,0,NOW(),NOW())
  135. """,
  136. (DEMAND_ID, item["itemnum"], qty, qty, need_date, need_date, DEMAND_MARKER, TENANT),
  137. )
  138. conn.commit()
  139. return {"action": "seeded", "itemnum": item["itemnum"], "qty": qty, "demand_id": DEMAND_ID}
  140. def refresh_s1_mdp(token):
  141. st, body = http_json("POST", "/api/AidopKanban/s1-mdp/refresh", None, token, timeout=600)
  142. return st, unwrap(body)
  143. def wait_dwd(conn, target=2, attempts=12, sleep_s=5):
  144. for i in range(attempts):
  145. c = count_dwd(conn)
  146. if c >= target:
  147. return c, i + 1
  148. time.sleep(sleep_s)
  149. return count_dwd(conn), attempts
  150. def main():
  151. report = {"tenant_id": str(TENANT), "bill_no": BILL_NO, "steps": []}
  152. token = login()
  153. report["login"] = "ok"
  154. with pymysql.connect(**CONN) as conn:
  155. report["before"] = {
  156. "dwd_ship_rows": count_dwd(conn),
  157. "demand_published": count_demand(conn),
  158. }
  159. st, mdp = refresh_s1_mdp(token)
  160. report["steps"].append({"step": "s1_mdp_refresh", "http": st, "body": mdp})
  161. dwd_after_mdp, tries = wait_dwd(conn, target=2)
  162. report["steps"].append({"step": "wait_dwd_ship", "rows": dwd_after_mdp, "attempts": tries})
  163. demand_result = ensure_demand_seed(conn)
  164. report["steps"].append({"step": "seed_s3_demand", **demand_result})
  165. report["after"] = {
  166. "dwd_ship_rows": count_dwd(conn),
  167. "demand_published": count_demand(conn),
  168. }
  169. report["checks"] = [
  170. {"name": "s1_mdp_http", "ok": report["steps"][0]["http"] == 200},
  171. {"name": "dwd_ship_main_ge2", "ok": report["after"]["dwd_ship_rows"] >= 2},
  172. {"name": "demand_published_gt0", "ok": report["after"]["demand_published"] > 0},
  173. ]
  174. report["passed"] = all(c["ok"] for c in report["checks"])
  175. out = r"d:\Projects\Ai-DOP\SourceCode\ZZYDOP\doc\_prep_close_evidence_result.json"
  176. with open(out, "w", encoding="utf-8") as f:
  177. json.dump(report, f, ensure_ascii=False, indent=2, default=str)
  178. print(json.dumps(report, ensure_ascii=False, indent=2, default=str))
  179. sys.exit(0 if report["passed"] else 7)
  180. if __name__ == "__main__":
  181. main()