feat: 增加批量重新采集和安全重新采购 (#138 #139)

This commit is contained in:
chengma
2026-08-11 10:42:51 +08:00
parent 7aa49c0e29
commit 598c808093
12 changed files with 420 additions and 60 deletions
+113
View File
@@ -20,6 +20,7 @@ from .task_models import (
StartedTaskRun,
TaskDetail,
TaskFilters,
TaskRerunPlan,
TaskStatus,
TaskSummary,
TaskType,
@@ -39,6 +40,10 @@ class CollectRerunError(ValueError):
"""当前任务不满足重新采集条件。"""
class PurchaseRerunError(ValueError):
"""当前任务不满足安全重新采购条件。"""
class TaskRemovalError(ValueError):
"""勾选任务不满足从普通列表移除的安全条件。"""
@@ -472,6 +477,42 @@ class TaskRepository:
connection.close()
return self._to_detail(row)
def plan_rerun_batch(
self, remote_task_ids: Iterable[str], target_type: TaskType
) -> TaskRerunPlan:
"""批量预检并分类:可执行、类型不符、被安全条件阻止。"""
stable_ids = tuple(dict.fromkeys(str(value) for value in remote_task_ids if value))
eligible: List[str] = []
filtered: List[str] = []
blocked: List[Tuple[str, str]] = []
connection = open_database(self._db_path)
try:
for task_id in stable_ids:
row = connection.execute(
"SELECT * FROM pdd_tasks WHERE remote_task_id = ?", (task_id,)
).fetchone()
if row is None:
blocked.append((task_id, "任务不存在"))
continue
if row["task_type"] != target_type.value:
filtered.append(task_id)
continue
try:
if target_type is TaskType.COLLECT:
self._check_collect_rerun(connection, row)
else:
self._check_purchase_rerun(connection, row)
except (CollectRerunError, PurchaseRerunError) as exc:
blocked.append((task_id, str(exc)))
else:
eligible.append(task_id)
finally:
connection.close()
return TaskRerunPlan(
target_type, len(stable_ids), tuple(eligible), tuple(filtered), tuple(blocked)
)
def prepare_collect_rerun(self, remote_task_id: str) -> TaskDetail:
"""事务内把已结束的采集任务恢复为待执行,保留旧结果。"""
@@ -508,6 +549,7 @@ class TaskRepository:
if row["task_type"] != TaskType.COLLECT.value:
raise CollectRerunError("采购任务不能重新执行,以免重复下单")
allowed_statuses = {
TaskStatus.CLAIMED.value,
TaskStatus.RETRY_WAIT.value,
TaskStatus.SUCCEEDED.value,
TaskStatus.FAILED.value,
@@ -547,6 +589,77 @@ class TaskRepository:
"任务仍有未发送的结果,请先勾选任务点击“重新上报”"
)
def validate_purchase_rerun(self, remote_task_id: str) -> TaskDetail:
"""校验采购任务能否重新采购;历史上已下单的任务永久拒绝。"""
connection = open_database(self._db_path)
try:
row = connection.execute(
"SELECT * FROM pdd_tasks WHERE remote_task_id = ?", (remote_task_id,)
).fetchone()
if row is None:
raise PurchaseRerunError(f"任务 {remote_task_id} 不存在")
self._check_purchase_rerun(connection, row)
finally:
connection.close()
return self._to_detail(row)
def prepare_purchase_rerun(self, remote_task_id: str) -> TaskDetail:
"""事务内恢复安全采购任务;不会清除任何历史执行记录。"""
now = utc_now_iso()
connection = open_database(self._db_path)
try:
with connection:
row = connection.execute(
"SELECT * FROM pdd_tasks WHERE remote_task_id = ?", (remote_task_id,)
).fetchone()
if row is None:
raise PurchaseRerunError(f"任务 {remote_task_id} 不存在")
self._check_purchase_rerun(connection, row)
connection.execute(
"UPDATE pdd_tasks SET status = 'claimed',"
" current_step = 'purchase_rerun_requested', finished_at = NULL,"
" last_error_code = NULL, last_error_message = NULL, updated_at = ?"
" WHERE id = ?",
(now, row["id"]),
)
finally:
connection.close()
task = self.get_task(remote_task_id)
assert task is not None
return task
@staticmethod
def _check_purchase_rerun(
connection: sqlite3.Connection, row: sqlite3.Row
) -> None:
if row["task_type"] != TaskType.PURCHASE.value:
raise PurchaseRerunError("采集任务不能重新采购")
irreversible = connection.execute(
"SELECT 1 FROM task_runs WHERE task_id = ?"
" AND irreversible_action_at IS NOT NULL LIMIT 1",
(row["id"],),
).fetchone()
if irreversible is not None:
raise PurchaseRerunError("任务历史上已进入下单阶段,只准核对订单,绝不重新下单")
allowed = {
TaskStatus.CLAIMED.value,
TaskStatus.RETRY_WAIT.value,
TaskStatus.SUCCEEDED.value,
TaskStatus.FAILED.value,
TaskStatus.CANCELLED.value,
TaskStatus.MANUAL_REVIEW.value,
}
if row["status"] not in allowed:
raise PurchaseRerunError(f"任务当前状态 {row['status']} 不能重新采购")
unsent = connection.execute(
"SELECT 1 FROM outbox_events WHERE task_id = ? AND status != 'sent' LIMIT 1",
(row["id"],),
).fetchone()
if unsent is not None:
raise PurchaseRerunError("任务仍有未发送数据,请先点击“重新上报”")
def start_collect_run(
self, remote_task_id: str, device_address: str
) -> StartedTaskRun: