fix(cash): 智享通凭证导入放开负数金额(红字记账)+日期来源三级优先(行内voucher_date优先) — 修复423张凭证整张拒收 (dama-zxt-import-endpoint-fix-20260908)
This commit is contained in:
@@ -12,12 +12,12 @@
|
||||
Bearer token,Bot hook 只有 X-BOT-KEY 到不了,故同前缀另挂本 router(FastAPI 允许多 router 同前缀)。
|
||||
|
||||
校验口径(对齐财务BOT映射说明 cma_field_mapping_finance_20260906.md + 任务单验收):
|
||||
1. 行级:voucher_no 必有;debit/credit 字符串→float round2,非负,借贷不同时0
|
||||
1. 行级:voucher_no 必有;debit/credit 字符串→float round2,允许负数(红字记账,负数行计数 neg_rows),借贷不同时0
|
||||
2. 结转行:summary 含"结转" → carry_forward=1(zxt 113行"期间损益结转"100%命中)
|
||||
3. 凭证级借贷平衡(每 Voucher_ID Σ借=Σ贷 容差0.01)—— 批级平衡对 zxt 快照不适用
|
||||
(已知单边凭证 30694 仅贷5479.65),不平衡凭证整张拒收记异常清单,不整批拒绝
|
||||
4. 日期/period:zxt 行无 voucher_date 字段,日期从 summary 前缀 YYYY-MM-DD 解析;
|
||||
解析不到的行(期间损益结转等)按 pending 拒收,不冒充文件生成时间
|
||||
4. 日期/period:三级优先——行内 voucher_date/VoucherDate/BillDate 字段 → summary 前缀
|
||||
YYYY-MM-DD 解析 → 均无则按 pending 拒收;不冒充文件生成时间/当前时间
|
||||
5. 科目:subject_code/subject_name 全 null(智享通侧未带)。解析优先级:
|
||||
a) 行内 subject_code/subject_name 有值 → 直用
|
||||
b) 无 → 查请求体 subject_map {CwAccountID: {subject_code, subject_name}}
|
||||
@@ -34,7 +34,7 @@ import hashlib
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime
|
||||
from datetime import date, datetime
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Request
|
||||
@@ -87,6 +87,33 @@ def _parse_date_from_summary(summary: str):
|
||||
return None
|
||||
|
||||
|
||||
def _parse_date_value(raw) -> Optional[datetime]:
|
||||
"""行内日期字段解析(voucher_date/VoucherDate/BillDate)。
|
||||
datetime/date 对象直用;字符串 strip 后 fromisoformat(YYYY-MM-DD / YYYY-MM-DD[ T]HH:MM:SS),
|
||||
失败回退 _DATE_RE search(YYYY/MM/DD 等);仍取不到返回 None——严禁冒充当前时间/文件时间。"""
|
||||
if raw is None:
|
||||
return None
|
||||
if isinstance(raw, datetime):
|
||||
return datetime(raw.year, raw.month, raw.day)
|
||||
if isinstance(raw, date):
|
||||
return datetime(raw.year, raw.month, raw.day)
|
||||
s = str(raw).strip()
|
||||
if not s:
|
||||
return None
|
||||
try:
|
||||
dt = datetime.fromisoformat(s)
|
||||
return datetime(dt.year, dt.month, dt.day)
|
||||
except ValueError:
|
||||
pass
|
||||
m = _DATE_RE.search(s)
|
||||
if not m:
|
||||
return None
|
||||
try:
|
||||
return datetime(int(m.group(1)), int(m.group(2)), int(m.group(3)))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _is_carry_forward(summary: str) -> bool:
|
||||
"""结转行识别(subject_name 缺失只能靠 summary):zxt 期间损益结转 100% 命中"""
|
||||
return "结转" in (summary or "")
|
||||
@@ -211,17 +238,23 @@ def api_import_vouchers_json(
|
||||
parsed = []
|
||||
pending_undated = 0
|
||||
pending_dict = 0
|
||||
neg_rows = 0
|
||||
for idx, raw in enumerate(norm_rows):
|
||||
voucher_no = _norm_str(raw.get("voucher_no") or raw.get("Voucher_ID"))
|
||||
if not voucher_no:
|
||||
errors.append({"row": idx + 1, "field": "voucher_no", "reason": "凭证号为空"})
|
||||
continue
|
||||
|
||||
date_obj = _parse_date_from_summary(_norm_str(raw.get("summary")))
|
||||
# 三级优先:行内日期字段 → summary 前缀 → 拒收
|
||||
date_obj = _parse_date_value(
|
||||
raw.get("voucher_date") or raw.get("VoucherDate") or raw.get("BillDate")
|
||||
)
|
||||
if date_obj is None:
|
||||
date_obj = _parse_date_from_summary(_norm_str(raw.get("summary")))
|
||||
if date_obj is None:
|
||||
pending_undated += 1
|
||||
errors.append({"row": idx + 1, "field": "voucher_date",
|
||||
"reason": f"summary无日期(待日期来源): {_norm_str(raw.get('summary'))[:40]}"})
|
||||
"reason": f"行内日期与summary均无日期(待日期来源): {_norm_str(raw.get('summary'))[:40]}"})
|
||||
continue
|
||||
|
||||
debit = _parse_amount(raw.get("debit"))
|
||||
@@ -233,8 +266,7 @@ def api_import_vouchers_json(
|
||||
errors.append({"row": idx + 1, "field": "credit", "reason": f"贷方金额不是有效数字: {raw.get('credit')}"})
|
||||
continue
|
||||
if debit < 0 or credit < 0:
|
||||
errors.append({"row": idx + 1, "field": "amount", "reason": "金额不能为负"})
|
||||
continue
|
||||
neg_rows += 1 # 红字记账允许负数入库,仅计数不拒收
|
||||
if debit == 0 and credit == 0:
|
||||
errors.append({"row": idx + 1, "field": "amount", "reason": "借贷金额不能同时为0"})
|
||||
continue
|
||||
@@ -275,6 +307,7 @@ def api_import_vouchers_json(
|
||||
"entity_id": entity_id,
|
||||
"pending_undated": pending_undated,
|
||||
"pending_dict_miss": pending_dict,
|
||||
"neg_rows": neg_rows,
|
||||
"hint": "科目字典:请协调智享通推送方补 subject_code/subject_name,或在 body 带 subject_map,或建 zxt_account_dict 表",
|
||||
}
|
||||
|
||||
@@ -406,6 +439,7 @@ def api_import_vouchers_json(
|
||||
"carry_forward_count": carry_forward_count,
|
||||
"pending_undated": pending_undated,
|
||||
"pending_dict_miss": pending_dict,
|
||||
"neg_rows": neg_rows,
|
||||
"batch": batch,
|
||||
"entity_id": entity_id,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user