136 lines
4.6 KiB
Python
136 lines
4.6 KiB
Python
# 接口 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
|