345 lines
16 KiB
Python
345 lines
16 KiB
Python
"""
|
|
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 |