"""表 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