freshyo/apps/backend/src/lib/manage-scheduled-availability.ts
2026-03-20 00:39:48 +05:30

129 lines
4.2 KiB
TypeScript

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<number>();
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
};
}