freshyo/packages/db_helper_postgres/src/admin-apis/coupon.ts
2026-03-25 18:11:46 +05:30

496 lines
12 KiB
TypeScript

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