This commit is contained in:
2026-08-21 17:20:40 +08:00
parent 465be7c8f3
commit c972ad408a
14 changed files with 504 additions and 118 deletions
@@ -113,9 +113,6 @@ function openAppointment() {
}
function openSmartDispatch() {
const error = !selectedRows.value.length ? '请至少选择 1 条入库单'
: new Set(selectedRows.value.map((row) => row.fbaWarehouse)).size !== 1 ? '所选入库单必须属于同一 FBA 仓库' : ''
if (error) { ElMessage.warning(error); return }
smartDispatchOpen.value = true
}
@@ -175,8 +172,8 @@ onMounted(() => { void load() })
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" :loading="loading" ariaLabel="入库单筛选" preference-key="overseas-inbound-orders-query" @search="search" @reset="reset" /></template>
<template v-if="loadError" #state><LoadErrorState title="入库单加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template>
<section v-if="!loadError" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-inbound-orders" row-key="lineId" empty-text="暂无入库单" @selection-change="onSelectionChange" @update:page="load()" @update:page-size="page = 1; load()">
<template #toolbar-actions><el-button v-if="access.can(permission.inboundOrderEdit)" plain :disabled="!selectedRows.length" @click="openAppointment"><CalendarCheck2 :size="16" />预约</el-button><el-button v-if="access.can(permission.inboundOrderEdit)" type="primary" :disabled="!selectedRows.length" @click="openSmartDispatch"><Truck :size="16" />智能排车</el-button></template>
<template #actions="{ row }"><el-button size="small" plain type="primary" @click="openDetail(row)"><Eye :size="15" />查看</el-button><el-button v-if="(row as InboundOrderListItem).appointmentNo !== '—' && access.can(permission.inboundOrderEdit)" size="small" plain @click="openAppointmentEdit(row)"><Pencil :size="15" />编辑</el-button></template>
<template #toolbar-actions><el-button v-if="access.can(permission.inboundOrderAppointment)" plain :disabled="!selectedRows.length" @click="openAppointment"><CalendarCheck2 :size="16" />预约</el-button><el-button v-if="access.can(permission.inboundOrderSmartDispatch)" type="primary" @click="openSmartDispatch"><Truck :size="16" />智能排车</el-button></template>
<template #actions="{ row }"><el-button size="small" plain type="primary" @click="openDetail(row)"><Eye :size="15" />查看</el-button><el-button v-if="(row as InboundOrderListItem).appointmentNo !== '—' && access.can(permission.inboundOrderAppointment)" size="small" plain @click="openAppointmentEdit(row)"><Pencil :size="15" />编辑</el-button></template>
<template #pagination-left>共 {{ total }} 条入库单 · 已选 {{ selectedRows.length }} 条</template>
</DataTable></section>
</ListPageLayout>
@@ -1,45 +1,65 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, reactive, ref } from 'vue'
import { Eye } from '@lucide/vue'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import { listLastMileOrders } from '../api/lastMileOrders'
import { convertLastMileOrderToInboundPlan, listLastMileOrders, transferLastMileOrders } from '../api/lastMileOrders'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { overseasWarehousePermissions as permission } from '../permissions'
import { toLastMileOrderListItem } from '../lastMilePresentation'
import type { LastMileOrderListItem } from '../types'
const rows = ref<LastMileOrderListItem[]>([])
const access = useAccessPreviewStore()
const loading = ref(false)
const loadError = ref<ApiErrorDetails | null>(null)
const router = useRouter()
const page = ref(1)
const pageSize = ref(20)
const total = ref(0)
const selectedRows = ref<LastMileOrderListItem[]>([])
const actionLoading = ref(false)
const actionError = ref<ApiErrorDetails | null>(null)
const transferOpen = ref(false)
const transferForm = reactive({ warehouseId: '', warehouseCode: '', warehouseName: '', remark: '' })
const statusTab = ref<'全部' | '待分配'>('全部')
const filters = ref<QueryModel>({ orderNo: '', inboundCode: '', warehouse: '', fbaWarehouse: '', orderType: '', owner: '', containerNo: '', status: '', consolidated: '', portEta: [] })
const filters = ref<QueryModel>({ orderNo: '', orderType: '', warehouse: '', inboundCode: '', sourceOrderNo: '', containerNo: '', status: '', transferWarehouseCode: '', abnormal: '', detained: '', createdAt: [] })
const fields = computed<QueryField[]>(() => [
{ key: 'orderNo', type: 'input', label: '尾程单号', placeholder: '请输入尾程单号' },
{ key: 'inboundCode', type: 'input', label: '入仓码', placeholder: '请输入入仓码' },
{ key: 'warehouse', type: 'select', label: '海外仓', placeholder: '接口暂不支持', options: [], props: { disabled: true } },
{ key: 'fbaWarehouse', type: 'select', label: 'FBA仓库', placeholder: '接口暂不支持', options: [], props: { disabled: true } },
{ key: 'orderType', type: 'select', label: '订单类型', placeholder: '接口暂不支持', options: ['一件代发', '头程', '拒收订单', '快递订单'].map((value) => ({ label: value, value, disabled: true })), props: { disabled: true } },
{ key: 'owner', type: 'input', label: '货主', placeholder: '请通过客户名称选择', props: { disabled: true } },
{ key: 'orderType', type: 'select', label: '订单类型', placeholder: '全部类型', options: [
{ label: '头程', value: 'FIRST_LEG' }, { label: '换标申请', value: 'RELABEL_APPLICATION' },
{ label: '转仓', value: 'WAREHOUSE_TRANSFER' }, { label: '本地拆板', value: 'LOCAL_LOOSEBOARD' },
{ label: '退货换标', value: 'RETURN_RELABEL' }, { label: '一件代发', value: 'ONE_CLICK_DROPSHIP' },
] },
{ key: 'warehouse', type: 'input', label: '海外仓', placeholder: '请输入仓库名称或编码' },
{ key: 'sourceOrderNo', type: 'input', label: '来源订单号', placeholder: '请输入来源订单号' },
{ key: 'containerNo', type: 'input', label: '集装箱号', placeholder: '请输入集装箱号' },
{ key: 'transferWarehouseCode', type: 'input', label: '转仓代码', placeholder: '请输入转仓代码' },
{ key: 'status', type: 'select', label: '当前状态', placeholder: '全部状态', options: [
{ label: '待分配', value: 'PENDING_ASSIGNMENT' },
{ label: '已分配目的仓', value: 'DESTINATION_ASSIGNED' },
{ label: '已生成入库计划', value: 'INBOUND_PLANNED' },
{ label: '待入库', value: 'PENDING_INBOUND' },
{ label: '入库完成', value: 'INBOUND_COMPLETED' },
{ label: '待派送', value: 'PENDING_DELIVERY' },
{ label: '派送中', value: 'DELIVERING' },
{ label: '派送完成', value: 'DELIVERED' },
] },
{ key: 'consolidated', type: 'select', label: '是否合托', placeholder: '接口暂不支持', options: [{ label: '是', value: 'yes', disabled: true }, { label: '否', value: 'no', disabled: true }], props: { disabled: true } },
{ key: 'portEta', type: 'daterange', label: '预计到港时间', props: { disabled: true, startPlaceholder: '接口暂不支持', endPlaceholder: '接口暂不支持' } },
{ key: 'abnormal', type: 'select', label: '是否异常', placeholder: '全部', options: [{ label: '是', value: 'true' }, { label: '否', value: 'false' }] },
{ key: 'detained', type: 'select', label: '是否暂扣', placeholder: '全部', options: [{ label: '是', value: 'true' }, { label: '否', value: 'false' }] },
{ key: 'createdAt', type: 'daterange', label: '创建时间' },
])
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 110, fixed: 'left', slotName: 'actions' },
{ key: 'selection', type: 'selection', width: 48, fixed: 'left' },
{ key: 'orderNo', prop: 'orderNo', label: '尾程单号', minWidth: 170 },
{ key: 'warning', prop: 'warning', label: '预警', width: 90 },
{ key: 'orderType', prop: 'orderType', label: '订单类型', minWidth: 110 },
@@ -77,13 +97,23 @@ async function load(model: QueryModel = filters.value) {
loading.value = true
loadError.value = null
try {
const dateRange = Array.isArray(filters.value.createdAt) ? filters.value.createdAt.map(String) : []
const booleanValue = (value: unknown) => value === 'true' ? true : value === 'false' ? false : undefined
const result = await listLastMileOrders({
page: page.value,
size: pageSize.value,
orderNo: String(filters.value.orderNo || '').trim() || undefined,
orderType: String(filters.value.orderType || '').trim() || undefined,
warehouse: String(filters.value.warehouse || '').trim() || undefined,
inboundCode: String(filters.value.inboundCode || '').trim() || undefined,
sourceOrderNo: String(filters.value.sourceOrderNo || '').trim() || undefined,
containerNo: String(filters.value.containerNo || '').trim() || undefined,
status: statusTab.value === '待分配' ? 'PENDING_ASSIGNMENT' : String(filters.value.status || '').trim() || undefined,
currentStatus: statusTab.value === '待分配' ? 'PENDING_ASSIGNMENT' : String(filters.value.status || '').trim() || undefined,
transferWarehouseCode: String(filters.value.transferWarehouseCode || '').trim() || undefined,
abnormal: booleanValue(filters.value.abnormal),
detained: booleanValue(filters.value.detained),
createdFrom: dateRange[0] ? `${dateRange[0]}T00:00:00` : undefined,
createdTo: dateRange[1] ? `${dateRange[1]}T23:59:59` : undefined,
})
rows.value = result.records.map(toLastMileOrderListItem)
total.value = result.total
@@ -101,6 +131,34 @@ async function load(model: QueryModel = filters.value) {
function search(model: QueryModel) { page.value = 1; void load(model) }
function reset(model: QueryModel) { page.value = 1; void load(model) }
function changeStatusTab() { page.value = 1; void load() }
function onSelectionChange(selection: object[]) { selectedRows.value = selection as LastMileOrderListItem[] }
function openTransfer() {
if (!selectedRows.value.length) { ElMessage.warning('请至少选择 1 条尾程订单'); return }
Object.assign(transferForm, { warehouseId: '', warehouseCode: '', warehouseName: '', remark: '' })
actionError.value = null
transferOpen.value = true
}
async function submitTransfer() {
if (!transferForm.warehouseId.trim() || !transferForm.warehouseCode.trim() || !transferForm.warehouseName.trim()) {
ElMessage.warning('请填写目的仓 ID、代码和名称')
return
}
actionLoading.value = true; actionError.value = null
try {
await transferLastMileOrders({ orderIds: selectedRows.value.map((row) => row.id), warehouseId: transferForm.warehouseId.trim(), warehouseCode: transferForm.warehouseCode.trim(), warehouseName: transferForm.warehouseName.trim(), remark: transferForm.remark.trim() || undefined })
transferOpen.value = false; selectedRows.value = []; ElMessage.success('转仓成功'); await load()
} catch (error) { actionError.value = getApiErrorDetails(error, '转仓失败,请重试') }
finally { actionLoading.value = false }
}
async function convertInboundPlan() {
if (selectedRows.value.length !== 1) { ElMessage.warning('生成入库计划时请选择 1 条尾程订单'); return }
actionLoading.value = true; actionError.value = null
try {
await convertLastMileOrderToInboundPlan({ orderId: selectedRows.value[0].id })
selectedRows.value = []; ElMessage.success('入库计划生成成功'); await load()
} catch (error) { actionError.value = getApiErrorDetails(error, '生成入库计划失败,请重试') }
finally { actionLoading.value = false }
}
function openDetail(source: object) {
const row = source as LastMileOrderListItem
@@ -111,7 +169,7 @@ onMounted(() => { void load() })
</script>
<template>
<AdminPage><ListPageLayout aria-label="尾程订单列表">
<AdminPage><OperationErrorToast :details="actionError" /><ListPageLayout aria-label="尾程订单列表">
<template #toolbar>
<section class="surface-panel last-mile-toolbar" aria-label="尾程订单状态与筛选">
<el-radio-group v-model="statusTab" aria-label="尾程订单状态" @change="changeStatusTab">
@@ -126,11 +184,20 @@ onMounted(() => { void load() })
<template #details><ApiErrorMeta :details="loadError" /></template>
</LoadErrorState>
</template>
<section v-if="!loadError" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-last-mile-orders" row-key="id" empty-text="暂无尾程订单" @update:page="load()" @update:page-size="page = 1; load()">
<section v-if="!loadError" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-last-mile-orders" row-key="id" empty-text="暂无尾程订单" @selection-change="onSelectionChange" @update:page="load()" @update:page-size="page = 1; load()">
<template #toolbar-actions><el-button v-if="access.can(permission.lastMileOrderTransfer)" plain :disabled="!selectedRows.length || actionLoading" @click="openTransfer">转仓</el-button><el-button v-if="access.can(permission.inboundOrderCreate)" type="primary" plain :disabled="selectedRows.length !== 1 || actionLoading" @click="convertInboundPlan">生成入库计划</el-button></template>
<template #actions="{ row }"><el-button size="small" plain type="primary" @click="openDetail(row)"><Eye :size="15" />查看</el-button></template>
<template #pagination-left>共 {{ total }} 条尾程订单</template>
<template #pagination-left>共 {{ total }} 条尾程订单 · 已选 {{ selectedRows.length }} 条</template>
</DataTable></section>
</ListPageLayout></AdminPage>
<AppDialog v-model="transferOpen" title="转仓" confirm-text="确认转仓" :confirm-loading="actionLoading" @confirm="submitTransfer">
<el-form label-position="top">
<el-form-item label="目的仓 ID" required><el-input v-model="transferForm.warehouseId" placeholder="请输入目的仓 ID" /></el-form-item>
<el-form-item label="目的仓代码" required><el-input v-model="transferForm.warehouseCode" placeholder="请输入目的仓代码" /></el-form-item>
<el-form-item label="目的仓名称" required><el-input v-model="transferForm.warehouseName" placeholder="请输入目的仓名称" /></el-form-item>
<el-form-item label="备注"><el-input v-model="transferForm.remark" type="textarea" maxlength="500" /></el-form-item>
</el-form>
</AppDialog>
</template>
<style scoped>
@@ -29,6 +29,7 @@ const saving = ref(false)
const deletingId = ref('')
const loadError = ref<ApiErrorDetails | null>(null)
const mutationError = ref<ApiErrorDetails | null>(null)
const loadErrorKind = ref<'context' | 'list'>('context')
const page = ref(1)
const pageSize = ref(20)
const total = ref(0)
@@ -60,11 +61,14 @@ function toRow(dto: PalletDto): PalletListItem { return { id: String(dto.id), cu
async function loadWarehouses() {
warehouses.value = []; filters.value.warehouseId = ''; rows.value = []; total.value = 0
contextLoading.value = true; loadError.value = null
contextLoading.value = true; loadError.value = null; loadErrorKind.value = 'context'
try {
const [warehousePage, platformWarehouses] = await Promise.all([listCustomerWarehouses(), listAllPlatformWarehouses()])
const [warehousePage, platformFbaWarehouses] = await Promise.all([
listCustomerWarehouses(),
listAllPlatformWarehouses({ warehouseType: 'FBA', status: 'ACTIVE' }),
])
warehouses.value = warehousePage.records
fbaWarehouses.value = platformWarehouses.filter((item) => item.status === 'ACTIVE' && item.warehouseType === 'FBA')
fbaWarehouses.value = platformFbaWarehouses
}
catch (error) { loadError.value = getApiErrorDetails(error, '海外仓列表加载失败,请重试') }
finally { contextLoading.value = false }
@@ -74,7 +78,7 @@ async function load(model: QueryModel = filters.value) {
Object.assign(filters.value, model)
const warehouseId = String(filters.value.warehouseId || '')
if (!warehouseId) { rows.value = []; total.value = 0; return }
loading.value = true; loadError.value = null
loading.value = true; loadError.value = null; loadErrorKind.value = 'list'
try {
const result = await listPallets(warehouseId, { page: page.value, size: pageSize.value, keyword: String(filters.value.code || '').trim() || undefined, palletType: String(filters.value.type || '') || undefined, status: String(filters.value.status || '') || undefined })
rows.value = result.records.map(toRow); total.value = result.total
@@ -82,9 +86,14 @@ async function load(model: QueryModel = filters.value) {
finally { loading.value = false }
}
function retryLoad() {
if (loading.value || contextLoading.value) return
if (loadErrorKind.value === 'list') void load()
else void loadWarehouses()
}
function openCreate() {
if (!filters.value.warehouseId) { ElMessage.warning('请先选择仓库'); return }
dialogMode.value = 'create'; Object.assign(form, { id: '', warehouseId: String(filters.value.warehouseId), fbaWarehouseCode: '', code: '', type: 'WOOD', volume: 2, purchasePrice: 0, status: 'IDLE', remark: '' }); mutationError.value = null; dialogOpen.value = true
dialogMode.value = 'create'; Object.assign(form, { id: '', warehouseId: '', fbaWarehouseCode: '', code: '', type: 'WOOD', volume: 2, purchasePrice: 0, status: 'IDLE', remark: '' }); mutationError.value = null; dialogOpen.value = true
}
async function openEdit(source: object) {
@@ -97,7 +106,7 @@ async function openEdit(source: object) {
}
async function save() {
if (!form.code.trim() || !form.type || !form.fbaWarehouseCode || form.volume <= 0 || form.purchasePrice < 0 || !form.status) { ElMessage.warning('请完整填写托盘编码、类型、容积、采购价格、所属 FBA 仓和状态'); return }
if (!form.warehouseId || !form.code.trim() || !form.type || !form.fbaWarehouseCode || form.volume <= 0 || form.purchasePrice < 0 || !form.status) { ElMessage.warning('请完整填写仓库、托盘编码、类型、容积、采购价格、所属 FBA 仓和状态'); return }
saving.value = true; mutationError.value = null
const payload = { palletCode: form.code.trim(), palletType: form.type, fbaWarehouseCode: form.fbaWarehouseCode, capacityCbm: form.volume, unitPrice: form.purchasePrice, status: form.status, locationId: null, remark: form.remark.trim() || null }
try {
@@ -126,9 +135,9 @@ onMounted(loadWarehouses)
<template>
<AdminPage><OperationErrorToast :details="mutationError" /><ListPageLayout aria-label="托盘管理列表">
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" :loading="loading || contextLoading" ariaLabel="托盘筛选" preference-key="overseas-pallets-query" @search="search" @reset="page = 1; rows = []; total = 0" /></template>
<template v-if="loadError" #state><LoadErrorState title="托盘数据加载失败" :message="loadError.message" :loading="loading || contextLoading" @retry="loadWarehouses"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template>
<template v-if="loadError" #state><LoadErrorState title="托盘数据加载失败" :message="loadError.message" :loading="loading || contextLoading" @retry="retryLoad"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template>
<section v-if="!loadError" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-pallets" row-key="id" :empty-text="filters.warehouseId ? '暂无托盘' : '请先选择仓库后查询'" @update:page="load()" @update:page-size="page = 1; load()">
<template #toolbar-actions><el-button v-if="access.can(permission.palletCreate)" type="primary" @click="openCreate"><Plus :size="16" />新增托盘</el-button></template>
<template #toolbar-actions><el-button type="primary" @click="openCreate"><Plus :size="16" />新增托盘</el-button></template>
<template #actions="{ row }"><el-button size="small" plain type="primary" @click="openDetail(row)"><Eye :size="15" />查看</el-button><el-button v-if="access.can(permission.palletEdit)" size="small" plain @click="openEdit(row)"><Pencil :size="15" />编辑</el-button><el-button v-if="access.can(permission.palletDelete)" size="small" plain type="danger" :loading="deletingId === (row as PalletListItem).id" @click="remove(row)"><Trash2 :size="15" />删除</el-button></template>
<template #pagination-left>共 {{ total }} 条托盘</template>
</DataTable></section>
@@ -139,7 +148,7 @@ onMounted(loadWarehouses)
<el-col :span="12"><el-form-item label="托盘类型" required><el-select v-model="form.type"><el-option v-for="value in ['WOOD', 'PLASTIC', 'METAL', 'PAPER', 'FUMIGATION_FREE']" :key="value" :label="palletTypeLabel(value)" :value="value" /></el-select></el-form-item></el-col>
<el-col :span="12"><el-form-item label="容积(m³)" required><el-input-number v-model="form.volume" :min="0.0001" :precision="4" controls-position="right" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="采购价格(USD)" required><el-input-number v-model="form.purchasePrice" :min="0" :precision="2" controls-position="right" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="仓库" required><el-select v-model="form.warehouseId" filterable :disabled="dialogMode === 'edit'"><el-option v-for="item in warehouses" :key="item.id" :label="`${item.warehouseName}(${item.warehouseCode})`" :value="String(item.id)" /></el-select></el-form-item></el-col>
<el-col :span="12"><el-form-item label="仓库" required><el-select v-model="form.warehouseId" filterable :disabled="dialogMode === 'edit'" placeholder="请选择海外仓"><el-option v-for="item in warehouses" :key="item.id" :label="`${item.warehouseName}(${item.warehouseCode})`" :value="String(item.id)" /></el-select></el-form-item></el-col>
<el-col :span="12"><el-form-item label="所属 FBA 仓" required><el-select v-model="form.fbaWarehouseCode" filterable placeholder="请选择所属 FBA 仓"><el-option v-for="item in fbaWarehouses" :key="String(item.id)" :label="`${item.warehouseName}(${item.warehouseCode})`" :value="item.warehouseCode" /></el-select></el-form-item></el-col>
<el-col :span="12"><el-form-item label="状态" required><el-select v-model="form.status"><el-option label="闲置" value="IDLE" /><el-option label="占用" value="OCCUPIED" /><el-option label="已报废" value="SCRAPPED" /></el-select></el-form-item></el-col>
<el-col :span="24"><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="500" /></el-form-item></el-col>
@@ -152,10 +152,18 @@ describe('overseas warehouse first-batch prototype contract', () => {
expect(value).toContain('760px')
expect(value).toContain("dialogMode === 'create' ? '新增托盘' : '编辑托盘'")
expect(value).toContain('createPallet')
expect(value).toContain('listAllPlatformWarehouses({ warehouseType: \'FBA\', status: \'ACTIVE\' })')
expect(value).toContain('placeholder="请选择海外仓"')
expect(value).toContain('!form.warehouseId')
expect(value).toContain('updatePallet')
expect(value).toContain('deletePallet')
expect(value).toContain('<el-button type="primary" @click="openCreate">')
expect(value).not.toContain('v-if="access.can(permission.palletCreate)"')
expect(value).toContain('v-model="form.fbaWarehouseCode"')
expect(value).toContain("warehouseType === 'FBA'")
expect(value).toContain("warehouseType: 'FBA'")
expect(value).toContain('listAllPlatformWarehouses')
expect(value).toContain('function retryLoad()')
expect(value).toContain('if (loading.value || contextLoading.value) return')
expect(value).toContain('palletCode: form.code.trim()')
expect(value).not.toContain(':readonly="dialogMode === \'edit\'"')
expect(value).not.toContain('ContractPendingFormDialog')
@@ -171,16 +179,65 @@ describe('overseas warehouse first-batch prototype contract', () => {
expect(value).toContain('所选入库单必须属于同一 FBA 仓库')
expect(value).toContain('<CalendarCheck2 :size="16" />预约')
expect(value).toContain('<Truck :size="16" />智能排车')
expect(value).toContain('type="primary" @click="openSmartDispatch"')
expect(value).not.toContain('type="primary" :disabled="!selectedRows.length" @click="openSmartDispatch"')
expect(value).toContain('access.can(permission.inboundOrderAppointment)')
expect(value).toContain('access.can(permission.inboundOrderSmartDispatch)')
expect(value).toContain('<SmartDispatchDialog')
expect(value).not.toContain('ContractPendingNotice')
})
it('keeps the six overseas warehouse lists connected to server-side pagination', () => {
for (const fileName of [
'LastMileOrdersView.vue',
'InboundOrdersView.vue',
'DispatchesView.vue',
'RelabelOrdersView.vue',
'PalletsView.vue',
'InventoryView.vue',
]) {
const value = source(fileName)
expect(value).toContain('const page = ref(1)')
expect(value).toContain('const pageSize = ref(20)')
expect(value).toContain('const total = ref(0)')
expect(value).toContain('page: page.value')
expect(value).toContain('size: pageSize.value')
expect(value).toMatch(/total\.value = \w+\.total/)
expect(value).toContain('v-model:page="page"')
expect(value).toContain('v-model:page-size="pageSize"')
expect(value).toContain(':total="total"')
expect(value).toContain('@update:page="load()"')
expect(value).toContain('@update:page-size="page = 1; load()"')
}
})
it('connects intelligent dispatch without a frontend customerId constraint', () => {
const dialog = source('../components/SmartDispatchDialog.vue')
expect(dialog).toContain('listDispatchCandidates')
expect(dialog).toContain('preArrangeDispatch')
expect(dialog).toContain('planPalletIds: selectedIds.value')
expect(dialog).toContain('planPalletIds: assignedIds.value')
expect(dialog).toContain('lockDispatchDraftPallet')
expect(dialog).toContain('unlockDispatchDraftPallet')
expect(dialog).toContain('releaseDispatchDraft')
expect(dialog).toContain("router.push({ name: 'overseas-dispatches' })")
expect(dialog).not.toContain('customerId:')
expect(dialog).toContain("planIds: planIds.value.length ? planIds.value.join(',') : undefined")
expect(dialog).toContain('v-model="query.fbaWarehouseCode" clearable')
expect(dialog).not.toContain(':model-value="fbaWarehouseCode" readonly')
expect(dialog).toContain("sourceStage: 'RECEIVED'")
expect(dialog).toContain('一次排车只能装同一客户的托盘')
expect(dialog).toContain('function ensureDraftToken()')
expect(dialog).toContain('lockDispatchDraftPallet(id, ensureDraftToken())')
expect(dialog).toContain('洛杉矶')
expect(dialog).toContain('纽约')
expect(dialog).toContain("value: 'ALL', label: '全部'")
expect(dialog).toContain('换标订单')
expect(dialog).toContain('退换标单')
expect(dialog).toContain('本土散板')
expect(dialog).toContain('仓储转运单')
expect(dialog).toContain('class="warehouse-tabs"')
expect(dialog).not.toContain('排车路线(选填)')
expect(dialog).not.toContain('v-model="route"')
})
it('adds the prototype inventory transaction page as a read-only inventory route', () => {
@@ -245,7 +302,7 @@ describe('overseas warehouse first-batch prototype contract', () => {
it('keeps every prototype list column in its frozen order', () => {
expect(columnKeys('LastMileOrdersView.vue')).toEqual([
'actions', 'orderNo', 'warning', 'orderType', 'rejectedAt', 'inboundCode', 'owner', 'warehouse',
'actions', 'selection', 'orderNo', 'warning', 'orderType', 'rejectedAt', 'inboundCode', 'owner', 'warehouse',
'transferWarehouse', 'fbaWarehouse', 'containerNo', 'abnormal', 'held', 'pallets',
'cartons', 'volume', 'quantity', 'volumeWeight', 'portEta', 'portAta', 'warehouseEta', 'inboundOrderNo',
'outboundOrderNo', 'dispatchOrderNo', 'relabelOrderNo', 'relabelStatus', 'consolidated', 'status',