Files
cmautobuy/client/src/task_models.py
T

236 lines
6.0 KiB
Python
Raw Normal View History

"""Client 任务数据库使用的枚举和简单数据对象。
本文件不依赖 Qt、Admin 客户端或 uiautomator2,只表达数据含义。
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, Mapping, Optional, Tuple
class TaskType(str, Enum):
"""PDD 任务类型。"""
COLLECT = "collect"
PURCHASE = "purchase"
class TaskStatus(str, Enum):
"""PDD 任务本地状态。"""
CLAIMED = "claimed"
RUNNING = "running"
RESULT_PENDING = "result_pending"
RETRY_WAIT = "retry_wait"
MANUAL_REVIEW = "manual_review"
SUCCEEDED = "succeeded"
FAILED = "failed"
CANCELLED = "cancelled"
class RunStatus(str, Enum):
"""单次任务执行状态。"""
RUNNING = "running"
SUCCEEDED = "succeeded"
FAILED = "failed"
CANCELLED = "cancelled"
MANUAL_REVIEW = "manual_review"
class OutboxEventType(str, Enum):
"""等待提交 Admin 的事件类型。"""
COLLECT_RESULT = "collect_result"
PURCHASE_RESULT = "purchase_result"
TASK_FAILURE = "task_failure"
class OutboxStatus(str, Enum):
"""Outbox 事件发送状态。"""
PENDING = "pending"
SENDING = "sending"
SENT = "sent"
FAILED = "failed"
@dataclass(frozen=True)
class NewClaimedTask:
"""刚从 Admin 领取、准备写入本地数据库的任务。"""
remote_task_id: str
task_type: TaskType
goods_url: str
execution_mode: str = "dry_run"
goods_id: Optional[str] = None
title: Optional[str] = None
target_color: Optional[str] = None
target_size: Optional[str] = None
price_cent: Optional[int] = None
quantity: Optional[int] = None
priority: int = 0
version: int = 1
admin_payload: Mapping[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
if not isinstance(self.task_type, TaskType):
raise ValueError("task_type 必须是 TaskType")
if not self.remote_task_id.strip():
raise ValueError("remote_task_id 不能为空")
if not self.goods_url.strip():
raise ValueError("goods_url 不能为空")
if self.execution_mode not in {"dry_run", "live"}:
raise ValueError("execution_mode 只允许 dry_run 或 live")
if (
self.task_type is not TaskType.PURCHASE
and self.execution_mode != "dry_run"
):
raise ValueError("只有采购任务允许 execution_mode=live")
if self.price_cent is not None and self.price_cent < 0:
raise ValueError("price_cent 不能小于 0")
if self.quantity is not None and self.quantity <= 0:
raise ValueError("quantity 必须大于 0")
if self.version <= 0:
raise ValueError("version 必须大于 0")
@dataclass(frozen=True)
class TaskFilters:
"""本地任务列表的可选筛选条件。"""
task_type: Optional[TaskType] = None
status: Optional[TaskStatus] = None
keyword: str = ""
@dataclass(frozen=True)
class TaskRerunPlan:
"""批量重新执行预检结果;界面只展示计数,不自行判断安全条件。"""
target_type: TaskType
selected_count: int
eligible_task_ids: Tuple[str, ...]
filtered_task_ids: Tuple[str, ...]
blocked: Tuple[Tuple[str, str], ...]
@dataclass(frozen=True)
class TaskRerunBatch:
"""交给固定设备线程串行处理的批量命令。"""
target_type: TaskType
task_ids: Tuple[str, ...]
@dataclass(frozen=True)
class TaskSummary:
"""任务表格使用的轻量数据,不包含完整 JSON。"""
id: int
remote_task_id: str
task_type: TaskType
goods_id: Optional[str]
title: Optional[str]
target_color: Optional[str]
target_size: Optional[str]
price_cent: Optional[int]
quantity: Optional[int]
status: TaskStatus
updated_at: str
shop_name: Optional[str] = None
latest_run_status: Optional[RunStatus] = None
duration_seconds: Optional[int] = None
order_no: Optional[str] = None
@dataclass(frozen=True)
class TaskDetail:
"""一条 PDD 任务的完整本地记录。"""
id: int
remote_task_id: str
task_type: TaskType
goods_id: Optional[str]
goods_url: str
title: Optional[str]
target_color: Optional[str]
target_size: Optional[str]
price_cent: Optional[int]
quantity: Optional[int]
status: TaskStatus
current_step: Optional[str]
priority: int
version: int
admin_payload: Dict[str, Any]
pdd_data: Optional[Dict[str, Any]]
retry_count: int
last_error_code: Optional[str]
last_error_message: Optional[str]
received_at: str
started_at: Optional[str]
finished_at: Optional[str]
created_at: str
updated_at: str
execution_mode: str = "dry_run"
@dataclass(frozen=True)
class TaskRunRecord:
"""一次任务执行的数据库记录。"""
id: int
task_id: int
attempt_id: str
attempt_no: int
device_address: str
run_status: RunStatus
current_step: Optional[str]
started_at: str
finished_at: Optional[str]
irreversible_action_at: Optional[str]
order_submitted_at: Optional[str]
error_code: Optional[str]
error_message: Optional[str]
diagnostics_json: Optional[Dict[str, Any]]
result_data: Optional[Dict[str, Any]]
artifact_directory: Optional[str]
created_at: str
updated_at: str
@dataclass(frozen=True)
class OutboxEventRecord:
"""一条等待提交 Admin 的可靠事件记录。"""
id: int
task_id: int
event_type: OutboxEventType
idempotency_key: str
payload_json: Dict[str, Any]
status: OutboxStatus
attempt_count: int
next_retry_at: Optional[str]
last_error: Optional[str]
created_at: str
updated_at: str
sent_at: Optional[str]
@dataclass(frozen=True)
class StartedTaskRun:
2026-08-09 23:39:37 +08:00
"""已进入执行状态的一次采集或采购尝试。"""
task: TaskDetail
attempt_id: str
attempt_no: int
@dataclass(frozen=True)
class AppSettingRecord:
"""一条非敏感应用设置。"""
setting_key: str
value: Any
updated_at: str