feat(heli): 库存管理功能优化

This commit is contained in:
zxy 2026-03-31 14:54:45 +08:00
parent 0de30962f3
commit 545ac4478e
12 changed files with 261 additions and 141 deletions

View File

@ -183,8 +183,8 @@ public interface ErrorCodeConstants {
ErrorCode MAT_CAT_CODE_ERROR = new ErrorCode(1_014_004, "该物料大类编码不正确,请检查!");
ErrorCode WMS_STORAGE_NOT_EXISTS = new ErrorCode(1_015_001, "当前数据不存在");
ErrorCode WMS_STORAGE_IS_EXPORT = new ErrorCode(1_015_002, "存在已导出的数据,请刷新界面。");
ErrorCode WMS_STORAGE_NOT_EXPORT = new ErrorCode(1_015_003, "当前数据不能删除");
ErrorCode WMS_STORAGE_IS_EXPORT = new ErrorCode(1_015_002, "当前数据已导出,请刷新界面。");
ErrorCode WMS_STORAGE_NOT_DELETE = new ErrorCode(1_015_003, "存在已导出的数据,请刷新界面。");
}

View File

@ -3,6 +3,7 @@ package com.chanko.yunxi.mes.module.heli.controller.admin.wmsstorage;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.chanko.yunxi.mes.framework.common.exception.ServiceException;
import com.chanko.yunxi.mes.framework.common.pojo.CommonResult;
import com.chanko.yunxi.mes.framework.common.pojo.PageResult;
import com.chanko.yunxi.mes.framework.common.util.object.BeanUtils;
@ -16,18 +17,31 @@ import com.chanko.yunxi.mes.module.heli.service.wmsstorage.WmsStorageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.util.function.Tuple2;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.sql.Date;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static com.chanko.yunxi.mes.framework.common.pojo.CommonResult.success;
import static com.chanko.yunxi.mes.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
@ -85,6 +99,118 @@ public class WmsStorageController {
return success(BeanUtils.toBean(pageResult, WmsStorageRespVO.class));
}
@GetMapping("/export")
@Operation(summary = "导出新中大入/出库主 Excel使用模板")
@PreAuthorize("@ss.hasPermission('heli:wms-storage:export')")
@OperateLog(type = EXPORT)
@Transactional(rollbackFor = Exception.class)
public void export(@RequestParam("stockType") Integer stockType,
@RequestParam("ids") String ids,
HttpServletResponse response) throws IOException {
// 处理数据更新导出状态
Tuple2<Boolean, List<WmsStorageDO>> objects = wmsStorageService.processWsmStorage(ids);
List<WmsStorageDO> list = objects.getT2();
if (!objects.getT1()) {
// list 统计单据编号,分割
String stockNoList = list.stream().map(WmsStorageDO::getStockNo).collect(Collectors.joining(","));
throw new ServiceException(1010, "单据编号{" + stockNoList + "}存在已导出的数据,请刷新界面。");
}
// 判断入库还是出库
boolean isInbound = stockType == 1;
String typeName = isInbound ? "入库" : "出库";
// 设置响应头
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
String fileName = URLEncoder.encode("新中大" + typeName + "单数据").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xls");
// 加载模板文件
String templatePath = "template/wms-storage-" + (isInbound ? "in" : "out") + ".xls";
ClassPathResource resource = new ClassPathResource(templatePath);
if (!resource.exists()) {
throw new RuntimeException("模板文件不存在: " + templatePath);
}
InputStream inputStream = resource.getInputStream();
Workbook workbook = new HSSFWorkbook(new POIFSFileSystem(inputStream));
// 处理第一个sheet - 主表数据
Sheet sheet0 = workbook.getSheetAt(0);
int rowIndex = 1; // 从第二行开始写入
for (WmsStorageDO wmsStorage : list) {
Row row = sheet0.createRow(rowIndex++);
if (isInbound) {
// 入库数据
setCellValue(row.createCell(0), wmsStorage.getStockNo());
setCellValue(row.createCell(1), wmsStorage.getBusiType());
setCellValue(row.createCell(2), wmsStorage.getWhName());
setCellValue(row.createCell(3), wmsStorage.getBusiDate().toString());
setCellValue(row.createCell(4), wmsStorage.getSupplierName());
setCellValue(row.createCell(5), wmsStorage.getPriceType());
setCellValue(row.createCell(6), wmsStorage.getDeptName());
} else {
// 出库数据
setCellValue(row.createCell(0), wmsStorage.getStockNo());
setCellValue(row.createCell(1), wmsStorage.getWhName());
setCellValue(row.createCell(2), wmsStorage.getBusiDate().toString());
setCellValue(row.createCell(3), wmsStorage.getDeptName());
setCellValue(row.createCell(4), wmsStorage.getBusiType());
setCellValue(row.createCell(5), wmsStorage.getAuItem());
}
}
// 处理第二个sheet - 明细数据
Sheet sheet1 = workbook.getSheetAt(1);
rowIndex = 1; // 从第二行开始写入
for (WmsStorageDO wmsStorage : list) {
if (wmsStorage.getDetailList() != null) {
for (WmsStorageDetailDO detail : wmsStorage.getDetailList()) {
Row row = sheet1.createRow(rowIndex++);
setCellValue(row.createCell(0), detail.getStockNo());
setCellValue(row.createCell(1), detail.getMatCode());
setCellValue(row.createCell(2), detail.getRgName());
setCellValue(row.createCell(3), detail.getStockNum());
if (isInbound) {
setCellValue(row.createCell(4), detail.getPrice());
}
}
}
}
// 写入输出流
OutputStream outputStream = response.getOutputStream();
workbook.write(outputStream);
workbook.close();
outputStream.flush();
outputStream.close();
inputStream.close();
}
/**
* 设置单元格值
*/
private void setCellValue(Cell cell, Object value) {
if (value == null) {
cell.setBlank();
return;
}
if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
} else if (value instanceof Boolean) {
cell.setCellValue((Boolean) value);
} else if (value instanceof LocalDate) {
cell.setCellValue(Date.valueOf((LocalDate) value));
} else if (value instanceof Date) {
cell.setCellValue((Date) value);
} else {
cell.setCellValue(value.toString());
}
}
@GetMapping("/export-excel")
@Operation(summary = "导出新中大入/出库主 Excel")
@PreAuthorize("@ss.hasPermission('heli:wms-storage:export')")
@ -93,10 +219,11 @@ public class WmsStorageController {
public void exportWmsStorageExcel(@RequestParam("stockType") Integer stockType,
@RequestParam("ids") String ids,
HttpServletResponse response) throws IOException {
List<WmsStorageDO> list = wmsStorageService.processWsmStorage(ids);
Tuple2<Boolean, List<WmsStorageDO>> objects = wmsStorageService.processWsmStorage(ids);
List<WmsStorageDO> list = objects.getT2();
// 导出 Excel - 两个sheet
try {
// 设置响应头
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
@ -150,15 +277,8 @@ public class WmsStorageController {
.head(DetailOutExcelVO.class).build();
excelWriter.write(detailData, writeSheet2);
}
// 关闭writer
excelWriter.finish();
} catch (Exception e) {
response.reset();
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println("{\"code\":500,\"msg\":\"导出Excel失败: " + e.getMessage() + "\"}");
}
}
}

