Add supplier and imporve inventory frontend
This commit is contained in:
@@ -4,8 +4,8 @@ Base repository class for common database operations
|
||||
"""
|
||||
|
||||
from typing import TypeVar, Generic, List, Optional, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import desc, asc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import desc, asc, select, func
|
||||
from uuid import UUID
|
||||
|
||||
T = TypeVar('T')
|
||||
@@ -14,55 +14,59 @@ T = TypeVar('T')
|
||||
class BaseRepository(Generic[T]):
|
||||
"""Base repository with common CRUD operations"""
|
||||
|
||||
def __init__(self, model: type, db: Session):
|
||||
def __init__(self, model: type, db: AsyncSession):
|
||||
self.model = model
|
||||
self.db = db
|
||||
|
||||
def create(self, obj_data: Dict[str, Any]) -> T:
|
||||
async def create(self, obj_data: Dict[str, Any]) -> T:
|
||||
"""Create a new record"""
|
||||
db_obj = self.model(**obj_data)
|
||||
self.db.add(db_obj)
|
||||
self.db.commit()
|
||||
self.db.refresh(db_obj)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def get_by_id(self, record_id: UUID) -> Optional[T]:
|
||||
async def get_by_id(self, record_id: UUID) -> Optional[T]:
|
||||
"""Get record by ID"""
|
||||
return self.db.query(self.model).filter(self.model.id == record_id).first()
|
||||
stmt = select(self.model).filter(self.model.id == record_id)
|
||||
result = await self.db.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
def get_by_tenant_id(self, tenant_id: UUID, limit: int = 100, offset: int = 0) -> List[T]:
|
||||
async def get_by_tenant_id(self, tenant_id: UUID, limit: int = 100, offset: int = 0) -> List[T]:
|
||||
"""Get records by tenant ID with pagination"""
|
||||
return (
|
||||
self.db.query(self.model)
|
||||
.filter(self.model.tenant_id == tenant_id)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.all()
|
||||
)
|
||||
stmt = select(self.model).filter(
|
||||
self.model.tenant_id == tenant_id
|
||||
).limit(limit).offset(offset)
|
||||
result = await self.db.execute(stmt)
|
||||
return result.scalars().all()
|
||||
|
||||
def update(self, record_id: UUID, update_data: Dict[str, Any]) -> Optional[T]:
|
||||
async def update(self, record_id: UUID, update_data: Dict[str, Any]) -> Optional[T]:
|
||||
"""Update record by ID"""
|
||||
db_obj = self.get_by_id(record_id)
|
||||
db_obj = await self.get_by_id(record_id)
|
||||
if db_obj:
|
||||
for key, value in update_data.items():
|
||||
if hasattr(db_obj, key):
|
||||
setattr(db_obj, key, value)
|
||||
self.db.commit()
|
||||
self.db.refresh(db_obj)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def delete(self, record_id: UUID) -> bool:
|
||||
async def delete(self, record_id: UUID) -> bool:
|
||||
"""Delete record by ID"""
|
||||
db_obj = self.get_by_id(record_id)
|
||||
db_obj = await self.get_by_id(record_id)
|
||||
if db_obj:
|
||||
self.db.delete(db_obj)
|
||||
self.db.commit()
|
||||
await self.db.delete(db_obj)
|
||||
await self.db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def count_by_tenant(self, tenant_id: UUID) -> int:
|
||||
async def count_by_tenant(self, tenant_id: UUID) -> int:
|
||||
"""Count records by tenant"""
|
||||
return self.db.query(self.model).filter(self.model.tenant_id == tenant_id).count()
|
||||
stmt = select(func.count()).select_from(self.model).filter(
|
||||
self.model.tenant_id == tenant_id
|
||||
)
|
||||
result = await self.db.execute(stmt)
|
||||
return result.scalar() or 0
|
||||
|
||||
def list_with_filters(
|
||||
self,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user