优化
This commit is contained in:
@@ -108,7 +108,8 @@ describe('four-type appointment calendar', () => {
|
||||
expect(wrapper.text()).toContain('正在读取安排')
|
||||
expect(wrapper.text()).not.toContain('当天暂无安排')
|
||||
await wrapper.setProps({ loading: false, incomplete: true })
|
||||
expect(wrapper.text()).toContain('部分数据不可用')
|
||||
expect(wrapper.get('[aria-label="当天安排"] header').text()).toContain('当前已加载 0 项安排')
|
||||
expect(wrapper.text()).not.toContain('部分数据不可用')
|
||||
expect(wrapper.text()).not.toContain('当天暂无安排')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -70,9 +70,9 @@ function changeMode(next: CalendarMode) {
|
||||
</div>
|
||||
</div>
|
||||
<aside class="calendar-detail workbench-panel is-flush" aria-label="当天安排">
|
||||
<header><strong>{{ undated ? '未定日期' : selectedDate }}</strong><p v-if="loading">正在读取安排…</p><p v-else>{{ incomplete ? '当前已加载' : undated ? '共有' : '当天共有' }} <b>{{ selectedEvents.length }}</b> 项安排<span v-if="incomplete">(部分数据不可用)</span></p></header>
|
||||
<header><strong>{{ undated ? '未定日期' : selectedDate }}</strong><p v-if="loading">正在读取安排…</p><p v-else>{{ incomplete ? '当前已加载' : undated ? '共有' : '当天共有' }} <b>{{ selectedEvents.length }}</b> 项安排</p></header>
|
||||
<div class="detail-events" aria-live="polite">
|
||||
<p v-if="!selectedEvents.length && !loading" class="empty-day">{{ incomplete ? '已加载数据中没有安排,请先处理上方数据提示。' : undated ? '没有未定日期的安排' : '当天暂无安排' }}</p>
|
||||
<p v-if="!selectedEvents.length && !loading" class="empty-day">{{ incomplete ? '已加载数据中没有安排。' : undated ? '没有未定日期的安排' : '当天暂无安排' }}</p>
|
||||
<article v-for="event in selectedEvents" :key="event.key" class="detail-event">
|
||||
<div class="event-heading"><span class="event-type" :class="event.type">{{ scheduleLabels[event.type] }}</span><span>{{ event.time || (event.date ? '日期计划' : '日期未定') }}</span></div>
|
||||
<h3>{{ event.title }}</h3><p v-if="event.subtitle" class="event-subtitle">{{ event.subtitle }}</p>
|
||||
|
||||
@@ -161,7 +161,7 @@ describe('simplified appointment entry', () => {
|
||||
expect(routing.push).toHaveBeenCalledWith({ path: '/overseas-warehouse/dispatches/DS%2F1', query: { appointmentReturn: '/appointment-calendar?date=2026-10-21&mode=week' } })
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('retains a complete dispatch with its container search fields while naming a different unresolved dispatch and its reason', async () => {
|
||||
it('retains a complete dispatch with its container search fields without showing an unresolved dispatch notice', async () => {
|
||||
const good = dispatchEntry()
|
||||
const unknown = dispatchEntry({ dispatchNo: 'PC-TIMEZONE-MISSING', containerNumbers: ['OTHER7654321'], originTimezone: null, incompleteReasons: ['ORIGIN_TIMEZONE_UNKNOWN'] })
|
||||
vi.mocked(listTripCalendar).mockResolvedValueOnce({ records: [good, good, unknown], unresolvedRecords: [] })
|
||||
@@ -173,11 +173,7 @@ describe('simplified appointment entry', () => {
|
||||
expect(dispatches[0]!.fields).toContainEqual({ label: '关联柜号', value: 'TSTU1234567、TSTU7654321' })
|
||||
expect(dispatches[0]!.fields).toContainEqual({ label: '计划出发(起运仓当地)', value: '2026-10-22 09:00' })
|
||||
expect(panel.props('incomplete')).toBe(true)
|
||||
const notice = wrapper.get('.data-notice').text()
|
||||
expect(notice).toContain('1 条排车未能定位到日历日期')
|
||||
expect(notice).toContain('PC-TIMEZONE-MISSING:起运仓时区待核实')
|
||||
expect(notice).toContain('无法确定当地日期')
|
||||
expect(notice).not.toContain('PC-CALENDAR-001')
|
||||
expect(wrapper.find('.data-notice').exists()).toBe(false)
|
||||
expect(dispatches.some(event => event.key === 'dispatch-PC-TIMEZONE-MISSING')).toBe(false)
|
||||
expect(listTripCalendar).toHaveBeenCalledTimes(1)
|
||||
wrapper.unmount()
|
||||
|
||||
@@ -16,7 +16,7 @@ import AppointmentCalendarPanel from './AppointmentCalendarPanel.vue'
|
||||
import { calendarRange, dateKey, type CalendarMode } from './calendar'
|
||||
import { getCalendarSources, getWarehouseArrivals, saveWarehouseArrival, type CalendarSources, type ScheduleRange, type WarehouseArrival, type WarehouseArrivalChange } from './scheduleApi'
|
||||
import { projectContainerSchedule, type ScheduleEvent } from './schedule'
|
||||
import { listTripCalendar, projectTripCalendar, tripIssues } from './tripCalendar'
|
||||
import { listTripCalendar, projectTripCalendar } from './tripCalendar'
|
||||
import { rejectedCommand, type LeaveGuard } from './useWorkDraft'
|
||||
import type { PlanningSourceLookup } from './planningApi'
|
||||
import type { WorkAction } from './workApi'
|
||||
@@ -37,7 +37,8 @@ const selectedDate = ref(initialDate()), mode = ref<CalendarMode>(route.query.mo
|
||||
const range = ref<ScheduleRange>(calendarRange(selectedDate.value, mode.value))
|
||||
const sources = ref<CalendarSources | null>(null), warehouses = ref<WarehouseArrival[] | null>(null), trips = ref<ScheduleEvent[]>([])
|
||||
const errors = ref<Array<{ title: string; details: ApiErrorDetails }>>([]), notices = ref<string[]>([]), loading = ref(false), active = ref(true)
|
||||
const incomplete = computed(() => !!errors.value.length || !!notices.value.length)
|
||||
const hasUnresolvedTrips = ref(false)
|
||||
const incomplete = computed(() => !!errors.value.length || !!notices.value.length || hasUnresolvedTrips.value)
|
||||
const events = computed(() => [...projectContainerSchedule(sources.value || { containers: [], emptyReturns: [], emptyReturnsReadable: true },
|
||||
warehouses.value, range.value, access.can('platform:firstleg:terminal-drayage:list')), ...trips.value])
|
||||
let generation = 0, controller: AbortController | null = null
|
||||
@@ -46,6 +47,7 @@ async function load(next = range.value) {
|
||||
const request = ++generation; controller?.abort(); controller = new AbortController()
|
||||
const signal = controller.signal
|
||||
loading.value = true; sources.value = null; warehouses.value = null; trips.value = []; errors.value = []; notices.value = []
|
||||
hasUnresolvedTrips.value = false
|
||||
const fail = (title: string, cause: unknown) => { if (request === generation) errors.value.push({ title, details: getApiErrorDetails(cause, title) }) }
|
||||
await Promise.allSettled([
|
||||
(async () => {
|
||||
@@ -71,10 +73,7 @@ async function load(next = range.value) {
|
||||
const result = projectTripCalendar(await listTripCalendar(next, signal), next)
|
||||
if (request !== generation) return
|
||||
trips.value = result.events
|
||||
if (result.unresolved.length) {
|
||||
const details = result.unresolved.slice(0, 3).map(row => `${row.dispatchNo?.trim() || '排车单号缺失'}:${tripIssues(row) || '资料待核实'}`).join(';')
|
||||
notices.value.push(`${result.unresolved.length} 条排车未能定位到日历日期(${details}${result.unresolved.length > 3 ? ';其余请到排车列表核对' : ''})。`)
|
||||
}
|
||||
hasUnresolvedTrips.value = result.unresolved.length > 0
|
||||
} catch (cause) { fail('排车读取失败', cause) }
|
||||
})(),
|
||||
])
|
||||
|
||||
@@ -18,7 +18,7 @@ beforeEach(() => { vi.clearAllMocks(); access.write = true; vi.mocked(previewPla
|
||||
it('shows readable preview requirements without exposing missing-field machine codes', async () => {
|
||||
vi.mocked(previewPlanningBatch).mockResolvedValue({ ...preview, missingFields: ['READY_WINDOW_UNVERIFIED', 'CARGO_QUANTITY_REVIEW'] })
|
||||
const wrapper = render(); await flushPromises()
|
||||
expect(wrapper.text()).toContain('货物备妥窗口待核实')
|
||||
expect(wrapper.text()).not.toContain('货物备妥窗口待核实')
|
||||
expect(wrapper.text()).toContain('货量或重复关联需要核对。')
|
||||
expect(wrapper.text()).not.toContain('READY_WINDOW_UNVERIFIED')
|
||||
expect(wrapper.text()).not.toContain('CARGO_QUANTITY_REVIEW')
|
||||
|
||||
@@ -19,6 +19,7 @@ const form = reactive(createAppointmentForm()), baseline = ref(JSON.stringify(fo
|
||||
const loading = ref(false), saving = ref(false), error = ref<ApiErrorDetails | null>(null), saveError = ref<ApiErrorDetails | null>(null)
|
||||
const pending = ref<SourceAppointmentPayload | null>(null)
|
||||
const writable = computed(() => props.canWrite && access.can(overseasWarehousePermissions.inboundOrderAppointment))
|
||||
const visibleMissingFields = computed(() => preview.value?.missingFields.filter(field => field !== 'READY_WINDOW_UNVERIFIED') || [])
|
||||
useWorkDraft(computed(() => baseline.value !== JSON.stringify(form)), computed(() => saving.value || !!pending.value))
|
||||
async function load() {
|
||||
if (!props.sources.length || props.sources.some(source => !source.customerId || !source.sourceKey || !source.destinationKey)) { error.value = { message: '缺少完整货源、客户或目的仓范围,无法新建预约。' }; return }
|
||||
@@ -60,8 +61,7 @@ onMounted(load)
|
||||
<tr><th scope="row">票数</th><td>{{ preview.group.sourceCount }}</td><th scope="row">箱数</th><td>{{ preview.group.packages ?? '箱数待核实' }}</td></tr>
|
||||
<tr><th scope="row">货物业务号</th><td colspan="3"><template v-for="(source, index) in preview.sources" :key="`${source.customerId}:${source.sourceKey}`">{{ index ? '、' : '' }}{{ source.inboundCode || source.bookingNo || '原票业务号未提供' }}</template></td></tr>
|
||||
</tbody></table>
|
||||
<p v-for="field in preview.missingFields" :key="field" role="status" class="density-dialog-note">{{ workReason(field) }}</p>
|
||||
<p v-if="preview.group.readinessIncomplete" role="status" class="density-dialog-note">货物备妥窗口待核实。</p>
|
||||
<p v-for="field in visibleMissingFields" :key="field" role="status" class="density-dialog-note">{{ workReason(field) }}</p>
|
||||
</section>
|
||||
<FbaAppointmentFields continuous ref="appointmentFields" :sources="sources.map(source => ({ ...source, destinationKey: source.destinationKey || undefined }))" @confirmation-ready="confirmationReady = $event" :model="form" :timezone="preview.destinationTimezone" :disabled="!writable || loading || saving || !!pending" />
|
||||
<LoadErrorState v-if="saveError" title="预约尚未创建" :message="saveError.message" :show-retry="false"><template #details><ApiErrorMeta :details="saveError" /></template></LoadErrorState>
|
||||
|
||||
Reference in New Issue
Block a user