This commit is contained in:
shafi54 2026-03-25 18:11:46 +05:30
parent 038733c14a
commit 306244e8df
33 changed files with 3537 additions and 475 deletions

View file

@ -1,197 +0,0 @@
import React from 'react';
import { View, ScrollView, Dimensions } from 'react-native';
import { Image } from 'expo-image';
import { MyText, tw } from 'common-ui';
import { trpc } from '../src/trpc-client';
interface FullOrderViewProps {
orderId: number;
}
export const FullOrderView: React.FC<FullOrderViewProps> = ({ orderId }) => {
const { data: order, isLoading, error } = trpc.admin.order.getFullOrder.useQuery({ orderId });
if (isLoading) {
return (
<View style={tw`p-6`}>
<MyText style={tw`text-center text-gray-600`}>Loading order details...</MyText>
</View>
);
}
if (error || !order) {
return (
<View style={tw`p-6`}>
<MyText style={tw`text-center text-red-600`}>Failed to load order details</MyText>
</View>
);
}
const totalAmount = order.items.reduce((sum, item) => sum + item.amount, 0);
return (
<ScrollView
style={[tw`flex-1`, { maxHeight: Dimensions.get('window').height * 0.8 }]}
showsVerticalScrollIndicator={false}
>
<View style={tw`p-6`}>
<MyText style={tw`text-2xl font-bold text-gray-800 mb-6`}>Order #{order.readableId}</MyText>
{/* Customer Information */}
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Customer Details</MyText>
<View style={tw`space-y-2`}>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Name:</MyText>
<MyText style={tw`font-medium`}>{order.customerName}</MyText>
</View>
{order.customerEmail && (
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Email:</MyText>
<MyText style={tw`font-medium`}>{order.customerEmail}</MyText>
</View>
)}
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Mobile:</MyText>
<MyText style={tw`font-medium`}>{order.customerMobile}</MyText>
</View>
</View>
</View>
{/* Delivery Address */}
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Delivery Address</MyText>
<View style={tw`space-y-1`}>
<MyText style={tw`text-gray-800`}>{order.address.line1}</MyText>
{order.address.line2 && <MyText style={tw`text-gray-800`}>{order.address.line2}</MyText>}
<MyText style={tw`text-gray-800`}>
{order.address.city}, {order.address.state} - {order.address.pincode}
</MyText>
<MyText style={tw`text-gray-800`}>Phone: {order.address.phone}</MyText>
</View>
</View>
{/* Order Details */}
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Order Details</MyText>
<View style={tw`space-y-2`}>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Order Date:</MyText>
<MyText style={tw`font-medium`}>
{new Date(order.createdAt).toLocaleDateString('en-IN', {
day: 'numeric',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</MyText>
</View>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Payment Method:</MyText>
<MyText style={tw`font-medium`}>
{order.isCod ? 'Cash on Delivery' : 'Online Payment'}
</MyText>
</View>
{order.slotInfo && (
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Delivery Slot:</MyText>
<MyText style={tw`font-medium`}>
{new Date(order.slotInfo.time).toLocaleDateString('en-IN', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit'
})}
</MyText>
</View>
)}
</View>
</View>
{/* Items */}
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Items ({order.items.length})</MyText>
{order.items.map((item, index) => (
<View key={item.id} style={tw`flex-row items-center py-3 ${index !== order.items.length - 1 ? 'border-b border-gray-100' : ''}`}>
<View style={tw`flex-1`}>
<MyText style={tw`font-medium text-gray-800`} numberOfLines={2}>
{item.productName}
</MyText>
<MyText style={tw`text-sm text-gray-600`}>
Qty: {item.quantity} {item.unit} × {parseFloat(item.price.toString()).toFixed(2)}
</MyText>
</View>
<MyText style={tw`font-semibold text-gray-800`}>{item.amount.toFixed(2)}</MyText>
</View>
))}
</View>
{/* Payment Information */}
{(order.payment || order.paymentInfo) && (
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Payment Information</MyText>
{order.payment && (
<View style={tw`space-y-2 mb-3`}>
<MyText style={tw`text-sm font-medium text-gray-700`}>Payment Details:</MyText>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Status:</MyText>
<MyText style={tw`font-medium capitalize`}>{order.payment.status}</MyText>
</View>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Gateway:</MyText>
<MyText style={tw`font-medium`}>{order.payment.gateway}</MyText>
</View>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Order ID:</MyText>
<MyText style={tw`font-medium`}>{order.payment.merchantOrderId}</MyText>
</View>
</View>
)}
{order.paymentInfo && (
<View style={tw`space-y-2`}>
<MyText style={tw`text-sm font-medium text-gray-700`}>Payment Info:</MyText>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Status:</MyText>
<MyText style={tw`font-medium capitalize`}>{order.paymentInfo.status}</MyText>
</View>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Gateway:</MyText>
<MyText style={tw`font-medium`}>{order.paymentInfo.gateway}</MyText>
</View>
<View style={tw`flex-row justify-between`}>
<MyText style={tw`text-gray-600`}>Order ID:</MyText>
<MyText style={tw`font-medium`}>{order.paymentInfo.merchantOrderId}</MyText>
</View>
</View>
)}
</View>
)}
{/* User Notes */}
{order.userNotes && (
<View style={tw`bg-blue-50 rounded-xl p-4 mb-4`}>
<MyText style={tw`text-lg font-semibold text-gray-800 mb-2`}>Customer Notes</MyText>
<MyText style={tw`text-gray-700`}>{order.userNotes}</MyText>
</View>
)}
{/* Admin Notes */}
{order.adminNotes && (
<View style={tw`bg-yellow-50 rounded-xl p-4 mb-4`}>
<MyText style={tw`text-lg font-semibold text-gray-800 mb-2`}>Admin Notes</MyText>
<MyText style={tw`text-gray-700`}>{order.adminNotes}</MyText>
</View>
)}
{/* Total */}
<View style={tw`bg-blue-50 rounded-xl p-4`}>
<View style={tw`flex-row justify-between items-center`}>
<MyText style={tw`text-xl font-bold text-gray-800`}>Total Amount</MyText>
<MyText style={tw`text-2xl font-bold text-blue-600`}>{parseFloat(order.totalAmount.toString()).toFixed(2)}</MyText>
</View>
</View>
</View>
</ScrollView>
);
};

View file

@ -2,13 +2,17 @@
// This file re-exports everything from postgresService to provide a clean abstraction layer
// Implementation is the responsibility of postgresService package
import { getOrderDetails as getOrderDetailsFromDb } from 'postgresService'
import type { AdminOrderDetails } from '@packages/shared'
// Re-export database connection
export { db } from 'postgresService';
export { db } from 'postgresService'
// Re-export all schema exports
export * from 'postgresService';
export * from 'postgresService'
// Re-export methods from postgresService (implementation lives there)
export {
// Banner methods
getBanners,
@ -22,14 +26,13 @@ export {
// Constants methods
getAllConstants,
upsertConstants,
// Coupon methods (batch 1 - non-transaction)
// Coupon methods
getAllCoupons,
getCouponById,
invalidateCoupon,
validateCoupon,
getReservedCoupons,
getUsersForCoupon,
// Coupon methods (batch 2 - transactions)
createCouponWithRelations,
updateCouponWithRelations,
generateCancellationCoupon,
@ -48,10 +51,9 @@ export {
// Staff-user methods
getStaffUserByName,
getAllStaff,
getStaffByName,
getAllUsers,
getUserWithDetails,
updateUserSuspension,
updateUserSuspensionStatus,
checkStaffUserExists,
checkStaffRoleExists,
createStaffUser,
@ -121,20 +123,57 @@ export {
updateSlotDeliverySequence,
// Order methods
updateOrderNotes,
getOrderWithDetails,
getFullOrder,
getOrderDetails,
getAllOrders,
getOrdersBySlotId,
updateOrderPackaged,
updateOrderDelivered,
updateOrderItemPackaging,
updateAddressCoords,
getOrderStatus,
cancelOrder,
getTodaysOrders,
removeDeliveryCharge,
} from 'postgresService';
getSlotOrders,
updateAddressCoords,
getAllOrders,
rebalanceSlots,
cancelOrder,
deleteOrderById,
} from 'postgresService'
// Re-export types from local types file (to avoid circular dependencies)
export type { Banner } from './types/db.types';
export async function getOrderDetails(orderId: number): Promise<AdminOrderDetails | null> {
return getOrderDetailsFromDb(orderId)
}
// Re-export all types from shared package
export type {
// Admin types
Banner,
Complaint,
ComplaintWithUser,
Constant,
ConstantUpdateResult,
Coupon,
CouponValidationResult,
UserMiniInfo,
Store,
StaffUser,
StaffRole,
AdminOrderRow,
AdminOrderDetails,
AdminOrderUpdateResult,
AdminOrderItemPackagingResult,
AdminOrderMessageResult,
AdminOrderBasicResult,
AdminGetSlotOrdersResult,
AdminGetAllOrdersResult,
AdminGetAllOrdersResultWithUserId,
AdminRebalanceSlotsResult,
AdminCancelOrderResult,
} from '@packages/shared';
export type {
// User types
User,
UserDetails,
Address,
Product,
CartItem,
Order,
OrderItem,
Payment,
} from '@packages/shared';

View file

@ -1,21 +1,5 @@
import { router, protectedProcedure } from "@/src/trpc/trpc-index"
import { z } from "zod";
import { db } from "@/src/db/db_index"
import {
orders,
orderItems,
orderStatus,
users,
addresses,
refunds,
coupons,
couponUsage,
complaints,
payments,
} from "@/src/db/schema";
import { eq, and, gte, lt, desc, SQL, inArray } from "drizzle-orm";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { ApiError } from "@/src/lib/api-error"
import {
sendOrderPackagedNotification,
@ -23,16 +7,38 @@ import {
} from "@/src/lib/notif-job";
import { publishCancellation } from "@/src/lib/post-order-handler"
import { getMultipleUserNegativityScores } from "@/src/stores/user-negativity-store"
import {
updateOrderNotes as updateOrderNotesInDb,
getOrderDetails as getOrderDetailsInDb,
updateOrderPackaged as updateOrderPackagedInDb,
updateOrderDelivered as updateOrderDeliveredInDb,
updateOrderItemPackaging as updateOrderItemPackagingInDb,
removeDeliveryCharge as removeDeliveryChargeInDb,
getSlotOrders as getSlotOrdersInDb,
updateAddressCoords as updateAddressCoordsInDb,
getAllOrders as getAllOrdersInDb,
rebalanceSlots as rebalanceSlotsInDb,
cancelOrder as cancelOrderInDb,
deleteOrderById as deleteOrderByIdInDb,
} from '@/src/dbService'
import type {
AdminCancelOrderResult,
AdminGetAllOrdersResult,
AdminGetSlotOrdersResult,
AdminOrderBasicResult,
AdminOrderDetails,
AdminOrderItemPackagingResult,
AdminOrderMessageResult,
AdminOrderRow,
AdminOrderUpdateResult,
AdminRebalanceSlotsResult,
} from "@packages/shared"
const updateOrderNotesSchema = z.object({
orderId: z.number(),
adminNotes: z.string(),
});
const getFullOrderSchema = z.object({
orderId: z.number(),
});
const getOrderDetailsSchema = z.object({
orderId: z.number(),
});
@ -57,10 +63,6 @@ const getSlotOrdersSchema = z.object({
slotId: z.string(),
});
const getTodaysOrdersSchema = z.object({
slotId: z.string().optional(),
});
const getAllOrdersSchema = z.object({
cursor: z.number().optional(),
limit: z.number().default(20),
@ -86,9 +88,13 @@ const getAllOrdersSchema = z.object({
export const orderRouter = router({
updateNotes: protectedProcedure
.input(updateOrderNotesSchema)
.mutation(async ({ input }) => {
.mutation(async ({ input }): Promise<AdminOrderRow> => {
const { orderId, adminNotes } = input;
const result = await updateOrderNotesInDb(orderId, adminNotes || null)
/*
// Old implementation - direct DB query:
const result = await db
.update(orders)
.set({
@ -100,125 +106,24 @@ export const orderRouter = router({
if (result.length === 0) {
throw new Error("Order not found");
}
*/
return result[0];
}),
getFullOrder: protectedProcedure
.input(getFullOrderSchema)
.query(async ({ input }) => {
const { orderId } = input;
const orderData = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
with: {
user: true,
address: true,
slot: true,
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
payment: true,
paymentInfo: true,
},
});
if (!orderData) {
throw new Error("Order not found");
if (!result) {
throw new Error("Order not found")
}
// Get order status separately
const statusRecord = await db.query.orderStatus.findFirst({
where: eq(orderStatus.orderId, orderId),
});
let status: "pending" | "delivered" | "cancelled" = "pending";
if (statusRecord?.isCancelled) {
status = "cancelled";
} else if (statusRecord?.isDelivered) {
status = "delivered";
}
// Get refund details if order is cancelled
let refund = null;
if (status === "cancelled") {
refund = await db.query.refunds.findFirst({
where: eq(refunds.orderId, orderId),
});
}
return {
id: orderData.id,
readableId: orderData.id,
customerName: `${orderData.user.name}`,
customerEmail: orderData.user.email,
customerMobile: orderData.user.mobile,
address: {
line1: orderData.address.addressLine1,
line2: orderData.address.addressLine2,
city: orderData.address.city,
state: orderData.address.state,
pincode: orderData.address.pincode,
phone: orderData.address.phone,
},
slotInfo: orderData.slot
? {
time: orderData.slot.deliveryTime.toISOString(),
sequence: orderData.slot.deliverySequence,
}
: null,
isCod: orderData.isCod,
isOnlinePayment: orderData.isOnlinePayment,
totalAmount: orderData.totalAmount,
adminNotes: orderData.adminNotes,
userNotes: orderData.userNotes,
createdAt: orderData.createdAt,
status,
isPackaged:
orderData.orderItems.every((item) => item.is_packaged) || false,
isDelivered: statusRecord?.isDelivered || false,
items: orderData.orderItems.map((item) => ({
id: item.id,
name: item.product.name,
quantity: item.quantity,
price: item.price,
unit: item.product.unit?.shortNotation,
amount:
parseFloat(item.price.toString()) *
parseFloat(item.quantity || "0"),
})),
payment: orderData.payment
? {
status: orderData.payment.status,
gateway: orderData.payment.gateway,
merchantOrderId: orderData.payment.merchantOrderId,
}
: null,
paymentInfo: orderData.paymentInfo
? {
status: orderData.paymentInfo.status,
gateway: orderData.paymentInfo.gateway,
merchantOrderId: orderData.paymentInfo.merchantOrderId,
}
: null,
// Cancellation details (only present for cancelled orders)
cancelReason: statusRecord?.cancelReason || null,
cancellationReviewed: statusRecord?.cancellationReviewed || false,
isRefundDone: refund?.refundStatus === "processed" || false,
};
return result as AdminOrderRow;
}),
getOrderDetails: protectedProcedure
.input(getOrderDetailsSchema)
.query(async ({ input }) => {
.query(async ({ input }): Promise<AdminOrderDetails> => {
const { orderId } = input;
const orderDetails = await getOrderDetailsInDb(orderId)
/*
// Old implementation - direct DB queries:
// Single optimized query with all relations
const orderData = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
@ -237,8 +142,8 @@ export const orderRouter = router({
},
payment: true,
paymentInfo: true,
orderStatus: true, // Include in main query
refunds: true, // Include in main query
orderStatus: true,
refunds: true,
},
});
@ -248,7 +153,7 @@ export const orderRouter = router({
// Get coupon usage for this specific order using new orderId field
const couponUsageData = await db.query.couponUsage.findMany({
where: eq(couponUsage.orderId, orderData.id), // Use new orderId field
where: eq(couponUsage.orderId, orderData.id),
with: {
coupon: true,
},
@ -380,13 +285,24 @@ export const orderRouter = router({
refundRecord: refund,
isFlashDelivery: orderData.isFlashDelivery,
};
*/
if (!orderDetails) {
throw new Error('Order not found')
}
return orderDetails
}),
updatePackaged: protectedProcedure
.input(updatePackagedSchema)
.mutation(async ({ input }) => {
.mutation(async ({ input }): Promise<AdminOrderUpdateResult> => {
const { orderId, isPackaged } = input;
const result = await updateOrderPackagedInDb(orderId, isPackaged)
/*
// Old implementation - direct DB queries:
// Update all order items to the specified packaged state
await db
.update(orderItems)
@ -412,13 +328,22 @@ export const orderRouter = router({
if (order) await sendOrderPackagedNotification(order.userId, orderId);
return { success: true };
*/
if (result.userId) await sendOrderPackagedNotification(result.userId, orderId)
return { success: true, userId: result.userId }
}),
updateDelivered: protectedProcedure
.input(updateDeliveredSchema)
.mutation(async ({ input }) => {
.mutation(async ({ input }): Promise<AdminOrderUpdateResult> => {
const { orderId, isDelivered } = input;
const result = await updateOrderDeliveredInDb(orderId, isDelivered)
/*
// Old implementation - direct DB queries:
await db
.update(orderStatus)
.set({ isDelivered })
@ -430,13 +355,22 @@ export const orderRouter = router({
if (order) await sendOrderDeliveredNotification(order.userId, orderId);
return { success: true };
*/
if (result.userId) await sendOrderDeliveredNotification(result.userId, orderId)
return { success: true, userId: result.userId }
}),
updateOrderItemPackaging: protectedProcedure
.input(updateOrderItemPackagingSchema)
.mutation(async ({ input }) => {
.mutation(async ({ input }): Promise<AdminOrderItemPackagingResult> => {
const { orderItemId, isPackaged, isPackageVerified } = input;
const result = await updateOrderItemPackagingInDb(orderItemId, isPackaged, isPackageVerified)
/*
// Old implementation - direct DB queries:
// Validate that orderItem exists
const orderItem = await db.query.orderItems.findFirst({
where: eq(orderItems.id, orderItemId),
@ -462,13 +396,24 @@ export const orderRouter = router({
.where(eq(orderItems.id, orderItemId));
return { success: true };
*/
if (!result.updated) {
throw new ApiError('Order item not found', 404)
}
return result
}),
removeDeliveryCharge: protectedProcedure
.input(z.object({ orderId: z.number() }))
.mutation(async ({ input }) => {
.mutation(async ({ input }): Promise<AdminOrderMessageResult> => {
const { orderId } = input;
const result = await removeDeliveryChargeInDb(orderId)
/*
// Old implementation - direct DB queries:
const order = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
});
@ -490,13 +435,24 @@ export const orderRouter = router({
.where(eq(orders.id, orderId));
return { success: true, message: 'Delivery charge removed' };
*/
if (!result) {
throw new Error('Order not found')
}
return result
}),
getSlotOrders: protectedProcedure
.input(getSlotOrdersSchema)
.query(async ({ input }) => {
.query(async ({ input }): Promise<AdminGetSlotOrdersResult> => {
const { slotId } = input;
const result = await getSlotOrdersInDb(slotId)
/*
// Old implementation - direct DB queries:
const slotOrders = await db.query.orders.findMany({
where: eq(orders.slotId, parseInt(slotId)),
with: {
@ -573,97 +529,9 @@ export const orderRouter = router({
});
return { success: true, data: formattedOrders };
}),
*/
getTodaysOrders: protectedProcedure
.input(getTodaysOrdersSchema)
.query(async ({ input }) => {
const { slotId } = input;
const start = dayjs().startOf("day").toDate();
const end = dayjs().endOf("day").toDate();
let whereCondition = and(
gte(orders.createdAt, start),
lt(orders.createdAt, end)
);
if (slotId) {
whereCondition = and(
whereCondition,
eq(orders.slotId, parseInt(slotId))
);
}
const todaysOrders = await db.query.orders.findMany({
where: whereCondition,
with: {
user: true,
address: true,
slot: true,
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
orderStatus: true,
},
});
const filteredOrders = todaysOrders.filter((order) => {
const statusRecord = order.orderStatus[0];
return (
order.isCod ||
(statusRecord && statusRecord.paymentStatus === "success")
);
});
const formattedOrders = filteredOrders.map((order) => {
const statusRecord = order.orderStatus[0]; // assuming one status per order
let status: "pending" | "delivered" | "cancelled" = "pending";
if (statusRecord?.isCancelled) {
status = "cancelled";
} else if (statusRecord?.isDelivered) {
status = "delivered";
}
const items = order.orderItems.map((item) => ({
name: item.product.name,
quantity: parseFloat(item.quantity),
price: parseFloat(item.price.toString()),
amount: parseFloat(item.quantity) * parseFloat(item.price.toString()),
unit: item.product.unit?.shortNotation || "",
}));
return {
orderId: order.id.toString(),
readableId: order.id,
customerName: order.user.name,
address: `${order.address.addressLine1}${
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
}, ${order.address.city}, ${order.address.state} - ${
order.address.pincode
}`,
totalAmount: parseFloat(order.totalAmount),
items,
deliveryTime: order.slot?.deliveryTime.toISOString() || null,
status,
isPackaged:
order.orderItems.every((item) => item.is_packaged) || false,
isDelivered: statusRecord?.isDelivered || false,
isCod: order.isCod,
paymentMode: order.isCod ? "COD" : "Online",
paymentStatus: statusRecord?.paymentStatus || "pending",
slotId: order.slotId,
adminNotes: order.adminNotes,
userNotes: order.userNotes,
};
});
return { success: true, data: formattedOrders };
return result
}),
updateAddressCoords: protectedProcedure
@ -674,9 +542,13 @@ export const orderRouter = router({
longitude: z.number(),
})
)
.mutation(async ({ input }) => {
.mutation(async ({ input }): Promise<AdminOrderBasicResult> => {
const { addressId, latitude, longitude } = input;
const result = await updateAddressCoordsInDb(addressId, latitude, longitude)
/*
// Old implementation - direct DB queries:
const result = await db
.update(addresses)
.set({
@ -691,12 +563,33 @@ export const orderRouter = router({
}
return { success: true };
*/
if (!result.success) {
throw new ApiError('Address not found', 404)
}
return result
}),
getAll: protectedProcedure
.input(getAllOrdersSchema)
.query(async ({ input }) => {
.query(async ({ input }): Promise<AdminGetAllOrdersResult | undefined> => {
try {
const result = await getAllOrdersInDb(input)
const userIds = [...new Set(result.orders.map((order) => order.userId))]
const negativityScores = await getMultipleUserNegativityScores(userIds)
const orders = result.orders.map((order) => {
const { userId, userNegativityScore, ...rest } = order
return {
...rest,
userNegativityScore: negativityScores[userId] || 0,
}
})
/*
// Old implementation - direct DB queries:
const {
cursor,
limit,
@ -858,6 +751,12 @@ export const orderRouter = router({
? ordersToReturn[ordersToReturn.length - 1].id
: undefined,
};
*/
return {
orders,
nextCursor: result.nextCursor,
}
} catch (e) {
console.log({ e });
}
@ -865,9 +764,13 @@ export const orderRouter = router({
rebalanceSlots: protectedProcedure
.input(z.object({ slotIds: z.array(z.number()).min(1).max(50) }))
.mutation(async ({ input }) => {
.mutation(async ({ input }): Promise<AdminRebalanceSlotsResult> => {
const slotIds = input.slotIds;
const result = await rebalanceSlotsInDb(slotIds)
/*
// Old implementation - direct DB queries:
const ordersList = await db.query.orders.findMany({
where: inArray(orders.slotId, slotIds),
with: {
@ -936,6 +839,9 @@ export const orderRouter = router({
});
return { success: true, updatedOrders: updatedOrderIds, message: `Rebalanced ${updatedOrderIds.length} orders.` };
*/
return result
}),
cancelOrder: protectedProcedure
@ -943,9 +849,13 @@ export const orderRouter = router({
orderId: z.number(),
reason: z.string().min(1, "Cancellation reason is required"),
}))
.mutation(async ({ input }) => {
.mutation(async ({ input }): Promise<AdminCancelOrderResult> => {
const { orderId, reason } = input;
const result = await cancelOrderInDb(orderId, reason)
/*
// Old implementation - direct DB queries:
const order = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
with: {
@ -997,14 +907,40 @@ export const orderRouter = router({
await publishCancellation(result.orderId, 'admin', reason);
return { success: true, message: "Order cancelled successfully" };
*/
if (!result.success) {
if (result.error === 'order_not_found') {
throw new ApiError(result.message, 404)
}
if (result.error === 'status_not_found') {
throw new ApiError(result.message, 400)
}
if (result.error === 'already_cancelled') {
throw new ApiError(result.message, 400)
}
if (result.error === 'already_delivered') {
throw new ApiError(result.message, 400)
}
throw new ApiError(result.message, 400)
}
if (result.orderId) {
await publishCancellation(result.orderId, 'admin', reason)
}
return { success: true, message: result.message }
}),
});
// {"id": "order_Rhh00qJNdjUp8o", "notes": {"retry": "true", "customerOrderId": "14"}, "amount": 21000, "entity": "order", "status": "created", "receipt": "order_14_retry", "attempts": 0, "currency": "INR", "offer_id": null, "signature": "6df20655021f1d6841340f2a2ef2ef9378cb3d43495ab09e85f08aea1a851583", "amount_due": 21000, "created_at": 1763575791, "payment_id": "pay_Rhh15cLL28YM7j", "amount_paid": 0}
type RefundStatus = "success" | "pending" | "failed" | "none" | "na";
export async function deleteOrderById(orderId: number): Promise<void> {
await deleteOrderByIdInDb(orderId)
/*
// Old implementation - direct DB queries:
await db.transaction(async (tx) => {
await tx.delete(orderItems).where(eq(orderItems.orderId, orderId));
await tx.delete(orderStatus).where(eq(orderStatus.orderId, orderId));
@ -1014,5 +950,5 @@ export async function deleteOrderById(orderId: number): Promise<void> {
await tx.delete(complaints).where(eq(complaints.orderId, orderId));
await tx.delete(orders).where(eq(orders.id, orderId));
});
*/
}

View file

@ -8,7 +8,7 @@ import {
getAllStaff,
getAllUsers,
getUserWithDetails,
updateUserSuspension,
upsertUserSuspension,
checkStaffUserExists,
checkStaffRoleExists,
createStaffUser,
@ -130,7 +130,7 @@ export const staffUserRouter = router({
.mutation(async ({ input }) => {
const { userId, isSuspended } = input;
await updateUserSuspension(userId, isSuspended);
await upsertUserSuspension(userId, isSuspended);
return { success: true };
}),

View file

@ -7,15 +7,161 @@ export { db } from './src/db/db_index';
// Re-export schema
export * from './src/db/schema';
// Re-export helper methods
export * from './src/helper_methods/banner';
export * from './src/helper_methods/complaint';
export * from './src/helper_methods/const';
export * from './src/helper_methods/coupon';
export * from './src/helper_methods/store';
export * from './src/helper_methods/staff-user';
export * from './src/helper_methods/user';
export * from './src/helper_methods/vendor-snippets';
export * from './src/helper_methods/product';
export * from './src/helper_methods/slots';
export * from './src/helper_methods/order';
// Admin API helpers - explicitly namespaced exports to avoid duplicates
export {
// Banner
getBanners,
getBannerById,
createBanner,
updateBanner,
deleteBanner,
} from './src/admin-apis/banner';
export {
// Complaint
getComplaints,
resolveComplaint,
} from './src/admin-apis/complaint';
export {
// Constants
getAllConstants,
upsertConstants,
} from './src/admin-apis/const';
export {
// Coupon
getAllCoupons,
getCouponById,
invalidateCoupon,
validateCoupon,
getReservedCoupons,
getUsersForCoupon,
createCouponWithRelations,
updateCouponWithRelations,
generateCancellationCoupon,
createReservedCouponWithProducts,
createCouponForUser,
checkUsersExist,
checkCouponExists,
checkReservedCouponExists,
getOrderWithUser,
} from './src/admin-apis/coupon';
export {
// Order
updateOrderNotes,
getOrderDetails,
updateOrderPackaged,
updateOrderDelivered,
updateOrderItemPackaging,
removeDeliveryCharge,
getSlotOrders,
updateAddressCoords,
getAllOrders,
rebalanceSlots,
cancelOrder,
deleteOrderById,
} from './src/admin-apis/order';
export {
// Product
getAllProducts,
getProductById,
createProduct,
updateProduct,
toggleProductOutOfStock,
getAllUnits,
getAllProductTags,
getProductReviews,
respondToReview,
getAllProductGroups,
createProductGroup,
updateProductGroup,
deleteProductGroup,
addProductToGroup,
removeProductFromGroup,
} from './src/admin-apis/product';
export {
// Slots
getAllSlots,
getSlotById,
createSlot,
updateSlot,
deleteSlot,
getSlotProducts,
addProductToSlot,
removeProductFromSlot,
clearSlotProducts,
updateSlotCapacity,
getSlotDeliverySequence,
updateSlotDeliverySequence,
} from './src/admin-apis/slots';
export {
// Staff User
getStaffUserByName,
getAllStaff,
getAllUsers,
getUserWithDetails,
updateUserSuspensionStatus,
checkStaffUserExists,
checkStaffRoleExists,
createStaffUser,
getAllRoles,
} from './src/admin-apis/staff-user';
export {
// Store
getAllStores,
getStoreById,
createStore,
updateStore,
deleteStore,
} from './src/admin-apis/store';
export {
// User
createUserByMobile,
getUserByMobile,
getUnresolvedComplaintsCount,
getAllUsersWithFilters,
getOrderCountsByUserIds,
getLastOrdersByUserIds,
getSuspensionStatusesByUserIds,
getUserBasicInfo,
getUserSuspensionStatus,
getUserOrders,
getOrderStatusesByOrderIds,
getItemCountsByOrderIds,
upsertUserSuspension,
searchUsers,
getAllNotifCreds,
getAllUnloggedTokens,
getNotifTokensByUserIds,
getUserIncidentsWithRelations,
createUserIncident,
} from './src/admin-apis/user';
export {
// Vendor Snippets
checkVendorSnippetExists,
getVendorSnippetById,
getVendorSnippetByCode,
getAllVendorSnippets,
createVendorSnippet,
updateVendorSnippet,
deleteVendorSnippet,
getProductsByIds,
getVendorSlotById,
getVendorOrdersBySlotId,
getOrderItemsByOrderIds,
getOrderStatusByOrderIds,
updateVendorOrderItemPackaging,
} from './src/admin-apis/vendor-snippets';
// Note: User API helpers are available in their respective files
// but not exported from main index to avoid naming conflicts
// Import them directly from the file paths if needed:
// import { getAllProducts } from '@packages/db_helper_postgres/src/user-apis/product'

View file

@ -0,0 +1,112 @@
import { db } from '../db/db_index';
import { homeBanners } from '../db/schema';
import { eq, desc } from 'drizzle-orm';
export interface Banner {
id: number;
name: string;
imageUrl: string;
description: string | null;
productIds: number[] | null;
redirectUrl: string | null;
serialNum: number | null;
isActive: boolean;
createdAt: Date;
lastUpdated: Date;
}
export async function getBanners(): Promise<Banner[]> {
const banners = await db.query.homeBanners.findMany({
orderBy: desc(homeBanners.createdAt),
});
return banners.map((banner) => ({
id: banner.id,
name: banner.name,
imageUrl: banner.imageUrl,
description: banner.description,
productIds: banner.productIds || [],
redirectUrl: banner.redirectUrl,
serialNum: banner.serialNum,
isActive: banner.isActive,
createdAt: banner.createdAt,
lastUpdated: banner.lastUpdated,
}));
}
export async function getBannerById(id: number): Promise<Banner | null> {
const banner = await db.query.homeBanners.findFirst({
where: eq(homeBanners.id, id),
});
if (!banner) return null;
return {
id: banner.id,
name: banner.name,
imageUrl: banner.imageUrl,
description: banner.description,
productIds: banner.productIds || [],
redirectUrl: banner.redirectUrl,
serialNum: banner.serialNum,
isActive: banner.isActive,
createdAt: banner.createdAt,
lastUpdated: banner.lastUpdated,
};
}
export type CreateBannerInput = Omit<Banner, 'id' | 'createdAt' | 'lastUpdated'>;
export async function createBanner(input: CreateBannerInput): Promise<Banner> {
const [banner] = await db.insert(homeBanners).values({
name: input.name,
imageUrl: input.imageUrl,
description: input.description,
productIds: input.productIds || [],
redirectUrl: input.redirectUrl,
serialNum: input.serialNum,
isActive: input.isActive,
}).returning();
return {
id: banner.id,
name: banner.name,
imageUrl: banner.imageUrl,
description: banner.description,
productIds: banner.productIds || [],
redirectUrl: banner.redirectUrl,
serialNum: banner.serialNum,
isActive: banner.isActive,
createdAt: banner.createdAt,
lastUpdated: banner.lastUpdated,
};
}
export type UpdateBannerInput = Partial<Omit<Banner, 'id' | 'createdAt'>>;
export async function updateBanner(id: number, input: UpdateBannerInput): Promise<Banner> {
const [banner] = await db.update(homeBanners)
.set({
...input,
lastUpdated: new Date(),
})
.where(eq(homeBanners.id, id))
.returning();
return {
id: banner.id,
name: banner.name,
imageUrl: banner.imageUrl,
description: banner.description,
productIds: banner.productIds || [],
redirectUrl: banner.redirectUrl,
serialNum: banner.serialNum,
isActive: banner.isActive,
createdAt: banner.createdAt,
lastUpdated: banner.lastUpdated,
};
}
export async function deleteBanner(id: number): Promise<void> {
await db.delete(homeBanners).where(eq(homeBanners.id, id));
}

View file

@ -0,0 +1,74 @@
import { db } from '../db/db_index';
import { complaints, users } from '../db/schema';
import { eq, desc, lt } from 'drizzle-orm';
export interface Complaint {
id: number;
complaintBody: string;
userId: number;
orderId: number | null;
isResolved: boolean;
response: string | null;
createdAt: Date;
images: string[] | null;
}
export interface ComplaintWithUser extends Complaint {
userName: string | null;
userMobile: string | null;
}
export async function getComplaints(
cursor?: number,
limit: number = 20
): Promise<{ complaints: ComplaintWithUser[]; hasMore: boolean }> {
let whereCondition = cursor ? lt(complaints.id, cursor) : undefined;
const complaintsData = await db
.select({
id: complaints.id,
complaintBody: complaints.complaintBody,
userId: complaints.userId,
orderId: complaints.orderId,
isResolved: complaints.isResolved,
response: complaints.response,
createdAt: complaints.createdAt,
images: complaints.images,
userName: users.name,
userMobile: users.mobile,
})
.from(complaints)
.leftJoin(users, eq(complaints.userId, users.id))
.where(whereCondition)
.orderBy(desc(complaints.id))
.limit(limit + 1);
const hasMore = complaintsData.length > limit;
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
return {
complaints: complaintsToReturn.map((c) => ({
id: c.id,
complaintBody: c.complaintBody,
userId: c.userId,
orderId: c.orderId,
isResolved: c.isResolved,
response: c.response,
createdAt: c.createdAt,
images: c.images as string[],
userName: c.userName,
userMobile: c.userMobile,
})),
hasMore,
};
}
export async function resolveComplaint(
id: number,
response?: string
): Promise<void> {
await db
.update(complaints)
.set({ isResolved: true, response })
.where(eq(complaints.id, id));
}

View file

@ -0,0 +1,29 @@
import { db } from '../db/db_index';
import { keyValStore } from '../db/schema';
export interface Constant {
key: string;
value: any;
}
export async function getAllConstants(): Promise<Constant[]> {
const constants = await db.select().from(keyValStore);
return constants.map(c => ({
key: c.key,
value: c.value,
}));
}
export async function upsertConstants(constants: Constant[]): Promise<void> {
await db.transaction(async (tx) => {
for (const { key, value } of constants) {
await tx.insert(keyValStore)
.values({ key, value })
.onConflictDoUpdate({
target: keyValStore.key,
set: { value },
});
}
});
}

View file

@ -0,0 +1,496 @@
import { db } from '../db/db_index';
import { coupons, reservedCoupons, users, orders, orderStatus, couponApplicableUsers, couponApplicableProducts } from '../db/schema';
import { eq, and, like, or, inArray, lt, desc } from 'drizzle-orm';
export interface Coupon {
id: number;
couponCode: string;
isUserBased: boolean;
discountPercent: string | null;
flatDiscount: string | null;
minOrder: string | null;
productIds: number[] | null;
maxValue: string | null;
isApplyForAll: boolean;
validTill: Date | null;
maxLimitForUser: number | null;
exclusiveApply: boolean;
isInvalidated: boolean;
createdAt: Date;
createdBy: number;
}
export async function getAllCoupons(
cursor?: number,
limit: number = 50,
search?: string
): Promise<{ coupons: any[]; hasMore: boolean }> {
let whereCondition = undefined;
const conditions = [];
if (cursor) {
conditions.push(lt(coupons.id, cursor));
}
if (search && search.trim()) {
conditions.push(like(coupons.couponCode, `%${search}%`));
}
if (conditions.length > 0) {
whereCondition = and(...conditions);
}
const result = await db.query.coupons.findMany({
where: whereCondition,
with: {
creator: true,
applicableUsers: {
with: {
user: true,
},
},
applicableProducts: {
with: {
product: true,
},
},
},
orderBy: (coupons, { desc }) => [desc(coupons.createdAt)],
limit: limit + 1,
});
const hasMore = result.length > limit;
const couponsList = hasMore ? result.slice(0, limit) : result;
return { coupons: couponsList, hasMore };
}
export async function getCouponById(id: number): Promise<any | null> {
return await db.query.coupons.findFirst({
where: eq(coupons.id, id),
with: {
creator: true,
applicableUsers: {
with: {
user: true,
},
},
applicableProducts: {
with: {
product: true,
},
},
},
});
}
export interface CreateCouponInput {
couponCode: string;
isUserBased: boolean;
discountPercent?: string;
flatDiscount?: string;
minOrder?: string;
productIds?: number[] | null;
maxValue?: string;
isApplyForAll: boolean;
validTill?: Date;
maxLimitForUser?: number;
exclusiveApply: boolean;
createdBy: number;
}
export async function createCouponWithRelations(
input: CreateCouponInput,
applicableUsers?: number[],
applicableProducts?: number[]
): Promise<Coupon> {
return await db.transaction(async (tx) => {
const [coupon] = await tx.insert(coupons).values({
couponCode: input.couponCode,
isUserBased: input.isUserBased,
discountPercent: input.discountPercent,
flatDiscount: input.flatDiscount,
minOrder: input.minOrder,
productIds: input.productIds,
createdBy: input.createdBy,
maxValue: input.maxValue,
isApplyForAll: input.isApplyForAll,
validTill: input.validTill,
maxLimitForUser: input.maxLimitForUser,
exclusiveApply: input.exclusiveApply,
}).returning();
if (applicableUsers && applicableUsers.length > 0) {
await tx.insert(couponApplicableUsers).values(
applicableUsers.map(userId => ({
couponId: coupon.id,
userId,
}))
);
}
if (applicableProducts && applicableProducts.length > 0) {
await tx.insert(couponApplicableProducts).values(
applicableProducts.map(productId => ({
couponId: coupon.id,
productId,
}))
);
}
return coupon as Coupon;
});
}
export interface UpdateCouponInput {
couponCode?: string;
isUserBased?: boolean;
discountPercent?: string;
flatDiscount?: string;
minOrder?: string;
productIds?: number[] | null;
maxValue?: string;
isApplyForAll?: boolean;
validTill?: Date | null;
maxLimitForUser?: number;
exclusiveApply?: boolean;
isInvalidated?: boolean;
}
export async function updateCouponWithRelations(
id: number,
input: UpdateCouponInput,
applicableUsers?: number[],
applicableProducts?: number[]
): Promise<Coupon> {
return await db.transaction(async (tx) => {
const [coupon] = await tx.update(coupons)
.set({
...input,
})
.where(eq(coupons.id, id))
.returning();
if (applicableUsers !== undefined) {
await tx.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, id));
if (applicableUsers.length > 0) {
await tx.insert(couponApplicableUsers).values(
applicableUsers.map(userId => ({
couponId: id,
userId,
}))
);
}
}
if (applicableProducts !== undefined) {
await tx.delete(couponApplicableProducts).where(eq(couponApplicableProducts.couponId, id));
if (applicableProducts.length > 0) {
await tx.insert(couponApplicableProducts).values(
applicableProducts.map(productId => ({
couponId: id,
productId,
}))
);
}
}
return coupon as Coupon;
});
}
export async function invalidateCoupon(id: number): Promise<Coupon> {
const result = await db.update(coupons)
.set({ isInvalidated: true })
.where(eq(coupons.id, id))
.returning();
return result[0] as Coupon;
}
export interface CouponValidationResult {
valid: boolean;
message?: string;
discountAmount?: number;
coupon?: Partial<Coupon>;
}
export async function validateCoupon(
code: string,
userId: number,
orderAmount: number
): Promise<CouponValidationResult> {
const coupon = await db.query.coupons.findFirst({
where: and(
eq(coupons.couponCode, code.toUpperCase()),
eq(coupons.isInvalidated, false)
),
});
if (!coupon) {
return { valid: false, message: "Coupon not found or invalidated" };
}
if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
return { valid: false, message: "Coupon has expired" };
}
if (!coupon.isApplyForAll && !coupon.isUserBased) {
return { valid: false, message: "Coupon is not available for use" };
}
const minOrderValue = coupon.minOrder ? parseFloat(coupon.minOrder) : 0;
if (minOrderValue > 0 && orderAmount < minOrderValue) {
return { valid: false, message: `Minimum order amount is ${minOrderValue}` };
}
let discountAmount = 0;
if (coupon.discountPercent) {
const percent = parseFloat(coupon.discountPercent);
discountAmount = (orderAmount * percent) / 100;
} else if (coupon.flatDiscount) {
discountAmount = parseFloat(coupon.flatDiscount);
}
const maxValueLimit = coupon.maxValue ? parseFloat(coupon.maxValue) : 0;
if (maxValueLimit > 0 && discountAmount > maxValueLimit) {
discountAmount = maxValueLimit;
}
return {
valid: true,
discountAmount,
coupon: {
id: coupon.id,
discountPercent: coupon.discountPercent,
flatDiscount: coupon.flatDiscount,
maxValue: coupon.maxValue,
}
};
}
export async function getReservedCoupons(
cursor?: number,
limit: number = 50,
search?: string
): Promise<{ coupons: any[]; hasMore: boolean }> {
let whereCondition = undefined;
const conditions = [];
if (cursor) {
conditions.push(lt(reservedCoupons.id, cursor));
}
if (search && search.trim()) {
conditions.push(or(
like(reservedCoupons.secretCode, `%${search}%`),
like(reservedCoupons.couponCode, `%${search}%`)
));
}
if (conditions.length > 0) {
whereCondition = and(...conditions);
}
const result = await db.query.reservedCoupons.findMany({
where: whereCondition,
with: {
redeemedUser: true,
creator: true,
},
orderBy: (reservedCoupons, { desc }) => [desc(reservedCoupons.createdAt)],
limit: limit + 1,
});
const hasMore = result.length > limit;
const couponsList = hasMore ? result.slice(0, limit) : result;
return { coupons: couponsList, hasMore };
}
export async function createReservedCouponWithProducts(
input: any,
applicableProducts?: number[]
): Promise<any> {
return await db.transaction(async (tx) => {
const [coupon] = await tx.insert(reservedCoupons).values({
secretCode: input.secretCode,
couponCode: input.couponCode,
discountPercent: input.discountPercent,
flatDiscount: input.flatDiscount,
minOrder: input.minOrder,
productIds: input.productIds,
maxValue: input.maxValue,
validTill: input.validTill,
maxLimitForUser: input.maxLimitForUser,
exclusiveApply: input.exclusiveApply,
createdBy: input.createdBy,
}).returning();
if (applicableProducts && applicableProducts.length > 0) {
await tx.insert(couponApplicableProducts).values(
applicableProducts.map(productId => ({
couponId: coupon.id,
productId,
}))
);
}
return coupon;
});
}
export async function checkUsersExist(userIds: number[]): Promise<boolean> {
const existingUsers = await db.query.users.findMany({
where: inArray(users.id, userIds),
columns: { id: true },
});
return existingUsers.length === userIds.length;
}
export async function checkCouponExists(couponCode: string): Promise<boolean> {
const existing = await db.query.coupons.findFirst({
where: eq(coupons.couponCode, couponCode),
});
return !!existing;
}
export async function checkReservedCouponExists(secretCode: string): Promise<boolean> {
const existing = await db.query.reservedCoupons.findFirst({
where: eq(reservedCoupons.secretCode, secretCode),
});
return !!existing;
}
export async function generateCancellationCoupon(
orderId: number,
staffUserId: number,
userId: number,
orderAmount: number,
couponCode: string
): Promise<Coupon> {
return await db.transaction(async (tx) => {
const expiryDate = new Date();
expiryDate.setDate(expiryDate.getDate() + 30);
const [coupon] = await tx.insert(coupons).values({
couponCode,
isUserBased: true,
flatDiscount: orderAmount.toString(),
minOrder: orderAmount.toString(),
maxValue: orderAmount.toString(),
validTill: expiryDate,
maxLimitForUser: 1,
createdBy: staffUserId,
isApplyForAll: false,
}).returning();
await tx.insert(couponApplicableUsers).values({
couponId: coupon.id,
userId,
});
await tx.update(orderStatus)
.set({ refundCouponId: coupon.id })
.where(eq(orderStatus.orderId, orderId));
return coupon as Coupon;
});
}
export async function getOrderWithUser(orderId: number): Promise<any | null> {
return await db.query.orders.findFirst({
where: eq(orders.id, orderId),
with: {
user: true,
},
});
}
export async function createCouponForUser(
mobile: string,
couponCode: string,
staffUserId: number
): Promise<{ coupon: Coupon; user: { id: number; mobile: string; name: string | null } }> {
return await db.transaction(async (tx) => {
let user = await tx.query.users.findFirst({
where: eq(users.mobile, mobile),
});
if (!user) {
const [newUser] = await tx.insert(users).values({
name: null,
email: null,
mobile,
}).returning();
user = newUser;
}
const [coupon] = await tx.insert(coupons).values({
couponCode,
isUserBased: true,
discountPercent: "20",
minOrder: "1000",
maxValue: "500",
maxLimitForUser: 1,
isApplyForAll: false,
exclusiveApply: false,
createdBy: staffUserId,
validTill: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
}).returning();
await tx.insert(couponApplicableUsers).values({
couponId: coupon.id,
userId: user.id,
});
return {
coupon: coupon as Coupon,
user: {
id: user.id,
mobile: user.mobile as string,
name: user.name,
},
};
});
}
export interface UserMiniInfo {
id: number;
name: string;
mobile: string | null;
}
export async function getUsersForCoupon(
search?: string,
limit: number = 20,
offset: number = 0
): Promise<{ users: UserMiniInfo[] }> {
let whereCondition = undefined;
if (search && search.trim()) {
whereCondition = or(
like(users.name, `%${search}%`),
like(users.mobile, `%${search}%`)
);
}
const userList = await db.query.users.findMany({
where: whereCondition,
columns: {
id: true,
name: true,
mobile: true,
},
limit: limit,
offset: offset,
orderBy: (users, { asc }) => [asc(users.name)],
});
return {
users: userList.map(user => ({
id: user.id,
name: user.name || 'Unknown',
mobile: user.mobile,
}))
};
}

View file

@ -0,0 +1,681 @@
import { db } from '../db/db_index'
import {
addresses,
complaints,
couponUsage,
orderItems,
orders,
orderStatus,
payments,
refunds,
} from '../db/schema'
import { and, desc, eq, inArray, lt, SQL } from 'drizzle-orm'
import type {
AdminOrderDetails,
AdminOrderRow,
AdminOrderStatusRecord,
AdminOrderUpdateResult,
AdminOrderItemPackagingResult,
AdminOrderMessageResult,
AdminOrderBasicResult,
AdminGetSlotOrdersResult,
AdminGetAllOrdersResultWithUserId,
AdminRebalanceSlotsResult,
AdminCancelOrderResult,
AdminRefundRecord,
RefundStatus,
PaymentStatus,
} from '@packages/shared'
export async function updateOrderNotes(orderId: number, adminNotes: string | null): Promise<AdminOrderRow | null> {
const [result] = await db
.update(orders)
.set({ adminNotes })
.where(eq(orders.id, orderId))
.returning()
return result || null
}
export async function updateOrderPackaged(orderId: string, isPackaged: boolean): Promise<AdminOrderUpdateResult> {
const orderIdNumber = parseInt(orderId)
await db
.update(orderItems)
.set({ is_packaged: isPackaged })
.where(eq(orderItems.orderId, orderIdNumber))
if (!isPackaged) {
await db
.update(orderStatus)
.set({ isPackaged, isDelivered: false })
.where(eq(orderStatus.orderId, orderIdNumber))
} else {
await db
.update(orderStatus)
.set({ isPackaged })
.where(eq(orderStatus.orderId, orderIdNumber))
}
const order = await db.query.orders.findFirst({
where: eq(orders.id, orderIdNumber),
})
return { success: true, userId: order?.userId ?? null }
}
export async function updateOrderDelivered(orderId: string, isDelivered: boolean): Promise<AdminOrderUpdateResult> {
const orderIdNumber = parseInt(orderId)
await db
.update(orderStatus)
.set({ isDelivered })
.where(eq(orderStatus.orderId, orderIdNumber))
const order = await db.query.orders.findFirst({
where: eq(orders.id, orderIdNumber),
})
return { success: true, userId: order?.userId ?? null }
}
export async function getOrderDetails(orderId: number): Promise<AdminOrderDetails | null> {
// Single optimized query with all relations
const orderData = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
with: {
user: true,
address: true,
slot: true,
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
payment: true,
paymentInfo: true,
orderStatus: true,
refunds: true,
},
})
if (!orderData) {
return null
}
const couponUsageData = await db.query.couponUsage.findMany({
where: eq(couponUsage.orderId, orderData.id),
with: {
coupon: true,
},
})
let couponData = null
if (couponUsageData.length > 0) {
let totalDiscountAmount = 0
const orderTotal = parseFloat(orderData.totalAmount.toString())
for (const usage of couponUsageData) {
let discountAmount = 0
if (usage.coupon.discountPercent) {
discountAmount =
(orderTotal * parseFloat(usage.coupon.discountPercent.toString())) /
100
} else if (usage.coupon.flatDiscount) {
discountAmount = parseFloat(usage.coupon.flatDiscount.toString())
}
if (
usage.coupon.maxValue &&
discountAmount > parseFloat(usage.coupon.maxValue.toString())
) {
discountAmount = parseFloat(usage.coupon.maxValue.toString())
}
totalDiscountAmount += discountAmount
}
couponData = {
couponCode: couponUsageData.map((u) => u.coupon.couponCode).join(', '),
couponDescription: `${couponUsageData.length} coupons applied`,
discountAmount: totalDiscountAmount,
}
}
const statusRecord = orderData.orderStatus?.[0]
const orderStatusRecord = statusRecord
? (statusRecord as AdminOrderStatusRecord)
: null
let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
if (orderStatusRecord?.isCancelled) {
status = 'cancelled'
} else if (orderStatusRecord?.isDelivered) {
status = 'delivered'
}
const refund = orderData.refunds?.[0]
const refundStatus = refund?.refundStatus
? (refund.refundStatus as RefundStatus)
: null
const refundRecord: AdminRefundRecord | null = refund
? {
id: refund.id,
orderId: refund.orderId,
refundAmount: refund.refundAmount,
refundStatus,
merchantRefundId: refund.merchantRefundId,
refundProcessedAt: refund.refundProcessedAt,
createdAt: refund.createdAt,
}
: null
return {
id: orderData.id,
readableId: orderData.id,
userId: orderData.user.id,
customerName: `${orderData.user.name}`,
customerEmail: orderData.user.email,
customerMobile: orderData.user.mobile,
address: {
name: orderData.address.name,
line1: orderData.address.addressLine1,
line2: orderData.address.addressLine2,
city: orderData.address.city,
state: orderData.address.state,
pincode: orderData.address.pincode,
phone: orderData.address.phone,
},
slotInfo: orderData.slot
? {
time: orderData.slot.deliveryTime.toISOString(),
sequence: orderData.slot.deliverySequence,
}
: null,
isCod: orderData.isCod,
isOnlinePayment: orderData.isOnlinePayment,
totalAmount:
parseFloat(orderData.totalAmount?.toString() || '0') -
parseFloat(orderData.deliveryCharge?.toString() || '0'),
deliveryCharge: parseFloat(orderData.deliveryCharge?.toString() || '0'),
adminNotes: orderData.adminNotes,
userNotes: orderData.userNotes,
createdAt: orderData.createdAt,
status,
isPackaged: orderStatusRecord?.isPackaged || false,
isDelivered: orderStatusRecord?.isDelivered || false,
items: orderData.orderItems.map((item) => ({
id: item.id,
name: item.product.name,
quantity: item.quantity,
productSize: item.product.productQuantity,
price: item.price,
unit: item.product.unit?.shortNotation,
amount: parseFloat(item.price.toString()) * parseFloat(item.quantity || '0'),
isPackaged: item.is_packaged,
isPackageVerified: item.is_package_verified,
})),
payment: orderData.payment
? {
status: orderData.payment.status,
gateway: orderData.payment.gateway,
merchantOrderId: orderData.payment.merchantOrderId,
}
: null,
paymentInfo: orderData.paymentInfo
? {
status: orderData.paymentInfo.status,
gateway: orderData.paymentInfo.gateway,
merchantOrderId: orderData.paymentInfo.merchantOrderId,
}
: null,
cancelReason: orderStatusRecord?.cancelReason || null,
cancellationReviewed: orderStatusRecord?.cancellationReviewed || false,
isRefundDone: refundStatus === 'processed' || false,
refundStatus,
refundAmount: refund?.refundAmount
? parseFloat(refund.refundAmount.toString())
: null,
couponData,
couponCode: couponData?.couponCode || null,
couponDescription: couponData?.couponDescription || null,
discountAmount: couponData?.discountAmount || null,
orderStatus: orderStatusRecord,
refundRecord,
isFlashDelivery: orderData.isFlashDelivery,
}
}
export async function updateOrderItemPackaging(
orderItemId: number,
isPackaged?: boolean,
isPackageVerified?: boolean
): Promise<AdminOrderItemPackagingResult> {
const orderItem = await db.query.orderItems.findFirst({
where: eq(orderItems.id, orderItemId),
})
if (!orderItem) {
return { success: false, updated: false }
}
const updateData: Partial<{
is_packaged: boolean
is_package_verified: boolean
}> = {}
if (isPackaged !== undefined) {
updateData.is_packaged = isPackaged
}
if (isPackageVerified !== undefined) {
updateData.is_package_verified = isPackageVerified
}
await db
.update(orderItems)
.set(updateData)
.where(eq(orderItems.id, orderItemId))
return { success: true, updated: true }
}
export async function removeDeliveryCharge(orderId: number): Promise<AdminOrderMessageResult | null> {
const order = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
})
if (!order) {
return null
}
const currentDeliveryCharge = parseFloat(order.deliveryCharge?.toString() || '0')
const currentTotalAmount = parseFloat(order.totalAmount?.toString() || '0')
const newTotalAmount = currentTotalAmount - currentDeliveryCharge
await db
.update(orders)
.set({
deliveryCharge: '0',
totalAmount: newTotalAmount.toString(),
})
.where(eq(orders.id, orderId))
return { success: true, message: 'Delivery charge removed' }
}
export async function getSlotOrders(slotId: string): Promise<AdminGetSlotOrdersResult> {
const slotOrders = await db.query.orders.findMany({
where: eq(orders.slotId, parseInt(slotId)),
with: {
user: true,
address: true,
slot: true,
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
orderStatus: true,
},
})
const filteredOrders = slotOrders.filter((order) => {
const statusRecord = order.orderStatus[0]
return order.isCod || (statusRecord && statusRecord.paymentStatus === 'success')
})
const formattedOrders = filteredOrders.map((order) => {
const statusRecord = order.orderStatus[0]
let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
if (statusRecord?.isCancelled) {
status = 'cancelled'
} else if (statusRecord?.isDelivered) {
status = 'delivered'
}
const items = order.orderItems.map((item) => ({
id: item.id,
name: item.product.name,
quantity: parseFloat(item.quantity),
price: parseFloat(item.price.toString()),
amount: parseFloat(item.quantity) * parseFloat(item.price.toString()),
unit: item.product.unit?.shortNotation || '',
isPackaged: item.is_packaged,
isPackageVerified: item.is_package_verified,
}))
return {
id: order.id,
readableId: order.id,
customerName: order.user.name || order.user.mobile+'',
address: `${order.address.addressLine1}${
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ''
}, ${order.address.city}, ${order.address.state} - ${
order.address.pincode
}, Phone: ${order.address.phone}`,
addressId: order.addressId,
latitude: order.address.adminLatitude ?? order.address.latitude,
longitude: order.address.adminLongitude ?? order.address.longitude,
totalAmount: parseFloat(order.totalAmount),
items,
deliveryTime: order.slot?.deliveryTime.toISOString() || null,
status,
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,
slotId: order.slotId,
adminNotes: order.adminNotes,
userNotes: order.userNotes,
}
})
return { success: true, data: formattedOrders }
}
export async function updateAddressCoords(
addressId: number,
latitude: number,
longitude: number
): Promise<AdminOrderBasicResult> {
const result = await db
.update(addresses)
.set({
adminLatitude: latitude,
adminLongitude: longitude,
})
.where(eq(addresses.id, addressId))
.returning()
return { success: result.length > 0 }
}
type GetAllOrdersInput = {
cursor?: number
limit: number
slotId?: number | null
packagedFilter?: 'all' | 'packaged' | 'not_packaged'
deliveredFilter?: 'all' | 'delivered' | 'not_delivered'
cancellationFilter?: 'all' | 'cancelled' | 'not_cancelled'
flashDeliveryFilter?: 'all' | 'flash' | 'regular'
}
export async function getAllOrders(input: GetAllOrdersInput): Promise<AdminGetAllOrdersResultWithUserId> {
const {
cursor,
limit,
slotId,
packagedFilter,
deliveredFilter,
cancellationFilter,
flashDeliveryFilter,
} = input
let whereCondition: SQL<unknown> | undefined = eq(orders.id, orders.id)
if (cursor) {
whereCondition = and(whereCondition, lt(orders.id, cursor))
}
if (slotId) {
whereCondition = and(whereCondition, eq(orders.slotId, slotId))
}
if (packagedFilter === 'packaged') {
whereCondition = and(whereCondition, eq(orderStatus.isPackaged, true))
} else if (packagedFilter === 'not_packaged') {
whereCondition = and(whereCondition, eq(orderStatus.isPackaged, false))
}
if (deliveredFilter === 'delivered') {
whereCondition = and(whereCondition, eq(orderStatus.isDelivered, true))
} else if (deliveredFilter === 'not_delivered') {
whereCondition = and(whereCondition, eq(orderStatus.isDelivered, false))
}
if (cancellationFilter === 'cancelled') {
whereCondition = and(whereCondition, eq(orderStatus.isCancelled, true))
} else if (cancellationFilter === 'not_cancelled') {
whereCondition = and(whereCondition, eq(orderStatus.isCancelled, false))
}
if (flashDeliveryFilter === 'flash') {
whereCondition = and(whereCondition, eq(orders.isFlashDelivery, true))
} else if (flashDeliveryFilter === 'regular') {
whereCondition = and(whereCondition, eq(orders.isFlashDelivery, false))
}
const allOrders = await db.query.orders.findMany({
where: whereCondition,
orderBy: desc(orders.createdAt),
limit: limit + 1,
with: {
user: true,
address: true,
slot: true,
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
orderStatus: true,
},
})
const hasMore = allOrders.length > limit
const ordersToReturn = hasMore ? allOrders.slice(0, limit) : allOrders
const filteredOrders = ordersToReturn.filter((order) => {
const statusRecord = order.orderStatus[0]
return order.isCod || (statusRecord && statusRecord.paymentStatus === 'success')
})
const formattedOrders = filteredOrders.map((order) => {
const statusRecord = order.orderStatus[0]
let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
if (statusRecord?.isCancelled) {
status = 'cancelled'
} else if (statusRecord?.isDelivered) {
status = 'delivered'
}
const items = order.orderItems
.map((item) => ({
id: item.id,
name: item.product.name,
quantity: parseFloat(item.quantity),
price: parseFloat(item.price.toString()),
amount: parseFloat(item.quantity) * parseFloat(item.price.toString()),
unit: item.product.unit?.shortNotation || '',
productSize: item.product.productQuantity,
isPackaged: item.is_packaged,
isPackageVerified: item.is_package_verified,
}))
.sort((first, second) => first.id - second.id)
return {
id: order.id,
orderId: order.id.toString(),
readableId: order.id,
customerName: order.user.name || order.user.mobile + '',
customerMobile: order.user.mobile,
address: `${order.address.addressLine1}${
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ''
}, ${order.address.city}, ${order.address.state} - ${
order.address.pincode
}, Phone: ${order.address.phone}`,
addressId: order.addressId,
latitude: order.address.adminLatitude ?? order.address.latitude,
longitude: order.address.adminLongitude ?? order.address.longitude,
totalAmount: parseFloat(order.totalAmount),
deliveryCharge: parseFloat(order.deliveryCharge || '0'),
items,
createdAt: order.createdAt,
deliveryTime: order.slot?.deliveryTime.toISOString() || null,
status,
isPackaged: order.orderItems.every((item) => item.is_packaged) || false,
isDelivered: statusRecord?.isDelivered || false,
isCod: order.isCod,
isFlashDelivery: order.isFlashDelivery,
userNotes: order.userNotes,
adminNotes: order.adminNotes,
userNegativityScore: 0,
userId: order.userId,
}
})
return {
orders: formattedOrders,
nextCursor: hasMore ? ordersToReturn[ordersToReturn.length - 1].id : undefined,
}
}
export async function rebalanceSlots(slotIds: number[]): Promise<AdminRebalanceSlotsResult> {
const ordersList = await db.query.orders.findMany({
where: inArray(orders.slotId, slotIds),
with: {
orderItems: {
with: {
product: true,
},
},
couponUsages: {
with: {
coupon: true,
},
},
},
})
const processedOrdersData = ordersList.map((order) => {
let newTotal = order.orderItems.reduce((acc, item) => {
const latestPrice = +item.product.price
const amount = latestPrice * Number(item.quantity)
return acc + amount
}, 0)
order.orderItems.forEach((item) => {
item.price = item.product.price
item.discountedPrice = item.product.price
})
const coupon = order.couponUsages[0]?.coupon
let discount = 0
if (coupon && !coupon.isInvalidated && (!coupon.validTill || new Date(coupon.validTill) > new Date())) {
const proportion = Number(order.orderGroupProportion || 1)
if (coupon.discountPercent) {
const maxDiscount = Number(coupon.maxValue || Infinity) * proportion
discount = Math.min((newTotal * parseFloat(coupon.discountPercent)) / 100, maxDiscount)
} else {
discount = Number(coupon.flatDiscount) * proportion
}
}
newTotal -= discount
const { couponUsages, orderItems: orderItemsRaw, ...rest } = order
const updatedOrderItems = orderItemsRaw.map((item) => {
const { product, ...rawOrderItem } = item
return rawOrderItem
})
return { order: rest, updatedOrderItems, newTotal }
})
const updatedOrderIds: number[] = []
await db.transaction(async (tx) => {
for (const { order, updatedOrderItems, newTotal } of processedOrdersData) {
await tx.update(orders).set({ totalAmount: newTotal.toString() }).where(eq(orders.id, order.id))
updatedOrderIds.push(order.id)
for (const item of updatedOrderItems) {
await tx
.update(orderItems)
.set({
price: item.price,
discountedPrice: item.discountedPrice,
})
.where(eq(orderItems.id, item.id))
}
}
})
return {
success: true,
updatedOrders: updatedOrderIds,
message: `Rebalanced ${updatedOrderIds.length} orders.`,
}
}
export async function cancelOrder(orderId: number, reason: string): Promise<AdminCancelOrderResult> {
const order = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
with: {
orderStatus: true,
},
})
if (!order) {
return { success: false, message: 'Order not found', error: 'order_not_found' }
}
const status = order.orderStatus[0]
if (!status) {
return { success: false, message: 'Order status not found', error: 'status_not_found' }
}
if (status.isCancelled) {
return { success: false, message: 'Order is already cancelled', error: 'already_cancelled' }
}
if (status.isDelivered) {
return { success: false, message: 'Cannot cancel delivered order', error: 'already_delivered' }
}
const result = await db.transaction(async (tx) => {
await tx
.update(orderStatus)
.set({
isCancelled: true,
isCancelledByAdmin: true,
cancelReason: reason,
cancellationAdminNotes: reason,
cancellationReviewed: true,
cancellationReviewedAt: new Date(),
})
.where(eq(orderStatus.id, status.id))
const refundStatus = order.isCod ? 'na' : 'pending'
await tx.insert(refunds).values({
orderId: order.id,
refundStatus,
})
return { orderId: order.id, userId: order.userId }
})
return {
success: true,
message: 'Order cancelled successfully',
orderId: result.orderId,
userId: result.userId,
}
}
export async function deleteOrderById(orderId: number): Promise<void> {
await db.transaction(async (tx) => {
await tx.delete(orderItems).where(eq(orderItems.orderId, orderId))
await tx.delete(orderStatus).where(eq(orderStatus.orderId, orderId))
await tx.delete(payments).where(eq(payments.orderId, orderId))
await tx.delete(refunds).where(eq(refunds.orderId, orderId))
await tx.delete(couponUsage).where(eq(couponUsage.orderId, orderId))
await tx.delete(complaints).where(eq(complaints.orderId, orderId))
await tx.delete(orders).where(eq(orders.id, orderId))
})
}

View file

@ -0,0 +1,130 @@
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';
export async function getAllProducts(): Promise<any[]> {
return await db.query.productInfo.findMany({
orderBy: productInfo.name,
with: {
unit: true,
store: true,
},
});
}
export async function getProductById(id: number): Promise<any | null> {
return 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,
},
},
},
});
}
export async function createProduct(input: any): Promise<any> {
const [product] = await db.insert(productInfo).values(input).returning();
return product;
}
export async function updateProduct(id: number, updates: any): Promise<any> {
const [product] = await db.update(productInfo)
.set(updates)
.where(eq(productInfo.id, id))
.returning();
return product;
}
export async function toggleProductOutOfStock(id: number, isOutOfStock: boolean): Promise<any> {
const [product] = await db.update(productInfo)
.set({ isOutOfStock })
.where(eq(productInfo.id, id))
.returning();
return product;
}
export async function getAllUnits(): Promise<any[]> {
return await db.query.units.findMany({
orderBy: units.name,
});
}
export async function getAllProductTags(): Promise<any[]> {
return await db.query.productTags.findMany({
with: {
products: {
with: {
product: true,
},
},
},
});
}
export async function getProductReviews(productId: number): Promise<any[]> {
return await db.query.productReviews.findMany({
where: eq(productReviews.productId, productId),
with: {
user: true,
},
orderBy: desc(productReviews.createdAt),
});
}
export async function respondToReview(reviewId: number, adminResponse: string): Promise<void> {
await db.update(productReviews)
.set({ adminResponse })
.where(eq(productReviews.id, reviewId));
}
export async function getAllProductGroups(): Promise<any[]> {
return await db.query.productGroupInfo.findMany({
with: {
products: {
with: {
product: true,
},
},
},
});
}
export async function createProductGroup(name: string): Promise<any> {
const [group] = await db.insert(productGroupInfo).values({ name }).returning();
return group;
}
export async function updateProductGroup(id: number, name: string): Promise<any> {
const [group] = await db.update(productGroupInfo)
.set({ name })
.where(eq(productGroupInfo.id, id))
.returning();
return group;
}
export async function deleteProductGroup(id: number): Promise<void> {
await db.delete(productGroupInfo).where(eq(productGroupInfo.id, id));
}
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
await db.insert(productGroupMembership).values({ groupId, productId });
}
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
await db.delete(productGroupMembership)
.where(and(
eq(productGroupMembership.groupId, groupId),
eq(productGroupMembership.productId, productId)
));
}

View file

@ -0,0 +1,95 @@
import { db } from '../db/db_index';
import { deliverySlotInfo, productSlots, productInfo } from '../db/schema';
import { eq, and, inArray, desc } from 'drizzle-orm';
export async function getAllSlots(): Promise<any[]> {
return await db.query.deliverySlotInfo.findMany({
orderBy: desc(deliverySlotInfo.createdAt),
with: {
productSlots: {
with: {
product: true,
},
},
},
});
}
export async function getSlotById(id: number): Promise<any | null> {
return await db.query.deliverySlotInfo.findFirst({
where: eq(deliverySlotInfo.id, id),
with: {
productSlots: {
with: {
product: true,
},
},
},
});
}
export async function createSlot(input: any): Promise<any> {
const [slot] = await db.insert(deliverySlotInfo).values(input).returning();
return slot;
}
export async function updateSlot(id: number, updates: any): Promise<any> {
const [slot] = await db.update(deliverySlotInfo)
.set(updates)
.where(eq(deliverySlotInfo.id, id))
.returning();
return slot;
}
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> {
const slot = await db.query.deliverySlotInfo.findFirst({
where: eq(deliverySlotInfo.id, slotId),
columns: {
deliverySequence: true,
},
});
return slot?.deliverySequence || null;
}
export async function updateSlotDeliverySequence(slotId: number, sequence: any): Promise<void> {
await db.update(deliverySlotInfo)
.set({ deliverySequence: sequence })
.where(eq(deliverySlotInfo.id, slotId));
}

View file

@ -0,0 +1,146 @@
import { db } from '../db/db_index';
import { staffUsers, staffRoles, users, userDetails, orders } from '../db/schema';
import { eq, or, ilike, and, lt, desc } from 'drizzle-orm';
export interface StaffUser {
id: number;
name: string;
password: string;
staffRoleId: number;
createdAt: Date;
}
export async function getStaffUserByName(name: string): Promise<StaffUser | null> {
const staff = await db.query.staffUsers.findFirst({
where: eq(staffUsers.name, name),
});
return staff || null;
}
export async function getAllStaff(): Promise<any[]> {
const staff = await db.query.staffUsers.findMany({
columns: {
id: true,
name: true,
},
with: {
role: {
with: {
rolePermissions: {
with: {
permission: true,
},
},
},
},
},
});
return staff;
}
export async function getAllUsers(
cursor?: number,
limit: number = 20,
search?: string
): Promise<{ users: any[]; hasMore: boolean }> {
let whereCondition = undefined;
if (search) {
whereCondition = or(
ilike(users.name, `%${search}%`),
ilike(users.email, `%${search}%`),
ilike(users.mobile, `%${search}%`)
);
}
if (cursor) {
const cursorCondition = lt(users.id, cursor);
whereCondition = whereCondition ? and(whereCondition, cursorCondition) : cursorCondition;
}
const allUsers = await db.query.users.findMany({
where: whereCondition,
with: {
userDetails: true,
},
orderBy: desc(users.id),
limit: limit + 1,
});
const hasMore = allUsers.length > limit;
const usersToReturn = hasMore ? allUsers.slice(0, limit) : allUsers;
return { users: usersToReturn, hasMore };
}
export async function getUserWithDetails(userId: number): Promise<any | null> {
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
with: {
userDetails: true,
orders: {
orderBy: desc(orders.createdAt),
limit: 1,
},
},
});
return user || null;
}
export async function updateUserSuspensionStatus(userId: number, isSuspended: boolean): Promise<void> {
await db
.insert(userDetails)
.values({ userId, isSuspended })
.onConflictDoUpdate({
target: userDetails.userId,
set: { isSuspended },
});
}
export async function checkStaffUserExists(name: string): Promise<boolean> {
const existingUser = await db.query.staffUsers.findFirst({
where: eq(staffUsers.name, name),
});
return !!existingUser;
}
export async function checkStaffRoleExists(roleId: number): Promise<boolean> {
const role = await db.query.staffRoles.findFirst({
where: eq(staffRoles.id, roleId),
});
return !!role;
}
export async function createStaffUser(
name: string,
password: string,
roleId: number
): Promise<StaffUser> {
const [newUser] = await db.insert(staffUsers).values({
name: name.trim(),
password,
staffRoleId: roleId,
}).returning();
return {
id: newUser.id,
name: newUser.name,
password: newUser.password,
staffRoleId: newUser.staffRoleId,
createdAt: newUser.createdAt,
};
}
export async function getAllRoles(): Promise<any[]> {
const roles = await db.query.staffRoles.findMany({
columns: {
id: true,
roleName: true,
},
});
return roles;
}

View file

@ -0,0 +1,145 @@
import { db } from '../db/db_index';
import { storeInfo, productInfo } from '../db/schema';
import { eq, inArray } from 'drizzle-orm';
export interface Store {
id: number;
name: string;
description: string | null;
imageUrl: string | null;
owner: number;
createdAt: Date;
updatedAt: Date;
}
export async function getAllStores(): Promise<any[]> {
const stores = await db.query.storeInfo.findMany({
with: {
owner: true,
},
});
return stores;
}
export async function getStoreById(id: number): Promise<any | null> {
const store = await db.query.storeInfo.findFirst({
where: eq(storeInfo.id, id),
with: {
owner: true,
},
});
return store || null;
}
export interface CreateStoreInput {
name: string;
description?: string;
imageUrl?: string;
owner: number;
}
export async function createStore(
input: CreateStoreInput,
products?: number[]
): Promise<Store> {
const [newStore] = await db
.insert(storeInfo)
.values({
name: input.name,
description: input.description,
imageUrl: input.imageUrl,
owner: input.owner,
})
.returning();
if (products && products.length > 0) {
await db
.update(productInfo)
.set({ storeId: newStore.id })
.where(inArray(productInfo.id, products));
}
return {
id: newStore.id,
name: newStore.name,
description: newStore.description,
imageUrl: newStore.imageUrl,
owner: newStore.owner,
createdAt: newStore.createdAt,
updatedAt: newStore.updatedAt,
};
}
export interface UpdateStoreInput {
name?: string;
description?: string;
imageUrl?: string;
owner?: number;
}
export async function updateStore(
id: number,
input: UpdateStoreInput,
products?: number[]
): Promise<Store> {
const [updatedStore] = await db
.update(storeInfo)
.set({
...input,
updatedAt: new Date(),
})
.where(eq(storeInfo.id, id))
.returning();
if (!updatedStore) {
throw new Error("Store not found");
}
if (products !== undefined) {
await db
.update(productInfo)
.set({ storeId: null })
.where(eq(productInfo.storeId, id));
if (products.length > 0) {
await db
.update(productInfo)
.set({ storeId: id })
.where(inArray(productInfo.id, products));
}
}
return {
id: updatedStore.id,
name: updatedStore.name,
description: updatedStore.description,
imageUrl: updatedStore.imageUrl,
owner: updatedStore.owner,
createdAt: updatedStore.createdAt,
updatedAt: updatedStore.updatedAt,
};
}
export async function deleteStore(id: number): Promise<{ message: string }> {
return await db.transaction(async (tx) => {
await tx
.update(productInfo)
.set({ storeId: null })
.where(eq(productInfo.storeId, id));
const [deletedStore] = await tx
.delete(storeInfo)
.where(eq(storeInfo.id, id))
.returning();
if (!deletedStore) {
throw new Error("Store not found");
}
return {
message: "Store deleted successfully",
};
});
}

View file

@ -0,0 +1,270 @@
import { db } from '../db/db_index';
import { users, userDetails, orders, orderItems, complaints, notifCreds, unloggedUserTokens, userIncidents, orderStatus } from '../db/schema';
import { eq, sql, desc, asc, count, max, inArray } from 'drizzle-orm';
export async function createUserByMobile(mobile: string): Promise<any> {
const [newUser] = await db
.insert(users)
.values({
name: null,
email: null,
mobile,
})
.returning();
return newUser;
}
export async function getUserByMobile(mobile: string): Promise<any | null> {
const [existingUser] = await db
.select()
.from(users)
.where(eq(users.mobile, mobile))
.limit(1);
return existingUser || null;
}
export async function getUnresolvedComplaintsCount(): Promise<number> {
const result = await db
.select({ count: count(complaints.id) })
.from(complaints)
.where(eq(complaints.isResolved, false));
return result[0]?.count || 0;
}
export async function getAllUsersWithFilters(
limit: number,
cursor?: number,
search?: string
): Promise<{ users: any[]; hasMore: boolean }> {
const whereConditions = [];
if (search && search.trim()) {
whereConditions.push(sql`${users.mobile} ILIKE ${`%${search.trim()}%`}`);
}
if (cursor) {
whereConditions.push(sql`${users.id} > ${cursor}`);
}
const usersList = await db
.select({
id: users.id,
name: users.name,
mobile: users.mobile,
createdAt: users.createdAt,
})
.from(users)
.where(whereConditions.length > 0 ? sql.join(whereConditions, sql` AND `) : undefined)
.orderBy(asc(users.id))
.limit(limit + 1);
const hasMore = usersList.length > limit;
const usersToReturn = hasMore ? usersList.slice(0, limit) : usersList;
return { users: usersToReturn, hasMore };
}
export async function getOrderCountsByUserIds(userIds: number[]): Promise<{ userId: number; totalOrders: number }[]> {
if (userIds.length === 0) return [];
return await db
.select({
userId: orders.userId,
totalOrders: count(orders.id),
})
.from(orders)
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
.groupBy(orders.userId);
}
export async function getLastOrdersByUserIds(userIds: number[]): Promise<{ userId: number; lastOrderDate: Date | null }[]> {
if (userIds.length === 0) return [];
return await db
.select({
userId: orders.userId,
lastOrderDate: max(orders.createdAt),
})
.from(orders)
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
.groupBy(orders.userId);
}
export async function getSuspensionStatusesByUserIds(userIds: number[]): Promise<{ userId: number; isSuspended: boolean }[]> {
if (userIds.length === 0) return [];
return await db
.select({
userId: userDetails.userId,
isSuspended: userDetails.isSuspended,
})
.from(userDetails)
.where(sql`${userDetails.userId} IN (${sql.join(userIds, sql`, `)})`);
}
export async function getUserBasicInfo(userId: number): Promise<any | null> {
const user = await db
.select({
id: users.id,
name: users.name,
mobile: users.mobile,
createdAt: users.createdAt,
})
.from(users)
.where(eq(users.id, userId))
.limit(1);
return user[0] || null;
}
export async function getUserSuspensionStatus(userId: number): Promise<boolean> {
const userDetail = await db
.select({
isSuspended: userDetails.isSuspended,
})
.from(userDetails)
.where(eq(userDetails.userId, userId))
.limit(1);
return userDetail[0]?.isSuspended ?? false;
}
export async function getUserOrders(userId: number): Promise<any[]> {
return await db
.select({
id: orders.id,
readableId: orders.readableId,
totalAmount: orders.totalAmount,
createdAt: orders.createdAt,
isFlashDelivery: orders.isFlashDelivery,
})
.from(orders)
.where(eq(orders.userId, userId))
.orderBy(desc(orders.createdAt));
}
export async function getOrderStatusesByOrderIds(orderIds: number[]): Promise<{ orderId: number; isDelivered: boolean; isCancelled: boolean }[]> {
if (orderIds.length === 0) return [];
return await db
.select({
orderId: orderStatus.orderId,
isDelivered: orderStatus.isDelivered,
isCancelled: orderStatus.isCancelled,
})
.from(orderStatus)
.where(sql`${orderStatus.orderId} IN (${sql.join(orderIds, sql`, `)})`);
}
export async function getItemCountsByOrderIds(orderIds: number[]): Promise<{ orderId: number; itemCount: number }[]> {
if (orderIds.length === 0) return [];
return await db
.select({
orderId: orderItems.orderId,
itemCount: count(orderItems.id),
})
.from(orderItems)
.where(sql`${orderItems.orderId} IN (${sql.join(orderIds, sql`, `)})`)
.groupBy(orderItems.orderId);
}
export async function upsertUserSuspension(userId: number, isSuspended: boolean): Promise<void> {
const existingDetail = await db
.select({ id: userDetails.id })
.from(userDetails)
.where(eq(userDetails.userId, userId))
.limit(1);
if (existingDetail.length > 0) {
await db
.update(userDetails)
.set({ isSuspended })
.where(eq(userDetails.userId, userId));
} else {
await db
.insert(userDetails)
.values({
userId,
isSuspended,
});
}
}
export async function searchUsers(search?: string): Promise<any[]> {
if (search && search.trim()) {
return await db
.select({
id: users.id,
name: users.name,
mobile: users.mobile,
})
.from(users)
.where(sql`${users.mobile} ILIKE ${`%${search.trim()}%`} OR ${users.name} ILIKE ${`%${search.trim()}%`}`);
} else {
return await db
.select({
id: users.id,
name: users.name,
mobile: users.mobile,
})
.from(users);
}
}
export async function getAllNotifCreds(): Promise<{ userId: number, token: string }[]> {
return await db
.select({ userId: notifCreds.userId, token: notifCreds.token })
.from(notifCreds);
}
export async function getAllUnloggedTokens(): Promise<{ token: string }[]> {
return await db
.select({ token: unloggedUserTokens.token })
.from(unloggedUserTokens);
}
export async function getNotifTokensByUserIds(userIds: number[]): Promise<{ token: string }[]> {
return await db
.select({ token: notifCreds.token })
.from(notifCreds)
.where(inArray(notifCreds.userId, userIds));
}
export async function getUserIncidentsWithRelations(userId: number): Promise<any[]> {
return await db.query.userIncidents.findMany({
where: eq(userIncidents.userId, userId),
with: {
order: {
with: {
orderStatus: true,
},
},
addedBy: true,
},
orderBy: desc(userIncidents.dateAdded),
});
}
export async function createUserIncident(
userId: number,
orderId: number | undefined,
adminComment: string | undefined,
adminUserId: number,
negativityScore: number | undefined
): Promise<any> {
const [incident] = await db.insert(userIncidents)
.values({
userId,
orderId,
adminComment,
addedBy: adminUserId,
negativityScore,
})
.returning();
return incident;
}

View file

@ -0,0 +1,130 @@
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';
export async function checkVendorSnippetExists(snippetCode: string): Promise<boolean> {
const existingSnippet = await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.snippetCode, snippetCode),
});
return !!existingSnippet;
}
export async function getVendorSnippetById(id: number): Promise<any | null> {
return await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.id, id),
with: {
slot: true,
},
});
}
export async function getVendorSnippetByCode(snippetCode: string): Promise<any | null> {
return await db.query.vendorSnippets.findFirst({
where: eq(vendorSnippets.snippetCode, snippetCode),
});
}
export async function getAllVendorSnippets(): Promise<any[]> {
return await db.query.vendorSnippets.findMany({
with: {
slot: true,
},
orderBy: (vendorSnippets, { desc }) => [desc(vendorSnippets.createdAt)],
});
}
export interface CreateVendorSnippetInput {
snippetCode: string;
slotId?: number;
productIds: number[];
isPermanent: boolean;
validTill?: Date;
}
export async function createVendorSnippet(input: CreateVendorSnippetInput): Promise<any> {
const [result] = await db.insert(vendorSnippets).values({
snippetCode: input.snippetCode,
slotId: input.slotId,
productIds: input.productIds,
isPermanent: input.isPermanent,
validTill: input.validTill,
}).returning();
return result;
}
export async function updateVendorSnippet(id: number, updates: any): Promise<any> {
const [result] = await db.update(vendorSnippets)
.set(updates)
.where(eq(vendorSnippets.id, id))
.returning();
return result;
}
export async function deleteVendorSnippet(id: number): Promise<void> {
await db.delete(vendorSnippets)
.where(eq(vendorSnippets.id, id));
}
export async function getProductsByIds(productIds: number[]): Promise<any[]> {
return await db.query.productInfo.findMany({
where: inArray(productInfo.id, productIds),
columns: { id: true, name: true },
});
}
export async function getVendorSlotById(slotId: number): Promise<any | null> {
return await db.query.deliverySlotInfo.findFirst({
where: eq(deliverySlotInfo.id, slotId),
});
}
export async function getVendorOrdersBySlotId(slotId: number): Promise<any[]> {
return 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)],
});
}
export async function getOrderItemsByOrderIds(orderIds: number[]): Promise<any[]> {
return await db.query.orderItems.findMany({
where: inArray(orderItems.orderId, orderIds),
with: {
product: {
with: {
unit: true,
},
},
},
});
}
export async function getOrderStatusByOrderIds(orderIds: number[]): Promise<any[]> {
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)
.set({
is_packaged: isPackaged,
is_package_verified: isPackageVerified,
})
.where(eq(orderItems.id, orderItemId));
}

View file

@ -0,0 +1,19 @@
// Common utility functions that can be used by both admin and user APIs
export function formatDate(date: Date): string {
return date.toISOString();
}
export function generateCode(prefix: string, length: number = 6): string {
const timestamp = Date.now().toString().slice(-length);
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
return `${prefix}${timestamp}${random}`;
}
export function calculateDiscount(amount: number, percent: number, maxDiscount?: number): number {
let discount = (amount * percent) / 100;
if (maxDiscount && discount > maxDiscount) {
discount = maxDiscount;
}
return discount;
}

View file

@ -0,0 +1,23 @@
import { db } from '../db/db_index';
import { addresses, addressAreas, addressZones } from '../db/schema';
import { eq, desc } from 'drizzle-orm';
export async function getZones(): Promise<any[]> {
const zones = await db.select().from(addressZones).orderBy(desc(addressZones.addedAt));
return zones;
}
export async function getAreas(): Promise<any[]> {
const areas = await db.select().from(addressAreas).orderBy(desc(addressAreas.createdAt));
return areas;
}
export async function createZone(zoneName: string): Promise<any> {
const [zone] = await db.insert(addressZones).values({ zoneName }).returning();
return zone;
}
export async function createArea(placeName: string, zoneId: number | null): Promise<any> {
const [area] = await db.insert(addressAreas).values({ placeName, zoneId }).returning();
return area;
}

View file

@ -0,0 +1,14 @@
import { db } from '../db/db_index';
import { users } from '../db/schema';
import { eq } from 'drizzle-orm';
export async function getUserByMobile(mobile: string): Promise<any | null> {
return await db.query.users.findFirst({
where: eq(users.mobile, mobile),
});
}
export async function createUser(userData: any): Promise<any> {
const [user] = await db.insert(users).values(userData).returning();
return user;
}

View file

@ -0,0 +1,11 @@
import { db } from '../db/db_index';
import { homeBanners } from '../db/schema';
import { eq, and, desc, sql } from 'drizzle-orm';
export async function getActiveBanners(): Promise<any[]> {
const banners = await db.query.homeBanners.findMany({
where: eq(homeBanners.isActive, true),
orderBy: desc(homeBanners.createdAt),
});
return banners;
}

View file

@ -0,0 +1,41 @@
import { db } from '../db/db_index';
import { cartItems, productInfo } from '../db/schema';
import { eq, and } from 'drizzle-orm';
export async function getCartItems(userId: number): Promise<any[]> {
return await db.query.cartItems.findMany({
where: eq(cartItems.userId, userId),
with: {
product: {
with: {
unit: true,
},
},
},
});
}
export async function addToCart(userId: number, productId: number, quantity: number): Promise<any> {
const [item] = await db.insert(cartItems).values({
userId,
productId,
quantity,
}).returning();
return item;
}
export async function updateCartItem(itemId: number, quantity: number): Promise<any> {
const [item] = await db.update(cartItems)
.set({ quantity })
.where(eq(cartItems.id, itemId))
.returning();
return item;
}
export async function removeFromCart(itemId: number): Promise<void> {
await db.delete(cartItems).where(eq(cartItems.id, itemId));
}
export async function clearCart(userId: number): Promise<void> {
await db.delete(cartItems).where(eq(cartItems.userId, userId));
}

View file

@ -0,0 +1,21 @@
import { db } from '../db/db_index';
import { complaints } from '../db/schema';
import { eq, desc } from 'drizzle-orm';
export async function getUserComplaints(userId: number): Promise<any[]> {
return await db.query.complaints.findMany({
where: eq(complaints.userId, userId),
orderBy: desc(complaints.createdAt),
});
}
export async function createComplaint(userId: number, orderId: number | null, complaintBody: string, images?: string[]): Promise<any> {
const [complaint] = await db.insert(complaints).values({
userId,
orderId,
complaintBody,
images,
isResolved: false,
}).returning();
return complaint;
}

View file

@ -0,0 +1,43 @@
import { db } from '../db/db_index';
import { coupons, couponUsage } from '../db/schema';
import { eq, and } from 'drizzle-orm';
export async function validateUserCoupon(code: string, userId: number, orderAmount: number): Promise<any> {
const coupon = await db.query.coupons.findFirst({
where: eq(coupons.couponCode, code.toUpperCase()),
});
if (!coupon || coupon.isInvalidated) {
return { valid: false, message: 'Invalid coupon' };
}
if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
return { valid: false, message: 'Coupon expired' };
}
let discountAmount = 0;
if (coupon.discountPercent) {
discountAmount = (orderAmount * parseFloat(coupon.discountPercent)) / 100;
} else if (coupon.flatDiscount) {
discountAmount = parseFloat(coupon.flatDiscount);
}
if (coupon.maxValue) {
const maxDiscount = parseFloat(coupon.maxValue);
if (discountAmount > maxDiscount) {
discountAmount = maxDiscount;
}
}
return {
valid: true,
discountAmount,
couponId: coupon.id,
};
}
export async function getUserCoupons(userId: number): Promise<any[]> {
return await db.query.coupons.findMany({
where: eq(coupons.userId, userId),
});
}

View file

@ -0,0 +1,59 @@
import { db } from '../db/db_index';
import { orders, orderItems, orderStatus } from '../db/schema';
import { eq, desc } from 'drizzle-orm';
export async function getUserOrders(userId: number): Promise<any[]> {
return await db.query.orders.findMany({
where: eq(orders.userId, userId),
with: {
orderItems: {
with: {
product: {
with: {
unit: true,
},
},
},
},
orderStatus: true,
slot: true,
},
orderBy: desc(orders.createdAt),
});
}
export async function getOrderById(orderId: number, userId: number): Promise<any | null> {
return await db.query.orders.findFirst({
where: eq(orders.id, orderId),
with: {
orderItems: {
with: {
product: true,
},
},
orderStatus: true,
address: true,
slot: true,
},
});
}
export async function createOrder(orderData: any, orderItemsData: any[]): Promise<any> {
return await db.transaction(async (tx) => {
const [order] = await tx.insert(orders).values(orderData).returning();
for (const item of orderItemsData) {
await tx.insert(orderItems).values({
...item,
orderId: order.id,
});
}
await tx.insert(orderStatus).values({
orderId: order.id,
paymentStatus: 'pending',
});
return order;
});
}

View file

@ -0,0 +1,22 @@
import { db } from '../db/db_index';
import { payments, orders, orderStatus } from '../db/schema';
import { eq } from 'drizzle-orm';
export async function createPayment(paymentData: any): Promise<any> {
const [payment] = await db.insert(payments).values(paymentData).returning();
return payment;
}
export async function updatePaymentStatus(paymentId: number, status: string): Promise<any> {
const [payment] = await db.update(payments)
.set({ paymentStatus: status })
.where(eq(payments.id, paymentId))
.returning();
return payment;
}
export async function getPaymentByOrderId(orderId: number): Promise<any | null> {
return await db.query.payments.findFirst({
where: eq(payments.orderId, orderId),
});
}

View file

@ -0,0 +1,41 @@
import { db } from '../db/db_index';
import { productInfo, productReviews } from '../db/schema';
import { eq, desc } from 'drizzle-orm';
export async function getAllProducts(): Promise<any[]> {
return await db.query.productInfo.findMany({
with: {
unit: true,
store: true,
specialDeals: true,
},
orderBy: productInfo.name,
});
}
export async function getProductById(id: number): Promise<any | null> {
return await db.query.productInfo.findFirst({
where: eq(productInfo.id, id),
with: {
unit: true,
store: true,
specialDeals: true,
productReviews: {
with: {
user: true,
},
orderBy: desc(productReviews.createdAt),
},
},
});
}
export async function createProductReview(userId: number, productId: number, rating: number, comment?: string): Promise<any> {
const [review] = await db.insert(productReviews).values({
userId,
productId,
rating,
comment,
}).returning();
return review;
}

View file

@ -0,0 +1,17 @@
import { db } from '../db/db_index';
import { deliverySlotInfo } from '../db/schema';
import { eq, gte, and } from 'drizzle-orm';
export async function getAvailableSlots(): Promise<any[]> {
const now = new Date();
return await db.query.deliverySlotInfo.findMany({
where: gte(deliverySlotInfo.freezeTime, now),
orderBy: deliverySlotInfo.deliveryTime,
});
}
export async function getSlotById(id: number): Promise<any | null> {
return await db.query.deliverySlotInfo.findFirst({
where: eq(deliverySlotInfo.id, id),
});
}

View file

@ -0,0 +1,20 @@
import { db } from '../db/db_index';
import { storeInfo } from '../db/schema';
import { eq } from 'drizzle-orm';
export async function getAllStores(): Promise<any[]> {
return await db.query.storeInfo.findMany({
with: {
owner: true,
},
});
}
export async function getStoreById(id: number): Promise<any | null> {
return await db.query.storeInfo.findFirst({
where: eq(storeInfo.id, id),
with: {
owner: true,
},
});
}

View file

@ -0,0 +1,28 @@
import { db } from '../db/db_index';
import { productTags } from '../db/schema';
import { eq } from 'drizzle-orm';
export async function getAllTags(): Promise<any[]> {
return await db.query.productTags.findMany({
with: {
products: {
with: {
product: true,
},
},
},
});
}
export async function getTagById(id: number): Promise<any | null> {
return await db.query.productTags.findFirst({
where: eq(productTags.id, id),
with: {
products: {
with: {
product: true,
},
},
},
});
}

View file

@ -0,0 +1,44 @@
import { db } from '../db/db_index';
import { users, userDetails, addresses } from '../db/schema';
import { eq, desc } from 'drizzle-orm';
export async function getCurrentUser(userId: number): Promise<any | null> {
return await db.query.users.findFirst({
where: eq(users.id, userId),
with: {
userDetails: true,
},
});
}
export async function updateUser(userId: number, updates: any): Promise<any> {
const [user] = await db.update(users)
.set(updates)
.where(eq(users.id, userId))
.returning();
return user;
}
export async function getUserAddresses(userId: number): Promise<any[]> {
return await db.query.addresses.findMany({
where: eq(addresses.userId, userId),
orderBy: desc(addresses.isDefault),
});
}
export async function createAddress(addressData: any): Promise<any> {
const [address] = await db.insert(addresses).values(addressData).returning();
return address;
}
export async function updateAddress(addressId: number, updates: any): Promise<any> {
const [address] = await db.update(addresses)
.set(updates)
.where(eq(addresses.id, addressId))
.returning();
return address;
}
export async function deleteAddress(addressId: number): Promise<void> {
await db.delete(addresses).where(eq(addresses.id, addressId));
}

View file

@ -0,0 +1,349 @@
// Admin-related types
export interface Banner {
id: number;
name: string;
imageUrl: string;
description: string | null;
productIds: number[] | null;
redirectUrl: string | null;
serialNum: number | null;
isActive: boolean;
createdAt: Date;
lastUpdated: Date;
}
export interface Complaint {
id: number;
complaintBody: string;
userId: number;
orderId: number | null;
isResolved: boolean;
response: string | null;
createdAt: Date;
images: string[] | null;
}
export interface ComplaintWithUser extends Complaint {
userName: string | null;
userMobile: string | null;
}
export interface Constant {
key: string;
value: any;
}
export interface ConstantUpdateResult {
success: boolean;
updatedCount: number;
keys: string[];
}
export interface Coupon {
id: number;
couponCode: string;
isUserBased: boolean;
discountPercent: string | null;
flatDiscount: string | null;
minOrder: string | null;
productIds: number[] | null;
maxValue: string | null;
isApplyForAll: boolean;
validTill: Date | null;
maxLimitForUser: number | null;
exclusiveApply: boolean;
isInvalidated: boolean;
createdAt: Date;
createdBy: number;
}
export interface CouponValidationResult {
valid: boolean;
message?: string;
discountAmount?: number;
coupon?: Partial<Coupon>;
}
export interface UserMiniInfo {
id: number;
name: string;
mobile: string | null;
}
export interface Store {
id: number;
name: string;
description: string | null;
imageUrl: string | null;
owner: number;
createdAt: Date;
updatedAt: Date;
}
export interface StaffUser {
id: number;
name: string;
password: string;
staffRoleId: number;
createdAt: Date;
}
export interface StaffRole {
id: number;
roleName: string;
}
export interface AdminOrderRow {
id: number;
userId: number;
addressId: number;
slotId: number | null;
isCod: boolean;
isOnlinePayment: boolean;
paymentInfoId: number | null;
totalAmount: string;
deliveryCharge: string;
readableId: number;
adminNotes: string | null;
userNotes: string | null;
orderGroupId: string | null;
orderGroupProportion: string | null;
isFlashDelivery: boolean;
createdAt: Date;
}
export type PaymentStatus = 'pending' | 'success' | 'cod' | 'failed'
export type RefundStatus = 'success' | 'pending' | 'failed' | 'none' | 'na' | 'processed'
export interface AdminOrderStatusRecord {
id: number;
orderTime: Date;
userId: number;
orderId: number;
isPackaged: boolean;
isDelivered: boolean;
isCancelled: boolean;
cancelReason: string | null;
isCancelledByAdmin: boolean | null;
paymentStatus: PaymentStatus;
cancellationUserNotes: string | null;
cancellationAdminNotes: string | null;
cancellationReviewed: boolean;
cancellationReviewedAt: Date | null;
refundCouponId: number | null;
}
export interface AdminRefundRecord {
id: number;
orderId: number;
refundAmount: string | null;
refundStatus: RefundStatus | null;
merchantRefundId: string | null;
refundProcessedAt: Date | null;
createdAt: Date;
}
export interface AdminOrderDetailsAddress {
name: string;
line1: string;
line2: string | null;
city: string;
state: string;
pincode: string;
phone: string;
}
export interface AdminOrderDetailsSlotInfo {
time: string;
sequence: unknown;
}
export interface AdminOrderDetailsItem {
id: number;
name: string;
quantity: string;
productSize: number;
price: string;
unit?: string;
amount: number;
isPackaged: boolean;
isPackageVerified: boolean;
}
export interface AdminOrderDetailsPayment {
status: string;
gateway: string;
merchantOrderId: string;
}
export interface AdminOrderDetailsCouponData {
couponCode: string;
couponDescription: string;
discountAmount: number;
}
export interface AdminOrderDetails {
id: number;
readableId: number;
userId: number;
customerName: string;
customerEmail: string | null;
customerMobile: string | null;
address: AdminOrderDetailsAddress;
slotInfo: AdminOrderDetailsSlotInfo | null;
isCod: boolean;
isOnlinePayment: boolean;
totalAmount: number;
deliveryCharge: number;
adminNotes: string | null;
userNotes: string | null;
createdAt: Date;
status: 'pending' | 'delivered' | 'cancelled';
isPackaged: boolean;
isDelivered: boolean;
items: AdminOrderDetailsItem[];
payment: AdminOrderDetailsPayment | null;
paymentInfo: AdminOrderDetailsPayment | null;
cancelReason: string | null;
cancellationReviewed: boolean;
isRefundDone: boolean;
refundStatus: RefundStatus | null;
refundAmount: number | null;
couponData: AdminOrderDetailsCouponData | null;
couponCode: string | null;
couponDescription: string | null;
discountAmount: number | null;
orderStatus: AdminOrderStatusRecord | null;
refundRecord: AdminRefundRecord | null;
isFlashDelivery: boolean;
}
export interface AdminOrderUpdateResult {
success: boolean;
userId: number | null;
}
export interface AdminOrderItemPackagingResult {
success: boolean;
updated: boolean;
}
export interface AdminOrderMessageResult {
success: boolean;
message: string;
}
export interface AdminOrderBasicResult {
success: boolean;
}
export interface AdminSlotOrderItem {
id: number;
name: string;
quantity: number;
price: number;
amount: number;
unit: string;
isPackaged: boolean;
isPackageVerified: boolean;
}
export interface AdminSlotOrder {
id: number;
readableId: number;
customerName: string;
address: string;
addressId: number;
latitude: number | null;
longitude: number | null;
totalAmount: number;
items: AdminSlotOrderItem[];
deliveryTime: string | null;
status: 'pending' | 'delivered' | 'cancelled';
isPackaged: boolean;
isDelivered: boolean;
isCod: boolean;
paymentMode: 'COD' | 'Online';
paymentStatus: PaymentStatus;
slotId: number | null;
adminNotes: string | null;
userNotes: string | null;
}
export interface AdminGetSlotOrdersResult {
success: boolean;
data: AdminSlotOrder[];
}
export interface AdminOrderListItemProduct {
id: number;
name: string;
quantity: number;
price: number;
amount: number;
unit: string;
productSize: number;
isPackaged: boolean;
isPackageVerified: boolean;
}
export interface AdminOrderListItem {
id: number;
orderId: string;
readableId: number;
customerName: string;
customerMobile: string | null;
address: string;
addressId: number;
latitude: number | null;
longitude: number | null;
totalAmount: number;
deliveryCharge: number;
items: AdminOrderListItemProduct[];
createdAt: Date;
deliveryTime: string | null;
status: 'pending' | 'delivered' | 'cancelled';
isPackaged: boolean;
isDelivered: boolean;
isCod: boolean;
isFlashDelivery: boolean;
userNotes: string | null;
adminNotes: string | null;
userNegativityScore: number;
}
export interface AdminOrderListItemWithUserId extends AdminOrderListItem {
userId: number;
}
export interface AdminGetAllOrdersResult {
orders: AdminOrderListItem[];
nextCursor?: number;
}
export interface AdminGetAllOrdersResultWithUserId {
orders: AdminOrderListItemWithUserId[];
nextCursor?: number;
}
export interface AdminRebalanceSlotsResult {
success: boolean;
updatedOrders: number[];
message: string;
}
export type AdminCancelOrderError =
| 'order_not_found'
| 'status_not_found'
| 'already_cancelled'
| 'already_delivered'
export interface AdminCancelOrderResult {
success: boolean;
message: string;
orderId?: number;
userId?: number;
error?: AdminCancelOrderError;
}

View file

@ -1,9 +1,5 @@
// Central types export file
// Re-export all types from the types folder
export type { Banner } from './banner.types';
export type { Complaint, ComplaintWithUser } from './complaint.types';
export type { Constant, ConstantUpdateResult } from './const.types';
export type { Coupon, ReservedCoupon, CouponValidationResult, UserMiniInfo } from './coupon.types';
export type { Store } from './store.types';
export type { StaffUser, StaffRole } from './staff-user.types';
export type * from './admin';
export type * from './user';

View file

@ -0,0 +1,82 @@
// User-related types
export interface User {
id: number;
name: string | null;
email: string | null;
mobile: string;
createdAt: Date;
}
export interface UserDetails {
id: number;
userId: number;
isSuspended: boolean;
profileImage: string | null;
}
export interface Address {
id: number;
userId: number;
addressLine1: string;
addressLine2: string | null;
city: string;
state: string;
pincode: string;
lat: number | null;
lng: number | null;
isDefault: boolean;
}
export interface Product {
id: number;
name: string;
description: string | null;
price: string;
productQuantity: string;
images: string[] | null;
isOutOfStock: boolean;
storeId: number | null;
unitId: number;
}
export interface CartItem {
id: number;
userId: number;
productId: number;
quantity: string;
}
export interface Order {
id: number;
userId: number;
addressId: number;
slotId: number | null;
totalAmount: string;
deliveryCharge: string;
isFlashDelivery: boolean;
adminNotes: string | null;
isPackaged: boolean;
isDelivered: boolean;
isCancelled: boolean;
createdAt: Date;
}
export interface OrderItem {
id: number;
orderId: number;
productId: number;
quantity: string;
price: string;
is_packaged: boolean;
is_package_verified: boolean;
}
export interface Payment {
id: number;
orderId: number;
paymentStatus: string;
paymentMethod: string;
amount: string;
createdAt: Date;
}