mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 04:15:43 +00:00
allow customers booking
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
Patch,
|
Patch,
|
||||||
Post,
|
Post,
|
||||||
Query,
|
Query,
|
||||||
|
Request,
|
||||||
UploadedFiles,
|
UploadedFiles,
|
||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
@@ -52,6 +53,7 @@ export class BookingsController {
|
|||||||
create(
|
create(
|
||||||
@Body() dto: CreateBookingDto,
|
@Body() dto: CreateBookingDto,
|
||||||
@UploadedFiles() files: Express.Multer.File[],
|
@UploadedFiles() files: Express.Multer.File[],
|
||||||
|
@Request() req: any,
|
||||||
) {
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
"[BookingsController] Files received:",
|
"[BookingsController] Files received:",
|
||||||
@@ -63,7 +65,8 @@ export class BookingsController {
|
|||||||
mimetype: f.mimetype,
|
mimetype: f.mimetype,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
return this.bookingsService.create(dto, files ?? []);
|
const userId: string | undefined = req.user?.id ?? req.user?.sub;
|
||||||
|
return this.bookingsService.create(dto, files ?? [], userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 2. Update draft booking (multipart/form-data) ─────────────────────
|
// ── 2. Update draft booking (multipart/form-data) ─────────────────────
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
|
|
||||||
|
import { CustomersModule } from "../customers/customers.module";
|
||||||
import { FilesModule } from "../files/files.module";
|
import { FilesModule } from "../files/files.module";
|
||||||
import { MinioModule } from "../minio/minio.module";
|
import { MinioModule } from "../minio/minio.module";
|
||||||
import { BookingsController } from "./bookings.controller";
|
import { BookingsController } from "./bookings.controller";
|
||||||
@@ -9,7 +10,7 @@ import { BookingsService } from "./bookings.service";
|
|||||||
import { Booking } from "./entities/booking.entity";
|
import { Booking } from "./entities/booking.entity";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Booking]), FilesModule, MinioModule],
|
imports: [TypeOrmModule.forFeature([Booking]), FilesModule, MinioModule, CustomersModule],
|
||||||
controllers: [BookingsController],
|
controllers: [BookingsController],
|
||||||
providers: [BookingsService, BookingsRepository],
|
providers: [BookingsService, BookingsRepository],
|
||||||
exports: [BookingsService],
|
exports: [BookingsService],
|
||||||
|
|||||||
@@ -20,6 +20,18 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
|||||||
return this.repository.findOne({ where: { reference } });
|
return this.repository.findOne({ where: { reference } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Count bookings created in a specific year. */
|
||||||
|
async countByYear(year: number): Promise<number> {
|
||||||
|
const startDate = new Date(year, 0, 1);
|
||||||
|
const endDate = new Date(year + 1, 0, 1);
|
||||||
|
|
||||||
|
return this.repository
|
||||||
|
.createQueryBuilder("booking")
|
||||||
|
.where("booking.created_at >= :startDate", { startDate })
|
||||||
|
.andWhere("booking.created_at < :endDate", { endDate })
|
||||||
|
.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
/** Find a booking by reference with associated files (polymorphic join). */
|
/** Find a booking by reference with associated files (polymorphic join). */
|
||||||
async findByReferenceWithFiles(reference: string): Promise<Booking | null> {
|
async findByReferenceWithFiles(reference: string): Promise<Booking | null> {
|
||||||
const booking = await this.repository
|
const booking = await this.repository
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { IsNull, Not } from "typeorm";
|
import { IsNull, Not } from "typeorm";
|
||||||
|
|
||||||
|
import { CustomersService } from "../customers/customers.service";
|
||||||
import { FilesService } from "../files/files.service";
|
import { FilesService } from "../files/files.service";
|
||||||
import { MinioService } from "../minio/minio.service";
|
import { MinioService } from "../minio/minio.service";
|
||||||
import { BookingsRepository } from "./bookings.repository";
|
import { BookingsRepository } from "./bookings.repository";
|
||||||
@@ -32,10 +33,23 @@ export class BookingsService {
|
|||||||
private readonly bookingsRepository: BookingsRepository,
|
private readonly bookingsRepository: BookingsRepository,
|
||||||
private readonly filesService: FilesService,
|
private readonly filesService: FilesService,
|
||||||
private readonly minioService: MinioService,
|
private readonly minioService: MinioService,
|
||||||
|
private readonly customersService: CustomersService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// ── helpers ──────────────────────────────────────────────────────────
|
// ── helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Generate a unique booking reference number. */
|
||||||
|
private async generateReference(): Promise<string> {
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
const prefix = `BK-${year}`;
|
||||||
|
|
||||||
|
// Get the count of bookings created this year
|
||||||
|
const count = await this.bookingsRepository.countByYear(year);
|
||||||
|
const sequenceNumber = String(count + 1).padStart(6, '0');
|
||||||
|
|
||||||
|
return `${prefix}-${sequenceNumber}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Resolve auto-consolidation flag. */
|
/** Resolve auto-consolidation flag. */
|
||||||
private resolveConsolidation(
|
private resolveConsolidation(
|
||||||
containers: Array<{ type: string; qty: number }> | undefined | null,
|
containers: Array<{ type: string; qty: number }> | undefined | null,
|
||||||
@@ -107,9 +121,23 @@ export class BookingsService {
|
|||||||
async create(
|
async create(
|
||||||
dto: CreateBookingDto,
|
dto: CreateBookingDto,
|
||||||
files: Express.Multer.File[],
|
files: Express.Multer.File[],
|
||||||
|
userId?: string,
|
||||||
): Promise<{ booking: Booking; warnings: string[] }> {
|
): Promise<{ booking: Booking; warnings: string[] }> {
|
||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
|
|
||||||
|
// Resolve customerId: use provided value (admin) or look up by IAM userId
|
||||||
|
let customerId = dto.customerId;
|
||||||
|
if (!customerId) {
|
||||||
|
if (!userId) {
|
||||||
|
throw new BadRequestException('customerId is required or must be resolvable from auth token');
|
||||||
|
}
|
||||||
|
const customer = await this.customersService.findByUserId(userId);
|
||||||
|
customerId = customer.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate reference if not provided
|
||||||
|
const reference = dto.reference || await this.generateReference();
|
||||||
|
|
||||||
const allowConsolidation = this.resolveConsolidation(
|
const allowConsolidation = this.resolveConsolidation(
|
||||||
dto.containers,
|
dto.containers,
|
||||||
dto.allowConsolidation,
|
dto.allowConsolidation,
|
||||||
@@ -131,6 +159,10 @@ export class BookingsService {
|
|||||||
|
|
||||||
const booking = await this.bookingsRepository.create({
|
const booking = await this.bookingsRepository.create({
|
||||||
...dto,
|
...dto,
|
||||||
|
reference,
|
||||||
|
customerId,
|
||||||
|
totalAmount: 0,
|
||||||
|
paymentStatus: "PENDING",
|
||||||
scheduledDate: new Date(dto.scheduledDate),
|
scheduledDate: new Date(dto.scheduledDate),
|
||||||
startDate: dto.startDate ? new Date(dto.startDate) : undefined,
|
startDate: dto.startDate ? new Date(dto.startDate) : undefined,
|
||||||
endDate: dto.endDate ? new Date(dto.endDate) : undefined,
|
endDate: dto.endDate ? new Date(dto.endDate) : undefined,
|
||||||
|
|||||||
@@ -69,14 +69,16 @@ export class ContainerItem {
|
|||||||
|
|
||||||
export class CreateBookingDto {
|
export class CreateBookingDto {
|
||||||
// ── core ─────────────────────────────────────────────────────────────
|
// ── core ─────────────────────────────────────────────────────────────
|
||||||
@ApiProperty({ description: "Unique booking reference" })
|
@ApiPropertyOptional({ description: "Unique booking reference (auto-generated if not provided)" })
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@Transform(({ value }) => (typeof value === "string" ? value.trim() : value))
|
@Transform(({ value }) => (typeof value === "string" ? value.trim() : value))
|
||||||
reference!: string;
|
reference?: string;
|
||||||
|
|
||||||
@ApiProperty({ format: "uuid" })
|
@ApiPropertyOptional({ format: "uuid", description: "Admin only: target customer. Omit to resolve from auth token." })
|
||||||
|
@IsOptional()
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
customerId!: string;
|
customerId?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ format: "uuid" })
|
@ApiPropertyOptional({ format: "uuid" })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -87,16 +89,6 @@ export class CreateBookingDto {
|
|||||||
@IsDateString()
|
@IsDateString()
|
||||||
scheduledDate!: string;
|
scheduledDate!: string;
|
||||||
|
|
||||||
@ApiProperty({ minimum: 0 })
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
@Transform(({ value }) => Number(value))
|
|
||||||
totalAmount!: number;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ enum: PAYMENT_STATUSES, default: "PENDING" })
|
|
||||||
@IsOptional()
|
|
||||||
@IsIn([...PAYMENT_STATUSES])
|
|
||||||
paymentStatus?: string;
|
|
||||||
|
|
||||||
// ── contract ─────────────────────────────────────────────────────────
|
// ── contract ─────────────────────────────────────────────────────────
|
||||||
@ApiProperty({ enum: CONTRACT_TYPES })
|
@ApiProperty({ enum: CONTRACT_TYPES })
|
||||||
|
|||||||
Reference in New Issue
Block a user