diff --git a/README.md b/README.md
index ba056ad..52840df 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ TFS 2013 工作项批量导入工具集,用于把 Excel/CSV 里的工时台账
| --- | --- | --- |
| 通信协议 | 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 均可,需自备) |
+| 数据源 | `.xlsx`(默认模板随程序发布) | `data/work_items.csv`(GBK/UTF-8 均可,需自备) |
| 界面 | 图形界面,预览后导入 | 命令行,`--dry-run` 先校验 |
Python 版在本环境会因 REST 路由 404 而失败,保留用于其它 TFS 实例或接口可用时的批量脚本化导入。
@@ -33,14 +33,12 @@ TFSImport/
├── data/
│ └── work_items.example.csv # 列结构与示例行(UTF-8 BOM);真实台账不入库
├── templates/
-│ ├── TFS导入模板.xlsx # Excel 模板成品
-│ ├── build_template.ps1 # 由 _parts 里的 XML 打包生成 xlsx
-│ └── _parts/ # xlsx 的 OOXML 源文件
+│ └── 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、生成模板、时间/数值归一
+ ├── ExcelService.cs # 读 Excel、导出默认模板、时间/数值归一
├── Config.cs # exe 同目录 config.json 读写
└── config.json # 桌面版配置
```
@@ -55,11 +53,13 @@ dotnet build TfsExcelImporter.sln -c Release
TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe
```
+「导出Excel模板」复制的是 exe 同目录的 `TFS导入模板.xlsx`(构建时自动拷过去);单独分发 exe 时要带上这个文件,否则该按钮会报「未找到默认模板文件」。
+
操作流程:
1. 填「TFS 连接」区:URL、项目、工作项类型、认证方式与域账号;内网保持「使用系统代理」**不勾选**。
2. 点「加载迭代列表」,选择目标迭代。
-3. 点「生成Excel模板」拿到表头,或「浏览...」选择已有 `.xlsx`;点「预览数据」核对。
+3. 点「导出Excel模板」拿到默认模板,或「浏览...」选择已有 `.xlsx`;点「预览数据」核对。
4. 需要挂父项时填「父 PBI Id」(默认全部挂同一个;Excel 里加 `PBI Id` 列可逐行指定不同父项)。
5. 点「开始导入 TFS」,日志区逐行输出 `OK/SKIP/FAIL` 与工作项 ID。
6. 历史数据 Area 不对时用「按迭代修Area」批量纠正。
@@ -89,14 +89,28 @@ python tfs_import.py --input other.csv --limit 5
| `auth.mode` | `ntlm`(域账号,典型)/ `basic` / `default`(当前 Windows 凭据,仅桌面版) |
| `field_refs` | 语义名 → 字段引用名;自定义字段留空时按 `resolve_fields_by_display_name` 的显示名在服务器字段表里查(支持前缀模糊匹配,如 `Completed` → `Completed Work`) |
| `parent_title` / `parent_id` | 父工作项,二选一;按标题匹配不到会直接报错并提示改用 `parent_id` |
-| `bypass_rules` | 是否尝试写入 `CreatedDate`/`ClosedDate` 等系统日期 |
+| `bypass_rules` | 是否请求绕过字段校验规则;桌面版走 Client OM(14.x 无 `SaveFlags.BypassRules`),实际由服务器规则决定 |
| `skip_existing_titles` | 同项目/同类型/同迭代下按标题去重 |
> 配置里含明文密码,请勿提交到共享仓库;`config.json` 保持空值,凭据本地填写。
## Excel / CSV 列
-`Title`、`State`、`Assigned To`、`FinancialProject`、`Completed`、`LogDate`、`Created Date`、`Closed Date`、`PBI Id`、`Notes`
+桌面版 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`),未随模板调整。
真实台账含人员与项目信息,不入库;本地准备数据文件:
@@ -104,7 +118,7 @@ python tfs_import.py --input other.csv --limit 5
copy data\work_items.example.csv data\work_items.csv # Python 版默认读这个路径,已在 .gitignore 里
```
-- 表头按名匹配,顺序不限,`AssignedTo` / `Assigned To` 等价;缺列自动按模板顺序兜底。
+- 表头按名匹配,顺序不限,`AssignedTo` / `Assigned To` 等价;缺列按模板顺序兜底,但该列已被别的表头占用时不套用位置(旧版模板文件不会串列)。
- 空 `Title` 的行直接跳过;`State` 为空时用 `default_state`;未识别的列(如 `Group`)忽略。
- 日期支持 `yyyy-MM-dd[ / ][ HH:mm[:ss]]` 等格式,Excel 日期序列号(如 `46300`)会被还原成日期。
- CSV 从 Excel 另存时注意编码,脚本按 `utf-8-sig` → `gbk` 依次尝试。
@@ -116,7 +130,7 @@ copy data\work_items.example.csv data\work_items.csv # Python 版默认读这
- **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` 时尝试写入,校验或保存失败自动降级为普通保存,保证行不丢。
+- **`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 或网络断开,不是代码问题;桌面版会对这类异常附加排查提示。
diff --git a/TfsExcelImporter/Config.cs b/TfsExcelImporter/Config.cs
index 89f9eb9..e11c842 100644
--- a/TfsExcelImporter/Config.cs
+++ b/TfsExcelImporter/Config.cs
@@ -36,6 +36,8 @@ namespace TfsExcelImporter
public string FinancialProjectRef { get; set; } = "";
public string CompletedRef { get; set; } = "";
public string LogDateRef { get; set; } = "";
+ // Original Estimate 导入时取 Completed 的值;引用名留空则按显示名解析
+ public string OriginalEstimateRef { get; set; } = "";
public string LastExcelPath { get; set; } = "";
diff --git a/TfsExcelImporter/ExcelService.cs b/TfsExcelImporter/ExcelService.cs
index ce46e81..2faecb0 100644
--- a/TfsExcelImporter/ExcelService.cs
+++ b/TfsExcelImporter/ExcelService.cs
@@ -16,22 +16,23 @@ namespace TfsExcelImporter
public string FinancialProject { get; set; }
public string Completed { get; set; }
public string LogDate { get; set; }
- public string CreatedDate { get; set; }
- public string ClosedDate { get; set; }
public string PbiId { get; set; }
- public string Notes { get; set; }
+ public string Description { get; set; }
}
///
- /// Excel 读取与模板生成。
- /// 模板列: Title | State | Assigned To | FinancialProject | Completed | LogDate | Created Date | Closed Date | PBI Id | Notes
+ /// Excel 读取与默认模板。模板内容(列宽、示例行、样式)只在
+ /// templates\TFS导入模板.xlsx 里维护,随构建复制到 exe 目录后原样导出。
+ /// 列: Title | State | Assigned To | FinancialProject | Completed | LogDate | PBI Id | Description
///
public static class ExcelService
{
+ public const string TemplateFileName = "TFS导入模板.xlsx";
+
public static readonly string[] Headers =
{
"Title", "State", "Assigned To", "FinancialProject",
- "Completed", "LogDate", "Created Date", "Closed Date", "PBI Id", "Notes"
+ "Completed", "LogDate", "PBI Id", "Description"
};
private static readonly string[] DateFormats =
@@ -41,68 +42,15 @@ namespace TfsExcelImporter
"yyyy-MM-dd", "yyyy/MM/dd"
};
- // --------------------------------------------------------- 生成模板
+ // --------------------------------------------------------- 默认模板
- public static void GenerateTemplate(string path)
+ /// 把随程序发布的默认模板原样复制到目标路径。
+ public static void CopyTemplate(string path)
{
- using (var wb = new XLWorkbook())
- {
- var ws = wb.Worksheets.Add("工作项");
-
- for (int i = 0; i < Headers.Length; i++)
- {
- var cell = ws.Cell(1, i + 1);
- cell.Value = Headers[i];
- cell.Style.Font.Bold = true;
- cell.Style.Fill.BackgroundColor = XLColor.LightGray;
- }
-
- // 示例行(To Do / Done 各一,帮助理解格式)
- var sample1 = new WorkItemRow
- {
- Title = "20261012 - 示例:货载报告解析并上传凭证受托报告相关内容",
- State = "To Do",
- AssignedTo = "李晓柯",
- FinancialProject = "信托 - 上海信托",
- Completed = "5",
- LogDate = "2026-10-12",
- CreatedDate = "2026-10-30 11:14:41",
- ClosedDate = "",
- Notes = "示例行,导入前请删除"
- };
- var sample2 = new WorkItemRow
- {
- Title = "20261009 示例:受托报告bug修复",
- State = "Done",
- AssignedTo = "陈卫宇",
- FinancialProject = "信托 - 上海信托",
- Completed = "8",
- LogDate = "2026-10-09",
- CreatedDate = "2026-10-10 18:20:00",
- ClosedDate = "2026-10-30 18:00:00",
- Notes = "示例行,导入前请删除"
- };
- WriteRow(ws, 2, sample1);
- WriteRow(ws, 3, sample2);
-
- ws.Columns().AdjustToContents();
- ws.SheetView.FreezeRows(1);
- wb.SaveAs(path);
- }
- }
-
- private static void WriteRow(IXLWorksheet ws, int r, WorkItemRow row)
- {
- ws.Cell(r, 1).Value = row.Title ?? "";
- ws.Cell(r, 2).Value = row.State ?? "";
- ws.Cell(r, 3).Value = row.AssignedTo ?? "";
- ws.Cell(r, 4).Value = row.FinancialProject ?? "";
- ws.Cell(r, 5).Value = row.Completed ?? "";
- ws.Cell(r, 6).Value = row.LogDate ?? "";
- ws.Cell(r, 7).Value = row.CreatedDate ?? "";
- ws.Cell(r, 8).Value = row.ClosedDate ?? "";
- ws.Cell(r, 9).Value = row.PbiId ?? "";
- ws.Cell(r, 10).Value = row.Notes ?? "";
+ var src = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, TemplateFileName);
+ if (!File.Exists(src))
+ throw new FileNotFoundException("未找到默认模板文件: " + src, src);
+ File.Copy(src, path, true);
}
// --------------------------------------------------------- 读取数据
@@ -132,10 +80,8 @@ namespace TfsExcelImporter
FinancialProject = CellText(ws, r, headerMap, "FinancialProject"),
Completed = CellText(ws, r, headerMap, "Completed"),
LogDate = CellText(ws, r, headerMap, "LogDate"),
- CreatedDate = CellText(ws, r, headerMap, "Created Date"),
- ClosedDate = CellText(ws, r, headerMap, "Closed Date"),
PbiId = CellText(ws, r, headerMap, "PBI Id"),
- Notes = CellText(ws, r, headerMap, "Notes")
+ Description = CellText(ws, r, headerMap, "Description")
});
}
}
@@ -151,7 +97,7 @@ namespace TfsExcelImporter
{
var text = ws.Cell(firstRow, c).GetString().Trim();
if (text.Length == 0) continue;
- // 归一化:去掉空格/下划线,兼容 "Assigned To" / "AssignedTo" / "Created Date" 等
+ // 归一化:去掉空格/下划线,兼容 "Assigned To" / "AssignedTo" / "PBI Id" 等
var key = text.Replace(" ", "").Replace("_", "");
if (!map.ContainsKey(key)) map[key] = c;
}
@@ -159,11 +105,12 @@ namespace TfsExcelImporter
foreach (var h in Headers)
{
var key = h.Replace(" ", "");
- if (!map.ContainsKey(key))
- {
- int idx = Array.IndexOf(Headers, h) + 1;
- if (idx <= lastCol) map[key] = idx;
- }
+ if (map.ContainsKey(key)) continue;
+ int idx = Array.IndexOf(Headers, h) + 1;
+ if (idx > lastCol) continue;
+ // 该列已有别的表头占用(如旧版模板的 Created Date 列)时不套用位置,避免读错列
+ bool taken = map.Values.Any(c => c == idx);
+ if (!taken) map[key] = idx;
}
return map;
}
diff --git a/TfsExcelImporter/MainForm.cs b/TfsExcelImporter/MainForm.cs
index 0d8c87d..4767823 100644
--- a/TfsExcelImporter/MainForm.cs
+++ b/TfsExcelImporter/MainForm.cs
@@ -144,10 +144,10 @@ namespace TfsExcelImporter
_btnBrowse.Click += (s, e) => BrowseExcel();
grpImport.Controls.Add(_btnBrowse);
- _btnTemplate.Text = "生成Excel模板";
+ _btnTemplate.Text = "导出Excel模板";
_btnTemplate.Location = new Point(760, 58); _btnTemplate.Size = new Size(110, 29);
_btnTemplate.Anchor = AnchorStyles.Top | AnchorStyles.Right;
- _btnTemplate.Click += (s, e) => GenerateTemplate();
+ _btnTemplate.Click += (s, e) => ExportTemplate();
grpImport.Controls.Add(_btnTemplate);
_chkSkip.Text = "跳过已存在标题";
@@ -155,7 +155,7 @@ namespace TfsExcelImporter
_chkSkip.Anchor = AnchorStyles.Top | AnchorStyles.Right;
grpImport.Controls.Add(_chkSkip);
- _chkBypass.Text = "bypassRules(可写系统日期)";
+ _chkBypass.Text = "bypassRules(绕过字段规则)";
_chkBypass.Location = new Point(886, 60); _chkBypass.Size = new Size(150, 24);
_chkBypass.Anchor = AnchorStyles.Top | AnchorStyles.Right;
grpImport.Controls.Add(_chkBypass);
@@ -357,25 +357,25 @@ namespace TfsExcelImporter
}
}
- private void GenerateTemplate()
+ private void ExportTemplate()
{
using (var dlg = new SaveFileDialog())
{
dlg.Filter = "Excel 文件 (*.xlsx)|*.xlsx";
- dlg.FileName = "TFS导入模板.xlsx";
- dlg.Title = "保存 Excel 模板";
+ dlg.FileName = ExcelService.TemplateFileName;
+ dlg.Title = "导出默认模板";
if (dlg.ShowDialog(this) != DialogResult.OK) return;
try
{
- ExcelService.GenerateTemplate(dlg.FileName);
- Log("模板已生成: " + dlg.FileName);
- MessageBox.Show(this, "模板已生成:\n" + dlg.FileName, "完成",
+ ExcelService.CopyTemplate(dlg.FileName);
+ Log("模板已导出: " + dlg.FileName);
+ MessageBox.Show(this, "模板已导出:\n" + dlg.FileName, "完成",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
- Log("生成模板失败: " + ex.Message);
- MessageBox.Show(this, "生成模板失败:\n" + ex.Message, "错误",
+ Log("导出模板失败: " + ex.Message);
+ MessageBox.Show(this, "导出模板失败:\n" + ex.Message, "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
@@ -402,9 +402,8 @@ namespace TfsExcelImporter
财务项目 = r.FinancialProject,
r.Completed,
LogDate = r.LogDate,
- Created = r.CreatedDate,
- Closed = r.ClosedDate,
- r.Notes
+ r.PbiId,
+ r.Description
}).ToList();
_lblStatus.Text = $"已读取 {_rows.Count} 行";
Log($"读取 {path} 共 {_rows.Count} 行");
@@ -563,13 +562,12 @@ namespace TfsExcelImporter
var assignedRef = "System.AssignedTo";
var iterationRef = "System.IterationPath";
var areaRef = "System.AreaPath";
- var createdRef = "System.CreatedDate";
- var closedRef = "Microsoft.VSTS.Common.ClosedDate";
var finRef = TfsClient.ResolveRef(fieldMap, "FinancialProject", _cfg.FinancialProjectRef);
var compRef = TfsClient.ResolveRef(fieldMap, "Completed", _cfg.CompletedRef);
var logRef = TfsClient.ResolveRef(fieldMap, "LogDate", _cfg.LogDateRef);
- Log($"字段: FinancialProject->{finRef ?? "(未找到)"} Completed->{compRef ?? "(未找到)"} LogDate->{logRef ?? "(未找到)"}");
+ var origEstRef = TfsClient.ResolveRef(fieldMap, "Original Estimate", _cfg.OriginalEstimateRef);
+ Log($"字段: FinancialProject->{finRef ?? "(未找到)"} Completed->{compRef ?? "(未找到)"} LogDate->{logRef ?? "(未找到)"} OriginalEstimate->{origEstRef ?? "(未找到)"}");
Log($"父PBI: '{(string.IsNullOrWhiteSpace(_cfg.ParentPbiId) ? "(未填)" : _cfg.ParentPbiId)}'——Excel 每行可用 PBI Id 列覆盖");
HashSet existing = null;
@@ -612,25 +610,17 @@ namespace TfsExcelImporter
if (completed != null && compRef != null)
fields.Add(new KeyValuePair(compRef, completed));
- // Notes -> Description(该 Task 模板必填)
- if (!string.IsNullOrWhiteSpace(row.Notes))
- fields.Add(new KeyValuePair("System.Description", row.Notes.Trim()));
+ // Original Estimate 默认取 Completed 的值
+ if (completed != null && origEstRef != null)
+ fields.Add(new KeyValuePair(origEstRef, completed));
+
+ if (!string.IsNullOrWhiteSpace(row.Description))
+ fields.Add(new KeyValuePair("System.Description", row.Description.Trim()));
var logDate = ExcelService.ToIsoDateTime(row.LogDate);
if (logDate != null && logRef != null)
fields.Add(new KeyValuePair(logRef, logDate));
- // 系统日期字段:bypassRules 时尝试写入
- if (_chkBypass.Checked)
- {
- var created = ExcelService.ToIsoDateTime(row.CreatedDate);
- if (created != null)
- fields.Add(new KeyValuePair(createdRef, created));
- var closed = ExcelService.ToIsoDateTime(row.ClosedDate);
- if (closed != null)
- fields.Add(new KeyValuePair(closedRef, closed));
- }
-
// 父 PBI:行级 PBI Id 列优先,其次界面默认值
var pbiText = !string.IsNullOrWhiteSpace(row.PbiId) ? row.PbiId.Trim() : _cfg.ParentPbiId;
int? parentPbi = int.TryParse(pbiText, out var pbiNum) ? pbiNum : (int?)null;
diff --git a/TfsExcelImporter/TfsExcelImporter.csproj b/TfsExcelImporter/TfsExcelImporter.csproj
index ce48ffc..35b37b4 100644
--- a/TfsExcelImporter/TfsExcelImporter.csproj
+++ b/TfsExcelImporter/TfsExcelImporter.csproj
@@ -26,6 +26,10 @@
PreserveNewest
+
+
+ PreserveNewest
+
diff --git a/TfsExcelImporter/config.json b/TfsExcelImporter/config.json
index 3aaa39b..0f4b896 100644
--- a/TfsExcelImporter/config.json
+++ b/TfsExcelImporter/config.json
@@ -15,6 +15,7 @@
"FinancialProjectRef": "",
"CompletedRef": "",
"LogDateRef": "",
+ "OriginalEstimateRef": "",
"LastExcelPath": "",
"ParentPbiId": ""
}
diff --git a/templates/TFS导入模板.xlsx b/templates/TFS导入模板.xlsx
index c5610ad..0a65aa9 100644
Binary files a/templates/TFS导入模板.xlsx and b/templates/TFS导入模板.xlsx differ
diff --git a/templates/_parts/[Content_Types].xml b/templates/_parts/[Content_Types].xml
deleted file mode 100644
index 0cf64ec..0000000
--- a/templates/_parts/[Content_Types].xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/templates/_parts/_rels/.rels b/templates/_parts/_rels/.rels
deleted file mode 100644
index ad3ced5..0000000
--- a/templates/_parts/_rels/.rels
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/templates/_parts/xl/_rels/workbook.xml.rels b/templates/_parts/xl/_rels/workbook.xml.rels
deleted file mode 100644
index 45a0a46..0000000
--- a/templates/_parts/xl/_rels/workbook.xml.rels
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/templates/_parts/xl/styles.xml b/templates/_parts/xl/styles.xml
deleted file mode 100644
index 75ce262..0000000
--- a/templates/_parts/xl/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/templates/_parts/xl/workbook.xml b/templates/_parts/xl/workbook.xml
deleted file mode 100644
index c26af18..0000000
--- a/templates/_parts/xl/workbook.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/templates/_parts/xl/worksheets/sheet1.xml b/templates/_parts/xl/worksheets/sheet1.xml
deleted file mode 100644
index 25ce4c8..0000000
--- a/templates/_parts/xl/worksheets/sheet1.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-Title
-State
-Assigned To
-FinancialProject
-Completed
-LogDate
-Created Date
-Closed Date
-PBI Id
-Notes
-
-
-20261012 - 示例:货载报告解析并上传凭证受托报告相关内容
-To Do
-李晓柯
-信托 - 上海信托
-5
-2026-10-12
-2026-10-30 11:14:41
-
-219975
-示例行,导入前请删除
-
-
-20261009 示例:受托报告bug修复
-Done
-陈卫宇
-信托 - 上海信托
-8
-2026-10-09
-2026-10-10 18:20:00
-2026-10-30 18:00:00
-219975
-示例行,导入前请删除
-
-
-
diff --git a/templates/build_template.ps1 b/templates/build_template.ps1
deleted file mode 100644
index 614e135..0000000
--- a/templates/build_template.ps1
+++ /dev/null
@@ -1,104 +0,0 @@
-# Build TFS import xlsx from XML parts as a stored (uncompressed) ZIP.
-# Pure in-process .NET byte APIs: no external processes, no assembly loading.
-# Output file name uses ASCII+char trick to stay encoding-safe.
-$ErrorActionPreference = 'Stop'
-
-$parts = Join-Path $PSScriptRoot '_parts'
-$outName = 'TFS' + [char]0x5BFC + [char]0x5165 + [char]0x6A21 + [char]0x677F + '.xlsx'
-$out = Join-Path $PSScriptRoot $outName
-
-# ---------- CRC32 (IEEE 802.3) ----------
-$tbl = New-Object 'System.Int64[]' 256
-for ($i = 0; $i -lt 256; $i++) {
- $c = [int64]$i
- for ($k = 0; $k -lt 8; $k++) {
- if (($c -band 1) -eq 1) { $c = (3988292384 -bxor ($c -shr 1)) } else { $c = ($c -shr 1) }
- $c = $c -band 4294967295
- }
- $tbl[$i] = $c
-}
-
-function Get-Crc32([byte[]]$data) {
- $crc = [int64]4294967295
- foreach ($b in $data) {
- $idx = [int](($crc -bxor [int64]$b) -band 255)
- $crc = ($tbl[$idx] -bxor ($crc -shr 8)) -band 4294967295
- }
- return ($crc -bxor 4294967295)
-}
-
-# ---------- entry list (source relative path, zip entry name) ----------
-$entries = @(
- @{ src = '[Content_Types].xml'; zip = '[Content_Types].xml' },
- @{ src = '_rels\.rels'; zip = '_rels/.rels' },
- @{ src = 'xl\workbook.xml'; zip = 'xl/workbook.xml' },
- @{ src = 'xl\_rels\workbook.xml.rels'; zip = 'xl/_rels/workbook.xml.rels' },
- @{ src = 'xl\styles.xml'; zip = 'xl/styles.xml' },
- @{ src = 'xl\worksheets\sheet1.xml'; zip = 'xl/worksheets/sheet1.xml' }
-)
-
-$ms = New-Object System.IO.MemoryStream
-$central = New-Object System.Collections.ArrayList
-
-foreach ($e in $entries) {
- $srcPath = Join-Path $parts $e['src']
- $bytes = [System.IO.File]::ReadAllBytes($srcPath)
- $nameBytes = [System.Text.Encoding]::ASCII.GetBytes([string]$e['zip'])
- $crc = Get-Crc32 $bytes
- $offset = $ms.Position
-
- $ms.Write([BitConverter]::GetBytes([uint32]0x04034b50), 0, 4) # local header signature
- $ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # version needed
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # flags
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # method: stored
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # mod time
- $ms.Write([BitConverter]::GetBytes([uint16]33), 0, 2) # mod date 1980-01-01
- $ms.Write([BitConverter]::GetBytes([uint32]$crc), 0, 4)
- $ms.Write([BitConverter]::GetBytes([uint32]$bytes.Length), 0, 4)
- $ms.Write([BitConverter]::GetBytes([uint32]$bytes.Length), 0, 4)
- $ms.Write([BitConverter]::GetBytes([uint16]$nameBytes.Length), 0, 2)
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # extra len
- $ms.Write($nameBytes, 0, $nameBytes.Length)
- $ms.Write($bytes, 0, $bytes.Length)
-
- [void]$central.Add(@{ name = $nameBytes; crc = $crc; size = $bytes.Length; offset = $offset })
-}
-
-# ---------- central directory ----------
-$cdStart = $ms.Position
-foreach ($c in $central) {
- $ms.Write([BitConverter]::GetBytes([uint32]0x02014b50), 0, 4) # central header signature
- $ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # made by
- $ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # version needed
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # flags
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # method
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # time
- $ms.Write([BitConverter]::GetBytes([uint16]33), 0, 2) # date
- $ms.Write([BitConverter]::GetBytes([uint32]$c['crc']), 0, 4)
- $ms.Write([BitConverter]::GetBytes([uint32]$c['size']), 0, 4)
- $ms.Write([BitConverter]::GetBytes([uint32]$c['size']), 0, 4)
- $ms.Write([BitConverter]::GetBytes([uint16]$c['name'].Length), 0, 2)
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # extra len
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # comment len
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # disk start
- $ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # internal attrs
- $ms.Write([BitConverter]::GetBytes([uint32]0), 0, 4) # external attrs
- $ms.Write([BitConverter]::GetBytes([uint32]$c['offset']), 0, 4)
- $ms.Write($c['name'], 0, $c['name'].Length)
-}
-$cdSize = $ms.Position - $cdStart
-
-# ---------- end of central directory ----------
-$ms.Write([BitConverter]::GetBytes([uint32]0x06054b50), 0, 4)
-$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # disk number
-$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # cd disk
-$ms.Write([BitConverter]::GetBytes([uint16]$central.Count), 0, 2)
-$ms.Write([BitConverter]::GetBytes([uint16]$central.Count), 0, 2)
-$ms.Write([BitConverter]::GetBytes([uint32]$cdSize), 0, 4)
-$ms.Write([BitConverter]::GetBytes([uint32]$cdStart), 0, 4)
-$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # comment len
-
-[System.IO.File]::WriteAllBytes($out, $ms.ToArray())
-$ms.Dispose()
-
-Write-Output ('CREATED ' + $out + ' size=' + (New-Object System.IO.FileInfo $out).Length)