View File

@ -16,25 +16,25 @@ import java.time.LocalDate;
@Data
@ExcelIgnoreUnannotated
public class WmsStorageInExcelVO {
@ExcelProperty("系统编号")
private Long id = 1L;
@ExcelProperty(value = "系统编号", index = 0)
private String stock_no;
@ExcelProperty("业务类型")
@ExcelProperty(value = "业务类型", index = 1)
private String busiType;
@ExcelProperty("仓库")
@ExcelProperty(value = "仓库", index = 2)
private String whName;
@ExcelProperty("业务日期")
@ExcelProperty(value = "业务日期", index = 3)
private LocalDate busiDate;
@ExcelProperty("供应商名称")
@ExcelProperty(value = "供应商名称", index = 4)
private String supplierName;
@ExcelProperty("价格类型")
@ExcelProperty(value = "价格类型", index = 5)
private String priceType;
@ExcelProperty("部门")
@ExcelProperty(value = "部门", index = 6)
private String deptName;

View File

@ -16,22 +16,22 @@ import java.time.LocalDate;
@Data
@ExcelIgnoreUnannotated
public class WmsStorageOutExcelVO {
@ExcelProperty("系统编号")
private Long id = 1L;
@ExcelProperty("系统编号*")
private String stock_no;
@ExcelProperty("仓库")
@ExcelProperty("仓库*")
private String whName;
@ExcelProperty("业务日期")
@ExcelProperty("业务日期*")
private LocalDate busiDate;
@ExcelProperty("部门")
@ExcelProperty("部门*")
private String deptName;
@ExcelProperty("业务类型")
@ExcelProperty("业务类型*")
private String busiType;
@ExcelProperty("辅助项")
@ExcelProperty("辅助项*")
private String auItem;

View File

@ -11,19 +11,19 @@ import java.math.BigDecimal;
@Data
@ExcelIgnoreUnannotated
public class DetailInExcelVO {
@ExcelProperty("系统编号")
private Long id = 1L;
@ExcelProperty(value = "系统编号*", index = 0)
private String stock_no;
@ExcelProperty("物料编码")
@ExcelProperty(value = "物料编码*", index = 1)
private String matCode;
@ExcelProperty("库位")
@ExcelProperty(value = "库位*", index = 2)
private String rgName;
@ExcelProperty("数量")
@ExcelProperty(value = "数量*", index = 3)
private BigDecimal stockNum;
@ExcelProperty("单价")
@ExcelProperty(value = "单价*", index = 4)
private BigDecimal price;
}

View File

@ -12,16 +12,16 @@ import java.math.BigDecimal;
@ExcelIgnoreUnannotated
public class DetailOutExcelVO {
@ExcelProperty("系统编号")
private Long id = 1L;
@ExcelProperty("系统编号*")
private String stock_no;
@ExcelProperty("物料编码")
@ExcelProperty("物料编码*")
private String matCode;
@ExcelProperty("库位")
@ExcelProperty("库位*")
private String rgName;
@ExcelProperty("数量")
@ExcelProperty("数量*")
private BigDecimal stockNum;

View File

@ -4,6 +4,7 @@ import com.chanko.yunxi.mes.framework.common.pojo.PageResult;
import com.chanko.yunxi.mes.module.heli.controller.admin.wmsstorage.vo.WmsStoragePageReqVO;
import com.chanko.yunxi.mes.module.heli.controller.admin.wmsstorage.vo.WmsStorageSaveReqVO;
import com.chanko.yunxi.mes.module.heli.dal.dataobject.wmsstorage.WmsStorageDO;
import reactor.util.function.Tuple2;
import javax.validation.Valid;
import java.util.List;
@ -53,5 +54,7 @@ public interface WmsStorageService {
*/
PageResult<WmsStorageDO> getWmsStoragePage(WmsStoragePageReqVO pageReqVO);
List<WmsStorageDO> processWsmStorage(String ids);
// 返回两个结果
Tuple2<Boolean, List<WmsStorageDO>> processWsmStorage(String ids);
}

View File

@ -11,21 +11,23 @@ import com.chanko.yunxi.mes.module.heli.dal.dataobject.wmsstoragedetail.WmsStora
import com.chanko.yunxi.mes.module.heli.dal.mysql.storagelog.StorageLogMapper;
import com.chanko.yunxi.mes.module.heli.dal.mysql.wmsstorage.WmsStorageMapper;
import com.chanko.yunxi.mes.module.heli.dal.mysql.wmsstoragedetail.WmsStorageDetailMapper;
import com.chanko.yunxi.mes.module.system.dal.dataobject.user.AdminUserDO;
import com.chanko.yunxi.mes.module.system.service.user.AdminUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static com.chanko.yunxi.mes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.chanko.yunxi.mes.module.heli.enums.ErrorCodeConstants.*;
import static com.chanko.yunxi.mes.module.heli.enums.ErrorCodeConstants.WMS_STORAGE_IS_EXPORT;
import static com.chanko.yunxi.mes.module.heli.enums.ErrorCodeConstants.WMS_STORAGE_NOT_EXISTS;
/**
* 新中大入/出库主 Service 实现类
@ -41,11 +43,8 @@ public class WmsStorageServiceImpl implements WmsStorageService {
private final WmsStorageDetailMapper wmsStorageDetailMapper;
private final AdminUserService userService;
private final StorageLogMapper storageLogMapper;
@Override
public Long createWmsStorage(WmsStorageSaveReqVO createReqVO) {
// 插入
@ -65,14 +64,13 @@ public class WmsStorageServiceImpl implements WmsStorageService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteWmsStorage(Long id) {
// 校验存在
validateWmsStorageExists(id);
// 校验状态
WmsStorageDO wmsStorage = wmsStorageMapper.selectById(id);
if (wmsStorage.getStatus() == 1) {
throw exception(WMS_STORAGE_NOT_EXPORT);
if (wmsStorage.getStatus() == 2) {
throw exception(WMS_STORAGE_IS_EXPORT);
}
// 删除
wmsStorageMapper.deleteById(id);
@ -99,18 +97,13 @@ public class WmsStorageServiceImpl implements WmsStorageService {
@Override
public WmsStorageDO getWmsStorage(Long id) {
WmsStorageDO wmsStorage = wmsStorageMapper.selectById(id);
if (wmsStorage != null) {
WmsStorageDO wmsStorageDO = wmsStorageMapper.selectById(id);
if (wmsStorageDO != null) {
List<WmsStorageDetailDO> detailList = wmsStorageDetailMapper.selectList(
WmsStorageDetailDO::getXzdWmsId, wmsStorage.getId());
wmsStorage.setDetailList(detailList);
// 查询用户信息
AdminUserDO user = userService.getUser(wmsStorage.getExportEmp());
if (user != null){
wmsStorage.setExportEmpName(user.getNickname());
WmsStorageDetailDO::getXzdWmsId, wmsStorageDO.getId());
wmsStorageDO.setDetailList(detailList);
}
}
return wmsStorage;
return wmsStorageDO;
}
@Override
@ -120,17 +113,23 @@ public class WmsStorageServiceImpl implements WmsStorageService {
@Transactional
@Override
public List<WmsStorageDO> processWsmStorage(String ids) {
public Tuple2<Boolean, List<WmsStorageDO>> processWsmStorage(String ids) {
// 分割并转换为 Long 类型
List<Long> idList = Arrays.stream(ids.split(",")).map(Long::valueOf).collect(Collectors.toList());
if (!idList.isEmpty()) {
// 根据ID查询出所有数据批量更新导出人导出状态
List<WmsStorageDO> wmsStorageDOS = wmsStorageMapper.selectList(WmsStorageDO::getId, idList);
// 如果状态为已导出则remove
List<WmsStorageDO> errWmsStorageDOS = new ArrayList<>();
for (WmsStorageDO wmsStorageDO : wmsStorageDOS) {
if (wmsStorageDO.getStatus() == 2) {
throw exception(WMS_STORAGE_IS_EXPORT);
errWmsStorageDOS.add(wmsStorageDO);
}
}
if (!errWmsStorageDOS.isEmpty()) {
return Tuples.of(false, errWmsStorageDOS);
}
// 如果状态为已导出则remove
for (WmsStorageDO wmsStorageDO : wmsStorageDOS) {
// 获取当前用户信息
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
wmsStorageDO.setExportEmp(loginUserId);
@ -140,8 +139,8 @@ public class WmsStorageServiceImpl implements WmsStorageService {
List<WmsStorageDetailDO> wmsStorageDetailDOS = wmsStorageDetailMapper.selectList(WmsStorageDetailDO::getXzdWmsId, wmsStorageDO.getId());
wmsStorageDO.setDetailList(wmsStorageDetailDOS);
}
return wmsStorageDOS;
return Tuples.of(true, wmsStorageDOS);
}
return Collections.emptyList();
return Tuples.of(false, Collections.emptyList());
}
}

View File

@ -62,7 +62,7 @@ export const deleteWmsStorage = async (id: number) => {
// 导出新中大入/出库主 Excel
export const exportWmsStorage = async (stockType: Number, ids: String) => {
return await request.download({
url: `/heli/wms-storage/export-excel`,
url: `/heli/wms-storage/export`,
params: { stockType, ids }
})
}

View File

@ -18,16 +18,6 @@
class="!w-260px"
/>
</el-form-item>
<el-form-item label="单据状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="item in HeliStorageStatusDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="单据类型" prop="stockType">
<el-select
v-model="queryParams.stockType"
@ -43,6 +33,17 @@
/>
</el-select>
</el-form-item>
<el-form-item label="单据状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="item in HeliStorageStatusDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
@ -61,13 +62,9 @@
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
height="500px"
@selection-change="handleSelectionChange"
>
v-loading="loading" :data="list" class="hl-table"
ref="multipleTableRef"
@selection-change="handleSelectionChange" :row-class-name="tableRowClassName">
<el-table-column
fixed
type="selection"
@ -75,13 +72,13 @@
align="center"
:selectable="isRowSelectable"
/>
<el-table-column fixed type="index" width="60" label="序号" align="center" />
<el-table-column label="单据类型" width="100" align="center" prop="stockType">
<el-table-column fixed="left" type="index" width="60" label="序号" align="center" />
<el-table-column fixed="left" label="单据类型" width="100" align="center" prop="stockType">
<template #default="scope">
{{ getHeliStockTypeLabel(scope.row.stockType) }}
</template>
</el-table-column>
<el-table-column label="单据状态" width="100" align="center" prop="status">
<el-table-column fixed="left" label="单据状态" min-width="120" align="center" prop="status">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">
<Icon icon="ep:check" class="mr-5px" />未导出
@ -91,23 +88,23 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="单据编号" align="center" prop="stockNo" />
<el-table-column label="仓库" align="center" prop="whName" />
<el-table-column label="单据编号" fixed="left" min-width="180" align="center" prop="stockNo" />
<el-table-column label="仓库" min-width="200" align="center" prop="whName" />
<el-table-column label="业务日期" width="120" align="center" prop="busiDate">
<template #default="scope">
{{ formatDate(scope.row.busiDate, 'YYYY-MM-DD') }}
</template>
</el-table-column>
<el-table-column label="供应商名称" align="center" prop="supplierName" />
<el-table-column label="价格类型" align="center" prop="priceType" />
<el-table-column label="部门" align="center" prop="deptName" />
<el-table-column label="供应商名称" min-width="300" align="center" prop="supplierName" />
<el-table-column label="价格类型" min-width="160" align="center" prop="priceType" />
<el-table-column label="部门" min-width="500" align="center" prop="deptName" />
<!-- <el-table-column label="业务类型" align="center" prop="busiType" />-->
<!-- <el-table-column label="备注" align="center" prop="description" />-->
<el-table-column label="辅助项" align="center" prop="auItem" />
<el-table-column label="辅助项" min-width="200" align="center" prop="auItem" />
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.status === 2"
v-if="scope.row.status === 1"
link
type="danger"
@click="handleDelete(scope.row.id)"
@ -144,6 +141,7 @@ import {
HeliStorageStatusDict,
getHeliStockTypeLabel
} from '@/api/heli/wmsstorage'
import {ElTable} from "element-plus";
defineOptions({ name: 'WmsStorage' })