feat(tasks): 阶段A后端核心 —— 多模式任务字段 + 接单资格判定 + 各模式限额守卫 + 公开广场/详情can_accept

- Task 增扩展列:publisher_role/visibility/c_visible/park_public/assign_type/assign_park_id/assign_opc_id/park_id/park_released/bid_quota/win_quota/eligibility(JSON)/settle_mode(默认operator_escrow)
- TaskRepository._to_dict/create/update 同步新字段;迁移 0020_task_multi_mode
- TaskService.can_accept(task,user):仅被指派/园区专属/指派园区未发单 + eligibility(证书/地域/性别/工时/领域/专长/案例) 判定,返回 ok/reasons
- 抢单(grab)与投标(bid)接入 can_accept 守卫;bid 加报名上限与重复报名校验
- /opc/tasks 公开广场改为 published+c_visible;/opc/tasks/{id} 详情同;登录后附带 can_accept/accept_reasons(经 optional_current_user)
This commit is contained in:
Pine
2026-08-27 19:05:18 +08:00
parent 11598abcc1
commit 076f96af74
5 changed files with 198 additions and 14 deletions
+48
View File
@@ -0,0 +1,48 @@
"""多端任务体系:任务扩展列(发布方/可见性/指派/园区/竞标配额/接单资格/托管)
Revision ID: 0020_task_multi_mode
Revises: 0019_content_review
Create Date: 2026-08-27
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = "0020_task_multi_mode"
down_revision = "0019_content_review"
branch_labels = None
depends_on = None
def upgrade() -> None:
cols = [
("publisher_role", sa.String(), "server_default", ""),
("visibility", sa.String(), "server_default", "public"),
("c_visible", sa.Boolean(), "server_default", sa.text("1")),
("park_public", sa.Boolean(), "server_default", sa.text("0")),
("assign_type", sa.String(), "server_default", ""),
("assign_park_id", sa.String(), "nullable", True),
("assign_opc_id", sa.String(), "nullable", True),
("park_id", sa.String(), "nullable", True),
("park_released", sa.Boolean(), "server_default", sa.text("0")),
("bid_quota", sa.Integer(), "server_default", "0"),
("win_quota", sa.Integer(), "server_default", "0"),
("eligibility", sa.Text(), "server_default", "{}"),
("settle_mode", sa.String(), "server_default", "operator_escrow"),
]
for name, typ, kw, val in cols:
if kw == "nullable":
op.add_column("tasks", sa.Column(name, typ, nullable=val))
elif kw == "server_default":
op.add_column("tasks", sa.Column(name, typ, nullable=False, server_default=val))
def downgrade() -> None:
for name, *_ in [
("publisher_role",), ("visibility",), ("c_visible",), ("park_public",),
("assign_type",), ("assign_park_id",), ("assign_opc_id",), ("park_id",),
("park_released",), ("bid_quota",), ("win_quota",), ("eligibility",),
("settle_mode",),
]:
op.drop_column("tasks", name)