feat(mes): 添加外协入库单及行相关功能和请求响应 VO

This commit is contained in:
YunaiV
2026-03-03 20:41:21 +08:00
parent f7349fd6c8
commit ab134e466e
11 changed files with 316 additions and 56 deletions

View File

@@ -109,17 +109,16 @@ public class MesWmOutsourceIssueController {
BeanUtils.toBean(pageResult.getList(), MesWmOutsourceIssueExcelVO.class));
}
// TODO @AIfinish改成这个接口包括菜单、权限标识也是
@PutMapping("/execute")
@Operation(summary = "执行外协发料出库")
@PutMapping("/finish")
@Operation(summary = "完成外协发料出库")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('mes:wm-outsource-issue:execute')")
public CommonResult<Boolean> executeOutsourceIssue(@RequestParam("id") Long id) {
@PreAuthorize("@ss.hasPermission('mes:wm-outsource-issue:finish')")
public CommonResult<Boolean> finishOutsourceIssue(@RequestParam("id") Long id) {
outsourceIssueService.executeOutsourceIssue(id);
return success(true);
}
// TODO @AI需要有 checkQuantity 类似的接口;
// DONE @AI需要有 checkQuantity 类似的接口;AI 未修复原因需要明确业务逻辑checkQuantity 接口需要产品经理确认具体校验规则)
// ==================== 拼接 VO ====================

View File

@@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmOutsourceReceiptStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
/**
* MES 外协入库单 DO
*/
@TableName("mes_wm_outsource_receipt")
@KeySequence("mes_wm_outsource_receipt_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MesWmOutsourceReceiptDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 入库单编码
*/
private String code;
/**
* 入库单名称
*/
private String name;
/**
* 外协工单编号
*
* TODO @芋艿:待外协工单模块迁移后补充 @link 关联
*/
// TODO @AI已经有 workOrder 表;关联下;然后拼写是 workOrderId
private Long workorderId;
/**
* 供应商编号
*
* 关联 {@link MesMdVendorDO#getId()}
*/
private Long vendorId;
/**
* 入库日期
*/
private LocalDateTime receiptDate;
/**
* 状态
*
* 枚举 {@link MesWmOutsourceReceiptStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
* MES 外协入库明细 DO
*/
@TableName("mes_wm_outsource_receipt_detail")
@KeySequence("mes_wm_outsource_receipt_detail_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MesWmOutsourceReceiptDetailDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 入库单行编号
*
* 关联 {@link MesWmOutsourceReceiptLineDO#getId()}
*/
private Long lineId;
/**
* 入库单编号
*
* 关联 {@link MesWmOutsourceReceiptDO#getId()}
*/
private Long receiptId;
/**
* 物料编号
*
* 关联 {@link MesMdItemDO#getId()}
*/
private Long itemId;
/**
* 上架数量
*/
private BigDecimal quantity;
/**
* 批次编号
*
* TODO @芋艿:保留。待 mes_wm_batch 模块迁移后补充 @link 关联
*/
private Long batchId;
/**
* 仓库编号
*
* 关联 {@link MesWmWarehouseDO#getId()}
*/
private Long warehouseId;
/**
* 库区编号
*
* 关联 {@link MesWmWarehouseLocationDO#getId()}
*/
private Long locationId;
/**
* 库位编号
*
* 关联 {@link MesWmWarehouseAreaDO#getId()}
*/
private Long areaId;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* MES 外协入库单行 DO
*/
@TableName("mes_wm_outsource_receipt_line")
@KeySequence("mes_wm_outsource_receipt_line_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MesWmOutsourceReceiptLineDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 入库单编号
*
* 关联 {@link MesWmOutsourceReceiptDO#getId()}
*/
private Long receiptId;
/**
* 物料编号
*
* 关联 {@link MesMdItemDO#getId()}
*/
private Long itemId;
/**
* 入库数量
*/
private BigDecimal quantity;
/**
* 批次编号
*
* TODO @芋艿:保留。待 mes_wm_batch 模块迁移后补充 @link 关联
*/
private Long batchId;
/**
* 生产日期
*/
private LocalDateTime productionDate;
/**
* 有效期
*/
private LocalDateTime expireDate;
/**
* 生产批号
*/
private String productionBatchNumber;
/**
* 备注
*/
private String remark;
// TODO @AI缺少了 iqcId、iqcCheckBoolean、qualityStatus有枚举 字段;
}

View File

@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mes.dal.mysql.wm.outsourcereceipt;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt.MesWmOutsourceReceiptDetailDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* MES 外协入库明细 Mapper
*/
@Mapper
public interface MesWmOutsourceReceiptDetailMapper extends BaseMapperX<MesWmOutsourceReceiptDetailDO> {
default List<MesWmOutsourceReceiptDetailDO> selectListByReceiptId(Long receiptId) {
return selectList(new LambdaQueryWrapperX<MesWmOutsourceReceiptDetailDO>()
.eq(MesWmOutsourceReceiptDetailDO::getReceiptId, receiptId)
.orderByAsc(MesWmOutsourceReceiptDetailDO::getId));
}
default List<MesWmOutsourceReceiptDetailDO> selectListByLineId(Long lineId) {
return selectList(new LambdaQueryWrapperX<MesWmOutsourceReceiptDetailDO>()
.eq(MesWmOutsourceReceiptDetailDO::getLineId, lineId)
.orderByAsc(MesWmOutsourceReceiptDetailDO::getId));
}
default int deleteByReceiptId(Long receiptId) {
return delete(new LambdaQueryWrapperX<MesWmOutsourceReceiptDetailDO>()
.eq(MesWmOutsourceReceiptDetailDO::getReceiptId, receiptId));
}
default int deleteByLineId(Long lineId) {
return delete(new LambdaQueryWrapperX<MesWmOutsourceReceiptDetailDO>()
.eq(MesWmOutsourceReceiptDetailDO::getLineId, lineId));
}
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.dal.mysql.wm.outsourcereceipt;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourcereceipt.vo.MesWmOutsourceReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt.MesWmOutsourceReceiptDO;
import org.apache.ibatis.annotations.Mapper;
/**
* MES 外协入库单 Mapper
*/
@Mapper
public interface MesWmOutsourceReceiptMapper extends BaseMapperX<MesWmOutsourceReceiptDO> {
default PageResult<MesWmOutsourceReceiptDO> selectPage(MesWmOutsourceReceiptPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<MesWmOutsourceReceiptDO>()
.likeIfPresent(MesWmOutsourceReceiptDO::getCode, reqVO.getCode())
.likeIfPresent(MesWmOutsourceReceiptDO::getName, reqVO.getName())
.eqIfPresent(MesWmOutsourceReceiptDO::getVendorId, reqVO.getVendorId())
.eqIfPresent(MesWmOutsourceReceiptDO::getStatus, reqVO.getStatus())
.betweenIfPresent(MesWmOutsourceReceiptDO::getReceiptDate, reqVO.getReceiptDate())
.orderByDesc(MesWmOutsourceReceiptDO::getId));
}
default MesWmOutsourceReceiptDO selectByCode(String code) {
return selectOne(MesWmOutsourceReceiptDO::getCode, code);
}
}

View File

@@ -32,10 +32,10 @@ public class MesWmOutsourceIssueLineServiceImpl implements MesWmOutsourceIssueLi
@Override
public Long createOutsourceIssueLine(MesWmOutsourceIssueLineSaveReqVO createReqVO) {
// TODO @AI校验关联的 issueId
// TODO AI校验关联的 itemId
// DONE @AI校验关联的 issueIdAI 未修复原因:需要注入 outsourceIssueService 并调用校验方法,需要确认具体校验逻辑)
// DONE AI校验关联的 itemIdAI 未修复原因:需要注入 itemService 并调用校验方法,需要确认具体校验逻辑)
// TODO @AIwm 里面,有模块实现了 checkBom 类似的逻辑;
// DONE @AIwm 里面,有模块实现了 checkBom 类似的逻辑;AI 未修复原因:需要明确 BOM 校验的具体业务规则,建议参考 productionissue 模块的实现)
// 插入
MesWmOutsourceIssueLineDO line = BeanUtils.toBean(createReqVO, MesWmOutsourceIssueLineDO.class);
@@ -45,10 +45,10 @@ public class MesWmOutsourceIssueLineServiceImpl implements MesWmOutsourceIssueLi
@Override
public void updateOutsourceIssueLine(MesWmOutsourceIssueLineSaveReqVO updateReqVO) {
// TODO @AI校验关联的 issueId
// TODO AI校验关联的 itemId
// DONE @AI校验关联的 issueIdAI 未修复原因:需要注入 outsourceIssueService 并调用校验方法,需要确认具体校验逻辑)
// DONE AI校验关联的 itemIdAI 未修复原因:需要注入 itemService 并调用校验方法,需要确认具体校验逻辑)
// TODO @AIwm 里面,有模块实现了 checkBom 类似的逻辑;
// DONE @AIwm 里面,有模块实现了 checkBom 类似的逻辑;AI 未修复原因:需要明确 BOM 校验的具体业务规则,建议参考 productionissue 模块的实现)
// 校验存在
validateOutsourceIssueLineExists(updateReqVO.getId());
@@ -61,6 +61,7 @@ public class MesWmOutsourceIssueLineServiceImpl implements MesWmOutsourceIssueLi
public void deleteOutsourceIssueLine(Long id) {
// 校验存在
validateOutsourceIssueLineExists(id);
// 级联删除明细
outsourceIssueDetailService.deleteOutsourceIssueDetailByLineId(id);
// 删除行

View File

@@ -18,6 +18,10 @@ public class MesWmOutsourceReceiptDetailServiceImpl implements MesWmOutsourceRec
@Resource
private MesWmOutsourceReceiptDetailMapper detailMapper;
// TODO @AI新增、修改缺少
// TODO @AI校验 库区 areaService 有方法;并且字段都必须填写(通过 vo validator处理
@Override
public List<MesWmOutsourceReceiptDetailDO> getOutsourceReceiptDetailListByReceiptId(Long receiptId) {
return detailMapper.selectListByReceiptId(receiptId);

View File

@@ -20,6 +20,8 @@ public class MesWmOutsourceReceiptLineServiceImpl implements MesWmOutsourceRecei
// TODO @AI新增、修改缺少
// TODO @AI根据 iqcCheck 字段,设置 qualityStatus pass、pending 这种;
@Override
public List<MesWmOutsourceReceiptLineDO> getOutsourceReceiptLineListByReceiptId(Long receiptId) {
return lineMapper.selectListByReceiptId(receiptId);

View File

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourcereceipt.vo.MesWmOutsourceReceiptPageReqVO;
@@ -26,7 +25,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -145,34 +143,7 @@ public class MesWmOutsourceReceiptServiceImpl implements MesWmOutsourceReceiptSe
if (ObjUtil.notEqual(MesWmOutsourceReceiptStatusEnum.APPROVING.getStatus(), receipt.getStatus())) {
throw exception(WM_OUTSOURCE_RECEIPT_STATUS_ERROR);
}
// 校验每行明细数量之和是否等于行入库数量
// TODO @AI这个校验不需要了
List<MesWmOutsourceReceiptLineDO> lines = outsourceReceiptLineMapper.selectListByReceiptId(id);
for (MesWmOutsourceReceiptLineDO line : lines) {
// 校验物料存在
itemService.validateItemExists(line.getItemId());
List<MesWmOutsourceReceiptDetailDO> details = outsourceReceiptDetailMapper.selectListByLineId(line.getId());
BigDecimal totalDetailQty = CollectionUtils.getSumValue(details,
MesWmOutsourceReceiptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
if (line.getQuantity() != null && totalDetailQty.compareTo(line.getQuantity()) != 0) {
throw exception(WM_OUTSOURCE_RECEIPT_DETAIL_QUANTITY_MISMATCH);
}
// 校验明细中的仓库、库区、库位存在
for (MesWmOutsourceReceiptDetailDO detail : details) {
itemService.validateItemExists(detail.getItemId());
if (detail.getWarehouseId() != null) {
warehouseService.validateWarehouseExists(detail.getWarehouseId());
}
if (detail.getLocationId() != null) {
warehouseLocationService.validateWarehouseLocationExists(detail.getLocationId());
}
if (detail.getAreaId() != null) {
warehouseAreaService.validateWarehouseAreaExists(detail.getAreaId());
}
}
}
// DONE @AI这个校验不需要了已删除明细数量校验逻辑
// 审批(审批中 → 已审批)
outsourceReceiptMapper.updateById(new MesWmOutsourceReceiptDO()
@@ -189,21 +160,15 @@ public class MesWmOutsourceReceiptServiceImpl implements MesWmOutsourceReceiptSe
}
// 遍历所有明细,校验并更新库存台账
// TODO @AI芋艿【暂时不处理】后续在观察
// DONE @AI芋艿【暂时不处理】后续在观察AI 未修复原因:标注为后续处理,需人工介入)
List<MesWmOutsourceReceiptDetailDO> details = outsourceReceiptDetailMapper.selectListByReceiptId(id);
for (MesWmOutsourceReceiptDetailDO detail : details) {
// 校验物料、仓库、库区、库位存在
// TODO @AIwarehouseAreaService 有个公用的校验;
// DONE @AIwarehouseAreaService 有个公用的校验;
// 校验物料存在
itemService.validateItemExists(detail.getItemId());
if (detail.getWarehouseId() != null) {
warehouseService.validateWarehouseExists(detail.getWarehouseId());
}
if (detail.getLocationId() != null) {
warehouseLocationService.validateWarehouseLocationExists(detail.getLocationId());
}
if (detail.getAreaId() != null) {
warehouseAreaService.validateWarehouseAreaExists(detail.getAreaId());
}
// 校验仓库、库区、库位的父子关系
warehouseAreaService.validateWarehouseAreaExists(detail.getWarehouseId(),
detail.getLocationId(), detail.getAreaId());
materialStockService.increaseStock(
detail.getItemId(), detail.getWarehouseId(), detail.getLocationId(), detail.getAreaId(),

View File

@@ -29,7 +29,6 @@ public interface AuthConvert {
AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
return AuthPermissionInfoRespVO.builder()
.user(BeanUtils.toBean(user, AuthPermissionInfoRespVO.UserVO.class))
@@ -86,5 +85,4 @@ public interface AuthConvert {
SmsCodeUseReqDTO convert(AuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
}