103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
"""中文大写金额与阿拉伯金额互转(发票大小写金额一致性校验用)。
|
||
|
||
支持 "壹仟陆佰伍拾元整"、"壹佰贰拾叁元肆角伍分"、"拾元整" 等常见发票大写写法;
|
||
同时兼容 "¥1,234.50" / "1234.50元" 等阿拉伯写法的清洗。
|
||
解析失败返回 None,由调用方降级为"待确认",不做硬性驳回。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from typing import Optional
|
||
|
||
_DIGITS = {
|
||
"零": 0,
|
||
"壹": 1,
|
||
"贰": 2,
|
||
"叁": 3,
|
||
"肆": 4,
|
||
"伍": 5,
|
||
"陆": 6,
|
||
"柒": 7,
|
||
"捌": 8,
|
||
"玖": 9,
|
||
}
|
||
_SMALL_UNITS = {"拾": 10, "佰": 100, "仟": 1000}
|
||
_GROUP_UNITS = {"万": 10**4, "亿": 10**8}
|
||
_ALL_KEYS = r"零壹贰叁肆伍陆柒捌玖拾佰仟万亿元圆角分整"
|
||
|
||
_TRAILING_CHARS = re.compile(r"[人民币¥¥\s,,元圆整]|人民币$")
|
||
|
||
|
||
def clean_amount_text(text: str) -> str:
|
||
"""清洗阿拉伯金额文本,返回可 float() 的字符串(失败返回原文本去掉常见缀符)。"""
|
||
cleaned = _TRAILING_CHARS.sub("", (text or "").strip())
|
||
return cleaned
|
||
|
||
|
||
def parse_cny_upper(text: str) -> Optional[float]:
|
||
"""解析中文大写金额为数值;无法解析时返回 None。"""
|
||
if not text:
|
||
return None
|
||
raw = (text or "").strip()
|
||
raw = raw.replace("人民币", "").replace("¥", "").replace("¥", "").strip()
|
||
raw = re.sub(r"[\s,,]", "", raw)
|
||
if not raw:
|
||
return None
|
||
if re.search(r"[0-9]", raw):
|
||
return None # 纯阿拉伯数字写法不属于大写金额
|
||
|
||
if not re.fullmatch(rf"[{_ALL_KEYS}]+", raw):
|
||
return None
|
||
|
||
int_part, frac_part = raw, ""
|
||
for sep in ("元", "圆"):
|
||
if sep in raw:
|
||
int_part, frac_part = raw.split(sep, 1)
|
||
break
|
||
|
||
try:
|
||
yuan = _parse_integer_section(int_part) if int_part else 0.0
|
||
except ValueError:
|
||
return None
|
||
|
||
jiao = fen = 0
|
||
m_jiao = re.search(r"([零壹贰叁肆伍陆柒捌玖])角", frac_part)
|
||
if m_jiao:
|
||
jiao = _DIGITS[m_jiao.group(1)]
|
||
m_fen = re.search(r"([零壹贰叁肆伍陆柒捌玖])分", frac_part)
|
||
if m_fen:
|
||
fen = _DIGITS[m_fen.group(1)]
|
||
return round(yuan + jiao / 10 + fen / 100, 2)
|
||
|
||
|
||
def _parse_integer_section(section: str) -> float:
|
||
"""解析整数段(元之前的部分),如 壹仟陆佰伍拾 / 拾贰万零壹佰。"""
|
||
if not section:
|
||
return 0.0
|
||
if section == "零":
|
||
return 0.0
|
||
|
||
total = 0.0
|
||
group_value = 0
|
||
number = 0
|
||
for ch in section:
|
||
if ch in _DIGITS:
|
||
number = _DIGITS[ch]
|
||
elif ch in _SMALL_UNITS:
|
||
unit = _SMALL_UNITS[ch]
|
||
group_value += (number if number else 1) * unit
|
||
number = 0
|
||
elif ch in _GROUP_UNITS:
|
||
unit = _GROUP_UNITS[ch]
|
||
group_value = (group_value + number) * unit
|
||
total += group_value
|
||
group_value = 0
|
||
number = 0
|
||
else:
|
||
raise ValueError(f"非法大写字符: {ch}")
|
||
total += group_value + number
|
||
if total <= 0:
|
||
raise ValueError("大写金额解析为 0")
|
||
return total
|