gaoluyang
12 小时以前 03965438c47dc6620bf5bf9ebb7ae97e805b4aae
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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); }
}