package cn.iocoder.yudao.module.mes.service.safety; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material.*; import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyMaterialDO; import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyMaterialStockDO; import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyMaterialMapper; import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyMaterialStockMapper; import cn.iocoder.yudao.module.mes.enums.safety.SafetyMaterialCategoryEnum; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*; /** * 安全生产 - 应急物资台账与出入库 Service * *

覆盖电力绝缘用具、危化品、消防物资。出库时行锁校验库存,避免并发扣减为负。

* * @author 超级管理员 */ @Service @Validated public class SafetyMaterialService { @Resource private SafetyMaterialMapper materialMapper; @Resource private SafetyRefResolver refResolver; @Resource private SafetyMaterialStockMapper stockMapper; // ==================== 物资台账 ==================== public Long createMaterial(SafetyMaterialSaveReqVO reqVO) { reqVO.setId(null); String code = StrUtil.trim(reqVO.getCode()); if (materialMapper.selectByCode(code) != null) { throw exception(SAFETY_MATERIAL_CODE_DUPLICATE); } SafetyMaterialDO material = BeanUtils.toBean(reqVO, SafetyMaterialDO.class); material.setCode(code); material.setName(StrUtil.trim(reqVO.getName())); material.setUnit(StrUtil.blankToDefault(reqVO.getUnit(), "个")); applyManager(material, reqVO.getManagerId()); material.setStockQty(reqVO.getStockQty() == null ? BigDecimal.ZERO : reqVO.getStockQty()); material.setSafeStock(reqVO.getSafeStock() == null ? BigDecimal.ZERO : reqVO.getSafeStock()); materialMapper.insert(material); return material.getId(); } public void updateMaterial(SafetyMaterialSaveReqVO reqVO) { SafetyMaterialDO old = validateMaterialExists(reqVO.getId()); String code = StrUtil.trim(reqVO.getCode()); SafetyMaterialDO byCode = materialMapper.selectByCode(code); if (byCode != null && !byCode.getId().equals(old.getId())) { throw exception(SAFETY_MATERIAL_CODE_DUPLICATE); } SafetyMaterialDO material = BeanUtils.toBean(reqVO, SafetyMaterialDO.class); material.setCode(code); material.setName(StrUtil.trim(reqVO.getName())); applyManager(material, reqVO.getManagerId()); // 库存数量只能通过出入库单变动,禁止直接编辑 material.setStockQty(null); materialMapper.updateById(material); } public void deleteMaterial(Long id) { validateMaterialExists(id); materialMapper.deleteById(id); } public SafetyMaterialDO getMaterial(Long id) { return validateMaterialExists(id); } public PageResult getMaterialPage(SafetyMaterialPageReqVO reqVO) { return materialMapper.selectPage(reqVO); } /** * 保管人:从员工档案带出姓名落快照 */ private void applyManager(SafetyMaterialDO material, Long managerId) { material.setManagerId(managerId); material.setManager(managerId == null ? "" : refResolver.requireEmployee(managerId, SAFETY_EMPLOYEE_REQUIRED).getName()); } /** * 批量回填保管人最新姓名 */ public void fillManagers(List materials) { if (CollUtil.isEmpty(materials)) { return; } var employeeMap = refResolver.employeeMap(materials.stream().map(SafetyMaterialDO::getManagerId).toList()); materials.forEach(material -> { var employee = employeeMap.get(material.getManagerId()); if (employee != null) material.setManager(employee.getName()); }); } public static void fillMaterialDisplay(SafetyMaterialDO material, SafetyMaterialRespVO respVO) { respVO.setCategoryName(SafetyMaterialCategoryEnum.getNameByCategory(material.getCategory())); BigDecimal stock = material.getStockQty() == null ? BigDecimal.ZERO : material.getStockQty(); BigDecimal safe = material.getSafeStock() == null ? BigDecimal.ZERO : material.getSafeStock(); respVO.setBelowSafeStock(safe.signum() > 0 && stock.compareTo(safe) < 0); if (material.getExpireDate() != null) { respVO.setExpireRemainingDays(ChronoUnit.DAYS.between(LocalDate.now(), material.getExpireDate())); } } // ==================== 出入库 ==================== /** * 出入库登记:行锁读取库存 → 计算变动 → 写流水 → 回写库存 */ @Transactional(rollbackFor = Exception.class) public Long createStock(SafetyMaterialStockSaveReqVO reqVO) { SafetyMaterialDO material = materialMapper.selectByIdForUpdate(reqVO.getMaterialId()); if (material == null) { throw exception(SAFETY_MATERIAL_NOT_EXISTS); } BigDecimal before = material.getStockQty() == null ? BigDecimal.ZERO : material.getStockQty(); boolean inbound = "IN".equals(reqVO.getBizType()); BigDecimal after = inbound ? before.add(reqVO.getQuantity()) : before.subtract(reqVO.getQuantity()); if (!inbound && after.signum() < 0) { throw exception(SAFETY_MATERIAL_STOCK_NOT_ENOUGH); } SafetyMaterialStockDO stock = BeanUtils.toBean(reqVO, SafetyMaterialStockDO.class); stock.setId(null); stock.setMaterialName(material.getName()); stock.setCategory(material.getCategory()); stock.setQuantity(reqVO.getQuantity()); stock.setStockBefore(before); stock.setStockAfter(after); stock.setBizDate(reqVO.getBizDate() == null ? LocalDate.now() : reqVO.getBizDate()); stock.setOperator(StrUtil.trim(reqVO.getOperator())); stockMapper.insert(stock); SafetyMaterialDO update = new SafetyMaterialDO(); update.setId(material.getId()); update.setStockQty(after); materialMapper.updateById(update); return stock.getId(); } public PageResult getStockPage(SafetyMaterialStockPageReqVO reqVO) { return stockMapper.selectPage(reqVO); } public static void fillStockDisplay(SafetyMaterialStockDO stock, SafetyMaterialStockRespVO respVO) { respVO.setCategoryName(SafetyMaterialCategoryEnum.getNameByCategory(stock.getCategory())); respVO.setBizTypeName("IN".equals(stock.getBizType()) ? "入库" : "出库"); } /** * 预警清单:低于安全库存的物资 */ public List getBelowSafeStockList() { return materialMapper.selectBelowSafeStock(); } /** * 预警清单:有效期临近(默认 30 天内)的物资 */ public List getExpiringList(Integer days) { int warnDays = days == null || days <= 0 ? 30 : days; return materialMapper.selectExpiringBefore(LocalDate.now().plusDays(warnDays)); } private SafetyMaterialDO validateMaterialExists(Long id) { SafetyMaterialDO material = id == null ? null : materialMapper.selectById(id); if (material == null) { throw exception(SAFETY_MATERIAL_NOT_EXISTS); } return material; } }