import { NextResponse } from 'next/server' import pool from '@/lib/db' // GET /api/drops/active - Get the earliest unfilled drop (not sold out) that has started export async function GET() { try { const now = new Date() // Clean up expired pending orders first to ensure accurate calculations await pool.execute( 'DELETE FROM pending_orders WHERE expires_at < NOW()', ) // Get all drops ordered by start_time (or created_at if start_time is null) const [rows] = await pool.execute( 'SELECT * FROM drops ORDER BY COALESCE(start_time, created_at) ASC' ) const drops = rows as any[] // Find the first drop that's not fully sold out and has started console.log(`Checking ${drops.length} drops for active drop`) for (const drop of drops) { // Check if drop has started (start_time is in the past or null) const startTime = drop.start_time ? new Date(drop.start_time) : new Date(drop.created_at) console.log(`Checking drop ${drop.id} (${drop.item}): startTime=${startTime.toISOString()}, now=${now.toISOString()}, started=${startTime <= now}`) if (startTime > now) { // Drop hasn't started yet - return it with a flag indicating it's upcoming // Calculate fill (will be 0 for upcoming drops, but include pending for consistency) const [salesRows] = await pool.execute( 'SELECT COALESCE(SUM(size), 0) as total_fill FROM sales WHERE drop_id = ?', [drop.id] ) const salesData = salesRows as any[] const totalFillInGrams = salesData[0]?.total_fill || 0 // Include non-expired pending orders in fill calculation const [pendingRows] = await pool.execute( 'SELECT COALESCE(SUM(size), 0) as total_pending FROM pending_orders WHERE drop_id = ? AND expires_at > NOW()', [drop.id] ) const pendingData = pendingRows as any[] const pendingFillInGrams = pendingData[0]?.total_pending || 0 const totalReservedInGrams = totalFillInGrams + pendingFillInGrams let salesFill = totalFillInGrams let pendingFill = pendingFillInGrams if (drop.unit === 'kg') { salesFill = totalFillInGrams / 1000 pendingFill = pendingFillInGrams / 1000 } const totalFill = salesFill + pendingFill console.log(`Returning upcoming drop ${drop.id} (${drop.item}): fill=${totalFill}, size=${drop.size}, starts at ${startTime.toISOString()}`) return NextResponse.json({ ...drop, fill: totalFill, sales_fill: salesFill, pending_fill: pendingFill, is_upcoming: true, start_time: drop.start_time || drop.created_at, }) } // Calculate fill from sales records and pending orders // Sales are stored in grams, so we need to convert based on drop unit const [salesRows] = await pool.execute( 'SELECT COALESCE(SUM(size), 0) as total_fill FROM sales WHERE drop_id = ?', [drop.id] ) const salesData = salesRows as any[] const totalFillInGrams = salesData[0]?.total_fill || 0 // Include non-expired pending orders in fill calculation (shows "on hold" inventory) const [pendingRows] = await pool.execute( 'SELECT COALESCE(SUM(size), 0) as total_pending FROM pending_orders WHERE drop_id = ? AND expires_at > NOW()', [drop.id] ) const pendingData = pendingRows as any[] // Ensure we get a number, handle null/undefined cases // When table is empty, SUM returns NULL, COALESCE converts it to 0 let pendingFillInGrams = 0 if (pendingData && pendingData.length > 0 && pendingData[0]) { const rawValue = pendingData[0].total_pending // Handle both null (from empty result) and actual 0 values if (rawValue !== null && rawValue !== undefined) { pendingFillInGrams = Number(rawValue) || 0 } } // Explicitly ensure it's 0 if we got here with no valid data pendingFillInGrams = Number(pendingFillInGrams) || 0 // Ensure totalFillInGrams and pendingFillInGrams are numbers, not strings const totalFillNum = Number(totalFillInGrams) || 0 const pendingFillNum = Number(pendingFillInGrams) || 0 const totalReservedInGrams = totalFillNum + pendingFillNum // Convert to drop's unit for display let salesFill = totalFillNum let pendingFill = pendingFillNum if (drop.unit === 'kg') { salesFill = totalFillNum / 1000 pendingFill = pendingFillNum / 1000 } const totalFill = salesFill + pendingFill // Ensure drop.size is a number for comparison const dropSize = typeof drop.size === 'string' ? parseFloat(drop.size) : Number(drop.size) const fillNum = Number(totalFill) // Check if drop is not fully sold out // Use a small epsilon for floating point comparison to handle precision issues // Consider sold out if fill is within epsilon of size (to handle rounding) const epsilon = drop.unit === 'kg' ? 0.00001 : 0.01 const remaining = dropSize - fillNum if (remaining > epsilon) { // Ensure pending_fill is explicitly 0 if no pending orders const finalPendingFill = Number(pendingFill) || 0 console.log(`Returning active drop ${drop.id} with fill ${fillNum} < size ${dropSize}, pending_fill=${finalPendingFill} (raw: ${pendingFill})`) return NextResponse.json({ ...drop, fill: fillNum, sales_fill: Number(salesFill) || 0, // Only confirmed sales pending_fill: finalPendingFill, // Items on hold (explicitly 0 if no pending orders) is_upcoming: false, start_time: drop.start_time || drop.created_at, }) } else { console.log(`Drop ${drop.id} is sold out: fill=${fillNum} >= size=${dropSize} (remaining=${remaining})`) } } // No active drops found return NextResponse.json(null) } catch (error) { console.error('Error fetching active drop:', error) return NextResponse.json( { error: 'Failed to fetch active drop' }, { status: 500 } ) } }