chore: prepare docfill project for gitea

This commit is contained in:
赵义仑
2026-06-24 09:45:35 +08:00
parent ca444bd5d0
commit 32e74d0f43
38 changed files with 2915 additions and 162 deletions

View File

@@ -0,0 +1,473 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import shutil
import sqlite3
from pathlib import Path
from docx import Document
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_ALIGNMENT
from docx.shared import Inches, Pt
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font, PatternFill, Border, Side
from openpyxl.utils import get_column_letter
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "manual_tests"
def main() -> None:
if OUT.exists():
shutil.rmtree(OUT)
OUT.mkdir(parents=True)
build_excel_to_word(OUT / "01_excel_to_word")
build_sqlite_to_excel(OUT / "02_sqlite_to_excel")
build_excel_to_excel(OUT / "03_excel_to_excel")
build_filter_work_order_to_word(OUT / "04_filter_work_order_to_word")
write_readme()
print(OUT)
def build_excel_to_word(path: Path) -> None:
path.mkdir(parents=True)
write_daily_source_xlsx(path / "source_daily_data.xlsx")
write_daily_word_template(path / "daily_report_template.docx")
write_json(
path / "task.json",
{
"vars": {"date": "2026-06-18", "batch": "默认班次"},
"data": {
"type": "excel",
"path": "./source_daily_data.xlsx",
"sheet": "Daily",
"header_row": 1,
},
"template": {"path": "./daily_report_template.docx"},
"output": {
"path": "./out/daily_{{date}}_{{name}}_{{_row}}.docx",
"overwrite": True,
},
},
)
def build_sqlite_to_excel(path: Path) -> None:
path.mkdir(parents=True)
write_summary_sqlite(path / "source_summary.db")
write_summary_excel_template(path / "department_summary_template.xlsx")
write_json(
path / "task.json",
{
"vars": {"date": "2026-06-18"},
"data": {
"type": "sqlite",
"path": "./source_summary.db",
"query": "select department, report_date, total_count, abnormal_count, owner from department_summary where report_date = ? order by department",
"params": ["{{date}}"],
},
"template": {"path": "./department_summary_template.xlsx"},
"output": {
"path": "./out/summary_{{department}}_{{_row}}.xlsx",
"overwrite": True,
},
},
)
def build_excel_to_excel(path: Path) -> None:
path.mkdir(parents=True)
write_audit_source_xlsx(path / "source_audit_data.xlsx")
write_audit_excel_template(path / "audit_template.xlsx")
write_json(
path / "task.json",
{
"vars": {"date": "2026-06-18"},
"data": {
"type": "excel",
"path": "./source_audit_data.xlsx",
"sheet": "Audit",
"header_row": 2,
},
"template": {"path": "./audit_template.xlsx"},
"output": {
"path": "./out/audit_{{date}}_{{employee}}_{{_row}}.xlsx",
"overwrite": True,
},
},
)
def build_filter_work_order_to_word(path: Path) -> None:
path.mkdir(parents=True)
write_work_order_source_xlsx(path / "source_work_orders.xlsx")
write_work_order_word_template(path / "work_order_template.docx")
write_json(
path / "task.json",
{
"vars": {"date": "2026-06-18"},
"data": {
"type": "excel",
"path": "./source_work_orders.xlsx",
"sheet": "工单",
"header_row": 1,
},
"filter": {
"equals": {
"日期": "{{date}}",
"工单编号": "{{ticket_no}}",
},
"expect": "one",
},
"template": {"path": "./work_order_template.docx"},
"output": {
"path": "./out/work_order_{{日期}}_{{工单编号}}.docx",
"overwrite": True,
},
},
)
def write_daily_source_xlsx(path: Path) -> None:
rows = [
["name", "department", "done", "plan", "risk"],
["张三/测试", "营销部", "完成 A 系统订单核对 128 条;同步 B 系统回款 35 笔", "跟进 3 条异常订单并补充截图", ""],
["李四", "客服部", "整理投诉工单 21 条;完成日报提交", "复核高优先级工单并同步主管", "等待业务确认 1 条"],
]
write_table_workbook(path, "Daily", rows, title="日报源数据")
def write_daily_word_template(path: Path) -> None:
doc = Document()
section = doc.sections[0]
section.top_margin = Inches(0.8)
section.bottom_margin = Inches(0.8)
section.left_margin = Inches(0.85)
section.right_margin = Inches(0.85)
title = doc.add_paragraph()
title_run = title.add_run("员工日报(测试模板)")
title_run.bold = True
title_run.font.size = Pt(18)
meta = doc.add_table(rows=4, cols=2)
meta.alignment = WD_TABLE_ALIGNMENT.LEFT
set_table_style(meta)
labels = ["日期", "员工", "部门", "班次"]
values = ["{{date}}", "{{na", "{{department}}", "{{batch}}"]
for i, label in enumerate(labels):
meta.cell(i, 0).text = label
if label == "员工":
p = meta.cell(i, 1).paragraphs[0]
p.add_run(values[i])
p.add_run("me}}")
else:
meta.cell(i, 1).text = values[i]
for heading, placeholder in [
("今日完成", "{{done}}"),
("明日计划", "{{plan}}"),
("风险备注", "{{risk}}"),
]:
doc.add_paragraph()
h = doc.add_paragraph()
r = h.add_run(heading)
r.bold = True
r.font.size = Pt(12)
p = doc.add_paragraph()
p.add_run(placeholder)
doc.save(path)
def write_summary_sqlite(path: Path) -> None:
conn = sqlite3.connect(path)
try:
conn.executescript(
"""
create table department_summary (
department text,
report_date text,
total_count integer,
abnormal_count integer,
owner text
);
insert into department_summary values
('客服部', '2026-06-18', 76, 0, '李四'),
('营销部', '2026-06-18', 128, 3, '张三'),
('营销部', '2026-06-17', 115, 2, '张三');
"""
)
conn.commit()
finally:
conn.close()
def write_summary_excel_template(path: Path) -> None:
wb = Workbook()
ws = wb.active
ws.title = "Summary"
ws.append(["部门日报汇总(测试模板)", ""])
ws.append(["部门", "{{department}}"])
ws.append(["日期", "{{report_date}}"])
ws.append(["负责人", "{{owner}}"])
ws.append(["处理总数", "{{total_count}}"])
ws.append(["异常数", "{{abnormal_count}}"])
style_sheet(ws, title_rows={1})
ws.column_dimensions["A"].width = 18
ws.column_dimensions["B"].width = 32
wb.save(path)
def write_audit_source_xlsx(path: Path) -> None:
rows = [
["日报质检导出"],
["employee", "date", "item", "score", "comment"],
["王五", "2026-06-18", "日报完整性", "92", "字段齐全,说明清楚"],
["赵六", "2026-06-18", "系统截图一致性", "87", "缺少 1 张截图,需要补充"],
]
write_table_workbook(path, "Audit", rows, title=None)
def write_audit_excel_template(path: Path) -> None:
wb = Workbook()
ws = wb.active
ws.title = "AuditReport"
ws.append(["日报质检结果(测试模板)", ""])
ws.append(["日期", "{{date}}"])
ws.append(["员工", "{{employee}}"])
ws.append(["检查项", "{{item}}"])
ws.append(["得分", "{{score}}"])
ws.append(["评语", "{{comment}}"])
style_sheet(ws, title_rows={1})
ws.column_dimensions["A"].width = 16
ws.column_dimensions["B"].width = 42
wb.save(path)
def write_work_order_source_xlsx(path: Path) -> None:
rows = [
["日期", "工单编号", "客户名称", "系统来源", "处理结果", "下一步"],
["2026-06-18", "GD-001", "A 公司", "PMS3.0", "已派单", "等待现场反馈"],
["2026-06-18", "GD-002", "B 公司", "D5000", "已完成", "归档并同步日报"],
["2026-06-19", "GD-001", "C 公司", "风控平台", "待复核", "补充截图"],
]
write_table_workbook(path, "工单", rows, title="工单源数据")
def write_work_order_word_template(path: Path) -> None:
doc = Document()
section = doc.sections[0]
section.top_margin = Inches(0.8)
section.bottom_margin = Inches(0.8)
section.left_margin = Inches(0.85)
section.right_margin = Inches(0.85)
title = doc.add_paragraph()
title_run = title.add_run("工单处理记录(筛选测试模板)")
title_run.bold = True
title_run.font.size = Pt(18)
table = doc.add_table(rows=6, cols=2)
table.alignment = WD_TABLE_ALIGNMENT.LEFT
set_table_style(table)
rows = [
("日期", "{{日期}}"),
("工单编号", "{{工单编号}}"),
("客户名称", "{{客户名称}}"),
("系统来源", "{{系统来源}}"),
("处理结果", "{{处理结果}}"),
("下一步", "{{下一步}}"),
]
for idx, (label, value) in enumerate(rows):
table.cell(idx, 0).text = label
table.cell(idx, 1).text = value
doc.save(path)
def write_table_workbook(path: Path, sheet: str, rows: list[list[str]], title: str | None) -> None:
wb = Workbook()
ws = wb.active
ws.title = sheet
for row in rows:
ws.append(row)
title_rows = {1} if title else set()
style_sheet(ws, title_rows=title_rows)
for idx, width in enumerate([18, 16, 44, 36, 28], start=1):
ws.column_dimensions[get_column_letter(idx)].width = width
wb.save(path)
def style_sheet(ws, title_rows: set[int]) -> None:
header_fill = PatternFill("solid", fgColor="D9EAF7")
title_fill = PatternFill("solid", fgColor="1F4E79")
thin = Side(style="thin", color="D9E2F3")
border = Border(top=thin, left=thin, right=thin, bottom=thin)
for row in ws.iter_rows():
for cell in row:
cell.alignment = Alignment(vertical="center", wrap_text=True)
cell.border = border
if cell.row in title_rows:
cell.font = Font(bold=True, color="FFFFFF", size=13)
cell.fill = title_fill
elif cell.row == min(title_rows or {1}) + 1 or (not title_rows and cell.row == 1):
cell.font = Font(bold=True)
cell.fill = header_fill
ws.freeze_panes = "A2"
def set_table_style(table) -> None:
for row in table.rows:
for cell in row.cells:
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
for paragraph in cell.paragraphs:
for run in paragraph.runs:
run.font.size = Pt(10.5)
def write_json(path: Path, data: dict) -> None:
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
def write_readme() -> None:
text = """# docfill 手工测试素材
本目录提供四组可直接运行的本地测试素材,用于验证 `docfill` 对筛选、Word、Excel、Excel 数据源和 SQLite 数据源的支持情况。
运行前进入项目根目录:
```bash
cd /Users/zhaoyilun/Documents/xls_doc
```
构建工具:
```bash
bash scripts/build.sh
```
## 案例 1Excel 数据生成 Word 日报
目录:
```text
manual_tests/01_excel_to_word
```
运行命令:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/01_excel_to_word/task.json --var batch=早班 --verbose
```
输出示例:
```text
generated 2 file(s)
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/01_excel_to_word/out/daily_2026-06-18_张三_测试_1.docx
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/01_excel_to_word/out/daily_2026-06-18_李四_2.docx
```
## 案例 2SQLite 数据生成 Excel 汇总表
目录:
```text
manual_tests/02_sqlite_to_excel
```
运行命令:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/02_sqlite_to_excel/task.json --var date=2026-06-18 --verbose
```
输出示例:
```text
generated 2 file(s)
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/02_sqlite_to_excel/out/summary_客服部_1.xlsx
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/02_sqlite_to_excel/out/summary_营销部_2.xlsx
```
## 案例 3Excel 数据生成 Excel 质检表
目录:
```text
manual_tests/03_excel_to_excel
```
运行命令:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/03_excel_to_excel/task.json --var date=2026-06-18 --verbose
```
输出示例:
```text
generated 2 file(s)
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/03_excel_to_excel/out/audit_2026-06-18_王五_1.xlsx
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/03_excel_to_excel/out/audit_2026-06-18_赵六_2.xlsx
```
## 案例 4按日期和工单编号筛选一条工单并生成 Word
目录:
```text
manual_tests/04_filter_work_order_to_word
```
输入数据内容:
```text
日期 工单编号 客户名称 系统来源 处理结果 下一步
2026-06-18 GD-001 A 公司 PMS3.0 已派单 等待现场反馈
2026-06-18 GD-002 B 公司 D5000 已完成 归档并同步日报
2026-06-19 GD-001 C 公司 风控平台 待复核 补充截图
```
运行命令:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002 --verbose
```
输出示例:
```text
generated 1 file(s)
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/out/work_order_2026-06-18_GD-002.docx
```
查看结果:
```bash
open manual_tests/04_filter_work_order_to_word/out
```
## 重新生成素材
如需重新生成全部手工测试文件,执行:
```bash
/Users/zhaoyilun/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 scripts/make-manual-test-materials.py
```
输出示例:
```text
/Users/zhaoyilun/Documents/xls_doc/manual_tests
```
"""
(OUT / "README.md").write_text(text, encoding="utf-8")
if __name__ == "__main__":
main()

