run_uat_purchase_receipt_fact_seed.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. #!/usr/bin/env python3
  2. """Create tenant-scoped UAT purchase receipt business documents."""
  3. from __future__ import annotations
  4. import argparse
  5. import json
  6. from collections import defaultdict
  7. from datetime import datetime, timedelta
  8. from decimal import Decimal
  9. from typing import Any
  10. from apply_sql_file import connect
  11. TENANTS = {
  12. 838257186181189: ("A", Decimal("0.85")),
  13. 838257212780613: ("B", Decimal("0.70")),
  14. 838257237606469: ("DEMO", Decimal("0.92")),
  15. }
  16. PREFIX = "UATRCT-"
  17. def load_po_lines(cursor: Any) -> dict[int, list[dict[str, Any]]]:
  18. """Load PO lines plus the supplier / UM / domain the receipt must inherit.
  19. Domain / Supp / UM must be resolved here, not left NULL: the S5 purchase-receipt
  20. MDP transform joins ItemMaster / SuppMaster / PurOrdDetail on (Domain, ...), and a
  21. NULL domain makes every dimension lookup miss, so the page shows blank supplier /
  22. item name / spec / PO number. Domain is taken from the supplier master of the PO's
  23. own supplier — a purchase receipt belongs to the ERP domain its supplier is
  24. defined in — falling back to the PO line, then the PO header.
  25. """
  26. tenant_ids = ",".join(str(x) for x in TENANTS)
  27. cursor.execute(
  28. f"""
  29. SELECT d.tenant_id,d.PurOrd,d.Line,d.ItemNum,d.QtyOrded,d.Potype,d.Site,d.UM,
  30. (SELECT m.Supp FROM PurOrdMaster m
  31. WHERE m.tenant_id=d.tenant_id AND m.PurOrd=d.PurOrd LIMIT 1) AS Supp,
  32. COALESCE(
  33. (SELECT s.Domain FROM SuppMaster s
  34. WHERE s.tenant_id=d.tenant_id
  35. AND s.Supp=(SELECT m2.Supp FROM PurOrdMaster m2
  36. WHERE m2.tenant_id=d.tenant_id AND m2.PurOrd=d.PurOrd LIMIT 1)
  37. LIMIT 1),
  38. d.Domain,
  39. (SELECT m3.Domain FROM PurOrdMaster m3
  40. WHERE m3.tenant_id=d.tenant_id AND m3.PurOrd=d.PurOrd LIMIT 1)
  41. ) AS Domain
  42. FROM PurOrdDetail d
  43. WHERE d.tenant_id IN ({tenant_ids})
  44. AND IFNULL(d.PurOrd,'')<>'' AND IFNULL(d.ItemNum,'')<>'' AND IFNULL(d.QtyOrded,0)>0
  45. ORDER BY d.tenant_id,d.PurOrd,d.Line
  46. """
  47. )
  48. rows: dict[int, list[dict[str, Any]]] = defaultdict(list)
  49. for row in cursor.fetchall():
  50. rows[int(row["tenant_id"])].append(row)
  51. return rows
  52. def apply() -> dict[str, Any]:
  53. conn = connect()
  54. result: dict[str, Any] = {"masters": 0, "details": 0, "tenants": {}}
  55. try:
  56. conn.begin()
  57. with conn.cursor() as cursor:
  58. tenant_ids = tuple(TENANTS)
  59. placeholders = ",".join(["%s"] * len(tenant_ids))
  60. cursor.execute(
  61. f"DELETE FROM PurOrdRctDetail WHERE tenant_id IN ({placeholders}) AND Receiver LIKE %s",
  62. (*tenant_ids, f"{PREFIX}%"),
  63. )
  64. cursor.execute(
  65. f"DELETE FROM PurOrdRctMaster WHERE tenant_id IN ({placeholders}) AND Receiver LIKE %s",
  66. (*tenant_ids, f"{PREFIX}%"),
  67. )
  68. cursor.execute("SELECT GREATEST(IFNULL(MAX(RecID),0),0) AS max_id FROM PurOrdRctMaster")
  69. master_id = int(cursor.fetchone()["max_id"])
  70. cursor.execute("SELECT GREATEST(IFNULL(MAX(RecID),0),0) AS max_id FROM PurOrdRctDetail")
  71. detail_id = int(cursor.fetchone()["max_id"])
  72. lines_by_tenant = load_po_lines(cursor)
  73. now = datetime.now().replace(microsecond=0)
  74. for tenant_id, (code, ratio) in TENANTS.items():
  75. grouped: dict[str, list[dict[str, Any]]] = defaultdict(list)
  76. for line in lines_by_tenant.get(tenant_id, []):
  77. grouped[str(line["PurOrd"])].append(line)
  78. tenant_result = {"masters": 0, "details": 0}
  79. for order_index, (po_no, lines) in enumerate(grouped.items(), 1):
  80. master_id += 1
  81. receiver = f"{PREFIX}{code}-{order_index:04d}"
  82. receipt_date = now - timedelta(days=order_index % 3)
  83. first = lines[0]
  84. cursor.execute(
  85. """
  86. INSERT INTO PurOrdRctMaster
  87. (RecID,Domain,Supp,RctType,Receiver,RctDate,OrdNbr,PerformDate,Site,BusinessID,
  88. CreateUser,UpdateUser,CreateTime,UpdateTime,IsActive,IsConfirm,Print,
  89. IsChanged,TaxIn,IsExport,IsPlan,IsBackwash,Remark,tenant_id)
  90. VALUES
  91. (%s,%s,%s,'rc',%s,%s,%s,%s,%s,%s,
  92. 'UAT_GENERATOR','UAT_GENERATOR',NOW(),NOW(),1,1,0,
  93. 0,0,0,0,0,'UAT_GENERATOR:采购收货业务事实',%s)
  94. """,
  95. (
  96. master_id,
  97. first["Domain"],
  98. first["Supp"],
  99. receiver,
  100. receipt_date,
  101. po_no,
  102. receipt_date,
  103. first["Site"],
  104. master_id,
  105. tenant_id,
  106. ),
  107. )
  108. result["masters"] += 1
  109. tenant_result["masters"] += 1
  110. for line_index, line in enumerate(lines, 1):
  111. detail_id += 1
  112. ordered = Decimal(str(line["QtyOrded"]))
  113. received = (ordered * ratio).quantize(Decimal("0.00001"))
  114. cursor.execute(
  115. """
  116. INSERT INTO PurOrdRctDetail
  117. (RecID,Domain,Supp,RctType,Receiver,Line,ItemNum,UM,Dimension1,Dimension2,
  118. OrdNbr,OrdLine,RctDate,POCost,QtyOrded,RctQty,QtyReceived,Site,
  119. Location,Potype,BusinessID,CreateUser,UpdateUser,CreateTime,UpdateTime,
  120. IsActive,IsConfirm,IsChanged,TaxIn,UMConversion,IsClosed,IsRounding,
  121. BlanketLine,ProjectLine,RctLine,QtyReturn,PurOrdRctRecID,QcQty,QC,
  122. Remark,tenant_id)
  123. VALUES
  124. (%s,%s,%s,'rc',%s,%s,%s,%s,'','',%s,%s,%s,1,%s,%s,%s,%s,
  125. 'UAT',%s,%s,'UAT_GENERATOR','UAT_GENERATOR',NOW(),NOW(),
  126. 1,1,0,0,1,0,0,0,0,%s,0,%s,%s,'PASS',
  127. 'UAT_GENERATOR:采购收货明细',%s)
  128. """,
  129. (
  130. detail_id,
  131. line["Domain"],
  132. line["Supp"],
  133. receiver,
  134. line_index,
  135. line["ItemNum"],
  136. line["UM"],
  137. po_no,
  138. int(line["Line"] or line_index),
  139. receipt_date,
  140. ordered,
  141. received,
  142. received,
  143. line["Site"],
  144. line["Potype"] or "PO",
  145. detail_id,
  146. line_index,
  147. master_id,
  148. received,
  149. tenant_id,
  150. ),
  151. )
  152. result["details"] += 1
  153. tenant_result["details"] += 1
  154. result["tenants"][code] = tenant_result
  155. conn.commit()
  156. except Exception:
  157. conn.rollback()
  158. raise
  159. finally:
  160. conn.close()
  161. return result
  162. def main() -> int:
  163. parser = argparse.ArgumentParser()
  164. parser.add_argument("--apply", action="store_true")
  165. args = parser.parse_args()
  166. result: dict[str, Any] = {"mode": "dry-run", "tenants": TENANTS}
  167. if args.apply:
  168. result = {"mode": "apply", **apply()}
  169. print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
  170. return 0
  171. if __name__ == "__main__":
  172. raise SystemExit(main())