Files
chenshoulin 58850314cf 初始提交: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 个单测通过。
2026-11-05 19:14:25 +08:00

110 lines
4.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""诊断工具:定位 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())