feat(tasks): 运营端任务详情/竞标/报名端点 + 园区发布任务端点
- 运营端 /admin/tasks/{id} 详情、/{id}/bids、/{id}/claims、/{id}/bids/{bid_id}/win 开标定标(余标置lost+进 in_progress)
- 园区端 POST /park/api/park/tasks 发布任务(publisher_role=park/park_id=本园/发布即上架/默认公有)
This commit is contained in:
@@ -75,6 +75,59 @@ async def operator_escrow_release(
|
||||
return released
|
||||
|
||||
|
||||
@router.get("/tasks/{task_id}", summary="任务详情")
|
||||
async def get_task_detail(
|
||||
task_id: str,
|
||||
db: Database = Depends(get_db),
|
||||
_u: dict = Depends(require_roles("operator")),
|
||||
):
|
||||
task = await db.tasks.get(task_id)
|
||||
if task is None:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
return task
|
||||
|
||||
|
||||
@router.get("/tasks/{task_id}/bids", summary="任务竞标(报名/投标情况)")
|
||||
async def get_task_bids(
|
||||
task_id: str,
|
||||
db: Database = Depends(get_db),
|
||||
_u: dict = Depends(require_roles("operator")),
|
||||
):
|
||||
return {"items": await db.bids.list_for_task(task_id)}
|
||||
|
||||
|
||||
@router.get("/tasks/{task_id}/claims", summary="任务报名/接单情况(抢单/指派/推荐)")
|
||||
async def get_task_claims(
|
||||
task_id: str,
|
||||
db: Database = Depends(get_db),
|
||||
_u: dict = Depends(require_roles("operator")),
|
||||
):
|
||||
return {"items": await db.task_claims.list_by_task(task_id)}
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/bids/{bid_id}/win", summary="开标/定标(选定中标 OPC)")
|
||||
async def operator_win_bid(
|
||||
task_id: str,
|
||||
bid_id: str,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
_u: dict = Depends(require_roles("operator")),
|
||||
):
|
||||
from ...services.task_service import TaskService
|
||||
|
||||
bid = await db.bids.set_status(bid_id, "win")
|
||||
if bid is None:
|
||||
raise HTTPException(status_code=404, detail="竞标不存在")
|
||||
# 其余标置为 lost
|
||||
for b in await db.bids.list_for_task(task_id):
|
||||
if b["id"] != bid_id and b.get("status") == "submitted":
|
||||
await db.bids.set_status(b["id"], "lost")
|
||||
await db.tasks.set_status(task_id, "in_progress")
|
||||
await write_audit(db, action="bid.win", resource="bid", resource_id=bid_id,
|
||||
detail=f"task={task_id} winner={bid.get('opc_name')}", user=_u, request=request)
|
||||
return bid
|
||||
|
||||
|
||||
@router.get("/tasks", summary="任务列表")
|
||||
async def list_tasks(
|
||||
status: str | None = None,
|
||||
|
||||
@@ -526,6 +526,54 @@ class ParkStatusBody(BaseModel):
|
||||
status: str # published/cancelled
|
||||
|
||||
|
||||
class ParkTaskCreateBody(BaseModel):
|
||||
title: str
|
||||
category: str = ""
|
||||
sub_category: str = ""
|
||||
description: str = ""
|
||||
tags: str = ""
|
||||
mode: str = "grab"
|
||||
budget_min: int = 0
|
||||
budget_max: int = 0
|
||||
deadline: str = ""
|
||||
delivery_days: int = 0
|
||||
headcount: int = 0
|
||||
exclusive: bool = False
|
||||
visibility: str = "public"
|
||||
c_visible: bool = True
|
||||
park_public: bool = True # 园区发布默认公有(各园大屏可见/本园可接)
|
||||
bid_quota: int = 0
|
||||
win_quota: int = 0
|
||||
eligibility: str = "{}"
|
||||
settle_mode: str = "operator_escrow"
|
||||
|
||||
|
||||
@router.post("/api/park/tasks", summary="园区发布任务")
|
||||
async def park_task_create(
|
||||
body: ParkTaskCreateBody,
|
||||
authorization: str | None = Header(None),
|
||||
tenant_id: str | None = None,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
"""园区发布任务:publisher_role=park、park_id=本园,发布即上架(published)。"""
|
||||
tid = await _resolve_tenant(authorization, tenant_id)
|
||||
repo = TaskRepository(session)
|
||||
t = await tenants.get_tenant(tid)
|
||||
task = await repo.create({
|
||||
"title": body.title, "category": body.category, "sub_category": body.sub_category,
|
||||
"description": body.description, "tags": body.tags, "mode": body.mode,
|
||||
"budget_min": body.budget_min, "budget_max": body.budget_max, "deadline": body.deadline,
|
||||
"delivery_days": body.delivery_days, "headcount": body.headcount, "exclusive": body.exclusive,
|
||||
"publisher_role": "park", "park_id": tid,
|
||||
"publisher_name": (t or {}).get("name", "") or "园区",
|
||||
"visibility": body.visibility, "c_visible": body.c_visible, "park_public": body.park_public,
|
||||
"bid_quota": body.bid_quota, "win_quota": body.win_quota,
|
||||
"eligibility": body.eligibility, "settle_mode": body.settle_mode,
|
||||
"status": "published", "display_priority": 40,
|
||||
})
|
||||
return task
|
||||
|
||||
|
||||
@router.get("/api/park/tasks", summary="园区端任务(本园发布 + 指派本园)")
|
||||
async def park_task_list(
|
||||
authorization: str | None = Header(None),
|
||||
@@ -1149,6 +1197,8 @@ async def carrier_company_add_member(body: CompanyMemberBody, user: dict = Depen
|
||||
r = await tenants.add_company_member(body.company_id, body.user_id)
|
||||
if r is None:
|
||||
raise HTTPException(status_code=404, detail="企业或用户不存在")
|
||||
if not r.get("ok"):
|
||||
raise HTTPException(status_code=400, detail=r.get("reason", "绑定失败"))
|
||||
return r
|
||||
|
||||
|
||||
@@ -1250,8 +1300,12 @@ async def park_user_create(body: ParkUserCreateBody, db=Depends(_carrier_db), us
|
||||
uname = body.username.strip()
|
||||
if not uname:
|
||||
raise HTTPException(status_code=400, detail="账号必填")
|
||||
if not (body.nickname or "").strip():
|
||||
raise HTTPException(status_code=400, detail="姓名必填")
|
||||
if await db.users.get_by_username(uname):
|
||||
raise HTTPException(status_code=400, detail="账号已存在")
|
||||
if body.phone and await db.users.find_by_phone(body.phone):
|
||||
raise HTTPException(status_code=400, detail="手机号已被使用")
|
||||
if not body.password:
|
||||
raise HTTPException(status_code=400, detail="请设置初始密码")
|
||||
u = await db.users.create(uname, body.password, nickname=body.nickname, gender=body.gender,
|
||||
|
||||
Reference in New Issue
Block a user