| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogVisible" title="éè´§å详æ
" width="90%" @close="closeDia"> |
| | | <div v-loading="loading"> |
| | | <span class="descriptions">åºæ¬ä¿¡æ¯</span> |
| | | <el-descriptions :column="4" border> |
| | | <el-descriptions-item label="éè´§åå·">{{ detail.returnNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ®ç¶æ"> |
| | | <el-tag :type="getStatusType(detail.status)">{{ getStatusText(detail.status) }}</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="客æ·åç§°">{{ detail.customerName }}</el-descriptions-item> |
| | | <el-descriptions-item label="订åå·">{{ detail.salesContractNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸å¡å">{{ detail.salesman }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
³èæ¹å·">{{ detail.shippingNo }}</el-descriptions-item> |
| | | <!-- <el-descriptions-item label="项ç®åç§°">{{ detail.projectName }}</el-descriptions-item> --> |
| | | <el-descriptions-item label="å¶å人">{{ detail.maker }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¶åæ¶é´">{{ detail.makeTime }}</el-descriptions-item> |
| | | <el-descriptions-item label="éè´§åå ">{{ detail.returnReason }}</el-descriptions-item> |
| | | <el-descriptions-item label="鿬¾æ»é¢">{{ detail.refundAmount }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="padding-top: 20px"> |
| | | <span class="descriptions">产åå表</span> |
| | | <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData"> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import { returnManagementGetById, returnManagementGetByShippingId } from "@/api/salesManagement/returnOrder.js"; |
| | | |
| | | const dialogVisible = ref(false); |
| | | const loading = ref(false); |
| | | const detail = ref({}); |
| | | const tableData = ref([]); |
| | | const availableProducts = ref([]); |
| | | |
| | | const sameKey = (a, b) => a != null && b != null && String(a) === String(b); |
| | | |
| | | /** ä¸ formDia ä¸è´ï¼ä¸¤ä»½å表æ id åå¹¶ï¼é¿å
åªå productDtoData æ¶ç¼ºåºåºåå·/æ¹æ¬¡/æ°é */ |
| | | const mergeShippingProductLists = (data) => { |
| | | const lists = [data?.shippingProductVoList, data?.productDtoData].filter(Array.isArray); |
| | | if (!lists.length) return []; |
| | | const map = new Map(); |
| | | for (const list of lists) { |
| | | for (const p of list) { |
| | | if (p == null) continue; |
| | | const key = p.id != null ? String(p.id) : null; |
| | | if (!key) continue; |
| | | const prev = map.get(key); |
| | | map.set(key, prev ? { ...prev, ...p } : { ...p }); |
| | | } |
| | | } |
| | | return Array.from(map.values()); |
| | | }; |
| | | |
| | | const pickShippingLine = (normalized) => { |
| | | const pid = normalized?.returnSaleLedgerProductId ?? normalized?.id; |
| | | const sid = normalized?.stockOutRecordId ?? normalized?.shippingProductId; |
| | | const direct = availableProducts.value.find( |
| | | (p) => |
| | | sameKey(p?.id, pid) || |
| | | sameKey(p?.stockOutRecordId, pid) || |
| | | sameKey(p?.id, sid) || |
| | | sameKey(p?.stockOutRecordId, sid) |
| | | ); |
| | | if (direct) return direct; |
| | | const pmid = normalized?.productModelId; |
| | | if (pmid == null || pmid === "") return undefined; |
| | | const candidates = availableProducts.value.filter((p) => sameKey(p?.productModelId, pmid)); |
| | | if (!candidates.length) return undefined; |
| | | if (candidates.length === 1) return candidates[0]; |
| | | const spec = String(normalized?.specificationModel ?? normalized?.model ?? ""); |
| | | if (spec) { |
| | | const hit = candidates.find((p) => { |
| | | const ps = String(p?.specificationModel ?? p?.model ?? ""); |
| | | return ps && ps === spec; |
| | | }); |
| | | if (hit) return hit; |
| | | } |
| | | return candidates[0]; |
| | | }; |
| | | |
| | | const isEmptyText = (v) => v === "" || v == null || v === undefined; |
| | | |
| | | const firstFiniteNumber = (...vals) => { |
| | | for (const v of vals) { |
| | | if (v === "" || v == null || v === undefined) continue; |
| | | const n = Number(v); |
| | | if (Number.isFinite(n)) return n; |
| | | } |
| | | return undefined; |
| | | }; |
| | | |
| | | const firstNonEmptyText = (...vals) => { |
| | | const hit = vals.find((v) => !isEmptyText(v)); |
| | | return hit === undefined ? "" : hit; |
| | | }; |
| | | |
| | | /** 详æ
è¡¨ç¨ productName / modelï¼åå¹¶æ¶å¿è®©ç©ºä¸²çæåºåºè¡å段 */ |
| | | const mergeDetailProductRow = (product, normalized) => { |
| | | const row = { ...product, ...normalized }; |
| | | row.outboundBatches = firstNonEmptyText( |
| | | row.outboundBatches, |
| | | product?.outboundBatches, |
| | | product?.shippingNo, |
| | | product?.outboundNo, |
| | | normalized?.outboundBatches, |
| | | normalized?.outboundNo, |
| | | normalized?.shippingNo |
| | | ); |
| | | row.batchNo = firstNonEmptyText( |
| | | row.batchNo, |
| | | product?.batchNo, |
| | | product?.batchNumber, |
| | | product?.lotNo, |
| | | product?.batchCode, |
| | | product?.shippingBatchNo, |
| | | normalized?.batchNo, |
| | | normalized?.batchNumber, |
| | | normalized?.lotNo, |
| | | normalized?.shippingBatchNo |
| | | ); |
| | | const stock = firstFiniteNumber( |
| | | row.stockOutNum, |
| | | product?.stockOutNum, |
| | | product?.totalQuantity, |
| | | product?.shippingQuantity, |
| | | product?.deliveryQuantity, |
| | | product?.quantity, |
| | | product?.outQuantity, |
| | | normalized?.stockOutNum, |
| | | normalized?.totalQuantity, |
| | | normalized?.shippingQuantity, |
| | | normalized?.deliveryQuantity |
| | | ); |
| | | if (stock !== undefined) row.stockOutNum = stock; |
| | | const un = firstFiniteNumber( |
| | | row.unQuantity, |
| | | product?.unQuantity, |
| | | product?.remainingQuantity, |
| | | product?.noReturnQuantity, |
| | | product?.canReturnQuantity, |
| | | product?.availableReturnNum, |
| | | normalized?.unQuantity, |
| | | normalized?.remainingQuantity, |
| | | normalized?.noReturnQuantity, |
| | | normalized?.canReturnQuantity |
| | | ); |
| | | if (un !== undefined) row.unQuantity = un; |
| | | else { |
| | | const s = Number(row.stockOutNum); |
| | | const ret = Number(row.totalReturnNum ?? 0); |
| | | if (Number.isFinite(s) && s >= 0 && Number.isFinite(ret) && ret >= 0) { |
| | | row.unQuantity = Math.max(0, s - ret); |
| | | } |
| | | } |
| | | const returned = firstFiniteNumber( |
| | | row.totalReturnNum, |
| | | product?.totalReturnNum, |
| | | product?.totalReturnedNum, |
| | | normalized?.totalReturnNum, |
| | | normalized?.totalReturnedNum |
| | | ); |
| | | if (returned !== undefined) row.totalReturnNum = returned; |
| | | else if (isEmptyText(row.totalReturnNum)) row.totalReturnNum = 0; |
| | | if (isEmptyText(row.unit)) { |
| | | row.unit = firstNonEmptyText(product?.unit, normalized?.unit); |
| | | } |
| | | row.productName = firstNonEmptyText( |
| | | row.productName, |
| | | normalized?.productName, |
| | | normalized?.productCategory, |
| | | product?.productName, |
| | | product?.productCategory |
| | | ); |
| | | row.model = firstNonEmptyText( |
| | | row.model, |
| | | normalized?.model, |
| | | normalized?.specificationModel, |
| | | product?.model, |
| | | product?.specificationModel |
| | | ); |
| | | return row; |
| | | }; |
| | | |
| | | const normalizeDetailRow = (raw) => { |
| | | const ledgerId = |
| | | raw?.returnSaleLedgerProductId ?? |
| | | raw?.saleLedgerProductId ?? |
| | | raw?.stockOutRecordId ?? |
| | | raw?.shippingProductId; |
| | | const productId = ledgerId ?? raw?.id; |
| | | const num = Number(raw?.num ?? raw?.returnQuantity ?? 0); |
| | | return { |
| | | ...raw, |
| | | id: productId, |
| | | returnSaleLedgerProductId: productId, |
| | | productModelId: raw?.productModelId, |
| | | stockOutRecordId: raw?.stockOutRecordId, |
| | | shippingProductId: raw?.shippingProductId, |
| | | productName: raw?.productName ?? raw?.productCategory ?? raw?.productTypeName ?? "", |
| | | model: raw?.model ?? raw?.specificationModel ?? raw?.specModel ?? "", |
| | | outboundBatches: raw?.outboundBatches ?? raw?.outboundNo ?? raw?.shippingNo, |
| | | batchNo: |
| | | raw?.batchNo ?? |
| | | raw?.batchNumber ?? |
| | | raw?.lotNo ?? |
| | | raw?.batchCode ?? |
| | | raw?.shippingBatchNo, |
| | | stockOutNum: |
| | | raw?.stockOutNum ?? |
| | | raw?.totalQuantity ?? |
| | | raw?.shippingQuantity ?? |
| | | raw?.deliveryQuantity ?? |
| | | raw?.quantity, |
| | | totalReturnNum: raw?.totalReturnNum ?? raw?.totalReturnedNum, |
| | | unQuantity: |
| | | raw?.unQuantity ?? |
| | | raw?.remainingQuantity ?? |
| | | raw?.noReturnQuantity ?? |
| | | raw?.canReturnQuantity, |
| | | returnQuantity: Number.isFinite(num) ? num : 0, |
| | | price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0), |
| | | amount: Number(raw?.amount ?? 0).toFixed(2), |
| | | isQuality: raw?.isQuality ?? 2, |
| | | remark: raw?.remark ?? "", |
| | | }; |
| | | }; |
| | | |
| | | const tableColumn = [ |
| | | {align: "center", label: "åºåºåå·", prop: "outboundBatches"}, |
| | | {align: "center", label: "æ¹æ¬¡å·", prop: "batchNo"}, |
| | | {align: "center", label: "产å大类", prop: "productName"}, |
| | | {align: "center", label: "è§æ ¼åå·", prop: "model"}, |
| | | {align: "center", label: "åä½", prop: "unit", width: 80}, |
| | | {align: "center", label: "æ»æ°é", prop: "stockOutNum", width: 120}, |
| | | {align: "center", label: "å·²éè´§æ°é", prop: "totalReturnNum", width: 120}, |
| | | {align: "center", label: "æªéè´§æ°é", prop: "unQuantity", width: 120}, |
| | | {align: "center", label: "éè´§æ°é", prop: "returnQuantity", width: 120}, |
| | | {align: "center", label: "é货产ååä»·", prop: "price", width: 120}, |
| | | {align: "center", label: "é货产åéé¢", prop: "amount", width: 120}, |
| | | {align: "center", label: "æ¯å¦æè´¨éé®é¢", prop: "isQuality", width: 140, formatData: (v) => ({ "1": "æ¯", "2": "å¦" }[String(v)] ?? v)}, |
| | | {align: "center", label: "夿³¨", prop: "remark", width: 150}, |
| | | ]; |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 0: "warning", |
| | | 1: "success" |
| | | }; |
| | | return statusMap[status] || "info"; |
| | | }; |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | 0: "å¾
å¤ç", |
| | | 1: "å·²å¤ç" |
| | | }; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | | const openDialog = async (row) => { |
| | | if (!row?.id) return; |
| | | dialogVisible.value = true; |
| | | loading.value = true; |
| | | try { |
| | | const res = await returnManagementGetById({ returnManagementId: row.id }); |
| | | detail.value = res?.data ?? res ?? {}; |
| | | |
| | | if (detail.value.shippingId) { |
| | | const productRes = await returnManagementGetByShippingId({ shippingId: detail.value.shippingId }); |
| | | if (productRes.code === 200) { |
| | | availableProducts.value = mergeShippingProductLists(productRes.data); |
| | | } |
| | | } |
| | | |
| | | const list = |
| | | detail.value?.returnSaleProducts || |
| | | detail.value?.returnSaleProductList || |
| | | detail.value?.returnSaleProductDtoData || |
| | | []; |
| | | |
| | | tableData.value = Array.isArray(list) |
| | | ? list.map((raw) => { |
| | | const normalized = normalizeDetailRow(raw); |
| | | const product = pickShippingLine(normalized); |
| | | return product ? mergeDetailProductRow(product, normalized) : normalized; |
| | | }) |
| | | : []; |
| | | |
| | | const headerShipNo = detail.value?.shippingNo; |
| | | if (headerShipNo && Array.isArray(tableData.value) && tableData.value.length) { |
| | | tableData.value = tableData.value.map((r) => |
| | | isEmptyText(r.outboundBatches) ? { ...r, outboundBatches: headerShipNo } : r |
| | | ); |
| | | } |
| | | } catch (e) { |
| | | console.error("Failed to load detail", e); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | dialogVisible.value = false; |
| | | detail.value = {}; |
| | | tableData.value = []; |
| | | availableProducts.value = []; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .descriptions { |
| | | margin-bottom: 20px; |
| | | display: inline-block; |
| | | font-size: 1rem; |
| | | font-weight: 600; |
| | | padding-left: 12px; |
| | | position: relative; |
| | | } |
| | | .descriptions::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 1rem; |
| | | background-color: #002FA7; |
| | | border-radius: 2px; |
| | | } |
| | | </style> |