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
|
*
|
* <p>覆盖电力绝缘用具、危化品、消防物资。出库时行锁校验库存,避免并发扣减为负。</p>
|
*
|
* @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<SafetyMaterialDO> 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<SafetyMaterialDO> 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<SafetyMaterialStockDO> 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<SafetyMaterialDO> getBelowSafeStockList() {
|
return materialMapper.selectBelowSafeStock();
|
}
|
|
/**
|
* 预警清单:有效期临近(默认 30 天内)的物资
|
*/
|
public List<SafetyMaterialDO> 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;
|
}
|
|
}
|