AutoRpa/rpa_oms/app/parse.py

130 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 入站和出站共用的字段解析。
#
# 入站把 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}"