From dd1a51612fcd3c839d31dfa0df83b3d3071c8145 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 30 十月 2026 17:50:11 +0800
Subject: [PATCH] feat: 完善无人值守系统后端

---
 src/main/java/com/ruoyi/unattended/service/impl/UnattendedOperationServiceImpl.java |  261 +++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 238 insertions(+), 23 deletions(-)

diff --git a/src/main/java/com/ruoyi/unattended/service/impl/UnattendedOperationServiceImpl.java b/src/main/java/com/ruoyi/unattended/service/impl/UnattendedOperationServiceImpl.java
index 76df4e4..758248c 100644
--- a/src/main/java/com/ruoyi/unattended/service/impl/UnattendedOperationServiceImpl.java
+++ b/src/main/java/com/ruoyi/unattended/service/impl/UnattendedOperationServiceImpl.java
@@ -1,46 +1,261 @@
 package com.ruoyi.unattended.service.impl;
 
-
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.ruoyi.account.mapper.AccountReconciliationMapper;
-import com.ruoyi.account.pojo.AccountReconciliation;
-import com.ruoyi.account.service.AccountReconciliationService;
 import com.ruoyi.unattended.mapper.UnattendedOperationMapper;
 import com.ruoyi.unattended.pojo.UnattendedOperation;
 import com.ruoyi.unattended.pojo.vo.UnattendedOperationVo;
 import com.ruoyi.unattended.service.UnattendedOperationService;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Map;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
-@AllArgsConstructor
+@Slf4j
 @Service
