chore(quality): apply ruff autofixes and formatting (29 fixes, 41 files reformatted)
This commit is contained in:
@@ -42,9 +42,7 @@ async def get_current_user(
|
||||
|
||||
# Load user with role (and its permissions) eagerly
|
||||
result = await session.execute(
|
||||
select(User)
|
||||
.options(joinedload(User.role))
|
||||
.where(User.id == user_id)
|
||||
select(User).options(joinedload(User.role)).where(User.id == user_id)
|
||||
)
|
||||
user = result.unique().scalars().first()
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ def _build_user_info(user: User, role: Role | None = None) -> UserInfo:
|
||||
)
|
||||
|
||||
|
||||
@router.post("/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def register(
|
||||
body: RegisterRequest,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
@@ -57,7 +59,9 @@ async def register(
|
||||
)
|
||||
|
||||
# Check if account name already exists
|
||||
result = await session.execute(select(Account).where(Account.name == body.account_name))
|
||||
result = await session.execute(
|
||||
select(Account).where(Account.name == body.account_name)
|
||||
)
|
||||
if result.scalars().first():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
@@ -71,13 +75,26 @@ async def register(
|
||||
|
||||
# Create admin role
|
||||
admin_permissions = [
|
||||
"projects:read", "projects:write", "projects:delete",
|
||||
"equipment:read", "equipment:write", "equipment:delete",
|
||||
"crew:read", "crew:write", "crew:delete",
|
||||
"vehicles:read", "vehicles:write", "vehicles:delete",
|
||||
"contacts:read", "contacts:write", "contacts:delete",
|
||||
"users:read", "users:write", "users:delete",
|
||||
"roles:read", "roles:write",
|
||||
"projects:read",
|
||||
"projects:write",
|
||||
"projects:delete",
|
||||
"equipment:read",
|
||||
"equipment:write",
|
||||
"equipment:delete",
|
||||
"crew:read",
|
||||
"crew:write",
|
||||
"crew:delete",
|
||||
"vehicles:read",
|
||||
"vehicles:write",
|
||||
"vehicles:delete",
|
||||
"contacts:read",
|
||||
"contacts:write",
|
||||
"contacts:delete",
|
||||
"users:read",
|
||||
"users:write",
|
||||
"users:delete",
|
||||
"roles:read",
|
||||
"roles:write",
|
||||
]
|
||||
admin_role = Role(
|
||||
account_id=account.id,
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy import select, func, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, Contact, Tag
|
||||
from app.models.contact import contact_tags
|
||||
from app.schemas.contact import (
|
||||
ContactCreateRequest,
|
||||
ContactUpdateRequest,
|
||||
@@ -24,7 +23,9 @@ async def list_contacts(
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(20, ge=1, le=100),
|
||||
search: str | None = Query(None, description="Search in name, email, phone"),
|
||||
type: str | None = Query(None, pattern="^(company|person)$", description="Filter by contact type"),
|
||||
type: str | None = Query(
|
||||
None, pattern="^(company|person)$", description="Filter by contact type"
|
||||
),
|
||||
current_user: User = Depends(require_permission("contacts:read")),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
@@ -56,8 +57,7 @@ async def list_contacts(
|
||||
|
||||
# Fetch page with tags eager-loaded
|
||||
q = (
|
||||
base_q
|
||||
.options(joinedload(Contact.tags))
|
||||
base_q.options(joinedload(Contact.tags))
|
||||
.order_by(Contact.updated_at.desc())
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
|
||||
+32
-17
@@ -3,9 +3,9 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy import select, func, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, Crew, CrewAvailability
|
||||
from app.schemas.crew import (
|
||||
@@ -24,6 +24,7 @@ avail_router = APIRouter(prefix="/crew-availabilities", tags=["crew-availabiliti
|
||||
|
||||
# ===== Crew Endpoints =====
|
||||
|
||||
|
||||
@router.get("", response_model=CrewListResponse)
|
||||
async def list_crew(
|
||||
page: int = Query(1, ge=1),
|
||||
@@ -55,8 +56,7 @@ async def list_crew(
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
|
||||
q = (
|
||||
base_q
|
||||
.options(selectinload(Crew.availabilities))
|
||||
base_q.options(selectinload(Crew.availabilities))
|
||||
.order_by(Crew.last_name, Crew.first_name)
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
@@ -102,7 +102,9 @@ async def get_crew(
|
||||
)
|
||||
member = result.unique().scalars().first()
|
||||
if not member:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Crew member not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Crew member not found"
|
||||
)
|
||||
return CrewResponse.model_validate(member)
|
||||
|
||||
|
||||
@@ -122,7 +124,9 @@ async def update_crew(
|
||||
)
|
||||
member = result.unique().scalars().first()
|
||||
if not member:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Crew member not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Crew member not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -146,7 +150,9 @@ async def delete_crew(
|
||||
)
|
||||
member = result.scalars().first()
|
||||
if not member:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Crew member not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Crew member not found"
|
||||
)
|
||||
|
||||
await session.delete(member)
|
||||
await session.commit()
|
||||
@@ -155,6 +161,7 @@ async def delete_crew(
|
||||
|
||||
# ===== CrewAvailability Endpoints =====
|
||||
|
||||
|
||||
@avail_router.get("", response_model=CrewAvailabilityListResponse)
|
||||
async def list_availabilities(
|
||||
page: int = Query(1, ge=1),
|
||||
@@ -166,11 +173,7 @@ async def list_availabilities(
|
||||
):
|
||||
"""List availabilities with filters."""
|
||||
account_id = current_user.account_id
|
||||
base_q = (
|
||||
select(CrewAvailability)
|
||||
.join(Crew)
|
||||
.where(Crew.account_id == account_id)
|
||||
)
|
||||
base_q = select(CrewAvailability).join(Crew).where(Crew.account_id == account_id)
|
||||
|
||||
if crew_id:
|
||||
base_q = base_q.where(CrewAvailability.crew_id == crew_id)
|
||||
@@ -180,7 +183,11 @@ async def list_availabilities(
|
||||
count_q = select(func.count()).select_from(base_q.subquery())
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
|
||||
q = base_q.order_by(CrewAvailability.start_date.desc()).offset((page - 1) * size).limit(size)
|
||||
q = (
|
||||
base_q.order_by(CrewAvailability.start_date.desc())
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
)
|
||||
result = await session.execute(q)
|
||||
items = result.scalars().all()
|
||||
|
||||
@@ -192,7 +199,9 @@ async def list_availabilities(
|
||||
)
|
||||
|
||||
|
||||
@avail_router.post("", response_model=CrewAvailabilityResponse, status_code=status.HTTP_201_CREATED)
|
||||
@avail_router.post(
|
||||
"", response_model=CrewAvailabilityResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_availability(
|
||||
body: CrewAvailabilityCreateRequest,
|
||||
current_user: User = Depends(require_permission("crew:write")),
|
||||
@@ -207,7 +216,9 @@ async def create_availability(
|
||||
)
|
||||
)
|
||||
if not crew_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Crew member not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Crew member not found"
|
||||
)
|
||||
|
||||
avail = CrewAvailability(**body.model_dump())
|
||||
session.add(avail)
|
||||
@@ -234,7 +245,9 @@ async def update_availability(
|
||||
)
|
||||
avail = result.scalars().first()
|
||||
if not avail:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Availability not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Availability not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -262,7 +275,9 @@ async def delete_availability(
|
||||
)
|
||||
avail = result.scalars().first()
|
||||
if not avail:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Availability not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Availability not found"
|
||||
)
|
||||
|
||||
await session.delete(avail)
|
||||
await session.commit()
|
||||
|
||||
@@ -5,7 +5,7 @@ from sqlalchemy import select, func, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, Equipment, StockLocation, Contact
|
||||
from app.schemas.equipment import (
|
||||
@@ -13,8 +13,6 @@ from app.schemas.equipment import (
|
||||
EquipmentUpdateRequest,
|
||||
EquipmentResponse,
|
||||
EquipmentListResponse,
|
||||
LocationRef,
|
||||
SupplierRef,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/equipment", tags=["equipment"])
|
||||
@@ -24,7 +22,9 @@ router = APIRouter(prefix="/equipment", tags=["equipment"])
|
||||
async def list_equipment(
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(20, ge=1, le=100),
|
||||
search: str | None = Query(None, description="Search in name, brand, serial_number"),
|
||||
search: str | None = Query(
|
||||
None, description="Search in name, brand, serial_number"
|
||||
),
|
||||
category: str | None = Query(None, description="Filter by category"),
|
||||
status: str | None = Query(None, description="Filter by status"),
|
||||
location_id: str | None = Query(None, description="Filter by location"),
|
||||
@@ -58,8 +58,7 @@ async def list_equipment(
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
|
||||
q = (
|
||||
base_q
|
||||
.options(joinedload(Equipment.location), joinedload(Equipment.supplier))
|
||||
base_q.options(joinedload(Equipment.location), joinedload(Equipment.supplier))
|
||||
.order_by(Equipment.updated_at.desc())
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
@@ -142,7 +141,9 @@ async def get_equipment(
|
||||
)
|
||||
item = result.unique().scalars().first()
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Equipment not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment not found"
|
||||
)
|
||||
return EquipmentResponse.model_validate(item)
|
||||
|
||||
|
||||
@@ -162,7 +163,9 @@ async def update_equipment(
|
||||
)
|
||||
item = result.unique().scalars().first()
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Equipment not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -182,11 +185,15 @@ async def delete_equipment(
|
||||
"""Delete an equipment item."""
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(
|
||||
select(Equipment).where(Equipment.id == item_id, Equipment.account_id == account_id)
|
||||
select(Equipment).where(
|
||||
Equipment.id == item_id, Equipment.account_id == account_id
|
||||
)
|
||||
)
|
||||
item = result.scalars().first()
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Equipment not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment not found"
|
||||
)
|
||||
|
||||
await session.delete(item)
|
||||
await session.commit()
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
"""EquipmentGroup (Bundle) CRUD endpoints."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy import select, func, or_
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, EquipmentGroup, Equipment, StockLocation
|
||||
from app.models import User, EquipmentGroup, Equipment
|
||||
from app.models.equipment_group import equipment_group_items
|
||||
from app.schemas.equipment_group import (
|
||||
EquipmentGroupCreateRequest,
|
||||
EquipmentGroupUpdateRequest,
|
||||
EquipmentGroupResponse,
|
||||
EquipmentGroupListResponse,
|
||||
EquipmentGroupItem as EquipmentGroupItemSchema,
|
||||
)
|
||||
from app.schemas.equipment import LocationRef
|
||||
|
||||
router = APIRouter(prefix="/equipment-groups", tags=["equipment-groups"])
|
||||
|
||||
@@ -48,8 +46,10 @@ async def list_equipment_groups(
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
|
||||
q = (
|
||||
base_q
|
||||
.options(joinedload(EquipmentGroup.default_location), selectinload(EquipmentGroup.items))
|
||||
base_q.options(
|
||||
joinedload(EquipmentGroup.default_location),
|
||||
selectinload(EquipmentGroup.items),
|
||||
)
|
||||
.order_by(EquipmentGroup.name)
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
@@ -65,7 +65,9 @@ async def list_equipment_groups(
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=EquipmentGroupResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"", response_model=EquipmentGroupResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_equipment_group(
|
||||
body: EquipmentGroupCreateRequest,
|
||||
current_user: User = Depends(require_permission("equipment:write")),
|
||||
@@ -110,7 +112,10 @@ async def create_equipment_group(
|
||||
# Reload with relationships
|
||||
await session.execute(
|
||||
select(EquipmentGroup)
|
||||
.options(joinedload(EquipmentGroup.default_location), selectinload(EquipmentGroup.items))
|
||||
.options(
|
||||
joinedload(EquipmentGroup.default_location),
|
||||
selectinload(EquipmentGroup.items),
|
||||
)
|
||||
.where(EquipmentGroup.id == group.id)
|
||||
)
|
||||
await session.refresh(group)
|
||||
@@ -128,12 +133,17 @@ async def get_equipment_group(
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(
|
||||
select(EquipmentGroup)
|
||||
.options(joinedload(EquipmentGroup.default_location), selectinload(EquipmentGroup.items))
|
||||
.options(
|
||||
joinedload(EquipmentGroup.default_location),
|
||||
selectinload(EquipmentGroup.items),
|
||||
)
|
||||
.where(EquipmentGroup.id == group_id, EquipmentGroup.account_id == account_id)
|
||||
)
|
||||
group = result.unique().scalars().first()
|
||||
if not group:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found"
|
||||
)
|
||||
return EquipmentGroupResponse.model_validate(group)
|
||||
|
||||
|
||||
@@ -148,12 +158,17 @@ async def update_equipment_group(
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(
|
||||
select(EquipmentGroup)
|
||||
.options(joinedload(EquipmentGroup.default_location), selectinload(EquipmentGroup.items))
|
||||
.options(
|
||||
joinedload(EquipmentGroup.default_location),
|
||||
selectinload(EquipmentGroup.items),
|
||||
)
|
||||
.where(EquipmentGroup.id == group_id, EquipmentGroup.account_id == account_id)
|
||||
)
|
||||
group = result.unique().scalars().first()
|
||||
if not group:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True, exclude={"items"})
|
||||
for field, value in update_data.items():
|
||||
@@ -201,7 +216,9 @@ async def delete_equipment_group(
|
||||
)
|
||||
group = result.scalars().first()
|
||||
if not group:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found"
|
||||
)
|
||||
|
||||
await session.delete(group)
|
||||
await session.commit()
|
||||
|
||||
+225
-78
@@ -1,11 +1,10 @@
|
||||
"""Project CRUD endpoints including sub-projects, function groups, and functions."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy import select, func, or_
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, Project, SubProject, ProjectFunctionGroup, ProjectFunction
|
||||
from app.schemas.project import (
|
||||
@@ -38,7 +37,10 @@ async def list_projects(
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(20, ge=1, le=100),
|
||||
search: str | None = Query(None, description="Search in project name"),
|
||||
status: str | None = Query(None, description="Filter by status (draft, confirmed, in_progress, completed, cancelled)"),
|
||||
status: str | None = Query(
|
||||
None,
|
||||
description="Filter by status (draft, confirmed, in_progress, completed, cancelled)",
|
||||
),
|
||||
current_user: User = Depends(require_permission("projects:read")),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
@@ -57,12 +59,7 @@ async def list_projects(
|
||||
count_q = select(func.count()).select_from(base_q.subquery())
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
|
||||
q = (
|
||||
base_q
|
||||
.order_by(Project.updated_at.desc())
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
)
|
||||
q = base_q.order_by(Project.updated_at.desc()).offset((page - 1) * size).limit(size)
|
||||
result = await session.execute(q)
|
||||
items = result.scalars().all()
|
||||
|
||||
@@ -98,11 +95,15 @@ async def get_project(
|
||||
"""Get a specific project."""
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(
|
||||
select(Project).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
project = result.scalars().first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
return ProjectResponse.model_validate(project)
|
||||
|
||||
|
||||
@@ -116,11 +117,15 @@ async def update_project(
|
||||
"""Update an existing project."""
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(
|
||||
select(Project).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
project = result.scalars().first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -140,11 +145,15 @@ async def delete_project(
|
||||
"""Delete a project."""
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(
|
||||
select(Project).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
project = result.scalars().first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
await session.delete(project)
|
||||
await session.commit()
|
||||
@@ -167,10 +176,14 @@ async def list_subprojects(
|
||||
|
||||
# Verify project ownership
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
base_q = select(SubProject).where(SubProject.project_id == project_id)
|
||||
count_q = select(func.count()).select_from(base_q.subquery())
|
||||
@@ -188,7 +201,11 @@ async def list_subprojects(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{project_id}/subprojects", response_model=SubProjectResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/{project_id}/subprojects",
|
||||
response_model=SubProjectResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_subproject(
|
||||
project_id: str,
|
||||
body: SubProjectCreateRequest,
|
||||
@@ -200,10 +217,14 @@ async def create_subproject(
|
||||
|
||||
# Verify project ownership
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
# Verify parent subproject if provided
|
||||
if body.parent_id:
|
||||
@@ -238,17 +259,25 @@ async def get_subproject(
|
||||
|
||||
# Verify project ownership
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(SubProject).where(SubProject.id == sub_id, SubProject.project_id == project_id)
|
||||
select(SubProject).where(
|
||||
SubProject.id == sub_id, SubProject.project_id == project_id
|
||||
)
|
||||
)
|
||||
sub = result.scalars().first()
|
||||
if not sub:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="SubProject not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="SubProject not found"
|
||||
)
|
||||
return SubProjectResponse.model_validate(sub)
|
||||
|
||||
|
||||
@@ -265,17 +294,25 @@ async def update_subproject(
|
||||
|
||||
# Verify project ownership
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(SubProject).where(SubProject.id == sub_id, SubProject.project_id == project_id)
|
||||
select(SubProject).where(
|
||||
SubProject.id == sub_id, SubProject.project_id == project_id
|
||||
)
|
||||
)
|
||||
sub = result.scalars().first()
|
||||
if not sub:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="SubProject not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="SubProject not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -286,7 +323,9 @@ async def update_subproject(
|
||||
return SubProjectResponse.model_validate(sub)
|
||||
|
||||
|
||||
@router.delete("/{project_id}/subprojects/{sub_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{project_id}/subprojects/{sub_id}", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def delete_subproject(
|
||||
project_id: str,
|
||||
sub_id: str,
|
||||
@@ -298,17 +337,25 @@ async def delete_subproject(
|
||||
|
||||
# Verify project ownership
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(SubProject).where(SubProject.id == sub_id, SubProject.project_id == project_id)
|
||||
select(SubProject).where(
|
||||
SubProject.id == sub_id, SubProject.project_id == project_id
|
||||
)
|
||||
)
|
||||
sub = result.scalars().first()
|
||||
if not sub:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="SubProject not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="SubProject not found"
|
||||
)
|
||||
|
||||
await session.delete(sub)
|
||||
await session.commit()
|
||||
@@ -318,7 +365,9 @@ async def delete_subproject(
|
||||
# ===== ProjectFunctionGroup Endpoints =====
|
||||
|
||||
|
||||
@router.get("/{project_id}/function-groups", response_model=ProjectFunctionGroupListResponse)
|
||||
@router.get(
|
||||
"/{project_id}/function-groups", response_model=ProjectFunctionGroupListResponse
|
||||
)
|
||||
async def list_function_groups(
|
||||
project_id: str,
|
||||
page: int = Query(1, ge=1),
|
||||
@@ -330,16 +379,26 @@ async def list_function_groups(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
base_q = select(ProjectFunctionGroup).where(ProjectFunctionGroup.project_id == project_id)
|
||||
base_q = select(ProjectFunctionGroup).where(
|
||||
ProjectFunctionGroup.project_id == project_id
|
||||
)
|
||||
count_q = select(func.count()).select_from(base_q.subquery())
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
|
||||
q = base_q.order_by(ProjectFunctionGroup.sort_order).offset((page - 1) * size).limit(size)
|
||||
q = (
|
||||
base_q.order_by(ProjectFunctionGroup.sort_order)
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
)
|
||||
result = await session.execute(q)
|
||||
items = result.scalars().all()
|
||||
|
||||
@@ -351,7 +410,11 @@ async def list_function_groups(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{project_id}/function-groups", response_model=ProjectFunctionGroupResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/{project_id}/function-groups",
|
||||
response_model=ProjectFunctionGroupResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_function_group(
|
||||
project_id: str,
|
||||
body: ProjectFunctionGroupCreateRequest,
|
||||
@@ -362,10 +425,14 @@ async def create_function_group(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
fg = ProjectFunctionGroup(project_id=project_id, **body.model_dump())
|
||||
session.add(fg)
|
||||
@@ -374,7 +441,9 @@ async def create_function_group(
|
||||
return ProjectFunctionGroupResponse.model_validate(fg)
|
||||
|
||||
|
||||
@router.get("/{project_id}/function-groups/{fg_id}", response_model=ProjectFunctionGroupResponse)
|
||||
@router.get(
|
||||
"/{project_id}/function-groups/{fg_id}", response_model=ProjectFunctionGroupResponse
|
||||
)
|
||||
async def get_function_group(
|
||||
project_id: str,
|
||||
fg_id: str,
|
||||
@@ -385,10 +454,14 @@ async def get_function_group(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(ProjectFunctionGroup).where(
|
||||
@@ -398,11 +471,15 @@ async def get_function_group(
|
||||
)
|
||||
fg = result.scalars().first()
|
||||
if not fg:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found"
|
||||
)
|
||||
return ProjectFunctionGroupResponse.model_validate(fg)
|
||||
|
||||
|
||||
@router.put("/{project_id}/function-groups/{fg_id}", response_model=ProjectFunctionGroupResponse)
|
||||
@router.put(
|
||||
"/{project_id}/function-groups/{fg_id}", response_model=ProjectFunctionGroupResponse
|
||||
)
|
||||
async def update_function_group(
|
||||
project_id: str,
|
||||
fg_id: str,
|
||||
@@ -414,10 +491,14 @@ async def update_function_group(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(ProjectFunctionGroup).where(
|
||||
@@ -427,7 +508,9 @@ async def update_function_group(
|
||||
)
|
||||
fg = result.scalars().first()
|
||||
if not fg:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -438,7 +521,9 @@ async def update_function_group(
|
||||
return ProjectFunctionGroupResponse.model_validate(fg)
|
||||
|
||||
|
||||
@router.delete("/{project_id}/function-groups/{fg_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{project_id}/function-groups/{fg_id}", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def delete_function_group(
|
||||
project_id: str,
|
||||
fg_id: str,
|
||||
@@ -449,10 +534,14 @@ async def delete_function_group(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(ProjectFunctionGroup).where(
|
||||
@@ -462,7 +551,9 @@ async def delete_function_group(
|
||||
)
|
||||
fg = result.scalars().first()
|
||||
if not fg:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found"
|
||||
)
|
||||
|
||||
await session.delete(fg)
|
||||
await session.commit()
|
||||
@@ -472,7 +563,10 @@ async def delete_function_group(
|
||||
# ===== ProjectFunction Endpoints =====
|
||||
|
||||
|
||||
@router.get("/{project_id}/function-groups/{fg_id}/functions", response_model=ProjectFunctionListResponse)
|
||||
@router.get(
|
||||
"/{project_id}/function-groups/{fg_id}/functions",
|
||||
response_model=ProjectFunctionListResponse,
|
||||
)
|
||||
async def list_project_functions(
|
||||
project_id: str,
|
||||
fg_id: str,
|
||||
@@ -485,10 +579,14 @@ async def list_project_functions(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
# Verify function group belongs to project
|
||||
fg_result = await session.execute(
|
||||
@@ -498,13 +596,19 @@ async def list_project_functions(
|
||||
)
|
||||
)
|
||||
if not fg_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found"
|
||||
)
|
||||
|
||||
base_q = select(ProjectFunction).where(ProjectFunction.function_group_id == fg_id)
|
||||
count_q = select(func.count()).select_from(base_q.subquery())
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
|
||||
q = base_q.order_by(ProjectFunction.sort_order).offset((page - 1) * size).limit(size)
|
||||
q = (
|
||||
base_q.order_by(ProjectFunction.sort_order)
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
)
|
||||
result = await session.execute(q)
|
||||
items = result.scalars().all()
|
||||
|
||||
@@ -516,7 +620,11 @@ async def list_project_functions(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{project_id}/function-groups/{fg_id}/functions", response_model=ProjectFunctionResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/{project_id}/function-groups/{fg_id}/functions",
|
||||
response_model=ProjectFunctionResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_project_function(
|
||||
project_id: str,
|
||||
fg_id: str,
|
||||
@@ -528,10 +636,14 @@ async def create_project_function(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
fg_result = await session.execute(
|
||||
select(ProjectFunctionGroup.id).where(
|
||||
@@ -540,7 +652,9 @@ async def create_project_function(
|
||||
)
|
||||
)
|
||||
if not fg_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found"
|
||||
)
|
||||
|
||||
func_obj = ProjectFunction(function_group_id=fg_id, **body.model_dump())
|
||||
session.add(func_obj)
|
||||
@@ -549,7 +663,10 @@ async def create_project_function(
|
||||
return ProjectFunctionResponse.model_validate(func_obj)
|
||||
|
||||
|
||||
@router.get("/{project_id}/function-groups/{fg_id}/functions/{func_id}", response_model=ProjectFunctionResponse)
|
||||
@router.get(
|
||||
"/{project_id}/function-groups/{fg_id}/functions/{func_id}",
|
||||
response_model=ProjectFunctionResponse,
|
||||
)
|
||||
async def get_project_function(
|
||||
project_id: str,
|
||||
fg_id: str,
|
||||
@@ -561,10 +678,14 @@ async def get_project_function(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
fg_result = await session.execute(
|
||||
select(ProjectFunctionGroup.id).where(
|
||||
@@ -573,7 +694,9 @@ async def get_project_function(
|
||||
)
|
||||
)
|
||||
if not fg_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(ProjectFunction).where(
|
||||
@@ -583,11 +706,16 @@ async def get_project_function(
|
||||
)
|
||||
func_obj = result.scalars().first()
|
||||
if not func_obj:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function not found"
|
||||
)
|
||||
return ProjectFunctionResponse.model_validate(func_obj)
|
||||
|
||||
|
||||
@router.put("/{project_id}/function-groups/{fg_id}/functions/{func_id}", response_model=ProjectFunctionResponse)
|
||||
@router.put(
|
||||
"/{project_id}/function-groups/{fg_id}/functions/{func_id}",
|
||||
response_model=ProjectFunctionResponse,
|
||||
)
|
||||
async def update_project_function(
|
||||
project_id: str,
|
||||
fg_id: str,
|
||||
@@ -600,10 +728,14 @@ async def update_project_function(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
fg_result = await session.execute(
|
||||
select(ProjectFunctionGroup.id).where(
|
||||
@@ -612,7 +744,9 @@ async def update_project_function(
|
||||
)
|
||||
)
|
||||
if not fg_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(ProjectFunction).where(
|
||||
@@ -622,7 +756,9 @@ async def update_project_function(
|
||||
)
|
||||
func_obj = result.scalars().first()
|
||||
if not func_obj:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -633,7 +769,10 @@ async def update_project_function(
|
||||
return ProjectFunctionResponse.model_validate(func_obj)
|
||||
|
||||
|
||||
@router.delete("/{project_id}/function-groups/{fg_id}/functions/{func_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{project_id}/function-groups/{fg_id}/functions/{func_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def delete_project_function(
|
||||
project_id: str,
|
||||
fg_id: str,
|
||||
@@ -645,10 +784,14 @@ async def delete_project_function(
|
||||
account_id = current_user.account_id
|
||||
|
||||
proj_result = await session.execute(
|
||||
select(Project.id).where(Project.id == project_id, Project.account_id == account_id)
|
||||
select(Project.id).where(
|
||||
Project.id == project_id, Project.account_id == account_id
|
||||
)
|
||||
)
|
||||
if not proj_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||
)
|
||||
|
||||
fg_result = await session.execute(
|
||||
select(ProjectFunctionGroup.id).where(
|
||||
@@ -657,7 +800,9 @@ async def delete_project_function(
|
||||
)
|
||||
)
|
||||
if not fg_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function group not found"
|
||||
)
|
||||
|
||||
result = await session.execute(
|
||||
select(ProjectFunction).where(
|
||||
@@ -667,7 +812,9 @@ async def delete_project_function(
|
||||
)
|
||||
func_obj = result.scalars().first()
|
||||
if not func_obj:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Function not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Function not found"
|
||||
)
|
||||
|
||||
await session.delete(func_obj)
|
||||
await session.commit()
|
||||
|
||||
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, Role
|
||||
from app.schemas.role import RoleCreateRequest, RoleUpdateRequest, RoleResponse
|
||||
|
||||
@@ -12,7 +12,10 @@ from app.api.v1.equipment import router as equipment_router
|
||||
from app.api.v1.stock_locations import router as stock_locations_router
|
||||
from app.api.v1.equipment_groups import router as equipment_groups_router
|
||||
from app.api.v1.crew import router as crew_router, avail_router as crew_avail_router
|
||||
from app.api.v1.vehicles import router as vehicles_router, assign_router as vehicle_assign_router
|
||||
from app.api.v1.vehicles import (
|
||||
router as vehicles_router,
|
||||
assign_router as vehicle_assign_router,
|
||||
)
|
||||
from app.api.v1.projects import router as projects_router
|
||||
|
||||
api_v1_router = APIRouter()
|
||||
|
||||
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, StockLocation
|
||||
from app.schemas.stock_location import (
|
||||
@@ -48,7 +48,9 @@ async def list_stock_locations(
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=StockLocationResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"", response_model=StockLocationResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_stock_location(
|
||||
body: StockLocationCreateRequest,
|
||||
current_user: User = Depends(require_permission("equipment:write")),
|
||||
@@ -60,8 +62,9 @@ async def create_stock_location(
|
||||
# If this is set as default, unset others
|
||||
if body.is_default:
|
||||
await session.execute(
|
||||
select(StockLocation)
|
||||
.where(StockLocation.account_id == account_id, StockLocation.is_default == True) # noqa: E712
|
||||
select(StockLocation).where(
|
||||
StockLocation.account_id == account_id, StockLocation.is_default == True
|
||||
) # noqa: E712
|
||||
)
|
||||
|
||||
loc = StockLocation(account_id=account_id, **body.model_dump())
|
||||
@@ -87,7 +90,9 @@ async def get_stock_location(
|
||||
)
|
||||
loc = result.scalars().first()
|
||||
if not loc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Stock location not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Stock location not found"
|
||||
)
|
||||
return StockLocationResponse.model_validate(loc)
|
||||
|
||||
|
||||
@@ -108,7 +113,9 @@ async def update_stock_location(
|
||||
)
|
||||
loc = result.scalars().first()
|
||||
if not loc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Stock location not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Stock location not found"
|
||||
)
|
||||
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -135,7 +142,9 @@ async def delete_stock_location(
|
||||
)
|
||||
loc = result.scalars().first()
|
||||
if not loc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Stock location not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Stock location not found"
|
||||
)
|
||||
|
||||
await session.delete(loc)
|
||||
await session.commit()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""Tags endpoints for listing and creating tags within a tenant."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, Tag
|
||||
from app.schemas.contact import TagResponse
|
||||
|
||||
@@ -4,11 +4,16 @@ from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.core.security import get_password_hash
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User
|
||||
from app.schemas.user import UserCreateRequest, UserUpdateRequest, UserResponse, UserListResponse
|
||||
from app.schemas.user import (
|
||||
UserCreateRequest,
|
||||
UserUpdateRequest,
|
||||
UserResponse,
|
||||
UserListResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from sqlalchemy import select, func, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.api.deps import get_current_user, require_permission
|
||||
from app.api.deps import require_permission
|
||||
from app.db.session import get_async_session
|
||||
from app.models import User, Vehicle, VehicleAssignment
|
||||
from app.schemas.vehicle import (
|
||||
@@ -25,6 +25,7 @@ assign_router = APIRouter(prefix="/vehicle-assignments", tags=["vehicle-assignme
|
||||
|
||||
# ===== Vehicle Endpoints =====
|
||||
|
||||
|
||||
@router.get("", response_model=VehicleListResponse)
|
||||
async def list_vehicles(
|
||||
page: int = Query(1, ge=1),
|
||||
@@ -41,8 +42,12 @@ async def list_vehicles(
|
||||
if search:
|
||||
pattern = f"%{search}%"
|
||||
base_q = base_q.where(
|
||||
or_(Vehicle.name.ilike(pattern), Vehicle.license_plate.ilike(pattern),
|
||||
Vehicle.brand.ilike(pattern), Vehicle.model.ilike(pattern))
|
||||
or_(
|
||||
Vehicle.name.ilike(pattern),
|
||||
Vehicle.license_plate.ilike(pattern),
|
||||
Vehicle.brand.ilike(pattern),
|
||||
Vehicle.model.ilike(pattern),
|
||||
)
|
||||
)
|
||||
if vehicle_type:
|
||||
base_q = base_q.where(Vehicle.vehicle_type == vehicle_type)
|
||||
@@ -52,11 +57,21 @@ async def list_vehicles(
|
||||
count_q = select(func.count()).select_from(base_q.subquery())
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
|
||||
q = base_q.options(selectinload(Vehicle.assignments)).order_by(Vehicle.name).offset((page-1)*size).limit(size)
|
||||
q = (
|
||||
base_q.options(selectinload(Vehicle.assignments))
|
||||
.order_by(Vehicle.name)
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
)
|
||||
result = await session.execute(q)
|
||||
items = result.unique().scalars().all()
|
||||
|
||||
return VehicleListResponse(items=[VehicleResponse.model_validate(i) for i in items], total=total, page=page, size=size)
|
||||
return VehicleListResponse(
|
||||
items=[VehicleResponse.model_validate(i) for i in items],
|
||||
total=total,
|
||||
page=page,
|
||||
size=size,
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=VehicleResponse, status_code=status.HTTP_201_CREATED)
|
||||
@@ -81,12 +96,15 @@ async def get_vehicle(
|
||||
):
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(
|
||||
select(Vehicle).options(selectinload(Vehicle.assignments))
|
||||
select(Vehicle)
|
||||
.options(selectinload(Vehicle.assignments))
|
||||
.where(Vehicle.id == vehicle_id, Vehicle.account_id == account_id)
|
||||
)
|
||||
vehicle = result.unique().scalars().first()
|
||||
if not vehicle:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found"
|
||||
)
|
||||
return VehicleResponse.model_validate(vehicle)
|
||||
|
||||
|
||||
@@ -99,12 +117,15 @@ async def update_vehicle(
|
||||
):
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(
|
||||
select(Vehicle).options(selectinload(Vehicle.assignments))
|
||||
select(Vehicle)
|
||||
.options(selectinload(Vehicle.assignments))
|
||||
.where(Vehicle.id == vehicle_id, Vehicle.account_id == account_id)
|
||||
)
|
||||
vehicle = result.unique().scalars().first()
|
||||
if not vehicle:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found"
|
||||
)
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(vehicle, field, value)
|
||||
@@ -120,10 +141,16 @@ async def delete_vehicle(
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
account_id = current_user.account_id
|
||||
result = await session.execute(select(Vehicle).where(Vehicle.id == vehicle_id, Vehicle.account_id == account_id))
|
||||
result = await session.execute(
|
||||
select(Vehicle).where(
|
||||
Vehicle.id == vehicle_id, Vehicle.account_id == account_id
|
||||
)
|
||||
)
|
||||
vehicle = result.scalars().first()
|
||||
if not vehicle:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found"
|
||||
)
|
||||
await session.delete(vehicle)
|
||||
await session.commit()
|
||||
return None
|
||||
@@ -131,6 +158,7 @@ async def delete_vehicle(
|
||||
|
||||
# ===== VehicleAssignment Endpoints =====
|
||||
|
||||
|
||||
@assign_router.get("", response_model=VehicleAssignmentListResponse)
|
||||
async def list_assignments(
|
||||
page: int = Query(1, ge=1),
|
||||
@@ -141,28 +169,47 @@ async def list_assignments(
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
account_id = current_user.account_id
|
||||
base_q = select(VehicleAssignment).join(Vehicle).where(Vehicle.account_id == account_id)
|
||||
base_q = (
|
||||
select(VehicleAssignment).join(Vehicle).where(Vehicle.account_id == account_id)
|
||||
)
|
||||
if vehicle_id:
|
||||
base_q = base_q.where(VehicleAssignment.vehicle_id == vehicle_id)
|
||||
if status:
|
||||
base_q = base_q.where(VehicleAssignment.status == status)
|
||||
count_q = select(func.count()).select_from(base_q.subquery())
|
||||
total = (await session.execute(count_q)).scalar() or 0
|
||||
q = base_q.order_by(VehicleAssignment.start_date.desc()).offset((page-1)*size).limit(size)
|
||||
q = (
|
||||
base_q.order_by(VehicleAssignment.start_date.desc())
|
||||
.offset((page - 1) * size)
|
||||
.limit(size)
|
||||
)
|
||||
result = await session.execute(q)
|
||||
items = result.scalars().all()
|
||||
return VehicleAssignmentListResponse(items=[VehicleAssignmentResponse.model_validate(i) for i in items], total=total, page=page, size=size)
|
||||
return VehicleAssignmentListResponse(
|
||||
items=[VehicleAssignmentResponse.model_validate(i) for i in items],
|
||||
total=total,
|
||||
page=page,
|
||||
size=size,
|
||||
)
|
||||
|
||||
|
||||
@assign_router.post("", response_model=VehicleAssignmentResponse, status_code=status.HTTP_201_CREATED)
|
||||
@assign_router.post(
|
||||
"", response_model=VehicleAssignmentResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_assignment(
|
||||
body: VehicleAssignmentCreateRequest,
|
||||
current_user: User = Depends(require_permission("vehicles:write")),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
v_result = await session.execute(select(Vehicle).where(Vehicle.id == body.vehicle_id, Vehicle.account_id == current_user.account_id))
|
||||
v_result = await session.execute(
|
||||
select(Vehicle).where(
|
||||
Vehicle.id == body.vehicle_id, Vehicle.account_id == current_user.account_id
|
||||
)
|
||||
)
|
||||
if not v_result.scalars().first():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found"
|
||||
)
|
||||
assign = VehicleAssignment(**body.model_dump())
|
||||
session.add(assign)
|
||||
await session.commit()
|
||||
@@ -178,11 +225,18 @@ async def update_assignment(
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
result = await session.execute(
|
||||
select(VehicleAssignment).join(Vehicle).where(VehicleAssignment.id == assign_id, Vehicle.account_id == current_user.account_id)
|
||||
select(VehicleAssignment)
|
||||
.join(Vehicle)
|
||||
.where(
|
||||
VehicleAssignment.id == assign_id,
|
||||
Vehicle.account_id == current_user.account_id,
|
||||
)
|
||||
)
|
||||
assign = result.scalars().first()
|
||||
if not assign:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Assignment not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Assignment not found"
|
||||
)
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(assign, field, value)
|
||||
@@ -198,11 +252,18 @@ async def delete_assignment(
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
result = await session.execute(
|
||||
select(VehicleAssignment).join(Vehicle).where(VehicleAssignment.id == assign_id, Vehicle.account_id == current_user.account_id)
|
||||
select(VehicleAssignment)
|
||||
.join(Vehicle)
|
||||
.where(
|
||||
VehicleAssignment.id == assign_id,
|
||||
Vehicle.account_id == current_user.account_id,
|
||||
)
|
||||
)
|
||||
assign = result.scalars().first()
|
||||
if not assign:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Assignment not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Assignment not found"
|
||||
)
|
||||
await session.delete(assign)
|
||||
await session.commit()
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user