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>
This commit is contained in:
Pine
2026-08-25 18:40:35 +08:00
parent b8131aec35
commit b9b5e2e2ff
14 changed files with 818 additions and 28 deletions
@@ -0,0 +1,59 @@
"""task claims and task display fields
Revision ID: f9f4eaef2e6f
Revises: 0007_user_classification
Create Date: 2026-08-25 18:23:09.123825
仅包含本次「任务中心 + 扫码接单」改动:tasks 增加展示/流转字段 + 新增 task_claims 流水表。
autogenerate 曾误带 training_* 删除与 users 列改动,已剔除,勿并入。)
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = 'f9f4eaef2e6f'
down_revision = '0007_user_classification'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ---- task_claims 接单流水表 ----
op.create_table('task_claims',
sa.Column('id', sa.String(), nullable=False),
sa.Column('task_id', sa.String(), nullable=False),
sa.Column('claimer_user_id', sa.String(), nullable=True),
sa.Column('claimer_name', sa.String(), nullable=False, server_default=''),
sa.Column('claim_source', sa.String(), nullable=False, server_default='scan'),
sa.Column('claimed_at', sa.String(), nullable=False, server_default=''),
sa.Column('status', sa.String(), nullable=False, server_default='claimed'),
sa.Column('created_at', sa.String(), nullable=False, server_default=''),
sa.Column('updated_at', sa.String(), nullable=False, server_default=''),
sa.ForeignKeyConstraint(['claimer_user_id'], ['users.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_task_claims_task_id'), 'task_claims', ['task_id'], unique=False)
# ---- tasks 增加展示/流转字段 ----
op.add_column('tasks', sa.Column('task_code', sa.String(), nullable=True))
op.add_column('tasks', sa.Column('tags', sa.String(), nullable=True))
op.add_column('tasks', sa.Column('display_priority', sa.Integer(), nullable=True))
op.add_column('tasks', sa.Column('claimed_by', sa.String(), nullable=True))
op.add_column('tasks', sa.Column('claimed_at', sa.String(), nullable=True))
op.add_column('tasks', sa.Column('doing_at', sa.String(), nullable=True))
op.create_index(op.f('ix_tasks_task_code'), 'tasks', ['task_code'], unique=False)
def downgrade() -> None:
op.drop_index(op.f('ix_tasks_task_code'), table_name='tasks')
op.drop_column('tasks', 'doing_at')
op.drop_column('tasks', 'claimed_at')
op.drop_column('tasks', 'claimed_by')
op.drop_column('tasks', 'display_priority')
op.drop_column('tasks', 'tags')
op.drop_column('tasks', 'task_code')
op.drop_index(op.f('ix_task_claims_task_id'), table_name='task_claims')
op.drop_table('task_claims')