# ================================================================ # services/procurement/app/repositories/procurement_plan_repository.py # ================================================================ """ Procurement Plan Repository - Database operations for procurement plans and requirements """ import uuid from datetime import datetime, date from typing import List, Optional, Dict, Any from sqlalchemy import select, and_, desc, func from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.models.procurement_plan 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 get_plans_by_tenant( self, tenant_id: uuid.UUID, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None ) -> List[ProcurementPlan]: """Get all procurement plans for a tenant with optional date filters""" conditions = [ProcurementPlan.tenant_id == tenant_id] if start_date: conditions.append(ProcurementPlan.created_at >= start_date) if end_date: conditions.append(ProcurementPlan.created_at <= end_date) stmt = ( select(ProcurementPlan) .where(and_(*conditions)) .order_by(desc(ProcurementPlan.created_at)) .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}" 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 update_requirement( self, requirement_id: uuid.UUID, updates: Dict[str, Any] ) -> Optional[ProcurementRequirement]: """Update procurement requirement""" 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 generate_requirement_number(self, plan_id: uuid.UUID) -> str: """Generate unique requirement number within a 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}" async def get_requirements_by_tenant( self, tenant_id: uuid.UUID, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None ) -> List[ProcurementRequirement]: """Get all procurement requirements for a tenant with optional date filters""" conditions = [ProcurementPlan.tenant_id == tenant_id] if start_date: conditions.append(ProcurementRequirement.created_at >= start_date) if end_date: conditions.append(ProcurementRequirement.created_at <= end_date) stmt = ( select(ProcurementRequirement) .join(ProcurementPlan) .where(and_(*conditions)) .order_by(desc(ProcurementRequirement.created_at)) ) result = await self.db.execute(stmt) return result.scalars().all()