# 接口 1 Oracle 落表。 # # 11.2 只能 thick(Instant Client)。连接从 rpa_oracle 池借。 # fid 由序列触发器生成,INSERT 不绑这一列。 # 唯一约束只认 uk_rpa_sgmd_oms_id 为 oms_id 重复;其它唯一约束当系统故障。 from __future__ import annotations from rpa_oracle.errors import RepoUnavailable, is_sales_oms_id_duplicate _INSERT_SQL = """ INSERT INTO rpa_sales_goods_mapping_data ( oms_id, bar_code, order_date, sale_price, sale_qty, plan_purchase_qty, goods_id, goods_name, goods_spec, manufacturer, approval_no, unit, customer_id, customer_name, org_code, goods_mdm_code, customer_mdm_code, create_time, create_user_id, create_user_name, update_time, update_user_id, update_user_name, deal_flag ) VALUES ( :oms_id, :bar_code, :order_date, :sale_price, :sale_qty, :plan_purchase_qty, :goods_id, :goods_name, :goods_spec, :manufacturer, :approval_no, :unit, :customer_id, :customer_name, :org_code, :goods_mdm_code, :customer_mdm_code, SYSDATE, 0, '系统管理员', SYSDATE, 0, '系统管理员', 0 ) """ _BIND_KEYS = ( "oms_id", "bar_code", "order_date", "sale_price", "sale_qty", "plan_purchase_qty", "goods_id", "goods_name", "goods_spec", "manufacturer", "approval_no", "unit", "customer_id", "customer_name", "org_code", "goods_mdm_code", "customer_mdm_code", ) # 接口 1 连 Oracle 的仓库,管表 1 查重和批量插入。 # # 连接由调用方从池里借来,一个请求一条。 class OracleSalesGoodsRepo: # 挂上本请求的连接。不要在仓库里再 connect。 def __init__(self, conn): self._conn = conn # 查出表 1 里已经有的 oms_id。空列表不访问库。 def existing_oms_ids(self, oms_ids: list[int]) -> set[int]: if not oms_ids: return set() placeholders, binds = in_binds(oms_ids) sql = ( "SELECT oms_id FROM rpa_sales_goods_mapping_data " f"WHERE oms_id IN ({placeholders})" ) try: with self._conn.cursor() as cur: cur.execute(sql, binds) return {row[0] for row in cur.fetchall()} except Exception as exc: raise RepoUnavailable("数据库不可用") from exc # 一次 executemany 插入待写入行。 # # 返回撞上 uk_rpa_sgmd_oms_id 的 oms_id。其它唯一约束或库故障抛 RepoUnavailable。 # 成功行在本方法内 commit。 def insert_many(self, records: list[dict]) -> set[int]: if not records: return set() payload = [{key: record[key] for key in _BIND_KEYS} for record in records] try: with self._conn.cursor() as cur: cur.executemany(_INSERT_SQL, payload, batcherrors=True) collided = unique_collisions( cur, records, "oms_id", is_sales_oms_id_duplicate ) self._conn.commit() return collided except RepoUnavailable: _rollback(self._conn) raise except Exception as exc: _rollback(self._conn) if is_sales_oms_id_duplicate(exc) and len(records) == 1: return {records[0]["oms_id"]} raise RepoUnavailable("数据库不可用") from exc # 把一组整数编成 :id0, :id1 占位和 bind 字典。 def in_binds(ids: list[int]) -> tuple[str, dict[str, int]]: binds = {f"id{i}": value for i, value in enumerate(ids)} placeholders = ", ".join(f":id{i}" for i in range(len(ids))) return placeholders, binds # 把 getbatcherrors() 单项收成 ora_code / ora_constraint 能认的异常。 def as_ora_exc(err) -> Exception: message = getattr(err, "message", str(err)) code = getattr(err, "code", None) info = type("Info", (), {"code": code, "message": message})() exc = Exception(message) exc.args = (info,) return exc # 从批错误里挑出幂等键冲突的 id;碰到未知约束则整批失败。 def unique_collisions(cur, records: list[dict], key: str, is_dup) -> set[int]: collided: set[int] = set() errors = cur.getbatcherrors() if hasattr(cur, "getbatcherrors") else [] for err in errors: wrapped = as_ora_exc(err) if is_dup(wrapped): collided.add(records[err.offset][key]) continue raise RepoUnavailable("数据库不可用") from wrapped return collided # 回滚本请求已写入的行;回滚失败忽略。 def _rollback(conn) -> None: try: conn.rollback() except Exception: pass