<template>
|
<div>
|
<el-dialog v-model="dialogFormVisible" :title="operationType === 'edit' ? '编辑退货单' : '新增退货单'" width="90%" @close="closeDia">
|
<div>
|
<span class="descriptions">基本信息</span>
|
<el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
|
<el-row :gutter="30">
|
<el-col :span="4">
|
<el-form-item label="退货单号:" prop="returnNo">
|
<el-input
|
:disabled="operationType === 'edit' || form.returnNoCheckbox"
|
v-model="form.returnNo"
|
placeholder="使用系统编号"
|
class="input-with-select"
|
>
|
<template v-if="operationType !== 'edit'" #append>
|
<el-checkbox v-model="form.returnNoCheckbox" @change="handleReturnNoCheckboxChange"></el-checkbox>
|
</template>
|
</el-input>
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="客户名称:" prop="customerId">
|
<el-select v-model="form.customerId" filterable placeholder="请选择客户" @change="customerNameChange">
|
<el-option
|
v-for="item in customerNameOptions"
|
:key="item.value"
|
:label="item.label"
|
:value="item.id"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="关联出库单号:" prop="shippingId">
|
<el-select v-model="form.shippingId" filterable placeholder="请选择出库单号" @change="outboundNoChange">
|
<el-option
|
v-for="item in outboundOptions"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="项目阶段:" prop="projectStage">
|
<el-input v-model="form.projectStage" placeholder="项目阶段" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="制单人:" prop="maker">
|
<el-select v-model="form.maker" filterable placeholder="请选择制单人">
|
<el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="制单时间:" prop="makeTime">
|
<el-date-picker v-model="form.makeTime" type="datetime" style="width:100%" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="结算人:" prop="settler">
|
<el-select v-model="form.settler" filterable placeholder="请选择结算人">
|
<el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="状态:" prop="status">
|
<el-select v-model="form.status" placeholder="请选择状态">
|
<el-option label="待审核" :value="0" />
|
<el-option label="审核中" :value="1" />
|
<el-option label="已审核" :value="2" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
<hr>
|
<div style="padding-top: 20px">
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
|
<span class="descriptions" style="margin-bottom:0">产品列表</span>
|
<el-button type="primary" @click="openProductSelection" :disabled="!form.shippingId">添加产品</el-button>
|
</div>
|
<PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData">
|
<template #returnQuantity="{ row }">
|
<el-input
|
v-model="row.returnQuantity"
|
style="width:100px"
|
placeholder="请输入"
|
type="number"
|
@input="(val) => handleReturnQuantityChange(val, row)"
|
/>
|
</template>
|
<template #action="{ row, index }">
|
<el-button type="danger" link @click="deleteRow(index)">删除</el-button>
|
</template>
|
</PIMTable>
|
</div>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary" @click="submitForm">确认</el-button>
|
<el-button @click="closeDia">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<el-dialog v-model="productSelectionVisible" title="选择产品" width="70%" append-to-body>
|
<el-table
|
:data="availableProducts"
|
style="width: 100%"
|
@selection-change="handleSelectionChange"
|
ref="productTableRef"
|
row-key="id"
|
>
|
<el-table-column align="center" type="selection" width="55" />
|
<el-table-column align="center" prop="productCategory" label="产品大类" />
|
<el-table-column align="center" prop="specificationModel" label="规格型号" />
|
<el-table-column align="center" prop="unit" label="单位" />
|
<el-table-column align="center" prop="quantity" label="总数量" />
|
<el-table-column align="center" prop="unQuantity" label="未退货数量" />
|
<el-table-column align="center" label="已退货数量">
|
<template #default="{ row }">{{ calcAlreadyReturned(row) }}</template>
|
</el-table-column>
|
|
</el-table>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary" @click="confirmProductSelection">确认添加</el-button>
|
<el-button @click="productSelectionVisible = false">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { reactive, ref, toRefs, getCurrentInstance } from "vue";
|
import { returnManagementAdd, returnManagementUpdate, returnManagementGetByShippingId, getSalesLedger, returnManagementGetById } from "@/api/salesManagement/returnOrder.js";
|
import { getAllCustomerList } from "@/api/customerService/index.js";
|
import useUserStore from "@/store/modules/user.js";
|
import { userListNoPageByTenantId } from "@/api/system/user.js";
|
import { listProject } from "@/api/oaSystem/projectManagement.js";
|
|
const { proxy } = getCurrentInstance();
|
const emit = defineEmits(['close'])
|
const dialogFormVisible = ref(false);
|
const operationType = ref('');
|
const formRef = ref(null);
|
const userStore = useUserStore();
|
|
const data = reactive({
|
form: {
|
returnNoCheckbox: true,
|
returnNo: "",
|
customerId: "",
|
shippingId: "",
|
projectId: "",
|
projectStage: "",
|
maker: "",
|
makeTime: "",
|
settler: "",
|
status: 0,
|
},
|
rules: {
|
returnNo: [{
|
validator: (rule, value, callback) => {
|
if (form.value.returnNoCheckbox) return callback();
|
if (!value) return callback(new Error("请输入退货单号"));
|
callback();
|
}, trigger: "blur"
|
}],
|
customerId: [{ required: true, message: "请选择客户", trigger: "change" }],
|
shippingId: [{ required: true, message: "请选择关联出库单号", trigger: "change" }],
|
}
|
});
|
const { form, rules } = toRefs(data);
|
|
const calcAlreadyReturned = (row) => {
|
const total = Number(row?.quantity ?? row?.totalQuantity ?? row?.totalReturnNum ?? 0);
|
const un = Number(row?.unQuantity ?? 0);
|
if (!Number.isFinite(total) || !Number.isFinite(un)) return 0;
|
return Math.max(total - un, 0);
|
};
|
|
const tableColumn = ref([
|
{align: "center", label: "产品大类", prop: "productCategory" },
|
{align: "center", label: "规格型号", prop: "specificationModel" },
|
{align: "center", label: "单位", prop: "unit", width: 80 },
|
{align: "center", label: "总数量", prop: "quantity", width: 120 },
|
{align: "center", label: "已退货数量", prop: "totalReturnNum", width: 120 },
|
{align: "center", label: "未退货数量", prop: "unQuantity", width: 120 },
|
{align: "center", label: "退货数量", prop: "returnQuantity", dataType: "slot", slot: "returnQuantity", width: 120 },
|
{align: "center", label: "操作" , prop: "action", dataType: "slot", slot: "action", width: 120 },
|
]);
|
const tableData = ref([]);
|
const customerNameOptions = ref([]);
|
const outboundOptions = ref([]);
|
const userOptions = ref([]);
|
const projectOptions = ref([]);
|
|
const deleteRow = (index) => {
|
tableData.value.splice(index, 1);
|
};
|
|
const normalizeDetailRow = (raw) => {
|
const productId = raw?.returnSaleLedgerProductId ?? raw?.saleLedgerProductId ?? raw?.id;
|
const returnSaleProductId = raw?.returnSaleProductId ?? raw?.id;
|
const num = Number(raw?.num ?? raw?.returnQuantity ?? 0);
|
return {
|
...raw,
|
id: productId,
|
returnSaleProductId,
|
returnSaleLedgerProductId: productId,
|
num,
|
returnQuantity: Number.isFinite(num) ? num : 0,
|
};
|
};
|
|
const setFormForEdit = async (row) => {
|
const res = await returnManagementGetById({ returnManagementId: row?.id });
|
console.log("res", res);
|
const detail = res?.data ?? res ?? {};
|
|
Object.assign(form.value, detail);
|
form.value.returnNoCheckbox = true;
|
|
if (form.value.customerId) {
|
await customerNameChange(form.value.customerId, false);
|
}
|
if (form.value.shippingId) {
|
await outboundNoChange(form.value.shippingId, false);
|
}
|
|
const list =
|
detail?.returnSaleProducts ||
|
detail?.returnSaleProductList ||
|
detail?.returnSaleProductDtoData ||
|
[];
|
|
tableData.value = Array.isArray(list)
|
? list.map((raw) => {
|
const normalized = normalizeDetailRow(raw);
|
const product = availableProducts.value.find((p) => p.id === normalized.id);
|
return product ? { ...product, ...normalized } : normalized;
|
})
|
: [];
|
};
|
|
const openDialog = async (type, row) => {
|
operationType.value = type;
|
dialogFormVisible.value = true;
|
proxy.resetForm("formRef");
|
await Promise.all([initCustomers(), initUsers(), initProjects()]);
|
if (type === "edit") {
|
await setFormForEdit(row);
|
} else {
|
tableData.value = [];
|
Object.assign(form.value, {
|
returnNoCheckbox: true,
|
returnNo: "",
|
customerId: "",
|
shippingId: "",
|
projectId: "",
|
projectStage: "",
|
maker: "",
|
makeTime: "",
|
settler: "",
|
status: 0,
|
});
|
form.value.maker = userStore.nickName || userStore.name || "";
|
form.value.makeTime = new Date().toISOString().replace('T', ' ').split('.')[0]; // Default to now
|
form.value.status = 0; // Default status
|
}
|
};
|
|
const submitForm = () => {
|
proxy.$refs["formRef"].validate(valid => {
|
if (!valid) return;
|
const returnSaleProducts = (tableData.value || []).map(el => ({
|
returnSaleLedgerProductId: el.returnSaleLedgerProductId ?? el.id,
|
num: Number(el.num ?? el.returnQuantity ?? 0),
|
id: operationType.value === "edit" ? (el.returnSaleProductId ?? "") : ""
|
}));
|
const payload = { ...form.value, returnSaleProducts };
|
delete payload.returnNoCheckbox;
|
if (operationType.value === "add" && form.value.returnNoCheckbox) delete payload.returnNo;
|
if (operationType.value === "add") {
|
returnManagementAdd(payload).then(() => {
|
proxy.$modal.msgSuccess("新增成功");
|
closeDia();
|
});
|
} else {
|
returnManagementUpdate(payload).then(() => {
|
proxy.$modal.msgSuccess("修改成功");
|
closeDia();
|
});
|
}
|
});
|
};
|
|
const closeDia = () => {
|
proxy.resetForm("formRef");
|
dialogFormVisible.value = false;
|
emit('close');
|
};
|
|
const initCustomers = async () => {
|
const res = await getAllCustomerList({});
|
if (res?.records) {
|
customerNameOptions.value = res.records.map(item => ({
|
label: item.customerName,
|
value: item.customerName, // Keep value as name if needed for other logic, but request says customerId
|
id: item.id,
|
code: item.customerCode
|
}));
|
}
|
};
|
|
const initUsers = async () => {
|
const res = await userListNoPageByTenantId();
|
if (res?.data) {
|
userOptions.value = res.data.map(u => ({ label: u.nickName || u.userName, value: u.nickName || u.userName }));
|
}
|
};
|
|
const initProjects = async () => {
|
try {
|
const res = await listProject({ pageSize: 1000 });
|
if (res?.rows) {
|
projectOptions.value = res.rows.map(p => ({ label: p.projectName, value: p.id }));
|
}
|
} catch (e) {
|
console.error("Failed to load projects", e);
|
}
|
};
|
|
const handleReturnNoCheckboxChange = (checked) => {
|
if (checked) form.value.returnNo = "";
|
formRef.value?.validateField('returnNo');
|
};
|
|
const customerNameChange = async (val, clearDownstream = true) => {
|
// val is customerId now
|
if (clearDownstream) {
|
form.value.shippingId = "";
|
outboundOptions.value = [];
|
}
|
|
// Find customer name for getSalesLedger if it requires name
|
const customer = customerNameOptions.value.find(c => c.id === val);
|
if (!customer) return;
|
|
// Assuming getSalesLedger takes customerName. If it takes ID, adjust accordingly.
|
// Previous code used customerName. Let's try passing customerName.
|
getSalesLedger({
|
customerName: customer.label,
|
}).then(res => {
|
if(res.code === 200){
|
outboundOptions.value = res.data.map(item => ({
|
label: item.salesContractNo, // Or whatever the outbound number field is
|
value: item.id,
|
}))
|
}
|
})
|
};
|
|
const outboundNoChange = async (val, clearTable = true) => {
|
// val is shippingId
|
let res = await returnManagementGetByShippingId({ shippingId: val });
|
if(res.code === 200){
|
// If backend returns project info, set it
|
if (res.data.projectId) form.value.projectId = res.data.projectId;
|
if (res.data.projectStage) form.value.projectStage = res.data.projectStage;
|
|
// Store available products for selection
|
availableProducts.value = res.data.productDtoData || [];
|
if (clearTable) tableData.value = [];
|
}
|
};
|
|
const handleReturnQuantityChange = (val, row) => {
|
if (val === "" || val === null) return;
|
const max = row.unQuantity === undefined || row.unQuantity === null ? Infinity : Number(row.unQuantity || 0);
|
const current = Number(val);
|
|
if (current > max) {
|
// Need nextTick to ensure update if user typed too fast or pasted
|
proxy.$nextTick(() => {
|
row.returnQuantity = max;
|
row.num = max;
|
});
|
proxy.$modal.msgWarning(`退货数量不能超过未退货数量(${max})`);
|
} else if (current < 0) {
|
proxy.$nextTick(() => {
|
row.returnQuantity = 0;
|
row.num = 0;
|
});
|
} else {
|
row.num = current;
|
}
|
};
|
|
const availableProducts = ref([]);
|
const productSelectionVisible = ref(false);
|
const selectedProducts = ref([]);
|
|
const openProductSelection = () => {
|
productSelectionVisible.value = true;
|
// Pre-select items already in tableData
|
proxy.$nextTick(() => {
|
if (proxy.$refs.productTableRef) {
|
proxy.$refs.productTableRef.clearSelection();
|
availableProducts.value.forEach(row => {
|
if (tableData.value.some(item => item.id === row.id)) {
|
proxy.$refs.productTableRef.toggleRowSelection(row, true);
|
}
|
});
|
}
|
});
|
};
|
|
const handleSelectionChange = (val) => {
|
selectedProducts.value = val;
|
};
|
|
// Removed checkSelectable to allow toggling existing items
|
const confirmProductSelection = () => {
|
// Rebuild tableData based on selection, preserving existing data (returnQuantity)
|
const newTableData = [];
|
|
selectedProducts.value.forEach(product => {
|
// Check if product was already in tableData to preserve user input
|
const existing = tableData.value.find(item => item.id === product.id);
|
if (existing) {
|
newTableData.push(existing);
|
} else {
|
// Create new entry
|
newTableData.push({
|
...product, // Keep all product display fields (productName, model, unit, etc.)
|
|
// Map to backend entity structure for submission
|
returnSaleLedgerProductId: product.id,
|
returnQuantity: 0, // Default input
|
num: 0, // Backend quantity field
|
|
// Ensure display fields are available if they come from 'product'
|
// If product has different field names than tableColumn expects, map them here
|
productName: product.productName,
|
specificationModel: product.specificationModel,
|
unit: product.unit,
|
quantity: product.quantity,
|
totalReturnNum: product.totalReturnNum,
|
unQuantity: product.unQuantity
|
});
|
}
|
});
|
|
tableData.value = newTableData;
|
productSelectionVisible.value = false;
|
};
|
|
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>
|