AutoRpa/rpa_oms/app/inbound/sales_goods.py

133 lines
4.7 KiB
Python
Raw 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.

# 接口 1 整批校验与落表。不跑选品。
#
# 入参 camelCase,落表 snake_case。长度跟 VARCHAR2。
#
# 先在内存里标 REJECTED / 同批 CONFLICT,再一次查重、一次插入。
# 单条结果:
# - ACCEPTED:入库,deal_flag=0,等选品作业捞
# - DUPLICATE:库里已有同一 omsId,或并发撞上 uk_rpa_sgmd_oms_id
# - CONFLICT:同一请求里 omsId 出现过(前一条哪怕被拒,后一条也算冲突)
# - REJECTED:字段不过,不入库
#
# 混批 HTTP 仍 200。omsId / 数量必须是 JSON 整数,不把 "10" 当 10。
from __future__ import annotations
from . import messages
from .parse import parse_date, parse_decimal, parse_int, parse_oms_id, parse_str
_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]:
results: list[dict | None] = [None] * len(rows)
pending: list[tuple[int, dict]] = []
seen: set[int] = set()
for index, row in enumerate(rows):
decided = _classify(row, seen)
if "resultCode" in decided:
results[index] = decided
else:
pending.append((index, decided))
if pending:
_persist(results, pending, repo)
return [item for item in results if item is not None]
# 内存判定:对象/字段/同批冲突当场出结果,通过则返回待插入记录。
def _classify(row: object, seen: set[int]) -> dict:
if not isinstance(row, dict):
return _result(None, "REJECTED", messages.ROW_NOT_OBJECT)
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", messages.SALES_CONFLICT)
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)
return parsed
# 对通过校验的行一次查重、一次插入,把 DUPLICATE / ACCEPTED 填回 results。
def _persist(results: list[dict | None], pending: list[tuple[int, dict]], repo) -> None:
existing = repo.existing_oms_ids([record["oms_id"] for _index, record in pending])
to_insert: list[dict] = []
insert_at: list[int] = []
for index, record in pending:
if record["oms_id"] in existing:
results[index] = _result(
record["oms_id"], "DUPLICATE", messages.SALES_DUPLICATE
)
else:
to_insert.append(record)
insert_at.append(index)
collided = repo.insert_many(to_insert) if to_insert else set()
for index, record in zip(insert_at, to_insert):
if record["oms_id"] in collided:
results[index] = _result(
record["oms_id"], "DUPLICATE", messages.SALES_DUPLICATE
)
else:
results[index] = _result(
record["oms_id"], "ACCEPTED", messages.SALES_ACCEPTED
)
# 把一条明细收成表 1 行。失败返回 (None, (字段, 说明))。
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"), "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
# 拼一条接口 1 的 results[] 项。
def _result(oms_id, result_code: str, message: str) -> dict:
return {"omsId": oms_id, "resultCode": result_code, "message": message}