112 lines
4.9 KiB
TypeScript
112 lines
4.9 KiB
TypeScript
import { Router } from 'express'
|
|
import { z } from 'zod'
|
|
import { prisma } from '../lib/prisma'
|
|
import { requireCompanyAuth } from '../middleware/requireCompanyAuth'
|
|
import { requireTenant } from '../middleware/requireTenant'
|
|
import { requireSubscription } from '../middleware/requireSubscription'
|
|
import { requireRole } from '../middleware/requireRole'
|
|
|
|
const router = Router()
|
|
router.use(requireCompanyAuth, requireTenant, requireSubscription)
|
|
|
|
const offerSchema = z.object({
|
|
title: z.string().min(1),
|
|
description: z.string().optional(),
|
|
termsAndConds: z.string().optional(),
|
|
type: z.enum(['PERCENTAGE','FIXED_AMOUNT','FREE_DAY','SPECIAL_RATE']),
|
|
discountValue: z.number().int().min(0),
|
|
specialRate: z.number().int().optional(),
|
|
appliesToAll: z.boolean().default(true),
|
|
categories: z.array(z.enum(['ECONOMY','COMPACT','MIDSIZE','FULLSIZE','SUV','LUXURY','VAN','TRUCK'])).default([]),
|
|
minRentalDays: z.number().int().optional(),
|
|
maxRentalDays: z.number().int().optional(),
|
|
promoCode: z.string().optional(),
|
|
maxRedemptions: z.number().int().optional(),
|
|
validFrom: z.string().datetime(),
|
|
validUntil: z.string().datetime(),
|
|
isActive: z.boolean().default(true),
|
|
isPublic: z.boolean().default(true),
|
|
isFeatured: z.boolean().default(false),
|
|
vehicleIds: z.array(z.string()).default([]),
|
|
})
|
|
|
|
router.get('/', async (req, res, next) => {
|
|
try {
|
|
const { active, public: pub } = req.query as Record<string, string>
|
|
const where: any = { companyId: req.companyId }
|
|
if (active !== undefined) where.isActive = active === 'true'
|
|
if (pub !== undefined) where.isPublic = pub === 'true'
|
|
const offers = await prisma.offer.findMany({ where, orderBy: { createdAt: 'desc' } })
|
|
res.json({ data: offers })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/', requireRole('MANAGER'), async (req, res, next) => {
|
|
try {
|
|
const { vehicleIds, ...body } = offerSchema.parse(req.body)
|
|
const offer = await prisma.offer.create({
|
|
data: { ...body, companyId: req.companyId, validFrom: new Date(body.validFrom), validUntil: new Date(body.validUntil), vehicles: vehicleIds.length > 0 ? { create: vehicleIds.map((id) => ({ vehicleId: id })) } : undefined },
|
|
include: { vehicles: true },
|
|
})
|
|
res.status(201).json({ data: offer })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.get('/:id', async (req, res, next) => {
|
|
try {
|
|
const offer = await prisma.offer.findFirstOrThrow({ where: { id: req.params.id, companyId: req.companyId }, include: { vehicles: true } })
|
|
res.json({ data: offer })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.patch('/:id', requireRole('MANAGER'), async (req, res, next) => {
|
|
try {
|
|
const { id } = z.object({ id: z.string() }).parse(req.params)
|
|
const { vehicleIds, ...body } = offerSchema.partial().parse(req.body)
|
|
const existing = await prisma.offer.findFirst({ where: { id, companyId: req.companyId } })
|
|
if (!existing) return res.status(404).json({ error: 'not_found', message: 'Offer not found', statusCode: 404 })
|
|
await prisma.$transaction(async (tx) => {
|
|
await tx.offer.update({ where: { id }, data: { ...body, validFrom: body.validFrom ? new Date(body.validFrom) : undefined, validUntil: body.validUntil ? new Date(body.validUntil) : undefined } })
|
|
if (vehicleIds !== undefined) {
|
|
await tx.offerVehicle.deleteMany({ where: { offerId: id } })
|
|
if (vehicleIds.length > 0) {
|
|
await tx.offerVehicle.createMany({ data: vehicleIds.map((vehicleId) => ({ offerId: id, vehicleId })) })
|
|
}
|
|
}
|
|
})
|
|
const updated = await prisma.offer.findUniqueOrThrow({ where: { id }, include: { vehicles: true } })
|
|
res.json({ data: updated })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.delete('/:id', requireRole('MANAGER'), async (req, res, next) => {
|
|
try {
|
|
await prisma.offer.deleteMany({ where: { id: req.params.id, companyId: req.companyId } })
|
|
res.json({ data: { success: true } })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/:id/activate', requireRole('MANAGER'), async (req, res, next) => {
|
|
try {
|
|
await prisma.offer.updateMany({ where: { id: req.params.id, companyId: req.companyId }, data: { isActive: true } })
|
|
res.json({ data: { success: true } })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/:id/deactivate', requireRole('MANAGER'), async (req, res, next) => {
|
|
try {
|
|
await prisma.offer.updateMany({ where: { id: req.params.id, companyId: req.companyId }, data: { isActive: false } })
|
|
res.json({ data: { success: true } })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.get('/:id/stats', async (req, res, next) => {
|
|
try {
|
|
const offer = await prisma.offer.findFirstOrThrow({ where: { id: req.params.id, companyId: req.companyId } })
|
|
const reservations = await prisma.reservation.count({ where: { offerId: offer.id } })
|
|
res.json({ data: { redemptions: offer.redemptionCount, reservations } })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
export default router
|