mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 21:50:57 +00:00
feat: implement shipping line bookings management
- Add ShippingLineBookingsPage for listing and managing shipping line bookings. - Create ShippingLineDocumentsModal for document uploads related to bookings. - Introduce ShippingLineInitiateModal for initiating new shipping line bookings. - Implement booking document state management with booking-doc-state utility. - Add shipping line bookings service for API interactions. - Update index to export new components and services. - Enhance types for freight to include shipping line credits.
This commit is contained in:
@@ -61,6 +61,8 @@ describe('RatesService — one rate per pattern', () => {
|
||||
})),
|
||||
} as never,
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never,
|
||||
// Shipping line companies — these rates carry no owner, so it is never hit.
|
||||
{ findById: jest.fn() } as never,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { PaginatedResponse, YardCountry } from '@edr/types';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
import { ShippingLineStatus } from '../../shipping-lines/entities/shipping-line-company.entity';
|
||||
import { ShippingLineCompaniesService } from '../../shipping-lines/shipping-line-companies.service';
|
||||
import { CreateRateDto } from '../dto/create-rate.dto';
|
||||
import { ListRatesQueryDto } from '../dto/list-rule-engine-query.dto';
|
||||
import { UpdateRateDto } from '../dto/update-rate.dto';
|
||||
@@ -39,6 +41,7 @@ export class RatesService {
|
||||
private readonly yardsRepository: IYardsRepository,
|
||||
@Inject(CARGO_TYPES_REPOSITORY)
|
||||
private readonly cargoTypesRepository: ICargoTypesRepository,
|
||||
private readonly shippingLineCompaniesService: ShippingLineCompaniesService,
|
||||
) {}
|
||||
|
||||
/** List rates — standard paginated envelope with server-side search. */
|
||||
@@ -491,6 +494,7 @@ export class RatesService {
|
||||
rateType: string;
|
||||
/** Passed only for additive surcharges — see {@link resolvesSingleRate}. */
|
||||
rateUnit?: string;
|
||||
shippingLineCompanyId: string | null;
|
||||
containerTypeId: string | null;
|
||||
cargoTypeId: string | null;
|
||||
tradeDirection: string | null;
|
||||
@@ -508,6 +512,35 @@ export class RatesService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the shipping line a rate is scoped to, when any.
|
||||
*
|
||||
* A shipping line only ever ships import — the export leg is sold through the
|
||||
* customer's contract — so a line rate carrying an EXPORT direction is
|
||||
* rejected here as well as by `CK_rates_shipping_line_import_only`.
|
||||
* Returns the owner id to store (null = the standard customer rate).
|
||||
*/
|
||||
private async resolveShippingLineScope(
|
||||
shippingLineCompanyId: string | null | undefined,
|
||||
tradeDirection: string | null,
|
||||
): Promise<string | null> {
|
||||
if (!shippingLineCompanyId) return null;
|
||||
|
||||
// Throws NotFoundException when the line does not exist.
|
||||
const line = await this.shippingLineCompaniesService.findById(shippingLineCompanyId);
|
||||
if (line.status !== ShippingLineStatus.Active) {
|
||||
throw new BadRequestException(
|
||||
`${line.name} is ${line.status} — rates can only be configured for an active shipping line.`,
|
||||
);
|
||||
}
|
||||
if (tradeDirection && tradeDirection !== 'IMPORT') {
|
||||
throw new BadRequestException(
|
||||
'Shipping line rates are import-only — the export leg is priced through the customer contract.',
|
||||
);
|
||||
}
|
||||
return shippingLineCompanyId;
|
||||
}
|
||||
|
||||
/** Create a rate in DRAFT status. */
|
||||
async create(dto: CreateRateDto, proposedByStaffId: string): Promise<Rate> {
|
||||
const appliesTo = dto.appliesTo as Rate['appliesTo'];
|
||||
@@ -568,6 +601,11 @@ export class RatesService {
|
||||
destinationYardId: dto.destinationYardId,
|
||||
});
|
||||
|
||||
const shippingLineCompanyId = await this.resolveShippingLineScope(
|
||||
dto.shippingLineCompanyId,
|
||||
tradeDirection,
|
||||
);
|
||||
|
||||
const rateType = deriveRateType({
|
||||
appliesTo,
|
||||
trigger,
|
||||
@@ -602,6 +640,7 @@ export class RatesService {
|
||||
await this.assertNoDuplicatePattern({
|
||||
rateType,
|
||||
...(this.resolvesSingleRate(appliesTo, trigger) ? {} : { rateUnit }),
|
||||
shippingLineCompanyId,
|
||||
containerTypeId,
|
||||
cargoTypeId,
|
||||
tradeDirection,
|
||||
@@ -614,6 +653,7 @@ export class RatesService {
|
||||
appliesTo,
|
||||
trigger,
|
||||
rateType,
|
||||
shippingLineCompanyId,
|
||||
containerTypeId,
|
||||
cargoTypeId,
|
||||
tradeDirection,
|
||||
@@ -791,6 +831,17 @@ export class RatesService {
|
||||
updates.originYardId = yardScope.originYardId;
|
||||
updates.destinationYardId = yardScope.destinationYardId;
|
||||
|
||||
// The owning line is re-validated on every edit: a patch that flips the
|
||||
// direction to EXPORT has to be refused for a line rate, and a patch that
|
||||
// moves the rate to a suspended line too.
|
||||
const shippingLineCompanyId = await this.resolveShippingLineScope(
|
||||
dto.shippingLineCompanyId !== undefined
|
||||
? dto.shippingLineCompanyId
|
||||
: existing.shippingLineCompanyId,
|
||||
updates.tradeDirection,
|
||||
);
|
||||
updates.shippingLineCompanyId = shippingLineCompanyId;
|
||||
|
||||
// Keep the derived rateType in sync with whatever changed.
|
||||
const rateType = deriveRateType({
|
||||
appliesTo,
|
||||
@@ -839,6 +890,7 @@ export class RatesService {
|
||||
await this.assertNoDuplicatePattern({
|
||||
rateType,
|
||||
...(this.resolvesSingleRate(appliesTo, trigger) ? {} : { rateUnit }),
|
||||
shippingLineCompanyId,
|
||||
containerTypeId: updates.containerTypeId,
|
||||
cargoTypeId: updates.cargoTypeId,
|
||||
tradeDirection: updates.tradeDirection,
|
||||
|
||||
Reference in New Issue
Block a user