|
@@ -5,7 +5,6 @@ import { Plus, Tickets } from '@element-plus/icons-vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import type { FormInstance, FormRules } from 'element-plus'
|
|
import type { FormInstance, FormRules } from 'element-plus'
|
|
|
import EvidenceUpload from '../components/EvidenceUpload.vue'
|
|
import EvidenceUpload from '../components/EvidenceUpload.vue'
|
|
|
-import EntryAudit from '../components/EntryAudit.vue'
|
|
|
|
|
import { api } from '../api'
|
|
import { api } from '../api'
|
|
|
import { blankEntry } from '../utils/entry'
|
|
import { blankEntry } from '../utils/entry'
|
|
|
import { useUploads } from '../utils/uploads'
|
|
import { useUploads } from '../utils/uploads'
|
|
@@ -13,7 +12,7 @@ import type { Attachment, Entry } from '../api/indexType'
|
|
|
import type { WmsOutboundRecordResponse, WmsOutboundSaveRequest, WmsSkuResponse, WmsReversalPreCheckResponse } from '../api/indexType'
|
|
import type { WmsOutboundRecordResponse, WmsOutboundSaveRequest, WmsSkuResponse, WmsReversalPreCheckResponse } from '../api/indexType'
|
|
|
import { dateTime, fieldErrors, report, reversalLimit, refreshAfterWrite } from '../utils/ui'
|
|
import { dateTime, fieldErrors, report, reversalLimit, refreshAfterWrite } from '../utils/ui'
|
|
|
|
|
|
|
|
-type OutboundEntry = Entry & { recordId?: number; alreadyReversedQty: number }
|
|
|
|
|
|
|
+type OutboundEntry = Entry & { recordId?: number; alreadyReversedQty: number; relatedReversalFlows?: WmsOutboundRecordResponse['relatedReversalFlows'] }
|
|
|
type OutboundDetail = WmsOutboundRecordResponse
|
|
type OutboundDetail = WmsOutboundRecordResponse
|
|
|
const outboundRows = ref<OutboundEntry[]>([])
|
|
const outboundRows = ref<OutboundEntry[]>([])
|
|
|
const total = ref(0)
|
|
const total = ref(0)
|
|
@@ -29,6 +28,7 @@ function toEntry(item: OutboundDetail): OutboundEntry {
|
|
|
return {
|
|
return {
|
|
|
...blankEntry('outbound'), recordId: item.id, id: item.flowNo || String(item.id ?? ''),
|
|
...blankEntry('outbound'), recordId: item.id, id: item.flowNo || String(item.id ?? ''),
|
|
|
alreadyReversedQty: item.alreadyReversedQty ?? 0, status: item.status === 1 ? 'completed' : 'draft',
|
|
alreadyReversedQty: item.alreadyReversedQty ?? 0, status: item.status === 1 ? 'completed' : 'draft',
|
|
|
|
|
+ relatedReversalFlows: item.relatedReversalFlows,
|
|
|
skuId: item.skuId || '', quantity: item.qty ?? null, orderType: item.orderType === 1 ? 'internal' : item.orderType === 2 ? 'external' : '',
|
|
skuId: item.skuId || '', quantity: item.qty ?? null, orderType: item.orderType === 1 ? 'internal' : item.orderType === 2 ? 'external' : '',
|
|
|
so: item.soNo || '', logistics: Object.keys(logisticsTypes).find(key => logisticsTypes[key] === item.logisticsType) || '',
|
|
so: item.soNo || '', logistics: Object.keys(logisticsTypes).find(key => logisticsTypes[key] === item.logisticsType) || '',
|
|
|
company: item.expressCompany || '', tracking: item.expressNo || '', remark: item.remark || '',
|
|
company: item.expressCompany || '', tracking: item.expressNo || '', remark: item.remark || '',
|
|
@@ -56,7 +56,9 @@ async function loadSkus(keyword = '') {
|
|
|
}
|
|
}
|
|
|
function searchSkus(keyword: string) { loadSkus(keyword).catch(report) }
|
|
function searchSkus(keyword: string) { loadSkus(keyword).catch(report) }
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
|
|
+let listLoadVersion = 0
|
|
|
async function loadData() {
|
|
async function loadData() {
|
|
|
|
|
+ const version = ++listLoadVersion
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
try {
|
|
try {
|
|
|
const filter = applied.value
|
|
const filter = applied.value
|
|
@@ -76,10 +78,12 @@ async function loadData() {
|
|
|
startTime: dates[0] ? `${dates[0]} 00:00:00` : undefined,
|
|
startTime: dates[0] ? `${dates[0]} 00:00:00` : undefined,
|
|
|
endTime: dates[1] ? `${dates[1]} 23:59:59` : undefined,
|
|
endTime: dates[1] ? `${dates[1]} 23:59:59` : undefined,
|
|
|
})
|
|
})
|
|
|
|
|
+ if (version !== listLoadVersion) return
|
|
|
outboundRows.value = (result.rows as WmsOutboundRecordResponse[]).map(toEntry)
|
|
outboundRows.value = (result.rows as WmsOutboundRecordResponse[]).map(toEntry)
|
|
|
total.value = result.total
|
|
total.value = result.total
|
|
|
}
|
|
}
|
|
|
- finally { loading.value = false }
|
|
|
|
|
|
|
+ catch (error) { if (version === listLoadVersion) throw error }
|
|
|
|
|
+ finally { if (version === listLoadVersion) loading.value = false }
|
|
|
}
|
|
}
|
|
|
const route = useRoute()
|
|
const route = useRoute()
|
|
|
const filters = reactive({ period: '', keyword: '', status: '', orderType: '', dates: [] as string[] })
|
|
const filters = reactive({ period: '', keyword: '', status: '', orderType: '', dates: [] as string[] })
|
|
@@ -232,7 +236,7 @@ async function submitReverse() {
|
|
|
<el-table-column label="SO 编号" width="164"><template #default="{ row }">{{ row.so || (row.orderType === 'external' ? '—' : '待填写') }}</template></el-table-column>
|
|
<el-table-column label="SO 编号" width="164"><template #default="{ row }">{{ row.so || (row.orderType === 'external' ? '—' : '待填写') }}</template></el-table-column>
|
|
|
<el-table-column label="物流信息" min-width="210"><template #default="{ row }">{{ logisticsText(row) }}</template></el-table-column>
|
|
<el-table-column label="物流信息" min-width="210"><template #default="{ row }">{{ logisticsText(row) }}</template></el-table-column>
|
|
|
<el-table-column prop="operator" label="操作人" width="100"><template #default="{ row }">{{ row.operator || '—' }}</template></el-table-column>
|
|
<el-table-column prop="operator" label="操作人" width="100"><template #default="{ row }">{{ row.operator || '—' }}</template></el-table-column>
|
|
|
- <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip><template #default="{ row }">{{ row.remark || '—' }}</template></el-table-column>
|
|
|
|
|
|
|
+ <!-- <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip><template #default="{ row }">{{ row.remark || '—' }}</template></el-table-column> -->
|
|
|
<el-table-column label="操作" width="190" fixed="right">
|
|
<el-table-column label="操作" width="190" fixed="right">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<template v-if="row.status === 'draft'">
|
|
<template v-if="row.status === 'draft'">
|
|
@@ -273,7 +277,7 @@ async function submitReverse() {
|
|
|
<div v-if="form.logistics === 'express'" class="outbound-grid"><el-form-item label="快递公司" prop="company" required :error="errors.company"><el-input v-model="form.company" placeholder="例如:顺丰速运" /></el-form-item><el-form-item label="快递单号" prop="tracking" required :error="errors.tracking"><el-input v-model="form.tracking" placeholder="填写快递单号" /></el-form-item></div>
|
|
<div v-if="form.logistics === 'express'" class="outbound-grid"><el-form-item label="快递公司" prop="company" required :error="errors.company"><el-input v-model="form.company" placeholder="例如:顺丰速运" /></el-form-item><el-form-item label="快递单号" prop="tracking" required :error="errors.tracking"><el-input v-model="form.tracking" placeholder="填写快递单号" /></el-form-item></div>
|
|
|
<el-form-item v-else label="物流截图" prop="screenshots" required :error="errors.screenshots"><EvidenceUpload :key="form.logistics" :active="dialog" :disabled="busy" v-model="form.screenshots" @update:model-value="validateAttachment('screenshots')" /></el-form-item>
|
|
<el-form-item v-else label="物流截图" prop="screenshots" required :error="errors.screenshots"><EvidenceUpload :key="form.logistics" :active="dialog" :disabled="busy" v-model="form.screenshots" @update:model-value="validateAttachment('screenshots')" /></el-form-item>
|
|
|
</template>
|
|
</template>
|
|
|
- <el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item>
|
|
|
|
|
|
|
+ <!-- <el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item> -->
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<template #footer><el-button :disabled="busy" @click="dialog = false">取消</el-button><el-button type="warning" plain :disabled="busy || pendingUploads > 0" @click="save(false)">保存草稿</el-button><el-button type="primary" :loading="busy" :disabled="pendingUploads > 0" @click="save(true)">提交出库</el-button></template>
|
|
<template #footer><el-button :disabled="busy" @click="dialog = false">取消</el-button><el-button type="warning" plain :disabled="busy || pendingUploads > 0" @click="save(false)">保存草稿</el-button><el-button type="primary" :loading="busy" :disabled="pendingUploads > 0" @click="save(true)">提交出库</el-button></template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
@@ -281,7 +285,7 @@ async function submitReverse() {
|
|
|
<el-dialog :model-value="!!detail" class="outbound-dialog" modal-class="outbound-overlay" width="760px" align-center @close="detail = undefined">
|
|
<el-dialog :model-value="!!detail" class="outbound-dialog" modal-class="outbound-overlay" width="760px" align-center @close="detail = undefined">
|
|
|
<template #header="{ titleId, titleClass }"><h2 :id="titleId" :class="titleClass">流水详情 · {{ detail?.id }}</h2><p v-if="detail" class="outbound-subtitle">已完成 · 原流水状态:{{ reversalStatus(detail) }} · 创建时间:{{ dateTime(detail.createdAt) }}</p></template>
|
|
<template #header="{ titleId, titleClass }"><h2 :id="titleId" :class="titleClass">流水详情 · {{ detail?.id }}</h2><p v-if="detail" class="outbound-subtitle">已完成 · 原流水状态:{{ reversalStatus(detail) }} · 创建时间:{{ dateTime(detail.createdAt) }}</p></template>
|
|
|
<template v-if="detail">
|
|
<template v-if="detail">
|
|
|
- <EntryAudit :entry="detail" :reversed-qty="detail.alreadyReversedQty" />
|
|
|
|
|
|
|
+ <!-- <EntryAudit :entry="detail" :reversed-qty="detail.alreadyReversedQty" /> -->
|
|
|
<div class="outbound-grid outbound-detail-fields">
|
|
<div class="outbound-grid outbound-detail-fields">
|
|
|
<div><label>skuID / 商品</label><div class="outbound-readonly">{{ detail.skuId }} · {{ entrySku(detail)?.name }}</div></div>
|
|
<div><label>skuID / 商品</label><div class="outbound-readonly">{{ detail.skuId }} · {{ entrySku(detail)?.name }}</div></div>
|
|
|
<div><label>库存变动</label><div class="outbound-readonly">{{ detail.direction === 'outbound' ? '出库 −' : '入库 +' }}{{ detail.quantity }}</div></div>
|
|
<div><label>库存变动</label><div class="outbound-readonly">{{ detail.direction === 'outbound' ? '出库 −' : '入库 +' }}{{ detail.quantity }}</div></div>
|
|
@@ -297,7 +301,13 @@ async function submitReverse() {
|
|
|
<span v-if="detail.source === 'normal'">物流信息:{{ logisticsText(detail) }}</span>
|
|
<span v-if="detail.source === 'normal'">物流信息:{{ logisticsText(detail) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
- <div v-if="detail.remark" class="outbound-related">备注 / 冲销原因:{{ detail.remark }}</div>
|
|
|
|
|
|
|
+ <!-- <div v-if="detail.remark" class="outbound-related">备注 / 冲销原因:{{ detail.remark }}</div> -->
|
|
|
|
|
+ <div v-if="detail.relatedReversalFlows?.length">
|
|
|
|
|
+ <div class="outbound-evidence-label">关联冲销流水</div>
|
|
|
|
|
+ <div class="outbound-readonly">
|
|
|
|
|
+ <div v-for="(flow, index) in detail.relatedReversalFlows" :key="index" class="outbound-related-reversal-item">{{ flow }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
<template #footer><el-button @click="detail = undefined">关闭</el-button><el-button v-if="detail && canReverse(detail)" type="warning" plain @click="openReverse(detail)">冲销 / 更正</el-button></template>
|
|
<template #footer><el-button @click="detail = undefined">关闭</el-button><el-button v-if="detail && canReverse(detail)" type="warning" plain @click="openReverse(detail)">冲销 / 更正</el-button></template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
@@ -354,6 +364,7 @@ async function submitReverse() {
|
|
|
.outbound-evidence-item { display: flex; align-items: center; gap: 8px; max-width: 100%; }
|
|
.outbound-evidence-item { display: flex; align-items: center; gap: 8px; max-width: 100%; }
|
|
|
.outbound-evidence-item .el-image { width: 46px; height: 42px; border-radius: 4px; flex-shrink: 0; cursor: zoom-in; }
|
|
.outbound-evidence-item .el-image { width: 46px; height: 42px; border-radius: 4px; flex-shrink: 0; cursor: zoom-in; }
|
|
|
.outbound-related, .outbound-reverse-form { margin-top: 16px; overflow-wrap: anywhere; }
|
|
.outbound-related, .outbound-reverse-form { margin-top: 16px; overflow-wrap: anywhere; }
|
|
|
|
|
+.outbound-related-reversal-item { min-height: 28px; padding: 4px 0; box-sizing: border-box; }
|
|
|
.outbound-reverse-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
|
.outbound-reverse-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
|
|
@media (max-width: 1000px) { .outbound-filters { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
@media (max-width: 1000px) { .outbound-filters { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
|
@media (max-width: 600px) { .outbound-heading { flex-direction: column; align-items: flex-start; gap: 14px; } .outbound-grid { grid-template-columns: minmax(0, 1fr); } .outbound-heading h1 { font-size: 22px; } }
|
|
@media (max-width: 600px) { .outbound-heading { flex-direction: column; align-items: flex-start; gap: 14px; } .outbound-grid { grid-template-columns: minmax(0, 1fr); } .outbound-heading h1 { font-size: 22px; } }
|