|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
|
|
|
|
import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
import { ArrowLeft, ChevronDown, Download, Plus, Printer, Trash2, Upload, X } from '@lucide/vue'
|
|
|
|
@@ -38,7 +38,7 @@ interface CargoRow { discardCartonIds?: string[]; id: number; included?: boolean
|
|
|
|
|
interface SkuRelabelRow { id: number; sourceRowId: number | null; cartonMark: string; originalSku: string; newSku: string; quantity: number; fbaCartonMark: string; shipmentNo: string; destinationWarehouseId: string; remark: string }
|
|
|
|
|
interface ImportedRelabelBinding extends SkuRelabelRow { templateType: RelabelTemplateType; selected: boolean; shipmentNo: string }
|
|
|
|
|
interface ServiceConfig { title: string; createTitle: string; numberLabel: string; emptyText: string; columns: string[] }
|
|
|
|
|
interface TransferCarton { cartonMark: string; customerCartonMark?: string; cartonIndex: number; totalCartons: number; status: string; transferItemId?: string | number; transferPalletId?: string | number; length?: number; width?: number; height?: number; weight?: number; quantity?: number; discrepancyReason?: string; countedAt?: string }
|
|
|
|
|
interface TransferCarton { items?: Array<{ transferItemId: string | number; skuCode: string; quantity: number }>; cartonMark: string; customerCartonMark?: string; cartonIndex: number; totalCartons: number; status: string; transferItemId?: string | number; transferPalletId?: string | number; length?: number; width?: number; height?: number; weight?: number; quantity?: number; discrepancyReason?: string; countedAt?: string }
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<{ service: ServiceType; createPage?: boolean; detailPage?: boolean; detailNo?: string }>(), { createPage: false, detailPage: false, detailNo: '' })
|
|
|
|
|
const auth = useAuthStore()
|
|
|
|
@@ -152,24 +152,28 @@ const showTransferCartons = ref(false)
|
|
|
|
|
function transferCartonItem(carton: TransferCarton) {
|
|
|
|
|
return selectedDetail.value?.items?.find((item) => carton.transferItemId != null && String(item.id) === String(carton.transferItemId))
|
|
|
|
|
}
|
|
|
|
|
function transferCartonLines(carton: TransferCarton) {
|
|
|
|
|
if (carton.items?.length) return carton.items.map(line => ({ ...line, productNameZh: selectedDetail.value?.items?.find(item => String(item.id) === String(line.transferItemId))?.productNameZh || '—' }))
|
|
|
|
|
const item = transferCartonItem(carton)
|
|
|
|
|
return [{ transferItemId: carton.transferItemId, skuCode: item?.skuCode || '—', productNameZh: item?.productNameZh || '—', quantity: Number(carton.quantity || 0) }]
|
|
|
|
|
}
|
|
|
|
|
function palletCartons(pallet: TransferPallet) {
|
|
|
|
|
return (selectedDetail.value?.cartons || []).filter((carton) => carton.status === 'COUNTED' && pallet.id != null && carton.transferPalletId != null && String(carton.transferPalletId) === String(pallet.id))
|
|
|
|
|
}
|
|
|
|
|
function palletSkuRows(pallet: TransferPallet) {
|
|
|
|
|
const summary = new Map<string, number>()
|
|
|
|
|
for (const carton of palletCartons(pallet)) {
|
|
|
|
|
const item = transferCartonItem(carton); const sku = item?.skuCode || '未标记 SKU'
|
|
|
|
|
summary.set(sku, (summary.get(sku) || 0) + Number(carton.quantity || 0))
|
|
|
|
|
for (const line of transferCartonLines(carton)) summary.set(line.skuCode, (summary.get(line.skuCode) || 0) + Number(line.quantity))
|
|
|
|
|
}
|
|
|
|
|
return [...summary.entries()].map(([sku, quantity]) => ({ sku, quantity }))
|
|
|
|
|
}
|
|
|
|
|
function palletQuantity(pallet: TransferPallet) { return palletCartons(pallet).reduce((sum, carton) => sum + Number(carton.quantity || 0), 0) }
|
|
|
|
|
function countedTransferCartons() { return (selectedDetail.value?.cartons || []).filter((carton) => carton.status === 'COUNTED') }
|
|
|
|
|
function receivedItemCartons(itemId: string | number | undefined) {
|
|
|
|
|
return countedTransferCartons().filter(carton => itemId != null && carton.transferItemId != null && String(carton.transferItemId) === String(itemId))
|
|
|
|
|
return countedTransferCartons().filter(carton => itemId != null && transferCartonLines(carton).some(line => String(line.transferItemId) === String(itemId)))
|
|
|
|
|
}
|
|
|
|
|
function receivedItemQuantity(itemId: string | number | undefined) {
|
|
|
|
|
return receivedItemCartons(itemId).reduce((sum, carton) => sum + Number(carton.quantity || 0), 0)
|
|
|
|
|
return receivedItemCartons(itemId).reduce((sum, carton) => sum + transferCartonLines(carton).filter(line => String(line.transferItemId) === String(itemId)).reduce((total, line) => total + Number(line.quantity), 0), 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const detailError = ref('')
|
|
|
|
@@ -418,6 +422,11 @@ onMounted(() => {
|
|
|
|
|
else void loadPage()
|
|
|
|
|
void nextTick(refreshWarehouseMarkLabels)
|
|
|
|
|
})
|
|
|
|
|
let activatedOnce = false
|
|
|
|
|
onActivated(() => {
|
|
|
|
|
if (activatedOnce && !props.createPage && !props.detailPage && mode.value === 'list') void loadPage()
|
|
|
|
|
activatedOnce = true
|
|
|
|
|
})
|
|
|
|
|
onBeforeUnmount(() => { clearCompletionPhotos(); clearMeasurementPhotos(); clearDiscardPhotos() })
|
|
|
|
|
function resetFilters() { Object.assign(filters, { number: '', inboundNo: '', warehouse: '', destination: '', status: '', start: '', end: '', reviewStart: '', reviewEnd: '', completedStart: '', completedEnd: '', measuredStart: '', measuredEnd: '', outboundStart: '', outboundEnd: '' }) }
|
|
|
|
|
function resetForm() {
|
|
|
|
@@ -477,6 +486,14 @@ function returnToList() {
|
|
|
|
|
function serviceListRouteName(service: ServiceType) {
|
|
|
|
|
return ({ labeling: 'overseas-labeling', transfer: 'overseas-storage-transfer', disposal: 'overseas-cargo-disposal', relabel: 'overseas-return-relabel' } as Record<ServiceType, string>)[service]
|
|
|
|
|
}
|
|
|
|
|
async function goToServiceList() {
|
|
|
|
|
if (props.createPage || props.detailPage) {
|
|
|
|
|
await router.push({ name: serviceListRouteName(props.service) })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
mode.value = 'list'
|
|
|
|
|
await loadPage()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function openEdit(transferNo: string) {
|
|
|
|
|
try {
|
|
|
|
@@ -1191,13 +1208,12 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
|
|
|
|
|
if (shippingLabelFile.value) await uploadRelabelShippingLabel(created.application.relabelNo, shippingLabelFile.value)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Creation already committed: return to the list even when an attachment still needs retrying.
|
|
|
|
|
if (props.createPage) await router.push({ name: serviceListRouteName(props.service) })
|
|
|
|
|
else { mode.value = 'list'; await load(); await openDetail({ '0': created.application.relabelNo }) }
|
|
|
|
|
await goToServiceList()
|
|
|
|
|
await ElMessageBox.alert(`申请 ${created.application.relabelNo} 已创建,部分附件上传失败:${error instanceof Error ? error.message : '上传失败'}。请在原单详情补传失败附件,不要重复下单。`, '申请已创建,附件待补传', { confirmButtonText: '知道了' })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ElMessage.success(`${config.value.title}提交成功`)
|
|
|
|
|
await router.push({ name: serviceListRouteName(props.service) })
|
|
|
|
|
await goToServiceList()
|
|
|
|
|
return
|
|
|
|
|
} else if (props.service === 'disposal') {
|
|
|
|
|
const items = cargoRows.value.filter((item) => item.quantity > 0).map((item) => {
|
|
|
|
@@ -1233,7 +1249,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
|
|
|
|
|
if (!relabelNo) {
|
|
|
|
|
ElMessage.success('转运单已提交审核;PDA 入库确认后将自动生成关联换标单')
|
|
|
|
|
await ElMessageBox.alert(`转运单 ${transferNo} 已提交。关联换标单会在 PDA 完成入库、客户确认实测库存后自动生成;届时请进入关联换标单详情上传贴标总文件。`, '转运单已提交', { confirmButtonText: '知道了' })
|
|
|
|
|
await router.push({ name: serviceListRouteName('transfer') })
|
|
|
|
|
await goToServiceList()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
@@ -1241,17 +1257,17 @@ async function submit(action: 'draft' | 'submit' = 'submit') {
|
|
|
|
|
if (skuTotalFile.value) await uploadRelabelFile(relabelNo, 'SKU_TOTAL', skuTotalFile.value)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await ElMessageBox.alert(`转运单 ${transferNo} 已提交,但换标总文件上传失败:${error instanceof Error ? error.message : '上传失败'}。请进入关联换标单详情补传,不要重复提交转运单。`, '转运单已提交,附件待补传', { confirmButtonText: '知道了' })
|
|
|
|
|
await openDetail({ '0': transferNo })
|
|
|
|
|
await goToServiceList()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ElMessage.success('转运单已提交审核')
|
|
|
|
|
await router.push({ name: serviceListRouteName('transfer') })
|
|
|
|
|
await goToServiceList()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ElMessage.success(props.service === 'transfer' ? '转运单草稿保存成功' : `${config.value.title}提交成功`)
|
|
|
|
|
await router.push({ name: serviceListRouteName(props.service) })
|
|
|
|
|
await goToServiceList()
|
|
|
|
|
} catch (error) { ElMessage.error(error instanceof Error ? error.message : '提交失败') }
|
|
|
|
|
finally { saving.value = false }
|
|
|
|
|
}
|
|
|
|
@@ -1440,7 +1456,7 @@ async function resubmitRejectedRelabel() {
|
|
|
|
|
idempotencyKey: createIdempotencyKey(),
|
|
|
|
|
})
|
|
|
|
|
ElMessage.success('贴标单已重新提交审核')
|
|
|
|
|
await router.push({ name: serviceListRouteName('labeling') })
|
|
|
|
|
await goToServiceList()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error !== 'cancel' && error !== 'close') ElMessage.error(error instanceof Error ? error.message : '重新提交失败')
|
|
|
|
|
}
|
|
|
|
@@ -1777,10 +1793,10 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
|
|
|
|
|
<div class="warehouse-cartons-dialog-body">
|
|
|
|
|
<el-collapse>
|
|
|
|
|
<el-collapse-item v-for="carton in countedTransferCartons()" :key="carton.cartonMark" :name="carton.cartonMark">
|
|
|
|
|
<template #title><strong>{{ carton.cartonMark }}</strong><span class="warehouse-carton-summary">已确认 · {{ transferCartonItem(carton)?.skuCode ? 1 : 0 }} 种 SKU · {{ carton.quantity ?? '—' }} 件</span></template>
|
|
|
|
|
<template #title><strong>{{ carton.cartonMark }}</strong><span class="warehouse-carton-summary">已确认 · {{ transferCartonLines(carton).length }} 种 SKU · {{ carton.quantity ?? '—' }} 件</span></template>
|
|
|
|
|
<div class="warehouse-carton-measurements">所属托盘:{{ selectedDetail?.pallets?.find((pallet) => carton.transferPalletId != null && String(pallet.id) === String(carton.transferPalletId))?.palletNo || '散箱' }} · 清点时间:{{ formatDateTime(carton.countedAt) }}</div>
|
|
|
|
|
<div class="warehouse-carton-measurements">长 {{ carton.length ?? '—' }} cm · 宽 {{ carton.width ?? '—' }} cm · 高 {{ carton.height ?? '—' }} cm · 重量 {{ carton.weight ?? '—' }} kg · 体积 {{ [carton.length, carton.width, carton.height].every(value => value != null) ? (Number(carton.length) * Number(carton.width) * Number(carton.height) / 1000000).toFixed(6) : '—' }} m³</div>
|
|
|
|
|
<el-table :data="[{ skuCode: transferCartonItem(carton)?.skuCode || '—', productNameZh: transferCartonItem(carton)?.productNameZh || '—', quantity: carton.quantity ?? '—' }]" size="small" border>
|
|
|
|
|
<el-table :data="transferCartonLines(carton)" size="small" border>
|
|
|
|
|
<el-table-column prop="skuCode" label="SKU" min-width="220" />
|
|
|
|
|
<el-table-column prop="productNameZh" label="中文品名" min-width="180" />
|
|
|
|
|
<el-table-column prop="quantity" label="实际签收数量" width="130" />
|
|
|
|
|