优化
This commit is contained in:
@@ -63,4 +63,20 @@ describe('co-load group API', () => {
|
|||||||
expect.objectContaining({ orderId: '2093000000000000008', inboundId: '2093000000000000009', inboundCode: 'NYEXX2608310002', fullPackageCount: 10 }),
|
expect.objectContaining({ orderId: '2093000000000000008', inboundId: '2093000000000000009', inboundCode: 'NYEXX2608310002', fullPackageCount: 10 }),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('normalizes missing and blank member inbound codes without exposing technical ids as fallbacks', async () => {
|
||||||
|
const incompleteGroup = {
|
||||||
|
...group,
|
||||||
|
members: [
|
||||||
|
{ ...group.members[0], inboundCode: undefined },
|
||||||
|
{ ...group.members[1], inboundCode: null },
|
||||||
|
{ ...group.members[1], inboundId: 2093000000000000012n, inboundCode: ' ' },
|
||||||
|
{ ...group.members[1], inboundId: 2093000000000000013n, inboundCode: ' NYEXX2608310003 ' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
vi.spyOn(http, 'post').mockResolvedValue({ code: '200', message: 'ok', data: incompleteGroup })
|
||||||
|
const result = await createPlatformCoLoadGroup(['2093000000000000005', '2093000000000000008'])
|
||||||
|
expect(result.members.map((member) => member.inboundCode)).toEqual(['', '', '', 'NYEXX2608310003'])
|
||||||
|
expect(result.members.map((member) => member.inboundCode)).not.toContain('2093000000000000012')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export interface CoLoadGroupMember {
|
|||||||
tenantId: string
|
tenantId: string
|
||||||
orderId: string
|
orderId: string
|
||||||
inboundId: string
|
inboundId: string
|
||||||
inboundCode: string
|
|
||||||
fullPackageCount: number
|
fullPackageCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,21 +23,31 @@ export interface CoLoadGroup {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderCoLoadGroupInfo {
|
export interface CoLoadBusinessMember {
|
||||||
orderId: string
|
inboundCode: string
|
||||||
group: CoLoadGroup
|
}
|
||||||
|
|
||||||
|
export interface CoLoadBusinessSummary {
|
||||||
|
status: CoLoadGroupStatus
|
||||||
|
memberCount: number
|
||||||
|
members: CoLoadBusinessMember[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawId = string | number
|
type RawId = string | number
|
||||||
|
type RawCoLoadGroupMember = Omit<CoLoadGroupMember, 'tenantId' | 'orderId' | 'inboundId'> & {
|
||||||
|
tenantId: RawId
|
||||||
|
orderId: RawId
|
||||||
|
inboundId: RawId
|
||||||
|
}
|
||||||
type RawCoLoadGroup = Omit<CoLoadGroup, 'groupId' | 'warehouseId' | 'lockedPlanId' | 'members'> & {
|
type RawCoLoadGroup = Omit<CoLoadGroup, 'groupId' | 'warehouseId' | 'lockedPlanId' | 'members'> & {
|
||||||
groupId: RawId
|
groupId: RawId
|
||||||
warehouseId: RawId
|
warehouseId: RawId
|
||||||
lockedPlanId: RawId | null
|
lockedPlanId: RawId | null
|
||||||
members: Array<Omit<CoLoadGroupMember, 'tenantId' | 'orderId' | 'inboundId'> & { tenantId: RawId; orderId: RawId; inboundId: RawId }>
|
members: RawCoLoadGroupMember[]
|
||||||
}
|
}
|
||||||
type RawOrderCoLoadGroupInfo = Omit<OrderCoLoadGroupInfo, 'orderId' | 'group'> & {
|
type RawCoLoadBusinessSummary = Omit<CoLoadBusinessSummary, 'memberCount' | 'members'> & {
|
||||||
orderId: RawId
|
memberCount: number | string
|
||||||
group: RawCoLoadGroup
|
members: Array<{ inboundCode?: string | null }>
|
||||||
}
|
}
|
||||||
|
|
||||||
const basePath = '/platform/v1/first-leg/coload-groups'
|
const basePath = '/platform/v1/first-leg/coload-groups'
|
||||||
@@ -81,13 +90,17 @@ export async function cancelPlatformCoLoadGroup(groupId: string, expectedVersion
|
|||||||
return normalizeGroup(unwrapApiResponse(response))
|
return normalizeGroup(unwrapApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function batchQueryPlatformCoLoadGroups(orderIds: string[]) {
|
export async function getPlatformCoLoadGroupByInboundCode(inboundCode: string) {
|
||||||
const response = await http.post<ApiEnvelope<RawOrderCoLoadGroupInfo[]>, ApiEnvelope<RawOrderCoLoadGroupInfo[]>>(
|
const response = await http.get<ApiEnvelope<RawCoLoadBusinessSummary>, ApiEnvelope<RawCoLoadBusinessSummary>>(
|
||||||
`${basePath}/batch-query`,
|
`${basePath}/by-inbound-code/${encodeURIComponent(inboundCode)}`,
|
||||||
{ orderIds },
|
|
||||||
)
|
)
|
||||||
return unwrapApiResponse(response).map((item) => ({
|
const raw = unwrapApiResponse(response)
|
||||||
orderId: String(item.orderId),
|
const members = (raw.members || []).map((member) => ({
|
||||||
group: normalizeGroup(item.group),
|
inboundCode: typeof member.inboundCode === 'string' ? member.inboundCode.trim() : '',
|
||||||
}))
|
}))
|
||||||
|
return {
|
||||||
|
status: raw.status,
|
||||||
|
memberCount: Number(raw.memberCount),
|
||||||
|
members,
|
||||||
|
} satisfies CoLoadBusinessSummary
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ export interface UnifiedOrderRelationSummary {
|
|||||||
childCount: number
|
childCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UnifiedOrderCoLoadGroupSummary {
|
||||||
|
status: string
|
||||||
|
memberCount: number
|
||||||
|
memberInboundCodes: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface UnifiedOrderResource {
|
export interface UnifiedOrderResource {
|
||||||
orderId: number | string
|
orderId: number | string
|
||||||
tenantId: number | string
|
tenantId: number | string
|
||||||
@@ -103,6 +109,7 @@ export interface UnifiedOrderResource {
|
|||||||
loading: UnifiedOrderLoadingSummary | null
|
loading: UnifiedOrderLoadingSummary | null
|
||||||
delivery: UnifiedOrderDeliverySummary | null
|
delivery: UnifiedOrderDeliverySummary | null
|
||||||
transport: UnifiedOrderTransportSummary | null
|
transport: UnifiedOrderTransportSummary | null
|
||||||
|
coLoadGroup: UnifiedOrderCoLoadGroupSummary | null
|
||||||
createdAt: string | null
|
createdAt: string | null
|
||||||
updatedAt: string | null
|
updatedAt: string | null
|
||||||
expectedArrivalAt: string | null
|
expectedArrivalAt: string | null
|
||||||
@@ -368,6 +375,13 @@ function normalizeRow(raw: UnifiedOrderResource): UnifiedOrderResource {
|
|||||||
evidenceCount: nullableInteger(raw.loading.evidenceCount),
|
evidenceCount: nullableInteger(raw.loading.evidenceCount),
|
||||||
} : null,
|
} : null,
|
||||||
transport: raw.transport ? { ...raw.transport } : null,
|
transport: raw.transport ? { ...raw.transport } : null,
|
||||||
|
coLoadGroup: raw.coLoadGroup ? {
|
||||||
|
status: String(raw.coLoadGroup.status || ''),
|
||||||
|
memberCount: integer(raw.coLoadGroup.memberCount, 0),
|
||||||
|
memberInboundCodes: (raw.coLoadGroup.memberInboundCodes || [])
|
||||||
|
.map((value) => typeof value === 'string' ? value.trim() : '')
|
||||||
|
.filter(Boolean),
|
||||||
|
} : null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ export {
|
|||||||
type UnifiedOrderResource,
|
type UnifiedOrderResource,
|
||||||
} from './api/unifiedOrders'
|
} from './api/unifiedOrders'
|
||||||
export {
|
export {
|
||||||
batchQueryPlatformCoLoadGroups,
|
|
||||||
cancelPlatformCoLoadGroup,
|
cancelPlatformCoLoadGroup,
|
||||||
createPlatformCoLoadGroup,
|
createPlatformCoLoadGroup,
|
||||||
|
getPlatformCoLoadGroupByInboundCode,
|
||||||
|
type CoLoadBusinessMember,
|
||||||
|
type CoLoadBusinessSummary,
|
||||||
type CoLoadGroup,
|
type CoLoadGroup,
|
||||||
type CoLoadGroupMember,
|
type CoLoadGroupMember,
|
||||||
type CoLoadGroupStatus,
|
type CoLoadGroupStatus,
|
||||||
type OrderCoLoadGroupInfo,
|
|
||||||
} from './api/coLoadGroups'
|
} from './api/coLoadGroups'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ describe('UnifiedOrdersView co-load group behavior', () => {
|
|||||||
expect(wrapper.text()).toContain('同柜组 · 2 票 · 未锁定')
|
expect(wrapper.text()).toContain('同柜组 · 2 票 · 未锁定')
|
||||||
expect(wrapper.text()).not.toContain('CLG-9001')
|
expect(wrapper.text()).not.toContain('CLG-9001')
|
||||||
await wrapper.get('[aria-label="查看同柜组 · 2 票 · 未锁定成员"]').trigger('click')
|
await wrapper.get('[aria-label="查看同柜组 · 2 票 · 未锁定成员"]').trigger('click')
|
||||||
expect(wrapper.text()).toContain('3 个入仓码')
|
expect(wrapper.text()).toContain('3 个成员')
|
||||||
expect(wrapper.text()).toContain('13 箱')
|
expect(wrapper.text()).toContain('13 箱')
|
||||||
expect(wrapper.text()).toContain('IN-1')
|
expect(wrapper.text()).toContain('IN-1')
|
||||||
expect(wrapper.text()).toContain('IN-1-B')
|
expect(wrapper.text()).toContain('IN-1-B')
|
||||||
@@ -116,4 +116,23 @@ describe('UnifiedOrdersView co-load group behavior', () => {
|
|||||||
expect(cancelPlatformCoLoadGroup).toHaveBeenCalledWith('9001', 0, '计划调整')
|
expect(cancelPlatformCoLoadGroup).toHaveBeenCalledWith('9001', 0, '计划调整')
|
||||||
expect(ElMessageBox.prompt).toHaveBeenCalledWith(expect.stringContaining('不支持单独移除成员'), '取消同柜不可拆组', expect.any(Object))
|
expect(ElMessageBox.prompt).toHaveBeenCalledWith(expect.stringContaining('不支持单独移除成员'), '取消同柜不可拆组', expect.any(Object))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['undefined', undefined],
|
||||||
|
['null', null],
|
||||||
|
['blank', ' '],
|
||||||
|
])('keeps the member dialog readable when a normalized inbound code is %s', async (_case, inboundCode) => {
|
||||||
|
const incompleteGroup = {
|
||||||
|
...group,
|
||||||
|
members: [{ ...group.members[0], inboundCode: inboundCode as unknown as string }, group.members[1]],
|
||||||
|
}
|
||||||
|
vi.mocked(batchQueryPlatformCoLoadGroups).mockResolvedValue(rows.map((row) => ({ orderId: row.orderId, group: incompleteGroup })))
|
||||||
|
const wrapper = mountView()
|
||||||
|
await flushPromises()
|
||||||
|
await wrapper.get('[aria-label="查看同柜组 · 2 票 · 未锁定成员"]').trigger('click')
|
||||||
|
expect(wrapper.text()).toContain('同柜组成员入仓码尚未返回,请刷新后重试')
|
||||||
|
expect(wrapper.text()).not.toContain('CLG-9001')
|
||||||
|
expect(wrapper.text()).not.toContain('ORD-1')
|
||||||
|
expect(wrapper.text()).not.toContain('5001')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -188,12 +188,21 @@ describe('UnifiedOrdersView contract', () => {
|
|||||||
expect(source).toContain('v-for="member in coLoadDetailMembers"')
|
expect(source).toContain('v-for="member in coLoadDetailMembers"')
|
||||||
expect(source).toContain('<th>入仓码</th><th>箱数</th>')
|
expect(source).toContain('<th>入仓码</th><th>箱数</th>')
|
||||||
expect(source).toContain('{{ member.inboundCode }}')
|
expect(source).toContain('{{ member.inboundCode }}')
|
||||||
|
expect(source).toContain('同柜组成员入仓码尚未返回,请刷新后重试')
|
||||||
|
expect(source).toContain('coLoadDetailHasMissingInboundCodes')
|
||||||
expect(source).not.toContain('visibleInboundCode')
|
expect(source).not.toContain('visibleInboundCode')
|
||||||
expect(source).not.toContain('visibleOrderNo')
|
expect(source).not.toContain('visibleOrderNo')
|
||||||
expect(source).not.toContain('<th>订单编码</th>')
|
expect(source).not.toContain('<th>订单编码</th>')
|
||||||
expect(source).not.toContain('客户 ID')
|
expect(source).not.toContain('客户 ID')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('names the cross-domain relationship entry as fulfillment details', () => {
|
||||||
|
expect(source).toContain('>履约详情</el-button>')
|
||||||
|
expect(source).toContain('aria-label="查看该入仓码的订单、装柜、尾程与库存关系"')
|
||||||
|
expect(source).toContain('title="查看该入仓码的订单、装柜、尾程与库存关系"')
|
||||||
|
expect(source).not.toContain('>关联数据</el-button>')
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps editable query values separate from the applied list request', () => {
|
it('keeps editable query values separate from the applied list request', () => {
|
||||||
expect(source).toContain('const appliedFilters = ref<FilterModel>(emptyFilters())')
|
expect(source).toContain('const appliedFilters = ref<FilterModel>(emptyFilters())')
|
||||||
expect(source).toContain('const activeFilters = appliedFilters.value')
|
expect(source).toContain('const activeFilters = appliedFilters.value')
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch, type Component } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch, type Component } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Eye, GitBranch, History, Link2, ListChecks, MoreHorizontal, PackageOpen, Ruler, SlidersHorizontal, StickyNote, Unlink, WalletCards } from '@lucide/vue'
|
import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Eye, GitBranch, History, ListChecks, MoreHorizontal, PackageOpen, Ruler, SlidersHorizontal, StickyNote, WalletCards } from '@lucide/vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
getPlatformUnifiedOrder,
|
getPlatformUnifiedOrder,
|
||||||
@@ -12,15 +12,11 @@ import {
|
|||||||
listPlatformUnifiedOrders,
|
listPlatformUnifiedOrders,
|
||||||
unifiedOrderStatusOptions,
|
unifiedOrderStatusOptions,
|
||||||
type UnifiedOrderDetailResource,
|
type UnifiedOrderDetailResource,
|
||||||
|
type UnifiedOrderCoLoadGroupSummary,
|
||||||
type UnifiedOrderMainStatus,
|
type UnifiedOrderMainStatus,
|
||||||
type UnifiedOrderResource,
|
type UnifiedOrderResource,
|
||||||
} from '@/modules/unified-orders/api/unifiedOrders'
|
} from '@/modules/unified-orders/api/unifiedOrders'
|
||||||
import {
|
import { createPlatformCoLoadGroup, getPlatformCoLoadGroupByInboundCode, type CoLoadBusinessSummary, type CoLoadGroup } from '@/modules/unified-orders/api/coLoadGroups'
|
||||||
batchQueryPlatformCoLoadGroups,
|
|
||||||
cancelPlatformCoLoadGroup,
|
|
||||||
createPlatformCoLoadGroup,
|
|
||||||
type CoLoadGroup,
|
|
||||||
} from '@/modules/unified-orders/api/coLoadGroups'
|
|
||||||
import OrderFulfillmentTreePanel from '@/modules/unified-orders/components/OrderFulfillmentTreePanel.vue'
|
import OrderFulfillmentTreePanel from '@/modules/unified-orders/components/OrderFulfillmentTreePanel.vue'
|
||||||
import InboundFeeDetailManager from '@/modules/unified-orders/components/InboundFeeDetailManager.vue'
|
import InboundFeeDetailManager from '@/modules/unified-orders/components/InboundFeeDetailManager.vue'
|
||||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||||
@@ -36,7 +32,6 @@ import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
|||||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||||
import LineClampText from '@/shared/components/LineClampText.vue'
|
import LineClampText from '@/shared/components/LineClampText.vue'
|
||||||
import InboundCodeRelationshipDialog from '@/shared/components/InboundCodeRelationshipDialog.vue'
|
|
||||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||||
import {
|
import {
|
||||||
@@ -129,9 +124,12 @@ const approvalSaving = ref(false)
|
|||||||
const batchApprovalSaving = ref(false)
|
const batchApprovalSaving = ref(false)
|
||||||
const selectedApprovalOrders = ref<UnifiedOrderResource[]>([])
|
const selectedApprovalOrders = ref<UnifiedOrderResource[]>([])
|
||||||
const selectedOrders = ref<UnifiedOrderResource[]>([])
|
const selectedOrders = ref<UnifiedOrderResource[]>([])
|
||||||
const coLoadGroupsByOrderId = ref(new Map<string, CoLoadGroup>())
|
|
||||||
const coLoadCreateOpen = ref(false)
|
const coLoadCreateOpen = ref(false)
|
||||||
const coLoadDetailGroup = ref<CoLoadGroup | null>(null)
|
const coLoadDetailOpen = ref(false)
|
||||||
|
const coLoadDetailInboundCode = ref('')
|
||||||
|
const coLoadDetailSummary = ref<CoLoadBusinessSummary | null>(null)
|
||||||
|
const coLoadDetailLoading = ref(false)
|
||||||
|
const coLoadDetailError = ref<ApiErrorDetails | null>(null)
|
||||||
const coLoadSaving = ref(false)
|
const coLoadSaving = ref(false)
|
||||||
const coLoadError = ref<ApiErrorDetails | null>(null)
|
const coLoadError = ref<ApiErrorDetails | null>(null)
|
||||||
const approvalOrder = ref<UnifiedOrderResource | null>(null)
|
const approvalOrder = ref<UnifiedOrderResource | null>(null)
|
||||||
@@ -141,8 +139,6 @@ const noteContent = ref('')
|
|||||||
const noteOrder = ref<UnifiedOrderResource | null>(null)
|
const noteOrder = ref<UnifiedOrderResource | null>(null)
|
||||||
const relationOpen = ref(false)
|
const relationOpen = ref(false)
|
||||||
const relationOrder = ref<UnifiedOrderResource | null>(null)
|
const relationOrder = ref<UnifiedOrderResource | null>(null)
|
||||||
const inboundRelationshipDialogOpen = ref(false)
|
|
||||||
const inboundRelationshipDialogCode = ref('')
|
|
||||||
const measurementDetail = ref<InboundMeasurementDetail | null>(null)
|
const measurementDetail = ref<InboundMeasurementDetail | null>(null)
|
||||||
const measurementLoading = ref(false)
|
const measurementLoading = ref(false)
|
||||||
const measurementError = ref<ApiErrorDetails | null>(null)
|
const measurementError = ref<ApiErrorDetails | null>(null)
|
||||||
@@ -709,7 +705,6 @@ async function loadWarehouses() {
|
|||||||
async function loadOrders() {
|
async function loadOrders() {
|
||||||
selectedApprovalOrders.value = []
|
selectedApprovalOrders.value = []
|
||||||
selectedOrders.value = []
|
selectedOrders.value = []
|
||||||
coLoadGroupsByOrderId.value = new Map()
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
loadError.value = null
|
loadError.value = null
|
||||||
coLoadError.value = null
|
coLoadError.value = null
|
||||||
@@ -739,14 +734,6 @@ async function loadOrders() {
|
|||||||
})
|
})
|
||||||
records.value = result.records
|
records.value = result.records
|
||||||
total.value = result.total
|
total.value = result.total
|
||||||
if (can('platform:firstleg:smart-loading:assign') && result.records.length) {
|
|
||||||
try {
|
|
||||||
const groups = await batchQueryPlatformCoLoadGroups(result.records.map((row) => String(row.orderId)))
|
|
||||||
coLoadGroupsByOrderId.value = new Map(groups.map((item) => [item.orderId, item.group]))
|
|
||||||
} catch (error) {
|
|
||||||
coLoadError.value = getApiErrorDetails(error, '同柜不可拆组信息加载失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
records.value = []
|
records.value = []
|
||||||
total.value = 0
|
total.value = 0
|
||||||
@@ -789,40 +776,55 @@ function handleOrderSelectionChange(rows: UnifiedOrderResource[]) {
|
|||||||
selectedApprovalOrders.value = rows.filter(canApprove)
|
selectedApprovalOrders.value = rows.filter(canApprove)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openInboundRelationship(row: UnifiedOrderResource) {
|
|
||||||
const code = row.inboundCode?.trim()
|
|
||||||
if (!code) return
|
|
||||||
inboundRelationshipDialogCode.value = code
|
|
||||||
inboundRelationshipDialogOpen.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function coLoadGroup(row: UnifiedOrderResource) {
|
function coLoadGroup(row: UnifiedOrderResource) {
|
||||||
return coLoadGroupsByOrderId.value.get(String(row.orderId)) || null
|
return row.coLoadGroup || null
|
||||||
}
|
|
||||||
|
|
||||||
function coLoadGroupOrderCount(group: CoLoadGroup) {
|
|
||||||
return new Set(group.members.map((member) => member.orderId)).size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function coLoadGroupMemberLabel(group: CoLoadGroup) {
|
function coLoadGroupMemberLabel(group: CoLoadGroup) {
|
||||||
const orderCount = coLoadGroupOrderCount(group)
|
const orderCount = new Set(group.members.map((member) => member.orderId)).size
|
||||||
return group.members.length === orderCount
|
return group.members.length === orderCount
|
||||||
? `${orderCount} 票`
|
? `${orderCount} 票`
|
||||||
: `${orderCount} 票 · ${group.members.length} 个入仓片段`
|
: `${orderCount} 票 · ${group.members.length} 个入仓片段`
|
||||||
}
|
}
|
||||||
|
|
||||||
function coLoadGroupStatusLabel(group: CoLoadGroup) {
|
function coLoadGroupStatusLabel(group: { status: string }) {
|
||||||
return group.status === 'ACTIVE' ? '未锁定' : group.status === 'LOCKED' ? '已锁定' : group.status === 'CONSUMED' ? '已消费' : '已取消'
|
return group.status === 'ACTIVE' ? '未锁定' : group.status === 'LOCKED' ? '已锁定' : group.status === 'CONSUMED' ? '已消费' : '已取消'
|
||||||
}
|
}
|
||||||
|
|
||||||
function coLoadGroupSummary(group: CoLoadGroup) {
|
function coLoadGroupSummary(group: UnifiedOrderCoLoadGroupSummary | CoLoadBusinessSummary) {
|
||||||
return `同柜组 · ${coLoadGroupOrderCount(group)} 票 · ${coLoadGroupStatusLabel(group)}`
|
return `同柜组 · ${group.memberCount} 票 · ${coLoadGroupStatusLabel(group)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const coLoadDetailMembers = computed(() => coLoadDetailGroup.value?.members.filter((member) => member.inboundCode.trim()) || [])
|
function hasCoLoadMemberInboundCode(member: CoLoadBusinessSummary['members'][number]) {
|
||||||
|
return typeof member.inboundCode === 'string' && Boolean(member.inboundCode.trim())
|
||||||
|
}
|
||||||
|
|
||||||
function openCoLoadDetail(group: CoLoadGroup) {
|
const coLoadDetailMembers = computed(() => coLoadDetailSummary.value?.members.filter(hasCoLoadMemberInboundCode) || [])
|
||||||
coLoadDetailGroup.value = group
|
const coLoadDetailHasMissingInboundCodes = computed(() => Boolean(coLoadDetailSummary.value)
|
||||||
|
&& (coLoadDetailMembers.value.length !== coLoadDetailSummary.value!.memberCount
|
||||||
|
|| coLoadDetailSummary.value!.members.some((member) => !hasCoLoadMemberInboundCode(member))))
|
||||||
|
|
||||||
|
async function loadCoLoadDetail() {
|
||||||
|
const inboundCode = coLoadDetailInboundCode.value
|
||||||
|
if (!inboundCode || coLoadDetailLoading.value) return
|
||||||
|
coLoadDetailLoading.value = true
|
||||||
|
coLoadDetailError.value = null
|
||||||
|
coLoadDetailSummary.value = null
|
||||||
|
try {
|
||||||
|
coLoadDetailSummary.value = await getPlatformCoLoadGroupByInboundCode(inboundCode)
|
||||||
|
} catch (error) {
|
||||||
|
coLoadDetailError.value = getApiErrorDetails(error, '同柜组成员加载失败,请刷新后重试')
|
||||||
|
} finally {
|
||||||
|
coLoadDetailLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCoLoadDetail(row: UnifiedOrderResource) {
|
||||||
|
coLoadDetailInboundCode.value = row.inboundCode?.trim() || ''
|
||||||
|
coLoadDetailOpen.value = true
|
||||||
|
coLoadDetailSummary.value = null
|
||||||
|
coLoadDetailError.value = coLoadDetailInboundCode.value ? null : getApiErrorDetails(new Error('同柜组成员入仓码尚未返回'), '同柜组成员入仓码尚未返回,请刷新后重试')
|
||||||
|
if (coLoadDetailInboundCode.value) void loadCoLoadDetail()
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCoLoadCreate() {
|
function openCoLoadCreate() {
|
||||||
@@ -847,31 +849,6 @@ async function createCoLoadGroup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cancelCoLoadGroup(row: UnifiedOrderResource) {
|
|
||||||
const group = coLoadGroup(row)
|
|
||||||
if (!group || group.status !== 'ACTIVE' || coLoadSaving.value) return
|
|
||||||
let reason = ''
|
|
||||||
try {
|
|
||||||
const result = await ElMessageBox.prompt(
|
|
||||||
`将整组取消(${coLoadGroupMemberLabel(group)})。不支持单独移除成员;如需调整,请取消后重新建组。`,
|
|
||||||
'取消同柜不可拆组',
|
|
||||||
{ confirmButtonText: '确认整组取消', cancelButtonText: '返回', inputPlaceholder: '取消原因(选填)', inputType: 'textarea' },
|
|
||||||
)
|
|
||||||
reason = result.value
|
|
||||||
} catch { return }
|
|
||||||
coLoadSaving.value = true
|
|
||||||
coLoadError.value = null
|
|
||||||
try {
|
|
||||||
await cancelPlatformCoLoadGroup(group.groupId, group.version, reason)
|
|
||||||
ElMessage.success('同柜不可拆组已整组取消')
|
|
||||||
await loadOrders()
|
|
||||||
} catch (error) {
|
|
||||||
coLoadError.value = getApiErrorDetails(error, '取消同柜不可拆组失败')
|
|
||||||
} finally {
|
|
||||||
coLoadSaving.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function batchApproveOrders() {
|
async function batchApproveOrders() {
|
||||||
const selected = [...selectedApprovalOrders.value]
|
const selected = [...selectedApprovalOrders.value]
|
||||||
if (!selected.length || batchApprovalSaving.value) return
|
if (!selected.length || batchApprovalSaving.value) return
|
||||||
@@ -925,19 +902,17 @@ function clearMeasurementOrder() {
|
|||||||
measurementOrder.value = null
|
measurementOrder.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
type UnifiedOrderRowAction = 'note' | 'approve' | 'measurement' | 'cancelCoLoad'
|
type UnifiedOrderRowAction = 'note' | 'approve' | 'measurement'
|
||||||
const unifiedOrderRowActionIcons: Record<UnifiedOrderRowAction, Component> = {
|
const unifiedOrderRowActionIcons: Record<UnifiedOrderRowAction, Component> = {
|
||||||
note: StickyNote,
|
note: StickyNote,
|
||||||
approve: CircleCheck,
|
approve: CircleCheck,
|
||||||
measurement: Ruler,
|
measurement: Ruler,
|
||||||
cancelCoLoad: Unlink,
|
|
||||||
}
|
}
|
||||||
function unifiedOrderSecondaryActions(row: UnifiedOrderResource): UnifiedOrderRowAction[] {
|
function unifiedOrderSecondaryActions(row: UnifiedOrderResource): UnifiedOrderRowAction[] {
|
||||||
return [
|
return [
|
||||||
...(can('platform:order:note') ? ['note' as const] : []),
|
...(can('platform:order:note') ? ['note' as const] : []),
|
||||||
...(canApprove(row) ? ['approve' as const] : []),
|
...(canApprove(row) ? ['approve' as const] : []),
|
||||||
...(row.mainStatus === 'WAIT_MEASURE' && row.inboundId && can('platform:firstleg:inbound:measure:view') ? ['measurement' as const] : []),
|
...(row.mainStatus === 'WAIT_MEASURE' && row.inboundId && can('platform:firstleg:inbound:measure:view') ? ['measurement' as const] : []),
|
||||||
...(can('platform:firstleg:smart-loading:assign') && coLoadGroup(row)?.status === 'ACTIVE' ? ['cancelCoLoad' as const] : []),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
function directUnifiedOrderActions(row: UnifiedOrderResource) {
|
function directUnifiedOrderActions(row: UnifiedOrderResource) {
|
||||||
@@ -952,17 +927,15 @@ function moreUnifiedOrderActionsLabel(row: UnifiedOrderResource) {
|
|||||||
function unifiedOrderActionLabel(action: UnifiedOrderRowAction) {
|
function unifiedOrderActionLabel(action: UnifiedOrderRowAction) {
|
||||||
if (action === 'note') return '添加备注'
|
if (action === 'note') return '添加备注'
|
||||||
if (action === 'approve') return '审核通过'
|
if (action === 'approve') return '审核通过'
|
||||||
if (action === 'cancelCoLoad') return '取消同柜组'
|
|
||||||
return '测量作业'
|
return '测量作业'
|
||||||
}
|
}
|
||||||
function unifiedOrderActionType(action: UnifiedOrderRowAction) {
|
function unifiedOrderActionType(action: UnifiedOrderRowAction) {
|
||||||
return action === 'approve' ? 'success' : action === 'cancelCoLoad' ? 'danger' : 'primary'
|
return action === 'approve' ? 'success' : 'primary'
|
||||||
}
|
}
|
||||||
function runMoreAction(command: string | number | object, row: UnifiedOrderResource) {
|
function runMoreAction(command: string | number | object, row: UnifiedOrderResource) {
|
||||||
if (command === 'note') openNoteDialog(row)
|
if (command === 'note') openNoteDialog(row)
|
||||||
if (command === 'approve') openApproval(row)
|
if (command === 'approve') openApproval(row)
|
||||||
if (command === 'measurement') openMeasurement(row)
|
if (command === 'measurement') openMeasurement(row)
|
||||||
if (command === 'cancelCoLoad') void cancelCoLoadGroup(row)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDetail() {
|
function closeDetail() {
|
||||||
@@ -1361,8 +1334,7 @@ onBeforeUnmount(() => {
|
|||||||
<template #inboundCode="{ row }">
|
<template #inboundCode="{ row }">
|
||||||
<div class="inbound-relation-cell order-code-cell">
|
<div class="inbound-relation-cell order-code-cell">
|
||||||
<strong class="inbound-code-value" :title="(row as UnifiedOrderResource).inboundCode || '待生成入仓码'">{{ (row as UnifiedOrderResource).inboundCode || '待生成入仓码' }}</strong>
|
<strong class="inbound-code-value" :title="(row as UnifiedOrderResource).inboundCode || '待生成入仓码'">{{ (row as UnifiedOrderResource).inboundCode || '待生成入仓码' }}</strong>
|
||||||
<button v-if="coLoadGroup(row as UnifiedOrderResource)" type="button" class="coload-tag-trigger" :aria-label="`查看${coLoadGroupSummary(coLoadGroup(row as UnifiedOrderResource)!)}成员`" @click.stop="openCoLoadDetail(coLoadGroup(row as UnifiedOrderResource)!)"><el-tag size="small" :type="coLoadGroup(row as UnifiedOrderResource)?.status === 'ACTIVE' ? 'warning' : 'info'">{{ coLoadGroupSummary(coLoadGroup(row as UnifiedOrderResource)!) }}</el-tag></button>
|
<button v-if="coLoadGroup(row as UnifiedOrderResource)" type="button" class="coload-tag-trigger" :aria-label="`查看${coLoadGroupSummary(coLoadGroup(row as UnifiedOrderResource)!)}成员`" @click.stop="openCoLoadDetail(row as UnifiedOrderResource)"><el-tag size="small" :type="coLoadGroup(row as UnifiedOrderResource)?.status === 'ACTIVE' ? 'warning' : 'info'">{{ coLoadGroupSummary(coLoadGroup(row as UnifiedOrderResource)!) }}</el-tag></button>
|
||||||
<el-button v-if="(row as UnifiedOrderResource).inboundCode" class="order-relation-link" link type="primary" aria-label="查看入仓码关联数据" @click="openInboundRelationship(row as UnifiedOrderResource)"><Link2 :size="14" />关联数据</el-button>
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="(row as UnifiedOrderResource).relation"
|
v-if="(row as UnifiedOrderResource).relation"
|
||||||
class="order-relation-link"
|
class="order-relation-link"
|
||||||
@@ -1395,16 +1367,18 @@ onBeforeUnmount(() => {
|
|||||||
<div class="coload-selection"><span v-for="row in selectedOrders" :key="String(row.orderId)">{{ row.inboundCode || '待生成入仓码' }}</span></div>
|
<div class="coload-selection"><span v-for="row in selectedOrders" :key="String(row.orderId)">{{ row.inboundCode || '待生成入仓码' }}</span></div>
|
||||||
<div v-if="coLoadError" class="coload-error" role="alert"><span>{{ coLoadError.message }}</span><ApiErrorMeta :details="coLoadError" /></div>
|
<div v-if="coLoadError" class="coload-error" role="alert"><span>{{ coLoadError.message }}</span><ApiErrorMeta :details="coLoadError" /></div>
|
||||||
</AppDialog>
|
</AppDialog>
|
||||||
<AppDialog :model-value="Boolean(coLoadDetailGroup)" title="同柜组成员" :show-confirm="false" cancel-text="关闭" width="min(720px, 94vw)" @update:model-value="coLoadDetailGroup = null">
|
<AppDialog v-model="coLoadDetailOpen" title="同柜组成员" :show-confirm="false" cancel-text="关闭" width="min(720px, 94vw)" :loading="coLoadDetailLoading">
|
||||||
<template v-if="coLoadDetailGroup">
|
<LoadErrorState v-if="coLoadDetailError" title="同柜组成员加载失败" :message="coLoadDetailError.message" :loading="coLoadDetailLoading" @retry="loadCoLoadDetail"><template #details><ApiErrorMeta :details="coLoadDetailError" /></template></LoadErrorState>
|
||||||
<div class="coload-detail-summary"><el-tag size="small" :type="coLoadDetailGroup.status === 'ACTIVE' ? 'warning' : 'info'">{{ coLoadGroupSummary(coLoadDetailGroup) }}</el-tag><span>{{ coLoadDetailMembers.length }} 个入仓码 · 共 {{ coLoadDetailGroup.fullPackageCount }} 箱</span></div>
|
<template v-else-if="coLoadDetailSummary">
|
||||||
<div v-if="coLoadDetailMembers.length" class="coload-detail-scroll">
|
<div class="coload-detail-summary"><el-tag size="small" :type="coLoadDetailSummary.status === 'ACTIVE' ? 'warning' : 'info'">{{ coLoadGroupSummary(coLoadDetailSummary) }}</el-tag><span>{{ coLoadDetailSummary.memberCount }} 个成员</span></div>
|
||||||
|
<el-alert v-if="coLoadDetailHasMissingInboundCodes" type="error" :closable="false" show-icon title="同柜组成员入仓码尚未返回,请刷新后重试" />
|
||||||
|
<div v-else-if="coLoadDetailMembers.length" class="coload-detail-scroll">
|
||||||
<table class="coload-detail-table">
|
<table class="coload-detail-table">
|
||||||
<thead><tr><th>入仓码</th><th>箱数</th></tr></thead>
|
<thead><tr><th>入仓码</th></tr></thead>
|
||||||
<tbody><tr v-for="member in coLoadDetailMembers" :key="member.inboundId"><td><strong :title="member.inboundCode">{{ member.inboundCode }}</strong></td><td>{{ member.fullPackageCount }} 箱</td></tr></tbody>
|
<tbody><tr v-for="member in coLoadDetailMembers" :key="member.inboundCode"><td><strong :title="member.inboundCode">{{ member.inboundCode }}</strong></td></tr></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<el-empty v-else description="暂无有效入仓码" :image-size="56" />
|
<el-empty v-else description="暂无同柜组成员" :image-size="56" />
|
||||||
</template>
|
</template>
|
||||||
</AppDialog>
|
</AppDialog>
|
||||||
</template>
|
</template>
|
||||||
@@ -1417,7 +1391,6 @@ onBeforeUnmount(() => {
|
|||||||
:inbound-code="relationOrder.inboundCode"
|
:inbound-code="relationOrder.inboundCode"
|
||||||
/>
|
/>
|
||||||
</AppDrawer>
|
</AppDrawer>
|
||||||
<InboundCodeRelationshipDialog v-if="inboundRelationshipDialogOpen" v-model="inboundRelationshipDialogOpen" :inbound-code="inboundRelationshipDialogCode" :can-read-base="access.can('platform:order:list')" :can-read-loading="access.can('platform:firstleg:smart-loading:list')" :can-read-overseas="access.can('platform:overseas-warehouse:inventory:list')" />
|
|
||||||
|
|
||||||
<AppDialog v-model="noteOpen" title="添加内部备注" confirm-text="保存备注" :confirm-loading="noteSaving" @confirm="saveNote">
|
<AppDialog v-model="noteOpen" title="添加内部备注" confirm-text="保存备注" :confirm-loading="noteSaving" @confirm="saveNote">
|
||||||
<el-input v-model="noteContent" type="textarea" :rows="5" maxlength="2000" show-word-limit placeholder="请输入仅平台内部可见的备注" aria-label="内部备注内容" />
|
<el-input v-model="noteContent" type="textarea" :rows="5" maxlength="2000" show-word-limit placeholder="请输入仅平台内部可见的备注" aria-label="内部备注内容" />
|
||||||
|
|||||||
Reference in New Issue
Block a user