Initial commit - production deployment

This commit is contained in:
2026-01-21 17:17:16 +01:00
commit c23d00dd92
2289 changed files with 638440 additions and 0 deletions

View File

@@ -0,0 +1 @@
# services/suppliers/app/repositories/__init__.py

View File

@@ -0,0 +1,100 @@
# services/suppliers/app/repositories/base.py
"""
Base repository class for common database operations
"""
from typing import TypeVar, Generic, List, Optional, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import desc, asc, select, func
from uuid import UUID
T = TypeVar('T')
class BaseRepository(Generic[T]):
"""Base repository with common CRUD operations"""
def __init__(self, model: type, db: AsyncSession):
self.model = model
self.db = db
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)
await self.db.commit()
await self.db.refresh(db_obj)
return db_obj
async def get_by_id(self, record_id: UUID) -> Optional[T]:
"""Get record by ID"""
stmt = select(self.model).filter(self.model.id == record_id)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
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"""
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()
async def update(self, record_id: UUID, update_data: Dict[str, Any]) -> Optional[T]:
"""Update record by 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)
await self.db.commit()
await self.db.refresh(db_obj)
return db_obj
async def delete(self, record_id: UUID) -> bool:
"""Delete record by ID"""
db_obj = await self.get_by_id(record_id)
if db_obj:
await self.db.delete(db_obj)
await self.db.commit()
return True
return False
async def count_by_tenant(self, tenant_id: UUID) -> int:
"""Count records by tenant"""
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,
tenant_id: UUID,
filters: Optional[Dict[str, Any]] = None,
sort_by: str = "created_at",
sort_order: str = "desc",
limit: int = 100,
offset: int = 0
) -> List[T]:
"""List records with filtering and sorting"""
query = self.db.query(self.model).filter(self.model.tenant_id == tenant_id)
# Apply filters
if filters:
for key, value in filters.items():
if hasattr(self.model, key) and value is not None:
query = query.filter(getattr(self.model, key) == value)
# Apply sorting
if hasattr(self.model, sort_by):
if sort_order.lower() == "desc":
query = query.order_by(desc(getattr(self.model, sort_by)))
else:
query = query.order_by(asc(getattr(self.model, sort_by)))
return query.limit(limit).offset(offset).all()
def exists(self, record_id: UUID) -> bool:
"""Check if record exists"""
return self.db.query(self.model).filter(self.model.id == record_id).first() is not None

View File

