云
18 小时以前 3edf8c4fc07f9784e2d04ee7a98692bb457749eb
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
package cn.iocoder.yudao.module.mes.service.pro.route;
 
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteCompleteImportWorkbook;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProRouteProcessSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.product.MesProRouteProductSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.productbom.MesProRouteProductBomSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.process.MesProProcessMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
 
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_ROUTE_IMPORT_LIST_IS_EMPTY;
 
/** 按路线分事务批量导入完整配置;失败路线不会留下部分产品或工序。 */
@Service
public class MesProRouteCompleteImportService {
    @Resource private TransactionTemplate transactionTemplate;
    @Resource private MesProRouteService routeService;
    @Resource private MesProRouteProductService productService;
    @Resource private MesProRouteProcessService processService;
    @Resource private MesProRouteProductBomService bomService;
    @Resource private MesMdItemService itemService;
    @Resource private MesProProcessMapper processMapper;
    @Resource private MesWmWarehouseService warehouseService;
    @Resource private MesWmWarehouseLocationService locationService;
    @Resource private MesWmWarehouseAreaService areaService;
 
    public MesProRouteImportRespVO importRoutes(MesProRouteCompleteImportWorkbook book) {
        if (book.getRoutes() == null || book.getRoutes().isEmpty()) {
            throw exception(PRO_ROUTE_IMPORT_LIST_IS_EMPTY);
        }
        MesProRouteImportRespVO result = MesProRouteImportRespVO.builder()
                .createCodes(new ArrayList<>()).updateCodes(new ArrayList<>())
                .failureCodes(new LinkedHashMap<>()).build();
        Set<String> seen = new HashSet<>();
        int rowNumber = 1;
        for (MesProRouteCompleteImportWorkbook.RouteRow row : book.getRoutes()) {
            rowNumber++;
            String code = trim(row.getRouteCode());
            String key = StrUtil.isBlank(code) ? "路线表第 " + rowNumber + " 行" : code;
            if (StrUtil.isBlank(code) || StrUtil.isBlank(row.getName())) {
                result.getFailureCodes().put(key, "路线编码和名称不能为空");
                continue;
            }
            if (!seen.add(code)) {
                result.getFailureCodes().put("路线表第 " + rowNumber + " 行(" + code + ")", "导入文件中路线编码重复");
                continue;
            }
            try {
                transactionTemplate.execute(status -> {
                    importOne(book, row, code);
                    return null;
                });
                result.getCreateCodes().add(code);
            } catch (RuntimeException ex) {
                result.getFailureCodes().put(key, ex.getMessage());
            }
        }
        Set<String> known = book.getRoutes().stream().map(r -> trim(r.getRouteCode()))
                .collect(Collectors.toSet());
        collectUnknownRoutes(result.getFailureCodes(), known, "产品", book.getProducts() == null ? List.of() :
                book.getProducts().stream().map(MesProRouteCompleteImportWorkbook.ProductRow::getRouteCode).toList());
        collectUnknownRoutes(result.getFailureCodes(), known, "工序", book.getProcesses() == null ? List.of() :
                book.getProcesses().stream().map(MesProRouteCompleteImportWorkbook.ProcessRow::getRouteCode).toList());
        collectUnknownRoutes(result.getFailureCodes(), known, "工序产出", book.getOutputs() == null ? List.of() :
                book.getOutputs().stream().map(MesProRouteCompleteImportWorkbook.OutputRow::getRouteCode).toList());
        collectUnknownRoutes(result.getFailureCodes(), known, "工序投料", book.getInputs() == null ? List.of() :
                book.getInputs().stream().map(MesProRouteCompleteImportWorkbook.InputRow::getRouteCode).toList());
        return result;
    }
 
