mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 02:58:11 +00:00
implement file module and add files relation to booking entity and populate on fetch
This commit is contained in:
@@ -39,7 +39,7 @@ export class BookingsController {
|
||||
@ApiBody({
|
||||
description:
|
||||
"Booking form data. Attach files with any field name (e.g. passport, tin_certificate). " +
|
||||
"File metadata is stored in the documents JSONB column.",
|
||||
"Each uploaded file is saved as a row in the files table (resource=bookings).",
|
||||
type: CreateBookingDto,
|
||||
})
|
||||
create(
|
||||
@@ -156,4 +156,5 @@ export class BookingsController {
|
||||
getConsolidationDetails(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.bookingsService.getConsolidationDetails(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
|
||||
import { MinioModule } from "../minio/minio.module";
|
||||
import { FilesModule } from "../files/files.module";
|
||||
import { BookingsController } from "./bookings.controller";
|
||||
import { BookingsRepository } from "./bookings.repository";
|
||||
import { BookingsService } from "./bookings.service";
|
||||
import { Booking } from "./entities/booking.entity";
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Booking]), MinioModule],
|
||||
imports: [TypeOrmModule.forFeature([Booking]), FilesModule],
|
||||
controllers: [BookingsController],
|
||||
providers: [BookingsService, BookingsRepository],
|
||||
exports: [BookingsService],
|
||||
|
||||
@@ -4,6 +4,7 @@ import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { In, IsNull, Not, Repository } from "typeorm";
|
||||
|
||||
import { Booking } from "./entities/booking.entity";
|
||||
import { FileRecord } from "../files/entities/file.entity";
|
||||
|
||||
@Injectable()
|
||||
export class BookingsRepository extends BaseRepository<Booking> {
|
||||
@@ -19,6 +20,36 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
||||
return this.repository.findOne({ where: { reference } });
|
||||
}
|
||||
|
||||
/** Find a booking by reference with associated files (polymorphic join). */
|
||||
async findByReferenceWithFiles(reference: string): Promise<Booking | null> {
|
||||
const booking = await this.repository
|
||||
.createQueryBuilder("booking")
|
||||
.where("booking.reference = :reference", { reference })
|
||||
.leftJoinAndMapMany(
|
||||
"booking.files",
|
||||
FileRecord,
|
||||
"file",
|
||||
"file.resource_id = booking.id AND file.resource = 'bookings'"
|
||||
)
|
||||
.getOne();
|
||||
return booking ?? null;
|
||||
}
|
||||
|
||||
/** Find a booking by ID with associated files (polymorphic join). */
|
||||
async findByIdWithFiles(id: string): Promise<Booking | null> {
|
||||
const booking = await this.repository
|
||||
.createQueryBuilder("booking")
|
||||
.where("booking.id = :id", { id })
|
||||
.leftJoinAndMapMany(
|
||||
"booking.files",
|
||||
FileRecord,
|
||||
"file",
|
||||
"file.resource_id = booking.id AND file.resource = 'bookings'"
|
||||
)
|
||||
.getOne();
|
||||
return booking ?? null;
|
||||
}
|
||||
|
||||
/** Find a compatible consolidation partner for the given booking. */
|
||||
async findConsolidationPartner(booking: Booking): Promise<Booking | null> {
|
||||
return this.repository.findOne({
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "@nestjs/common";
|
||||
import { IsNull, Not } from "typeorm";
|
||||
|
||||
import { MinioService } from "../minio/minio.service";
|
||||
import { FilesService } from "../files/files.service";
|
||||
import { BookingsRepository } from "./bookings.repository";
|
||||
import { CreateBookingDto } from "./dto/create-booking.dto";
|
||||
import { FilterBookingDto } from "./dto/filter-booking.dto";
|
||||
@@ -28,7 +28,7 @@ const HIGH_VOLUME_THRESHOLD_TONS = 500;
|
||||
export class BookingsService {
|
||||
constructor(
|
||||
private readonly bookingsRepository: BookingsRepository,
|
||||
private readonly minioService: MinioService,
|
||||
private readonly filesService: FilesService,
|
||||
) {}
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────
|
||||
@@ -97,29 +97,6 @@ export class BookingsService {
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/** Build file metadata from uploaded files and upload to MinIO. */
|
||||
private async buildDocuments(
|
||||
bookingId: string,
|
||||
files: Express.Multer.File[],
|
||||
): Promise<Record<string, { originalName: string; size: number; mimeType: string; url: string }>> {
|
||||
console.log('[BookingsService] buildDocuments called with bookingId:', bookingId, 'files count:', files.length);
|
||||
const docs: Record<string, { originalName: string; size: number; mimeType: string; url: string }> = {};
|
||||
for (const file of files) {
|
||||
console.log('[BookingsService] Processing file:', file.fieldname, file.originalname, 'size:', file.size);
|
||||
const objectName = `bookings/${bookingId}/${Date.now()}_${file.originalname}`;
|
||||
console.log('[BookingsService] Uploading to MinIO:', objectName);
|
||||
const url = await this.minioService.uploadFile(objectName, file.buffer, file.mimetype);
|
||||
console.log('[BookingsService] Upload successful, URL:', url);
|
||||
docs[file.fieldname] = {
|
||||
originalName: file.originalname,
|
||||
size: file.size,
|
||||
mimeType: file.mimetype,
|
||||
url,
|
||||
};
|
||||
}
|
||||
console.log('[BookingsService] All files processed, docs:', docs);
|
||||
return docs;
|
||||
}
|
||||
|
||||
// ── CRUD ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -157,15 +134,11 @@ export class BookingsService {
|
||||
status: "DRAFT",
|
||||
allowConsolidation,
|
||||
priorityScore,
|
||||
documents: null,
|
||||
});
|
||||
|
||||
// Upload files to MinIO and update booking with documents
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
const documents = await this.buildDocuments(booking.id, files);
|
||||
await this.bookingsRepository.update(booking.id, { documents });
|
||||
booking.documents = documents;
|
||||
await this.filesService.uploadMany(booking.id, "bookings", files);
|
||||
} catch (err) {
|
||||
console.error('[BookingsService] File upload failed, booking still created:', err);
|
||||
warnings.push('File upload failed — booking was created without attached files.');
|
||||
@@ -210,10 +183,8 @@ export class BookingsService {
|
||||
const overweightWarnings = this.checkOverweight(containers, direction);
|
||||
warnings.push(...overweightWarnings);
|
||||
|
||||
// Merge documents - upload new files to MinIO
|
||||
if (files.length > 0) {
|
||||
const newDocs = await this.buildDocuments(id, files);
|
||||
updates.documents = { ...(existing.documents ?? {}), ...newDocs };
|
||||
await this.filesService.uploadMany(id, "bookings", files);
|
||||
}
|
||||
|
||||
const booking = await this.bookingsRepository.update(id, updates);
|
||||
@@ -256,18 +227,18 @@ export class BookingsService {
|
||||
return { items, total };
|
||||
}
|
||||
|
||||
/** Get a single booking by ID, throwing if not found. */
|
||||
/** Get a single booking by ID with files, throwing if not found. */
|
||||
async findById(id: string): Promise<Booking> {
|
||||
const booking = await this.bookingsRepository.findById(id);
|
||||
const booking = await this.bookingsRepository.findByIdWithFiles(id);
|
||||
if (!booking) {
|
||||
throw new NotFoundException(`Booking ${id} not found`);
|
||||
}
|
||||
return booking;
|
||||
}
|
||||
|
||||
/** Find booking by reference. */
|
||||
/** Find booking by reference with files. */
|
||||
async findByReference(reference: string): Promise<Booking> {
|
||||
const booking = await this.bookingsRepository.findByReference(reference);
|
||||
const booking = await this.bookingsRepository.findByReferenceWithFiles(reference);
|
||||
if (!booking) {
|
||||
throw new NotFoundException(`Booking with reference "${reference}" not found`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BaseEntity } from "@edr/api-common";
|
||||
import { Column, Entity } from "typeorm";
|
||||
import { Column, Entity, OneToMany } from "typeorm";
|
||||
import { FileRecord } from "../../files/entities/file.entity";
|
||||
|
||||
@Entity({ schema:"freight",name: "bookings" })
|
||||
export class Booking extends BaseEntity {
|
||||
@@ -138,7 +139,10 @@ export class Booking extends BaseEntity {
|
||||
@Column({ name: "consolidation_partner_id", type: "uuid", nullable: true })
|
||||
consolidationPartnerId?: string | null;
|
||||
|
||||
// ── documents (JSONB) ──────────────────────────────────────────────────
|
||||
@Column({ name: "documents", type: "jsonb", nullable: true })
|
||||
documents?: Record<string, { originalName: string; size: number; mimeType: string; url?: string }> | null;
|
||||
// ── files ────────────────────────────────────────────────────────────
|
||||
@OneToMany(() => FileRecord, (file) => file.resourceId, {
|
||||
createForeignKeyConstraints: false,
|
||||
})
|
||||
files?: FileRecord[];
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user