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