# -*- coding: utf-8 -*- """业务层 · 结算/合同/争议/撮合服务。 资金相关规则(佣金、结算、争议联动)在业务层集中,接口层瘦身。 """ from __future__ import annotations from fastapi import HTTPException from ..infrastructure.repositories import Database # 平台佣金比例 COMMISSION_RATE = 0.05 class SettlementService: """结算托管、电子合同、争议、投资撮合。""" def __init__(self, db: Database): self.db = db async def release_escrow(self, task_id: str) -> dict: """验收通过后结算:任务须 completed;无托管则兜底创建(佣金 5%)。""" task = await self.db.tasks.get(task_id) if task is None or task["status"] != "completed": raise HTTPException(status_code=400, detail="任务未完成,不能结算") escrows = await self.db.escrows.list() esc = next((e for e in escrows if e["task_id"] == task_id), None) if esc is None: amount = max(task.get("budget_min", 0), task.get("budget_max", 0)) commission = int(amount * COMMISSION_RATE) esc = await self.db.escrows.create(task_id, task["title"], amount, commission) return await self.db.escrows.set_status(esc["id"], "released") async def sign_contract(self, task_id: str, actor: dict, opc_id: str | None = None) -> dict: """签订电子合同(幂等:已签返回现有合同)。""" task = await self.db.tasks.get(task_id) if task is None: raise HTTPException(status_code=404, detail="Task not found") existing = await self.db.contracts.get_for_task(task_id) if existing is not None: return existing resolved_opc = opc_id or (actor["id"] if actor.get("role") == "opc_member" else "") return await self.db.contracts.create(task_id, task["title"], actor["id"], resolved_opc) async def create_dispute(self, task_id: str, actor: dict, reason: str) -> dict: """发起争议并联动任务置为 disputed。""" task = await self.db.tasks.get(task_id) if task is None: raise HTTPException(status_code=404, detail="Task not found") dispute = await self.db.disputes.create(task_id, task["title"], actor["id"], reason) await self.db.tasks.set_status(task_id, "disputed") return dispute async def resolve_dispute(self, dispute_id: str) -> dict: dispute = await self.db.disputes.set_status(dispute_id, "resolved", resolution="平台调解结案") if dispute is None: raise HTTPException(status_code=404, detail="Dispute not found") return dispute async def investor_matches(self, user_id: str) -> dict: """投资撮合:按投资人偏好(行业/阶段关键词)打分排序。""" pref = await self.db.investor_prefs.get(user_id) or {} industries = set(pref.get("industries") or []) data = await self.db.portal_pages.get("investor", "projects") or {"items": []} scored = [] for it in data.get("items", []): text = f"{it.get('title', '')} {it.get('meta', '')}" score = sum(10 for ind in industries if ind and ind in text) if pref.get("stage") and pref["stage"] in text: score += 5 scored.append((score, it)) scored.sort(key=lambda x: x[0], reverse=True) return {"items": [{"title": it["title"], "meta": it["meta"], "tag": it.get("tag"), "match": min(100, s + 50)} for s, it in scored]}