初始提交:QuickTeam 工单接口导入工具
用「WCF + 通用存储过程网关」接口替代 Selenium(D://Learning//Python//工单自动化)导入工单: 单 Sprint 12 个项目 175s 跑完(旧方案 25~35 分钟),失败可断点续跑且不重复建单。 - 分层:transport(HTTP/协议)/ api(存储过程语义)/ domain(纯规则)/ workflow(阶段编排)/ store(SQLite 台账)/ report / CLI - 幂等:台账驱动断点续跑;dry-run 与跳过行不污染进度;--rollback-on-fail 清理半成品 - 命令:plan / apply / report / status,真实写入需 --yes - tools:只读探针、存储过程源码导出(sp_helptext)、导入诊断 - docs:调研与设计文档(含联调踩到的 5 个坑)+ 6 个关键存储过程源码 实测:Sprint 2026 Sprint 10 的 12 个项目全部完成建单→导入→确认→推送 TFS, 工单 225134–225145,35 个功能模块,重跑 plan 全部 skip-pushed。69 个单测通过。
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"""诊断工具:定位 BatchImportFeature 空引用异常。
|
||||
|
||||
只操作一张临时草稿工单,结束时删除,不影响业务数据。
|
||||
|
||||
用法:
|
||||
python tools/diag_import.py # 建单→填单→读字段→试导入→删单
|
||||
python tools/diag_import.py --keep # 保留草稿(便于在页面上比对)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
from qtimport.api import CONN_QUICKTEAM, SP_IMPORT_EXCEL, SP_WORKSHEET, QuickTeamApi # noqa: E402
|
||||
from qtimport.config import Config # noqa: E402
|
||||
from qtimport.transport import Transport # noqa: E402
|
||||
|
||||
|
||||
def dump(title: str, rows) -> dict:
|
||||
print(f"\n=== {title} ===")
|
||||
if isinstance(rows, list) and rows:
|
||||
print(json.dumps(rows[0], ensure_ascii=False, indent=2))
|
||||
return rows[0]
|
||||
print(json.dumps(rows, ensure_ascii=False, indent=2))
|
||||
return {}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--config", default="config/config.json")
|
||||
parser.add_argument("--project", default="资管 - 华泰保险资管")
|
||||
parser.add_argument("--excel", default=r"D:\Work\QuickTeam\2026 Sprint 10\资管 - 华泰保险资管.xlsx")
|
||||
parser.add_argument("--keep", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
cfg = Config.load(args.config)
|
||||
t = Transport(cfg.base_url, timeout=cfg.timeout, retries=0)
|
||||
api = QuickTeamApi(t)
|
||||
user = api.login(cfg.username, cfg.password)
|
||||
team = api.resolve_team(api.teams(user.user_id), cfg.team_name)
|
||||
project = next(p for p in api.projects(team.team_id) if p.project_name == args.project)
|
||||
print(f"登录:{user.real_name};项目:{project.project_name}")
|
||||
|
||||
worksheet_id = api.create_worksheet()
|
||||
print(f"草稿工单:{worksheet_id}")
|
||||
try:
|
||||
dump("建单后", t.exec_table(CONN_QUICKTEAM, SP_WORKSHEET,
|
||||
[("WorkSheetId", worksheet_id, "int")], op="raw"))
|
||||
|
||||
evaluator, technical, customer = cfg.resolved_roles(user.real_name)
|
||||
print(f"角色:评估={evaluator} 技术={technical} 客户={customer}")
|
||||
result = api.fill_worksheet(
|
||||
worksheet_id, project,
|
||||
system_name=cfg.sprint, start_time=cfg.start_time, end_time=cfg.end_time,
|
||||
evaluator=evaluator, technical_leader=technical, customer_leader=customer,
|
||||
belong_team=cfg.belong_team, content=cfg.content_desc,
|
||||
operator=user.real_name, service_type=cfg.service_type,
|
||||
)
|
||||
print(f"填单 Result={result}")
|
||||
|
||||
after = dump("填单后", t.exec_table(CONN_QUICKTEAM, SP_WORKSHEET,
|
||||
[("WorkSheetId", worksheet_id, "int")], op="raw"))
|
||||
print(f"CurrentVersionId={after.get('CurrentVersionId')!r} VersionCode={after.get('VersionCode')!r}")
|
||||
|
||||
remote = api.upload_excel(args.excel)
|
||||
print(f"\n上传成功:{remote}")
|
||||
|
||||
# 逐个尝试参数组合,看哪种不报空引用
|
||||
variants = [
|
||||
("VersionId=0", [("WorkSheetId", worksheet_id, "int"), ("VersionId", 0, "int")]),
|
||||
("VersionId=-1", [("WorkSheetId", worksheet_id, "int"), ("VersionId", -1, "int")]),
|
||||
("VersionId 省略", [("WorkSheetId", worksheet_id, "int")]),
|
||||
("VersionId=空串", [("WorkSheetId", worksheet_id, "int"), ("VersionId", "", "string")]),
|
||||
]
|
||||
for label, params in variants:
|
||||
print(f"\n--- 尝试 {label} ---")
|
||||
try:
|
||||
out = t.batch_import(SP_IMPORT_EXCEL, params, remote)
|
||||
print("返回:", json.dumps(out, ensure_ascii=False))
|
||||
if isinstance(out, list) and out and isinstance(out[0], dict) and int(out[0].get("Column1", -1)) == 0:
|
||||
print(f"✔ {label} 成功")
|
||||
break
|
||||
except Exception as error: # noqa: BLE001
|
||||
print(f"✘ {type(error).__name__}: {error}")
|
||||
|
||||
dump("导入后", t.exec_table(CONN_QUICKTEAM, SP_WORKSHEET,
|
||||
[("WorkSheetId", worksheet_id, "int")], op="raw"))
|
||||
feats = api.features(worksheet_id)
|
||||
print(f"功能模块数量:{len(feats)}")
|
||||
for f in feats[:3]:
|
||||
print(" -", f.feature_id, f.code, f.name)
|
||||
finally:
|
||||
if args.keep:
|
||||
print(f"\n草稿保留:{worksheet_id}(请手工核对后删除)")
|
||||
else:
|
||||
try:
|
||||
api.delete_worksheet(worksheet_id)
|
||||
print(f"\n已删除草稿 {worksheet_id}")
|
||||
except Exception as error: # noqa: BLE001
|
||||
print(f"\n删除失败:{error}(请手工删除 {worksheet_id})")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user