enh
This commit is contained in:
parent
97812fa4c5
commit
d9652405ca
12 changed files with 1216 additions and 462 deletions
|
|
@ -9,7 +9,37 @@ export { db } from 'postgresService';
|
|||
export * from 'postgresService';
|
||||
|
||||
// Re-export methods from postgresService (implementation lives there)
|
||||
export { getBanners, getBannerById, createBanner, updateBanner, deleteBanner } from 'postgresService';
|
||||
export {
|
||||
// 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,
|
||||
} from 'postgresService';
|
||||
|
||||
// Re-export types from local types file (to avoid circular dependencies)
|
||||
export type { Banner } from './types/db.types';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||
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 { getComplaints as getComplaintsFromDb, resolveComplaint as resolveComplaintInDb } from '@/src/dbService'
|
||||
import type { ComplaintWithUser } from '@packages/shared'
|
||||
|
||||
export const complaintRouter = router({
|
||||
getAll: protectedProcedure
|
||||
|
|
@ -11,7 +10,27 @@ export const complaintRouter = router({
|
|||
cursor: z.number().optional(),
|
||||
limit: z.number().default(20),
|
||||
}))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input }): Promise<{
|
||||
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;
|
||||
|
||||
let whereCondition = cursor
|
||||
|
|
@ -37,10 +56,13 @@ export const complaintRouter = router({
|
|||
.limit(limit + 1);
|
||||
|
||||
const hasMore = complaintsData.length > limit;
|
||||
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
||||
*/
|
||||
|
||||
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
||||
|
||||
const complaintsWithSignedImages = await Promise.all(
|
||||
complaintsToReturn.map(async (c) => {
|
||||
complaintsToReturn.map(async (c: ComplaintWithUser) => {
|
||||
const signedImages = c.images
|
||||
? await generateSignedUrlsFromS3Urls(c.images as string[])
|
||||
: [];
|
||||
|
|
@ -69,11 +91,17 @@ export const complaintRouter = router({
|
|||
|
||||
resolve: protectedProcedure
|
||||
.input(z.object({ id: z.string(), response: z.string().optional() }))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input }): Promise<{ message: string }> => {
|
||||
// Using dbService helper (new implementation)
|
||||
await resolveComplaintInDb(parseInt(input.id), input.response);
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB query:
|
||||
await db
|
||||
.update(complaints)
|
||||
.set({ isResolved: true, response: input.response })
|
||||
.where(eq(complaints.id, parseInt(input.id)));
|
||||
*/
|
||||
|
||||
return { message: 'Complaint resolved successfully' };
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,22 +1,27 @@
|
|||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||
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 { 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({
|
||||
getConstants: protectedProcedure
|
||||
.query(async () => {
|
||||
.query(async (): Promise<Constant[]> => {
|
||||
// Using dbService helper (new implementation)
|
||||
const constants = await getAllConstantsFromDb();
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB query:
|
||||
const constants = await db.select().from(keyValStore);
|
||||
|
||||
const resp = constants.map(c => ({
|
||||
key: c.key,
|
||||
value: c.value,
|
||||
}));
|
||||
*/
|
||||
|
||||
return resp;
|
||||
return constants;
|
||||
}),
|
||||
|
||||
updateConstants: protectedProcedure
|
||||
|
|
@ -26,7 +31,7 @@ export const constRouter = router({
|
|||
value: z.any(),
|
||||
})),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input }): Promise<ConstantUpdateResult> => {
|
||||
const { constants } = input;
|
||||
|
||||
const validKeys = Object.values(CONST_KEYS) as string[];
|
||||
|
|
@ -38,6 +43,11 @@ export const constRouter = router({
|
|||
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) => {
|
||||
for (const { key, value } of constants) {
|
||||
await tx.insert(keyValStore)
|
||||
|
|
@ -48,6 +58,7 @@ export const constRouter = router({
|
|||
});
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// Refresh all constants in Redis after database update
|
||||
await computeConstants();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,26 @@
|
|||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||
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 {
|
||||
// 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({
|
||||
couponCode: z.string().optional(),
|
||||
|
|
@ -31,7 +48,7 @@ const validateCouponBodySchema = z.object({
|
|||
export const couponRouter = router({
|
||||
create: protectedProcedure
|
||||
.input(createCouponBodySchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
.mutation(async ({ input, ctx }): Promise<Coupon> => {
|
||||
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||
|
||||
// Validation: ensure at least one discount type is provided
|
||||
|
|
@ -49,17 +66,6 @@ export const couponRouter = router({
|
|||
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
|
||||
const staffUserId = ctx.staffUser?.id;
|
||||
if (!staffUserId) {
|
||||
|
|
@ -69,22 +75,27 @@ export const couponRouter = router({
|
|||
// Generate coupon code if not provided
|
||||
let finalCouponCode = couponCode;
|
||||
if (!finalCouponCode) {
|
||||
// Generate a unique coupon code
|
||||
const timestamp = Date.now().toString().slice(-6);
|
||||
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
finalCouponCode = `MF${timestamp}${random}`;
|
||||
}
|
||||
|
||||
// Check if coupon code already exists
|
||||
const existingCoupon = await db.query.coupons.findFirst({
|
||||
where: eq(coupons.couponCode, finalCouponCode),
|
||||
});
|
||||
|
||||
if (existingCoupon) {
|
||||
// Using dbService helper (new implementation)
|
||||
const codeExists = await checkCouponExists(finalCouponCode);
|
||||
if (codeExists) {
|
||||
throw new Error("Coupon code already exists");
|
||||
}
|
||||
|
||||
const result = await db.insert(coupons).values({
|
||||
// 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(),
|
||||
|
|
@ -95,9 +106,29 @@ export const couponRouter = router({
|
|||
maxValue: maxValue?.toString(),
|
||||
isApplyForAll: isApplyForAll || false,
|
||||
validTill: validTill ? dayjs(validTill).toDate() : undefined,
|
||||
maxLimitForUser: maxLimitForUser,
|
||||
maxLimitForUser,
|
||||
exclusiveApply: exclusiveApply || false,
|
||||
}).returning();
|
||||
},
|
||||
applicableUsers,
|
||||
applicableProducts
|
||||
);
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB query with transaction:
|
||||
const result = await db.insert(coupons).values({
|
||||
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,
|
||||
}).returning();
|
||||
|
||||
const coupon = result[0];
|
||||
|
||||
|
|
@ -120,6 +151,7 @@ export const couponRouter = router({
|
|||
}))
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
return coupon;
|
||||
}),
|
||||
|
|
@ -130,71 +162,22 @@ export const couponRouter = router({
|
|||
limit: z.number().default(50),
|
||||
search: z.string().optional(),
|
||||
}))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input }): Promise<{ coupons: any[]; nextCursor?: number }> => {
|
||||
const { cursor, limit, search } = input;
|
||||
|
||||
let whereCondition = undefined;
|
||||
const conditions = [];
|
||||
const { coupons: couponsList, hasMore } = await getAllCouponsFromDb(cursor, limit, search);
|
||||
|
||||
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;
|
||||
const nextCursor = hasMore ? couponsList[couponsList.length - 1].id : undefined;
|
||||
|
||||
return { coupons: couponsList, nextCursor };
|
||||
}),
|
||||
|
||||
getById: protectedProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input }): Promise<any> => {
|
||||
const couponId = input.id;
|
||||
|
||||
const result = await db.query.coupons.findFirst({
|
||||
where: eq(coupons.id, couponId),
|
||||
with: {
|
||||
creator: true,
|
||||
applicableUsers: {
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
applicableProducts: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = await getCouponByIdFromDb(couponId);
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Coupon not found");
|
||||
|
|
@ -203,8 +186,8 @@ export const couponRouter = router({
|
|||
return {
|
||||
...result,
|
||||
productIds: (result.productIds as number[]) || undefined,
|
||||
applicableUsers: result.applicableUsers.map(au => au.user),
|
||||
applicableProducts: result.applicableProducts.map(ap => ap.product),
|
||||
applicableUsers: result.applicableUsers.map((au: any) => au.user),
|
||||
applicableProducts: result.applicableProducts.map((ap: any) => ap.product),
|
||||
};
|
||||
}),
|
||||
|
||||
|
|
@ -215,7 +198,7 @@ export const couponRouter = router({
|
|||
isInvalidated: z.boolean().optional(),
|
||||
}),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input }): Promise<Coupon> => {
|
||||
const { id, updates } = input;
|
||||
|
||||
// Validation: ensure discount types are valid
|
||||
|
|
@ -225,43 +208,31 @@ export const couponRouter = router({
|
|||
}
|
||||
}
|
||||
|
||||
// If updating to user-based, applicableUsers is required
|
||||
if (updates.isUserBased && (!updates.applicableUsers || updates.applicableUsers.length === 0)) {
|
||||
const existingCount = await db.$count(couponApplicableUsers, eq(couponApplicableUsers.couponId, id));
|
||||
if (existingCount === 0) {
|
||||
throw new Error("applicableUsers is required for user-based coupons");
|
||||
}
|
||||
}
|
||||
// Prepare update data
|
||||
const updateData: any = {};
|
||||
if (updates.couponCode !== undefined) updateData.couponCode = updates.couponCode;
|
||||
if (updates.isUserBased !== undefined) updateData.isUserBased = updates.isUserBased;
|
||||
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.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;
|
||||
|
||||
// If applicableUsers is provided, verify users exist
|
||||
if (updates.applicableUsers && updates.applicableUsers.length > 0) {
|
||||
const existingUsers = await db.query.users.findMany({
|
||||
where: inArray(users.id, updates.applicableUsers),
|
||||
columns: { id: true },
|
||||
});
|
||||
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;
|
||||
}
|
||||
// Using dbService helper (new implementation)
|
||||
const coupon = await updateCouponWithRelations(
|
||||
id,
|
||||
updateData,
|
||||
updates.applicableUsers,
|
||||
updates.applicableProducts
|
||||
);
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB query:
|
||||
const result = await db.update(coupons)
|
||||
.set(updateData)
|
||||
.where(eq(coupons.id, id))
|
||||
|
|
@ -271,8 +242,6 @@ export const couponRouter = router({
|
|||
throw new Error("Coupon not found");
|
||||
}
|
||||
|
||||
console.log('updated coupon successfully')
|
||||
|
||||
// Update applicable users: delete existing and insert new
|
||||
if (updates.applicableUsers !== undefined) {
|
||||
await db.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, id));
|
||||
|
|
@ -298,246 +267,43 @@ export const couponRouter = router({
|
|||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return result[0];
|
||||
return coupon;
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ input }): Promise<{ message: string }> => {
|
||||
const { id } = input;
|
||||
|
||||
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");
|
||||
}
|
||||
await invalidateCouponInDb(id);
|
||||
|
||||
return { message: "Coupon invalidated successfully" };
|
||||
}),
|
||||
|
||||
validate: protectedProcedure
|
||||
.input(validateCouponBodySchema)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ input }): Promise<CouponValidationResult> => {
|
||||
const { code, userId, orderAmount } = input;
|
||||
|
||||
if (!code || typeof code !== 'string') {
|
||||
return { valid: false, message: "Invalid coupon code" };
|
||||
}
|
||||
|
||||
const coupon = await db.query.coupons.findFirst({
|
||||
where: and(
|
||||
eq(coupons.couponCode, code.toUpperCase()),
|
||||
eq(coupons.isInvalidated, false)
|
||||
),
|
||||
});
|
||||
const result = await validateCouponInDb(code, userId, orderAmount);
|
||||
|
||||
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
|
||||
.input(
|
||||
z.object({
|
||||
orderId: z.number(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { orderId } = input;
|
||||
|
||||
// Get staff user ID from auth middleware
|
||||
const staffUserId = ctx.staffUser?.id;
|
||||
if (!staffUserId) {
|
||||
throw new Error("Unauthorized");
|
||||
}
|
||||
|
||||
// Find the order with user and order status information
|
||||
const order = await db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
user: true,
|
||||
orderStatus: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!order) {
|
||||
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) {
|
||||
throw new Error("User not found for this order");
|
||||
}
|
||||
|
||||
// Generate coupon code: first 3 letters of user name or mobile + orderId
|
||||
const userNamePrefix = (order.user.name || order.user.mobile || 'USR').substring(0, 3).toUpperCase();
|
||||
const couponCode = `${userNamePrefix}${orderId}`;
|
||||
|
||||
// Check if coupon code already exists
|
||||
const existingCoupon = await db.query.coupons.findFirst({
|
||||
where: eq(coupons.couponCode, couponCode),
|
||||
});
|
||||
|
||||
if (existingCoupon) {
|
||||
throw new Error("Coupon code already exists");
|
||||
}
|
||||
|
||||
// Get order total amount
|
||||
const orderAmount = parseFloat(order.totalAmount);
|
||||
|
||||
// Calculate expiry date (30 days from now)
|
||||
const expiryDate = new Date();
|
||||
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
|
||||
const result = 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();
|
||||
|
||||
const coupon = result[0];
|
||||
|
||||
// Insert applicable users
|
||||
await tx.insert(couponApplicableUsers).values({
|
||||
couponId: coupon.id,
|
||||
userId: order.userId,
|
||||
});
|
||||
|
||||
// Update order_status with refund coupon ID
|
||||
await tx.update(orderStatus)
|
||||
.set({ refundCouponId: coupon.id })
|
||||
.where(eq(orderStatus.orderId, orderId));
|
||||
|
||||
return coupon;
|
||||
});
|
||||
|
||||
return coupon;
|
||||
}),
|
||||
|
||||
getReservedCoupons: protectedProcedure
|
||||
.input(z.object({
|
||||
cursor: z.number().optional(),
|
||||
limit: z.number().default(50),
|
||||
search: z.string().optional(),
|
||||
}))
|
||||
.query(async ({ input }) => {
|
||||
const { cursor, limit, search } = input;
|
||||
|
||||
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;
|
||||
|
||||
return {
|
||||
coupons,
|
||||
nextCursor,
|
||||
};
|
||||
return result;
|
||||
}),
|
||||
|
||||
createReservedCoupon: protectedProcedure
|
||||
.input(createCouponBodySchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||
|
||||
// Validation: ensure at least one discount type is provided
|
||||
if ((!discountPercent && !flatDiscount) || (discountPercent && flatDiscount)) {
|
||||
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
|
||||
generateCancellationCoupon: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
orderId: z.number(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input, ctx }): Promise<Coupon> => {
|
||||
const { orderId } = input;
|
||||
|
||||
// Get staff user ID from auth middleware
|
||||
const staffUserId = ctx.staffUser?.id;
|
||||
|
|
@ -545,21 +311,144 @@ export const couponRouter = router({
|
|||
throw new Error("Unauthorized");
|
||||
}
|
||||
|
||||
// Generate secret code if not provided (use couponCode as base)
|
||||
// Using dbService helper (new implementation)
|
||||
const order = await getOrderWithUser(orderId);
|
||||
|
||||
if (!order) {
|
||||
throw new Error("Order not found");
|
||||
}
|
||||
|
||||
if (!order.user) {
|
||||
throw new Error("User not found for this order");
|
||||
}
|
||||
|
||||
// Generate coupon code: first 3 letters of user name or mobile + orderId
|
||||
const userNamePrefix = (order.user.name || order.user.mobile || 'USR').substring(0, 3).toUpperCase();
|
||||
const couponCode = `${userNamePrefix}${orderId}`;
|
||||
|
||||
// Check if coupon code already exists
|
||||
const codeExists = await checkCouponExists(couponCode);
|
||||
if (codeExists) {
|
||||
throw new Error("Coupon code already exists");
|
||||
}
|
||||
|
||||
// Get order total amount
|
||||
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)
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setDate(expiryDate.getDate() + 30);
|
||||
|
||||
// Create the coupon
|
||||
const result = 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();
|
||||
|
||||
const coupon = result[0];
|
||||
|
||||
// Insert applicable users
|
||||
await tx.insert(couponApplicableUsers).values({
|
||||
couponId: coupon.id,
|
||||
userId: order.userId,
|
||||
});
|
||||
|
||||
// Update order_status with refund coupon ID
|
||||
await tx.update(orderStatus)
|
||||
.set({ refundCouponId: coupon.id })
|
||||
.where(eq(orderStatus.orderId, orderId));
|
||||
|
||||
return coupon;
|
||||
});
|
||||
*/
|
||||
|
||||
return coupon;
|
||||
}),
|
||||
|
||||
getReservedCoupons: protectedProcedure
|
||||
.input(z.object({
|
||||
cursor: z.number().optional(),
|
||||
limit: z.number().default(50),
|
||||
search: z.string().optional(),
|
||||
}))
|
||||
.query(async ({ input }): Promise<{ coupons: any[]; nextCursor?: number }> => {
|
||||
const { cursor, limit, search } = input;
|
||||
|
||||
const { coupons: result, hasMore } = await getReservedCouponsFromDb(cursor, limit, search);
|
||||
|
||||
const nextCursor = hasMore ? result[result.length - 1].id : undefined;
|
||||
|
||||
return {
|
||||
coupons: result,
|
||||
nextCursor,
|
||||
};
|
||||
}),
|
||||
|
||||
createReservedCoupon: protectedProcedure
|
||||
.input(createCouponBodySchema)
|
||||
.mutation(async ({ input, ctx }): Promise<any> => {
|
||||
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, productIds, applicableProducts, maxValue, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||
|
||||
// Validation: ensure at least one discount type is provided
|
||||
if ((!discountPercent && !flatDiscount) || (discountPercent && flatDiscount)) {
|
||||
throw new Error("Either discountPercent or flatDiscount must be provided (but not both)");
|
||||
}
|
||||
|
||||
// Get staff user ID from auth middleware
|
||||
const staffUserId = ctx.staffUser?.id;
|
||||
if (!staffUserId) {
|
||||
throw new Error("Unauthorized");
|
||||
}
|
||||
|
||||
// Generate secret code if not provided
|
||||
let secretCode = couponCode || `SECRET${Date.now().toString().slice(-6)}${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
|
||||
|
||||
// Check if secret code already exists
|
||||
const existing = await db.query.reservedCoupons.findFirst({
|
||||
where: eq(reservedCoupons.secretCode, secretCode),
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
// Using dbService helper (new implementation)
|
||||
const codeExists = await checkReservedCouponExists(secretCode);
|
||||
if (codeExists) {
|
||||
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({
|
||||
secretCode,
|
||||
couponCode: couponCode || `RESERVED${Date.now().toString().slice(-6)}`,
|
||||
couponCode: couponCode || RESERVED${Date.now().toString().slice(-6)},
|
||||
discountPercent: discountPercent?.toString(),
|
||||
flatDiscount: flatDiscount?.toString(),
|
||||
minOrder: minOrder?.toString(),
|
||||
|
|
@ -582,6 +471,7 @@ export const couponRouter = router({
|
|||
}))
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
return coupon;
|
||||
}),
|
||||
|
|
@ -592,120 +482,97 @@ export const couponRouter = router({
|
|||
limit: z.number().min(1).max(50).default(20),
|
||||
offset: z.number().min(0).default(0),
|
||||
}))
|
||||
.query(async ({ input }) => {
|
||||
const { search, limit } = input;
|
||||
.query(async ({ input }): Promise<{ users: UserMiniInfo[] }> => {
|
||||
const { search, limit, offset } = input;
|
||||
|
||||
let whereCondition = undefined;
|
||||
if (search && search.trim()) {
|
||||
whereCondition = or(
|
||||
like(users.name, `%${search}%`),
|
||||
like(users.mobile, `%${search}%`)
|
||||
);
|
||||
}
|
||||
const result = await getUsersForCouponFromDb(search, limit, offset);
|
||||
|
||||
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,
|
||||
}))
|
||||
};
|
||||
return result;
|
||||
}),
|
||||
|
||||
createCoupon: protectedProcedure
|
||||
.input(z.object({
|
||||
mobile: z.string().min(1, 'Mobile number is required'),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { mobile } = input;
|
||||
createCoupon: protectedProcedure
|
||||
.input(z.object({
|
||||
mobile: z.string().min(1, 'Mobile number is required'),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }): Promise<{ success: boolean; coupon: any }> => {
|
||||
const { mobile } = input;
|
||||
|
||||
// Get staff user ID from auth middleware
|
||||
const staffUserId = ctx.staffUser?.id;
|
||||
if (!staffUserId) {
|
||||
throw new Error("Unauthorized");
|
||||
}
|
||||
// Get staff user ID from auth middleware
|
||||
const staffUserId = ctx.staffUser?.id;
|
||||
if (!staffUserId) {
|
||||
throw new Error("Unauthorized");
|
||||
}
|
||||
|
||||
// Clean mobile number (remove non-digits)
|
||||
const cleanMobile = mobile.replace(/\D/g, '');
|
||||
// Clean mobile number (remove non-digits)
|
||||
const cleanMobile = mobile.replace(/\D/g, '');
|
||||
|
||||
// Validate: exactly 10 digits
|
||||
if (cleanMobile.length !== 10) {
|
||||
throw new Error("Mobile number must be exactly 10 digits");
|
||||
}
|
||||
// Validate: exactly 10 digits
|
||||
if (cleanMobile.length !== 10) {
|
||||
throw new Error("Mobile number must be exactly 10 digits");
|
||||
}
|
||||
|
||||
// Check if user exists, create if not
|
||||
let user = await db.query.users.findFirst({
|
||||
where: eq(users.mobile, cleanMobile),
|
||||
});
|
||||
// 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}`;
|
||||
|
||||
if (!user) {
|
||||
// Create new user
|
||||
const [newUser] = await db.insert(users).values({
|
||||
name: null,
|
||||
email: null,
|
||||
mobile: cleanMobile,
|
||||
}).returning();
|
||||
user = newUser;
|
||||
}
|
||||
// Using dbService helper (new implementation)
|
||||
const codeExists = await checkCouponExists(couponCode);
|
||||
if (codeExists) {
|
||||
throw new Error("Generated coupon code already exists - please try again");
|
||||
}
|
||||
|
||||
// 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}`;
|
||||
const { coupon, user } = await createCouponForUser(cleanMobile, couponCode, staffUserId);
|
||||
|
||||
// Check if coupon code already exists (very unlikely but safe)
|
||||
const existingCode = await db.query.coupons.findFirst({
|
||||
where: eq(coupons.couponCode, couponCode),
|
||||
});
|
||||
/*
|
||||
// Old implementation - direct DB query with transaction:
|
||||
// Check if user exists, create if not
|
||||
let user = await db.query.users.findFirst({
|
||||
where: eq(users.mobile, cleanMobile),
|
||||
});
|
||||
|
||||
if (existingCode) {
|
||||
throw new Error("Generated coupon code already exists - please try again");
|
||||
}
|
||||
|
||||
// Create the coupon
|
||||
const [coupon] = await db.insert(coupons).values({
|
||||
couponCode,
|
||||
isUserBased: true,
|
||||
discountPercent: "20", // 20% discount
|
||||
minOrder: "1000", // ₹1000 minimum order
|
||||
maxValue: "500", // ₹500 maximum discount
|
||||
maxLimitForUser: 1, // One-time use
|
||||
isApplyForAll: false,
|
||||
exclusiveApply: false,
|
||||
createdBy: staffUserId,
|
||||
validTill: dayjs().add(90, 'days').toDate(), // 90 days from now
|
||||
if (!user) {
|
||||
const [newUser] = await db.insert(users).values({
|
||||
name: null,
|
||||
email: null,
|
||||
mobile: cleanMobile,
|
||||
}).returning();
|
||||
user = newUser;
|
||||
}
|
||||
|
||||
// Associate coupon with user
|
||||
await db.insert(couponApplicableUsers).values({
|
||||
couponId: coupon.id,
|
||||
// Create the coupon
|
||||
const [coupon] = await db.insert(coupons).values({
|
||||
couponCode,
|
||||
isUserBased: true,
|
||||
discountPercent: "20",
|
||||
minOrder: "1000",
|
||||
maxValue: "500",
|
||||
maxLimitForUser: 1,
|
||||
isApplyForAll: false,
|
||||
exclusiveApply: false,
|
||||
createdBy: staffUserId,
|
||||
validTill: dayjs().add(90, 'days').toDate(),
|
||||
}).returning();
|
||||
|
||||
// Associate coupon with user
|
||||
await db.insert(couponApplicableUsers).values({
|
||||
couponId: coupon.id,
|
||||
userId: user.id,
|
||||
});
|
||||
*/
|
||||
|
||||
return {
|
||||
success: true,
|
||||
coupon: {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
coupon: {
|
||||
id: coupon.id,
|
||||
couponCode: coupon.couponCode,
|
||||
userId: user.id,
|
||||
userMobile: user.mobile,
|
||||
discountPercent: 20,
|
||||
minOrder: 1000,
|
||||
maxValue: 500,
|
||||
maxLimitForUser: 1,
|
||||
},
|
||||
};
|
||||
}),
|
||||
userMobile: user.mobile,
|
||||
discountPercent: 20,
|
||||
minOrder: 1000,
|
||||
maxValue: 500,
|
||||
maxLimitForUser: 1,
|
||||
},
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,3 +9,6 @@ export * from './src/db/schema';
|
|||
|
||||
// Re-export helper methods
|
||||
export * from './src/helper_methods/banner';
|
||||
export * from './src/helper_methods/complaint';
|
||||
export * from './src/helper_methods/const';
|
||||
export * from './src/helper_methods/coupon';
|
||||
|
|
|
|||
74
packages/db_helper_postgres/src/helper_methods/complaint.ts
Normal file
74
packages/db_helper_postgres/src/helper_methods/complaint.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { complaints, users } from '../db/schema';
|
||||
import { eq, desc, lt } from 'drizzle-orm';
|
||||
|
||||
export interface Complaint {
|
||||
id: number;
|
||||
complaintBody: string;
|
||||
userId: number;
|
||||
orderId: number | null;
|
||||
isResolved: boolean;
|
||||
response: string | null;
|
||||
createdAt: Date;
|
||||
images: string[] | null;
|
||||
}
|
||||
|
||||
export interface ComplaintWithUser extends Complaint {
|
||||
userName: string | null;
|
||||
userMobile: string | null;
|
||||
}
|
||||
|
||||
export async function getComplaints(
|
||||
cursor?: number,
|
||||
limit: number = 20
|
||||
): Promise<{ complaints: ComplaintWithUser[]; hasMore: boolean }> {
|
||||
let whereCondition = cursor ? lt(complaints.id, cursor) : undefined;
|
||||
|
||||
const complaintsData = await db
|
||||
.select({
|
||||
id: complaints.id,
|
||||
complaintBody: complaints.complaintBody,
|
||||
userId: complaints.userId,
|
||||
orderId: complaints.orderId,
|
||||
isResolved: complaints.isResolved,
|
||||
response: complaints.response,
|
||||
createdAt: complaints.createdAt,
|
||||
images: complaints.images,
|
||||
userName: users.name,
|
||||
userMobile: users.mobile,
|
||||
})
|
||||
.from(complaints)
|
||||
.leftJoin(users, eq(complaints.userId, users.id))
|
||||
.where(whereCondition)
|
||||
.orderBy(desc(complaints.id))
|
||||
.limit(limit + 1);
|
||||
|
||||
const hasMore = complaintsData.length > limit;
|
||||
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
||||
|
||||
return {
|
||||
complaints: complaintsToReturn.map((c) => ({
|
||||
id: c.id,
|
||||
complaintBody: c.complaintBody,
|
||||
userId: c.userId,
|
||||
orderId: c.orderId,
|
||||
isResolved: c.isResolved,
|
||||
response: c.response,
|
||||
createdAt: c.createdAt,
|
||||
images: c.images,
|
||||
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));
|
||||
}
|
||||
29
packages/db_helper_postgres/src/helper_methods/const.ts
Normal file
29
packages/db_helper_postgres/src/helper_methods/const.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { db } from '../db/db_index';
|
||||
import { keyValStore } from '../db/schema';
|
||||
|
||||
export interface Constant {
|
||||
key: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export async function getAllConstants(): Promise<Constant[]> {
|
||||
const constants = await db.select().from(keyValStore);
|
||||
|
||||
return constants.map(c => ({
|
||||
key: c.key,
|
||||
value: c.value,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function upsertConstants(constants: Constant[]): Promise<void> {
|
||||
await db.transaction(async (tx) => {
|
||||
for (const { key, value } of constants) {
|
||||
await tx.insert(keyValStore)
|
||||
.values({ key, value })
|
||||
.onConflictDoUpdate({
|
||||
target: keyValStore.key,
|
||||
set: { value },
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
633
packages/db_helper_postgres/src/helper_methods/coupon.ts
Normal file
633
packages/db_helper_postgres/src/helper_methods/coupon.ts
Normal file
|
|
@ -0,0 +1,633 @@
|
|||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
20
packages/shared/types/complaint.types.ts
Normal file
20
packages/shared/types/complaint.types.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
15
packages/shared/types/const.types.ts
Normal file
15
packages/shared/types/const.types.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* 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[];
|
||||
}
|
||||
41
packages/shared/types/coupon.types.ts
Normal file
41
packages/shared/types/coupon.types.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* 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,3 +2,6 @@
|
|||
// Re-export all types from the types folder
|
||||
|
||||
export type { Banner } from './banner.types';
|
||||
export type { Complaint, ComplaintWithUser } from './complaint.types';
|
||||
export type { Constant, ConstantUpdateResult } from './const.types';
|
||||
export type { Coupon, ReservedCoupon, CouponValidationResult, UserMiniInfo } from './coupon.types';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue