huang
4 天以前 9dcde5b27b70a4b0c0885347af5405eb2d1ef089
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
@@ -5,17 +5,21 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mes.device.entity.GlassInfo;
import com.mes.device.mapper.DeviceGlassInfoMapper;
import com.mes.device.service.EngineeringSequenceService;
import com.mes.device.service.GlassInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static java.util.stream.IntStream.range;
/**
 * 玻璃信息服务实现类
@@ -24,8 +28,12 @@
 * @since 2024-11-20
 */
@Slf4j
@RefreshScope
@Service("deviceGlassInfoService")
public class GlassInfoServiceImpl extends ServiceImpl<DeviceGlassInfoMapper, GlassInfo> implements GlassInfoService {
    @Autowired
    private EngineeringSequenceService engineeringSequenceService;
    @Override
    public GlassInfo getGlassInfo(String glassId) {
@@ -208,5 +216,484 @@
            return false;
        }
    }
    @Value("${mes.engineering.import-url}")
    private String mesEngineeringImportUrl;
    @Override
    public String getMesEngineeringImportUrl() {
        return mesEngineeringImportUrl;
    }
    @Override
    public Map<String, Object> buildEngineerImportPayload(List<Map<String, Object>> excelRows) {
        Map<String, Object> result = new HashMap<>();
        if (excelRows == null || excelRows.isEmpty()) {
            return result;
        }
        // 工程号生成:每次导入都生成新的工程号(使用数据库自增序号,避免重复)
        final String engineerId = engineeringSequenceService.generateAndSaveEngineeringId(new Date());
        final String filmsIdDefault = firstValue(excelRows, "filmsId", "白玻");
        final double thicknessDefault = parseDouble(firstValue(excelRows, "thickness"), 0d);
        // glassInfolList
        final String engineerIdFinal = engineerId;
        final String filmsIdDefaultFinal = filmsIdDefault;
        final double thicknessDefaultFinal = thicknessDefault;
        // 用于按 flowCardId 全局计数序号
        Map<String, Integer> flowCardSequenceCounter = new HashMap<>();
        // 用于按 flowCardId 计数 temperingFeedSequence
        Map<String, Integer> temperingFeedSequenceCounter = new HashMap<>();
        // 用于按 flowCardId 分配 temperingLayoutId
        Map<String, Integer> temperingLayoutIdMap = new HashMap<>();
        AtomicInteger nextTemperingLayoutId = new AtomicInteger(1);
        // 先建立原片映射,用于后续匹配 rawSequence
        Map<String, Integer> rawSequenceMap = new HashMap<>();
        for (Map<String, Object> row : excelRows) {
            double width = parseDouble(row.get("width"), 0d);
            double height = parseDouble(row.get("height"), 0d);
            double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal);
            String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal);
            String key = width + "_" + height + "_" + thickness + "_" + filmsId;
            if (!rawSequenceMap.containsKey(key)) {
                rawSequenceMap.put(key, rawSequenceMap.size() + 1);
            }
        }
        List<Map<String, Object>> glassInfolList = excelRows.stream()
                .flatMap(row -> {
                    Object qtyObj = row.getOrDefault("quantity", 1);
                    int qty = parseDouble(qtyObj, 1) > 0 ? (int) parseDouble(qtyObj, 1) : 1;
                    String glassId = str(row.get("glassId"));
                    String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal);
                    String flowCardId = str(row.get("flowCardId"));
                    // orderNumber 是整型(玻璃类型),从 Excel 读取或使用默认值 1
                    Object orderNumberObj = row.get("orderNumber");
                    final Integer finalOrderNumber = orderNumberObj != null
                            ? (int) parseDouble(orderNumberObj, 1)
                            : 1;
                    String productName = str(row.get("productName"));
                    String customerName = str(row.get("customerName"));
                    double width = parseDouble(row.get("width"), 0d);
                    double height = parseDouble(row.get("height"), 0d);
                    double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal);
                    // 计算 rawSequence
                    String rawKey = width + "_" + height + "_" + thickness + "_" + filmsId;
                    Integer rawSequence = rawSequenceMap.get(rawKey);
                    int finalQty = qty;
                    log.info("解析到数量:row={}, quantity={}, 最终qty={}", row, qtyObj, finalQty);
                    return range(0, qty).mapToObj(idx -> {
                        String baseGlassId = engineerIdFinal + glassId;
                        String finalGlassId = finalQty > 1 ? baseGlassId + (idx + 1) : baseGlassId;
                        String baseFlowCardId = flowCardId.isEmpty() ? baseGlassId : flowCardId;
                        // 如果 baseFlowCardId 已经包含尾部 "/数字",先去掉,再由后端统一追加序号
                        baseFlowCardId = baseFlowCardId.replaceFirst("/\\d+$", "");
                        // 按 flowCardId 全局递增序号
                        int sequenceNum = flowCardSequenceCounter.compute(baseFlowCardId, (k, v) -> (v == null ? 0 : v) + 1);
                        String finalFlowCardSequence = baseFlowCardId + "/" + 1;
                        // 按 flowCardId 分配 temperingLayoutId
                        Integer temperingLayoutId = temperingLayoutIdMap.computeIfAbsent(baseFlowCardId, k -> nextTemperingLayoutId.getAndIncrement());
                        // 按 flowCardId 递增 temperingFeedSequence
                        int temperingFeedSequence = temperingFeedSequenceCounter.compute(baseFlowCardId, (k, v) -> (v == null ? 0 : v) + 1);
                        log.debug("生成flowCardSequence: idx={}, baseFlowCardId={}, sequenceNum={}, finalFlowCardSequence={}, temperingLayoutId={}, temperingFeedSequence={}",
                                idx, baseFlowCardId, sequenceNum, finalFlowCardSequence, temperingLayoutId, temperingFeedSequence);
                        Map<String, Object> m = new HashMap<>();
                        m.put("xAxis", 0);
                        m.put("xCoordinate", 0);
                        m.put("yAxis", 0);
                        m.put("yCoordinate", 0);
                        m.put("glassId", finalGlassId);
                        m.put("engineerId", engineerIdFinal);
                        m.put("flowCardId", baseFlowCardId);
                        m.put("orderNumber", finalOrderNumber);
                        m.put("productSortNumber", 1); // 统一为1
                        m.put("hollowCombineDirection", "");
                        m.put("width", width);
                        m.put("height", height);
                        m.put("thickness", thickness);
                        m.put("filmsId", filmsId);
                        m.put("layer", 1);
                        m.put("totalLayer", 1);
                        m.put("edgWidth", width);
                        m.put("edgHeight", height);
                        m.put("isMultiple", finalQty > 1 ? 1 : 0); // 数量>1时为1
                        m.put("maxWidth", width);
                        m.put("maxHeight", height);
                        m.put("isHorizontal", 0);
                        m.put("rawSequence", rawSequence != null ? rawSequence : 0);
                        m.put("temperingLayoutId", temperingLayoutId);
                        m.put("temperingFeedSequence", temperingFeedSequence);
                        m.put("angle", 0);
                        m.put("ruleId", 0);
                        m.put("combine", 0);
                        m.put("markIcon", "");
                        m.put("filmRemove", 0);
                        m.put("flowCardSequence", finalFlowCardSequence);
                        m.put("process", "");
                        m.put("rawAngle", 0);
                        m.put("graphNo", 0);
                        m.put("processParam", "");
                        return m;
                    });
                })
                .collect(Collectors.toList());
        // 原片信息去重
        Map<String, Map<String, Object>> rawGlassMap = new HashMap<>();
        for (Map<String, Object> row : excelRows) {
            double width = parseDouble(row.get("width"), 0d);
            double height = parseDouble(row.get("height"), 0d);
            double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal);
            String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal);
            String key = width + "_" + height + "_" + thickness + "_" + filmsId;
            if (!rawGlassMap.containsKey(key)) {
                Map<String, Object> m = new HashMap<>();
                m.put("engineeringId", engineerIdFinal);
                m.put("filmsId", filmsId);
                m.put("rawGlassWidth", width);
                m.put("rawGlassHeight", height);
                m.put("rawGlassThickness", thickness);
                m.put("rawSequence", rawGlassMap.size() + 1);
                m.put("usageRate", "0.95");
                rawGlassMap.put(key, m);
            }
        }
        List<Map<String, Object>> engineeringRawQueueList = rawGlassMap.values().stream().collect(Collectors.toList());
        // 流程卡信息(需要统计每个 flowCardId 的实际玻璃数量,考虑 quantity)
        Map<String, Map<String, Object>> flowCardMap = new HashMap<>();
        for (Map<String, Object> row : excelRows) {
            String glassId = str(row.get("glassId"));
            String flowCardId = str(row.get("flowCardId"));
            if (flowCardId.isEmpty()) {
                flowCardId = engineerIdFinal + glassId;
            }
            // 去掉尾部 "/数字"(如果有)
            flowCardId = flowCardId.replaceFirst("/\\d+$", "");
            // orderNumber 是整型(玻璃类型),从 Excel 读取或使用默认值 1
            Object orderNumberObj = row.get("orderNumber");
            Integer orderNumber = orderNumberObj != null
                    ? (int) parseDouble(orderNumberObj, 1)
                    : 1;
            Object qtyObj = row.getOrDefault("quantity", 1);
            int qty = parseDouble(qtyObj, 1) > 0 ? (int) parseDouble(qtyObj, 1) : 1;
            double width = parseDouble(row.get("width"), 0d);
            double height = parseDouble(row.get("height"), 0d);
            double thickness = parseDouble(row.get("thickness"), thicknessDefaultFinal);
            String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal);
            String productName = str(row.get("productName"));
            String customerName = str(row.get("customerName"));
            Map<String, Object> exist = flowCardMap.get(flowCardId);
            if (exist == null) {
                Map<String, Object> m = new HashMap<>();
                m.put("flowCardId", flowCardId);
                m.put("width", width);
                m.put("height", height);
                m.put("thickness", thickness);
                m.put("filmsId", filmsId);
                m.put("totalLayer", 1);
                m.put("layer", 1);
                m.put("glassTotal", qty); // 使用实际数量
                m.put("orderNumber", orderNumber);
                m.put("productName", productName);
                m.put("customerName", customerName);
                flowCardMap.put(flowCardId, m);
            } else {
                int count = (int) exist.getOrDefault("glassTotal", 0);
                exist.put("glassTotal", count + qty); // 累加数量
            }
        }
        List<Map<String, Object>> flowCardInfoList = flowCardMap.values().stream().collect(Collectors.toList());
        // 汇总
        int glassTotal = glassInfolList.size();
        double glassTotalArea = glassInfolList.stream()
                .mapToDouble(m -> parseDouble(m.get("width"), 0d) * parseDouble(m.get("height"), 0d) / 1_000_000d)
                .sum();
        double patternArea = engineeringRawQueueList.stream()
                .mapToDouble(m -> parseDouble(m.get("rawGlassWidth"), 0d) * parseDouble(m.get("rawGlassHeight"), 0d) / 1_000_000d)
                .sum();
        // 计算最大负载尺寸(用于钢化参数)
        double maxLoadWidth = glassInfolList.stream()
                .mapToDouble(m -> parseDouble(m.get("width"), 0d))
                .max().orElse(2000);
        double maxLoadLength = glassInfolList.stream()
                .mapToDouble(m -> parseDouble(m.get("height"), 0d))
                .max().orElse(3000);
        // 生成钢化参数
        List<Map<String, Object>> temperingParameterList = new ArrayList<>();
        Map<String, Object> temperingParam = new HashMap<>();
        temperingParam.put("engineerId", engineerIdFinal);
        temperingParam.put("heatMode", 1);
        temperingParam.put("chaosPct", 50);
        temperingParam.put("maxLoadPct", 100);
        temperingParam.put("loadWidth", (int) Math.round(maxLoadWidth));
        temperingParam.put("loadLength", (int) Math.round(maxLoadLength));
        temperingParam.put("xSpace", 50);
        temperingParam.put("ySpace", 50);
        temperingParam.put("rotateMode", 0);
        temperingParam.put("allowRotate", 0);
        temperingParam.put("tempering", 1);
        temperingParam.put("curtainWall", 0);
        temperingParameterList.add(temperingParam);
        result.put("engineerId", engineerIdFinal);
        result.put("engineerName", thicknessDefaultFinal + "mm" + filmsIdDefaultFinal);
        result.put("avgAvailability", "90");
        result.put("validAvailability", "90");
        result.put("lastAvailability", "90");
        result.put("glassTotal", glassTotal);
        result.put("glassTotalArea", round2(glassTotalArea));
        result.put("planPatternTotal", engineeringRawQueueList.size());
        result.put("planPatternTotalArea", round2(patternArea));
        result.put("realityPatternTotal", engineeringRawQueueList.size());
        result.put("realityPatternTotalArea", round2(patternArea));
        result.put("filmsId", filmsIdDefaultFinal);
        result.put("thickness", thicknessDefaultFinal);
        result.put("engineeringRawQueueList", engineeringRawQueueList);
        result.put("glassInfolList", glassInfolList);
        result.put("flowCardInfoList", flowCardInfoList);
        result.put("hollowFormulaDetailsList", null);
        result.put("temperingParameterList", null);
        return result;
    }
    /**
     * 提取List中第一个Map的指定key值(默认空字符串)
     *
     * @param rows 数据行列表(可为null/空)
     * @param key  要提取的键
     * @return 第一个Map的key对应值(空则返回"")
     */
    private String firstValue(List<Map<String, Object>> rows, String key) {
        return firstValue(rows, key, "");
    }
    /**
     * 提取List中第一个Map的指定key值(自定义默认值)
     *
     * @param rows       数据行列表(可为null/空)
     * @param key        要提取的键
     * @param defaultVal 空值时的默认返回值
     * @return 第一个Map的key对应值(空则返回defaultVal)
     */
    private String firstValue(List<Map<String, Object>> rows, String key, String defaultVal) {
        if (rows == null || rows.isEmpty() || key == null) {
            return defaultVal;
        }
        Map<String, Object> firstRow = rows.get(0);
        Object value = firstRow.get(key);
        return value == null ? defaultVal : value.toString();
    }
    /**
     * 对象转字符串(null转空串,自动去除首尾空格)
     *
     * @param v 待转换对象
     * @return 处理后的字符串
     */
    private String str(Object v) {
        return v == null ? "" : v.toString().trim();
    }
    /**
     * 对象转字符串(空串时返回默认值,自动去除首尾空格)
     *
     * @param v   待转换对象
     * @param def 空值默认值
     * @return 处理后的字符串
     */
    private String strOrDefault(Object v, String def) {
        String result = str(v);
        return result.isEmpty() ? def : result;
    }
    /**
     * 解析对象为double(失败/空值返回默认值)
     *
     * @param v   待解析对象(支持数字/字符串类型)
     * @param def 解析失败时的默认值
     * @return 解析后的double值
     */
    private double parseDouble(Object v, double def) {
        if (v == null) {
            return def;
        }
        try {
            if (v instanceof Number) {
                return ((Number) v).doubleValue();
            }
            return Double.parseDouble(v.toString().trim());
        } catch (NumberFormatException e) {
            // 仅捕获数字格式化异常,避免吞掉其他异常
            return def;
        }
    }
    /**
     * 保留两位小数(四舍五入)
     *
     * @param v 原始数值
     * @return 保留两位小数后的数值
     */
    private double round2(double v) {
        return Math.round(v * 100.0) / 100.0;
    }
    @Override
    public List<GlassInfo> getGlassInfosByEngineeringId(String engineeringId) {
        if (engineeringId == null || engineeringId.trim().isEmpty()) {
            return Collections.emptyList();
        }
        try {
            return baseMapper.selectByEngineeringId(engineeringId.trim());
        } catch (Exception e) {
            log.error("根据工程号查询玻璃信息失败, engineeringId={}", engineeringId, e);
            return Collections.emptyList();
        }
    }
    @Override
    public void saveGlassInfosFromExcel(List<Map<String, Object>> excelRows, String engineeringId) {
        if (excelRows == null || excelRows.isEmpty() || engineeringId == null || engineeringId.trim().isEmpty()) {
            return;
        }
        List<GlassInfo> glassInfos = new ArrayList<>();
        Date now = new Date();
        for (Map<String, Object> row : excelRows) {
            String glassId = str(row.get("glassId"));
            if (glassId == null || glassId.trim().isEmpty()) {
                continue;
            }
            int qty = (int) parseDouble(row.getOrDefault("quantity", 1), 1);
            if (qty <= 0) qty = 1;
            double width = parseDouble(row.get("width"), 0d);
            double height = parseDouble(row.get("height"), 0d);
            double thickness = parseDouble(row.get("thickness"), 0d);
            // 与导入规则保持一致:glassId 前加工程号前缀,数量>1时追加序号
            String baseGlassId = engineeringId.trim() + glassId;
            for (int idx = 0; idx < qty; idx++) {
                String finalGlassId = qty > 1 ? baseGlassId + "_" + (idx + 1) : baseGlassId;
                GlassInfo glassInfo = new GlassInfo();
                glassInfo.setGlassId(finalGlassId);
                glassInfo.setEngineeringId(engineeringId.trim());
                glassInfo.setGlassLength((int) Math.round(height));
                glassInfo.setGlassWidth((int) Math.round(width));
                glassInfo.setGlassThickness(BigDecimal.valueOf(thickness));
                glassInfo.setStatus(GlassInfo.Status.ACTIVE);
                glassInfo.setState(0);
                glassInfo.setCreatedTime(now);
                glassInfo.setUpdatedTime(now);
                glassInfo.setCreatedBy("system");
                glassInfo.setUpdatedBy("system");
                glassInfos.add(glassInfo);
            }
        }
        if (!glassInfos.isEmpty()) {
            batchSaveOrUpdateGlassInfo(glassInfos);
            log.info("已保存 {} 条玻璃信息到本地数据库,工程号: {}", glassInfos.size(), engineeringId);
        }
    }
    @Override
    public boolean updateGlassStateAfterScan(String glassId, Integer width, Integer height, Integer workLine) {
        if (glassId == null || glassId.trim().isEmpty()) {
            return false;
        }
        try {
            // 查询已存在的玻璃信息
            GlassInfo existing = baseMapper.selectByGlassId(glassId.trim());
            if (existing == null) {
                log.debug("玻璃信息不存在,无法更新状态: glassId={}", glassId);
                return false;
            }
            // 更新状态为1(已扫码交互)
            LambdaUpdateWrapper<GlassInfo> wrapper = new LambdaUpdateWrapper<>();
            wrapper.eq(GlassInfo::getGlassId, glassId.trim())
                   .eq(GlassInfo::getIsDeleted, 0)
                   .set(GlassInfo::getState, 1)
                   .set(GlassInfo::getUpdatedTime, new Date())
                   .set(GlassInfo::getUpdatedBy, "system");
            // 如果提供了尺寸信息,也更新尺寸
            if (width != null) {
                wrapper.set(GlassInfo::getGlassWidth, width);
            }
            if (height != null) {
                wrapper.set(GlassInfo::getGlassLength, height);
            }
            if (workLine != null) {
                wrapper.set(GlassInfo::getWorkLine, workLine);
            }
            boolean updated = this.update(wrapper);
            if (updated) {
                log.info("已更新玻璃信息状态为已扫码交互: glassId={}, state=1", glassId);
            }
            return updated;
        } catch (Exception e) {
            log.error("更新玻璃信息状态失败: glassId={}", glassId, e);
            return false;
        }
    }
    @Override
    public int deleteGlassInfosByEngineeringId(String engineeringId) {
        if (engineeringId == null || engineeringId.trim().isEmpty()) {
            return 0;
        }
        try {
            // 先查询要删除的数量(删除前)
            LambdaQueryWrapper<GlassInfo> countWrapper = new LambdaQueryWrapper<>();
            countWrapper.eq(GlassInfo::getEngineeringId, engineeringId.trim())
                       .eq(GlassInfo::getIsDeleted, 0); // 查询未删除的记录
            long count = this.count(countWrapper);
            // 使用MyBatis-Plus的remove方法,会根据@TableLogic自动进行逻辑删除
            LambdaQueryWrapper<GlassInfo> removeWrapper = new LambdaQueryWrapper<>();
            removeWrapper.eq(GlassInfo::getEngineeringId, engineeringId.trim())
                        .eq(GlassInfo::getIsDeleted, 0); // 只删除未删除的记录
            boolean result = this.remove(removeWrapper);
            if (result) {
                log.info("已删除工程号下的玻璃信息: engineeringId={}, count={}", engineeringId, count);
                return (int) count;
            }
            return 0;
        } catch (Exception e) {
            log.error("删除工程号下的玻璃信息失败: engineeringId={}", engineeringId, e);
            return 0;
        }
    }
}