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