日常报销和差旅报销都可以走通

This commit is contained in:
wandering
2026-06-12 12:06:06 +08:00
parent 3bd325e827
commit 07f5872027
21 changed files with 1342 additions and 810 deletions
+42 -4
View File
@@ -18,12 +18,13 @@
2. 解析发票和刷卡记录的金额,进行总额校验
- 发票总额 < 刷卡总额时发出 warning
3. 按金额降序排序
4. 根据数量关系选择匹配策略:
4. 文件名匹配(最高优先级):发票和刷卡记录的文件名(不含后缀)一致时直接匹配
5. 根据数量关系选择匹配策略:
- 数量相等 → 一对一匹配:按金额从大到小依次配对,相对容差内即匹配
- 发票更多 → 一对多匹配:对每张刷卡记录贪心凑金额,相对容差内结束
5. 构建以支付记录为主键的结果列表
6. 未匹配的发票单独作为一条记录(无刷卡信息)
7. 清理内部字段,输出支付记录列表
6. 构建以支付记录为主键的结果列表
7. 未匹配的发票单独作为一条记录(无刷卡信息)
8. 清理内部字段,输出支付记录列表
## 容差计算
@@ -45,6 +46,7 @@
match_invoices_to_cards(invoices, cards, tolerance) -> list[dict]
"""
from pathlib import Path
from typing import Any
from .. import get_logger
@@ -170,10 +172,18 @@ def _match(
"""执行匹配,返回 {card_index: [invoice_indices]} 的映射
tolerance 为相对容差比例(如 0.03 表示 3%)
匹配优先级(从高到低):
1. 文件名匹配:发票和刷卡记录的文件名(不含后缀)一致时直接匹配
2. 精确匹配:金额差 <= 0.01 元
3. 一对一 / 一对多贪心匹配:按金额容差匹配
"""
result: dict[int, list[int]] = {}
assigned: set[int] = set()
# ---- 阶段 0:文件名匹配(最高优先级)----
_match_by_filename(invoices, cards, assigned, result)
if len(invoices) == len(cards):
_match_one_to_one(invoices, cards, tolerance, assigned, result)
else:
@@ -182,6 +192,34 @@ def _match(
return result
def _match_by_filename(
invoices: list[dict[str, Any]],
cards: list[dict[str, Any]],
assigned: set[int],
result: dict[int, list[int]],
) -> None:
"""文件名匹配:发票和刷卡记录的文件名(不含后缀)一致时直接匹配"""
for card_idx, card in enumerate(cards):
card_name = card.get("_source_file", "")
if not card_name:
continue
card_stem = Path(card_name).stem
for idx, inv in enumerate(invoices):
if idx in assigned:
continue
inv_name = inv.get("_source_file", "")
if not inv_name:
continue
inv_stem = Path(inv_name).stem
if inv_stem == card_stem:
assigned.add(idx)
result[card_idx] = [idx]
log.info(f"[文件名匹配] {inv_name} ↔ {card_name}")
break
def _match_one_to_one(
invoices: list[dict[str, Any]],
cards: list[dict[str, Any]],