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;
}
}