New enterprise feature

This commit is contained in:
Urtzi Alfaro
2025-11-30 09:12:40 +01:00
parent f9d0eec6ec
commit 972db02f6d
176 changed files with 19741 additions and 1361 deletions

View File

@@ -0,0 +1,175 @@
"""
Internal Transfer API Endpoints
"""
from fastapi import APIRouter, Depends, HTTPException, Body
from typing import List, Optional, Dict, Any
from datetime import date
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from app.services.internal_transfer_service import InternalTransferService
from app.repositories.purchase_order_repository import PurchaseOrderRepository
from app.core.database import get_db
from shared.auth.tenant_access import verify_tenant_permission_dep
from shared.clients import get_recipes_client, get_production_client, get_inventory_client
from app.core.config import settings
router = APIRouter()
# Pydantic models for request validation
class InternalTransferItem(BaseModel):
product_id: str
product_name: Optional[str] = None
quantity: float
unit_of_measure: str = 'units'
class InternalTransferRequest(BaseModel):
parent_tenant_id: str
items: List[InternalTransferItem]
delivery_date: str
notes: Optional[str] = None
class ApprovalRequest(BaseModel):
pass # Empty for now, might add approval notes later
def get_internal_transfer_service(db: AsyncSession = Depends(get_db)) -> InternalTransferService:
"""Dependency to get internal transfer service"""
purchase_order_repository = PurchaseOrderRepository(db)
recipe_client = get_recipes_client(config=settings, service_name="procurement-service")
production_client = get_production_client(config=settings, service_name="procurement-service")
inventory_client = get_inventory_client(config=settings, service_name="procurement-service")
return InternalTransferService(
purchase_order_repository=purchase_order_repository,
recipe_client=recipe_client,
production_client=production_client,
inventory_client=inventory_client
)
@router.post("/tenants/{tenant_id}/procurement/internal-transfers", response_model=None)
async def create_internal_purchase_order(
tenant_id: str,
transfer_request: InternalTransferRequest,
internal_transfer_service: InternalTransferService = Depends(get_internal_transfer_service),
verified_tenant: str = Depends(verify_tenant_permission_dep)
):
"""
Create an internal purchase order from child to parent tenant
**Enterprise Tier Feature**: Internal transfers require Enterprise subscription.
"""
try:
# Validate subscription tier for internal transfers
from shared.subscription.plans import PlanFeatures
from shared.clients import get_tenant_client
tenant_client = get_tenant_client(config=settings, service_name="procurement-service")
subscription = await tenant_client.get_tenant_subscription(tenant_id)
if not subscription:
raise HTTPException(
status_code=403,
detail="No active subscription found. Internal transfers require Enterprise tier."
)
# Check if tier supports internal transfers
if not PlanFeatures.validate_internal_transfers(subscription.get("plan", "starter")):
raise HTTPException(
status_code=403,
detail=f"Internal transfers require Enterprise tier. Current tier: {subscription.get('plan', 'starter')}"
)
# Parse delivery_date
from datetime import datetime
delivery_date = datetime.fromisoformat(transfer_request.delivery_date.split('T')[0]).date()
# Convert Pydantic items to dict
items = [item.model_dump() for item in transfer_request.items]
# Create the internal purchase order
result = await internal_transfer_service.create_internal_purchase_order(
child_tenant_id=tenant_id,
parent_tenant_id=transfer_request.parent_tenant_id,
items=items,
delivery_date=delivery_date,
requested_by_user_id="temp_user_id", # Would come from auth context
notes=transfer_request.notes
)
return result
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to create internal purchase order: {str(e)}")
@router.post("/tenants/{tenant_id}/procurement/internal-transfers/{po_id}/approve", response_model=None)
async def approve_internal_transfer(
tenant_id: str,
po_id: str,
approval_request: Optional[ApprovalRequest] = None,
internal_transfer_service: InternalTransferService = Depends(get_internal_transfer_service),
verified_tenant: str = Depends(verify_tenant_permission_dep)
):
"""
Approve an internal transfer request
"""
try:
approved_by_user_id = "temp_user_id" # Would come from auth context
result = await internal_transfer_service.approve_internal_transfer(
po_id=po_id,
approved_by_user_id=approved_by_user_id
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to approve internal transfer: {str(e)}")
@router.get("/tenants/{tenant_id}/procurement/internal-transfers/pending", response_model=None)
async def get_pending_internal_transfers(
tenant_id: str,
internal_transfer_service: InternalTransferService = Depends(get_internal_transfer_service),
verified_tenant: str = Depends(verify_tenant_permission_dep)
):
"""
Get pending internal transfers for a tenant
"""
try:
result = await internal_transfer_service.get_pending_internal_transfers(tenant_id=tenant_id)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get pending internal transfers: {str(e)}")
@router.get("/tenants/{tenant_id}/procurement/internal-transfers/history", response_model=None)
async def get_internal_transfer_history(
tenant_id: str,
parent_tenant_id: Optional[str] = None,
child_tenant_id: Optional[str] = None,
start_date: Optional[date] = None,
end_date: Optional[date] = None,
internal_transfer_service: InternalTransferService = Depends(get_internal_transfer_service),
verified_tenant: str = Depends(verify_tenant_permission_dep)
):
"""
Get internal transfer history with optional filtering
"""
try:
result = await internal_transfer_service.get_internal_transfer_history(
tenant_id=tenant_id,
parent_tenant_id=parent_tenant_id,
child_tenant_id=child_tenant_id,
start_date=start_date,
end_date=end_date
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get internal transfer history: {str(e)}")