- Implemented the items tab form in Svelte for editing invoices, displaying item quantities and import values. - Created saveInvoice function to handle both creation and updating of invoices, including validation of required fields and building a unified payload for API requests. - Added support for compliance, financials, and logistics data in the invoice payload.
235 lines
7.4 KiB
Python
235 lines
7.4 KiB
Python
"""
|
|
API Endpoints for Items management
|
|
Handles CRUD operations for Item with one-to-many relationships to LineItems
|
|
"""
|
|
|
|
from typing import Dict, Any, Optional
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Path, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from core.database import get_core_db
|
|
from core.security import get_current_user, validate_access_to_resource
|
|
|
|
from .schemas import (
|
|
ItemCreate,
|
|
ItemUpdate,
|
|
ItemResponse,
|
|
ItemListResponse,
|
|
)
|
|
from .service import ItemService
|
|
|
|
router = APIRouter(prefix="/items", tags=["Items"])
|
|
|
|
|
|
# ============================================================================
|
|
# ITEM CRUD ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.post("/", response_model=ItemResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_item(
|
|
item_data: ItemCreate,
|
|
company_id: int = Query(..., description="Company ID"),
|
|
db: Session = Depends(get_core_db),
|
|
current_user: Dict[str, Any] = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Create a new item with multiple line items and their nested data
|
|
|
|
The item follows a one-to-many relationship structure:
|
|
- Item has many LineItems
|
|
- Each LineItem has one LineFinancial
|
|
- Each LineItem has one LineQuantity
|
|
- Each LineItem has one LineCustoms
|
|
- Each LineItem has one LineDescription
|
|
- Each LineItem has one LineReference
|
|
"""
|
|
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
|
|
|
service = ItemService()
|
|
return service.create(db, item_data, tenant_id, company_id)
|
|
|
|
|
|
@router.get("/{item_id}", response_model=ItemResponse)
|
|
async def get_item(
|
|
item_id: int = Path(..., description="Item ID"),
|
|
company_id: int = Query(..., description="Company ID"),
|
|
db: Session = Depends(get_core_db),
|
|
current_user: Dict[str, Any] = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Get a specific item by ID with all nested data
|
|
"""
|
|
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
|
|
|
service = ItemService()
|
|
item = service.get_by_id(db, item_id, tenant_id, company_id)
|
|
|
|
if not item:
|
|
raise HTTPException(status_code=404, detail="Item not found")
|
|
|
|
return item
|
|
|
|
|
|
@router.get("/", response_model=ItemListResponse)
|
|
async def list_items(
|
|
company_id: int = Query(..., description="Company ID"),
|
|
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
|
limit: int = Query(100, ge=1, le=1000,
|
|
description="Maximum records to return"),
|
|
invoice_id: Optional[int] = Query(
|
|
None, description="Filter by invoice ID"),
|
|
item_type: Optional[str] = Query(None, description="Filter by item type"),
|
|
system_origin: Optional[str] = Query(
|
|
None, description="Filter by system origin (SCAF/SCAII)"),
|
|
search: Optional[str] = Query(
|
|
None, description="Search term for invoice number, reference, order, or guide"),
|
|
db: Session = Depends(get_core_db),
|
|
current_user: Dict[str, Any] = Depends(get_current_user),
|
|
):
|
|
"""
|
|
List items with optional filtering and pagination
|
|
|
|
Filters:
|
|
- invoice_id: Filter by specific invoice
|
|
- item_type: Filter by item type (IMPORT_TEMP, IMPORT_DEF, EXPORT, REPAIR)
|
|
- system_origin: Filter by system (SCAF, SCAII)
|
|
- search: Search across multiple fields
|
|
"""
|
|
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
|
|
|
service = ItemService()
|
|
|
|
filters = {
|
|
"invoice_id": invoice_id,
|
|
"item_type": item_type,
|
|
"system_origin": system_origin,
|
|
"search": search,
|
|
}
|
|
|
|
items, total = service.get_all(
|
|
db, tenant_id, company_id, skip, limit, filters)
|
|
|
|
return ItemListResponse(
|
|
total=total,
|
|
items=items,
|
|
skip=skip,
|
|
limit=limit
|
|
)
|
|
|
|
|
|
@router.put("/{item_id}", response_model=ItemResponse)
|
|
async def update_item(
|
|
item_id: int = Path(..., description="Item ID"),
|
|
item_data: ItemUpdate = ...,
|
|
company_id: int = Query(..., description="Company ID"),
|
|
db: Session = Depends(get_core_db),
|
|
current_user: Dict[str, Any] = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Update an item and optionally its nested line data
|
|
"""
|
|
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
|
|
|
# Verify the item exists and belongs to the tenant/company
|
|
service = ItemService()
|
|
existing_item = service.get_by_id(db, item_id, tenant_id, company_id)
|
|
if not existing_item:
|
|
raise HTTPException(status_code=404, detail="Item not found")
|
|
|
|
return service.update(db, item_id, item_data, tenant_id, company_id)
|
|
|
|
|
|
@router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_item(
|
|
item_id: int = Path(..., description="Item ID"),
|
|
company_id: int = Query(..., description="Company ID"),
|
|
db: Session = Depends(get_core_db),
|
|
current_user: Dict[str, Any] = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Delete an item and all its related data (cascade delete)
|
|
"""
|
|
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
|
|
|
service = ItemService()
|
|
# Verify the item exists and belongs to the tenant/company
|
|
existing_item = service.get_by_id(db, item_id, tenant_id, company_id)
|
|
if not existing_item:
|
|
raise HTTPException(status_code=404, detail="Item not found")
|
|
|
|
success = service.delete(db, item_id, tenant_id, company_id)
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Item not found")
|
|
|
|
return None
|
|
|
|
|
|
# ============================================================================
|
|
# ADDITIONAL ENDPOINTS FOR INVOICE
|
|
# ============================================================================
|
|
|
|
@router.get("/invoice/{invoice_id}/items", response_model=ItemListResponse)
|
|
async def get_items_by_invoice(
|
|
invoice_id: int = Path(..., description="Invoice ID"),
|
|
company_id: int = Query(..., description="Company ID"),
|
|
skip: int = Query(0, ge=0),
|
|
limit: int = Query(100, ge=1, le=1000),
|
|
db: Session = Depends(get_core_db),
|
|
current_user: Dict[str, Any] = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Get all items for a specific invoice
|
|
"""
|
|
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
|
|
|
service = ItemService()
|
|
items, total = service.get_by_invoice(
|
|
db, invoice_id, tenant_id, company_id, skip, limit)
|
|
|
|
return ItemListResponse(
|
|
total=total,
|
|
items=items,
|
|
skip=skip,
|
|
limit=limit
|
|
)
|
|
# STATISTICS & UTILITIES
|
|
# ============================================================================
|
|
|
|
|
|
@router.get("/stats/summary")
|
|
async def get_items_summary(
|
|
invoice_id: Optional[int] = Query(
|
|
None, description="Filter by invoice ID"),
|
|
db: Session = Depends(get_core_db),
|
|
current_user: dict = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Get summary statistics for items
|
|
"""
|
|
service = ItemService(db)
|
|
items, total = service.list_items(
|
|
skip=0,
|
|
limit=10000, # Get all for stats
|
|
invoice_id=invoice_id
|
|
)
|
|
|
|
# Calculate stats
|
|
stats = {
|
|
"total_items": total,
|
|
"by_type": {},
|
|
"by_system": {},
|
|
}
|
|
|
|
for item in items:
|
|
# Count by type
|
|
if item.item_type:
|
|
stats["by_type"][item.item_type] = stats["by_type"].get(
|
|
item.item_type, 0) + 1
|
|
|
|
# Count by system
|
|
if item.system_origin:
|
|
stats["by_system"][item.system_origin] = stats["by_system"].get(
|
|
item.system_origin, 0) + 1
|
|
|
|
return stats
|