feat(inventory): 增强入库管理功能并优化采购订单选择流程

- 在 GenerateDeliveryForm 中增加采购订单选择对话框,支持预填充仓库、价格和税率信息
- 修改库存内部页面,新增产品类型筛选和仓库类型筛选功能
- 调整表单校验逻辑,确保采购数量与实际入库数量一致
- 优化库存信息导入逻辑,移除对仓库名称的强制校验
- 更新后端接口,支持传递 purchaseNo 和 itemId 参数用于关联采购订单
- 修复供应商确认状态更新后未关闭详情抽屉的问题
- 调整发货记录操作列宽度以改善显示效果
dev_1.0.0
chenhao 2025-12-01 16:18:56 +08:00
parent 9dc06d049c
commit 43e2225c3d
14 changed files with 137 additions and 36 deletions

View File

@ -67,7 +67,7 @@
<span>{{ parseTime(scope.row.deliveryTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-document" @click="handleView(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-refresh-left" @click="handleRecall(scope.row)"

View File

@ -7,15 +7,38 @@
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="88px">
<el-form-item label="产品类型" prop="productType">
<el-select v-model="queryParams.productType" clearable placeholder="请选择产品类型">
<el-option v-for="dict in dict.type.product_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<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="purchaseNo">
<el-input v-model="queryParams.purchaseNo" placeholder="请输入采购单号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item v-if="activeTab === 'service'" label="合同编号" prop="orderCode">
<el-form-item v-show="activeTab === 'service'" label="合同编号" prop="orderCode">
<el-input v-model="queryParams.orderCode" placeholder="请输入合同编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item v-show="activeTab === 'order'" label="入库类型" prop="warehouseType">
<el-select v-model="queryParams.warehouseType" placeholder="请选择产品类型">
<el-option v-for="dict in dict.type.warehouse_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item v-show="activeTab === 'order'" label="入库仓" prop="warehouseId">
<el-select v-model="queryParams.warehouseId" placeholder="请选择产品类型">
<el-option v-for="dict in warehouseOptions" :key="dict.id" :label="dict.warehouseName" :value="dict.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="制造商" prop="vendorCode">
<el-select v-model="queryParams.vendorCode" placeholder="请选择产品类型">
<el-option v-for="dict in vendorOptions" :key="dict.vendorCode" :label="dict.vendorName" :value="dict.vendorCode"></el-option>
</el-select>
</el-form-item>
<el-form-item label="产品编码" prop="productCode">
<el-input v-model="queryParams.productCode" placeholder="请输入产品编码" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
@ -105,7 +128,7 @@
<!-- 新增/修改 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-form ref="form" :model="form" :rules="rules" label-width="100px" style="max-height: 70vh;overflow-y: auto">
<el-row>
<el-col :span="24"><h3>入库信息</h3></el-col>
<el-col :span="12">
@ -516,6 +539,10 @@ export default {
this.$modal.msgWarning("请至少添加一条产品信息");
return;
}
if (this.form.quantity!==this.form.inventoryInfoList.length){
this.$modal.msgWarning("采购数量与入库数量不一致");
return;
}
if (this.form.id != null) {
updateInner(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
@ -569,6 +596,11 @@ export default {
handleImportSuccess(res, file) {
if (res.code === 0) {
this.form.inventoryInfoList.push(...res.data);
this.form.inventoryInfoList.forEach(item=>{
item.warehouseId=this.form.warehouseId;
item.warehouseName=this.form.warehouseName;
item.innerPrice=this.form.price;
})
this.$modal.msgSuccess("导入成功");
} else {
this.$modal.msgError(res.msg);

View File

@ -52,7 +52,7 @@
<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-button type="info" plain icon="el-icon-upload2" size="mini" @click="handleSelectPurchaseBeforeImport"></el-button>
</el-col>
<el-col :span="21">
<div style="text-align: right;">已选: {{ selectedSnList.length }} ()</div>
@ -69,6 +69,8 @@
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getSnList"/>
<purchase-order-select-dialog :visible.sync="purchaseOrderSelectVisible" :productCode="queryParams.productCode" :quantity="queryParams.pageSize" :productTypeList="queryProductType" @select="handlePurchaseOrderSelect"/>
<input type="file" ref="uploadInput" @change="onFileChange" accept=".xls,.xlsx" style="display: none" />
<div slot="footer" class="dialog-footer">
@ -81,8 +83,13 @@
<script>
import { addDelivery, listProductSn } from '@/api/inventory/delivery';
import {importSnData,exportDownloadTemplate} from '@/api/inventory/outer'
import PurchaseOrderSelectDialog from '../../../purchaseorder/components/PurchaseOrderSelectDialog.vue';
export default {
name: "GenerateDeliveryForm",
components: {
PurchaseOrderSelectDialog
},
dicts: ['delivery_type', 'logistics_company'],
data() {
return {
@ -113,6 +120,12 @@ export default {
warehouseId: undefined,
},
isImported: false, // Flag to track if data is from import
purchaseOrderSelectVisible: false,
queryProductType: ['1', '2', '99'],
price: null,
warehouseId: null,
taxRate: 0.13,
warehouseName: '',
};
},
watch: {
@ -156,13 +169,23 @@ export default {
this.selectedSnList = selection;
},
handleDownloadTemplate() {
exportDownloadTemplate().then(response => {
const fileName = response.msg;
window.location.href = process.env.VUE_APP_BASE_API + "/common/download?fileName=" + encodeURIComponent(fileName) + "&delete=" + true;
}).catch(() => {
});
},
handleSelectPurchaseBeforeImport() {
this.purchaseOrderSelectVisible = true;
},
handlePurchaseOrderSelect(order) {
this.warehouseId = order.warehouseId;
this.warehouseName = order.warehouseName;
this.price = order.price;
this.taxRate = order.taxRate;
this.form.purchaseNo = order.purchaseNo;
this.form.itemId = order.itemId;
this.handleImport();
},
handleImport() {
this.$refs.uploadInput.click();
@ -179,7 +202,14 @@ export default {
importSnData(formData).then(response => {
this.$message.success('导入成功');
this.snList = response.data.productSnDataList;
this.queryParams.warehouseId = response.data.warehouseId;
if (this.warehouseName) {
this.snList.forEach(item => {
item.warehouseName = this.warehouseName;
item.warehouseId = this.warehouseId;
item.innerPrice = this.price;
item.taxRate = this.taxRate;
});
}
this.total = this.snList.length;
this.queryParams.pageNum = 1;
this.queryParams.pageSize = this.total;
@ -245,6 +275,11 @@ export default {
warehouseId: undefined,
};
this.isImported = false;
this.price = null;
this.taxRate = 0.13;
this.warehouseId = null;
this.purchaseNo = '';
this.warehouseName = '';
}
}
};

View File

@ -88,6 +88,14 @@ export default {
type:{
type: String,
default: 'inner'
},
productCode:{
type: String,
default: null
},
quantity:{
type: Number,
default: null
}
},
dicts:['product_type'],
@ -113,7 +121,8 @@ export default {
vendorName: null,
approveStatus: '2', //
confirmStatus:'1',
type: this.type
type: this.type,
productCode: this.productCode
},
};
},
@ -146,6 +155,10 @@ export default {
this.handleQuery();
},
handleSelect(row) {
if (this.quantity && this.quantity!==row.quantity){
this.$message.error("请选择数量为"+this.quantity+"的采购单");
return
}
this.$emit("select", row);
this.handleClose();
},

View File

@ -341,6 +341,7 @@ export default {
confirmStatus: type // 1 ()
};
vendorConfirmStatus(data).then(() => {
this.showDetailDrawer=false;
this.getList();
this.$modal.msgSuccess("操作成功");
});

View File

@ -80,7 +80,9 @@ public class InventoryDelivery extends BaseEntity
private Long detailId;
private String createByName;
private String purchaseNo;
private String remark;
private Long itemId;

View File

@ -58,6 +58,7 @@ public class InventoryInfo extends BaseEntity
/** 入库价 */
@Excel(name = "入库价")
private BigDecimal innerPrice;
private BigDecimal taxRate;
/** 应付账单号 */
private String payableBillCode;
/** 出库价 */

View File

@ -42,5 +42,6 @@ public class OmsPurchaseOrderItemDto extends OmsPurchaseOrder {
private String productType;
private String productModel;
private String productDesc;
private String warehouseName;
private List<String> productTypeList;
}

View File

@ -64,6 +64,6 @@ public interface IOmsInventoryInnerService
public int deleteOmsInventoryInnerById(Long id);
void importByOuter(List<InventoryInfo> inventoryInfoList,String productCode);
void importByOuter(List<InventoryInfo> inventoryInfoList, String productCode, String purchaseNo,Long itemId);
Map<String,Object> getInventoryInfoList(List<InventoryInfoExcelDto> inventoryInfoExcelDtoList, String productCode);
}

View File

@ -130,7 +130,7 @@ public class InventoryDeliveryServiceImpl implements IInventoryDeliveryService {
detail.setProductSn(inventoryInfo.getProductSn());
detailList.add(detail);
}
omsInventoryInnerService.importByOuter(productSnDataList,inventoryDelivery.getProductCode());
omsInventoryInnerService.importByOuter(productSnDataList,inventoryDelivery.getProductCode(),inventoryDelivery.getPurchaseNo(),inventoryDelivery.getItemId());
}
InventoryOuterDetail inventoryOuterDetail = new InventoryOuterDetail();
inventoryOuterDetail.setId(inventoryDelivery.getDetailId());

View File

@ -143,16 +143,16 @@ public class InventoryInfoServiceImpl implements IInventoryInfoService {
inventoryInfo.setInventoryStatus(InventoryInfo.InventoryStatusEnum.INNER.getCode());
inventoryInfo.setProductCode(excel.getProductCode());
inventoryInfo.setProductSn(excel.getProductSn());
if (excel.getInnerPrice()==null){
throw new ServiceException("入库价未填写,导入失败");
}
inventoryInfo.setInnerPrice(excel.getInnerPrice());
if (!warehouseInfoMap.containsKey(excel.getWarehouseName())) {
throw new ServiceException("仓库未找到,导入失败");
}
OmsWarehouseInfo omsWarehouseInfo = warehouseInfoMap.get(excel.getWarehouseName());
inventoryInfo.setWarehouseId(omsWarehouseInfo.getId());
inventoryInfo.setWarehouseName(omsWarehouseInfo.getWarehouseName());
// if (excel.getInnerPrice()==null){
// throw new ServiceException("入库价未填写,导入失败");
// }
// inventoryInfo.setInnerPrice(excel.getInnerPrice());
// if (!warehouseInfoMap.containsKey(excel.getWarehouseName())) {
// throw new ServiceException("仓库未找到,导入失败");
// }
// OmsWarehouseInfo omsWarehouseInfo = warehouseInfoMap.get(excel.getWarehouseName());
// inventoryInfo.setWarehouseId(omsWarehouseInfo.getId());
// inventoryInfo.setWarehouseName(omsWarehouseInfo.getWarehouseName());
if (!productInfoMap.containsKey(excel.getProductCode())) {
throw new ServiceException("产品未找到,导入失败");
}

View File

@ -1,8 +1,8 @@
package com.ruoyi.sip.service.impl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import cn.hutool.core.collection.CollUtil;
@ -233,13 +233,27 @@ public class OmsInventoryInnerServiceImpl implements IOmsInventoryInnerService {
}
@Override
public void importByOuter(List<InventoryInfo> inventoryInfoList,String productCode) {
public void importByOuter(List<InventoryInfo> inventoryInfoList, String productCode, String purchaseNo,Long itemId) {
List<ProductInfo> productInfos = productInfoService.selectProductInfoByCodeList(Collections.singletonList(productCode));
OmsInventoryInner omsInventoryInner = new OmsInventoryInner();
omsInventoryInner.setVendorCode(productInfos.get(0).getVendorCode());
omsInventoryInner.setProductCode(productCode);
omsInventoryInner.setProductType(productInfos.get(0).getType());
omsInventoryInner.setPurchaseNo(purchaseNo);
omsInventoryInner.setTotalAmount(inventoryInfoList.stream().map(InventoryInfo::getInnerPrice).reduce(BigDecimal.ZERO, BigDecimal::add));
omsInventoryInner.setTaxRate(inventoryInfoList.get(0).getTaxRate());
// omsInventoryInner.setTaxTotal(inventoryInfoList.stream().map(item->
// item.getInnerPrice().divide(BigDecimal.ONE.add(item.getTaxRate()),2, RoundingMode.HALF_UP)
// ).reduce(BigDecimal.ZERO, BigDecimal::add));
BigDecimal divide = omsInventoryInner.getTotalAmount().divide(BigDecimal.ONE.add(omsInventoryInner.getTaxRate()), 2, RoundingMode.HALF_UP);
omsInventoryInner.setTaxTotal(omsInventoryInner.getTotalAmount().subtract(divide));
omsInventoryInner.setProductType(productInfos.get(0).getType());
omsInventoryInner.setWarehouseId(inventoryInfoList.get(0).getWarehouseId());
omsInventoryInner.setInventoryInfoList(inventoryInfoList);
omsInventoryInner.setItemId(itemId);
this.insertOmsInventoryInner(omsInventoryInner);
}
@ -250,19 +264,19 @@ public class OmsInventoryInnerServiceImpl implements IOmsInventoryInnerService {
if (count > 0){
throw new ServiceException("导入清单的产品与出库单不符");
}
List<String> warehouseNameList = inventoryInfoExcelDtoList.stream().map(InventoryInfoExcelDto::getWarehouseName).distinct().collect(Collectors.toList());
if (warehouseNameList.size() > 1){
throw new ServiceException("导入清单只能有一个仓库");
}
// List<String> warehouseNameList = inventoryInfoExcelDtoList.stream().map(InventoryInfoExcelDto::getWarehouseName).distinct().collect(Collectors.toList());
// if (warehouseNameList.size() > 1){
// throw new ServiceException("导入清单只能有一个仓库");
// }
List<ProductInfo> productInfos = productInfoService.selectProductInfoByCodeList(Collections.singletonList(productCode));
if (CollUtil.isEmpty(productInfos)){
throw new ServiceException("产品编码对应产品未找到");
}
List<OmsWarehouseInfo> warehouseInfoList = warehouseInfoService.listByNameList(warehouseNameList);
if (CollUtil.isEmpty(warehouseInfoList)) {
throw new ServiceException("仓库未找到,导入失败");
}
Map<String, OmsWarehouseInfo> warehouseInfoMap = warehouseInfoList.stream().collect(Collectors.toMap(OmsWarehouseInfo::getWarehouseName, Function.identity(), (v1, v2) -> v1));
// List<OmsWarehouseInfo> warehouseInfoList = warehouseInfoService.listByNameList(warehouseNameList);
// if (CollUtil.isEmpty(warehouseInfoList)) {
// throw new ServiceException("仓库未找到,导入失败");
// }
// Map<String, OmsWarehouseInfo> warehouseInfoMap = warehouseInfoList.stream().collect(Collectors.toMap(OmsWarehouseInfo::getWarehouseName, Function.identity(), (v1, v2) -> v1));
List<InventoryInfo> inventoryInfoList = inventoryInfoExcelDtoList.stream().map(item -> {
InventoryInfo info = new InventoryInfo();
info.setInventoryStatus(InventoryInfo.InventoryStatusEnum.INNER.getCode());
@ -271,9 +285,9 @@ public class OmsInventoryInnerServiceImpl implements IOmsInventoryInnerService {
info.setModel(productInfos.get(0).getModel());
info.setProductDesc(productInfos.get(0).getDescription());
info.setInnerPrice(item.getInnerPrice());
OmsWarehouseInfo omsWarehouseInfo = warehouseInfoMap.get(item.getWarehouseName());
info.setWarehouseId(omsWarehouseInfo.getId());
info.setWarehouseName(omsWarehouseInfo.getWarehouseName());
// OmsWarehouseInfo omsWarehouseInfo = warehouseInfoMap.get(item.getWarehouseName());
// info.setWarehouseId(omsWarehouseInfo.getId());
// info.setWarehouseName(omsWarehouseInfo.getWarehouseName());
return info;
}).collect(Collectors.toList());
@ -282,7 +296,7 @@ public class OmsInventoryInnerServiceImpl implements IOmsInventoryInnerService {
throw new ServiceException(StrUtil.format("SN码[{}]已存在,导入失败", repeatSnList));
}
Map<String,Object> result=new HashMap<>();
result.put("warehouseId",warehouseInfoList.get(0).getId());
// result.put("warehouseId",warehouseInfoList.get(0).getId());
result.put("productSnDataList",inventoryInfoList);
return result;
}

View File

@ -49,6 +49,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="productCode != null and productCode != ''"> and t1.product_code = #{productCode}</if>
<if test="quantity != null "> and t1.quantity = #{quantity}</if>
<if test="warehouseId != null "> and t1.warehouse_id = #{warehouseId}</if>
<if test="warehouseType != null "> and t2.warehouse_type = #{warehouseType}</if>
<if test="createBy != null and createBy != ''"> and t1.create_by like concat('%', #{createBy}, '%')</if>
<if test="createByName != null and createByName != ''"> and t3.user_name like concat('%', #{createByName}, '%')</if>
<if test="purchaseNo != null and purchaseNo != ''"> and t1.purchase_no like concat('%', #{purchaseNo}, '%')</if>

View File

@ -168,7 +168,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
t1.tax_total,
t1.tax_rate,
t1.id as item_id,
t5.warehouse_name,
t2.warehouse_id,
t3.description as product_desc
@ -177,6 +177,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
LEFT JOIN oms_purchase_order t2 ON t1.purchase_id = t2.id
left join product_info t3 on t1.product_code=t3.product_code
left join oms_vendor_info t4 on t2.vendor_id=t4.vendor_id
left join oms_warehouse_info t5 on t2.warehouse_id=t5.id
<where>
<if test="purchaseNo != null and purchaseNo != ''">and t2.purchase_no = #{purchaseNo}</if>
<if test="type != null and 'inner'.toString() == type">and t1.quantity != t1.inner_quantity</if>