根据采购单生成入库单

This commit is contained in:
砂糖
2025-07-19 15:19:00 +08:00
parent f4001e4bab
commit 3a0c9a7684
8 changed files with 621 additions and 14 deletions

View File

@@ -81,3 +81,12 @@ export function updateStockIoStatus(stockIoId, status) {
}
})
}
export function addStockIoWithDetail(data) {
return request({
url: '/wms/stockIo/withDetail',
method: 'post',
data: data
})
}

View File

@@ -0,0 +1,119 @@
<template>
<el-select
v-model="selected"
:placeholder="placeholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
filterable
@change="onChange"
style="width: 100%"
>
<el-option
v-for="item in warehouseOptions"
:key="item.warehouseId"
:label="item.warehouseName"
:value="item.warehouseId"
>
<span :style="{ paddingLeft: item.level * 20 + 'px' }">
{{ item.warehouseName }}
</span>
</el-option>
</el-select>
</template>
<script>
import { listWarehouse } from '@/api/wms/warehouse';
export default {
name: 'WarehouseSelect',
props: {
value: {
type: [Number, String, null],
default: null
},
placeholder: {
type: String,
default: '请选择仓库'
},
clearable: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'mini'
},
showTop: {
type: Boolean,
default: false // 是否显示顶级节点
}
},
data() {
return {
warehouseOptions: [],
selected: this.value
};
},
watch: {
value(val) {
this.selected = val;
}
},
mounted() {
this.loadOptions();
},
methods: {
loadOptions() {
listWarehouse().then(response => {
console.log('仓库API返回数据:', response);
const data = response.data || [];
console.log('处理后的数据:', data);
this.warehouseOptions = this.buildTreeOptions(data);
console.log('构建的树形选项:', this.warehouseOptions);
}).catch(error => {
console.error("加载仓库选项失败:", error);
this.warehouseOptions = [];
});
},
buildTreeOptions(data, parentId = null, level = 0) {
const options = [];
data.forEach(item => {
if (item.parentId === parentId) {
const option = {
warehouseId: item.warehouseId,
warehouseName: item.warehouseName,
level: level
};
// 递归构建子节点
const children = this.buildTreeOptions(data, item.warehouseId, level + 1);
if (children.length > 0) {
options.push(option);
options.push(...children);
} else {
options.push(option);
}
}
});
return options;
},
onChange(val) {
this.$emit('input', val);
this.$emit('change', val);
}
}
};
</script>
<style scoped>
.el-select {
width: 100%;
}
</style>

24
klp-ui/src/utils/enums.js Normal file
View File

@@ -0,0 +1,24 @@
// 订单状态
export const EOrderStatus = {
NEW: 0,
PRODUCTIONING: 1,
FINISH: 2,
CANCEL: 3
}
// 采购计划状态
export const EPurchasePlanStatus = {
NEW: 0,
PRUCHASEING: 1,
FINISH: 2,
CANCEL: 3
}
// 采购明细状态
export const EPurchaseDetailStatus = {
NEW: 0,
ONWAY: 1,
ARRIVAL: 2,
REVIEW: 3,
FINISH: 4
}

View File

