Add supplier and imporve inventory frontend

This commit is contained in:
Urtzi Alfaro
2025-09-18 23:32:53 +02:00
parent ae77a0e1c5
commit d61056df33
40 changed files with 2022 additions and 629 deletions

View File

@@ -4,8 +4,8 @@ Supplier repository for database operations
"""
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import and_, or_, func, select
from uuid import UUID
from datetime import datetime
@@ -16,36 +16,32 @@ from app.repositories.base import BaseRepository
class SupplierRepository(BaseRepository[Supplier]):
"""Repository for supplier management operations"""
def __init__(self, db: Session):
def __init__(self, db: AsyncSession):
super().__init__(Supplier, db)
def get_by_name(self, tenant_id: UUID, name: str) -> Optional[Supplier]:
async def get_by_name(self, tenant_id: UUID, name: str) -> Optional[Supplier]:
"""Get supplier by name within tenant"""
return (
self.db.query(self.model)
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.name == name
)
stmt = select(self.model).filter(
and_(
self.model.tenant_id == tenant_id,
self.model.name == name
)
.first()
)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
def get_by_supplier_code(self, tenant_id: UUID, supplier_code: str) -> Optional[Supplier]:
async def get_by_supplier_code(self, tenant_id: UUID, supplier_code: str) -> Optional[Supplier]:
"""Get supplier by supplier code within tenant"""
return (
self.db.query(self.model)
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.supplier_code == supplier_code
)
stmt = select(self.model).filter(
and_(
self.model.tenant_id == tenant_id,
self.model.supplier_code == supplier_code
)
.first()
)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
def search_suppliers(
async def search_suppliers(
self,
tenant_id: UUID,
search_term: Optional[str] = None,
@@ -55,8 +51,8 @@ class SupplierRepository(BaseRepository[Supplier]):
offset: int = 0
) -> List[Supplier]:
"""Search suppliers with filters"""
query = self.db.query(self.model).filter(self.model.tenant_id == tenant_id)
stmt = select(self.model).filter(self.model.tenant_id == tenant_id)
# Search term filter (name, contact person, email)
if search_term:
search_filter = or_(
@@ -64,31 +60,30 @@ class SupplierRepository(BaseRepository[Supplier]):
self.model.contact_person.ilike(f"%{search_term}%"),
self.model.email.ilike(f"%{search_term}%")
)
query = query.filter(search_filter)
stmt = stmt.filter(search_filter)
# Type filter
if supplier_type:
query = query.filter(self.model.supplier_type == supplier_type)
stmt = stmt.filter(self.model.supplier_type == supplier_type)
# Status filter
if status:
query = query.filter(self.model.status == status)
return query.order_by(self.model.name).limit(limit).offset(offset).all()
stmt = stmt.filter(self.model.status == status)
stmt = stmt.order_by(self.model.name).limit(limit).offset(offset)
result = await self.db.execute(stmt)
return result.scalars().all()
def get_active_suppliers(self, tenant_id: UUID) -> List[Supplier]:
async def get_active_suppliers(self, tenant_id: UUID) -> List[Supplier]:
"""Get all active suppliers for a tenant"""
return (
self.db.query(self.model)
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.ACTIVE
)
stmt = select(self.model).filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.active
)
.order_by(self.model.name)
.all()
)
).order_by(self.model.name)
result = await self.db.execute(stmt)
return result.scalars().all()
def get_suppliers_by_type(
self,
@@ -102,7 +97,7 @@ class SupplierRepository(BaseRepository[Supplier]):
and_(
self.model.tenant_id == tenant_id,
self.model.supplier_type == supplier_type,
self.model.status == SupplierStatus.ACTIVE
self.model.status == SupplierStatus.active
)
)
.order_by(self.model.quality_rating.desc(), self.model.name)
@@ -120,7 +115,7 @@ class SupplierRepository(BaseRepository[Supplier]):
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.ACTIVE
self.model.status == SupplierStatus.active
)
)
.order_by(
@@ -178,7 +173,7 @@ class SupplierRepository(BaseRepository[Supplier]):
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.ACTIVE,
self.model.status == SupplierStatus.active,
or_(
self.model.quality_rating < 3.0, # Poor rating
self.model.delivery_rating < 3.0, # Poor delivery
@@ -190,66 +185,33 @@ class SupplierRepository(BaseRepository[Supplier]):
.all()
)
def get_supplier_statistics(self, tenant_id: UUID) -> Dict[str, Any]:
async def get_supplier_statistics(self, tenant_id: UUID) -> Dict[str, Any]:
"""Get supplier statistics for dashboard"""
total_suppliers = self.count_by_tenant(tenant_id)
active_suppliers = (
self.db.query(self.model)
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.ACTIVE
)
)
.count()
)
pending_suppliers = (
self.db.query(self.model)
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.PENDING_APPROVAL
)
)
.count()
)
avg_quality_rating = (
self.db.query(func.avg(self.model.quality_rating))
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.ACTIVE,
self.model.quality_rating > 0
)
)
.scalar()
) or 0.0
avg_delivery_rating = (
self.db.query(func.avg(self.model.delivery_rating))
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.ACTIVE,
self.model.delivery_rating > 0
)
)
.scalar()
) or 0.0
total_spend = (
self.db.query(func.sum(self.model.total_amount))
.filter(self.model.tenant_id == tenant_id)
.scalar()
) or 0.0
total_suppliers = await self.count_by_tenant(tenant_id)
# Get all suppliers for this tenant to avoid multiple queries and enum casting issues
all_stmt = select(self.model).filter(self.model.tenant_id == tenant_id)
all_result = await self.db.execute(all_stmt)
all_suppliers = all_result.scalars().all()
# Calculate statistics in Python to avoid database enum casting issues
active_suppliers = [s for s in all_suppliers if s.status == SupplierStatus.active]
pending_suppliers = [s for s in all_suppliers if s.status == SupplierStatus.pending_approval]
# Calculate averages from active suppliers
quality_ratings = [s.quality_rating for s in active_suppliers if s.quality_rating and s.quality_rating > 0]
avg_quality_rating = sum(quality_ratings) / len(quality_ratings) if quality_ratings else 0.0
delivery_ratings = [s.delivery_rating for s in active_suppliers if s.delivery_rating and s.delivery_rating > 0]
avg_delivery_rating = sum(delivery_ratings) / len(delivery_ratings) if delivery_ratings else 0.0
# Total spend for all suppliers
total_spend = sum(float(s.total_amount or 0) for s in all_suppliers)
return {
"total_suppliers": total_suppliers,
"active_suppliers": active_suppliers,
"pending_suppliers": pending_suppliers,
"active_suppliers": len(active_suppliers),
"pending_suppliers": len(pending_suppliers),
"avg_quality_rating": round(float(avg_quality_rating), 2),
"avg_delivery_rating": round(float(avg_delivery_rating), 2),
"total_spend": float(total_spend)