-public class UnattendedOperationServiceImpl extends ServiceImpl<UnattendedOperationMapper, UnattendedOperation> implements UnattendedOperationService {
+@AllArgsConstructor
+public class UnattendedOperationServiceImpl
+        extends ServiceImpl<UnattendedOperationMapper, UnattendedOperation>
+        implements UnattendedOperationService {
 
+    /** 绾㈠鍋忕Щ闃堝��(%)锛岃秴杩囧垽瀹氫负杞﹁締鏈畬鍏ㄤ笂纾� */
+    private static final BigDecimal OFFSET_LIMIT = new BigDecimal("5");
 
-    private UnattendedOperationMapper unattendedOperationMapper;
+    private final UnattendedOperationMapper unattendedOperationMapper;
 
+    /**
+     * 姣忔棩杩囩缁熻锛氭寜澶╄仛鍚堬紝璐ч噺鍙栧噣閲嶄箣鍜岋紝鍋忓樊鐜囧彇鍋忕Щ瓒呴檺杞︽鍗犳瘮銆�
+     */
     @Override
     public List<UnattendedOperationVo> weighbridgeDailyReport(LocalDateTime startDate, LocalDateTime endDate) {
-        List<UnattendedOperation> unattendedOperations = unattendedOperationMapper.selectList(Wrappers.<UnattendedOperation>lambdaQuery().between(UnattendedOperation::getWeightTime, startDate, endDate));
-        Map<LocalDateTime, List<UnattendedOperation>> listMap = unattendedOperations.stream().collect(Collectors.groupingBy(UnattendedOperation::getWeightTime));
-        List<UnattendedOperationVo> list = listMap.entrySet().stream().map(entry -> {
-            UnattendedOperationVo unattendedOperationVo = new UnattendedOperationVo();
-            unattendedOperationVo.setDate(entry.getKey().toLocalDate());
-            unattendedOperationVo.setTripCount((long) entry.getValue().size());
-            unattendedOperationVo.setCargoVolume(entry.getValue().stream().map(UnattendedOperation::getGrossWeight).mapToLong(BigDecimal::longValue).sum());
-            long sum = entry.getValue().stream().map(UnattendedOperation::getPositionOffset).mapToLong(BigDecimal::longValue).sum();
-            unattendedOperationVo.setDeviationRate(BigDecimal.valueOf(sum).divide(BigDecimal.valueOf(entry.getValue().size()), 4, BigDecimal.ROUND_HALF_UP));
-            return unattendedOperationVo;
-        }).collect(Collectors.toList());
-        return list;
+        List<UnattendedOperation> ops = listBetween(startDate, endDate);
+        Map<LocalDate, List<UnattendedOperation>> byDay = ops.stream()
+                .filter(o -> o.getWeightTime() != null)
+                .collect(Collectors.groupingBy(o -> o.getWeightTime().toLocalDate(), TreeMap::new, Collectors.toList()));
+
+        List<UnattendedOperationVo> result = new ArrayList<>();
+        for (Map.Entry<LocalDate, List<UnattendedOperation>> entry : byDay.entrySet()) {
+            List<UnattendedOperation> day = entry.getValue();
+            UnattendedOperationVo vo = new UnattendedOperationVo();
+            vo.setDate(entry.getKey());
+            vo.setTripCount((long) day.size());
+
+            BigDecimal cargoVolume = day.stream()
+                    .map(this::netWeightOf)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            vo.setCargoVolume(scale(cargoVolume));
+
+            // 鍋忓樊鐜� = 鍋忕Щ瓒呴檺杞︽ / 鎬昏溅娆� * 100
+            long abnormal = day.stream().filter(this::isAbnormal).count();
+            BigDecimal rate = day.isEmpty() ? BigDecimal.ZERO
+                    : BigDecimal.valueOf(abnormal)
+                    .multiply(BigDecimal.valueOf(100))
+                    .divide(BigDecimal.valueOf(day.size()), 2, RoundingMode.HALF_UP);
+            vo.setDeviationRate(rate);
+
+            // 闄勫姞缁熻锛圴O 鏂板瀛楁锛�
+            vo.setAbnormalCount(abnormal);
+            vo.setCheatCount(day.stream().filter(o -> o.getCheatFlag() != null && o.getCheatFlag() == 1).count());
+            result.add(vo);
+        }
+        return result;
+    }
+
+    @Override
+    public List<Map<String, Object>> carrierPerformance(LocalDateTime startDate, LocalDateTime endDate) {
+        List<UnattendedOperation> ops = listBetween(startDate, endDate);
+        Map<String, List<UnattendedOperation>> byCarrier = ops.stream()
+                .collect(Collectors.groupingBy(
+                        o -> StringUtils.hasText(o.getCarrierName()) ? o.getCarrierName() : "鏈寚瀹氭壙杩愬晢"));
+
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (Map.Entry<String, List<UnattendedOperation>> entry : byCarrier.entrySet()) {
+            List<UnattendedOperation> list = entry.getValue();
+            long total = list.size();
+            long abnormal = list.stream().filter(this::isAbnormal).count();
+            long cheat = list.stream().filter(o -> o.getCheatFlag() != null && o.getCheatFlag() == 1).count();
+            // 鍑嗘椂鐜囷細鏃犲紓甯镐笖绾㈠鐘舵�佹甯哥殑杞︽鍗犳瘮
+            long onTime = total - abnormal;
+            BigDecimal onTimeRate = total == 0 ? BigDecimal.ZERO
+                    : BigDecimal.valueOf(onTime).multiply(BigDecimal.valueOf(100))
+                    .divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP);
+            BigDecimal cargo = scale(list.stream().map(this::netWeightOf).reduce(BigDecimal.ZERO, BigDecimal::add));
+            BigDecimal cheatRate = total == 0 ? BigDecimal.ZERO
+                    : BigDecimal.valueOf(cheat).multiply(BigDecimal.valueOf(100))
+                    .divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP);
+
+            Map<String, Object> row = new LinkedHashMap<>();
+            row.put("carrierName", entry.getKey());
+            row.put("tripCount", total);
+            row.put("cargoVolume", cargo);
+            row.put("onTimeCount", onTime);
+            row.put("onTimeRate", onTimeRate);
+            row.put("cheatCount", cheat);
+            row.put("cheatRate", cheatRate);
+            // 缁╂晥璇勫垎锛氬噯鏃剁巼鍗� 70%锛屼綔寮婄巼鍙嶅悜鍗� 30%
+            BigDecimal score = onTimeRate.multiply(new BigDecimal("0.7"))
+                    .add(new BigDecimal("100").subtract(cheatRate).multiply(new BigDecimal("0.3")))
+                    .setScale(2, RoundingMode.HALF_UP);
+            row.put("score", score);
+            result.add(row);
+        }
+        result.sort((a, b) -> ((BigDecimal) b.get("score")).compareTo((BigDecimal) a.get("score")));
+        return result;
+    }
+
+    /**
+     * 鑷姩绉伴噸娴佺▼锛�
+     * 1. 鏍¢獙鐨噸/姣涢噸锛岃绠楀噣閲�
+     * 2. 绾㈠鍋忕Щ闃蹭綔寮婂垽瀹氾紙鍋忕Щ鐜囪秴闃堝�煎垯鎶ヨ骞舵爣璁颁綔寮婏級
+     * 3. 鍖归厤閲囪喘/閿�鍞鍗曪紙鎸夎溅鐗�+鎵胯繍鍟嗗湪鏈夋晥鍙拌处涓尮閰嶏級
+     * 4. 鐢熸垚纾呭崟鍙蜂笌浜岀淮鐮佸唴瀹�
+     */
+    @Override
+    public UnattendedOperation autoWeigh(UnattendedOperation op) {
+        if (op.getTareWeight() == null || op.getGrossWeight() == null) {
+            throw new IllegalArgumentException("鐨噸鍜屾瘺閲嶄笉鑳戒负绌�");
+        }
+        if (op.getGrossWeight().compareTo(op.getTareWeight()) <= 0) {
+            throw new IllegalArgumentException("姣涢噸蹇呴』澶т簬鐨噸");
+        }
+        if (op.getWeightTime() == null) {
+            op.setWeightTime(LocalDateTime.now());
+        }
+
+        // 1. 鍑�閲�
+        op.setNetWeight(scale(op.getGrossWeight().subtract(op.getTareWeight())));
+
+        // 2. 绾㈠闃蹭綔寮婏細positionOffset 琛ㄧず杞﹁締浣嶇疆鍋忕Щ鐧惧垎姣�
+        BigDecimal offset = op.getPositionOffset() == null ? BigDecimal.ZERO : op.getPositionOffset();
+        if (offset.compareTo(OFFSET_LIMIT) > 0) {
+            op.setInfraredStatus("alarm");
+            op.setCheatFlag(1);
+            op.setCheatReason("杞﹁締浣嶇疆鍋忕Щ " + offset + "%锛岃秴杩囬槇鍊肩殑 " + OFFSET_LIMIT + "%锛岀枒浼兼湭瀹屽叏涓婄");
+            op.setWeighStatus("abnormal");
+        } else {
+            op.setInfraredStatus("normal");
+            op.setCheatFlag(0);
+            op.setCheatReason(null);
+            op.setWeighStatus("done");
+        }
+
+        // 3. 璁㈠崟鍖归厤锛氭寜璐х墿鍚�/鎵胯繍鍟嗗尮閰嶉噰璐垨閿�鍞彴璐�
+        matchOrder(op);
+
+        // 4. 纾呭崟鍙� + 浜岀淮鐮�
+        if (!StringUtils.hasText(op.getWeighNo())) {
+            op.setWeighNo(generateWeighNo(op.getWeightTime()));
+        }
+        op.setQrCode(buildQrContent(op));
+
+        // 5. ERP 榛樿寰呮帹閫�
+        if (!StringUtils.hasText(op.getErpPushStatus())) {
+            op.setErpPushStatus("pending");
+        }
+        return op;
+    }
+
+    @Override
+    public Map<String, Object> pushErp(Long id) {
+        UnattendedOperation op = getById(id);
+        if (op == null) {
+            throw new IllegalArgumentException("鍦扮璁板綍涓嶅瓨鍦�");
+        }
+        Map<String, Object> result = new LinkedHashMap<>();
+        // 瀹為檯椤圭洰姝ゅ璋冪敤 ERP 鎺ュ彛锛涘綋鍓嶄互鏈湴鐘舵�佹祦杞繚璇佷笟鍔¢棴鐜�
+        boolean success = true;
+        UnattendedOperation update = new UnattendedOperation();
+        update.setId(id);
+        update.setErpPushStatus(success ? "success" : "failed");
+        update.setErpPushTime(LocalDateTime.now());
+        updateById(update);
+
+        result.put("id", id);
+        result.put("weighNo", op.getWeighNo());
+        result.put("erpPushStatus", update.getErpPushStatus());
+        result.put("erpPushTime", update.getErpPushTime());
+        result.put("message", success ? "宸叉帹閫佽嚦 ERP" : "ERP 鎺ㄩ�佸け璐�");
+        log.info("[ERP鎺ㄩ�乚 weighNo={}, status={}", op.getWeighNo(), update.getErpPushStatus());
+        return result;
+    }
+
+    /* ------------------------------ 绉佹湁鏂规硶 ------------------------------ */
+
+    private List<UnattendedOperation> listBetween(LocalDateTime start, LocalDateTime end) {
+        return unattendedOperationMapper.selectList(
+                Wrappers.<UnattendedOperation>lambdaQuery()
+                        .between(UnattendedOperation::getWeightTime, start, end)
+                        .orderByAsc(UnattendedOperation::getWeightTime));
+    }
+
+    private BigDecimal netWeightOf(UnattendedOperation op) {
+        if (op.getNetWeight() != null) {
+            return op.getNetWeight();
+        }
+        if (op.getGrossWeight() != null && op.getTareWeight() != null) {
+            return op.getGrossWeight().subtract(op.getTareWeight());
+        }
+        return BigDecimal.ZERO;
+    }
+
+    private boolean isAbnormal(UnattendedOperation op) {
+        if (op.getCheatFlag() != null && op.getCheatFlag() == 1) {
+            return true;
+        }
+        if ("abnormal".equalsIgnoreCase(op.getInfraredStatus())) {
+            return true;
+        }
+        return op.getPositionOffset() != null && op.getPositionOffset().compareTo(OFFSET_LIMIT) > 0;
+    }
+
+    private void matchOrder(UnattendedOperation op) {
+        // 閫氳繃璁惧/璐х墿淇℃伅鍖归厤鍙拌处锛涘懡涓嵆鍐欏叆璁㈠崟淇℃伅銆�
+        List<Map<String, Object>> rows = Collections.emptyList();
+        if (StringUtils.hasText(op.getCarrierName())) {
+            rows = unattendedOperationMapper.matchSalesOrder(op.getCarrierName());
+            if (rows == null || rows.isEmpty()) {
+                rows = unattendedOperationMapper.matchPurchaseOrder(op.getCarrierName());
+            }
+        }
+        if (rows != null && !rows.isEmpty()) {
+            Map<String, Object> row = rows.get(0);
+            op.setOrderNo(Objects.toString(row.get("order_no"), null));
+            op.setOrderType(Objects.toString(row.get("order_type"), null));
+            op.setOrderMatched(1);
+        } else {
+            op.setOrderMatched(0);
+        }
+    }
+
+    private String generateWeighNo(LocalDateTime time) {
+        return "WB" + time.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+                + String.format("%03d", ThreadLocalRandom.current().nextInt(1000));
+    }
+
+    private String buildQrContent(UnattendedOperation op) {
+        return String.format("%s|%s|%s|%s|%s|%s",
+                text(op.getWeighNo()),
+                text(op.getPlateNumber()),
+                op.getNetWeight() == null ? "" : op.getNetWeight().toPlainString(),
+                op.getWeightTime() == null ? "" : op.getWeightTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
+                text(op.getCarrierName()),
+                text(op.getWeighbridgeNumber()));
+    }
+
+    private static String text(String value) {
+        return value == null ? "" : value;
+    }
+
+    private static BigDecimal scale(BigDecimal v) {
+        return v == null ? BigDecimal.ZERO : v.setScale(2, RoundingMode.HALF_UP);
     }
 }

--
Gitblit v1.9.3