diff --git a/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/controller/BomController.java b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/controller/BomController.java index e4e9b0b..af0f94d 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/controller/BomController.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/controller/BomController.java @@ -3,6 +3,8 @@ package com.njzscloud.dispose.mfg.bom.controller; import com.njzscloud.common.core.utils.R; import com.njzscloud.common.mp.support.PageParam; import com.njzscloud.common.mp.support.PageResult; +import com.njzscloud.dispose.mfg.bom.pojo.dto.BomAddDto; +import com.njzscloud.dispose.mfg.bom.pojo.dto.BomQueryDto; import com.njzscloud.dispose.mfg.bom.pojo.entity.BomEntity; import com.njzscloud.dispose.mfg.bom.service.BomService; import lombok.RequiredArgsConstructor; @@ -13,6 +15,8 @@ import java.util.List; /** * 物料清单 + * + * @author ljw */ @Slf4j @RestController @@ -25,8 +29,8 @@ public class BomController { * 新增 */ @PostMapping("/add") - public R add(@RequestBody BomEntity bomEntity) { - bomService.add(bomEntity); + public R add(@RequestBody BomAddDto bomAddDTO) { + bomService.add(bomAddDTO); return R.success(); } @@ -60,7 +64,7 @@ public class BomController { * 分页查询 */ @GetMapping("/paging") - public R> paging(PageParam pageParam, BomEntity bomEntity) { - return R.success(bomService.paging(pageParam, bomEntity)); + public R> paging(PageParam pageParam, BomQueryDto queryDto) { + return R.success(bomService.paging(pageParam, queryDto)); } } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomAddDto.java b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomAddDto.java new file mode 100644 index 0000000..60af310 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomAddDto.java @@ -0,0 +1,44 @@ +package com.njzscloud.dispose.mfg.bom.pojo.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +/** + * BOM新增DTO + * 包含BOM主表信息和明细列表 + * @author ljw + */ +@Getter +@Setter +@ToString +public class BomAddDto { + + /** + * 主产品 Id;gds_goods.id + */ + private Long goodId; + + /** + * 物料清单版本号 + */ + private String bomVer; + + /** + * 是否可用;0-->否、1-->是 + */ + private Boolean canuse; + + /** + * 备注 + */ + private String memo; + + /** + * BOM明细列表 + */ + private List details; + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomDetailAddDto.java b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomDetailAddDto.java new file mode 100644 index 0000000..35cc0ae --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomDetailAddDto.java @@ -0,0 +1,46 @@ +package com.njzscloud.dispose.mfg.bom.pojo.dto; + +import com.njzscloud.dispose.mfg.bom.constant.MtlType; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; + +/** + * 物料清单明细 + * @author ljw + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class BomDetailAddDto { + + /** + * 产品 Id;gds_goods.id + */ + private Long goodsId; + + /** + * 物料类型,YuanLiao-->原料、FuChanPin-->副产品、ZhuChanPin-->主产品 + */ + private MtlType mtlType; + + /** + * 是否为产出;0-->否、1-->是 + */ + private Boolean chu; + + /** + * 消耗/产出量;如果为产出,则此值为 1 + */ + private BigDecimal quantity; + + /** + * 是否必需;0-->否、1-->是 + */ + private Boolean mandatory; + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomQueryDto.java b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomQueryDto.java new file mode 100644 index 0000000..d781fa7 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/pojo/dto/BomQueryDto.java @@ -0,0 +1,32 @@ +package com.njzscloud.dispose.mfg.bom.pojo.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * BOM查询DTO + * 用于分页查询的查询条件 + * @author ljw + */ +@Getter +@Setter +@ToString +public class BomQueryDto { + + /** + * 编码(模糊查询) + */ + private String sn; + + /** + * 主产品 Id;gds_goods.id + */ + private Long goodId; + + /** + * 是否可用;0-->否、1-->是 + */ + private Boolean canuse; + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/service/BomService.java b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/service/BomService.java index 88cd6b3..7425224 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/service/BomService.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/dispose/mfg/bom/service/BomService.java @@ -1,32 +1,65 @@ package com.njzscloud.dispose.mfg.bom.service; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njzscloud.common.core.ex.Exceptions; import com.njzscloud.common.mp.support.PageParam; import com.njzscloud.common.mp.support.PageResult; +import com.njzscloud.common.sn.support.SnUtil; +import com.njzscloud.dispose.mfg.bom.constant.MtlType; import com.njzscloud.dispose.mfg.bom.mapper.BomMapper; +import com.njzscloud.dispose.mfg.bom.pojo.dto.BomAddDto; +import com.njzscloud.dispose.mfg.bom.pojo.dto.BomDetailAddDto; +import com.njzscloud.dispose.mfg.bom.pojo.dto.BomQueryDto; +import com.njzscloud.dispose.mfg.bom.pojo.entity.BomDetailEntity; import com.njzscloud.dispose.mfg.bom.pojo.entity.BomEntity; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.List; /** * 物料清单 + * + * @author ljw */ @Slf4j @Service @RequiredArgsConstructor public class BomService extends ServiceImpl implements IService { + private final BomDetailService bomDetailService; + /** * 新增 */ - public void add(BomEntity bomEntity) { + @Transactional(rollbackFor = Exception.class) + public void add(BomAddDto bomAddDTO) { + BomEntity bomEntity = BeanUtil.copyProperties(bomAddDTO, BomEntity.class).setSn(this.generateSn()); + + // 校验版本号唯一性 + validateBomVersionUniqueness(bomEntity.getGoodId(), bomEntity.getBomVer(), null); + // 校验可用性(同一主产品ID只能有一个可用的BOM) + validateBomAvailability(bomEntity.getGoodId(), null); + + // 预校验明细数据 + validateBomDetails(bomAddDTO.getDetails(), bomEntity.getGoodId()); + + // 保存BOM主表 this.save(bomEntity); + + // 保存BOM明细 + for (BomDetailAddDto detail : bomAddDTO.getDetails()) { + // 直接保存,不使用BomDetailService的add方法(避免重复校验) + BomDetailEntity detailEntity = BeanUtil.copyProperties(detail, BomDetailEntity.class).setBomId(bomEntity.getId()); + bomDetailService.save(detailEntity); + } } /** @@ -54,7 +87,94 @@ public class BomService extends ServiceImpl implements ISe /** * 分页查询 */ - public PageResult paging(PageParam pageParam, BomEntity bomEntity) { - return PageResult.of(this.page(pageParam.toPage(), Wrappers.query(bomEntity))); + public PageResult paging(PageParam pageParam, BomQueryDto queryDto) { + return PageResult.of(this.page(pageParam.toPage(), Wrappers.lambdaQuery() + .like(StrUtil.isNotBlank(queryDto.getSn()), BomEntity::getSn, queryDto.getSn()) + .eq(queryDto.getGoodId() != null, BomEntity::getGoodId, queryDto.getGoodId()) + .eq(queryDto.getCanuse() != null, BomEntity::getCanuse, queryDto.getCanuse()))); + } + + /** + * 校验BOM版本号唯一性 + * + * @param goodId 主产品ID + * @param bomVer 版本号 + * @param excludeId 排除的ID(用于修改时排除自身) + */ + private void validateBomVersionUniqueness(Long goodId, String bomVer, Long excludeId) { + + long count = this.count(Wrappers.lambdaQuery() + .eq(BomEntity::getGoodId, goodId) + .eq(BomEntity::getBomVer, bomVer) + .ne(excludeId != null, BomEntity::getId, excludeId)); + + if (count > 0) { + throw Exceptions.clierr("同一主产品的BOM版本号必须唯一"); + } + } + + /** + * 校验BOM可用性(同一主产品ID只能有一个可用的BOM) + * + * @param goodId 主产品ID + * @param excludeId 排除的ID(用于修改时排除自身) + */ + private void validateBomAvailability(Long goodId, Long excludeId) { + long count = this.count(Wrappers.lambdaQuery() + .eq(BomEntity::getGoodId, goodId) + .eq(BomEntity::getCanuse, Boolean.TRUE) + .ne(excludeId != null, BomEntity::getId, excludeId)); + + if (count > 0) { + throw Exceptions.clierr("同一主产品只能有一个可用的BOM"); + } + } + + + /** + * 预校验BOM明细数据 + * + * @param details 明细列表 + * @param mainProductId 主产品ID + */ + private void validateBomDetails(List details, Long mainProductId) { + if (details == null || details.isEmpty()) { + throw Exceptions.clierr("必须包含至少一个明细"); + } + + boolean hasMainProduct = false; + + for (BomDetailAddDto detail : details) { + // 校验产出物料数量必须为1 + if (Boolean.TRUE.equals(detail.getChu()) && (detail.getQuantity() == null || detail.getQuantity().compareTo(BigDecimal.ONE) != 0)) { + throw Exceptions.clierr("产出物料的数量必须为1"); + } + + // 检查是否包含主产品明细 + if (detail.getGoodsId() != null && detail.getGoodsId().equals(mainProductId)) { + if (detail.getMtlType() == MtlType.ZhuChanPin) { + hasMainProduct = true; + } else { + throw Exceptions.clierr("与主产品ID相同的产品必须是主产品类型"); + } + } + } + + if (!hasMainProduct) { + throw Exceptions.clierr("明细中必须包含与主产品ID相同且类型为主产品的记录"); + } + } + + /** + * 生成编码 + * + * @return sn 编码 + */ + public String generateSn() { + String sn = SnUtil.next("Bom-SN"); + if (this.exists(Wrappers.lambdaQuery().eq(BomEntity::getSn, sn).eq(BomEntity::getDeleted, Boolean.FALSE))) { + this.generateSn(); + } + return sn; } }