feat: добавить отзывы, milestone-платежи, портфолио, skill-tests и верификацию (фичи Upwork/Fiverr)
Фичи конкурентов внедрены: - Reviews API + UI — система отзывов с рейтингом 1-5 звёзд - Milestones (Upwork-style) — разделение escrow на этапы с submit/approve - Portfolio — портфолио фрилансера с превью работ и технологиями - Skill Tests (Upwork-style) — сертификация навыков с тестами - Verification Badges — верификация email/phone/id/bank Модели: Milestone, PortfolioItem, SkillTest/SkillTestResult, Verification
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"""Endpoints для Milestone-платежей (Upwork-style)."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_user
|
||||
from app.models.project import Project
|
||||
from app.models.milestone import Milestone
|
||||
from app.models.escrow import EscrowTransaction
|
||||
|
||||
router = APIRouter(prefix="/api/projects/{project_id}/milestones", tags=["milestones"])
|
||||
|
||||
|
||||
@router.post("", response_model=dict)
|
||||
async def create_milestone(
|
||||
project_id: str, data: dict, db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Создать milestone для проекта."""
|
||||
|
||||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||||
project = result.scalar_one_or_none()
|
||||
|
||||
if not project or project.client_id != user["id"]:
|
||||
raise HTTPException(status_code=403, detail="Только владелец проекта может создавать milestones")
|
||||
|
||||
milestone = Milestone(
|
||||
project_id=project_id,
|
||||
title=data.get("title", ""),
|
||||
description=data.get("description"),
|
||||
amount=float(data.get("amount")),
|
||||
due_date=None, # ISO format string
|
||||
)
|
||||
db.add(milestone)
|
||||
await db.commit()
|
||||
await db.refresh(milestone)
|
||||
|
||||
return {"id": str(milestone.id), "status": milestone.status}
|
||||
|
||||
|
||||
@router.patch("/{milestone_id}/submit")
|
||||
async def submit_milestone(milestone_id: str, db: AsyncSession = Depends(get_db)):
|
||||
"""Фрилансер завершает milestone."""
|
||||
|
||||
result = await db.execute(select(Milestone).where(Milestone.id == milestone_id))
|
||||
milestone = result.scalar_one_or_none()
|
||||
|
||||
if not milestone or milestone.status != "funded":
|
||||
raise HTTPException(status_code=400, detail="Milestone не может быть завершён")
|
||||
|
||||
milestone.status = "submitted"
|
||||
await db.commit()
|
||||
|
||||
return {"status": "submitted", "milestone_id": str(milestone.id)}
|
||||
|
||||
|
||||
@router.patch("/{milestone_id}/approve")
|
||||
async def approve_milestone(milestone_id: str, user: dict = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
"""Клиент одобряет milestone."""
|
||||
|
||||
result = await db.execute(select(Milestone).where(Milestone.id == milestone_id))
|
||||
milestone = result.scalar_one_or_none()
|
||||
|
||||
if not milestone or milestone.status != "submitted":
|
||||
raise HTTPException(status_code=400, detail="Milestone не может быть одобрен")
|
||||
|
||||
milestone.status = "approved"
|
||||
await db.commit()
|
||||
|
||||
return {"status": "approved", "milestone_id": str(milestone.id)}
|
||||
Reference in New Issue
Block a user