270 lines
9.0 KiB
Python
270 lines
9.0 KiB
Python
"""成本管理路由"""
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from typing import List, Optional
|
|
from datetime import date
|
|
from database import get_db
|
|
from models import (
|
|
User, AIToolCost, AIToolCostAllocation, OutsourceCost,
|
|
CostOverride, OverheadCost, SubscriptionPeriod, CostAllocationType,
|
|
OutsourceType, OverheadCostType
|
|
)
|
|
from schemas import (
|
|
AIToolCostCreate, AIToolCostOut, OutsourceCostCreate, OutsourceCostOut,
|
|
CostOverrideCreate, OverheadCostCreate, OverheadCostOut
|
|
)
|
|
from auth import get_current_user, require_permission
|
|
|
|
router = APIRouter(prefix="/api/costs", tags=["成本管理"])
|
|
|
|
|
|
# ──────────────────── AI 工具成本 ────────────────────
|
|
|
|
@router.get("/ai-tools", response_model=List[AIToolCostOut])
|
|
def list_ai_tool_costs(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:view"))
|
|
):
|
|
costs = db.query(AIToolCost).order_by(AIToolCost.record_date.desc()).all()
|
|
return [
|
|
AIToolCostOut(
|
|
id=c.id, tool_name=c.tool_name,
|
|
subscription_period=c.subscription_period.value if hasattr(c.subscription_period, 'value') else c.subscription_period,
|
|
amount=c.amount,
|
|
allocation_type=c.allocation_type.value if hasattr(c.allocation_type, 'value') else c.allocation_type,
|
|
project_id=c.project_id,
|
|
recorded_by=c.recorded_by,
|
|
record_date=c.record_date,
|
|
created_at=c.created_at,
|
|
)
|
|
for c in costs
|
|
]
|
|
|
|
|
|
@router.post("/ai-tools", response_model=AIToolCostOut)
|
|
def create_ai_tool_cost(
|
|
req: AIToolCostCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:create"))
|
|
):
|
|
cost = AIToolCost(
|
|
tool_name=req.tool_name,
|
|
subscription_period=SubscriptionPeriod(req.subscription_period),
|
|
amount=req.amount,
|
|
allocation_type=CostAllocationType(req.allocation_type),
|
|
project_id=req.project_id,
|
|
recorded_by=current_user.id,
|
|
record_date=req.record_date,
|
|
)
|
|
db.add(cost)
|
|
db.flush()
|
|
|
|
# 处理手动分摊
|
|
if req.allocation_type == "手动分摊" and req.allocations:
|
|
for alloc in req.allocations:
|
|
db.add(AIToolCostAllocation(
|
|
ai_tool_cost_id=cost.id,
|
|
project_id=alloc["project_id"],
|
|
percentage=alloc["percentage"],
|
|
))
|
|
db.commit()
|
|
db.refresh(cost)
|
|
return AIToolCostOut(
|
|
id=cost.id, tool_name=cost.tool_name,
|
|
subscription_period=cost.subscription_period.value,
|
|
amount=cost.amount,
|
|
allocation_type=cost.allocation_type.value,
|
|
project_id=cost.project_id,
|
|
recorded_by=cost.recorded_by,
|
|
record_date=cost.record_date,
|
|
created_at=cost.created_at,
|
|
)
|
|
|
|
|
|
@router.delete("/ai-tools/{cost_id}")
|
|
def delete_ai_tool_cost(
|
|
cost_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:delete"))
|
|
):
|
|
cost = db.query(AIToolCost).filter(AIToolCost.id == cost_id).first()
|
|
if not cost:
|
|
raise HTTPException(status_code=404, detail="记录不存在")
|
|
db.query(AIToolCostAllocation).filter(AIToolCostAllocation.ai_tool_cost_id == cost_id).delete()
|
|
db.delete(cost)
|
|
db.commit()
|
|
return {"message": "已删除"}
|
|
|
|
|
|
# ──────────────────── 外包成本 ────────────────────
|
|
|
|
@router.get("/outsource", response_model=List[OutsourceCostOut])
|
|
def list_outsource_costs(
|
|
project_id: Optional[int] = Query(None),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:view"))
|
|
):
|
|
q = db.query(OutsourceCost)
|
|
if project_id:
|
|
q = q.filter(OutsourceCost.project_id == project_id)
|
|
costs = q.order_by(OutsourceCost.record_date.desc()).all()
|
|
return [
|
|
OutsourceCostOut(
|
|
id=c.id, project_id=c.project_id,
|
|
outsource_type=c.outsource_type.value if hasattr(c.outsource_type, 'value') else c.outsource_type,
|
|
episode_start=c.episode_start, episode_end=c.episode_end,
|
|
amount=c.amount, recorded_by=c.recorded_by,
|
|
record_date=c.record_date, created_at=c.created_at,
|
|
)
|
|
for c in costs
|
|
]
|
|
|
|
|
|
@router.post("/outsource", response_model=OutsourceCostOut)
|
|
def create_outsource_cost(
|
|
req: OutsourceCostCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:create"))
|
|
):
|
|
cost = OutsourceCost(
|
|
project_id=req.project_id,
|
|
outsource_type=OutsourceType(req.outsource_type),
|
|
episode_start=req.episode_start,
|
|
episode_end=req.episode_end,
|
|
amount=req.amount,
|
|
recorded_by=current_user.id,
|
|
record_date=req.record_date,
|
|
)
|
|
db.add(cost)
|
|
db.commit()
|
|
db.refresh(cost)
|
|
return OutsourceCostOut(
|
|
id=cost.id, project_id=cost.project_id,
|
|
outsource_type=cost.outsource_type.value,
|
|
episode_start=cost.episode_start, episode_end=cost.episode_end,
|
|
amount=cost.amount, recorded_by=cost.recorded_by,
|
|
record_date=cost.record_date, created_at=cost.created_at,
|
|
)
|
|
|
|
|
|
@router.delete("/outsource/{cost_id}")
|
|
def delete_outsource_cost(
|
|
cost_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:delete"))
|
|
):
|
|
cost = db.query(OutsourceCost).filter(OutsourceCost.id == cost_id).first()
|
|
if not cost:
|
|
raise HTTPException(status_code=404, detail="记录不存在")
|
|
db.delete(cost)
|
|
db.commit()
|
|
return {"message": "已删除"}
|
|
|
|
|
|
# ──────────────────── 人力成本手动调整 ────────────────────
|
|
|
|
@router.post("/overrides")
|
|
def create_cost_override(
|
|
req: CostOverrideCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:create"))
|
|
):
|
|
override = CostOverride(
|
|
user_id=req.user_id,
|
|
date=req.date,
|
|
project_id=req.project_id,
|
|
override_amount=req.override_amount,
|
|
adjusted_by=current_user.id,
|
|
reason=req.reason,
|
|
)
|
|
db.add(override)
|
|
db.commit()
|
|
return {"message": "已保存成本调整"}
|
|
|
|
|
|
@router.get("/overrides")
|
|
def list_cost_overrides(
|
|
user_id: Optional[int] = Query(None),
|
|
project_id: Optional[int] = Query(None),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:view"))
|
|
):
|
|
q = db.query(CostOverride)
|
|
if user_id:
|
|
q = q.filter(CostOverride.user_id == user_id)
|
|
if project_id:
|
|
q = q.filter(CostOverride.project_id == project_id)
|
|
records = q.order_by(CostOverride.date.desc()).all()
|
|
return [
|
|
{
|
|
"id": r.id, "user_id": r.user_id, "date": r.date,
|
|
"project_id": r.project_id, "override_amount": r.override_amount,
|
|
"adjusted_by": r.adjusted_by, "reason": r.reason,
|
|
"created_at": r.created_at,
|
|
}
|
|
for r in records
|
|
]
|
|
|
|
|
|
# ──────────────────── 固定开支(办公室租金、水电费) ────────────────────
|
|
|
|
@router.get("/overhead", response_model=List[OverheadCostOut])
|
|
def list_overhead_costs(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:view"))
|
|
):
|
|
costs = db.query(OverheadCost).order_by(OverheadCost.record_month.desc()).all()
|
|
return [
|
|
OverheadCostOut(
|
|
id=c.id,
|
|
cost_type=c.cost_type.value if hasattr(c.cost_type, 'value') else c.cost_type,
|
|
amount=c.amount,
|
|
record_month=c.record_month,
|
|
note=c.note,
|
|
recorded_by=c.recorded_by,
|
|
created_at=c.created_at,
|
|
)
|
|
for c in costs
|
|
]
|
|
|
|
|
|
@router.post("/overhead", response_model=OverheadCostOut)
|
|
def create_overhead_cost(
|
|
req: OverheadCostCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:create"))
|
|
):
|
|
cost = OverheadCost(
|
|
cost_type=OverheadCostType(req.cost_type),
|
|
amount=req.amount,
|
|
record_month=req.record_month,
|
|
note=req.note,
|
|
recorded_by=current_user.id,
|
|
)
|
|
db.add(cost)
|
|
db.commit()
|
|
db.refresh(cost)
|
|
return OverheadCostOut(
|
|
id=cost.id,
|
|
cost_type=cost.cost_type.value,
|
|
amount=cost.amount,
|
|
record_month=cost.record_month,
|
|
note=cost.note,
|
|
recorded_by=cost.recorded_by,
|
|
created_at=cost.created_at,
|
|
)
|
|
|
|
|
|
@router.delete("/overhead/{cost_id}")
|
|
def delete_overhead_cost(
|
|
cost_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("cost:delete"))
|
|
):
|
|
cost = db.query(OverheadCost).filter(OverheadCost.id == cost_id).first()
|
|
if not cost:
|
|
raise HTTPException(status_code=404, detail="记录不存在")
|
|
db.delete(cost)
|
|
db.commit()
|
|
return {"message": "已删除"}
|