import { db } from '@/src/db/db_index' import { productInfo, productAvailabilitySchedules } from '@/src/db/schema' import { eq, inArray } from 'drizzle-orm'; import { initializeAllStores } from '@/src/stores/store-initializer'; import dayjs from 'dayjs'; /** * Get all products that should be in stock or out of stock based on current schedules * Only processes products that are actually involved in availability schedules * Automatically updates products that need to change their availability status * @returns Promise<{ inStock: number[], outOfStock: number[], changed: number[] }> */ export async function verifyProductsAvailabilityBySchedule(reInitialize:boolean = false): Promise<{ inStock: number[]; outOfStock: number[]; changed: number[]; }> { // Get all schedules const allSchedules = await db.query.productAvailabilitySchedules.findMany(); // Extract all unique product IDs from all schedules const allScheduledProductIds = new Set(); for (const schedule of allSchedules) { for (const productId of schedule.productIds) { allScheduledProductIds.add(productId); } } // If no products are in any schedule, return empty arrays if (allScheduledProductIds.size === 0) { return { inStock: [], outOfStock: [], changed: [] }; } // Get current time const currentTime = dayjs().format('HH:mm'); const computedInStock: number[] = []; const computedOutOfStock: number[] = []; // Process each product that is involved in schedules for (const productId of allScheduledProductIds) { // Find applicable schedules for this product const applicableSchedules = allSchedules.filter(schedule => { return schedule.productIds.includes(productId); }); // Filter active schedules (time <= current time) const activeSchedules = applicableSchedules.filter(schedule => schedule.time <= currentTime ); if (activeSchedules.length === 0) { // No active schedule applies - skip this product // (we only care about products with active schedule rules) continue; } // Get most recent schedule const mostRecentSchedule = activeSchedules.sort((a, b) => { if (a.time !== b.time) { return b.time.localeCompare(a.time); } return b.id - a.id; })[0]; // Categorize based on schedule action if (mostRecentSchedule.action === 'in') { computedInStock.push(productId); } else { computedOutOfStock.push(productId); } } // Query products to check current availability status const allProductIds = [...computedInStock, ...computedOutOfStock]; if (allProductIds.length === 0) { return { inStock: [], outOfStock: [], changed: [] }; } const products = await db.query.productInfo.findMany({ where: inArray(productInfo.id, allProductIds), }); // Find products that need to change const toMarkInStock: number[] = []; const toMarkOutOfStock: number[] = []; const changed: number[] = []; for (const product of products) { const shouldBeInStock = computedInStock.includes(product.id); const currentlyOutOfStock = product.isOutOfStock; if (shouldBeInStock && currentlyOutOfStock) { // Should be in stock but currently out of stock - needs change toMarkInStock.push(product.id); changed.push(product.id); } else if (!shouldBeInStock && !currentlyOutOfStock) { // Should be out of stock but currently in stock - needs change toMarkOutOfStock.push(product.id); changed.push(product.id); } } // Batch update products in a single query if (toMarkInStock.length > 0) { await db.update(productInfo) .set({ isOutOfStock: false }) .where(inArray(productInfo.id, toMarkInStock)); } if (toMarkOutOfStock.length > 0) { await db.update(productInfo) .set({ isOutOfStock: true }) .where(inArray(productInfo.id, toMarkOutOfStock)); } // Reinitialize stores if any products changed if (changed.length > 0 && reInitialize) { console.log(`Reinitializing stores after availability changes for ${changed.length} products`); await initializeAllStores(); } return { inStock: computedInStock, outOfStock: computedOutOfStock, changed }; }