Improve the frontend and repository layer
This commit is contained in:
82
services/pos/app/repositories/pos_config_repository.py
Normal file
82
services/pos/app/repositories/pos_config_repository.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
POS Configuration Repository using Repository Pattern
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Dict, Any
|
||||
from uuid import UUID
|
||||
from sqlalchemy import select, and_, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import structlog
|
||||
|
||||
from app.models.pos_config import POSConfiguration
|
||||
from shared.database.repository import BaseRepository
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class POSConfigurationRepository(BaseRepository[POSConfiguration, dict, dict]):
|
||||
"""Repository for POS configuration operations"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
super().__init__(POSConfiguration, session)
|
||||
|
||||
async def get_configurations_by_tenant(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
pos_system: Optional[str] = None,
|
||||
is_active: Optional[bool] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) -> List[POSConfiguration]:
|
||||
"""Get POS configurations for a specific tenant with optional filters"""
|
||||
try:
|
||||
query = select(self.model).where(self.model.tenant_id == tenant_id)
|
||||
|
||||
# Apply filters
|
||||
conditions = []
|
||||
if pos_system:
|
||||
conditions.append(self.model.pos_system == pos_system)
|
||||
if is_active is not None:
|
||||
conditions.append(self.model.is_active == is_active)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
query = query.offset(skip).limit(limit).order_by(self.model.created_at.desc())
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get configurations by tenant", error=str(e), tenant_id=tenant_id)
|
||||
raise
|
||||
|
||||
async def count_configurations_by_tenant(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
pos_system: Optional[str] = None,
|
||||
is_active: Optional[bool] = None
|
||||
) -> int:
|
||||
"""Count POS configurations for a specific tenant with optional filters"""
|
||||
try:
|
||||
from sqlalchemy import func
|
||||
|
||||
query = select(func.count(self.model.id)).where(self.model.tenant_id == tenant_id)
|
||||
|
||||
# Apply filters
|
||||
conditions = []
|
||||
if pos_system:
|
||||
conditions.append(self.model.pos_system == pos_system)
|
||||
if is_active is not None:
|
||||
conditions.append(self.model.is_active == is_active)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
result = await self.session.execute(query)
|
||||
count = result.scalar() or 0
|
||||
return count
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to count configurations by tenant", error=str(e), tenant_id=tenant_id)
|
||||
raise
|
||||
113
services/pos/app/repositories/pos_transaction_item_repository.py
Normal file
113
services/pos/app/repositories/pos_transaction_item_repository.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
POS Transaction Item Repository using Repository Pattern
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
from sqlalchemy import select, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import structlog
|
||||
|
||||
from app.models.pos_transaction import POSTransactionItem
|
||||
from shared.database.repository import BaseRepository
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class POSTransactionItemRepository(BaseRepository[POSTransactionItem, dict, dict]):
|
||||
"""Repository for POS transaction item operations"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
super().__init__(POSTransactionItem, session)
|
||||
|
||||
async def get_items_by_transaction(
|
||||
self,
|
||||
transaction_id: UUID
|
||||
) -> List[POSTransactionItem]:
|
||||
"""Get all items for a transaction"""
|
||||
try:
|
||||
query = select(POSTransactionItem).where(
|
||||
POSTransactionItem.transaction_id == transaction_id
|
||||
).order_by(POSTransactionItem.created_at)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get transaction items",
|
||||
transaction_id=str(transaction_id),
|
||||
error=str(e))
|
||||
raise
|
||||
|
||||
async def get_items_by_product(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
product_name: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) -> List[POSTransactionItem]:
|
||||
"""Get all transaction items for a specific product"""
|
||||
try:
|
||||
query = select(POSTransactionItem).where(
|
||||
and_(
|
||||
POSTransactionItem.tenant_id == tenant_id,
|
||||
POSTransactionItem.product_name.ilike(f"%{product_name}%")
|
||||
)
|
||||
).order_by(POSTransactionItem.created_at.desc()).offset(skip).limit(limit)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get items by product",
|
||||
product_name=product_name,
|
||||
error=str(e))
|
||||
raise
|
||||
|
||||
async def get_items_by_sku(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
sku: str
|
||||
) -> List[POSTransactionItem]:
|
||||
"""Get all transaction items for a specific SKU"""
|
||||
try:
|
||||
query = select(POSTransactionItem).where(
|
||||
and_(
|
||||
POSTransactionItem.tenant_id == tenant_id,
|
||||
POSTransactionItem.sku == sku
|
||||
)
|
||||
).order_by(POSTransactionItem.created_at.desc())
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get items by SKU",
|
||||
sku=sku,
|
||||
error=str(e))
|
||||
raise
|
||||
|
||||
async def get_items_by_category(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
category: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) -> List[POSTransactionItem]:
|
||||
"""Get all transaction items for a specific category"""
|
||||
try:
|
||||
query = select(POSTransactionItem).where(
|
||||
and_(
|
||||
POSTransactionItem.tenant_id == tenant_id,
|
||||
POSTransactionItem.product_category == category
|
||||
)
|
||||
).order_by(POSTransactionItem.created_at.desc()).offset(skip).limit(limit)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get items by category",
|
||||
category=category,
|
||||
error=str(e))
|
||||
raise
|
||||
362
services/pos/app/repositories/pos_transaction_repository.py
Normal file
362
services/pos/app/repositories/pos_transaction_repository.py
Normal file
@@ -0,0 +1,362 @@
|
||||
"""
|
||||
POS Transaction Repository using Repository Pattern
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Dict, Any
|
||||
from uuid import UUID
|
||||
from datetime import datetime, date, timedelta
|
||||
from sqlalchemy import select, func, and_, or_, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
import structlog
|
||||
|
||||
from app.models.pos_transaction import POSTransaction, POSTransactionItem
|
||||
from shared.database.repository import BaseRepository
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class POSTransactionRepository(BaseRepository[POSTransaction, dict, dict]):
|
||||
"""Repository for POS transaction operations"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
super().__init__(POSTransaction, session)
|
||||
|
||||
async def get_transactions_by_tenant(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
pos_system: Optional[str] = None,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
status: Optional[str] = None,
|
||||
is_synced: Optional[bool] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 50
|
||||
) -> List[POSTransaction]:
|
||||
"""Get POS transactions for a specific tenant with optional filters"""
|
||||
try:
|
||||
query = select(self.model).options(
|
||||
selectinload(POSTransaction.items)
|
||||
).where(self.model.tenant_id == tenant_id)
|
||||
|
||||
# Apply filters
|
||||
conditions = []
|
||||
if pos_system:
|
||||
conditions.append(self.model.pos_system == pos_system)
|
||||
if status:
|
||||
conditions.append(self.model.status == status)
|
||||
if is_synced is not None:
|
||||
conditions.append(self.model.is_synced_to_sales == is_synced)
|
||||
if start_date:
|
||||
conditions.append(self.model.transaction_date >= start_date)
|
||||
if end_date:
|
||||
conditions.append(self.model.transaction_date <= end_date)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
query = query.order_by(desc(self.model.transaction_date)).offset(skip).limit(limit)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get transactions by tenant", error=str(e), tenant_id=tenant_id)
|
||||
raise
|
||||
|
||||
async def count_transactions_by_tenant(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
pos_system: Optional[str] = None,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
status: Optional[str] = None,
|
||||
is_synced: Optional[bool] = None
|
||||
) -> int:
|
||||
"""Count POS transactions for a specific tenant with optional filters"""
|
||||
try:
|
||||
query = select(func.count(self.model.id)).where(self.model.tenant_id == tenant_id)
|
||||
|
||||
# Apply filters
|
||||
conditions = []
|
||||
if pos_system:
|
||||
conditions.append(self.model.pos_system == pos_system)
|
||||
if status:
|
||||
conditions.append(self.model.status == status)
|
||||
if is_synced is not None:
|
||||
conditions.append(self.model.is_synced_to_sales == is_synced)
|
||||
if start_date:
|
||||
conditions.append(self.model.transaction_date >= start_date)
|
||||
if end_date:
|
||||
conditions.append(self.model.transaction_date <= end_date)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
result = await self.session.execute(query)
|
||||
count = result.scalar() or 0
|
||||
return count
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to count transactions by tenant", error=str(e), tenant_id=tenant_id)
|
||||
raise
|
||||
|
||||
async def get_transaction_with_items(
|
||||
self,
|
||||
transaction_id: UUID,
|
||||
tenant_id: UUID
|
||||
) -> Optional[POSTransaction]:
|
||||
"""Get transaction with all its items"""
|
||||
try:
|
||||
query = select(POSTransaction).options(
|
||||
selectinload(POSTransaction.items)
|
||||
).where(
|
||||
and_(
|
||||
POSTransaction.id == transaction_id,
|
||||
POSTransaction.tenant_id == tenant_id
|
||||
)
|
||||
)
|
||||
result = await self.session.execute(query)
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e:
|
||||
logger.error("Failed to get transaction with items",
|
||||
transaction_id=str(transaction_id),
|
||||
error=str(e))
|
||||
raise
|
||||
|
||||
async def get_transactions_by_pos_config(
|
||||
self,
|
||||
pos_config_id: UUID,
|
||||
skip: int = 0,
|
||||
limit: int = 50
|
||||
) -> List[POSTransaction]:
|
||||
"""Get transactions for a specific POS configuration"""
|
||||
try:
|
||||
query = select(POSTransaction).options(
|
||||
selectinload(POSTransaction.items)
|
||||
).where(
|
||||
POSTransaction.pos_config_id == pos_config_id
|
||||
).order_by(desc(POSTransaction.transaction_date)).offset(skip).limit(limit)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalars().all()
|
||||
except Exception as e:
|
||||
logger.error("Failed to get transactions by pos config",
|
||||
pos_config_id=str(pos_config_id),
|
||||
error=str(e))
|
||||
raise
|
||||
|
||||
async def get_transactions_by_date_range(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
start_date: date,
|
||||
end_date: date,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) -> List[POSTransaction]:
|
||||
"""Get transactions within date range"""
|
||||
try:
|
||||
start_datetime = datetime.combine(start_date, datetime.min.time())
|
||||
end_datetime = datetime.combine(end_date, datetime.max.time())
|
||||
|
||||
query = select(POSTransaction).options(
|
||||
selectinload(POSTransaction.items)
|
||||
).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.transaction_date >= start_datetime,
|
||||
POSTransaction.transaction_date <= end_datetime
|
||||
)
|
||||
).order_by(desc(POSTransaction.transaction_date)).offset(skip).limit(limit)
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return result.scalars().all()
|
||||
except Exception as e:
|
||||
logger.error("Failed to get transactions by date range",
|
||||
start_date=str(start_date),
|
||||
end_date=str(end_date),
|
||||
error=str(e))
|
||||
raise
|
||||
|
||||
async def get_dashboard_metrics(
|
||||
self,
|
||||
tenant_id: UUID
|
||||
) -> Dict[str, Any]:
|
||||
"""Get dashboard metrics for transactions"""
|
||||
try:
|
||||
# Today's metrics
|
||||
today = datetime.now().date()
|
||||
today_start = datetime.combine(today, datetime.min.time())
|
||||
today_end = datetime.combine(today, datetime.max.time())
|
||||
|
||||
week_start = today - timedelta(days=today.weekday())
|
||||
week_start_datetime = datetime.combine(week_start, datetime.min.time())
|
||||
|
||||
month_start = today.replace(day=1)
|
||||
month_start_datetime = datetime.combine(month_start, datetime.min.time())
|
||||
|
||||
# Transaction counts by period
|
||||
transactions_today = await self.session.execute(
|
||||
select(func.count()).select_from(POSTransaction).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.transaction_date >= today_start,
|
||||
POSTransaction.transaction_date <= today_end,
|
||||
POSTransaction.status == "completed"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
transactions_week = await self.session.execute(
|
||||
select(func.count()).select_from(POSTransaction).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.transaction_date >= week_start_datetime,
|
||||
POSTransaction.status == "completed"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
transactions_month = await self.session.execute(
|
||||
select(func.count()).select_from(POSTransaction).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.transaction_date >= month_start_datetime,
|
||||
POSTransaction.status == "completed"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Revenue by period
|
||||
revenue_today = await self.session.execute(
|
||||
select(func.coalesce(func.sum(POSTransaction.total_amount), 0)).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.transaction_date >= today_start,
|
||||
POSTransaction.transaction_date <= today_end,
|
||||
POSTransaction.status == "completed"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
revenue_week = await self.session.execute(
|
||||
select(func.coalesce(func.sum(POSTransaction.total_amount), 0)).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.transaction_date >= week_start_datetime,
|
||||
POSTransaction.status == "completed"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
revenue_month = await self.session.execute(
|
||||
select(func.coalesce(func.sum(POSTransaction.total_amount), 0)).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.transaction_date >= month_start_datetime,
|
||||
POSTransaction.status == "completed"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Status breakdown
|
||||
status_counts = await self.session.execute(
|
||||
select(POSTransaction.status, func.count()).select_from(POSTransaction).where(
|
||||
POSTransaction.tenant_id == tenant_id
|
||||
).group_by(POSTransaction.status)
|
||||
)
|
||||
|
||||
status_breakdown = {status: count for status, count in status_counts.fetchall()}
|
||||
|
||||
# Payment method breakdown
|
||||
payment_counts = await self.session.execute(
|
||||
select(POSTransaction.payment_method, func.count()).select_from(POSTransaction).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.status == "completed"
|
||||
)
|
||||
).group_by(POSTransaction.payment_method)
|
||||
)
|
||||
|
||||
payment_breakdown = {method: count for method, count in payment_counts.fetchall()}
|
||||
|
||||
# Average transaction value
|
||||
avg_transaction_value = await self.session.execute(
|
||||
select(func.coalesce(func.avg(POSTransaction.total_amount), 0)).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.status == "completed"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"total_transactions_today": transactions_today.scalar(),
|
||||
"total_transactions_this_week": transactions_week.scalar(),
|
||||
"total_transactions_this_month": transactions_month.scalar(),
|
||||
"revenue_today": float(revenue_today.scalar()),
|
||||
"revenue_this_week": float(revenue_week.scalar()),
|
||||
"revenue_this_month": float(revenue_month.scalar()),
|
||||
"status_breakdown": status_breakdown,
|
||||
"payment_method_breakdown": payment_breakdown,
|
||||
"average_transaction_value": float(avg_transaction_value.scalar())
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Failed to get dashboard metrics", error=str(e), tenant_id=tenant_id)
|
||||
raise
|
||||
|
||||
async def get_sync_status_summary(
|
||||
self,
|
||||
tenant_id: UUID
|
||||
) -> Dict[str, Any]:
|
||||
"""Get sync status summary for transactions"""
|
||||
try:
|
||||
# Count synced vs unsynced
|
||||
synced_count = await self.session.execute(
|
||||
select(func.count()).select_from(POSTransaction).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.is_synced_to_sales == True
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
pending_count = await self.session.execute(
|
||||
select(func.count()).select_from(POSTransaction).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.is_synced_to_sales == False,
|
||||
POSTransaction.sync_error.is_(None)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
failed_count = await self.session.execute(
|
||||
select(func.count()).select_from(POSTransaction).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.is_synced_to_sales == False,
|
||||
POSTransaction.sync_error.isnot(None)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Get last sync time
|
||||
last_sync = await self.session.execute(
|
||||
select(func.max(POSTransaction.sync_completed_at)).where(
|
||||
and_(
|
||||
POSTransaction.tenant_id == tenant_id,
|
||||
POSTransaction.is_synced_to_sales == True
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"synced": synced_count.scalar(),
|
||||
"pending": pending_count.scalar(),
|
||||
"failed": failed_count.scalar(),
|
||||
"last_sync_at": last_sync.scalar()
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Failed to get sync status summary", error=str(e), tenant_id=tenant_id)
|
||||
raise
|
||||
Reference in New Issue
Block a user