enh
This commit is contained in:
parent
306244e8df
commit
3c836e274d
12 changed files with 2099 additions and 332 deletions
|
|
@ -92,12 +92,17 @@ export {
|
|||
getOrderItemsByOrderIds,
|
||||
getOrderStatusByOrderIds,
|
||||
updateVendorOrderItemPackaging,
|
||||
getVendorOrders,
|
||||
// Product methods
|
||||
getAllProducts,
|
||||
getProductById,
|
||||
deleteProduct,
|
||||
createProduct,
|
||||
updateProduct,
|
||||
toggleProductOutOfStock,
|
||||
updateSlotProducts,
|
||||
getSlotProductIds,
|
||||
getSlotsProductIds,
|
||||
getAllUnits,
|
||||
getAllProductTags,
|
||||
getProductReviews,
|
||||
|
|
@ -108,16 +113,15 @@ export {
|
|||
deleteProductGroup,
|
||||
addProductToGroup,
|
||||
removeProductFromGroup,
|
||||
updateProductPrices,
|
||||
// Slots methods
|
||||
getAllSlots,
|
||||
getSlotById,
|
||||
createSlot,
|
||||
updateSlot,
|
||||
deleteSlot,
|
||||
getSlotProducts,
|
||||
addProductToSlot,
|
||||
removeProductFromSlot,
|
||||
clearSlotProducts,
|
||||
getActiveSlotsWithProducts,
|
||||
getActiveSlots,
|
||||
getSlotsAfterDate,
|
||||
getSlotByIdWithRelations,
|
||||
createSlotWithRelations,
|
||||
updateSlotWithRelations,
|
||||
deleteSlotById,
|
||||
updateSlotCapacity,
|
||||
getSlotDeliverySequence,
|
||||
updateSlotDeliverySequence,
|
||||
|
|
@ -164,6 +168,58 @@ export type {
|
|||
AdminGetAllOrdersResultWithUserId,
|
||||
AdminRebalanceSlotsResult,
|
||||
AdminCancelOrderResult,
|
||||
AdminUnit,
|
||||
AdminProduct,
|
||||
AdminProductWithRelations,
|
||||
AdminProductWithDetails,
|
||||
AdminProductTagInfo,
|
||||
AdminProductTagWithProducts,
|
||||
AdminProductListResponse,
|
||||
AdminProductResponse,
|
||||
AdminDeleteProductResult,
|
||||
AdminToggleOutOfStockResult,
|
||||
AdminUpdateSlotProductsResult,
|
||||
AdminSlotProductIdsResult,
|
||||
AdminSlotsProductIdsResult,
|
||||
AdminProductReview,
|
||||
AdminProductReviewWithSignedUrls,
|
||||
AdminProductReviewsResult,
|
||||
AdminProductReviewResponse,
|
||||
AdminProductGroup,
|
||||
AdminProductGroupsResult,
|
||||
AdminProductGroupResponse,
|
||||
AdminProductGroupInfo,
|
||||
AdminUpdateProductPricesResult,
|
||||
AdminDeliverySlot,
|
||||
AdminSlotProductSummary,
|
||||
AdminSlotWithProducts,
|
||||
AdminSlotWithProductsAndSnippets,
|
||||
AdminSlotWithProductsAndSnippetsBase,
|
||||
AdminSlotsResult,
|
||||
AdminSlotsListResult,
|
||||
AdminSlotResult,
|
||||
AdminSlotCreateResult,
|
||||
AdminSlotUpdateResult,
|
||||
AdminSlotDeleteResult,
|
||||
AdminDeliverySequence,
|
||||
AdminDeliverySequenceResult,
|
||||
AdminUpdateDeliverySequenceResult,
|
||||
AdminUpdateSlotCapacityResult,
|
||||
AdminVendorSnippet,
|
||||
AdminVendorSnippetWithAccess,
|
||||
AdminVendorSnippetWithSlot,
|
||||
AdminVendorSnippetProduct,
|
||||
AdminVendorSnippetWithProducts,
|
||||
AdminVendorSnippetCreateInput,
|
||||
AdminVendorSnippetUpdateInput,
|
||||
AdminVendorSnippetDeleteResult,
|
||||
AdminVendorSnippetOrderProduct,
|
||||
AdminVendorSnippetOrderSummary,
|
||||
AdminVendorSnippetOrdersResult,
|
||||
AdminVendorSnippetOrdersWithSlotResult,
|
||||
AdminVendorOrderSummary,
|
||||
AdminUpcomingSlotsResult,
|
||||
AdminVendorUpdatePackagingResult,
|
||||
} from '@packages/shared';
|
||||
|
||||
export type {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,47 @@
|
|||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||
import { z } from 'zod';
|
||||
import { db } from '@/src/db/db_index'
|
||||
import { productInfo, units, specialDeals, productSlots, productTags, productReviews, users, productGroupInfo, productGroupMembership } from '@/src/db/schema'
|
||||
import { eq, and, inArray, desc, sql } from 'drizzle-orm';
|
||||
import { z } from 'zod'
|
||||
import { ApiError } from '@/src/lib/api-error'
|
||||
import { imageUploadS3, generateSignedUrlsFromS3Urls, getOriginalUrlFromSignedUrl, claimUploadUrl } from '@/src/lib/s3-client'
|
||||
import { deleteS3Image } from '@/src/lib/delete-image'
|
||||
import type { SpecialDeal } from '@/src/db/types'
|
||||
import { generateSignedUrlsFromS3Urls, claimUploadUrl } from '@/src/lib/s3-client'
|
||||
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
||||
import {
|
||||
getAllProducts as getAllProductsInDb,
|
||||
getProductById as getProductByIdInDb,
|
||||
deleteProduct as deleteProductInDb,
|
||||
toggleProductOutOfStock as toggleProductOutOfStockInDb,
|
||||
updateSlotProducts as updateSlotProductsInDb,
|
||||
getSlotProductIds as getSlotProductIdsInDb,
|
||||
getSlotsProductIds as getSlotsProductIdsInDb,
|
||||
getProductReviews as getProductReviewsInDb,
|
||||
respondToReview as respondToReviewInDb,
|
||||
getAllProductGroups as getAllProductGroupsInDb,
|
||||
createProductGroup as createProductGroupInDb,
|
||||
updateProductGroup as updateProductGroupInDb,
|
||||
deleteProductGroup as deleteProductGroupInDb,
|
||||
updateProductPrices as updateProductPricesInDb,
|
||||
} from '@/src/dbService'
|
||||
import type {
|
||||
AdminProductGroupsResult,
|
||||
AdminProductGroupResponse,
|
||||
AdminProductReviewsResult,
|
||||
AdminProductReviewResponse,
|
||||
AdminProductListResponse,
|
||||
AdminProductResponse,
|
||||
AdminDeleteProductResult,
|
||||
AdminToggleOutOfStockResult,
|
||||
AdminUpdateSlotProductsResult,
|
||||
AdminSlotProductIdsResult,
|
||||
AdminSlotsProductIdsResult,
|
||||
AdminUpdateProductPricesResult,
|
||||
} from '@packages/shared'
|
||||
|
||||
|
||||
type CreateDeal = {
|
||||
quantity: number;
|
||||
price: number;
|
||||
validTill: string;
|
||||
};
|
||||
|
||||
export const productRouter = router({
|
||||
getProducts: protectedProcedure
|
||||
.query(async ({ ctx }) => {
|
||||
.query(async (): Promise<AdminProductListResponse> => {
|
||||
const products = await getAllProductsInDb()
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB query:
|
||||
const products = await db.query.productInfo.findMany({
|
||||
orderBy: productInfo.name,
|
||||
with: {
|
||||
|
|
@ -26,28 +49,32 @@ export const productRouter = router({
|
|||
store: true,
|
||||
},
|
||||
});
|
||||
*/
|
||||
|
||||
// Generate signed URLs for all product images
|
||||
const productsWithSignedUrls = await Promise.all(
|
||||
products.map(async (product) => ({
|
||||
...product,
|
||||
images: await generateSignedUrlsFromS3Urls((product.images as string[]) || []),
|
||||
}))
|
||||
);
|
||||
)
|
||||
|
||||
return {
|
||||
products: productsWithSignedUrls,
|
||||
count: productsWithSignedUrls.length,
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
getProductById: protectedProcedure
|
||||
.input(z.object({
|
||||
id: z.number(),
|
||||
}))
|
||||
.query(async ({ input, ctx }) => {
|
||||
.query(async ({ input }): Promise<AdminProductResponse> => {
|
||||
const { id } = input;
|
||||
|
||||
const product = await getProductByIdInDb(id)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const product = await db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, id),
|
||||
with: {
|
||||
|
|
@ -84,15 +111,33 @@ export const productRouter = router({
|
|||
return {
|
||||
product: productWithSignedUrls,
|
||||
};
|
||||
*/
|
||||
|
||||
if (!product) {
|
||||
throw new ApiError('Product not found', 404)
|
||||
}
|
||||
|
||||
const productWithSignedUrls = {
|
||||
...product,
|
||||
images: await generateSignedUrlsFromS3Urls((product.images as string[]) || []),
|
||||
}
|
||||
|
||||
return {
|
||||
product: productWithSignedUrls,
|
||||
}
|
||||
}),
|
||||
|
||||
deleteProduct: protectedProcedure
|
||||
.input(z.object({
|
||||
id: z.number(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input }): Promise<AdminDeleteProductResult> => {
|
||||
const { id } = input;
|
||||
|
||||
const deletedProduct = await deleteProductInDb(id)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB query:
|
||||
const [deletedProduct] = await db
|
||||
.delete(productInfo)
|
||||
.where(eq(productInfo.id, id))
|
||||
|
|
@ -101,22 +146,31 @@ export const productRouter = router({
|
|||
if (!deletedProduct) {
|
||||
throw new ApiError("Product not found", 404);
|
||||
}
|
||||
*/
|
||||
|
||||
if (!deletedProduct) {
|
||||
throw new ApiError('Product not found', 404)
|
||||
}
|
||||
|
||||
// Reinitialize stores to reflect changes
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
message: "Product deleted successfully",
|
||||
};
|
||||
message: 'Product deleted successfully',
|
||||
}
|
||||
}),
|
||||
|
||||
toggleOutOfStock: protectedProcedure
|
||||
.input(z.object({
|
||||
id: z.number(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input }): Promise<AdminToggleOutOfStockResult> => {
|
||||
const { id } = input;
|
||||
|
||||
const updatedProduct = await toggleProductOutOfStockInDb(id)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const product = await db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, id),
|
||||
});
|
||||
|
|
@ -132,14 +186,18 @@ export const productRouter = router({
|
|||
})
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning();
|
||||
*/
|
||||
|
||||
if (!updatedProduct) {
|
||||
throw new ApiError('Product not found', 404)
|
||||
}
|
||||
|
||||
// Reinitialize stores to reflect changes
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
product: updatedProduct,
|
||||
message: `Product marked as ${updatedProduct.isOutOfStock ? 'out of stock' : 'in stock'}`,
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
updateSlotProducts: protectedProcedure
|
||||
|
|
@ -147,13 +205,17 @@ export const productRouter = router({
|
|||
slotId: z.string(),
|
||||
productIds: z.array(z.string()),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input }): Promise<AdminUpdateSlotProductsResult> => {
|
||||
const { slotId, productIds } = input;
|
||||
|
||||
if (!Array.isArray(productIds)) {
|
||||
throw new ApiError("productIds must be an array", 400);
|
||||
}
|
||||
|
||||
const result = await updateSlotProductsInDb(slotId, productIds)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
// Get current associations
|
||||
const currentAssociations = await db.query.productSlots.findMany({
|
||||
where: eq(productSlots.slotId, parseInt(slotId)),
|
||||
|
|
@ -197,15 +259,28 @@ export const productRouter = router({
|
|||
added: productsToAdd.length,
|
||||
removed: productsToRemove.length,
|
||||
};
|
||||
*/
|
||||
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
message: 'Slot products updated successfully',
|
||||
added: result.added,
|
||||
removed: result.removed,
|
||||
}
|
||||
}),
|
||||
|
||||
getSlotProductIds: protectedProcedure
|
||||
.input(z.object({
|
||||
slotId: z.string(),
|
||||
}))
|
||||
.query(async ({ input, ctx }) => {
|
||||
.query(async ({ input }): Promise<AdminSlotProductIdsResult> => {
|
||||
const { slotId } = input;
|
||||
|
||||
const productIds = await getSlotProductIdsInDb(slotId)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const associations = await db.query.productSlots.findMany({
|
||||
where: eq(productSlots.slotId, parseInt(slotId)),
|
||||
columns: {
|
||||
|
|
@ -218,19 +293,28 @@ export const productRouter = router({
|
|||
return {
|
||||
productIds,
|
||||
};
|
||||
*/
|
||||
|
||||
return {
|
||||
productIds,
|
||||
}
|
||||
}),
|
||||
|
||||
getSlotsProductIds: protectedProcedure
|
||||
.input(z.object({
|
||||
slotIds: z.array(z.number()),
|
||||
}))
|
||||
.query(async ({ input, ctx }) => {
|
||||
.query(async ({ input }): Promise<AdminSlotsProductIdsResult> => {
|
||||
const { slotIds } = input;
|
||||
|
||||
if (!Array.isArray(slotIds)) {
|
||||
throw new ApiError("slotIds must be an array", 400);
|
||||
}
|
||||
|
||||
const result = await getSlotsProductIdsInDb(slotIds)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
if (slotIds.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -261,6 +345,9 @@ export const productRouter = router({
|
|||
});
|
||||
|
||||
return result;
|
||||
*/
|
||||
|
||||
return result
|
||||
}),
|
||||
|
||||
getProductReviews: protectedProcedure
|
||||
|
|
@ -269,9 +356,13 @@ export const productRouter = router({
|
|||
limit: z.number().int().min(1).max(50).optional().default(10),
|
||||
offset: z.number().int().min(0).optional().default(0),
|
||||
}))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input }): Promise<AdminProductReviewsResult> => {
|
||||
const { productId, limit, offset } = input;
|
||||
|
||||
const { reviews, totalCount } = await getProductReviewsInDb(productId, limit, offset)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const reviews = await db
|
||||
.select({
|
||||
id: productReviews.id,
|
||||
|
|
@ -309,6 +400,19 @@ export const productRouter = router({
|
|||
const hasMore = offset + limit < totalCount;
|
||||
|
||||
return { reviews: reviewsWithSignedUrls, hasMore };
|
||||
*/
|
||||
|
||||
const reviewsWithSignedUrls = await Promise.all(
|
||||
reviews.map(async (review) => ({
|
||||
...review,
|
||||
signedImageUrls: await generateSignedUrlsFromS3Urls((review.imageUrls as string[]) || []),
|
||||
signedAdminImageUrls: await generateSignedUrlsFromS3Urls((review.adminResponseImages as string[]) || []),
|
||||
}))
|
||||
)
|
||||
|
||||
const hasMore = offset + limit < totalCount
|
||||
|
||||
return { reviews: reviewsWithSignedUrls, hasMore }
|
||||
}),
|
||||
|
||||
respondToReview: protectedProcedure
|
||||
|
|
@ -318,9 +422,13 @@ export const productRouter = router({
|
|||
adminResponseImages: z.array(z.string()).optional().default([]),
|
||||
uploadUrls: z.array(z.string()).optional().default([]),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input }): Promise<AdminProductReviewResponse> => {
|
||||
const { reviewId, adminResponse, adminResponseImages, uploadUrls } = input;
|
||||
|
||||
const updatedReview = await respondToReviewInDb(reviewId, adminResponse, adminResponseImages)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const [updatedReview] = await db
|
||||
.update(productReviews)
|
||||
.set({
|
||||
|
|
@ -341,10 +449,25 @@ export const productRouter = router({
|
|||
}
|
||||
|
||||
return { success: true, review: updatedReview };
|
||||
*/
|
||||
|
||||
if (!updatedReview) {
|
||||
throw new ApiError('Review not found', 404)
|
||||
}
|
||||
|
||||
if (uploadUrls && uploadUrls.length > 0) {
|
||||
await Promise.all(uploadUrls.map(url => claimUploadUrl(url)))
|
||||
}
|
||||
|
||||
return { success: true, review: updatedReview }
|
||||
}),
|
||||
|
||||
getGroups: protectedProcedure
|
||||
.query(async ({ ctx }) => {
|
||||
.query(async (): Promise<AdminProductGroupsResult> => {
|
||||
const groups = await getAllProductGroupsInDb()
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const groups = await db.query.productGroupInfo.findMany({
|
||||
with: {
|
||||
memberships: {
|
||||
|
|
@ -355,14 +478,18 @@ export const productRouter = router({
|
|||
},
|
||||
orderBy: desc(productGroupInfo.createdAt),
|
||||
});
|
||||
*/
|
||||
|
||||
return {
|
||||
groups: groups.map(group => ({
|
||||
...group,
|
||||
products: group.memberships.map(m => m.product),
|
||||
products: group.memberships.map(m => ({
|
||||
...m.product,
|
||||
images: (m.product.images as string[]) || null,
|
||||
})),
|
||||
productCount: group.memberships.length,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
createGroup: protectedProcedure
|
||||
|
|
@ -371,9 +498,13 @@ export const productRouter = router({
|
|||
description: z.string().optional(),
|
||||
product_ids: z.array(z.number()).default([]),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input }): Promise<AdminProductGroupResponse> => {
|
||||
const { group_name, description, product_ids } = input;
|
||||
|
||||
const newGroup = await createProductGroupInDb(group_name, description, product_ids)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const [newGroup] = await db
|
||||
.insert(productGroupInfo)
|
||||
.values({
|
||||
|
|
@ -398,6 +529,14 @@ export const productRouter = router({
|
|||
group: newGroup,
|
||||
message: 'Group created successfully',
|
||||
};
|
||||
*/
|
||||
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
group: newGroup,
|
||||
message: 'Group created successfully',
|
||||
}
|
||||
}),
|
||||
|
||||
updateGroup: protectedProcedure
|
||||
|
|
@ -407,9 +546,13 @@ export const productRouter = router({
|
|||
description: z.string().optional(),
|
||||
product_ids: z.array(z.number()).optional(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input }): Promise<AdminProductGroupResponse> => {
|
||||
const { id, group_name, description, product_ids } = input;
|
||||
|
||||
const updatedGroup = await updateProductGroupInDb(id, group_name, description, product_ids)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const updateData: any = {};
|
||||
if (group_name !== undefined) updateData.groupName = group_name;
|
||||
if (description !== undefined) updateData.description = description;
|
||||
|
|
@ -446,15 +589,31 @@ export const productRouter = router({
|
|||
group: updatedGroup,
|
||||
message: 'Group updated successfully',
|
||||
};
|
||||
*/
|
||||
|
||||
if (!updatedGroup) {
|
||||
throw new ApiError('Group not found', 404)
|
||||
}
|
||||
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
group: updatedGroup,
|
||||
message: 'Group updated successfully',
|
||||
}
|
||||
}),
|
||||
|
||||
deleteGroup: protectedProcedure
|
||||
.input(z.object({
|
||||
id: z.number(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input }): Promise<AdminDeleteProductResult> => {
|
||||
const { id } = input;
|
||||
|
||||
const deletedGroup = await deleteProductGroupInDb(id)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
// Delete memberships first
|
||||
await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id));
|
||||
|
||||
|
|
@ -474,6 +633,17 @@ export const productRouter = router({
|
|||
return {
|
||||
message: 'Group deleted successfully',
|
||||
};
|
||||
*/
|
||||
|
||||
if (!deletedGroup) {
|
||||
throw new ApiError('Group not found', 404)
|
||||
}
|
||||
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
message: 'Group deleted successfully',
|
||||
}
|
||||
}),
|
||||
|
||||
updateProductPrices: protectedProcedure
|
||||
|
|
@ -486,9 +656,17 @@ export const productRouter = router({
|
|||
isFlashAvailable: z.boolean().optional(),
|
||||
})),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { updates } = input;
|
||||
.mutation(async ({ input }): Promise<AdminUpdateProductPricesResult> => {
|
||||
const { updates } = input;
|
||||
|
||||
if (updates.length === 0) {
|
||||
throw new ApiError('No updates provided', 400)
|
||||
}
|
||||
|
||||
const result = await updateProductPricesInDb(updates)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
if (updates.length === 0) {
|
||||
throw new ApiError('No updates provided', 400);
|
||||
}
|
||||
|
|
@ -531,5 +709,17 @@ export const productRouter = router({
|
|||
message: `Updated prices for ${updates.length} product(s)`,
|
||||
updatedCount: updates.length,
|
||||
};
|
||||
}),
|
||||
*/
|
||||
|
||||
if (result.invalidIds.length > 0) {
|
||||
throw new ApiError(`Invalid product IDs: ${result.invalidIds.join(', ')}`, 400)
|
||||
}
|
||||
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
message: `Updated prices for ${result.updatedCount} product(s)`,
|
||||
updatedCount: result.updatedCount,
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,38 @@
|
|||
import { router, protectedProcedure } from "@/src/trpc/trpc-index"
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/src/db/db_index"
|
||||
import { deliverySlotInfo, productSlots, productInfo, vendorSnippets, productGroupInfo } from "@/src/db/schema"
|
||||
import { eq, inArray, and, desc } from "drizzle-orm";
|
||||
import { ApiError } from "@/src/lib/api-error"
|
||||
import { appUrl } from "@/src/lib/env-exporter"
|
||||
import redisClient from "@/src/lib/redis-client"
|
||||
import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
|
||||
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
||||
import {
|
||||
getActiveSlotsWithProducts as getActiveSlotsWithProductsInDb,
|
||||
getActiveSlots as getActiveSlotsInDb,
|
||||
getSlotsAfterDate as getSlotsAfterDateInDb,
|
||||
getSlotByIdWithRelations as getSlotByIdWithRelationsInDb,
|
||||
createSlotWithRelations as createSlotWithRelationsInDb,
|
||||
updateSlotWithRelations as updateSlotWithRelationsInDb,
|
||||
deleteSlotById as deleteSlotByIdInDb,
|
||||
updateSlotCapacity as updateSlotCapacityInDb,
|
||||
getSlotDeliverySequence as getSlotDeliverySequenceInDb,
|
||||
updateSlotDeliverySequence as updateSlotDeliverySequenceInDb,
|
||||
updateSlotProducts as updateSlotProductsInDb,
|
||||
getSlotsProductIds as getSlotsProductIdsInDb,
|
||||
} from '@/src/dbService'
|
||||
import type {
|
||||
AdminDeliverySequenceResult,
|
||||
AdminSlotResult,
|
||||
AdminSlotsResult,
|
||||
AdminSlotsListResult,
|
||||
AdminSlotCreateResult,
|
||||
AdminSlotUpdateResult,
|
||||
AdminSlotDeleteResult,
|
||||
AdminUpdateDeliverySequenceResult,
|
||||
AdminUpdateSlotCapacityResult,
|
||||
AdminSlotsProductIdsResult,
|
||||
AdminUpdateSlotProductsResult,
|
||||
} from '@packages/shared'
|
||||
|
||||
|
||||
interface CachedDeliverySequence {
|
||||
|
|
@ -64,11 +88,15 @@ const updateDeliverySequenceSchema = z.object({
|
|||
|
||||
export const slotsRouter = router({
|
||||
// Exact replica of GET /av/slots
|
||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
||||
getAll: protectedProcedure.query(async ({ ctx }): Promise<AdminSlotsResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
||||
const slots = await getActiveSlotsWithProductsInDb()
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const slots = await db.query.deliverySlotInfo
|
||||
.findMany({
|
||||
where: eq(deliverySlotInfo.isActive, true),
|
||||
|
|
@ -94,17 +122,18 @@ export const slotsRouter = router({
|
|||
products: slot.productSlots.map((ps) => ps.product),
|
||||
}))
|
||||
);
|
||||
*/
|
||||
|
||||
return {
|
||||
slots,
|
||||
count: slots.length,
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
// Exact replica of POST /av/products/slots/product-ids
|
||||
getSlotsProductIds: protectedProcedure
|
||||
.input(z.object({ slotIds: z.array(z.number()) }))
|
||||
.query(async ({ input, ctx }) => {
|
||||
.query(async ({ input, ctx }): Promise<AdminSlotsProductIdsResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
|
@ -118,6 +147,10 @@ export const slotsRouter = router({
|
|||
});
|
||||
}
|
||||
|
||||
const result = await getSlotsProductIdsInDb(slotIds)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
if (slotIds.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -148,6 +181,9 @@ export const slotsRouter = router({
|
|||
});
|
||||
|
||||
return result;
|
||||
*/
|
||||
|
||||
return result
|
||||
}),
|
||||
|
||||
// Exact replica of PUT /av/products/slots/:slotId/products
|
||||
|
|
@ -158,7 +194,7 @@ export const slotsRouter = router({
|
|||
productIds: z.array(z.number()),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<AdminUpdateSlotProductsResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
|
@ -172,6 +208,10 @@ export const slotsRouter = router({
|
|||
});
|
||||
}
|
||||
|
||||
const result = await updateSlotProductsInDb(String(slotId), productIds.map(String))
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
// Get current associations
|
||||
const currentAssociations = await db.query.productSlots.findMany({
|
||||
where: eq(productSlots.slotId, slotId),
|
||||
|
|
@ -223,11 +263,20 @@ export const slotsRouter = router({
|
|||
added: productsToAdd.length,
|
||||
removed: productsToRemove.length,
|
||||
};
|
||||
*/
|
||||
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
message: result.message,
|
||||
added: result.added,
|
||||
removed: result.removed,
|
||||
}
|
||||
}),
|
||||
|
||||
createSlot: protectedProcedure
|
||||
.input(createSlotSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<AdminSlotCreateResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
|
@ -239,6 +288,17 @@ export const slotsRouter = router({
|
|||
throw new ApiError("Delivery time and orders close time are required", 400);
|
||||
}
|
||||
|
||||
const result = await createSlotWithRelationsInDb({
|
||||
deliveryTime,
|
||||
freezeTime,
|
||||
isActive,
|
||||
productIds,
|
||||
vendorSnippets: snippets,
|
||||
groupIds,
|
||||
})
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const result = await db.transaction(async (tx) => {
|
||||
// Create slot
|
||||
const [newSlot] = await tx
|
||||
|
|
@ -297,76 +357,84 @@ export const slotsRouter = router({
|
|||
message: "Slot created successfully",
|
||||
};
|
||||
});
|
||||
*/
|
||||
|
||||
// Reinitialize stores to reflect changes (outside transaction)
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return result;
|
||||
return result
|
||||
}),
|
||||
|
||||
getSlots: protectedProcedure.query(async ({ ctx }) => {
|
||||
getSlots: protectedProcedure.query(async ({ ctx }): Promise<AdminSlotsListResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
||||
const slots = await getActiveSlotsInDb()
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const slots = await db.query.deliverySlotInfo.findMany({
|
||||
where: eq(deliverySlotInfo.isActive, true),
|
||||
});
|
||||
*/
|
||||
|
||||
return {
|
||||
slots,
|
||||
count: slots.length,
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
getSlotById: protectedProcedure
|
||||
.input(getSlotByIdSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
.query(async ({ input, ctx }): Promise<AdminSlotResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
||||
const { id } = input;
|
||||
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, id),
|
||||
with: {
|
||||
productSlots: {
|
||||
with: {
|
||||
product: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
images: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
vendorSnippets: true,
|
||||
},
|
||||
});
|
||||
const slot = await getSlotByIdWithRelationsInDb(id)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, id),
|
||||
with: {
|
||||
productSlots: {
|
||||
with: {
|
||||
product: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
images: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
vendorSnippets: true,
|
||||
},
|
||||
});
|
||||
*/
|
||||
|
||||
if (!slot) {
|
||||
throw new ApiError("Slot not found", 404);
|
||||
throw new ApiError('Slot not found', 404)
|
||||
}
|
||||
|
||||
return {
|
||||
slot: {
|
||||
...slot,
|
||||
deliverySequence: slot.deliverySequence as number[],
|
||||
groupIds: slot.groupIds as number[],
|
||||
products: slot.productSlots.map((ps) => ps.product),
|
||||
vendorSnippets: slot.vendorSnippets?.map(snippet => ({
|
||||
vendorSnippets: slot.vendorSnippets.map(snippet => ({
|
||||
...snippet,
|
||||
accessUrl: `${appUrl}/vendor-order-list?id=${snippet.snippetCode}`
|
||||
accessUrl: `${appUrl}/vendor-order-list?id=${snippet.snippetCode}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
updateSlot: protectedProcedure
|
||||
.input(updateSlotSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<AdminSlotUpdateResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
|
@ -377,6 +445,18 @@ export const slotsRouter = router({
|
|||
throw new ApiError("Delivery time and orders close time are required", 400);
|
||||
}
|
||||
|
||||
const result = await updateSlotWithRelationsInDb({
|
||||
id,
|
||||
deliveryTime,
|
||||
freezeTime,
|
||||
isActive,
|
||||
productIds,
|
||||
vendorSnippets: snippets,
|
||||
groupIds,
|
||||
})
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
// Filter groupIds to only include valid (existing) groups
|
||||
let validGroupIds = groupIds;
|
||||
if (groupIds && groupIds.length > 0) {
|
||||
|
|
@ -456,11 +536,16 @@ export const slotsRouter = router({
|
|||
message: "Slot updated successfully",
|
||||
};
|
||||
});
|
||||
*/
|
||||
|
||||
if (!result) {
|
||||
throw new ApiError('Slot not found', 404)
|
||||
}
|
||||
|
||||
// Reinitialize stores to reflect changes (outside transaction)
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
catch(e) {
|
||||
console.log(e)
|
||||
|
|
@ -470,13 +555,17 @@ export const slotsRouter = router({
|
|||
|
||||
deleteSlot: protectedProcedure
|
||||
.input(deleteSlotSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<AdminSlotDeleteResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
||||
const { id } = input;
|
||||
|
||||
const deletedSlot = await deleteSlotByIdInDb(id)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const [deletedSlot] = await db
|
||||
.update(deliverySlotInfo)
|
||||
.set({ isActive: false })
|
||||
|
|
@ -486,18 +575,23 @@ export const slotsRouter = router({
|
|||
if (!deletedSlot) {
|
||||
throw new ApiError("Slot not found", 404);
|
||||
}
|
||||
*/
|
||||
|
||||
if (!deletedSlot) {
|
||||
throw new ApiError('Slot not found', 404)
|
||||
}
|
||||
|
||||
// Reinitialize stores to reflect changes
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return {
|
||||
message: "Slot deleted successfully",
|
||||
};
|
||||
message: 'Slot deleted successfully',
|
||||
}
|
||||
}),
|
||||
|
||||
getDeliverySequence: protectedProcedure
|
||||
.input(getDeliverySequenceSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
.query(async ({ input, ctx }): Promise<AdminDeliverySequenceResult> => {
|
||||
|
||||
const { id } = input;
|
||||
const slotId = parseInt(id);
|
||||
|
|
@ -507,7 +601,7 @@ export const slotsRouter = router({
|
|||
const cached = await redisClient.get(cacheKey);
|
||||
if (cached) {
|
||||
const parsed = JSON.parse(cached);
|
||||
const validated = cachedSequenceSchema.parse(parsed) as CachedDeliverySequence;
|
||||
const validated = cachedSequenceSchema.parse(parsed);
|
||||
console.log('sending cached response')
|
||||
|
||||
return { deliverySequence: validated };
|
||||
|
|
@ -518,6 +612,10 @@ export const slotsRouter = router({
|
|||
}
|
||||
|
||||
// Fallback to DB
|
||||
const slot = await getSlotDeliverySequenceInDb(slotId)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
|
|
@ -526,6 +624,13 @@ export const slotsRouter = router({
|
|||
throw new ApiError("Slot not found", 404);
|
||||
}
|
||||
|
||||
const sequence = cachedSequenceSchema.parse(slot.deliverySequence || {});
|
||||
*/
|
||||
|
||||
if (!slot) {
|
||||
throw new ApiError('Slot not found', 404)
|
||||
}
|
||||
|
||||
const sequence = (slot.deliverySequence || {}) as CachedDeliverySequence;
|
||||
|
||||
// Cache the validated result
|
||||
|
|
@ -536,18 +641,22 @@ export const slotsRouter = router({
|
|||
console.warn('Redis cache write failed:', cacheError);
|
||||
}
|
||||
|
||||
return { deliverySequence: sequence };
|
||||
return { deliverySequence: sequence }
|
||||
}),
|
||||
|
||||
updateDeliverySequence: protectedProcedure
|
||||
.input(updateDeliverySequenceSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<AdminUpdateDeliverySequenceResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
||||
const { id, deliverySequence } = input;
|
||||
|
||||
const updatedSlot = await updateSlotDeliverySequenceInDb(id, deliverySequence)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const [updatedSlot] = await db
|
||||
.update(deliverySlotInfo)
|
||||
.set({ deliverySequence })
|
||||
|
|
@ -560,6 +669,11 @@ export const slotsRouter = router({
|
|||
if (!updatedSlot) {
|
||||
throw new ApiError("Slot not found", 404);
|
||||
}
|
||||
*/
|
||||
|
||||
if (!updatedSlot) {
|
||||
throw new ApiError('Slot not found', 404)
|
||||
}
|
||||
|
||||
// Cache the updated sequence
|
||||
const cacheKey = getSlotSequenceKey(id);
|
||||
|
|
@ -572,8 +686,8 @@ export const slotsRouter = router({
|
|||
|
||||
return {
|
||||
slot: updatedSlot,
|
||||
message: "Delivery sequence updated successfully",
|
||||
};
|
||||
message: 'Delivery sequence updated successfully',
|
||||
}
|
||||
}),
|
||||
|
||||
updateSlotCapacity: protectedProcedure
|
||||
|
|
@ -581,13 +695,17 @@ export const slotsRouter = router({
|
|||
slotId: z.number(),
|
||||
isCapacityFull: z.boolean(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<AdminUpdateSlotCapacityResult> => {
|
||||
if (!ctx.staffUser?.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||
}
|
||||
|
||||
const { slotId, isCapacityFull } = input;
|
||||
|
||||
const result = await updateSlotCapacityInDb(slotId, isCapacityFull)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const [updatedSlot] = await db
|
||||
.update(deliverySlotInfo)
|
||||
.set({ isCapacityFull })
|
||||
|
|
@ -606,5 +724,14 @@ export const slotsRouter = router({
|
|||
slot: updatedSlot,
|
||||
message: `Slot ${isCapacityFull ? 'marked as full capacity' : 'capacity reset'}`,
|
||||
};
|
||||
*/
|
||||
|
||||
if (!result) {
|
||||
throw new ApiError('Slot not found', 404)
|
||||
}
|
||||
|
||||
scheduleStoreInitialization()
|
||||
|
||||
return result
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,33 @@
|
|||
import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||
import { z } from 'zod';
|
||||
import dayjs from 'dayjs';
|
||||
import { db } from '@/src/db/db_index'
|
||||
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, users, orderStatus } from '@/src/db/schema'
|
||||
import { eq, and, inArray, isNotNull, gt, sql, asc, ne } from 'drizzle-orm';
|
||||
import { z } from 'zod'
|
||||
import dayjs from 'dayjs'
|
||||
import { appUrl } from '@/src/lib/env-exporter'
|
||||
import {
|
||||
checkVendorSnippetExists as checkVendorSnippetExistsInDb,
|
||||
getVendorSnippetById as getVendorSnippetByIdInDb,
|
||||
getVendorSnippetByCode as getVendorSnippetByCodeInDb,
|
||||
getAllVendorSnippets as getAllVendorSnippetsInDb,
|
||||
createVendorSnippet as createVendorSnippetInDb,
|
||||
updateVendorSnippet as updateVendorSnippetInDb,
|
||||
deleteVendorSnippet as deleteVendorSnippetInDb,
|
||||
getProductsByIds as getProductsByIdsInDb,
|
||||
getVendorSlotById as getVendorSlotByIdInDb,
|
||||
getVendorOrdersBySlotId as getVendorOrdersBySlotIdInDb,
|
||||
getVendorOrders as getVendorOrdersInDb,
|
||||
updateVendorOrderItemPackaging as updateVendorOrderItemPackagingInDb,
|
||||
getSlotsAfterDate as getSlotsAfterDateInDb,
|
||||
} from '@/src/dbService'
|
||||
import type {
|
||||
AdminVendorSnippet,
|
||||
AdminVendorSnippetWithProducts,
|
||||
AdminVendorSnippetWithSlot,
|
||||
AdminVendorSnippetDeleteResult,
|
||||
AdminVendorSnippetOrdersResult,
|
||||
AdminVendorSnippetOrdersWithSlotResult,
|
||||
AdminVendorOrderSummary,
|
||||
AdminUpcomingSlotsResult,
|
||||
AdminVendorUpdatePackagingResult,
|
||||
} from '@packages/shared'
|
||||
|
||||
const createSnippetSchema = z.object({
|
||||
snippetCode: z.string().min(1, "Snippet code is required"),
|
||||
|
|
@ -26,7 +49,7 @@ const updateSnippetSchema = z.object({
|
|||
export const vendorSnippetsRouter = router({
|
||||
create: protectedProcedure
|
||||
.input(createSnippetSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<AdminVendorSnippet> => {
|
||||
const { snippetCode, slotId, productIds, validTill, isPermanent } = input;
|
||||
|
||||
// Get staff user ID from auth middleware
|
||||
|
|
@ -35,6 +58,33 @@ export const vendorSnippetsRouter = router({
|
|||
throw new Error("Unauthorized");
|
||||
}
|
||||
|
||||
if(slotId) {
|
||||
const slot = await getVendorSlotByIdInDb(slotId)
|
||||
if (!slot) {
|
||||
throw new Error("Invalid slot ID")
|
||||
}
|
||||
}
|
||||
|
||||
const products = await getProductsByIdsInDb(productIds)
|
||||
if (products.length !== productIds.length) {
|
||||
throw new Error("One or more invalid product IDs")
|
||||
}
|
||||
|
||||
const existingSnippet = await checkVendorSnippetExistsInDb(snippetCode)
|
||||
if (existingSnippet) {
|
||||
throw new Error("Snippet code already exists")
|
||||
}
|
||||
|
||||
const result = await createVendorSnippetInDb({
|
||||
snippetCode,
|
||||
slotId,
|
||||
productIds,
|
||||
isPermanent,
|
||||
validTill: validTill ? new Date(validTill) : undefined,
|
||||
})
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
// Validate slot exists
|
||||
if(slotId) {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
|
|
@ -70,13 +120,32 @@ export const vendorSnippetsRouter = router({
|
|||
}).returning();
|
||||
|
||||
return result[0];
|
||||
*/
|
||||
|
||||
return result
|
||||
}),
|
||||
|
||||
getAll: protectedProcedure
|
||||
.query(async () => {
|
||||
.query(async (): Promise<AdminVendorSnippetWithProducts[]> => {
|
||||
console.log('from the vendor snipptes methods')
|
||||
|
||||
try {
|
||||
const result = await getAllVendorSnippetsInDb()
|
||||
|
||||
const snippetsWithProducts = await Promise.all(
|
||||
result.map(async (snippet) => {
|
||||
const products = await getProductsByIdsInDb(snippet.productIds)
|
||||
|
||||
return {
|
||||
...snippet,
|
||||
accessUrl: `${appUrl}/vendor-order-list?id=${snippet.snippetCode}`,
|
||||
products,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const result = await db.query.vendorSnippets.findMany({
|
||||
with: {
|
||||
slot: true,
|
||||
|
|
@ -100,18 +169,25 @@ export const vendorSnippetsRouter = router({
|
|||
);
|
||||
|
||||
return snippetsWithProducts;
|
||||
*/
|
||||
|
||||
return snippetsWithProducts
|
||||
}
|
||||
catch(e) {
|
||||
console.log(e)
|
||||
}
|
||||
return [];
|
||||
return []
|
||||
}),
|
||||
|
||||
getById: protectedProcedure
|
||||
.input(z.object({ id: z.number().int().positive() }))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input }): Promise<AdminVendorSnippetWithSlot> => {
|
||||
const { id } = input;
|
||||
|
||||
const result = await getVendorSnippetByIdInDb(id)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const result = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.id, id),
|
||||
with: {
|
||||
|
|
@ -124,14 +200,57 @@ export const vendorSnippetsRouter = router({
|
|||
}
|
||||
|
||||
return result;
|
||||
*/
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Vendor snippet not found')
|
||||
}
|
||||
|
||||
return result
|
||||
}),
|
||||
|
||||
update: protectedProcedure
|
||||
.input(updateSnippetSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input }): Promise<AdminVendorSnippet> => {
|
||||
const { id, updates } = input;
|
||||
|
||||
// Check if snippet exists
|
||||
const existingSnippet = await getVendorSnippetByIdInDb(id)
|
||||
if (!existingSnippet) {
|
||||
throw new Error('Vendor snippet not found')
|
||||
}
|
||||
|
||||
if (updates.slotId) {
|
||||
const slot = await getVendorSlotByIdInDb(updates.slotId)
|
||||
if (!slot) {
|
||||
throw new Error('Invalid slot ID')
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.productIds) {
|
||||
const products = await getProductsByIdsInDb(updates.productIds)
|
||||
if (products.length !== updates.productIds.length) {
|
||||
throw new Error('One or more invalid product IDs')
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.snippetCode && updates.snippetCode !== existingSnippet.snippetCode) {
|
||||
const duplicateSnippet = await checkVendorSnippetExistsInDb(updates.snippetCode)
|
||||
if (duplicateSnippet) {
|
||||
throw new Error('Snippet code already exists')
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
...updates,
|
||||
validTill: updates.validTill !== undefined
|
||||
? (updates.validTill ? new Date(updates.validTill) : null)
|
||||
: undefined,
|
||||
}
|
||||
|
||||
const result = await updateVendorSnippetInDb(id, updateData)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.id, id),
|
||||
});
|
||||
|
|
@ -184,13 +303,24 @@ export const vendorSnippetsRouter = router({
|
|||
}
|
||||
|
||||
return result[0];
|
||||
*/
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Failed to update vendor snippet')
|
||||
}
|
||||
|
||||
return result
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ id: z.number().int().positive() }))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input }): Promise<AdminVendorSnippetDeleteResult> => {
|
||||
const { id } = input;
|
||||
|
||||
const result = await deleteVendorSnippetInDb(id)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const result = await db.delete(vendorSnippets)
|
||||
.where(eq(vendorSnippets.id, id))
|
||||
.returning();
|
||||
|
|
@ -200,15 +330,26 @@ export const vendorSnippetsRouter = router({
|
|||
}
|
||||
|
||||
return { message: "Vendor snippet deleted successfully" };
|
||||
*/
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Vendor snippet not found')
|
||||
}
|
||||
|
||||
return { message: 'Vendor snippet deleted successfully' }
|
||||
}),
|
||||
|
||||
getOrdersBySnippet: publicProcedure
|
||||
.input(z.object({
|
||||
snippetCode: z.string().min(1, "Snippet code is required")
|
||||
}))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input }): Promise<AdminVendorSnippetOrdersResult> => {
|
||||
const { snippetCode } = input;
|
||||
|
||||
const snippet = await getVendorSnippetByCodeInDb(snippetCode)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
// Find the snippet
|
||||
const snippet = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||
|
|
@ -242,6 +383,21 @@ export const vendorSnippetsRouter = router({
|
|||
},
|
||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||
});
|
||||
*/
|
||||
|
||||
if (!snippet) {
|
||||
throw new Error('Vendor snippet not found')
|
||||
}
|
||||
|
||||
if (snippet.validTill && new Date(snippet.validTill) < new Date()) {
|
||||
throw new Error('Vendor snippet has expired')
|
||||
}
|
||||
|
||||
if (!snippet.slotId) {
|
||||
throw new Error('Vendor snippet not associated with a slot')
|
||||
}
|
||||
|
||||
const matchingOrders = await getVendorOrdersBySlotIdInDb(snippet.slotId)
|
||||
|
||||
// Filter orders that contain at least one of the snippet's products
|
||||
const filteredOrders = matchingOrders.filter(order => {
|
||||
|
|
@ -273,11 +429,11 @@ export const vendorSnippetsRouter = router({
|
|||
|
||||
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
||||
|
||||
return {
|
||||
orderId: `ORD${order.id}`,
|
||||
orderDate: order.createdAt.toISOString(),
|
||||
customerName: order.user.name,
|
||||
totalAmount: orderTotal,
|
||||
return {
|
||||
orderId: `ORD${order.id}`,
|
||||
orderDate: order.createdAt.toISOString(),
|
||||
customerName: order.user.name || '',
|
||||
totalAmount: orderTotal,
|
||||
slotInfo: order.slot ? {
|
||||
time: order.slot.deliveryTime.toISOString(),
|
||||
sequence: order.slot.deliverySequence,
|
||||
|
|
@ -300,11 +456,15 @@ export const vendorSnippetsRouter = router({
|
|||
createdAt: snippet.createdAt.toISOString(),
|
||||
isPermanent: snippet.isPermanent,
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
getVendorOrders: protectedProcedure
|
||||
.query(async () => {
|
||||
.query(async (): Promise<AdminVendorOrderSummary[]> => {
|
||||
const vendorOrders = await getVendorOrdersInDb()
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const vendorOrders = await db.query.orders.findMany({
|
||||
with: {
|
||||
user: true,
|
||||
|
|
@ -320,10 +480,11 @@ export const vendorSnippetsRouter = router({
|
|||
},
|
||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||
});
|
||||
*/
|
||||
|
||||
return vendorOrders.map(order => ({
|
||||
id: order.id,
|
||||
status: 'pending', // Default status since orders table may not have status field
|
||||
status: 'pending',
|
||||
orderDate: order.createdAt.toISOString(),
|
||||
totalQuantity: order.orderItems.reduce((sum, item) => sum + parseFloat(item.quantity || '0'), 0),
|
||||
products: order.orderItems.map(item => ({
|
||||
|
|
@ -331,12 +492,16 @@ export const vendorSnippetsRouter = router({
|
|||
quantity: parseFloat(item.quantity || '0'),
|
||||
unit: item.product.unit?.shortNotation || 'unit',
|
||||
})),
|
||||
}));
|
||||
}))
|
||||
}),
|
||||
|
||||
getUpcomingSlots: publicProcedure
|
||||
.query(async () => {
|
||||
.query(async (): Promise<AdminUpcomingSlotsResult> => {
|
||||
const threeHoursAgo = dayjs().subtract(3, 'hour').toDate();
|
||||
const slots = await getSlotsAfterDateInDb(threeHoursAgo)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
const slots = await db.query.deliverySlotInfo.findMany({
|
||||
where: and(
|
||||
eq(deliverySlotInfo.isActive, true),
|
||||
|
|
@ -344,6 +509,7 @@ export const vendorSnippetsRouter = router({
|
|||
),
|
||||
orderBy: asc(deliverySlotInfo.deliveryTime),
|
||||
});
|
||||
*/
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
@ -353,7 +519,7 @@ export const vendorSnippetsRouter = router({
|
|||
freezeTime: slot.freezeTime.toISOString(),
|
||||
deliverySequence: slot.deliverySequence,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
getOrdersBySnippetAndSlot: publicProcedure
|
||||
|
|
@ -361,9 +527,14 @@ export const vendorSnippetsRouter = router({
|
|||
snippetCode: z.string().min(1, "Snippet code is required"),
|
||||
slotId: z.number().int().positive("Valid slot ID is required"),
|
||||
}))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input }): Promise<AdminVendorSnippetOrdersWithSlotResult> => {
|
||||
const { snippetCode, slotId } = input;
|
||||
|
||||
const snippet = await getVendorSnippetByCodeInDb(snippetCode)
|
||||
const slot = await getVendorSlotByIdInDb(slotId)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
// Find the snippet
|
||||
const snippet = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||
|
|
@ -401,6 +572,17 @@ export const vendorSnippetsRouter = router({
|
|||
},
|
||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||
});
|
||||
*/
|
||||
|
||||
if (!snippet) {
|
||||
throw new Error('Vendor snippet not found')
|
||||
}
|
||||
|
||||
if (!slot) {
|
||||
throw new Error('Slot not found')
|
||||
}
|
||||
|
||||
const matchingOrders = await getVendorOrdersBySlotIdInDb(slotId)
|
||||
|
||||
// Filter orders that contain at least one of the snippet's products
|
||||
const filteredOrders = matchingOrders.filter(order => {
|
||||
|
|
@ -435,7 +617,7 @@ export const vendorSnippetsRouter = router({
|
|||
return {
|
||||
orderId: `ORD${order.id}`,
|
||||
orderDate: order.createdAt.toISOString(),
|
||||
customerName: order.user.name,
|
||||
customerName: order.user.name || '',
|
||||
totalAmount: orderTotal,
|
||||
slotInfo: order.slot ? {
|
||||
time: order.slot.deliveryTime.toISOString(),
|
||||
|
|
@ -465,7 +647,7 @@ export const vendorSnippetsRouter = router({
|
|||
freezeTime: slot.freezeTime.toISOString(),
|
||||
deliverySequence: slot.deliverySequence,
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
updateOrderItemPackaging: publicProcedure
|
||||
|
|
@ -473,7 +655,7 @@ export const vendorSnippetsRouter = router({
|
|||
orderItemId: z.number().int().positive("Valid order item ID required"),
|
||||
is_packaged: z.boolean()
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<AdminVendorUpdatePackagingResult> => {
|
||||
const { orderItemId, is_packaged } = input;
|
||||
|
||||
// Get staff user ID from auth middleware
|
||||
|
|
@ -482,6 +664,10 @@ export const vendorSnippetsRouter = router({
|
|||
// throw new Error("Unauthorized");
|
||||
// }
|
||||
|
||||
const result = await updateVendorOrderItemPackagingInDb(orderItemId, is_packaged)
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB queries:
|
||||
// Check if order item exists and get related data
|
||||
const orderItem = await db.query.orderItems.findFirst({
|
||||
where: eq(orderItems.id, orderItemId),
|
||||
|
|
@ -527,5 +713,12 @@ export const vendorSnippetsRouter = router({
|
|||
orderItemId,
|
||||
is_packaged
|
||||
};
|
||||
*/
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.message)
|
||||
}
|
||||
|
||||
return result
|
||||
}),
|
||||
});
|
||||
|
|
@ -68,9 +68,13 @@ export {
|
|||
// Product
|
||||
getAllProducts,
|
||||
getProductById,
|
||||
deleteProduct,
|
||||
createProduct,
|
||||
updateProduct,
|
||||
toggleProductOutOfStock,
|
||||
updateSlotProducts,
|
||||
getSlotProductIds,
|
||||
getSlotsProductIds,
|
||||
getAllUnits,
|
||||
getAllProductTags,
|
||||
getProductReviews,
|
||||
|
|
@ -81,19 +85,18 @@ export {
|
|||
deleteProductGroup,
|
||||
addProductToGroup,
|
||||
removeProductFromGroup,
|
||||
updateProductPrices,
|
||||
} from './src/admin-apis/product';
|
||||
|
||||
export {
|
||||
// Slots
|
||||
getAllSlots,
|
||||
getSlotById,
|
||||
createSlot,
|
||||
updateSlot,
|
||||
deleteSlot,
|
||||
getSlotProducts,
|
||||
addProductToSlot,
|
||||
removeProductFromSlot,
|
||||
clearSlotProducts,
|
||||
getActiveSlotsWithProducts,
|
||||
getActiveSlots,
|
||||
getSlotsAfterDate,
|
||||
getSlotByIdWithRelations,
|
||||
createSlotWithRelations,
|
||||
updateSlotWithRelations,
|
||||
deleteSlotById,
|
||||
updateSlotCapacity,
|
||||
getSlotDeliverySequence,
|
||||
updateSlotDeliverySequence,
|
||||
|
|
@ -159,6 +162,7 @@ export {
|
|||
getOrderItemsByOrderIds,
|
||||
getOrderStatusByOrderIds,
|
||||
updateVendorOrderItemPackaging,
|
||||
getVendorOrders,
|
||||
} from './src/admin-apis/vendor-snippets';
|
||||
|
||||
// Note: User API helpers are available in their respective files
|
||||
|
|
|
|||
|
|
@ -26,6 +26,33 @@ import type {
|
|||
RefundStatus,
|
||||
PaymentStatus,
|
||||
} from '@packages/shared'
|
||||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
|
||||
const isPaymentStatus = (value: string): value is PaymentStatus =>
|
||||
value === 'pending' || value === 'success' || value === 'cod' || value === 'failed'
|
||||
|
||||
const isRefundStatus = (value: string): value is RefundStatus =>
|
||||
value === 'success' || value === 'pending' || value === 'failed' || value === 'none' || value === 'na' || value === 'processed'
|
||||
|
||||
type OrderStatusRow = InferSelectModel<typeof orderStatus>
|
||||
|
||||
const mapOrderStatusRecord = (record: OrderStatusRow): AdminOrderStatusRecord => ({
|
||||
id: record.id,
|
||||
orderTime: record.orderTime,
|
||||
userId: record.userId,
|
||||
orderId: record.orderId,
|
||||
isPackaged: record.isPackaged,
|
||||
isDelivered: record.isDelivered,
|
||||
isCancelled: record.isCancelled,
|
||||
cancelReason: record.cancelReason ?? null,
|
||||
isCancelledByAdmin: record.isCancelledByAdmin ?? null,
|
||||
paymentStatus: isPaymentStatus(record.paymentStatus) ? record.paymentStatus : 'pending',
|
||||
cancellationUserNotes: record.cancellationUserNotes ?? null,
|
||||
cancellationAdminNotes: record.cancellationAdminNotes ?? null,
|
||||
cancellationReviewed: record.cancellationReviewed,
|
||||
cancellationReviewedAt: record.cancellationReviewedAt ?? null,
|
||||
refundCouponId: record.refundCouponId ?? null,
|
||||
})
|
||||
|
||||
export async function updateOrderNotes(orderId: number, adminNotes: string | null): Promise<AdminOrderRow | null> {
|
||||
const [result] = await db
|
||||
|
|
@ -147,9 +174,7 @@ export async function getOrderDetails(orderId: number): Promise<AdminOrderDetail
|
|||
}
|
||||
|
||||
const statusRecord = orderData.orderStatus?.[0]
|
||||
const orderStatusRecord = statusRecord
|
||||
? (statusRecord as AdminOrderStatusRecord)
|
||||
: null
|
||||
const orderStatusRecord = statusRecord ? mapOrderStatusRecord(statusRecord) : null
|
||||
let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
|
||||
if (orderStatusRecord?.isCancelled) {
|
||||
status = 'cancelled'
|
||||
|
|
@ -158,8 +183,8 @@ export async function getOrderDetails(orderId: number): Promise<AdminOrderDetail
|
|||
}
|
||||
|
||||
const refund = orderData.refunds?.[0]
|
||||
const refundStatus = refund?.refundStatus
|
||||
? (refund.refundStatus as RefundStatus)
|
||||
const refundStatus = refund?.refundStatus && isRefundStatus(refund.refundStatus)
|
||||
? refund.refundStatus
|
||||
: null
|
||||
const refundRecord: AdminRefundRecord | null = refund
|
||||
? {
|
||||
|
|
@ -351,6 +376,8 @@ export async function getSlotOrders(slotId: string): Promise<AdminGetSlotOrdersR
|
|||
isPackageVerified: item.is_package_verified,
|
||||
}))
|
||||
|
||||
const paymentMode: 'COD' | 'Online' = order.isCod ? 'COD' : 'Online'
|
||||
|
||||
return {
|
||||
id: order.id,
|
||||
readableId: order.id,
|
||||
|
|
@ -370,8 +397,10 @@ export async function getSlotOrders(slotId: string): Promise<AdminGetSlotOrdersR
|
|||
isPackaged: order.orderItems.every((item) => item.is_packaged) || false,
|
||||
isDelivered: statusRecord?.isDelivered || false,
|
||||
isCod: order.isCod,
|
||||
paymentMode: (order.isCod ? 'COD' : 'Online') as 'COD' | 'Online',
|
||||
paymentStatus: (statusRecord?.paymentStatus || 'pending') as PaymentStatus,
|
||||
paymentMode,
|
||||
paymentStatus: isPaymentStatus(statusRecord?.paymentStatus || 'pending')
|
||||
? statusRecord?.paymentStatus || 'pending'
|
||||
: 'pending',
|
||||
slotId: order.slotId,
|
||||
adminNotes: order.adminNotes,
|
||||
userNotes: order.userNotes,
|
||||
|
|
|
|||
|
|
@ -1,67 +1,265 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { productInfo, units, specialDeals, productSlots, productTags, productReviews, productGroupInfo, productGroupMembership } from '../db/schema';
|
||||
import { eq, and, inArray, desc, sql, asc } from 'drizzle-orm';
|
||||
import { db } from '../db/db_index'
|
||||
import {
|
||||
productInfo,
|
||||
units,
|
||||
specialDeals,
|
||||
productSlots,
|
||||
productTags,
|
||||
productReviews,
|
||||
productGroupInfo,
|
||||
productGroupMembership,
|
||||
productTagInfo,
|
||||
users,
|
||||
storeInfo,
|
||||
} from '../db/schema'
|
||||
import { and, desc, eq, inArray, sql } from 'drizzle-orm'
|
||||
import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'
|
||||
import type {
|
||||
AdminProduct,
|
||||
AdminProductGroupInfo,
|
||||
AdminProductTagWithProducts,
|
||||
AdminProductReview,
|
||||
AdminProductWithDetails,
|
||||
AdminProductWithRelations,
|
||||
AdminSpecialDeal,
|
||||
AdminUnit,
|
||||
AdminUpdateSlotProductsResult,
|
||||
Store,
|
||||
} from '@packages/shared'
|
||||
|
||||
export async function getAllProducts(): Promise<any[]> {
|
||||
return await db.query.productInfo.findMany({
|
||||
type ProductRow = InferSelectModel<typeof productInfo>
|
||||
type UnitRow = InferSelectModel<typeof units>
|
||||
type StoreRow = InferSelectModel<typeof storeInfo>
|
||||
type SpecialDealRow = InferSelectModel<typeof specialDeals>
|
||||
type ProductTagInfoRow = InferSelectModel<typeof productTagInfo>
|
||||
type ProductTagRow = InferSelectModel<typeof productTags>
|
||||
type ProductGroupRow = InferSelectModel<typeof productGroupInfo>
|
||||
type ProductGroupMembershipRow = InferSelectModel<typeof productGroupMembership>
|
||||
type ProductReviewRow = InferSelectModel<typeof productReviews>
|
||||
|
||||
const getStringArray = (value: unknown): string[] | null => {
|
||||
if (!Array.isArray(value)) return null
|
||||
return value.map((item) => String(item))
|
||||
}
|
||||
|
||||
const mapUnit = (unit: UnitRow): AdminUnit => ({
|
||||
id: unit.id,
|
||||
shortNotation: unit.shortNotation,
|
||||
fullName: unit.fullName,
|
||||
})
|
||||
|
||||
const mapStore = (store: StoreRow): Store => ({
|
||||
id: store.id,
|
||||
name: store.name,
|
||||
description: store.description,
|
||||
imageUrl: store.imageUrl,
|
||||
owner: store.owner,
|
||||
createdAt: store.createdAt,
|
||||
// updatedAt: store.createdAt,
|
||||
})
|
||||
|
||||
const mapProduct = (product: ProductRow): AdminProduct => ({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
shortDescription: product.shortDescription ?? null,
|
||||
longDescription: product.longDescription ?? null,
|
||||
unitId: product.unitId,
|
||||
price: product.price.toString(),
|
||||
marketPrice: product.marketPrice ? product.marketPrice.toString() : null,
|
||||
images: getStringArray(product.images),
|
||||
isOutOfStock: product.isOutOfStock,
|
||||
isSuspended: product.isSuspended,
|
||||
isFlashAvailable: product.isFlashAvailable,
|
||||
flashPrice: product.flashPrice ? product.flashPrice.toString() : null,
|
||||
createdAt: product.createdAt,
|
||||
incrementStep: product.incrementStep,
|
||||
productQuantity: product.productQuantity,
|
||||
storeId: product.storeId,
|
||||
})
|
||||
|
||||
const mapSpecialDeal = (deal: SpecialDealRow): AdminSpecialDeal => ({
|
||||
id: deal.id,
|
||||
productId: deal.productId,
|
||||
quantity: deal.quantity.toString(),
|
||||
price: deal.price.toString(),
|
||||
validTill: deal.validTill,
|
||||
})
|
||||
|
||||
const mapTagInfo = (tag: ProductTagInfoRow) => ({
|
||||
id: tag.id,
|
||||
tagName: tag.tagName,
|
||||
tagDescription: tag.tagDescription ?? null,
|
||||
imageUrl: tag.imageUrl ?? null,
|
||||
isDashboardTag: tag.isDashboardTag,
|
||||
relatedStores: tag.relatedStores,
|
||||
createdAt: tag.createdAt,
|
||||
})
|
||||
|
||||
export async function getAllProducts(): Promise<AdminProductWithRelations[]> {
|
||||
const products = await db.query.productInfo.findMany({
|
||||
orderBy: productInfo.name,
|
||||
with: {
|
||||
unit: true,
|
||||
store: true,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
return products.map((product) => ({
|
||||
...mapProduct(product),
|
||||
unit: mapUnit(product.unit),
|
||||
store: product.store ? mapStore(product.store) : null,
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getProductById(id: number): Promise<any | null> {
|
||||
return await db.query.productInfo.findFirst({
|
||||
export async function getProductById(id: number): Promise<AdminProductWithDetails | null> {
|
||||
const product = await db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, id),
|
||||
with: {
|
||||
unit: true,
|
||||
store: true,
|
||||
productSlots: {
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
},
|
||||
specialDeals: true,
|
||||
productTags: {
|
||||
with: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
if (!product) {
|
||||
return null
|
||||
}
|
||||
|
||||
const deals = await db.query.specialDeals.findMany({
|
||||
where: eq(specialDeals.productId, id),
|
||||
orderBy: specialDeals.quantity,
|
||||
})
|
||||
|
||||
const productTagsData = await db.query.productTags.findMany({
|
||||
where: eq(productTags.productId, id),
|
||||
with: {
|
||||
tag: true,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
...mapProduct(product),
|
||||
unit: mapUnit(product.unit),
|
||||
deals: deals.map(mapSpecialDeal),
|
||||
tags: productTagsData.map((tag) => mapTagInfo(tag.tag)),
|
||||
}
|
||||
}
|
||||
|
||||
export async function createProduct(input: any): Promise<any> {
|
||||
const [product] = await db.insert(productInfo).values(input).returning();
|
||||
return product;
|
||||
export async function deleteProduct(id: number): Promise<AdminProduct | null> {
|
||||
const [deletedProduct] = await db
|
||||
.delete(productInfo)
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning()
|
||||
|
||||
if (!deletedProduct) {
|
||||
return null
|
||||
}
|
||||
|
||||
return mapProduct(deletedProduct)
|
||||
}
|
||||
|
||||
export async function updateProduct(id: number, updates: any): Promise<any> {
|
||||
type ProductInfoInsert = InferInsertModel<typeof productInfo>
|
||||
type ProductInfoUpdate = Partial<ProductInfoInsert>
|
||||
|
||||
export async function createProduct(input: ProductInfoInsert): Promise<AdminProduct> {
|
||||
const [product] = await db.insert(productInfo).values(input).returning()
|
||||
return mapProduct(product)
|
||||
}
|
||||
|
||||
export async function updateProduct(id: number, updates: ProductInfoUpdate): Promise<AdminProduct | null> {
|
||||
const [product] = await db.update(productInfo)
|
||||
.set(updates)
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning();
|
||||
return product;
|
||||
.returning()
|
||||
if (!product) {
|
||||
return null
|
||||
}
|
||||
|
||||
return mapProduct(product)
|
||||
}
|
||||
|
||||
export async function toggleProductOutOfStock(id: number, isOutOfStock: boolean): Promise<any> {
|
||||
const [product] = await db.update(productInfo)
|
||||
.set({ isOutOfStock })
|
||||
export async function toggleProductOutOfStock(id: number): Promise<AdminProduct | null> {
|
||||
const product = await db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, id),
|
||||
})
|
||||
|
||||
if (!product) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [updatedProduct] = await db
|
||||
.update(productInfo)
|
||||
.set({
|
||||
isOutOfStock: !product.isOutOfStock,
|
||||
})
|
||||
.where(eq(productInfo.id, id))
|
||||
.returning();
|
||||
return product;
|
||||
.returning()
|
||||
|
||||
if (!updatedProduct) {
|
||||
return null
|
||||
}
|
||||
|
||||
return mapProduct(updatedProduct)
|
||||
}
|
||||
|
||||
export async function getAllUnits(): Promise<any[]> {
|
||||
return await db.query.units.findMany({
|
||||
orderBy: units.name,
|
||||
});
|
||||
export async function updateSlotProducts(slotId: string, productIds: string[]): Promise<AdminUpdateSlotProductsResult> {
|
||||
const currentAssociations = await db.query.productSlots.findMany({
|
||||
where: eq(productSlots.slotId, parseInt(slotId)),
|
||||
columns: {
|
||||
productId: true,
|
||||
},
|
||||
})
|
||||
|
||||
const currentProductIds = currentAssociations.map((assoc) => assoc.productId)
|
||||
const newProductIds = productIds.map((id) => parseInt(id))
|
||||
|
||||
const productsToAdd = newProductIds.filter((id) => !currentProductIds.includes(id))
|
||||
const productsToRemove = currentProductIds.filter((id) => !newProductIds.includes(id))
|
||||
|
||||
if (productsToRemove.length > 0) {
|
||||
await db.delete(productSlots).where(
|
||||
and(
|
||||
eq(productSlots.slotId, parseInt(slotId)),
|
||||
inArray(productSlots.productId, productsToRemove)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (productsToAdd.length > 0) {
|
||||
const newAssociations = productsToAdd.map((productId) => ({
|
||||
productId,
|
||||
slotId: parseInt(slotId),
|
||||
}))
|
||||
|
||||
await db.insert(productSlots).values(newAssociations)
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Slot products updated successfully',
|
||||
added: productsToAdd.length,
|
||||
removed: productsToRemove.length,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllProductTags(): Promise<any[]> {
|
||||
return await db.query.productTags.findMany({
|
||||
export async function getSlotProductIds(slotId: string): Promise<number[]> {
|
||||
const associations = await db.query.productSlots.findMany({
|
||||
where: eq(productSlots.slotId, parseInt(slotId)),
|
||||
columns: {
|
||||
productId: true,
|
||||
},
|
||||
})
|
||||
|
||||
return associations.map((assoc) => assoc.productId)
|
||||
}
|
||||
|
||||
export async function getAllUnits(): Promise<AdminUnit[]> {
|
||||
const allUnits = await db.query.units.findMany({
|
||||
orderBy: units.shortNotation,
|
||||
})
|
||||
|
||||
return allUnits.map(mapUnit)
|
||||
}
|
||||
|
||||
export async function getAllProductTags(): Promise<AdminProductTagWithProducts[]> {
|
||||
const tags = await db.query.productTagInfo.findMany({
|
||||
with: {
|
||||
products: {
|
||||
with: {
|
||||
|
|
@ -69,56 +267,242 @@ export async function getAllProductTags(): Promise<any[]> {
|
|||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
return tags.map((tag) => ({
|
||||
...mapTagInfo(tag),
|
||||
products: tag.products.map((assignment) => ({
|
||||
productId: assignment.productId,
|
||||
tagId: assignment.tagId,
|
||||
assignedAt: assignment.assignedAt,
|
||||
product: mapProduct(assignment.product),
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getProductReviews(productId: number): Promise<any[]> {
|
||||
return await db.query.productReviews.findMany({
|
||||
where: eq(productReviews.productId, productId),
|
||||
with: {
|
||||
user: true,
|
||||
export async function getSlotsProductIds(slotIds: number[]): Promise<Record<number, number[]>> {
|
||||
if (slotIds.length === 0) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const associations = await db.query.productSlots.findMany({
|
||||
where: inArray(productSlots.slotId, slotIds),
|
||||
columns: {
|
||||
slotId: true,
|
||||
productId: true,
|
||||
},
|
||||
orderBy: desc(productReviews.createdAt),
|
||||
});
|
||||
})
|
||||
|
||||
const result: Record<number, number[]> = {}
|
||||
for (const assoc of associations) {
|
||||
if (!result[assoc.slotId]) {
|
||||
result[assoc.slotId] = []
|
||||
}
|
||||
result[assoc.slotId].push(assoc.productId)
|
||||
}
|
||||
|
||||
slotIds.forEach((slotId) => {
|
||||
if (!result[slotId]) {
|
||||
result[slotId] = []
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function respondToReview(reviewId: number, adminResponse: string): Promise<void> {
|
||||
await db.update(productReviews)
|
||||
.set({ adminResponse })
|
||||
.where(eq(productReviews.id, reviewId));
|
||||
export async function getProductReviews(productId: number, limit: number, offset: number) {
|
||||
const reviews = await db
|
||||
.select({
|
||||
id: productReviews.id,
|
||||
reviewBody: productReviews.reviewBody,
|
||||
ratings: productReviews.ratings,
|
||||
imageUrls: productReviews.imageUrls,
|
||||
reviewTime: productReviews.reviewTime,
|
||||
adminResponse: productReviews.adminResponse,
|
||||
adminResponseImages: productReviews.adminResponseImages,
|
||||
userName: users.name,
|
||||
})
|
||||
.from(productReviews)
|
||||
.innerJoin(users, eq(productReviews.userId, users.id))
|
||||
.where(eq(productReviews.productId, productId))
|
||||
.orderBy(desc(productReviews.reviewTime))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
|
||||
const totalCountResult = await db
|
||||
.select({ count: sql`count(*)` })
|
||||
.from(productReviews)
|
||||
.where(eq(productReviews.productId, productId))
|
||||
|
||||
const totalCount = Number(totalCountResult[0].count)
|
||||
|
||||
const mappedReviews: AdminProductReview[] = reviews.map((review) => ({
|
||||
id: review.id,
|
||||
reviewBody: review.reviewBody,
|
||||
ratings: review.ratings,
|
||||
imageUrls: review.imageUrls,
|
||||
reviewTime: review.reviewTime,
|
||||
adminResponse: review.adminResponse ?? null,
|
||||
adminResponseImages: review.adminResponseImages,
|
||||
userName: review.userName ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
reviews: mappedReviews,
|
||||
totalCount,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllProductGroups(): Promise<any[]> {
|
||||
return await db.query.productGroupInfo.findMany({
|
||||
export async function respondToReview(
|
||||
reviewId: number,
|
||||
adminResponse: string | undefined,
|
||||
adminResponseImages: string[]
|
||||
): Promise<AdminProductReview | null> {
|
||||
const [updatedReview] = await db
|
||||
.update(productReviews)
|
||||
.set({
|
||||
adminResponse,
|
||||
adminResponseImages,
|
||||
})
|
||||
.where(eq(productReviews.id, reviewId))
|
||||
.returning()
|
||||
|
||||
if (!updatedReview) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: updatedReview.id,
|
||||
reviewBody: updatedReview.reviewBody,
|
||||
ratings: updatedReview.ratings,
|
||||
imageUrls: updatedReview.imageUrls,
|
||||
reviewTime: updatedReview.reviewTime,
|
||||
adminResponse: updatedReview.adminResponse ?? null,
|
||||
adminResponseImages: updatedReview.adminResponseImages,
|
||||
userName: null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllProductGroups() {
|
||||
const groups = await db.query.productGroupInfo.findMany({
|
||||
with: {
|
||||
products: {
|
||||
memberships: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
orderBy: desc(productGroupInfo.createdAt),
|
||||
})
|
||||
|
||||
return groups.map((group) => ({
|
||||
id: group.id,
|
||||
groupName: group.groupName,
|
||||
description: group.description ?? null,
|
||||
createdAt: group.createdAt,
|
||||
products: group.memberships.map((membership) => mapProduct(membership.product)),
|
||||
productCount: group.memberships.length,
|
||||
memberships: group.memberships
|
||||
}))
|
||||
}
|
||||
|
||||
export async function createProductGroup(name: string): Promise<any> {
|
||||
const [group] = await db.insert(productGroupInfo).values({ name }).returning();
|
||||
return group;
|
||||
export async function createProductGroup(
|
||||
groupName: string,
|
||||
description: string | undefined,
|
||||
productIds: number[]
|
||||
): Promise<AdminProductGroupInfo> {
|
||||
const [newGroup] = await db
|
||||
.insert(productGroupInfo)
|
||||
.values({
|
||||
groupName,
|
||||
description,
|
||||
})
|
||||
.returning()
|
||||
|
||||
if (productIds.length > 0) {
|
||||
const memberships = productIds.map((productId) => ({
|
||||
productId,
|
||||
groupId: newGroup.id,
|
||||
}))
|
||||
|
||||
await db.insert(productGroupMembership).values(memberships)
|
||||
}
|
||||
|
||||
return {
|
||||
id: newGroup.id,
|
||||
groupName: newGroup.groupName,
|
||||
description: newGroup.description ?? null,
|
||||
createdAt: newGroup.createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateProductGroup(id: number, name: string): Promise<any> {
|
||||
const [group] = await db.update(productGroupInfo)
|
||||
.set({ name })
|
||||
export async function updateProductGroup(
|
||||
id: number,
|
||||
groupName: string | undefined,
|
||||
description: string | undefined,
|
||||
productIds: number[] | undefined
|
||||
): Promise<AdminProductGroupInfo | null> {
|
||||
const updateData: Partial<{
|
||||
groupName: string
|
||||
description: string | null
|
||||
}> = {}
|
||||
|
||||
if (groupName !== undefined) updateData.groupName = groupName
|
||||
if (description !== undefined) updateData.description = description
|
||||
|
||||
const [updatedGroup] = await db
|
||||
.update(productGroupInfo)
|
||||
.set(updateData)
|
||||
.where(eq(productGroupInfo.id, id))
|
||||
.returning();
|
||||
return group;
|
||||
.returning()
|
||||
|
||||
if (!updatedGroup) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (productIds !== undefined) {
|
||||
await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id))
|
||||
|
||||
if (productIds.length > 0) {
|
||||
const memberships = productIds.map((productId) => ({
|
||||
productId,
|
||||
groupId: id,
|
||||
}))
|
||||
|
||||
await db.insert(productGroupMembership).values(memberships)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: updatedGroup.id,
|
||||
groupName: updatedGroup.groupName,
|
||||
description: updatedGroup.description ?? null,
|
||||
createdAt: updatedGroup.createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteProductGroup(id: number): Promise<void> {
|
||||
await db.delete(productGroupInfo).where(eq(productGroupInfo.id, id));
|
||||
export async function deleteProductGroup(id: number): Promise<AdminProductGroupInfo | null> {
|
||||
await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id))
|
||||
|
||||
const [deletedGroup] = await db
|
||||
.delete(productGroupInfo)
|
||||
.where(eq(productGroupInfo.id, id))
|
||||
.returning()
|
||||
|
||||
if (!deletedGroup) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: deletedGroup.id,
|
||||
groupName: deletedGroup.groupName,
|
||||
description: deletedGroup.description ?? null,
|
||||
createdAt: deletedGroup.createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
|
||||
await db.insert(productGroupMembership).values({ groupId, productId });
|
||||
await db.insert(productGroupMembership).values({ groupId, productId })
|
||||
}
|
||||
|
||||
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
|
||||
|
|
@ -126,5 +510,49 @@ export async function removeProductFromGroup(groupId: number, productId: number)
|
|||
.where(and(
|
||||
eq(productGroupMembership.groupId, groupId),
|
||||
eq(productGroupMembership.productId, productId)
|
||||
));
|
||||
))
|
||||
}
|
||||
|
||||
export async function updateProductPrices(updates: Array<{
|
||||
productId: number
|
||||
price?: number
|
||||
marketPrice?: number | null
|
||||
flashPrice?: number | null
|
||||
isFlashAvailable?: boolean
|
||||
}>) {
|
||||
if (updates.length === 0) {
|
||||
return { updatedCount: 0, invalidIds: [] }
|
||||
}
|
||||
|
||||
const productIds = updates.map((update) => update.productId)
|
||||
const existingProducts = await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIds),
|
||||
columns: { id: true },
|
||||
})
|
||||
|
||||
const existingIds = new Set(existingProducts.map((product) => product.id))
|
||||
const invalidIds = productIds.filter((id) => !existingIds.has(id))
|
||||
|
||||
if (invalidIds.length > 0) {
|
||||
return { updatedCount: 0, invalidIds }
|
||||
}
|
||||
|
||||
const updatePromises = updates.map((update) => {
|
||||
const { productId, price, marketPrice, flashPrice, isFlashAvailable } = update
|
||||
const updateData: Partial<Pick<ProductInfoInsert, 'price' | 'marketPrice' | 'flashPrice' | 'isFlashAvailable'>> = {}
|
||||
|
||||
if (price !== undefined) updateData.price = price.toString()
|
||||
if (marketPrice !== undefined) updateData.marketPrice = marketPrice === null ? null : marketPrice.toString()
|
||||
if (flashPrice !== undefined) updateData.flashPrice = flashPrice === null ? null : flashPrice.toString()
|
||||
if (isFlashAvailable !== undefined) updateData.isFlashAvailable = isFlashAvailable
|
||||
|
||||
return db
|
||||
.update(productInfo)
|
||||
.set(updateData)
|
||||
.where(eq(productInfo.id, productId))
|
||||
})
|
||||
|
||||
await Promise.all(updatePromises)
|
||||
|
||||
return { updatedCount: updates.length, invalidIds: [] }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,95 +1,351 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { deliverySlotInfo, productSlots, productInfo } from '../db/schema';
|
||||
import { eq, and, inArray, desc } from 'drizzle-orm';
|
||||
import { db } from '../db/db_index'
|
||||
import {
|
||||
deliverySlotInfo,
|
||||
productSlots,
|
||||
productInfo,
|
||||
vendorSnippets,
|
||||
productGroupInfo,
|
||||
} from '../db/schema'
|
||||
import { and, asc, desc, eq, gt, inArray } from 'drizzle-orm'
|
||||
import type {
|
||||
AdminDeliverySlot,
|
||||
AdminSlotWithProducts,
|
||||
AdminSlotWithProductsAndSnippetsBase,
|
||||
AdminSlotCreateResult,
|
||||
AdminSlotUpdateResult,
|
||||
AdminVendorSnippet,
|
||||
AdminSlotProductSummary,
|
||||
AdminUpdateSlotCapacityResult,
|
||||
} from '@packages/shared'
|
||||
|
||||
export async function getAllSlots(): Promise<any[]> {
|
||||
return await db.query.deliverySlotInfo.findMany({
|
||||
orderBy: desc(deliverySlotInfo.createdAt),
|
||||
with: {
|
||||
productSlots: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
type SlotSnippetInput = {
|
||||
name: string
|
||||
productIds: number[]
|
||||
validTill?: string
|
||||
}
|
||||
|
||||
export async function getSlotById(id: number): Promise<any | null> {
|
||||
return await db.query.deliverySlotInfo.findFirst({
|
||||
const getStringArray = (value: unknown): string[] | null => {
|
||||
if (!Array.isArray(value)) return null
|
||||
return value.map((item) => String(item))
|
||||
}
|
||||
|
||||
const getNumberArray = (value: unknown): number[] => {
|
||||
if (!Array.isArray(value)) return []
|
||||
return value.map((item) => Number(item))
|
||||
}
|
||||
|
||||
const mapDeliverySlot = (slot: typeof deliverySlotInfo.$inferSelect): AdminDeliverySlot => ({
|
||||
id: slot.id,
|
||||
deliveryTime: slot.deliveryTime,
|
||||
freezeTime: slot.freezeTime,
|
||||
isActive: slot.isActive,
|
||||
isFlash: slot.isFlash,
|
||||
isCapacityFull: slot.isCapacityFull,
|
||||
deliverySequence: slot.deliverySequence,
|
||||
groupIds: slot.groupIds,
|
||||
})
|
||||
|
||||
const mapSlotProductSummary = (product: { id: number; name: string; images: unknown }): AdminSlotProductSummary => ({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
images: getStringArray(product.images),
|
||||
})
|
||||
|
||||
const mapVendorSnippet = (snippet: typeof vendorSnippets.$inferSelect): AdminVendorSnippet => ({
|
||||
id: snippet.id,
|
||||
snippetCode: snippet.snippetCode,
|
||||
slotId: snippet.slotId ?? null,
|
||||
productIds: snippet.productIds,
|
||||
isPermanent: snippet.isPermanent,
|
||||
validTill: snippet.validTill ?? null,
|
||||
createdAt: snippet.createdAt,
|
||||
})
|
||||
|
||||
export async function getActiveSlotsWithProducts(): Promise<AdminSlotWithProducts[]> {
|
||||
const slots = await db.query.deliverySlotInfo
|
||||
.findMany({
|
||||
where: eq(deliverySlotInfo.isActive, true),
|
||||
orderBy: desc(deliverySlotInfo.deliveryTime),
|
||||
with: {
|
||||
productSlots: {
|
||||
with: {
|
||||
product: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
images: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return slots.map((slot) => ({
|
||||
...mapDeliverySlot(slot),
|
||||
deliverySequence: getNumberArray(slot.deliverySequence),
|
||||
products: slot.productSlots.map((ps) => mapSlotProductSummary(ps.product)),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getActiveSlots(): Promise<AdminDeliverySlot[]> {
|
||||
const slots = await db.query.deliverySlotInfo.findMany({
|
||||
where: eq(deliverySlotInfo.isActive, true),
|
||||
})
|
||||
|
||||
return slots.map(mapDeliverySlot)
|
||||
}
|
||||
|
||||
export async function getSlotsAfterDate(afterDate: Date): Promise<AdminDeliverySlot[]> {
|
||||
const slots = await db.query.deliverySlotInfo.findMany({
|
||||
where: and(
|
||||
eq(deliverySlotInfo.isActive, true),
|
||||
gt(deliverySlotInfo.deliveryTime, afterDate)
|
||||
),
|
||||
orderBy: asc(deliverySlotInfo.deliveryTime),
|
||||
})
|
||||
|
||||
return slots.map(mapDeliverySlot)
|
||||
}
|
||||
|
||||
export async function getSlotByIdWithRelations(id: number): Promise<AdminSlotWithProductsAndSnippetsBase | null> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, id),
|
||||
with: {
|
||||
productSlots: {
|
||||
with: {
|
||||
product: true,
|
||||
product: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
images: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
vendorSnippets: true,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
if (!slot) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...mapDeliverySlot(slot),
|
||||
deliverySequence: getNumberArray(slot.deliverySequence),
|
||||
groupIds: getNumberArray(slot.groupIds),
|
||||
products: slot.productSlots.map((ps) => mapSlotProductSummary(ps.product)),
|
||||
vendorSnippets: slot.vendorSnippets.map(mapVendorSnippet),
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSlot(input: any): Promise<any> {
|
||||
const [slot] = await db.insert(deliverySlotInfo).values(input).returning();
|
||||
return slot;
|
||||
export async function createSlotWithRelations(input: {
|
||||
deliveryTime: string
|
||||
freezeTime: string
|
||||
isActive?: boolean
|
||||
productIds?: number[]
|
||||
vendorSnippets?: SlotSnippetInput[]
|
||||
groupIds?: number[]
|
||||
}): Promise<AdminSlotCreateResult> {
|
||||
const { deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets, groupIds } = input
|
||||
|
||||
const result = await db.transaction(async (tx) => {
|
||||
const [newSlot] = await tx
|
||||
.insert(deliverySlotInfo)
|
||||
.values({
|
||||
deliveryTime: new Date(deliveryTime),
|
||||
freezeTime: new Date(freezeTime),
|
||||
isActive: isActive !== undefined ? isActive : true,
|
||||
groupIds: groupIds !== undefined ? groupIds : [],
|
||||
})
|
||||
.returning()
|
||||
|
||||
if (productIds && productIds.length > 0) {
|
||||
const associations = productIds.map((productId) => ({
|
||||
productId,
|
||||
slotId: newSlot.id,
|
||||
}))
|
||||
await tx.insert(productSlots).values(associations)
|
||||
}
|
||||
|
||||
let createdSnippets: AdminVendorSnippet[] = []
|
||||
if (snippets && snippets.length > 0) {
|
||||
for (const snippet of snippets) {
|
||||
const products = await tx.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, snippet.productIds),
|
||||
})
|
||||
if (products.length !== snippet.productIds.length) {
|
||||
throw new Error(`One or more invalid product IDs in snippet "${snippet.name}"`)
|
||||
}
|
||||
|
||||
const existingSnippet = await tx.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippet.name),
|
||||
})
|
||||
if (existingSnippet) {
|
||||
throw new Error(`Snippet name "${snippet.name}" already exists`)
|
||||
}
|
||||
|
||||
const [createdSnippet] = await tx.insert(vendorSnippets).values({
|
||||
snippetCode: snippet.name,
|
||||
slotId: newSlot.id,
|
||||
productIds: snippet.productIds,
|
||||
validTill: snippet.validTill ? new Date(snippet.validTill) : undefined,
|
||||
}).returning()
|
||||
|
||||
createdSnippets.push(mapVendorSnippet(createdSnippet))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
slot: mapDeliverySlot(newSlot),
|
||||
createdSnippets,
|
||||
message: 'Slot created successfully',
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function updateSlot(id: number, updates: any): Promise<any> {
|
||||
const [slot] = await db.update(deliverySlotInfo)
|
||||
.set(updates)
|
||||
export async function updateSlotWithRelations(input: {
|
||||
id: number
|
||||
deliveryTime: string
|
||||
freezeTime: string
|
||||
isActive?: boolean
|
||||
productIds?: number[]
|
||||
vendorSnippets?: SlotSnippetInput[]
|
||||
groupIds?: number[]
|
||||
}): Promise<AdminSlotUpdateResult | null> {
|
||||
const { id, deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets, groupIds } = input
|
||||
|
||||
let validGroupIds = groupIds
|
||||
if (groupIds && groupIds.length > 0) {
|
||||
const existingGroups = await db.query.productGroupInfo.findMany({
|
||||
where: inArray(productGroupInfo.id, groupIds),
|
||||
columns: { id: true },
|
||||
})
|
||||
validGroupIds = existingGroups.map((group) => group.id)
|
||||
}
|
||||
|
||||
const result = await db.transaction(async (tx) => {
|
||||
const [updatedSlot] = await tx
|
||||
.update(deliverySlotInfo)
|
||||
.set({
|
||||
deliveryTime: new Date(deliveryTime),
|
||||
freezeTime: new Date(freezeTime),
|
||||
isActive: isActive !== undefined ? isActive : true,
|
||||
groupIds: validGroupIds !== undefined ? validGroupIds : [],
|
||||
})
|
||||
.where(eq(deliverySlotInfo.id, id))
|
||||
.returning()
|
||||
|
||||
if (!updatedSlot) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (productIds !== undefined) {
|
||||
await tx.delete(productSlots).where(eq(productSlots.slotId, id))
|
||||
|
||||
if (productIds.length > 0) {
|
||||
const associations = productIds.map((productId) => ({
|
||||
productId,
|
||||
slotId: id,
|
||||
}))
|
||||
await tx.insert(productSlots).values(associations)
|
||||
}
|
||||
}
|
||||
|
||||
let createdSnippets: AdminVendorSnippet[] = []
|
||||
if (snippets && snippets.length > 0) {
|
||||
for (const snippet of snippets) {
|
||||
const products = await tx.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, snippet.productIds),
|
||||
})
|
||||
if (products.length !== snippet.productIds.length) {
|
||||
throw new Error(`One or more invalid product IDs in snippet "${snippet.name}"`)
|
||||
}
|
||||
|
||||
const existingSnippet = await tx.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippet.name),
|
||||
})
|
||||
if (existingSnippet) {
|
||||
throw new Error(`Snippet name "${snippet.name}" already exists`)
|
||||
}
|
||||
|
||||
const [createdSnippet] = await tx.insert(vendorSnippets).values({
|
||||
snippetCode: snippet.name,
|
||||
slotId: id,
|
||||
productIds: snippet.productIds,
|
||||
validTill: snippet.validTill ? new Date(snippet.validTill) : undefined,
|
||||
}).returning()
|
||||
|
||||
createdSnippets.push(mapVendorSnippet(createdSnippet))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
slot: mapDeliverySlot(updatedSlot),
|
||||
createdSnippets,
|
||||
message: 'Slot updated successfully',
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function deleteSlotById(id: number): Promise<AdminDeliverySlot | null> {
|
||||
const [deletedSlot] = await db
|
||||
.update(deliverySlotInfo)
|
||||
.set({ isActive: false })
|
||||
.where(eq(deliverySlotInfo.id, id))
|
||||
.returning();
|
||||
return slot;
|
||||
.returning()
|
||||
|
||||
if (!deletedSlot) {
|
||||
return null
|
||||
}
|
||||
|
||||
return mapDeliverySlot(deletedSlot)
|
||||
}
|
||||
|
||||
export async function deleteSlot(id: number): Promise<void> {
|
||||
await db.delete(deliverySlotInfo).where(eq(deliverySlotInfo.id, id));
|
||||
}
|
||||
|
||||
export async function getSlotProducts(slotId: number): Promise<any[]> {
|
||||
return await db.query.productSlots.findMany({
|
||||
where: eq(productSlots.slotId, slotId),
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function addProductToSlot(slotId: number, productId: number): Promise<void> {
|
||||
await db.insert(productSlots).values({ slotId, productId });
|
||||
}
|
||||
|
||||
export async function removeProductFromSlot(slotId: number, productId: number): Promise<void> {
|
||||
await db.delete(productSlots)
|
||||
.where(and(
|
||||
eq(productSlots.slotId, slotId),
|
||||
eq(productSlots.productId, productId)
|
||||
));
|
||||
}
|
||||
|
||||
export async function clearSlotProducts(slotId: number): Promise<void> {
|
||||
await db.delete(productSlots).where(eq(productSlots.slotId, slotId));
|
||||
}
|
||||
|
||||
export async function updateSlotCapacity(slotId: number, maxCapacity: number): Promise<any> {
|
||||
const [slot] = await db.update(deliverySlotInfo)
|
||||
.set({ maxCapacity })
|
||||
.where(eq(deliverySlotInfo.id, slotId))
|
||||
.returning();
|
||||
return slot;
|
||||
}
|
||||
|
||||
export async function getSlotDeliverySequence(slotId: number): Promise<any | null> {
|
||||
export async function getSlotDeliverySequence(slotId: number): Promise<AdminDeliverySlot | null> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
columns: {
|
||||
deliverySequence: true,
|
||||
},
|
||||
});
|
||||
return slot?.deliverySequence || null;
|
||||
})
|
||||
|
||||
if (!slot) {
|
||||
return null
|
||||
}
|
||||
|
||||
return mapDeliverySlot(slot)
|
||||
}
|
||||
|
||||
export async function updateSlotDeliverySequence(slotId: number, sequence: any): Promise<void> {
|
||||
await db.update(deliverySlotInfo)
|
||||
export async function updateSlotDeliverySequence(slotId: number, sequence: unknown) {
|
||||
const [updatedSlot] = await db
|
||||
.update(deliverySlotInfo)
|
||||
.set({ deliverySequence: sequence })
|
||||
.where(eq(deliverySlotInfo.id, slotId));
|
||||
.where(eq(deliverySlotInfo.id, slotId))
|
||||
.returning({
|
||||
id: deliverySlotInfo.id,
|
||||
deliverySequence: deliverySlotInfo.deliverySequence,
|
||||
})
|
||||
|
||||
return updatedSlot || null
|
||||
}
|
||||
|
||||
export async function updateSlotCapacity(slotId: number, isCapacityFull: boolean): Promise<AdminUpdateSlotCapacityResult | null> {
|
||||
const [updatedSlot] = await db
|
||||
.update(deliverySlotInfo)
|
||||
.set({ isCapacityFull })
|
||||
.where(eq(deliverySlotInfo.id, slotId))
|
||||
.returning()
|
||||
|
||||
if (!updatedSlot) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
slot: mapDeliverySlot(updatedSlot),
|
||||
message: `Slot ${isCapacityFull ? 'marked as full capacity' : 'capacity reset'}`,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export interface StaffUser {
|
|||
id: number;
|
||||
name: string;
|
||||
password: string;
|
||||
staffRoleId: number;
|
||||
staffRoleId: number | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export interface Store {
|
|||
imageUrl: string | null;
|
||||
owner: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
// updatedAt: Date;
|
||||
}
|
||||
|
||||
export async function getAllStores(): Promise<any[]> {
|
||||
|
|
@ -68,7 +68,7 @@ export async function createStore(
|
|||
imageUrl: newStore.imageUrl,
|
||||
owner: newStore.owner,
|
||||
createdAt: newStore.createdAt,
|
||||
updatedAt: newStore.updatedAt,
|
||||
// updatedAt: newStore.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ export async function updateStore(
|
|||
.update(storeInfo)
|
||||
.set({
|
||||
...input,
|
||||
updatedAt: new Date(),
|
||||
// updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(storeInfo.id, id))
|
||||
.returning();
|
||||
|
|
@ -118,7 +118,7 @@ export async function updateStore(
|
|||
imageUrl: updatedStore.imageUrl,
|
||||
owner: updatedStore.owner,
|
||||
createdAt: updatedStore.createdAt,
|
||||
updatedAt: updatedStore.updatedAt,
|
||||
// updatedAt: updatedStore.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,86 +1,152 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, orderStatus } from '../db/schema';
|
||||
import { eq, and, inArray, gt, sql, asc } from 'drizzle-orm';
|
||||
import { db } from '../db/db_index'
|
||||
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, orderStatus } from '../db/schema'
|
||||
import { desc, eq, inArray } from 'drizzle-orm'
|
||||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
import type {
|
||||
AdminDeliverySlot,
|
||||
AdminVendorSnippet,
|
||||
AdminVendorSnippetWithSlot,
|
||||
AdminVendorSnippetProduct,
|
||||
AdminVendorUpdatePackagingResult,
|
||||
} from '@packages/shared'
|
||||
|
||||
type VendorSnippetRow = InferSelectModel<typeof vendorSnippets>
|
||||
type DeliverySlotRow = InferSelectModel<typeof deliverySlotInfo>
|
||||
type ProductRow = InferSelectModel<typeof productInfo>
|
||||
|
||||
const mapVendorSnippet = (snippet: VendorSnippetRow): AdminVendorSnippet => ({
|
||||
id: snippet.id,
|
||||
snippetCode: snippet.snippetCode,
|
||||
slotId: snippet.slotId ?? null,
|
||||
productIds: snippet.productIds,
|
||||
isPermanent: snippet.isPermanent,
|
||||
validTill: snippet.validTill ?? null,
|
||||
createdAt: snippet.createdAt,
|
||||
})
|
||||
|
||||
const mapDeliverySlot = (slot: DeliverySlotRow): AdminDeliverySlot => ({
|
||||
id: slot.id,
|
||||
deliveryTime: slot.deliveryTime,
|
||||
freezeTime: slot.freezeTime,
|
||||
isActive: slot.isActive,
|
||||
isFlash: slot.isFlash,
|
||||
isCapacityFull: slot.isCapacityFull,
|
||||
deliverySequence: slot.deliverySequence,
|
||||
groupIds: slot.groupIds,
|
||||
})
|
||||
|
||||
const mapProductSummary = (product:{id:number, name: string}): AdminVendorSnippetProduct => ({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
})
|
||||
|
||||
export async function checkVendorSnippetExists(snippetCode: string): Promise<boolean> {
|
||||
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||
});
|
||||
return !!existingSnippet;
|
||||
})
|
||||
return !!existingSnippet
|
||||
}
|
||||
|
||||
export async function getVendorSnippetById(id: number): Promise<any | null> {
|
||||
return await db.query.vendorSnippets.findFirst({
|
||||
export async function getVendorSnippetById(id: number): Promise<AdminVendorSnippetWithSlot | null> {
|
||||
const snippet = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.id, id),
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
if (!snippet) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...mapVendorSnippet(snippet),
|
||||
slot: snippet.slot ? mapDeliverySlot(snippet.slot) : null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getVendorSnippetByCode(snippetCode: string): Promise<any | null> {
|
||||
return await db.query.vendorSnippets.findFirst({
|
||||
export async function getVendorSnippetByCode(snippetCode: string): Promise<AdminVendorSnippet | null> {
|
||||
const snippet = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||
});
|
||||
})
|
||||
|
||||
return snippet ? mapVendorSnippet(snippet) : null
|
||||
}
|
||||
|
||||
export async function getAllVendorSnippets(): Promise<any[]> {
|
||||
return await db.query.vendorSnippets.findMany({
|
||||
export async function getAllVendorSnippets(): Promise<AdminVendorSnippetWithSlot[]> {
|
||||
const snippets = await db.query.vendorSnippets.findMany({
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
orderBy: (vendorSnippets, { desc }) => [desc(vendorSnippets.createdAt)],
|
||||
});
|
||||
})
|
||||
|
||||
return snippets.map((snippet) => ({
|
||||
...mapVendorSnippet(snippet),
|
||||
slot: snippet.slot ? mapDeliverySlot(snippet.slot) : null,
|
||||
}))
|
||||
}
|
||||
|
||||
export interface CreateVendorSnippetInput {
|
||||
snippetCode: string;
|
||||
slotId?: number;
|
||||
productIds: number[];
|
||||
isPermanent: boolean;
|
||||
validTill?: Date;
|
||||
}
|
||||
|
||||
export async function createVendorSnippet(input: CreateVendorSnippetInput): Promise<any> {
|
||||
export async function createVendorSnippet(input: {
|
||||
snippetCode: string
|
||||
slotId?: number
|
||||
productIds: number[]
|
||||
isPermanent: boolean
|
||||
validTill?: Date
|
||||
}): Promise<AdminVendorSnippet> {
|
||||
const [result] = await db.insert(vendorSnippets).values({
|
||||
snippetCode: input.snippetCode,
|
||||
slotId: input.slotId,
|
||||
productIds: input.productIds,
|
||||
isPermanent: input.isPermanent,
|
||||
validTill: input.validTill,
|
||||
}).returning();
|
||||
}).returning()
|
||||
|
||||
return result;
|
||||
return mapVendorSnippet(result)
|
||||
}
|
||||
|
||||
export async function updateVendorSnippet(id: number, updates: any): Promise<any> {
|
||||
export async function updateVendorSnippet(id: number, updates: {
|
||||
snippetCode?: string
|
||||
slotId?: number | null
|
||||
productIds?: number[]
|
||||
isPermanent?: boolean
|
||||
validTill?: Date | null
|
||||
}): Promise<AdminVendorSnippet | null> {
|
||||
const [result] = await db.update(vendorSnippets)
|
||||
.set(updates)
|
||||
.where(eq(vendorSnippets.id, id))
|
||||
.returning();
|
||||
.returning()
|
||||
|
||||
return result;
|
||||
return result ? mapVendorSnippet(result) : null
|
||||
}
|
||||
|
||||
export async function deleteVendorSnippet(id: number): Promise<void> {
|
||||
await db.delete(vendorSnippets)
|
||||
.where(eq(vendorSnippets.id, id));
|
||||
export async function deleteVendorSnippet(id: number): Promise<AdminVendorSnippet | null> {
|
||||
const [result] = await db.delete(vendorSnippets)
|
||||
.where(eq(vendorSnippets.id, id))
|
||||
.returning()
|
||||
|
||||
return result ? mapVendorSnippet(result) : null
|
||||
}
|
||||
|
||||
export async function getProductsByIds(productIds: number[]): Promise<any[]> {
|
||||
return await db.query.productInfo.findMany({
|
||||
export async function getProductsByIds(productIds: number[]): Promise<AdminVendorSnippetProduct[]> {
|
||||
const products = await db.query.productInfo.findMany({
|
||||
where: inArray(productInfo.id, productIds),
|
||||
columns: { id: true, name: true },
|
||||
});
|
||||
})
|
||||
|
||||
const prods = products.map(mapProductSummary)
|
||||
return prods;
|
||||
}
|
||||
|
||||
export async function getVendorSlotById(slotId: number): Promise<any | null> {
|
||||
return await db.query.deliverySlotInfo.findFirst({
|
||||
export async function getVendorSlotById(slotId: number): Promise<AdminDeliverySlot | null> {
|
||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||
where: eq(deliverySlotInfo.id, slotId),
|
||||
});
|
||||
})
|
||||
|
||||
return slot ? mapDeliverySlot(slot) : null
|
||||
}
|
||||
|
||||
export async function getVendorOrdersBySlotId(slotId: number): Promise<any[]> {
|
||||
export async function getVendorOrdersBySlotId(slotId: number) {
|
||||
return await db.query.orders.findMany({
|
||||
where: eq(orders.slotId, slotId),
|
||||
with: {
|
||||
|
|
@ -98,10 +164,28 @@ export async function getVendorOrdersBySlotId(slotId: number): Promise<any[]> {
|
|||
slot: true,
|
||||
},
|
||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]): Promise<any[]> {
|
||||
export async function getVendorOrders() {
|
||||
return await db.query.orders.findMany({
|
||||
with: {
|
||||
user: true,
|
||||
orderItems: {
|
||||
with: {
|
||||
product: {
|
||||
with: {
|
||||
unit: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||
})
|
||||
}
|
||||
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderItems.findMany({
|
||||
where: inArray(orderItems.orderId, orderIds),
|
||||
with: {
|
||||
|
|
@ -111,20 +195,56 @@ export async function getOrderItemsByOrderIds(orderIds: number[]): Promise<any[]
|
|||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export async function getOrderStatusByOrderIds(orderIds: number[]): Promise<any[]> {
|
||||
export async function getOrderStatusByOrderIds(orderIds: number[]) {
|
||||
return await db.query.orderStatus.findMany({
|
||||
where: inArray(orderStatus.orderId, orderIds),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateVendorOrderItemPackaging(orderItemId: number, isPackaged: boolean, isPackageVerified: boolean): Promise<void> {
|
||||
await db.update(orderItems)
|
||||
export async function updateVendorOrderItemPackaging(
|
||||
orderItemId: number,
|
||||
isPackaged: boolean
|
||||
): Promise<AdminVendorUpdatePackagingResult> {
|
||||
const orderItem = await db.query.orderItems.findFirst({
|
||||
where: eq(orderItems.id, orderItemId),
|
||||
with: {
|
||||
order: {
|
||||
with: {
|
||||
slot: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (!orderItem) {
|
||||
return { success: false, message: 'Order item not found' }
|
||||
}
|
||||
|
||||
if (!orderItem.order.slotId) {
|
||||
return { success: false, message: 'Order item not associated with a vendor slot' }
|
||||
}
|
||||
|
||||
const snippetExists = await db.query.vendorSnippets.findFirst({
|
||||
where: eq(vendorSnippets.slotId, orderItem.order.slotId),
|
||||
})
|
||||
|
||||
if (!snippetExists) {
|
||||
return { success: false, message: "No vendor snippet found for this order's slot" }
|
||||
}
|
||||
|
||||
const [updatedItem] = await db.update(orderItems)
|
||||
.set({
|
||||
is_packaged: isPackaged,
|
||||
is_package_verified: isPackageVerified,
|
||||
})
|
||||
.where(eq(orderItems.id, orderItemId));
|
||||
.where(eq(orderItems.id, orderItemId))
|
||||
.returning({ id: orderItems.id })
|
||||
|
||||
if (!updatedItem) {
|
||||
return { success: false, message: 'Failed to update packaging status' }
|
||||
}
|
||||
|
||||
return { success: true, orderItemId, is_packaged: isPackaged }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export interface Store {
|
|||
imageUrl: string | null;
|
||||
owner: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
// updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface StaffUser {
|
||||
|
|
@ -347,3 +347,367 @@ export interface AdminCancelOrderResult {
|
|||
userId?: number;
|
||||
error?: AdminCancelOrderError;
|
||||
}
|
||||
|
||||
export interface AdminUnit {
|
||||
id: number;
|
||||
shortNotation: string;
|
||||
fullName: string;
|
||||
}
|
||||
|
||||
export interface AdminProduct {
|
||||
id: number;
|
||||
name: string;
|
||||
shortDescription: string | null;
|
||||
longDescription: string | null;
|
||||
unitId: number;
|
||||
price: string;
|
||||
marketPrice: string | null;
|
||||
images: string[] | null;
|
||||
isOutOfStock: boolean;
|
||||
isSuspended: boolean;
|
||||
isFlashAvailable: boolean;
|
||||
flashPrice: string | null;
|
||||
createdAt: Date;
|
||||
incrementStep: number;
|
||||
productQuantity: number;
|
||||
storeId: number | null;
|
||||
}
|
||||
|
||||
export interface AdminProductWithRelations extends AdminProduct {
|
||||
unit: AdminUnit;
|
||||
store: Store | null;
|
||||
}
|
||||
|
||||
export interface AdminProductTagInfo {
|
||||
id: number;
|
||||
tagName: string;
|
||||
tagDescription: string | null;
|
||||
imageUrl: string | null;
|
||||
isDashboardTag: boolean;
|
||||
relatedStores: unknown;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface AdminProductTagAssignment {
|
||||
productId: number;
|
||||
tagId: number;
|
||||
assignedAt: Date;
|
||||
product: AdminProduct;
|
||||
}
|
||||
|
||||
export interface AdminProductTagWithProducts extends AdminProductTagInfo {
|
||||
products: AdminProductTagAssignment[];
|
||||
}
|
||||
|
||||
export interface AdminSpecialDeal {
|
||||
id: number;
|
||||
productId: number;
|
||||
quantity: string;
|
||||
price: string;
|
||||
validTill: Date;
|
||||
}
|
||||
|
||||
export interface AdminProductWithDetails extends AdminProduct {
|
||||
unit: AdminUnit;
|
||||
deals: AdminSpecialDeal[];
|
||||
tags: AdminProductTagInfo[];
|
||||
}
|
||||
|
||||
export interface AdminProductListResponse {
|
||||
products: AdminProductWithRelations[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface AdminProductResponse {
|
||||
product: AdminProductWithDetails;
|
||||
}
|
||||
|
||||
export interface AdminDeleteProductResult {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AdminToggleOutOfStockResult {
|
||||
product: AdminProduct;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AdminUpdateSlotProductsResult {
|
||||
message: string;
|
||||
added: number;
|
||||
removed: number;
|
||||
}
|
||||
|
||||
export interface AdminSlotProductIdsResult {
|
||||
productIds: number[];
|
||||
}
|
||||
|
||||
export type AdminSlotsProductIdsResult = Record<number, number[]>;
|
||||
|
||||
export interface AdminProductReview {
|
||||
id: number;
|
||||
reviewBody: string;
|
||||
ratings: number;
|
||||
imageUrls: unknown;
|
||||
reviewTime: Date;
|
||||
adminResponse: string | null;
|
||||
adminResponseImages: unknown;
|
||||
userName: string | null;
|
||||
}
|
||||
|
||||
export interface AdminProductReviewWithSignedUrls extends AdminProductReview {
|
||||
signedImageUrls: string[];
|
||||
signedAdminImageUrls: string[];
|
||||
}
|
||||
|
||||
export interface AdminProductReviewsResult {
|
||||
reviews: AdminProductReviewWithSignedUrls[];
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export interface AdminProductReviewResponse {
|
||||
success: boolean;
|
||||
review: AdminProductReview;
|
||||
}
|
||||
|
||||
export interface AdminProductGroup {
|
||||
id: number;
|
||||
groupName: string;
|
||||
description: string | null;
|
||||
createdAt: Date;
|
||||
products: AdminProduct[];
|
||||
productCount: number;
|
||||
}
|
||||
|
||||
export interface AdminProductGroupsResult {
|
||||
groups: AdminProductGroup[];
|
||||
}
|
||||
|
||||
export interface AdminProductGroupResponse {
|
||||
group: AdminProductGroupInfo;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AdminProductGroupInfo {
|
||||
id: number;
|
||||
groupName: string;
|
||||
description: string | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface AdminUpdateProductPricesResult {
|
||||
message: string;
|
||||
updatedCount: number;
|
||||
}
|
||||
|
||||
export interface AdminDeliverySlot {
|
||||
id: number;
|
||||
deliveryTime: Date;
|
||||
freezeTime: Date;
|
||||
isActive: boolean;
|
||||
isFlash: boolean;
|
||||
isCapacityFull: boolean;
|
||||
deliverySequence: unknown;
|
||||
groupIds: unknown;
|
||||
}
|
||||
|
||||
export interface AdminSlotProductSummary {
|
||||
id: number;
|
||||
name: string;
|
||||
images: string[] | null;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippet {
|
||||
id: number;
|
||||
snippetCode: string;
|
||||
slotId: number | null;
|
||||
productIds: number[];
|
||||
isPermanent: boolean;
|
||||
validTill: Date | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetWithAccess extends AdminVendorSnippet {
|
||||
accessUrl: string;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetWithSlot extends AdminVendorSnippet {
|
||||
slot: AdminDeliverySlot | null;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetProduct {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetWithProducts extends AdminVendorSnippetWithSlot {
|
||||
accessUrl: string;
|
||||
products: AdminVendorSnippetProduct[];
|
||||
}
|
||||
|
||||
export interface AdminSlotWithProducts extends AdminDeliverySlot {
|
||||
deliverySequence: number[];
|
||||
products: AdminSlotProductSummary[];
|
||||
}
|
||||
|
||||
export interface AdminSlotWithProductsAndSnippets extends AdminSlotWithProducts {
|
||||
groupIds: number[];
|
||||
vendorSnippets: AdminVendorSnippetWithAccess[];
|
||||
}
|
||||
|
||||
export interface AdminSlotWithProductsAndSnippetsBase extends AdminSlotWithProducts {
|
||||
groupIds: number[];
|
||||
vendorSnippets: AdminVendorSnippet[];
|
||||
}
|
||||
|
||||
export interface AdminSlotsResult {
|
||||
slots: AdminSlotWithProducts[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface AdminSlotsListResult {
|
||||
slots: AdminDeliverySlot[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface AdminSlotResult {
|
||||
slot: AdminSlotWithProductsAndSnippets;
|
||||
}
|
||||
|
||||
export interface AdminSlotCreateResult {
|
||||
slot: AdminDeliverySlot;
|
||||
createdSnippets: AdminVendorSnippet[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AdminSlotUpdateResult {
|
||||
slot: AdminDeliverySlot;
|
||||
createdSnippets: AdminVendorSnippet[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AdminSlotDeleteResult {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type AdminDeliverySequence = Record<string, number[]>;
|
||||
|
||||
export interface AdminDeliverySequenceResult {
|
||||
deliverySequence: AdminDeliverySequence;
|
||||
}
|
||||
|
||||
export interface AdminUpdateDeliverySequenceResult {
|
||||
slot: {
|
||||
id: number;
|
||||
deliverySequence: unknown;
|
||||
};
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AdminUpdateSlotCapacityResult {
|
||||
success: boolean;
|
||||
slot: AdminDeliverySlot;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetCreateInput {
|
||||
snippetCode: string;
|
||||
slotId?: number;
|
||||
productIds: number[];
|
||||
validTill?: string;
|
||||
isPermanent: boolean;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetUpdateInput {
|
||||
snippetCode?: string;
|
||||
slotId?: number;
|
||||
productIds?: number[];
|
||||
validTill?: string | null;
|
||||
isPermanent?: boolean;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetDeleteResult {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetOrderProduct {
|
||||
orderItemId: number;
|
||||
productId: number;
|
||||
productName: string;
|
||||
quantity: number;
|
||||
productSize: number;
|
||||
price: number;
|
||||
unit: string;
|
||||
subtotal: number;
|
||||
is_packaged: boolean;
|
||||
is_package_verified: boolean;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetOrderSummary {
|
||||
orderId: string;
|
||||
orderDate: string;
|
||||
customerName: string;
|
||||
totalAmount: number;
|
||||
slotInfo: {
|
||||
time: string;
|
||||
sequence: unknown;
|
||||
} | null;
|
||||
products: AdminVendorSnippetOrderProduct[];
|
||||
matchedProducts: number[];
|
||||
snippetCode: string;
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetOrdersResult {
|
||||
success: boolean;
|
||||
data: AdminVendorSnippetOrderSummary[];
|
||||
snippet: {
|
||||
id: number;
|
||||
snippetCode: string;
|
||||
slotId: number | null;
|
||||
productIds: number[];
|
||||
validTill?: string;
|
||||
createdAt: string;
|
||||
isPermanent: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminVendorSnippetOrdersWithSlotResult extends AdminVendorSnippetOrdersResult {
|
||||
selectedSlot: {
|
||||
id: number;
|
||||
deliveryTime: string;
|
||||
freezeTime: string;
|
||||
deliverySequence: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminVendorOrderSummary {
|
||||
id: number;
|
||||
status: string;
|
||||
orderDate: string;
|
||||
totalQuantity: number;
|
||||
products: {
|
||||
name: string;
|
||||
quantity: number;
|
||||
unit: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface AdminUpcomingSlotsResult {
|
||||
success: boolean;
|
||||
data: {
|
||||
id: number;
|
||||
deliveryTime: string;
|
||||
freezeTime: string;
|
||||
deliverySequence: unknown;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type AdminVendorUpdatePackagingResult =
|
||||
| {
|
||||
success: true;
|
||||
orderItemId: number;
|
||||
is_packaged: boolean;
|
||||
}
|
||||
| {
|
||||
success: false;
|
||||
message: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue