6ecb110768
Фичи конкурентов внедрены: - 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
72 lines
2.7 KiB
Python
72 lines
2.7 KiB
Python
"""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)}
|