task claim 表单承载:0058 迁移修复(TEXT 去 server_default) + register/grab 申请表单落库 + claims 返回 form
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
"""0058 task claim 表单
|
||||
|
||||
任务申请(报名/抢单)承载提交表单:intro/skills/quote/contact/remark/attachments。
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
# alembic 在 revision 目录执行时,项目根不在 sys.path;显式加入保证可 import models
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
||||
|
||||
from alembic import op # noqa: E402
|
||||
|
||||
revision = "0058_task_claim_form"
|
||||
down_revision = "0057"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"task_claims",
|
||||
sa.Column("form_json", sa.Text(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("task_claims", "form_json")
|
||||
@@ -280,16 +280,27 @@ async def hall_publish_task(
|
||||
return {"ok": True, "id": task["id"], "status": status, "item": task}
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/register", summary="报名任务(register 模式;先到先得,达上限关闭)")
|
||||
@router.post("/tasks/{task_id}/register", summary="报名任务(register 模式;先到先得,达上限关闭;可携带报名表单)")
|
||||
async def hall_register_task(
|
||||
task_id: str,
|
||||
body: dict,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_cap("opc_member")),
|
||||
):
|
||||
from ...services.task_service import TaskService
|
||||
|
||||
claim = await TaskService(db).register(task_id, actor)
|
||||
# 报名表单:自我介绍/技能/期望报价/联系方式/备注/附件(可选)
|
||||
form = {
|
||||
"intro": str(body.get("intro") or "").strip(),
|
||||
"skills": str(body.get("skills") or "").strip(),
|
||||
"quote": int(body.get("quote") or 0),
|
||||
"contact": str(body.get("contact") or "").strip(),
|
||||
"remark": str(body.get("remark") or "").strip(),
|
||||
"attachments": str(body.get("attachments") or "").strip(),
|
||||
}
|
||||
form = {k: v for k, v in form.items() if v not in ("", 0, [])}
|
||||
claim = await TaskService(db).register(task_id, actor, form)
|
||||
# 实时通知:报名者确认 + 发布者收报名提醒
|
||||
from ...services.notification_service import notify as _n
|
||||
task = await db.tasks.get(task_id)
|
||||
@@ -1068,13 +1079,17 @@ async def hall_task_cancel(task_id: str, request: Request,
|
||||
|
||||
# ── 任务统一合并到大厅:OPC 接单侧操作(原 /opc/tasks/*,2026-09 增量合并) ──
|
||||
|
||||
@router.post("/tasks/{task_id}/grab", summary="抢单(grab 模式,先到先得)")
|
||||
async def hall_task_grab(task_id: str, request: Request,
|
||||
@router.post("/tasks/{task_id}/grab", summary="抢单(grab 模式,先到先得;可携带接单备注)")
|
||||
async def hall_task_grab(task_id: str, body: dict, request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(require_cap("opc_member"))):
|
||||
from ...services.task_service import TaskService
|
||||
|
||||
updated = await TaskService(db).grab(task_id, user)
|
||||
form = {}
|
||||
remark = str(body.get("remark") or "").strip()
|
||||
if remark:
|
||||
form["remark"] = remark
|
||||
updated = await TaskService(db).grab(task_id, user, form or None)
|
||||
# 信用事件:接单成功 +1
|
||||
if updated and updated.get("status") in ("doing", "claimed", "accepted"):
|
||||
from ...services.credit_engine import on_credit_event
|
||||
|
||||
@@ -326,6 +326,7 @@ class TaskClaim(Base):
|
||||
claim_source: Mapped[str] = mapped_column(String, default="scan") # scan/grab/bid/assign/recommend
|
||||
claimed_at: Mapped[str] = mapped_column(String, default="")
|
||||
status: Mapped[str] = mapped_column(String, default="claimed") # claimed/doing/completed/withdrawn/assigned/recommended
|
||||
form_json: Mapped[str] = mapped_column(Text, default="{}") # 申请表单(报名/抢单备注:intro/skills/quote/contact/remark/attachments)
|
||||
created_at: Mapped[str] = mapped_column(String, default="")
|
||||
updated_at: Mapped[str] = mapped_column(String, default="")
|
||||
|
||||
|
||||
@@ -1584,20 +1584,27 @@ class TaskClaimRepository:
|
||||
self.session = session
|
||||
|
||||
def _to_dict(self, c: TaskClaim) -> dict:
|
||||
try:
|
||||
form = json.loads(c.form_json or "{}")
|
||||
except Exception:
|
||||
form = {}
|
||||
return {
|
||||
"id": c.id, "task_id": c.task_id,
|
||||
"claimer_user_id": c.claimer_user_id, "claimer_name": c.claimer_name,
|
||||
"claim_source": c.claim_source, "claimed_at": c.claimed_at,
|
||||
"status": c.status, "created_at": c.created_at, "updated_at": c.updated_at,
|
||||
"status": c.status, "form": form,
|
||||
"created_at": c.created_at, "updated_at": c.updated_at,
|
||||
}
|
||||
|
||||
async def create(self, task_id: str, user_id: str, name: str,
|
||||
source: str = "scan", status: str = "claimed") -> dict:
|
||||
source: str = "scan", status: str = "claimed",
|
||||
form: dict | None = None) -> dict:
|
||||
now = utcnow_iso()
|
||||
c = TaskClaim(id=new_id("claim"), task_id=task_id,
|
||||
claimer_user_id=user_id, claimer_name=name,
|
||||
claim_source=source, claimed_at=now,
|
||||
status=status, created_at=now, updated_at=now)
|
||||
status=status, created_at=now, updated_at=now,
|
||||
form_json=json.dumps(form or {}, ensure_ascii=False))
|
||||
self.session.add(c)
|
||||
await self.session.commit()
|
||||
return self._to_dict(c)
|
||||
|
||||
@@ -184,19 +184,21 @@ class TaskService:
|
||||
# ── 统一匹配落单(claim 双写)────────────────────────────────
|
||||
|
||||
async def _match(self, task: dict, actor: dict, taker_id: str, taker_name: str,
|
||||
source: str, claim_status: str = "claimed") -> dict:
|
||||
source: str, claim_status: str = "claimed",
|
||||
form: dict | None = None) -> dict:
|
||||
"""匹配成功统一动作:任务 → claimed + TaskClaim 流水 + IM 同步 + 信用事件。"""
|
||||
updated = await self.db.tasks.claim(task["id"], taker_id)
|
||||
await self.db.task_claims.create(
|
||||
task["id"], taker_id, taker_name, source=source, status=claim_status,
|
||||
form=form or {},
|
||||
)
|
||||
_sync_task_group(task["id"])
|
||||
await _credit_event(self.db, taker_id, "task_match", "任务匹配成功",
|
||||
ref_type="task", ref_id=task["id"])
|
||||
return updated
|
||||
|
||||
async def grab(self, task_id: str, actor: dict) -> dict:
|
||||
return await self.claim_by_id(task_id, actor, source="grab")
|
||||
async def grab(self, task_id: str, actor: dict, form: dict | None = None) -> dict:
|
||||
return await self.claim_by_id(task_id, actor, source="grab", form=form)
|
||||
|
||||
async def claim(self, task_code: str, actor: dict, source: str = "scan") -> dict:
|
||||
task = await self.db.tasks.get_by_code(task_code)
|
||||
@@ -204,7 +206,8 @@ class TaskService:
|
||||
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:
|
||||
async def claim_by_id(self, task_id: str, actor: dict, source: str,
|
||||
form: dict | None = None) -> dict:
|
||||
task = await self.db.tasks.get(task_id)
|
||||
if task is None:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
@@ -225,7 +228,7 @@ class TaskService:
|
||||
raise HTTPException(status_code=403, detail="不满足接单条件:" + ";".join(acc["reasons"]))
|
||||
return await self._match(task, actor, actor["id"],
|
||||
actor.get("nickname") or actor.get("username", ""),
|
||||
source)
|
||||
source, form=form)
|
||||
|
||||
async def assign(self, task_id: str, taker_user_id: str, actor: dict) -> dict:
|
||||
task = await self.db.tasks.get(task_id)
|
||||
@@ -274,7 +277,7 @@ class TaskService:
|
||||
return await self._match(task, actor, taker_user_id, taker_name,
|
||||
source="recommend", claim_status="assigned")
|
||||
|
||||
async def register(self, task_id: str, actor: dict) -> dict:
|
||||
async def register(self, task_id: str, actor: dict, form: dict | None = None) -> dict:
|
||||
task = await self.db.tasks.get(task_id)
|
||||
if task is None or task["status"] != "published" or task["mode"] != "register":
|
||||
raise HTTPException(status_code=400, detail="任务不可报名")
|
||||
@@ -292,7 +295,7 @@ class TaskService:
|
||||
raise HTTPException(status_code=403, detail="不满足报名条件:" + ";".join(acc["reasons"]))
|
||||
return await self.db.task_claims.create(
|
||||
task_id, actor["id"], actor.get("nickname") or actor.get("username", ""),
|
||||
source="register", status="registered",
|
||||
source="register", status="registered", form=form or {},
|
||||
)
|
||||
|
||||
async def register_select(self, task_id: str, claim_id: str, actor: dict) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user