@@ -0,0 +1,289 @@
# services/suppliers/app/repositories/supplier_performance_repository.py
"""
Supplier Performance Repository - Calculate and manage supplier trust scores
Handles supplier performance metrics, trust scores, and auto-approval eligibility
"""
from typing import Optional, Dict, Any
from uuid import UUID
from datetime import datetime, timedelta, timezone
from decimal import Decimal
import structlog
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_, case
from sqlalchemy.orm import selectinload
from app.models.suppliers import (
Supplier,
PurchaseOrder,
PurchaseOrderStatus
)
logger = structlog.get_logger()
class SupplierPerformanceRepository:
"""Repository for calculating and managing supplier performance metrics"""
def __init__(self, db: AsyncSession):
self.db = db
async def calculate_trust_score(self, supplier_id: UUID) -> float:
"""
Calculate comprehensive trust score for a supplier
Score components (weighted):
- Quality rating: 30%
- Delivery rating: 30%
- On-time delivery rate: 20%
- Fulfillment rate: 15%
- Order history: 5%
Returns:
float: Trust score between 0.0 and 1.0
"""
try:
# Get supplier with current metrics
stmt = select(Supplier).where(Supplier.id == supplier_id)
result = await self.db.execute(stmt)
supplier = result.scalar_one_or_none()
if not supplier:
logger.warning("Supplier not found for trust score calculation", supplier_id=str(supplier_id))
return 0.0
# Calculate on-time delivery rate from recent POs
on_time_rate = await self._calculate_on_time_delivery_rate(supplier_id)
# Calculate fulfillment rate from recent POs
fulfillment_rate = await self._calculate_fulfillment_rate(supplier_id)
# Calculate order history score (more orders = higher confidence)
order_history_score = min(1.0, supplier.total_pos_count / 50.0)
# Weighted components
quality_score = (supplier.quality_rating or 0.0) / 5.0 # Normalize to 0-1
delivery_score = (supplier.delivery_rating or 0.0) / 5.0 # Normalize to 0-1
trust_score = (
quality_score * 0.30 +
delivery_score * 0.30 +
on_time_rate * 0.20 +
fulfillment_rate * 0.15 +
order_history_score * 0.05
)
# Ensure score is between 0 and 1
trust_score = max(0.0, min(1.0, trust_score))
logger.info(
"Trust score calculated",
supplier_id=str(supplier_id),
trust_score=trust_score,
quality_score=quality_score,
delivery_score=delivery_score,
on_time_rate=on_time_rate,
fulfillment_rate=fulfillment_rate,
order_history_score=order_history_score
)
return trust_score
except Exception as e:
logger.error("Error calculating trust score", supplier_id=str(supplier_id), error=str(e))
return 0.0
async def _calculate_on_time_delivery_rate(self, supplier_id: UUID, days: int = 90) -> float:
"""Calculate percentage of orders delivered on time in the last N days"""
try:
cutoff_date = datetime.now(timezone.utc) - timedelta(days=days)
# Get completed orders with delivery dates
stmt = select(
func.count(PurchaseOrder.id).label('total_orders'),
func.count(
case(
(PurchaseOrder.actual_delivery_date <= PurchaseOrder.required_delivery_date, 1)
)
).label('on_time_orders')
).where(
and_(
PurchaseOrder.supplier_id == supplier_id,
PurchaseOrder.status == PurchaseOrderStatus.completed,
PurchaseOrder.actual_delivery_date.isnot(None),
PurchaseOrder.created_at >= cutoff_date
)
)
result = await self.db.execute(stmt)
row = result.one()
if row.total_orders == 0:
return 0.0
on_time_rate = float(row.on_time_orders) / float(row.total_orders)
return on_time_rate
except Exception as e:
logger.error("Error calculating on-time delivery rate", supplier_id=str(supplier_id), error=str(e))
return 0.0
async def _calculate_fulfillment_rate(self, supplier_id: UUID, days: int = 90) -> float:
"""Calculate percentage of orders fully fulfilled (no shortages) in the last N days"""
try:
cutoff_date = datetime.now(timezone.utc) - timedelta(days=days)
# Get completed/confirmed orders
stmt = select(
func.count(PurchaseOrder.id).label('total_orders'),
func.count(
case(
(PurchaseOrder.status == PurchaseOrderStatus.completed, 1)
)
).label('completed_orders')
).where(
and_(
PurchaseOrder.supplier_id == supplier_id,
PurchaseOrder.status.in_([
PurchaseOrderStatus.completed,
PurchaseOrderStatus.partially_received
]),
PurchaseOrder.created_at >= cutoff_date
)
)
result = await self.db.execute(stmt)
row = result.one()
if row.total_orders == 0:
return 0.0
fulfillment_rate = float(row.completed_orders) / float(row.total_orders)
return fulfillment_rate
except Exception as e:
logger.error("Error calculating fulfillment rate", supplier_id=str(supplier_id), error=str(e))
return 0.0
async def update_supplier_performance_metrics(self, supplier_id: UUID) -> Dict[str, Any]:
"""
Update all performance metrics for a supplier
Returns:
Dict with updated metrics
"""
try:
# Calculate all metrics
trust_score = await self.calculate_trust_score(supplier_id)
on_time_rate = await self._calculate_on_time_delivery_rate(supplier_id)
fulfillment_rate = await self._calculate_fulfillment_rate(supplier_id)
# Get current supplier
stmt = select(Supplier).where(Supplier.id == supplier_id)
result = await self.db.execute(stmt)
supplier = result.scalar_one_or_none()
if not supplier:
return {}
# Update supplier metrics
supplier.trust_score = trust_score
supplier.on_time_delivery_rate = on_time_rate
supplier.fulfillment_rate = fulfillment_rate
supplier.last_performance_update = datetime.now(timezone.utc)
# Auto-update preferred status based on performance
supplier.is_preferred_supplier = (
supplier.total_pos_count >= 10 and
trust_score >= 0.80 and
supplier.status.value == 'active'
)
# Auto-update auto-approve eligibility
supplier.auto_approve_enabled = (
supplier.total_pos_count >= 20 and
trust_score >= 0.85 and
on_time_rate >= 0.90 and
supplier.is_preferred_supplier and
supplier.status.value == 'active'
)
await self.db.commit()
logger.info(
"Supplier performance metrics updated",
supplier_id=str(supplier_id),
trust_score=trust_score,
is_preferred=supplier.is_preferred_supplier,
auto_approve_enabled=supplier.auto_approve_enabled
)
return {
"supplier_id": str(supplier_id),
"trust_score": trust_score,
"on_time_delivery_rate": on_time_rate,
"fulfillment_rate": fulfillment_rate,
"is_preferred_supplier": supplier.is_preferred_supplier,
"auto_approve_enabled": supplier.auto_approve_enabled,
"last_updated": supplier.last_performance_update.isoformat()
}
except Exception as e:
await self.db.rollback()
logger.error("Error updating supplier performance metrics", supplier_id=str(supplier_id), error=str(e))
raise
async def increment_po_counters(self, supplier_id: UUID, approved: bool = False):
"""Increment PO counters when a new PO is created or approved"""
try:
stmt = select(Supplier).where(Supplier.id == supplier_id)
result = await self.db.execute(stmt)
supplier = result.scalar_one_or_none()
if supplier:
supplier.total_pos_count += 1
if approved:
supplier.approved_pos_count += 1
await self.db.commit()
logger.info(
"Supplier PO counters incremented",
supplier_id=str(supplier_id),
total=supplier.total_pos_count,
approved=supplier.approved_pos_count
)
except Exception as e:
await self.db.rollback()
logger.error("Error incrementing PO counters", supplier_id=str(supplier_id), error=str(e))
async def get_supplier_with_performance(self, supplier_id: UUID) -> Optional[Dict[str, Any]]:
"""Get supplier data with all performance metrics"""
try:
stmt = select(Supplier).where(Supplier.id == supplier_id)
result = await self.db.execute(stmt)
supplier = result.scalar_one_or_none()
if not supplier:
return None
return {
"id": str(supplier.id),
"name": supplier.name,
"trust_score": supplier.trust_score,
"is_preferred_supplier": supplier.is_preferred_supplier,
"auto_approve_enabled": supplier.auto_approve_enabled,
"total_pos_count": supplier.total_pos_count,
"approved_pos_count": supplier.approved_pos_count,
"on_time_delivery_rate": supplier.on_time_delivery_rate,
"fulfillment_rate": supplier.fulfillment_rate,
"quality_rating": supplier.quality_rating,
"delivery_rating": supplier.delivery_rating,
"status": supplier.status.value if supplier.status else None,
"last_performance_update": supplier.last_performance_update.isoformat() if supplier.last_performance_update else None
}
except Exception as e:
logger.error("Error getting supplier with performance", supplier_id=str(supplier_id), error=str(e))
return None

