fix:修改仓库管理模块审核相关问题

main
HuangHuiKang 2 weeks ago
parent adf8208fa0
commit 928b87dcde

@ -153,6 +153,13 @@ public interface ErrorCodeConstants {
ErrorCode STOCK_MOVE_APPROVE_FAIL = new ErrorCode(1_030_402_003, "审核失败,只有未审核的调拨单才能审核");
ErrorCode STOCK_MOVE_NO_EXISTS = new ErrorCode(1_030_402_004, "生成调拨号失败,请重新提交");
ErrorCode STOCK_MOVE_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_402_005, "库存调拨单({})已审核,无法修改");
ErrorCode STOCK_MOVE_UPDATE_FAIL_PROCESS = new ErrorCode(1_030_402_006, "库存调拨单({})审核中,无法修改");
ErrorCode STOCK_MOVE_DELETE_FAIL_PROCESS = new ErrorCode(1_030_402_007, "库存调拨单({})审核中,无法删除");
ErrorCode STOCK_MOVE_SUBMIT_FAIL_STATUS = new ErrorCode(1_030_402_008, "提交审核失败,只有待提交或已驳回的调拨单才能提交");
ErrorCode STOCK_MOVE_SUBMIT_FAIL_USER = new ErrorCode(1_030_402_009, "提交审核失败,只有创建人才能提交审核");
ErrorCode STOCK_MOVE_AUDIT_FAIL_STATUS = new ErrorCode(1_030_402_010, "审核失败,只有审核中的调拨单才能审核");
ErrorCode STOCK_MOVE_AUDIT_FAIL_USER = new ErrorCode(1_030_402_011, "审核失败,当前用户不是指定审核人");
ErrorCode STOCK_MOVE_AUDIT_FAIL_RESULT = new ErrorCode(1_030_402_012, "审核失败,审核结果只支持通过或驳回");
// ========== ERP 库存盘点单 1-030-403-000 ==========
ErrorCode STOCK_CHECK_NOT_EXISTS = new ErrorCode(1_030_403_000, "库存盘点单不存在");

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.erp.enums.stock;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum ErpStockMoveApproveActionEnum {
SUBMIT("SUBMIT"),
APPROVE("APPROVE"),
REJECT("REJECT"),
AUTO_APPROVE("AUTO_APPROVE");
private final String actionType;
}

