mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 18:48:11 +00:00
191 lines
6.4 KiB
TypeScript
191 lines
6.4 KiB
TypeScript
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
|
|
import { PrismaService } from '../../common/prisma.service';
|
|
import { CreatePromotionDto } from './promos.dto';
|
|
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
|
|
|
|
@Injectable()
|
|
export class PromosService {
|
|
constructor(private prisma: PrismaService) {}
|
|
|
|
getActive() {
|
|
return this.prisma.promotion.findMany({
|
|
where: { active: true, validUntil: { gte: new Date() } },
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
}
|
|
|
|
async getAll(filters: { search?: string; active?: boolean; page?: number; pageSize?: number }) {
|
|
const { search, active, page = 1, pageSize = 10 } = filters;
|
|
const skip = (page - 1) * pageSize;
|
|
|
|
const where: any = {};
|
|
if (search) {
|
|
where.OR = [
|
|
{ code: { contains: search, mode: 'insensitive' } },
|
|
{ title: { contains: search, mode: 'insensitive' } },
|
|
];
|
|
}
|
|
if (active !== undefined) {
|
|
where.active = active;
|
|
}
|
|
|
|
const [items, total] = await Promise.all([
|
|
this.prisma.promotion.findMany({
|
|
where,
|
|
skip,
|
|
take: pageSize,
|
|
orderBy: { createdAt: 'desc' },
|
|
}),
|
|
this.prisma.promotion.count({ where }),
|
|
]);
|
|
|
|
return { items: this.formatItems(items), total, page, pageSize };
|
|
}
|
|
|
|
async getById(id: string) {
|
|
const promo = await this.prisma.promotion.findUnique({ where: { id } });
|
|
if (!promo) throw new NotFoundException('Promo not found');
|
|
return this.formatItem(promo);
|
|
}
|
|
|
|
async validate(code: string) {
|
|
const promo = await this.prisma.promotion.findUnique({ where: { code } });
|
|
if (!promo || !promo.active || promo.validUntil < new Date())
|
|
return { applicable: false, message: 'Promo code invalid or expired' };
|
|
return {
|
|
code: promo.code,
|
|
percentOff: promo.percentOff,
|
|
amountOffMinor: promo.amountOffMinor,
|
|
validUntil: promo.validUntil,
|
|
applicable: true,
|
|
message: promo.percentOff
|
|
? `${promo.percentOff}% off`
|
|
: `ETB ${((promo.amountOffMinor ?? 0) / 100).toFixed(2)} off`,
|
|
};
|
|
}
|
|
|
|
async create(dto: CreatePromotionDto & { discountType?: string; discountValue?: number }) {
|
|
try {
|
|
// Map frontend fields to database fields
|
|
let percentOff: number | undefined;
|
|
let amountOffMinor: number | undefined;
|
|
|
|
if (dto.discountType && dto.discountValue !== undefined) {
|
|
if (dto.discountType === 'PERCENTAGE') {
|
|
percentOff = dto.discountValue;
|
|
} else if (dto.discountType === 'FIXED') {
|
|
amountOffMinor = dto.discountValue;
|
|
}
|
|
} else {
|
|
// Fallback to direct fields
|
|
percentOff = dto.percentOff;
|
|
amountOffMinor = dto.amountOffMinor;
|
|
}
|
|
|
|
const promo = await this.prisma.promotion.create({
|
|
data: {
|
|
code: dto.code,
|
|
title: dto.title,
|
|
subtitle: dto.subtitle,
|
|
percentOff,
|
|
amountOffMinor,
|
|
validUntil: new Date(dto.validUntil),
|
|
ctaLabel: dto.ctaLabel,
|
|
deepLink: dto.deepLink,
|
|
active: dto.active ?? true,
|
|
},
|
|
});
|
|
return this.formatItem(promo);
|
|
} catch (error) {
|
|
if (error instanceof PrismaClientKnownRequestError) {
|
|
if (error.code === 'P2002') {
|
|
const field = (error.meta?.target as string[])?.[0];
|
|
throw new BadRequestException(
|
|
`A promo code with this ${field} already exists. Please use a different ${field}.`,
|
|
);
|
|
}
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async update(id: string, dto: Partial<CreatePromotionDto> & { discountType?: string; discountValue?: number }) {
|
|
const promo = await this.prisma.promotion.findUnique({ where: { id } });
|
|
if (!promo) throw new NotFoundException('Promo not found');
|
|
|
|
const updateData: any = {};
|
|
|
|
// Map frontend fields to database fields
|
|
if (dto.discountType && dto.discountValue !== undefined) {
|
|
// Clear existing discount fields
|
|
updateData.percentOff = null;
|
|
updateData.amountOffMinor = null;
|
|
|
|
if (dto.discountType === 'PERCENTAGE') {
|
|
updateData.percentOff = dto.discountValue;
|
|
} else if (dto.discountType === 'FIXED') {
|
|
updateData.amountOffMinor = dto.discountValue;
|
|
}
|
|
} else {
|
|
// Only include fields that are explicitly provided
|
|
if (dto.percentOff !== undefined) updateData.percentOff = dto.percentOff;
|
|
if (dto.amountOffMinor !== undefined) updateData.amountOffMinor = dto.amountOffMinor;
|
|
}
|
|
|
|
if (dto.title !== undefined) updateData.title = dto.title;
|
|
if (dto.subtitle !== undefined) updateData.subtitle = dto.subtitle;
|
|
if (dto.ctaLabel !== undefined) updateData.ctaLabel = dto.ctaLabel;
|
|
if (dto.deepLink !== undefined) updateData.deepLink = dto.deepLink;
|
|
if (dto.active !== undefined) updateData.active = dto.active;
|
|
if (dto.validUntil !== undefined) updateData.validUntil = new Date(dto.validUntil);
|
|
|
|
// Don't allow updating code - it's immutable after creation
|
|
|
|
try {
|
|
const updated = await this.prisma.promotion.update({
|
|
where: { id },
|
|
data: updateData,
|
|
});
|
|
return this.formatItem(updated);
|
|
} catch (error) {
|
|
if (error instanceof PrismaClientKnownRequestError && error.code === 'P2002') {
|
|
const field = (error.meta?.target as string[])?.[0];
|
|
throw new BadRequestException(
|
|
`A promo code with this ${field} already exists. Please use a different ${field}.`,
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async delete(id: string) {
|
|
const promo = await this.prisma.promotion.findUnique({ where: { id } });
|
|
if (!promo) throw new NotFoundException('Promo not found');
|
|
return this.prisma.promotion.delete({ where: { id } });
|
|
}
|
|
|
|
private formatItem(promo: any) {
|
|
return {
|
|
id: promo.id,
|
|
code: promo.code,
|
|
title: promo.title,
|
|
discountType: promo.percentOff ? 'PERCENTAGE' : 'FIXED',
|
|
discountValue: promo.percentOff || promo.amountOffMinor || 0,
|
|
maxDiscount: undefined,
|
|
minBookingAmount: undefined,
|
|
maxUsagePerUser: undefined,
|
|
totalUsageLimit: undefined,
|
|
usageCount: 0,
|
|
validFrom: promo.createdAt,
|
|
validUntil: promo.validUntil,
|
|
isActive: promo.active,
|
|
createdAt: promo.createdAt,
|
|
updatedAt: promo.createdAt,
|
|
};
|
|
}
|
|
|
|
private formatItems(promos: any[]) {
|
|
return promos.map((promo) => this.formatItem(promo));
|
|
}
|
|
}
|