integrate global logistics staff user into seeder

refactor pricing data seeder to fold surcharge types into rates
update route meta subtitle to remove surcharge types
enhance RuleEngineFormDialog to support conditional field visibility
 remove surcharge types from URL constants and related services
add cargo leaf options query for bulk cargo type selection
update RuleEngineResourcePage to utilize cargo leaf options
modify resources configuration to remove surcharge types
implement migration to fold surcharge types into rates
create utility to derive legacy rate types from new rate structure
This commit is contained in:
Marshal
2026-06-23 23:15:32 +00:00
parent 2b4dfc6490
commit 9d81a2e1ee
30 changed files with 642 additions and 632 deletions

View File

@@ -2,6 +2,7 @@ import { BadRequestException, Inject, Injectable, NotFoundException } from '@nes
import { CreateRateDto } from '../dto/create-rate.dto';
import { UpdateRateDto } from '../dto/update-rate.dto';
import { Rate } from '../entities/rate.entity';
import { deriveRateType } from '../entities/rate-type.util';
import { IRatesRepository, RATES_REPOSITORY } from '../interfaces/rates.repository.interface';
@Injectable()
@@ -47,10 +48,27 @@ export class RatesService {
/** Create a rate in DRAFT status. */
async create(dto: CreateRateDto, proposedByStaffId: string): Promise<Rate> {
const appliesTo = dto.appliesTo as Rate['appliesTo'];
const trigger = dto.trigger as Rate['trigger'];
// Surcharges (trigger ≠ ALWAYS) carry no direction/scope — clear them so
// the engine never accidentally narrows a surcharge by container/direction.
const isSurcharge = trigger !== 'ALWAYS';
const containerTypeId = isSurcharge ? null : (dto.containerTypeId ?? null);
const cargoTypeId = isSurcharge ? null : (dto.cargoTypeId ?? null);
const tradeDirection = isSurcharge ? null : (dto.tradeDirection ?? null);
return this.repository.create({
rateType: dto.rateType as Rate['rateType'],
containerTypeId: dto.containerTypeId,
tradeDirection: dto.tradeDirection,
appliesTo,
trigger,
rateType: deriveRateType({
appliesTo,
trigger,
tradeDirection,
isBulk: Boolean(cargoTypeId),
}),
containerTypeId,
cargoTypeId,
tradeDirection,
currency: dto.currency ?? 'USD',
rateValue: dto.rateValue,
rateUnit: dto.rateUnit as Rate['rateUnit'],
@@ -68,9 +86,41 @@ export class RatesService {
throw new BadRequestException('Only DRAFT rates can be updated');
}
const updates: Partial<Rate> = {};
if (dto.rateType) updates.rateType = dto.rateType as Rate['rateType'];
if (dto.containerTypeId !== undefined) updates.containerTypeId = dto.containerTypeId;
if (dto.tradeDirection !== undefined) updates.tradeDirection = dto.tradeDirection;
const appliesTo = (dto.appliesTo as Rate['appliesTo']) ?? existing.appliesTo;
const trigger = (dto.trigger as Rate['trigger']) ?? existing.trigger;
const isSurcharge = trigger !== 'ALWAYS';
if (dto.appliesTo) updates.appliesTo = appliesTo;
if (dto.trigger) updates.trigger = trigger;
const containerTypeId = isSurcharge
? null
: dto.containerTypeId !== undefined
? dto.containerTypeId
: existing.containerTypeId;
const cargoTypeId = isSurcharge
? null
: dto.cargoTypeId !== undefined
? dto.cargoTypeId
: existing.cargoTypeId;
const tradeDirection = isSurcharge
? null
: dto.tradeDirection !== undefined
? dto.tradeDirection
: existing.tradeDirection;
updates.containerTypeId = containerTypeId;
updates.cargoTypeId = cargoTypeId;
updates.tradeDirection = tradeDirection;
// Keep the derived rateType in sync with whatever changed.
updates.rateType = deriveRateType({
appliesTo,
trigger,
tradeDirection,
isBulk: Boolean(cargoTypeId),
});
updates.currency = dto.currency ?? existing.currency ?? 'USD';
if (dto.rateValue !== undefined) updates.rateValue = dto.rateValue;
if (dto.rateUnit) updates.rateUnit = dto.rateUnit as Rate['rateUnit'];

View File

@@ -1,78 +0,0 @@
import { ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { generateCode } from '../../../common/utils/generate-code.util';
import { CreateSurchargeTypeDto } from '../dto/create-surcharge-type.dto';
import { UpdateSurchargeTypeDto } from '../dto/update-surcharge-type.dto';
import { SurchargeType } from '../entities/surcharge-type.entity';
import {
ISurchargeTypesRepository,
SURCHARGE_TYPES_REPOSITORY,
} from '../interfaces/surcharge-types.repository.interface';
@Injectable()
export class SurchargeTypesService {
constructor(
@Inject(SURCHARGE_TYPES_REPOSITORY)
private readonly repository: ISurchargeTypesRepository,
) {}
/** List surcharge types with pagination. */
async findAll(filter: {
isActive?: boolean;
page?: number;
pageSize?: number;
}): Promise<{ data: SurchargeType[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 20;
const where: Record<string, unknown> = {};
if (filter.isActive !== undefined) where.isActive = filter.isActive;
const [data, total] = await this.repository.findAndCount({
where,
relations: { rate: true },
order: { label: 'ASC' },
skip: (page - 1) * pageSize,
take: pageSize,
});
return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } };
}
/** Get a single surcharge type by ID. */
async findById(id: string): Promise<SurchargeType> {
const entity = await this.repository.findById(id);
if (!entity) throw new NotFoundException(`Surcharge type ${id} not found`);
return entity;
}
/** Create a new surcharge type. */
async create(dto: CreateSurchargeTypeDto): Promise<SurchargeType> {
const code = generateCode(dto.label);
const existing = await this.repository.findByCode(code);
if (existing) throw new ConflictException(`Surcharge type with label "${dto.label}" conflicts with existing code "${code}"`);
return this.repository.create({
code,
label: dto.label,
triggerCondition: dto.triggerCondition as SurchargeType['triggerCondition'],
rateId: dto.rateId,
isActive: dto.isActive ?? true,
});
}
/** Update an existing surcharge type. */
async update(id: string, dto: UpdateSurchargeTypeDto): Promise<SurchargeType> {
await this.findById(id);
const patch: Partial<SurchargeType> = {};
if (dto.label !== undefined) patch.label = dto.label;
if (dto.triggerCondition !== undefined) patch.triggerCondition = dto.triggerCondition as SurchargeType['triggerCondition'];
if (dto.rateId !== undefined) patch.rateId = dto.rateId;
if (dto.isActive !== undefined) patch.isActive = dto.isActive;
const updated = await this.repository.update(id, patch);
if (!updated) throw new NotFoundException(`Surcharge type ${id} not found`);
return updated;
}
/** Soft-delete a surcharge type. */
async remove(id: string): Promise<void> {
await this.findById(id);
await this.repository.softDelete(id);
}
}