<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" />
|
</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>
|