diff --git a/apps/backend/src/lib/post-order-handler.ts b/apps/backend/src/lib/post-order-handler.ts
index 29dd536..13abcd0 100644
--- a/apps/backend/src/lib/post-order-handler.ts
+++ b/apps/backend/src/lib/post-order-handler.ts
@@ -48,7 +48,9 @@ const formatOrderMessageWithFullData = (ordersData: any[]): string => {
message += 'š¦ Items:\n';
order.orderItems?.forEach((item: any) => {
- message += ` ⢠${item.product?.name || 'Unknown'} ⢠${item.product.productQuantity}${item.product.unit?.shortNotation}x${item.quantity}\n`;
+ const sku = item.sku
+ const features = (sku?.features || []).map((f: any) => f.featureValue).join(' ')
+ message += ` ⢠${sku?.product?.name || 'Unknown'} ${features} x${item.quantity}\n`;
});
message += `\nš° Total: ā¹${order.totalAmount}\n`;
@@ -87,7 +89,7 @@ const formatCancellationMessage = (orderData: any, cancellationData: Cancellatio
š Phone: ${orderData.address?.phone || 'N/A'}
š¦ Items:
-${orderData.orderItems?.map((item: any) => ` ⢠${item.product?.name || 'Unknown'} x${item.quantity}`).join('\n') || ' N/A'}
+${orderData.orderItems?.map((item: any) => ` ⢠${item.sku?.product?.name || 'Unknown'} x${item.quantity}`).join('\n') || ' N/A'}
š° Total: ā¹${orderData.totalAmount}
š³ Refund: ${orderData.refundStatus === 'na' ? 'N/A (COD)' : orderData.refundStatus || 'Pending'}
diff --git a/apps/backend/src/sqliteImporter.ts b/apps/backend/src/sqliteImporter.ts
index c245555..d685475 100644
--- a/apps/backend/src/sqliteImporter.ts
+++ b/apps/backend/src/sqliteImporter.ts
@@ -282,7 +282,7 @@ export {
type OrderWithFullData,
type OrderWithCancellationData,
// Common API helpers
- getSuspendedProductIds,
+ getSuspendedSkuIds,
getNextDeliveryDateWithCapacity,
getStoresSummary,
healthCheck,
diff --git a/apps/backend/src/stores/product-store.ts b/apps/backend/src/stores/product-store.ts
index 4c981b5..2b500bf 100644
--- a/apps/backend/src/stores/product-store.ts
+++ b/apps/backend/src/stores/product-store.ts
@@ -76,18 +76,18 @@ export async function initializeProducts(): Promise {
const allDeliverySlots = await getAllDeliverySlotsForCache()
const deliverySlotsMap = new Map()
for (const slot of allDeliverySlots) {
- if (!deliverySlotsMap.has(slot.productId))
- deliverySlotsMap.set(slot.productId, [])
- deliverySlotsMap.get(slot.productId)!.push(slot)
+ if (!deliverySlotsMap.has(slot.skuId))
+ deliverySlotsMap.set(slot.skuId, [])
+ deliverySlotsMap.get(slot.skuId)!.push(slot)
}
// Fetch all special deals
const allSpecialDeals = await getAllSpecialDealsForCache()
const specialDealsMap = new Map()
for (const deal of allSpecialDeals) {
- if (!specialDealsMap.has(deal.productId))
- specialDealsMap.set(deal.productId, [])
- specialDealsMap.get(deal.productId)!.push(deal)
+ if (!specialDealsMap.has(deal.skuId))
+ specialDealsMap.set(deal.skuId, [])
+ specialDealsMap.get(deal.skuId)!.push(deal)
}
// Fetch all product tags
@@ -153,70 +153,11 @@ export async function initializeProducts(): Promise {
export async function getProductById(id: number): Promise {
try {
- // const key = `product:${id}`
- // const data = await redisClient.get(key)
- // if (!data) return null
- // return JSON.parse(data) as Product
-
- const product = await getProductByIdFromDb(id)
- if (!product) return null
-
- const signedImages = scaffoldAssetUrl(
- (product.images as string[]) || []
- )
-
- // Fetch store info
- const allStores = await getAllStoresForCache()
- const store = product.storeId
- ? allStores.find(s => s.id === product.storeId) || null
- : null
-
- // Fetch delivery slots for this product
- const allDeliverySlots = await getAllDeliverySlotsForCache()
- const productSlots = allDeliverySlots.filter(s => s.productId === id)
-
- // Fetch special deals for this product
- const allSpecialDeals = await getAllSpecialDealsForCache()
- const productDeals = allSpecialDeals.filter(d => d.productId === id)
-
- // Fetch product tags for this product
- const allProductTags = await getAllProductTagsForCache()
- const productTagNames = allProductTags
- .filter(t => t.productId === id)
- .map(t => t.tagName)
-
- return {
- id: product.id,
- name: product.name,
- shortDescription: product.shortDescription,
- longDescription: product.longDescription,
- price: product.price.toString(),
- marketPrice: product.marketPrice?.toString() || null,
- unitNotation: product.unit.shortNotation,
- images: signedImages,
- isOutOfStock: product.isOutOfStock,
- store: store
- ? { id: store.id, name: store.name, description: store.description }
- : null,
- incrementStep: product.incrementStep,
- productQuantity: product.productQuantity,
- isFlashAvailable: product.isFlashAvailable,
- flashPrice: product.flashPrice?.toString() || null,
- deliverySlots: productSlots.map((s) => ({
- id: s.id,
- deliveryTime: s.deliveryTime,
- freezeTime: s.freezeTime,
- isCapacityFull: s.isCapacityFull,
- })),
- specialDeals: productDeals.map((d) => ({
- quantity: d.quantity.toString(),
- price: d.price.toString(),
- validTill: d.validTill,
- })),
- productTags: productTagNames,
- }
+ const allProducts = await getAllProducts()
+ const product = allProducts.find(p => p.id === id)
+ return product || null
} catch (error) {
- console.error(`Error getting product ${id}:`, error)
+ console.error('Error getting product by ID:', error)
return null
}
}
@@ -250,17 +191,17 @@ export async function getAllProducts(): Promise {
const allDeliverySlots = await getAllDeliverySlotsForCache()
const deliverySlotsMap = new Map()
for (const slot of allDeliverySlots) {
- if (!deliverySlotsMap.has(slot.productId))
- deliverySlotsMap.set(slot.productId, [])
- deliverySlotsMap.get(slot.productId)!.push(slot)
+ if (!deliverySlotsMap.has(slot.skuId))
+ deliverySlotsMap.set(slot.skuId, [])
+ deliverySlotsMap.get(slot.skuId)!.push(slot)
}
const allSpecialDeals = await getAllSpecialDealsForCache()
const specialDealsMap = new Map()
for (const deal of allSpecialDeals) {
- if (!specialDealsMap.has(deal.productId))
- specialDealsMap.set(deal.productId, [])
- specialDealsMap.get(deal.productId)!.push(deal)
+ if (!specialDealsMap.has(deal.skuId))
+ specialDealsMap.set(deal.skuId, [])
+ specialDealsMap.get(deal.skuId)!.push(deal)
}
const allProductTags = await getAllProductTagsForCache()
@@ -281,7 +222,7 @@ export async function getAllProducts(): Promise {
: null
const deliverySlots = deliverySlotsMap.get(product.id) || []
const specialDeals = specialDealsMap.get(product.id) || []
- const productTags = productTagsMap.get(product.id) || []
+ const productTags = productTagsMap.get(product.productId) || []
products.push({
id: product.id,
@@ -290,7 +231,7 @@ export async function getAllProducts(): Promise {
longDescription: product.longDescription,
price: product.price.toString(),
marketPrice: product.marketPrice?.toString() || null,
- unitNotation: product.unitShortNotation,
+ unitNotation: product.unitNotation,
images: signedImages,
isOutOfStock: product.isOutOfStock,
store: store
diff --git a/apps/backend/src/trpc/apis/common-apis/common.ts b/apps/backend/src/trpc/apis/common-apis/common.ts
index 2869025..b9522fc 100644
--- a/apps/backend/src/trpc/apis/common-apis/common.ts
+++ b/apps/backend/src/trpc/apis/common-apis/common.ts
@@ -1,6 +1,6 @@
import { router, publicProcedure } from '@/src/trpc/trpc-index'
import {
- getSuspendedProductIds,
+ getSuspendedSkuIds,
getNextDeliveryDateWithCapacity,
getStoresSummary,
getAllSkusSummary as getAllSkusSummaryInDb,
@@ -30,10 +30,10 @@ export async function scaffoldProducts() {
.where(eq(productInfo.isSuspended, true));
*/
- const suspendedProductIds = new Set(await getSuspendedProductIds());
+ const suspendedSkuIds = new Set(await getSuspendedSkuIds());
// Filter out suspended products
- products = products.filter(product => !suspendedProductIds.has(product.id));
+ products = products.filter(product => !suspendedSkuIds.has(product.id));
// Format products to match the expected response structure
const formattedProducts = await Promise.all(
diff --git a/apps/backend/src/trpc/apis/user-apis/apis/order.ts b/apps/backend/src/trpc/apis/user-apis/apis/order.ts
index 1df4cd4..32548fc 100644
--- a/apps/backend/src/trpc/apis/user-apis/apis/order.ts
+++ b/apps/backend/src/trpc/apis/user-apis/apis/order.ts
@@ -46,7 +46,7 @@ import type {
const placeOrderUtil = async (params: {
userId: number;
selectedItems: Array<{
- productId: number;
+ skuId: number;
quantity: number;
slotId: number | null;
}>;
@@ -86,7 +86,7 @@ const placeOrderUtil = async (params: {
const ordersBySlot = new Map<
number | null,
Array<{
- productId: number;
+ skuId: number;
quantity: number;
slotId: number | null;
product: Awaited>;
@@ -94,9 +94,9 @@ const placeOrderUtil = async (params: {
>();
for (const item of selectedItems) {
- const product = await getOrderProductById(item.productId);
+ const product = await getOrderProductById(item.skuId);
if (!product) {
- throw new ApiError(`Product ${item.productId} not found`, 400);
+ throw new ApiError(`Product ${item.skuId} not found`, 400);
}
if (!ordersBySlot.has(item.slotId)) {
@@ -107,9 +107,9 @@ const placeOrderUtil = async (params: {
if (params.isFlash) {
for (const item of selectedItems) {
- const product = await getOrderProductById(item.productId);
+ const product = await getOrderProductById(item.skuId);
if (!product?.isFlashAvailable) {
- throw new ApiError(`Product ${item.productId} is not available for flash delivery`, 400);
+ throw new ApiError(`Product ${item.skuId} is not available for flash delivery`, 400);
}
}
}
@@ -198,7 +198,7 @@ const placeOrderUtil = async (params: {
return {
orderId: 0,
- productId: item.productId,
+ skuId: item.skuId,
quantity: item.quantity.toString(),
price: priceString,
discountedPrice: priceString,
@@ -225,7 +225,7 @@ const placeOrderUtil = async (params: {
await deleteUserCartItemsForOrder(
userId,
- selectedItems.map((item) => item.productId)
+ selectedItems.map((item) => item.skuId)
);
if (appliedCoupon && createdOrders.length > 0) {
@@ -252,7 +252,7 @@ export const orderRouter = router({
z.object({
selectedItems: z.array(
z.object({
- productId: z.number().int().positive(),
+ skuId: z.number().int().positive(),
quantity: z.number().int().positive(),
slotId: z.union([z.number().int(), z.null()]),
})
@@ -373,13 +373,14 @@ export const orderRouter = router({
const items = await Promise.all(
order.orderItems.map(async (item) => {
- const signedImages = item.product.images
+ const signedImages = item.sku?.images
? scaffoldAssetUrl(
- item.product.images as string[]
+ item.sku.images as string[]
)
: [];
return {
- productName: item.product.name,
+ productName: item.sku?.product?.name || 'Unknown',
+ skuName: item.sku?.name || null,
quantity: parseFloat(item.quantity),
price: parseFloat(item.price.toString()),
discountedPrice: parseFloat(
@@ -512,13 +513,14 @@ export const orderRouter = router({
const items = await Promise.all(
order.orderItems.map(async (item) => {
- const signedImages = item.product.images
+ const signedImages = item.sku?.images
? scaffoldAssetUrl(
- item.product.images as string[]
+ item.sku.images as string[]
)
: [];
return {
- productName: item.product.name,
+ productName: item.sku?.product?.name || 'Unknown',
+ skuName: item.sku?.name || null,
quantity: parseFloat(item.quantity),
price: parseFloat(item.price.toString()),
discountedPrice: parseFloat(
diff --git a/apps/user-ui/app/(drawer)/(tabs)/flash-delivery/(products)/index.tsx b/apps/user-ui/app/(drawer)/(tabs)/flash-delivery/(products)/index.tsx
index 8143020..8bd2651 100644
--- a/apps/user-ui/app/(drawer)/(tabs)/flash-delivery/(products)/index.tsx
+++ b/apps/user-ui/app/(drawer)/(tabs)/flash-delivery/(products)/index.tsx
@@ -26,7 +26,7 @@ const FlashAddToCartDialog = () => {
React.useEffect(() => {
if (isOpen && product) {
addToCart.mutate(
- { productId: product.id, quantity: 1, slotId: 0 },
+ { skuId: product.id, quantity: 1, slotId: 0 },
{
onSuccess: () => {
clearAddedFlashProduct();
diff --git a/apps/user-ui/components/BannerCarousel.tsx b/apps/user-ui/components/BannerCarousel.tsx
index 893f8b0..6d0a811 100644
--- a/apps/user-ui/components/BannerCarousel.tsx
+++ b/apps/user-ui/components/BannerCarousel.tsx
@@ -12,7 +12,7 @@ interface Banner {
name: string;
imageUrl: string;
description?: string | null;
- productIds?: number[] | null;
+ skuIds?: number[] | null;
redirectUrl?: string | null;
serialNum?: number | null;
isActive: boolean;
@@ -61,13 +61,13 @@ export default function BannerCarousel() {
if (error || !banners || banners.length === 0) return null;
const handleBannerPress = (banner: Banner) => {
- if (banner.productIds && banner.productIds.length > 0) {
+ if (banner.skuIds && banner.skuIds.length > 0) {
// Navigate to the first product's detail page
- router.push(`/(drawer)/(tabs)/home/product-detail/${banner.productIds[0]}`);
+ router.push(`/(drawer)/(tabs)/home/product-detail/${banner.skuIds[0]}`);
} else if (banner.redirectUrl) {
// Handle external URL - could open in browser or handle deep links
}
- // If no productIds or redirectUrl, banner is just for display
+ // If no skuIds or redirectUrl, banner is just for display
};
const handleScroll = (event: NativeSyntheticEvent) => {
diff --git a/apps/user-ui/components/PaymentAndOrderComponent.tsx b/apps/user-ui/components/PaymentAndOrderComponent.tsx
index 12578f7..a088e46 100644
--- a/apps/user-ui/components/PaymentAndOrderComponent.tsx
+++ b/apps/user-ui/components/PaymentAndOrderComponent.tsx
@@ -128,10 +128,10 @@ const PaymentAndOrderComponent: React.FC = ({
const availableItems = cartItems
.filter(item => {
- if (productSlotsMap[item.productId]?.isOutOfStock) return false;
+ if (productSlotsMap[item.skuId]?.isOutOfStock) return false;
// For flash delivery, check if product supports flash delivery
if (isFlashDelivery) {
- return flashEligibleProductIds.has(item.productId);
+ return flashEligibleProductIds.has(item.skuId);
}
// For regular delivery, only include items with assigned slots
return selectedSlots[item.id];
@@ -150,7 +150,7 @@ const PaymentAndOrderComponent: React.FC = ({
selectedItems: availableItems.map(itemId => {
const item = cartItems.find(cartItem => cartItem.id === itemId);
return {
- productId: item.productId,
+ skuId: item.skuId,
quantity: item.quantity,
slotId: isFlashDelivery ? null : selectedSlots[itemId]
};
diff --git a/apps/user-ui/components/ProductCard.tsx b/apps/user-ui/components/ProductCard.tsx
index 8ee25d1..6d3e401 100644
--- a/apps/user-ui/components/ProductCard.tsx
+++ b/apps/user-ui/components/ProductCard.tsx
@@ -78,7 +78,7 @@ const ProductCard: React.FC = ({
}) || {};
// Find current quantity from cart data
- const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id);
+ const cartItem = cartData?.items?.find((cartItem: any) => cartItem.skuId === item.id);
const quantity = cartItem?.quantity || 0;
// Get slots data from central store
diff --git a/apps/user-ui/components/ProductDetail.tsx b/apps/user-ui/components/ProductDetail.tsx
index d4ce7a5..02205ca 100644
--- a/apps/user-ui/components/ProductDetail.tsx
+++ b/apps/user-ui/components/ProductDetail.tsx
@@ -83,7 +83,7 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver
}, [slotsData, productDetail])
// Find current quantity from cart data
- const cartItem = productDetail ? cartData?.data?.items?.find((item: any) => item.productId === productDetail.id) : null;
+ const cartItem = productDetail ? cartData?.data?.items?.find((item: any) => item.skuId === productDetail.id) : null;
const quantity = cartItem?.quantity || 0;
const handleQuantityChange = (newQuantity: number) => {
@@ -143,7 +143,7 @@ const ProductDetail: React.FC = ({ productId, isFlashDeliver
};
const handleSlotAddToCart = (productId: number, selectedSlotId: number) => {
- const cartItem = cartData.data?.items?.find((item: any) => item.productId === productId);
+ const cartItem = cartData.data?.items?.find((item: any) => item.skuId === productId);
setIsLoadingDialogOpen(true);
if (cartItem) {
removeFromCart.mutate(
diff --git a/apps/user-ui/components/SlotSpecificView.tsx b/apps/user-ui/components/SlotSpecificView.tsx
index 7798640..f4977e8 100644
--- a/apps/user-ui/components/SlotSpecificView.tsx
+++ b/apps/user-ui/components/SlotSpecificView.tsx
@@ -252,7 +252,7 @@ const CompactProductCard = ({
refetchCart: true,
}, cartType);
- const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id);
+ const cartItem = cartData?.items?.find((cartItem: any) => cartItem.skuId === item.id);
const quantity = cartItem?.quantity || 0;
const isOutOfStock = productSlotsMap[item.id]?.isOutOfStock;
diff --git a/apps/user-ui/components/cart-page.tsx b/apps/user-ui/components/cart-page.tsx
index 0bb4723..1b4b897 100644
--- a/apps/user-ui/components/cart-page.tsx
+++ b/apps/user-ui/components/cart-page.tsx
@@ -49,7 +49,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
} = useGetCart({ refetchOnWindowFocus: true }, cartType);
// Extract product IDs from cart items
- const productIds = cartData?.items.map(item => item.productId) || [];
+ const productIds = cartData?.items.map(item => item.skuId) || [];
// Get cart slots for the products in cart
const { data: slotsData, refetch: refetchSlots, isLoading: isSlotsLoading } = trpc.user.cart.getCartSlots.useQuery(
@@ -106,9 +106,9 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
const baseTotalPrice = useMemo(
() =>
cartItems
- .filter((item) => !productSlotsMap[item.productId]?.isOutOfStock)
+ .filter((item) => !productSlotsMap[item.skuId]?.isOutOfStock)
.reduce((sum, item) => {
- const product = productsById[item.productId];
+ const product = productsById[item.skuId];
const price = product?.price || 0;
return sum + price * (quantities[item.id] || item.quantity);
}, 0),
@@ -208,9 +208,9 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
);
const totalPrice = cartItems
- .filter((item) => !productSlotsMap[item.productId]?.isOutOfStock)
+ .filter((item) => !productSlotsMap[item.skuId]?.isOutOfStock)
.reduce((sum, item) => {
- const product = productsById[item.productId];
+ const product = productsById[item.skuId];
const quantity = quantities[item.id] || item.quantity;
const price = isFlashDelivery ? (product?.flashPrice ?? product?.price ?? 0) : (product?.price || 0);
return sum + Number(price) * quantity;
@@ -282,7 +282,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
const finalTotalWithDelivery = finalTotal + deliveryCharge;
- const hasAvailableItems = cartItems.some(item => !productSlotsMap[item.productId]?.isOutOfStock);
+ const hasAvailableItems = cartItems.some(item => !productSlotsMap[item.skuId]?.isOutOfStock);
useEffect(() => {
const initial: Record = {};
@@ -308,7 +308,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
if (isFlashDelivery) {
newSelectedSlots[item.id] = 0;
} else {
- const productSlots = slotsData?.[item.productId];
+ const productSlots = slotsData?.[item.skuId];
if (!productSlots || productSlots.length === 0) return;
const now = dayjs();
@@ -416,11 +416,11 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
<>
{cartItems.map((item, index) => {
- const productSlots = getAvailableSlotsForProduct(item.productId);
+ const productSlots = getAvailableSlotsForProduct(item.skuId);
const selectedSlotForItem = selectedSlots[item.id];
- const isFlashEligible = isFlashDelivery ? flashEligibleProductIds.has(item.productId) : true;
- const product = productsById[item.productId];
- const productSlotInfo = productSlotsMap[item.productId];
+ const isFlashEligible = isFlashDelivery ? flashEligibleProductIds.has(item.skuId) : true;
+ const product = productsById[item.skuId];
+ const productSlotInfo = productSlotsMap[item.skuId];
// const isAvailable = (productSlots.length > 0 || isFlashDelivery) && !item.product?.isOutOfStock && isFlashEligible;
let isAvailable = true;
@@ -687,7 +687,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
{productSlotInfo?.isOutOfStock
? "Out of Stock"
- : isFlashDelivery && !flashEligibleProductIds.has(item.productId)
+ : isFlashDelivery && !flashEligibleProductIds.has(item.skuId)
? "Not available for flash delivery. Please remove"
: "No delivery slots available"}
@@ -919,10 +919,10 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
onPress={() => {
const availableItems = cartItems
.filter(item => {
- if (productSlotsMap[item.productId]?.isOutOfStock) return false;
+ if (productSlotsMap[item.skuId]?.isOutOfStock) return false;
if (isFlashDelivery) {
// Check if product supports flash delivery
- return flashEligibleProductIds.has(item.productId);
+ return flashEligibleProductIds.has(item.skuId);
}
return selectedSlots[item.id]; // Regular delivery requires slot selection
})
@@ -930,8 +930,8 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
if (availableItems.length === 0) {
// Determine why no items are available
- const outOfStockItems = cartItems.filter(item => productSlotsMap[item.productId]?.isOutOfStock);
- const inStockItems = cartItems.filter(item => !productSlotsMap[item.productId]?.isOutOfStock);
+ const outOfStockItems = cartItems.filter(item => productSlotsMap[item.skuId]?.isOutOfStock);
+ const inStockItems = cartItems.filter(item => !productSlotsMap[item.skuId]?.isOutOfStock);
let errorTitle = "Cannot Proceed";
let errorMessage = "";
@@ -943,7 +943,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
} else if (isFlashDelivery) {
// Check if any items are flash-eligible
const flashEligibleItems = inStockItems.filter(item =>
- flashEligibleProductIds.has(item.productId)
+ flashEligibleProductIds.has(item.skuId)
);
if (flashEligibleItems.length === 0) {
errorTitle = "1 Hr Delivery Unavailable";
@@ -970,7 +970,7 @@ export default function CartPage({ isFlashDelivery = false }: CartPageProps) {
// Check if there are items without slots (for regular delivery)
if (!isFlashDelivery && availableItems.length < cartItems.length) {
- const itemsWithoutSlots = cartItems.filter(item => !selectedSlots[item.id] && !productSlotsMap[item.productId]?.isOutOfStock);
+ const itemsWithoutSlots = cartItems.filter(item => !selectedSlots[item.id] && !productSlotsMap[item.skuId]?.isOutOfStock);
if (itemsWithoutSlots.length > 0) {
Alert.alert(
"Delivery Slot Required",
diff --git a/apps/user-ui/components/checkout-page.tsx b/apps/user-ui/components/checkout-page.tsx
index f0ec290..637a5cc 100644
--- a/apps/user-ui/components/checkout-page.tsx
+++ b/apps/user-ui/components/checkout-page.tsx
@@ -90,7 +90,7 @@ const CheckoutPage: React.FC = ({ isFlashDelivery = false })
const selectedItems = cartItems.filter(item => {
// For flash delivery, check if product supports flash delivery
if (isFlashDelivery) {
- return flashEligibleProductIds.has(item.productId);
+ return flashEligibleProductIds.has(item.skuId);
}
// For regular delivery, only include items with assigned slots
return selectedSlots[item.id];
@@ -132,10 +132,10 @@ const CheckoutPage: React.FC = ({ isFlashDelivery = false })
const totalPrice = selectedItems
- .filter((item) => !productSlotsMap[item.productId]?.isOutOfStock)
+ .filter((item) => !productSlotsMap[item.skuId]?.isOutOfStock)
.reduce(
(sum, item) => {
- const product = productsById[item.productId];
+ const product = productsById[item.skuId];
const price = isFlashDelivery ? (product?.flashPrice ?? product?.price ?? 0) : (product?.price || 0);
return sum + price * item.quantity;
},
diff --git a/apps/user-ui/components/floating-cart-bar.tsx b/apps/user-ui/components/floating-cart-bar.tsx
index 044e055..717278e 100644
--- a/apps/user-ui/components/floating-cart-bar.tsx
+++ b/apps/user-ui/components/floating-cart-bar.tsx
@@ -122,17 +122,17 @@ const FloatingCartBar: React.FC = ({
const itemsToUpdate = cartItems.filter(item => {
if (isFlashDelivery || !item.slotId) return false;
- const availableSlots = productSlotsMap[item.productId]?.slots || [];
+ const availableSlots = productSlotsMap[item.skuId]?.slots || [];
const isSlotAvailable = availableSlots.some((slot) => slot.id === item.slotId);
return !isSlotAvailable;
});
itemsToUpdate.forEach((item) => {
- const availableSlots = productSlotsMap[item.productId]?.slots || [];
+ const availableSlots = productSlotsMap[item.skuId]?.slots || [];
if (availableSlots.length > 0 && !isFlashDelivery) {
const nearestSlotId = availableSlots[0].id;
removeFromCart.mutate({ itemId: item.id });
- addToCartHook.addToCart(item.productId, item.quantity, nearestSlotId);
+ addToCartHook.addToCart(item.skuId, item.quantity, nearestSlotId);
}
});
}, []);
@@ -143,7 +143,7 @@ const FloatingCartBar: React.FC = ({
// Calculate total cart value and free delivery info
const totalCartValue = cartItems.reduce(
(sum, item) => {
- const product = productsById[item.productId];
+ const product = productsById[item.skuId];
const basePrice = product?.price ?? 0;
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice;
return sum + price * item.quantity;
@@ -268,20 +268,20 @@ const FloatingCartBar: React.FC = ({
- {formatQuantity(productsById[item.productId]?.productQuantity || 1, productsById[item.productId]?.unitNotation || '').display}
+ {formatQuantity(productsById[item.skuId]?.productQuantity || 1, productsById[item.skuId]?.unitNotation || '').display}
= ({
updateCartItem.mutate({ itemId: item.id, quantity: value });
}
}}
- step={productsById[item.productId]?.incrementStep || 1}
+ step={productsById[item.skuId]?.incrementStep || 1}
showUnits={true}
- // unit={productsById[item.productId]?.unitNotation}
+ // unit={productsById[item.skuId]?.unitNotation}
/>
- {item.slotId && slotsData && productSlotsMap[item.productId] && (
+ {item.slotId && slotsData && productSlotsMap[item.skuId] && (
{
+ options={(productSlotsMap[item.skuId]?.slots || []).map((slot) => {
return {
label: slot ? formatTimeRange(slot.deliveryTime) : "N/A",
value: slot.id,
@@ -313,7 +313,7 @@ const FloatingCartBar: React.FC = ({
const newSlot = slotsData.slots.find(s => s.id === val);
if (!newSlot) return;
- const productId = item.productId;
+ const productId = item.skuId;
const quantity = item.quantity;
const itemId = item.id;
const slotId = typeof val === 'number' ? val : Number(val);
@@ -340,7 +340,7 @@ const FloatingCartBar: React.FC = ({
)}
ā¹{(() => {
- const product = productsById[item.productId];
+ const product = productsById[item.skuId];
const basePrice = product?.price ?? 0;
const price = isFlashDelivery ? (product?.flashPrice ?? basePrice) : basePrice;
return price * item.quantity;
diff --git a/apps/user-ui/hooks/cart-query-hooks.tsx b/apps/user-ui/hooks/cart-query-hooks.tsx
index b63bfda..09b7003 100644
--- a/apps/user-ui/hooks/cart-query-hooks.tsx
+++ b/apps/user-ui/hooks/cart-query-hooks.tsx
@@ -13,7 +13,7 @@ const getCartStorageKey = (cartType: CartType = "regular"): string => {
interface LocalCartItem {
id: number;
- productId: number;
+ skuId: number;
quantity: number;
slotId: number;
addedAt: string;
@@ -35,7 +35,7 @@ interface ProductSummary {
export interface CartItem {
id: number;
- productId: number;
+ skuId: number;
quantity: number;
addedAt: string;
subtotal: number;
@@ -66,7 +66,7 @@ interface UseGetCartReturn {
}
interface AddToCartVariables {
- productId: number;
+ skuId: number;
quantity: number;
slotId: number;
}
@@ -94,8 +94,8 @@ interface UseAddToCartReturn {
isLoading: boolean;
error: Error | null;
data: LocalCartItem[] | undefined;
- addToCart: (productId: number, quantity?: number, slotId?: number, onSettled?: (data: LocalCartItem[] | undefined, error: Error | null) => void) => void;
- addToCartAsync: (productId: number, quantity?: number, slotId?: number) => Promise;
+ addToCart: (skuId: number, quantity?: number, slotId?: number, onSettled?: (data: LocalCartItem[] | undefined, error: Error | null) => void) => void;
+ addToCartAsync: (skuId: number, quantity?: number, slotId?: number) => Promise;
}
interface UseUpdateCartItemReturn {
@@ -135,9 +135,9 @@ const getNextCartItemId = (items: LocalCartItem[]): number => {
return maxId + 1;
};
-const addToLocalCart = async (productId: number, quantity: number, slotId: number | undefined, cartType: CartType = "regular"): Promise => {
+const addToLocalCart = async (skuId: number, quantity: number, slotId: number | undefined, cartType: CartType = "regular"): Promise => {
const items = await getLocalCart(cartType);
- const existingIndex = items.findIndex(item => item.productId === productId);
+ const existingIndex = items.findIndex(item => item.skuId === skuId);
if (existingIndex >= 0) {
items[existingIndex].quantity += quantity;
@@ -148,7 +148,7 @@ const addToLocalCart = async (productId: number, quantity: number, slotId: numbe
const newId = getNextCartItemId(items);
const cartItem: LocalCartItem = {
id: newId,
- productId,
+ skuId,
quantity,
slotId: slotId ?? 0,
addedAt: new Date().toISOString(),
@@ -211,14 +211,14 @@ export function useGetCart(options: UseGetCartOptions = {}, cartType: CartType =
const items: CartItem[] = cartItems
.map((cartItem): CartItem | null => {
- const productBasic = productMap[cartItem.productId];
- const productAvailability = productSlotsMap[cartItem.productId];
+ const productBasic = productMap[cartItem.skuId];
+ const productAvailability = productSlotsMap[cartItem.skuId];
if (!productBasic || !productAvailability) return null;
return {
id: cartItem.id,
- productId: cartItem.productId,
+ skuId: cartItem.skuId,
quantity: cartItem.quantity,
addedAt: cartItem.addedAt,
subtotal: Number(productBasic.price) * cartItem.quantity,
@@ -256,8 +256,8 @@ export function useAddToCart(options: MutationOptions = useMutation({
- mutationFn: async ({ productId, quantity, slotId }: AddToCartVariables): Promise => {
- return await addToLocalCart(productId, quantity, slotId, cartType);
+ mutationFn: async ({ skuId, quantity, slotId }: AddToCartVariables): Promise => {
+ return await addToLocalCart(skuId, quantity, slotId, cartType);
},
onSuccess: (data: LocalCartItem[], variables: AddToCartVariables) => {
queryClient.invalidateQueries({ queryKey: [`local-cart-${cartType}`] });
@@ -274,11 +274,11 @@ export function useAddToCart(options: MutationOptions void): void => {
+ const addToCart = (skuId: number, quantity = 1, slotId?: number, onSettled?: (data: LocalCartItem[] | undefined, error: Error | null) => void): void => {
if (slotId == null) {
throw new Error('slotId is required for adding to cart');
}
- mutation.mutate({ productId, quantity, slotId }, {
+ mutation.mutate({ skuId, quantity, slotId }, {
onSettled: (data: LocalCartItem[] | undefined, error: Error | null) => {
onSettled?.(data, error);
}
@@ -292,11 +292,11 @@ export function useAddToCart(options: MutationOptions => {
+ addToCartAsync: (skuId: number, quantity = 1, slotId?: number): Promise => {
if (slotId == null) {
throw new Error('slotId is required for adding to cart');
}
- return mutation.mutateAsync({ productId, quantity, slotId });
+ return mutation.mutateAsync({ skuId, quantity, slotId });
},
};
}
diff --git a/apps/user-ui/src/components/AddToCartDialog.tsx b/apps/user-ui/src/components/AddToCartDialog.tsx
index 9d72357..9bb9bd2 100644
--- a/apps/user-ui/src/components/AddToCartDialog.tsx
+++ b/apps/user-ui/src/components/AddToCartDialog.tsx
@@ -132,7 +132,7 @@ export default function AddToCartDialog() {
} else {
const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0;
addToCart.mutate(
- { productId: product.id, quantity, slotId },
+ { skuId: product.id, quantity, slotId },
{ onSuccess: () => clearAddedToCartProduct() }
);
}
diff --git a/packages/db_helper_sqlite/drizzle/0002_sku_split.sql b/packages/db_helper_sqlite/drizzle/0002_sku_split.sql
index fcc01de..6d7c60b 100644
--- a/packages/db_helper_sqlite/drizzle/0002_sku_split.sql
+++ b/packages/db_helper_sqlite/drizzle/0002_sku_split.sql
@@ -222,7 +222,18 @@ ALTER TABLE `coupons` RENAME COLUMN `product_ids` TO `sku_ids`;
ALTER TABLE `reserved_coupons` RENAME COLUMN `product_ids` TO `sku_ids`;
ALTER TABLE `vendor_snippets` RENAME COLUMN `product_ids` TO `sku_ids`;
--- 8. Clean up helper table.
+-- 8. Update popularItems in key_val_store from product IDs to SKU IDs.
+UPDATE `key_val_store`
+SET `value` = (
+ SELECT json_group_array(`m`.`sku_id`)
+ FROM json_each(`key_val_store`.`value`) AS `je`
+ JOIN `__product_to_sku` `m` ON `m`.`product_id` = `je`.`value`
+)
+WHERE `key` = 'popularItems'
+ AND `value` IS NOT NULL
+ AND `value` LIKE '[%';
+
+-- 9. Clean up helper table.
DROP TABLE `__product_to_sku`;
-- PRAGMA foreign_keys=ON;
diff --git a/packages/db_helper_sqlite/index.ts b/packages/db_helper_sqlite/index.ts
index 789b16d..703feb7 100644
--- a/packages/db_helper_sqlite/index.ts
+++ b/packages/db_helper_sqlite/index.ts
@@ -311,7 +311,7 @@ export {
cancelOrderTransaction as cancelUserOrderTransaction,
updateOrderNotes as updateUserOrderNotes,
getRecentlyDeliveredOrderIds as getUserRecentlyDeliveredOrderIds,
- getProductIdsFromOrders as getUserProductIdsFromOrders,
+ getSkuIdsFromOrders as getUserProductIdsFromOrders,
getProductsForRecentOrders as getUserProductsForRecentOrders,
// Post-order handler helpers
getOrdersByIdsWithFullData,
@@ -364,7 +364,7 @@ export {
// Common API Helpers
export {
- getSuspendedProductIds,
+ getSuspendedSkuIds,
getNextDeliveryDateWithCapacity,
} from './src/user-apis/product'
diff --git a/packages/db_helper_sqlite/src/admin-apis/product.ts b/packages/db_helper_sqlite/src/admin-apis/product.ts
index c2f5c10..a8102b6 100644
--- a/packages/db_helper_sqlite/src/admin-apis/product.ts
+++ b/packages/db_helper_sqlite/src/admin-apis/product.ts
@@ -792,12 +792,12 @@ export async function updateProductPrices(updates: Array<{
}
const productIds = updates.map((update) => update.productId)
- const existingProducts = await db.query.productInfo.findMany({
- where: inArray(productInfo.id, productIds),
+ const existingSkus = await db.query.productSkus.findMany({
+ where: inArray(productSkus.id, productIds),
columns: { id: true },
}) as Array<{ id: number }>
- const existingIds = new Set(existingProducts.map((product: { id: number }) => product.id))
+ const existingIds = new Set(existingSkus.map((sku: { id: number }) => sku.id))
const invalidIds = productIds.filter((id) => !existingIds.has(id))
if (invalidIds.length > 0) {
@@ -806,7 +806,7 @@ export async function updateProductPrices(updates: Array<{
const updatePromises = updates.map((update) => {
const { productId, price, marketPrice, flashPrice, isFlashAvailable } = update
- const updateData: Partial> = {}
+ const updateData: any = {}
if (price !== undefined) updateData.price = price.toString()
if (marketPrice !== undefined) updateData.marketPrice = marketPrice === null ? null : marketPrice.toString()
@@ -814,9 +814,9 @@ export async function updateProductPrices(updates: Array<{
if (isFlashAvailable !== undefined) updateData.isFlashAvailable = isFlashAvailable
return db
- .update(productInfo)
+ .update(productSkus)
.set(updateData)
- .where(eq(productInfo.id, productId))
+ .where(eq(productSkus.id, productId))
})
await Promise.all(updatePromises)
diff --git a/packages/db_helper_sqlite/src/stores/store-helpers.ts b/packages/db_helper_sqlite/src/stores/store-helpers.ts
index 4629d5c..90f5701 100644
--- a/packages/db_helper_sqlite/src/stores/store-helpers.ts
+++ b/packages/db_helper_sqlite/src/stores/store-helpers.ts
@@ -5,7 +5,8 @@ import { db } from '../db/db_index'
import {
homeBanners,
productInfo,
- units,
+ productSkus,
+ skuFeatures,
deliverySlotInfo,
specialDeals,
storeInfo,
@@ -24,7 +25,7 @@ export interface BannerData {
name: string
imageUrl: string | null
serialNum: number | null
- productIds: number[] | null
+ skuIds: number[] | null
createdAt: Date
}
@@ -41,7 +42,9 @@ export async function getAllBannersForCache(): Promise {
export interface ProductBasicData {
id: number
+ productId: number
name: string
+ skuName: string | null
shortDescription: string | null
longDescription: string | null
price: string
@@ -49,7 +52,7 @@ export interface ProductBasicData {
images: unknown
isOutOfStock: boolean
storeId: number | null
- unitShortNotation: string
+ unitNotation: string
incrementStep: number
productQuantity: number
isFlashAvailable: boolean
@@ -63,7 +66,7 @@ export interface StoreBasicData {
}
export interface DeliverySlotData {
- productId: number
+ skuId: number
id: number
deliveryTime: Date
freezeTime: Date
@@ -71,44 +74,64 @@ export interface DeliverySlotData {
}
export interface SpecialDealData {
- productId: number
+ skuId: number
quantity: string
price: string
validTill: Date
}
+export async function getAllSpecialDealsForCache(): Promise {
+ const results = await db
+ .select({
+ skuId: specialDeals.skuId,
+ quantity: specialDeals.quantity,
+ price: specialDeals.price,
+ validTill: specialDeals.validTill,
+ })
+ .from(specialDeals)
+ .where(gt(specialDeals.validTill, sql`CURRENT_TIMESTAMP`))
+
+ return results.map((deal) => ({
+ ...deal,
+ quantity: String(deal.quantity ?? '0'),
+ price: String(deal.price ?? '0'),
+ }))
+}
+
export interface ProductTagData {
productId: number
tagName: string
}
export async function getAllProductsForCache(): Promise {
- const results = await db
- .select({
- id: productInfo.id,
- name: productInfo.name,
- shortDescription: productInfo.shortDescription,
- longDescription: productInfo.longDescription,
- price: productInfo.price,
- marketPrice: productInfo.marketPrice,
- images: productInfo.images,
- isOutOfStock: productInfo.isOutOfStock,
- storeId: productInfo.storeId,
- unitShortNotation: units.shortNotation,
- incrementStep: productInfo.incrementStep,
- productQuantity: productInfo.productQuantity,
- isFlashAvailable: productInfo.isFlashAvailable,
- flashPrice: productInfo.flashPrice,
- })
- .from(productInfo)
- .innerJoin(units, eq(productInfo.unitId, units.id))
+ const skus = await db.query.productSkus.findMany({
+ with: {
+ product: true,
+ features: true,
+ },
+ })
- return results.map((product) => ({
- ...product,
- price: String(product.price ?? '0'),
- marketPrice: product.marketPrice ? String(product.marketPrice) : null,
- flashPrice: product.flashPrice ? String(product.flashPrice) : null,
- }))
+ return skus.map((sku) => {
+ const features = sku.features || []
+ return {
+ id: sku.id,
+ productId: sku.productId,
+ name: sku.product?.name ?? 'Unknown',
+ skuName: sku.name ?? null,
+ shortDescription: sku.product?.shortDescription ?? null,
+ longDescription: sku.product?.longDescription ?? null,
+ price: String(sku.price ?? '0'),
+ marketPrice: sku.marketPrice ? String(sku.marketPrice) : null,
+ images: sku.images,
+ isOutOfStock: sku.isOutOfStock,
+ storeId: sku.product?.storeId ?? null,
+ unitNotation: features.map((f) => f.featureValue).join(' '),
+ incrementStep: sku.product?.incrementStep ?? 1,
+ productQuantity: 1,
+ isFlashAvailable: sku.isFlashAvailable,
+ flashPrice: sku.flashPrice ? String(sku.flashPrice) : null,
+ }
+ })
}
export async function getAllStoresForCache(): Promise {
@@ -126,13 +149,12 @@ export async function getAllDeliverySlotsForCache(): Promise
),
})
- // Flatten slots with their product IDs
const result: DeliverySlotData[] = []
for (const slot of slots) {
- const productIds = slot.productIds || []
- for (const productId of productIds) {
+ const skuIds = slot.skuIds || []
+ for (const skuId of skuIds) {
result.push({
- productId,
+ skuId,
id: slot.id,
deliveryTime: slot.deliveryTime,
freezeTime: slot.freezeTime,
@@ -144,24 +166,6 @@ export async function getAllDeliverySlotsForCache(): Promise
return result
}
-export async function getAllSpecialDealsForCache(): Promise {
- const results = await db
- .select({
- productId: specialDeals.productId,
- quantity: specialDeals.quantity,
- price: specialDeals.price,
- validTill: specialDeals.validTill,
- })
- .from(specialDeals)
- .where(gt(specialDeals.validTill, sql`CURRENT_TIMESTAMP`))
-
- return results.map((deal) => ({
- ...deal,
- quantity: String(deal.quantity ?? '0'),
- price: String(deal.price ?? '0'),
- }))
-}
-
export async function getAllProductTagsForCache(): Promise {
return db
.select({
@@ -224,23 +228,26 @@ export interface SlotWithProductsData {
isCapacityFull: boolean
products: Array<{
id: number
+ productId: number
name: string
+ skuName: string | null
productQuantity: number
shortDescription: string | null
price: string
marketPrice: string | null
- unit: { shortNotation: string } | null
+ unitNotation: string
store: { id: number; name: string; description: string | null } | null
images: unknown
isOutOfStock: boolean
storeId: number | null
+ isFlashAvailable: boolean
+ flashPrice: string | null
}>
}
export async function getAllSlotsWithProductsForCache(): Promise {
const now = new Date()
- // Get all active future slots
const slots = await db.query.deliverySlotInfo.findMany({
where: and(
eq(deliverySlotInfo.isActive, true),
@@ -249,68 +256,64 @@ export async function getAllSlotsWithProductsForCache(): Promise()
+ const allSkuIds = new Set()
for (const slot of slots) {
- for (const productId of (slot.productIds || [])) {
- allProductIds.add(productId)
+ for (const skuId of (slot.skuIds || [])) {
+ allSkuIds.add(skuId)
}
}
- // Fetch all products in one query
- const productIdsArray = Array.from(allProductIds)
- const productIdSet = new Set(productIdsArray);
- // const productsData = productIdsArray.length > 0
- // ? await db.query.productInfo.findMany({
- // where: inArray(productInfo.id, productIdsArray),
- // with: {
- // unit: true,
- // store: true,
- // },
- // })
- // : []
- let productsData = productIdsArray.length > 0
- ? await db.query.productInfo.findMany({
- // where: inArray(productInfo.id, productIdsArray),
- with: {
- unit: true,
- store: true,
+ const skuIdsArray = Array.from(allSkuIds)
+ const skuIdSet = new Set(skuIdsArray)
+
+ let skusData: any[] = []
+ if (skuIdsArray.length > 0) {
+ skusData = await db.query.productSkus.findMany({
+ with: {
+ product: {
+ with: { store: true },
},
- })
- : []
+ features: true,
+ },
+ })
+ skusData = skusData.filter((item: any) => skuIdSet.has(item.id))
+ }
- productsData = productsData.filter(item => productIdSet.has(item.id))
+ const skuMap = new Map(skusData.map((s: any) => [s.id, s]))
- // Create a map for quick lookup
- const productMap = new Map(productsData.map(p => [p.id, p]))
-
- // Build the result
- return slots.map(slot => ({
+ return slots.map((slot) => ({
id: slot.id,
deliveryTime: slot.deliveryTime,
freezeTime: slot.freezeTime,
isActive: slot.isActive,
isCapacityFull: slot.isCapacityFull,
- products: (slot.productIds || [])
- .map(productId => productMap.get(productId))
+ products: (slot.skuIds || [])
+ .map((skuId: number) => skuMap.get(skuId))
.filter((p): p is NonNullable => p != null)
- .map(product => ({
- id: product.id,
- name: product.name,
- productQuantity: product.productQuantity,
- shortDescription: product.shortDescription,
- price: String(product.price ?? '0'),
- marketPrice: product.marketPrice ? String(product.marketPrice) : null,
- unit: product.unit ? { shortNotation: product.unit.shortNotation } : null,
- store: product.store ? {
- id: product.store.id,
- name: product.store.name,
- description: product.store.description
- } : null,
- images: product.images,
- isOutOfStock: product.isOutOfStock,
- storeId: product.storeId,
- })),
+ .map((sku: any) => {
+ const features = sku.features || []
+ return {
+ id: sku.id,
+ productId: sku.productId,
+ name: sku.product?.name ?? 'Unknown',
+ skuName: sku.name ?? null,
+ productQuantity: 1,
+ shortDescription: sku.product?.shortDescription ?? null,
+ price: String(sku.price ?? '0'),
+ marketPrice: sku.marketPrice ? String(sku.marketPrice) : null,
+ unitNotation: features.map((f: any) => f.featureValue).join(' '),
+ store: sku.product?.store ? {
+ id: sku.product.store.id,
+ name: sku.product.store.name,
+ description: sku.product.store.description
+ } : null,
+ images: sku.images,
+ isOutOfStock: sku.isOutOfStock,
+ storeId: sku.product?.storeId ?? null,
+ isFlashAvailable: sku.isFlashAvailable,
+ flashPrice: sku.flashPrice ? String(sku.flashPrice) : null,
+ }
+ }),
})) as SlotWithProductsData[]
}
diff --git a/packages/db_helper_sqlite/src/user-apis/order.ts b/packages/db_helper_sqlite/src/user-apis/order.ts
index 81d31c6..acc4bc9 100644
--- a/packages/db_helper_sqlite/src/user-apis/order.ts
+++ b/packages/db_helper_sqlite/src/user-apis/order.ts
@@ -5,6 +5,7 @@ import {
orderStatus,
addresses,
productInfo,
+ productSkus,
paymentInfoTable,
coupons,
couponUsage,
@@ -258,9 +259,9 @@ export async function getAddressByIdAndUser(
})
}
-export async function getProductById(productId: number) {
- return db.query.productInfo.findFirst({
- where: eq(productInfo.id, productId),
+export async function getProductById(skuId: number) {
+ return db.query.productSkus.findFirst({
+ where: eq(productSkus.id, skuId),
})
}
@@ -341,12 +342,12 @@ export async function placeOrderTransaction(params: {
export async function deleteCartItemsForOrder(
userId: number,
- productIds: number[]
+ skuIds: number[]
): Promise {
await db.delete(cartItems).where(
and(
eq(cartItems.userId, userId),
- inArray(cartItems.productId, productIds)
+ inArray(cartItems.skuId, skuIds)
)
)
}
@@ -375,10 +376,16 @@ export async function getOrdersWithRelations(
with: {
orderItems: {
with: {
- product: {
+ sku: {
+ with: {
+ product: {
+ columns: {
+ name: true,
+ },
+ },
+ },
columns: {
id: true,
- name: true,
images: true,
},
},
@@ -451,10 +458,16 @@ export async function getOrderByIdWithRelations(
with: {
orderItems: {
with: {
- product: {
+ sku: {
+ with: {
+ product: {
+ columns: {
+ name: true,
+ },
+ },
+ },
columns: {
id: true,
- name: true,
images: true,
},
},
@@ -610,15 +623,15 @@ export async function getRecentlyDeliveredOrderIds(
return recentOrders.map((order) => order.id)
}
-export async function getProductIdsFromOrders(
+export async function getSkuIdsFromOrders(
orderIds: number[]
): Promise {
const orderItemsResult = await db
- .select({ productId: orderItems.productId })
+ .select({ skuId: orderItems.skuId })
.from(orderItems)
.where(inArray(orderItems.orderId, orderIds))
- return [...new Set(orderItemsResult.map((item) => item.productId))]
+ return [...new Set(orderItemsResult.map((item) => item.skuId))]
}
export interface RecentProductData {
@@ -714,14 +727,17 @@ export async function getOrdersByIdsWithFullData(
},
orderItems: {
with: {
- product: {
+ sku: {
columns: {
name: true,
- productQuantity: true,
+ price: true,
},
with: {
- unit: true
- }
+ product: {
+ columns: { name: true },
+ },
+ features: true,
+ },
},
},
},
diff --git a/packages/db_helper_sqlite/src/user-apis/product.ts b/packages/db_helper_sqlite/src/user-apis/product.ts
index 3373d19..900b7e4 100644
--- a/packages/db_helper_sqlite/src/user-apis/product.ts
+++ b/packages/db_helper_sqlite/src/user-apis/product.ts
@@ -1,5 +1,5 @@
import { db } from '../db/db_index'
-import { deliverySlotInfo, productInfo, productReviews, productTags, specialDeals, storeInfo, units, users } from '../db/schema'
+import { deliverySlotInfo, productInfo, productSkus, productReviews, productTags, specialDeals, storeInfo, units, users } from '../db/schema'
import { and, desc, eq, gt, sql } from 'drizzle-orm'
import type { UserProductDetailData, UserProductReview } from '@packages/shared'
@@ -8,42 +8,27 @@ const getStringArray = (value: unknown): string[] | null => {
return value.map((item) => String(item))
}
-export async function getProductDetailById(productId: number): Promise {
- const productData = await db
- .select({
- id: productInfo.id,
- name: productInfo.name,
- shortDescription: productInfo.shortDescription,
- longDescription: productInfo.longDescription,
- price: productInfo.price,
- marketPrice: productInfo.marketPrice,
- images: productInfo.images,
- isOutOfStock: productInfo.isOutOfStock,
- storeId: productInfo.storeId,
- unitShortNotation: units.shortNotation,
- incrementStep: productInfo.incrementStep,
- productQuantity: productInfo.productQuantity,
- isFlashAvailable: productInfo.isFlashAvailable,
- flashPrice: productInfo.flashPrice,
- })
- .from(productInfo)
- .innerJoin(units, eq(productInfo.unitId, units.id))
- .where(eq(productInfo.id, productId))
- .limit(1)
+export async function getProductDetailById(skuId: number): Promise {
+ const sku = await db.query.productSkus.findFirst({
+ where: eq(productSkus.id, skuId),
+ with: {
+ product: true,
+ features: true,
+ },
+ })
- if (productData.length === 0) {
+ if (!sku) {
return null
}
- const product = productData[0]
+ const features = sku.features || []
+ const product = sku.product
- const storeData = product.storeId ? await db.query.storeInfo.findFirst({
+ const storeData = product?.storeId ? await db.query.storeInfo.findFirst({
where: eq(storeInfo.id, product.storeId),
columns: { id: true, name: true, description: true },
}) : null
- // Note: deliverySlots are now fetched from cache in the frontend via useSlots()
- // This avoids expensive database joins on every product detail view
const specialDealsData = await db
.select({
quantity: specialDeals.quantity,
@@ -53,32 +38,32 @@ export async function getProductDetailById(productId: number): Promise f.featureValue).join(' '),
+ images: getStringArray(sku.images),
+ isOutOfStock: sku.isOutOfStock,
store: storeData ? {
id: storeData.id,
name: storeData.name,
description: storeData.description ?? null,
} : null,
- incrementStep: product.incrementStep,
- productQuantity: product.productQuantity,
- isFlashAvailable: product.isFlashAvailable,
- flashPrice: product.flashPrice?.toString() || null,
- deliverySlots: [], // Fetched from cache in frontend via useSlots()
+ incrementStep: product?.incrementStep ?? 1,
+ productQuantity: 1,
+ isFlashAvailable: sku.isFlashAvailable,
+ flashPrice: sku.flashPrice?.toString() || null,
+ deliverySlots: [],
specialDeals: specialDealsData.map((deal) => ({
quantity: String(deal.quantity ?? '0'),
price: String(deal.price ?? '0'),
@@ -126,9 +111,9 @@ export async function getProductReviews(productId: number, limit: number, offset
}
}
-export async function getProductById(productId: number) {
- return db.query.productInfo.findFirst({
- where: eq(productInfo.id, productId),
+export async function getProductById(skuId: number) {
+ return db.query.productSkus.findFirst({
+ where: eq(productSkus.id, skuId),
})
}
@@ -221,20 +206,20 @@ export async function getAllProductsWithUnits(tagId?: number): Promise {
- const suspendedProducts = await db
- .select({ id: productInfo.id })
- .from(productInfo)
- .where(eq(productInfo.isSuspended, true))
+export async function getSuspendedSkuIds(): Promise {
+ const suspendedSkus = await db
+ .select({ id: productSkus.id })
+ .from(productSkus)
+ .where(eq(productSkus.isSuspended, true))
- return suspendedProducts.map(sp => sp.id)
+ return suspendedSkus.map(sp => sp.id)
}
/**
* Get next delivery date for a product (with capacity check)
* This version filters by both isActive AND isCapacityFull
*/
-export async function getNextDeliveryDateWithCapacity(productId: number): Promise {
+export async function getNextDeliveryDateWithCapacity(skuId: number): Promise {
const slots = await db.query.deliverySlotInfo.findMany({
where: and(
eq(deliverySlotInfo.isActive, true),
@@ -244,10 +229,9 @@ export async function getNextDeliveryDateWithCapacity(productId: number): Promis
orderBy: desc(deliverySlotInfo.deliveryTime),
})
- // Find the first slot that contains this product
for (const slot of slots) {
- const productIds = slot.productIds || []
- if (productIds.includes(productId)) {
+ const skuIds = slot.skuIds || []
+ if (skuIds.includes(skuId)) {
return slot.deliveryTime
}
}
diff --git a/packages/db_helper_sqlite/src/user-apis/slots.ts b/packages/db_helper_sqlite/src/user-apis/slots.ts
index 4863a46..8ad89ce 100644
--- a/packages/db_helper_sqlite/src/user-apis/slots.ts
+++ b/packages/db_helper_sqlite/src/user-apis/slots.ts
@@ -1,5 +1,5 @@
import { db } from '../db/db_index'
-import { deliverySlotInfo, productInfo } from '../db/schema'
+import { deliverySlotInfo, productSkus } from '../db/schema'
import { asc, eq } from 'drizzle-orm'
import type { InferSelectModel } from 'drizzle-orm'
import type { UserDeliverySlot, UserSlotAvailability } from '@packages/shared'
@@ -27,20 +27,17 @@ export async function getActiveSlotsList(): Promise {
}
export async function getProductAvailability(): Promise {
- const products = await db
- .select({
- id: productInfo.id,
- name: productInfo.name,
- isOutOfStock: productInfo.isOutOfStock,
- isFlashAvailable: productInfo.isFlashAvailable,
- })
- .from(productInfo)
- .where(eq(productInfo.isSuspended, false))
+ const skus = await db.query.productSkus.findMany({
+ where: eq(productSkus.isSuspended, false),
+ with: {
+ product: { columns: { name: true } },
+ },
+ })
- return products.map((product) => ({
- id: product.id,
- name: product.name,
- isOutOfStock: product.isOutOfStock,
- isFlashAvailable: product.isFlashAvailable,
+ return skus.map((sku) => ({
+ id: sku.id,
+ name: sku.product?.name ?? 'Unknown',
+ isOutOfStock: sku.isOutOfStock,
+ isFlashAvailable: sku.isFlashAvailable,
}))
}
diff --git a/packages/db_helper_sqlite/src/user-apis/stores.ts b/packages/db_helper_sqlite/src/user-apis/stores.ts
index 076b4ce..9383afb 100644
--- a/packages/db_helper_sqlite/src/user-apis/stores.ts
+++ b/packages/db_helper_sqlite/src/user-apis/stores.ts
@@ -1,22 +1,10 @@
import { db } from '../db/db_index'
-import { productInfo, storeInfo, units } from '../db/schema'
-import { and, eq, sql } from 'drizzle-orm'
+import { productInfo, productSkus, storeInfo } from '../db/schema'
+import { and, eq, inArray, sql } from 'drizzle-orm'
import type { InferSelectModel } from 'drizzle-orm'
import type { UserStoreDetailData, UserStoreProductData, UserStoreSummaryData, StoreSummary } from '@packages/shared'
type StoreRow = InferSelectModel
-type StoreProductRow = {
- id: number
- name: string
- shortDescription: string | null
- price: string | null
- marketPrice: string | null
- images: unknown
- isOutOfStock: boolean
- incrementStep: number
- unitShortNotation: string
- productQuantity: number
-}
const getStringArray = (value: unknown): string[] | null => {
if (!Array.isArray(value)) return null
@@ -24,32 +12,59 @@ const getStringArray = (value: unknown): string[] | null => {
}
export async function getStoreSummaries(): Promise {
+ // Count SKUs per store, filtering by suspended SKUs
const storesData = await db
.select({
id: storeInfo.id,
name: storeInfo.name,
description: storeInfo.description,
imageUrl: storeInfo.imageUrl,
- productCount: sql`count(${productInfo.id})`.as('productCount'),
+ productCount: sql`count(${productSkus.id})`.as('productCount'),
})
.from(storeInfo)
.leftJoin(
productInfo,
- and(eq(productInfo.storeId, storeInfo.id), eq(productInfo.isSuspended, false))
+ eq(productInfo.storeId, storeInfo.id)
+ )
+ .leftJoin(
+ productSkus,
+ and(
+ eq(productSkus.productId, productInfo.id),
+ eq(productSkus.isSuspended, false)
+ )
)
.groupBy(storeInfo.id)
const storesWithDetails = await Promise.all(
storesData.map(async (store) => {
- const sampleProducts = await db
- .select({
- id: productInfo.id,
- name: productInfo.name,
- images: productInfo.images,
- })
- .from(productInfo)
- .where(and(eq(productInfo.storeId, store.id), eq(productInfo.isSuspended, false)))
- .limit(3)
+ let sampleProducts: any[] = []
+ // Get sample SKUs from this store
+ if (store.productCount > 0) {
+ const storeProductIds = await db
+ .select({ id: productInfo.id })
+ .from(productInfo)
+ .where(eq(productInfo.storeId, store.id))
+
+ const productIdArr = storeProductIds.map((p) => p.id)
+ if (productIdArr.length > 0) {
+ const skus = await db.query.productSkus.findMany({
+ where: and(
+ inArray(productSkus.productId, productIdArr),
+ eq(productSkus.isSuspended, false)
+ ),
+ with: {
+ product: { columns: { name: true } },
+ },
+ columns: { id: true, images: true, name: true },
+ limit: 3,
+ })
+ sampleProducts = skus.map((sku) => ({
+ id: sku.id,
+ name: sku.product?.name ?? sku.name ?? 'Unknown',
+ images: getStringArray(sku.images),
+ }))
+ }
+ }
return {
id: store.id,
@@ -57,11 +72,7 @@ export async function getStoreSummaries(): Promise {
description: store.description ?? null,
imageUrl: store.imageUrl ?? null,
productCount: store.productCount || 0,
- sampleProducts: sampleProducts.map((product) => ({
- id: product.id,
- name: product.name,
- images: getStringArray(product.images),
- })),
+ sampleProducts,
}
})
)
@@ -84,36 +95,42 @@ export async function getStoreDetail(storeId: number): Promise ({
- id: product.id,
- name: product.name,
- shortDescription: product.shortDescription ?? null,
- price: String(product.price ?? '0'),
- marketPrice: product.marketPrice ? String(product.marketPrice) : null,
- incrementStep: product.incrementStep,
- unit: product.unitShortNotation,
- unitNotation: product.unitShortNotation,
- images: getStringArray(product.images),
- isOutOfStock: product.isOutOfStock,
- productQuantity: product.productQuantity,
- }))
+ const productIdArr = storeProductIds.map((p) => p.id)
+
+ const skus = productIdArr.length > 0
+ ? await db.query.productSkus.findMany({
+ where: and(
+ inArray(productSkus.productId, productIdArr),
+ eq(productSkus.isSuspended, false)
+ ),
+ with: {
+ product: true,
+ features: true,
+ },
+ })
+ : []
+
+ const products: UserStoreProductData[] = skus.map((sku) => {
+ const features = sku.features || []
+ return {
+ id: sku.id,
+ name: sku.product?.name ?? 'Unknown',
+ shortDescription: sku.product?.shortDescription ?? null,
+ price: String(sku.price ?? '0'),
+ marketPrice: sku.marketPrice ? String(sku.marketPrice) : null,
+ incrementStep: sku.product?.incrementStep ?? 1,
+ unit: features.map((f) => f.featureValue).join(' '),
+ unitNotation: features.map((f) => f.featureValue).join(' '),
+ images: getStringArray(sku.images),
+ isOutOfStock: sku.isOutOfStock,
+ productQuantity: 1,
+ }
+ })
return {
store: {
@@ -126,10 +143,6 @@ export async function getStoreDetail(storeId: number): Promise {
return db.query.storeInfo.findMany({
columns: {
diff --git a/packages/shared/types/user.ts b/packages/shared/types/user.ts
index f45bd16..2c19277 100644
--- a/packages/shared/types/user.ts
+++ b/packages/shared/types/user.ts
@@ -121,7 +121,7 @@ export interface UserBanner {
name: string;
imageUrl: string;
description: string | null;
- productIds: number[] | null;
+ skuIds: number[] | null;
redirectUrl: string | null;
serialNum: number | null;
isActive: boolean;