feat: 接口4从表4组装合同协议报文
This commit is contained in:
parent
a80cce5fc5
commit
47ad02a6dc
|
|
@ -0,0 +1,69 @@
|
||||||
|
"""接口 4:从表 4 取未推行,组装文档报文。
|
||||||
|
|
||||||
|
同一非空 contractNo 必须在同一批 data 里。一批最多 500 条。
|
||||||
|
空 contractNo(失败行)按行独立。不改 push_status,不发 HTTP。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
MAX_BATCH = 500
|
||||||
|
|
||||||
|
|
||||||
|
def load_contract_agreement_envelope(repo, limit: int = MAX_BATCH) -> dict:
|
||||||
|
return build_contract_agreement_envelope(repo.list_unpushed(), limit=limit)
|
||||||
|
|
||||||
|
|
||||||
|
def build_contract_agreement_envelope(rows: list[dict], limit: int = MAX_BATCH) -> dict:
|
||||||
|
chosen = _take_complete_groups(rows, limit)
|
||||||
|
return {"data": [_to_item(row) for row in chosen]}
|
||||||
|
|
||||||
|
|
||||||
|
def _group_key(row: dict):
|
||||||
|
number = row.get("contract_no")
|
||||||
|
if number is None or str(number).strip() == "":
|
||||||
|
return ("fid", row["fid"])
|
||||||
|
return ("no", str(number).strip())
|
||||||
|
|
||||||
|
|
||||||
|
def _take_complete_groups(rows: list[dict], limit: int) -> list[dict]:
|
||||||
|
groups: OrderedDict = OrderedDict()
|
||||||
|
for row in rows:
|
||||||
|
groups.setdefault(_group_key(row), []).append(row)
|
||||||
|
batch: list[dict] = []
|
||||||
|
for group in groups.values():
|
||||||
|
if len(batch) + len(group) > limit and batch:
|
||||||
|
break
|
||||||
|
batch.extend(group)
|
||||||
|
return batch
|
||||||
|
|
||||||
|
|
||||||
|
def _to_item(row: dict) -> dict:
|
||||||
|
return {
|
||||||
|
"fid": _as_int(row["fid"]),
|
||||||
|
"omsId": _as_int(row["oms_id"]),
|
||||||
|
"actionCode": _as_int(row["action_code"]),
|
||||||
|
"contractStatus": _as_int(row.get("contract_status")),
|
||||||
|
"contractFailReason": _as_str(row.get("contract_fail_reason")),
|
||||||
|
"contractNo": _as_str(row.get("contract_no")),
|
||||||
|
"agreementStatus": _as_int(row.get("agreement_status")),
|
||||||
|
"agreementFailReason": _as_str(row.get("agreement_fail_reason")),
|
||||||
|
"agreementId": _as_str(row.get("agreement_id")),
|
||||||
|
"agreementDtlId": _as_str(row.get("agreement_dtl_id")),
|
||||||
|
"orgCode": _as_str(row.get("org_code")),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _as_int(value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _as_str(value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
text = str(value)
|
||||||
|
if text.strip() == "":
|
||||||
|
return None
|
||||||
|
return text
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""内存表 2。只给单测用。"""
|
"""内存表 2 / 表 4。只给单测用。"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -8,3 +8,11 @@ class MemoryCartSettlementRepo:
|
||||||
|
|
||||||
def list_unpushed(self) -> list[dict]:
|
def list_unpushed(self) -> list[dict]:
|
||||||
return [dict(row) for row in self._rows if row.get("push_status", 0) == 0]
|
return [dict(row) for row in self._rows if row.get("push_status", 0) == 0]
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryContractAgreementRepo:
|
||||||
|
def __init__(self, rows: list[dict] | None = None):
|
||||||
|
self._rows = [dict(row) for row in (rows or [])]
|
||||||
|
|
||||||
|
def list_unpushed(self) -> list[dict]:
|
||||||
|
return [dict(row) for row in self._rows if row.get("push_status", 0) == 0]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
"""接口 4:从表 4 取 push_status=0,组装文档报文。不连 Oracle,不发 HTTP。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from app.outbound.contract_agreement import (
|
||||||
|
MAX_BATCH,
|
||||||
|
build_contract_agreement_envelope,
|
||||||
|
load_contract_agreement_envelope,
|
||||||
|
)
|
||||||
|
from app.outbound.memory_repo import MemoryContractAgreementRepo
|
||||||
|
|
||||||
|
|
||||||
|
def _row(**overrides):
|
||||||
|
row = {
|
||||||
|
"fid": 300001,
|
||||||
|
"oms_id": 20260928000001,
|
||||||
|
"action_code": 1,
|
||||||
|
"contract_status": 1,
|
||||||
|
"contract_fail_reason": None,
|
||||||
|
"contract_no": "HT1",
|
||||||
|
"agreement_status": None,
|
||||||
|
"agreement_fail_reason": None,
|
||||||
|
"agreement_id": None,
|
||||||
|
"agreement_dtl_id": None,
|
||||||
|
"org_code": "901",
|
||||||
|
"push_status": 0,
|
||||||
|
}
|
||||||
|
row.update(overrides)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def test_assembles_unpushed_rows_as_document_envelope():
|
||||||
|
repo = MemoryContractAgreementRepo(
|
||||||
|
[
|
||||||
|
_row(),
|
||||||
|
_row(
|
||||||
|
fid=300002,
|
||||||
|
action_code=2,
|
||||||
|
contract_status=None,
|
||||||
|
contract_no="HT1",
|
||||||
|
agreement_status=1,
|
||||||
|
agreement_id="AGR1",
|
||||||
|
agreement_dtl_id="AGRD1",
|
||||||
|
),
|
||||||
|
_row(fid=300003, push_status=1, contract_no="HT-SKIP"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
envelope = load_contract_agreement_envelope(repo)
|
||||||
|
assert envelope == {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"fid": 300001,
|
||||||
|
"omsId": 20260928000001,
|
||||||
|
"actionCode": 1,
|
||||||
|
"contractStatus": 1,
|
||||||
|
"contractFailReason": None,
|
||||||
|
"contractNo": "HT1",
|
||||||
|
"agreementStatus": None,
|
||||||
|
"agreementFailReason": None,
|
||||||
|
"agreementId": None,
|
||||||
|
"agreementDtlId": None,
|
||||||
|
"orgCode": "901",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fid": 300002,
|
||||||
|
"omsId": 20260928000001,
|
||||||
|
"actionCode": 2,
|
||||||
|
"contractStatus": None,
|
||||||
|
"contractFailReason": None,
|
||||||
|
"contractNo": "HT1",
|
||||||
|
"agreementStatus": 1,
|
||||||
|
"agreementFailReason": None,
|
||||||
|
"agreementId": "AGR1",
|
||||||
|
"agreementDtlId": "AGRD1",
|
||||||
|
"orgCode": "901",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
assert isinstance(envelope["data"][0]["omsId"], int)
|
||||||
|
|
||||||
|
|
||||||
|
def test_oversized_contract_no_group_enters_when_batch_empty():
|
||||||
|
rows = [
|
||||||
|
_row(fid=1, oms_id=1, contract_no="HT-BIG"),
|
||||||
|
_row(fid=2, oms_id=2, contract_no="HT-BIG"),
|
||||||
|
_row(fid=3, oms_id=3, contract_no="HT-BIG"),
|
||||||
|
]
|
||||||
|
envelope = build_contract_agreement_envelope(rows, limit=2)
|
||||||
|
assert [item["fid"] for item in envelope["data"]] == [1, 2, 3]
|
||||||
|
|
||||||
|
|
||||||
|
def test_does_not_split_contract_no_when_batch_already_has_rows():
|
||||||
|
rows = [
|
||||||
|
_row(fid=1, oms_id=1, contract_no="HT-A"),
|
||||||
|
_row(fid=2, oms_id=2, contract_no="HT-B"),
|
||||||
|
_row(fid=3, oms_id=3, contract_no="HT-B"),
|
||||||
|
]
|
||||||
|
envelope = build_contract_agreement_envelope(rows, limit=2)
|
||||||
|
assert [item["fid"] for item in envelope["data"]] == [1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_contract_no_rows_are_independent():
|
||||||
|
rows = [
|
||||||
|
_row(fid=1, oms_id=1, contract_no=None, contract_status=3),
|
||||||
|
_row(fid=2, oms_id=2, contract_no="", contract_status=3),
|
||||||
|
_row(fid=3, oms_id=3, contract_no="HT-C"),
|
||||||
|
]
|
||||||
|
envelope = build_contract_agreement_envelope(rows, limit=1)
|
||||||
|
assert [item["fid"] for item in envelope["data"]] == [1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_batch_cap_is_500():
|
||||||
|
assert MAX_BATCH == 500
|
||||||
|
|
||||||
|
|
||||||
|
def test_payload_omits_push_status():
|
||||||
|
item = build_contract_agreement_envelope([_row()])["data"][0]
|
||||||
|
assert "pushStatus" not in item
|
||||||
|
assert "push_status" not in item
|
||||||
Loading…
Reference in New Issue