169 lines
5.3 KiB
Python
169 lines
5.3 KiB
Python
"""接口 3 逐条校验与落表。不跑合同作业。
|
||
|
||
入参 camelCase,落表 snake_case。幂等键是对方 fid。
|
||
请求里不录合同。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import date, datetime
|
||
|
||
from .repo import DuplicateFid
|
||
from .sales_goods import _parse_decimal, _parse_int, _parse_oms_id, _parse_str
|
||
|
||
_STRING_FIELDS = (
|
||
("supplierId", "supplier_id", 20),
|
||
("supplierName", "supplier_name", 200),
|
||
("goodsId", "goods_id", 20),
|
||
("goodsName", "goods_name", 200),
|
||
("regNo", "reg_no", 100),
|
||
("spec", "spec", 100),
|
||
("unit", "unit", 20),
|
||
("goodsSourceId", "goods_source_id", 20),
|
||
("purchaserId", "purchaser_id", 20),
|
||
("purchaserName", "purchaser_name", 100),
|
||
("plannerId", "planner_id", 20),
|
||
("plannerName", "planner_name", 100),
|
||
("orgCode", "org_code", 10),
|
||
("goodsMdmCode", "goods_mdm_code", 100),
|
||
("supplierMdmCode", "supplier_mdm_code", 100),
|
||
)
|
||
|
||
_DATE_FIELDS = (
|
||
("contractSignDate", "contract_sign_date"),
|
||
("contractStartDate", "contract_start_date"),
|
||
("contractEndDate", "contract_end_date"),
|
||
)
|
||
|
||
_DECIMAL_FIELDS = (
|
||
("qty", "qty"),
|
||
("purchaseTaxRate", "purchase_tax_rate"),
|
||
("unitPrice", "unit_price"),
|
||
)
|
||
|
||
|
||
def receive_purchase_contracts(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", "明细必须是对象")
|
||
|
||
fid, fid_error = _parse_fid(row.get("fid"))
|
||
if fid is not None and fid in seen:
|
||
return _result(fid, "CONFLICT", "同一请求中 fid 重复")
|
||
if fid is not None:
|
||
seen.add(fid)
|
||
|
||
parsed, error = _parse_row(row, fid, fid_error)
|
||
if error:
|
||
_camel, message = error
|
||
return _result(row.get("fid"), "REJECTED", message)
|
||
|
||
try:
|
||
repo.insert(parsed)
|
||
except DuplicateFid:
|
||
return _result(parsed["fid"], "DUPLICATE", "序号已存在,未重复入库")
|
||
|
||
return _result(parsed["fid"], "ACCEPTED", "采购合同数据已接收,等待处理")
|
||
|
||
|
||
def _parse_row(row: dict, fid: int | None, fid_error: str | None):
|
||
if fid_error:
|
||
return None, ("fid", fid_error)
|
||
|
||
oms_id, oms_error = _parse_oms_id(row.get("omsId"))
|
||
if oms_error:
|
||
return None, ("omsId", oms_error)
|
||
|
||
record: dict = {"fid": fid, "oms_id": oms_id, "process_status": 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
|
||
|
||
for camel, snake in _DATE_FIELDS:
|
||
value, error = _parse_date(row.get(camel), camel)
|
||
if error:
|
||
return None, (camel, error)
|
||
record[snake] = value
|
||
|
||
if record["contract_end_date"] < record["contract_start_date"]:
|
||
return None, ("contractEndDate", "contractEndDate 不得早于 contractStartDate")
|
||
|
||
payment_term, error = _parse_int(row.get("paymentTerm"), "paymentTerm")
|
||
if error:
|
||
return None, ("paymentTerm", error)
|
||
record["payment_term"] = payment_term
|
||
|
||
for camel, snake in _DECIMAL_FIELDS:
|
||
value, error = _parse_decimal(row.get(camel), camel)
|
||
if error:
|
||
return None, (camel, error)
|
||
record[snake] = value
|
||
|
||
customer_type, error = _parse_int(row.get("customerType"), "customerType")
|
||
if error:
|
||
return None, ("customerType", error)
|
||
record["customer_type"] = customer_type
|
||
|
||
entry_status, error = _parse_entry_status(row.get("contractEntryStatus"))
|
||
if error:
|
||
return None, ("contractEntryStatus", error)
|
||
record["contract_entry_status"] = entry_status
|
||
|
||
contract_no, error = _parse_contract_no(row.get("contractNo"), entry_status)
|
||
if error:
|
||
return None, ("contractNo", error)
|
||
record["contract_no"] = contract_no
|
||
|
||
return record, None
|
||
|
||
|
||
def _parse_fid(value) -> tuple[int | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, "缺少必填字段 fid"
|
||
if type(value) is int and 0 < value < 10**19:
|
||
return value, None
|
||
return None, "fid 必须是整数"
|
||
|
||
|
||
def _parse_date(value, camel: str) -> tuple[date | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, f"缺少必填字段 {camel}"
|
||
if isinstance(value, datetime):
|
||
return value.date(), None
|
||
if isinstance(value, date):
|
||
return value, None
|
||
if not isinstance(value, str):
|
||
return None, f"{camel} 必须是 YYYY-MM-DD"
|
||
try:
|
||
return date.fromisoformat(value.strip()), None
|
||
except ValueError:
|
||
return None, f"{camel} 必须是 YYYY-MM-DD"
|
||
|
||
|
||
def _parse_entry_status(value) -> tuple[int | None, str | None]:
|
||
if value is None or value == "":
|
||
return None, None
|
||
if type(value) is int and value in (0, 1):
|
||
return value, None
|
||
return None, "contractEntryStatus 必须是 0 或 1"
|
||
|
||
|
||
def _parse_contract_no(value, entry_status: int | None) -> tuple[str | None, str | None]:
|
||
if entry_status == 0:
|
||
return _parse_str(value, "contractNo", 40)
|
||
if value is None or value == "":
|
||
return None, None
|
||
return _parse_str(value, "contractNo", 40)
|
||
|
||
|
||
def _result(fid, result_code: str, message: str) -> dict:
|
||
return {"fid": fid, "resultCode": result_code, "message": message}
|