Compare commits
No commits in common. "038733c14a4ee0fb3c1afde88a8706f6b1a9bc3b" and "97812fa4c542cec508d58ea178756fcdb80f4d80" have entirely different histories.
038733c14a
...
97812fa4c5
24 changed files with 910 additions and 2784 deletions
|
|
@ -9,132 +9,7 @@ export { db } from 'postgresService';
|
||||||
export * from 'postgresService';
|
export * from 'postgresService';
|
||||||
|
|
||||||
// Re-export methods from postgresService (implementation lives there)
|
// Re-export methods from postgresService (implementation lives there)
|
||||||
export {
|
export { getBanners, getBannerById, createBanner, updateBanner, deleteBanner } from 'postgresService';
|
||||||
// Banner methods
|
|
||||||
getBanners,
|
|
||||||
getBannerById,
|
|
||||||
createBanner,
|
|
||||||
updateBanner,
|
|
||||||
deleteBanner,
|
|
||||||
// Complaint methods
|
|
||||||
getComplaints,
|
|
||||||
resolveComplaint,
|
|
||||||
// Constants methods
|
|
||||||
getAllConstants,
|
|
||||||
upsertConstants,
|
|
||||||
// Coupon methods (batch 1 - non-transaction)
|
|
||||||
getAllCoupons,
|
|
||||||
getCouponById,
|
|
||||||
invalidateCoupon,
|
|
||||||
validateCoupon,
|
|
||||||
getReservedCoupons,
|
|
||||||
getUsersForCoupon,
|
|
||||||
// Coupon methods (batch 2 - transactions)
|
|
||||||
createCouponWithRelations,
|
|
||||||
updateCouponWithRelations,
|
|
||||||
generateCancellationCoupon,
|
|
||||||
createReservedCouponWithProducts,
|
|
||||||
createCouponForUser,
|
|
||||||
checkUsersExist,
|
|
||||||
checkCouponExists,
|
|
||||||
checkReservedCouponExists,
|
|
||||||
getOrderWithUser,
|
|
||||||
// Store methods
|
|
||||||
getAllStores,
|
|
||||||
getStoreById,
|
|
||||||
createStore,
|
|
||||||
updateStore,
|
|
||||||
deleteStore,
|
|
||||||
// Staff-user methods
|
|
||||||
getStaffUserByName,
|
|
||||||
getAllStaff,
|
|
||||||
getStaffByName,
|
|
||||||
getAllUsers,
|
|
||||||
getUserWithDetails,
|
|
||||||
updateUserSuspension,
|
|
||||||
checkStaffUserExists,
|
|
||||||
checkStaffRoleExists,
|
|
||||||
createStaffUser,
|
|
||||||
getAllRoles,
|
|
||||||
// User methods
|
|
||||||
createUserByMobile,
|
|
||||||
getUserByMobile,
|
|
||||||
getUnresolvedComplaintsCount,
|
|
||||||
getAllUsersWithFilters,
|
|
||||||
getOrderCountsByUserIds,
|
|
||||||
getLastOrdersByUserIds,
|
|
||||||
getSuspensionStatusesByUserIds,
|
|
||||||
getUserBasicInfo,
|
|
||||||
getUserSuspensionStatus,
|
|
||||||
getUserOrders,
|
|
||||||
getOrderStatusesByOrderIds,
|
|
||||||
getItemCountsByOrderIds,
|
|
||||||
upsertUserSuspension,
|
|
||||||
searchUsers,
|
|
||||||
getAllNotifCreds,
|
|
||||||
getAllUnloggedTokens,
|
|
||||||
getNotifTokensByUserIds,
|
|
||||||
getUserIncidentsWithRelations,
|
|
||||||
createUserIncident,
|
|
||||||
// Vendor-snippets methods
|
|
||||||
checkVendorSnippetExists,
|
|
||||||
getVendorSnippetById,
|
|
||||||
getVendorSnippetByCode,
|
|
||||||
getAllVendorSnippets,
|
|
||||||
createVendorSnippet,
|
|
||||||
updateVendorSnippet,
|
|
||||||
deleteVendorSnippet,
|
|
||||||
getProductsByIds,
|
|
||||||
getVendorSlotById,
|
|
||||||
getVendorOrdersBySlotId,
|
|
||||||
getOrderItemsByOrderIds,
|
|
||||||
getOrderStatusByOrderIds,
|
|
||||||
updateVendorOrderItemPackaging,
|
|
||||||
// Product methods
|
|
||||||
getAllProducts,
|
|
||||||
getProductById,
|
|
||||||
createProduct,
|
|
||||||
updateProduct,
|
|
||||||
toggleProductOutOfStock,
|
|
||||||
getAllUnits,
|
|
||||||
getAllProductTags,
|
|
||||||
getProductReviews,
|
|
||||||
respondToReview,
|
|
||||||
getAllProductGroups,
|
|
||||||
createProductGroup,
|
|
||||||
updateProductGroup,
|
|
||||||
deleteProductGroup,
|
|
||||||
addProductToGroup,
|
|
||||||
removeProductFromGroup,
|
|
||||||
// Slots methods
|
|
||||||
getAllSlots,
|
|
||||||
getSlotById,
|
|
||||||
createSlot,
|
|
||||||
updateSlot,
|
|
||||||
deleteSlot,
|
|
||||||
getSlotProducts,
|
|
||||||
addProductToSlot,
|
|
||||||
removeProductFromSlot,
|
|
||||||
clearSlotProducts,
|
|
||||||
updateSlotCapacity,
|
|
||||||
getSlotDeliverySequence,
|
|
||||||
updateSlotDeliverySequence,
|
|
||||||
// Order methods
|
|
||||||
updateOrderNotes,
|
|
||||||
getOrderWithDetails,
|
|
||||||
getFullOrder,
|
|
||||||
getOrderDetails,
|
|
||||||
getAllOrders,
|
|
||||||
getOrdersBySlotId,
|
|
||||||
updateOrderPackaged,
|
|
||||||
updateOrderDelivered,
|
|
||||||
updateOrderItemPackaging,
|
|
||||||
updateAddressCoords,
|
|
||||||
getOrderStatus,
|
|
||||||
cancelOrder,
|
|
||||||
getTodaysOrders,
|
|
||||||
removeDeliveryCharge,
|
|
||||||
} from 'postgresService';
|
|
||||||
|
|
||||||
// Re-export types from local types file (to avoid circular dependencies)
|
// Re-export types from local types file (to avoid circular dependencies)
|
||||||
export type { Banner } from './types/db.types';
|
export type { Banner } from './types/db.types';
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { db } from '@/src/db/db_index'
|
||||||
|
import { complaints, users } from '@/src/db/schema'
|
||||||
|
import { eq, desc, lt, and } from 'drizzle-orm';
|
||||||
import { generateSignedUrlsFromS3Urls } from '@/src/lib/s3-client'
|
import { generateSignedUrlsFromS3Urls } from '@/src/lib/s3-client'
|
||||||
import { getComplaints as getComplaintsFromDb, resolveComplaint as resolveComplaintInDb } from '@/src/dbService'
|
|
||||||
import type { ComplaintWithUser } from '@packages/shared'
|
|
||||||
|
|
||||||
export const complaintRouter = router({
|
export const complaintRouter = router({
|
||||||
getAll: protectedProcedure
|
getAll: protectedProcedure
|
||||||
|
|
@ -10,27 +11,7 @@ export const complaintRouter = router({
|
||||||
cursor: z.number().optional(),
|
cursor: z.number().optional(),
|
||||||
limit: z.number().default(20),
|
limit: z.number().default(20),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input }): Promise<{
|
.query(async ({ input }) => {
|
||||||
complaints: Array<{
|
|
||||||
id: number;
|
|
||||||
text: string;
|
|
||||||
userId: number;
|
|
||||||
userName: string | null;
|
|
||||||
userMobile: string | null;
|
|
||||||
orderId: number | null;
|
|
||||||
status: string;
|
|
||||||
createdAt: Date;
|
|
||||||
images: string[];
|
|
||||||
}>;
|
|
||||||
nextCursor?: number;
|
|
||||||
}> => {
|
|
||||||
const { cursor, limit } = input;
|
|
||||||
|
|
||||||
// Using dbService helper (new implementation)
|
|
||||||
const { complaints: complaintsData, hasMore } = await getComplaintsFromDb(cursor, limit);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query:
|
|
||||||
const { cursor, limit } = input;
|
const { cursor, limit } = input;
|
||||||
|
|
||||||
let whereCondition = cursor
|
let whereCondition = cursor
|
||||||
|
|
@ -56,13 +37,10 @@ export const complaintRouter = router({
|
||||||
.limit(limit + 1);
|
.limit(limit + 1);
|
||||||
|
|
||||||
const hasMore = complaintsData.length > limit;
|
const hasMore = complaintsData.length > limit;
|
||||||
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
|
||||||
*/
|
|
||||||
|
|
||||||
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
||||||
|
|
||||||
const complaintsWithSignedImages = await Promise.all(
|
const complaintsWithSignedImages = await Promise.all(
|
||||||
complaintsToReturn.map(async (c: ComplaintWithUser) => {
|
complaintsToReturn.map(async (c) => {
|
||||||
const signedImages = c.images
|
const signedImages = c.images
|
||||||
? await generateSignedUrlsFromS3Urls(c.images as string[])
|
? await generateSignedUrlsFromS3Urls(c.images as string[])
|
||||||
: [];
|
: [];
|
||||||
|
|
@ -91,17 +69,11 @@ export const complaintRouter = router({
|
||||||
|
|
||||||
resolve: protectedProcedure
|
resolve: protectedProcedure
|
||||||
.input(z.object({ id: z.string(), response: z.string().optional() }))
|
.input(z.object({ id: z.string(), response: z.string().optional() }))
|
||||||
.mutation(async ({ input }): Promise<{ message: string }> => {
|
.mutation(async ({ input }) => {
|
||||||
// Using dbService helper (new implementation)
|
|
||||||
await resolveComplaintInDb(parseInt(input.id), input.response);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query:
|
|
||||||
await db
|
await db
|
||||||
.update(complaints)
|
.update(complaints)
|
||||||
.set({ isResolved: true, response: input.response })
|
.set({ isResolved: true, response: input.response })
|
||||||
.where(eq(complaints.id, parseInt(input.id)));
|
.where(eq(complaints.id, parseInt(input.id)));
|
||||||
*/
|
|
||||||
|
|
||||||
return { message: 'Complaint resolved successfully' };
|
return { message: 'Complaint resolved successfully' };
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,22 @@
|
||||||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { db } from '@/src/db/db_index'
|
||||||
|
import { keyValStore } from '@/src/db/schema'
|
||||||
import { computeConstants } from '@/src/lib/const-store'
|
import { computeConstants } from '@/src/lib/const-store'
|
||||||
import { CONST_KEYS } from '@/src/lib/const-keys'
|
import { CONST_KEYS } from '@/src/lib/const-keys'
|
||||||
import { getAllConstants as getAllConstantsFromDb, upsertConstants as upsertConstantsInDb } from '@/src/dbService'
|
|
||||||
import type { Constant, ConstantUpdateResult } from '@packages/shared'
|
|
||||||
|
|
||||||
export const constRouter = router({
|
export const constRouter = router({
|
||||||
getConstants: protectedProcedure
|
getConstants: protectedProcedure
|
||||||
.query(async (): Promise<Constant[]> => {
|
.query(async () => {
|
||||||
// Using dbService helper (new implementation)
|
|
||||||
const constants = await getAllConstantsFromDb();
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query:
|
|
||||||
const constants = await db.select().from(keyValStore);
|
const constants = await db.select().from(keyValStore);
|
||||||
|
|
||||||
const resp = constants.map(c => ({
|
const resp = constants.map(c => ({
|
||||||
key: c.key,
|
key: c.key,
|
||||||
value: c.value,
|
value: c.value,
|
||||||
}));
|
}));
|
||||||
*/
|
|
||||||
|
|
||||||
return constants;
|
return resp;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateConstants: protectedProcedure
|
updateConstants: protectedProcedure
|
||||||
|
|
@ -31,7 +26,7 @@ export const constRouter = router({
|
||||||
value: z.any(),
|
value: z.any(),
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input }): Promise<ConstantUpdateResult> => {
|
.mutation(async ({ input }) => {
|
||||||
const { constants } = input;
|
const { constants } = input;
|
||||||
|
|
||||||
const validKeys = Object.values(CONST_KEYS) as string[];
|
const validKeys = Object.values(CONST_KEYS) as string[];
|
||||||
|
|
@ -43,11 +38,6 @@ export const constRouter = router({
|
||||||
throw new Error(`Invalid constant keys: ${invalidKeys.join(', ')}`);
|
throw new Error(`Invalid constant keys: ${invalidKeys.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using dbService helper (new implementation)
|
|
||||||
await upsertConstantsInDb(constants);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query:
|
|
||||||
await db.transaction(async (tx) => {
|
await db.transaction(async (tx) => {
|
||||||
for (const { key, value } of constants) {
|
for (const { key, value } of constants) {
|
||||||
await tx.insert(keyValStore)
|
await tx.insert(keyValStore)
|
||||||
|
|
@ -58,7 +48,6 @@ export const constRouter = router({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
// Refresh all constants in Redis after database update
|
// Refresh all constants in Redis after database update
|
||||||
await computeConstants();
|
await computeConstants();
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,9 @@
|
||||||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { db } from '@/src/db/db_index'
|
||||||
|
import { coupons, users, staffUsers, orders, couponApplicableUsers, couponApplicableProducts, orderStatus, reservedCoupons } from '@/src/db/schema'
|
||||||
|
import { eq, and, like, or, inArray, lt } from 'drizzle-orm';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import {
|
|
||||||
// Batch 1 - Non-transaction methods
|
|
||||||
getAllCoupons as getAllCouponsFromDb,
|
|
||||||
getCouponById as getCouponByIdFromDb,
|
|
||||||
invalidateCoupon as invalidateCouponInDb,
|
|
||||||
validateCoupon as validateCouponInDb,
|
|
||||||
getReservedCoupons as getReservedCouponsFromDb,
|
|
||||||
getUsersForCoupon as getUsersForCouponFromDb,
|
|
||||||
// Batch 2 - Transaction methods
|
|
||||||
createCouponWithRelations,
|
|
||||||
updateCouponWithRelations,
|
|
||||||
generateCancellationCoupon,
|
|
||||||
createReservedCouponWithProducts,
|
|
||||||
createCouponForUser,
|
|
||||||
checkUsersExist,
|
|
||||||
checkCouponExists,
|
|
||||||
checkReservedCouponExists,
|
|
||||||
getOrderWithUser,
|
|
||||||
} from '@/src/dbService'
|
|
||||||
import type { Coupon, CouponValidationResult, UserMiniInfo } from '@packages/shared'
|
|
||||||
|
|
||||||
const createCouponBodySchema = z.object({
|
const createCouponBodySchema = z.object({
|
||||||
couponCode: z.string().optional(),
|
couponCode: z.string().optional(),
|
||||||
|
|
@ -48,7 +31,7 @@ const validateCouponBodySchema = z.object({
|
||||||
export const couponRouter = router({
|
export const couponRouter = router({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
.input(createCouponBodySchema)
|
.input(createCouponBodySchema)
|
||||||
.mutation(async ({ input, ctx }): Promise<Coupon> => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;
|
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||||
|
|
||||||
// Validation: ensure at least one discount type is provided
|
// Validation: ensure at least one discount type is provided
|
||||||
|
|
@ -66,6 +49,17 @@ export const couponRouter = router({
|
||||||
throw new Error("Cannot be both user-based and apply for all users");
|
throw new Error("Cannot be both user-based and apply for all users");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If applicableUsers is provided, verify users exist
|
||||||
|
if (applicableUsers && applicableUsers.length > 0) {
|
||||||
|
const existingUsers = await db.query.users.findMany({
|
||||||
|
where: inArray(users.id, applicableUsers),
|
||||||
|
columns: { id: true },
|
||||||
|
});
|
||||||
|
if (existingUsers.length !== applicableUsers.length) {
|
||||||
|
throw new Error("Some applicable users not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get staff user ID from auth middleware
|
// Get staff user ID from auth middleware
|
||||||
const staffUserId = ctx.staffUser?.id;
|
const staffUserId = ctx.staffUser?.id;
|
||||||
if (!staffUserId) {
|
if (!staffUserId) {
|
||||||
|
|
@ -75,46 +69,21 @@ export const couponRouter = router({
|
||||||
// Generate coupon code if not provided
|
// Generate coupon code if not provided
|
||||||
let finalCouponCode = couponCode;
|
let finalCouponCode = couponCode;
|
||||||
if (!finalCouponCode) {
|
if (!finalCouponCode) {
|
||||||
|
// Generate a unique coupon code
|
||||||
const timestamp = Date.now().toString().slice(-6);
|
const timestamp = Date.now().toString().slice(-6);
|
||||||
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
|
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||||
finalCouponCode = `MF${timestamp}${random}`;
|
finalCouponCode = `MF${timestamp}${random}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using dbService helper (new implementation)
|
// Check if coupon code already exists
|
||||||
const codeExists = await checkCouponExists(finalCouponCode);
|
const existingCoupon = await db.query.coupons.findFirst({
|
||||||
if (codeExists) {
|
where: eq(coupons.couponCode, finalCouponCode),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingCoupon) {
|
||||||
throw new Error("Coupon code already exists");
|
throw new Error("Coupon code already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If applicableUsers is provided, verify users exist
|
|
||||||
if (applicableUsers && applicableUsers.length > 0) {
|
|
||||||
const usersExist = await checkUsersExist(applicableUsers);
|
|
||||||
if (!usersExist) {
|
|
||||||
throw new Error("Some applicable users not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const coupon = await createCouponWithRelations(
|
|
||||||
{
|
|
||||||
couponCode: finalCouponCode,
|
|
||||||
isUserBased: isUserBased || false,
|
|
||||||
discountPercent: discountPercent?.toString(),
|
|
||||||
flatDiscount: flatDiscount?.toString(),
|
|
||||||
minOrder: minOrder?.toString(),
|
|
||||||
productIds: productIds || null,
|
|
||||||
createdBy: staffUserId,
|
|
||||||
maxValue: maxValue?.toString(),
|
|
||||||
isApplyForAll: isApplyForAll || false,
|
|
||||||
validTill: validTill ? dayjs(validTill).toDate() : undefined,
|
|
||||||
maxLimitForUser,
|
|
||||||
exclusiveApply: exclusiveApply || false,
|
|
||||||
},
|
|
||||||
applicableUsers,
|
|
||||||
applicableProducts
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query with transaction:
|
|
||||||
const result = await db.insert(coupons).values({
|
const result = await db.insert(coupons).values({
|
||||||
couponCode: finalCouponCode,
|
couponCode: finalCouponCode,
|
||||||
isUserBased: isUserBased || false,
|
isUserBased: isUserBased || false,
|
||||||
|
|
@ -126,7 +95,7 @@ export const couponRouter = router({
|
||||||
maxValue: maxValue?.toString(),
|
maxValue: maxValue?.toString(),
|
||||||
isApplyForAll: isApplyForAll || false,
|
isApplyForAll: isApplyForAll || false,
|
||||||
validTill: validTill ? dayjs(validTill).toDate() : undefined,
|
validTill: validTill ? dayjs(validTill).toDate() : undefined,
|
||||||
maxLimitForUser,
|
maxLimitForUser: maxLimitForUser,
|
||||||
exclusiveApply: exclusiveApply || false,
|
exclusiveApply: exclusiveApply || false,
|
||||||
}).returning();
|
}).returning();
|
||||||
|
|
||||||
|
|
@ -151,7 +120,6 @@ export const couponRouter = router({
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
return coupon;
|
return coupon;
|
||||||
}),
|
}),
|
||||||
|
|
@ -162,22 +130,71 @@ export const couponRouter = router({
|
||||||
limit: z.number().default(50),
|
limit: z.number().default(50),
|
||||||
search: z.string().optional(),
|
search: z.string().optional(),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input }): Promise<{ coupons: any[]; nextCursor?: number }> => {
|
.query(async ({ input }) => {
|
||||||
const { cursor, limit, search } = input;
|
const { cursor, limit, search } = input;
|
||||||
|
|
||||||
const { coupons: couponsList, hasMore } = await getAllCouponsFromDb(cursor, limit, search);
|
let whereCondition = undefined;
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
const nextCursor = hasMore ? couponsList[couponsList.length - 1].id : undefined;
|
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;
|
||||||
|
const nextCursor = hasMore ? result[result.length - 1].id : undefined;
|
||||||
|
|
||||||
return { coupons: couponsList, nextCursor };
|
return { coupons: couponsList, nextCursor };
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getById: protectedProcedure
|
getById: protectedProcedure
|
||||||
.input(z.object({ id: z.number() }))
|
.input(z.object({ id: z.number() }))
|
||||||
.query(async ({ input }): Promise<any> => {
|
.query(async ({ input }) => {
|
||||||
const couponId = input.id;
|
const couponId = input.id;
|
||||||
|
|
||||||
const result = await getCouponByIdFromDb(couponId);
|
const result = await db.query.coupons.findFirst({
|
||||||
|
where: eq(coupons.id, couponId),
|
||||||
|
with: {
|
||||||
|
creator: true,
|
||||||
|
applicableUsers: {
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
applicableProducts: {
|
||||||
|
with: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new Error("Coupon not found");
|
throw new Error("Coupon not found");
|
||||||
|
|
@ -186,8 +203,8 @@ export const couponRouter = router({
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
productIds: (result.productIds as number[]) || undefined,
|
productIds: (result.productIds as number[]) || undefined,
|
||||||
applicableUsers: result.applicableUsers.map((au: any) => au.user),
|
applicableUsers: result.applicableUsers.map(au => au.user),
|
||||||
applicableProducts: result.applicableProducts.map((ap: any) => ap.product),
|
applicableProducts: result.applicableProducts.map(ap => ap.product),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
@ -198,7 +215,7 @@ export const couponRouter = router({
|
||||||
isInvalidated: z.boolean().optional(),
|
isInvalidated: z.boolean().optional(),
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input }): Promise<Coupon> => {
|
.mutation(async ({ input }) => {
|
||||||
const { id, updates } = input;
|
const { id, updates } = input;
|
||||||
|
|
||||||
// Validation: ensure discount types are valid
|
// Validation: ensure discount types are valid
|
||||||
|
|
@ -208,31 +225,43 @@ export const couponRouter = router({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare update data
|
// If updating to user-based, applicableUsers is required
|
||||||
const updateData: any = {};
|
if (updates.isUserBased && (!updates.applicableUsers || updates.applicableUsers.length === 0)) {
|
||||||
if (updates.couponCode !== undefined) updateData.couponCode = updates.couponCode;
|
const existingCount = await db.$count(couponApplicableUsers, eq(couponApplicableUsers.couponId, id));
|
||||||
if (updates.isUserBased !== undefined) updateData.isUserBased = updates.isUserBased;
|
if (existingCount === 0) {
|
||||||
if (updates.discountPercent !== undefined) updateData.discountPercent = updates.discountPercent?.toString();
|
throw new Error("applicableUsers is required for user-based coupons");
|
||||||
if (updates.flatDiscount !== undefined) updateData.flatDiscount = updates.flatDiscount?.toString();
|
}
|
||||||
if (updates.minOrder !== undefined) updateData.minOrder = updates.minOrder?.toString();
|
}
|
||||||
if (updates.maxValue !== undefined) updateData.maxValue = updates.maxValue?.toString();
|
|
||||||
if (updates.isApplyForAll !== undefined) updateData.isApplyForAll = updates.isApplyForAll;
|
|
||||||
if (updates.validTill !== undefined) updateData.validTill = updates.validTill ? dayjs(updates.validTill).toDate() : null;
|
|
||||||
if (updates.maxLimitForUser !== undefined) updateData.maxLimitForUser = updates.maxLimitForUser;
|
|
||||||
if (updates.exclusiveApply !== undefined) updateData.exclusiveApply = updates.exclusiveApply;
|
|
||||||
if (updates.isInvalidated !== undefined) updateData.isInvalidated = updates.isInvalidated;
|
|
||||||
if (updates.productIds !== undefined) updateData.productIds = updates.productIds;
|
|
||||||
|
|
||||||
// Using dbService helper (new implementation)
|
// If applicableUsers is provided, verify users exist
|
||||||
const coupon = await updateCouponWithRelations(
|
if (updates.applicableUsers && updates.applicableUsers.length > 0) {
|
||||||
id,
|
const existingUsers = await db.query.users.findMany({
|
||||||
updateData,
|
where: inArray(users.id, updates.applicableUsers),
|
||||||
updates.applicableUsers,
|
columns: { id: true },
|
||||||
updates.applicableProducts
|
});
|
||||||
);
|
if (existingUsers.length !== updates.applicableUsers.length) {
|
||||||
|
throw new Error("Some applicable users not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateData: any = { ...updates };
|
||||||
|
delete updateData.applicableUsers; // Remove since we use couponApplicableUsers table
|
||||||
|
if (updates.discountPercent !== undefined) {
|
||||||
|
updateData.discountPercent = updates.discountPercent?.toString();
|
||||||
|
}
|
||||||
|
if (updates.flatDiscount !== undefined) {
|
||||||
|
updateData.flatDiscount = updates.flatDiscount?.toString();
|
||||||
|
}
|
||||||
|
if (updates.minOrder !== undefined) {
|
||||||
|
updateData.minOrder = updates.minOrder?.toString();
|
||||||
|
}
|
||||||
|
if (updates.maxValue !== undefined) {
|
||||||
|
updateData.maxValue = updates.maxValue?.toString();
|
||||||
|
}
|
||||||
|
if (updates.validTill !== undefined) {
|
||||||
|
updateData.validTill = updates.validTill ? dayjs(updates.validTill).toDate() : null;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query:
|
|
||||||
const result = await db.update(coupons)
|
const result = await db.update(coupons)
|
||||||
.set(updateData)
|
.set(updateData)
|
||||||
.where(eq(coupons.id, id))
|
.where(eq(coupons.id, id))
|
||||||
|
|
@ -242,6 +271,8 @@ export const couponRouter = router({
|
||||||
throw new Error("Coupon not found");
|
throw new Error("Coupon not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('updated coupon successfully')
|
||||||
|
|
||||||
// Update applicable users: delete existing and insert new
|
// Update applicable users: delete existing and insert new
|
||||||
if (updates.applicableUsers !== undefined) {
|
if (updates.applicableUsers !== undefined) {
|
||||||
await db.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, id));
|
await db.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, id));
|
||||||
|
|
@ -267,33 +298,88 @@ export const couponRouter = router({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
return coupon;
|
return result[0];
|
||||||
}),
|
}),
|
||||||
|
|
||||||
delete: protectedProcedure
|
delete: protectedProcedure
|
||||||
.input(z.object({ id: z.number() }))
|
.input(z.object({ id: z.number() }))
|
||||||
.mutation(async ({ input }): Promise<{ message: string }> => {
|
.mutation(async ({ input }) => {
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
await invalidateCouponInDb(id);
|
const result = await db.update(coupons)
|
||||||
|
.set({ isInvalidated: true })
|
||||||
|
.where(eq(coupons.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
throw new Error("Coupon not found");
|
||||||
|
}
|
||||||
|
|
||||||
return { message: "Coupon invalidated successfully" };
|
return { message: "Coupon invalidated successfully" };
|
||||||
}),
|
}),
|
||||||
|
|
||||||
validate: protectedProcedure
|
validate: protectedProcedure
|
||||||
.input(validateCouponBodySchema)
|
.input(validateCouponBodySchema)
|
||||||
.query(async ({ input }): Promise<CouponValidationResult> => {
|
.query(async ({ input }) => {
|
||||||
const { code, userId, orderAmount } = input;
|
const { code, userId, orderAmount } = input;
|
||||||
|
|
||||||
if (!code || typeof code !== 'string') {
|
if (!code || typeof code !== 'string') {
|
||||||
return { valid: false, message: "Invalid coupon code" };
|
return { valid: false, message: "Invalid coupon code" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await validateCouponInDb(code, userId, orderAmount);
|
const coupon = await db.query.coupons.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(coupons.couponCode, code.toUpperCase()),
|
||||||
|
eq(coupons.isInvalidated, false)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
if (!coupon) {
|
||||||
|
return { valid: false, message: "Coupon not found or invalidated" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check expiry date
|
||||||
|
if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
|
||||||
|
return { valid: false, message: "Coupon has expired" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if coupon applies to all users or specific user
|
||||||
|
if (!coupon.isApplyForAll && !coupon.isUserBased) {
|
||||||
|
return { valid: false, message: "Coupon is not available for use" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check minimum order amount
|
||||||
|
const minOrderValue = coupon.minOrder ? parseFloat(coupon.minOrder) : 0;
|
||||||
|
if (minOrderValue > 0 && orderAmount < minOrderValue) {
|
||||||
|
return { valid: false, message: `Minimum order amount is ${minOrderValue}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate discount
|
||||||
|
let discountAmount = 0;
|
||||||
|
if (coupon.discountPercent) {
|
||||||
|
const percent = parseFloat(coupon.discountPercent);
|
||||||
|
discountAmount = (orderAmount * percent) / 100;
|
||||||
|
} else if (coupon.flatDiscount) {
|
||||||
|
discountAmount = parseFloat(coupon.flatDiscount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply max value limit
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
generateCancellationCoupon: protectedProcedure
|
generateCancellationCoupon: protectedProcedure
|
||||||
|
|
@ -302,7 +388,7 @@ export const couponRouter = router({
|
||||||
orderId: z.number(),
|
orderId: z.number(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }): Promise<Coupon> => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { orderId } = input;
|
const { orderId } = input;
|
||||||
|
|
||||||
// Get staff user ID from auth middleware
|
// Get staff user ID from auth middleware
|
||||||
|
|
@ -311,13 +397,31 @@ export const couponRouter = router({
|
||||||
throw new Error("Unauthorized");
|
throw new Error("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using dbService helper (new implementation)
|
// Find the order with user and order status information
|
||||||
const order = await getOrderWithUser(orderId);
|
const order = await db.query.orders.findFirst({
|
||||||
|
where: eq(orders.id, orderId),
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
orderStatus: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!order) {
|
if (!order) {
|
||||||
throw new Error("Order not found");
|
throw new Error("Order not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if order is cancelled (check if any status entry has isCancelled: true)
|
||||||
|
// const isOrderCancelled = order.orderStatus?.some(status => status.isCancelled) || false;
|
||||||
|
// if (!isOrderCancelled) {
|
||||||
|
// throw new Error("Order is not cancelled");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Check if payment method is COD
|
||||||
|
// if (order.isCod) {
|
||||||
|
// throw new Error("Can't generate refund coupon for CoD Order");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Verify user exists
|
||||||
if (!order.user) {
|
if (!order.user) {
|
||||||
throw new Error("User not found for this order");
|
throw new Error("User not found for this order");
|
||||||
}
|
}
|
||||||
|
|
@ -327,29 +431,23 @@ export const couponRouter = router({
|
||||||
const couponCode = `${userNamePrefix}${orderId}`;
|
const couponCode = `${userNamePrefix}${orderId}`;
|
||||||
|
|
||||||
// Check if coupon code already exists
|
// Check if coupon code already exists
|
||||||
const codeExists = await checkCouponExists(couponCode);
|
const existingCoupon = await db.query.coupons.findFirst({
|
||||||
if (codeExists) {
|
where: eq(coupons.couponCode, couponCode),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingCoupon) {
|
||||||
throw new Error("Coupon code already exists");
|
throw new Error("Coupon code already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get order total amount
|
// Get order total amount
|
||||||
const orderAmount = parseFloat(order.totalAmount);
|
const orderAmount = parseFloat(order.totalAmount);
|
||||||
|
|
||||||
const coupon = await generateCancellationCoupon(
|
|
||||||
orderId,
|
|
||||||
staffUserId,
|
|
||||||
order.userId,
|
|
||||||
orderAmount,
|
|
||||||
couponCode
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query with transaction:
|
|
||||||
const coupon = await db.transaction(async (tx) => {
|
|
||||||
// Calculate expiry date (30 days from now)
|
// Calculate expiry date (30 days from now)
|
||||||
const expiryDate = new Date();
|
const expiryDate = new Date();
|
||||||
expiryDate.setDate(expiryDate.getDate() + 30);
|
expiryDate.setDate(expiryDate.getDate() + 30);
|
||||||
|
|
||||||
|
// Create the coupon and update order status in a transaction
|
||||||
|
const coupon = await db.transaction(async (tx) => {
|
||||||
// Create the coupon
|
// Create the coupon
|
||||||
const result = await tx.insert(coupons).values({
|
const result = await tx.insert(coupons).values({
|
||||||
couponCode,
|
couponCode,
|
||||||
|
|
@ -378,7 +476,6 @@ export const couponRouter = router({
|
||||||
|
|
||||||
return coupon;
|
return coupon;
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
return coupon;
|
return coupon;
|
||||||
}),
|
}),
|
||||||
|
|
@ -389,66 +486,80 @@ export const couponRouter = router({
|
||||||
limit: z.number().default(50),
|
limit: z.number().default(50),
|
||||||
search: z.string().optional(),
|
search: z.string().optional(),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input }): Promise<{ coupons: any[]; nextCursor?: number }> => {
|
.query(async ({ input }) => {
|
||||||
const { cursor, limit, search } = input;
|
const { cursor, limit, search } = input;
|
||||||
|
|
||||||
const { coupons: result, hasMore } = await getReservedCouponsFromDb(cursor, limit, search);
|
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, // Fetch one extra to check if there's more
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMore = result.length > limit;
|
||||||
|
const coupons = hasMore ? result.slice(0, limit) : result;
|
||||||
const nextCursor = hasMore ? result[result.length - 1].id : undefined;
|
const nextCursor = hasMore ? result[result.length - 1].id : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
coupons: result,
|
coupons,
|
||||||
nextCursor,
|
nextCursor,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
createReservedCoupon: protectedProcedure
|
createReservedCoupon: protectedProcedure
|
||||||
.input(createCouponBodySchema)
|
.input(createCouponBodySchema)
|
||||||
.mutation(async ({ input, ctx }): Promise<any> => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableProducts, maxValue, validTill, maxLimitForUser, exclusiveApply } = input;
|
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||||
|
|
||||||
// Validation: ensure at least one discount type is provided
|
// Validation: ensure at least one discount type is provided
|
||||||
if ((!discountPercent && !flatDiscount) || (discountPercent && flatDiscount)) {
|
if ((!discountPercent && !flatDiscount) || (discountPercent && flatDiscount)) {
|
||||||
throw new Error("Either discountPercent or flatDiscount must be provided (but not both)");
|
throw new Error("Either discountPercent or flatDiscount must be provided (but not both)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For reserved coupons, applicableUsers is not used, as it's redeemed by one user
|
||||||
|
|
||||||
// Get staff user ID from auth middleware
|
// Get staff user ID from auth middleware
|
||||||
const staffUserId = ctx.staffUser?.id;
|
const staffUserId = ctx.staffUser?.id;
|
||||||
if (!staffUserId) {
|
if (!staffUserId) {
|
||||||
throw new Error("Unauthorized");
|
throw new Error("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate secret code if not provided
|
// Generate secret code if not provided (use couponCode as base)
|
||||||
let secretCode = couponCode || `SECRET${Date.now().toString().slice(-6)}${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
|
let secretCode = couponCode || `SECRET${Date.now().toString().slice(-6)}${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
|
||||||
|
|
||||||
// Using dbService helper (new implementation)
|
// Check if secret code already exists
|
||||||
const codeExists = await checkReservedCouponExists(secretCode);
|
const existing = await db.query.reservedCoupons.findFirst({
|
||||||
if (codeExists) {
|
where: eq(reservedCoupons.secretCode, secretCode),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
throw new Error("Secret code already exists");
|
throw new Error("Secret code already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
const coupon = await createReservedCouponWithProducts(
|
|
||||||
{
|
|
||||||
secretCode,
|
|
||||||
couponCode: couponCode || `RESERVED${Date.now().toString().slice(-6)}`,
|
|
||||||
discountPercent: discountPercent?.toString(),
|
|
||||||
flatDiscount: flatDiscount?.toString(),
|
|
||||||
minOrder: minOrder?.toString(),
|
|
||||||
productIds,
|
|
||||||
maxValue: maxValue?.toString(),
|
|
||||||
validTill: validTill ? dayjs(validTill).toDate() : undefined,
|
|
||||||
maxLimitForUser,
|
|
||||||
exclusiveApply: exclusiveApply || false,
|
|
||||||
createdBy: staffUserId,
|
|
||||||
},
|
|
||||||
applicableProducts
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query:
|
|
||||||
const result = await db.insert(reservedCoupons).values({
|
const result = await db.insert(reservedCoupons).values({
|
||||||
secretCode,
|
secretCode,
|
||||||
couponCode: couponCode || RESERVED${Date.now().toString().slice(-6)},
|
couponCode: couponCode || `RESERVED${Date.now().toString().slice(-6)}`,
|
||||||
discountPercent: discountPercent?.toString(),
|
discountPercent: discountPercent?.toString(),
|
||||||
flatDiscount: flatDiscount?.toString(),
|
flatDiscount: flatDiscount?.toString(),
|
||||||
minOrder: minOrder?.toString(),
|
minOrder: minOrder?.toString(),
|
||||||
|
|
@ -471,7 +582,6 @@ export const couponRouter = router({
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
return coupon;
|
return coupon;
|
||||||
}),
|
}),
|
||||||
|
|
@ -482,19 +592,43 @@ export const couponRouter = router({
|
||||||
limit: z.number().min(1).max(50).default(20),
|
limit: z.number().min(1).max(50).default(20),
|
||||||
offset: z.number().min(0).default(0),
|
offset: z.number().min(0).default(0),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input }): Promise<{ users: UserMiniInfo[] }> => {
|
.query(async ({ input }) => {
|
||||||
const { search, limit, offset } = input;
|
const { search, limit } = input;
|
||||||
|
|
||||||
const result = await getUsersForCouponFromDb(search, limit, offset);
|
let whereCondition = undefined;
|
||||||
|
if (search && search.trim()) {
|
||||||
|
whereCondition = or(
|
||||||
|
like(users.name, `%${search}%`),
|
||||||
|
like(users.mobile, `%${search}%`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
const userList = await db.query.users.findMany({
|
||||||
|
where: whereCondition,
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
mobile: true,
|
||||||
|
},
|
||||||
|
limit: limit,
|
||||||
|
offset: input.offset,
|
||||||
|
orderBy: (users, { asc }) => [asc(users.name)],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
users: userList.map(user => ({
|
||||||
|
id: user.id,
|
||||||
|
name: user.name || 'Unknown',
|
||||||
|
mobile: user.mobile,
|
||||||
|
}))
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
createCoupon: protectedProcedure
|
createCoupon: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
mobile: z.string().min(1, 'Mobile number is required'),
|
mobile: z.string().min(1, 'Mobile number is required'),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }): Promise<{ success: boolean; coupon: any }> => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { mobile } = input;
|
const { mobile } = input;
|
||||||
|
|
||||||
// Get staff user ID from auth middleware
|
// Get staff user ID from auth middleware
|
||||||
|
|
@ -511,27 +645,13 @@ export const couponRouter = router({
|
||||||
throw new Error("Mobile number must be exactly 10 digits");
|
throw new Error("Mobile number must be exactly 10 digits");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate unique coupon code
|
|
||||||
const timestamp = Date.now().toString().slice(-6);
|
|
||||||
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
||||||
const couponCode = `MF${cleanMobile.slice(-4)}${timestamp}${random}`;
|
|
||||||
|
|
||||||
// Using dbService helper (new implementation)
|
|
||||||
const codeExists = await checkCouponExists(couponCode);
|
|
||||||
if (codeExists) {
|
|
||||||
throw new Error("Generated coupon code already exists - please try again");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { coupon, user } = await createCouponForUser(cleanMobile, couponCode, staffUserId);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query with transaction:
|
|
||||||
// Check if user exists, create if not
|
// Check if user exists, create if not
|
||||||
let user = await db.query.users.findFirst({
|
let user = await db.query.users.findFirst({
|
||||||
where: eq(users.mobile, cleanMobile),
|
where: eq(users.mobile, cleanMobile),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
// Create new user
|
||||||
const [newUser] = await db.insert(users).values({
|
const [newUser] = await db.insert(users).values({
|
||||||
name: null,
|
name: null,
|
||||||
email: null,
|
email: null,
|
||||||
|
|
@ -540,18 +660,32 @@ export const couponRouter = router({
|
||||||
user = newUser;
|
user = newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate unique coupon code
|
||||||
|
const timestamp = Date.now().toString().slice(-6);
|
||||||
|
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
||||||
|
const couponCode = `MF${cleanMobile.slice(-4)}${timestamp}${random}`;
|
||||||
|
|
||||||
|
// Check if coupon code already exists (very unlikely but safe)
|
||||||
|
const existingCode = await db.query.coupons.findFirst({
|
||||||
|
where: eq(coupons.couponCode, couponCode),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingCode) {
|
||||||
|
throw new Error("Generated coupon code already exists - please try again");
|
||||||
|
}
|
||||||
|
|
||||||
// Create the coupon
|
// Create the coupon
|
||||||
const [coupon] = await db.insert(coupons).values({
|
const [coupon] = await db.insert(coupons).values({
|
||||||
couponCode,
|
couponCode,
|
||||||
isUserBased: true,
|
isUserBased: true,
|
||||||
discountPercent: "20",
|
discountPercent: "20", // 20% discount
|
||||||
minOrder: "1000",
|
minOrder: "1000", // ₹1000 minimum order
|
||||||
maxValue: "500",
|
maxValue: "500", // ₹500 maximum discount
|
||||||
maxLimitForUser: 1,
|
maxLimitForUser: 1, // One-time use
|
||||||
isApplyForAll: false,
|
isApplyForAll: false,
|
||||||
exclusiveApply: false,
|
exclusiveApply: false,
|
||||||
createdBy: staffUserId,
|
createdBy: staffUserId,
|
||||||
validTill: dayjs().add(90, 'days').toDate(),
|
validTill: dayjs().add(90, 'days').toDate(), // 90 days from now
|
||||||
}).returning();
|
}).returning();
|
||||||
|
|
||||||
// Associate coupon with user
|
// Associate coupon with user
|
||||||
|
|
@ -559,7 +693,6 @@ export const couponRouter = router({
|
||||||
couponId: coupon.id,
|
couponId: coupon.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,11 @@
|
||||||
import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
|
import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { db } from '@/src/db/db_index'
|
||||||
|
import { staffUsers, staffRoles, users, userDetails, orders } from '@/src/db/schema'
|
||||||
|
import { eq, or, ilike, and, lt, desc } from 'drizzle-orm';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { ApiError } from '@/src/lib/api-error'
|
import { ApiError } from '@/src/lib/api-error'
|
||||||
import {
|
|
||||||
getStaffUserByName,
|
|
||||||
getAllStaff,
|
|
||||||
getAllUsers,
|
|
||||||
getUserWithDetails,
|
|
||||||
updateUserSuspension,
|
|
||||||
checkStaffUserExists,
|
|
||||||
checkStaffRoleExists,
|
|
||||||
createStaffUser,
|
|
||||||
getAllRoles,
|
|
||||||
} from '@/src/dbService'
|
|
||||||
import type { StaffUser, StaffRole } from '@packages/shared'
|
|
||||||
|
|
||||||
export const staffUserRouter = router({
|
export const staffUserRouter = router({
|
||||||
login: publicProcedure
|
login: publicProcedure
|
||||||
|
|
@ -29,7 +20,9 @@ export const staffUserRouter = router({
|
||||||
throw new ApiError('Name and password are required', 400);
|
throw new ApiError('Name and password are required', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const staff = await getStaffUserByName(name);
|
const staff = await db.query.staffUsers.findFirst({
|
||||||
|
where: eq(staffUsers.name, name),
|
||||||
|
});
|
||||||
|
|
||||||
if (!staff) {
|
if (!staff) {
|
||||||
throw new ApiError('Invalid credentials', 401);
|
throw new ApiError('Invalid credentials', 401);
|
||||||
|
|
@ -55,7 +48,23 @@ export const staffUserRouter = router({
|
||||||
|
|
||||||
getStaff: protectedProcedure
|
getStaff: protectedProcedure
|
||||||
.query(async ({ ctx }) => {
|
.query(async ({ ctx }) => {
|
||||||
const staff = await getAllStaff();
|
const staff = await db.query.staffUsers.findMany({
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
with: {
|
||||||
|
role: {
|
||||||
|
with: {
|
||||||
|
rolePermissions: {
|
||||||
|
with: {
|
||||||
|
permission: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Transform the data to include role and permissions in a cleaner format
|
// Transform the data to include role and permissions in a cleaner format
|
||||||
const transformedStaff = staff.map((user) => ({
|
const transformedStaff = staff.map((user) => ({
|
||||||
|
|
@ -65,7 +74,7 @@ export const staffUserRouter = router({
|
||||||
id: user.role.id,
|
id: user.role.id,
|
||||||
name: user.role.roleName,
|
name: user.role.roleName,
|
||||||
} : null,
|
} : null,
|
||||||
permissions: user.role?.rolePermissions.map((rp: any) => ({
|
permissions: user.role?.rolePermissions.map((rp) => ({
|
||||||
id: rp.permission.id,
|
id: rp.permission.id,
|
||||||
name: rp.permission.permissionName,
|
name: rp.permission.permissionName,
|
||||||
})) || [],
|
})) || [],
|
||||||
|
|
@ -85,9 +94,34 @@ export const staffUserRouter = router({
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { cursor, limit, search } = input;
|
const { cursor, limit, search } = input;
|
||||||
|
|
||||||
const { users: usersToReturn, hasMore } = await getAllUsers(cursor, limit, search);
|
let whereCondition = undefined;
|
||||||
|
|
||||||
const formattedUsers = usersToReturn.map((user: any) => ({
|
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, // fetch one extra to check if there's more
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMore = allUsers.length > limit;
|
||||||
|
const usersToReturn = hasMore ? allUsers.slice(0, limit) : allUsers;
|
||||||
|
|
||||||
|
const formattedUsers = usersToReturn.map(user => ({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
|
@ -106,7 +140,16 @@ export const staffUserRouter = router({
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { userId } = input;
|
const { userId } = input;
|
||||||
|
|
||||||
const user = await getUserWithDetails(userId);
|
const user = await db.query.users.findFirst({
|
||||||
|
where: eq(users.id, userId),
|
||||||
|
with: {
|
||||||
|
userDetails: true,
|
||||||
|
orders: {
|
||||||
|
orderBy: desc(orders.createdAt),
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new ApiError("User not found", 404);
|
throw new ApiError("User not found", 404);
|
||||||
|
|
@ -130,7 +173,13 @@ export const staffUserRouter = router({
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const { userId, isSuspended } = input;
|
const { userId, isSuspended } = input;
|
||||||
|
|
||||||
await updateUserSuspension(userId, isSuspended);
|
await db
|
||||||
|
.insert(userDetails)
|
||||||
|
.values({ userId, isSuspended })
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: userDetails.userId,
|
||||||
|
set: { isSuspended },
|
||||||
|
});
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}),
|
}),
|
||||||
|
|
@ -145,16 +194,20 @@ export const staffUserRouter = router({
|
||||||
const { name, password, roleId } = input;
|
const { name, password, roleId } = input;
|
||||||
|
|
||||||
// Check if staff user already exists
|
// Check if staff user already exists
|
||||||
const existingUser = await checkStaffUserExists(name);
|
const existingUser = await db.query.staffUsers.findFirst({
|
||||||
|
where: eq(staffUsers.name, name),
|
||||||
|
});
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
throw new ApiError('Staff user with this name already exists', 409);
|
throw new ApiError('Staff user with this name already exists', 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if role exists
|
// Check if role exists
|
||||||
const roleExists = await checkStaffRoleExists(roleId);
|
const role = await db.query.staffRoles.findFirst({
|
||||||
|
where: eq(staffRoles.id, roleId),
|
||||||
|
});
|
||||||
|
|
||||||
if (!roleExists) {
|
if (!role) {
|
||||||
throw new ApiError('Invalid role selected', 400);
|
throw new ApiError('Invalid role selected', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,17 +215,26 @@ export const staffUserRouter = router({
|
||||||
const hashedPassword = await bcrypt.hash(password, 12);
|
const hashedPassword = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
// Create staff user
|
// Create staff user
|
||||||
const newUser = await createStaffUser(name, hashedPassword, roleId);
|
const [newUser] = await db.insert(staffUsers).values({
|
||||||
|
name: name.trim(),
|
||||||
|
password: hashedPassword,
|
||||||
|
staffRoleId: roleId,
|
||||||
|
}).returning();
|
||||||
|
|
||||||
return { success: true, user: { id: newUser.id, name: newUser.name } };
|
return { success: true, user: { id: newUser.id, name: newUser.name } };
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getRoles: protectedProcedure
|
getRoles: protectedProcedure
|
||||||
.query(async ({ ctx }) => {
|
.query(async ({ ctx }) => {
|
||||||
const roles = await getAllRoles();
|
const roles = await db.query.staffRoles.findMany({
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
roleName: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roles: roles.map((role: any) => ({
|
roles: roles.map(role => ({
|
||||||
id: role.id,
|
id: role.id,
|
||||||
name: role.roleName,
|
name: role.roleName,
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,30 @@
|
||||||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { db } from '@/src/db/db_index'
|
||||||
|
import { storeInfo, productInfo } from '@/src/db/schema'
|
||||||
|
import { eq, inArray } from 'drizzle-orm';
|
||||||
import { ApiError } from '@/src/lib/api-error'
|
import { ApiError } from '@/src/lib/api-error'
|
||||||
import { extractKeyFromPresignedUrl, deleteImageUtil, generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
|
import { extractKeyFromPresignedUrl, deleteImageUtil, generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
|
||||||
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
||||||
import {
|
|
||||||
getAllStores as getAllStoresFromDb,
|
|
||||||
getStoreById as getStoreByIdFromDb,
|
|
||||||
createStore as createStoreInDb,
|
|
||||||
updateStore as updateStoreInDb,
|
|
||||||
deleteStore as deleteStoreFromDb,
|
|
||||||
} from '@/src/dbService'
|
|
||||||
import type { Store } from '@packages/shared'
|
|
||||||
|
|
||||||
export const storeRouter = router({
|
export const storeRouter = router({
|
||||||
getStores: protectedProcedure
|
getStores: protectedProcedure
|
||||||
.query(async ({ ctx }): Promise<{ stores: any[]; count: number }> => {
|
.query(async ({ ctx }) => {
|
||||||
const stores = await getAllStoresFromDb();
|
const stores = await db.query.storeInfo.findMany({
|
||||||
|
with: {
|
||||||
|
owner: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await Promise.all(stores.map(async store => {
|
Promise.all(stores.map(async store => {
|
||||||
if(store.imageUrl)
|
if(store.imageUrl)
|
||||||
store.imageUrl = await generateSignedUrlFromS3Url(store.imageUrl)
|
store.imageUrl = await generateSignedUrlFromS3Url(store.imageUrl)
|
||||||
})).catch((e) => {
|
})).catch((e) => {
|
||||||
throw new ApiError("Unable to find store image urls")
|
throw new ApiError("Unable to find store image urls")
|
||||||
})
|
}
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
stores,
|
stores,
|
||||||
count: stores.length,
|
count: stores.length,
|
||||||
|
|
@ -34,10 +35,15 @@ export const storeRouter = router({
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input, ctx }): Promise<{ store: any }> => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
const store = await getStoreByIdFromDb(id);
|
const store = await db.query.storeInfo.findFirst({
|
||||||
|
where: eq(storeInfo.id, id),
|
||||||
|
with: {
|
||||||
|
owner: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!store) {
|
if (!store) {
|
||||||
throw new ApiError("Store not found", 404);
|
throw new ApiError("Store not found", 404);
|
||||||
|
|
@ -56,23 +62,11 @@ export const storeRouter = router({
|
||||||
owner: z.number().min(1, "Owner is required"),
|
owner: z.number().min(1, "Owner is required"),
|
||||||
products: z.array(z.number()).optional(),
|
products: z.array(z.number()).optional(),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }): Promise<{ store: Store; message: string }> => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { name, description, imageUrl, owner, products } = input;
|
const { name, description, imageUrl, owner, products } = input;
|
||||||
|
|
||||||
const imageKey = imageUrl ? extractKeyFromPresignedUrl(imageUrl) : undefined;
|
const imageKey = imageUrl ? extractKeyFromPresignedUrl(imageUrl) : undefined;
|
||||||
|
|
||||||
const newStore = await createStoreInDb(
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
imageUrl: imageKey,
|
|
||||||
owner,
|
|
||||||
},
|
|
||||||
products
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query:
|
|
||||||
const [newStore] = await db
|
const [newStore] = await db
|
||||||
.insert(storeInfo)
|
.insert(storeInfo)
|
||||||
.values({
|
.values({
|
||||||
|
|
@ -90,7 +84,6 @@ export const storeRouter = router({
|
||||||
.set({ storeId: newStore.id })
|
.set({ storeId: newStore.id })
|
||||||
.where(inArray(productInfo.id, products));
|
.where(inArray(productInfo.id, products));
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
scheduleStoreInitialization()
|
scheduleStoreInitialization()
|
||||||
|
|
@ -110,10 +103,12 @@ export const storeRouter = router({
|
||||||
owner: z.number().min(1, "Owner is required"),
|
owner: z.number().min(1, "Owner is required"),
|
||||||
products: z.array(z.number()).optional(),
|
products: z.array(z.number()).optional(),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }): Promise<{ store: Store; message: string }> => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { id, name, description, imageUrl, owner, products } = input;
|
const { id, name, description, imageUrl, owner, products } = input;
|
||||||
|
|
||||||
const existingStore = await getStoreByIdFromDb(id);
|
const existingStore = await db.query.storeInfo.findFirst({
|
||||||
|
where: eq(storeInfo.id, id),
|
||||||
|
});
|
||||||
|
|
||||||
if (!existingStore) {
|
if (!existingStore) {
|
||||||
throw new ApiError("Store not found", 404);
|
throw new ApiError("Store not found", 404);
|
||||||
|
|
@ -137,19 +132,6 @@ export const storeRouter = router({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedStore = await updateStoreInDb(
|
|
||||||
id,
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
imageUrl: newImageKey,
|
|
||||||
owner,
|
|
||||||
},
|
|
||||||
products
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query:
|
|
||||||
const [updatedStore] = await db
|
const [updatedStore] = await db
|
||||||
.update(storeInfo)
|
.update(storeInfo)
|
||||||
.set({
|
.set({
|
||||||
|
|
@ -181,7 +163,6 @@ export const storeRouter = router({
|
||||||
.where(inArray(productInfo.id, products));
|
.where(inArray(productInfo.id, products));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
scheduleStoreInitialization()
|
scheduleStoreInitialization()
|
||||||
|
|
@ -196,13 +177,9 @@ export const storeRouter = router({
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
storeId: z.number(),
|
storeId: z.number(),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }): Promise<{ message: string }> => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { storeId } = input;
|
const { storeId } = input;
|
||||||
|
|
||||||
const result = await deleteStoreFromDb(storeId);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Old implementation - direct DB query with transaction:
|
|
||||||
const result = await db.transaction(async (tx) => {
|
const result = await db.transaction(async (tx) => {
|
||||||
// First, update all products of this store to set storeId to null
|
// First, update all products of this store to set storeId to null
|
||||||
await tx
|
await tx
|
||||||
|
|
@ -224,11 +201,10 @@ export const storeRouter = router({
|
||||||
message: "Store deleted successfully",
|
message: "Store deleted successfully",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes (outside transaction)
|
// Reinitialize stores to reflect changes (outside transaction)
|
||||||
scheduleStoreInitialization()
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,15 @@
|
||||||
import { protectedProcedure } from '@/src/trpc/trpc-index';
|
import { protectedProcedure } from '@/src/trpc/trpc-index';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { db } from '@/src/db/db_index';
|
||||||
|
import { users, complaints, orders, orderItems, notifCreds, unloggedUserTokens, userDetails, userIncidents } from '@/src/db/schema';
|
||||||
|
import { eq, sql, desc, asc, count, max, inArray } from 'drizzle-orm';
|
||||||
import { ApiError } from '@/src/lib/api-error';
|
import { ApiError } from '@/src/lib/api-error';
|
||||||
import { notificationQueue } from '@/src/lib/notif-job';
|
import { notificationQueue } from '@/src/lib/notif-job';
|
||||||
import { recomputeUserNegativityScore } from '@/src/stores/user-negativity-store';
|
import { recomputeUserNegativityScore } from '@/src/stores/user-negativity-store';
|
||||||
import {
|
|
||||||
createUserByMobile,
|
|
||||||
getUserByMobile,
|
|
||||||
getUnresolvedComplaintsCount,
|
|
||||||
getAllUsersWithFilters,
|
|
||||||
getOrderCountsByUserIds,
|
|
||||||
getLastOrdersByUserIds,
|
|
||||||
getSuspensionStatusesByUserIds,
|
|
||||||
getUserBasicInfo,
|
|
||||||
getUserSuspensionStatus,
|
|
||||||
getUserOrders,
|
|
||||||
getOrderStatusesByOrderIds,
|
|
||||||
getItemCountsByOrderIds,
|
|
||||||
upsertUserSuspension,
|
|
||||||
searchUsers,
|
|
||||||
getAllNotifCreds,
|
|
||||||
getAllUnloggedTokens,
|
|
||||||
getNotifTokensByUserIds,
|
|
||||||
getUserIncidentsWithRelations,
|
|
||||||
createUserIncident,
|
|
||||||
} from '@/src/dbService';
|
|
||||||
|
|
||||||
export const userRouter = {
|
async function createUserByMobile(mobile: string): Promise<typeof users.$inferSelect> {
|
||||||
createUserByMobile: protectedProcedure
|
|
||||||
.input(z.object({
|
|
||||||
mobile: z.string().min(1, 'Mobile number is required'),
|
|
||||||
}))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
// Clean mobile number (remove non-digits)
|
// Clean mobile number (remove non-digits)
|
||||||
const cleanMobile = input.mobile.replace(/\D/g, '');
|
const cleanMobile = mobile.replace(/\D/g, '');
|
||||||
|
|
||||||
// Validate: exactly 10 digits
|
// Validate: exactly 10 digits
|
||||||
if (cleanMobile.length !== 10) {
|
if (cleanMobile.length !== 10) {
|
||||||
|
|
@ -40,13 +17,36 @@ export const userRouter = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user already exists
|
// Check if user already exists
|
||||||
const existingUser = await getUserByMobile(cleanMobile);
|
const [existingUser] = await db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.mobile, cleanMobile))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
throw new ApiError('User with this mobile number already exists', 409);
|
throw new ApiError('User with this mobile number already exists', 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUser = await createUserByMobile(cleanMobile);
|
// Create user
|
||||||
|
const [newUser] = await db
|
||||||
|
.insert(users)
|
||||||
|
.values({
|
||||||
|
name: null,
|
||||||
|
email: null,
|
||||||
|
mobile: cleanMobile,
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userRouter = {
|
||||||
|
createUserByMobile: protectedProcedure
|
||||||
|
.input(z.object({
|
||||||
|
mobile: z.string().min(1, 'Mobile number is required'),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const newUser = await createUserByMobile(input.mobile);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -56,10 +56,10 @@ export const userRouter = {
|
||||||
|
|
||||||
getEssentials: protectedProcedure
|
getEssentials: protectedProcedure
|
||||||
.query(async () => {
|
.query(async () => {
|
||||||
const count = await getUnresolvedComplaintsCount();
|
const count = await db.$count(complaints, eq(complaints.isResolved, false));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unresolvedComplaints: count,
|
unresolvedComplaints: count || 0,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
@ -72,14 +72,71 @@ export const userRouter = {
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { limit, cursor, search } = input;
|
const { limit, cursor, search } = input;
|
||||||
|
|
||||||
const { users: usersToReturn, hasMore } = await getAllUsersWithFilters(limit, cursor, search);
|
// Build where conditions
|
||||||
|
const whereConditions = [];
|
||||||
|
|
||||||
|
if (search && search.trim()) {
|
||||||
|
whereConditions.push(sql`${users.mobile} ILIKE ${`%${search.trim()}%`}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
whereConditions.push(sql`${users.id} > ${cursor}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get users with filters applied
|
||||||
|
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); // Get one extra to determine if there's more
|
||||||
|
|
||||||
|
// Check if there are more results
|
||||||
|
const hasMore = usersList.length > limit;
|
||||||
|
const usersToReturn = hasMore ? usersList.slice(0, limit) : usersList;
|
||||||
|
|
||||||
// Get order stats for each user
|
// Get order stats for each user
|
||||||
const userIds = usersToReturn.map((u: any) => u.id);
|
const userIds = usersToReturn.map(u => u.id);
|
||||||
|
|
||||||
const orderCounts = await getOrderCountsByUserIds(userIds);
|
let orderCounts: { userId: number; totalOrders: number }[] = [];
|
||||||
const lastOrders = await getLastOrdersByUserIds(userIds);
|
let lastOrders: { userId: number; lastOrderDate: Date | null }[] = [];
|
||||||
const suspensionStatuses = await getSuspensionStatusesByUserIds(userIds);
|
let suspensionStatuses: { userId: number; isSuspended: boolean }[] = [];
|
||||||
|
|
||||||
|
if (userIds.length > 0) {
|
||||||
|
// Get total orders per user
|
||||||
|
orderCounts = await db
|
||||||
|
.select({
|
||||||
|
userId: orders.userId,
|
||||||
|
totalOrders: count(orders.id),
|
||||||
|
})
|
||||||
|
.from(orders)
|
||||||
|
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
|
||||||
|
.groupBy(orders.userId);
|
||||||
|
|
||||||
|
// Get last order date per user
|
||||||
|
lastOrders = await db
|
||||||
|
.select({
|
||||||
|
userId: orders.userId,
|
||||||
|
lastOrderDate: max(orders.createdAt),
|
||||||
|
})
|
||||||
|
.from(orders)
|
||||||
|
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
|
||||||
|
.groupBy(orders.userId);
|
||||||
|
|
||||||
|
// Get suspension status for each user
|
||||||
|
suspensionStatuses = await db
|
||||||
|
.select({
|
||||||
|
userId: userDetails.userId,
|
||||||
|
isSuspended: userDetails.isSuspended,
|
||||||
|
})
|
||||||
|
.from(userDetails)
|
||||||
|
.where(sql`${userDetails.userId} IN (${sql.join(userIds, sql`, `)})`);
|
||||||
|
}
|
||||||
|
|
||||||
// Create lookup maps
|
// Create lookup maps
|
||||||
const orderCountMap = new Map(orderCounts.map(o => [o.userId, o.totalOrders]));
|
const orderCountMap = new Map(orderCounts.map(o => [o.userId, o.totalOrders]));
|
||||||
|
|
@ -87,7 +144,7 @@ export const userRouter = {
|
||||||
const suspensionMap = new Map(suspensionStatuses.map(s => [s.userId, s.isSuspended]));
|
const suspensionMap = new Map(suspensionStatuses.map(s => [s.userId, s.isSuspended]));
|
||||||
|
|
||||||
// Combine data
|
// Combine data
|
||||||
const usersWithStats = usersToReturn.map((user: any) => ({
|
const usersWithStats = usersToReturn.map(user => ({
|
||||||
...user,
|
...user,
|
||||||
totalOrders: orderCountMap.get(user.id) || 0,
|
totalOrders: orderCountMap.get(user.id) || 0,
|
||||||
lastOrderDate: lastOrderMap.get(user.id) || null,
|
lastOrderDate: lastOrderMap.get(user.id) || null,
|
||||||
|
|
@ -112,24 +169,69 @@ export const userRouter = {
|
||||||
const { userId } = input;
|
const { userId } = input;
|
||||||
|
|
||||||
// Get user info
|
// Get user info
|
||||||
const user = await getUserBasicInfo(userId);
|
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);
|
||||||
|
|
||||||
if (!user) {
|
if (!user || user.length === 0) {
|
||||||
throw new ApiError('User not found', 404);
|
throw new ApiError('User not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user suspension status
|
// Get user suspension status
|
||||||
const isSuspended = await getUserSuspensionStatus(userId);
|
const userDetail = await db
|
||||||
|
.select({
|
||||||
|
isSuspended: userDetails.isSuspended,
|
||||||
|
})
|
||||||
|
.from(userDetails)
|
||||||
|
.where(eq(userDetails.userId, userId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
// Get all orders for this user
|
// Get all orders for this user with order items count
|
||||||
const userOrders = await getUserOrders(userId);
|
const userOrders = 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));
|
||||||
|
|
||||||
// Get order status for each order
|
// Get order status for each order
|
||||||
const orderIds = userOrders.map((o: any) => o.id);
|
const orderIds = userOrders.map(o => o.id);
|
||||||
const orderStatuses = await getOrderStatusesByOrderIds(orderIds);
|
|
||||||
|
let orderStatuses: { orderId: number; isDelivered: boolean; isCancelled: boolean }[] = [];
|
||||||
|
|
||||||
|
if (orderIds.length > 0) {
|
||||||
|
const { orderStatus } = await import('@/src/db/schema');
|
||||||
|
orderStatuses = await db
|
||||||
|
.select({
|
||||||
|
orderId: orderStatus.orderId,
|
||||||
|
isDelivered: orderStatus.isDelivered,
|
||||||
|
isCancelled: orderStatus.isCancelled,
|
||||||
|
})
|
||||||
|
.from(orderStatus)
|
||||||
|
.where(sql`${orderStatus.orderId} IN (${sql.join(orderIds, sql`, `)})`);
|
||||||
|
}
|
||||||
|
|
||||||
// Get item counts for each order
|
// Get item counts for each order
|
||||||
const itemCounts = await getItemCountsByOrderIds(orderIds);
|
const itemCounts = await db
|
||||||
|
.select({
|
||||||
|
orderId: orderItems.orderId,
|
||||||
|
itemCount: count(orderItems.id),
|
||||||
|
})
|
||||||
|
.from(orderItems)
|
||||||
|
.where(sql`${orderItems.orderId} IN (${sql.join(orderIds, sql`, `)})`)
|
||||||
|
.groupBy(orderItems.orderId);
|
||||||
|
|
||||||
// Create lookup maps
|
// Create lookup maps
|
||||||
const statusMap = new Map(orderStatuses.map(s => [s.orderId, s]));
|
const statusMap = new Map(orderStatuses.map(s => [s.orderId, s]));
|
||||||
|
|
@ -144,7 +246,7 @@ export const userRouter = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Combine data
|
// Combine data
|
||||||
const ordersWithDetails = userOrders.map((order: any) => {
|
const ordersWithDetails = userOrders.map(order => {
|
||||||
const status = statusMap.get(order.id);
|
const status = statusMap.get(order.id);
|
||||||
return {
|
return {
|
||||||
id: order.id,
|
id: order.id,
|
||||||
|
|
@ -159,8 +261,8 @@ export const userRouter = {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: {
|
user: {
|
||||||
...user,
|
...user[0],
|
||||||
isSuspended,
|
isSuspended: userDetail[0]?.isSuspended ?? false,
|
||||||
},
|
},
|
||||||
orders: ordersWithDetails,
|
orders: ordersWithDetails,
|
||||||
};
|
};
|
||||||
|
|
@ -174,7 +276,39 @@ export const userRouter = {
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const { userId, isSuspended } = input;
|
const { userId, isSuspended } = input;
|
||||||
|
|
||||||
await upsertUserSuspension(userId, isSuspended);
|
// Check if user exists
|
||||||
|
const user = await db
|
||||||
|
.select({ id: users.id })
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, userId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!user || user.length === 0) {
|
||||||
|
throw new ApiError('User not found', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user_details record exists
|
||||||
|
const existingDetail = await db
|
||||||
|
.select({ id: userDetails.id })
|
||||||
|
.from(userDetails)
|
||||||
|
.where(eq(userDetails.userId, userId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (existingDetail.length > 0) {
|
||||||
|
// Update existing record
|
||||||
|
await db
|
||||||
|
.update(userDetails)
|
||||||
|
.set({ isSuspended })
|
||||||
|
.where(eq(userDetails.userId, userId));
|
||||||
|
} else {
|
||||||
|
// Insert new record
|
||||||
|
await db
|
||||||
|
.insert(userDetails)
|
||||||
|
.values({
|
||||||
|
userId,
|
||||||
|
isSuspended,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -189,15 +323,36 @@ export const userRouter = {
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { search } = input;
|
const { search } = input;
|
||||||
|
|
||||||
const usersList = await searchUsers(search);
|
// Get all users
|
||||||
|
let usersList;
|
||||||
|
if (search && search.trim()) {
|
||||||
|
usersList = 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 {
|
||||||
|
usersList = await db
|
||||||
|
.select({
|
||||||
|
id: users.id,
|
||||||
|
name: users.name,
|
||||||
|
mobile: users.mobile,
|
||||||
|
})
|
||||||
|
.from(users);
|
||||||
|
}
|
||||||
|
|
||||||
// Get eligible users (have notif_creds entry)
|
// Get eligible users (have notif_creds entry)
|
||||||
const eligibleUsers = await getAllNotifCreds();
|
const eligibleUsers = await db
|
||||||
|
.select({ userId: notifCreds.userId })
|
||||||
|
.from(notifCreds);
|
||||||
|
|
||||||
const eligibleSet = new Set(eligibleUsers.map(u => u.userId));
|
const eligibleSet = new Set(eligibleUsers.map(u => u.userId));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users: usersList.map((user: any) => ({
|
users: usersList.map(user => ({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
mobile: user.mobile,
|
mobile: user.mobile,
|
||||||
|
|
@ -220,8 +375,8 @@ export const userRouter = {
|
||||||
|
|
||||||
if (userIds.length === 0) {
|
if (userIds.length === 0) {
|
||||||
// Send to all users - get tokens from both logged-in and unlogged users
|
// Send to all users - get tokens from both logged-in and unlogged users
|
||||||
const loggedInTokens = await getAllNotifCreds();
|
const loggedInTokens = await db.select({ token: notifCreds.token }).from(notifCreds);
|
||||||
const unloggedTokens = await getAllUnloggedTokens();
|
const unloggedTokens = await db.select({ token: unloggedUserTokens.token }).from(unloggedUserTokens);
|
||||||
|
|
||||||
tokens = [
|
tokens = [
|
||||||
...loggedInTokens.map(t => t.token),
|
...loggedInTokens.map(t => t.token),
|
||||||
|
|
@ -229,7 +384,11 @@ export const userRouter = {
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
// Send to specific users - get their tokens
|
// Send to specific users - get their tokens
|
||||||
const userTokens = await getNotifTokensByUserIds(userIds);
|
const userTokens = await db
|
||||||
|
.select({ token: notifCreds.token })
|
||||||
|
.from(notifCreds)
|
||||||
|
.where(inArray(notifCreds.userId, userIds));
|
||||||
|
|
||||||
tokens = userTokens.map(t => t.token);
|
tokens = userTokens.map(t => t.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,10 +427,21 @@ export const userRouter = {
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { userId } = input;
|
const { userId } = input;
|
||||||
|
|
||||||
const incidents = await getUserIncidentsWithRelations(userId);
|
const incidents = await db.query.userIncidents.findMany({
|
||||||
|
where: eq(userIncidents.userId, userId),
|
||||||
|
with: {
|
||||||
|
order: {
|
||||||
|
with: {
|
||||||
|
orderStatus: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addedBy: true,
|
||||||
|
},
|
||||||
|
orderBy: desc(userIncidents.dateAdded),
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
incidents: incidents.map((incident: any) => ({
|
incidents: incidents.map(incident => ({
|
||||||
id: incident.id,
|
id: incident.id,
|
||||||
userId: incident.userId,
|
userId: incident.userId,
|
||||||
orderId: incident.orderId,
|
orderId: incident.orderId,
|
||||||
|
|
@ -300,13 +470,14 @@ export const userRouter = {
|
||||||
throw new ApiError('Admin user not authenticated', 401);
|
throw new ApiError('Admin user not authenticated', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
const incident = await createUserIncident(
|
|
||||||
userId,
|
const incidentObj = { userId, orderId, adminComment, addedBy: adminUserId, negativityScore };
|
||||||
orderId,
|
|
||||||
adminComment,
|
const [incident] = await db.insert(userIncidents)
|
||||||
adminUserId,
|
.values({
|
||||||
negativityScore
|
...incidentObj,
|
||||||
);
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
recomputeUserNegativityScore(userId);
|
recomputeUserNegativityScore(userId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,3 @@ export * from './src/db/schema';
|
||||||
|
|
||||||
// Re-export helper methods
|
// Re-export helper methods
|
||||||
export * from './src/helper_methods/banner';
|
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';
|
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
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,
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
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 },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,633 +0,0 @@
|
||||||
import { db } from '../db/db_index';
|
|
||||||
import { coupons, reservedCoupons, users } 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> {
|
|
||||||
const result = await db.query.coupons.findFirst({
|
|
||||||
where: eq(coupons.id, id),
|
|
||||||
with: {
|
|
||||||
creator: true,
|
|
||||||
applicableUsers: {
|
|
||||||
with: {
|
|
||||||
user: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
applicableProducts: {
|
|
||||||
with: {
|
|
||||||
product: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return result || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
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" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check expiry date
|
|
||||||
if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
|
|
||||||
return { valid: false, message: "Coupon has expired" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if coupon applies to all users or specific user
|
|
||||||
if (!coupon.isApplyForAll && !coupon.isUserBased) {
|
|
||||||
return { valid: false, message: "Coupon is not available for use" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check minimum order amount
|
|
||||||
const minOrderValue = coupon.minOrder ? parseFloat(coupon.minOrder) : 0;
|
|
||||||
if (minOrderValue > 0 && orderAmount < minOrderValue) {
|
|
||||||
return { valid: false, message: `Minimum order amount is ${minOrderValue}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate discount
|
|
||||||
let discountAmount = 0;
|
|
||||||
if (coupon.discountPercent) {
|
|
||||||
const percent = parseFloat(coupon.discountPercent);
|
|
||||||
discountAmount = (orderAmount * percent) / 100;
|
|
||||||
} else if (coupon.flatDiscount) {
|
|
||||||
discountAmount = parseFloat(coupon.flatDiscount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply max value limit
|
|
||||||
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 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,
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// BATCH 2: Transaction Methods
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import { couponApplicableUsers, couponApplicableProducts, orders, orderStatus } from '../db/schema';
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
// Create the coupon
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Insert applicable users
|
|
||||||
if (applicableUsers && applicableUsers.length > 0) {
|
|
||||||
await tx.insert(couponApplicableUsers).values(
|
|
||||||
applicableUsers.map(userId => ({
|
|
||||||
couponId: coupon.id,
|
|
||||||
userId,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert applicable products
|
|
||||||
if (applicableProducts && applicableProducts.length > 0) {
|
|
||||||
await tx.insert(couponApplicableProducts).values(
|
|
||||||
applicableProducts.map(productId => ({
|
|
||||||
couponId: coupon.id,
|
|
||||||
productId,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: coupon.id,
|
|
||||||
couponCode: coupon.couponCode,
|
|
||||||
isUserBased: coupon.isUserBased,
|
|
||||||
discountPercent: coupon.discountPercent,
|
|
||||||
flatDiscount: coupon.flatDiscount,
|
|
||||||
minOrder: coupon.minOrder,
|
|
||||||
productIds: coupon.productIds,
|
|
||||||
maxValue: coupon.maxValue,
|
|
||||||
isApplyForAll: coupon.isApplyForAll,
|
|
||||||
validTill: coupon.validTill,
|
|
||||||
maxLimitForUser: coupon.maxLimitForUser,
|
|
||||||
exclusiveApply: coupon.exclusiveApply,
|
|
||||||
isInvalidated: coupon.isInvalidated,
|
|
||||||
createdAt: coupon.createdAt,
|
|
||||||
createdBy: coupon.createdBy,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
// Update the coupon
|
|
||||||
const [coupon] = await tx.update(coupons)
|
|
||||||
.set({
|
|
||||||
...input,
|
|
||||||
lastUpdated: new Date(),
|
|
||||||
})
|
|
||||||
.where(eq(coupons.id, id))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
// Update applicable users: delete existing and insert new
|
|
||||||
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,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update applicable products: delete existing and insert new
|
|
||||||
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 {
|
|
||||||
id: coupon.id,
|
|
||||||
couponCode: coupon.couponCode,
|
|
||||||
isUserBased: coupon.isUserBased,
|
|
||||||
discountPercent: coupon.discountPercent,
|
|
||||||
flatDiscount: coupon.flatDiscount,
|
|
||||||
minOrder: coupon.minOrder,
|
|
||||||
productIds: coupon.productIds,
|
|
||||||
maxValue: coupon.maxValue,
|
|
||||||
isApplyForAll: coupon.isApplyForAll,
|
|
||||||
validTill: coupon.validTill,
|
|
||||||
maxLimitForUser: coupon.maxLimitForUser,
|
|
||||||
exclusiveApply: coupon.exclusiveApply,
|
|
||||||
isInvalidated: coupon.isInvalidated,
|
|
||||||
createdAt: coupon.createdAt,
|
|
||||||
createdBy: coupon.createdBy,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generateCancellationCoupon(
|
|
||||||
orderId: number,
|
|
||||||
staffUserId: number,
|
|
||||||
userId: number,
|
|
||||||
orderAmount: number,
|
|
||||||
couponCode: string
|
|
||||||
): Promise<Coupon> {
|
|
||||||
return await db.transaction(async (tx) => {
|
|
||||||
// Calculate expiry date (30 days from now)
|
|
||||||
const expiryDate = new Date();
|
|
||||||
expiryDate.setDate(expiryDate.getDate() + 30);
|
|
||||||
|
|
||||||
// Create the coupon
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Insert applicable users
|
|
||||||
await tx.insert(couponApplicableUsers).values({
|
|
||||||
couponId: coupon.id,
|
|
||||||
userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update order_status with refund coupon ID
|
|
||||||
await tx.update(orderStatus)
|
|
||||||
.set({ refundCouponId: coupon.id })
|
|
||||||
.where(eq(orderStatus.orderId, orderId));
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: coupon.id,
|
|
||||||
couponCode: coupon.couponCode,
|
|
||||||
isUserBased: coupon.isUserBased,
|
|
||||||
discountPercent: coupon.discountPercent,
|
|
||||||
flatDiscount: coupon.flatDiscount,
|
|
||||||
minOrder: coupon.minOrder,
|
|
||||||
productIds: coupon.productIds,
|
|
||||||
maxValue: coupon.maxValue,
|
|
||||||
isApplyForAll: coupon.isApplyForAll,
|
|
||||||
validTill: coupon.validTill,
|
|
||||||
maxLimitForUser: coupon.maxLimitForUser,
|
|
||||||
exclusiveApply: coupon.exclusiveApply,
|
|
||||||
isInvalidated: coupon.isInvalidated,
|
|
||||||
createdAt: coupon.createdAt,
|
|
||||||
createdBy: coupon.createdBy,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateReservedCouponInput {
|
|
||||||
secretCode: string;
|
|
||||||
couponCode: string;
|
|
||||||
discountPercent?: string;
|
|
||||||
flatDiscount?: string;
|
|
||||||
minOrder?: string;
|
|
||||||
productIds?: number[] | null;
|
|
||||||
maxValue?: string;
|
|
||||||
validTill?: Date;
|
|
||||||
maxLimitForUser?: number;
|
|
||||||
exclusiveApply: boolean;
|
|
||||||
createdBy: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createReservedCouponWithProducts(
|
|
||||||
input: CreateReservedCouponInput,
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Insert applicable products if provided
|
|
||||||
if (applicableProducts && applicableProducts.length > 0) {
|
|
||||||
await tx.insert(couponApplicableProducts).values(
|
|
||||||
applicableProducts.map(productId => ({
|
|
||||||
couponId: coupon.id,
|
|
||||||
productId,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return coupon;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrCreateUserByMobile(
|
|
||||||
mobile: string
|
|
||||||
): Promise<{ id: number; mobile: string; name: string | null }> {
|
|
||||||
return await db.transaction(async (tx) => {
|
|
||||||
// Check if user exists
|
|
||||||
let user = await tx.query.users.findFirst({
|
|
||||||
where: eq(users.mobile, mobile),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
// Create new user
|
|
||||||
const [newUser] = await tx.insert(users).values({
|
|
||||||
name: null,
|
|
||||||
email: null,
|
|
||||||
mobile,
|
|
||||||
}).returning();
|
|
||||||
user = newUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: user.id,
|
|
||||||
mobile: user.mobile,
|
|
||||||
name: user.name,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
// Get or create user
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the coupon
|
|
||||||
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), // 90 days from now
|
|
||||||
}).returning();
|
|
||||||
|
|
||||||
// Associate coupon with user
|
|
||||||
await tx.insert(couponApplicableUsers).values({
|
|
||||||
couponId: coupon.id,
|
|
||||||
userId: user.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
coupon: {
|
|
||||||
id: coupon.id,
|
|
||||||
couponCode: coupon.couponCode,
|
|
||||||
isUserBased: coupon.isUserBased,
|
|
||||||
discountPercent: coupon.discountPercent,
|
|
||||||
flatDiscount: coupon.flatDiscount,
|
|
||||||
minOrder: coupon.minOrder,
|
|
||||||
productIds: coupon.productIds,
|
|
||||||
maxValue: coupon.maxValue,
|
|
||||||
isApplyForAll: coupon.isApplyForAll,
|
|
||||||
validTill: coupon.validTill,
|
|
||||||
maxLimitForUser: coupon.maxLimitForUser,
|
|
||||||
exclusiveApply: coupon.exclusiveApply,
|
|
||||||
isInvalidated: coupon.isInvalidated,
|
|
||||||
createdAt: coupon.createdAt,
|
|
||||||
createdBy: coupon.createdBy,
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
mobile: user.mobile,
|
|
||||||
name: user.name,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Utility Functions
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
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 getOrderWithUser(orderId: number): Promise<any | null> {
|
|
||||||
return await db.query.orders.findFirst({
|
|
||||||
where: eq(orders.id, orderId),
|
|
||||||
with: {
|
|
||||||
user: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,259 +0,0 @@
|
||||||
import { db } from '../db/db_index';
|
|
||||||
import { orders, orderItems, orderStatus, users, addresses, refunds, complaints, payments } from '../db/schema';
|
|
||||||
import { eq, and, gte, lt, desc, inArray, sql } from 'drizzle-orm';
|
|
||||||
|
|
||||||
export async function updateOrderNotes(orderId: number, adminNotes: string | null): Promise<any> {
|
|
||||||
const [result] = await db
|
|
||||||
.update(orders)
|
|
||||||
.set({ adminNotes })
|
|
||||||
.where(eq(orders.id, orderId))
|
|
||||||
.returning();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrderWithDetails(orderId: number): Promise<any | null> {
|
|
||||||
return await db.query.orders.findFirst({
|
|
||||||
where: eq(orders.id, orderId),
|
|
||||||
with: {
|
|
||||||
orderItems: {
|
|
||||||
with: {
|
|
||||||
product: {
|
|
||||||
with: {
|
|
||||||
unit: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: true,
|
|
||||||
address: true,
|
|
||||||
orderStatus: true,
|
|
||||||
slot: true,
|
|
||||||
payments: true,
|
|
||||||
refunds: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFullOrder(orderId: number): Promise<any | null> {
|
|
||||||
return await db.query.orders.findFirst({
|
|
||||||
where: eq(orders.id, orderId),
|
|
||||||
with: {
|
|
||||||
orderItems: {
|
|
||||||
with: {
|
|
||||||
product: {
|
|
||||||
with: {
|
|
||||||
unit: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
with: {
|
|
||||||
userDetails: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
address: true,
|
|
||||||
orderStatus: true,
|
|
||||||
slot: true,
|
|
||||||
payments: true,
|
|
||||||
refunds: true,
|
|
||||||
complaints: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrderDetails(orderId: number): Promise<any | null> {
|
|
||||||
return await db.query.orders.findFirst({
|
|
||||||
where: eq(orders.id, orderId),
|
|
||||||
with: {
|
|
||||||
orderItems: {
|
|
||||||
with: {
|
|
||||||
product: {
|
|
||||||
with: {
|
|
||||||
unit: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: true,
|
|
||||||
address: true,
|
|
||||||
orderStatus: true,
|
|
||||||
slot: true,
|
|
||||||
payments: true,
|
|
||||||
refunds: true,
|
|
||||||
complaints: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllOrders(
|
|
||||||
limit: number,
|
|
||||||
cursor?: number,
|
|
||||||
slotId?: number | null,
|
|
||||||
filters?: any
|
|
||||||
): Promise<{ orders: any[]; hasMore: boolean }> {
|
|
||||||
let whereConditions = [];
|
|
||||||
|
|
||||||
if (cursor) {
|
|
||||||
whereConditions.push(lt(orders.id, cursor));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slotId) {
|
|
||||||
whereConditions.push(eq(orders.slotId, slotId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add filter conditions
|
|
||||||
if (filters) {
|
|
||||||
if (filters.packagedFilter === 'packaged') {
|
|
||||||
whereConditions.push(eq(orders.isPackaged, true));
|
|
||||||
} else if (filters.packagedFilter === 'not_packaged') {
|
|
||||||
whereConditions.push(eq(orders.isPackaged, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.deliveredFilter === 'delivered') {
|
|
||||||
whereConditions.push(eq(orders.isDelivered, true));
|
|
||||||
} else if (filters.deliveredFilter === 'not_delivered') {
|
|
||||||
whereConditions.push(eq(orders.isDelivered, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.flashDeliveryFilter === 'flash') {
|
|
||||||
whereConditions.push(eq(orders.isFlashDelivery, true));
|
|
||||||
} else if (filters.flashDeliveryFilter === 'regular') {
|
|
||||||
whereConditions.push(eq(orders.isFlashDelivery, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ordersList = await db.query.orders.findMany({
|
|
||||||
where: whereConditions.length > 0 ? and(...whereConditions) : undefined,
|
|
||||||
with: {
|
|
||||||
orderItems: {
|
|
||||||
with: {
|
|
||||||
product: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: true,
|
|
||||||
orderStatus: true,
|
|
||||||
slot: true,
|
|
||||||
},
|
|
||||||
orderBy: desc(orders.id),
|
|
||||||
limit: limit + 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasMore = ordersList.length > limit;
|
|
||||||
return { orders: hasMore ? ordersList.slice(0, limit) : ordersList, hasMore };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrdersBySlotId(slotId: number): Promise<any[]> {
|
|
||||||
return await db.query.orders.findMany({
|
|
||||||
where: eq(orders.slotId, slotId),
|
|
||||||
with: {
|
|
||||||
orderItems: {
|
|
||||||
with: {
|
|
||||||
product: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: true,
|
|
||||||
orderStatus: true,
|
|
||||||
address: true,
|
|
||||||
},
|
|
||||||
orderBy: desc(orders.createdAt),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateOrderPackaged(orderId: number, isPackaged: boolean): Promise<any> {
|
|
||||||
const [result] = await db
|
|
||||||
.update(orders)
|
|
||||||
.set({ isPackaged })
|
|
||||||
.where(eq(orders.id, orderId))
|
|
||||||
.returning();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateOrderDelivered(orderId: number, isDelivered: boolean): Promise<any> {
|
|
||||||
const [result] = await db
|
|
||||||
.update(orders)
|
|
||||||
.set({ isDelivered })
|
|
||||||
.where(eq(orders.id, orderId))
|
|
||||||
.returning();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateOrderItemPackaging(
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateAddressCoords(addressId: number, lat: number, lng: number): Promise<void> {
|
|
||||||
await db.update(addresses)
|
|
||||||
.set({ lat, lng })
|
|
||||||
.where(eq(addresses.id, addressId));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrderStatus(orderId: number): Promise<any | null> {
|
|
||||||
return await db.query.orderStatus.findFirst({
|
|
||||||
where: eq(orderStatus.orderId, orderId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function cancelOrder(orderId: number, reason: string): Promise<any> {
|
|
||||||
return await db.transaction(async (tx) => {
|
|
||||||
// Update order status
|
|
||||||
const [order] = await tx.update(orders)
|
|
||||||
.set({ isCancelled: true, cancellationReason: reason })
|
|
||||||
.where(eq(orders.id, orderId))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
// Create order status entry
|
|
||||||
await tx.insert(orderStatus).values({
|
|
||||||
orderId,
|
|
||||||
isCancelled: true,
|
|
||||||
cancelReason: reason,
|
|
||||||
});
|
|
||||||
|
|
||||||
return order;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTodaysOrders(slotId?: number): Promise<any[]> {
|
|
||||||
const today = new Date();
|
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
const tomorrow = new Date(today);
|
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
||||||
|
|
||||||
let whereConditions = [
|
|
||||||
gte(orders.createdAt, today),
|
|
||||||
lt(orders.createdAt, tomorrow),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (slotId) {
|
|
||||||
whereConditions.push(eq(orders.slotId, slotId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await db.query.orders.findMany({
|
|
||||||
where: and(...whereConditions),
|
|
||||||
with: {
|
|
||||||
orderItems: {
|
|
||||||
with: {
|
|
||||||
product: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user: true,
|
|
||||||
orderStatus: true,
|
|
||||||
},
|
|
||||||
orderBy: desc(orders.createdAt),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeDeliveryCharge(orderId: number): Promise<any> {
|
|
||||||
const [result] = await db
|
|
||||||
.update(orders)
|
|
||||||
.set({ deliveryCharge: '0' })
|
|
||||||
.where(eq(orders.id, orderId))
|
|
||||||
.returning();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
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)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
import { db } from '../db/db_index';
|
|
||||||
import { deliverySlotInfo, productSlots, productInfo, vendorSnippets } 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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
vendorSnippets: 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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
vendorSnippets: {
|
|
||||||
with: {
|
|
||||||
slot: 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));
|
|
||||||
}
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
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 getStaffByName(name: string): Promise<StaffUser | null> {
|
|
||||||
const staff = await db.query.staffUsers.findFirst({
|
|
||||||
where: eq(staffUsers.name, name),
|
|
||||||
});
|
|
||||||
return staff || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 updateUserSuspension(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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
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();
|
|
||||||
|
|
||||||
// Assign selected products to this store
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update products if provided
|
|
||||||
if (products !== undefined) {
|
|
||||||
// First, set storeId to null for products not in the list but currently assigned to this store
|
|
||||||
await db
|
|
||||||
.update(productInfo)
|
|
||||||
.set({ storeId: null })
|
|
||||||
.where(eq(productInfo.storeId, id));
|
|
||||||
|
|
||||||
// Then, assign the selected products to this store
|
|
||||||
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) => {
|
|
||||||
// First, update all products of this store to set storeId to null
|
|
||||||
await tx
|
|
||||||
.update(productInfo)
|
|
||||||
.set({ storeId: null })
|
|
||||||
.where(eq(productInfo.storeId, id));
|
|
||||||
|
|
||||||
// Then delete the store
|
|
||||||
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",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,270 +0,0 @@
|
||||||
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 }[]> {
|
|
||||||
return await db
|
|
||||||
.select({ userId: notifCreds.userId })
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/**
|
|
||||||
* Complaint Types
|
|
||||||
* Central type definitions for complaint-related data structures
|
|
||||||
*/
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/**
|
|
||||||
* Constants Types
|
|
||||||
* Central type definitions for key-value store constants
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface Constant {
|
|
||||||
key: string;
|
|
||||||
value: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConstantUpdateResult {
|
|
||||||
success: boolean;
|
|
||||||
updatedCount: number;
|
|
||||||
keys: string[];
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
/**
|
|
||||||
* Coupon Types
|
|
||||||
* Central type definitions for coupon-related data structures
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 ReservedCoupon extends Coupon {
|
|
||||||
secretCode: string;
|
|
||||||
redeemedUserId: number | null;
|
|
||||||
redeemedAt: Date | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CouponValidationResult {
|
|
||||||
valid: boolean;
|
|
||||||
message?: string;
|
|
||||||
discountAmount?: number;
|
|
||||||
coupon?: Partial<Coupon>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserMiniInfo {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
mobile: string | null;
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,3 @@
|
||||||
// Re-export all types from the types folder
|
// Re-export all types from the types folder
|
||||||
|
|
||||||
export type { Banner } from './banner.types';
|
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';
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/**
|
|
||||||
* Staff User Types
|
|
||||||
* Central type definitions for staff user-related data structures
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface StaffUser {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
password: string;
|
|
||||||
staffRoleId: number;
|
|
||||||
createdAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StaffRole {
|
|
||||||
id: number;
|
|
||||||
roleName: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* Store Types
|
|
||||||
* Central type definitions for store-related data structures
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface Store {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
description: string | null;
|
|
||||||
imageUrl: string | null;
|
|
||||||
owner: number;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
Loading…
Add table
Reference in a new issue