Initial commit - production deployment
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
"""
|
||||
Delivery Route Repository
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import date, datetime
|
||||
import uuid
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
from app.models.distribution import DeliveryRoute, DeliveryRouteStatus
|
||||
from shared.database.base import Base
|
||||
|
||||
|
||||
class DeliveryRouteRepository:
|
||||
def __init__(self, db_session: AsyncSession):
|
||||
self.db_session = db_session
|
||||
|
||||
async def create_route(self, route_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new delivery route
|
||||
"""
|
||||
# Define system user ID to use when user_id is not provided
|
||||
SYSTEM_USER_ID = uuid.UUID("50000000-0000-0000-0000-000000000004")
|
||||
|
||||
route = DeliveryRoute(
|
||||
id=uuid.uuid4(),
|
||||
tenant_id=route_data['tenant_id'],
|
||||
route_number=route_data['route_number'],
|
||||
route_date=route_data['route_date'],
|
||||
vehicle_id=route_data.get('vehicle_id'),
|
||||
driver_id=route_data.get('driver_id'),
|
||||
total_distance_km=route_data.get('total_distance_km'),
|
||||
estimated_duration_minutes=route_data.get('estimated_duration_minutes'),
|
||||
route_sequence=route_data.get('route_sequence'),
|
||||
status=route_data.get('status', 'planned'),
|
||||
created_by=route_data.get('created_by', SYSTEM_USER_ID),
|
||||
updated_by=route_data.get('updated_by', SYSTEM_USER_ID)
|
||||
)
|
||||
|
||||
self.db_session.add(route)
|
||||
await self.db_session.commit()
|
||||
await self.db_session.refresh(route)
|
||||
|
||||
# Convert SQLAlchemy object to dict for return
|
||||
return {
|
||||
'id': str(route.id),
|
||||
'tenant_id': str(route.tenant_id),
|
||||
'route_number': route.route_number,
|
||||
'route_date': route.route_date,
|
||||
'vehicle_id': route.vehicle_id,
|
||||
'driver_id': route.driver_id,
|
||||
'total_distance_km': route.total_distance_km,
|
||||
'estimated_duration_minutes': route.estimated_duration_minutes,
|
||||
'route_sequence': route.route_sequence,
|
||||
'status': route.status.value if hasattr(route.status, 'value') else route.status,
|
||||
'created_at': route.created_at,
|
||||
'updated_at': route.updated_at
|
||||
}
|
||||
|
||||
async def get_routes_by_date(self, tenant_id: str, target_date: date) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all delivery routes for a specific date and tenant
|
||||
"""
|
||||
stmt = select(DeliveryRoute).where(
|
||||
(DeliveryRoute.tenant_id == tenant_id) &
|
||||
(DeliveryRoute.route_date >= datetime.combine(target_date, datetime.min.time())) &
|
||||
(DeliveryRoute.route_date < datetime.combine(target_date, datetime.max.time().replace(hour=23, minute=59, second=59)))
|
||||
)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
routes = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
'id': str(route.id),
|
||||
'tenant_id': str(route.tenant_id),
|
||||
'route_number': route.route_number,
|
||||
'route_date': route.route_date,
|
||||
'vehicle_id': route.vehicle_id,
|
||||
'driver_id': route.driver_id,
|
||||
'total_distance_km': route.total_distance_km,
|
||||
'estimated_duration_minutes': route.estimated_duration_minutes,
|
||||
'route_sequence': route.route_sequence,
|
||||
'status': route.status.value if hasattr(route.status, 'value') else route.status,
|
||||
'created_at': route.created_at,
|
||||
'updated_at': route.updated_at
|
||||
}
|
||||
for route in routes
|
||||
]
|
||||
|
||||
async def get_routes_by_date_range(self, tenant_id: str, start_date: date, end_date: date) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all delivery routes for a specific date range and tenant
|
||||
"""
|
||||
stmt = select(DeliveryRoute).where(
|
||||
(DeliveryRoute.tenant_id == tenant_id) &
|
||||
(DeliveryRoute.route_date >= datetime.combine(start_date, datetime.min.time())) &
|
||||
(DeliveryRoute.route_date <= datetime.combine(end_date, datetime.max.time().replace(hour=23, minute=59, second=59)))
|
||||
)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
routes = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
'id': str(route.id),
|
||||
'tenant_id': str(route.tenant_id),
|
||||
'route_number': route.route_number,
|
||||
'route_date': route.route_date,
|
||||
'vehicle_id': route.vehicle_id,
|
||||
'driver_id': route.driver_id,
|
||||
'total_distance_km': route.total_distance_km,
|
||||
'estimated_duration_minutes': route.estimated_duration_minutes,
|
||||
'route_sequence': route.route_sequence,
|
||||
'status': route.status.value if hasattr(route.status, 'value') else route.status,
|
||||
'created_at': route.created_at,
|
||||
'updated_at': route.updated_at
|
||||
}
|
||||
for route in routes
|
||||
]
|
||||
|
||||
async def get_route_by_id(self, route_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get a specific delivery route by ID
|
||||
"""
|
||||
stmt = select(DeliveryRoute).where(DeliveryRoute.id == route_id)
|
||||
result = await self.db_session.execute(stmt)
|
||||
route = result.scalar_one_or_none()
|
||||
|
||||
if route:
|
||||
return {
|
||||
'id': str(route.id),
|
||||
'tenant_id': str(route.tenant_id),
|
||||
'route_number': route.route_number,
|
||||
'route_date': route.route_date,
|
||||
'vehicle_id': route.vehicle_id,
|
||||
'driver_id': route.driver_id,
|
||||
'total_distance_km': route.total_distance_km,
|
||||
'estimated_duration_minutes': route.estimated_duration_minutes,
|
||||
'route_sequence': route.route_sequence,
|
||||
'status': route.status.value if hasattr(route.status, 'value') else route.status,
|
||||
'created_at': route.created_at,
|
||||
'updated_at': route.updated_at
|
||||
}
|
||||
return None
|
||||
|
||||
async def update_route_status(self, route_id: str, status: str, user_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Update route status
|
||||
"""
|
||||
stmt = select(DeliveryRoute).where(DeliveryRoute.id == route_id)
|
||||
result = await self.db_session.execute(stmt)
|
||||
route = result.scalar_one_or_none()
|
||||
|
||||
if not route:
|
||||
return None
|
||||
|
||||
# Handle system user ID if passed as string
|
||||
if user_id == 'system':
|
||||
SYSTEM_USER_ID = uuid.UUID("50000000-0000-0000-0000-000000000004")
|
||||
route.updated_by = SYSTEM_USER_ID
|
||||
else:
|
||||
route.updated_by = user_id
|
||||
route.status = status
|
||||
await self.db_session.commit()
|
||||
await self.db_session.refresh(route)
|
||||
|
||||
return {
|
||||
'id': str(route.id),
|
||||
'tenant_id': str(route.tenant_id),
|
||||
'route_number': route.route_number,
|
||||
'route_date': route.route_date,
|
||||
'vehicle_id': route.vehicle_id,
|
||||
'driver_id': route.driver_id,
|
||||
'total_distance_km': route.total_distance_km,
|
||||
'estimated_duration_minutes': route.estimated_duration_minutes,
|
||||
'route_sequence': route.route_sequence,
|
||||
'status': route.status.value if hasattr(route.status, 'value') else route.status,
|
||||
'created_at': route.created_at,
|
||||
'updated_at': route.updated_at
|
||||
}
|
||||
|
||||
async def get_all_routes_for_tenant(self, tenant_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all delivery routes for a tenant
|
||||
"""
|
||||
stmt = select(DeliveryRoute).where(DeliveryRoute.tenant_id == tenant_id)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
routes = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
'id': str(route.id),
|
||||
'tenant_id': str(route.tenant_id),
|
||||
'route_number': route.route_number,
|
||||
'route_date': route.route_date,
|
||||
'vehicle_id': route.vehicle_id,
|
||||
'driver_id': route.driver_id,
|
||||
'total_distance_km': route.total_distance_km,
|
||||
'estimated_duration_minutes': route.estimated_duration_minutes,
|
||||
'route_sequence': route.route_sequence,
|
||||
'status': route.status.value if hasattr(route.status, 'value') else route.status,
|
||||
'created_at': route.created_at,
|
||||
'updated_at': route.updated_at
|
||||
}
|
||||
for route in routes
|
||||
]
|
||||
|
||||
async def delete_demo_routes_for_tenant(self, tenant_id: str) -> int:
|
||||
"""
|
||||
Delete all demo routes for a tenant
|
||||
Used for demo session cleanup
|
||||
|
||||
Args:
|
||||
tenant_id: The tenant ID to delete routes for
|
||||
|
||||
Returns:
|
||||
Number of routes deleted
|
||||
"""
|
||||
from sqlalchemy import delete
|
||||
|
||||
# Delete routes with DEMO- prefix in route_number
|
||||
stmt = delete(DeliveryRoute).where(
|
||||
(DeliveryRoute.tenant_id == uuid.UUID(tenant_id)) &
|
||||
(DeliveryRoute.route_number.like('DEMO-%'))
|
||||
)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
await self.db_session.commit()
|
||||
|
||||
deleted_count = result.rowcount
|
||||
return deleted_count
|
||||
|
||||
async def update_route_vrp_metrics(self, route_id: str, vrp_metrics: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Update VRP optimization metrics for a route
|
||||
"""
|
||||
stmt = select(DeliveryRoute).where(DeliveryRoute.id == route_id)
|
||||
result = await self.db_session.execute(stmt)
|
||||
route = result.scalar_one_or_none()
|
||||
|
||||
if not route:
|
||||
return None
|
||||
|
||||
# Update VRP metrics fields
|
||||
route.vrp_optimization_savings = vrp_metrics.get('vrp_optimization_savings')
|
||||
route.vrp_algorithm_version = vrp_metrics.get('vrp_algorithm_version')
|
||||
route.vrp_optimization_timestamp = vrp_metrics.get('vrp_optimization_timestamp')
|
||||
route.vrp_constraints_satisfied = vrp_metrics.get('vrp_constraints_satisfied')
|
||||
route.vrp_objective_value = vrp_metrics.get('vrp_objective_value')
|
||||
|
||||
await self.db_session.commit()
|
||||
await self.db_session.refresh(route)
|
||||
|
||||
return {
|
||||
'id': str(route.id),
|
||||
'vrp_optimization_savings': route.vrp_optimization_savings,
|
||||
'vrp_algorithm_version': route.vrp_algorithm_version,
|
||||
'vrp_optimization_timestamp': route.vrp_optimization_timestamp,
|
||||
'vrp_constraints_satisfied': route.vrp_constraints_satisfied,
|
||||
'vrp_objective_value': route.vrp_objective_value
|
||||
}
|
||||
|
||||
async def get_routes_by_tenant(self, tenant_id: str, limit: int = None, offset: int = None, order_by: str = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all routes for a specific tenant with pagination and ordering
|
||||
"""
|
||||
stmt = select(DeliveryRoute).where(DeliveryRoute.tenant_id == tenant_id)
|
||||
|
||||
# Apply ordering if specified
|
||||
if order_by:
|
||||
if 'vrp_optimization_timestamp' in order_by:
|
||||
if 'DESC' in order_by:
|
||||
stmt = stmt.order_by(DeliveryRoute.vrp_optimization_timestamp.desc())
|
||||
else:
|
||||
stmt = stmt.order_by(DeliveryRoute.vrp_optimization_timestamp.asc())
|
||||
elif 'route_date' in order_by:
|
||||
if 'DESC' in order_by:
|
||||
stmt = stmt.order_by(DeliveryRoute.route_date.desc())
|
||||
else:
|
||||
stmt = stmt.order_by(DeliveryRoute.route_date.asc())
|
||||
|
||||
# Apply pagination if specified
|
||||
if limit is not None:
|
||||
stmt = stmt.limit(limit)
|
||||
if offset is not None:
|
||||
stmt = stmt.offset(offset)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
routes = result.scalars().all()
|
||||
|
||||
return [{
|
||||
'id': str(route.id),
|
||||
'tenant_id': str(route.tenant_id),
|
||||
'route_number': route.route_number,
|
||||
'route_date': route.route_date,
|
||||
'vehicle_id': route.vehicle_id,
|
||||
'driver_id': route.driver_id,
|
||||
'total_distance_km': route.total_distance_km,
|
||||
'estimated_duration_minutes': route.estimated_duration_minutes,
|
||||
'route_sequence': route.route_sequence,
|
||||
'status': route.status.value if hasattr(route.status, 'value') else route.status,
|
||||
'created_at': route.created_at,
|
||||
'updated_at': route.updated_at,
|
||||
'vrp_optimization_savings': route.vrp_optimization_savings,
|
||||
'vrp_algorithm_version': route.vrp_algorithm_version,
|
||||
'vrp_optimization_timestamp': route.vrp_optimization_timestamp,
|
||||
'vrp_constraints_satisfied': route.vrp_constraints_satisfied,
|
||||
'vrp_objective_value': route.vrp_objective_value
|
||||
} for route in routes]
|
||||
@@ -0,0 +1,74 @@
|
||||
from typing import List, Optional, Dict, Any
|
||||
from uuid import UUID
|
||||
from sqlalchemy import select, update, delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
import structlog
|
||||
|
||||
from app.models.distribution import DeliverySchedule
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
class DeliveryScheduleRepository:
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
async def create_schedule(self, schedule_data: Dict[str, Any]) -> DeliverySchedule:
|
||||
"""Create a new delivery schedule"""
|
||||
try:
|
||||
schedule = DeliverySchedule(**schedule_data)
|
||||
self.session.add(schedule)
|
||||
await self.session.commit()
|
||||
await self.session.refresh(schedule)
|
||||
return schedule
|
||||
except IntegrityError as e:
|
||||
await self.session.rollback()
|
||||
logger.error("Error creating delivery schedule", error=str(e))
|
||||
raise ValueError(f"Failed to create delivery schedule: {e}")
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
logger.error("Unexpected error creating delivery schedule", error=str(e))
|
||||
raise
|
||||
|
||||
async def get_schedule_by_id(self, schedule_id: UUID) -> Optional[DeliverySchedule]:
|
||||
"""Get a delivery schedule by ID"""
|
||||
result = await self.session.execute(
|
||||
select(DeliverySchedule).where(DeliverySchedule.id == schedule_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_schedules_by_tenant(self, tenant_id: UUID) -> List[DeliverySchedule]:
|
||||
"""Get all delivery schedules for a tenant"""
|
||||
result = await self.session.execute(
|
||||
select(DeliverySchedule).where(DeliverySchedule.tenant_id == tenant_id)
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
async def update_schedule(self, schedule_id: UUID, update_data: Dict[str, Any]) -> Optional[DeliverySchedule]:
|
||||
"""Update a delivery schedule"""
|
||||
try:
|
||||
stmt = (
|
||||
update(DeliverySchedule)
|
||||
.where(DeliverySchedule.id == schedule_id)
|
||||
.values(**update_data)
|
||||
.returning(DeliverySchedule)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
await self.session.commit()
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
logger.error("Error updating delivery schedule", error=str(e), schedule_id=schedule_id)
|
||||
raise
|
||||
|
||||
async def delete_schedule(self, schedule_id: UUID) -> bool:
|
||||
"""Delete a delivery schedule"""
|
||||
try:
|
||||
stmt = delete(DeliverySchedule).where(DeliverySchedule.id == schedule_id)
|
||||
result = await self.session.execute(stmt)
|
||||
await self.session.commit()
|
||||
return result.rowcount > 0
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
logger.error("Error deleting delivery schedule", error=str(e), schedule_id=schedule_id)
|
||||
raise
|
||||
345
services/distribution/app/repositories/shipment_repository.py
Normal file
345
services/distribution/app/repositories/shipment_repository.py
Normal file
@@ -0,0 +1,345 @@
|
||||
"""
|
||||
Shipment Repository
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import date, datetime
|
||||
import uuid
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
from app.models.distribution import Shipment, ShipmentStatus
|
||||
from shared.database.base import Base
|
||||
|
||||
|
||||
class ShipmentRepository:
|
||||
def __init__(self, db_session: AsyncSession):
|
||||
self.db_session = db_session
|
||||
|
||||
async def create_shipment(self, shipment_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new shipment
|
||||
"""
|
||||
# Define system user ID to use when user_id is not provided
|
||||
SYSTEM_USER_ID = uuid.UUID("50000000-0000-0000-0000-000000000004")
|
||||
|
||||
shipment = Shipment(
|
||||
id=uuid.uuid4(),
|
||||
tenant_id=shipment_data['tenant_id'],
|
||||
parent_tenant_id=shipment_data['parent_tenant_id'],
|
||||
child_tenant_id=shipment_data['child_tenant_id'],
|
||||
purchase_order_id=shipment_data.get('purchase_order_id'),
|
||||
delivery_route_id=shipment_data.get('delivery_route_id'),
|
||||
shipment_number=shipment_data['shipment_number'],
|
||||
shipment_date=shipment_data['shipment_date'],
|
||||
status=shipment_data.get('status', 'pending'),
|
||||
total_weight_kg=shipment_data.get('total_weight_kg'),
|
||||
total_volume_m3=shipment_data.get('total_volume_m3'),
|
||||
created_by=shipment_data.get('created_by', SYSTEM_USER_ID),
|
||||
updated_by=shipment_data.get('updated_by', SYSTEM_USER_ID)
|
||||
)
|
||||
|
||||
self.db_session.add(shipment)
|
||||
await self.db_session.commit()
|
||||
await self.db_session.refresh(shipment)
|
||||
|
||||
# Convert SQLAlchemy object to dict for return
|
||||
return {
|
||||
'id': str(shipment.id),
|
||||
'tenant_id': str(shipment.tenant_id),
|
||||
'parent_tenant_id': str(shipment.parent_tenant_id),
|
||||
'child_tenant_id': str(shipment.child_tenant_id),
|
||||
'purchase_order_id': str(shipment.purchase_order_id) if shipment.purchase_order_id else None,
|
||||
'delivery_route_id': str(shipment.delivery_route_id) if shipment.delivery_route_id else None,
|
||||
'shipment_number': shipment.shipment_number,
|
||||
'shipment_date': shipment.shipment_date,
|
||||
'current_location_lat': shipment.current_location_lat,
|
||||
'current_location_lng': shipment.current_location_lng,
|
||||
'last_tracked_at': shipment.last_tracked_at,
|
||||
'status': shipment.status.value if hasattr(shipment.status, 'value') else shipment.status,
|
||||
'actual_delivery_time': shipment.actual_delivery_time,
|
||||
'signature': shipment.signature,
|
||||
'photo_url': shipment.photo_url,
|
||||
'received_by_name': shipment.received_by_name,
|
||||
'delivery_notes': shipment.delivery_notes,
|
||||
'total_weight_kg': shipment.total_weight_kg,
|
||||
'total_volume_m3': shipment.total_volume_m3,
|
||||
'created_at': shipment.created_at,
|
||||
'updated_at': shipment.updated_at
|
||||
}
|
||||
|
||||
async def get_shipments_by_date(self, tenant_id: str, target_date: date) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all shipments for a specific date and tenant
|
||||
"""
|
||||
stmt = select(Shipment).where(
|
||||
(Shipment.tenant_id == tenant_id) &
|
||||
(Shipment.shipment_date >= datetime.combine(target_date, datetime.min.time())) &
|
||||
(Shipment.shipment_date < datetime.combine(target_date, datetime.max.time().replace(hour=23, minute=59, second=59)))
|
||||
)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
shipments = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
'id': str(shipment.id),
|
||||
'tenant_id': str(shipment.tenant_id),
|
||||
'parent_tenant_id': str(shipment.parent_tenant_id),
|
||||
'child_tenant_id': str(shipment.child_tenant_id),
|
||||
'purchase_order_id': str(shipment.purchase_order_id) if shipment.purchase_order_id else None,
|
||||
'delivery_route_id': str(shipment.delivery_route_id) if shipment.delivery_route_id else None,
|
||||
'shipment_number': shipment.shipment_number,
|
||||
'shipment_date': shipment.shipment_date,
|
||||
'current_location_lat': shipment.current_location_lat,
|
||||
'current_location_lng': shipment.current_location_lng,
|
||||
'last_tracked_at': shipment.last_tracked_at,
|
||||
'status': shipment.status.value if hasattr(shipment.status, 'value') else shipment.status,
|
||||
'actual_delivery_time': shipment.actual_delivery_time,
|
||||
'signature': shipment.signature,
|
||||
'photo_url': shipment.photo_url,
|
||||
'received_by_name': shipment.received_by_name,
|
||||
'delivery_notes': shipment.delivery_notes,
|
||||
'total_weight_kg': shipment.total_weight_kg,
|
||||
'total_volume_m3': shipment.total_volume_m3,
|
||||
'created_at': shipment.created_at,
|
||||
'updated_at': shipment.updated_at
|
||||
}
|
||||
for shipment in shipments
|
||||
]
|
||||
|
||||
async def get_shipments_by_date_range(self, tenant_id: str, start_date: date, end_date: date) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all shipments for a specific date range and tenant
|
||||
"""
|
||||
stmt = select(Shipment).where(
|
||||
(Shipment.tenant_id == tenant_id) &
|
||||
(Shipment.shipment_date >= datetime.combine(start_date, datetime.min.time())) &
|
||||
(Shipment.shipment_date <= datetime.combine(end_date, datetime.max.time().replace(hour=23, minute=59, second=59)))
|
||||
)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
shipments = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
'id': str(shipment.id),
|
||||
'tenant_id': str(shipment.tenant_id),
|
||||
'parent_tenant_id': str(shipment.parent_tenant_id),
|
||||
'child_tenant_id': str(shipment.child_tenant_id),
|
||||
'purchase_order_id': str(shipment.purchase_order_id) if shipment.purchase_order_id else None,
|
||||
'delivery_route_id': str(shipment.delivery_route_id) if shipment.delivery_route_id else None,
|
||||
'shipment_number': shipment.shipment_number,
|
||||
'shipment_date': shipment.shipment_date,
|
||||
'current_location_lat': shipment.current_location_lat,
|
||||
'current_location_lng': shipment.current_location_lng,
|
||||
'last_tracked_at': shipment.last_tracked_at,
|
||||
'status': shipment.status.value if hasattr(shipment.status, 'value') else shipment.status,
|
||||
'actual_delivery_time': shipment.actual_delivery_time,
|
||||
'signature': shipment.signature,
|
||||
'photo_url': shipment.photo_url,
|
||||
'received_by_name': shipment.received_by_name,
|
||||
'delivery_notes': shipment.delivery_notes,
|
||||
'total_weight_kg': shipment.total_weight_kg,
|
||||
'total_volume_m3': shipment.total_volume_m3,
|
||||
'created_at': shipment.created_at,
|
||||
'updated_at': shipment.updated_at
|
||||
}
|
||||
for shipment in shipments
|
||||
]
|
||||
|
||||
async def get_shipment_by_id(self, shipment_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get a specific shipment by ID
|
||||
"""
|
||||
stmt = select(Shipment).where(Shipment.id == shipment_id)
|
||||
result = await self.db_session.execute(stmt)
|
||||
shipment = result.scalar_one_or_none()
|
||||
|
||||
if shipment:
|
||||
return {
|
||||
'id': str(shipment.id),
|
||||
'tenant_id': str(shipment.tenant_id),
|
||||
'parent_tenant_id': str(shipment.parent_tenant_id),
|
||||
'child_tenant_id': str(shipment.child_tenant_id),
|
||||
'purchase_order_id': str(shipment.purchase_order_id) if shipment.purchase_order_id else None,
|
||||
'delivery_route_id': str(shipment.delivery_route_id) if shipment.delivery_route_id else None,
|
||||
'shipment_number': shipment.shipment_number,
|
||||
'shipment_date': shipment.shipment_date,
|
||||
'current_location_lat': shipment.current_location_lat,
|
||||
'current_location_lng': shipment.current_location_lng,
|
||||
'last_tracked_at': shipment.last_tracked_at,
|
||||
'status': shipment.status.value if hasattr(shipment.status, 'value') else shipment.status,
|
||||
'actual_delivery_time': shipment.actual_delivery_time,
|
||||
'signature': shipment.signature,
|
||||
'photo_url': shipment.photo_url,
|
||||
'received_by_name': shipment.received_by_name,
|
||||
'delivery_notes': shipment.delivery_notes,
|
||||
'total_weight_kg': shipment.total_weight_kg,
|
||||
'total_volume_m3': shipment.total_volume_m3,
|
||||
'created_at': shipment.created_at,
|
||||
'updated_at': shipment.updated_at
|
||||
}
|
||||
return None
|
||||
|
||||
async def update_shipment_status(self, shipment_id: str, status: str, user_id: str, metadata: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Update shipment status
|
||||
"""
|
||||
stmt = select(Shipment).where(Shipment.id == shipment_id)
|
||||
result = await self.db_session.execute(stmt)
|
||||
shipment = result.scalar_one_or_none()
|
||||
|
||||
if not shipment:
|
||||
return None
|
||||
|
||||
# Handle system user ID if passed as string
|
||||
if user_id == 'system':
|
||||
SYSTEM_USER_ID = uuid.UUID("50000000-0000-0000-0000-000000000004")
|
||||
shipment.updated_by = SYSTEM_USER_ID
|
||||
else:
|
||||
shipment.updated_by = user_id
|
||||
shipment.status = status
|
||||
|
||||
# Update tracking information if provided in metadata
|
||||
if metadata:
|
||||
if 'current_location_lat' in metadata:
|
||||
shipment.current_location_lat = metadata['current_location_lat']
|
||||
if 'current_location_lng' in metadata:
|
||||
shipment.current_location_lng = metadata['current_location_lng']
|
||||
if 'last_tracked_at' in metadata:
|
||||
from datetime import datetime
|
||||
shipment.last_tracked_at = datetime.fromisoformat(metadata['last_tracked_at']) if isinstance(metadata['last_tracked_at'], str) else metadata['last_tracked_at']
|
||||
if 'signature' in metadata:
|
||||
shipment.signature = metadata['signature']
|
||||
if 'photo_url' in metadata:
|
||||
shipment.photo_url = metadata['photo_url']
|
||||
if 'received_by_name' in metadata:
|
||||
shipment.received_by_name = metadata['received_by_name']
|
||||
if 'delivery_notes' in metadata:
|
||||
shipment.delivery_notes = metadata['delivery_notes']
|
||||
if 'actual_delivery_time' in metadata:
|
||||
from datetime import datetime
|
||||
shipment.actual_delivery_time = datetime.fromisoformat(metadata['actual_delivery_time']) if isinstance(metadata['actual_delivery_time'], str) else metadata['actual_delivery_time']
|
||||
|
||||
await self.db_session.commit()
|
||||
await self.db_session.refresh(shipment)
|
||||
|
||||
return {
|
||||
'id': str(shipment.id),
|
||||
'tenant_id': str(shipment.tenant_id),
|
||||
'parent_tenant_id': str(shipment.parent_tenant_id),
|
||||
'child_tenant_id': str(shipment.child_tenant_id),
|
||||
'purchase_order_id': str(shipment.purchase_order_id) if shipment.purchase_order_id else None,
|
||||
'delivery_route_id': str(shipment.delivery_route_id) if shipment.delivery_route_id else None,
|
||||
'shipment_number': shipment.shipment_number,
|
||||
'shipment_date': shipment.shipment_date,
|
||||
'current_location_lat': shipment.current_location_lat,
|
||||
'current_location_lng': shipment.current_location_lng,
|
||||
'last_tracked_at': shipment.last_tracked_at,
|
||||
'status': shipment.status.value if hasattr(shipment.status, 'value') else shipment.status,
|
||||
'actual_delivery_time': shipment.actual_delivery_time,
|
||||
'signature': shipment.signature,
|
||||
'photo_url': shipment.photo_url,
|
||||
'received_by_name': shipment.received_by_name,
|
||||
'delivery_notes': shipment.delivery_notes,
|
||||
'total_weight_kg': shipment.total_weight_kg,
|
||||
'total_volume_m3': shipment.total_volume_m3,
|
||||
'created_at': shipment.created_at,
|
||||
'updated_at': shipment.updated_at
|
||||
}
|
||||
|
||||
async def assign_shipments_to_route(self, route_id: str, shipment_ids: List[str], user_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Assign multiple shipments to a specific route
|
||||
"""
|
||||
stmt = select(Shipment).where(Shipment.id.in_(shipment_ids))
|
||||
result = await self.db_session.execute(stmt)
|
||||
shipments = result.scalars().all()
|
||||
|
||||
# Handle system user ID if passed as string
|
||||
actual_user_id = user_id
|
||||
if user_id == 'system':
|
||||
actual_user_id = uuid.UUID("50000000-0000-0000-0000-000000000004")
|
||||
|
||||
updated_shipments = []
|
||||
for shipment in shipments:
|
||||
shipment.delivery_route_id = route_id
|
||||
shipment.updated_by = actual_user_id
|
||||
await self.db_session.refresh(shipment)
|
||||
|
||||
updated_shipments.append({
|
||||
'id': str(shipment.id),
|
||||
'shipment_number': shipment.shipment_number,
|
||||
'status': shipment.status.value if hasattr(shipment.status, 'value') else shipment.status,
|
||||
'delivery_route_id': str(shipment.delivery_route_id)
|
||||
})
|
||||
|
||||
await self.db_session.commit()
|
||||
|
||||
return {
|
||||
'route_id': route_id,
|
||||
'updated_shipments': updated_shipments,
|
||||
'count': len(updated_shipments)
|
||||
}
|
||||
|
||||
async def get_all_shipments_for_tenant(self, tenant_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all shipments for a tenant
|
||||
"""
|
||||
stmt = select(Shipment).where(Shipment.tenant_id == tenant_id)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
shipments = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
'id': str(shipment.id),
|
||||
'tenant_id': str(shipment.tenant_id),
|
||||
'parent_tenant_id': str(shipment.parent_tenant_id),
|
||||
'child_tenant_id': str(shipment.child_tenant_id),
|
||||
'purchase_order_id': str(shipment.purchase_order_id) if shipment.purchase_order_id else None,
|
||||
'delivery_route_id': str(shipment.delivery_route_id) if shipment.delivery_route_id else None,
|
||||
'shipment_number': shipment.shipment_number,
|
||||
'shipment_date': shipment.shipment_date,
|
||||
'current_location_lat': shipment.current_location_lat,
|
||||
'current_location_lng': shipment.current_location_lng,
|
||||
'last_tracked_at': shipment.last_tracked_at,
|
||||
'status': shipment.status.value if hasattr(shipment.status, 'value') else shipment.status,
|
||||
'actual_delivery_time': shipment.actual_delivery_time,
|
||||
'signature': shipment.signature,
|
||||
'photo_url': shipment.photo_url,
|
||||
'received_by_name': shipment.received_by_name,
|
||||
'delivery_notes': shipment.delivery_notes,
|
||||
'total_weight_kg': shipment.total_weight_kg,
|
||||
'total_volume_m3': shipment.total_volume_m3,
|
||||
'created_at': shipment.created_at,
|
||||
'updated_at': shipment.updated_at
|
||||
}
|
||||
for shipment in shipments
|
||||
]
|
||||
|
||||
async def delete_demo_shipments_for_tenant(self, tenant_id: str) -> int:
|
||||
"""
|
||||
Delete all demo shipments for a tenant
|
||||
Used for demo session cleanup
|
||||
|
||||
Args:
|
||||
tenant_id: The tenant ID to delete shipments for
|
||||
|
||||
Returns:
|
||||
Number of shipments deleted
|
||||
"""
|
||||
from sqlalchemy import delete
|
||||
|
||||
# Delete shipments with DEMOSHP- prefix in shipment_number
|
||||
stmt = delete(Shipment).where(
|
||||
(Shipment.tenant_id == uuid.UUID(tenant_id)) &
|
||||
(Shipment.shipment_number.like('DEMOSHP-%'))
|
||||
)
|
||||
|
||||
result = await self.db_session.execute(stmt)
|
||||
await self.db_session.commit()
|
||||
|
||||
deleted_count = result.rowcount
|
||||
return deleted_count
|
||||
Reference in New Issue
Block a user