Improve the frontend 3

This commit is contained in:
Urtzi Alfaro
2025-10-30 21:08:07 +01:00
parent 36217a2729
commit 63f5c6d512
184 changed files with 21512 additions and 7442 deletions

View File

@@ -0,0 +1,62 @@
# ================================================================
# services/procurement/app/repositories/base_repository.py
# ================================================================
"""
Base Repository Pattern for Procurement Service
"""
from typing import Generic, TypeVar, Type, Optional, List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from shared.database.base import Base
ModelType = TypeVar("ModelType", bound=Base)
class BaseRepository(Generic[ModelType]):
"""Base repository with common database operations"""
def __init__(self, model: Type[ModelType]):
self.model = model
async def get_by_id(self, db: AsyncSession, id: Any) -> Optional[ModelType]:
"""Get entity by ID"""
result = await db.execute(select(self.model).where(self.model.id == id))
return result.scalar_one_or_none()
async def get_all(self, db: AsyncSession, skip: int = 0, limit: int = 100) -> List[ModelType]:
"""Get all entities with pagination"""
result = await db.execute(select(self.model).offset(skip).limit(limit))
return result.scalars().all()
async def create(self, db: AsyncSession, **kwargs) -> ModelType:
"""Create new entity"""
instance = self.model(**kwargs)
db.add(instance)
await db.flush()
await db.refresh(instance)
return instance
async def update(self, db: AsyncSession, id: Any, **kwargs) -> Optional[ModelType]:
"""Update entity"""
instance = await self.get_by_id(db, id)
if not instance:
return None
for key, value in kwargs.items():
if hasattr(instance, key):
setattr(instance, key, value)
await db.flush()
await db.refresh(instance)
return instance
async def delete(self, db: AsyncSession, id: Any) -> bool:
"""Delete entity"""
instance = await self.get_by_id(db, id)
if not instance:
return False
await db.delete(instance)
await db.flush()
return True

View File

@@ -0,0 +1,206 @@
# ================================================================
# 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 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}"

View File

