feat(inventory): 新增出库单管理功能

- 实现出库单列表查询、导出功能
- 添加出库单详情查看抽屉
- 支持出库单状态变更操作(退回、确认接收)- 集成出库单编辑表单组件
- 实现出库单搜索条件过滤
- 添加分页组件支持数据分页展示
dev_1.0.0
chenhao 2025-11-18 09:10:19 +08:00
parent a3e2a95fc6
commit 1b3b23a63e
12 changed files with 1663 additions and 11 deletions

View File

@ -52,10 +52,10 @@ export function exportVendor(query) {
})
}
// 获取仓库列表
export function listAllWarehouse() {
// 获取所有制造商列表
export function listAllVendor() {
return request({
url: '/warehouse/info/vue/listAll',
url: '/system/vendor/vue/listAll',
method: 'get'
})
}

View File

@ -52,10 +52,10 @@ export function exportProduct(query) {
})
}
// 获取制造商列表
export function listAllVendor() {
return request({
url: '/system/vendor/vue/listAll',
method: 'get'
})
// a new function that queries a product by its code
export function getProductByCode(code) {
return request({
url: '/system/product/vue/query/' + code,
method: 'get'
})
}

View File

@ -0,0 +1,631 @@
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="88px">
<el-form-item label="入库单号" prop="innerCode">
<el-input v-model="queryParams.innerCode" placeholder="请输入入库单号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="产品编码" prop="productCode">
<el-input v-model="queryParams.productCode" placeholder="请输入产品编码" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="经办人" prop="createByName">
<el-input v-model="queryParams.createByName" placeholder="请输入经办人" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="入库时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" @click="handleAdd" v-hasPermi="['inventory:inner:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" @click="handleExport" v-hasPermi="['inventory:inner:export']"></el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="innerList">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="入库单号" align="center" prop="innerCode" show-overflow-tooltip/>
<el-table-column label="产品编码" align="center" prop="productCode" show-overflow-tooltip/>
<el-table-column label="数量" align="center" prop="quantity" />
<el-table-column label="制造商" align="center" prop="vendorName" show-overflow-tooltip/>
<el-table-column label="入库仓" align="center" prop="warehouseName" show-overflow-tooltip/>
<el-table-column label="经办人" align="center" prop="createByName" />
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip/>
<el-table-column label="入库时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)" v-hasPermi="['inventory:inner:view']"></el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['inventory:inner:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 新增/修改 Dialog -->
<el-dialog :title="title" :visible.sync="open" width="80%" append-to-body :close-on-click-modal="false">
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24"><h3>入库信息</h3></el-col>
<el-col :span="8">
<el-form-item label="入库单号" prop="innerCode">
<el-input v-model="form.innerCode" placeholder="保存后自动生成" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="制造商" prop="vendorCode">
<el-select v-model="form.vendorCode" placeholder="请选择制造商" style="width:100%">
<el-option v-for="item in vendorOptions" :key="item.vendorCode" :label="item.vendorName" :value="item.vendorCode"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入库时间" prop="createTime">
<el-input v-model="form.createTime" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="附件信息">
<el-upload
:action="upload.url"
:headers="upload.headers"
:on-success="handleFileSuccess"
:on-remove="handleFileRemove"
:before-upload="beforeUpload"
:file-list="upload.fileList"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24"><h3>产品信息</h3></el-col>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-plus" @click="handleProductAdd"></el-button>
</el-col>
<el-col :span="1.5">
<el-upload
:action="importApi.url"
:headers="importApi.headers"
:on-success="handleImportSuccess"
:show-file-list="false"
>
<el-button type="warning" plain icon="el-icon-upload2">导入</el-button>
</el-upload>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-download" @click="handleDownloadTemplate"></el-button>
</el-col>
</el-row>
<el-table :data="form.inventoryInfoList">
<el-table-column label="SN码" prop="productSn" />
<el-table-column label="产品编码" prop="productCode" />
<el-table-column label="产品型号" prop="model" />
<el-table-column label="产品描述" prop="productDesc" width="200" show-overflow-tooltip/>
<el-table-column label="入库价(含税)" prop="innerPrice" />
<el-table-column label="仓库" prop="warehouseName" />
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleProductDelete(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 查看详情 Drawer -->
<el-drawer :title="title" :visible.sync="drawerOpen" direction="rtl" size="70%">
<div style="padding: 20px;">
<el-form :model="form" label-width="100px">
<el-row>
<el-col :span="24"><h3>入库信息</h3></el-col>
<el-col :span="12">
<el-form-item label="入库单号:">{{ form.innerCode }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="制造商:">{{ form.vendorName }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入库时间:">{{ parseTime(form.createTime) }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注:">{{ form.remark }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="附件信息:">
<div v-if="form.originalFilename">
<el-link type="primary" @click="handleDownloadFile">{{ form.originalFilename }}</el-link>
</div>
<span v-else></span>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24"><h3>产品信息</h3></el-col>
</el-row>
<el-table :data="form.inventoryInfoList">
<el-table-column label="SN码" prop="productSn" />
<el-table-column label="产品编码" prop="productCode" />
<el-table-column label="产品型号" prop="model" />
<el-table-column label="产品描述" prop="productDesc" width="200" show-overflow-tooltip/>
<el-table-column label="入库价(含税)" prop="innerPrice" />
<el-table-column label="仓库" prop="warehouseName" />
</el-table>
</el-form>
</div>
</el-drawer>
<!-- 添加产品 Dialog -->
<el-dialog title="添加产品" :visible.sync="productOpen" width="60%" append-to-body>
<el-form :model="productQuery" ref="productQueryForm" :inline="true" label-width="88px">
<el-form-item label="产品编码" prop="productCodeQuery">
<el-input v-model="productQuery.productCodeQuery" placeholder="请输入产品编码" clearable @keyup.enter.native="handleProductQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleProductQuery"></el-button>
<el-button icon="el-icon-refresh" @click="resetProductQuery"></el-button>
</el-form-item>
</el-form>
<el-form :model="productForm" ref="productForm" :rules="productRules" label-width="140px">
<el-row>
<el-col :span="12">
<el-form-item label="产品编码" prop="productCode">
<el-input v-model="productForm.productCode" readonly/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品型号" prop="model">
<el-input v-model="productForm.model" readonly/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="产品描述" prop="productDesc">
<el-input v-model="productForm.productDesc" readonly/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入库价(含税)" prop="innerPrice">
<el-input-number v-model="productForm.innerPrice" :precision="2" :step="0.1" :min="0"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数量" prop="quantity">
<el-input-number v-model="productForm.quantity" @change="calcSn" :min="1" :max="3000" step="1"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入库仓" prop="warehouseId">
<el-select v-model="productForm.warehouseId" placeholder="请选择仓库" style="width:100%">
<el-option v-for="item in warehouseOptions" :key="item.id" :label="item.warehouseName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="该批次起始SN码" prop="productSn">
<el-input v-model="productForm.productSn" @input="calcSn" placeholder="只能输入数字和字母"/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="SN码范围">
<span>{{ snRange }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitProductForm"> </el-button>
<el-button @click="cancelProduct"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listInner, getInner, addInner, updateInner, delInner, exportInner, importProductData, downloadTemplate } from "@/api/inventory/inner";
import { listAllVendor } from "@/api/base/vendor";
import { listAllWarehouse } from "@/api/base/warehouse";
import { getProductByCode } from "@/api/system/product";
import { getToken } from "@/utils/auth";
import {exportProject} from "@/api/project/info";
export default {
name: "Inner",
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
innerList: [],
//
title: "",
//
open: false,
//
drawerOpen: false,
//
isView: false,
//
dateRange: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
innerCode: null,
productCode: null,
createByName: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
form: {},
//
rules: {
vendorCode: [
{ required: true, message: "制造商不能为空", trigger: "change" }
],
},
//
vendorOptions: [],
//
warehouseOptions: [],
//
upload: {
url: process.env.VUE_APP_BASE_API + "/common/upload",
headers: { Authorization: "Bearer " + getToken() },
fileList: []
},
//
importApi: {
url: process.env.VUE_APP_BASE_API + "/inventory/inner/vue/importData",
headers: { Authorization: "Bearer " + getToken() },
},
//
productOpen: false,
productQuery: {
productCodeQuery: ''
},
productForm: {},
productRules: {
productCode: [{ required: true, message: "产品编码不能为空", trigger: "blur" }],
model: [{ required: true, message: "产品型号不能为空", trigger: "blur" }],
productDesc: [{ required: true, message: "产品描述不能为空", trigger: "blur" }],
innerPrice: [{ required: true, message: "入库价不能为空", trigger: "blur" }],
quantity: [{ required: true, message: "数量不能为空", trigger: "blur" }],
warehouseId: [{ required: true, message: "入库仓不能为空", trigger: "change" }],
productSn: [{ required: true, message: "起始SN码不能为空", trigger: "blur" }],
},
snRange: ''
};
},
created() {
this.getList();
this.getVendorList();
this.getWarehouseList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
listInner(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.innerList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
//
reset() {
this.form = {
id: null,
innerCode: null,
vendorCode: null,
remark: null,
fileId: null,
inventoryInfoList: []
};
this.upload.fileList = [];
this.resetForm("form");
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.isView = false;
this.title = "添加入库单";
},
/** 查看详情按钮操作 */
handleView(row) {
this.reset();
const id = row.id;
getInner(id).then(response => {
this.form = response.data;
if (this.form.fileId && this.form.originalFilename) {
this.upload.fileList = [{
name: this.form.originalFilename,
url: process.env.VUE_APP_BASE_API + "/common/file/download?id=" + this.form.fileId,
id: this.form.fileId // Add id for potential future use
}];
}
this.drawerOpen = true;
this.title = "查看入库单详情";
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id;
this.$modal.confirm('是否确认删除入库单号为"' + row.innerCode + '"的数据项?').then(function() {
return delInner(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.$modal.confirm('是否确认导出所有数据项?').then(() => {
this.loading = true;
return exportInner(this.queryParams);
}).then(response => {
this.loading = false;
const fileName = response.msg;
window.location.href = process.env.VUE_APP_BASE_API + "/common/download?fileName=" + encodeURIComponent(fileName) + "&delete=" + true;
}).catch(() => {
this.loading = false;
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.inventoryInfoList.length === 0) {
this.$modal.msgWarning("请至少添加一条产品信息");
return;
}
if (this.form.id != null) {
updateInner(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addInner(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
//
cancel() {
this.open = false;
this.drawerOpen = false;
this.reset();
},
/** 获取厂商列表 */
getVendorList() {
listAllVendor().then(res => {
this.vendorOptions = res.data;
})
},
/** 获取仓库列表 */
getWarehouseList() {
listAllWarehouse().then(res => {
this.warehouseOptions = res.data;
})
},
//
handleFileSuccess(res, file, fileList) {
if (res.code === 0) {
this.form.fileId = res.id || res.data.id;
this.form.originalFilename = res.originalFilename || res.data.originalFilename; // Store filename
this.upload.fileList = fileList;
} else {
this.$modal.msgError(res.msg);
}
},
handleFileRemove(file, fileList) {
this.form.fileId = null;
this.form.originalFilename = null; // Clear filename
this.upload.fileList = fileList;
},
beforeUpload(file) {
// Add file type/size validation if needed
return true;
},
handleDownloadFile() {
if (this.form.fileId) {
window.location.href = process.env.VUE_APP_BASE_API + "/common/file/download?id=" + this.form.fileId;
} else {
this.$modal.msgWarning("文件ID不存在无法下载");
}
},
//
handleProductAdd() {
this.resetProductForm();
this.productOpen = true;
},
handleImportSuccess(res, file) {
if (res.code === 0) {
this.form.inventoryInfoList.push(...res.data);
this.$modal.msgSuccess("导入成功");
} else {
this.$modal.msgError(res.msg);
}
},
handleDownloadTemplate() {
downloadTemplate().then(res => {
this.download(res, "入库数据模板.xlsx");
})
},
handleProductDelete(index) {
this.form.inventoryInfoList.splice(index, 1);
},
//
resetProductForm() {
this.productForm = {
productCode: null,
model: null,
productDesc: null,
innerPrice: 0,
quantity: 1,
warehouseId: null,
productSn: null
};
this.snRange = '';
this.resetForm("productForm");
},
handleProductQuery() {
const code = this.productQuery.productCodeQuery;
if (!code) {
this.$modal.msgError("请输入产品编码");
return;
}
getProductByCode(code).then(res => {
if (res.data) {
this.productForm.productCode = res.data.productCode;
this.productForm.model = res.data.model;
this.productForm.productDesc = res.data.description;
} else {
this.$modal.msgError("未查询到该产品");
}
});
},
resetProductQuery() {
this.productQuery.productCodeQuery = '';
this.resetProductForm();
},
calcSn() {
let quantity = this.productForm.quantity;
let productSn = this.productForm.productSn;
if (!quantity || !productSn) {
this.snRange = '';
return;
}
quantity = Number(quantity);
productSn = productSn.replace(/[^a-zA-Z0-9]/g, '');
this.productForm.productSn = productSn;
let snPrefix = "";
let snNumber = "";
for (let i = productSn.length - 1; i >= 0; i--) {
if (!isNaN(productSn[i]) && productSn[i] !== ' ') {
snNumber = productSn[i] + snNumber;
} else {
snPrefix = productSn.substring(0, i + 1);
break;
}
}
if (snNumber.length === 0) {
this.snRange = "SN码最后一位必须为数字";
return;
}
let startNumber = parseInt(snNumber);
let endNumber = startNumber + quantity - 1;
let formattedEndNumber = String(endNumber).padStart(snNumber.length, '0');
let lastSn = snPrefix + formattedEndNumber;
this.snRange = `(${productSn} - ${lastSn})`;
},
submitProductForm() {
this.$refs["productForm"].validate(valid => {
if (valid) {
const warehouse = this.warehouseOptions.find(w => w.id === this.productForm.warehouseId);
const warehouseName = warehouse ? warehouse.warehouseName : '';
let quantity = Number(this.productForm.quantity);
let productSn = this.productForm.productSn;
let snPrefix = "";
let snNumber = "";
for (let i = productSn.length - 1; i >= 0; i--) {
if (!isNaN(productSn[i]) && productSn[i] !== ' ') {
snNumber = productSn[i] + snNumber;
} else {
snPrefix = productSn.substring(0, i + 1);
break;
}
}
if (snNumber.length === 0) {
this.$modal.msgError("SN码最后一位必须为数字");
return;
}
let startNumber = parseInt(snNumber);
let productsToAdd = [];
for (let i = 0; i < quantity; i++) {
let currentNumber = startNumber + i;
let formattedNumber = String(currentNumber).padStart(snNumber.length, '0');
let currentSn = snPrefix + formattedNumber;
productsToAdd.push({
productSn: currentSn,
productCode: this.productForm.productCode,
model: this.productForm.model,
productDesc: this.productForm.productDesc,
innerPrice: this.productForm.innerPrice,
warehouseId: this.productForm.warehouseId,
warehouseName: warehouseName
});
}
this.form.inventoryInfoList.push(...productsToAdd);
this.productOpen = false;
}
});
},
cancelProduct() {
this.productOpen = false;
this.resetProductForm();
}
}
};
</script>

View File

@ -0,0 +1,254 @@
<template>
<el-dialog title="生成发货单" :visible.sync="visible" width="85%" top="5vh" append-to-body @close="handleCancel">
<div class="required-delivery-time">
要求发货时间: {{ requiredDeliveryTime }}
</div>
<el-form :model="form" ref="form" label-width="100px" :rules="rules">
<el-row>
<el-col :span="6">
<el-form-item label="物流单号" prop="logisticsCode">
<el-input v-model="form.logisticsCode" placeholder="请输入物流单号" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="发货方式" prop="deliveryType">
<el-select v-model="form.deliveryType" placeholder="请选择">
<el-option v-for="dict in dict.type.delivery_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="物流公司" prop="logisticsCompany">
<el-select v-model="form.logisticsCompany" placeholder="请选择">
<el-option v-for="dict in dict.type.logistics_company" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
<el-form-item label="发货时间类型" prop="deliveryTimeType">
<el-select v-model="form.deliveryTimeType">
<el-option label="立即发货" value="0"></el-option>
<el-option label="自定义" value="1"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="发货时间" prop="deliveryTime">
<el-date-picker v-model="form.deliveryTime" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" :disabled="form.deliveryTimeType === '0'"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleDownloadTemplate"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-upload2" size="mini" @click="handleImport"></el-button>
</el-col>
<el-col :span="21">
<div style="text-align: right;">已选: {{ selectedSnList.length }} ()</div>
</el-col>
</el-row>
<el-table ref="snTable" :data="snList" @selection-change="handleSelectionChange" height="400px">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="SN码" prop="productSn" />
<el-table-column label="产品编码" prop="productCode" />
<el-table-column label="产品型号" prop="model" />
<el-table-column label="描述" prop="productDesc" />
<el-table-column label="仓库" prop="warehouseName" />
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getSnList"/>
<input type="file" ref="uploadInput" @change="onFileChange" accept=".xls,.xlsx" style="display: none" />
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="handleCancel"> </el-button>
</div>
</el-dialog>
</template>
<script>
import { addDelivery, importSnData, listProductSn } from '@/api/inventory/delivery';
export default {
name: "GenerateDeliveryForm",
dicts: ['delivery_type', 'logistics_company'],
data() {
return {
visible: false,
requiredDeliveryTime: '',
productData: {},
outerData: {},
form: {
logisticsCode: '',
deliveryType: '',
logisticsCompany: '',
deliveryTimeType: '0',
deliveryTime: new Date(),
remark: '',
},
rules: {
logisticsCode: [{ required: true, message: "物流单号不能为空", trigger: "blur" }],
deliveryType: [{ required: true, message: "发货方式不能为空", trigger: "change" }],
},
snList: [],
selectedSnList: [],
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
inventoryStatus: '0',
productCode: undefined,
warehouseId: undefined,
},
isImported: false, // Flag to track if data is from import
};
},
watch: {
'form.deliveryTimeType'(val) {
if (val === '0') {
this.form.deliveryTime = new Date();
}
}
},
methods: {
open(productRow, outerForm) {
this.reset();
this.productData = productRow;
this.outerData = outerForm;
this.requiredDeliveryTime = outerForm.deliveryTime;
this.queryParams.productCode = productRow.productCode;
this.queryParams.warehouseId = productRow.warehouseId;
this.queryParams.pageSize = productRow.quantity - productRow.deliveryGenerateQuantity - productRow.deliveryConfirmQuantity;
this.getSnList();
this.visible = true;
},
getSnList() {
listProductSn(this.queryParams).then(response => {
this.snList = response.rows;
this.total = response.total;
this.$nextTick(() => {
if (this.$refs.snTable) {
this.$refs.snTable.toggleAllSelection();
}
});
});
},
handleSelectionChange(selection) {
if (this.isImported) {
this.$message.error("导入数据不允许取消勾选");
this.$refs.snTable.toggleAllSelection();
return;
}
this.selectedSnList = selection;
},
handleDownloadTemplate() {
this.download('/inventory/outer/vue/importTemplate', {}, `sn_import_template.xlsx`);
},
handleImport() {
this.$refs.uploadInput.click();
},
onFileChange(event) {
const file = event.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
formData.append('productCode', this.productData.productCode);
formData.append('quantity', this.queryParams.pageSize);
importSnData(formData).then(response => {
this.$message.success('导入成功');
this.isImported = true;
this.snList = response.data.productSnDataList;
this.queryParams.warehouseId = response.data.warehouseId;
this.total = this.snList.length;
this.queryParams.pageNum = 1;
this.queryParams.pageSize = this.total;
this.$nextTick(() => {
if (this.$refs.snTable) {
this.$refs.snTable.toggleAllSelection();
this.selectedSnList = this.snList;
}
});
}).catch(() => {
this.$message.error('导入失败');
});
this.$refs.uploadInput.value = ''; // Reset file input
},
submitForm() {
this.$refs.form.validate(valid => {
if (valid) {
if (this.selectedSnList.length === 0) {
this.$message.error("应发货数量应该大于0");
return;
}
const data = {
...this.form,
productSnList: this.selectedSnList.map(item => item.productSn),
productCode: this.productData.productCode,
quantity: this.selectedSnList.length,
warehouseId: this.queryParams.warehouseId,
outerCode: this.outerData.outerCode,
detailId: this.productData.id,
productSnDataList: this.isImported ? this.snList : [],
};
addDelivery(data).then(() => {
this.$message.success('生成发货单成功');
this.visible = false;
this.$emit('success');
});
}
});
},
handleCancel() {
this.visible = false;
},
reset() {
this.form = {
logisticsCode: '',
deliveryType: '',
logisticsCompany: '',
deliveryTimeType: '0',
deliveryTime: new Date(),
remark: '',
};
this.resetForm("form");
this.snList = [];
this.selectedSnList = [];
this.queryParams = {
pageNum: 1,
pageSize: 10,
inventoryStatus: '0',
productCode: undefined,
warehouseId: undefined,
};
this.isImported = false;
}
}
};
</script>
<style scoped>
.required-delivery-time {
font-weight: bold;
font-size: 18px;
text-align: right;
margin-bottom: 15px;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<el-drawer :visible.sync="drawerVisible" title="出库单详情" direction="rtl" size="60%">
<div style="padding: 20px;">
<el-descriptions :column="2" border>
<el-descriptions-item label="出库单号">{{ details.outerCode }}</el-descriptions-item>
<el-descriptions-item label="项目编号">{{ details.projectCode }}</el-descriptions-item>
<el-descriptions-item label="合同编号">{{ details.orderCode }}</el-descriptions-item>
<el-descriptions-item label="项目名称">{{ details.projectName }}</el-descriptions-item>
<el-descriptions-item label="发货时间">
<dict-tag :options="delivery_time_type" :value="details.deliveryTimeType" />
<span v-if="details.deliveryTimeType === '1'"> - {{ details.deliveryTime }}</span>
</el-descriptions-item>
<el-descriptions-item label="发货状态">
<dict-tag :options="dict.type.outer_delivery_status" :value="details.deliveryStatus" />
</el-descriptions-item> <el-descriptions-item label="">{{ details.contactPerson }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ details.contactPhone }}</el-descriptions-item>
<el-descriptions-item label="联系地址" :span="2">{{ details.contactAddress }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ details.remark }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">产品信息</el-divider>
<el-table :data="details.products" border style="width: 100%">
<el-table-column prop="productCode" label="产品编码" show-overflow-tooltip />
<el-table-column prop="model" label="产品型号" show-overflow-tooltip />
<el-table-column prop="quantity" label="应发货" align="center" />
<el-table-column prop="inventoryQuantity" label="库存" align="center" />
<el-table-column prop="deliveryQuantity" label="本次发货" align="center" />
<el-table-column prop="productRemark" label="备注" show-overflow-tooltip />
</el-table>
<el-divider content-position="left">发货记录</el-divider>
<el-table :data="details.deliveryRecords" border style="width: 100%">
<el-table-column prop="deliveryCode" label="发货单号" show-overflow-tooltip />
<el-table-column prop="logisticsCode" label="物流单号" show-overflow-tooltip />
<el-table-column prop="deliveryTime" label="发货时间" align="center" />
<el-table-column prop="quantity" label="发货数量" align="center" />
<el-table-column prop="createBy" label="创建人" align="center" />
<el-table-column prop="createTime" label="创建时间" align="center" />
</el-table>
</div>
</el-drawer>
</template>
<script>
import { getOuter } from '@/api/inventory/outer';
export default {
name: "OuterDetails",
dicts: ['outer_delivery_status'],
data() {
return {
drawerVisible: false,
details: {},
delivery_time_type: [
{ value: '0', label: '立即发货' },
{ value: '1', label: '自定义' },
],
};
},
methods: {
open(id) {
this.drawerVisible = true;
getOuter(id).then(response => {
this.details = response.data;
});
}
}
};
</script>

View File

@ -0,0 +1,197 @@
<template>
<el-dialog :title="title" :visible.sync="visible" width="80%" append-to-body @close="handleCancel">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<!-- 基础信息 -->
<div class="section-title" style="justify-content: center">基础信息</div>
<el-form :model="form" ref="form" label-width="100px" class="form-horizontal m">
<el-row>
<el-col :span="12">
<el-form-item label="出库单号:">
<el-input :value="form.outerCode" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目编号:">
<el-input :value="form.projectCode" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="合同编号:">
<el-input :value="form.orderCode" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目名称:">
<el-input :value="form.projectName" readonly />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 产品清单 -->
<div class="section-title">产品清单</div>
<el-table :data="productList" border>
<el-table-column label="序号" type="index" align="center" />
<el-table-column label="产品编码" prop="productCode" />
<el-table-column label="产品型号" prop="model" />
<el-table-column label="应发货" prop="quantity" />
<el-table-column label="已确认发货" prop="deliveryConfirmQuantity" />
<el-table-column label="已生成发货" prop="deliveryGenerateQuantity" />
<el-table-column label="实时库存" prop="availableCount" />
<el-table-column label="仓库" prop="warehouseName" />
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button
size="mini"
type="success"
@click="handleDeliver(scope.row)"
:disabled="Number(scope.row.quantity) <= Number(scope.row.deliveryGenerateQuantity) + Number(scope.row.deliveryConfirmQuantity)"
>发货</el-button>
</template>
</el-table-column>
</el-table>
<!-- 已生成发货记录 -->
<div class="section-title">已生成发货记录</div>
<el-table :data="deliveryList" border>
<el-table-column label="出库单号" prop="outerCode" />
<el-table-column label="物流单号" prop="logisticsCode" />
<el-table-column label="仓库" prop="warehouseName" />
<el-table-column label="发货时间" prop="deliveryTime" />
<el-table-column label="发货数量" prop="quantity" />
<el-table-column label="发货人" prop="createByName" />
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<div v-if="scope.row.deliveryStatus === '0'">
<el-button size="mini" type="danger" @click="handleDeleteDelivery(scope.row.id)"></el-button>
<el-button size="mini" type="primary" @click="handleConfirmDelivery(scope.row)"></el-button>
</div>
<div v-else>
<el-button size="mini" type="info" @click="handleViewDelivery(scope.row.id)"></el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 发货弹窗 -->
<generate-delivery-form ref="deliveryForm" @success="refreshTables"></generate-delivery-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel"> </el-button>
</div>
</el-dialog>
</template>
<script>
import { getOuter, queryInfo } from '@/api/inventory/outer';
import { removeDelivery, updateDeliveryStatus, getDelivery } from '@/api/inventory/delivery';
import GenerateDeliveryForm from './GenerateDeliveryForm.vue';
export default {
name: "OuterForm",
components: { GenerateDeliveryForm },
data() {
return {
visible: false,
title: '出库单发货管理',
form: {},
productList: [],
deliveryList: [],
};
},
methods: {
open(id) {
this.reset();
this.visible = true;
if (id) {
getOuter(id).then(response => {
this.form = response.data;
this.productList = response.data.productList || [];
this.deliveryList = response.data.deliveryList || [];
});
}
},
handleCancel() {
this.visible = false;
this.reset();
},
reset() {
this.form = {};
this.productList = [];
this.deliveryList = [];
},
//
refreshTables() {
queryInfo(this.form.id).then(res => {
this.productList = res.data.productList || [];
this.deliveryList = res.data.deliveryList || [];
});
},
//
handleDeliver(row) {
this.$refs.deliveryForm.open(row, this.form);
},
//
handleDeleteDelivery(deliveryId) {
this.$confirm('确定撤销该发货记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
removeDelivery(deliveryId).then(() => {
this.$message.success('撤销成功');
this.refreshTables();
});
}).catch(() => {});
},
//
handleConfirmDelivery(row) {
this.$confirm('请确认SN码的准确性当日24点后无法更改', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const data = {
id: row.id,
outerCode: row.outerCode,
orderCode: row.orderCode,
productCode: row.productCode
};
updateDeliveryStatus(data).then(() => {
this.$message.success('确认成功');
this.refreshTables();
});
}).catch(() => {});
},
//
handleViewDelivery(deliveryId) {
// This could open another component for viewing, for now, we just log it.
// Or maybe reuse the GenerateDeliveryForm in a read-only mode.
console.log("TODO: View delivery details:", deliveryId);
this.$router.push({ path: '/inventory/delivery/view/' + deliveryId });
}
}
};
</script>
<style scoped>
.section-title {
font-weight: bold;
font-size: 16px;
padding: 10px 0;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
}
.form-horizontal .el-form-item {
margin-bottom: 15px;
}
.el-input.is-readonly .el-input__inner {
background-color: #f5f7fa;
border-color: #e4e7ed;
color: #c0c4cc;
cursor: not-allowed;
}
</style>

View File

@ -0,0 +1,157 @@
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="出库单号" prop="outerCode">
<el-input v-model="queryParams.outerCode" placeholder="请输入出库单号" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目编号" prop="projectCode">
<el-input v-model="queryParams.projectCode" placeholder="请输入项目编号" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="合同编号" prop="orderCode">
<el-input v-model="queryParams.orderCode" placeholder="请输入合同编号" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="发货状态" prop="deliveryStatus">
<el-select v-model="queryParams.deliveryStatus" placeholder="请选择发货状态" clearable size="small">
<el-option v-for="dict in dict.type.outer_delivery_status" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<!-- 操作按钮栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['inventory:outer:export']"></el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="outerList">
<el-table-column label="序号" type="index" width="55" align="center"/>
<el-table-column label="出库单号" prop="outerCode" width="180" show-overflow-tooltip/>
<el-table-column label="要求发货时间" align="center" width="180">
<template slot-scope="scope">
<span>{{ scope.row.deliveryTimeType == '0' ? '立即发货' : scope.row.deliveryTime }}</span>
</template>
</el-table-column>
<el-table-column label="项目编号" prop="projectCode" width="150" show-overflow-tooltip/>
<el-table-column label="合同编号" prop="orderCode" width="150" show-overflow-tooltip/>
<el-table-column label="项目名称" prop="projectName" width="200" show-overflow-tooltip/>
<el-table-column label="产品编码" prop="productCode" width="150" show-overflow-tooltip/>
<el-table-column label="产品型号" prop="model" width="150" show-overflow-tooltip/>
<el-table-column label="应发货" prop="quantity" width="100" align="center"/>
<el-table-column label="发货状态" align="center" width="100">
<template slot-scope="scope">
<dict-tag :options="dict.type.outer_delivery_status" :value="scope.row.deliveryStatus" />
</template>
</el-table-column>
<el-table-column label="生成时间" prop="createTime" width="180" align="center"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="220">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
<template v-if="scope.row.outerStatus === '3'">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['inventory:outer:edit']"></el-button>
</template>
<template v-else>
<el-button size="mini" type="text" @click="handleStatusChange(scope.row, '4')">退回</el-button>
<el-button size="mini" type="text" @click="handleStatusChange(scope.row, '3')">确认接收</el-button>
</template>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 新增/编辑弹窗 -->
<outer-form ref="form" @success="getList"></outer-form>
<!-- 详情抽屉 -->
<outer-details ref="details"></outer-details>
</div>
</template>
<script>
import { listOuter, exportOuter, changeOuterStatus } from '@/api/inventory/outer';
import OuterForm from './components/OuterForm.vue';
import OuterDetails from './components/OuterDetails.vue';
export default {
name: "Outer",
components: { OuterForm, OuterDetails },
dicts: ['outer_delivery_status'],
data() {
return {
loading: true,
showSearch: true,
outerList: [],
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
outerCode: undefined,
projectCode: undefined,
orderCode: undefined,
projectName: undefined,
deliveryStatus: undefined,
},
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listOuter(this.queryParams).then(response => {
this.outerList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm('queryForm');
this.handleQuery();
},
handleAdd() {
this.$refs.form.open();
},
handleExport() {
this.download('/inventory/outer/vue/export', {
...this.queryParams
}, `outer_${new Date().getTime()}.xlsx`)
},
handleView(row) {
this.$refs.details.open(row.id);
},
handleUpdate(row) {
this.$refs.form.open(row.id);
},
handleStatusChange(row, status) {
const actionText = status === '3' ? '确认接收' : '退回';
this.$confirm(`确认要"${actionText}"该出库单吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
return changeOuterStatus(row.id, status, row.orderCode);
}).then(() => {
this.$message.success(`${actionText}成功`);
this.getList();
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,105 @@
package com.ruoyi.sip.controller.vue;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.sip.domain.ProjectOrderInfo;
import com.ruoyi.sip.dto.inventory.CheckOutDto;
import com.ruoyi.sip.dto.inventory.ProductWarehouseInfo;
import com.ruoyi.sip.service.IExecutionTrackService;
import com.ruoyi.sip.service.IInventoryAuthService;
import com.ruoyi.sip.service.IInventoryOuterService;
import com.ruoyi.sip.service.IProjectOrderInfoService;
import com.ruoyi.sip.vo.ExecutionOrderVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* Controller (Vue)
*
* @author ruoyi
* @date 2025-11-17
*/
@RestController
@RequestMapping("/inventory/execution/vue")
public class VueInventoryExecutionController extends BaseController
{
@Autowired
private IExecutionTrackService inventoryExecutionService;
@Autowired
private IInventoryOuterService inventoryOuterService;
@Autowired
private IInventoryAuthService inventoryAuthService;
@Autowired
private IProjectOrderInfoService projectOrderInfoService;
/**
*
*/
@RequiresPermissions("inventory:execution:list")
@GetMapping("/list")
public TableDataInfo list(ProjectOrderInfo projectOrderInfo)
{
projectOrderInfo.setOrderStatus(ProjectOrderInfo.OrderStatus.APPROVE_COMPLETE.getCode());
if (!inventoryAuthService.authAll()) {
List<String> productCodeList = inventoryAuthService.authProductCode();
if (CollUtil.isEmpty(productCodeList)) {
return getDataTable(Collections.emptyList());
}
projectOrderInfo.setProductCodeList(productCodeList);
}
startPage();
List<ProjectOrderInfo> list = projectOrderInfoService.selectProjectOrderInfoList(projectOrderInfo);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions("inventory:execution:query")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
HashMap<String, Object> result = new HashMap<>();
ExecutionOrderVo vo = inventoryExecutionService.selectInfo(id);
result.put("projectOrderInfo", vo.getProjectOrderInfo());
result.put("productDetailList", vo.getProductDetailList());
result.put("inventoryOuterList", vo.getInventoryOuterList());
return success(result);
}
/**
*
*/
@RequiresPermissions("inventory:execution:recall")
@PostMapping("/recall")
@Log(title = "执行单撤单", businessType = BusinessType.DELETE)
public AjaxResult recall(@RequestBody Long id)
{
inventoryExecutionService.recall(id);
return AjaxResult.success();
}
/**
*
*/
@RequiresPermissions("inventory:execution:edit")
@PostMapping("/checkOut/preview")
public AjaxResult checkOutPreview(@RequestBody CheckOutDto dto)
{
List<ProductWarehouseInfo> list = inventoryExecutionService.checkOutPreview(dto.getProductCode(),dto.getQuantity());
return success(list);
}
}

View File

@ -0,0 +1,88 @@
package com.ruoyi.sip.controller.vue;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.sip.domain.InventoryOuter;
import com.ruoyi.sip.service.IInventoryAuthService;
import com.ruoyi.sip.service.IInventoryOuterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Controller (Vue)
*
* @author ruoyi
* @date 2025-11-17
*/
@RestController
@RequestMapping("/inventory/outer/vue")
public class VueInventoryOuterController extends BaseController
{
@Autowired
private IInventoryOuterService inventoryOuterService;
@Autowired
private IInventoryAuthService inventoryAuthService;
@GetMapping()
@ResponseBody
public TableDataInfo list(InventoryOuter inventoryOuter)
{
inventoryOuter.setOuterStatusList(Arrays.asList(InventoryOuter.OuterStatusEnum.WAIT_RECEIVE.getCode(), InventoryOuter.OuterStatusEnum.RECEIVED.getCode()));
if (!inventoryAuthService.authAll()){
List<String> productCodeList = inventoryAuthService.authProductCode();
if (CollUtil.isEmpty(productCodeList)){
return getDataTable(Collections.emptyList());
}
inventoryOuter.setProductCodeList(productCodeList);
}
startPage();
List<InventoryOuter> list = inventoryOuterService.selectInventoryOuterList(inventoryOuter);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions("inventory:outer:add")
@PostMapping
public AjaxResult add(@RequestBody InventoryOuter inventoryOuter)
{
return toAjax(inventoryOuterService.insertInventoryOuter(inventoryOuter));
}
/**
*
*/
@RequiresPermissions("inventory:outer:remove")
@DeleteMapping("/{id}")
public AjaxResult remove(@PathVariable Long id)
{
return toAjax(inventoryOuterService.deleteInventoryOuterById(id));
}
/**
* ()
*/
@RequiresPermissions("inventory:outer:edit")
@PostMapping("/status")
public AjaxResult updateStatus(@RequestBody InventoryOuter inventoryOuter)
{
return toAjax(inventoryOuterService.updateInventoryOuter(inventoryOuter));
}
/**
*
*/
@RequiresPermissions("@inventory:outer:query")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(inventoryOuterService.selectInventoryOuterById(id));
}
}

View File

@ -0,0 +1,122 @@
package com.ruoyi.sip.controller.vue;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.sip.domain.OmsInventoryInner;
import com.ruoyi.sip.dto.warehouse.WarehouseInnerExcelDto;
import com.ruoyi.sip.service.IInventoryInfoService;
import com.ruoyi.sip.service.IOmsInventoryInnerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* (Vue) Controller
*
* @author ruoyi
* @date 2025-08-06
*/
@RestController
@RequestMapping("/inventory/inner/vue")
public class VueOmsInventoryInnerController extends BaseController {
@Autowired
private IOmsInventoryInnerService omsInventoryInnerService;
@Autowired
private IInventoryInfoService inventoryInfoService;
/**
*
*/
@GetMapping("/list")
public TableDataInfo list(OmsInventoryInner omsInventoryInner) {
startPage();
List<OmsInventoryInner> list = omsInventoryInnerService.selectOmsInventoryInnerList(omsInventoryInner);
return getDataTable(list);
}
/**
*
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(omsInventoryInnerService.selectOmsInventoryInnerById(id));
}
/**
*
*/
@Log(title = "入库单信息", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody OmsInventoryInner omsInventoryInner) {
return toAjax(omsInventoryInnerService.insertOmsInventoryInner(omsInventoryInner));
}
/**
*
*/
@Log(title = "入库单信息", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody OmsInventoryInner omsInventoryInner) {
return toAjax(omsInventoryInnerService.updateOmsInventoryInner(omsInventoryInner));
}
/**
*
*/
@Log(title = "入库单信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String ids) {
return toAjax(omsInventoryInnerService.deleteOmsInventoryInnerByIds(ids));
}
/**
*
*/
@Log(title = "入库单信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public AjaxResult export(@RequestBody OmsInventoryInner omsInventoryInner) {
List<OmsInventoryInner> list = omsInventoryInnerService.selectOmsInventoryInnerList(omsInventoryInner);
ExcelUtil<OmsInventoryInner> util = new ExcelUtil<>(OmsInventoryInner.class);
return util.exportExcel(list, "入库单信息数据");
}
/**
* Excel
*/
@PostMapping("/export/template")
public AjaxResult exportTemplate() {
ExcelUtil<WarehouseInnerExcelDto> util = new ExcelUtil<>(WarehouseInnerExcelDto.class);
return util.exportExcel(Collections.emptyList(), "入库数据模板");
}
/**
*
*/
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file) throws IOException {
if (file == null || file.isEmpty()) {
return AjaxResult.error("上传文件不能为空");
}
ExcelUtil<WarehouseInnerExcelDto> util = new ExcelUtil<>(WarehouseInnerExcelDto.class);
try {
List<WarehouseInnerExcelDto> excelList = util.importExcel(file.getInputStream());
return AjaxResult.success(inventoryInfoService.fillBaseInfo(excelList));
} catch (ServiceException e) {
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
logger.error("导入产品数据失败", e);
return AjaxResult.error("导入失败,请检查文件格式或联系管理员");
}
}
}

View File

@ -7,11 +7,13 @@ import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.sip.domain.ProductInfo;
import com.ruoyi.sip.service.IInventoryAuthService;
import com.ruoyi.sip.service.IProductInfoService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.List;
/**
@ -26,7 +28,8 @@ public class VueProductInfoController extends BaseController {
@Autowired
private IProductInfoService productInfoService;
@Autowired
private IInventoryAuthService inventoryAuthService;
/**
*
*/
@ -77,6 +80,25 @@ public class VueProductInfoController extends BaseController {
return toAjax(productInfoService.deleteProductInfoByIds(ids));
}
/**
*
*/
@GetMapping("/query/{productCode}")
public AjaxResult queryByCode(@PathVariable("productCode") String productCode) {
if (!inventoryAuthService.authAll()) {
List<String> authProductCode = inventoryAuthService.authProductCode();
if (!authProductCode.contains(productCode)) {
return AjaxResult.error("无产品权限");
}
}
List<ProductInfo> list = productInfoService.selectProductInfoByCodeList(Collections.singletonList(productCode));
if (list == null || list.isEmpty()) {
return AjaxResult.error("未找到该产品");
}
return AjaxResult.success(list.get(0));
}
/**
*
*/

View File

@ -52,7 +52,14 @@ public class OmsInventoryInnerServiceImpl implements IOmsInventoryInnerService {
*/
@Override
public OmsInventoryInner selectOmsInventoryInnerById(Long id) {
return omsInventoryInnerMapper.selectOmsInventoryInnerById(id);
OmsInventoryInner omsInventoryInner = omsInventoryInnerMapper.selectOmsInventoryInnerById(id);
if (omsInventoryInner != null && StringUtils.isNotEmpty(omsInventoryInner.getInnerCode())) {
InventoryInfo queryParams = new InventoryInfo();
queryParams.setInnerCode(omsInventoryInner.getInnerCode());
List<InventoryInfo> inventoryInfos = inventoryInfoService.selectInventoryInfoList(queryParams);
omsInventoryInner.setInventoryInfoList(inventoryInfos);
}
return omsInventoryInner;
}
/**