# ================================================================ # services/orders/app/repositories/procurement_repository.py # ================================================================ """ Procurement Repository - Database operations for procurement plans and requirements """ import uuid from datetime import datetime, date from decimal import Decimal from typing import List, Optional, Dict, Any from sqlalchemy import select, and_, or_, desc, func from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.models.procurement import ProcurementPlan, ProcurementRequirement from app.repositories.base_repository import BaseRepository class ProcurementPlanRepository(BaseRepository): """Repository for procurement plan operations""" def __init__(self, db: AsyncSession): super().__init__(ProcurementPlan) self.db = db async def create_plan(self, plan_data: Dict[str, Any]) -> ProcurementPlan: """Create a new procurement plan""" plan = ProcurementPlan(**plan_data) self.db.add(plan) await self.db.flush() return plan async def get_plan_by_id(self, plan_id: uuid.UUID, tenant_id: uuid.UUID) -> Optional[ProcurementPlan]: """Get procurement plan by ID""" stmt = select(ProcurementPlan).where( and_( ProcurementPlan.id == plan_id, ProcurementPlan.tenant_id == tenant_id ) ).options(selectinload(ProcurementPlan.requirements)) result = await self.db.execute(stmt) return result.scalar_one_or_none() async def get_plan_by_date(self, plan_date: date, tenant_id: uuid.UUID) -> Optional[ProcurementPlan]: """Get procurement plan for a specific date""" stmt = select(ProcurementPlan).where( and_( ProcurementPlan.plan_date == plan_date, ProcurementPlan.tenant_id == tenant_id ) ).options(selectinload(ProcurementPlan.requirements)) result = await self.db.execute(stmt) return result.scalar_one_or_none() async def get_current_plan(self, tenant_id: uuid.UUID) -> Optional[ProcurementPlan]: """Get the current day's procurement plan""" today = date.today() return await self.get_plan_by_date(today, tenant_id) async def list_plans( self, tenant_id: uuid.UUID, status: Optional[str] = None, start_date: Optional[date] = None, end_date: Optional[date] = None, limit: int = 50, offset: int = 0 ) -> List[ProcurementPlan]: """List procurement plans with filters""" conditions = [ProcurementPlan.tenant_id == tenant_id] if status: conditions.append(ProcurementPlan.status == status) if start_date: conditions.append(ProcurementPlan.plan_date >= start_date) if end_date: conditions.append(ProcurementPlan.plan_date <= end_date) stmt = ( select(ProcurementPlan) .where(and_(*conditions)) .order_by(desc(ProcurementPlan.plan_date)) .limit(limit) .offset(offset) .options(selectinload(ProcurementPlan.requirements)) ) result = await self.db.execute(stmt) return result.scalars().all() async def update_plan(self, plan_id: uuid.UUID, tenant_id: uuid.UUID, updates: Dict[str, Any]) -> Optional[ProcurementPlan]: """Update procurement plan""" plan = await self.get_plan_by_id(plan_id, tenant_id) if not plan: return None for key, value in updates.items(): if hasattr(plan, key): setattr(plan, key, value) plan.updated_at = datetime.utcnow() await self.db.flush() return plan async def delete_plan(self, plan_id: uuid.UUID, tenant_id: uuid.UUID) -> bool: """Delete procurement plan""" plan = await self.get_plan_by_id(plan_id, tenant_id) if not plan: return False await self.db.delete(plan) return True async def generate_plan_number(self, tenant_id: uuid.UUID, plan_date: date) -> str: """Generate unique plan number""" date_str = plan_date.strftime("%Y%m%d") # Count existing plans for the same date stmt = select(func.count(ProcurementPlan.id)).where( and_( ProcurementPlan.tenant_id == tenant_id, ProcurementPlan.plan_date == plan_date ) ) result = await self.db.execute(stmt) count = result.scalar() or 0 return f"PP-{date_str}-{count + 1:03d}" async def archive_plan(self, plan_id: uuid.UUID, tenant_id: uuid.UUID) -> bool: """Archive a completed plan""" plan = await self.get_plan_by_id(plan_id, tenant_id) if not plan: return False # Add archived flag to metadata if you have a JSONB field # or just mark as archived in status if hasattr(plan, 'metadata'): metadata = plan.metadata or {} metadata['archived'] = True metadata['archived_at'] = datetime.utcnow().isoformat() plan.metadata = metadata plan.status = 'archived' plan.updated_at = datetime.utcnow() await self.db.flush() return True class ProcurementRequirementRepository(BaseRepository): """Repository for procurement requirement operations""" def __init__(self, db: AsyncSession): super().__init__(ProcurementRequirement) self.db = db async def create_requirement(self, requirement_data: Dict[str, Any]) -> ProcurementRequirement: """Create a new procurement requirement""" requirement = ProcurementRequirement(**requirement_data) self.db.add(requirement) await self.db.flush() return requirement async def create_requirements_batch(self, requirements_data: List[Dict[str, Any]]) -> List[ProcurementRequirement]: """Create multiple procurement requirements""" requirements = [ProcurementRequirement(**data) for data in requirements_data] self.db.add_all(requirements) await self.db.flush() return requirements async def get_requirement_by_id(self, requirement_id: uuid.UUID, tenant_id: uuid.UUID) -> Optional[ProcurementRequirement]: """Get procurement requirement by ID""" stmt = select(ProcurementRequirement).join(ProcurementPlan).where( and_( ProcurementRequirement.id == requirement_id, ProcurementPlan.tenant_id == tenant_id ) ) result = await self.db.execute(stmt) return result.scalar_one_or_none() async def get_requirements_by_plan(self, plan_id: uuid.UUID) -> List[ProcurementRequirement]: """Get all requirements for a specific plan""" stmt = select(ProcurementRequirement).where( ProcurementRequirement.plan_id == plan_id ).order_by(ProcurementRequirement.priority.desc(), ProcurementRequirement.required_by_date) result = await self.db.execute(stmt) return result.scalars().all() async def get_requirements_by_product( self, tenant_id: uuid.UUID, product_id: uuid.UUID, status: Optional[str] = None ) -> List[ProcurementRequirement]: """Get requirements for a specific product""" conditions = [ ProcurementPlan.tenant_id == tenant_id, ProcurementRequirement.product_id == product_id ] if status: conditions.append(ProcurementRequirement.status == status) stmt = select(ProcurementRequirement).join(ProcurementPlan).where( and_(*conditions) ).order_by(desc(ProcurementRequirement.required_by_date)) result = await self.db.execute(stmt) return result.scalars().all() async def update_requirement( self, requirement_id: uuid.UUID, updates: Dict[str, Any] ) -> Optional[ProcurementRequirement]: """Update procurement requirement (without tenant_id check for internal use)""" stmt = select(ProcurementRequirement).where( ProcurementRequirement.id == requirement_id ) result = await self.db.execute(stmt) requirement = result.scalar_one_or_none() if not requirement: return None for key, value in updates.items(): if hasattr(requirement, key): setattr(requirement, key, value) requirement.updated_at = datetime.utcnow() await self.db.flush() return requirement async def get_by_id(self, requirement_id: uuid.UUID) -> Optional[ProcurementRequirement]: """Get requirement by ID with plan preloaded""" stmt = select(ProcurementRequirement).where( ProcurementRequirement.id == requirement_id ).options(selectinload(ProcurementRequirement.plan)) result = await self.db.execute(stmt) return result.scalar_one_or_none() async def get_pending_requirements(self, tenant_id: uuid.UUID) -> List[ProcurementRequirement]: """Get all pending requirements across plans""" stmt = select(ProcurementRequirement).join(ProcurementPlan).where( and_( ProcurementPlan.tenant_id == tenant_id, ProcurementRequirement.status == 'pending' ) ).order_by(ProcurementRequirement.priority.desc(), ProcurementRequirement.required_by_date) result = await self.db.execute(stmt) return result.scalars().all() async def get_critical_requirements(self, tenant_id: uuid.UUID) -> List[ProcurementRequirement]: """Get critical priority requirements""" stmt = select(ProcurementRequirement).join(ProcurementPlan).where( and_( ProcurementPlan.tenant_id == tenant_id, ProcurementRequirement.priority == 'critical', ProcurementRequirement.status.in_(['pending', 'approved']) ) ).order_by(ProcurementRequirement.required_by_date) result = await self.db.execute(stmt) return result.scalars().all() async def generate_requirement_number(self, plan_id: uuid.UUID) -> str: """Generate unique requirement number within a plan""" # Count existing requirements in the plan stmt = select(func.count(ProcurementRequirement.id)).where( ProcurementRequirement.plan_id == plan_id ) result = await self.db.execute(stmt) count = result.scalar() or 0 return f"REQ-{count + 1:05d}"