"""接口 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}