368 lines
12 KiB
Python
368 lines
12 KiB
Python
"""表 4 起草行领取、表 3 只录协议行领取、表 4 写入。"""
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass, field
|
|
from db import RepoUnavailable, connect_from_env, oracle_dsn
|
|
from persist import STAMP_CLAIMING, STAMP_CONTRACT, STAMP_DONE, GroupSave
|
|
|
|
ACTION_CONTRACT = 1
|
|
CONTRACT_DRAFT = 1
|
|
PROCESS_PENDING = 0
|
|
PROCESS_CLAIMING = 3
|
|
ENTRY_PROTOCOL_ONLY = 0
|
|
|
|
|
|
@dataclass
|
|
class Claimed:
|
|
drafts: list[dict] = field(default_factory=list)
|
|
protocols: list[dict] = field(default_factory=list)
|
|
|
|
|
|
def _has_contract_no(row: dict) -> bool:
|
|
return bool((row.get("contract_no") or "").strip())
|
|
|
|
|
|
def is_draft(row: dict) -> bool:
|
|
if int(row.get("action_code") or 0) != ACTION_CONTRACT:
|
|
return False
|
|
if int(row.get("contract_status") or 0) != CONTRACT_DRAFT:
|
|
return False
|
|
if not _has_contract_no(row):
|
|
return False
|
|
stamp = row.get("update_user_name") or STAMP_CONTRACT
|
|
return stamp == STAMP_CONTRACT
|
|
|
|
|
|
def is_protocol_only(row: dict) -> bool:
|
|
if int(row.get("process_status") or 0) != PROCESS_PENDING:
|
|
return False
|
|
entry = row.get("contract_entry_status")
|
|
if entry is None or int(entry) != ENTRY_PROTOCOL_ONLY:
|
|
return False
|
|
return _has_contract_no(row)
|
|
|
|
|
|
class MemoryRebateRepo:
|
|
def __init__(
|
|
self,
|
|
table4: list[dict] | None = None,
|
|
table3: list[dict] | None = None,
|
|
):
|
|
self.table4: dict[int, dict] = {
|
|
int(row["fid"]): dict(row) for row in (table4 or [])
|
|
}
|
|
self.table3: dict[int, dict] = {
|
|
int(row["fid"]): dict(row) for row in (table3 or [])
|
|
}
|
|
self._next_fid = (max(self.table4) + 1) if self.table4 else 1
|
|
|
|
def claim_pending(self, limit: int | None = None) -> Claimed:
|
|
drafts = sorted(
|
|
(row for row in self.table4.values() if is_draft(row)),
|
|
key=lambda row: int(row["fid"]),
|
|
)
|
|
protocols = sorted(
|
|
(row for row in self.table3.values() if is_protocol_only(row)),
|
|
key=lambda row: int(row["fid"]),
|
|
)
|
|
if limit is not None:
|
|
if len(drafts) >= limit:
|
|
drafts = drafts[:limit]
|
|
protocols = []
|
|
else:
|
|
protocols = protocols[: max(0, limit - len(drafts))]
|
|
for row in drafts:
|
|
row["update_user_name"] = STAMP_CLAIMING
|
|
for row in protocols:
|
|
row["process_status"] = PROCESS_CLAIMING
|
|
return Claimed(drafts=list(drafts), protocols=list(protocols))
|
|
|
|
def save_group(self, payload: GroupSave) -> None:
|
|
for item in payload.table4_updates:
|
|
target = self.table4[int(item["fid"])]
|
|
target["contract_status"] = item["contract_status"]
|
|
target["update_user_name"] = item["update_user_name"]
|
|
for fid in payload.table4_release_fids:
|
|
self.table4[int(fid)]["update_user_name"] = STAMP_CONTRACT
|
|
for row in payload.table4_inserts:
|
|
item = dict(row)
|
|
item["fid"] = self._next_fid
|
|
self._next_fid += 1
|
|
self.table4[item["fid"]] = item
|
|
for item in payload.table3_updates:
|
|
target = self.table3[int(item["fid"])]
|
|
target["process_status"] = item["process_status"]
|
|
target["process_result"] = item["process_result"]
|
|
|
|
def load_contract_goods(self, contract_no: str) -> list[dict]:
|
|
number = (contract_no or "").strip()
|
|
oms_ids = {
|
|
int(row["oms_id"])
|
|
for row in self.table4.values()
|
|
if (row.get("contract_no") or "").strip() == number and row.get("oms_id") is not None
|
|
}
|
|
return [
|
|
dict(row)
|
|
for row in self.table3.values()
|
|
if (row.get("contract_no") or "").strip() == number
|
|
or int(row.get("oms_id") or 0) in oms_ids
|
|
]
|
|
|
|
|
|
_SELECT_DRAFTS = """
|
|
SELECT
|
|
fid, oms_id, action_code, contract_status, contract_fail_reason,
|
|
contract_no, agreement_status, agreement_fail_reason,
|
|
agreement_id, agreement_dtl_id, org_code, push_status,
|
|
NVL(update_user_name, '合同班') AS update_user_name
|
|
FROM rpa_contract_agreement_result
|
|
WHERE action_code = 1
|
|
AND contract_status = 1
|
|
AND contract_no IS NOT NULL
|
|
AND LENGTH(TRIM(contract_no)) > 0
|
|
AND NVL(update_user_name, '合同班') = '合同班'
|
|
ORDER BY fid
|
|
"""
|
|
|
|
_SELECT_DRAFTS_LIMIT = f"""
|
|
SELECT * FROM (
|
|
{_SELECT_DRAFTS}
|
|
) WHERE ROWNUM <= :limit
|
|
"""
|
|
|
|
_CLAIM_DRAFT_SQL = """
|
|
UPDATE rpa_contract_agreement_result
|
|
SET update_user_name = :stamp,
|
|
update_time = SYSTIMESTAMP
|
|
WHERE fid = :fid
|
|
AND action_code = 1
|
|
AND contract_status = 1
|
|
AND NVL(update_user_name, :contract_stamp) = :contract_stamp
|
|
"""
|
|
|
|
_SELECT_PROTOCOLS = """
|
|
SELECT
|
|
fid, oms_id, contract_no, contract_entry_status, process_status, org_code,
|
|
NVL(process_result, '') AS process_result
|
|
FROM rpa_purchase_contract_data
|
|
WHERE process_status = 0
|
|
AND NVL(contract_entry_status, -1) = 0
|
|
AND contract_no IS NOT NULL
|
|
AND LENGTH(TRIM(contract_no)) > 0
|
|
ORDER BY fid
|
|
"""
|
|
|
|
_SELECT_PROTOCOLS_LIMIT = f"""
|
|
SELECT * FROM (
|
|
{_SELECT_PROTOCOLS}
|
|
) WHERE ROWNUM <= :limit
|
|
"""
|
|
|
|
_CLAIM_PROTOCOL_SQL = """
|
|
UPDATE rpa_purchase_contract_data
|
|
SET process_status = 3,
|
|
process_time = SYSTIMESTAMP,
|
|
update_time = SYSTIMESTAMP
|
|
WHERE fid = :fid AND process_status = 0
|
|
"""
|
|
|
|
_UPDATE_DRAFT_SQL = """
|
|
UPDATE rpa_contract_agreement_result
|
|
SET contract_status = :contract_status,
|
|
update_user_id = 0,
|
|
update_user_name = :stamp,
|
|
update_time = SYSTIMESTAMP
|
|
WHERE fid = :fid AND update_user_name = :claim_stamp
|
|
"""
|
|
|
|
_RELEASE_DRAFT_SQL = """
|
|
UPDATE rpa_contract_agreement_result
|
|
SET update_user_name = :stamp,
|
|
update_time = SYSTIMESTAMP
|
|
WHERE fid = :fid AND update_user_name = :claim_stamp
|
|
"""
|
|
|
|
_INSERT_TABLE4_SQL = """
|
|
INSERT INTO rpa_contract_agreement_result (
|
|
oms_id, action_code, contract_status, contract_fail_reason, contract_no,
|
|
agreement_status, agreement_fail_reason, agreement_id, agreement_dtl_id,
|
|
push_status, create_time, update_time,
|
|
create_user_id, create_user_name, update_user_id, update_user_name, org_code
|
|
) VALUES (
|
|
:oms_id, :action_code, :contract_status, :contract_fail_reason, :contract_no,
|
|
:agreement_status, :agreement_fail_reason, :agreement_id, :agreement_dtl_id,
|
|
0, SYSTIMESTAMP, SYSTIMESTAMP,
|
|
0, '返利班', 0, '返利班', :org_code
|
|
)
|
|
"""
|
|
|
|
_COMPLETE_PROTOCOL_SQL = """
|
|
UPDATE rpa_purchase_contract_data
|
|
SET process_status = :process_status,
|
|
process_result = :process_result,
|
|
process_time = SYSTIMESTAMP,
|
|
update_time = SYSTIMESTAMP
|
|
WHERE fid = :fid AND process_status = 3
|
|
"""
|
|
|
|
_TABLE4_BIND_KEYS = (
|
|
"oms_id",
|
|
"action_code",
|
|
"contract_status",
|
|
"contract_fail_reason",
|
|
"contract_no",
|
|
"agreement_status",
|
|
"agreement_fail_reason",
|
|
"agreement_id",
|
|
"agreement_dtl_id",
|
|
"org_code",
|
|
)
|
|
|
|
|
|
_SELECT_CONTRACT_GOODS = """
|
|
SELECT
|
|
fid, oms_id, supplier_id, supplier_name, supplier_mdm_code,
|
|
planner_id, planner_name, purchaser_name, org_code,
|
|
goods_id, qty, unit_price, goods_source_id, purchase_tax_rate,
|
|
contract_start_date, contract_end_date, payment_term
|
|
FROM rpa_purchase_contract_data
|
|
WHERE TRIM(contract_no) = :contract_no
|
|
OR oms_id IN (
|
|
SELECT oms_id FROM rpa_contract_agreement_result
|
|
WHERE TRIM(contract_no) = :contract_no
|
|
)
|
|
ORDER BY fid
|
|
"""
|
|
|
|
|
|
def _records(cur) -> list[dict]:
|
|
cols = [item[0].lower() for item in cur.description]
|
|
return [dict(zip(cols, rec)) for rec in cur.fetchall()]
|
|
|
|
|
|
class OracleRebateRepo:
|
|
def __init__(self, connect: Callable):
|
|
self._connect = connect
|
|
|
|
@classmethod
|
|
def from_env(cls) -> "OracleRebateRepo":
|
|
oracle_dsn()
|
|
return cls(connect=connect_from_env)
|
|
|
|
def claim_pending(self, limit: int | None = None) -> Claimed:
|
|
conn = self._connect()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
drafts = self._claim_drafts(cur, limit)
|
|
remaining = None if limit is None else max(0, int(limit) - len(drafts))
|
|
protocols: list[dict] = []
|
|
if remaining is None or remaining > 0:
|
|
protocols = self._claim_protocols(cur, remaining)
|
|
conn.commit()
|
|
return Claimed(drafts=drafts, protocols=protocols)
|
|
except Exception as exc:
|
|
try:
|
|
conn.rollback()
|
|
except Exception:
|
|
pass
|
|
raise RepoUnavailable(str(exc)) from exc
|
|
finally:
|
|
conn.close()
|
|
|
|
def load_contract_goods(self, contract_no: str) -> list[dict]:
|
|
number = (contract_no or "").strip()
|
|
if not number:
|
|
return []
|
|
conn = self._connect()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
cur.execute(_SELECT_CONTRACT_GOODS, {"contract_no": number})
|
|
return _records(cur)
|
|
except Exception as exc:
|
|
raise RepoUnavailable(str(exc)) from exc
|
|
finally:
|
|
conn.close()
|
|
|
|
def save_group(self, payload: GroupSave) -> None:
|
|
conn = self._connect()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
for item in payload.table4_updates:
|
|
cur.execute(
|
|
_UPDATE_DRAFT_SQL,
|
|
{
|
|
"fid": int(item["fid"]),
|
|
"contract_status": item["contract_status"],
|
|
"stamp": STAMP_DONE,
|
|
"claim_stamp": STAMP_CLAIMING,
|
|
},
|
|
)
|
|
for fid in payload.table4_release_fids:
|
|
cur.execute(
|
|
_RELEASE_DRAFT_SQL,
|
|
{
|
|
"fid": int(fid),
|
|
"stamp": STAMP_CONTRACT,
|
|
"claim_stamp": STAMP_CLAIMING,
|
|
},
|
|
)
|
|
for row in payload.table4_inserts:
|
|
cur.execute(
|
|
_INSERT_TABLE4_SQL,
|
|
{key: row.get(key) for key in _TABLE4_BIND_KEYS},
|
|
)
|
|
for item in payload.table3_updates:
|
|
cur.execute(
|
|
_COMPLETE_PROTOCOL_SQL,
|
|
{
|
|
"fid": int(item["fid"]),
|
|
"process_status": item["process_status"],
|
|
"process_result": item["process_result"],
|
|
},
|
|
)
|
|
conn.commit()
|
|
except Exception as exc:
|
|
try:
|
|
conn.rollback()
|
|
except Exception:
|
|
pass
|
|
raise RepoUnavailable(str(exc)) from exc
|
|
finally:
|
|
conn.close()
|
|
|
|
def _claim_drafts(self, cur, limit: int | None) -> list[dict]:
|
|
if limit is None:
|
|
cur.execute(_SELECT_DRAFTS)
|
|
else:
|
|
cur.execute(_SELECT_DRAFTS_LIMIT, {"limit": int(limit)})
|
|
fetched = _records(cur)
|
|
claimed: list[dict] = []
|
|
for row in fetched:
|
|
cur.execute(
|
|
_CLAIM_DRAFT_SQL,
|
|
{
|
|
"fid": row["fid"],
|
|
"stamp": STAMP_CLAIMING,
|
|
"contract_stamp": STAMP_CONTRACT,
|
|
},
|
|
)
|
|
if cur.rowcount == 1:
|
|
row["update_user_name"] = STAMP_CLAIMING
|
|
claimed.append(row)
|
|
return claimed
|
|
|
|
def _claim_protocols(self, cur, limit: int | None) -> list[dict]:
|
|
if limit is None:
|
|
cur.execute(_SELECT_PROTOCOLS)
|
|
else:
|
|
cur.execute(_SELECT_PROTOCOLS_LIMIT, {"limit": int(limit)})
|
|
fetched = _records(cur)
|
|
claimed: list[dict] = []
|
|
for row in fetched:
|
|
cur.execute(_CLAIM_PROTOCOL_SQL, {"fid": row["fid"]})
|
|
if cur.rowcount == 1:
|
|
row["process_status"] = PROCESS_CLAIMING
|
|
claimed.append(row)
|
|
return claimed
|