云
3 小时以前 e09f103259fa602382bb5ce2a03361a6397af46a
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package cn.iocoder.yudao.module.mes.service.pro.card;
 
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.card.vo.MesProCardPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.card.vo.MesProCardSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.card.MesProCardDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.card.MesProCardProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.card.MesProCardMapper;
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
 
/**
 * MES 生产流转卡 Service 实现类
 *
 * @author 超级管理员
 */
@Service
@Validated
public class MesProCardServiceImpl implements MesProCardService {
 
    @Resource
    private MesProCardMapper cardMapper;
 
    @Resource
    @Lazy
    private MesProCardProcessService cardProcessService;
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
    private MesMdItemService itemService;
    @Resource
    private MesWmBarcodeService barcodeService;
    @Resource
    private MesProWorkOrderProcessService workOrderProcessService;
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createCard(MesProCardSaveReqVO createReqVO) {
        // 1. 校验关联数据
        MesProWorkOrderDO workOrder = validateCardSaveData(createReqVO);
 
        // 2. 插入
        MesProCardDO card = BeanUtils.toBean(createReqVO, MesProCardDO.class)
                .setStatus(MesProWorkOrderStatusEnum.PREPARE.getStatus());
        cardMapper.insert(card);
 
        // 3. 按工单工序 + 收率生成工序记录
        generateCardProcesses(card.getId(), workOrder);
 
        // 4. 自动生成条码
        barcodeService.autoGenerateBarcode(BarcodeBizTypeEnum.PROCARD.getValue(),
                card.getId(), card.getCode(), card.getCode());
        return card.getId();
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCard(MesProCardSaveReqVO updateReqVO) {
        // 1.1 校验存在 + 草稿状态
        MesProCardDO card = validateCardExistsAndPrepareInternal(updateReqVO.getId());
        // 1.2 校验关联数据
        MesProWorkOrderDO workOrder = validateCardSaveData(updateReqVO);
 
        // 2. 更新
        MesProCardDO updateObj = BeanUtils.toBean(updateReqVO, MesProCardDO.class);
        cardMapper.updateById(updateObj);
 
        // 3. 更换了生产订单:原工序记录已失效,按新工单重建
        if (ObjUtil.notEqual(card.getWorkOrderId(), updateReqVO.getWorkOrderId())) {
            cardProcessService.deleteCardProcessByCardId(card.getId());
            generateCardProcesses(card.getId(), workOrder);
        }
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteCard(Long id) {
        // 1. 校验存在 + 草稿状态
        validateCardExistsAndPrepare(id);
 
        // 2. 删除流转卡 + 级联删除工序记录
        cardMapper.deleteById(id);
        cardProcessService.deleteCardProcessByCardId(id);
    }
 
    @Override
    public MesProCardDO getCard(Long id) {
        return cardMapper.selectById(id);
    }
 
    @Override
    public PageResult<MesProCardDO> getCardPage(MesProCardPageReqVO pageReqVO) {
        return cardMapper.selectPage(pageReqVO);
    }
 
    @Override
    public void submitCard(Long id) {
        // 1. 校验存在 + 草稿状态
        validateCardExistsAndStatus(id, MesProWorkOrderStatusEnum.PREPARE);
 
        // 2. 提交(草稿 → 已确认)
        cardMapper.updateById(new MesProCardDO()
                .setId(id).setStatus(MesProWorkOrderStatusEnum.CONFIRMED.getStatus()));
    }
 
    @Override
    public void finishCard(Long id) {
        // 1. 校验存在 + 已确认状态
        validateCardExistsAndStatus(id, MesProWorkOrderStatusEnum.CONFIRMED);
 
        // 2. 完成(已确认 → 已完成)
        cardMapper.updateById(new MesProCardDO()
                .setId(id).setStatus(MesProWorkOrderStatusEnum.FINISHED.getStatus()));
    }
 
    @Override
    public void cancelCard(Long id) {
        // 1.1 校验存在
        MesProCardDO card = validateCardExistsInternal(id);
        // 1.2 已完成和已取消不允许取消
        if (ObjectUtils.equalsAny(card.getStatus(),
                MesProWorkOrderStatusEnum.FINISHED.getStatus(),
                MesProWorkOrderStatusEnum.CANCELED.getStatus())) {
            throw exception(PRO_CARD_CANCEL_NOT_ALLOWED);
        }
 
        // 2. 取消
        cardMapper.updateById(new MesProCardDO()
                .setId(id).setStatus(MesProWorkOrderStatusEnum.CANCELED.getStatus()));
    }
 
    // ==================== 校验方法 ====================
 
    @Override
    public void validateCardExists(Long id) {
        validateCardExistsInternal(id);
    }
 
    @Override
    public void validateCardExistsAndPrepare(Long id) {
        validateCardExistsAndPrepareInternal(id);
    }
 
    private MesProCardDO validateCardExistsAndPrepareInternal(Long id) {
        MesProCardDO card = validateCardExistsInternal(id);
        if (ObjUtil.notEqual(MesProWorkOrderStatusEnum.PREPARE.getStatus(), card.getStatus())) {
            throw exception(PRO_CARD_NOT_PREPARE);
        }
        return card;
    }
 
    private MesProCardDO validateCardExistsInternal(Long id) {
        MesProCardDO card = cardMapper.selectById(id);
        if (card == null) {
            throw exception(PRO_CARD_NOT_EXISTS);
        }
        return card;
    }
 
    private MesProCardDO validateCardExistsAndStatus(Long id, MesProWorkOrderStatusEnum status) {
        MesProCardDO card = validateCardExistsInternal(id);
        if (ObjUtil.notEqual(status.getStatus(), card.getStatus())) {
            throw exception(PRO_CARD_STATUS_ERROR);
        }
        return card;
    }
 
    private void validateCardCodeUnique(Long id, String code) {
        if (code == null) {
            return;
        }
        MesProCardDO card = cardMapper.selectByCode(code);
        if (card == null) {
            return;
        }
        if (ObjUtil.notEqual(card.getId(), id)) {
            throw exception(PRO_CARD_CODE_DUPLICATE);
        }
    }
 
    private MesProWorkOrderDO validateCardSaveData(MesProCardSaveReqVO reqVO) {
        // 校验编码唯一
        validateCardCodeUnique(reqVO.getId(), reqVO.getCode());
        // 校验工单存在
        MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
        // 校验物料存在
        itemService.validateItemExists(reqVO.getItemId());
        if (ObjUtil.notEqual(workOrder.getProductId(), reqVO.getItemId())) {
            throw exception(PRO_WORK_ORDER_PRODUCT_MISMATCH);
        }
        return workOrder;
    }
 
    /**
     * 按工单工序与工序收率生成流转卡工序记录
     *
     * <p>数量口径与一键排产一致(见 MesProBatchScheduleService#calcTargetQuantities):
     * 末道工序产出 = 工单计划数量,前面各道逐级放大后向上取整;
     * 本道投入 = 上道产出(首道投入 = 首道产出 / 收率,向上取整),不合格品 = 投入 - 产出。
     *
     * <p>进入/出工序时间、工位、操作人留空,待生产实际发生时填写。
     */
    private void generateCardProcesses(Long cardId, MesProWorkOrderDO workOrder) {
        List<MesProWorkOrderProcessDO> processes = workOrderProcessService
                .getWorkOrderProcessListByWorkOrderId(workOrder.getId());
        if (CollUtil.isEmpty(processes) || workOrder.getQuantity() == null) {
            return; // 工单未维护工序或数量缺失,工序记录留待手工维护
        }
        processes = new ArrayList<>(processes);
        processes.sort(Comparator.comparing(MesProWorkOrderProcessDO::getSort,
                Comparator.nullsLast(Comparator.naturalOrder())));
 
        // 1. 按收率从末道往前倒推各道产出量(向上取整为整数件)
        int size = processes.size();
        BigDecimal[] outputs = new BigDecimal[size];
        BigDecimal cumulativeYield = BigDecimal.ONE;
        for (int i = size - 1; i >= 0; i--) {
            outputs[i] = workOrder.getQuantity().divide(cumulativeYield, 0, RoundingMode.CEILING);
            BigDecimal yieldRate = processes.get(i).getYieldRate();
            if (yieldRate != null && yieldRate.signum() > 0) {
                cumulativeYield = cumulativeYield.multiply(yieldRate);
            }
        }
 
        // 2. 逐道生成记录:投入 = 上道产出(首道 = 本道产出 / 收率,向上取整)
        List<MesProCardProcessDO> cardProcesses = new ArrayList<>(size);
        BigDecimal previousOutput = null;
        for (int i = 0; i < size; i++) {
            MesProWorkOrderProcessDO process = processes.get(i);
            BigDecimal input;
            if (previousOutput != null) {
                input = previousOutput;
            } else {
                BigDecimal firstYield = process.getYieldRate();
                input = firstYield == null || firstYield.signum() <= 0
                        ? outputs[i] : outputs[i].divide(firstYield, 0, RoundingMode.CEILING);
            }
            cardProcesses.add(MesProCardProcessDO.builder()
                    .cardId(cardId).sort(process.getSort()).processId(process.getProcessId())
                    .inputQuantity(input).outputQuantity(outputs[i])
                    .unqualifiedQuantity(BigDecimal.ZERO)
                    .build());
            previousOutput = outputs[i];
        }
        cardProcessService.createCardProcessBatch(cardProcesses);
    }
 
}