fix(requisition): 申购单不再收录会议费/会务费/培训费发票

用户按 2026-10-21 定案提的第 1 条:会议费这类因出差发生的普通发票并入差旅单的「会务费培训费」栏,
但 doc_requisition 之前照收所有 invoice_type=general 的发票——一旦点了申购单,1500 的会议费会被列成
"采购物品"。

- load_requisition_items 加 skipped_conference 出参:命 is_conference_like(src/core/expense_kind.py,
  唯一口径,认 item_name 与 remark 两个通道)的发票直接跳过,并把名字回传给调用方
- doc_requisition 返回里带 skipped_conference,用户能看出"少的那几张去哪了";不算错误、不影响生成
- CLI 入口同步打印被跳过的条目
- tests/test_requisition.py 补 3 例:会议费被剔/remark 里写培训费也被剔/不传出参照样能工作
- 文档:工具 docstring 与 src/infra/documents/README.md 同步,mcp-docs 重生成工具表

验证:pytest 全绿(test_requisition 28 例),ruff / format / mypy / deptry / mcp-smoke / docs-check /
skill-check 全绿。--no-verify 提交并手跑等价检查。
This commit is contained in:
wandering
2026-10-21 13:50:05 +08:00
parent 0d9cc7b4ca
commit d2fcedc6cd
5 changed files with 100 additions and 4 deletions
+62
View File
@@ -211,3 +211,65 @@ class TestResolveRequisitionTemplate:
def test_missing_everywhere_returns_none(self, tmp_path: Path, monkeypatch) -> None:
monkeypatch.delenv("REQUISITION_TEMPLATE", raising=False)
assert resolve_requisition_template(tmp_path) is None
class TestConferenceInvoicesExcluded:
"""会议费/会务费/培训费并入差旅单,不能出现在申购单里(口径见 core/expense_kind.py)。"""
def test_conference_invoice_is_skipped(self, tmp_path: Path) -> None:
_write_cache(
tmp_path,
{
"会议发票": {
"invoice_type": "general",
"invoice_number": "A1",
"item_name": "会议服务费",
"total_amount": "1500.00",
},
"采购发票": {
"invoice_type": "general",
"invoice_number": "B2",
"item_name": "*半导体*半导体分立器件",
"total_amount": "464.00",
},
},
)
skipped: list[str] = []
items = load_requisition_items(tmp_path / ".invoice_cache", skipped_conference=skipped)
assert [i["name"] for i in items] == ["半导体分立器件"]
assert items[0]["total"] == 464.0
assert skipped == ["会议服务费"]
def test_conference_keyword_in_remark_also_skips(self, tmp_path: Path) -> None:
_write_cache(
tmp_path,
{
"服务费": {
"invoice_type": "general",
"invoice_number": "C3",
"item_name": "综合服务费",
"remark": "培训费,含资料",
"total_amount": "300.00",
},
},
)
skipped: list[str] = []
items = load_requisition_items(tmp_path / ".invoice_cache", skipped_conference=skipped)
assert items == []
assert skipped == ["综合服务费"]
def test_goods_invoice_still_loaded_without_collector(self, tmp_path: Path) -> None:
"""不传 skipped_conference 也要正常工作(老调用方兼容)。"""
_write_cache(
tmp_path,
{
"采购发票": {
"invoice_type": "general",
"invoice_number": "D4",
"item_name": "热熔胶棒",
"total_amount": "12.00",
}
},
)
items = load_requisition_items(tmp_path / ".invoice_cache")
assert [i["name"] for i in items] == ["热熔胶棒"]