feat(admin): add sailing calendar management
This commit is contained in:
@@ -32,6 +32,7 @@ import { overseasWarehouseRoutes } from '@/modules/overseas-warehouse/routes'
|
||||
import { overseasBusinessRoutes } from '@/modules/overseas-business/routes'
|
||||
import { overseasMasterDataRoutes } from '@/modules/overseas-master-data/routes'
|
||||
import { pdaReleaseRoutes } from '@/modules/pda-releases/routes'
|
||||
import { sailingCalendarRoutes } from '@/modules/sailing-calendar/routes'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
@@ -77,6 +78,7 @@ const router = createRouter({
|
||||
...downloadRoutes,
|
||||
...iamRoutes,
|
||||
...pdaReleaseRoutes,
|
||||
...sailingCalendarRoutes,
|
||||
{
|
||||
path: '403',
|
||||
name: 'forbidden',
|
||||
|
||||
@@ -35,6 +35,7 @@ describe('adminNavigation', () => {
|
||||
'库位管理',
|
||||
'托盘管理',
|
||||
'整柜业务',
|
||||
'船期日历',
|
||||
])
|
||||
expect(adminNavigation.find((item) => item.key === 'customer-service')?.children?.map((item) => item.title)).toEqual(['异常订单', '客户回访'])
|
||||
expect(adminNavigation.find((item) => item.key === 'supplier-center')).toBeUndefined()
|
||||
@@ -47,6 +48,7 @@ describe('adminNavigation', () => {
|
||||
expect(adminNavigation.find((item) => item.key === 'overseas-inventory')?.children?.map((item) => item.title)).toEqual([
|
||||
'尾程订单',
|
||||
'入库单',
|
||||
'库位管理',
|
||||
'排车列表',
|
||||
'出库单',
|
||||
'派送单列表',
|
||||
@@ -68,6 +70,7 @@ describe('adminNavigation', () => {
|
||||
'船期价格表',
|
||||
'车辆列表',
|
||||
'下载中心',
|
||||
'称重机器',
|
||||
])
|
||||
expect(adminNavigation.find((item) => item.key === 'notifications')).toBeUndefined()
|
||||
expect(adminNavigation.find((item) => item.key === 'system')?.children?.map((item) => item.title)).toEqual([
|
||||
@@ -76,6 +79,7 @@ describe('adminNavigation', () => {
|
||||
'租户管理',
|
||||
'品牌设置',
|
||||
'菜单权限',
|
||||
'PDA版本',
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Component } from 'vue'
|
||||
import {
|
||||
Building2,
|
||||
CalendarDays,
|
||||
ContactRound,
|
||||
ClipboardList,
|
||||
CircleDollarSign,
|
||||
@@ -70,6 +71,7 @@ export const adminNavigation: AdminNavigationNode[] = [
|
||||
{ key: 'locations', title: '库位管理', icon: WarehouseIcon, path: '/master-data/locations', permission: 'platform:location:list' },
|
||||
{ key: 'pallets', title: '托盘管理', icon: WarehouseIcon, path: '/master-data/pallets', permission: 'platform:pallet:list' },
|
||||
{ key: 'domestic-containers', title: '整柜业务', icon: WarehouseIcon, path: '/domestic-containers', permission: 'platform:domestic-container:list' },
|
||||
{ key: 'sailing-calendar', title: '船期日历', icon: CalendarDays, path: '/sailing-calendar', permission: 'platform:firstleg:sailing-calendar:list' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
||||
import type { SailingAssignment, SailingBookingAdvice, SailingBookingLinkRequest, SailingCalendarEntry, SailingCalendarQuery, SailingCalendarResponse, SailingChannelResource, SailingChannelSaveRequest, SailingDetail, SailingExceptionPage, SailingExceptionQuery, SailingExceptionRecord, SailingExceptionReassignRequest, SailingExceptionReassignResult, SailingGenerationPreview, SailingGenerationResult, SailingSaveRequest, SailingTemplateResource, SailingTemplateSaveRequest, SailingVolumeDetail } from '../types'
|
||||
|
||||
const basePath = '/platform/v1/sailings'
|
||||
|
||||
function number(value: unknown, fallback = 0) {
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : fallback
|
||||
}
|
||||
|
||||
function entry(raw: SailingCalendarEntry): SailingCalendarEntry {
|
||||
return {
|
||||
...raw,
|
||||
id: String(raw.id),
|
||||
sailingNo: String(raw.sailingNo ?? ''),
|
||||
warehouseId: String(raw.warehouseId ?? ''),
|
||||
channelCode: String(raw.channelCode ?? ''),
|
||||
channelName: String(raw.channelName ?? raw.channelCode ?? ''),
|
||||
volumeSummary: summary(raw.volumeSummary),
|
||||
version: number(raw.version),
|
||||
}
|
||||
}
|
||||
|
||||
function summary(raw?: Partial<SailingCalendarEntry['volumeSummary']> | null) {
|
||||
return {
|
||||
actualReceivedCbm: number(raw?.actualReceivedCbm),
|
||||
catchableForecastCbm: number(raw?.catchableForecastCbm),
|
||||
riskUnmatchedCbm: number(raw?.riskUnmatchedCbm),
|
||||
bookedContainerCount: number(raw?.bookedContainerCount),
|
||||
recommendedContainerCount: number(raw?.recommendedContainerCount),
|
||||
pendingContainerCount: number(raw?.pendingContainerCount),
|
||||
lateBatchCount: number(raw?.lateBatchCount),
|
||||
overCapacity: Boolean(raw?.overCapacity),
|
||||
planningCapacityCbm: raw?.planningCapacityCbm == null ? null : number(raw.planningCapacityCbm),
|
||||
bookedPlanningCapacityCbm: raw?.bookedPlanningCapacityCbm == null ? null : number(raw.bookedPlanningCapacityCbm),
|
||||
catchableReceivedCbm: raw?.catchableReceivedCbm == null ? null : number(raw.catchableReceivedCbm),
|
||||
lateReceivedCbm: raw?.lateReceivedCbm == null ? null : number(raw.lateReceivedCbm),
|
||||
snapshotAt: raw?.snapshotAt ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
function assignment(raw: SailingAssignment): SailingAssignment {
|
||||
return {
|
||||
...raw,
|
||||
id: String(raw.id),
|
||||
volumeCbm: number(raw.volumeCbm),
|
||||
assignmentVersion: raw.assignmentVersion == null ? null : number(raw.assignmentVersion),
|
||||
}
|
||||
}
|
||||
|
||||
function history(raw: SailingDetail['histories'][number]): SailingDetail['histories'][number] {
|
||||
return { ...raw, id: String(raw.id) }
|
||||
}
|
||||
|
||||
function calendar(raw: SailingCalendarResponse): SailingCalendarResponse {
|
||||
const source = raw || {} as SailingCalendarResponse
|
||||
const rawSummary = source.summary || {} as SailingCalendarResponse['summary']
|
||||
return {
|
||||
...source,
|
||||
records: (source.records || []).map(entry),
|
||||
summary: { ...summary(rawSummary), sailingCount: number(rawSummary.sailingCount) },
|
||||
filterOptions: {
|
||||
warehouses: source.filterOptions?.warehouses || [],
|
||||
channels: source.filterOptions?.channels || [],
|
||||
departurePorts: source.filterOptions?.departurePorts || [],
|
||||
destinationPorts: source.filterOptions?.destinationPorts || [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function listSailingCalendar(query: SailingCalendarQuery) {
|
||||
const response = await http.get<ApiEnvelope<SailingCalendarResponse>, ApiEnvelope<SailingCalendarResponse>>(`${basePath}/calendar`, { params: query })
|
||||
return calendar(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function getSailing(id: string) {
|
||||
const response = await http.get<ApiEnvelope<SailingDetail>, ApiEnvelope<SailingDetail>>(`${basePath}/${encodeURIComponent(id)}`)
|
||||
const result = unwrapApiResponse(response)
|
||||
return { ...result, sailing: entry(result.sailing), histories: (result.histories || []).map(history) }
|
||||
}
|
||||
|
||||
function idempotencyKey(prefix: string) { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}` }
|
||||
|
||||
export async function createSailing(payload: SailingSaveRequest) {
|
||||
const response = await http.post<ApiEnvelope<SailingDetail>, ApiEnvelope<SailingDetail>>(basePath, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing') } })
|
||||
const result = unwrapApiResponse(response)
|
||||
return { ...result, sailing: entry(result.sailing), histories: (result.histories || []).map(history) }
|
||||
}
|
||||
|
||||
export async function updateSailing(id: string, payload: SailingSaveRequest) {
|
||||
const response = await http.put<ApiEnvelope<SailingDetail>, ApiEnvelope<SailingDetail>>(`${basePath}/${encodeURIComponent(id)}`, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing') } })
|
||||
const result = unwrapApiResponse(response)
|
||||
return { ...result, sailing: entry(result.sailing), histories: (result.histories || []).map(history) }
|
||||
}
|
||||
|
||||
export async function cancelSailing(id: string, expectedVersion: number, reason: string) {
|
||||
const response = await http.post<ApiEnvelope<SailingDetail>, ApiEnvelope<SailingDetail>>(`${basePath}/${encodeURIComponent(id)}/cancel`, { expectedVersion, reason }, { headers: { 'Idempotency-Key': idempotencyKey('sailing-cancel') } })
|
||||
const result = unwrapApiResponse(response)
|
||||
return { ...result, sailing: entry(result.sailing), histories: (result.histories || []).map(history) }
|
||||
}
|
||||
|
||||
export async function copySailing(id: string, payload: { etdAt: string; cutoffAt: string }) {
|
||||
const response = await http.post<ApiEnvelope<SailingDetail>, ApiEnvelope<SailingDetail>>(`${basePath}/${encodeURIComponent(id)}/copy`, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing-copy') } })
|
||||
const result = unwrapApiResponse(response)
|
||||
return { ...result, sailing: entry(result.sailing), histories: (result.histories || []).map(history) }
|
||||
}
|
||||
|
||||
export async function getSailingVolumeDetail(id: string) {
|
||||
const response = await http.get<ApiEnvelope<SailingVolumeDetail>, ApiEnvelope<SailingVolumeDetail>>(`${basePath}/${encodeURIComponent(id)}/volume-summary`)
|
||||
const result = unwrapApiResponse(response)
|
||||
return { ...result, summary: summary(result.summary), assignments: (result.assignments || []).map(assignment) }
|
||||
}
|
||||
|
||||
export async function getSailingBookingAdvice(id: string) {
|
||||
const response = await http.get<ApiEnvelope<SailingBookingAdvice>, ApiEnvelope<SailingBookingAdvice>>(`${basePath}/${encodeURIComponent(id)}/booking-advice`)
|
||||
const result = unwrapApiResponse(response)
|
||||
return {
|
||||
...result,
|
||||
actualReceivedCbm: number(result.actualReceivedCbm),
|
||||
catchableTotalCbm: number(result.catchableTotalCbm),
|
||||
planningCapacityCbm: result.planningCapacityCbm == null ? null : number(result.planningCapacityCbm),
|
||||
bookedContainerCount: number(result.bookedContainerCount),
|
||||
recommendedContainerCount: number(result.recommendedContainerCount),
|
||||
pendingContainerCount: number(result.pendingContainerCount),
|
||||
catchableReceivedCbm: result.catchableReceivedCbm == null ? null : number(result.catchableReceivedCbm),
|
||||
lateReceivedCbm: result.lateReceivedCbm == null ? null : number(result.lateReceivedCbm),
|
||||
scenarios: (result.scenarios || []).map((scenario) => ({ ...scenario, capacityCbm: number(scenario.capacityCbm), recommendedContainerCount: number(scenario.recommendedContainerCount), pendingContainerCount: number(scenario.pendingContainerCount) })),
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSailingBookingLink(id: string, payload: SailingBookingLinkRequest) {
|
||||
const response = await http.post<ApiEnvelope<unknown>, ApiEnvelope<unknown>>(`${basePath}/${encodeURIComponent(id)}/booking-links`, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing-booking-link') } })
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
const channelPath = '/platform/v1/sailing-channels'
|
||||
const templatePath = '/platform/v1/sailing-templates'
|
||||
const exceptionPath = '/platform/v1/sailing-assignments/exceptions'
|
||||
|
||||
function channel(raw: SailingChannelResource): SailingChannelResource { return { ...raw, id: String(raw.id), defaultCutoffDays: number(raw.defaultCutoffDays), version: number(raw.version) } }
|
||||
function template(raw: SailingTemplateResource): SailingTemplateResource { return { ...raw, id: String(raw.id), channelId: String(raw.channelId), warehouseId: String(raw.warehouseId), weekdays: (raw.weekdays || []).map((value) => number(value)), cutoffDays: number(raw.cutoffDays), version: number(raw.version) } }
|
||||
|
||||
export async function listSailingChannels() {
|
||||
const response = await http.get<ApiEnvelope<SailingChannelResource[]>, ApiEnvelope<SailingChannelResource[]>>(channelPath)
|
||||
const result = unwrapApiResponse(response) as SailingChannelResource[] | { records?: SailingChannelResource[] } | null
|
||||
return (Array.isArray(result) ? result : result?.records || []).map(channel)
|
||||
}
|
||||
export async function createSailingChannel(payload: SailingChannelSaveRequest) {
|
||||
const response = await http.post<ApiEnvelope<SailingChannelResource>, ApiEnvelope<SailingChannelResource>>(channelPath, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing-channel') } })
|
||||
return channel(unwrapApiResponse(response))
|
||||
}
|
||||
export async function updateSailingChannel(id: string, payload: SailingChannelSaveRequest) {
|
||||
const response = await http.put<ApiEnvelope<SailingChannelResource>, ApiEnvelope<SailingChannelResource>>(`${channelPath}/${encodeURIComponent(id)}`, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing-channel') } })
|
||||
return channel(unwrapApiResponse(response))
|
||||
}
|
||||
export async function listSailingTemplates() {
|
||||
const response = await http.get<ApiEnvelope<SailingTemplateResource[]>, ApiEnvelope<SailingTemplateResource[]>>(templatePath)
|
||||
const result = unwrapApiResponse(response) as SailingTemplateResource[] | { records?: SailingTemplateResource[] } | null
|
||||
return (Array.isArray(result) ? result : result?.records || []).map(template)
|
||||
}
|
||||
export async function createSailingTemplate(payload: SailingTemplateSaveRequest) {
|
||||
const response = await http.post<ApiEnvelope<SailingTemplateResource>, ApiEnvelope<SailingTemplateResource>>(templatePath, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing-template') } })
|
||||
return template(unwrapApiResponse(response))
|
||||
}
|
||||
export async function updateSailingTemplate(id: string, payload: SailingTemplateSaveRequest) {
|
||||
const response = await http.put<ApiEnvelope<SailingTemplateResource>, ApiEnvelope<SailingTemplateResource>>(`${templatePath}/${encodeURIComponent(id)}`, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing-template') } })
|
||||
return template(unwrapApiResponse(response))
|
||||
}
|
||||
export async function previewSailingTemplateGeneration(id: string, payload: { from: string; to: string }) {
|
||||
const response = await http.post<ApiEnvelope<SailingGenerationPreview>, ApiEnvelope<SailingGenerationPreview>>(`${templatePath}/${encodeURIComponent(id)}/generation-preview`, payload)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
export async function generateSailingTemplate(id: string, payload: { from: string; to: string }) {
|
||||
const response = await http.post<ApiEnvelope<SailingGenerationResult>, ApiEnvelope<SailingGenerationResult>>(`${templatePath}/${encodeURIComponent(id)}/generate`, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing-template-generate') } })
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
function exceptionRecord(raw: SailingExceptionRecord): SailingExceptionRecord {
|
||||
return {
|
||||
...raw,
|
||||
assignmentId: String(raw.assignmentId),
|
||||
volumeCbm: number(raw.volumeCbm),
|
||||
waitingDays: raw.waitingDays == null ? null : number(raw.waitingDays),
|
||||
assignmentVersion: raw.assignmentVersion == null ? null : number(raw.assignmentVersion),
|
||||
}
|
||||
}
|
||||
|
||||
export async function listSailingExceptions(query: SailingExceptionQuery = {}) {
|
||||
const response = await http.get<ApiEnvelope<SailingExceptionPage>, ApiEnvelope<SailingExceptionPage>>(exceptionPath, { params: query })
|
||||
const raw = unwrapApiResponse(response) as SailingExceptionPage | SailingExceptionRecord[] | null
|
||||
if (Array.isArray(raw)) return { records: raw.map(exceptionRecord), page: 1, size: raw.length, total: raw.length, pages: raw.length ? 1 : 0 }
|
||||
return { ...(raw || { records: [], page: 1, size: 20, total: 0, pages: 0 }), records: (raw?.records || []).map(exceptionRecord) }
|
||||
}
|
||||
|
||||
/** Restricted exception reassignment; the server validates cutoff, route and warehouse scope. */
|
||||
export async function reassignSailingAssignment(id: string, payload: SailingExceptionReassignRequest) {
|
||||
const response = await http.post<ApiEnvelope<SailingExceptionReassignResult>, ApiEnvelope<SailingExceptionReassignResult>>(`${basePath}/${encodeURIComponent(id)}/exception-reassign`, payload, { headers: { 'Idempotency-Key': idempotencyKey('sailing-exception-reassign') } })
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './types'
|
||||
export * from './api/platformSailingCalendar'
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export const sailingCalendarRoutes = [{
|
||||
path: 'sailing-calendar',
|
||||
name: 'sailing-calendar',
|
||||
component: () => import('./pages/SailingCalendarView.vue'),
|
||||
meta: { title: '船期日历', description: '维护计划船期、查看收货体积和订柜建议', permission: 'platform:firstleg:sailing-calendar:list' },
|
||||
}] satisfies RouteRecordRaw[]
|
||||
@@ -0,0 +1,295 @@
|
||||
export type SailingStatus = 'DRAFT' | 'OPEN' | 'CLOSED' | 'DEPARTED' | 'CANCELLED' | 'COMPLETED'
|
||||
|
||||
export type SailingAssignmentStatus = 'FORECAST_MATCHED' | 'RECEIVED' | 'LATE' | 'UNMATCHED' | 'SUPERSEDED'
|
||||
|
||||
export interface SailingVolumeSummary {
|
||||
actualReceivedCbm: number
|
||||
catchableForecastCbm: number
|
||||
riskUnmatchedCbm: number
|
||||
bookedContainerCount: number
|
||||
recommendedContainerCount: number
|
||||
pendingContainerCount: number
|
||||
lateBatchCount: number
|
||||
overCapacity: boolean
|
||||
planningCapacityCbm?: number | null
|
||||
bookedPlanningCapacityCbm?: number | null
|
||||
catchableReceivedCbm?: number | null
|
||||
lateReceivedCbm?: number | null
|
||||
snapshotAt?: string | null
|
||||
}
|
||||
|
||||
export interface SailingCalendarEntry {
|
||||
id: string
|
||||
sailingNo: string
|
||||
warehouseId: string
|
||||
warehouseName: string
|
||||
channelCode: string
|
||||
channelName: string
|
||||
vesselName: string
|
||||
voyageNo: string
|
||||
departurePortCode: string
|
||||
departurePortName?: string | null
|
||||
destinationPortCode: string
|
||||
destinationPortName?: string | null
|
||||
etdAt: string
|
||||
cutoffAt: string
|
||||
status: SailingStatus
|
||||
volumeSummary: SailingVolumeSummary
|
||||
version: number
|
||||
remark?: string | null
|
||||
}
|
||||
|
||||
export interface SailingFilterOption {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface SailingCalendarFilterOptions {
|
||||
warehouses: SailingFilterOption[]
|
||||
channels: SailingFilterOption[]
|
||||
departurePorts: SailingFilterOption[]
|
||||
destinationPorts: SailingFilterOption[]
|
||||
}
|
||||
|
||||
export interface SailingCalendarSummary extends SailingVolumeSummary {
|
||||
sailingCount: number
|
||||
}
|
||||
|
||||
export interface SailingCalendarResponse {
|
||||
month: string
|
||||
timezone: string
|
||||
records: SailingCalendarEntry[]
|
||||
summary: SailingCalendarSummary
|
||||
filterOptions: SailingCalendarFilterOptions
|
||||
}
|
||||
|
||||
export interface SailingCalendarQuery {
|
||||
month: string
|
||||
warehouseId?: string
|
||||
channelCode?: string
|
||||
departurePortCode?: string
|
||||
destinationPortCode?: string
|
||||
keyword?: string
|
||||
status?: SailingStatus
|
||||
}
|
||||
|
||||
export interface SailingSaveRequest {
|
||||
templateId?: string | null
|
||||
channelId?: string | null
|
||||
warehouseId: string
|
||||
warehouseName?: string | null
|
||||
channelCode: string
|
||||
channelName: string
|
||||
vesselName: string
|
||||
voyageNo: string
|
||||
departurePortCode: string
|
||||
departurePortName?: string | null
|
||||
destinationPortCode: string
|
||||
destinationPortName?: string | null
|
||||
etdAt: string
|
||||
etaAt?: string | null
|
||||
cutoffAt: string
|
||||
sourceType?: string | null
|
||||
plannedCapacityCbm?: number | null
|
||||
remark?: string | null
|
||||
expectedVersion?: number
|
||||
}
|
||||
|
||||
export interface SailingDetail {
|
||||
sailing: SailingCalendarEntry
|
||||
histories: SailingHistory[]
|
||||
}
|
||||
|
||||
export interface SailingHistory {
|
||||
id: string
|
||||
action: string
|
||||
fromStatus?: string | null
|
||||
toStatus?: string | null
|
||||
reason?: string | null
|
||||
operatorName?: string | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface SailingVolumeDetail {
|
||||
summary: SailingVolumeSummary
|
||||
assignments: SailingAssignment[]
|
||||
}
|
||||
|
||||
export interface SailingAssignment {
|
||||
id: string
|
||||
receiptBatchId?: string | null
|
||||
forecastId?: string | null
|
||||
inboundCode?: string | null
|
||||
receivedAt?: string | null
|
||||
volumeCbm: number
|
||||
status: SailingAssignmentStatus
|
||||
assignmentSource: 'AUTO' | 'MANUAL' | 'ROLLOVER'
|
||||
assignmentVersion?: number | null
|
||||
reason?: string | null
|
||||
}
|
||||
|
||||
export interface SailingExceptionReassignRequest {
|
||||
assignmentId: string
|
||||
targetSailingId: string
|
||||
expectedAssignmentVersion: number
|
||||
expectedTargetVersion: number
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface SailingExceptionReassignResult {
|
||||
oldAssignmentId: string
|
||||
newAssignmentId: string
|
||||
sourceSailingId: string
|
||||
targetSailingId: string
|
||||
assignmentVersion: number
|
||||
matchStatus: SailingAssignmentStatus
|
||||
volumeCbm?: number | null
|
||||
forecastVolumeCbm?: number | null
|
||||
reason?: string | null
|
||||
}
|
||||
|
||||
export type SailingExceptionStatus = Extract<SailingAssignmentStatus, 'LATE' | 'UNMATCHED'>
|
||||
|
||||
export interface SailingExceptionRecord {
|
||||
assignmentId: string
|
||||
inboundCode?: string | null
|
||||
receiptBatchId?: string | null
|
||||
forecastId?: string | null
|
||||
warehouseId: string
|
||||
warehouseName?: string | null
|
||||
channelCode?: string | null
|
||||
channelName?: string | null
|
||||
sourceSailingNo?: string | null
|
||||
sourceVoyageNo?: string | null
|
||||
status: SailingExceptionStatus
|
||||
volumeCbm: number
|
||||
receivedAt?: string | null
|
||||
expectedArrivalAt?: string | null
|
||||
waitingDays?: number | null
|
||||
reason?: string | null
|
||||
assignmentVersion?: number | null
|
||||
}
|
||||
|
||||
export interface SailingExceptionPage {
|
||||
records: SailingExceptionRecord[]
|
||||
page: number
|
||||
size: number
|
||||
total: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
export interface SailingExceptionQuery {
|
||||
warehouseId?: string
|
||||
channelCode?: string
|
||||
status?: SailingExceptionStatus
|
||||
page?: number
|
||||
size?: number
|
||||
}
|
||||
|
||||
export interface SailingBookingAdvice {
|
||||
actualReceivedCbm: number
|
||||
catchableTotalCbm: number
|
||||
planningCapacityCbm?: number | null
|
||||
bookedContainerCount: number
|
||||
recommendedContainerCount: number
|
||||
pendingContainerCount: number
|
||||
scenarios: Array<{
|
||||
containerTypeCode: string
|
||||
containerTypeName: string
|
||||
capacityCbm: number
|
||||
recommendedContainerCount: number
|
||||
pendingContainerCount: number
|
||||
}>
|
||||
calculatedAt: string
|
||||
catchableReceivedCbm?: number | null
|
||||
lateReceivedCbm?: number | null
|
||||
}
|
||||
|
||||
export interface SailingBookingLinkRequest {
|
||||
oceanBookingId: string
|
||||
expectedVersion?: number
|
||||
}
|
||||
|
||||
export type SailingChannelStatus = 'ACTIVE' | 'INACTIVE'
|
||||
export type SailingChannelSourceType = 'CATALOG' | 'SCHEDULE_ONLY'
|
||||
|
||||
export interface SailingChannelResource {
|
||||
id: string
|
||||
channelCode: string
|
||||
channelName: string
|
||||
sourceType: SailingChannelSourceType
|
||||
mappedChannelCode?: string | null
|
||||
defaultDeparturePortCode?: string | null
|
||||
defaultDestinationPortCode?: string | null
|
||||
defaultCutoffDays: number
|
||||
status: SailingChannelStatus
|
||||
remark?: string | null
|
||||
version: number
|
||||
}
|
||||
|
||||
export interface SailingChannelSaveRequest {
|
||||
channelCode: string
|
||||
channelName: string
|
||||
sourceType: SailingChannelSourceType
|
||||
mappedChannelCode?: string | null
|
||||
defaultDeparturePortCode?: string | null
|
||||
defaultDestinationPortCode?: string | null
|
||||
defaultCutoffDays: number
|
||||
remark?: string | null
|
||||
expectedVersion?: number
|
||||
status?: SailingChannelStatus
|
||||
}
|
||||
|
||||
export type SailingTemplateStatus = 'DRAFT' | 'ACTIVE' | 'INACTIVE'
|
||||
|
||||
export interface SailingTemplateResource {
|
||||
id: string
|
||||
templateNo: string
|
||||
channelId: string
|
||||
channelName: string
|
||||
warehouseId: string
|
||||
warehouseName?: string | null
|
||||
weekdays: number[]
|
||||
templateName?: string | null
|
||||
effectiveFrom: string
|
||||
effectiveTo?: string | null
|
||||
departureTime: string
|
||||
cutoffDays: number
|
||||
cutoffLocalTime: string
|
||||
voyageRule?: string | null
|
||||
vesselName?: string | null
|
||||
departurePortCode?: string | null
|
||||
destinationPortCode?: string | null
|
||||
remark?: string | null
|
||||
status: SailingTemplateStatus
|
||||
version: number
|
||||
}
|
||||
|
||||
export interface SailingTemplateSaveRequest {
|
||||
templateName?: string | null
|
||||
channelId: string
|
||||
warehouseId: string
|
||||
warehouseName?: string | null
|
||||
weekdays: number[]
|
||||
effectiveFrom: string
|
||||
effectiveTo?: string | null
|
||||
departureTime: string
|
||||
cutoffDays: number
|
||||
cutoffLocalTime: string
|
||||
voyageRule?: string | null
|
||||
vesselName?: string | null
|
||||
departurePortCode?: string | null
|
||||
destinationPortCode?: string | null
|
||||
remark?: string | null
|
||||
expectedVersion?: number
|
||||
}
|
||||
|
||||
export interface SailingGenerationPreview {
|
||||
templateId: string
|
||||
records: Array<{ date?: string; etdAt: string; cutoffAt: string; voyageNo?: string | null; sailingNo?: string | null; conflict?: boolean; conflictReason?: string | null }>
|
||||
}
|
||||
|
||||
export interface SailingGenerationResult extends SailingGenerationPreview {
|
||||
generatedCount: number
|
||||
skippedCount: number
|
||||
}
|
||||
Reference in New Issue
Block a user