feat: 实现受控真实下单安全边界 (#99)
This commit is contained in:
@@ -70,14 +70,16 @@ class TaskRepository:
|
||||
with connection:
|
||||
cursor = connection.execute(
|
||||
"INSERT INTO pdd_tasks ("
|
||||
" remote_task_id, task_type, goods_id, goods_url, title,"
|
||||
" remote_task_id, task_type, execution_mode, goods_id,"
|
||||
" goods_url, title,"
|
||||
" target_color, target_size, price_cent, quantity, status,"
|
||||
" priority, version, admin_payload, received_at, created_at,"
|
||||
" updated_at"
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
task.remote_task_id.strip(),
|
||||
task.task_type.value,
|
||||
task.execution_mode,
|
||||
task.goods_id,
|
||||
task.goods_url.strip(),
|
||||
task.title,
|
||||
@@ -298,6 +300,22 @@ class TaskRepository:
|
||||
connection.close()
|
||||
return self._to_detail(row) if row is not None else None
|
||||
|
||||
def unresolved_irreversible_purchase(self) -> Optional[TaskDetail]:
|
||||
"""查找仍在运行且已有不可逆标记的采购,防止继续控制设备。"""
|
||||
|
||||
connection = open_database(self._db_path)
|
||||
try:
|
||||
row = connection.execute(
|
||||
"SELECT t.* FROM pdd_tasks t JOIN task_runs r ON r.task_id = t.id"
|
||||
" WHERE t.task_type = 'purchase' AND t.status = 'running'"
|
||||
" AND r.run_status = 'running'"
|
||||
" AND r.irreversible_action_at IS NOT NULL"
|
||||
" ORDER BY r.attempt_no DESC LIMIT 1"
|
||||
).fetchone()
|
||||
finally:
|
||||
connection.close()
|
||||
return self._to_detail(row) if row is not None else None
|
||||
|
||||
def latest_task_run(
|
||||
self, remote_task_id: str
|
||||
) -> Optional[TaskRunRecord]:
|
||||
@@ -583,6 +601,102 @@ class TaskRepository:
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def mark_purchase_irreversible(
|
||||
self, remote_task_id: str, attempt_id: str
|
||||
) -> str:
|
||||
"""事务写入不可逆时间;成功返回后才允许点击提交订单。"""
|
||||
|
||||
now = utc_now_iso()
|
||||
step = "purchase_irreversible_step_entered"
|
||||
connection = open_database(self._db_path)
|
||||
try:
|
||||
with connection:
|
||||
task = connection.execute(
|
||||
"SELECT id, task_type, status, execution_mode FROM pdd_tasks"
|
||||
" WHERE remote_task_id = ?",
|
||||
(remote_task_id,),
|
||||
).fetchone()
|
||||
if task is None:
|
||||
raise ValueError(f"任务 {remote_task_id} 不存在")
|
||||
if task["task_type"] != TaskType.PURCHASE.value:
|
||||
raise ValueError("当前任务不是采购任务")
|
||||
if task["execution_mode"] != "live":
|
||||
raise ValueError("演练任务不能进入不可逆阶段")
|
||||
if task["status"] != TaskStatus.RUNNING.value:
|
||||
raise ValueError("采购任务当前不在执行中")
|
||||
cursor = connection.execute(
|
||||
"UPDATE task_runs SET irreversible_action_at = ?,"
|
||||
" current_step = ?, updated_at = ?"
|
||||
" WHERE task_id = ? AND attempt_id = ?"
|
||||
" AND run_status = 'running'"
|
||||
" AND irreversible_action_at IS NULL",
|
||||
(now, step, now, task["id"], attempt_id),
|
||||
)
|
||||
if cursor.rowcount != 1:
|
||||
raise ValueError("不可逆标记写入失败或已经存在")
|
||||
connection.execute(
|
||||
"UPDATE pdd_tasks SET current_step = ?, updated_at = ?"
|
||||
" WHERE id = ?",
|
||||
(step, now, task["id"]),
|
||||
)
|
||||
return now
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def move_purchase_to_reconcile(
|
||||
self,
|
||||
remote_task_id: str,
|
||||
attempt_id: str,
|
||||
*,
|
||||
order_submitted_at: Optional[str] = None,
|
||||
message: str = "订单提交结果待核对,绝不重新下单",
|
||||
) -> None:
|
||||
"""不可逆点击后转入只读核单状态,不创建可重试下单路径。"""
|
||||
|
||||
now = utc_now_iso()
|
||||
connection = open_database(self._db_path)
|
||||
try:
|
||||
with connection:
|
||||
task = connection.execute(
|
||||
"SELECT id, task_type, status FROM pdd_tasks"
|
||||
" WHERE remote_task_id = ?",
|
||||
(remote_task_id,),
|
||||
).fetchone()
|
||||
if task is None:
|
||||
raise ValueError(f"任务 {remote_task_id} 不存在")
|
||||
if task["task_type"] != TaskType.PURCHASE.value:
|
||||
raise ValueError("当前任务不是采购任务")
|
||||
cursor = connection.execute(
|
||||
"UPDATE task_runs SET run_status = 'manual_review',"
|
||||
" current_step = 'reconcile_purchase',"
|
||||
" order_submitted_at = COALESCE(order_submitted_at, ?),"
|
||||
" error_code = 'PURCHASE_OUTCOME_UNKNOWN',"
|
||||
" error_message = ?, finished_at = ?, updated_at = ?"
|
||||
" WHERE task_id = ? AND attempt_id = ?"
|
||||
" AND run_status = 'running'"
|
||||
" AND irreversible_action_at IS NOT NULL",
|
||||
(
|
||||
order_submitted_at,
|
||||
message,
|
||||
now,
|
||||
now,
|
||||
task["id"],
|
||||
attempt_id,
|
||||
),
|
||||
)
|
||||
if cursor.rowcount != 1:
|
||||
raise ValueError("不可逆采购执行记录不存在或已经结束")
|
||||
connection.execute(
|
||||
"UPDATE pdd_tasks SET status = 'manual_review',"
|
||||
" current_step = 'reconcile_purchase',"
|
||||
" last_error_code = 'PURCHASE_OUTCOME_UNKNOWN',"
|
||||
" last_error_message = ?, finished_at = ?, updated_at = ?"
|
||||
" WHERE id = ?",
|
||||
(message, now, now, task["id"]),
|
||||
)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def save_purchase_reconciliation(
|
||||
self,
|
||||
remote_task_id: str,
|
||||
@@ -1369,6 +1483,7 @@ class TaskRepository:
|
||||
id=row["id"],
|
||||
remote_task_id=row["remote_task_id"],
|
||||
task_type=TaskType(row["task_type"]),
|
||||
execution_mode=row["execution_mode"],
|
||||
goods_id=row["goods_id"],
|
||||
goods_url=row["goods_url"],
|
||||
title=row["title"],
|
||||
|
||||
Reference in New Issue
Block a user