- Move import scripts from scripts/odoo to datasets/hub - Add ensure_reference_data() to create transport categories and loading types - Fix logistics_api.py to use code field directly instead of name mapping 🤖 Generated with [Claude Code](https://claude.com/claude-code)
218 lines
7.6 KiB
Python
218 lines
7.6 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
||
from pydantic import BaseModel
|
||
from typing import List
|
||
from odoo import http
|
||
from odoo.addons.fastapi.dependencies import odoo_env
|
||
|
||
router = APIRouter(tags=["logistics"])
|
||
|
||
|
||
class LogisticsNodeResponse(BaseModel):
|
||
uuid: str
|
||
name: str
|
||
latitude: float
|
||
longitude: float
|
||
country: str
|
||
country_code: str
|
||
transport_types: List[str] # ['auto', 'rail', 'air', 'sea']
|
||
|
||
|
||
def get_node_transport_types(node) -> List[str]:
|
||
"""Получить список типов транспорта для узла из его соединений"""
|
||
types = set()
|
||
for conn in node.connection_ids:
|
||
if conn.transport_category_id and conn.transport_category_id.code:
|
||
types.add(conn.transport_category_id.code)
|
||
return list(types)
|
||
|
||
|
||
class LogisticsRouteResponse(BaseModel):
|
||
uuid: str
|
||
name: str
|
||
departure_node_uuid: str
|
||
departure_node_name: str
|
||
arrival_node_uuid: str
|
||
arrival_node_name: str
|
||
transport_type: str
|
||
distance_km: float | None
|
||
travel_time_hours: float
|
||
active: bool
|
||
|
||
|
||
class LogisticsScheduleResponse(BaseModel):
|
||
uuid: str
|
||
name: str
|
||
departure_node_uuid: str
|
||
departure_node_name: str
|
||
arrival_node_uuid: str
|
||
arrival_node_name: str
|
||
route_uuid: str | None
|
||
transport_type: str
|
||
departure_time: float
|
||
frequency: str
|
||
travel_time_hours: float | None
|
||
active: bool
|
||
|
||
|
||
@router.get("/nodes")
|
||
def get_logistics_nodes(env=Depends(odoo_env)) -> List[LogisticsNodeResponse]:
|
||
"""Получить список всех логистических узлов"""
|
||
|
||
LogisticsNode = env['logistics.node']
|
||
nodes = LogisticsNode.search([])
|
||
|
||
result = []
|
||
for node in nodes:
|
||
result.append(LogisticsNodeResponse(
|
||
uuid=node.uuid,
|
||
name=node.name,
|
||
latitude=node.latitude,
|
||
longitude=node.longitude,
|
||
country=node.country_id.name if node.country_id else '',
|
||
country_code=node.country_id.code if node.country_id else '',
|
||
transport_types=get_node_transport_types(node)
|
||
))
|
||
|
||
return result
|
||
|
||
|
||
@router.get("/nodes/{node_uuid}")
|
||
def get_logistics_node(node_uuid: str, env=Depends(odoo_env)) -> LogisticsNodeResponse:
|
||
"""Получить логистический узел по UUID"""
|
||
|
||
LogisticsNode = env['logistics.node']
|
||
node = LogisticsNode.search([('uuid', '=', node_uuid)], limit=1)
|
||
|
||
if not node:
|
||
raise HTTPException(status_code=404, detail=f"Node with UUID {node_uuid} not found")
|
||
|
||
return LogisticsNodeResponse(
|
||
uuid=node.uuid,
|
||
name=node.name,
|
||
latitude=node.latitude,
|
||
longitude=node.longitude,
|
||
country=node.country_id.name if node.country_id else '',
|
||
country_code=node.country_id.code if node.country_id else '',
|
||
transport_types=get_node_transport_types(node)
|
||
)
|
||
|
||
|
||
@router.get("/routes")
|
||
def get_logistics_routes(env=Depends(odoo_env)) -> List[LogisticsRouteResponse]:
|
||
"""Get all active logistics routes"""
|
||
|
||
LogisticsRoute = env['logistics.route']
|
||
routes = LogisticsRoute.search([('active', '=', True)])
|
||
|
||
result = []
|
||
for route in routes:
|
||
result.append(LogisticsRouteResponse(
|
||
uuid=route.uuid if hasattr(route, 'uuid') else str(route.id),
|
||
name=route.name,
|
||
departure_node_uuid=route.departure_node_id.uuid,
|
||
departure_node_name=route.departure_node_id.name,
|
||
arrival_node_uuid=route.arrival_node_id.uuid,
|
||
arrival_node_name=route.arrival_node_id.name,
|
||
transport_type=route.transport_type,
|
||
distance_km=route.distance_km,
|
||
travel_time_hours=route.travel_time_hours,
|
||
active=route.active
|
||
))
|
||
|
||
return result
|
||
|
||
|
||
@router.get("/routes/{route_uuid}")
|
||
def get_logistics_route(route_uuid: str, env=Depends(odoo_env)) -> LogisticsRouteResponse:
|
||
"""Get logistics route by UUID"""
|
||
|
||
LogisticsRoute = env['logistics.route']
|
||
|
||
# Try to find by uuid field first, fallback to id
|
||
route = LogisticsRoute.search([('uuid', '=', route_uuid)], limit=1)
|
||
if not route:
|
||
try:
|
||
route = LogisticsRoute.browse(int(route_uuid))
|
||
if not route.exists():
|
||
route = None
|
||
except (ValueError, TypeError):
|
||
route = None
|
||
|
||
if not route:
|
||
raise HTTPException(status_code=404, detail=f"Route with UUID {route_uuid} not found")
|
||
|
||
return LogisticsRouteResponse(
|
||
uuid=route.uuid if hasattr(route, 'uuid') else str(route.id),
|
||
name=route.name,
|
||
departure_node_uuid=route.departure_node_id.uuid,
|
||
departure_node_name=route.departure_node_id.name,
|
||
arrival_node_uuid=route.arrival_node_id.uuid,
|
||
arrival_node_name=route.arrival_node_id.name,
|
||
transport_type=route.transport_type,
|
||
distance_km=route.distance_km,
|
||
travel_time_hours=route.travel_time_hours,
|
||
active=route.active
|
||
)
|
||
|
||
|
||
@router.get("/schedules")
|
||
def get_logistics_schedules(env=Depends(odoo_env)) -> List[LogisticsScheduleResponse]:
|
||
"""Get all active logistics schedules"""
|
||
|
||
LogisticsSchedule = env['logistics.schedule']
|
||
schedules = LogisticsSchedule.search([('active', '=', True)])
|
||
|
||
result = []
|
||
for schedule in schedules:
|
||
result.append(LogisticsScheduleResponse(
|
||
uuid=schedule.uuid if hasattr(schedule, 'uuid') else str(schedule.id),
|
||
name=schedule.name,
|
||
departure_node_uuid=schedule.departure_node_id.uuid,
|
||
departure_node_name=schedule.departure_node_id.name,
|
||
arrival_node_uuid=schedule.arrival_node_id.uuid,
|
||
arrival_node_name=schedule.arrival_node_id.name,
|
||
route_uuid=schedule.route_id.uuid if schedule.route_id and hasattr(schedule.route_id, 'uuid') else None,
|
||
transport_type=schedule.route_id.transport_type if schedule.route_id else 'auto',
|
||
departure_time=schedule.departure_time,
|
||
frequency=schedule.frequency,
|
||
travel_time_hours=schedule.route_id.travel_time_hours if schedule.route_id else None,
|
||
active=schedule.active
|
||
))
|
||
|
||
return result
|
||
|
||
|
||
@router.get("/schedules/{schedule_uuid}")
|
||
def get_logistics_schedule(schedule_uuid: str, env=Depends(odoo_env)) -> LogisticsScheduleResponse:
|
||
"""Get logistics schedule by UUID"""
|
||
|
||
LogisticsSchedule = env['logistics.schedule']
|
||
|
||
# Try to find by uuid field first, fallback to id
|
||
schedule = LogisticsSchedule.search([('uuid', '=', schedule_uuid)], limit=1)
|
||
if not schedule:
|
||
try:
|
||
schedule = LogisticsSchedule.browse(int(schedule_uuid))
|
||
if not schedule.exists():
|
||
schedule = None
|
||
except (ValueError, TypeError):
|
||
schedule = None
|
||
|
||
if not schedule:
|
||
raise HTTPException(status_code=404, detail=f"Schedule with UUID {schedule_uuid} not found")
|
||
|
||
return LogisticsScheduleResponse(
|
||
uuid=schedule.uuid if hasattr(schedule, 'uuid') else str(schedule.id),
|
||
name=schedule.name,
|
||
departure_node_uuid=schedule.departure_node_id.uuid,
|
||
departure_node_name=schedule.departure_node_id.name,
|
||
arrival_node_uuid=schedule.arrival_node_id.uuid,
|
||
arrival_node_name=schedule.arrival_node_id.name,
|
||
route_uuid=schedule.route_id.uuid if schedule.route_id and hasattr(schedule.route_id, 'uuid') else None,
|
||
transport_type=schedule.route_id.transport_type if schedule.route_id else 'auto',
|
||
departure_time=schedule.departure_time,
|
||
frequency=schedule.frequency,
|
||
travel_time_hours=schedule.route_id.travel_time_hours if schedule.route_id else None,
|
||
active=schedule.active
|
||
)
|