@@ -116,6 +116,7 @@
type="text"
icon="el-icon-s-operation"
@click="showClac(scope.row)"
v-if="scope.row.orderStatus === EOrderStatus.NEW"
>智能采购单</el-button>
</template>
</el-table-column>
@@ -158,7 +159,7 @@
<!-- 智能采购单弹窗 -->
<el-dialog :title="`智能采购单订单ID${clacOrderId}`" :visible.sync="clacDialogVisible" width="90%" append-to-body>
<clac-panel :orderId="clacOrderId" />
<clac-panel :orderId="clacOrderId" @confirm="handleRecommendConfirm" />
</el-dialog>
</div>
</template>
@@ -167,6 +168,7 @@
import { listOrder, getOrder, delOrder, addOrder, updateOrder } from "@/api/wms/order";
import OrderDetailPanel from './panels/detail.vue';
import ClacPanel from '../purchasePlan/panels/clac.vue';
import { EOrderStatus } from "@/utils/enums";
export default {
name: "Order",
@@ -174,6 +176,8 @@ export default {
dicts: ['order_status'],
data() {
return {
// 订单状态枚举
EOrderStatus,
// 按钮loading
buttonLoading: false,
// 遮罩层
@@ -227,6 +231,13 @@ export default {
this.loading = false;
});
},
/** 推荐采购计划确认 */
handleRecommendConfirm(data) {
console.log('推荐采购计划数据:', data);
this.$modal.msgSuccess("推荐采购计划已生成");
this.clacDialogVisible = false;
this.getList(); // 刷新列表
},
// 取消按钮
cancel() {
this.open = false;

View File

@@ -191,11 +191,6 @@
<el-table-column label="订单编号" align="center" prop="orderCode" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="销售经理" align="center" prop="salesManager" />
<el-table-column label="订单状态" align="center" prop="orderStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.order_status" :value="scope.row.orderStatus"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
</el-table>
@@ -253,6 +248,7 @@ import { listPurchasePlan, getPurchasePlan, delPurchasePlan, addPurchasePlan, up
import { listOrder } from "@/api/wms/order";
import PurchasePlanClac from "./panels/clac.vue";
import PurchasePlanDetail from "./panels/detail.vue";
import { EOrderStatus } from "../../../utils/enums";
export default {
name: "PurchasePlan",
@@ -315,7 +311,6 @@ export default {
orderCode: undefined,
customerName: undefined,
salesManager: undefined,
orderStatus: undefined,
},
// 表单参数
form: {},
@@ -336,7 +331,7 @@ export default {
}
};
},
created() {
mounted() {
this.getList();
},
methods: {
@@ -459,7 +454,7 @@ export default {
/** 获取订单列表 */
getOrderList() {
this.orderLoading = true;
listOrder(this.orderQueryParams).then(response => {
listOrder({...this.orderQueryParams, orderStatus: EOrderStatus.NEW}).then(response => {
this.orderList = response.rows;
this.orderTotal = response.total;
this.orderLoading = false;

View File

@@ -72,6 +72,14 @@
<script>
import { recommendPurchasePlan, createPurchasePlan } from '@/api/wms/purchasePlan'
import RawMaterialSelect from '@/components/KLPService/RawMaterialSelect'
import { updateOrder } from '@/api/wms/order'
const EOrderStatus = {
NEW: 0,
PRODUCTIONING: 1,
FINISH: 2,
CANCEL: 3
}
export default {
name: 'PurchasePlanClac',
@@ -158,7 +166,7 @@ export default {
this.submitLoading = true;
// 校验主数据表单
this.$refs.mainFormRef.validate((valid) => {
this.$refs.mainFormRef.validate(async(valid) => {
if (!valid) {
this.$message.error('请完整填写主数据信息');
this.submitLoading = false;
@@ -182,8 +190,12 @@ export default {
orderId: this.orderId,
detailList: filtered
};
createPurchasePlan(submitData);
this.$message.success('操作已确认');
await createPurchasePlan(submitData);
await updateOrder({
orderId: this.orderId,
orderStatus: EOrderStatus.PRODUCTIONING
})
// this.$message.success('操作已确认');
console.log(submitData);
this.$emit('confirm', submitData);
this.submitLoading = false;

View File

@@ -39,10 +39,20 @@
@click="handleExport"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-plus"
size="mini"
:disabled="!hasArrivalItems"
@click="handleCreateStockIn"
>创建入库单</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="purchasePlanDetailList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" :data="purchasePlanDetailList" @selection-change="handleSelectionChange" ref="purchasePlanDetailTable">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="明细ID" align="center" prop="detailId" v-if="true"/>
<el-table-column label="采购计划ID" align="center" prop="planId" />
@@ -50,6 +60,11 @@
<el-table-column label="负责人" align="center" prop="owner" />
<el-table-column label="计划采购数量" align="center" prop="quantity" />
<el-table-column label="单位" align="center" prop="unit" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.pruchase_detail_status" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
@@ -65,6 +80,35 @@
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
<!-- 状态修改按钮 -->
<el-button
v-if="scope.row.status === EPurchaseDetailStatus.NEW"
size="mini"
type="text"
icon="el-icon-refresh"
@click="handleStatusChange(scope.row, EPurchaseDetailStatus.ONWAY, '在途')"
>设为在途</el-button>
<el-button
v-if="scope.row.status === EPurchaseDetailStatus.ONWAY"
size="mini"
type="text"
icon="el-icon-refresh"
@click="handleStatusChange(scope.row, EPurchaseDetailStatus.ARRIVAL, '到货')"
>设为到货</el-button>
<el-button
v-if="scope.row.status === EPurchaseDetailStatus.ARRIVAL"
size="mini"
type="text"
icon="el-icon-refresh"
@click="handleStatusChange(scope.row, EPurchaseDetailStatus.REVIEW, '待审核')"
>设为待审核</el-button>
<el-button
v-if="scope.row.status === EPurchaseDetailStatus.REVIEW"
size="mini"
type="text"
icon="el-icon-refresh"
@click="handleStatusChange(scope.row, EPurchaseDetailStatus.FINISH, '采购完成')"
>设为完成</el-button>
</template>
</el-table-column>
</el-table>
@@ -104,22 +148,36 @@
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 入库单创建对话框 -->
<stock-in-dialog
:visible.sync="stockInVisible"
:selected-items="selectedArrivalItems"
@success="handleStockInSuccess"
/>
</div>
</template>
<script>
import { listPurchasePlanDetail, getPurchasePlanDetail, delPurchasePlanDetail, addPurchasePlanDetail, updatePurchasePlanDetail } from "@/api/wms/purchasePlanDetail";
import { EPurchaseDetailStatus } from "@/utils/enums";
import StockInDialog from "./stockin.vue";
export default {
name: "PurchasePlanDetail",
components: {
StockInDialog
},
props: {
planId: {
type: [String, Number],
default: null
}
},
dicts: ['pruchase_detail_status'],
data() {
return {
EPurchaseDetailStatus,
// 按钮loading
buttonLoading: false,
// 遮罩层
@@ -169,9 +227,18 @@ export default {
unit: [
{ required: true, message: "单位不能为空", trigger: "blur" }
],
}
},
// 入库单相关
stockInVisible: false,
selectedArrivalItems: []
};
},
computed: {
/** 是否有到货状态的明细 */
hasArrivalItems() {
return this.purchasePlanDetailList.some(item => item.status === EPurchaseDetailStatus.ARRIVAL);
}
},
created() {
this.getList();
},
@@ -301,6 +368,48 @@ export default {
this.download('klp/purchasePlanDetail/export', {
...this.queryParams
}, `purchasePlanDetail_${new Date().getTime()}.xlsx`)
},
/** 状态修改按钮操作 */
handleStatusChange(row, status, label) {
this.$modal.confirm('是否确认将采购计划明细编号为"' + row.detailId + '"的状态改为"' + label + '"').then(() => {
this.loading = true;
updatePurchasePlanDetail({ detailId: row.detailId, status: status }).then(response => {
this.$modal.msgSuccess("状态修改成功");
this.getList();
}).catch(error => {
console.error(error);
this.$modal.msgError("状态修改失败");
}).finally(() => {
this.loading = false;
});
});
},
/** 创建入库单按钮操作 */
handleCreateStockIn() {
// 获取用户选中的明细
const selectedItems = this.$refs.purchasePlanDetailTable.selection;
if (selectedItems.length === 0) {
this.$modal.msgWarning("请先选择要入库的明细");
return;
}
// 检查选中的明细是否都是到货状态
const nonArrivalItems = selectedItems.filter(item => item.status !== EPurchaseDetailStatus.ARRIVAL);
if (nonArrivalItems.length > 0) {
this.$modal.msgWarning("只能选择到货状态的明细进行入库操作");
return;
}
this.selectedArrivalItems = selectedItems;
this.stockInVisible = true;
},
/** 入库单创建成功后的回调 */
handleStockInSuccess() {
this.$modal.msgSuccess("入库单创建成功,相关明细状态已更新为采购完成");
this.getList();
this.stockInVisible = false;
this.selectedArrivalItems = []; // 清空选中的明细
}
}
};

View File

@@ -0,0 +1,328 @@
<template>
<div class="stockin-container">
<el-dialog
:title="title"
:visible.sync="visible"
width="80%"
:before-close="handleClose"
append-to-body
>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<!-- 入库单基本信息 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="入库单号" prop="stockIoCode">
<el-input v-model="form.stockIoCode" placeholder="系统自动生成" :disabled="true" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入库类型" prop="ioType">
<el-select v-model="form.ioType" placeholder="请选择入库类型" :disabled="true">
<el-option label="入库" value="in" />
<el-option label="出库" value="out" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="业务类型" prop="bizType">
<el-select v-model="form.bizType" placeholder="请选择业务类型" :disabled="true">
<el-option label="采购入库" value="purchase" />
<el-option label="销售退货" value="return" />
<el-option label="调拨入库" value="transfer" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单据状态" prop="status">
<el-select v-model="form.status" placeholder="请选择单据状态" :disabled="true">
<el-option label="草稿" :value="0" />
<el-option label="已提交" :value="1" />
<el-option label="已审核" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</el-form-item>
<!-- 入库明细表格 -->
<el-form-item label="入库明细">
<el-table
:data="form.details"
border
style="width: 100%"
>
<el-table-column label="原材料" align="center" prop="rawMaterialName" />
<el-table-column label="计划数量" align="center" prop="planQuantity" />
<el-table-column label="入库数量" align="center" prop="quantity">
<template slot-scope="scope">
<el-input
v-model.number="scope.row.quantity"
size="mini"
@change="handleQuantityChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="单位" align="center" prop="unit" />
<el-table-column label="仓库" align="center" prop="warehouseId">
<template slot-scope="scope">
<warehouse-select
v-model="scope.row.warehouseId"
placeholder="请选择仓库"
size="mini"
style="width: 100%"
/>
</template>
</el-table-column>
<el-table-column label="批次号" align="center" prop="batchNo">
<template slot-scope="scope">
<el-input
v-model="scope.row.batchNo"
size="mini"
placeholder="请输入批次号"
/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark">
<template slot-scope="scope">
<el-input
v-model="scope.row.remark"
size="mini"
placeholder="请输入备注"
/>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { addStockIoWithDetail } from "@/api/wms/stockIo";
import { updatePurchasePlanDetail } from "@/api/wms/purchasePlanDetail";
import { getRawMaterial } from "@/api/wms/rawMaterial";
import { EPurchaseDetailStatus } from "@/utils/enums";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
export default {
name: "StockInDialog",
components: {
WarehouseSelect
},
props: {
visible: {
type: Boolean,
default: false
},
selectedItems: {
type: Array,
default: () => []
}
},
data() {
return {
title: "创建入库单",
submitLoading: false,
form: {
stockIoCode: "",
ioType: "in",
bizType: "purchase",
status: 0,
remark: "",
details: []
},
rules: {
remark: [
{ max: 500, message: "备注长度不能超过500个字符", trigger: "blur" }
]
}
};
},
watch: {
visible(newVal) {
if (newVal) {
this.initForm();
}
},
// selectedItems: {
// handler(newVal) {
// if (newVal && newVal.length > 0 && this.visible) {
// this.initDetails();
// }
// },
// immediate: false
// }
},
methods: {
/** 初始化表单 */
initForm() {
this.form = {
stockIoCode: `IN-${new Date().getTime()}`,
ioType: "in",
bizType: "purchase",
status: 0,
remark: `采购计划入库单 - ${new Date().toLocaleDateString()}`,
details: []
};
// 如果已经有选中的明细,则初始化明细
if (this.selectedItems && this.selectedItems.length > 0) {
this.initDetails();
}
},
/** 初始化明细数据 */
async initDetails() {
if (this.selectedItems && this.selectedItems.length > 0) {
this.form.details = [];
// 逐个获取原材料信息
for (const item of this.selectedItems) {
try {
const rawMaterialInfo = await getRawMaterial(item.rawMaterialId);
this.form.details.push({
detailId: item.detailId,
rawMaterialId: item.rawMaterialId,
rawMaterialName: rawMaterialInfo.data.rawMaterialName || `原材料${item.rawMaterialId}`,
planQuantity: item.quantity,
quantity: item.quantity, // 默认入库数量等于计划数量
unit: item.unit,
warehouseId: null, // 默认仓库ID
batchNo: `BATCH-${new Date().getTime()}-${item.rawMaterialId}`,
remark: "",
itemType: "raw_material"
});
} catch (error) {
console.error(`获取原材料信息失败: ${item.rawMaterialId}`, error);
// 如果获取失败,使用默认名称
this.form.details.push({
detailId: item.detailId,
rawMaterialId: item.rawMaterialId,
rawMaterialName: `原材料${item.rawMaterialId}`,
planQuantity: item.quantity,
quantity: item.quantity,
unit: item.unit,
warehouseId: null,
batchNo: `BATCH-${new Date().getTime()}-${item.rawMaterialId}`,
remark: "",
itemType: "raw_material"
});
}
}
}
},
/** 处理数量变化 */
handleQuantityChange(row) {
// 验证入库数量不能超过计划数量
if (row.quantity > row.planQuantity) {
this.$message.warning(`入库数量不能超过计划数量 ${row.planQuantity}`);
row.quantity = row.planQuantity;
}
if (row.quantity < 0) {
this.$message.warning("入库数量不能为负数");
row.quantity = 0;
}
},
/** 关闭对话框 */
handleClose() {
this.$confirm("确认关闭?未保存的数据将丢失", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$emit("update:visible", false);
}).catch(() => {});
},
/** 提交表单 */
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
// 验证是否有明细
if (this.form.details.length === 0) {
this.$message.warning("没有可入库的明细");
return;
}
// 验证必填字段
const invalidDetails = this.form.details.filter(detail =>
!detail.warehouseId || !detail.batchNo || detail.quantity <= 0
);
if (invalidDetails.length > 0) {
this.$message.warning("请完善明细的仓库、批次号和入库数量信息");
return;
}
this.submitLoading = true;
// 构建入库单数据
const stockInData = {
...this.form,
details: this.form.details.map(detail => ({
warehouseId: detail.warehouseId,
itemType: detail.itemType,
itemId: detail.rawMaterialId,
quantity: detail.quantity,
unit: detail.unit,
batchNo: detail.batchNo,
remark: detail.remark
}))
};
// 调用创建入库单API
addStockIoWithDetail(stockInData).then(response => {
// 创建入库单成功后,更新采购明细状态
const updatePromises = this.form.details.map(detail =>
updatePurchasePlanDetail({
detailId: detail.detailId,
status: EPurchaseDetailStatus.FINISH
})
);
Promise.all(updatePromises).then(() => {
this.$modal.msgSuccess("入库单创建成功,相关明细状态已更新为采购完成");
this.$emit("success");
this.$emit("update:visible", false);
}).catch(error => {
console.error(error);
this.$modal.msgError("状态更新失败");
});
}).catch(error => {
console.error(error);
this.$modal.msgError("入库单创建失败");
}).finally(() => {
this.submitLoading = false;
});
}
});
}
}
};
</script>
<style scoped>
.stockin-container {
padding: 20px;
}
.dialog-footer {
text-align: right;
}
</style>