@@ -0,0 +1,318 @@
# ================================================================
# services/procurement/app/repositories/purchase_order_repository.py
# ================================================================
"""
Purchase Order Repository - Database operations for purchase orders
Migrated from Suppliers Service
"""
import uuid
from datetime import datetime, date
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.purchase_order import (
PurchaseOrder,
PurchaseOrderItem,
PurchaseOrderStatus,
Delivery,
DeliveryStatus,
SupplierInvoice,
)
from app.repositories.base_repository import BaseRepository
class PurchaseOrderRepository(BaseRepository):
"""Repository for purchase order operations"""
def __init__(self, db: AsyncSession):
super().__init__(PurchaseOrder)
self.db = db
async def create_po(self, po_data: Dict[str, Any]) -> PurchaseOrder:
"""Create a new purchase order"""
po = PurchaseOrder(**po_data)
self.db.add(po)
await self.db.flush()
return po
async def get_po_by_id(self, po_id: uuid.UUID, tenant_id: uuid.UUID) -> Optional[PurchaseOrder]:
"""Get purchase order by ID with items loaded"""
stmt = select(PurchaseOrder).where(
and_(
PurchaseOrder.id == po_id,
PurchaseOrder.tenant_id == tenant_id
)
).options(
selectinload(PurchaseOrder.items),
selectinload(PurchaseOrder.deliveries),
selectinload(PurchaseOrder.invoices)
)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def get_po_by_number(self, po_number: str, tenant_id: uuid.UUID) -> Optional[PurchaseOrder]:
"""Get purchase order by PO number"""
stmt = select(PurchaseOrder).where(
and_(
PurchaseOrder.po_number == po_number,
PurchaseOrder.tenant_id == tenant_id
)
).options(selectinload(PurchaseOrder.items))
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def list_purchase_orders(
self,
tenant_id: uuid.UUID,
status: Optional[PurchaseOrderStatus] = None,
supplier_id: Optional[uuid.UUID] = None,
priority: Optional[str] = None,
start_date: Optional[date] = None,
end_date: Optional[date] = None,
limit: int = 50,
offset: int = 0
) -> List[PurchaseOrder]:
"""List purchase orders with filters"""
conditions = [PurchaseOrder.tenant_id == tenant_id]
if status:
conditions.append(PurchaseOrder.status == status)
if supplier_id:
conditions.append(PurchaseOrder.supplier_id == supplier_id)
if priority:
conditions.append(PurchaseOrder.priority == priority)
if start_date:
conditions.append(PurchaseOrder.order_date >= start_date)
if end_date:
conditions.append(PurchaseOrder.order_date <= end_date)
stmt = (
select(PurchaseOrder)
.where(and_(*conditions))
.order_by(desc(PurchaseOrder.order_date))
.limit(limit)
.offset(offset)
.options(selectinload(PurchaseOrder.items))
)
result = await self.db.execute(stmt)
return result.scalars().all()
async def get_pending_approval(self, tenant_id: uuid.UUID) -> List[PurchaseOrder]:
"""Get purchase orders pending approval"""
stmt = select(PurchaseOrder).where(
and_(
PurchaseOrder.tenant_id == tenant_id,
PurchaseOrder.status == PurchaseOrderStatus.pending_approval
)
).order_by(PurchaseOrder.total_amount.desc())
result = await self.db.execute(stmt)
return result.scalars().all()
async def update_po(self, po_id: uuid.UUID, tenant_id: uuid.UUID, updates: Dict[str, Any]) -> Optional[PurchaseOrder]:
"""Update purchase order"""
po = await self.get_po_by_id(po_id, tenant_id)
if not po:
return None
for key, value in updates.items():
if hasattr(po, key):
setattr(po, key, value)
po.updated_at = datetime.utcnow()
await self.db.flush()
return po
async def generate_po_number(self, tenant_id: uuid.UUID) -> str:
"""Generate unique PO number"""
today = date.today()
date_str = today.strftime("%Y%m%d")
# Count existing POs for today
stmt = select(func.count(PurchaseOrder.id)).where(
and_(
PurchaseOrder.tenant_id == tenant_id,
func.date(PurchaseOrder.order_date) == today
)
)
result = await self.db.execute(stmt)
count = result.scalar() or 0
return f"PO-{date_str}-{count + 1:04d}"
class PurchaseOrderItemRepository(BaseRepository):
"""Repository for purchase order item operations"""
def __init__(self, db: AsyncSession):
super().__init__(PurchaseOrderItem)
self.db = db
async def create_item(self, item_data: Dict[str, Any]) -> PurchaseOrderItem:
"""Create a purchase order item"""
item = PurchaseOrderItem(**item_data)
self.db.add(item)
await self.db.flush()
return item
async def create_items_batch(self, items_data: List[Dict[str, Any]]) -> List[PurchaseOrderItem]:
"""Create multiple purchase order items"""
items = [PurchaseOrderItem(**data) for data in items_data]
self.db.add_all(items)
await self.db.flush()
return items
async def get_items_by_po(self, po_id: uuid.UUID) -> List[PurchaseOrderItem]:
"""Get all items for a purchase order"""
stmt = select(PurchaseOrderItem).where(
PurchaseOrderItem.purchase_order_id == po_id
)
result = await self.db.execute(stmt)
return result.scalars().all()
class DeliveryRepository(BaseRepository):
"""Repository for delivery operations"""
def __init__(self, db: AsyncSession):
super().__init__(Delivery)
self.db = db
async def create_delivery(self, delivery_data: Dict[str, Any]) -> Delivery:
"""Create a new delivery"""
delivery = Delivery(**delivery_data)
self.db.add(delivery)
await self.db.flush()
return delivery
async def get_delivery_by_id(self, delivery_id: uuid.UUID, tenant_id: uuid.UUID) -> Optional[Delivery]:
"""Get delivery by ID with items loaded"""
stmt = select(Delivery).where(
and_(
Delivery.id == delivery_id,
Delivery.tenant_id == tenant_id
)
).options(selectinload(Delivery.items))
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def get_deliveries_by_po(self, po_id: uuid.UUID) -> List[Delivery]:
"""Get all deliveries for a purchase order"""
stmt = select(Delivery).where(
Delivery.purchase_order_id == po_id
).options(selectinload(Delivery.items))
result = await self.db.execute(stmt)
return result.scalars().all()
async def create_delivery_item(self, item_data: Dict[str, Any]):
"""Create a delivery item"""
from app.models.purchase_order import DeliveryItem
item = DeliveryItem(**item_data)
self.db.add(item)
await self.db.flush()
return item
async def update_delivery(
self,
delivery_id: uuid.UUID,
tenant_id: uuid.UUID,
updates: Dict[str, Any]
) -> Optional[Delivery]:
"""Update delivery"""
delivery = await self.get_delivery_by_id(delivery_id, tenant_id)
if not delivery:
return None
for key, value in updates.items():
if hasattr(delivery, key):
setattr(delivery, key, value)
delivery.updated_at = datetime.utcnow()
await self.db.flush()
return delivery
async def generate_delivery_number(self, tenant_id: uuid.UUID) -> str:
"""Generate unique delivery number"""
today = date.today()
date_str = today.strftime("%Y%m%d")
stmt = select(func.count(Delivery.id)).where(
and_(
Delivery.tenant_id == tenant_id,
func.date(Delivery.created_at) == today
)
)
result = await self.db.execute(stmt)
count = result.scalar() or 0
return f"DEL-{date_str}-{count + 1:04d}"
class SupplierInvoiceRepository(BaseRepository):
"""Repository for supplier invoice operations"""
def __init__(self, db: AsyncSession):
super().__init__(SupplierInvoice)
self.db = db
async def create_invoice(self, invoice_data: Dict[str, Any]) -> SupplierInvoice:
"""Create a new supplier invoice"""
invoice = SupplierInvoice(**invoice_data)
self.db.add(invoice)
await self.db.flush()
return invoice
async def get_invoice_by_id(self, invoice_id: uuid.UUID, tenant_id: uuid.UUID) -> Optional[SupplierInvoice]:
"""Get invoice by ID"""
stmt = select(SupplierInvoice).where(
and_(
SupplierInvoice.id == invoice_id,
SupplierInvoice.tenant_id == tenant_id
)
)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def get_invoices_by_po(self, po_id: uuid.UUID) -> List[SupplierInvoice]:
"""Get all invoices for a purchase order"""
stmt = select(SupplierInvoice).where(
SupplierInvoice.purchase_order_id == po_id
)
result = await self.db.execute(stmt)
return result.scalars().all()
async def get_invoices_by_supplier(self, supplier_id: uuid.UUID, tenant_id: uuid.UUID) -> List[SupplierInvoice]:
"""Get all invoices for a supplier"""
stmt = select(SupplierInvoice).where(
and_(
SupplierInvoice.supplier_id == supplier_id,
SupplierInvoice.tenant_id == tenant_id
)
).order_by(SupplierInvoice.invoice_date.desc())
result = await self.db.execute(stmt)
return result.scalars().all()
async def generate_invoice_number(self, tenant_id: uuid.UUID) -> str:
"""Generate unique invoice number"""
today = date.today()
date_str = today.strftime("%Y%m%d")
stmt = select(func.count(SupplierInvoice.id)).where(
and_(
SupplierInvoice.tenant_id == tenant_id,
func.date(SupplierInvoice.created_at) == today
)
)
result = await self.db.execute(stmt)
count = result.scalar() or 0
return f"INV-{date_str}-{count + 1:04d}"