freshyo/verifier/admin-apis/apis/vendor-snippets.ts
2026-03-22 20:20:18 +05:30

531 lines
No EOL
16 KiB
TypeScript

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 { appUrl } from '@/src/lib/env-exporter'
const createSnippetSchema = z.object({
snippetCode: z.string().min(1, "Snippet code is required"),
slotId: z.number().optional(),
productIds: z.array(z.number().int().positive()).min(1, "At least one product is required"),
validTill: z.string().optional(),
isPermanent: z.boolean().default(false)
});
const updateSnippetSchema = z.object({
id: z.number().int().positive(),
updates: createSnippetSchema.partial().extend({
snippetCode: z.string().min(1).optional(),
productIds: z.array(z.number().int().positive()).optional(),
isPermanent: z.boolean().default(false)
}),
});
export const vendorSnippetsRouter = router({
create: protectedProcedure
.input(createSnippetSchema)
.mutation(async ({ input, ctx }) => {
const { snippetCode, slotId, productIds, validTill, isPermanent } = input;
// Get staff user ID from auth middleware
const staffUserId = ctx.staffUser?.id;
if (!staffUserId) {
throw new Error("Unauthorized");
}
// Validate slot exists
if(slotId) {
const slot = await db.query.deliverySlotInfo.findFirst({
where: eq(deliverySlotInfo.id, slotId),
});
if (!slot) {
throw new Error("Invalid slot ID");
}
}
// Validate products exist
const products = await db.query.productInfo.findMany({
where: inArray(productInfo.id, productIds),
});
if (products.length !== productIds.length) {
throw new Error("One or more invalid product IDs");
}
// Check if snippet code already exists
const existingSnippet = await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.snippetCode, snippetCode),
});
if (existingSnippet) {
throw new Error("Snippet code already exists");
}
const result = await db.insert(vendorSnippets).values({
snippetCode,
slotId,
productIds,
isPermanent,
validTill: validTill ? new Date(validTill) : undefined,
}).returning();
return result[0];
}),
getAll: protectedProcedure
.query(async () => {
console.log('from the vendor snipptes methods')
try {
const result = await db.query.vendorSnippets.findMany({
with: {
slot: true,
},
orderBy: (vendorSnippets, { desc }) => [desc(vendorSnippets.createdAt)],
});
const snippetsWithProducts = await Promise.all(
result.map(async (snippet) => {
const products = await db.query.productInfo.findMany({
where: inArray(productInfo.id, snippet.productIds),
columns: { id: true, name: true },
});
return {
...snippet,
accessUrl: `${appUrl}/vendor-order-list?id=${snippet.snippetCode}`,
products: products.map(p => ({ id: p.id, name: p.name })),
};
})
);
return snippetsWithProducts;
}
catch(e) {
console.log(e)
}
return [];
}),
getById: protectedProcedure
.input(z.object({ id: z.number().int().positive() }))
.query(async ({ input }) => {
const { id } = input;
const result = await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.id, id),
with: {
slot: true,
},
});
if (!result) {
throw new Error("Vendor snippet not found");
}
return result;
}),
update: protectedProcedure
.input(updateSnippetSchema)
.mutation(async ({ input }) => {
const { id, updates } = input;
// Check if snippet exists
const existingSnippet = await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.id, id),
});
if (!existingSnippet) {
throw new Error("Vendor snippet not found");
}
// Validate slot if being updated
if (updates.slotId) {
const slot = await db.query.deliverySlotInfo.findFirst({
where: eq(deliverySlotInfo.id, updates.slotId),
});
if (!slot) {
throw new Error("Invalid slot ID");
}
}
// Validate products if being updated
if (updates.productIds) {
const products = await db.query.productInfo.findMany({
where: inArray(productInfo.id, updates.productIds),
});
if (products.length !== updates.productIds.length) {
throw new Error("One or more invalid product IDs");
}
}
// Check snippet code uniqueness if being updated
if (updates.snippetCode && updates.snippetCode !== existingSnippet.snippetCode) {
const duplicateSnippet = await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.snippetCode, updates.snippetCode),
});
if (duplicateSnippet) {
throw new Error("Snippet code already exists");
}
}
const updateData: any = { ...updates };
if (updates.validTill !== undefined) {
updateData.validTill = updates.validTill ? new Date(updates.validTill) : null;
}
const result = await db.update(vendorSnippets)
.set(updateData)
.where(eq(vendorSnippets.id, id))
.returning();
if (result.length === 0) {
throw new Error("Failed to update vendor snippet");
}
return result[0];
}),
delete: protectedProcedure
.input(z.object({ id: z.number().int().positive() }))
.mutation(async ({ input }) => {
const { id } = input;
const result = await db.delete(vendorSnippets)
.where(eq(vendorSnippets.id, id))
.returning();
if (result.length === 0) {
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 }) => {
const { snippetCode } = input;
// Find the snippet
const snippet = await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.snippetCode, snippetCode),
});
if (!snippet) {
throw new Error("Vendor snippet not found");
}
// Check if snippet is still valid
if (snippet.validTill && new Date(snippet.validTill) < new Date()) {
throw new Error("Vendor snippet has expired");
}
// Query orders that match the snippet criteria
const matchingOrders = await db.query.orders.findMany({
where: eq(orders.slotId, snippet.slotId!),
with: {
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
orderStatus: true,
user: true,
slot: true,
},
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
});
// Filter orders that contain at least one of the snippet's products
const filteredOrders = matchingOrders.filter(order => {
const status = order.orderStatus;
if (status[0].isCancelled) return false;
const orderProductIds = order.orderItems.map(item => item.productId);
return snippet.productIds.some(productId => orderProductIds.includes(productId));
});
// Format the response
const formattedOrders = filteredOrders.map(order => {
// Filter orderItems to only include products attached to the snippet
const attachedOrderItems = order.orderItems.filter(item =>
snippet.productIds.includes(item.productId)
);
const products = attachedOrderItems.map(item => ({
orderItemId: item.id,
productId: item.productId,
productName: item.product.name,
quantity: parseFloat(item.quantity),
productSize: item.product.productQuantity,
price: parseFloat(item.price.toString()),
unit: item.product.unit?.shortNotation || 'unit',
subtotal: parseFloat(item.price.toString()) * parseFloat(item.quantity),
is_packaged: item.is_packaged,
is_package_verified: item.is_package_verified,
}));
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,
slotInfo: order.slot ? {
time: order.slot.deliveryTime.toISOString(),
sequence: order.slot.deliverySequence,
} : null,
products,
matchedProducts: snippet.productIds, // All snippet products are considered matched
snippetCode: snippet.snippetCode,
};
});
return {
success: true,
data: formattedOrders,
snippet: {
id: snippet.id,
snippetCode: snippet.snippetCode,
slotId: snippet.slotId,
productIds: snippet.productIds,
validTill: snippet.validTill?.toISOString(),
createdAt: snippet.createdAt.toISOString(),
isPermanent: snippet.isPermanent,
},
};
}),
getVendorOrders: protectedProcedure
.query(async () => {
const vendorOrders = await db.query.orders.findMany({
with: {
user: true,
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
},
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
orderDate: order.createdAt.toISOString(),
totalQuantity: order.orderItems.reduce((sum, item) => sum + parseFloat(item.quantity || '0'), 0),
products: order.orderItems.map(item => ({
name: item.product.name,
quantity: parseFloat(item.quantity || '0'),
unit: item.product.unit?.shortNotation || 'unit',
})),
}));
}),
getUpcomingSlots: publicProcedure
.query(async () => {
const threeHoursAgo = dayjs().subtract(3, 'hour').toDate();
const slots = await db.query.deliverySlotInfo.findMany({
where: and(
eq(deliverySlotInfo.isActive, true),
gt(deliverySlotInfo.deliveryTime, threeHoursAgo)
),
orderBy: asc(deliverySlotInfo.deliveryTime),
});
return {
success: true,
data: slots.map(slot => ({
id: slot.id,
deliveryTime: slot.deliveryTime.toISOString(),
freezeTime: slot.freezeTime.toISOString(),
deliverySequence: slot.deliverySequence,
})),
};
}),
getOrdersBySnippetAndSlot: publicProcedure
.input(z.object({
snippetCode: z.string().min(1, "Snippet code is required"),
slotId: z.number().int().positive("Valid slot ID is required"),
}))
.query(async ({ input }) => {
const { snippetCode, slotId } = input;
// Find the snippet
const snippet = await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.snippetCode, snippetCode),
});
if (!snippet) {
throw new Error("Vendor snippet not found");
}
// Find the slot
const slot = await db.query.deliverySlotInfo.findFirst({
where: eq(deliverySlotInfo.id, slotId),
});
if (!slot) {
throw new Error("Slot not found");
}
// Query orders that match the slot and snippet criteria
const matchingOrders = await db.query.orders.findMany({
where: eq(orders.slotId, slotId),
with: {
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
orderStatus: true,
user: true,
slot: true,
},
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
});
// Filter orders that contain at least one of the snippet's products
const filteredOrders = matchingOrders.filter(order => {
const status = order.orderStatus;
if (status[0]?.isCancelled) return false;
const orderProductIds = order.orderItems.map(item => item.productId);
return snippet.productIds.some(productId => orderProductIds.includes(productId));
});
// Format the response
const formattedOrders = filteredOrders.map(order => {
// Filter orderItems to only include products attached to the snippet
const attachedOrderItems = order.orderItems.filter(item =>
snippet.productIds.includes(item.productId)
);
const products = attachedOrderItems.map(item => ({
orderItemId: item.id,
productId: item.productId,
productName: item.product.name,
quantity: parseFloat(item.quantity),
price: parseFloat(item.price.toString()),
unit: item.product.unit?.shortNotation || 'unit',
subtotal: parseFloat(item.price.toString()) * parseFloat(item.quantity),
productSize: item.product.productQuantity,
is_packaged: item.is_packaged,
is_package_verified: item.is_package_verified,
}));
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,
slotInfo: order.slot ? {
time: order.slot.deliveryTime.toISOString(),
sequence: order.slot.deliverySequence,
} : null,
products,
matchedProducts: snippet.productIds,
snippetCode: snippet.snippetCode,
};
});
return {
success: true,
data: formattedOrders,
snippet: {
id: snippet.id,
snippetCode: snippet.snippetCode,
slotId: snippet.slotId,
productIds: snippet.productIds,
validTill: snippet.validTill?.toISOString(),
createdAt: snippet.createdAt.toISOString(),
isPermanent: snippet.isPermanent,
},
selectedSlot: {
id: slot.id,
deliveryTime: slot.deliveryTime.toISOString(),
freezeTime: slot.freezeTime.toISOString(),
deliverySequence: slot.deliverySequence,
},
};
}),
updateOrderItemPackaging: publicProcedure
.input(z.object({
orderItemId: z.number().int().positive("Valid order item ID required"),
is_packaged: z.boolean()
}))
.mutation(async ({ input, ctx }) => {
const { orderItemId, is_packaged } = input;
// Get staff user ID from auth middleware
// const staffUserId = ctx.staffUser?.id;
// if (!staffUserId) {
// throw new Error("Unauthorized");
// }
// Check if order item exists and get related data
const orderItem = await db.query.orderItems.findFirst({
where: eq(orderItems.id, orderItemId),
with: {
order: {
with: {
slot: true
}
}
}
});
if (!orderItem) {
throw new Error("Order item not found");
}
// Check if this order item belongs to a slot that has vendor snippets
// This ensures only order items from vendor-accessible orders can be updated
if (!orderItem.order.slotId) {
throw new Error("Order item not associated with a vendor slot");
}
const snippetExists = await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.slotId, orderItem.order.slotId),
});
if (!snippetExists) {
throw new Error("No vendor snippet found for this order's slot");
}
// Update the is_packaged field
const result = await db.update(orderItems)
.set({ is_packaged })
.where(eq(orderItems.id, orderItemId))
.returning();
if (result.length === 0) {
throw new Error("Failed to update packaging status");
}
return {
success: true,
orderItemId,
is_packaged
};
}),
});