任务资格要求:最低信用分/要求勋章/要求认证(0061),can_accept 校验,发布端点接收
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
"""0061: 任务资格要求扩展:最低信用分 / 要求勋章 / 要求认证
|
||||
|
||||
发布方可在发布任务时指定:
|
||||
- min_credit_score:接单方最低信用分(0=不限制)
|
||||
- required_badges:要求持有徽章 code 列表(JSON)
|
||||
- required_certifications:要求完成认证 code 列表(JSON,如 opc_talent/business_reg/corporate_account 等)
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "0061_task_eligibility"
|
||||
down_revision = "0060_task_sign"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("tasks", sa.Column("min_credit_score", sa.Integer(), nullable=True, server_default="0"))
|
||||
op.add_column("tasks", sa.Column("required_badges", sa.Text(), nullable=True))
|
||||
op.add_column("tasks", sa.Column("required_certifications", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("tasks", "required_certifications")
|
||||
op.drop_column("tasks", "required_badges")
|
||||
op.drop_column("tasks", "min_credit_score")
|
||||
@@ -250,6 +250,9 @@ async def hall_publish_task(
|
||||
"escrow_ratio": int(body.get("escrowRatio") or body.get("escrow_ratio") or 100),
|
||||
"invoice_req": json.dumps(body.get("invoiceReq") or {}, ensure_ascii=False),
|
||||
"milestone_json": json.dumps(body.get("milestones") or [], ensure_ascii=False),
|
||||
"min_credit_score": int(body.get("minCreditScore") or body.get("min_credit_score") or 0),
|
||||
"required_badges": json.dumps(body.get("requiredBadges") or body.get("required_badges") or [], ensure_ascii=False),
|
||||
"required_certifications": json.dumps(body.get("requiredCertifications") or body.get("required_certifications") or [], ensure_ascii=False),
|
||||
"status": status,
|
||||
"published_at": utcnow_iso() if status == "published" else "",
|
||||
"publisher_id": user.get("id"),
|
||||
|
||||
@@ -288,6 +288,9 @@ class Task(Base):
|
||||
win_quota: Mapped[int] = mapped_column(Integer, default=0) # 竞标中标人数(默认1)
|
||||
register_quota: Mapped[int] = mapped_column(Integer, default=0) # 报名模式人数上限(0=不限)(0035)
|
||||
eligibility: Mapped[str] = mapped_column(Text, default="{}") # 接单资格条件(JSON 集)
|
||||
min_credit_score: Mapped[int] = mapped_column(Integer, default=0) # 最低信用分要求(0=不限制)
|
||||
required_badges: Mapped[str] = mapped_column(Text, default="[]") # 要求徽章 code 列表(JSON)
|
||||
required_certifications: Mapped[str] = mapped_column(Text, default="[]") # 要求认证 code 列表(JSON)
|
||||
settle_mode: Mapped[str] = mapped_column(String, default="operator_escrow") # 资金托管方式
|
||||
# ── 任务系统 v2 扩展字段(0056)──
|
||||
resource_support_json: Mapped[str] = mapped_column(Text, default="{}") # 提供的支持(算力/数据/场地/工具/对接/资金)
|
||||
|
||||
@@ -1321,6 +1321,9 @@ class TaskRepository:
|
||||
"bid_quota": t.bid_quota, "win_quota": t.win_quota,
|
||||
"register_quota": t.register_quota,
|
||||
"eligibility": t.eligibility, "settle_mode": t.settle_mode,
|
||||
"min_credit_score": t.min_credit_score,
|
||||
"required_badges": t.required_badges,
|
||||
"required_certifications": t.required_certifications,
|
||||
"created_at": t.created_at, "updated_at": t.updated_at,
|
||||
}
|
||||
|
||||
@@ -1381,6 +1384,9 @@ class TaskRepository:
|
||||
win_quota=fields.get("win_quota", 0),
|
||||
register_quota=fields.get("register_quota", 0),
|
||||
eligibility=fields.get("eligibility", "{}"),
|
||||
min_credit_score=fields.get("min_credit_score", 0),
|
||||
required_badges=fields.get("required_badges", "[]"),
|
||||
required_certifications=fields.get("required_certifications", "[]"),
|
||||
settle_mode=fields.get("settle_mode", "operator_escrow"),
|
||||
resource_support_json=fields.get("resource_support_json", "{}"),
|
||||
raw_materials_json=fields.get("raw_materials_json", "[]"),
|
||||
@@ -1409,7 +1415,8 @@ class TaskRepository:
|
||||
"publisher_role", "visibility", "c_visible", "park_public",
|
||||
"assign_type", "assign_park_id", "assign_opc_id", "park_id",
|
||||
"park_released", "bid_quota", "win_quota", "register_quota",
|
||||
"eligibility", "settle_mode",
|
||||
"eligibility", "min_credit_score", "required_badges", "required_certifications",
|
||||
"settle_mode",
|
||||
"resource_support_json", "raw_materials_json", "deliver_way",
|
||||
"deliver_items", "accept_standard", "price_type", "pay_type",
|
||||
"escrow_ratio", "invoice_req", "milestone_json",
|
||||
@@ -1597,6 +1604,9 @@ class TaskRepository:
|
||||
"bid_quota": t.bid_quota, "win_quota": t.win_quota,
|
||||
"register_quota": t.register_quota,
|
||||
"eligibility": t.eligibility, "settle_mode": t.settle_mode,
|
||||
"min_credit_score": t.min_credit_score,
|
||||
"required_badges": t.required_badges,
|
||||
"required_certifications": t.required_certifications,
|
||||
"resource_support_json": t.resource_support_json,
|
||||
"raw_materials_json": t.raw_materials_json,
|
||||
"deliver_way": t.deliver_way, "deliver_items": t.deliver_items,
|
||||
|
||||
@@ -98,10 +98,28 @@ class TaskService:
|
||||
return {}
|
||||
full = (await self.db.users.get_by_id(uid)) or {}
|
||||
credit = await self.db.credit_scores.get(uid)
|
||||
# 勋章(active)
|
||||
badges: list[str] = []
|
||||
try:
|
||||
for ub in await self.db.user_badges.list_active(uid):
|
||||
if ub.get("badge_code"):
|
||||
badges.append(ub["badge_code"])
|
||||
except Exception: # noqa: BLE001
|
||||
badges = []
|
||||
# 认证(approved/active)
|
||||
certs: list[str] = []
|
||||
try:
|
||||
for c in await self.db.certifications.list_by_user(uid):
|
||||
if c.get("status") in ("approved", "active") and c.get("cert_type"):
|
||||
certs.append(c["cert_type"])
|
||||
except Exception: # noqa: BLE001
|
||||
certs = []
|
||||
return {
|
||||
**full,
|
||||
"credit_level": (credit or {}).get("level", "L1"),
|
||||
"credit_score": (credit or {}).get("total_score", 0),
|
||||
"badges": badges,
|
||||
"certifications": certs,
|
||||
}
|
||||
|
||||
async def can_accept(self, task: dict | None, actor: dict | None) -> dict:
|
||||
@@ -169,6 +187,29 @@ class TaskService:
|
||||
if cond.get("deposit") and int(cond["deposit"] or 0) > 0:
|
||||
reasons.append("需缴纳接单保证金")
|
||||
|
||||
# ── 独立资格字段(0061):最低信用分 / 要求勋章 / 要求认证 ──
|
||||
min_score = int(task.get("min_credit_score") or 0)
|
||||
if min_score > 0 and int(full.get("credit_score") or 0) < min_score:
|
||||
reasons.append(f"信用分需 ≥ {min_score}(当前 {full.get('credit_score', 0)})")
|
||||
try:
|
||||
req_badges = json.loads(task.get("required_badges") or "[]")
|
||||
except Exception: # noqa: BLE001
|
||||
req_badges = []
|
||||
if isinstance(req_badges, list) and req_badges:
|
||||
have_badges = set(full.get("badges") or [])
|
||||
missing_b = [b for b in req_badges if b not in have_badges]
|
||||
if missing_b:
|
||||
reasons.append(f"需持有徽章:{'、'.join(missing_b)}")
|
||||
try:
|
||||
req_certs = json.loads(task.get("required_certifications") or "[]")
|
||||
except Exception: # noqa: BLE001
|
||||
req_certs = []
|
||||
if isinstance(req_certs, list) and req_certs:
|
||||
have_certs = set(full.get("certifications") or [])
|
||||
missing_c = [c for c in req_certs if c not in have_certs]
|
||||
if missing_c:
|
||||
reasons.append(f"需完成认证:{'、'.join(missing_c)}")
|
||||
|
||||
# 软性提示(不阻断)
|
||||
if cond.get("price_range") and isinstance(cond["price_range"], dict):
|
||||
pr = cond["price_range"]
|
||||
|
||||
Reference in New Issue
Block a user