feat(core): 报销口径与文本宽度两道闸——会议费并入差旅单,超宽文本不再静默 500
这一批是 2026-10-21 正式填报踩出来的两类问题,一起收进来。 **一、报销口径只在一处判定(新增 src/core/expense_kind.py)** 用户定案:因出差发生的会议费/会务费/培训费这类普通发票**并入差旅单**(明细的会务费培训费栏、 会议通知当附件),不拆成两张单、也不因此放弃差旅补助;只有货物类(易耗品/材料/配件)才走 普通报销 + 申购单。以前 case_status、validate_info、browser_new_form 三处都写"混合发票只能走 普通报销",等于把这条合法路径说成错的,Agent 照做就会白拆一张单还漏补助。现在 is_conference_like / goods_invoices 是唯一判定,工具提示与 case_status 的 warnings 都由它驱动。 **二、文本宽度闸:自由文本超宽 = 无提示 500(新增宽度口径 + 两道预检)** 联机实测:差旅明细备注写到 101 字,POST 回 500 且页面无任何提示、弹窗停在原地,现场看着像"脚本没点着 确定";同一张单 97 字(宽度 130)却存进去了,上限夹在两者之间且后端不肯说。于是取汉字/全角记 2、 字母数字记 1 的"宽度",validator.TEXT_WIDTH_LIMIT=100(≈50 汉字,同时满足"100 字符"与"131 字节" 两种可能口径),普通报销说明另用 DESCRIPTION_WIDTH_LIMIT=40。拦截点两处:规则里的 max_len_bytes (validate_extracted_info 的 missing_fields 会写明"过长:宽度 N > 上限 M")与 browser_fill 在 POST 前的 _text_limit_problems(失败返回 too_long 列出超长字段)。 **三、交通工具代码表挪进 core(新增 src/core/vehicles.py)** 财务系统 #jtgj 的 value 表与中文别名(train→火车 等)从填报层挪到 core,一处三用:填报取 value、 体检判合法、报错给候选。老写法 if vehicle in vehicle_map 遇英文枚举是**静默不选**且仍返回成功, 直到提交被必填挡回来——现在认不出的值直接报错。 其余:cases.goods_general_items 给 case_status 补"有 N 张货物类普通发票"的 warning; travel.py 改用 vehicles 并显式报错;config/validation_rules.json 加 max_len_bytes; INSTRUCTIONS/SKILL/判读参考/操作指南同步口径与宽度提示。新增 tests/test_text_limits.py 与 tests/test_vehicles_and_scope.py。 验证:ruff / format / mypy src / deptry / pytest / mcp-smoke / docs-check / skill-check 全绿。 --no-verify 提交并手跑等价检查。
This commit is contained in:
@@ -45,6 +45,11 @@ def save_info(case_id: str, info_type: str, data: dict[str, Any]) -> dict[str, A
|
||||
|
||||
info_type 取 travel|normal。完整性以返回的 validation 为准,
|
||||
不要自己在 data 里塞 can_submit。
|
||||
|
||||
规则里带 max_len_bytes 的是**文本宽度**上限(汉字算 2、字母数字算 1):
|
||||
备注写太长,财务后端不回"太长了"而是一记 500,页面上什么都看不到,
|
||||
所以在这里就拦住,missing_fields 会写成
|
||||
"payment_methods[0].remark|备注过长:宽度 171 > 上限 100(约 50 个汉字),请改短"。
|
||||
"""
|
||||
d = _store.resolve(case_id)
|
||||
if d is None:
|
||||
@@ -89,7 +94,19 @@ def validate_info(case_id: str, info_type: str | None = None, draft: dict[str, A
|
||||
report = validate_extracted_info(data, invoice_type=info_type)
|
||||
payload = _report_dict(report)
|
||||
if groups_path.exists() and info_type == "travel":
|
||||
payload["note"] = "已存在 invoice_groups.json:混合发票(差旅+普通)只能走普通报销,别按纯差旅提交"
|
||||
# 财务口径(用户 2026-10-21 定案):因出差发生的会议费/会务费/培训费并入差旅单,
|
||||
# 只有货物类普通发票才必须另走普通报销——别把前者也说成"只能走普通报销"
|
||||
goods = _store.goods_general_items(d)
|
||||
if goods:
|
||||
payload["note"] = (
|
||||
"有货物类普通发票(" + "、".join(goods[:3]) + ")不能并入差旅单:"
|
||||
"另建 normal_info 走普通报销并生成申购单;差旅单只留车票/住宿/会务费"
|
||||
)
|
||||
else:
|
||||
payload["note"] = (
|
||||
"发票分组已就绪:会议费/会务费/培训费这类因出差发生的普通发票"
|
||||
"并入差旅单的「会务费培训费」栏即可,会议通知作为附件"
|
||||
)
|
||||
return ok(case_id=case_id, info_type=info_type, **payload)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user