Package inquiry, UAT related update

This commit is contained in:
Stephanos A
2026-06-26 13:57:26 +03:00
parent eefbe6e8ed
commit 86c7b2e4eb
23 changed files with 648 additions and 93 deletions

View File

@@ -2,7 +2,7 @@ import { Body, Controller, Get, Param, Post, Patch, Delete, UseGuards, Request,
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator';
import { PackagesService } from './packages.service';
import { CreatePackageDto, BookPackageDto, CreatePriceTierDto, UpdatePriceTierDto } from './packages.dto';
import { CreatePackageDto, BookPackageDto, CreatePriceTierDto, UpdatePriceTierDto, CreateInquiryDto, UpdateInquiryStatusDto } from './packages.dto';
import { IamGuard } from '../../common/iam-adapter';
import { JwtGuard } from '../../common/jwt.guard';
import { OptionalJwtGuard } from '../verifayda/optional-jwt.guard';
@@ -12,6 +12,42 @@ import { OptionalJwtGuard } from '../verifayda/optional-jwt.guard';
export class PackagesController {
constructor(private readonly service: PackagesService) {}
@Post('inquiries')
@IsPublic()
@ApiOperation({ summary: 'Submit a package inquiry (public)' })
createInquiry(@Body() dto: CreateInquiryDto) {
return this.service.createInquiry(dto);
}
@Get('inquiries')
@UseGuards(IamGuard)
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'List all inquiries (backoffice)' })
listInquiries(
@Query('packageId') packageId?: string,
@Query('status') status?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.service.listInquiries({ packageId, status, page: page ? +page : 1, pageSize: pageSize ? +pageSize : 20 });
}
@Patch('inquiries/:id/status')
@UseGuards(IamGuard)
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Update inquiry status (backoffice)' })
updateInquiryStatus(@Param('id') id: string, @Body() dto: UpdateInquiryStatusDto) {
return this.service.updateInquiryStatus(id, dto.status);
}
@Delete('inquiries/:id')
@UseGuards(IamGuard)
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Delete inquiry (backoffice)' })
deleteInquiry(@Param('id') id: string) {
return this.service.deleteInquiry(id);
}
@Get()
@IsPublic()
@ApiOperation({ summary: 'List active packages' })

View File

@@ -16,6 +16,20 @@ export class CreatePriceTierDto {
@IsInt() @Min(0) availableSeats: number;
}
export class CreateInquiryDto {
@ApiProperty() @IsUUID() packageId: string;
@ApiPropertyOptional() @IsOptional() @IsUUID() priceTierId?: string;
@ApiProperty({ example: 2 }) @IsInt() @Min(1) travelerCount: number;
@ApiProperty() @IsString() contactName: string;
@ApiPropertyOptional() @IsOptional() @IsString() contactEmail?: string;
@ApiPropertyOptional() @IsOptional() @IsString() contactPhone?: string;
@ApiPropertyOptional() @IsOptional() @IsString() notes?: string;
}
export class UpdateInquiryStatusDto {
@ApiProperty({ example: 'CONTACTED' }) @IsString() status: string;
}
export class UpdatePriceTierDto {
@ApiPropertyOptional() @IsOptional() @IsString() seatType?: string;
@ApiPropertyOptional() @IsOptional() @IsString() label?: string;

View File

@@ -1,7 +1,7 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { CurrencyService } from '../currency/currency.service';
import { CreatePackageDto, BookPackageDto, UpdatePriceTierDto, CreatePriceTierDto } from './packages.dto';
import { CreatePackageDto, BookPackageDto, UpdatePriceTierDto, CreatePriceTierDto, CreateInquiryDto } from './packages.dto';
import { Currency } from '@prisma/client';
function generateRef(): string {
@@ -17,6 +17,53 @@ export class PackagesService {
private readonly currencyService: CurrencyService,
) {}
async createInquiry(dto: CreateInquiryDto) {
return this.prisma.packageInquiry.create({
data: {
packageId: dto.packageId,
priceTierId: dto.priceTierId ?? null,
travelerCount: dto.travelerCount,
contactName: dto.contactName,
contactEmail: dto.contactEmail ?? null,
contactPhone: dto.contactPhone ?? null,
notes: dto.notes ?? null,
enquiredAt: new Date(),
},
include: { package: { select: { id: true, name: true, code: true } }, priceTier: { select: { id: true, label: true } } },
});
}
async listInquiries({ packageId, status, page = 1, pageSize = 20 }: { packageId?: string; status?: string; page?: number; pageSize?: number }) {
const where: any = {};
if (packageId) where.packageId = packageId;
if (status) where.status = status;
const skip = (page - 1) * pageSize;
const [items, total] = await Promise.all([
this.prisma.packageInquiry.findMany({
where,
include: { package: { select: { id: true, name: true, code: true } }, priceTier: { select: { id: true, label: true, priceMinor: true } } },
orderBy: { enquiredAt: 'desc' },
skip,
take: pageSize,
}),
this.prisma.packageInquiry.count({ where }),
]);
return { items, total, page, pageSize };
}
async updateInquiryStatus(id: string, status: string) {
const inquiry = await this.prisma.packageInquiry.findUnique({ where: { id } });
if (!inquiry) throw new NotFoundException('Inquiry not found');
return this.prisma.packageInquiry.update({ where: { id }, data: { status } });
}
async deleteInquiry(id: string) {
const inquiry = await this.prisma.packageInquiry.findUnique({ where: { id } });
if (!inquiry) throw new NotFoundException('Inquiry not found');
await this.prisma.packageInquiry.delete({ where: { id } });
return { deleted: true };
}
listActive() {
const now = new Date();
return this.prisma.travelPackage.findMany({