flag special orders in ui

This commit is contained in:
zman 2025-05-31 12:00:28 -04:00
parent dca11b0ede
commit fa089adb53
5 changed files with 69 additions and 6 deletions

View File

@ -32,7 +32,8 @@ router = APIRouter(prefix="/orders")
@router.get("/", response_model=List[TCGPlayerAPIOrderSummary]) @router.get("/", response_model=List[TCGPlayerAPIOrderSummary])
async def get_orders( async def get_orders(
search_range: SearchRange = SearchRange.LAST_THREE_MONTHS, search_range: SearchRange = SearchRange.LAST_THREE_MONTHS,
open_only: bool = False open_only: bool = False,
db: Session = Depends(get_db)
) -> List[TCGPlayerAPIOrderSummary]: ) -> List[TCGPlayerAPIOrderSummary]:
""" """
Retrieve orders from TCGPlayer based on search criteria. Retrieve orders from TCGPlayer based on search criteria.
@ -47,6 +48,7 @@ async def get_orders(
try: try:
order_management = service_manager.get_service('order_management') order_management = service_manager.get_service('order_management')
orders = await order_management.get_orders(search_range, open_only) orders = await order_management.get_orders(search_range, open_only)
orders = await order_management.add_item_quantity(db, orders)
return orders return orders
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to fetch orders: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to fetch orders: {str(e)}")

View File

@ -76,6 +76,7 @@ class TCGPlayerAPIOrderSummary(BaseModel):
orderStatus: str orderStatus: str
buyerName: str buyerName: str
shippingType: str shippingType: str
itemQuantity: int
productAmount: float productAmount: float
shippingAmount: float shippingAmount: float
totalAmount: float totalAmount: float

View File

@ -24,6 +24,7 @@ import csv
import io import io
from app.schemas.file import FileInDB from app.schemas.file import FileInDB
from datetime import datetime from datetime import datetime
from sqlalchemy import func
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class OrderManagementService(BaseTCGPlayerService): class OrderManagementService(BaseTCGPlayerService):
@ -40,7 +41,34 @@ class OrderManagementService(BaseTCGPlayerService):
self.pull_sheet_endpoint = f"/pull-sheets/export{self.API_VERSION}" self.pull_sheet_endpoint = f"/pull-sheets/export{self.API_VERSION}"
self.shipping_endpoint = f"/shipping/export{self.API_VERSION}" self.shipping_endpoint = f"/shipping/export{self.API_VERSION}"
async def add_item_quantity(self, db: Session, orders: list[TCGPlayerAPIOrderSummary]) -> list[TCGPlayerAPIOrderSummary]:
"""
Add item quantity to orders using SQL aggregation for better performance
"""
# Get order numbers from the input orders
order_numbers = [order["orderNumber"] for order in orders]
# Use SQL aggregation to get the sum of quantities directly from the database
quantity_sums = (
db.query(
TCGPlayerOrderProduct.order_number,
func.sum(TCGPlayerOrderProduct.quantity).label('total_quantity')
)
.filter(TCGPlayerOrderProduct.order_number.in_(order_numbers))
.group_by(TCGPlayerOrderProduct.order_number)
.all()
)
# Create a lookup dictionary for faster access
quantity_lookup = {order_number: total_quantity for order_number, total_quantity in quantity_sums}
# Update orders with quantities
for order in orders:
order["itemQuantity"] = quantity_lookup.get(order["orderNumber"], 0)
return orders
async def get_orders(self, search_range: str = "LastThreeMonths", open_only: bool = False, filter_out: list[str] = [], filter_in: list[str] = []) -> list[TCGPlayerAPIOrderSummary]: async def get_orders(self, search_range: str = "LastThreeMonths", open_only: bool = False, filter_out: list[str] = [], filter_in: list[str] = []) -> list[TCGPlayerAPIOrderSummary]:
""" """
search range options: search range options:
@ -79,6 +107,9 @@ class OrderManagementService(BaseTCGPlayerService):
orders = [order for order in orders if order.get("orderNumber") not in filter_out] orders = [order for order in orders if order.get("orderNumber") not in filter_out]
if filter_in: if filter_in:
orders = [order for order in orders if order.get("orderNumber") in filter_in] orders = [order for order in orders if order.get("orderNumber") in filter_in]
# add item quantity to orders as none
for order in orders:
order["itemQuantity"] = 0
return orders return orders
async def get_order_ids(self, search_range: str = "LastThreeMonths", open_only: bool = False, filter_out: list[str] = [], filter_in: list[str] = []): async def get_order_ids(self, search_range: str = "LastThreeMonths", open_only: bool = False, filter_out: list[str] = [], filter_in: list[str] = []):

View File

@ -75,7 +75,7 @@ class SchedulerService(BaseService):
await self.scheduler.schedule_task( await self.scheduler.schedule_task(
task_name="update_open_orders_hourly", task_name="update_open_orders_hourly",
func=self.update_open_orders_hourly, func=self.update_open_orders_hourly,
cron_expression="10 * * * *", # Run at minute 10 of every hour cron_expression="*/10 * * * *", # Run at minute 10 of every hour
db=db db=db
) )
# Schedule all orders update to run daily at 3 AM # Schedule all orders update to run daily at 3 AM

View File

@ -67,16 +67,30 @@ function displayOrders(orders) {
} }
orders.forEach(order => { orders.forEach(order => {
const hasHighQuantity = order.itemQuantity > 9;
const hasHighAmount = order.productAmount > 40.00;
const orderCard = document.createElement('div'); const orderCard = document.createElement('div');
orderCard.className = `bg-gray-700 rounded-lg shadow-sm p-4 border border-gray-600 hover:shadow-md transition-shadow cursor-pointer ${ orderCard.className = `bg-gray-700 rounded-lg shadow-sm p-4 border border-gray-600 hover:shadow-md transition-shadow cursor-pointer ${
selectedOrders.has(order.orderNumber) ? 'ring-2 ring-blue-500' : '' selectedOrders.has(order.orderNumber) ? 'ring-2 ring-blue-500' : ''
}`; } ${hasHighQuantity || hasHighAmount ? 'border-yellow-500' : ''}`;
orderCard.dataset.orderId = order.orderNumber; orderCard.dataset.orderId = order.orderNumber;
orderCard.innerHTML = ` orderCard.innerHTML = `
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<div class="flex justify-between items-start mb-3"> <div class="flex justify-between items-start mb-3">
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h3 class="text-lg font-bold text-blue-400 truncate">#${order.orderNumber || 'N/A'}</h3> <div class="flex items-center gap-2">
<h3 class="text-lg font-bold text-blue-400 truncate">
<a href="https://sellerportal.tcgplayer.com/orders/${order.orderNumber}" target="_blank" rel="noopener noreferrer" class="hover:underline" onclick="event.stopPropagation()">${order.orderNumber || 'N/A'}</a>
</h3>
${(hasHighQuantity || hasHighAmount) ? `
<span class="text-yellow-400" title="${hasHighQuantity ? 'High item quantity' : ''}${hasHighQuantity && hasHighAmount ? ' and ' : ''}${hasHighAmount ? 'High product amount' : ''}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</span>
` : ''}
</div>
<p class="text-sm text-gray-400">${order.buyerName || 'N/A'}</p> <p class="text-sm text-gray-400">${order.buyerName || 'N/A'}</p>
</div> </div>
<span class="px-2 py-1 text-xs rounded-full ${ <span class="px-2 py-1 text-xs rounded-full ${
@ -86,8 +100,23 @@ function displayOrders(orders) {
<div class="mt-auto"> <div class="mt-auto">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<p class="text-sm text-gray-400">${order.orderDate ? new Date(order.orderDate).toLocaleString() : 'N/A'}</p> <p class="text-sm text-gray-400">${order.orderDate ? new Date(order.orderDate).toLocaleString() : 'N/A'}</p>
<p class="text-lg font-bold text-white">$${order.totalAmount ? order.totalAmount.toFixed(2) : '0.00'}</p> <div class="flex items-center gap-2">
${hasHighAmount ? `
<span class="text-yellow-400 text-sm"></span>
` : ''}
<p class="text-lg font-bold ${hasHighAmount ? 'text-yellow-400' : 'text-white'}">$${order.totalAmount ? order.totalAmount.toFixed(2) : '0.00'}</p>
</div>
</div> </div>
${hasHighQuantity ? `
<div class="mt-2 text-sm text-yellow-400">
<span class="flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd" />
</svg>
High quantity: ${order.itemQuantity} items
</span>
</div>
` : ''}
</div> </div>
</div> </div>
`; `;