This commit is contained in:
2026-08-04 21:01:39 +08:00
parent 88bd939170
commit 5cb1ca307d
77 changed files with 9914 additions and 131 deletions
@@ -0,0 +1,76 @@
<script setup lang="ts">
import type { InboundFileSummary, InboundInternalNote, InboundPalletLocation, InboundStatusHistory } from './types'
defineProps<{
palletLocations: InboundPalletLocation[]
files: InboundFileSummary[]
internalNotes: InboundInternalNote[]
statusHistory: InboundStatusHistory[]
publicRemark: string | null
}>()
function value(value: string | number | null | undefined) { return value === null || value === undefined || value === '' ? '—' : String(value) }
function number(value: number | null) { return value === null ? '—' : value.toFixed(3) }
function date(value: string | null) {
if (!value) return '—'
const parsed = new Date(value)
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString('zh-CN', { hour12: false })
}
</script>
<template>
<div class="history-panel">
<section class="history-section">
<div class="section-heading"><h3>托盘与库位</h3><span>{{ palletLocations.length }} 条分配</span></div>
<div v-if="palletLocations.length" class="allocation-list">
<div v-for="item in palletLocations" :key="`${item.allocationNo}-${item.palletId}-${item.locationId}`" class="allocation-row">
<div><strong>{{ value(item.palletCode) }}</strong><span>分配 {{ value(item.allocationNo) }}</span></div>
<div><span>{{ number(item.allocatedVolumeCbm) }} CBM</span><span>{{ value(item.allocatedPackageCount) }} 箱</span></div>
<div class="allocation-location">{{ value(item.locationCode) }}</div>
</div>
</div>
<p v-else class="muted-empty">暂无托盘库位分配记录</p>
</section>
<section class="history-section">
<div class="section-heading"><h3>文件记录</h3><span>{{ files.length }} 个文件</span></div>
<div v-if="files.length" class="simple-list"><div v-for="file in files" :key="`${file.fileId}-${file.fileVersion}`"><strong>{{ value(file.fileNameSnapshot) }}</strong><span>{{ value(file.fileType) }} · {{ date(file.uploadedAt) }}</span></div></div>
<p v-else class="muted-empty">暂无文件记录</p>
</section>
<section class="history-section">
<div class="section-heading"><h3>内部备注</h3><span>{{ internalNotes.length }} 条</span></div>
<div v-if="internalNotes.length" class="simple-list"><div v-for="note in internalNotes" :key="String(note.id)"><strong>{{ value(note.createdByName || note.createdBy) }}</strong><span>{{ value(note.content) }} · {{ date(note.createdAt) }}</span></div></div>
<p v-else class="muted-empty">暂无内部备注</p>
</section>
<section class="history-section">
<div class="section-heading"><h3>状态历史</h3><span>{{ statusHistory.length }} 条</span></div>
<ol v-if="statusHistory.length" class="timeline"><li v-for="(history, index) in statusHistory" :key="`${history.occurredAt}-${index}`"><span class="timeline-dot" /><div><strong>{{ value(history.action) }}</strong><span>{{ value(history.fromStatus) }} → {{ value(history.toStatus) }} · {{ value(history.operatorName || history.operatorId) }}</span><small>{{ date(history.occurredAt) }}<template v-if="history.reason"> · {{ history.reason }}</template></small></div></li></ol>
<p v-else class="muted-empty">暂无状态历史</p>
</section>
<section v-if="publicRemark" class="history-section remark-section"><div class="section-heading"><h3>公开备注</h3></div><p>{{ publicRemark }}</p></section>
</div>
</template>
<style scoped>
.history-panel { display: grid; gap: 22px; min-width: 0; }
.history-section { min-width: 0; }
.section-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.section-heading h3 { margin: 0; color: var(--cc-text); font-size: 14px; }
.section-heading span { color: var(--cc-muted); font-size: 12px; }
.allocation-list, .simple-list { overflow: hidden; border: 1px solid var(--cc-border); border-radius: 8px; }
.allocation-row { display: grid; grid-template-columns: 1fr auto minmax(100px, 0.8fr); gap: 12px; align-items: center; padding: 11px 12px; border-bottom: 1px solid var(--cc-border); font-size: 12px; }
.allocation-row:last-child, .simple-list > div:last-child { border-bottom: 0; }
.allocation-row > div, .simple-list > div { min-width: 0; }
.allocation-row span, .simple-list span { display: block; margin-top: 3px; color: var(--cc-muted); }
.allocation-location { overflow-wrap: anywhere; color: var(--cc-primary); text-align: right; }
.simple-list > div { padding: 11px 12px; border-bottom: 1px solid var(--cc-border); font-size: 12px; }
.simple-list strong, .simple-list span { overflow-wrap: anywhere; }
.muted-empty { margin: 0; padding: 18px 12px; border: 1px dashed var(--cc-border); border-radius: 8px; color: var(--cc-muted); font-size: 12px; text-align: center; }
.timeline { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.timeline li { display: grid; grid-template-columns: 16px 1fr; gap: 10px; min-width: 0; padding-bottom: 16px; }
.timeline-dot { width: 8px; height: 8px; margin-top: 5px; border: 2px solid var(--cc-primary); border-radius: 50%; background: #fff; box-shadow: 0 0 0 3px color-mix(in srgb, var(--cc-primary) 12%, transparent); }
.timeline li > div { min-width: 0; padding-bottom: 12px; border-bottom: 1px solid var(--cc-border); }
.timeline strong, .timeline span, .timeline small { display: block; overflow-wrap: anywhere; }
.timeline span, .timeline small { margin-top: 3px; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
.remark-section p { margin: 0; padding: 12px; border-radius: 8px; background: #f8fafc; color: var(--cc-text); font-size: 13px; line-height: 1.6; white-space: pre-wrap; }
@media (max-width: 480px) { .allocation-row { grid-template-columns: 1fr auto; } .allocation-location { grid-column: 1 / -1; text-align: left; } }
</style>
@@ -0,0 +1,23 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
const source = readFileSync(resolve(process.cwd(), 'src/modules/inbounds/core/InboundSummaryPanel.vue'), 'utf8')
describe('InboundSummaryPanel', () => {
it('organizes the aggregate detail into the prototype-aligned backend-backed sections', () => {
expect(source).toContain('基本信息')
expect(source).toContain('仓库与运输')
expect(source).toContain('测量与计费')
expect(source).toContain('入库资源')
expect(source).toContain('summary.deliveryWarehouseName')
expect(source).toContain('summary.chargeableWeightKg')
expect(source).toContain('summary.locationCodes')
expect(source).toContain('summary.notificationStatus')
})
it('does not restore excluded receiving review or customer fee confirmation actions', () => {
expect(source).not.toContain('接收审核')
expect(source).not.toContain('客户确认费用')
})
})
@@ -0,0 +1,124 @@
<script setup lang="ts">
import { Box, MapPin, Scale, Warehouse } from '@lucide/vue'
import type { InboundOrderSummary } from './types'
defineProps<{ summary: InboundOrderSummary }>()
const processLabels: Record<string, string> = {
WAIT_MEASURE: '待测量', MEASURING: '测量中', MEASURE_PENDING: '待审核', MEASURE_REJECTED: '测量驳回',
WAIT_PRICING: '待计费', FEE_CONFIRMING: '待发送费用', INBOUNDING: '入库处理中', INBOUNDED: '已入库', VOIDED: '已作废',
}
function processLabel(status: string) { return processLabels[status] || status || '未知状态' }
function processType(status: string) {
if (status === 'INBOUNDED') return 'success'
if (status === 'MEASURE_REJECTED' || status === 'VOIDED') return 'danger'
if (status === 'MEASURING' || status === 'INBOUNDING') return 'info'
return 'warning'
}
function value(value: string | number | null | undefined) { return value === null || value === undefined || value === '' ? '—' : String(value) }
function number(value: number | null, digits = 3) { return value === null ? '—' : value.toFixed(digits) }
function date(value: string | null) {
if (!value) return '—'
const parsed = new Date(value)
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString('zh-CN', { hour12: false })
}
function yesNo(value: boolean | null) { return value === null ? '—' : value ? '是' : '否' }
const measureReviewLabels: Record<string, string> = { PENDING: '待审核', APPROVED: '审核通过', REJECTED: '审核驳回' }
const feeLabels: Record<string, string> = { UNPRICED: '未计费', CALCULATED: '已试算', CONFIRMED: '已确认', SENT: '已发送', VOIDED: '已作废' }
const notificationLabels: Record<string, string> = { NOT_CREATED: '未创建', PENDING: '待发送', SUCCESS: '发送成功', FAILED: '发送失败' }
function label(value: string | null, labels: Record<string, string>) { return value ? labels[value] || value : '—' }
</script>
<template>
<section class="summary-panel" aria-label="入库单概览">
<div class="summary-heading">
<div>
<h3>{{ summary.inboundCode || `入库单 ${summary.id}` }}</h3>
<p class="summary-subtitle">订单 {{ value(summary.orderNo) }} · {{ value(summary.customerName) }}</p>
</div>
<el-tag :type="processType(summary.processStatus)" effect="light">{{ processLabel(summary.processStatus) }}</el-tag>
</div>
<p v-if="summary.holdFlag" class="hold-note" role="status">当前挂起:{{ summary.currentHoldReason || '待处理异常' }}</p>
<div class="summary-sections">
<section aria-labelledby="inbound-basic-heading">
<h4 id="inbound-basic-heading">基本信息</h4>
<dl class="summary-grid">
<div><dt>头程订单号</dt><dd>{{ value(summary.orderNo) }}</dd></div>
<div><dt>订单状态</dt><dd>{{ value(summary.orderStatusSnapshot) }}</dd></div>
<div><dt>客户</dt><dd>{{ value(summary.customerName) }}</dd></div>
<div><dt>公司</dt><dd>{{ value(summary.companyName) }}</dd></div>
<div><dt>业务类型</dt><dd>{{ value(summary.businessType) }}</dd></div>
<div><dt>渠道</dt><dd>{{ value(summary.channelName || summary.channelCode) }}</dd></div>
<div><dt>计税模式</dt><dd>{{ value(summary.taxMode) }}</dd></div>
<div><dt>装载模式</dt><dd>{{ value(summary.containerMode) }}</dd></div>
<div><dt>商检验</dt><dd>{{ yesNo(summary.customsInspection) }}</dd></div>
<div><dt>创建时间</dt><dd>{{ date(summary.createdAt) }}</dd></div>
</dl>
</section>
<section aria-labelledby="inbound-route-heading">
<h4 id="inbound-route-heading">仓库与运输</h4>
<dl class="summary-grid">
<div><dt><Warehouse :size="14" />收货仓</dt><dd>{{ value(summary.warehouseName) }}</dd></div>
<div><dt>交货仓</dt><dd>{{ value(summary.deliveryWarehouseName) }}</dd></div>
<div><dt><MapPin :size="14" />目的仓</dt><dd>{{ value(summary.destinationName || summary.destinationCode) }}</dd></div>
<div><dt>目的仓类型</dt><dd>{{ value(summary.destinationType) }}</dd></div>
<div><dt>集装箱号</dt><dd>{{ value(summary.containerNo) }}</dd></div>
<div><dt>柜号</dt><dd>{{ value(summary.cabinetNo) }}</dd></div>
<div><dt>船名 / 航次</dt><dd>{{ value(summary.vesselName) }} / {{ value(summary.voyageNo) }}</dd></div>
<div><dt>配载状态</dt><dd>{{ value(summary.loadingStatus) }}</dd></div>
<div><dt>预计到达</dt><dd>{{ date(summary.expectedArrivalAt) }}</dd></div>
<div><dt>开船 / 到港</dt><dd>{{ date(summary.sailingAt) }} / {{ date(summary.portArrivalAt) }}</dd></div>
<div><dt>快递公司</dt><dd>{{ value(summary.expressCompanyName || summary.expressCompanyCode) }}</dd></div>
<div><dt>快递单号</dt><dd>{{ value(summary.expressTrackingNo) }}</dd></div>
</dl>
</section>
<section aria-labelledby="inbound-measure-heading">
<h4 id="inbound-measure-heading">测量与计费</h4>
<dl class="summary-grid">
<div><dt><Box :size="14" />总箱数</dt><dd>{{ value(summary.totalPackageCount) }} 箱</dd></div>
<div><dt><Scale :size="14" />总毛重</dt><dd>{{ number(summary.actualGrossWeightKg) }} kg</dd></div>
<div><dt>总净重</dt><dd>{{ number(summary.actualNetWeightKg) }} kg</dd></div>
<div><dt>总体积</dt><dd>{{ number(summary.actualVolumeCbm) }} CBM</dd></div>
<div><dt>总材积重</dt><dd>{{ number(summary.volumeWeightKg) }} kg</dd></div>
<div><dt>计费重量</dt><dd>{{ number(summary.chargeableWeightKg) }} kg</dd></div>
<div><dt>计费体积</dt><dd>{{ number(summary.chargeableVolumeCbm) }} CBM</dd></div>
<div><dt>测量审核</dt><dd>{{ label(summary.measureReviewStatus, measureReviewLabels) }}</dd></div>
<div><dt>费用状态</dt><dd>{{ label(summary.feeStatus, feeLabels) }}</dd></div>
<div><dt>费用审核状态</dt><dd>{{ value(summary.feeReviewStatus) }}</dd></div>
<div><dt>测量完成时间</dt><dd>{{ date(summary.measureCompletedAt) }}</dd></div>
<div><dt>通知状态</dt><dd>{{ label(summary.notificationStatus, notificationLabels) }}</dd></div>
</dl>
</section>
<section aria-labelledby="inbound-storage-heading">
<h4 id="inbound-storage-heading">入库资源</h4>
<dl class="summary-grid">
<div><dt>总托盘数</dt><dd>{{ value(summary.palletCount) }}</dd></div>
<div><dt>库位位置</dt><dd>{{ value(summary.locationCodes) }}</dd></div>
<div><dt>入库完成时间</dt><dd>{{ date(summary.inboundedAt) }}</dd></div>
<div><dt>最后更新时间</dt><dd>{{ date(summary.updatedAt) }}</dd></div>
</dl>
</section>
</div>
</section>
</template>
<style scoped>
.summary-panel { min-width: 0; }
.summary-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding-bottom: 18px; border-bottom: 1px solid var(--cc-border); }
h3 { margin: 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: 18px; line-height: 1.3; }
.summary-subtitle { margin: 6px 0 0; color: var(--cc-muted); font-size: 12px; }
.summary-sections { display: grid; min-width: 0; gap: 22px; margin-top: 18px; }
.summary-sections section { min-width: 0; }
h4 { margin: 0 0 9px; color: var(--cc-text); font-size: 14px; line-height: 1.4; }
.summary-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); }
.summary-grid > div { min-width: 0; min-height: 66px; padding: 11px 12px; border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }
dt { display: flex; align-items: center; gap: 6px; color: var(--cc-muted); font-size: 12px; }
dd { margin: 5px 0 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: 13px; line-height: 1.5; }
.hold-note { margin: 16px 0 0; padding: 10px 12px; border: 1px solid #f5c26b; border-radius: 8px; background: #fffaeb; color: #93370d; font-size: 12px; line-height: 1.5; }
@media (max-width: 900px) { .summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .summary-heading { align-items: stretch; flex-direction: column; } .summary-grid { grid-template-columns: 1fr; } }
</style>
+86
View File
@@ -0,0 +1,86 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
import { getPlatformInbound, listPlatformInbounds } from './api'
vi.mock('@/shared/api/http', () => ({
http: { get: vi.fn() },
unwrapApiResponse: <T>(response: { data: T }) => response.data,
}))
describe('platform inbound query API', () => {
beforeEach(() => vi.mocked(http.get).mockReset())
it('omits the backend tenant filter for an all-customer list query', async () => {
vi.mocked(http.get).mockResolvedValue({
code: '200', message: 'success', data: { records: [], page: 1, size: 20, total: 0, pages: 0 },
})
await listPlatformInbounds(undefined, { page: 1, size: 20 })
expect(http.get).toHaveBeenCalledWith('/platform/inbounds', { params: { page: 1, size: 20 } })
})
it('uses the implemented non-v1 list path and normalizes page values and IDs', async () => {
vi.mocked(http.get).mockResolvedValue({
code: '200', message: 'success', data: {
records: [{ id: 19001, orderId: 18001, customerId: 10001, totalPackageCount: '6', holdFlag: 'false' }],
page: '1', size: '20', total: '1', pages: '1',
},
})
await expect(listPlatformInbounds('10001', {
page: 1, size: 20, tab: 'WAIT_MEASURE', inboundCode: 'RK', holdFlag: false,
})).resolves.toMatchObject({
records: [{ id: '19001', orderId: '18001', customerId: '10001', totalPackageCount: 6, holdFlag: false }],
total: 1, pages: 1,
})
expect(http.get).toHaveBeenCalledWith('/platform/inbounds', {
params: { tenantId: '10001', page: 1, size: 20, tab: 'WAIT_MEASURE', inboundCode: 'RK', holdFlag: false },
})
})
it('loads a tenant-scoped aggregate detail and keeps nested IDs as strings', async () => {
vi.mocked(http.get).mockResolvedValue({
code: '200', message: 'success', data: {
summary: { id: 19001, orderId: 18001 }, publicRemark: null,
palletLocations: [{ palletId: 21001, locationId: 22001 }], files: [{ fileId: 23001, uploadedBy: 2 }],
internalNotes: [{ id: 24001, createdBy: 2 }], statusHistory: [{ operatorId: 2 }],
},
})
await expect(getPlatformInbound('10001', '19001')).resolves.toMatchObject({
summary: { id: '19001', orderId: '18001' },
palletLocations: [{ palletId: '21001', locationId: '22001' }],
files: [{ fileId: '23001', uploadedBy: '2' }],
internalNotes: [{ id: '24001', createdBy: '2' }],
statusHistory: [{ operatorId: '2' }],
})
expect(http.get).toHaveBeenCalledWith('/platform/inbounds/19001', { params: { tenantId: '10001' } })
})
it('passes the implemented order, process, measurement and fee review filters without renaming them', async () => {
vi.mocked(http.get).mockResolvedValue({
code: '200', message: 'success', data: { records: [], page: 1, size: 20, total: 0, pages: 0 },
})
await listPlatformInbounds('10001', {
page: 1,
size: 20,
processStatus: 'MEASURE_PENDING',
orderStatus: 'APPROVED',
measureReviewStatus: 'PENDING',
feeStatus: 'UNPRICED',
feeReviewStatus: 'PENDING',
warehouseId: '9007199254740993',
deliveryWarehouseId: '9007199254740995',
})
expect(http.get).toHaveBeenCalledWith('/platform/inbounds', {
params: {
tenantId: '10001', page: 1, size: 20, processStatus: 'MEASURE_PENDING',
orderStatus: 'APPROVED', measureReviewStatus: 'PENDING', feeStatus: 'UNPRICED', feeReviewStatus: 'PENDING',
warehouseId: '9007199254740993', deliveryWarehouseId: '9007199254740995',
},
})
})
})
+127
View File
@@ -0,0 +1,127 @@
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type {
InboundFileSummary,
InboundInternalNote,
InboundListQuery,
InboundOrderDetail,
InboundOrderSummary,
InboundPageResult,
InboundPalletLocation,
InboundStatusHistory,
} from './types'
const basePath = '/platform/inbounds'
function id(value: unknown): string | null {
return value === null || value === undefined ? null : String(value)
}
function numberValue(value: unknown): number | null {
if (value === null || value === undefined || value === '') return null
const parsed = typeof value === 'number' ? value : Number(value)
return Number.isFinite(parsed) ? parsed : null
}
function integerValue(value: unknown): number | null {
const parsed = numberValue(value)
return parsed === null ? null : Math.trunc(parsed)
}
function boolValue(value: unknown): boolean | null {
if (value === null || value === undefined) return null
if (typeof value === 'boolean') return value
if (value === 'true' || value === 1 || value === '1') return true
if (value === 'false' || value === 0 || value === '0') return false
return null
}
export function normalizeInboundSummary(raw: InboundOrderSummary): InboundOrderSummary {
return {
...raw,
id: String(raw.id),
orderId: id(raw.orderId),
customerId: id(raw.customerId),
companyId: id(raw.companyId),
warehouseId: id(raw.warehouseId),
deliveryWarehouseId: id(raw.deliveryWarehouseId),
destinationWarehouseId: id(raw.destinationWarehouseId),
version: integerValue(raw.version),
customsInspection: boolValue(raw.customsInspection),
holdFlag: boolValue(raw.holdFlag),
totalPackageCount: integerValue(raw.totalPackageCount),
actualGrossWeightKg: numberValue(raw.actualGrossWeightKg),
actualNetWeightKg: numberValue(raw.actualNetWeightKg),
actualVolumeCbm: numberValue(raw.actualVolumeCbm),
volumeWeightKg: numberValue(raw.volumeWeightKg),
chargeableWeightKg: numberValue(raw.chargeableWeightKg),
chargeableVolumeCbm: numberValue(raw.chargeableVolumeCbm),
palletCount: integerValue(raw.palletCount),
}
}
function normalizePage(raw: InboundPageResult): InboundPageResult {
return {
records: (raw.records || []).map(normalizeInboundSummary),
page: integerValue(raw.page) || 1,
size: integerValue(raw.size) || 20,
total: integerValue(raw.total) || 0,
pages: integerValue(raw.pages) || 0,
}
}
function normalizePallet(raw: InboundPalletLocation): InboundPalletLocation {
return {
...raw,
allocationNo: integerValue(raw.allocationNo),
warehouseId: id(raw.warehouseId),
palletId: id(raw.palletId),
locationId: id(raw.locationId),
palletCapacityCbm: numberValue(raw.palletCapacityCbm),
allocatedVolumeCbm: numberValue(raw.allocatedVolumeCbm),
allocatedPackageCount: integerValue(raw.allocatedPackageCount),
}
}
function normalizeFile(raw: InboundFileSummary): InboundFileSummary {
return { ...raw, fileId: id(raw.fileId), uploadedBy: id(raw.uploadedBy), fileVersion: integerValue(raw.fileVersion) }
}
function normalizeNote(raw: InboundInternalNote): InboundInternalNote {
return { ...raw, id: id(raw.id), createdBy: id(raw.createdBy) }
}
function normalizeHistory(raw: InboundStatusHistory): InboundStatusHistory {
return { ...raw, operatorId: id(raw.operatorId) }
}
function normalizeDetail(raw: InboundOrderDetail): InboundOrderDetail {
return {
...raw,
summary: normalizeInboundSummary(raw.summary),
palletLocations: (raw.palletLocations || []).map(normalizePallet),
files: (raw.files || []).map(normalizeFile),
internalNotes: (raw.internalNotes || []).map(normalizeNote),
statusHistory: (raw.statusHistory || []).map(normalizeHistory),
}
}
function queryParams(tenantId: string | undefined, query: InboundListQuery) {
return Object.fromEntries(
Object.entries({ tenantId, ...query }).filter(([, value]) => value !== undefined && value !== ''),
)
}
export async function listPlatformInbounds(tenantId: string | undefined, query: InboundListQuery) {
const response = await http.get<ApiEnvelope<InboundPageResult>, ApiEnvelope<InboundPageResult>>(basePath, {
params: queryParams(tenantId, query),
})
return normalizePage(unwrapApiResponse(response))
}
export async function getPlatformInbound(tenantId: string, inboundId: string) {
const response = await http.get<ApiEnvelope<InboundOrderDetail>, ApiEnvelope<InboundOrderDetail>>(
`${basePath}/${inboundId}`,
{ params: { tenantId } },
)
return normalizeDetail(unwrapApiResponse(response))
}
+153
View File
@@ -0,0 +1,153 @@
export type InboundListTab = 'WAIT_MEASURE' | 'WAIT_REVIEW' | 'WAIT_FEE_SEND' | 'INBOUNDED'
export type InboundProcessStatus =
| 'WAIT_MEASURE'
| 'MEASURING'
| 'MEASURE_PENDING'
| 'MEASURE_REJECTED'
| 'WAIT_PRICING'
| 'FEE_CONFIRMING'
| 'INBOUNDING'
| 'INBOUNDED'
| 'VOIDED'
export type InboundFeeStatus = 'UNPRICED' | 'CALCULATED' | 'CONFIRMED' | 'SENT' | 'VOIDED' | string
export interface InboundOrderSummary {
id: string
orderId: string | null
orderNo: string | null
inboundCode: string | null
processStatus: InboundProcessStatus | string
orderStatusSnapshot: string | null
measureReviewStatus: string | null
feeStatus: InboundFeeStatus | null
feeReviewStatus: string | null
notificationStatus: string | null
version: number | null
expectedArrivalAt: string | null
customerId: string | null
customerName: string | null
companyId: string | null
companyName: string | null
warehouseId: string | null
warehouseName: string | null
deliveryWarehouseId: string | null
deliveryWarehouseName: string | null
businessType: string | null
channelCode: string | null
channelName: string | null
taxMode: string | null
containerMode: string | null
customsInspection: boolean | null
destinationType: string | null
destinationWarehouseId: string | null
destinationCode: string | null
destinationName: string | null
holdFlag: boolean | null
currentHoldReason: string | null
totalPackageCount: number | null
actualGrossWeightKg: number | null
actualNetWeightKg: number | null
actualVolumeCbm: number | null
volumeWeightKg: number | null
chargeableWeightKg: number | null
chargeableVolumeCbm: number | null
palletCount: number | null
locationCodes: string | null
measureCompletedAt: string | null
inboundedAt: string | null
expressCompanyCode: string | null
expressCompanyName: string | null
expressTrackingNo: string | null
loadingStatus: string | null
containerNo: string | null
cabinetNo: string | null
vesselName: string | null
voyageNo: string | null
sailingAt: string | null
portArrivalAt: string | null
createdAt: string | null
updatedAt: string | null
}
export interface InboundPalletLocation {
allocationNo: number | null
warehouseId: string | null
palletId: string | null
palletCode: string | null
palletCapacityCbm: number | null
allocatedVolumeCbm: number | null
allocatedPackageCount: number | null
locationId: string | null
locationCode: string | null
reservationNo: string | null
allocationStatus: string | null
}
export interface InboundFileSummary {
fileId: string | null
fileType: string | null
fileNameSnapshot: string | null
visibility: string | null
fileVersion: number | null
uploadedBy: string | null
uploadedAt: string | null
}
export interface InboundInternalNote {
id: string | null
content: string | null
createdBy: string | null
createdByName: string | null
createdAt: string | null
}
export interface InboundStatusHistory {
fromStatus: string | null
toStatus: string | null
action: string | null
reason: string | null
operatorId: string | null
operatorName: string | null
occurredAt: string | null
}
export interface InboundOrderDetail {
summary: InboundOrderSummary
publicRemark: string | null
palletLocations: InboundPalletLocation[]
files: InboundFileSummary[]
internalNotes: InboundInternalNote[]
statusHistory: InboundStatusHistory[]
}
export interface InboundPageResult {
records: InboundOrderSummary[]
page: number
size: number
total: number
pages: number
}
export interface InboundListQuery {
page: number
size: number
tab?: InboundListTab
inboundCode?: string
orderNo?: string
processStatus?: string
orderStatus?: string
measureReviewStatus?: string
customerName?: string
destinationCode?: string
feeStatus?: string
feeReviewStatus?: string
customerId?: string
holdFlag?: boolean
companyId?: string
warehouseId?: string
deliveryWarehouseId?: string
expectedArrivalFrom?: string
expectedArrivalTo?: string
}