171 lines
5.5 KiB
Python
171 lines
5.5 KiB
Python
"""接口 1 逐条校验与落表。不跑选品。
|
||
|
||
入参 camelCase,落表 snake_case。长度跟 VARCHAR2。
|
||
|
||
单条结果:
|
||
- ACCEPTED:入库,deal_flag=0,等选品作业捞
|
||
- DUPLICATE:库里已有同一 omsId,不更新、不重跑
|
||
- CONFLICT:同一请求里 omsId 出现过(前一条哪怕被拒,后一条也算冲突)
|
||
- REJECTED:字段不过,不入库
|
||
|
||
混批 HTTP 仍 200。omsId / 数量必须是 JSON 整数,不把 "10" 当 10。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import date, datetime
|
||
from decimal import Decimal, InvalidOperation
|
||
|
||
from .repo import DuplicateOmsId
|
||
|
||
# (接口字段, 表列, 最大字符数)
|
||
_STRING_FIELDS = (
|
||
("barCode", "bar_code", 40),
|
||
("goodsId", "goods_id", 20),
|
||
("goodsName", "goods_name", 200),
|
||
("goodsSpec", "goods_spec", 100),
|
||
("manufacturer", "manufacturer", 100),
|
||
("approvalNo", "approval_no", 100),
|
||
("unit", "unit", 20),
|
||
("customerId", "customer_id", 20),
|
||
("customerName", "customer_name", 200),
|
||
("orgCode", "org_code", 10),
|
||
("goodsMdmCode", "goods_mdm_code", 100),
|
||
("customerMdmCode", "customer_mdm_code", 100),
|
||
)
|
||
|
||
_INT_FIELDS = (
|
||
("saleQty", "sale_qty"),
|
||
("planPurchaseQty", "plan_purchase_qty"),
|
||
)
|
||
|
||
|
||
def receive_sales_goods(rows: list, repo) -> list[dict]:
|
||
seen: set[int] = set()
|
||
results: list[dict] = []
|
||
for row in rows:
|
||
results.append(_receive_one(row, repo, seen))
|
||
return results
|
||
|
||
|
||
def _receive_one(row: object, repo, seen: set[int]) -> dict:
|
||
if not isinstance(row, dict):
|
||
return _result(None, "REJECTED", "明细必须是对象")
|
||
|
||
oms_id, oms_error = _parse_oms_id(row.get("omsId"))
|
||
if oms_id is not None and oms_id in seen:
|
||
return _result(oms_id, "CONFLICT", "同一请求中 omsId 重复")
|
||
if oms_id is not None:
|
||
seen.add(oms_id)
|
||
|
||
parsed, error = _parse_row(row, oms_id, oms_error)
|
||
if error:
|
||
_camel, message = error
|
||
return _result(row.get("omsId"), "REJECTED", message)
|
||
|
||
try:
|
||
repo.insert(parsed)
|
||
except DuplicateOmsId:
|
||
return _result(parsed["oms_id"], "DUPLICATE", "订单中心序号已存在,未重复入库")
|
||
|
||
return _result(parsed["oms_id"], "ACCEPTED", "接收成功,等待RPA处理")
|
||
|
||
|
||
def _parse_row(row: dict, oms_id: int | None, oms_error: str | None):
|
||
if oms_error:
|
||
return None, ("omsId", oms_error)
|
||
|
||
record: dict = {"oms_id": oms_id, "deal_flag": 0}
|
||
for camel, snake, max_len in _STRING_FIELDS:
|
||
value, error = _parse_str(row.get(camel), camel, max_len)
|
||
if error:
|
||
return None, (camel, error)
|
||
record[snake] = value
|
||
|
||
order_date, error = _parse_date(row.get("orderDate"))
|
||
if error:
|
||
return None, ("orderDate", error)
|
||
record["order_date"] = order_date
|
||
|
||
sale_price, error = _parse_decimal(row.get("salePrice"), "salePrice")
|
||
if error:
|
||
return None, ("salePrice", error)
|
||
record["sale_price"] = sale_price
|
||
|
||
for camel, snake in _INT_FIELDS:
|
||
value, error = _parse_int(row.get(camel), camel)
|
||
if error:
|
||
return None, (camel, error)
|
||
record[snake] = value
|
||
|
||
return record, None
|
||
|
||
|
||
def _parse_oms_id(value) -> tuple[int | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, "缺少必填字段 omsId"
|
||
# bool 是 int 子类,必须用 type(value) is int。oms_id 列 number(20)。
|
||
if type(value) is int and 0 < value < 10**20:
|
||
return value, None
|
||
return None, "omsId 必须是整数"
|
||
|
||
|
||
def _parse_str(value, camel: str, max_len: int) -> tuple[str | None, str | None]:
|
||
if value is None:
|
||
return None, f"缺少必填字段 {camel}"
|
||
if not isinstance(value, str):
|
||
return None, f"{camel} 必须是字符串"
|
||
text = value.strip()
|
||
if not text:
|
||
return None, f"缺少必填字段 {camel}"
|
||
if len(text) > max_len:
|
||
return None, f"{camel} 超出长度"
|
||
return text, None
|
||
|
||
|
||
def _parse_int(value, camel: str) -> tuple[int | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, f"缺少必填字段 {camel}"
|
||
if type(value) is int:
|
||
return value, None
|
||
return None, f"{camel} 必须是整数"
|
||
|
||
|
||
def _parse_decimal(value, camel: str) -> tuple[Decimal | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, f"缺少必填字段 {camel}"
|
||
if type(value) is bool:
|
||
return None, f"{camel} 必须是数字"
|
||
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"{camel} 必须是数字"
|
||
except (InvalidOperation, ValueError):
|
||
return None, f"{camel} 必须是数字"
|
||
return amount, None
|
||
|
||
|
||
def _parse_date(value) -> tuple[date | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, "缺少必填字段 orderDate"
|
||
if isinstance(value, datetime):
|
||
return value.date(), None
|
||
if isinstance(value, date):
|
||
return value, None
|
||
if not isinstance(value, str):
|
||
return None, "orderDate 必须是 YYYY-MM-DD"
|
||
try:
|
||
return date.fromisoformat(value.strip()), None
|
||
except ValueError:
|
||
return None, "orderDate 必须是 YYYY-MM-DD"
|
||
|
||
|
||
def _result(oms_id, result_code: str, message: str) -> dict:
|
||
return {"omsId": oms_id, "resultCode": result_code, "message": message}
|