mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 23:28:11 +00:00
changes
This commit is contained in:
@@ -17,6 +17,7 @@ import { BillingModule } from "./modules/billing/billing.module";
|
||||
import { NotificationsModule } from "./modules/notifications/notifications.module";
|
||||
import { FileUploadSettingsModule } from "./modules/file-upload-settings/file-upload-settings.module";
|
||||
import { DropdownSettingsModule } from "./modules/dropdown-settings/dropdown-settings.module";
|
||||
import { DropdownSettingsService } from "./modules/dropdown-settings/dropdown-settings.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -44,9 +45,13 @@ import { DropdownSettingsModule } from "./modules/dropdown-settings/dropdown-set
|
||||
],
|
||||
})
|
||||
export class AppModule implements OnApplicationBootstrap {
|
||||
constructor(private readonly seeder: DataSeeder) {}
|
||||
constructor(
|
||||
private readonly seeder: DataSeeder,
|
||||
private readonly dropdownSettingsService: DropdownSettingsService,
|
||||
) {}
|
||||
|
||||
async onApplicationBootstrap() {
|
||||
await this.seeder.run();
|
||||
await this.dropdownSettingsService.seedDefaultStations();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,13 @@ import {
|
||||
UseInterceptors,
|
||||
} from "@nestjs/common";
|
||||
import { AnyFilesInterceptor } from "@nestjs/platform-express";
|
||||
import { ApiBody, ApiConsumes, ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
ApiConsumes,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
} from "@nestjs/swagger";
|
||||
|
||||
import { BookingsService } from "./bookings.service";
|
||||
import { CreateBookingDto } from "./dto/create-booking.dto";
|
||||
@@ -23,8 +29,9 @@ import { UpdateStatusDto } from "./dto/update-status.dto";
|
||||
|
||||
@ApiTags("bookings")
|
||||
@Controller("bookings")
|
||||
@ApiBearerAuth()
|
||||
export class BookingsController {
|
||||
constructor(private readonly bookingsService: BookingsService) {}
|
||||
constructor(private readonly bookingsService: BookingsService) { }
|
||||
|
||||
// ── 1. Create booking (multipart/form-data) ──────────────────────────
|
||||
@Post()
|
||||
@@ -46,7 +53,16 @@ export class BookingsController {
|
||||
@Body() dto: CreateBookingDto,
|
||||
@UploadedFiles() files: Express.Multer.File[],
|
||||
) {
|
||||
console.log('[BookingsController] Files received:', files?.length, files?.map(f => ({ fieldname: f.fieldname, originalname: f.originalname, size: f.size, mimetype: f.mimetype })));
|
||||
console.log(
|
||||
"[BookingsController] Files received:",
|
||||
files?.length,
|
||||
files?.map((f) => ({
|
||||
fieldname: f.fieldname,
|
||||
originalname: f.originalname,
|
||||
size: f.size,
|
||||
mimetype: f.mimetype,
|
||||
})),
|
||||
);
|
||||
return this.bookingsService.create(dto, files ?? []);
|
||||
}
|
||||
|
||||
@@ -56,7 +72,8 @@ export class BookingsController {
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiOperation({
|
||||
summary: "Update a draft booking",
|
||||
description: "Only DRAFT bookings can be updated. New files are merged into existing documents.",
|
||||
description:
|
||||
"Only DRAFT bookings can be updated. New files are merged into existing documents.",
|
||||
})
|
||||
@ApiBody({ type: UpdateBookingDto })
|
||||
update(
|
||||
@@ -141,7 +158,8 @@ export class BookingsController {
|
||||
@Delete(":id/consolidation")
|
||||
@ApiOperation({
|
||||
summary: "Remove consolidation pairing",
|
||||
description: "Unpairs both bookings and returns them to PENDING_CONSOLIDATION status.",
|
||||
description:
|
||||
"Unpairs both bookings and returns them to PENDING_CONSOLIDATION status.",
|
||||
})
|
||||
removeConsolidation(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.bookingsService.removeConsolidation(id);
|
||||
@@ -151,7 +169,8 @@ export class BookingsController {
|
||||
@Get(":id/consolidation")
|
||||
@ApiOperation({
|
||||
summary: "Get consolidation details",
|
||||
description: "Returns partner booking details and split billing information.",
|
||||
description:
|
||||
"Returns partner booking details and split billing information.",
|
||||
})
|
||||
getConsolidationDetails(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.bookingsService.getConsolidationDetails(id);
|
||||
|
||||
@@ -2,13 +2,14 @@ import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
|
||||
import { FilesModule } from "../files/files.module";
|
||||
import { MinioModule } from "../minio/minio.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]), FilesModule],
|
||||
imports: [TypeOrmModule.forFeature([Booking]), FilesModule, MinioModule],
|
||||
controllers: [BookingsController],
|
||||
providers: [BookingsService, BookingsRepository],
|
||||
exports: [BookingsService],
|
||||
|
||||
@@ -7,12 +7,14 @@ import {
|
||||
import { IsNull, Not } from "typeorm";
|
||||
|
||||
import { FilesService } from "../files/files.service";
|
||||
import { MinioService } from "../minio/minio.service";
|
||||
import { BookingsRepository } from "./bookings.repository";
|
||||
import { CreateBookingDto } from "./dto/create-booking.dto";
|
||||
import { FilterBookingDto } from "./dto/filter-booking.dto";
|
||||
import { UpdateBookingDto } from "./dto/update-booking.dto";
|
||||
import { UpdateStatusDto } from "./dto/update-status.dto";
|
||||
import { Booking } from "./entities/booking.entity";
|
||||
import { FileRecord } from "../files/entities/file.entity";
|
||||
|
||||
/** Weight thresholds (tons) that trigger overweight surcharge alerts. */
|
||||
const WEIGHT_LIMITS = {
|
||||
@@ -29,6 +31,7 @@ export class BookingsService {
|
||||
constructor(
|
||||
private readonly bookingsRepository: BookingsRepository,
|
||||
private readonly filesService: FilesService,
|
||||
private readonly minioService: MinioService,
|
||||
) {}
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────
|
||||
@@ -233,15 +236,45 @@ export class BookingsService {
|
||||
if (!booking) {
|
||||
throw new NotFoundException(`Booking ${id} not found`);
|
||||
}
|
||||
|
||||
// Add signed URLs for files (5-minute expiration)
|
||||
if (booking.files && booking.files.length > 0) {
|
||||
booking.files = await Promise.all(
|
||||
booking.files.map(async (file: FileRecord) => {
|
||||
const objectName = this.extractObjectName(file.url);
|
||||
const signedUrl = await this.minioService.getSignedUrl(objectName, 300);
|
||||
return { ...file, signedUrl };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return booking;
|
||||
}
|
||||
|
||||
/** Extract object name from Minio URL. */
|
||||
private extractObjectName(url: string): string {
|
||||
const parts = url.split("/");
|
||||
return parts.slice(4).join("/");
|
||||
}
|
||||
|
||||
/** Find booking by reference with files. */
|
||||
async findByReference(reference: string): Promise<Booking> {
|
||||
const booking = await this.bookingsRepository.findByReferenceWithFiles(reference);
|
||||
if (!booking) {
|
||||
throw new NotFoundException(`Booking with reference "${reference}" not found`);
|
||||
}
|
||||
|
||||
// Add signed URLs for files (5-minute expiration)
|
||||
if (booking.files && booking.files.length > 0) {
|
||||
booking.files = await Promise.all(
|
||||
booking.files.map(async (file: FileRecord) => {
|
||||
const objectName = this.extractObjectName(file.url);
|
||||
const signedUrl = await this.minioService.getSignedUrl(objectName, 300);
|
||||
return { ...file, signedUrl };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return booking;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,71 @@ import {
|
||||
IDropdownSettingsRepository,
|
||||
} from "./interfaces/dropdown-settings.repository.interface";
|
||||
|
||||
const STATIONS_TER_CODE = "stations_ter";
|
||||
|
||||
const DEFAULT_STATION_OPTIONS: CreateDropdownOptionDto[] = [
|
||||
{
|
||||
value: "inside_addis_ababa",
|
||||
label: "Addis Ababa",
|
||||
note: "Inside country",
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
value: "inside_adama",
|
||||
label: "Adama",
|
||||
note: "Inside country",
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
value: "inside_mojo",
|
||||
label: "Mojo",
|
||||
note: "Inside country",
|
||||
order: 3,
|
||||
},
|
||||
{
|
||||
value: "inside_awash",
|
||||
label: "Awash",
|
||||
note: "Inside country",
|
||||
order: 4,
|
||||
},
|
||||
{
|
||||
value: "inside_mieso",
|
||||
label: "Mieso",
|
||||
note: "Inside country",
|
||||
order: 5,
|
||||
},
|
||||
{
|
||||
value: "inside_dire_dawa",
|
||||
label: "Dire Dawa",
|
||||
note: "Inside country",
|
||||
order: 6,
|
||||
},
|
||||
{
|
||||
value: "outside_ali_sabieh",
|
||||
label: "Ali Sabieh",
|
||||
note: "Outside country",
|
||||
order: 7,
|
||||
},
|
||||
{
|
||||
value: "outside_holhol",
|
||||
label: "Holhol",
|
||||
note: "Outside country",
|
||||
order: 8,
|
||||
},
|
||||
{
|
||||
value: "outside_djibouti_city",
|
||||
label: "Djibouti City",
|
||||
note: "Outside country",
|
||||
order: 9,
|
||||
},
|
||||
{
|
||||
value: "outside_doraleh_terminal",
|
||||
label: "Doraleh Terminal",
|
||||
note: "Outside country",
|
||||
order: 10,
|
||||
},
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class DropdownSettingsService {
|
||||
constructor(
|
||||
@@ -62,6 +127,34 @@ export class DropdownSettingsService {
|
||||
return this.getById(setting.id);
|
||||
}
|
||||
|
||||
async seedDefaultStations(): Promise<void> {
|
||||
const existing = await this.repository.findByCode(STATIONS_TER_CODE);
|
||||
|
||||
if (!existing) {
|
||||
await this.create({
|
||||
code: STATIONS_TER_CODE,
|
||||
label: "Stations TER",
|
||||
description:
|
||||
"Temporary freight station list used by booking origin and destination yards.",
|
||||
multiple: false,
|
||||
meta: {
|
||||
searchable: true,
|
||||
clearable: true,
|
||||
version: "temporary",
|
||||
},
|
||||
children: DEFAULT_STATION_OPTIONS,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ((existing.children?.length ?? 0) === 0) {
|
||||
await this.repository.replaceOptions(
|
||||
existing.id,
|
||||
DEFAULT_STATION_OPTIONS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
dto: UpdateDropdownSettingDto,
|
||||
|
||||
@@ -72,4 +72,13 @@ export class MinioService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getSignedUrl(objectName: string, expirySeconds: number = 300): Promise<string> {
|
||||
try {
|
||||
return await this.client.presignedGetObject(this.bucket, objectName, expirySeconds);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to generate signed URL for ${objectName}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user