@ -6,18 +6,30 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.ratelimiter.core.annotation.RateLimiter;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveApproveRecordRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMovePageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveProductStockRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSubmitReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveApproveRecordDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehousearea.WarehouseAreaDO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockMoveService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockService;
import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService;
import cn.iocoder.yudao.module.erp.service.warehousearea.WarehouseAreaService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
@ -32,11 +44,19 @@ import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@ -52,6 +72,10 @@ public class ErpStockMoveController {
private ErpStockService stockService;
@Resource
private ErpProductService productService;
@Resource
private ErpWarehouseService warehouseService;
@Resource
private WarehouseAreaService warehouseAreaService;
@Resource
private AdminUserApi adminUserApi;
@ -80,6 +104,42 @@ public class ErpStockMoveController {
return success(true);
}
@PutMapping("/submit")
@Operation(summary = "提交库存调拨单审核")
@PreAuthorize("@ss.hasPermission('erp:stock-move:submit')")
@RateLimiter(count = 1, timeUnit = TimeUnit.SECONDS)
public CommonResult<Boolean> submitStockMoveAudit(@Valid @RequestBody ErpStockMoveSubmitReqVO submitReqVO) {
stockMoveService.submitStockMoveAudit(submitReqVO);
return success(true);
}
@PutMapping("/audit")
@Operation(summary = "审核库存调拨单")
@PreAuthorize("@ss.hasPermission('erp:stock-move:audit')")
@RateLimiter(count = 1, timeUnit = TimeUnit.SECONDS)
public CommonResult<Boolean> auditStockMove(@Valid @RequestBody ErpStockMoveAuditReqVO auditReqVO) {
stockMoveService.auditStockMove(auditReqVO);
return success(true);
}
@GetMapping("/approve-record-list")
@Operation(summary = "获得库存调拨单审核记录")
@PreAuthorize("@ss.hasPermission('erp:stock-move:query')")
public CommonResult<List<ErpStockMoveApproveRecordRespVO>> getApproveRecordList(@RequestParam("id") Long id) {
return success(buildApproveRecordRespList(stockMoveService.getStockMoveApproveRecordList(id)));
}
@GetMapping("/product-stock-list")
@Operation(summary = "获得产品仓库库区库存列表")
@PreAuthorize("@ss.hasPermission('erp:stock-move:query')")
public CommonResult<List<ErpStockMoveProductStockRespVO>> getProductStockList(
@RequestParam("productId") Long productId,
@RequestParam(value = "warehouseId", required = false) Long warehouseId) {
List<ErpStockDO> stockList = stockService.getStockList(Collections.singleton(productId),
warehouseId == null ? Collections.emptySet() : Collections.singleton(warehouseId), Collections.emptySet());
return success(buildProductStockRespList(stockList));
}
@DeleteMapping("/delete")
@Operation(summary = "删除库存调拨单")
@Parameter(name = "ids", description = "编号数组", required = true)
@ -98,16 +158,9 @@ public class ErpStockMoveController {
if (stockMove == null) {
return success(null);
}
List<ErpStockMoveItemDO> stockMoveItemList = stockMoveService.getStockMoveItemListByMoveId(id);
Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
convertSet(stockMoveItemList, ErpStockMoveItemDO::getProductId));
return success(BeanUtils.toBean(stockMove, ErpStockMoveRespVO.class, stockMoveVO ->
stockMoveVO.setItems(BeanUtils.toBean(stockMoveItemList, ErpStockMoveRespVO.Item.class, item -> {
ErpStockDO stock = stockService.getStock(item.getProductId(), item.getFromWarehouseId());
item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO);
MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
.setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()));
}))));
return success(buildStockMoveRespVO(stockMove,
stockMoveService.getStockMoveItemListByMoveId(id),
stockMoveService.getStockMoveApproveRecordList(id)));
}
@GetMapping("/page")
@ -134,27 +187,135 @@ public class ErpStockMoveController {
if (CollUtil.isEmpty(pageResult.getList())) {
return PageResult.empty(pageResult.getTotal());
}
// 1.1 调拨项
List<ErpStockMoveDO> stockMoves = pageResult.getList();
List<ErpStockMoveItemDO> stockMoveItemList = stockMoveService.getStockMoveItemListByMoveIds(
convertSet(pageResult.getList(), ErpStockMoveDO::getId));
convertSet(stockMoves, ErpStockMoveDO::getId));
Map<Long, List<ErpStockMoveItemDO>> stockMoveItemMap = convertMultiMap(stockMoveItemList, ErpStockMoveItemDO::getMoveId);
// 1.2 产品信息
Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
convertSet(stockMoveItemList, ErpStockMoveItemDO::getProductId));
// 1.3 TODO 芋艿:搞仓库信息
// 1.4 管理员信息
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(pageResult.getList(), stockMove -> Long.parseLong(stockMove.getCreator())));
// 2. 开始拼接
Map<Long, ErpWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(convertListByFlatMap(stockMoveItemList,
item -> Stream.of(item.getFromWarehouseId(), item.getToWarehouseId())));
Map<Long, WarehouseAreaDO> areaMap = warehouseAreaService.getWarehouseAreaMap(convertListByFlatMap(stockMoveItemList,
item -> Stream.of(item.getFromAreaId(), item.getToAreaId())));
Map<String, ErpStockDO> stockMap = buildStockMap(stockMoveItemList);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(stockMoves,
stockMove -> Stream.of(NumberUtils.parseLong(stockMove.getCreator()), stockMove.getAuditUserId())));
return BeanUtils.toBean(pageResult, ErpStockMoveRespVO.class, stockMove -> {
stockMove.setItems(BeanUtils.toBean(stockMoveItemMap.get(stockMove.getId()), ErpStockMoveRespVO.Item.class,
item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
.setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()))));
item -> fillStockMoveItem(item, productMap, warehouseMap, areaMap, stockMap)));
stockMove.setProductNames(CollUtil.join(stockMove.getItems(), "", ErpStockMoveRespVO.Item::getProductName));
// TODO 芋艿:
// MapUtils.findAndThen(customerMap, stockMove.getCustomerId(), supplier -> stockMove.setCustomerName(supplier.getName()));
MapUtils.findAndThen(userMap, Long.parseLong(stockMove.getCreator()), user -> stockMove.setCreatorName(user.getNickname()));
stockMove.setStatusName(getAuditStatusName(stockMove.getStatus()));
MapUtils.findAndThen(userMap, NumberUtils.parseLong(stockMove.getCreator()), user -> stockMove.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, stockMove.getAuditUserId(), user -> stockMove.setAuditUserName(user.getNickname()));
});
}
private List<ErpStockMoveProductStockRespVO> buildProductStockRespList(List<ErpStockDO> stockList) {
if (CollUtil.isEmpty(stockList)) {
return Collections.emptyList();
}
Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(convertSet(stockList, ErpStockDO::getProductId));
Map<Long, ErpWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(convertSet(stockList, ErpStockDO::getWarehouseId));
Map<Long, WarehouseAreaDO> areaMap = warehouseAreaService.getWarehouseAreaMap(convertSet(stockList, ErpStockDO::getAreaId));
return stockList.stream()
.sorted(Comparator.comparing(ErpStockDO::getWarehouseId, Comparator.nullsLast(Long::compareTo))
.thenComparing(ErpStockDO::getAreaId, Comparator.nullsLast(Long::compareTo)))
.map(stock -> BeanUtils.toBean(stock, ErpStockMoveProductStockRespVO.class, respVO -> {
respVO.setStockCount(stock.getCount());
MapUtils.findAndThen(productMap, stock.getProductId(), product -> respVO.setProductName(product.getName())
.setProductBarCode(product.getBarCode()));
MapUtils.findAndThen(warehouseMap, stock.getWarehouseId(), warehouse -> respVO.setWarehouseName(warehouse.getName()));
if (respVO.getAreaName() == null) {
MapUtils.findAndThen(areaMap, stock.getAreaId(), area -> respVO.setAreaName(area.getAreaName()));
}
}))
.collect(Collectors.toList());
}
private ErpStockMoveRespVO buildStockMoveRespVO(ErpStockMoveDO stockMove, List<ErpStockMoveItemDO> stockMoveItemList,
List<ErpStockMoveApproveRecordDO> approveRecords) {
Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
convertSet(stockMoveItemList, ErpStockMoveItemDO::getProductId));
Map<Long, ErpWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(convertListByFlatMap(stockMoveItemList,
item -> Stream.of(item.getFromWarehouseId(), item.getToWarehouseId())));
Map<Long, WarehouseAreaDO> areaMap = warehouseAreaService.getWarehouseAreaMap(convertListByFlatMap(stockMoveItemList,
item -> Stream.of(item.getFromAreaId(), item.getToAreaId())));
Map<String, ErpStockDO> stockMap = buildStockMap(stockMoveItemList);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(approveRecords,
record -> Stream.of(NumberUtils.parseLong(record.getCreator()), record.getTargetUserId())));
userMap.putAll(adminUserApi.getUserMap(convertListByFlatMap(Collections.singletonList(stockMove),
item -> Stream.of(NumberUtils.parseLong(item.getCreator()), item.getAuditUserId()))));
return BeanUtils.toBean(stockMove, ErpStockMoveRespVO.class, respVO -> {
respVO.setItems(BeanUtils.toBean(stockMoveItemList, ErpStockMoveRespVO.Item.class,
item -> fillStockMoveItem(item, productMap, warehouseMap, areaMap, stockMap)));
respVO.setProductNames(CollUtil.join(respVO.getItems(), "", ErpStockMoveRespVO.Item::getProductName));
respVO.setStatusName(getAuditStatusName(respVO.getStatus()));
respVO.setApproveRecords(buildApproveRecordRespList(approveRecords, userMap));
MapUtils.findAndThen(userMap, NumberUtils.parseLong(respVO.getCreator()), user -> respVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, respVO.getAuditUserId(), user -> respVO.setAuditUserName(user.getNickname()));
});
}
}
private void fillStockMoveItem(ErpStockMoveRespVO.Item item, Map<Long, ErpProductRespVO> productMap,
Map<Long, ErpWarehouseDO> warehouseMap, Map<Long, WarehouseAreaDO> areaMap,
Map<String, ErpStockDO> stockMap) {
MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
.setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()));
MapUtils.findAndThen(warehouseMap, item.getFromWarehouseId(), warehouse -> item.setFromWarehouseName(warehouse.getName()));
MapUtils.findAndThen(warehouseMap, item.getToWarehouseId(), warehouse -> item.setToWarehouseName(warehouse.getName()));
MapUtils.findAndThen(areaMap, item.getFromAreaId(), area -> item.setFromAreaName(area.getAreaName()));
MapUtils.findAndThen(areaMap, item.getToAreaId(), area -> item.setToAreaName(area.getAreaName()));
ErpStockDO stock = stockMap.get(buildStockKey(item.getProductId(), item.getFromWarehouseId(), item.getFromAreaId()));
item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO);
}
private Map<String, ErpStockDO> buildStockMap(List<ErpStockMoveItemDO> stockMoveItemList) {
if (CollUtil.isEmpty(stockMoveItemList)) {
return Collections.emptyMap();
}
List<ErpStockDO> stockList = stockService.getStockList(convertSet(stockMoveItemList, ErpStockMoveItemDO::getProductId),
convertSet(stockMoveItemList, ErpStockMoveItemDO::getFromWarehouseId),
convertSet(stockMoveItemList, ErpStockMoveItemDO::getFromAreaId));
Map<String, ErpStockDO> stockMap = new java.util.HashMap<>();
for (ErpStockDO stock : stockList) {
stockMap.put(buildStockKey(stock.getProductId(), stock.getWarehouseId(), stock.getAreaId()), stock);
}
return stockMap;
}
private String buildStockKey(Long productId, Long warehouseId, Long areaId) {
return String.valueOf(productId) + "_" + String.valueOf(warehouseId) + "_" + String.valueOf(areaId);
}
private List<ErpStockMoveApproveRecordRespVO> buildApproveRecordRespList(List<ErpStockMoveApproveRecordDO> records) {
if (CollUtil.isEmpty(records)) {
return new ArrayList<>();
}
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(records,
record -> Stream.of(NumberUtils.parseLong(record.getCreator()), record.getTargetUserId())));
return buildApproveRecordRespList(records, userMap);
}
private List<ErpStockMoveApproveRecordRespVO> buildApproveRecordRespList(List<ErpStockMoveApproveRecordDO> records,
Map<Long, AdminUserRespDTO> userMap) {
if (CollUtil.isEmpty(records)) {
return new ArrayList<>();
}
return BeanUtils.toBean(records, ErpStockMoveApproveRecordRespVO.class, record -> {
MapUtils.findAndThen(userMap, NumberUtils.parseLong(record.getCreator()), user -> record.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, record.getTargetUserId(), user -> record.setTargetUserName(user.getNickname()));
record.setFromStatusName(getAuditStatusName(record.getFromStatus()));
record.setToStatusName(getAuditStatusName(record.getToStatus()));
});
}
private String getAuditStatusName(Integer status) {
for (ErpAuditStatus item : ErpAuditStatus.values()) {
if (item.getStatus().equals(status)) {
return item.getName();
}
}
return null;
}
}

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - ERP 库存调拨单审核记录 Response VO")
@Data
public class ErpStockMoveApproveRecordRespVO {
@Schema(description = "编号", example = "1")
private Long id;
@Schema(description = "调拨单编号", example = "1")
private Long stockMoveId;
@Schema(description = "操作类型", example = "SUBMIT")
private String actionType;
@Schema(description = "变更前状态", example = "0")
private Integer fromStatus;
@Schema(description = "变更前状态名称", example = "待提交")
private String fromStatusName;
@Schema(description = "变更后状态", example = "10")
private Integer toStatus;
@Schema(description = "变更后状态名称", example = "审核中")
private String toStatusName;
@Schema(description = "目标审核人编号", example = "1")
private Long targetUserId;
@Schema(description = "目标审核人名称", example = "李四")
private String targetUserName;
@Schema(description = "备注", example = "请审核")
private String remark;
@Schema(description = "操作人", example = "1")
private String creator;
@Schema(description = "操作人名称", example = "张三")
private String creatorName;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - ERP 库存调拨单审核 Request VO")
@Data
public class ErpStockMoveAuditReqVO {
@Schema(description = "调拨单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756")
@NotNull(message = "调拨单编号不能为空")
private Long id;
@Schema(description = "审核结果 20-通过 1-驳回", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
@NotNull(message = "审核结果不能为空")
private Integer status;
@Schema(description = "审核备注", example = "审核通过")
private String remark;
}

@ -42,8 +42,17 @@ public class ErpStockMovePageReqVO extends PageParam {
@Schema(description = "调出仓库编号", example = "1")
private Long fromWarehouseId;
@Schema(description = "调出库区编号", example = "1")
private Long fromAreaId;
@Schema(description = "调入仓库编号", example = "1")
private Long toWarehouseId;
@Schema(description = "调入库区编号", example = "1")
private Long toAreaId;
@Schema(description = "id集合导出用")
private String ids;
}
}

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - ERP 库存调拨产品库区库存 Response VO")
@Data
public class ErpStockMoveProductStockRespVO {
@Schema(description = "产品编号", example = "1")
private Long productId;
@Schema(description = "产品名称", example = "产品A")
private String productName;
@Schema(description = "产品编码", example = "CP001")
private String productBarCode;
@Schema(description = "仓库编号", example = "10")
private Long warehouseId;
@Schema(description = "仓库名称", example = "成品仓")
private String warehouseName;
@Schema(description = "库区编号", example = "100")
private Long areaId;
@Schema(description = "库区名称", example = "A区")
private String areaName;
@Schema(description = "当前库存数量", example = "120")
private BigDecimal stockCount;
@Schema(description = "库存单位编号", example = "1")
private Long unitId;
@Schema(description = "库存单位名称", example = "个")
private String unitName;
@Schema(description = "包装单位编号", example = "2")
private Long purchaseUnitId;
@Schema(description = "包装单位名称", example = "箱")
private String purchaseUnitName;
@Schema(description = "包装单位与库存单位换算数量", example = "20")
private BigDecimal purchaseUnitConvertQuantity;
}

@ -43,6 +43,18 @@ public class ErpStockMoveRespVO {
@DictFormat(AUDIT_STATUS)
private Integer status;
@Schema(description = "状态名称", example = "待提交")
private String statusName;
@Schema(description = "是否需要审核", example = "true")
private Boolean needAudit;
@Schema(description = "审核人编号", example = "1888")
private Long auditUserId;
@Schema(description = "审核人名称", example = "李四")
private String auditUserName;
@Schema(description = "备注", example = "随便")
@ExcelProperty("备注")
private String remark;
@ -62,6 +74,9 @@ public class ErpStockMoveRespVO {
@Schema(description = "调拨项列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Item> items;
@Schema(description = "审核记录")
private List<ErpStockMoveApproveRecordRespVO> approveRecords;
@Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("产品信息")
private String productNames;
@ -75,9 +90,27 @@ public class ErpStockMoveRespVO {
@Schema(description = "调出仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
private Long fromWarehouseId;
@Schema(description = "调出仓库名称", example = "A仓")
private String fromWarehouseName;
@Schema(description = "调出库区编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
private Long fromAreaId;
@Schema(description = "调出库区名称", example = "A-01")
private String fromAreaName;
@Schema(description = "调入仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
private Long toWarehouseId;
@Schema(description = "调入仓库名称", example = "B仓")
private String toWarehouseName;
@Schema(description = "调入库区编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
private Long toAreaId;
@Schema(description = "调入库区名称", example = "B-01")
private String toAreaName;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
private Long productId;
@ -104,4 +137,4 @@ public class ErpStockMoveRespVO {
}
}
}

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutApproveRecordRespVO;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -33,7 +35,10 @@ public class ErpStockMoveSaveReqVO {
@Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc")
private String fileUrl;
@Schema(description = "调拨项列表", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "审核人编号", example = "1888")
private Long auditUserId;
@Schema(description = "调拨项列表", implementation = ErpStockMoveSaveReqVO.Item.class)
@NotEmpty(message = "调拨项列表不能为空")
@Valid
private List<Item> items;
@ -48,10 +53,18 @@ public class ErpStockMoveSaveReqVO {
@NotNull(message = "调出仓库编号不能为空")
private Long fromWarehouseId;
@Schema(description = "调出库区编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
@NotNull(message = "调出库区编号不能为空")
private Long fromAreaId;
@Schema(description = "调入仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
@NotNull(message = "调入仓库编号不能为空")
private Long toWarehouseId;
@Schema(description = "调入库区编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
@NotNull(message = "调入库区编号不能为空")
private Long toAreaId;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
@NotNull(message = "产品编号不能为空")
private Long productId;
@ -69,9 +82,9 @@ public class ErpStockMoveSaveReqVO {
@AssertTrue(message = "调出、调仓仓库不能相同")
@JsonIgnore
public boolean isWarehouseValid() {
return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId);
return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId) || ObjectUtil.notEqual(fromAreaId, toAreaId);
}
}
}
}

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - ERP 库存调拨单提交审核 Request VO")
@Data
public class ErpStockMoveSubmitReqVO {
@Schema(description = "调拨单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756")
@NotNull(message = "调拨单编号不能为空")
private Long id;
@Schema(description = "审核人编号", example = "1888")
private Long auditUserId;
@Schema(description = "提交备注", example = "请审核")
private String remark;
}

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.stock;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
@TableName("erp_stock_move_approve_record")
@KeySequence("erp_stock_move_approve_record_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErpStockMoveApproveRecordDO extends BaseDO {
@TableId
private Long id;
private Long stockMoveId;
private String actionType;
private Integer fromStatus;
private Integer toStatus;
private Long targetUserId;
private String remark;
}

@ -45,6 +45,8 @@ public class ErpStockMoveDO extends BaseDO {
*
*/
private BigDecimal totalPrice;
private Boolean needAudit;
private Long auditUserId;
/**
*
*
@ -60,4 +62,4 @@ public class ErpStockMoveDO extends BaseDO {
*/
private String fileUrl;
}
}

@ -41,12 +41,16 @@ public class ErpStockMoveItemDO extends BaseDO {
* {@link ErpWarehouseDO#getId()}
*/
private Long fromWarehouseId;
private Long fromAreaId;
private String fromAreaName;
/**
*
*
* {@link ErpWarehouseDO#getId()}
*/
private Long toWarehouseId;
private Long toAreaId;
private String toAreaName;
/**
*
*
@ -76,4 +80,4 @@ public class ErpStockMoveItemDO extends BaseDO {
*/
private String remark;
}
}

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.erp.dal.mysql.stock;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveApproveRecordDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ErpStockMoveApproveRecordMapper extends BaseMapperX<ErpStockMoveApproveRecordDO> {
default List<ErpStockMoveApproveRecordDO> selectListByStockMoveId(Long stockMoveId) {
return selectList(new LambdaQueryWrapperX<ErpStockMoveApproveRecordDO>()
.eq(ErpStockMoveApproveRecordDO::getStockMoveId, stockMoveId)
.orderByAsc(ErpStockMoveApproveRecordDO::getId));
}
default int deleteByStockMoveId(Long stockMoveId) {
return delete(ErpStockMoveApproveRecordDO::getStockMoveId, stockMoveId);
}
}

@ -31,9 +31,14 @@ public interface ErpStockMoveMapper extends BaseMapperX<ErpStockMoveDO> {
.likeIfPresent(ErpStockMoveDO::getRemark, reqVO.getRemark())
.eqIfPresent(ErpStockMoveDO::getCreator, reqVO.getCreator())
.orderByDesc(ErpStockMoveDO::getId);
if (reqVO.getFromWarehouseId() != null || reqVO.getProductId() != null) {
if (reqVO.getFromWarehouseId() != null || reqVO.getFromAreaId() != null
|| reqVO.getToWarehouseId() != null || reqVO.getToAreaId() != null
|| reqVO.getProductId() != null) {
query.leftJoin(ErpStockMoveItemDO.class, ErpStockMoveItemDO::getMoveId, ErpStockMoveDO::getId)
.eq(reqVO.getFromWarehouseId() != null, ErpStockMoveItemDO::getFromWarehouseId, reqVO.getFromWarehouseId())
.eq(reqVO.getFromAreaId() != null, ErpStockMoveItemDO::getFromAreaId, reqVO.getFromAreaId())
.eq(reqVO.getToWarehouseId() != null, ErpStockMoveItemDO::getToWarehouseId, reqVO.getToWarehouseId())
.eq(reqVO.getToAreaId() != null, ErpStockMoveItemDO::getToAreaId, reqVO.getToAreaId())
.eq(reqVO.getProductId() != null, ErpStockMoveItemDO::getProductId, reqVO.getProductId())
.groupBy(ErpStockMoveDO::getId); // 避免 1 对多查询,产生相同的 1
}
@ -62,4 +67,4 @@ public interface ErpStockMoveMapper extends BaseMapperX<ErpStockMoveDO> {
return selectOne(ErpStockMoveDO::getNo, no);
}
}
}

@ -118,7 +118,7 @@ public class ErpStockCheckServiceImpl implements ErpStockCheckService {
ErpStockCheckDO stockCheck = BeanUtils.toBean(createReqVO, ErpStockCheckDO.class, in -> in
.setNo(finalCode)
.setNeedAudit(needAudit())
.setStatus(ErpAuditStatus.DRAFT.getStatus())
.setStatus(ErpAuditStatus.PROCESS.getStatus())
.setTotalCount(getSumValue(stockCheckItems, ErpStockCheckItemDO::getCount, BigDecimal::add, BigDecimal.ZERO))
.setTotalPrice(getSumValue(stockCheckItems, ErpStockCheckItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)));
stockCheckMapper.insert(stockCheck);
@ -134,9 +134,9 @@ public class ErpStockCheckServiceImpl implements ErpStockCheckService {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockCheck.getStatus())) {
throw exception(STOCK_CHECK_UPDATE_FAIL_APPROVE, stockCheck.getNo());
}
if (ErpAuditStatus.PROCESS.getStatus().equals(stockCheck.getStatus())) {
throw exception(STOCK_CHECK_UPDATE_FAIL_PROCESS, stockCheck.getNo());
}
// if (ErpAuditStatus.PROCESS.getStatus().equals(stockCheck.getStatus())) {
// throw exception(STOCK_CHECK_UPDATE_FAIL_PROCESS, stockCheck.getNo());
// }
validateSourceType(updateReqVO.getSourceType());
List<ErpStockCheckItemDO> stockCheckItems = validateStockCheckItems(updateReqVO.getItems());
@ -181,9 +181,9 @@ public class ErpStockCheckServiceImpl implements ErpStockCheckService {
public void submitStockCheckAudit(ErpStockCheckSubmitReqVO submitReqVO) {
ErpStockCheckDO stockCheck = validateStockCheckExists(submitReqVO.getId());
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
if (!Objects.equals(NumberUtils.parseLong(stockCheck.getCreator()), loginUserId)) {
throw exception(STOCK_CHECK_SUBMIT_FAIL_USER);
}
// if (!Objects.equals(NumberUtils.parseLong(stockCheck.getCreator()), loginUserId)) {
// throw exception(STOCK_CHECK_SUBMIT_FAIL_USER);
// }
if (!ErpAuditStatus.DRAFT.getStatus().equals(stockCheck.getStatus())
&& !ErpAuditStatus.UN_APPROVE.getStatus().equals(stockCheck.getStatus())) {
throw exception(STOCK_CHECK_SUBMIT_FAIL_STATUS);
@ -214,11 +214,12 @@ public class ErpStockCheckServiceImpl implements ErpStockCheckService {
// if (!ErpAuditStatus.PROCESS.getStatus().equals(stockCheck.getStatus())) {
// throw exception(STOCK_CHECK_AUDIT_FAIL_STATUS);
// }
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
Integer fromStatus = stockCheck.getStatus();
if (!needAudit()) {
int updateCount = stockCheckMapper.updateByIdAndStatus(stockCheck.getId(), fromStatus,
new ErpStockCheckDO().setNeedAudit(false).setStatus(ErpAuditStatus.APPROVE.getStatus()));
new ErpStockCheckDO().setNeedAudit(false).setStatus(ErpAuditStatus.APPROVE.getStatus()).setAuditUserId(loginUserId));
if (updateCount == 0) {
throw exception(STOCK_CHECK_AUDIT_FAIL_STATUS);
}
@ -230,18 +231,17 @@ public class ErpStockCheckServiceImpl implements ErpStockCheckService {
return;
}
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
boolean adminCanAudit = permissionApi.hasAnyRoles(loginUserId, RoleCodeEnum.SUPER_ADMIN.getCode());
if (!Objects.equals(stockCheck.getAuditUserId(), loginUserId) && !adminCanAudit) {
throw exception(STOCK_CHECK_AUDIT_FAIL_USER);
}
// if (!Objects.equals(stockCheck.getAuditUserId(), loginUserId) && !adminCanAudit) {
// throw exception(STOCK_CHECK_AUDIT_FAIL_USER);
// }
if (!ErpAuditStatus.APPROVE.getStatus().equals(auditReqVO.getStatus())
&& !ErpAuditStatus.UN_APPROVE.getStatus().equals(auditReqVO.getStatus())) {
throw exception(STOCK_CHECK_AUDIT_FAIL_RESULT);
}
int updateCount = stockCheckMapper.updateByIdAndStatus(stockCheck.getId(), fromStatus,
new ErpStockCheckDO().setNeedAudit(true).setStatus(auditReqVO.getStatus()));
new ErpStockCheckDO().setNeedAudit(true).setStatus(auditReqVO.getStatus()).setAuditUserId(loginUserId));
if (updateCount == 0) {
throw exception(STOCK_CHECK_AUDIT_FAIL_STATUS);
}
@ -268,9 +268,9 @@ public class ErpStockCheckServiceImpl implements ErpStockCheckService {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockCheck.getStatus())) {
throw exception(STOCK_CHECK_DELETE_FAIL_APPROVE, stockCheck.getNo());
}
if (ErpAuditStatus.PROCESS.getStatus().equals(stockCheck.getStatus())) {
throw exception(STOCK_CHECK_DELETE_FAIL_PROCESS, stockCheck.getNo());
}
// if (ErpAuditStatus.PROCESS.getStatus().equals(stockCheck.getStatus())) {
// throw exception(STOCK_CHECK_DELETE_FAIL_PROCESS, stockCheck.getNo());
// }
});
stockChecks.forEach(stockCheck -> {

@ -42,8 +42,6 @@ import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
import cn.iocoder.yudao.module.erp.service.warehousearea.WarehouseAreaService;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -104,8 +102,6 @@ public class ErpStockInServiceImpl implements ErpStockInService {
@Resource
private AdminUserApi adminUserApi;
@Resource
private PermissionApi permissionApi;
@Resource
private ConfigApi configApi;
@Override
@ -125,7 +121,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
}
boolean needAudit = needAudit();
Integer status = ErpAuditStatus.DRAFT.getStatus();
Integer status = ErpAuditStatus.PROCESS.getStatus();
ErpStockInDO stockIn = BeanUtils.toBean(createReqVO, ErpStockInDO.class, in -> in
.setNo(no)
.setNeedAudit(needAudit)
@ -159,9 +155,9 @@ public class ErpStockInServiceImpl implements ErpStockInService {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockIn.getStatus())) {
throw exception(STOCK_IN_UPDATE_FAIL_APPROVE, stockIn.getNo());
}
if (ErpAuditStatus.PROCESS.getStatus().equals(stockIn.getStatus())) {
throw exception(STOCK_IN_UPDATE_FAIL_PROCESS, stockIn.getNo());
}
// if (ErpAuditStatus.PROCESS.getStatus().equals(stockIn.getStatus())) {
// throw exception(STOCK_IN_UPDATE_FAIL_PROCESS, stockIn.getNo());
// }
if (updateReqVO.getStockUserId() != null) {
adminUserApi.validateUser(updateReqVO.getStockUserId());
}
@ -211,9 +207,9 @@ public class ErpStockInServiceImpl implements ErpStockInService {
public void submitStockInAudit(ErpStockInSubmitReqVO submitReqVO) {
ErpStockInDO stockIn = validateStockInExists(submitReqVO.getId());
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
if (!Objects.equals(NumberUtils.parseLong(stockIn.getCreator()), loginUserId)) {
throw exception(STOCK_IN_SUBMIT_FAIL_USER);
}
// if (!Objects.equals(NumberUtils.parseLong(stockIn.getCreator()), loginUserId)) {
// throw exception(STOCK_IN_SUBMIT_FAIL_USER);
// }
if (!ErpAuditStatus.DRAFT.getStatus().equals(stockIn.getStatus())
&& !ErpAuditStatus.UN_APPROVE.getStatus().equals(stockIn.getStatus())) {
throw exception(STOCK_IN_SUBMIT_FAIL_STATUS);
@ -243,9 +239,11 @@ public class ErpStockInServiceImpl implements ErpStockInService {
// }
Integer fromStatus = stockIn.getStatus();
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
if (!needAudit()) {
int updateCount = stockInMapper.updateByIdAndStatus(stockIn.getId(), fromStatus,
new ErpStockInDO().setNeedAudit(false).setStatus(ErpAuditStatus.APPROVE.getStatus()));
new ErpStockInDO().setNeedAudit(false).setAuditUserId(loginUserId)
.setStatus(ErpAuditStatus.APPROVE.getStatus()).setAuditUserId(loginUserId));
if (updateCount == 0) {
throw exception(STOCK_IN_AUDIT_FAIL_STATUS);
}
@ -256,18 +254,13 @@ public class ErpStockInServiceImpl implements ErpStockInService {
auditReqVO.getRemark() != null ? auditReqVO.getRemark() : "无需审核,审核时自动入库");
return;
}
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
boolean adminCanAudit = permissionApi.hasAnyRoles(loginUserId, RoleCodeEnum.SUPER_ADMIN.getCode());
if (!Objects.equals(stockIn.getAuditUserId(), loginUserId) && !adminCanAudit) {
throw exception(STOCK_IN_AUDIT_FAIL_USER);
}
if (!ErpAuditStatus.APPROVE.getStatus().equals(auditReqVO.getStatus())
&& !ErpAuditStatus.UN_APPROVE.getStatus().equals(auditReqVO.getStatus())) {
throw exception(STOCK_IN_AUDIT_FAIL_RESULT);
}
int updateCount = stockInMapper.updateByIdAndStatus(stockIn.getId(), fromStatus,
new ErpStockInDO().setNeedAudit(true).setStatus(auditReqVO.getStatus()));
new ErpStockInDO().setNeedAudit(true).setAuditUserId(loginUserId).setStatus(auditReqVO.getStatus()));
if (updateCount == 0) {
throw exception(STOCK_IN_AUDIT_FAIL_STATUS);
}
@ -293,9 +286,9 @@ public class ErpStockInServiceImpl implements ErpStockInService {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockIn.getStatus())) {
throw exception(STOCK_IN_DELETE_FAIL_APPROVE, stockIn.getNo());
}
if (ErpAuditStatus.PROCESS.getStatus().equals(stockIn.getStatus())) {
throw exception(STOCK_IN_DELETE_FAIL_PROCESS, stockIn.getNo());
}
// if (ErpAuditStatus.PROCESS.getStatus().equals(stockIn.getStatus())) {
// throw exception(STOCK_IN_DELETE_FAIL_PROCESS, stockIn.getNo());
// }
});
stockIns.forEach(stockIn -> {
stockInMapper.deleteById(stockIn.getId());

@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.erp.service.stock;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMovePageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSubmitReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveApproveRecordDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveItemDO;
@ -40,6 +43,12 @@ public interface ErpStockMoveService {
*/
void updateStockMoveStatus(Long id, Integer status);
void submitStockMoveAudit(ErpStockMoveSubmitReqVO submitReqVO);
void auditStockMove(ErpStockMoveAuditReqVO auditReqVO);
List<ErpStockMoveApproveRecordDO> getStockMoveApproveRecordList(Long id);
/**
*
*
@ -81,4 +90,4 @@ public interface ErpStockMoveService {
*/
List<ErpStockMoveItemDO> getStockMoveItemListByMoveIds(Collection<Long> moveIds);
}
}

@ -3,19 +3,33 @@ package cn.iocoder.yudao.module.erp.service.stock;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveAuditReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMovePageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSubmitReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveApproveRecordDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.warehousearea.WarehouseAreaDO;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMoveApproveRecordMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMoveItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMoveMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.stock.ErpStockMoveApproveActionEnum;
import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
import cn.iocoder.yudao.module.erp.service.warehousearea.WarehouseAreaService;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@ -26,13 +40,14 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
// TODO 芋艿:记录操作日志
// TODO 记录操作日志
/**
* ERP Service
@ -43,10 +58,16 @@ import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
@Validated
public class ErpStockMoveServiceImpl implements ErpStockMoveService {
private static final String AUDIT_CONFIG_CATEGORY = "biz";
private static final String AUDIT_CONFIG_KEY = "transferAduit";
private static final String AUDIT_CONFIG_DISABLE_VALUE = "0";
@Resource
private ErpStockMoveMapper stockMoveMapper;
@Resource
private ErpStockMoveItemMapper stockMoveItemMapper;
@Resource
private ErpStockMoveApproveRecordMapper stockMoveApproveRecordMapper;
@Resource
private ErpNoRedisDAO noRedisDAO;
@ -57,11 +78,19 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
private ErpWarehouseService warehouseService;
@Resource
private ErpStockRecordService stockRecordService;
@Resource
private WarehouseAreaService warehouseAreaService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private ConfigApi configApi;
@Resource
private PermissionApi permissionApi;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createStockMove(ErpStockMoveSaveReqVO createReqVO) {
// 1.1 校验出库项的有效性
// 1.1 校验调拨项的有效性
List<ErpStockMoveItemDO> stockMoveItems = validateStockMoveItems(createReqVO.getItems());
// 1.2 生成调拨单号,并校验唯一性
String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_MOVE_NO_PREFIX);
@ -69,13 +98,18 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
throw exception(STOCK_MOVE_NO_EXISTS);
}
// 2.1 插入出库单
if (createReqVO.getAuditUserId() != null) {
adminUserApi.validateUser(createReqVO.getAuditUserId());
}
boolean needAudit = needAudit();
ErpStockMoveDO stockMove = BeanUtils.toBean(createReqVO, ErpStockMoveDO.class, in -> in
.setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
.setNo(no)
.setNeedAudit(needAudit)
.setStatus(ErpAuditStatus.PROCESS.getStatus())
.setTotalCount(getSumValue(stockMoveItems, ErpStockMoveItemDO::getCount, BigDecimal::add))
.setTotalPrice(getSumValue(stockMoveItems, ErpStockMoveItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)));
stockMoveMapper.insert(stockMove);
// 2.2 插入出库单项
// 2.2 插入调拨单项
stockMoveItems.forEach(o -> o.setMoveId(stockMove.getId()));
stockMoveItemMapper.insertBatch(stockMoveItems);
return stockMove.getId();
@ -89,38 +123,119 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) {
throw exception(STOCK_MOVE_UPDATE_FAIL_APPROVE, stockMove.getNo());
}
// 1.2 校验出库项的有效性
// if (ErpAuditStatus.PROCESS.getStatus().equals(stockMove.getStatus())) {
// throw exception(STOCK_MOVE_UPDATE_FAIL_PROCESS, stockMove.getNo());
// }
List<ErpStockMoveItemDO> stockMoveItems = validateStockMoveItems(updateReqVO.getItems());
if (updateReqVO.getAuditUserId() != null) {
adminUserApi.validateUser(updateReqVO.getAuditUserId());
}
boolean needAudit = needAudit();
// 2.1 更新出库单
ErpStockMoveDO updateObj = BeanUtils.toBean(updateReqVO, ErpStockMoveDO.class, in -> in
.setNeedAudit(needAudit)
.setTotalCount(getSumValue(stockMoveItems, ErpStockMoveItemDO::getCount, BigDecimal::add))
.setTotalPrice(getSumValue(stockMoveItems, ErpStockMoveItemDO::getTotalPrice, BigDecimal::add)));
stockMoveMapper.updateById(updateObj);
// 2.2 更新出库单项
// 2.2 更新调拨单项
updateStockMoveItemList(updateReqVO.getId(), stockMoveItems);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStockMoveStatus(Long id, Integer status) {
boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
// 1.1 校验存在
ErpStockMoveDO stockMove = validateStockMoveExists(id);
// 1.2 校验状态
if (stockMove.getStatus().equals(status)) {
throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL);
boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
if (approve) {
if (!ErpAuditStatus.PROCESS.getStatus().equals(stockMove.getStatus())) {
throw exception(STOCK_MOVE_APPROVE_FAIL);
}
} else {
if (!ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) {
throw exception(STOCK_MOVE_PROCESS_FAIL);
}
}
// 2. 更新状态
int updateCount = stockMoveMapper.updateByIdAndStatus(id, stockMove.getStatus(),
new ErpStockMoveDO().setStatus(status));
if (updateCount == 0) {
throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL);
}
// 3. 变更库存
List<ErpStockMoveItemDO> stockMoveItems = stockMoveItemMapper.selectListByMoveId(id);
operateStockMoveEffect(stockMove, stockMoveItems, approve);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void submitStockMoveAudit(ErpStockMoveSubmitReqVO submitReqVO) {
ErpStockMoveDO stockMove = validateStockMoveExists(submitReqVO.getId());
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
// if (!Objects.equals(NumberUtils.parseLong(stockMove.getCreator()), loginUserId)) {
// throw exception(STOCK_MOVE_SUBMIT_FAIL_USER);
// }
if (!ErpAuditStatus.DRAFT.getStatus().equals(stockMove.getStatus())
&& !ErpAuditStatus.UN_APPROVE.getStatus().equals(stockMove.getStatus())) {
throw exception(STOCK_MOVE_SUBMIT_FAIL_STATUS);
}
Integer fromStatus = stockMove.getStatus();
Long auditUserId = submitReqVO.getAuditUserId() != null ? submitReqVO.getAuditUserId() : stockMove.getAuditUserId();
if (auditUserId != null) {
adminUserApi.validateUser(auditUserId);
}
int updateCount = stockMoveMapper.updateByIdAndStatus(stockMove.getId(), fromStatus,
new ErpStockMoveDO().setAuditUserId(auditUserId).setStatus(ErpAuditStatus.PROCESS.getStatus()));
if (updateCount == 0) {
throw exception(STOCK_MOVE_SUBMIT_FAIL_STATUS);
}
createApproveRecord(stockMove.getId(), ErpStockMoveApproveActionEnum.SUBMIT, fromStatus,
ErpAuditStatus.PROCESS.getStatus(), auditUserId, submitReqVO.getRemark());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void auditStockMove(ErpStockMoveAuditReqVO auditReqVO) {
ErpStockMoveDO stockMove = validateStockMoveExists(auditReqVO.getId());
Integer fromStatus = stockMove.getStatus();
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
if (!ErpAuditStatus.PROCESS.getStatus().equals(fromStatus) && needAudit()) {
throw exception(STOCK_MOVE_AUDIT_FAIL_STATUS);
}
if (!needAudit()) {
int updateCount = stockMoveMapper.updateByIdAndStatus(stockMove.getId(), fromStatus,
new ErpStockMoveDO().setNeedAudit(false).setStatus(ErpAuditStatus.APPROVE.getStatus()).setAuditUserId(loginUserId));
if (updateCount == 0) {
throw exception(STOCK_MOVE_AUDIT_FAIL_STATUS);
}
List<ErpStockMoveItemDO> stockMoveItems = stockMoveItemMapper.selectListByMoveId(stockMove.getId());
operateStockMoveEffect(stockMove, stockMoveItems, true);
createApproveRecord(stockMove.getId(), ErpStockMoveApproveActionEnum.AUTO_APPROVE, fromStatus,
ErpAuditStatus.APPROVE.getStatus(), NumberUtils.parseLong(stockMove.getCreator()),
auditReqVO.getRemark() != null ? auditReqVO.getRemark() : "无需审核,审核时自动调拨");
return;
}
boolean adminCanAudit = permissionApi.hasAnyRoles(loginUserId, RoleCodeEnum.SUPER_ADMIN.getCode());
// if (!Objects.equals(stockMove.getAuditUserId(), loginUserId) && !adminCanAudit) {
// throw exception(STOCK_MOVE_AUDIT_FAIL_USER);
// }
if (!ErpAuditStatus.APPROVE.getStatus().equals(auditReqVO.getStatus())
&& !ErpAuditStatus.UN_APPROVE.getStatus().equals(auditReqVO.getStatus())) {
throw exception(STOCK_MOVE_AUDIT_FAIL_RESULT);
}
int updateCount = stockMoveMapper.updateByIdAndStatus(stockMove.getId(), fromStatus,
new ErpStockMoveDO().setNeedAudit(true).setStatus(auditReqVO.getStatus()).setAuditUserId(loginUserId));
if (updateCount == 0) {
throw exception(STOCK_MOVE_AUDIT_FAIL_STATUS);
}
List<ErpStockMoveItemDO> stockMoveItems = stockMoveItemMapper.selectListByMoveId(stockMove.getId());
if (ErpAuditStatus.APPROVE.getStatus().equals(auditReqVO.getStatus())) {
operateStockMoveEffect(stockMove, stockMoveItems, true);
}
createApproveRecord(stockMove.getId(), ErpAuditStatus.APPROVE.getStatus().equals(auditReqVO.getStatus())
? ErpStockMoveApproveActionEnum.APPROVE : ErpStockMoveApproveActionEnum.REJECT,
fromStatus, auditReqVO.getStatus(), NumberUtils.parseLong(stockMove.getCreator()), auditReqVO.getRemark());
}
private void operateStockMoveEffect(ErpStockMoveDO stockMove, List<ErpStockMoveItemDO> stockMoveItems, boolean approve) {
Integer fromBizType = approve ? ErpStockRecordBizTypeEnum.MOVE_OUT.getType()
: ErpStockRecordBizTypeEnum.MOVE_OUT_CANCEL.getType();
Integer toBizType = approve ? ErpStockRecordBizTypeEnum.MOVE_IN.getType()
@ -128,13 +243,15 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
stockMoveItems.forEach(stockMoveItem -> {
BigDecimal fromCount = approve ? stockMoveItem.getCount().negate() : stockMoveItem.getCount();
BigDecimal toCount = approve ? stockMoveItem.getCount() : stockMoveItem.getCount().negate();
ErpProductDO productDO = productService.getProduct(stockMoveItem.getProductId());
ErpProductRespVO productDO = productService.getProduct(stockMoveItem.getProductId());
stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
stockMoveItem.getProductId(),productDO.getCategoryId(), stockMoveItem.getFromWarehouseId(), fromCount,
stockMoveItem.getProductId(), productDO.getCategoryId(), productDO.getCategoryType(),
stockMoveItem.getFromWarehouseId(), stockMoveItem.getFromAreaId(), stockMoveItem.getFromAreaName(), fromCount,
fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),stockMove.getMoveTime()));
stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
stockMoveItem.getProductId(),productDO.getCategoryId(), stockMoveItem.getToWarehouseId(), toCount,
stockMoveItem.getProductId(), productDO.getCategoryId(), productDO.getCategoryType(),
stockMoveItem.getToWarehouseId(), stockMoveItem.getToAreaId(), stockMoveItem.getToAreaName(), toCount,
toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo(),stockMove.getMoveTime()));
});
}
@ -147,12 +264,24 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
// 1.2 校验仓库存在
warehouseService.validWarehouseList(convertSetByFlatMap(list,
item -> Stream.of(item.getFromWarehouseId(), item.getToWarehouseId())));
Map<Long, WarehouseAreaDO> areaMap = warehouseAreaService.getWarehouseAreaMap(convertSetByFlatMap(list,
item -> Stream.of(item.getFromAreaId(), item.getToAreaId())));
// 2. 转化为 ErpStockMoveItemDO 列表
return convertList(list, o -> BeanUtils.toBean(o, ErpStockMoveItemDO.class, item -> item
.setProductUnitId(productMap.get(item.getProductId()).getUnitId())
.setFromAreaName(validateMoveArea(item.getFromWarehouseId(), item.getFromAreaId(), areaMap).getAreaName())
.setToAreaName(validateMoveArea(item.getToWarehouseId(), item.getToAreaId(), areaMap).getAreaName())
.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()))));
}
private WarehouseAreaDO validateMoveArea(Long warehouseId, Long areaId, Map<Long, WarehouseAreaDO> areaMap) {
WarehouseAreaDO area = areaMap.get(areaId);
if (area == null || !Objects.equals(area.getWarehouseId(), warehouseId)) {
throw exception(WAREHOUSE_AREA_NOT_EXISTS);
}
return area;
}
private void updateStockMoveItemList(Long id, List<ErpStockMoveItemDO> newList) {
// 第一步,对比新老数据,获得添加、修改、删除的列表
List<ErpStockMoveItemDO> oldList = stockMoveItemMapper.selectListByMoveId(id);
@ -184,17 +313,37 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) {
throw exception(STOCK_MOVE_DELETE_FAIL_APPROVE, stockMove.getNo());
}
// if (ErpAuditStatus.PROCESS.getStatus().equals(stockMove.getStatus())) {
// throw exception(STOCK_MOVE_DELETE_FAIL_PROCESS, stockMove.getNo());
// }
});
// 2. 遍历删除,并记录操作日志
stockMoves.forEach(stockMove -> {
// 2.1 删除出库
// 2.1 删除调拨
stockMoveMapper.deleteById(stockMove.getId());
// 2.2 删除出库单项
// 2.2 删除调拨单项
stockMoveItemMapper.deleteByMoveId(stockMove.getId());
stockMoveApproveRecordMapper.deleteByStockMoveId(stockMove.getId());
});
}
private boolean needAudit() {
return !AUDIT_CONFIG_DISABLE_VALUE.equals(configApi.getConfigValueByCategoryAndKey(AUDIT_CONFIG_CATEGORY, AUDIT_CONFIG_KEY));
}
private void createApproveRecord(Long stockMoveId, ErpStockMoveApproveActionEnum action, Integer fromStatus,
Integer toStatus, Long targetUserId, String remark) {
stockMoveApproveRecordMapper.insert(ErpStockMoveApproveRecordDO.builder()
.stockMoveId(stockMoveId)
.actionType(action.getActionType())
.fromStatus(fromStatus)
.toStatus(toStatus)
.targetUserId(targetUserId)
.remark(remark)
.build());
}
private ErpStockMoveDO validateStockMoveExists(Long id) {
ErpStockMoveDO stockMove = stockMoveMapper.selectById(id);
if (stockMove == null) {
@ -213,7 +362,12 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
return stockMoveMapper.selectPage(pageReqVO);
}
// ==================== 出库项 ====================
@Override
public List<ErpStockMoveApproveRecordDO> getStockMoveApproveRecordList(Long id) {
return stockMoveApproveRecordMapper.selectListByStockMoveId(id);
}
// ==================== 调拨项 ====================
@Override
public List<ErpStockMoveItemDO> getStockMoveItemListByMoveId(Long moveId) {
@ -228,4 +382,4 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
return stockMoveItemMapper.selectListByMoveIds(moveIds);
}
}
}

@ -144,7 +144,7 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
}
boolean needAudit = needAudit();
Integer status = ErpAuditStatus.DRAFT.getStatus();
Integer status = ErpAuditStatus.PROCESS.getStatus();
ErpStockOutDO stockOut = BeanUtils.toBean(createReqVO, ErpStockOutDO.class, out -> out
.setNo(no)
.setNeedAudit(needAudit)
@ -180,9 +180,9 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockOut.getStatus())) {
throw exception(STOCK_OUT_UPDATE_FAIL_APPROVE, stockOut.getNo());
}
if (ErpAuditStatus.PROCESS.getStatus().equals(stockOut.getStatus())) {
throw exception(STOCK_OUT_UPDATE_FAIL_PROCESS, stockOut.getNo());
}
// if (ErpAuditStatus.PROCESS.getStatus().equals(stockOut.getStatus())) {
// throw exception(STOCK_OUT_UPDATE_FAIL_PROCESS, stockOut.getNo());
// }
if (updateReqVO.getCustomerId() != null) {
customerService.validateCustomer(updateReqVO.getCustomerId());
}
@ -237,9 +237,9 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
public void submitStockOutAudit(ErpStockOutSubmitReqVO submitReqVO) {
ErpStockOutDO stockOut = validateStockOutExists(submitReqVO.getId());
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
if (!Objects.equals(NumberUtils.parseLong(stockOut.getCreator()), loginUserId)) {
throw exception(STOCK_OUT_SUBMIT_FAIL_USER);
}
// if (!Objects.equals(NumberUtils.parseLong(stockOut.getCreator()), loginUserId)) {
// throw exception(STOCK_OUT_SUBMIT_FAIL_USER);
// }
if (!ErpAuditStatus.DRAFT.getStatus().equals(stockOut.getStatus())
&& !ErpAuditStatus.UN_APPROVE.getStatus().equals(stockOut.getStatus())) {
throw exception(STOCK_OUT_SUBMIT_FAIL_STATUS);
@ -267,11 +267,12 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
// if (!ErpAuditStatus.PROCESS.getStatus().equals(stockOut.getStatus())) {
// throw exception(STOCK_OUT_AUDIT_FAIL_STATUS);
// }
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
Integer fromStatus = stockOut.getStatus();
if (!needAudit()) {
int updateCount = stockOutMapper.updateByIdAndStatus(stockOut.getId(), fromStatus,
new ErpStockOutDO().setNeedAudit(false).setStatus(ErpAuditStatus.APPROVE.getStatus()));
new ErpStockOutDO().setNeedAudit(false).setStatus(ErpAuditStatus.APPROVE.getStatus()).setAuditUserId(loginUserId));
if (updateCount == 0) {
throw exception(STOCK_OUT_AUDIT_FAIL_STATUS);
}
@ -283,12 +284,11 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
return;
}
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
boolean adminCanSubmit = permissionApi.hasAnyRoles(loginUserId, RoleCodeEnum.SUPER_ADMIN.getCode());
if (!Objects.equals(stockOut.getAuditUserId(), loginUserId) && !adminCanSubmit) {
throw exception(STOCK_OUT_AUDIT_FAIL_USER);
}
// if (!Objects.equals(stockOut.getAuditUserId(), loginUserId) && !adminCanSubmit) {
// throw exception(STOCK_OUT_AUDIT_FAIL_USER);
// }
if (!ErpAuditStatus.APPROVE.getStatus().equals(auditReqVO.getStatus())
&& !ErpAuditStatus.UN_APPROVE.getStatus().equals(auditReqVO.getStatus())) {
@ -296,7 +296,7 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
}
int updateCount = stockOutMapper.updateByIdAndStatus(stockOut.getId(), fromStatus,
new ErpStockOutDO().setNeedAudit(true).setStatus(auditReqVO.getStatus()));
new ErpStockOutDO().setNeedAudit(true).setStatus(auditReqVO.getStatus()).setAuditUserId(loginUserId));
if (updateCount == 0) {
throw exception(STOCK_OUT_AUDIT_FAIL_STATUS);
}
@ -322,9 +322,9 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockOut.getStatus())) {
throw exception(STOCK_OUT_DELETE_FAIL_APPROVE, stockOut.getNo());
}
if (ErpAuditStatus.PROCESS.getStatus().equals(stockOut.getStatus())) {
throw exception(STOCK_OUT_DELETE_FAIL_PROCESS, stockOut.getNo());
}
// if (ErpAuditStatus.PROCESS.getStatus().equals(stockOut.getStatus())) {
// throw exception(STOCK_OUT_DELETE_FAIL_PROCESS, stockOut.getNo());
// }
});
stockOuts.forEach(stockOut -> {
stockOutMapper.deleteById(stockOut.getId());

Loading…
Cancel
Save