|
|
|
@@ -1,7 +1,7 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch, type Component } from 'vue'
|
|
|
|
|
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 {
|
|
|
|
|
getPlatformUnifiedOrder,
|
|
|
|
@@ -12,15 +12,11 @@ import {
|
|
|
|
|
listPlatformUnifiedOrders,
|
|
|
|
|
unifiedOrderStatusOptions,
|
|
|
|
|
type UnifiedOrderDetailResource,
|
|
|
|
|
type UnifiedOrderCoLoadGroupSummary,
|
|
|
|
|
type UnifiedOrderMainStatus,
|
|
|
|
|
type UnifiedOrderResource,
|
|
|
|
|
} from '@/modules/unified-orders/api/unifiedOrders'
|
|
|
|
|
import {
|
|
|
|
|
batchQueryPlatformCoLoadGroups,
|
|
|
|
|
cancelPlatformCoLoadGroup,
|
|
|
|
|
createPlatformCoLoadGroup,
|
|
|
|
|
type CoLoadGroup,
|
|
|
|
|
} from '@/modules/unified-orders/api/coLoadGroups'
|
|
|
|
|
import { createPlatformCoLoadGroup, getPlatformCoLoadGroupByInboundCode, type CoLoadBusinessSummary, type CoLoadGroup } from '@/modules/unified-orders/api/coLoadGroups'
|
|
|
|
|
import OrderFulfillmentTreePanel from '@/modules/unified-orders/components/OrderFulfillmentTreePanel.vue'
|
|
|
|
|
import InboundFeeDetailManager from '@/modules/unified-orders/components/InboundFeeDetailManager.vue'
|
|
|
|
|
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 LoadErrorState from '@/shared/components/LoadErrorState.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 StatusBadge from '@/shared/components/StatusBadge.vue'
|
|
|
|
|
import {
|
|
|
|
@@ -129,9 +124,12 @@ const approvalSaving = ref(false)
|
|
|
|
|
const batchApprovalSaving = ref(false)
|
|
|
|
|
const selectedApprovalOrders = ref<UnifiedOrderResource[]>([])
|
|
|
|
|
const selectedOrders = ref<UnifiedOrderResource[]>([])
|
|
|
|
|
const coLoadGroupsByOrderId = ref(new Map<string, CoLoadGroup>())
|
|
|
|
|
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 coLoadError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
const approvalOrder = ref<UnifiedOrderResource | null>(null)
|
|
|
|
@@ -141,8 +139,6 @@ const noteContent = ref('')
|
|
|
|
|
const noteOrder = ref<UnifiedOrderResource | null>(null)
|
|
|
|
|
const relationOpen = ref(false)
|
|
|
|
|
const relationOrder = ref<UnifiedOrderResource | null>(null)
|
|
|
|
|
const inboundRelationshipDialogOpen = ref(false)
|
|
|
|
|
const inboundRelationshipDialogCode = ref('')
|
|
|
|
|
const measurementDetail = ref<InboundMeasurementDetail | null>(null)
|
|
|
|
|
const measurementLoading = ref(false)
|
|
|
|
|
const measurementError = ref<ApiErrorDetails | null>(null)
|
|
|
|
@@ -709,7 +705,6 @@ async function loadWarehouses() {
|
|
|
|
|
async function loadOrders() {
|
|
|
|
|
selectedApprovalOrders.value = []
|
|
|
|
|
selectedOrders.value = []
|
|
|
|
|
coLoadGroupsByOrderId.value = new Map()
|
|
|
|
|
loading.value = true
|
|
|
|
|
loadError.value = null
|
|
|
|
|
coLoadError.value = null
|
|
|
|
@@ -739,14 +734,6 @@ async function loadOrders() {
|
|
|
|
|
})
|
|
|
|
|
records.value = result.records
|
|
|
|
|
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) {
|
|
|
|
|
records.value = []
|
|
|
|
|
total.value = 0
|
|
|
|
@@ -789,40 +776,55 @@ function handleOrderSelectionChange(rows: UnifiedOrderResource[]) {
|
|
|
|
|
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) {
|
|
|
|
|
return coLoadGroupsByOrderId.value.get(String(row.orderId)) || null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function coLoadGroupOrderCount(group: CoLoadGroup) {
|
|
|
|
|
return new Set(group.members.map((member) => member.orderId)).size
|
|
|
|
|
return row.coLoadGroup || null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function coLoadGroupMemberLabel(group: CoLoadGroup) {
|
|
|
|
|
const orderCount = coLoadGroupOrderCount(group)
|
|
|
|
|
const orderCount = new Set(group.members.map((member) => member.orderId)).size
|
|
|
|
|
return group.members.length === orderCount
|
|
|
|
|
? `${orderCount} 票`
|
|
|
|
|
: `${orderCount} 票 · ${group.members.length} 个入仓片段`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function coLoadGroupStatusLabel(group: CoLoadGroup) {
|
|
|
|
|
function coLoadGroupStatusLabel(group: { status: string }) {
|
|
|
|
|
return group.status === 'ACTIVE' ? '未锁定' : group.status === 'LOCKED' ? '已锁定' : group.status === 'CONSUMED' ? '已消费' : '已取消'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function coLoadGroupSummary(group: CoLoadGroup) {
|
|
|
|
|
return `同柜组 · ${coLoadGroupOrderCount(group)} 票 · ${coLoadGroupStatusLabel(group)}`
|
|
|
|
|
function coLoadGroupSummary(group: UnifiedOrderCoLoadGroupSummary | CoLoadBusinessSummary) {
|
|
|
|
|
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) {
|
|
|
|
|
coLoadDetailGroup.value = group
|
|
|
|
|
const coLoadDetailMembers = computed(() => coLoadDetailSummary.value?.members.filter(hasCoLoadMemberInboundCode) || [])
|
|
|
|
|
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() {
|
|
|
|
@@ -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() {
|
|
|
|
|
const selected = [...selectedApprovalOrders.value]
|
|
|
|
|
if (!selected.length || batchApprovalSaving.value) return
|
|
|
|
@@ -925,19 +902,17 @@ function clearMeasurementOrder() {
|
|
|
|
|
measurementOrder.value = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type UnifiedOrderRowAction = 'note' | 'approve' | 'measurement' | 'cancelCoLoad'
|
|
|
|
|
type UnifiedOrderRowAction = 'note' | 'approve' | 'measurement'
|
|
|
|
|
const unifiedOrderRowActionIcons: Record<UnifiedOrderRowAction, Component> = {
|
|
|
|
|
note: StickyNote,
|
|
|
|
|
approve: CircleCheck,
|
|
|
|
|
measurement: Ruler,
|
|
|
|
|
cancelCoLoad: Unlink,
|
|
|
|
|
}
|
|
|
|
|
function unifiedOrderSecondaryActions(row: UnifiedOrderResource): UnifiedOrderRowAction[] {
|
|
|
|
|
return [
|
|
|
|
|
...(can('platform:order:note') ? ['note' as const] : []),
|
|
|
|
|
...(canApprove(row) ? ['approve' 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) {
|
|
|
|
@@ -952,17 +927,15 @@ function moreUnifiedOrderActionsLabel(row: UnifiedOrderResource) {
|
|
|
|
|
function unifiedOrderActionLabel(action: UnifiedOrderRowAction) {
|
|
|
|
|
if (action === 'note') return '添加备注'
|
|
|
|
|
if (action === 'approve') return '审核通过'
|
|
|
|
|
if (action === 'cancelCoLoad') return '取消同柜组'
|
|
|
|
|
return '测量作业'
|
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
|
if (command === 'note') openNoteDialog(row)
|
|
|
|
|
if (command === 'approve') openApproval(row)
|
|
|
|
|
if (command === 'measurement') openMeasurement(row)
|
|
|
|
|
if (command === 'cancelCoLoad') void cancelCoLoadGroup(row)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeDetail() {
|
|
|
|
@@ -1361,8 +1334,7 @@ onBeforeUnmount(() => {
|
|
|
|
|
<template #inboundCode="{ row }">
|
|
|
|
|
<div class="inbound-relation-cell order-code-cell">
|
|
|
|
|
<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>
|
|
|
|
|
<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>
|
|
|
|
|
<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).relation"
|
|
|
|
|
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 v-if="coLoadError" class="coload-error" role="alert"><span>{{ coLoadError.message }}</span><ApiErrorMeta :details="coLoadError" /></div>
|
|
|
|
|
</AppDialog>
|
|
|
|
|
<AppDialog :model-value="Boolean(coLoadDetailGroup)" title="同柜组成员" :show-confirm="false" cancel-text="关闭" width="min(720px, 94vw)" @update:model-value="coLoadDetailGroup = null">
|
|
|
|
|
<template v-if="coLoadDetailGroup">
|
|
|
|
|
<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>
|
|
|
|
|
<div v-if="coLoadDetailMembers.length" class="coload-detail-scroll">
|
|
|
|
|
<AppDialog v-model="coLoadDetailOpen" title="同柜组成员" :show-confirm="false" cancel-text="关闭" width="min(720px, 94vw)" :loading="coLoadDetailLoading">
|
|
|
|
|
<LoadErrorState v-if="coLoadDetailError" title="同柜组成员加载失败" :message="coLoadDetailError.message" :loading="coLoadDetailLoading" @retry="loadCoLoadDetail"><template #details><ApiErrorMeta :details="coLoadDetailError" /></template></LoadErrorState>
|
|
|
|
|
<template v-else-if="coLoadDetailSummary">
|
|
|
|
|
<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">
|
|
|
|
|
<thead><tr><th>入仓码</th><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>
|
|
|
|
|
<thead><tr><th>入仓码</th></tr></thead>
|
|
|
|
|
<tbody><tr v-for="member in coLoadDetailMembers" :key="member.inboundCode"><td><strong :title="member.inboundCode">{{ member.inboundCode }}</strong></td></tr></tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<el-empty v-else description="暂无有效入仓码" :image-size="56" />
|
|
|
|
|
<el-empty v-else description="暂无同柜组成员" :image-size="56" />
|
|
|
|
|
</template>
|
|
|
|
|
</AppDialog>
|
|
|
|
|
</template>
|
|
|
|
@@ -1417,7 +1391,6 @@ onBeforeUnmount(() => {
|
|
|
|
|
:inbound-code="relationOrder.inboundCode"
|
|
|
|
|
/>
|
|
|
|
|
</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">
|
|
|
|
|
<el-input v-model="noteContent" type="textarea" :rows="5" maxlength="2000" show-word-limit placeholder="请输入仅平台内部可见的备注" aria-label="内部备注内容" />
|
|
|
|
|