Improve the production frontend

This commit is contained in:
Urtzi Alfaro
2025-09-21 07:45:19 +02:00
parent 5e941f5f03
commit 13ca3e90b4
21 changed files with 1416 additions and 1357 deletions

View File

@@ -46,15 +46,20 @@ async def get_dashboard_summary(
):
"""Get production dashboard summary using shared auth"""
try:
# Extract tenant from user context for security
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
summary = await production_service.get_dashboard_summary(tenant_id)
logger.info("Retrieved production dashboard summary",
logger.info("Retrieved production dashboard summary",
tenant_id=str(tenant_id), user_id=current_user.get("user_id"))
return summary
except Exception as e:
logger.error("Error getting production dashboard summary",
logger.error("Error getting production dashboard summary",
error=str(e), tenant_id=str(tenant_id))
raise HTTPException(status_code=500, detail="Failed to get dashboard summary")
@@ -68,6 +73,7 @@ async def get_daily_requirements(
):
"""Get daily production requirements"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
@@ -94,6 +100,7 @@ async def get_production_requirements(
):
"""Get production requirements for procurement planning"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
@@ -124,6 +131,7 @@ async def create_production_batch(
):
"""Create a new production batch"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
@@ -151,6 +159,7 @@ async def get_active_batches(
):
"""Get currently active production batches"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
@@ -185,6 +194,7 @@ async def get_batch_details(
):
"""Get detailed information about a production batch"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
@@ -218,6 +228,7 @@ async def update_batch_status(
):
"""Update production batch status"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
@@ -253,6 +264,7 @@ async def get_production_schedule(
):
"""Get production schedule for a date range"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
@@ -316,6 +328,7 @@ async def get_capacity_status(
):
"""Get production capacity status for a specific date"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")
@@ -353,6 +366,7 @@ async def get_yield_metrics(
):
"""Get production yield metrics for analysis"""
try:
current_tenant = current_user.get("tenant_id")
if str(tenant_id) != current_tenant:
raise HTTPException(status_code=403, detail="Access denied to this tenant")

View File

@@ -18,21 +18,21 @@ from shared.database.base import Base
class ProductionStatus(str, enum.Enum):
"""Production batch status enumeration"""
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
CANCELLED = "cancelled"
ON_HOLD = "on_hold"
QUALITY_CHECK = "quality_check"
FAILED = "failed"
PENDING = "PENDING"
IN_PROGRESS = "IN_PROGRESS"
COMPLETED = "COMPLETED"
CANCELLED = "CANCELLED"
ON_HOLD = "ON_HOLD"
QUALITY_CHECK = "QUALITY_CHECK"
FAILED = "FAILED"
class ProductionPriority(str, enum.Enum):
"""Production priority levels"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
URGENT = "urgent"
LOW = "LOW"
MEDIUM = "MEDIUM"
HIGH = "HIGH"
URGENT = "URGENT"

View File

@@ -23,7 +23,6 @@ class ProductionBaseRepository(BaseRepository):
# Production data is more dynamic, shorter cache time (5 minutes)
super().__init__(model, session, cache_ttl)
@transactional
async def get_by_tenant_id(self, tenant_id: str, skip: int = 0, limit: int = 100) -> List:
"""Get records by tenant ID"""
if hasattr(self.model, 'tenant_id'):
@@ -36,7 +35,6 @@ class ProductionBaseRepository(BaseRepository):
)
return await self.get_multi(skip=skip, limit=limit)
@transactional
async def get_by_status(
self,
tenant_id: str,

View File

@@ -26,7 +26,6 @@ class ProductionBatchRepository(ProductionBaseRepository, BatchCountProvider):
# Production batches are dynamic, short cache time (5 minutes)
super().__init__(ProductionBatch, session, cache_ttl)
@transactional
async def create_batch(self, batch_data: Dict[str, Any]) -> ProductionBatch:
"""Create a new production batch with validation"""
try:
@@ -84,7 +83,6 @@ class ProductionBatchRepository(ProductionBaseRepository, BatchCountProvider):
logger.error("Error creating production batch", error=str(e))
raise DatabaseError(f"Failed to create production batch: {str(e)}")
@transactional
async def get_active_batches(self, tenant_id: str) -> List[ProductionBatch]:
"""Get active production batches for a tenant"""
try:
@@ -113,7 +111,6 @@ class ProductionBatchRepository(ProductionBaseRepository, BatchCountProvider):
logger.error("Error fetching active batches", error=str(e))
raise DatabaseError(f"Failed to fetch active batches: {str(e)}")
@transactional
async def get_batches_by_date_range(
self,
tenant_id: str,
@@ -152,7 +149,6 @@ class ProductionBatchRepository(ProductionBaseRepository, BatchCountProvider):
logger.error("Error fetching batches by date range", error=str(e))
raise DatabaseError(f"Failed to fetch batches by date range: {str(e)}")
@transactional
async def get_batches_by_product(
self,
tenant_id: str,
@@ -182,7 +178,6 @@ class ProductionBatchRepository(ProductionBaseRepository, BatchCountProvider):
logger.error("Error fetching batches by product", error=str(e))
raise DatabaseError(f"Failed to fetch batches by product: {str(e)}")
@transactional
async def update_batch_status(
self,
batch_id: UUID,
@@ -240,7 +235,6 @@ class ProductionBatchRepository(ProductionBaseRepository, BatchCountProvider):
logger.error("Error updating batch status", error=str(e))
raise DatabaseError(f"Failed to update batch status: {str(e)}")
@transactional
async def get_production_metrics(
self,
tenant_id: str,
@@ -297,7 +291,6 @@ class ProductionBatchRepository(ProductionBaseRepository, BatchCountProvider):
logger.error("Error calculating production metrics", error=str(e))
raise DatabaseError(f"Failed to calculate production metrics: {str(e)}")
@transactional
async def get_urgent_batches(self, tenant_id: str, hours_ahead: int = 4) -> List[ProductionBatch]:
"""Get batches that need to start within the specified hours"""
try:

View File

@@ -14,21 +14,21 @@ from enum import Enum
class ProductionStatusEnum(str, Enum):
"""Production batch status enumeration for API"""
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
CANCELLED = "cancelled"
ON_HOLD = "on_hold"
QUALITY_CHECK = "quality_check"
FAILED = "failed"
PENDING = "PENDING"
IN_PROGRESS = "IN_PROGRESS"
COMPLETED = "COMPLETED"
CANCELLED = "CANCELLED"
ON_HOLD = "ON_HOLD"
QUALITY_CHECK = "QUALITY_CHECK"
FAILED = "FAILED"
class ProductionPriorityEnum(str, Enum):
"""Production priority levels for API"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
URGENT = "urgent"
LOW = "LOW"
MEDIUM = "MEDIUM"
HIGH = "HIGH"
URGENT = "URGENT"

View File

@@ -49,14 +49,14 @@ class ProductionAlertService(BaseAlertService, AlertServiceMixin):
max_instances=1
)
# Equipment monitoring - every 3 minutes (alerts)
self.scheduler.add_job(
self.check_equipment_status,
CronTrigger(minute='*/3'),
id='equipment_check',
misfire_grace_time=30,
max_instances=1
)
# Equipment monitoring - disabled (equipment tables not available in production database)
# self.scheduler.add_job(
# self.check_equipment_status,
# CronTrigger(minute='*/3'),
# id='equipment_check',
# misfire_grace_time=30,
# max_instances=1
# )
# Efficiency recommendations - every 30 minutes (recommendations)
self.scheduler.add_job(
@@ -127,7 +127,7 @@ class ProductionAlertService(BaseAlertService, AlertServiceMixin):
FROM production_batches pb
WHERE pb.planned_start_time >= CURRENT_DATE
AND pb.planned_start_time <= CURRENT_DATE + INTERVAL '3 days'
AND pb.status IN ('planned', 'pending', 'in_progress')
AND pb.status IN ('PLANNED', 'PENDING', 'IN_PROGRESS')
GROUP BY pb.tenant_id, DATE(pb.planned_start_time)
HAVING COUNT(*) > 10 -- Alert if more than 10 batches per day
ORDER BY total_planned DESC
@@ -226,15 +226,15 @@ class ProductionAlertService(BaseAlertService, AlertServiceMixin):
COALESCE(pb.priority::text, 'medium') as priority_level,
1 as affected_orders -- Default to 1 since we can't count orders
FROM production_batches pb
WHERE pb.status IN ('in_progress', 'delayed')
WHERE pb.status IN ('IN_PROGRESS', 'DELAYED')
AND (
(pb.planned_end_time < NOW() AND pb.status = 'in_progress')
OR pb.status = 'delayed'
(pb.planned_end_time < NOW() AND pb.status = 'IN_PROGRESS')
OR pb.status = 'DELAYED'
)
AND pb.planned_end_time > NOW() - INTERVAL '24 hours'
ORDER BY
CASE COALESCE(pb.priority::text, 'medium')
WHEN 'urgent' THEN 1 WHEN 'high' THEN 2 ELSE 3
CASE COALESCE(pb.priority::text, 'MEDIUM')
WHEN 'URGENT' THEN 1 WHEN 'HIGH' THEN 2 ELSE 3
END,
delay_minutes DESC
"""
@@ -481,7 +481,7 @@ class ProductionAlertService(BaseAlertService, AlertServiceMixin):
AVG(pb.yield_percentage) as avg_yield,
EXTRACT(hour FROM pb.actual_start_time) as start_hour
FROM production_batches pb
WHERE pb.status = 'completed'
WHERE pb.status = 'COMPLETED'
AND pb.actual_completion_time > CURRENT_DATE - INTERVAL '30 days'
AND pb.tenant_id = $1
GROUP BY pb.tenant_id, pb.product_name, EXTRACT(hour FROM pb.actual_start_time)

View File

@@ -78,7 +78,6 @@ class ProductionService:
error=str(e), tenant_id=str(tenant_id), date=target_date.isoformat())
raise
@transactional
async def create_production_batch(
self,
tenant_id: UUID,
@@ -129,7 +128,6 @@ class ProductionService:
error=str(e), tenant_id=str(tenant_id))
raise
@transactional
async def update_batch_status(
self,
tenant_id: UUID,
@@ -167,7 +165,6 @@ class ProductionService:
error=str(e), batch_id=str(batch_id), tenant_id=str(tenant_id))
raise
@transactional
async def get_dashboard_summary(self, tenant_id: UUID) -> ProductionDashboardSummary:
"""Get production dashboard summary data"""
try:
@@ -215,7 +212,6 @@ class ProductionService:
error=str(e), tenant_id=str(tenant_id))
raise
@transactional
async def get_production_requirements(
self,
tenant_id: UUID,