View File

@@ -25,6 +25,10 @@ func main() {
fmt.Fprintf(os.Stderr, "write excel-to-word fixtures: %v\n", err)
os.Exit(1)
}
if err := writeExcelFilterToWord(filepath.Join(*base, "excel-filter-to-word")); err != nil {
fmt.Fprintf(os.Stderr, "write excel-filter-to-word fixtures: %v\n", err)
os.Exit(1)
}
if err := writeSQLiteToExcel(filepath.Join(*base, "sqlite-to-excel")); err != nil {
fmt.Fprintf(os.Stderr, "write sqlite-to-excel fixtures: %v\n", err)
os.Exit(1)
@@ -45,6 +49,21 @@ func writeExcelToWord(dir string) error {
return writeDocx(filepath.Join(dir, "template.docx"), `<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>员工:{{na</w:t></w:r><w:r><w:t>me}}</w:t></w:r></w:p><w:p><w:r><w:t>日期:{{date}}</w:t></w:r></w:p><w:p><w:r><w:t>完成:{{done}}</w:t></w:r></w:p><w:p><w:r><w:t>计划:{{plan}}</w:t></w:r></w:p></w:body></w:document>`)
}
func writeExcelFilterToWord(dir string) error {
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
if err := writeWorkbook(filepath.Join(dir, "data.xlsx"), "Tickets", [][]string{
{"date", "ticket_no", "customer", "result"},
{"2026-06-18", "GD-001", "A 公司", "已派单"},
{"2026-06-18", "GD-002", "B 公司", "已完成"},
{"2026-06-19", "GD-001", "C 公司", "待复核"},
}); err != nil {
return err
}
return writeDocx(filepath.Join(dir, "template.docx"), `<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>工单:{{ticket_no}}</w:t></w:r></w:p><w:p><w:r><w:t>日期:{{date}}</w:t></w:r></w:p><w:p><w:r><w:t>客户:{{customer}}</w:t></w:r></w:p><w:p><w:r><w:t>结果:{{result}}</w:t></w:r></w:p></w:body></w:document>`)
}
func writeSQLiteToExcel(dir string) error {
if err := os.MkdirAll(dir, 0o755); err != nil {
return err

View File

@@ -17,10 +17,13 @@ cp -R examples "$TMP/examples"
go run ./scripts/smoke-fixtures --base "$TMP/examples"
"$TMP/docfill" run -c "$TMP/examples/excel-to-word/task.json" --var date=2026-06-18
"$TMP/docfill" run -c "$TMP/examples/excel-filter-to-word/task.json" --var date=2026-06-18 --var ticket_no=GD-002
"$TMP/docfill" run -c "$TMP/examples/sqlite-to-excel/task.json" --var date=2026-06-18
test -f "$TMP/examples/excel-to-word/out/daily_2026-06-18_张三_1.docx"
test -f "$TMP/examples/excel-to-word/out/daily_2026-06-18_李四_2.docx"
test -f "$TMP/examples/excel-filter-to-word/out/ticket_2026-06-18_GD-002.docx"
test ! -f "$TMP/examples/excel-filter-to-word/out/ticket_2026-06-18_GD-001.docx"
xlsx_count="$(find "$TMP/examples/sqlite-to-excel/out" -type f -name '*.xlsx' | wc -l | tr -d ' ')"
if [[ "$xlsx_count" != "2" ]]; then