<template>
|
<div>
|
<vol-box
|
v-model="showDetialBox"
|
:lazy="true"
|
width="75%"
|
:padding="15"
|
title="单据明细信息"
|
>
|
<div class="box-head">
|
<el-alert :closable="false" style="width: 100%">
|
<el-row>
|
<el-col :span="16">
|
<span>已选中 {{ selection.length }} 项</span>
|
</el-col>
|
<el-col :span="8">
|
<el-link
|
type="primary"
|
size="small"
|
v-if="isBatch === 0"
|
style="float: right; height: 20px"
|
@click="handleOpenPicking"
|
>拣选</el-link
|
>
|
<el-link
|
type="primary"
|
size="small"
|
style="float: right; height: 20px; margin-right: 10px"
|
v-if="isBatch === 1"
|
@click="handleOpenBatchPicking"
|
>分批拣选</el-link
|
>
|
<el-link
|
type="primary"
|
size="small"
|
v-if="isBatch === 0"
|
style="float: right; height: 20px; margin-right: 10px"
|
@click="outbound"
|
>直接出库</el-link
|
>
|
<el-link
|
type="primary"
|
size="small"
|
v-if="isBatch === 1"
|
style="float: right; height: 20px; margin-right: 10px"
|
@click="outboundbatch"
|
>分批出库</el-link
|
>
|
<el-link
|
type="primary"
|
size="small"
|
style="float: right; height: 20px; margin-right: 10px"
|
@click="getData"
|
>刷新</el-link
|
>
|
</el-col>
|
</el-row>
|
</el-alert>
|
</div>
|
<div class="box-table" style="margin-top: 1%">
|
<el-table
|
ref="singleTable"
|
:data="groupedTableData"
|
style="width: 100%; height: 100%"
|
highlight-current-row
|
@current-change="handleCurrentChange"
|
height="500px"
|
@row-click="handleRowClick"
|
@selection-change="handleSelectionChange"
|
>
|
<el-table-column type="selection" width="55"> </el-table-column>
|
<el-table-column
|
label="序号"
|
type="index"
|
fixed="left"
|
width="55"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="materielCode"
|
label="物料编号"
|
width="120"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="materielName"
|
label="物料名称"
|
width="150"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="batchNo"
|
label="批次号"
|
width="90"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="supplyCode"
|
label="供应商编号"
|
width="90"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="orderQuantity"
|
label="单据数量"
|
width="90"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="lockQuantity"
|
label="锁定数量"
|
width="90"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="overOutQuantity"
|
label="已出数量"
|
width="90"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="moveQty"
|
label="挪料数量"
|
width="90"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="unit"
|
label="单位"
|
width="80"
|
align="center"
|
></el-table-column>
|
<el-table-column
|
prop="orderDetailStatus"
|
label="订单明细状态"
|
width="90"
|
align="center"
|
>
|
<template #default="scoped">
|
<el-tag size="small">
|
{{ getDictionaryForGroup(scoped.row) }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column
|
prop="assignStock"
|
label="指定库存"
|
width="90"
|
align="center"
|
:hidden="mainBusinessType !== '22'"
|
>
|
<template #default="scoped">
|
<el-tooltip
|
class="item"
|
effect="dark"
|
content="指定库存"
|
placement="bottom"
|
>
|
<el-link
|
type="primary"
|
:disabled="getButtonEnable('assignStock', scoped.row)"
|
@click="handleAssignStock(scoped.row)"
|
>
|
<i class="el-icon-s-grid" style="font-size: 22px"></i>
|
</el-link>
|
</el-tooltip>
|
</template>
|
</el-table-column>
|
<el-table-column
|
prop="viewDetail"
|
label="出库详细"
|
width="90"
|
align="center"
|
>
|
<template #default="scoped">
|
<el-tooltip
|
class="item"
|
effect="dark"
|
content="查看出库详细"
|
placement="bottom"
|
>
|
<el-link
|
type="primary"
|
@click="handleViewDetail(scoped.row)"
|
>
|
<i class="el-icon-s-operation" style="font-size: 22px"></i>
|
</el-link>
|
</el-tooltip>
|
</template>
|
</el-table-column>
|
<el-table-column
|
prop="detailCount"
|
label="明细数量"
|
width="80"
|
align="center"
|
>
|
<template #default="scoped">
|
<el-tag type="info" size="small">
|
{{ scoped.row.detailCount }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
</vol-box>
|
<stock-select ref="child" @parentCall="parentCall"></stock-select>
|
<selected-stock
|
ref="selectedStock"
|
@parentCall="parentCall"
|
></selected-stock>
|
<NoStockOut ref="NoStockOut" @parentCall="parentCall"></NoStockOut>
|
</div>
|
</template>
|
|
<script>
|
import VolBox from "@/components/basic/VolBox.vue";
|
import VolForm from "@/components/basic/VolForm.vue";
|
import StockSelect from "./StockSelect.vue";
|
import SelectedStock from "./SelectedStock.vue";
|
import NoStockOut from "./NoStockOut.vue";
|
import { h, createVNode, render, reactive } from "vue";
|
import {
|
ElDialog,
|
ElForm,
|
ElFormItem,
|
ElSelect,
|
ElOption,
|
ElButton,
|
ElInput,
|
ElMessage,
|
} from "element-plus";
|
|
export default {
|
components: { VolBox, VolForm, StockSelect, SelectedStock, NoStockOut },
|
data() {
|
return {
|
row: null,
|
isBatch: 0,
|
showDetialBox: false,
|
flag: false,
|
currentRow: null,
|
selection: [],
|
tableData: [], // 原始数据
|
groupedTableData: [], // 分组后的数据
|
mainBusinessType: null, // 存储主单据的businessType
|
paginations: {
|
sort: "id",
|
order: "desc",
|
Foots: "",
|
total: 0,
|
sizes: [30, 60, 100, 120],
|
size: 30,
|
Wheres: [],
|
page: 1,
|
rows: 30,
|
},
|
searchFormOptions: [
|
[
|
{
|
title: "单据编号",
|
field: "allocation_code",
|
type: "like",
|
},
|
{
|
title: "单据类型",
|
field: "allocation_type",
|
type: "select",
|
dataKey: "OrderType",
|
data: [],
|
},
|
{
|
title: "单据状态",
|
field: "allocation_state",
|
type: "select",
|
dataKey: "OrderState",
|
data: [],
|
},
|
],
|
],
|
searchFormFields: {
|
allocation_code: "",
|
allocation_type: "",
|
allocation_state: "",
|
},
|
dictionaryList: null,
|
};
|
},
|
methods: {
|
// 根据物料编号、批次、供应商分组数据
|
groupDataByMaterial() {
|
const groups = {};
|
|
this.tableData.forEach(item => {
|
// 构建分组键,批次和供应商可以为空
|
const groupKey = `${item.materielCode}_${item.batchNo || ''}_${item.supplyCode || ''}`;
|
|
if (!groups[groupKey]) {
|
// 创建新分组
|
groups[groupKey] = {
|
materielCode: item.materielCode,
|
materielName: item.materielName,
|
batchNo: item.batchNo || '-',
|
supplyCode: item.supplyCode || '-',
|
orderQuantity: 0,
|
lockQuantity: 0,
|
overOutQuantity: 0,
|
moveQty: 0,
|
unit: item.unit,
|
orderDetailStatus: item.orderDetailStatus,
|
detailCount: 0,
|
originalDetails: [] // 保存原始明细数据
|
};
|
}
|
|
// 累加数量
|
groups[groupKey].orderQuantity += item.orderQuantity || 0;
|
groups[groupKey].lockQuantity += item.lockQuantity || 0;
|
groups[groupKey].overOutQuantity += item.overOutQuantity || 0;
|
groups[groupKey].moveQty += item.moveQty || 0;
|
groups[groupKey].detailCount += 1;
|
groups[groupKey].originalDetails.push(item);
|
|
// 如果状态不同,可以用第一个状态或特殊处理
|
// 这里使用第一个明细的状态
|
});
|
|
// 转换为数组
|
this.groupedTableData = Object.values(groups);
|
|
console.log('分组后的数据:', this.groupedTableData);
|
},
|
|
open(row) {
|
this.row = row;
|
this.showDetialBox = true;
|
console.log("主单据数据:", this.row);
|
this.isBatch = row.isBatch;
|
this.mainBusinessType = row.businessType;
|
this.getDictionaryData();
|
this.getData();
|
},
|
|
getData() {
|
var wheres = [{ name: "orderId", value: this.row.id }];
|
var param = {
|
page: this.paginations.page,
|
rows: this.paginations.rows,
|
sort: this.paginations.sort,
|
order: this.paginations.order,
|
wheres: JSON.stringify(wheres),
|
};
|
this.http
|
.post("api/OutboundOrderDetail/GetPageData", param, "查询中")
|
.then((x) => {
|
this.tableData = x.rows;
|
// 分组数据
|
this.groupDataByMaterial();
|
});
|
},
|
|
// 重点修改:处理指定库存点击事件 - 传递完整的分组明细信息
|
handleAssignStock(row) {
|
// 传递完整的分组明细数据(包含所有原始明细ID和物料信息)
|
if (row.originalDetails && row.originalDetails.length > 0) {
|
this.$refs.child.open({
|
// 物料基本信息(取第一个明细的信息)
|
materielCode: row.originalDetails[0].materielCode,
|
materielName: row.originalDetails[0].materielName,
|
// 分组后的总需求数量
|
qty: row.orderQuantity,
|
// 主单据编号
|
upperOrderNo: this.row.upperOrderNo,
|
// 关键:当前分组的所有明细ID集合
|
detailIds: row.originalDetails.map(detail => detail.id),
|
// 主单据ID
|
mainOrderId: this.row.id,
|
// 完整分组行信息(备用)
|
groupRow: row
|
});
|
} else {
|
ElMessage.warning("该分组没有明细数据,无法指定库存");
|
}
|
},
|
|
handleViewDetail(row) {
|
// 查看分组明细的详细信息
|
if (row.originalDetails && row.originalDetails.length > 0) {
|
// 这里可以打开一个新的弹窗显示所有明细,或者使用第一个明细
|
this.$refs.selectedStock.open(row.originalDetails[0]);
|
}
|
},
|
|
lockstocks() {
|
if (this.selection.length === 0) {
|
return this.$message.error("请选择单据明细");
|
}
|
|
// 获取所有选中分组的原始明细ID
|
const detailIds = [];
|
this.selection.forEach(group => {
|
if (group.originalDetails && group.originalDetails.length > 0) {
|
group.originalDetails.forEach(detail => {
|
if (detail.id) {
|
detailIds.push(detail.id);
|
}
|
});
|
}
|
});
|
|
if (detailIds.length === 0) {
|
return this.$message.error("没有找到可锁定的明细");
|
}
|
|
this.http
|
.post("api/OutboundOrderDetail/LockOutboundStocks", detailIds, "数据处理中")
|
.then((x) => {
|
if (!x.status) return this.$message.error(x.message);
|
this.$message.success("操作成功");
|
this.showDetialBox = false;
|
this.$emit("parentCall", ($vue) => {
|
$vue.getData();
|
});
|
});
|
},
|
|
handleOpenPicking() {
|
this.$router.push({
|
path: "/outbound/picking",
|
query: { orderId: this.row.id, orderNo: this.row.orderNo },
|
});
|
},
|
|
handleOpenBatchPicking() {
|
this.$router.push({
|
path: "/outbound/batchpicking",
|
query: { orderId: this.row.id, orderNo: this.row.orderNo },
|
});
|
},
|
|
outbound() {
|
if (this.selection.length === 0) {
|
return this.$message.error("请选择单据明细");
|
}
|
|
// 获取所有选中分组的原始明细ID
|
const detailIds = [];
|
this.selection.forEach(group => {
|
if (group.originalDetails && group.originalDetails.length > 0) {
|
group.originalDetails.forEach(detail => {
|
if (detail.id) {
|
detailIds.push(detail.id);
|
}
|
});
|
}
|
});
|
|
if (detailIds.length === 0) {
|
return this.$message.error("没有找到可出库的明细");
|
}
|
|
const platformOptions = [
|
{ label: "站台2", value: "2-1" },
|
{ label: "站台3", value: "3-1" },
|
];
|
const mountNode = document.createElement("div");
|
document.body.appendChild(mountNode);
|
|
const formData = reactive({
|
selectedPlatform: platformOptions[0].value,
|
});
|
|
const vnode = createVNode(
|
ElDialog,
|
{
|
title: "出库操作 - 选择出库站台",
|
width: "500px",
|
modelValue: true,
|
appendToBody: true,
|
"onUpdate:modelValue": (isVisible) => {
|
if (!isVisible) {
|
render(null, mountNode);
|
document.body.removeChild(mountNode);
|
}
|
},
|
style: {
|
padding: "20px 0",
|
borderRadius: "8px",
|
},
|
},
|
{
|
default: () =>
|
h(
|
ElForm,
|
{
|
model: formData,
|
rules: {
|
selectedPlatform: [
|
{ required: true, message: "请选择出库站台", trigger: "change" },
|
],
|
},
|
ref: "outboundForm",
|
labelWidth: "100px",
|
style: {
|
padding: "0 30px",
|
},
|
},
|
[
|
h(ElFormItem, {
|
label: "出库站台",
|
prop: "selectedPlatform",
|
style: {
|
marginBottom: "24px",
|
},
|
}, [
|
h(ElSelect, {
|
placeholder: "请选择出库站台",
|
modelValue: formData.selectedPlatform,
|
"onUpdate:modelValue": (val) => {
|
formData.selectedPlatform = val;
|
},
|
style: {
|
width: "100%",
|
height: "40px",
|
borderRadius: "4px",
|
borderColor: "#dcdfe6",
|
},
|
}, platformOptions.map((platform) =>
|
h(ElOption, { label: platform.label, value: platform.value })
|
)),
|
]),
|
h("div", {
|
style: {
|
textAlign: "right",
|
marginTop: "8px",
|
paddingRight: "4px",
|
},
|
}, [
|
h(ElButton, {
|
type: "text",
|
onClick: () => {
|
render(null, mountNode);
|
document.body.removeChild(mountNode);
|
ElMessage.info("取消出库操作");
|
},
|
style: {
|
marginRight: "8px",
|
color: "#606266",
|
},
|
}, "取消"),
|
h(ElButton, {
|
type: "primary",
|
onClick: async () => {
|
const formRef = vnode.component.refs.outboundForm;
|
try {
|
await formRef.validate();
|
} catch (err) {
|
return;
|
}
|
|
const requestParams = {
|
taskIds: detailIds,
|
outboundPlatform: formData.selectedPlatform,
|
};
|
|
this.http
|
.post(
|
"api/Task/GenerateOutboundTasks",
|
requestParams,
|
"数据处理中"
|
)
|
.then((x) => {
|
if (!x.status) return ElMessage.error(x.message);
|
ElMessage.success("操作成功");
|
this.showDetialBox = false;
|
this.$emit("parentCall", ($vue) => {
|
$vue.getData();
|
});
|
render(null, mountNode);
|
document.body.removeChild(mountNode);
|
})
|
.catch(() => {
|
ElMessage.error("请求失败,请稍后重试");
|
});
|
},
|
style: {
|
borderRadius: "4px",
|
padding: "8px 20px",
|
},
|
}, "确定出库"),
|
]),
|
]),
|
}
|
);
|
|
vnode.appContext = this.$.appContext;
|
render(vnode, mountNode);
|
},
|
|
outboundbatch() {
|
if (this.selection.length === 0) {
|
return this.$message.error("请选择单据明细");
|
}
|
if (this.selection.length > 1) {
|
return this.$message.error("只能选择一条单据明细进行分批出库");
|
}
|
|
const selectedGroup = this.selection[0];
|
if (!selectedGroup.originalDetails || selectedGroup.originalDetails.length === 0) {
|
return this.$message.error("没有找到明细数据");
|
}
|
|
// 分批出库通常针对单个明细
|
// 这里使用第一个明细
|
const selectedDetail = selectedGroup.originalDetails[0];
|
|
const platformOptions = [
|
{ label: "站台2", value: "2-1" },
|
{ label: "站台3", value: "3-1" },
|
];
|
const mountNode = document.createElement("div");
|
document.body.appendChild(mountNode);
|
|
const formData = reactive({
|
selectedPlatform: platformOptions[0].value,
|
outboundDecimal: "",
|
});
|
|
const vnode = createVNode(
|
ElDialog,
|
{
|
title: "出库操作 - 选择出库站台",
|
width: "500px",
|
modelValue: true,
|
appendToBody: true,
|
"onUpdate:modelValue": (isVisible) => {
|
if (!isVisible) {
|
render(null, mountNode);
|
document.body.removeChild(mountNode);
|
}
|
},
|
style: {
|
padding: "20px 0",
|
borderRadius: "8px",
|
},
|
},
|
{
|
default: () =>
|
h(
|
ElForm,
|
{
|
model: formData,
|
rules: {
|
selectedPlatform: [
|
{ required: true, message: "请选择出库站台", trigger: "change" },
|
],
|
outboundDecimal: [
|
{ required: true, message: "请输入小数数值", trigger: "blur" },
|
{
|
validator: (rule, value, callback) => {
|
const decimalReg = /^(([1-9]\d*)|0)(\.\d{1,2})?$/;
|
if (value && !decimalReg.test(value)) {
|
callback(new Error("请输入有效的小数(正数,最多2位小数)"));
|
} else {
|
callback();
|
}
|
},
|
trigger: "blur",
|
},
|
],
|
},
|
ref: "outboundForm",
|
labelWidth: "100px",
|
style: {
|
padding: "0 30px",
|
},
|
},
|
[
|
h(ElFormItem, {
|
label: "出库站台",
|
prop: "selectedPlatform",
|
style: {
|
marginBottom: "24px",
|
},
|
}, [
|
h(ElSelect, {
|
placeholder: "请选择出库站台",
|
modelValue: formData.selectedPlatform,
|
"onUpdate:modelValue": (val) => {
|
formData.selectedPlatform = val;
|
},
|
style: {
|
width: "100%",
|
height: "40px",
|
borderRadius: "4px",
|
borderColor: "#dcdfe6",
|
},
|
}, platformOptions.map((platform) =>
|
h(ElOption, { label: platform.label, value: platform.value })
|
)),
|
]),
|
h(ElFormItem, {
|
label: "出库数",
|
prop: "outboundDecimal",
|
style: {
|
marginBottom: "24px",
|
},
|
}, [
|
h(ElInput, {
|
type: "number",
|
placeholder: "请输入小数数值(最多2位小数)",
|
modelValue: formData.outboundDecimal,
|
"onUpdate:modelValue": (val) => {
|
formData.outboundDecimal = val;
|
},
|
style: {
|
width: "100%",
|
height: "40px",
|
borderRadius: "4px",
|
borderColor: "#dcdfe6",
|
},
|
step: "0.01",
|
precision: 2,
|
min: 0.01,
|
max: selectedDetail.orderQuantity - selectedDetail.overOutQuantity - selectedDetail.lockQuantity - selectedDetail.moveQty,
|
}),
|
]),
|
h("div", {
|
style: {
|
textAlign: "right",
|
marginTop: "8px",
|
paddingRight: "4px",
|
},
|
}, [
|
h(ElButton, {
|
type: "text",
|
onClick: () => {
|
render(null, mountNode);
|
document.body.removeChild(mountNode);
|
ElMessage.info("取消分批出库操作");
|
},
|
style: {
|
marginRight: "8px",
|
color: "#606266",
|
},
|
}, "取消"),
|
h(ElButton, {
|
type: "primary",
|
onClick: async () => {
|
const formRef = vnode.component.refs.outboundForm;
|
try {
|
await formRef.validate();
|
} catch (err) {
|
return;
|
}
|
|
const requestParams = {
|
orderDetailId: selectedDetail.id,
|
outboundPlatform: formData.selectedPlatform,
|
batchQuantity: formData.outboundDecimal,
|
};
|
|
this.http
|
.post(
|
"api/Task/GenerateOutboundBatchTasks",
|
requestParams,
|
"数据处理中"
|
)
|
.then((x) => {
|
if (!x.status) return ElMessage.error(x.message);
|
ElMessage.success("操作成功");
|
this.showDetialBox = false;
|
this.$emit("parentCall", ($vue) => {
|
$vue.getData();
|
});
|
render(null, mountNode);
|
document.body.removeChild(mountNode);
|
})
|
.catch(() => {
|
ElMessage.error("请求失败,请稍后重试");
|
});
|
},
|
style: {
|
borderRadius: "4px",
|
padding: "8px 20px",
|
},
|
}, "确定分批出库"),
|
]),
|
]),
|
}
|
);
|
|
vnode.appContext = this.$appContext;
|
render(vnode, mountNode);
|
},
|
|
setCurrent(row) {
|
this.$refs.singleTable.setCurrentRow(row);
|
},
|
|
handleCurrentChange(val) {
|
this.currentRow = val;
|
},
|
|
getButtonEnable(propName, row) {
|
if (propName == "assignStock") {
|
if (
|
row.orderDetailStatus !== 0 &&
|
row.orderDetailStatus !== 70
|
) {
|
return false;
|
} else {
|
return true;
|
}
|
}
|
return false;
|
},
|
|
parentCall(fun) {
|
if (typeof fun != "function") {
|
return console.log("扩展组件需要传入一个回调方法才能获取父级Vue对象");
|
}
|
fun(this);
|
},
|
|
handleRowClick(row) {
|
this.$refs.singleTable.toggleRowSelection(row);
|
},
|
|
handleSelectionChange(val) {
|
this.selection = val;
|
},
|
|
getDictionaryData() {
|
if (this.dictionaryList) {
|
return;
|
}
|
var param = ["orderDetailStatusEnum"];
|
this.http
|
.post("api/Sys_Dictionary/GetVueDictionary", param, "查询中")
|
.then((x) => {
|
if (x.length > 0) {
|
this.dictionaryList = x;
|
}
|
});
|
},
|
|
getDictionaryForGroup(row) {
|
if (this.dictionaryList) {
|
var item = this.dictionaryList.find((x) => x.dicNo == "orderDetailStatusEnum");
|
if (item) {
|
var dicItem = item.data.find((x) => x.key == row.orderDetailStatus);
|
if (dicItem) {
|
return dicItem.value;
|
} else {
|
return row.orderDetailStatus;
|
}
|
} else {
|
return row.orderDetailStatus;
|
}
|
}
|
return row.orderDetailStatus;
|
},
|
},
|
};
|
</script>
|
|
<style scoped>
|
.text-button {
|
border: 0px;
|
}
|
</style>
|
|
<style>
|
.text-button:hover {
|
background-color: #f0f9eb !important;
|
}
|
|
.el-table .warning-row {
|
background: oldlace;
|
}
|
|
.box-table .el-table tbody tr:hover > td {
|
background-color: #d8e0d4 !important;
|
}
|
|
.box-table .el-table tbody tr.current-row > td {
|
background-color: #f0f9eb !important;
|
}
|
|
.el-table .success-row {
|
background: #f0f9eb;
|
}
|
|
.box-table .el-table {
|
border: 1px solid #ebeef5;
|
}
|
</style>
|