94 lines
3.5 KiB
Python
Executable File
94 lines
3.5 KiB
Python
Executable File
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/expenses.py
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func
|
|
from app.api.deps import get_db, get_current_user
|
|
from app.models import Asset, AssetCost, SystemParameter
|
|
from app.schemas.asset_cost import AssetCostCreate
|
|
from datetime import datetime
|
|
|
|
router = APIRouter()
|
|
|
|
@router.post("/", status_code=201)
|
|
async def create_expense(
|
|
expense: AssetCostCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Create a new expense (fuel, service, tax, insurance) for an asset.
|
|
Uses AssetCostCreate schema which includes mileage_at_cost, cost_type, etc.
|
|
"""
|
|
# Validate asset exists
|
|
stmt = select(Asset).where(Asset.id == expense.asset_id)
|
|
result = await db.execute(stmt)
|
|
asset = result.scalar_one_or_none()
|
|
if not asset:
|
|
raise HTTPException(status_code=404, detail="Asset not found.")
|
|
|
|
# Dynamic Gatekeeper: Check draft expense limit
|
|
if asset.status == "draft":
|
|
# 1. Get VEHICLE_DRAFT_MAX_EXPENSES parameter
|
|
param_stmt = select(SystemParameter).where(
|
|
SystemParameter.key == "VEHICLE_DRAFT_MAX_EXPENSES",
|
|
SystemParameter.scope_level == "global"
|
|
)
|
|
param_result = await db.execute(param_stmt)
|
|
param = param_result.scalar_one_or_none()
|
|
|
|
if param:
|
|
limit = param.value.get("limit", 10) # Default to 10 if not found
|
|
else:
|
|
limit = 10 # Default fallback
|
|
|
|
# 2. Count existing expenses for this asset
|
|
count_stmt = select(func.count(AssetCost.id)).where(AssetCost.asset_id == asset.id)
|
|
count_result = await db.execute(count_stmt)
|
|
expense_count = count_result.scalar()
|
|
|
|
# 3. Check if limit reached
|
|
if expense_count >= limit:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail=f"DRAFT_LIMIT_REACHED: Draft vehicles are limited to {limit} expenses. This asset already has {expense_count} expenses."
|
|
)
|
|
|
|
# Determine organization_id from asset (required by AssetCost model)
|
|
organization_id = asset.current_organization_id or asset.owner_org_id
|
|
if not organization_id:
|
|
raise HTTPException(status_code=400, detail="Asset has no associated organization.")
|
|
|
|
# Map cost_type to cost_category (AssetCost uses cost_category)
|
|
cost_category = expense.cost_type
|
|
|
|
# Prepare data JSON for extra fields (mileage_at_cost, description, etc.)
|
|
data = expense.data.copy() if expense.data else {}
|
|
if expense.mileage_at_cost is not None:
|
|
data["mileage_at_cost"] = expense.mileage_at_cost
|
|
if expense.description:
|
|
data["description"] = expense.description
|
|
|
|
# Create AssetCost instance
|
|
new_cost = AssetCost(
|
|
asset_id=expense.asset_id,
|
|
organization_id=organization_id,
|
|
cost_category=cost_category,
|
|
amount_net=expense.amount_local,
|
|
currency=expense.currency_local,
|
|
date=expense.date,
|
|
invoice_number=data.get("invoice_number"),
|
|
data=data
|
|
)
|
|
|
|
db.add(new_cost)
|
|
await db.commit()
|
|
await db.refresh(new_cost)
|
|
|
|
return {
|
|
"status": "success",
|
|
"id": new_cost.id,
|
|
"asset_id": new_cost.asset_id,
|
|
"cost_category": new_cost.cost_category,
|
|
"amount_net": new_cost.amount_net,
|
|
"date": new_cost.date
|
|
} |