View File

@@ -0,0 +1,454 @@
# services/suppliers/app/repositories/supplier_repository.py
"""
Supplier repository for database operations
"""
from typing import List, Optional, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import and_, or_, func, select
from uuid import UUID
from datetime import datetime
from app.models.suppliers import Supplier, SupplierStatus, SupplierType
from app.repositories.base import BaseRepository
class SupplierRepository(BaseRepository[Supplier]):
"""Repository for supplier management operations"""
def __init__(self, db: AsyncSession):
super().__init__(Supplier, db)
async def get_by_name(self, tenant_id: UUID, name: str) -> Optional[Supplier]:
"""Get supplier by name within tenant"""
stmt = select(self.model).filter(
and_(
self.model.tenant_id == tenant_id,
self.model.name == name
)
)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def get_by_supplier_code(self, tenant_id: UUID, supplier_code: str) -> Optional[Supplier]:
"""Get supplier by supplier code within tenant"""
stmt = select(self.model).filter(
and_(
self.model.tenant_id == tenant_id,
self.model.supplier_code == supplier_code
)
)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def search_suppliers(
self,
tenant_id: UUID,
search_term: Optional[str] = None,
supplier_type: Optional[SupplierType] = None,
status: Optional[SupplierStatus] = None,
limit: int = 50,
offset: int = 0
) -> List[Supplier]:
"""Search suppliers with filters"""
stmt = select(self.model).filter(self.model.tenant_id == tenant_id)
# Search term filter (name, contact person, email)
if search_term:
search_filter = or_(
self.model.name.ilike(f"%{search_term}%"),
self.model.contact_person.ilike(f"%{search_term}%"),
self.model.email.ilike(f"%{search_term}%")
)
stmt = stmt.filter(search_filter)
# Type filter
if supplier_type:
stmt = stmt.filter(self.model.supplier_type == supplier_type)
# Status filter
if status:
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()
async def get_active_suppliers(self, tenant_id: UUID) -> List[Supplier]:
"""Get all active suppliers for a tenant"""
stmt = select(self.model).filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.active
)
).order_by(self.model.name)
result = await self.db.execute(stmt)
return result.scalars().all()
async def get_suppliers_by_ids(self, tenant_id: UUID, supplier_ids: List[UUID]) -> List[Supplier]:
"""Get multiple suppliers by IDs in a single query (batch fetch)"""
if not supplier_ids:
return []
stmt = select(self.model).filter(
and_(
self.model.tenant_id == tenant_id,
self.model.id.in_(supplier_ids)
)
).order_by(self.model.name)
result = await self.db.execute(stmt)
return result.scalars().all()
def get_suppliers_by_type(
self,
tenant_id: UUID,
supplier_type: SupplierType
) -> List[Supplier]:
"""Get suppliers by type"""
return (
self.db.query(self.model)
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.supplier_type == supplier_type,
self.model.status == SupplierStatus.active
)
)
.order_by(self.model.quality_rating.desc(), self.model.name)
.all()
)
def get_top_suppliers(
self,
tenant_id: UUID,
limit: int = 10
) -> List[Supplier]:
"""Get top suppliers by quality rating and order value"""
return (
self.db.query(self.model)
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.active
)
)
.order_by(
self.model.quality_rating.desc(),
self.model.total_amount.desc()
)
.limit(limit)
.all()
)
def update_supplier_stats(
self,
supplier_id: UUID,
total_orders_increment: int = 0,
total_amount_increment: float = 0.0,
new_quality_rating: Optional[float] = None,
new_delivery_rating: Optional[float] = None
) -> Optional[Supplier]:
"""Update supplier performance statistics"""
supplier = self.get_by_id(supplier_id)
if not supplier:
return None
# Update counters
if total_orders_increment:
supplier.total_orders += total_orders_increment
if total_amount_increment:
supplier.total_amount += total_amount_increment
# Update ratings (these should be calculated averages)
if new_quality_rating is not None:
supplier.quality_rating = new_quality_rating
if new_delivery_rating is not None:
supplier.delivery_rating = new_delivery_rating
supplier.updated_at = datetime.utcnow()
self.db.commit()
self.db.refresh(supplier)
return supplier
def get_suppliers_needing_review(
self,
tenant_id: UUID,
days_since_last_order: int = 30
) -> List[Supplier]:
"""Get suppliers that may need performance review"""
from datetime import datetime, timedelta
cutoff_date = datetime.utcnow() - timedelta(days=days_since_last_order)
return (
self.db.query(self.model)
.filter(
and_(
self.model.tenant_id == tenant_id,
self.model.status == SupplierStatus.active,
or_(
self.model.quality_rating < 3.0, # Poor rating
self.model.delivery_rating < 3.0, # Poor delivery
self.model.updated_at < cutoff_date # Long time since interaction
)
)
)
.order_by(self.model.quality_rating.asc())
.all()
)
async def get_supplier_statistics(self, tenant_id: UUID) -> Dict[str, Any]:
"""Get supplier statistics for dashboard"""
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": 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)
}
async def approve_supplier(
self,
supplier_id: UUID,
approved_by: UUID,
approval_date: Optional[datetime] = None
) -> Optional[Supplier]:
"""Approve a pending supplier"""
supplier = await self.get_by_id(supplier_id)
if not supplier or supplier.status != SupplierStatus.pending_approval:
return None
supplier.status = SupplierStatus.active
supplier.approved_by = approved_by
supplier.approved_at = approval_date or datetime.utcnow()
supplier.rejection_reason = None
supplier.updated_at = datetime.utcnow()
await self.db.commit()
await self.db.refresh(supplier)
return supplier
async def reject_supplier(
self,
supplier_id: UUID,
rejection_reason: str,
approved_by: UUID
) -> Optional[Supplier]:
"""Reject a pending supplier"""
supplier = await self.get_by_id(supplier_id)
if not supplier or supplier.status != SupplierStatus.pending_approval:
return None
supplier.status = SupplierStatus.inactive
supplier.rejection_reason = rejection_reason
supplier.approved_by = approved_by
supplier.approved_at = datetime.utcnow()
supplier.updated_at = datetime.utcnow()
await self.db.commit()
await self.db.refresh(supplier)
return supplier
async def hard_delete_supplier(self, supplier_id: UUID) -> Dict[str, Any]:
"""
Hard delete supplier and all associated data
Returns counts of deleted records
"""
from app.models.suppliers import (
SupplierPriceList, SupplierQualityReview,
SupplierAlert, SupplierScorecard, PurchaseOrderStatus, PurchaseOrder
)
from app.models.performance import SupplierPerformanceMetric
from sqlalchemy import delete
# Get supplier first
supplier = await self.get_by_id(supplier_id)
if not supplier:
return None
# Check for active purchase orders (block deletion if any exist)
active_statuses = [
PurchaseOrderStatus.draft,
PurchaseOrderStatus.pending_approval,
PurchaseOrderStatus.approved,
PurchaseOrderStatus.sent_to_supplier,
PurchaseOrderStatus.confirmed
]
stmt = select(PurchaseOrder).where(
PurchaseOrder.supplier_id == supplier_id,
PurchaseOrder.status.in_(active_statuses)
)
result = await self.db.execute(stmt)
active_pos = result.scalars().all()
if active_pos:
raise ValueError(
f"Cannot delete supplier with {len(active_pos)} active purchase orders. "
"Complete or cancel all purchase orders first."
)
# Count related records before deletion
stmt = select(SupplierPriceList).where(SupplierPriceList.supplier_id == supplier_id)
result = await self.db.execute(stmt)
price_lists_count = len(result.scalars().all())
stmt = select(SupplierQualityReview).where(SupplierQualityReview.supplier_id == supplier_id)
result = await self.db.execute(stmt)
quality_reviews_count = len(result.scalars().all())
stmt = select(SupplierPerformanceMetric).where(SupplierPerformanceMetric.supplier_id == supplier_id)
result = await self.db.execute(stmt)
metrics_count = len(result.scalars().all())
stmt = select(SupplierAlert).where(SupplierAlert.supplier_id == supplier_id)
result = await self.db.execute(stmt)
alerts_count = len(result.scalars().all())
stmt = select(SupplierScorecard).where(SupplierScorecard.supplier_id == supplier_id)
result = await self.db.execute(stmt)
scorecards_count = len(result.scalars().all())
# Delete related records (in reverse dependency order)
stmt = delete(SupplierScorecard).where(SupplierScorecard.supplier_id == supplier_id)
await self.db.execute(stmt)
stmt = delete(SupplierAlert).where(SupplierAlert.supplier_id == supplier_id)
await self.db.execute(stmt)
stmt = delete(SupplierPerformanceMetric).where(SupplierPerformanceMetric.supplier_id == supplier_id)
await self.db.execute(stmt)
stmt = delete(SupplierQualityReview).where(SupplierQualityReview.supplier_id == supplier_id)
await self.db.execute(stmt)
stmt = delete(SupplierPriceList).where(SupplierPriceList.supplier_id == supplier_id)
await self.db.execute(stmt)
# Delete the supplier itself
await self.delete(supplier_id)
await self.db.commit()
return {
"supplier_name": supplier.name,
"deleted_price_lists": price_lists_count,
"deleted_quality_reviews": quality_reviews_count,
"deleted_performance_metrics": metrics_count,
"deleted_alerts": alerts_count,
"deleted_scorecards": scorecards_count,
"deletion_timestamp": datetime.utcnow()
}
async def get_supplier_price_lists(
self,
supplier_id: UUID,
tenant_id: UUID,
is_active: bool = True
) -> List[Any]:
"""Get all price list items for a supplier"""
from app.models.suppliers import SupplierPriceList
stmt = select(SupplierPriceList).filter(
and_(
SupplierPriceList.supplier_id == supplier_id,
SupplierPriceList.tenant_id == tenant_id
)
)
if is_active:
stmt = stmt.filter(SupplierPriceList.is_active == True)
result = await self.db.execute(stmt)
return result.scalars().all()
async def get_supplier_price_list(
self,
price_list_id: UUID,
tenant_id: UUID
) -> Optional[Any]:
"""Get specific price list item"""
from app.models.suppliers import SupplierPriceList
stmt = select(SupplierPriceList).filter(
and_(
SupplierPriceList.id == price_list_id,
SupplierPriceList.tenant_id == tenant_id
)
)
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def create_supplier_price_list(
self,
create_data: Dict[str, Any]
) -> Any:
"""Create a new price list item"""
from app.models.suppliers import SupplierPriceList
price_list = SupplierPriceList(**create_data)
self.db.add(price_list)
await self.db.commit()
await self.db.refresh(price_list)
return price_list
async def update_supplier_price_list(
self,
price_list_id: UUID,
update_data: Dict[str, Any]
) -> Any:
"""Update a price list item"""
from app.models.suppliers import SupplierPriceList
stmt = select(SupplierPriceList).filter(SupplierPriceList.id == price_list_id)
result = await self.db.execute(stmt)
price_list = result.scalar_one_or_none()
if not price_list:
raise ValueError("Price list item not found")
# Update fields
for key, value in update_data.items():
if hasattr(price_list, key):
setattr(price_list, key, value)
await self.db.commit()
await self.db.refresh(price_list)
return price_list
async def delete_supplier_price_list(
self,
price_list_id: UUID
) -> bool:
"""Delete a price list item"""
from app.models.suppliers import SupplierPriceList
from sqlalchemy import delete
stmt = delete(SupplierPriceList).filter(SupplierPriceList.id == price_list_id)
result = await self.db.execute(stmt)
await self.db.commit()
return result.rowcount > 0