AutoRpa/rpa_oms/app/inbound/purchase_contract.py

176 lines
6.0 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.

# 接口 3 整批校验与落表。不跑合同作业。
#
# 入参 camelCase,落表 snake_case。幂等键是对方 fid。
# 请求里不录合同。
from __future__ import annotations
from . import messages
from .parse import parse_date, parse_decimal, parse_fid, 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]:
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]
# 内存判定:对象/字段/同批 fid 冲突当场出结果,通过则返回待插入记录。
def _classify(row: object, seen: set[int]) -> dict:
if not isinstance(row, dict):
return _result(None, "REJECTED", messages.ROW_NOT_OBJECT)
fid, fid_error = parse_fid(row.get("fid"))
if fid is not None and fid in seen:
return _result(fid, "CONFLICT", messages.CONTRACT_CONFLICT)
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)
return parsed
# 对通过校验的行一次查重、一次插入,把 DUPLICATE / ACCEPTED 填回 results。
def _persist(results: list[dict | None], pending: list[tuple[int, dict]], repo) -> None:
existing = repo.existing_fids([record["fid"] for _index, record in pending])
to_insert: list[dict] = []
insert_at: list[int] = []
for index, record in pending:
if record["fid"] in existing:
results[index] = _result(
record["fid"], "DUPLICATE", messages.CONTRACT_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["fid"] in collided:
results[index] = _result(
record["fid"], "DUPLICATE", messages.CONTRACT_DUPLICATE
)
else:
results[index] = _result(
record["fid"], "ACCEPTED", messages.CONTRACT_ACCEPTED
)
# 把一条明细收成表 3 行。失败返回 (None, (字段, 说明))。
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
# 录入合同状态:缺省当 None;有值必须是 0 或 1。
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"
# contractEntryStatus=0 时合同号必填;其它情况可空。
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)
# 拼一条接口 3 的 results[] 项。
def _result(fid, result_code: str, message: str) -> dict:
return {"fid": fid, "resultCode": result_code, "message": message}