    private void importOne(MesProRouteCompleteImportWorkbook book,
                           MesProRouteCompleteImportWorkbook.RouteRow row, String code) {
        List<MesProRouteCompleteImportWorkbook.ProductRow> products = rows(book.getProducts()).stream()
                .filter(r -> code.equals(trim(r.getRouteCode()))).toList();
        List<MesProRouteCompleteImportWorkbook.ProcessRow> processes = rows(book.getProcesses()).stream()
                .filter(r -> code.equals(trim(r.getRouteCode())))
                .sorted(Comparator.comparing(MesProRouteCompleteImportWorkbook.ProcessRow::getSort,
                        Comparator.nullsLast(Comparator.naturalOrder()))).toList();
        if (products.isEmpty() || processes.isEmpty()) {
            throw new IllegalArgumentException("至少需要一条产品和一道工序");
        }
        MesProRouteSaveReqVO route = new MesProRouteSaveReqVO();
        route.setCode(code);
        route.setName(trim(row.getName()));
        route.setDescription(row.getDescription());
        route.setRemark(row.getRemark());
        Long routeId = routeService.createRoute(route);
        Set<String> productCodes = new HashSet<>();
        for (MesProRouteCompleteImportWorkbook.ProductRow product : products) {
            String itemCode = trim(product.getItemCode());
            if (!productCodes.add(itemCode)) {
                throw new IllegalArgumentException("产品编码重复:" + itemCode);
            }
            MesProRouteProductSaveReqVO request = new MesProRouteProductSaveReqVO();
            request.setRouteId(routeId);
            request.setItemId(itemId(itemCode));
            MesWmWarehouseDO warehouse = warehouseService.getWarehouseByCode(trim(product.getWarehouseCode()));
            MesWmWarehouseLocationDO location = locationService.getWarehouseLocationByCode(trim(product.getLocationCode()));
            MesWmWarehouseAreaDO area = areaService.getWarehouseAreaByCode(trim(product.getAreaCode()));
            if (warehouse == null || location == null || area == null) {
                throw new IllegalArgumentException("产品 " + itemCode + " 的仓库、库区或库位编码不存在");
            }
            request.setWarehouseId(warehouse.getId());
            request.setLocationId(location.getId());
            request.setAreaId(area.getId());
            request.setQuantity(product.getQuantity());
            request.setProductionTime(product.getProductionTime());
            request.setTimeUnitType(product.getTimeUnitType());
            request.setRemark(product.getRemark());
            productService.createRouteProduct(request);
        }
        Set<String> processCodes = new HashSet<>();
        Set<Integer> sorts = new HashSet<>();
        for (MesProRouteCompleteImportWorkbook.ProcessRow process : processes) {
            String processCode = trim(process.getProcessCode());
            if (!processCodes.add(processCode) || process.getSort() == null || !sorts.add(process.getSort())) {
                throw new IllegalArgumentException("工序编码或排序重复/为空:" + processCode);
            }
            MesProProcessDO processDO = processMapper.selectByCode(processCode);
            if (processDO == null) {
                throw new IllegalArgumentException("工序编码不存在:" + processCode);
            }
            MesProRouteProcessSaveReqVO request = new MesProRouteProcessSaveReqVO();
            request.setRouteId(routeId);
            request.setProcessId(processDO.getId());
            request.setSort(process.getSort());
            request.setParamTemplateId(process.getParamTemplateId());
            request.setYieldRate(process.getYieldRate());
            request.setLinkType(process.getLinkType());
            request.setPrepareTime(process.getPrepareTime());
            request.setWaitTime(process.getWaitTime());
            request.setColorCode(process.getColorCode());
            request.setKeyFlag(flag(process.getKeyFlag(), processCode, "关键工序"));
            request.setCheckFlag(flag(process.getCheckFlag(), processCode, "质检工序"));
            request.setBackflushFlag(flag(process.getBackflushFlag(), processCode, "倒冲"));
            request.setRemark(process.getRemark());
            List<MesProRouteProcessSaveReqVO.OutputItem> outputs = new ArrayList<>();
            for (MesProRouteCompleteImportWorkbook.OutputRow output : rows(book.getOutputs())) {
                if (!code.equals(trim(output.getRouteCode())) || !processCode.equals(trim(output.getProcessCode()))) {
                    continue;
                }
                MesProRouteProcessSaveReqVO.OutputItem value = new MesProRouteProcessSaveReqVO.OutputItem();
                value.setItemId(itemId(output.getItemCode()));
                if (StrUtil.isNotBlank(output.getProductCode())) {
                    String ownerCode = trim(output.getProductCode());
                    if (!productCodes.contains(ownerCode)) {
                        throw new IllegalArgumentException("产出归属产品未关联路线:" + ownerCode);
                    }
                    value.setProductId(itemId(ownerCode));
                }
                value.setSort(output.getSort());
                value.setRemark(output.getRemark());
                outputs.add(value);
            }
            request.setOutputs(outputs);
            processService.createRouteProcess(request);
        }
        for (MesProRouteCompleteImportWorkbook.OutputRow output : rows(book.getOutputs())) {
            if (code.equals(trim(output.getRouteCode())) && !processCodes.contains(trim(output.getProcessCode()))) {
                throw new IllegalArgumentException("产出引用了未配置的工序:" + output.getProcessCode());
            }
        }
        for (MesProRouteCompleteImportWorkbook.InputRow input : rows(book.getInputs())) {
            if (!code.equals(trim(input.getRouteCode()))) {
                continue;
            }
            String processCode = trim(input.getProcessCode());
            String productCode = trim(input.getProductCode());
            if (!processCodes.contains(processCode) || !productCodes.contains(productCode)) {
                throw new IllegalArgumentException("投料引用了未配置的工序或产品:" + processCode + "/" + productCode);
            }
            MesProRouteProductBomSaveReqVO request = new MesProRouteProductBomSaveReqVO();
            request.setRouteId(routeId);
            request.setProcessId(processMapper.selectByCode(processCode).getId());
            request.setProductId(itemId(productCode));
            request.setItemId(itemId(input.getItemCode()));
            request.setQuantity(input.getQuantity());
            request.setRemark(input.getRemark());
            bomService.createRouteProductBom(request);
        }
    }
 
    private Long itemId(String code) {
        String normalized = trim(code);
        MesMdItemDO item = StrUtil.isBlank(normalized) ? null : itemService.getItemByCode(normalized);
        if (item == null) {
            throw new IllegalArgumentException("物料编码不存在:" + normalized);
        }
        return item.getId();
    }
 
    private static boolean flag(String value, String processCode, String field) {
        String normalized = trim(value);
        if (StrUtil.isBlank(normalized) || "否".equals(normalized)
                || "false".equalsIgnoreCase(normalized) || "0".equals(normalized)) {
            return false;
        }
        if ("是".equals(normalized) || "true".equalsIgnoreCase(normalized) || "1".equals(normalized)) {
            return true;
        }
        throw new IllegalArgumentException("工序 " + processCode + " 的" + field + "只能填写是或否");
    }
 
    private static String trim(String value) {
        return StrUtil.trim(value);
    }
 
    private static <T> List<T> rows(List<T> values) {
        return values == null ? List.of() : values;
    }
 
    private static void collectUnknownRoutes(Map<String, String> failures, Set<String> known,
                                             String sheet, List<String> codes) {
        for (String code : codes) {
            if (!known.contains(trim(code))) {
                failures.put(sheet + "表路线 " + code, "路线表中不存在此路线编码");
            }
        }
    }
}