283 lines
11 KiB
Python
283 lines
11 KiB
Python
# ================================================================
|
|
# 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}" |