package cn.iocoder.yudao.module.mes.service.dv.spare;
|
|
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
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.dv.spare.vo.*;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.spare.*;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.repair.MesDvRepairDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.spare.*;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.repair.MesDvRepairMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper;
|
import cn.iocoder.yudao.module.mes.enums.dv.MesDvRepairStatusEnum;
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
import jakarta.annotation.Resource;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.*;
|
import java.math.BigDecimal;
|
import java.util.*;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
/** 备件库存只通过留痕的流水变更,元数据编辑不允许覆盖库存。 */
|
@Service
|
public class MesDvSpareService {
|
private static final ErrorCode INVALID = new ErrorCode(1_040_807_001, "备件操作失败:{}");
|
private static final BigDecimal MAX_QUANTITY = new BigDecimal("999999999999999.999");
|
@Resource private MesDvSparePartMapper partMapper;
|
@Resource private MesDvSpareMovementMapper movementMapper;
|
@Resource private MesDvRepairMapper repairMapper;
|
@Resource private MesDvMachineryMapper machineryMapper;
|
@Resource private AdminUserApi adminUserApi;
|
|
@Transactional(rollbackFor=Exception.class, isolation=Isolation.READ_COMMITTED)
|
public Long create(MesDvSparePartSaveReqVO req, Long operator) {
|
validateMetadata(req);
|
MesDvSparePartDO part = BeanUtils.toBean(req,MesDvSparePartDO.class);
|
part.setStockQuantity(req.getInitialQuantity());
|
partMapper.insert(part);
|
if (req.getInitialQuantity().signum()>0) {
|
writeMovement(part,1,req.getInitialQuantity(),BigDecimal.ZERO,null,null,null,operator,
|
"opening-"+UUID.randomUUID(),"登记初始库存");
|
}
|
return part.getId();
|
}
|
@Transactional(rollbackFor=Exception.class, isolation=Isolation.READ_COMMITTED)
|
public void update(MesDvSparePartSaveReqVO req) {
|
MesDvSparePartDO part = requirePart(req.getId());
|
validateMetadata(req);
|
MesDvSparePartDO changes = BeanUtils.toBean(req,MesDvSparePartDO.class);
|
changes.setStockQuantity(part.getStockQuantity());
|
partMapper.updateById(changes);
|
}
|
private void validateMetadata(MesDvSparePartSaveReqVO req) {
|
req.setCode(req.getCode().trim());
|
req.setName(req.getName().trim());
|
req.setUnit(req.getUnit().trim());
|
if (req.getCode().isEmpty() || req.getName().isEmpty() || req.getUnit().isEmpty()) fail("编码、名称和单位不能为空");
|
var old=partMapper.selectOne(MesDvSparePartDO::getCode,req.getCode());
|
if(old!=null&&!Objects.equals(old.getId(),req.getId())) fail("备件编码已存在");
|
if(req.getId()!=null && movementMapper.selectCount(MesDvSpareMovementDO::getSparePartId,req.getId())>0) {
|
var saved=partMapper.selectById(req.getId());
|
if(!Objects.equals(saved.getUnit(),req.getUnit())) fail("已有库存流水的备件不能修改计量单位");
|
}
|
}
|
@Transactional(rollbackFor=Exception.class, isolation=Isolation.READ_COMMITTED)
|
public void delete(Long id) {
|
var part=requirePart(id);
|
if(part.getStockQuantity().signum()!=0 || movementMapper.selectCount(MesDvSpareMovementDO::getSparePartId,id)>0)
|
fail("有库存或已有流水的备件不能删除,请停用");
|
partMapper.deleteById(id);
|
}
|
public MesDvSparePartDO get(Long id) { return partMapper.selectById(id); }
|
public PageResult<MesDvSparePartDO> page(MesDvSparePageReqVO req) { return partMapper.page(req); }
|
public PageResult<MesDvSpareMovementRespVO> movements(MesDvSparePageReqVO req) {
|
var page=movementMapper.page(req);
|
var ids=page.getList().stream().filter(m->m.getType()==3).map(MesDvSpareMovementDO::getId).toList();
|
Map<Long,BigDecimal> totals=new HashMap<>();
|
movementMapper.returns(ids).forEach(m->totals.merge(m.getOriginalId(),m.getQuantity(),BigDecimal::add));
|
return new PageResult<>(page.getList().stream().map(m-> {
|
var vo=BeanUtils.toBean(m,MesDvSpareMovementRespVO.class);
|
vo.setReturnedQuantity(totals.getOrDefault(m.getId(),BigDecimal.ZERO));
|
vo.setReturnableQuantity(m.getType()==3?m.getQuantity().subtract(vo.getReturnedQuantity()):BigDecimal.ZERO);
|
return vo;
|
}).toList(),page.getTotal());
|
}
|
@Transactional(rollbackFor=Exception.class, isolation=Isolation.READ_COMMITTED)
|
public Long receive(MesDvSpareActionReqVO req,Long operator) {
|
validateAction(req);
|
var part=requirePart(req.getSparePartId());
|
Long prior=duplicate(req,2,operator,part.getId(),null,null,null);
|
if(prior!=null)return prior;
|
requireEnabled(part);
|
var before=part.getStockQuantity();
|
changeStock(part,req.getQuantity());
|
return writeMovement(part,2,req.getQuantity(),before,null,null,null,operator,req.getRequestKey(),req.getRemark());
|
}
|
@Transactional(rollbackFor=Exception.class, isolation=Isolation.READ_COMMITTED)
|
public Long issue(MesDvSpareActionReqVO req,Long operator) {
|
validateAction(req);
|
if(req.getRepairId()==null || req.getSparePartId()==null || req.getReceiverUserId()==null) fail("请选择维修单、备件和领用人");
|
// 与维修状态流转使用同一行锁;先维修单再备件,保持一致锁序。
|
var repair=repairMapper.lockForSpare(req.getRepairId());
|
if(repair==null)fail("维修单不存在");
|
var part=requirePart(req.getSparePartId());
|
Long prior=duplicate(req,3,operator,part.getId(),repair.getId(),null,req.getReceiverUserId());
|
if(prior!=null)return prior;
|
if(!Objects.equals(repair.getStatus(),MesDvRepairStatusEnum.PREPARE.getStatus()) &&
|
!Objects.equals(repair.getStatus(),MesDvRepairStatusEnum.CONFIRMED.getStatus()))
|
fail("仅草稿或维修中的维修单可以领用备件");
|
requireEnabled(part);
|
var receiver=adminUserApi.getUser(req.getReceiverUserId());
|
if(receiver==null || !Objects.equals(receiver.getStatus(),0))fail("领用人不存在或已停用");
|
var before=part.getStockQuantity();
|
changeStock(part,req.getQuantity().negate());
|
return writeMovement(part,3,req.getQuantity(),before,repair,null,req.getReceiverUserId(),
|
operator,req.getRequestKey(),req.getRemark());
|
}
|
@Transactional(rollbackFor=Exception.class, isolation=Isolation.READ_COMMITTED)
|
public Long returnPart(MesDvSpareActionReqVO req,Long operator) {
|
validateAction(req);
|
if(req.getOriginalId()==null)fail("请选择原领用记录");
|
var original=movementMapper.lock(req.getOriginalId());
|
if(original==null || original.getType()!=3)fail("原领用记录不存在");
|
var part=requirePart(original.getSparePartId());
|
Long prior=duplicate(req,4,operator,part.getId(),original.getRepairId(),original.getId(),original.getReceiverUserId());
|
if(prior!=null)return prior;
|
BigDecimal returned=movementMapper.returns(List.of(original.getId())).stream()
|
.map(MesDvSpareMovementDO::getQuantity).reduce(BigDecimal.ZERO,BigDecimal::add);
|
if(returned.add(req.getQuantity()).compareTo(original.getQuantity())>0)fail("退回数量超过尚未退回的领用数量");
|
var before=part.getStockQuantity();
|
changeStock(part,req.getQuantity()); // 已停用备件仍允许退回。
|
var record=snapshot(part,4,req.getQuantity(),before,operator,req.getRequestKey(),req.getRemark());
|
record.setOriginalId(original.getId());
|
record.setRepairId(original.getRepairId()); record.setRepairCode(original.getRepairCode());
|
record.setMachineryId(original.getMachineryId()); record.setMachineryName(original.getMachineryName());
|
record.setReceiverUserId(original.getReceiverUserId()); record.setReceiverName(original.getReceiverName());
|
movementMapper.insert(record);
|
return record.getId();
|
}
|
private Long duplicate(MesDvSpareActionReqVO req,int type,Long operator,Long partId,Long repairId,Long originalId,Long receiverId) {
|
var old=movementMapper.byKey(req.getRequestKey());
|
if(old==null)return null;
|
if(old.getType()!=type || !Objects.equals(old.getOperatorUserId(),operator)
|
|| !Objects.equals(old.getSparePartId(),partId) || !Objects.equals(old.getRepairId(),repairId)
|
|| !Objects.equals(old.getOriginalId(),originalId) || !Objects.equals(old.getReceiverUserId(),receiverId)
|
|| old.getQuantity().compareTo(req.getQuantity())!=0 || !Objects.equals(old.getRemark(),req.getRemark()))
|
fail("请求标识已用于其他操作,请刷新后重试");
|
return old.getId();
|
}
|
private void validateAction(MesDvSpareActionReqVO req) {
|
if(req.getQuantity()==null || req.getQuantity().signum()<=0 || req.getQuantity().scale()>3
|
|| req.getQuantity().compareTo(MAX_QUANTITY)>0)fail("数量必须大于0,最多三位小数");
|
if(req.getRemark()==null || req.getRemark().trim().isEmpty())fail("请填写操作说明");
|
}
|
private MesDvSparePartDO requirePart(Long id) {
|
if(id==null)fail("请选择备件");
|
var part=partMapper.lock(id);
|
if(part==null)fail("备件不存在");
|
return part;
|
}
|
private void requireEnabled(MesDvSparePartDO part) { if(!Objects.equals(part.getStatus(),0))fail("备件已停用"); }
|
private void changeStock(MesDvSparePartDO part,BigDecimal delta) {
|
var after=part.getStockQuantity().add(delta);
|
if(after.signum()<0)fail("库存不足,当前可用:"+part.getStockQuantity()+part.getUnit());
|
if(after.compareTo(MAX_QUANTITY)>0)fail("库存数量超出系统允许范围");
|
part.setStockQuantity(after);
|
partMapper.updateById(new MesDvSparePartDO().setId(part.getId()).setStockQuantity(after));
|
}
|
private MesDvSpareMovementDO snapshot(MesDvSparePartDO part,int type,BigDecimal qty,BigDecimal before,
|
Long operator,String key,String remark) {
|
return new MesDvSpareMovementDO().setRequestKey(key).setSparePartId(part.getId())
|
.setSparePartCode(part.getCode()).setSparePartName(part.getName()).setSpecification(part.getSpecification())
|
.setUnit(part.getUnit()).setType(type).setQuantity(qty).setBeforeQuantity(before)
|
.setAfterQuantity(part.getStockQuantity()).setOperatorUserId(operator).setRemark(remark);
|
}
|
private Long writeMovement(MesDvSparePartDO part,int type,BigDecimal qty,BigDecimal before,MesDvRepairDO repair,
|
Long original,Long receiver,Long operator,String key,String remark) {
|
var record=snapshot(part,type,qty,before,operator,key,remark).setOriginalId(original).setReceiverUserId(receiver);
|
if(receiver!=null)record.setReceiverName(adminUserApi.getUser(receiver).getNickname());
|
if(repair!=null) {
|
var machine=machineryMapper.selectById(repair.getMachineryId());
|
if(machine==null)fail("维修设备不存在");
|
record.setRepairId(repair.getId()).setRepairCode(repair.getCode()).setMachineryId(machine.getId()).setMachineryName(machine.getName());
|
}
|
movementMapper.insert(record);
|
return record.getId();
|
}
|
private static void fail(String reason) { throw exception(INVALID,reason); }
|
}
|