run_uat_t8_fact_seed.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. #!/usr/bin/env python3
  2. """Seed tenant-scoped UAT T8 facts into the MDP staging layer.
  3. The script intentionally stops at ``mdp_stg_t8_*``. S5/S6/S7 module rebuilds
  4. must perform STG -> STD -> DWD -> KPI conversion afterwards.
  5. """
  6. from __future__ import annotations
  7. import argparse
  8. import json
  9. from datetime import date, datetime, time, timedelta
  10. from typing import Any
  11. from apply_sql_file import connect
  12. TENANTS = (
  13. ("A", 838257186181189, 10, 3, 8, 0.90),
  14. ("B", 838257212780613, 6, 2, 5, 0.75),
  15. ("DEMO", 838257237606469, 30, 5, 16, 0.83),
  16. )
  17. SOURCE_SYSTEM = "UAT_GENERATOR"
  18. ZTID = "pbxfxp"
  19. TABLES = {
  20. "tz_head": "mdp_stg_t8_kc_tz_head",
  21. "tz_list": "mdp_stg_t8_kc_tz_list",
  22. "dd_head": "mdp_stg_t8_kc_dd_head",
  23. "dd_list": "mdp_stg_t8_kc_dd_list",
  24. "zj_list": "mdp_stg_t8_kc_zj_list",
  25. "pelist": "mdp_stg_t8_sys_pelist",
  26. "bg_head": "mdp_stg_t8_cj_bg_head_rep",
  27. "cllist": "mdp_stg_t8_kc_dd_list_cllist",
  28. }
  29. def previous_month_window(today: date) -> tuple[date, date]:
  30. month_start = today.replace(day=1)
  31. end = month_start - timedelta(days=1)
  32. return end.replace(day=1), end
  33. def dt(day: date, hour: int = 8) -> str:
  34. return datetime.combine(day, time(hour=hour)).isoformat(timespec="seconds")
  35. def stage_row(
  36. rows: list[tuple[str, int, str, str, dict[str, Any]]],
  37. table_key: str,
  38. tenant_id: int,
  39. row_id: str,
  40. raw: dict[str, Any],
  41. ) -> None:
  42. rows.append((TABLES[table_key], tenant_id, row_id, row_id, raw))
  43. def build_rows(today: date) -> list[tuple[str, int, str, str, dict[str, Any]]]:
  44. month_start, month_end = previous_month_window(today)
  45. rows: list[tuple[str, int, str, str, dict[str, Any]]] = []
  46. for code, tenant_id, order_count, warehouse_staff, production_staff, on_time_ratio in TENANTS:
  47. prefix = f"UAT-T8-{code}"
  48. id_base = tenant_id % 1_000_000_000 * 100_000
  49. for index in range(warehouse_staff):
  50. rid = f"{prefix}-PE-WH-{index + 1:03d}"
  51. stage_row(rows, "pelist", tenant_id, rid, {
  52. "Id": id_base + 1_000 + index,
  53. "ztid": ZTID,
  54. "zzzt": "在职",
  55. "gw": "仓管",
  56. })
  57. for index in range(production_staff):
  58. rid = f"{prefix}-PE-PROD-{index + 1:03d}"
  59. stage_row(rows, "pelist", tenant_id, rid, {
  60. "Id": id_base + 2_000 + index,
  61. "ztid": ZTID,
  62. "zzzt": "在职",
  63. "gw": "生产",
  64. })
  65. for index in range(2):
  66. rid = f"{prefix}-PE-PLAN-{index + 1:03d}"
  67. stage_row(rows, "pelist", tenant_id, rid, {
  68. "Id": id_base + 3_000 + index,
  69. "ztid": ZTID,
  70. "zzzt": "在职",
  71. "gw": "计划",
  72. })
  73. for index in range(order_count):
  74. seq = index + 1
  75. product = f"{prefix}-FG-{seq % 5 + 1:02d}"
  76. material = f"{prefix}-MAT-{seq % 8 + 1:02d}"
  77. work_order = f"{prefix}-WO-{seq:03d}"
  78. task_no = f"{work_order}-TASK"
  79. sales_order = f"{prefix}-SO-{seq:03d}"
  80. sales_line = f"{sales_order}-L1"
  81. qty = 80 + (seq % 6) * 20
  82. # Spread the facts through the previous month so Demo has a visible story.
  83. offset = min((seq * 2) % max(month_end.day - 8, 1), month_end.day - 9)
  84. receipt_day = month_start + timedelta(days=offset)
  85. issue_day = receipt_day + timedelta(days=2)
  86. start_day = issue_day + timedelta(days=1)
  87. plan_finish = start_day + timedelta(days=4)
  88. is_on_time = (seq / order_count) <= on_time_ratio
  89. finish_day = plan_finish - timedelta(days=1) if is_on_time else plan_finish + timedelta(days=2)
  90. fqc_day = finish_day
  91. ship_day = finish_day + timedelta(days=1)
  92. prod_head_id = id_base + 10_000 + seq
  93. prod_line_id = id_base + 20_000 + seq
  94. sales_head_id = id_base + 30_000 + seq
  95. sales_line_id = id_base + 40_000 + seq
  96. stage_row(rows, "dd_head", tenant_id, f"{prefix}-DDH-PROD-{seq:03d}", {
  97. "Id": prod_head_id,
  98. "ztid": ZTID,
  99. "lbs": "生产任务",
  100. "zf": 0,
  101. "shyn": 1,
  102. "noid": work_order,
  103. })
  104. stage_row(rows, "dd_list", tenant_id, f"{prefix}-DDL-PROD-{seq:03d}", {
  105. "Id": prod_line_id,
  106. "idid": prod_head_id,
  107. "rwnoid": task_no,
  108. "code": product,
  109. "sl": qty,
  110. "slzx": qty if is_on_time else round(qty * 0.72, 2),
  111. "jhdate": plan_finish.isoformat(),
  112. "gdyn": 1 if is_on_time else 0,
  113. "gdtime": dt(finish_day, 17),
  114. "addtime": dt(start_day, 8),
  115. })
  116. stage_row(rows, "dd_head", tenant_id, f"{prefix}-DDH-SALES-{seq:03d}", {
  117. "Id": sales_head_id,
  118. "ztid": ZTID,
  119. "lbs": "销售订单",
  120. "zf": 0,
  121. "shyn": 1,
  122. "noid": sales_order,
  123. })
  124. stage_row(rows, "dd_list", tenant_id, f"{prefix}-DDL-SALES-{seq:03d}", {
  125. "Id": sales_line_id,
  126. "idid": sales_head_id,
  127. "rwnoid": sales_line,
  128. "code": product,
  129. "sl": qty,
  130. "slzx": qty,
  131. "jhdate": (ship_day + timedelta(days=1 if is_on_time else -1)).isoformat(),
  132. "gdyn": 1,
  133. "gdtime": dt(ship_day, 17),
  134. "addtime": dt(start_day, 8),
  135. })
  136. stage_row(rows, "cllist", tenant_id, f"{prefix}-CL-{seq:03d}", {
  137. "Id": id_base + 50_000 + seq,
  138. "idid": prod_head_id,
  139. })
  140. stage_row(rows, "bg_head", tenant_id, f"{prefix}-BG-{seq:03d}", {
  141. "Id": id_base + 60_000 + seq,
  142. "noid": work_order,
  143. "kgdate": dt(start_day, 8),
  144. "ztid": ZTID,
  145. })
  146. stage_row(rows, "zj_list", tenant_id, f"{prefix}-FQC-{seq:03d}", {
  147. "Id": id_base + 70_000 + seq,
  148. "ztid": ZTID,
  149. "lyid": sales_line_id,
  150. "zjyn": 1,
  151. "shdate": dt(fqc_day, 9),
  152. })
  153. quality_return_ratio = (
  154. 0.003 if code == "A"
  155. else 0.008 if code == "B"
  156. else (0.002, 0.006, 0.012)[(seq - 1) % 3]
  157. )
  158. return_day = min(ship_day + timedelta(days=2), month_end)
  159. transactions = (
  160. ("采购入库", material, "", receipt_day, qty * 2),
  161. ("生产领料", material, work_order, issue_day, qty * 1.6),
  162. ("生产入库", product, task_no, finish_day, qty if is_on_time else qty * 0.72),
  163. ("销售出库", product, sales_line, ship_day, qty),
  164. ("销售退货", product, sales_line, return_day, qty * quality_return_ratio),
  165. )
  166. for tx_index, (tx_type, item, linkage, tx_day, actual_qty) in enumerate(transactions, 1):
  167. tx_head_id = id_base + 100_000 + seq * 10 + tx_index
  168. tx_key = f"{prefix}-TZ-{seq:03d}-{tx_index}"
  169. stage_row(rows, "tz_head", tenant_id, f"{tx_key}-H", {
  170. "Id": tx_head_id,
  171. "ztid": ZTID,
  172. "lbs": tx_type,
  173. "lynoid": work_order if tx_type == "生产领料" else "",
  174. "hzyn": 0,
  175. "zfyn": 0,
  176. "shyn": 1,
  177. "shtime": dt(tx_day, 16),
  178. "date0": tx_day.isoformat(),
  179. })
  180. stage_row(rows, "tz_list", tenant_id, f"{tx_key}-L", {
  181. "Id": id_base + 200_000 + seq * 10 + tx_index,
  182. "idid": tx_head_id,
  183. "code": item,
  184. "lynoid": linkage,
  185. "slzx": round(actual_qty, 2),
  186. "sl": qty,
  187. "gdyn": 1 if actual_qty >= qty else 0,
  188. "gdtime": dt(tx_day, 16),
  189. "rwnoid": task_no,
  190. "jhdate": plan_finish.isoformat(),
  191. "addtime": dt(tx_day, 8),
  192. })
  193. return rows
  194. def apply(rows: list[tuple[str, int, str, str, dict[str, Any]]]) -> dict[str, int]:
  195. batch_id = f"UAT_T8_FACT_{datetime.now():%Y%m%d%H%M%S}"
  196. counts: dict[str, int] = {}
  197. conn = connect()
  198. try:
  199. conn.begin()
  200. with conn.cursor() as cursor:
  201. for table, tenant_id, row_id, biz_key, raw in rows:
  202. cursor.execute(
  203. f"""
  204. INSERT INTO {table}
  205. (tenant_id,source_system,source_table,source_row_id,source_biz_key,
  206. raw_data,sync_batch_id,sync_time,process_status,process_message)
  207. VALUES
  208. (%s,%s,%s,%s,%s,%s,%s,NOW(),'PENDING',NULL)
  209. ON DUPLICATE KEY UPDATE
  210. tenant_id=VALUES(tenant_id),
  211. source_row_id=VALUES(source_row_id),
  212. raw_data=VALUES(raw_data),
  213. sync_batch_id=VALUES(sync_batch_id),
  214. sync_time=NOW(),
  215. process_status='PENDING',
  216. process_message=NULL,
  217. update_time=NOW()
  218. """,
  219. (
  220. tenant_id,
  221. SOURCE_SYSTEM,
  222. table,
  223. row_id,
  224. biz_key,
  225. json.dumps(raw, ensure_ascii=False),
  226. batch_id,
  227. ),
  228. )
  229. counts[table] = counts.get(table, 0) + 1
  230. conn.commit()
  231. except Exception:
  232. conn.rollback()
  233. raise
  234. finally:
  235. conn.close()
  236. return counts
  237. def main() -> int:
  238. parser = argparse.ArgumentParser()
  239. parser.add_argument("--apply", action="store_true", help="write staging rows")
  240. args = parser.parse_args()
  241. rows = build_rows(date.today())
  242. summary: dict[str, Any] = {
  243. "sourceSystem": SOURCE_SYSTEM,
  244. "tenantCount": len(TENANTS),
  245. "rowCount": len(rows),
  246. "tables": {},
  247. "mode": "apply" if args.apply else "dry-run",
  248. }
  249. for table, *_ in rows:
  250. summary["tables"][table] = summary["tables"].get(table, 0) + 1
  251. if args.apply:
  252. summary["applied"] = apply(rows)
  253. print(json.dumps(summary, ensure_ascii=False, indent=2))
  254. return 0
  255. if __name__ == "__main__":
  256. raise SystemExit(main())