130 lines
4.5 KiB
Python
130 lines
4.5 KiB
Python
# 入站和出站共用的字段解析。
|
||
#
|
||
# 入站把 camelCase JSON 收成落表值。缺字段、类型不对或超长时返回
|
||
# (None, 错误说明),调用方据此标 REJECTED。omsId / fid / 数量必须是 JSON
|
||
# 整数,不把 "10" 当 10。
|
||
#
|
||
# 出站读库行,用 as_int / as_str / money4 收成文档字段。金额是四位小数字符串。
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import date, datetime
|
||
from decimal import Decimal, ROUND_HALF_UP, InvalidOperation
|
||
|
||
|
||
# 把接口字段收成去掉首尾空白的字符串,并检查是否超过 max_len。
|
||
#
|
||
# required=False 时,缺省或空白返回 (None, None)。
|
||
def parse_str(
|
||
value, field: str, max_len: int, *, required: bool = True
|
||
) -> tuple[str | None, str | None]:
|
||
if value is None or (isinstance(value, str) and not value.strip()):
|
||
if required:
|
||
return None, f"缺少必填字段 {field}"
|
||
return None, None
|
||
if not isinstance(value, str):
|
||
return None, f"{field} 必须是字符串"
|
||
text = value.strip()
|
||
if len(text) > max_len:
|
||
return None, f"{field} 超出长度"
|
||
return text, None
|
||
|
||
|
||
# 把接口字段收成 JSON 整数。布尔值不算整数。
|
||
def parse_int(value, field: str, *, required: bool = True) -> tuple[int | None, str | None]:
|
||
if value is None or value == "":
|
||
if required:
|
||
return None, f"缺少必填字段 {field}"
|
||
return None, None
|
||
if type(value) is int:
|
||
return value, None
|
||
return None, f"{field} 必须是整数"
|
||
|
||
|
||
# 把接口字段收成开区间 (0, max_exclusive) 内的 JSON 整数。
|
||
def parse_positive_int(
|
||
value, field: str, max_exclusive: int
|
||
) -> tuple[int | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, f"缺少必填字段 {field}"
|
||
if type(value) is int and 0 < value < max_exclusive:
|
||
return value, None
|
||
return None, f"{field} 必须是整数"
|
||
|
||
|
||
# 把 omsId 收成正整数,上限跟表 1 NUMBER(20)。
|
||
def parse_oms_id(value) -> tuple[int | None, str | None]:
|
||
return parse_positive_int(value, "omsId", 10**20)
|
||
|
||
|
||
# 把接口 3 的 fid 收成正整数,上限跟表 3 NUMBER(19)。
|
||
def parse_fid(value) -> tuple[int | None, str | None]:
|
||
return parse_positive_int(value, "fid", 10**19)
|
||
|
||
|
||
# 把接口字段收成 Decimal。布尔值拒绝;字符串、int、float 可转。
|
||
def parse_decimal(value, field: str) -> tuple[Decimal | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, f"缺少必填字段 {field}"
|
||
if type(value) is bool:
|
||
return None, f"{field} 必须是数字"
|
||
try:
|
||
if isinstance(value, Decimal):
|
||
amount = value
|
||
elif isinstance(value, str):
|
||
amount = Decimal(value.strip())
|
||
elif type(value) is int:
|
||
amount = Decimal(value)
|
||
elif isinstance(value, float):
|
||
amount = Decimal(str(value))
|
||
else:
|
||
return None, f"{field} 必须是数字"
|
||
except (InvalidOperation, ValueError):
|
||
return None, f"{field} 必须是数字"
|
||
return amount, None
|
||
|
||
|
||
# 把接口字段收成 date。接受 YYYY-MM-DD 字符串、date、datetime。
|
||
def parse_date(value, field: str) -> tuple[date | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, f"缺少必填字段 {field}"
|
||
if isinstance(value, datetime):
|
||
return value.date(), None
|
||
if isinstance(value, date):
|
||
return value, None
|
||
if not isinstance(value, str):
|
||
return None, f"{field} 必须是 YYYY-MM-DD"
|
||
try:
|
||
return date.fromisoformat(value.strip()), None
|
||
except ValueError:
|
||
return None, f"{field} 必须是 YYYY-MM-DD"
|
||
|
||
|
||
# 出站整数。None 保持 None。JSON 整数走 parse_int,库里的 Decimal 交给 int。
|
||
def as_int(value) -> int | None:
|
||
if value is None:
|
||
return None
|
||
parsed, error = parse_int(value, "value", required=False)
|
||
if error is None and parsed is not None:
|
||
return parsed
|
||
return int(value)
|
||
|
||
|
||
# 出站字符串。None 和空白当成 None,其余转 str,不改非空原文。
|
||
def as_str(value) -> str | None:
|
||
if value is None:
|
||
return None
|
||
text = str(value)
|
||
if text.strip() == "":
|
||
return None
|
||
return text
|
||
|
||
|
||
# 金额四舍五入到四位小数,再格式成字符串。
|
||
def money4(value) -> str:
|
||
amount, error = parse_decimal(value, "amount")
|
||
if error is not None or amount is None:
|
||
raise ValueError(error or "缺少金额")
|
||
quantized = amount.quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)
|
||
return f"{quantized:.4f}"
|