Files
server-core/app/services/task_service.py
T
Pine b9b5e2e2ff feat(task): 任务中心扫码接单(多端) —— 服务端任务状态机/端口/迁移
- Task 加 task_code/tags/display_priority/claimed_by/claimed_at/doing_at;新增 TaskClaim 流水表。
- TaskService 加 claimed/doing/completed 状态机(claim/start_doing/complete),grab 并入 claim。
- TaskRepository 加 list_published/get_by_code/update/claim/set_doing + TaskClaimRepository;挂 Database。
- 端口:
  · opc  /tasks/grab-by-code、/tasks/{id}/doing、/tasks/{id}/complete
  · operator POST /tasks(auto task_code) + PATCH /tasks/:id
  · park  /park/api/tasks(大屏展示,含 scan_payload 二维码载荷)
  · training /api/tasks/claim-by-code、/api/tasks/my(小程序 C 端账号→OPC 身份 find-or-create 领单)
- 迁移 0008(tasks 加列 + task_claims)已应用+stamp;seed 补 task_code/tags/grab demo。
- tests/test_task_claim.py(自包含内存库, 状态机+流水+list_published), 直接 async 校验通过。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 18:40:35 +08:00

103 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""业务层 · 任务状态机(抢单 / 投标 / 交付 / 中标 / 验收)。
业务层只处理业务逻辑;不感知 HTTP 之外的框架细节。依赖基础设施层
Repository(经 Database 门面),不反向依赖接口层。
"""
from __future__ import annotations
from fastapi import HTTPException
from ..infrastructure.repositories import Database
class TaskService:
"""统一任务状态流转:grab/bid/deliver/win/review。"""
def __init__(self, db: Database):
self.db = db
async def grab(self, task_id: str, actor: dict) -> dict:
"""抢单(兼容旧入口):published + grab → claimed,记录接单流水。"""
return await self.claim_by_id(task_id, actor, source="grab")
async def claim(self, task_code: str, actor: dict, source: str = "scan") -> dict:
"""扫码/按短码接单:解析 task_code 后领单。"""
task = await self.db.tasks.get_by_code(task_code)
if task is None:
raise HTTPException(status_code=404, detail="任务不存在")
return await self.claim_by_id(task["id"], actor, source=source)
async def claim_by_id(self, task_id: str, actor: dict, source: str) -> dict:
"""published + grab 模式 → claimedclaimed_by/claimed_at + TaskClaim 流水)。"""
task = await self.db.tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="任务不存在")
if task["status"] != "published":
raise HTTPException(status_code=400, detail="任务不可接单")
if task["mode"] != "grab":
raise HTTPException(status_code=400, detail="任务不支持扫码接单")
updated = await self.db.tasks.claim(task_id, actor["id"])
await self.db.task_claims.create(
task_id, actor["id"],
actor.get("nickname") or actor.get("username", ""), source,
)
return updated
async def start_doing(self, task_id: str, actor: dict) -> dict:
"""claimed → doing。"""
task = await self.db.tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="任务不存在")
if task["status"] != "claimed":
raise HTTPException(status_code=400, detail="任务未接单,无法开始")
return await self.db.tasks.set_doing(task_id)
async def complete(self, task_id: str, actor: dict) -> dict:
"""doing → completed。"""
task = await self.db.tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="任务不存在")
if task["status"] != "doing":
raise HTTPException(status_code=400, detail="任务未在进行中,无法完成")
return await self.db.tasks.set_status(task_id, "completed")
async def bid(self, task_id: str, actor: dict, quote: int, plan: str) -> dict:
"""投标:仅 published + bid 模式可投。"""
task = await self.db.tasks.get(task_id)
if task is None or task["status"] != "published" or task["mode"] != "bid":
raise HTTPException(status_code=400, detail="任务不可投标")
return await self.db.bids.create(
task_id, actor["id"], actor.get("nickname") or actor["username"],
quote, plan,
)
async def deliver(self, task_id: str, actor: dict) -> dict:
"""交付:任务存在且处于进行中(in_progress)才可交付 → delivered。
补齐原实现「无任何状态预检」的缺陷,防随意交付。
"""
task = await self.db.tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
if task["status"] != "in_progress":
raise HTTPException(status_code=400, detail="任务未在进行中,无法交付")
return await self.db.tasks.set_status(task_id, "delivered")
async def enterprise_submit(self, task_id: str) -> dict:
"""企业提交审核:pending 任务 → published。"""
return await self.db.tasks.set_status(task_id, "published")
async def win_bid(self, task_id: str, bid_id: str) -> dict:
"""企业评标中标:bid → wintask → in_progress(双状态联动)。"""
bid = await self.db.bids.get(bid_id)
if bid is None or bid["task_id"] != task_id:
raise HTTPException(status_code=404, detail="竞标不存在")
await self.db.bids.set_status(bid_id, "win")
return await self.db.tasks.set_status(task_id, "in_progress")
async def review(self, task_id: str, accept: bool) -> dict:
"""企业验收:accept → completedreject → in_progress。"""
target = "completed" if accept else "in_progress"
return await self.db.tasks.set_status(task_id, target)