test(admin): cover booking-scoped trucking
This commit is contained in:
@@ -41,6 +41,16 @@ describe('truck appointment API', () => {
|
|||||||
}, { headers: { 'Idempotency-Key': expect.stringContaining('truck-appointment-pickup-9001-') } })
|
}, { headers: { 'Idempotency-Key': expect.stringContaining('truck-appointment-pickup-9001-') } })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.each(['SUBMIT', 'DISPATCH', 'PICKUP'] as const)('sends %s without any container field', async (action) => {
|
||||||
|
vi.mocked(http.post).mockResolvedValue({ data: { id: '9001', status: 'PENDING_DISPATCH' } })
|
||||||
|
await performTruckAppointmentAction('9001', action, { expectedVersion: 4, reason: '演示流转' })
|
||||||
|
const body = vi.mocked(http.post).mock.calls.at(-1)?.[1] as Record<string, unknown>
|
||||||
|
expect(body).toEqual({ expectedVersion: 4, reason: '演示流转' })
|
||||||
|
expect(body).not.toHaveProperty('containerNo')
|
||||||
|
expect(body).not.toHaveProperty('containerBookingId')
|
||||||
|
expect(body).not.toHaveProperty('loadingPlanId')
|
||||||
|
})
|
||||||
|
|
||||||
it('drops legacy customer fields instead of forwarding hidden compatibility data', async () => {
|
it('drops legacy customer fields instead of forwarding hidden compatibility data', async () => {
|
||||||
vi.mocked(http.post).mockResolvedValueOnce({ data: { id: '9001', status: 'DRAFT', attachmentCount: 0 } })
|
vi.mocked(http.post).mockResolvedValueOnce({ data: { id: '9001', status: 'DRAFT', attachmentCount: 0 } })
|
||||||
const payload = { carrierId: '200', oceanBookingId: '300', containerBookingId: '400', loadingPlanId: '500', plannedLoadingAt: '2026-08-10T10:00:00', expectedVersion: 0, currency: 'EUR', receivableAmount: '999.00', customerId: 'legacy-customer', customerName: '旧客户快照' }
|
const payload = { carrierId: '200', oceanBookingId: '300', containerBookingId: '400', loadingPlanId: '500', plannedLoadingAt: '2026-08-10T10:00:00', expectedVersion: 0, currency: 'EUR', receivableAmount: '999.00', customerId: 'legacy-customer', customerName: '旧客户快照' }
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
|||||||
const labels = wrapper.findAllComponents({ name: 'ElOption' }).map((option) => String(option.props('label') || ''))
|
const labels = wrapper.findAllComponents({ name: 'ElOption' }).map((option) => String(option.props('label') || ''))
|
||||||
expect(labels.some((label) => label.includes('OB-1') && label.includes('BL-1'))).toBe(true)
|
expect(labels.some((label) => label.includes('OB-1') && label.includes('BL-1'))).toBe(true)
|
||||||
expect(labels.some((label) => label.includes('CAB-'))).toBe(false)
|
expect(labels.some((label) => label.includes('CAB-'))).toBe(false)
|
||||||
|
expect(candidates[0]).not.toHaveProperty('containerBookingId')
|
||||||
const emitted = wrapper.emitted('update:modelValue')?.at(-1)?.[0] as SaveTruckAppointmentRequest
|
const emitted = wrapper.emitted('update:modelValue')?.at(-1)?.[0] as SaveTruckAppointmentRequest
|
||||||
expect(emitted.oceanBookingId).toBe('booking-1')
|
expect(emitted.oceanBookingId).toBe('booking-1')
|
||||||
expect(emitted.billOfLadingNo).toBe('BL-1')
|
expect(emitted.billOfLadingNo).toBe('BL-1')
|
||||||
@@ -65,6 +66,30 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
|||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps only the latest candidate response when option reloads overlap', async () => {
|
||||||
|
let resolveSlow!: (value: Awaited<ReturnType<typeof listTruckAppointmentCandidates>>) => void
|
||||||
|
let resolveFast!: (value: Awaited<ReturnType<typeof listTruckAppointmentCandidates>>) => void
|
||||||
|
vi.mocked(listTruckAppointmentCandidates)
|
||||||
|
.mockResolvedValueOnce({ records: candidates, page: 1, size: 200, total: candidates.length, pages: 1 })
|
||||||
|
.mockImplementationOnce(() => new Promise((resolve) => { resolveSlow = resolve }))
|
||||||
|
.mockImplementationOnce(() => new Promise((resolve) => { resolveFast = resolve }))
|
||||||
|
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
||||||
|
await flushPromises()
|
||||||
|
const vm = wrapper.vm as unknown as { loadOptions: () => Promise<void> }
|
||||||
|
const slow = vm.loadOptions()
|
||||||
|
const fast = vm.loadOptions()
|
||||||
|
resolveFast({ records: [candidates[1]!], page: 1, size: 200, total: 1, pages: 1 })
|
||||||
|
await fast
|
||||||
|
resolveSlow({ records: [candidates[0]!], page: 1, size: 200, total: 1, pages: 1 })
|
||||||
|
await slow
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const labels = wrapper.findAllComponents({ name: 'ElOption' }).map((option) => String(option.props('label') || ''))
|
||||||
|
expect(labels.some((label) => label.includes('OB-2'))).toBe(true)
|
||||||
|
expect(labels.some((label) => label.includes('OB-1'))).toBe(false)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
it('replaces frozen booking snapshots when switching the selected booking', async () => {
|
it('replaces frozen booking snapshots when switching the selected booking', async () => {
|
||||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|||||||
@@ -69,9 +69,10 @@ describe('TruckAppointmentForm interaction contract', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('keeps container identity out of the booking-scoped form', () => {
|
it('keeps container identity out of the booking-scoped form', () => {
|
||||||
expect(source).not.toContain('柜次号:')
|
expect(source).not.toContain('柜次')
|
||||||
expect(source).not.toContain('集装箱号:')
|
expect(source).not.toContain('装柜计划')
|
||||||
expect(source).toContain('真实柜次及集装箱号在货柜与预装流程维护')
|
expect(source).not.toContain('集装箱号')
|
||||||
|
expect(source).toContain('货柜资料在货柜与预装流程维护')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps the form usable at 390px', () => {
|
it('keeps the form usable at 390px', () => {
|
||||||
@@ -94,7 +95,7 @@ describe('TruckAppointmentForm interaction contract', () => {
|
|||||||
expect(source).not.toContain('应收拖车费')
|
expect(source).not.toContain('应收拖车费')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses four compact desktop columns while retaining two-column relations and wide text fields', () => {
|
it('keeps the 1920 desktop contract compact without horizontal form overflow', () => {
|
||||||
expect(source).toContain('@media (min-width: 1200px)')
|
expect(source).toContain('@media (min-width: 1200px)')
|
||||||
expect(source).toContain('max-width:25%; flex:0 0 25%')
|
expect(source).toContain('max-width:25%; flex:0 0 25%')
|
||||||
expect(source).toContain('class="relation-column"')
|
expect(source).toContain('class="relation-column"')
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const phoneRule = (label: string) => ({ validator: (_rule: unknown, value: unkno
|
|||||||
}, trigger: 'blur' })
|
}, trigger: 'blur' })
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
oceanBookingId: [{ required: true, message: '请选择已放舱订舱', trigger: 'change' }, { validator: (_rule, value, callback) => {
|
oceanBookingId: [{ required: true, message: '请选择已放舱订舱', trigger: 'change' }, { validator: (_rule, value, callback) => {
|
||||||
if (!(bookings.value.some((item) => item.id === String(value)) || (relationLocked.value && selectedBookingFallback.value?.id === String(value)))) return callback(new Error('请选择仍有可预约柜次的已放舱订舱'))
|
if (!(bookings.value.some((item) => item.id === String(value)) || (relationLocked.value && selectedBookingFallback.value?.id === String(value)))) return callback(new Error('请选择可创建拖车单的已放舱订舱'))
|
||||||
if (bookingContainersError.value) return callback(new Error('订舱资料状态加载失败,请重试'))
|
if (bookingContainersError.value) return callback(new Error('订舱资料状态加载失败,请重试'))
|
||||||
if (!draft.billOfLadingNo?.trim()) return callback(new Error('所选订舱缺少有效提单号'))
|
if (!draft.billOfLadingNo?.trim()) return callback(new Error('所选订舱缺少有效提单号'))
|
||||||
return callback(bookingDocumentReady.value ? undefined : new Error('所选订舱缺少有效的订舱确认文件或提单文件'))
|
return callback(bookingDocumentReady.value ? undefined : new Error('所选订舱缺少有效的订舱确认文件或提单文件'))
|
||||||
@@ -124,7 +124,7 @@ async function loadOptions() {
|
|||||||
if (sequence !== optionsLoadSequence) return
|
if (sequence !== optionsLoadSequence) return
|
||||||
if (results[0].status === 'fulfilled') carriers.value = results[0].value.records.filter((item) => item.category === 'TRUCKING' && item.status === 'ACTIVE')
|
if (results[0].status === 'fulfilled') carriers.value = results[0].value.records.filter((item) => item.category === 'TRUCKING' && item.status === 'ACTIVE')
|
||||||
if (results[1].status === 'fulfilled') candidates.value = results[1].value.records
|
if (results[1].status === 'fulfilled') candidates.value = results[1].value.records
|
||||||
const failed = ['承运商', '可预约订舱柜次'].filter((_, index) => results[index]?.status === 'rejected')
|
const failed = ['承运商', '可用订舱'].filter((_, index) => results[index]?.status === 'rejected')
|
||||||
if (failed.length) optionsError.value = `${failed.join('、')}选项加载失败`
|
if (failed.length) optionsError.value = `${failed.join('、')}选项加载失败`
|
||||||
optionsLoading.value = false
|
optionsLoading.value = false
|
||||||
if (relationLocked.value && draft.oceanBookingId) await loadCurrentBookingContext(String(draft.oceanBookingId))
|
if (relationLocked.value && draft.oceanBookingId) await loadCurrentBookingContext(String(draft.oceanBookingId))
|
||||||
@@ -140,7 +140,7 @@ async function loadOptions() {
|
|||||||
}
|
}
|
||||||
async function validate() { return formRef.value?.validate() }
|
async function validate() { return formRef.value?.validate() }
|
||||||
async function submitForm() { if (props.submitting) return; if (await formRef.value?.validate()) emit('submit') }
|
async function submitForm() { if (props.submitting) return; if (await formRef.value?.validate()) emit('submit') }
|
||||||
defineExpose({ validate })
|
defineExpose({ validate, loadOptions })
|
||||||
onMounted(() => { void loadOptions() })
|
onMounted(() => { void loadOptions() })
|
||||||
watch(() => props.readonly, (readonly) => { if (!readonly && !carriers.value.length) void loadOptions() })
|
watch(() => props.readonly, (readonly) => { if (!readonly && !carriers.value.length) void loadOptions() })
|
||||||
watch(() => props.resource?.id, (id, previous) => { if (id && id !== previous) void loadOptions() })
|
watch(() => props.resource?.id, (id, previous) => { if (id && id !== previous) void loadOptions() })
|
||||||
@@ -148,7 +148,7 @@ watch(() => props.resource?.id, (id, previous) => { if (id && id !== previous) v
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form ref="formRef" :model="draft" :rules="rules" label-position="top" :disabled="readonly || submitting" class="truck-form">
|
<el-form ref="formRef" :model="draft" :rules="rules" label-position="top" :disabled="readonly || submitting" class="truck-form">
|
||||||
<div class="relation-note"><span>一张已放舱订舱对应一张有效拖车单;仓库和提单号由订舱冻结资料带入,真实柜次及集装箱号在货柜与预装流程维护,最终资格仍由服务端校验。</span><div v-if="$slots['relation-note-actions']" class="relation-note__actions"><slot name="relation-note-actions" /></div></div>
|
<div class="relation-note"><span>一张已放舱订舱对应一张有效拖车单;仓库和提单号由订舱冻结资料带入,货柜资料在货柜与预装流程维护,最终资格仍由服务端校验。</span><div v-if="$slots['relation-note-actions']" class="relation-note__actions"><slot name="relation-note-actions" /></div></div>
|
||||||
<el-alert v-if="relationWarning" type="warning" :closable="false" show-icon :title="relationWarning" class="relation-alert" />
|
<el-alert v-if="relationWarning" type="warning" :closable="false" show-icon :title="relationWarning" class="relation-alert" />
|
||||||
<el-alert v-if="optionsError" type="warning" :closable="false" show-icon :title="optionsError" class="relation-alert"><el-button link type="warning" :loading="optionsLoading" @click="loadOptions">重新加载选项</el-button></el-alert>
|
<el-alert v-if="optionsError" type="warning" :closable="false" show-icon :title="optionsError" class="relation-alert"><el-button link type="warning" :loading="optionsLoading" @click="loadOptions">重新加载选项</el-button></el-alert>
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ describe('TruckAppointmentDetailView status contract', () => {
|
|||||||
expect(source).toContain('v-if="relatedError"')
|
expect(source).toContain('v-if="relatedError"')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('never exposes container identity or operator database ids', () => {
|
||||||
|
expect(source).not.toContain('cabinetNo')
|
||||||
|
expect(source).not.toContain('containerNo')
|
||||||
|
expect(source).not.toContain('containerBookingId')
|
||||||
|
expect(source).not.toContain('loadingPlanId')
|
||||||
|
expect(source).not.toContain('item.operatorId')
|
||||||
|
})
|
||||||
|
|
||||||
it('reloads a cached detail after an error without showing stale resource data', () => {
|
it('reloads a cached detail after an error without showing stale resource data', () => {
|
||||||
expect(source).toContain("resource.value = null; bookingReadiness.value = null; history.value = []; relatedError.value = ''")
|
expect(source).toContain("resource.value = null; bookingReadiness.value = null; history.value = []; relatedError.value = ''")
|
||||||
expect(source).toContain('onActivated(() => {')
|
expect(source).toContain('onActivated(() => {')
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ onActivated(() => {
|
|||||||
<LoadErrorState v-if="error && !resource" title="详情加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
<LoadErrorState v-if="error && !resource" title="详情加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||||||
<template v-else-if="resource">
|
<template v-else-if="resource">
|
||||||
<section class="surface-panel form-panel"><TruckAppointmentForm v-model="form" :resource="resource" :readonly="!canEdit" :submitting="saving" @submit="submit" @attachments="attachmentOpen = true" /></section>
|
<section class="surface-panel form-panel"><TruckAppointmentForm v-model="form" :resource="resource" :readonly="!canEdit" :submitting="saving" @submit="submit" @attachments="attachmentOpen = true" /></section>
|
||||||
<section class="surface-panel history-section"><h3>状态历史</h3><el-empty v-if="!history.length" description="暂无状态历史" :image-size="48" /><ol v-else><li v-for="(item, index) in history" :key="item.id || `${item.createdAt}-${index}`"><strong>{{ truckAppointmentActionLabel(item.action) }}</strong><span>{{ truckAppointmentStatusLabel(item.fromStatus) }} → {{ truckAppointmentStatusLabel(item.toStatus) }}</span><small>{{ item.operatorName || item.operatorId || '系统' }} · {{ item.createdAt || item.occurredAt || '—' }}<template v-if="item.reason"> · {{ item.reason }}</template></small></li></ol></section>
|
<section class="surface-panel history-section"><h3>状态历史</h3><el-empty v-if="!history.length" description="暂无状态历史" :image-size="48" /><ol v-else><li v-for="(item, index) in history" :key="item.id || `${item.createdAt}-${index}`"><strong>{{ truckAppointmentActionLabel(item.action) }}</strong><span>{{ truckAppointmentStatusLabel(item.fromStatus) }} → {{ truckAppointmentStatusLabel(item.toStatus) }}</span><small>{{ item.operatorName || '系统' }} · {{ item.createdAt || item.occurredAt || '—' }}<template v-if="item.reason"> · {{ item.reason }}</template></small></li></ol></section>
|
||||||
<TruckAttachmentDialog v-model="attachmentOpen" :appointment-id="id" :attachments="resource.attachments || []" :can-manage="canManageAttachments" @changed="loadRelated" />
|
<TruckAttachmentDialog v-model="attachmentOpen" :appointment-id="id" :attachments="resource.attachments || []" :can-manage="canManageAttachments" @changed="loadRelated" />
|
||||||
</template>
|
</template>
|
||||||
</DetailPageLayout>
|
</DetailPageLayout>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import ElementPlus from 'element-plus'
|
|||||||
import { createPinia, setActivePinia } from 'pinia'
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
import { flushPromises, mount } from '@vue/test-utils'
|
import { flushPromises, mount } from '@vue/test-utils'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { ApiResponseError } from '@/shared/api/http'
|
||||||
import TruckAppointmentsView from './TruckAppointmentsView.vue'
|
import TruckAppointmentsView from './TruckAppointmentsView.vue'
|
||||||
import { listTruckAppointments } from '../api/platformTrucking'
|
import { listTruckAppointments } from '../api/platformTrucking'
|
||||||
import type { TruckAppointmentResource } from '../types'
|
import type { TruckAppointmentResource } from '../types'
|
||||||
@@ -62,10 +63,10 @@ describe('TruckAppointmentsView', () => {
|
|||||||
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
expect(wrapper.find('button[aria-label="查看 9002"]').exists()).toBe(true)
|
|
||||||
expect(wrapper.find('button[aria-label="查看拖车预约详情"]').exists()).toBe(true)
|
expect(wrapper.find('button[aria-label="查看拖车预约详情"]').exists()).toBe(true)
|
||||||
const labels = wrapper.findAll('button[aria-label]').map((button) => button.attributes('aria-label'))
|
const labels = wrapper.findAll('button[aria-label]').map((button) => button.attributes('aria-label'))
|
||||||
expect(labels.every((label) => Boolean(label) && !/undefined|null/.test(label ?? ''))).toBe(true)
|
expect(labels.every((label) => Boolean(label) && !/undefined|null/.test(label ?? ''))).toBe(true)
|
||||||
|
expect(labels.every((label) => !label?.includes('9002'))).toBe(true)
|
||||||
expect(source).toContain("appointmentActionLabel('编辑', row as TruckAppointmentResource)")
|
expect(source).toContain("appointmentActionLabel('编辑', row as TruckAppointmentResource)")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -91,6 +92,48 @@ describe('TruckAppointmentsView', () => {
|
|||||||
expect(source.match(/empty-text=/g)).toHaveLength(1)
|
expect(source.match(/empty-text=/g)).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders the server empty state', async () => {
|
||||||
|
vi.mocked(listTruckAppointments).mockResolvedValue({ records: [], page: 1, size: 20, total: 0, pages: 1 })
|
||||||
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.text()).toContain('暂无符合筛选条件的拖车预约')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('marks the table busy while the list request is pending', async () => {
|
||||||
|
let resolveLoad!: (value: Awaited<ReturnType<typeof listTruckAppointments>>) => void
|
||||||
|
vi.mocked(listTruckAppointments).mockImplementationOnce(() => new Promise((resolve) => { resolveLoad = resolve }))
|
||||||
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.find('section[aria-busy="true"]').exists()).toBe(true)
|
||||||
|
resolveLoad({ records: [row], page: 1, size: 20, total: 1, pages: 1 })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.find('section[aria-busy="false"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps a recoverable load error and retries without losing the page', async () => {
|
||||||
|
vi.mocked(listTruckAppointments).mockRejectedValueOnce(new ApiResponseError('候选服务暂不可用', 'A0503', 503, undefined, 'req-truck', 'trace-truck'))
|
||||||
|
const wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.text()).toContain('候选服务暂不可用')
|
||||||
|
expect(wrapper.text()).toContain('req-truck')
|
||||||
|
vi.mocked(listTruckAppointments).mockResolvedValueOnce({ records: [row], page: 1, size: 20, total: 1, pages: 1 })
|
||||||
|
await wrapper.get('button').trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
expect(listTruckAppointments).toHaveBeenCalledTimes(2)
|
||||||
|
expect(wrapper.text()).toContain('TA-9001')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows create only with the independent create permission', async () => {
|
||||||
|
let wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.text()).not.toContain('新增拖车')
|
||||||
|
wrapper.unmount()
|
||||||
|
useAuthStore().permissions = ['platform:firstleg:truck-appointment:list', 'platform:firstleg:truck-appointment:create']
|
||||||
|
wrapper = mount(TruckAppointmentsView, { global: { plugins: [ElementPlus] } })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.text()).toContain('新增拖车')
|
||||||
|
})
|
||||||
|
|
||||||
it('shows the two frequent row actions directly without a more menu', () => {
|
it('shows the two frequent row actions directly without a more menu', () => {
|
||||||
expect(source).not.toContain('platform:firstleg:truck-appointment:view')
|
expect(source).not.toContain('platform:firstleg:truck-appointment:view')
|
||||||
expect(source).toContain("access.can('platform:firstleg:truck-appointment:edit') && ['DRAFT', 'PENDING_DISPATCH', 'RESCHEDULED'].includes(row.status)")
|
expect(source).toContain("access.can('platform:firstleg:truck-appointment:edit') && ['DRAFT', 'PENDING_DISPATCH', 'RESCHEDULED'].includes(row.status)")
|
||||||
|
|||||||
@@ -47,9 +47,7 @@ function formatAmount(value?: string | null, currency?: string | null) {
|
|||||||
return currency ? `${display} ${currency}` : display
|
return currency ? `${display} ${currency}` : display
|
||||||
}
|
}
|
||||||
function appointmentActionLabel(action: '查看' | '编辑', row: TruckAppointmentResource) {
|
function appointmentActionLabel(action: '查看' | '编辑', row: TruckAppointmentResource) {
|
||||||
const identifier = [row.appointmentNo, row.id]
|
const identifier = String(row.appointmentNo ?? '').trim()
|
||||||
.map((value) => String(value ?? '').trim())
|
|
||||||
.find(Boolean)
|
|
||||||
return identifier ? `${action} ${identifier}` : `${action}拖车预约详情`
|
return identifier ? `${action} ${identifier}` : `${action}拖车预约详情`
|
||||||
}
|
}
|
||||||
function statusLabel(value: unknown) {
|
function statusLabel(value: unknown) {
|
||||||
|
|||||||
Reference in New Issue
Block a user