mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 17:08:18 +00:00
finalize gl flow for import
This commit is contained in:
@@ -645,6 +645,129 @@ export class GlOperationsService {
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* GL ET advises (or skips) the post-arrival additional duty/tax round (import).
|
||||
* Customer then attaches a slip; SECOND_DUTY_PAID completes on that upload.
|
||||
*/
|
||||
async adviseSecondDuty(
|
||||
bookingId: string,
|
||||
input: {
|
||||
dutyRequired: boolean;
|
||||
amount?: number;
|
||||
currency?: string;
|
||||
declarationSerial?: string;
|
||||
},
|
||||
attachment?: Express.Multer.File,
|
||||
userId?: string,
|
||||
): Promise<{ advised: boolean; skipped: boolean }> {
|
||||
const booking = await this.getBooking(bookingId);
|
||||
if (!booking.customsClearingEnabled) {
|
||||
throw new BadRequestException('Additional duty applies to customs bookings only.');
|
||||
}
|
||||
const tradeDirection = booking.tradeDirection ?? 'IMPORT';
|
||||
if (tradeDirection !== 'IMPORT') {
|
||||
throw new BadRequestException('Additional duty applies to import shipments only.');
|
||||
}
|
||||
|
||||
await this.milestoneService.ensureForBooking(bookingId, 'SECOND_DUTY_ADVISED', tradeDirection);
|
||||
await this.milestoneService.ensureForBooking(bookingId, 'SECOND_DUTY_PAID', tradeDirection);
|
||||
|
||||
if (!input.dutyRequired) {
|
||||
await this.milestoneService.skipForBooking(bookingId, 'SECOND_DUTY_ADVISED');
|
||||
await this.milestoneService.skipForBooking(bookingId, 'SECOND_DUTY_PAID');
|
||||
return { advised: false, skipped: true };
|
||||
}
|
||||
|
||||
if (!input.amount || input.amount <= 0) {
|
||||
throw new BadRequestException('Duty amount must be greater than zero.');
|
||||
}
|
||||
const files = await this.filesService.findByResource(bookingId, 'bookings');
|
||||
const hasNotice = files.some((f) => f.code === 'duty_tax_notice_2');
|
||||
if (!attachment && !hasNotice) {
|
||||
throw new BadRequestException('Attach the additional duty/tax notice.');
|
||||
}
|
||||
if (attachment) {
|
||||
await this.filesService.upsertByCode({
|
||||
resourceId: bookingId,
|
||||
resource: 'bookings',
|
||||
code: 'duty_tax_notice_2',
|
||||
file: attachment,
|
||||
});
|
||||
}
|
||||
|
||||
await this.milestoneService.completeWithMetadataForBooking(
|
||||
bookingId,
|
||||
'SECOND_DUTY_ADVISED',
|
||||
{
|
||||
dutyAmount: input.amount,
|
||||
dutyCurrency: input.currency ?? 'ETB',
|
||||
declarationSerial: input.declarationSerial,
|
||||
},
|
||||
userId,
|
||||
);
|
||||
return { advised: true, skipped: false };
|
||||
}
|
||||
|
||||
/** Customer attaches the payment slip for the additional duty round. */
|
||||
async uploadSecondDutySlip(
|
||||
bookingId: string,
|
||||
file: Express.Multer.File,
|
||||
): Promise<{ milestoneCompleted: boolean }> {
|
||||
const booking = await this.getBooking(bookingId);
|
||||
if (!file) throw new BadRequestException('No payment slip uploaded');
|
||||
|
||||
const milestones = await this.milestoneService.listForBooking(bookingId);
|
||||
const advised = milestones.find((m) => m.milestoneCode === 'SECOND_DUTY_ADVISED');
|
||||
if (advised?.status !== 'COMPLETED') {
|
||||
throw new BadRequestException('No additional duty has been advised for this shipment.');
|
||||
}
|
||||
|
||||
await this.filesService.upsertByCode({
|
||||
resourceId: bookingId,
|
||||
resource: 'bookings',
|
||||
code: 'duty_tax_receipt_2',
|
||||
file,
|
||||
});
|
||||
|
||||
await this.milestoneService.ensureForBooking(
|
||||
bookingId,
|
||||
'SECOND_DUTY_PAID',
|
||||
booking.tradeDirection ?? 'IMPORT',
|
||||
);
|
||||
await this.milestoneService.completeForBooking(bookingId, 'SECOND_DUTY_PAID');
|
||||
return { milestoneCompleted: true };
|
||||
}
|
||||
|
||||
/** Second duty round state for clearance views. */
|
||||
secondDutyState(
|
||||
milestones: Array<{
|
||||
milestoneCode: string;
|
||||
status: string;
|
||||
metadata?: { dutyAmount?: number; dutyCurrency?: string; declarationSerial?: string } | null;
|
||||
}>,
|
||||
files: Array<{ code?: string | null; id: string; name: string; url: string }>,
|
||||
): Freight.ClearanceSecondDuty | null {
|
||||
const advised = milestones.find((m) => m.milestoneCode === 'SECOND_DUTY_ADVISED');
|
||||
const paid = milestones.find((m) => m.milestoneCode === 'SECOND_DUTY_PAID');
|
||||
if (!advised && !paid) return null;
|
||||
|
||||
const toRef = (code: string) => {
|
||||
const f = files.find((x) => x.code === code);
|
||||
return f ? { id: f.id, name: f.name, url: f.url } : null;
|
||||
};
|
||||
|
||||
return {
|
||||
advised: advised?.status === 'COMPLETED',
|
||||
skipped: advised?.status === 'SKIPPED',
|
||||
amount: advised?.metadata?.dutyAmount ?? null,
|
||||
currency: advised?.metadata?.dutyCurrency ?? null,
|
||||
declarationSerial: advised?.metadata?.declarationSerial ?? null,
|
||||
noticeFile: toRef('duty_tax_notice_2'),
|
||||
slipFile: toRef('duty_tax_receipt_2'),
|
||||
paid: paid?.status === 'COMPLETED',
|
||||
};
|
||||
}
|
||||
|
||||
/** Final-invoice state joined with its document + slip files, for clearance views. */
|
||||
async finalInvoiceSummary(
|
||||
bookingId: string,
|
||||
|
||||
Reference in New Issue
Block a user