Files
TFSImport/README.md
T
chenshoulin c2d121d185 refactor: 更新 README 和代码以支持默认模板导出功能
- README.md: 修改数据源描述,明确默认模板随程序发布,并更新操作流程中的按钮名称。
- ExcelService.cs: 重命名模板生成方法为 CopyTemplate,简化模板导出逻辑。
- MainForm.cs: 更新按钮文本和事件处理,改为导出默认模板。
- Config.cs 和 config.json: 添加 OriginalEstimateRef 字段以支持新功能。
- 删除不再使用的 build_template.ps1 脚本及相关模板部件。
2026-11-01 12:14:10 +08:00

147 lines
8.8 KiB
Markdown
Raw 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.
# TFSImport
TFS 2013 工作项批量导入工具集,用于把 Excel/CSV 里的工时台账(标题、状态、指派人、完成量、日期等)批量写成团队项目里的工作项,并挂到指定的父项(Product Backlog Item / 占位 Backlog Item)下,使其在「积压工作」页和任务板可见。
目标环境:
| 项 | 值 |
| --- | --- |
| 服务器 | `http://pdtfs01.dev.goldenstand.cn:8080/tfs/DefaultCollection` |
| 团队项目 | `Tools_Dev` |
| 团队 | `DataTechnologies` |
| 迭代 | `2026年Sprint\2026年Sprint10` |
| 认证 | 域账号 NTLM(内网直连,不走代理) |
## 两套工具,先用哪个
| | [TfsExcelImporter](TfsExcelImporter)(C# WinForms) | [tfs_import.py](tfs_import.py)(Python CLI) |
| --- | --- | --- |
| 通信协议 | TFS Client OM(VS/Team Explorer 同款内部 SOAP) | REST `/_apis/wit/*` |
| 适用 | **本环境可用**。该 TFS 2013 实例只有 `/_apis/projects` 通,`wit/fields`、`wit/classificationnodes`、`wit/wiql` 全部 404 | 服务器开放了 wit REST 路由时使用 |
| 数据源 | `.xlsx`(默认模板随程序发布) | `data/work_items.csv`(GBK/UTF-8 均可,需自备) |
| 界面 | 图形界面,预览后导入 | 命令行,`--dry-run` 先校验 |
Python 版在本环境会因 REST 路由 404 而失败,保留用于其它 TFS 实例或接口可用时的批量脚本化导入。
## 目录结构
```
TFSImport/
├── tfs_import.py # Python 命令行导入器(REST)
├── config.json # Python 版配置
├── requirements.txt # requests / requests-ntlm
├── data/
│ └── work_items.example.csv # 列结构与示例行(UTF-8 BOM);真实台账不入库
├── templates/
│ └── TFS导入模板.xlsx # 默认模板:csproj 随构建拷到 exe 目录,「导出Excel模板」原样复制它
└── TfsExcelImporter/ # C# WinForms 桌面版(主用)
├── TfsExcelImporter.csproj # net48 + ClosedXML + TFS ExtendedClient 14.*
├── MainForm.cs # 界面与导入/修 Area 流程
├── TfsClient.cs # TFS Client OM 封装
├── ExcelService.cs # 读 Excel、导出默认模板、时间/数值归一
├── Config.cs # exe 同目录 config.json 读写
└── config.json # 桌面版配置
```
## 快速开始
### C# 桌面版(主用)
```powershell
dotnet build TfsExcelImporter.sln -c Release
# 或用 Visual Studio 打开 TfsExcelImporter.sln(目标框架 net48,无需装 VS 也能 dotnet build)
TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe
```
「导出Excel模板」复制的是 exe 同目录的 `TFS导入模板.xlsx`(构建时自动拷过去);单独分发 exe 时要带上这个文件,否则该按钮会报「未找到默认模板文件」。
操作流程:
1. 填「TFS 连接」区:URL、项目、工作项类型、认证方式与域账号;内网保持「使用系统代理」**不勾选**。
2. 点「加载迭代列表」,选择目标迭代。
3. 点「导出Excel模板」拿到默认模板,或「浏览...」选择已有 `.xlsx`;点「预览数据」核对。
4. 需要挂父项时填「父 PBI Id」(默认全部挂同一个;Excel 里加 `PBI Id` 列可逐行指定不同父项)。
5. 点「开始导入 TFS」,日志区逐行输出 `OK/SKIP/FAIL` 与工作项 ID。
6. 历史数据 Area 不对时用「按迭代修Area」批量纠正。
### Python 版
```powershell
pip install -r requirements.txt
python tfs_import.py --dry-run # 只校验数据与字段映射,不写入
python tfs_import.py --list-fields # 列出服务器全部字段(显示名/引用名)
python tfs_import.py # 正式导入
python tfs_import.py --link-existing # 已导入的项补挂父链接(不新建)
python tfs_import.py --skip-existing-titles # 跳过服务器已有的同标题项
python tfs_import.py --input other.csv --limit 5
```
每次运行在 `data/import_report_<时间戳>.csv` 生成逐行结果报告(行号 / Title / 结果 / WorkItemId / 错误)。
## 配置说明
配置的语义、留空字段如何解析见 [tfs_import.py:49](tfs_import.py#L49) 与 [Config.cs](TfsExcelImporter/Config.cs),关键字段:
| 键 | 说明 |
| --- | --- |
| `tfs_url` / `project` / `work_item_type` | 目标集合、项目、工作项类型(`Task` 或 `Product Backlog Item`) |
| `iteration_path` | 可只写 `2026年Sprint\2026年Sprint10`,程序自动补项目名前缀 |
| `auth.mode` | `ntlm`(域账号,典型)/ `basic` / `default`(当前 Windows 凭据,仅桌面版) |
| `field_refs` | 语义名 → 字段引用名;自定义字段留空时按 `resolve_fields_by_display_name` 的显示名在服务器字段表里查(支持前缀模糊匹配,如 `Completed` → `Completed Work`) |
| `parent_title` / `parent_id` | 父工作项,二选一;按标题匹配不到会直接报错并提示改用 `parent_id` |
| `bypass_rules` | 是否请求绕过字段校验规则;桌面版走 Client OM(14.x 无 `SaveFlags.BypassRules`),实际由服务器规则决定 |
| `skip_existing_titles` | 同项目/同类型/同迭代下按标题去重 |
> 配置里含明文密码,请勿提交到共享仓库;`config.json` 保持空值,凭据本地填写。
## Excel / CSV 列
桌面版 Excel 模板 = 仓库里的 `templates\TFS导入模板.xlsx`,界面「导出Excel模板」直接复制它:
`Title`、`State`、`Assigned To`、`FinancialProject`、`Completed`、`LogDate`、`PBI Id`、`Description`
写入 TFS 的对应关系:
| 模板列 | 目标字段 |
| --- | --- |
| `Description` | `System.Description` |
| `Completed` | 显示名 `Completed`(模糊匹配到 `Completed Work`)解析出的字段 |
| —(无列) | `Original Estimate` 取该行 `Completed` 的值;引用名留空则按显示名 `Original Estimate` 解析,可写在 `OriginalEstimateRef` 里固定 |
系统日期列(`Created Date` / `Closed Date`)已从模板移除:`System.CreatedDate` 由服务器按导入时间填写,`Microsoft.VSTS.Common.ClosedDate` 不再写入、保持为空。
Python 版仍读 `data/work_items.example.csv` 的旧列(含 `CreatedDate`、`ClosedDate`、`Notes`),未随模板调整。
真实台账含人员与项目信息,不入库;本地准备数据文件:
```powershell
copy data\work_items.example.csv data\work_items.csv # Python 版默认读这个路径,已在 .gitignore 里
```
- 表头按名匹配,顺序不限,`AssignedTo` / `Assigned To` 等价;缺列按模板顺序兜底,但该列已被别的表头占用时不套用位置(旧版模板文件不会串列)。
- 空 `Title` 的行直接跳过;`State` 为空时用 `default_state`;未识别的列(如 `Group`)忽略。
- 日期支持 `yyyy-MM-dd[ / ][ HH:mm[:ss]]` 等格式,Excel 日期序列号(如 `46300`)会被还原成日期。
- CSV 从 Excel 另存时注意编码,脚本按 `utf-8-sig` → `gbk` 依次尝试。
## 环境坑点
这些是实测结论,改代码前先看:
- **wit REST 路由全 404**:TFS 2013 RTM/低 Update 不带 `/_apis/wit/*`,`Classification.asmx?WSDL` 也被禁 → 桌面版整体走 Client OM(NuGet `Microsoft.TeamFoundationServer.ExtendedClient` 钉 `14.*`,对应 VS2015 客户端代际;15.x 不兼容 TFS 2013)。
- **502 Bad Gateway**:TFS OM 内部用 `HttpWebRequest`,默认跟随 IE 系统代理,代理会拦内网 TFS。桌面版默认把 `WebRequest.DefaultWebProxy` 置空直连;确实要走代理时勾「使用系统代理」并带上 Windows 凭据过代理认证。
- **TF400276 / 关闭状态只读**:改已关闭工作项的 Area 会被拒;`FixAreaAsync` 走「重新打开 → 改 Area → 还原状态」的状态循环,中途失败回滚状态。
- **`System.CreatedDate` 写不进去**:OM 14.x 没有 `SaveFlags.BypassRules`(15.x 才加),该字段服务器托管;`Microsoft.VSTS.Common.ClosedDate` 在 `State=Done` 时按普通字段可尝试写入。模板已去掉日期列,所以导入不再走这条路——若某模板要求 `Done` 必填 Closed Date,需另加列或改回写日期。
- **孤立 Task 在积压工作页不可见**:TFS 2013 的 backlog/任务板按「父项 → 子任务」组织,没挂父链接的 Task 只能在查询里看到,所以导入必须带 `parent_title`/`parent_id`。
- **DNS 解析失败 / 超时**:多为 VPN 或网络断开,不是代码问题;桌面版会对这类异常附加排查提示。
## 验证
改动 Python 版后至少跑:
```powershell
python -m py_compile tfs_import.py
python tfs_import.py --dry-run
```
桌面版改完跑一次 `--limit` 级别的小批量导入(先删服务器已有同标题项或开启跳过),并在积压工作页展开父项确认子任务可见。