seed_layout_items_all_modules.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. # -*- coding: utf-8 -*-
  2. """
  3. 批量初始化 AiDOP LayoutItem + HomeModule 种子(仅对"当前无布局"的模块写入)。
  4. 规则(Q2-C):
  5. - L1:统一为 home-grid 行,panel_zone=NULL
  6. - L2/L3:按 MetricName 命名模式分配 panel_zone
  7. * 含"周期" 或 "人效" → left
  8. * 含"满足率" 或 "周转" → right
  9. * 其他 → 奇数 sortNo=left,偶数 sortNo=right
  10. - is_enabled = 1 全部启用(Q2-C 不截断)
  11. - HomeModule:若该模块无记录,插入 layout_pattern='card_grid'
  12. 幂等:模块已有 LayoutItem 时整模块跳过(不覆盖用户手动配置)。
  13. 两个租户都会跑。
  14. """
  15. from __future__ import annotations
  16. import sys
  17. from datetime import datetime
  18. import pymysql
  19. sys.stdout.reconfigure(encoding='utf-8')
  20. CONN = dict(
  21. host='123.60.180.165', port=3306,
  22. user='aidopremote', password='1234567890aiDOP#',
  23. database='aidopdev', charset='utf8mb4', autocommit=False,
  24. )
  25. MODULES = ["S1", "S2", "S3", "S5", "S6", "S7", "S9"] # 跳过 S4(已配)、S8(独立)
  26. TENANTS = [1300000000001, 1300000000888]
  27. FACTORY_ID = 1
  28. def assign_zone(metric_name: str, sort_no: int) -> str:
  29. """按命名模式分配 panel_zone。"""
  30. nm = metric_name or ""
  31. if ("周期" in nm) or ("人效" in nm):
  32. return "left"
  33. if ("满足率" in nm) or ("周转" in nm):
  34. return "right"
  35. return "left" if (sort_no % 2 == 1) else "right"
  36. def next_id(cur) -> int:
  37. cur.execute("SELECT MAX(Id) AS mx FROM ado_smart_ops_layout_item")
  38. row = cur.fetchone()
  39. return (row['mx'] or 0) + 1
  40. def next_hm_id(cur) -> int:
  41. cur.execute("SELECT MAX(Id) AS mx FROM ado_smart_ops_home_module")
  42. row = cur.fetchone()
  43. return (row['mx'] or 0) + 1
  44. def process_tenant_module(cur, tenant_id: int, module_code: str) -> dict:
  45. """处理单个 (tenant, module)。返回统计。"""
  46. # 1. 检查是否已有布局
  47. cur.execute(
  48. "SELECT COUNT(*) AS c FROM ado_smart_ops_layout_item "
  49. "WHERE TenantId=%s AND ModuleCode=%s",
  50. (tenant_id, module_code),
  51. )
  52. existing = cur.fetchone()['c']
  53. if existing > 0:
  54. return {"skip": True, "existing": existing, "inserted": 0, "hm": False}
  55. # 2. 查出该模块所有启用的 KpiMaster
  56. cur.execute(
  57. "SELECT MetricCode, MetricName, MetricLevel, SortNo, Formula "
  58. "FROM ado_smart_ops_kpi_master "
  59. "WHERE TenantId=%s AND ModuleCode=%s AND IsEnabled=1 "
  60. "ORDER BY MetricLevel, SortNo",
  61. (tenant_id, module_code),
  62. )
  63. kpis = cur.fetchall()
  64. if not kpis:
  65. return {"skip": True, "existing": 0, "inserted": 0, "hm": False, "reason": "no kpi"}
  66. # 3. 生成 LayoutItem 行
  67. now = datetime.now()
  68. next_layout_id = next_id(cur)
  69. inserted = 0
  70. for k in kpis:
  71. lv = k['MetricLevel']
  72. code = k['MetricCode']
  73. name = k['MetricName']
  74. sort_no = k['SortNo'] or 0
  75. row_id = f"{module_code}-L{lv}-{code}"
  76. zone = None
  77. if lv >= 2:
  78. zone = assign_zone(name, sort_no)
  79. formula_text = (k['Formula'] or None)
  80. # 简化 formula:若超长,截断到 400 字符
  81. if formula_text and len(formula_text) > 400:
  82. formula_text = formula_text[:400]
  83. cur.execute(
  84. """
  85. INSERT INTO ado_smart_ops_layout_item
  86. (Id, TenantId, FactoryId, ModuleCode, RowId, MetricLevel, MetricCode,
  87. DisplayName, SortNo, ParentRowId, FormulaText, PanelZone, IsEnabled, UpdateTime)
  88. VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 1, %s)
  89. """,
  90. (
  91. next_layout_id, tenant_id, FACTORY_ID, module_code, row_id, lv, code,
  92. None, sort_no, None, formula_text, zone, now,
  93. ),
  94. )
  95. next_layout_id += 1
  96. inserted += 1
  97. # 4. 检查 HomeModule
  98. cur.execute(
  99. "SELECT COUNT(*) AS c FROM ado_smart_ops_home_module "
  100. "WHERE TenantId=%s AND FactoryId=%s AND ModuleCode=%s",
  101. (tenant_id, FACTORY_ID, module_code),
  102. )
  103. hm_exists = cur.fetchone()['c']
  104. hm_inserted = False
  105. if hm_exists == 0:
  106. hm_id = next_hm_id(cur)
  107. cur.execute(
  108. """
  109. INSERT INTO ado_smart_ops_home_module
  110. (Id, TenantId, FactoryId, ModuleCode, LayoutPattern, UpdateTime)
  111. VALUES (%s, %s, %s, %s, 'card_grid', %s)
  112. """,
  113. (hm_id, tenant_id, FACTORY_ID, module_code, now),
  114. )
  115. hm_inserted = True
  116. return {"skip": False, "existing": 0, "inserted": inserted, "hm": hm_inserted}
  117. def main(dry_run: bool = False) -> None:
  118. conn = pymysql.connect(**CONN)
  119. total_inserted = 0
  120. total_hm_inserted = 0
  121. try:
  122. with conn.cursor(pymysql.cursors.DictCursor) as cur:
  123. print("=" * 96)
  124. print(f"{'模式':<8}{'DRY_RUN' if dry_run else '实写入'} ({'不会提交' if dry_run else '会提交'})")
  125. print("=" * 96)
  126. for tid in TENANTS:
  127. print(f"\n>>> 租户 {tid}")
  128. for m in MODULES:
  129. r = process_tenant_module(cur, tid, m)
  130. if r['skip']:
  131. reason = r.get('reason', f"已有 {r['existing']} 条布局")
  132. print(f" {m:<4} 跳过({reason})")
  133. else:
  134. tag = "(+HomeModule)" if r['hm'] else ""
  135. print(f" {m:<4} 插入 {r['inserted']} 条 LayoutItem {tag}")
  136. total_inserted += r['inserted']
  137. if r['hm']:
  138. total_hm_inserted += 1
  139. if dry_run:
  140. print("\nDRY_RUN 模式:回滚事务")
  141. conn.rollback()
  142. else:
  143. print("\n提交事务…")
  144. conn.commit()
  145. print(f"\n合计:LayoutItem 新增 {total_inserted} 条,HomeModule 新增 {total_hm_inserted} 条。")
  146. except Exception as ex:
  147. conn.rollback()
  148. print(f"\n出错回滚:{ex}")
  149. raise
  150. finally:
  151. conn.close()
  152. if __name__ == "__main__":
  153. dry = "--dry-run" in sys.argv or "-n" in sys.argv
  154. main(dry_run=dry)