Przeglądaj źródła

Merge remote-tracking branch 'origin/dev_20260131' into dev_20260131

donglang 2 miesięcy temu
rodzic
commit
f968ec72b8
22 zmienionych plików z 2003 dodań i 306 usunięć
  1. 2 0
      sckw-modules-api/sckw-contract-api/src/main/java/com/sckw/contract/api/model/dto/res/LogisticsOrderDto.java
  2. 2 2
      sckw-modules-api/sckw-payment-api/src/main/java/com/sckw/payment/api/feign/PaymentFeignService.java
  3. 3 3
      sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/feign/VehicleTraceFeignConfig.java
  4. 41 10
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/service/operateService/KwcContractTradeService.java
  5. 1 0
      sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/req/TradeOrderParam.java
  6. 35 31
      sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java
  7. 74 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsPrinterController.java
  8. 101 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsWeighbridgeController.java
  9. 142 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsWeighbridgeRecordController.java
  10. 1 1
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/server/DataPermissionServerController.java
  11. 16 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/ValidateLicensePlateMapper.java
  12. 53 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/ValidateLicensePlate.java
  13. 26 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/LicensePlateValidateRequest.java
  14. 58 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/WeighbridgePushRequest.java
  15. 68 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/LicensePlateValidateResponse.java
  16. 345 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/DataPermissionHelper.java
  17. 644 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsWeighbridgeManageService.java
  18. 327 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsWeighbridgeRecordManageService.java
  19. 18 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/ValidateLicensePlateService.java
  20. 36 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/ValidateLicensePlateServiceImpl.java
  21. 0 259
      sckw-modules/sckw-system/src/main/java/com/sckw/system/utils/DataPermissionHelper.java
  22. 10 0
      sql/2026/04/2026_04_08_validate_license_plate.sql

+ 2 - 0
sckw-modules-api/sckw-contract-api/src/main/java/com/sckw/contract/api/model/dto/res/LogisticsOrderDto.java

@@ -29,4 +29,6 @@ public class LogisticsOrderDto implements Serializable {
      * 企业类型(3-托运 4-承运)
      */
     private Integer entType;
+
+    private Long tradeContractId;
 }

+ 2 - 2
sckw-modules-api/sckw-payment-api/src/main/java/com/sckw/payment/api/feign/PaymentFeignService.java

@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestParam;
 
 import java.math.BigDecimal;
 
-@FeignClient(name = "sckw-payment-new",contextId = "paymentFeignService")
+@FeignClient(name = "sckw-ng-payment-new",contextId = "paymentFeignService")
 public interface PaymentFeignService {
 
     @GetMapping("/walletPrepaid/queryPrepaidBalance")
@@ -21,7 +21,7 @@ public interface PaymentFeignService {
     /**
      * 初始化预付清单
      */
-    @PostMapping("/initPayable")
+    @PostMapping("/initPrepaidBalance")
     BaseResult<Object> initPrepaidBalance(@RequestBody WalletPrepaidDto prepaidDto);
 
     /**

+ 3 - 3
sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/feign/VehicleTraceFeignConfig.java

@@ -25,7 +25,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.concurrent.TimeUnit;
 
 /**
- * 车辆轨迹服务 Feign 配置
+ * 远程服务 Feign 配置
  * @author system
  * @date 2024
  */
@@ -78,9 +78,9 @@ public class VehicleTraceFeignConfig {
             } catch (IOException e) {
                 log.warn("读取响应体失败", e);
             }
-            log.error("车辆轨迹服务调用失败, 方法: {}, 状态码: {}, 响应体: {}", 
+            log.error("远程服务调用失败, 方法: {}, 状态码: {}, 响应体: {}",
                 methodKey, response.status(), responseBody);
-            return new RuntimeException("车辆轨迹服务调用失败: " + response.reason() + ", 响应: " + responseBody);
+            return new RuntimeException("远程服务调用失败: " + response.reason() + ", 响应: " + responseBody);
         };
     }
 

+ 41 - 10
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/service/operateService/KwcContractTradeService.java

@@ -128,7 +128,7 @@ public class KwcContractTradeService {
     @DubboReference(version = "1.0.0", group = "design", check = false)
     private GoodsInfoService goodsInfoService;
 
-    @DubboReference(version = "1.0.0", group = "design", check = false,timeout = 8000)
+    @DubboReference(version = "1.0.0", group = "design", check = false, timeout = 8000)
     private TradeOrderInfoService tradeOrderInfoService;
     @DubboReference(version = "1.0.0", group = "design", check = false, timeout = 8000)
     private TransportRemoteService transportRemoteService;
@@ -363,7 +363,7 @@ public class KwcContractTradeService {
             if (Objects.equals(baseInfo.getUnloadWay(), Integer.parseInt(DictEnum.LOAD_UNLOAD_WAY_2.getValue())) && Objects.isNull(baseInfo.getMeasurementWay())) {
                 throw new SystemException("卸货方式为按卸货筽量时,计量方式不能为空!");
             }
-            if(Objects.equals(baseInfo.getSettlement(),1)){
+            if (Objects.equals(baseInfo.getSettlement(), 1)) {
                 throw new SystemException("暂不支持普通结算模式!");
             }
         }
@@ -1414,7 +1414,7 @@ public class KwcContractTradeService {
             if (StringUtils.isNotBlank(baseInfo.getContractName()) && baseInfo.getContractName().length() > 20) {
                 throw new SystemException("合同名称超长!");
             }
-            if(Objects.equals(baseInfo.getSettlement(),1)){
+            if (Objects.equals(baseInfo.getSettlement(), 1)) {
                 throw new SystemException("暂不支持普通结算模式!");
             }
         }
@@ -1482,7 +1482,7 @@ public class KwcContractTradeService {
             updateKwcContractTrade.setStatus(ContractStatusEnum.COMPLETE.getCode());
         }
         Boolean result = kwcContractTradeRepository.updateByContractId(updateKwcContractTrade);
-        if(Objects.equals(req.getStatus(), ContractStatusEnum.SIGNED.getCode()) && Objects.equals(updateKwcContractTrade.getStatus(),ContractStatusEnum.SIGNED.getCode())){
+        if (Objects.equals(req.getStatus(), ContractStatusEnum.SIGNED.getCode()) && Objects.equals(updateKwcContractTrade.getStatus(), ContractStatusEnum.SIGNED.getCode())) {
             //签约成功,初始化预付清单
             //根据订单号查询交易企业信息
             List<KwcContractTradeUnit> tradeUnits = kwcContractTradeUnitRepository.queryByContractId(kwcContractTrade.getId());
@@ -1493,9 +1493,9 @@ public class KwcContractTradeService {
             prepaidDto.setTradeAmount(BigDecimal.ZERO);
             prepaidDto.setRemark("预付清单初始化");
             BaseResult<Object> prepaidResult = paymentFeignService.initPrepaidBalance(prepaidDto);
-            if (prepaidResult.getCode() == HttpStatus.SUCCESS_CODE){
+            if (prepaidResult.getCode() == HttpStatus.SUCCESS_CODE) {
                 log.info("创建预付清单成功");
-            }else {
+            } else {
                 log.error("创建预付清单失败");
                 throw new BusinessException("创建预付清单失败");
             }
@@ -1924,7 +1924,7 @@ public class KwcContractTradeService {
 
 
     public List<TradeEntInfoResVo> queryTradeEntList(TradeEntListQueryFeignDto queryFeignDto) {
-        List<TradeEntInfoResVo> resVos =  new ArrayList<>();
+        List<TradeEntInfoResVo> resVos = new ArrayList<>();
         Long entId = queryFeignDto.getCurEntId();
         Set<Long> entIds = Sets.newHashSet();
         entIds.add(entId);
@@ -2107,9 +2107,40 @@ public class KwcContractTradeService {
         }
         Map<String, KwcContractLogisticsUnit> finalContractLogisticsMap = contractLogisticsMap;
         Map<Long, KwcContractLogisticsGoods> finalContractLogisticsGoodsMap = contractLogisticsGoodsMap;
-        return logistics.stream()
+        List<ContractLogisticsOrderResDto> collect = logistics.stream()
                 .map(log -> getContractLogisticsOrderResDto(log, finalContractLogisticsMap, finalContractLogisticsGoodsMap))
                 .collect(Collectors.toList());
+        // 贸易合同 按装货量 并且买家托运时
+        Long tradeContractId = logisticsOrderDto.getTradeContractId();
+
+        if (Objects.nonNull(tradeContractId)) {
+            KwcContractTrade kwcContractTrade = kwcContractTradeMapper.selectById(tradeContractId);
+            if (Objects.nonNull(kwcContractTrade)) {
+                if (Objects.equals(kwcContractTrade.getConsignment(), 1) && Objects.equals(kwcContractTrade.getUnloadWay(), 1)) {
+                    ContractLogisticsOrderResDto contractLogisticsOrderResDto = new ContractLogisticsOrderResDto();
+                    contractLogisticsOrderResDto.setId(0L);
+                    contractLogisticsOrderResDto.setContactName("平台默认物流");
+                    contractLogisticsOrderResDto.setContractNo("MRWL");
+                    contractLogisticsOrderResDto.setSigningWay(0);
+                    contractLogisticsOrderResDto.setSigningWayName("");
+                    contractLogisticsOrderResDto.setStatus(0);
+                    contractLogisticsOrderResDto.setStatusName("");
+                    contractLogisticsOrderResDto.setConsignEntId(0L);
+                    contractLogisticsOrderResDto.setConsignEntName("");
+                    contractLogisticsOrderResDto.setConsignEntPhone("");
+                    contractLogisticsOrderResDto.setConsignEntContactId(0L);
+                    contractLogisticsOrderResDto.setConsignEntContact("");
+                    contractLogisticsOrderResDto.setAcceptCarriageEntId(0L);
+                    contractLogisticsOrderResDto.setAcceptCarriageEntPhone("");
+                    contractLogisticsOrderResDto.setAcceptCarriageEntContactId(0L);
+                    contractLogisticsOrderResDto.setAcceptCarriageEntContact("");
+                    contractLogisticsOrderResDto.setAcceptCarriageEntName("");
+                    contractLogisticsOrderResDto.setPrice(new BigDecimal("0"));
+                    collect.add(0, contractLogisticsOrderResDto);
+                }
+            }
+        }
+        return collect;
 
     }
 
@@ -2678,14 +2709,14 @@ public class KwcContractTradeService {
                     entOk = record.getEntId() != null && perm.getVisibleEntIds().contains(record.getEntId());
                 }
             }
-            
+
             // 校验销售人员个人数据权限
             boolean salesOk = true;
             if (perm.isPersonalDataEnabled()) {
                 // 如果开启了个人数据权限,检查当前用户是否为该合同的销售人员
                 salesOk = perm.getUserId() != null && perm.getUserId().equals(record.getSalesmanId());
             }
-            
+
             // 同时满足企业权限和销售人员权限
             return entOk && salesOk;
         }).collect(Collectors.toList());

+ 1 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/req/TradeOrderParam.java

@@ -62,6 +62,7 @@ public class TradeOrderParam {
     /**
      * 物流合同 承运单位
      * 合同托运方式是“采购方托运”时,才展示物流信息填写,“供应方托运”时隐藏这一块
+     * 买方托运时,可以选择平台默认物流
      */
     @Schema(description = "物流合同信息")
     private List<LogisticsEntDto> logisticsContractList;

+ 35 - 31
sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java

@@ -8,8 +8,8 @@ import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -43,20 +43,19 @@ import com.sckw.manage.api.model.dto.res.FindEntCooperateResVo;
 import com.sckw.mongo.model.SckwTradeOrder;
 import com.sckw.order.api.model.*;
 import com.sckw.order.dao.KwoTradeOrderMapper;
-import com.sckw.order.enums.*;
 import com.sckw.order.enums.ContractStatusEnum;
+import com.sckw.order.enums.*;
 import com.sckw.order.model.*;
 import com.sckw.order.model.dto.*;
-import com.sckw.order.model.vo.req.*;
 import com.sckw.order.model.vo.req.ContractInfo;
-import com.sckw.order.model.vo.res.*;
+import com.sckw.order.model.vo.req.*;
 import com.sckw.order.model.vo.res.GoodsInfoDetailRes;
 import com.sckw.order.model.vo.res.OrderDetailRes;
 import com.sckw.order.model.vo.res.UnitInfoDetailRes;
+import com.sckw.order.model.vo.res.*;
 import com.sckw.order.repository.KwoTradeOrderUnitRepository;
 import com.sckw.payment.api.dubbo.PayCenterDubboService;
 import com.sckw.payment.api.dubbo.PaymentDubboService;
-import com.sckw.payment.api.model.WalletFreeze;
 import com.sckw.payment.api.feign.PaymentFeignService;
 import com.sckw.payment.api.model.constant.ChannelEnum;
 import com.sckw.payment.api.model.dto.WalletDto;
@@ -2209,6 +2208,11 @@ public class KwoTradeOrderService {
                 kwoTradeOrderTransport.setStatus(0);
                 kwoTradeOrderTransport.setCreateBy(LoginUserHolder.getUserId());
                 kwoTradeOrderTransport.setCreateTime(LocalDateTime.now());
+                if (Objects.equals(logisticsEntDto.getContractId(), 0L)) {//查询平台默认物流对应的企业id
+                    kwoTradeOrderTransport.setEntId(1L);//todo 硬编码,平台默认物流企业
+                    kwoTradeOrderTransport.setFirmName("平台默认物流");
+                }
+
                 kwoTradeOrderTransportService.insert(kwoTradeOrderTransport);
             }
         }
@@ -2235,15 +2239,15 @@ public class KwoTradeOrderService {
         kwoTradeOrderUnitService.insertBatch(list);
         //钱包
 //        BaseResult<Boolean> booleanBaseResult = paymentDubboService.freezeMoney(walletFreeze);
-        BaseResult<Object> freezeBaseResult = new  BaseResult<>();
-        if(Objects.equals(tradeContractResDto.getSettlement(),1)){
+        BaseResult<Object> freezeBaseResult = new BaseResult<>();
+        if (Objects.equals(tradeContractResDto.getSettlement(), 1)) {
             // todo 普通结算冻结
 
-        }else if(Objects.equals(tradeContractResDto.getSettlement(),2)){
+        } else if (Objects.equals(tradeContractResDto.getSettlement(), 2)) {
             //预付制结算冻结
             walletFreeze.setOrderType(4);
             walletFreeze.setTradeType(4);
-            walletFreeze.setRemark("贸易订单号:"+walletFreeze.getOrderNo()+",下单冻结");
+            walletFreeze.setRemark("贸易订单号:" + walletFreeze.getOrderNo() + ",下单冻结");
             freezeBaseResult = paymentFeignService.updatePrepaidBalance(walletFreeze);
         }
 
@@ -2286,7 +2290,7 @@ public class KwoTradeOrderService {
         freezeDto.setOrderNo(String.valueOf(order.getId()));
         freezeDto.setOrderType(4);
         freezeDto.setTradeType(5);
-        freezeDto.setRemark("贸易订单号:"+freezeDto.getOrderNo()+",撤销订单");
+        freezeDto.setRemark("贸易订单号:" + freezeDto.getOrderNo() + ",撤销订单");
         BaseResult<Object> balanceResult;
         try {
             balanceResult = paymentFeignService.updatePrepaidBalance(freezeDto);
@@ -2345,10 +2349,10 @@ public class KwoTradeOrderService {
         //结算方式校验余额
         BigDecimal orderAmount = NumberUtil.mul(tradeOrderParam.getAmount(), goodsInfoDto.getPrice());
         Integer settlement = tradeContractResDto.getSettlement();
-        if(Objects.equals(settlement, 1)){
+        if (Objects.equals(settlement, 1)) {
             // todo 普通结算
 
-        }else {
+        } else {
             //预付制结算
             BaseResult<BigDecimal> prepaidBalanceResult = null;
             try {
@@ -2357,11 +2361,11 @@ public class KwoTradeOrderService {
                 log.error("查询线下钱包服务异常", e);
                 throw new BusinessException("查询线下钱包服务异常,请稍后再试");
             }
-            if(prepaidBalanceResult.getCode() == HttpStatus.SUCCESS_CODE){
-                if(orderAmount.compareTo(prepaidBalanceResult.getData()) > 0){
+            if (prepaidBalanceResult.getCode() == HttpStatus.SUCCESS_CODE) {
+                if (orderAmount.compareTo(prepaidBalanceResult.getData()) > 0) {
                     throw new BusinessException("当前贸易订单金额大于可用预付余额");
                 }
-            }else{
+            } else {
                 throw new BusinessException("查询对应供应商预付余额失败");
             }
         }
@@ -2427,14 +2431,14 @@ public class KwoTradeOrderService {
             walletFreeze.setSupEntId(unitMap.get(String.valueOf(CooperateTypeEnum.SUPPLIER.getCode())).getEntId());
             walletFreeze.setOrderType(4);
             walletFreeze.setTradeType(5);
-            walletFreeze.setRemark("贸易订单:"+walletFreeze.getOrderNo()+",审核拒绝解冻");
+            walletFreeze.setRemark("贸易订单:" + walletFreeze.getOrderNo() + ",审核拒绝解冻");
 //            BaseResult<Boolean> booleanBaseResult = paymentDubboService.unfreezeMoney(walletFreeze);
 
-            BaseResult<Object> booleanBaseResult = new  BaseResult<>();
-            if(Objects.equals(kwoTradeOrder.getSettlement(),1)){
+            BaseResult<Object> booleanBaseResult = new BaseResult<>();
+            if (Objects.equals(kwoTradeOrder.getSettlement(), 1)) {
                 // todo 普通结算解冻结
 
-            }else if(Objects.equals(kwoTradeOrder.getSettlement(),2)){
+            } else if (Objects.equals(kwoTradeOrder.getSettlement(), 2)) {
                 //预付制结算解冻
                 booleanBaseResult = paymentFeignService.updatePrepaidBalance(walletFreeze);
             }
@@ -2808,13 +2812,13 @@ public class KwoTradeOrderService {
         return true;
     }
 
-    private void calculatePrepaidBalance(KwoTradeOrder kwoTradeOrder){
-        if(Objects.equals(kwoTradeOrder.getStatus(),TradeOrderStatusEnum.SUCCESS.getCode())){
+    private void calculatePrepaidBalance(KwoTradeOrder kwoTradeOrder) {
+        if (Objects.equals(kwoTradeOrder.getStatus(), TradeOrderStatusEnum.SUCCESS.getCode())) {
             //贸易订单已完结,计算预付余额
-            if(Objects.equals(kwoTradeOrder.getSettlement(),1)){
+            if (Objects.equals(kwoTradeOrder.getSettlement(), 1)) {
                 //todo 普通结算
 
-            }else if(Objects.equals(kwoTradeOrder.getSettlement(),2)){
+            } else if (Objects.equals(kwoTradeOrder.getSettlement(), 2)) {
                 //预付制模式
                 //1解冻运费
                 List<KwoTradeOrderUnit> orderUnits = kwoTradeOrderUnitService.getByOrderId(kwoTradeOrder.getId());
@@ -2832,11 +2836,11 @@ public class KwoTradeOrderService {
                 unFreezePrepaidDto.setSupEntId(unitMap.get(String.valueOf(CooperateTypeEnum.SUPPLIER.getCode())).getEntId());
                 unFreezePrepaidDto.setOrderType(4);
                 unFreezePrepaidDto.setTradeType(5);
-                unFreezePrepaidDto.setRemark("贸易订单:"+unFreezePrepaidDto.getOrderNo()+",");
+                unFreezePrepaidDto.setRemark("贸易订单:" + unFreezePrepaidDto.getOrderNo() + ",");
                 BaseResult<Object> unFreezeResult = paymentFeignService.updatePrepaidBalance(unFreezePrepaidDto);
-                if(unFreezeResult.getCode()!= HttpStatus.SUCCESS_CODE){
-                    log.error("贸易订单号:{},订单完结解冻失败,异常信息为:{}",unFreezePrepaidDto.getOrderNo(),unFreezeResult.getMessage());
-                    throw new BusinessException("贸易订单号:"+unFreezePrepaidDto.getOrderNo()+",订单完结解冻失败");
+                if (unFreezeResult.getCode() != HttpStatus.SUCCESS_CODE) {
+                    log.error("贸易订单号:{},订单完结解冻失败,异常信息为:{}", unFreezePrepaidDto.getOrderNo(), unFreezeResult.getMessage());
+                    throw new BusinessException("贸易订单号:" + unFreezePrepaidDto.getOrderNo() + ",订单完结解冻失败");
                 }
                 //2计算订单金额
                 WalletPrepaidDto consumePrepaidDto = new WalletPrepaidDto();
@@ -2846,11 +2850,11 @@ public class KwoTradeOrderService {
                 unFreezePrepaidDto.setOrderType(4);
                 unFreezePrepaidDto.setTradeType(6);
                 consumePrepaidDto.setTradeAmount(kwoTradeOrder.getPrice());
-                consumePrepaidDto.setRemark("贸易订单号:"+consumePrepaidDto.getOrderNo()+",订单完结消费");
+                consumePrepaidDto.setRemark("贸易订单号:" + consumePrepaidDto.getOrderNo() + ",订单完结消费");
                 BaseResult<Object> consumeResult = paymentFeignService.updatePrepaidBalance(consumePrepaidDto);
-                if(consumeResult.getCode()!= HttpStatus.SUCCESS_CODE){
-                    log.error("贸易订单号:{},订单完结消费失败,异常信息为:{}",consumePrepaidDto.getOrderNo(),consumeResult.getMessage());
-                    throw new BusinessException("贸易订单号:"+consumePrepaidDto.getOrderNo()+",订单完结消费失败");
+                if (consumeResult.getCode() != HttpStatus.SUCCESS_CODE) {
+                    log.error("贸易订单号:{},订单完结消费失败,异常信息为:{}", consumePrepaidDto.getOrderNo(), consumeResult.getMessage());
+                    throw new BusinessException("贸易订单号:" + consumePrepaidDto.getOrderNo() + ",订单完结消费失败");
                 }
             }
         }

+ 74 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsPrinterController.java

@@ -0,0 +1,74 @@
+package com.sckw.system.controller;
+
+import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.core.web.response.HttpResult;
+import com.sckw.system.model.vo.req.PrinterPageReqVo;
+import com.sckw.system.model.vo.req.PrinterSaveReqVo;
+import com.sckw.system.model.vo.req.PrinterStatusReqVo;
+import com.sckw.system.service.KwsPrinterManageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 打印机管理控制器。
+ */
+@RestController
+@RequestMapping("/kwsPrinter")
+@Tag(name = "打印机管理")
+@RequiredArgsConstructor
+public class KwsPrinterController {
+
+    private final KwsPrinterManageService kwsPrinterManageService;
+
+    @PostMapping("/page")
+    @Operation(summary = "打印机分页查询")
+    public HttpResult page(@RequestBody PrinterPageReqVo reqVo) {
+        return HttpResult.ok(kwsPrinterManageService.page(reqVo));
+    }
+
+    @GetMapping("/detail")
+    @Operation(summary = "打印机详情")
+    public HttpResult detail(@RequestParam Long id) {
+        return HttpResult.ok(kwsPrinterManageService.detail(id));
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "新增打印机")
+    public HttpResult add(@RequestBody PrinterSaveReqVo reqVo) {
+        kwsPrinterManageService.add(reqVo);
+        return HttpResult.ok(HttpStatus.MSG_003);
+    }
+
+    @PostMapping("/update")
+    @Operation(summary = "编辑打印机")
+    public HttpResult update(@RequestBody PrinterSaveReqVo reqVo) {
+        kwsPrinterManageService.update(reqVo);
+        return HttpResult.ok(HttpStatus.MSG_005);
+    }
+
+    @PostMapping("/updateStatus")
+    @Operation(summary = "启用停用打印机")
+    public HttpResult updateStatus(@RequestBody PrinterStatusReqVo reqVo) {
+        kwsPrinterManageService.updateStatus(reqVo);
+        return HttpResult.ok(HttpStatus.MSG_005);
+    }
+
+    @GetMapping("/enterpriseOptions")
+    @Operation(summary = "企业模糊搜索")
+    public HttpResult enterpriseOptions(@RequestParam(required = false) String keyword) {
+        return HttpResult.ok(kwsPrinterManageService.enterpriseOptions(keyword));
+    }
+
+    @GetMapping("/optionsByEntId")
+    @Operation(summary = "按企业查询打印机下拉")
+    public HttpResult optionsByEntId(@RequestParam Long entId) {
+        return HttpResult.ok(kwsPrinterManageService.optionByEntId(entId));
+    }
+}

+ 101 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsWeighbridgeController.java

@@ -0,0 +1,101 @@
+package com.sckw.system.controller;
+
+import com.sckw.core.utils.TruckNoUtils;
+import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.core.web.response.HttpResult;
+import com.sckw.system.model.vo.req.*;
+import com.sckw.system.model.vo.res.LicensePlateValidateResponse;
+import com.sckw.system.service.KwsWeighbridgeManageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.math.BigDecimal;
+
+/**
+ * 地磅控制器
+ */
+@RestController
+@RequestMapping("/kwsWeighbridge")
+@Tag(name = "地磅管理")
+@RequiredArgsConstructor
+public class KwsWeighbridgeController {
+
+    private final KwsWeighbridgeManageService kwsWeighbridgeManageService;
+
+    @PostMapping("/page")
+    @Operation(summary = "地磅分页查询")
+    public HttpResult page(@RequestBody WeighbridgePageReqVo reqVo) {
+        return HttpResult.ok(kwsWeighbridgeManageService.page(reqVo));
+    }
+
+    @GetMapping("/detail")
+    @Operation(summary = "地磅详情")
+    public HttpResult detail(@RequestParam Long id) {
+        return HttpResult.ok(kwsWeighbridgeManageService.detail(id));
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "新增地磅")
+    public HttpResult add(@RequestBody WeighbridgeSaveReqVo reqVo) {
+        kwsWeighbridgeManageService.add(reqVo);
+        return HttpResult.ok(HttpStatus.MSG_003);
+    }
+
+    @PostMapping("/update")
+    @Operation(summary = "更新地磅")
+    public HttpResult update(@RequestBody WeighbridgeSaveReqVo reqVo) {
+        kwsWeighbridgeManageService.update(reqVo);
+        return HttpResult.ok(HttpStatus.MSG_005);
+    }
+
+    @PostMapping("/updateStatus")
+    @Operation(summary = "更新地磅状态,停用启用接口")
+    public HttpResult updateStatus(@RequestBody WeighbridgeStatusReqVo reqVo) {
+        kwsWeighbridgeManageService.updateStatus(reqVo);
+        return HttpResult.ok(HttpStatus.MSG_005);
+    }
+
+    @PostMapping("/updateDiffConfig")
+    @Operation(summary = "保存地磅差值配置")
+    public HttpResult updateDiffConfig(@RequestBody WeighbridgeDiffConfigReqVo reqVo) {
+        kwsWeighbridgeManageService.updateDiffConfig(reqVo);
+        return HttpResult.ok(HttpStatus.MSG_005);
+    }
+
+    @GetMapping("/diffConfigDetail")
+    @Operation(summary = "获取地磅差值配置详情")
+    public HttpResult diffConfigDetail(@RequestParam Long entId) {
+        return HttpResult.ok(kwsWeighbridgeManageService.diffConfigDetail(entId));
+    }
+
+    @PostMapping("/restart")
+    @Operation(summary = "重启地磅")
+    public HttpResult restart(@RequestBody WeighbridgeRestartReqVo reqVo) {
+        kwsWeighbridgeManageService.restart(reqVo);
+        return HttpResult.ok("重启命令已记录");
+    }
+
+    @GetMapping("/enterpriseOptions")
+    @Operation(summary = "搜索企业选项")
+    public HttpResult enterpriseOptions(@RequestParam(required = false) String keyword) {
+        return HttpResult.ok(kwsWeighbridgeManageService.enterpriseOptions(keyword));
+    }
+
+    @GetMapping("/checkUniqueCode")
+    @Operation(summary = "检查唯一编码可用性")
+    public HttpResult checkUniqueCode(@RequestParam String uniqueCode,
+                                      @RequestParam(required = false) Long id) {
+        return HttpResult.ok(kwsWeighbridgeManageService.checkUniqueCodeAvailable(uniqueCode, id));
+    }
+
+}

+ 142 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsWeighbridgeRecordController.java

@@ -0,0 +1,142 @@
+package com.sckw.system.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.sckw.core.utils.TruckNoUtils;
+import com.sckw.core.web.response.HttpResult;
+import com.sckw.excel.utils.ExcelUtil;
+import com.sckw.system.model.report.WeighbridgeRecordExcel;
+import com.sckw.system.model.vo.req.LicensePlateValidateRequest;
+import com.sckw.system.model.vo.req.WeighbridgePushRequest;
+import com.sckw.system.model.vo.req.WeighbridgeRecordPageReqVo;
+import com.sckw.system.model.vo.res.LicensePlateValidateResponse;
+import com.sckw.system.service.KwsWeighbridgeRecordManageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 地磅记录控制器
+ */
+@RestController
+@RequestMapping("/kwsWeighbridgeRecord")
+@Tag(name = "地磅记录")
+@RequiredArgsConstructor
+public class KwsWeighbridgeRecordController {
+
+    private final KwsWeighbridgeRecordManageService kwsWeighbridgeRecordManageService;
+
+    @PostMapping("/page")
+    @Operation(summary = "地磅记录分页查询")
+    public HttpResult page(@RequestBody WeighbridgeRecordPageReqVo reqVo) {
+        return HttpResult.ok(kwsWeighbridgeRecordManageService.page(reqVo));
+    }
+
+    @PostMapping("/export")
+    @Operation(summary = "导出地磅记录")
+    public void export(@RequestBody WeighbridgeRecordPageReqVo reqVo, HttpServletResponse response) throws IOException {
+        List<WeighbridgeRecordExcel> data = kwsWeighbridgeRecordManageService.exportList(reqVo);
+        if (data == null || data.isEmpty()) {
+            response.setContentType("application/json");
+            response.setCharacterEncoding("UTF-8");
+            response.getWriter().write(JSONObject.toJSONString(HttpResult.error("没有可导出的地磅记录")));
+            return;
+        }
+        ExcelUtil.download(response, WeighbridgeRecordExcel.class, data);
+    }
+
+
+
+    /**
+     * 地磅过磅数据上报
+     */
+    @Operation(summary = "地磅过磅数据上报", description = "接收地磅设备上报的过磅数据和图片")
+    @PostMapping("/weighBridgePush")
+    public LicensePlateValidateResponse weighBridgePush(
+            @Parameter(description = "车牌号") @RequestParam("licensePlate") String licensePlate,
+            @Parameter(description = "地磅编号") @RequestParam("weighbridgeCode") String weighbridgeCode,
+            @Parameter(description = "称重重量(吨)") @RequestParam("grossWeight") String grossWeight,
+            @Parameter(description = "时间戳(秒或毫秒)") @RequestParam("timestamp") String timestamp,
+            @Parameter(description = "处理标签") @RequestParam(value = "tag", required = false) String tag,
+            @Parameter(description = "车辆照片") @RequestParam(value = "images[]", required = false) MultipartFile[] images
+    ) {
+        String cleanedLicensePlate = sanitizeText(licensePlate);
+        if (!TruckNoUtils.isValidTruckNo(cleanedLicensePlate)) {
+            return buildPushErrorResponse("车牌号格式不正确");
+        }
+
+        WeighbridgePushRequest request = new WeighbridgePushRequest();
+        request.setLicensePlate(TruckNoUtils.formatTruckNo(cleanedLicensePlate));
+        request.setWeighbridgeCode(sanitizeText(weighbridgeCode));
+        request.setGrossWeight(parseBigDecimal(grossWeight));
+        request.setTimestamp(parseLong(timestamp));
+        request.setTag(sanitizeText(tag));
+        request.setImages(images);
+
+        // 调用业务层处理
+        return kwsWeighbridgeRecordManageService.handleWeighbridgePush(request);
+    }
+
+    /**
+     * 车牌验证
+     */
+    @Operation(summary = "车牌验证", description = "用于验证车牌是否合法,允许上磅")
+    @PostMapping("/validateLicensePlate")
+    public LicensePlateValidateResponse validateLicensePlate(
+            @Parameter(description = "车牌号") @RequestParam("licensePlate") String licensePlate,
+            @Parameter(description = "厂商来源标识") @RequestParam("uuid") String uuid
+    ) {
+        LicensePlateValidateRequest request = new LicensePlateValidateRequest();
+        String replac2 = "";
+        if (StringUtils.isNotBlank(licensePlate)) {
+            String trim = licensePlate.trim();
+            String replace = trim.replace("\\r", "");
+            String replace1 = replace.replace("\\n", "");
+            replac2 = replace1.replace("\\r\\n", "");
+        }
+
+        request.setLicensePlate(replac2);
+        request.setUuid(uuid);
+        return kwsWeighbridgeRecordManageService.handleValidateLicensePlate(request);
+    }
+
+    private String sanitizeText(String value) {
+        if (StringUtils.isBlank(value)) {
+            return "";
+        }
+        return value.trim()
+                .replace("\r", "")
+                .replace("\n", "");
+    }
+
+    private BigDecimal parseBigDecimal(String value) {
+        String cleanedValue = sanitizeText(value);
+        return StringUtils.isBlank(cleanedValue) ? null : new BigDecimal(cleanedValue);
+    }
+
+    private Long parseLong(String value) {
+        String cleanedValue = sanitizeText(value);
+        return StringUtils.isBlank(cleanedValue) ? null : Long.valueOf(cleanedValue);
+    }
+
+    private LicensePlateValidateResponse buildPushErrorResponse(String message) {
+        LicensePlateValidateResponse response = new LicensePlateValidateResponse();
+        response.setStatus(false);
+        response.setCode(400);
+        response.setMessage(message);
+        LicensePlateValidateResponse.Data data = new LicensePlateValidateResponse.Data();
+        data.setTimestamp(System.currentTimeMillis() / 1000);
+        data.setScreen_message(message);
+        data.setVoice_message(message);
+        response.setData(data);
+        return response;
+    }
+}

+ 1 - 1
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/server/DataPermissionServerController.java

@@ -2,7 +2,7 @@ package com.sckw.system.controller.server;
 
 import com.sckw.system.api.model.dto.req.DataPermissionFilterReqDto;
 import com.sckw.system.api.model.dto.res.DataPermissionDTO;
-import com.sckw.system.utils.DataPermissionHelper;
+import com.sckw.system.service.DataPermissionHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;

+ 16 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/ValidateLicensePlateMapper.java

@@ -0,0 +1,16 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import com.sckw.system.model.ValidateLicensePlate;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @Author: cxf
+ * @CreateTime: 2026-01-21
+ * @Description: 地磅过磅记录Mapper接口
+ * @Version: 1.0
+ */
+@Mapper
+public interface ValidateLicensePlateMapper extends BaseMapper<ValidateLicensePlate> {
+}

+ 53 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/ValidateLicensePlate.java

@@ -0,0 +1,53 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 车牌识别表实体类
+ *
+ * @author cxf
+ */
+@Data
+@TableName("validate_license_plate")
+public class ValidateLicensePlate {
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 车牌号
+     */
+    @TableField("license_plate")
+    private String licensePlate;
+
+    /**
+     * 厂商来源标识
+     */
+    @TableField("uuid")
+    private String uuid;
+
+    /**
+     * 状态,0=可用,1=不可用
+     */
+    @TableField("status")
+    private Integer status;
+
+    /**
+     * 记录创建时间
+     */
+    @TableField(value = "created_at", fill = FieldFill.INSERT)
+    private LocalDateTime createdAt;
+
+    /**
+     * 记录更新时间
+     */
+    @TableField(value = "updated_at", fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updatedAt;
+
+}

+ 26 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/LicensePlateValidateRequest.java

@@ -0,0 +1,26 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 车牌验证请求参数
+ * @author PC
+ */
+@Data
+@Schema(description = "车辆识别请求参数")
+public class LicensePlateValidateRequest {
+
+    /**
+     * 车牌号
+     */
+    @Schema(description = "车牌号", example = "川A1234")
+    private String licensePlate;
+
+    /**
+     * 厂商来源标识
+     */
+    @Schema(description = "厂商来源标识", example = "kw38146288dce12d6938d30ebc3a9db6c5")
+    private String uuid;
+
+}

+ 58 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/WeighbridgePushRequest.java

@@ -0,0 +1,58 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.math.BigDecimal;
+
+/**
+ * 地磅过磅数据上报请求参数
+ * @author PC
+ */
+@Data
+@Schema(description = "地磅过磅数据上报请求参数")
+public class WeighbridgePushRequest {
+
+    /**
+     * 车牌号
+     */
+    @NotBlank(message = "车牌号不能为空")
+    @Schema(description = "车牌号", example = "川A1234")
+    private String licensePlate;
+
+    /**
+     * 地磅编号
+     */
+    @NotBlank(message = "地磅编号不能为空")
+    @Schema(description = "地磅编号", example = "10100111")
+    private String weighbridgeCode;
+
+    /**
+     * 称重重量(吨)
+     */
+    @NotNull(message = "称重重量不能为空")
+    @Schema(description = "称重重量(吨)", example = "34.5")
+    private BigDecimal grossWeight;
+
+    /**
+     * 时间戳(支持秒或毫秒)
+     */
+    @NotNull(message = "时间戳不能为空")
+    @Schema(description = "时间戳(秒或毫秒)", example = "1745483981")
+    private Long timestamp;
+
+    /**
+     * 处理标签,用于指定处理方向
+     */
+    @Schema(description = "处理标签", example = "kll")
+    private String tag;
+
+    /**
+     * 车辆图片
+     */
+    @Schema(description = "车辆图片数组")
+    private MultipartFile[] images;
+}

+ 68 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/LicensePlateValidateResponse.java

@@ -0,0 +1,68 @@
+package com.sckw.system.model.vo.res;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * Author: donglang
+ * Time: 2026-01-21
+ * Des: 车牌验证通用返回模型
+ * Version: 1.0
+ */
+
+@Data
+@Schema(description = "车牌验证通用返回模型")
+public class LicensePlateValidateResponse {
+    /**
+     * 返回状态
+     */
+    @Schema(description = "返回状态")
+    private Boolean status;
+
+    /**
+     * 返回状态值
+     */
+    @Schema(description = "返回描述")
+    private Integer code;
+
+    /**
+     * 返回数据集
+     */
+    @Schema(description = "返回数据集")
+    private String message;
+
+    /**
+     * 数据
+     */
+    @Schema(description = "数据")
+    private Data data;
+
+    @lombok.Data
+    public static class Data {
+
+        /**
+         * 返回处理时间
+         */
+        @Schema(description = "返回处理时间")
+        private Long timestamp;
+
+        /**
+         * 播报内容
+         */
+        @Schema(description = "播报内容")
+        private String screen_message;
+
+        /**
+         * LED显示内容
+         */
+        @Schema(description = "LED显示内容")
+        private String voice_message;
+
+        /**
+         * 警告
+         */
+        @Schema(description = "用于给出警告信息,提示当前环境为测试服。")
+        private String warning;
+    }
+}

+ 345 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/DataPermissionHelper.java

@@ -0,0 +1,345 @@
+package com.sckw.system.service;
+
+import com.sckw.core.utils.CollectionUtils;
+import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.system.api.model.dto.res.DataPermissionDTO;
+import com.sckw.system.model.KwsRole;
+import com.sckw.system.model.KwsUser;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * 数据权限过滤工具类
+ * <p>
+ * 用于业务查询拦截,在查询贸易合同/订单/运单等业务数据时,
+ * 根据当前用户角色的数据权限配置过滤可见数据范围。
+ * <p>
+ * 企业数据权限 + 个人数据权限为并集(组合)关系:
+ * <ul>
+ *     <li>企业数据权限:限制可见企业范围 → SQL: ent_id IN (已选企业)</li>
+ *     <li>个人数据权限开启后:在企业范围内仅查看销售人员=本人的数据
+ *         → SQL: ent_id IN (已选企业) AND salesman_id = 当前用户</li>
+ *     <li>覆盖范围:贸易合同及其衍生的贸易订单、物流订单、运单</li>
+ * </ul>
+ * <p>
+ *
+ * @author system
+ * @date 2026-03-26
+ */
+@Slf4j
+@Component
+public class DataPermissionHelper {
+
+    @Autowired
+    private KwsDataPermissionService kwsDataPermissionService;
+
+    @Autowired
+    private KwsRoleService kwsRoleService;
+
+    @Autowired
+    private KwsUserService kwsUserService;
+
+    /**
+     * 获取当前登录用户的数据权限过滤条件
+     * <p>
+     * 规则:
+     * - 平台端管理员:仅管理平台自身创建的角色,数据权限按角色配置
+     * - 客户端主账号:可管理本企业+下级企业的角色
+     * - 所有用户:企业数据权限 + 个人数据权限为并集关系
+     * - 个人数据权限开启后:在已勾选企业范围内,仅查看销售人员=当前用户的数据
+     *
+     * @return 数据权限过滤条件
+     */
+    public DataPermissionFilter getPermissionFilter() {
+        Long userId = LoginUserHolder.getUserId();
+        log.info("开始计算数据权限过滤条件, userId={}", userId);
+
+        DataPermissionFilter filter = new DataPermissionFilter();
+        filter.setUserId(userId);
+
+        // 平台管理员默认可见全部
+        if (LoginUserHolder.isManager()) {
+            Long roleId = LoginUserHolder.getCurrentRoleId();
+            log.info("当前用户为平台管理员, userId={}, roleId={}", userId, roleId);
+            if (Objects.isNull(roleId)) {
+                log.info("平台管理员未指定角色ID,默认拥有全部数据权限, userId={}", userId);
+                filter.setAllVisible(true);
+                return filter;
+            }
+            DataPermissionFilter result = buildFilterByRole(roleId, userId, false);
+            log.info("平台管理员基于角色构建权限完成, userId={}, roleId={}, allVisible={}, entIdsSize={}",
+                    userId, roleId, result.isAllVisible(),
+                    result.getVisibleEntIds() != null ? result.getVisibleEntIds().size() : 0);
+            return result;
+        }
+
+        // 客户端用户:获取当前使用的角色
+        Long roleId = LoginUserHolder.getCurrentRoleId();
+        log.info("当前用户为客户端用户, userId={}, currentRoleId={}", userId, roleId);
+
+        if (Objects.isNull(roleId)) {
+            // 无角色,查询用户所有角色取并集
+            log.info("当前用户未指定角色ID,尝试获取用户所有角色并集, userId={}", userId);
+            List<KwsRole> roles = kwsRoleService.queryRoleByUserId(userId);
+            if (CollectionUtils.isEmpty(roles)) {
+                log.warn("用户无任何角色关联,设置为无数据权限, userId={}", userId);
+                filter.setAllVisible(false);
+                filter.setVisibleEntIds(Collections.emptySet());
+                return filter;
+            }
+            DataPermissionFilter result = buildFilterByRoles(roles, userId, true);
+            log.info("基于用户多角色并集构建权限完成, userId={}, roleCount={}, allVisible={}, entIdsSize={}",
+                    userId, roles.size(), result.isAllVisible(),
+                    result.getVisibleEntIds() != null ? result.getVisibleEntIds().size() : 0);
+            return result;
+        }
+
+        DataPermissionFilter result = buildFilterByRole(roleId, userId, true);
+        log.info("基于单角色构建权限完成, userId={}, roleId={}, allVisible={}, entIdsSize={}",
+                userId, roleId, result.isAllVisible(),
+                result.getVisibleEntIds() != null ? result.getVisibleEntIds().size() : 0);
+        return result;
+    }
+
+    /**
+     * 解析受限的企业ID集合
+     * <p>
+     * 确保返回的企业ID在当前用户所属企业或角色所属企业的合法范围内。
+     *
+     * @param role           角色信息
+     * @param userId         用户ID
+     * @param configuredEntIds 配置的企业ID集合
+     * @return 经过校验后的企业ID集合
+     */
+    private Set<Long> resolveScopedEntIds(KwsRole role, Long userId, Set<Long> configuredEntIds) {
+        if (Objects.isNull(role) || Objects.isNull(role.getEntId())) {
+            log.warn("角色信息不完整,无法解析企业权限, userId={}, roleId={}", userId, role != null ? role.getId() : null);
+            return Collections.emptySet();
+        }
+
+        Long userEntId = resolveUserEntId(userId);
+        // 校验用户所属企业与角色所属企业是否一致(防止跨企业越权)
+        if (Objects.nonNull(userEntId) && !Objects.equals(userEntId, role.getEntId())) {
+            log.warn("数据权限企业不匹配,可能存在越权风险, userId={}, userEntId={}, roleId={}, roleEntId={}",
+                    userId, userEntId, role.getId(), role.getEntId());
+            return Collections.emptySet();
+        }
+
+        // 如果未配置具体企业ID,则默认仅允许访问角色所属企业
+        if (CollectionUtils.isEmpty(configuredEntIds)) {
+            log.debug("未配置具体企业权限,默认限制为角色所属企业, userId={}, roleId={}, entId={}",
+                    userId, role.getId(), role.getEntId());
+            return new HashSet<>(Collections.singleton(role.getEntId()));
+        }
+
+        // 保留配置中与角色所属企业一致的ID(通常配置中只包含角色所属企业或其子企业,此处做交集校验)
+        // 注意:retainAll会修改原集合,这里假设configuredEntIds是副本或允许修改
+        configuredEntIds.retainAll(Collections.singleton(role.getEntId()));
+        log.debug("解析受限企业ID完成, userId={}, roleId={}, finalEntIds={}", userId, role.getId(), configuredEntIds);
+        return configuredEntIds;
+    }
+
+    /**
+     * 解析用户所属的企业ID
+     *
+     * @param userId 用户ID
+     * @return 企业ID,若用户不存在则返回null
+     */
+    private Long resolveUserEntId(Long userId) {
+        if (Objects.isNull(userId)) {
+            return null;
+        }
+        KwsUser user = kwsUserService.selectByKey(userId);
+        if (user == null) {
+            log.warn("未找到用户信息, userId={}", userId);
+            return null;
+        }
+        return user.getEntId();
+    }
+
+    /**
+     * 根据单个角色构建过滤条件
+     *
+     * @param roleId          角色ID
+     * @param userId          用户ID
+     * @param restrictToOwnedEnt 是否限制在用户所属企业范围内
+     * @return 数据权限过滤条件
+     */
+    private DataPermissionFilter buildFilterByRole(Long roleId, Long userId, boolean restrictToOwnedEnt) {
+        log.debug("开始构建单角色权限过滤, userId={}, roleId={}, restrictToOwnedEnt={}", userId, roleId, restrictToOwnedEnt);
+
+        DataPermissionFilter filter = new DataPermissionFilter();
+        filter.setUserId(userId);
+
+        // 获取角色配置的企业ID列表
+        Set<Long> entIds = new HashSet<>(kwsDataPermissionService.getCachedEntIds(roleId));
+        // 获取是否开启个人数据权限(仅看本人)
+        boolean personalFlag = kwsDataPermissionService.getCachedPersonalFlag(roleId);
+        log.debug("读取角色缓存权限, roleId={}, configuredEntIdsSize={}, personalFlag={}",
+                roleId, entIds.size(), personalFlag);
+
+        // 如果需要限制在所属企业范围内,进行二次校验
+        if (restrictToOwnedEnt) {
+            KwsRole role = kwsRoleService.selectByKey(roleId);
+            entIds = resolveScopedEntIds(role, userId, entIds);
+        }
+
+        if (CollectionUtils.isEmpty(entIds)) {
+            // 未配置企业数据权限或校验后为空,设置为不可见任何企业数据(除非allVisible为true,但此处显式设为false)
+            log.info("角色无有效企业数据权限,设置为空权限, userId={}, roleId={}", userId, roleId);
+            filter.setAllVisible(false);
+            filter.setVisibleEntIds(Collections.emptySet());
+        } else {
+            filter.setAllVisible(false);
+            filter.setVisibleEntIds(entIds);
+        }
+
+        filter.setPersonalDataEnabled(personalFlag);
+        return filter;
+    }
+
+    /**
+     * 根据多个角色构建过滤条件(取并集)
+     *
+     * @param roles           角色列表
+     * @param userId          用户ID
+     * @param restrictToOwnedEnt 是否限制在用户所属企业范围内
+     * @return 数据权限过滤条件
+     */
+    private DataPermissionFilter buildFilterByRoles(List<KwsRole> roles, Long userId, boolean restrictToOwnedEnt) {
+        log.debug("开始构建多角色权限过滤(并集), userId={}, roleCount={}, restrictToOwnedEnt={}",
+                userId, roles.size(), restrictToOwnedEnt);
+
+        DataPermissionFilter filter = new DataPermissionFilter();
+        filter.setUserId(userId);
+
+        Set<Long> allEntIds = new HashSet<>();
+        boolean anyPersonalFlag = false;
+
+        for (KwsRole role : roles) {
+            // 获取当前角色配置的企业ID
+            Set<Long> entIds = new HashSet<>(kwsDataPermissionService.getCachedEntIds(role.getId()));
+
+            // 如果需要限制在所属企业范围内,进行二次校验
+            if (restrictToOwnedEnt) {
+                entIds = resolveScopedEntIds(role, userId, entIds);
+            }
+
+            // 合并企业ID
+            if (CollectionUtils.isNotEmpty(entIds)) {
+                allEntIds.addAll(entIds);
+            }
+
+            // 只要有一个角色开启了个人数据权限,最终结果即开启
+            if (kwsDataPermissionService.getCachedPersonalFlag(role.getId())) {
+                anyPersonalFlag = true;
+            }
+        }
+
+        if (allEntIds.isEmpty()) {
+            log.info("多角色合并后无有效企业数据权限,设置为空权限, userId={}", userId);
+            filter.setAllVisible(false);
+            filter.setVisibleEntIds(Collections.emptySet());
+        } else {
+            filter.setAllVisible(false);
+            filter.setVisibleEntIds(allEntIds);
+            log.debug("多角色权限合并完成, userId={}, totalEntIdsSize={}, anyPersonalFlag={}",
+                    userId, allEntIds.size(), anyPersonalFlag);
+        }
+
+        filter.setPersonalDataEnabled(anyPersonalFlag);
+        return filter;
+    }
+
+
+
+    /**
+     * 根据显式参数获取数据权限过滤条件(不依赖LoginUserHolder,适用于Feign远程调用)
+     *
+     * @param userId    用户ID
+     * @param roleId    当前使用的角色ID,null时取用户所有角色的并集
+     * @param isManager 是否平台管理员
+     * @return 数据权限过滤条件
+     */
+    public DataPermissionFilter getPermissionFilter(Long userId, Long roleId, boolean isManager) {
+        log.info("通过显式参数计算数据权限过滤条件, userId={}, roleId={}, isManager={}", userId, roleId, isManager);
+
+        DataPermissionFilter filter = new DataPermissionFilter();
+        filter.setUserId(userId);
+
+        if (isManager) {
+            if (Objects.isNull(roleId)) {
+                log.info("平台管理员未指定角色ID,默认拥有全部数据权限, userId={}", userId);
+                filter.setAllVisible(true);
+                return filter;
+            }
+            DataPermissionFilter result = buildFilterByRole(roleId, userId, false);
+            log.info("平台管理员基于角色构建权限完成(远程调用), userId={}, roleId={}", userId, roleId);
+            return result;
+        }
+
+        if (Objects.isNull(roleId)) {
+            log.info("客户端用户未指定角色ID,尝试获取用户所有角色并集(远程调用), userId={}", userId);
+            List<KwsRole> roles = kwsRoleService.queryRoleByUserId(userId);
+            if (CollectionUtils.isEmpty(roles)) {
+                log.warn("用户无任何角色关联,设置为无数据权限(远程调用), userId={}", userId);
+                filter.setAllVisible(false);
+                filter.setVisibleEntIds(Collections.emptySet());
+                return filter;
+            }
+            DataPermissionFilter result = buildFilterByRoles(roles, userId, true);
+            log.info("基于用户多角色并集构建权限完成(远程调用), userId={}, roleCount={}", userId, roles.size());
+            return result;
+        }
+
+        DataPermissionFilter result = buildFilterByRole(roleId, userId, true);
+        log.info("基于单角色构建权限完成(远程调用), userId={}, roleId={}", userId, roleId);
+        return result;
+    }
+
+    /**
+     * 构建数据权限DTO(供Feign远程接口返回给其他模块)
+     * <p>
+     * 其他模块通过 DataPermissionFeignService.getDataPermissionFilter() 间接调用本方法,
+     * 获取可序列化的 DataPermissionDTO,然后调用 dto.applyTo(params) 注入查询参数。
+     *
+     * @param userId    用户ID
+     * @param roleId    当前使用的角色ID
+     * @param isManager 是否平台管理员
+     * @return 可序列化的数据权限过滤DTO
+     */
+    public DataPermissionDTO buildPermissionDTO(Long userId, Long roleId, boolean isManager) {
+        log.info("构建数据权限DTO, userId={}, roleId={}, isManager={}", userId, roleId, isManager);
+        DataPermissionFilter filter = getPermissionFilter(userId, roleId, isManager);
+        DataPermissionDTO dto = new DataPermissionDTO();
+        dto.setUserId(filter.getUserId());
+        dto.setAllVisible(filter.isAllVisible());
+        dto.setVisibleEntIds(filter.getVisibleEntIds());
+        dto.setPersonalDataEnabled(filter.isPersonalDataEnabled());
+        log.info("数据权限DTO构建完成, allVisible={}, entIdsSize={}, personalEnabled={}",
+                dto.isAllVisible(),
+                dto.getVisibleEntIds() != null ? dto.getVisibleEntIds().size() : 0,
+                dto.isPersonalDataEnabled());
+        return dto;
+    }
+
+    /**
+     * 数据权限过滤条件
+     */
+    @Data
+    public static class DataPermissionFilter {
+        /** 当前用户ID */
+        private Long userId;
+        /** 是否可见全部数据(未配置数据权限时为true) */
+        private boolean allVisible;
+        /** 可见的企业ID集合 */
+        private Set<Long> visibleEntIds;
+        /** 是否开启个人数据权限(仅查看本人作为销售的数据) */
+        private boolean personalDataEnabled;
+    }
+
+}

+ 644 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsWeighbridgeManageService.java

@@ -0,0 +1,644 @@
+package com.sckw.system.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.sckw.core.exception.SystemException;
+import com.sckw.core.model.page.PageResult;
+import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.system.model.*;
+import com.sckw.system.model.vo.req.*;
+import com.sckw.system.model.vo.res.*;
+import com.sckw.system.repository.KwsEnterpriseRepository;
+import com.sckw.system.repository.KwsPrinterRepository;
+import com.sckw.system.repository.KwsWeighbridgeRecordRepository;
+import com.sckw.system.repository.KwsWeighbridgeDiffConfigRepository;
+import com.sckw.system.repository.KwsWeighbridgeRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 地磅管理服务类
+ * 提供地磅的增删改查、状态管理、误差配置及关联信息查询等功能
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class KwsWeighbridgeManageService {
+
+    /**
+     * 地磅数据访问层
+     */
+    private final KwsWeighbridgeRepository kwsWeighbridgeRepository;
+    /**
+     * 企业数据访问层
+     */
+    private final KwsEnterpriseRepository kwsEnterpriseRepository;
+    /**
+     * 打印机数据访问层
+     */
+    private final KwsPrinterRepository kwsPrinterRepository;
+    /**
+     * 地磅误差配置数据访问层
+     */
+    private final KwsWeighbridgeDiffConfigRepository kwsWeighbridgeDiffConfigRepository;
+    /**
+     * 地磅记录数据访问层
+     */
+    private final KwsWeighbridgeRecordRepository kwsWeighbridgeRecordRepository;
+
+    private final ValidateLicensePlateService validateLicensePlateService;
+
+    /**
+     * 分页查询地磅列表
+     * <p>
+     * 根据请求参数中的企业名称解析出对应的企业ID集合,结合地磅名称进行分页查询。
+     * 若解析出的企业ID集合为空(例如普通用户无权限访问任何企业),则直接返回空结果。
+     *
+     * @param reqVo 分页查询请求参数,包含页码、每页大小、地磅名称、企业名称等
+     * @return 分页结果,包含地磅列表及总数
+     */
+    public PageResult page(WeighbridgePageReqVo reqVo) {
+        log.info("分页查询地磅列表, reqVo: {}", JSON.toJSONString(reqVo));
+        // 根据企业名称解析有权限访问的企业ID集合
+        Set<Long> entIds = resolveQueryEntIds(reqVo.getEnterpriseName());
+        if (entIds.isEmpty()) {
+            // 若无有效企业ID,直接返回空分页结果
+            return PageResult.build(reqVo.getPage(), reqVo.getPageSize(), 0L, Collections.emptyList());
+        }
+        // 执行数据库分页查询
+        IPage<KwsWeighbridge> page = kwsWeighbridgeRepository.pageQuery(reqVo.getPage(), reqVo.getPageSize(),
+                reqVo.getWeighbridgeName(), entIds);
+        // 构建并返回响应结果
+        return PageResult.of(page, buildPageRes(page.getRecords()));
+    }
+
+    /**
+     * 获取地磅详情
+     * <p>
+     * 根据地磅ID查询地磅信息,并填充企业名称、打印机名称等关联信息。
+     *
+     * @param id 地磅ID
+     * @return 地磅详情响应对象
+     */
+    public WeighbridgeDetailResVo detail(Long id) {
+        log.info("获取地磅详情, id: {}", id);
+        // 获取并校验地磅实体(包含权限校验)
+        KwsWeighbridge weighbridge = getAndCheck(id);
+        WeighbridgeDetailResVo resVo = new WeighbridgeDetailResVo();
+        // 填充基础信息及关联名称
+        fillBaseRes(weighbridge,
+                enterpriseNameMap(Collections.singleton(weighbridge.getEntId())),
+                printerNameMap(weighbridge.getPrinterId() == null ? Collections.emptySet() : Collections.singleton(weighbridge.getPrinterId())),
+                resVo);
+        // 设置误差配置JSON字符串
+        resVo.setDiffConfig(weighbridge.getDiffConfig());
+        return resVo;
+    }
+
+
+    /**
+     * 新增地磅信息
+     * <p>
+     * 校验请求参数合法性及唯一性后,创建新的地磅记录。
+     *
+     * @param reqVo 地磅保存请求参数
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void add(WeighbridgeSaveReqVo reqVo) {
+        log.info("新增地磅信息, reqVo: {}", JSON.toJSONString(reqVo));
+        // 校验请求参数(新增时excludeId为null)
+        validateSaveReq(reqVo, null);
+        Date now = new Date();
+        // 构建地磅实体对象
+        KwsWeighbridge weighbridge = new KwsWeighbridge()
+                .setEntId(reqVo.getEntId())
+                .setWeighbridgeName(reqVo.getWeighbridgeName())
+                .setUniqueCode(reqVo.getUniqueCode())
+                .setOnlineStatus(defaultOnlineStatus(reqVo.getOnlineStatus()))
+                .setPrinterId(reqVo.getPrinterId())
+                .setDescription(reqVo.getDescription())
+                .setDiffConfig(reqVo.getDiffConfig())
+                .setStatus(0) // 默认启用
+                .setDelFlag(0) // 默认未删除
+                .setCreateBy(LoginUserHolder.getUserId())
+                .setCreateTime(now)
+                .setUpdateBy(LoginUserHolder.getUserId())
+                .setUpdateTime(now);
+        // 保存至数据库
+        if (!kwsWeighbridgeRepository.save(weighbridge)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, HttpStatus.INSERT_FAIL);
+        }
+    }
+
+    /**
+     * 更新地磅信息
+     * <p>
+     * 校验请求参数及权限后,更新指定地磅的信息。
+     *
+     * @param reqVo 地磅保存请求参数,必须包含地磅ID
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void update(WeighbridgeSaveReqVo reqVo) {
+        log.info("更新地磅信息, reqVo: {}", JSON.toJSONString(reqVo));
+        if (reqVo.getId() == null) {
+            throw new SystemException("地磅ID不能为空");
+        }
+        // 获取并校验原地磅信息
+        KwsWeighbridge weighbridge = getAndCheck(reqVo.getId());
+        // 校验请求参数(更新时需排除当前ID以检查唯一编码冲突)
+        validateSaveReq(reqVo, weighbridge.getId());
+        // 更新字段
+        weighbridge.setEntId(reqVo.getEntId());
+        weighbridge.setWeighbridgeName(reqVo.getWeighbridgeName());
+        weighbridge.setUniqueCode(reqVo.getUniqueCode());
+        weighbridge.setOnlineStatus(defaultOnlineStatus(reqVo.getOnlineStatus()));
+        weighbridge.setPrinterId(reqVo.getPrinterId());
+        weighbridge.setDescription(reqVo.getDescription());
+        weighbridge.setDiffConfig(reqVo.getDiffConfig());
+        weighbridge.setUpdateBy(LoginUserHolder.getUserId());
+        weighbridge.setUpdateTime(new Date());
+        // 执行更新
+        if (!kwsWeighbridgeRepository.updateById(weighbridge)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, HttpStatus.UPDATE_FAIL);
+        }
+    }
+
+    /**
+     * 更新地磅状态
+     * <p>
+     * 仅允许将状态更新为0(启用)或1(停用)。
+     *
+     * @param reqVo 状态更新请求参数,包含地磅ID和目标状态
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void updateStatus(WeighbridgeStatusReqVo reqVo) {
+        log.info("更新地磅状态, reqVo: {}", JSON.toJSONString(reqVo));
+        if (reqVo.getId() == null) {
+            throw new SystemException("地磅ID不能为空");
+        }
+        // 校验状态值合法性
+        if (!Objects.equals(reqVo.getStatus(), 0) && !Objects.equals(reqVo.getStatus(), 1)) {
+            throw new SystemException("无效的地磅状态");
+        }
+        // 获取并校验地磅
+        KwsWeighbridge weighbridge = getAndCheck(reqVo.getId());
+        weighbridge.setStatus(reqVo.getStatus());
+        weighbridge.setUpdateBy(LoginUserHolder.getUserId());
+        weighbridge.setUpdateTime(new Date());
+        if (!kwsWeighbridgeRepository.updateById(weighbridge)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, HttpStatus.UPDATE_FAIL);
+        }
+    }
+
+    /**
+     * 更新地磅误差配置
+     * <p>
+     * 更新指定企业的误差配置表,并同步更新地磅主表中的误差配置JSON字段。
+     * 若配置不存在则新增,存在则更新。
+     *
+     * @param reqVo 误差配置请求参数,包含企业ID及各项误差值
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void updateDiffConfig(WeighbridgeDiffConfigReqVo reqVo) {
+        log.info("更新地磅误差配置, reqVo: {}", JSON.toJSONString(reqVo));
+        // 校验请求参数非空
+        validateDiffConfigReq(reqVo);
+        // 校验企业权限及状态
+        checkEnterprise(reqVo.getEntId());
+        Date now = new Date();
+        // 查询现有配置
+        KwsWeighbridgeDiffConfig config = kwsWeighbridgeDiffConfigRepository.findByEntId(reqVo.getEntId());
+        if (config == null) {
+            // 新增配置
+            config = new KwsWeighbridgeDiffConfig()
+                    .setEntId(reqVo.getEntId())
+                    .setTareErrorValue(reqVo.getTareErrorValue())
+                    .setLoadErrorValue(reqVo.getLoadErrorValue())
+                    .setEmptyLoadValue(reqVo.getEmptyLoadValue())
+                    .setCreateBy(LoginUserHolder.getUserId())
+                    .setCreateTime(now)
+                    .setUpdateBy(LoginUserHolder.getUserId())
+                    .setUpdateTime(now)
+                    .setDelFlag(0);
+            if (!kwsWeighbridgeDiffConfigRepository.save(config)) {
+                throw new SystemException(HttpStatus.CRUD_FAIL_CODE, HttpStatus.INSERT_FAIL);
+            }
+        } else {
+            // 更新配置
+            config.setTareErrorValue(reqVo.getTareErrorValue());
+            config.setLoadErrorValue(reqVo.getLoadErrorValue());
+            config.setEmptyLoadValue(reqVo.getEmptyLoadValue());
+            config.setUpdateBy(LoginUserHolder.getUserId());
+            config.setUpdateTime(now);
+            if (!kwsWeighbridgeDiffConfigRepository.updateById(config)) {
+                throw new SystemException(HttpStatus.CRUD_FAIL_CODE, HttpStatus.UPDATE_FAIL);
+            }
+        }
+        // 同步更新地磅表中的误差配置JSON字段,确保数据一致性
+        kwsWeighbridgeRepository.updateDiffConfigByEntId(reqVo.getEntId(), buildDiffConfigJson(reqVo), LoginUserHolder.getUserId());
+    }
+
+    /**
+     * 获取地磅误差配置详情
+     * <p>
+     * 查询指定企业的误差配置信息。
+     *
+     * @param entId 企业ID
+     * @return 误差配置详情响应对象
+     */
+    public WeighbridgeDiffConfigResVo diffConfigDetail(Long entId) {
+        log.info("获取地磅误差配置详情, entId: {}", entId);
+        if (entId == null) {
+            throw new SystemException("企业ID不能为空");
+        }
+        // 校验企业权限
+        checkEnterprise(entId);
+        WeighbridgeDiffConfigResVo resVo = new WeighbridgeDiffConfigResVo();
+        resVo.setEntId(entId);
+        // 填充企业名称
+        resVo.setEnterpriseName(enterpriseNameMap(Collections.singleton(entId)).getOrDefault(entId, ""));
+        // 查询配置详情
+        KwsWeighbridgeDiffConfig config = kwsWeighbridgeDiffConfigRepository.findByEntId(entId);
+        if (config != null) {
+            resVo.setId(config.getId());
+            resVo.setTareErrorValue(config.getTareErrorValue());
+            resVo.setLoadErrorValue(config.getLoadErrorValue());
+            resVo.setEmptyLoadValue(config.getEmptyLoadValue());
+        }
+        return resVo;
+    }
+
+
+    /**
+     * 重启地磅(更新最后重启时间)
+     * <p>
+     * 记录地磅最后一次重启的时间戳。
+     *
+     * @param reqVo 重启请求参数,包含地磅ID
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void restart(WeighbridgeRestartReqVo reqVo) {
+        log.info("重启地磅, reqVo: {}", JSON.toJSONString(reqVo));
+        if (reqVo.getId() == null) {
+            throw new SystemException("地磅ID不能为空");
+        }
+        // 获取并校验地磅
+        KwsWeighbridge weighbridge = getAndCheck(reqVo.getId());
+        Date now = new Date();
+        weighbridge.setLastRestartTime(now);
+        weighbridge.setUpdateBy(LoginUserHolder.getUserId());
+        weighbridge.setUpdateTime(now);
+        if (!kwsWeighbridgeRepository.updateById(weighbridge)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, HttpStatus.UPDATE_FAIL);
+        }
+    }
+
+    /**
+     * 获取企业选项列表
+     * <p>
+     * 根据当前用户权限获取可选的企业列表,支持关键词搜索。
+     * 管理员可查看所有企业,普通用户仅可查看授权范围内的企业。
+     *
+     * @param keyword 搜索关键词
+     * @return 企业选项列表
+     */
+    public List<PlatformEnterpriseResVo> enterpriseOptions(String keyword) {
+        log.info("获取企业选项列表, keyword: {}", keyword);
+        // 获取当前用户授权的企业ID集合
+        Set<Long> authEntIds = resolveAuthorizedEntIds();
+        List<KwsEnterprise> enterpriseList;
+        if (LoginUserHolder.isManager()) {
+            // 管理员查询所有匹配的企业
+            enterpriseList = kwsEnterpriseRepository.queryByEntIdAndName(null, keyword);
+        } else if (authEntIds.isEmpty()) {
+            // 普通用户无授权企业,返回空列表
+            return Collections.emptyList();
+        } else {
+            // 普通用户查询授权范围内的匹配企业
+            enterpriseList = kwsEnterpriseRepository.queryByEntIds(authEntIds, keyword);
+        }
+        // 转换为响应VO
+        return enterpriseList.stream().map(item -> {
+            PlatformEnterpriseResVo resVo = new PlatformEnterpriseResVo();
+            resVo.setId(item.getId());
+            resVo.setFirmName(item.getFirmName());
+            return resVo;
+        }).toList();
+    }
+
+    /**
+     * 检查唯一编码是否可用
+     * <p>
+     * 用于前端实时校验或提交前校验,确保唯一编码未被其他地磅占用。
+     *
+     * @param uniqueCode 唯一编码
+     * @param id         地磅ID(更新时传入当前ID以排除自身)
+     * @return 是否可用
+     */
+    public boolean checkUniqueCodeAvailable(String uniqueCode, Long id) {
+        log.info("检查唯一编码是否可用, uniqueCode: {}, id: {}", uniqueCode, id);
+        if (StringUtils.isBlank(uniqueCode)) {
+            return false;
+        }
+        // 查询是否存在该编码的地磅
+        KwsWeighbridge exists = kwsWeighbridgeRepository.findByUniqueCode(uniqueCode.trim());
+        // 若不存在,或存在的记录即为当前记录,则视为可用
+        return exists == null || Objects.equals(exists.getId(), id);
+    }
+
+    /**
+     * 构建分页响应结果
+     * <p>
+     * 批量查询关联的企业名称和打印机名称,避免N+1查询问题,并组装响应VO列表。
+     *
+     * @param records 地磅记录列表
+     * @return 分页响应VO列表
+     */
+    private List<WeighbridgePageResVo> buildPageRes(List<KwsWeighbridge> records) {
+        if (records == null || records.isEmpty()) {
+            return Collections.emptyList();
+        }
+        // 批量获取企业名称映射
+        Map<Long, String> entNameMap = enterpriseNameMap(records.stream().map(KwsWeighbridge::getEntId).collect(Collectors.toSet()));
+        // 批量获取打印机名称映射(过滤null ID)
+        Map<Long, String> printerNameMap = printerNameMap(records.stream().map(KwsWeighbridge::getPrinterId)
+                .filter(Objects::nonNull).collect(Collectors.toSet()));
+        List<WeighbridgePageResVo> result = new ArrayList<>(records.size());
+        for (KwsWeighbridge record : records) {
+            WeighbridgePageResVo resVo = new WeighbridgePageResVo();
+            // 填充单个记录的基础信息
+            fillBaseRes(record, entNameMap, printerNameMap, resVo);
+            result.add(resVo);
+        }
+        return result;
+    }
+
+    /**
+     * 填充基础响应信息
+     * <p>
+     * 将地磅实体数据及关联的名称映射填充到响应VO中,并转换状态枚举为中文描述。
+     *
+     * @param record        地磅实体
+     * @param entNameMap    企业名称映射
+     * @param printerNameMap 打印机名称映射
+     * @param resVo         响应VO
+     */
+    private void fillBaseRes(KwsWeighbridge record, Map<Long, String> entNameMap, Map<Long, String> printerNameMap,
+                             WeighbridgePageResVo resVo) {
+        resVo.setId(record.getId());
+        resVo.setEntId(record.getEntId());
+        resVo.setEnterpriseName(entNameMap.getOrDefault(record.getEntId(), ""));
+        resVo.setWeighbridgeName(record.getWeighbridgeName());
+        resVo.setUniqueCode(record.getUniqueCode());
+        resVo.setOnlineStatus(record.getOnlineStatus());
+        // 转换在线状态:1-在线,0-离线
+        resVo.setOnlineStatusName(Objects.equals(record.getOnlineStatus(), 1) ? "在线" : "离线");
+        resVo.setPrinterId(record.getPrinterId());
+        resVo.setPrinterName(printerNameMap.getOrDefault(record.getPrinterId(), ""));
+        resVo.setDescription(record.getDescription());
+        resVo.setStatus(record.getStatus());
+        // 转换启用状态:0-启用,1-停用
+        resVo.setStatusName(Objects.equals(record.getStatus(), 0) ? "启用" : "停用");
+        resVo.setCreateTime(record.getCreateTime());
+        resVo.setLastRestartTime(record.getLastRestartTime());
+    }
+
+    /**
+     * 获取企业名称映射
+     * <p>
+     * 根据企业ID集合批量查询企业名称,返回ID到名称的Map。
+     *
+     * @param entIds 企业ID集合
+     * @return 企业ID到名称的映射
+     */
+    private Map<Long, String> enterpriseNameMap(Collection<Long> entIds) {
+        if (entIds == null || entIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return kwsEnterpriseRepository.listByIds(entIds).stream()
+                .collect(Collectors.toMap(KwsEnterprise::getId, KwsEnterprise::getFirmName, (a, b) -> a));
+    }
+
+    /**
+     * 获取打印机名称映射
+     * <p>
+     * 根据打印机ID集合批量查询打印机名称,返回ID到名称的Map。
+     *
+     * @param printerIds 打印机ID集合
+     * @return 打印机ID到名称的映射
+     */
+    private Map<Long, String> printerNameMap(Collection<Long> printerIds) {
+        if (printerIds == null || printerIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return kwsPrinterRepository.listByIds(printerIds).stream()
+                .collect(Collectors.toMap(KwsPrinter::getId, KwsPrinter::getPrinterName, (a, b) -> a));
+    }
+
+    /**
+     * 校验地磅保存请求参数
+     * <p>
+     * 检查必填项、企业权限、打印机有效性及唯一编码冲突。
+     *
+     * @param reqVo     保存请求参数
+     * @param excludeId 排除的地磅ID(用于更新时排除自身,新增时为null)
+     */
+    private void validateSaveReq(WeighbridgeSaveReqVo reqVo, Long excludeId) {
+        if (reqVo.getEntId() == null) {
+            throw new SystemException("企业ID不能为空");
+        }
+        if (StringUtils.isBlank(reqVo.getWeighbridgeName())) {
+            throw new SystemException("地磅名称不能为空");
+        }
+        if (StringUtils.isBlank(reqVo.getUniqueCode())) {
+            throw new SystemException("唯一编码不能为空");
+        }
+        // 校验企业是否存在且有权限操作
+        checkEnterprise(reqVo.getEntId());
+        // 校验打印机是否属于该企业且可用
+        checkPrinter(reqVo.getEntId(), reqVo.getPrinterId());
+        // 校验唯一编码是否冲突
+        KwsWeighbridge exists = kwsWeighbridgeRepository.findByUniqueCode(reqVo.getUniqueCode().trim());
+        if (exists != null && !Objects.equals(exists.getId(), excludeId)) {
+            throw new SystemException("唯一编码已存在");
+        }
+    }
+
+    /**
+     * 校验地磅误差配置请求参数
+     * <p>
+     * 检查企业ID及各项误差值是否为空。
+     *
+     * @param reqVo 误差配置请求参数
+     */
+    private void validateDiffConfigReq(WeighbridgeDiffConfigReqVo reqVo) {
+        if (reqVo.getEntId() == null) {
+            throw new SystemException("企业ID不能为空");
+        }
+        if (reqVo.getTareErrorValue() == null) {
+            throw new SystemException("皮重误差值不能为空");
+        }
+        if (reqVo.getLoadErrorValue() == null) {
+            throw new SystemException("载重误差值不能为空");
+        }
+        if (reqVo.getEmptyLoadValue() == null) {
+            throw new SystemException("空载值不能为空");
+        }
+    }
+
+    /**
+     * 校验打印机权限及状态
+     * <p>
+     * 确保指定的打印机ID属于当前企业且处于可用状态。
+     *
+     * @param entId     企业ID
+     * @param printerId 打印机ID
+     */
+    private void checkPrinter(Long entId, Long printerId) {
+        if (printerId == null) {
+            return;
+        }
+        KwsPrinter printer = kwsPrinterRepository.findAvailableById(printerId);
+        if (printer == null || !Objects.equals(printer.getEntId(), entId)) {
+            throw new SystemException("该企业的打印机不可用");
+        }
+    }
+
+    /**
+     * 校验企业权限及状态
+     * <p>
+     * 确保当前用户有权限操作该企业,且企业未被删除或停用。
+     *
+     * @param entId 企业ID
+     */
+    private void checkEnterprise(Long entId) {
+        // 判断是否有权限:管理员或企业在授权列表中
+        boolean allowed = LoginUserHolder.isManager() || resolveAuthorizedEntIds().contains(entId);
+        if (!allowed) {
+            throw new SystemException("无权操作该企业");
+        }
+        // 检查企业实体状态
+        KwsEnterprise enterprise = kwsEnterpriseRepository.getById(entId);
+        if (enterprise == null || Objects.equals(enterprise.getDelFlag(), 1) || Objects.equals(enterprise.getStatus(), 1)) {
+            throw new SystemException(HttpStatus.ENT_NOT_EXISTS);
+        }
+    }
+
+    /**
+     * 获取并校验地磅信息
+     * <p>
+     * 根据地磅ID查询地磅,并校验其存在性及当前用户的操作权限。
+     *
+     * @param id 地磅ID
+     * @return 地磅实体
+     */
+    private KwsWeighbridge getAndCheck(Long id) {
+        KwsWeighbridge weighbridge = kwsWeighbridgeRepository.findAvailableById(id);
+        if (weighbridge == null) {
+            throw new SystemException("地磅不存在");
+        }
+        // 非管理员需校验地磅所属企业是否在授权范围内
+        if (!LoginUserHolder.isManager() && !resolveAuthorizedEntIds().contains(weighbridge.getEntId())) {
+            throw new SystemException("无权操作该地磅");
+        }
+        return weighbridge;
+    }
+
+    /**
+     * 解析查询条件中的企业ID集合
+     * <p>
+     * 根据企业名称关键词和用户权限,解析出可用于查询的地磅所属企业ID集合。
+     *
+     * @param enterpriseName 企业名称关键词
+     * @return 企业ID集合
+     */
+    private Set<Long> resolveQueryEntIds(String enterpriseName) {
+        Set<Long> authEntIds = resolveAuthorizedEntIds();
+        List<KwsEnterprise> enterpriseList;
+        if (LoginUserHolder.isManager()) {
+            // 管理员可根据名称模糊查询所有企业
+            enterpriseList = kwsEnterpriseRepository.queryByEntIdAndName(null, enterpriseName);
+        } else if (authEntIds.isEmpty()) {
+            // 普通用户无授权企业,返回空集合
+            return Collections.emptySet();
+        } else {
+            // 普通用户在授权企业中根据名称模糊查询
+            enterpriseList = kwsEnterpriseRepository.queryByEntIds(authEntIds, enterpriseName);
+        }
+        // 提取ID并去重,保持插入顺序
+        return enterpriseList.stream().map(KwsEnterprise::getId).collect(Collectors.toCollection(LinkedHashSet::new));
+    }
+
+    /**
+     * 解析当前用户授权的企业ID集合
+     * <p>
+     * 获取当前登录用户有权访问的所有企业ID,包括当前企业、授权企业及子企业。
+     * 管理员返回空集合,表示拥有所有权限(通常在业务逻辑中特殊处理)。
+     *
+     * @return 企业ID集合
+     */
+    private Set<Long> resolveAuthorizedEntIds() {
+        if (LoginUserHolder.isManager()) {
+            return Collections.emptySet();
+        }
+        Set<Long> result = new LinkedHashSet<>();
+        // 添加当前登录用户所属企业
+        if (LoginUserHolder.getEntId() != null) {
+            result.add(LoginUserHolder.getEntId());
+        }
+        // 添加额外授权的企业
+        result.addAll(LoginUserHolder.getAuthEntIdList());
+        // 添加子企业
+        result.addAll(LoginUserHolder.getChildEntList());
+        return result;
+    }
+
+    /**
+     * 获取默认的在线状态
+     * <p>
+     * 若传入状态为1则返回1,否则默认返回0(离线)。
+     *
+     * @param onlineStatus 传入的在线状态
+     * @return 默认在线状态(1为在线,0为离线)
+     */
+    private Integer defaultOnlineStatus(Integer onlineStatus) {
+        return Objects.equals(onlineStatus, 1) ? 1 : 0;
+    }
+
+    /**
+     * 构建误差配置JSON字符串
+     * <p>
+     * 将误差配置请求参数转换为JSON字符串,用于存储在地磅表的diff_config字段中。
+     *
+     * @param reqVo 误差配置请求参数
+     * @return JSON字符串
+     */
+    private String buildDiffConfigJson(WeighbridgeDiffConfigReqVo reqVo) {
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("entId", reqVo.getEntId());
+        jsonObject.put("tareErrorValue", reqVo.getTareErrorValue());
+        jsonObject.put("loadErrorValue", reqVo.getLoadErrorValue());
+        jsonObject.put("emptyLoadValue", reqVo.getEmptyLoadValue());
+        return jsonObject.toJSONString();
+    }
+
+
+}

+ 327 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsWeighbridgeRecordManageService.java

@@ -0,0 +1,327 @@
+package com.sckw.system.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.sckw.core.model.page.PageResult;
+import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.system.model.KwsEnterprise;
+import com.sckw.system.model.KwsWeighbridge;
+import com.sckw.system.model.KwsWeighbridgeRecord;
+import com.sckw.system.model.ValidateLicensePlate;
+import com.sckw.system.model.report.WeighbridgeRecordExcel;
+import com.sckw.system.model.vo.req.LicensePlateValidateRequest;
+import com.sckw.system.model.vo.req.WeighbridgePushRequest;
+import com.sckw.system.model.vo.req.WeighbridgeRecordPageReqVo;
+import com.sckw.system.model.vo.res.LicensePlateValidateResponse;
+import com.sckw.system.model.vo.res.WeighbridgeRecordResVo;
+import com.sckw.system.repository.KwsEnterpriseRepository;
+import com.sckw.system.repository.KwsWeighbridgeRecordRepository;
+import com.sckw.system.repository.KwsWeighbridgeRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 地磅称重记录业务服务。
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class KwsWeighbridgeRecordManageService {
+
+    private final KwsWeighbridgeRecordRepository kwsWeighbridgeRecordRepository;
+    private final KwsWeighbridgeRepository kwsWeighbridgeRepository;
+    private final KwsEnterpriseRepository kwsEnterpriseRepository;
+    private final ValidateLicensePlateService validateLicensePlateService;
+
+    public PageResult page(WeighbridgeRecordPageReqVo reqVo) {
+        Set<Long> weighbridgeIds = resolveWeighbridgeIds(reqVo);
+        if (weighbridgeIds.isEmpty()) {
+            return PageResult.build(reqVo.getPage(), reqVo.getPageSize(), 0L, Collections.emptyList());
+        }
+        IPage<KwsWeighbridgeRecord> page = kwsWeighbridgeRecordRepository.pageQuery(reqVo.getPage(), reqVo.getPageSize(),
+                weighbridgeIds, reqVo.getTruckNo(), reqVo.getMinWeight(), reqVo.getMaxWeight(),
+                reqVo.getCreateStartTime(), reqVo.getCreateEndTime(), reqVo.getReceiveStartTime(), reqVo.getReceiveEndTime());
+        return PageResult.of(page, buildRes(page.getRecords()));
+    }
+
+    public List<WeighbridgeRecordExcel> exportList(WeighbridgeRecordPageReqVo reqVo) {
+        Set<Long> weighbridgeIds = resolveWeighbridgeIds(reqVo);
+        if (weighbridgeIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<KwsWeighbridgeRecord> records = kwsWeighbridgeRecordRepository.listQuery(weighbridgeIds, reqVo.getTruckNo(),
+                reqVo.getMinWeight(), reqVo.getMaxWeight(), reqVo.getCreateStartTime(), reqVo.getCreateEndTime(),
+                reqVo.getReceiveStartTime(), reqVo.getReceiveEndTime());
+        return buildExportRows(buildRes(records));
+    }
+
+    private List<WeighbridgeRecordResVo> buildRes(List<KwsWeighbridgeRecord> records) {
+        if (records == null || records.isEmpty()) {
+            return Collections.emptyList();
+        }
+        Map<Long, KwsWeighbridge> weighbridgeMap = weighbridgeMap(records.stream()
+                .map(KwsWeighbridgeRecord::getWeighbridgeId).collect(Collectors.toSet()));
+        Map<Long, String> entNameMap = enterpriseNameMap(weighbridgeMap.values().stream()
+                .map(KwsWeighbridge::getEntId).collect(Collectors.toSet()));
+        List<WeighbridgeRecordResVo> result = new ArrayList<>(records.size());
+        for (KwsWeighbridgeRecord record : records) {
+            KwsWeighbridge weighbridge = weighbridgeMap.get(record.getWeighbridgeId());
+            WeighbridgeRecordResVo resVo = new WeighbridgeRecordResVo();
+            resVo.setId(record.getId());
+            resVo.setWeighbridgeId(record.getWeighbridgeId());
+            resVo.setWeighbridgeName(weighbridge == null ? "" : weighbridge.getWeighbridgeName());
+            resVo.setUniqueCode(weighbridge == null ? "" : weighbridge.getUniqueCode());
+            resVo.setEntId(weighbridge == null ? null : weighbridge.getEntId());
+            resVo.setEnterpriseName(weighbridge == null ? "" : entNameMap.getOrDefault(weighbridge.getEntId(), ""));
+            resVo.setTruckNo(record.getTruckNo());
+            resVo.setWeight(record.getWeight());
+            resVo.setCreateTime(record.getCreateTime());
+            resVo.setReceiveTime(record.getReceiveTime());
+            result.add(resVo);
+        }
+        return result;
+    }
+
+    private List<WeighbridgeRecordExcel> buildExportRows(List<WeighbridgeRecordResVo> rows) {
+        if (rows == null || rows.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<WeighbridgeRecordExcel> result = new ArrayList<>(rows.size());
+        for (WeighbridgeRecordResVo row : rows) {
+            WeighbridgeRecordExcel excel = new WeighbridgeRecordExcel();
+            excel.setWeighbridgeName(row.getWeighbridgeName());
+            excel.setUniqueCode(row.getUniqueCode());
+            excel.setEnterpriseName(row.getEnterpriseName());
+            excel.setTruckNo(row.getTruckNo());
+            excel.setWeight(row.getWeight());
+            excel.setCreateTime(row.getCreateTime());
+            excel.setReceiveTime(row.getReceiveTime());
+            result.add(excel);
+        }
+        return result;
+    }
+
+    private Set<Long> resolveWeighbridgeIds(WeighbridgeRecordPageReqVo reqVo) {
+        if (reqVo.getWeighbridgeId() != null) {
+            KwsWeighbridge weighbridge = kwsWeighbridgeRepository.findAvailableById(reqVo.getWeighbridgeId());
+            if (weighbridge == null || !checkEntPermission(weighbridge.getEntId())) {
+                return Collections.emptySet();
+            }
+            return Collections.singleton(weighbridge.getId());
+        }
+        Set<Long> entIds = resolveAuthorizedEntIds();
+        List<KwsWeighbridge> weighbridges;
+        if (LoginUserHolder.isManager()) {
+            weighbridges = kwsWeighbridgeRepository.listByNameAndCode(reqVo.getWeighbridgeName(), reqVo.getUniqueCode(), null);
+        } else if (entIds.isEmpty()) {
+            return Collections.emptySet();
+        } else {
+            weighbridges = kwsWeighbridgeRepository.listByNameAndCode(reqVo.getWeighbridgeName(), reqVo.getUniqueCode(), entIds);
+        }
+        return weighbridges.stream().map(KwsWeighbridge::getId).collect(Collectors.toCollection(LinkedHashSet::new));
+    }
+
+    private boolean checkEntPermission(Long entId) {
+        return LoginUserHolder.isManager() || resolveAuthorizedEntIds().contains(entId);
+    }
+
+    private Set<Long> resolveAuthorizedEntIds() {
+        if (LoginUserHolder.isManager()) {
+            return Collections.emptySet();
+        }
+        Set<Long> result = new LinkedHashSet<>();
+        if (LoginUserHolder.getEntId() != null) {
+            result.add(LoginUserHolder.getEntId());
+        }
+        result.addAll(LoginUserHolder.getAuthEntIdList());
+        result.addAll(LoginUserHolder.getChildEntList());
+        return result;
+    }
+
+    private Map<Long, KwsWeighbridge> weighbridgeMap(Collection<Long> weighbridgeIds) {
+        if (weighbridgeIds == null || weighbridgeIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return kwsWeighbridgeRepository.listByIds(weighbridgeIds).stream()
+                .filter(item -> Objects.equals(item.getDelFlag(), 0))
+                .collect(Collectors.toMap(KwsWeighbridge::getId, item -> item, (a, b) -> a));
+    }
+
+    private Map<Long, String> enterpriseNameMap(Collection<Long> entIds) {
+        if (entIds == null || entIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return kwsEnterpriseRepository.listByIds(entIds).stream()
+                .collect(Collectors.toMap(KwsEnterprise::getId, KwsEnterprise::getFirmName, (a, b) -> a));
+    }
+
+
+
+    /**
+     * 车牌验证
+     * @param request 地磅上报请求参数
+     * @return 是否保存成功
+     */
+    public LicensePlateValidateResponse handleValidateLicensePlate(LicensePlateValidateRequest request) {
+        log.info("车牌验证, 车牌:{}", request.getLicensePlate());
+        LicensePlateValidateResponse response = new LicensePlateValidateResponse();
+        ValidateLicensePlate validateLicensePlate;
+        //查询车牌是不存在
+        try {
+            validateLicensePlate = validateLicensePlateService.queryByLicensePlate(request.getLicensePlate(), request.getUuid());
+        } catch (Exception e) {
+            log.error("车牌验证失败,请重新识别或联系管理员", e);
+            return getValidateLicensePlateError(response);
+        }
+
+        if (validateLicensePlate != null) {
+            log.info("车牌验证成功");
+            response.setStatus(Boolean.TRUE);
+            response.setCode(200);
+            response.setMessage("车牌验证成功");
+
+            response.setData(new LicensePlateValidateResponse.Data());
+            response.getData().setTimestamp(System.currentTimeMillis() / 1000);
+            response.getData().setScreen_message("车牌验证成功");
+            response.getData().setVoice_message("车牌验证成功");
+            response.getData().setWarning(null);
+            return response;
+        } else {
+            return getValidateLicensePlateError(response);
+        }
+    }
+    private LicensePlateValidateResponse getValidateLicensePlateError(LicensePlateValidateResponse response) {
+        log.info("车牌验证失败");
+        response.setStatus(Boolean.FALSE);
+        response.setCode(400);
+        response.setMessage("车牌验证异常");
+
+        response.setData(new LicensePlateValidateResponse.Data());
+        response.getData().setTimestamp(System.currentTimeMillis()/1000);
+        response.getData().setScreen_message("车牌验证失败");
+        response.getData().setVoice_message("车牌验证失败");
+        response.getData().setWarning(null);
+        return response;
+    }
+
+    /**
+     * 处理地磅过磅数据上报
+     * @param request 地磅上报请求参数
+     * @return 是否保存成功
+     */
+    public LicensePlateValidateResponse handleWeighbridgePush(WeighbridgePushRequest request) {
+        log.info("处理地磅数据上报 - 车牌:{}, 地磅编号:{}, 重量:{}, 时间戳:{}",
+                request.getLicensePlate(),
+                request.getWeighbridgeCode(),
+                request.getGrossWeight(),
+                request.getTimestamp());
+        LicensePlateValidateResponse licensePlateValidateResponse = new LicensePlateValidateResponse();
+        if (StringUtils.isAnyBlank(request.getLicensePlate(), request.getWeighbridgeCode()) || Objects.isNull(request.getGrossWeight()) || Objects.isNull(request.getTimestamp())) {
+            return getLicensePlateValidateResponse(request, licensePlateValidateResponse);
+        }
+        try {
+            KwsWeighbridge weighbridge = kwsWeighbridgeRepository.findByUniqueCode(request.getWeighbridgeCode().trim());
+            if (weighbridge == null || Objects.equals(weighbridge.getDelFlag(), 1)) {
+                log.error("地磅数据保存失败 - 地磅不存在, 地磅编号: {}", request.getWeighbridgeCode());
+                return getLicensePlateValidateResponse(request, licensePlateValidateResponse);
+            }
+
+            KwsWeighbridgeRecord record = buildWeighbridgeRecord(request, weighbridge.getId());
+            boolean saved = kwsWeighbridgeRecordRepository.save(record);
+            if (saved) {
+                // 异步处理图片上传
+                updateImageUrls(request, record);
+                licensePlateValidateResponse.setStatus(true);
+                licensePlateValidateResponse.setCode(200);
+                licensePlateValidateResponse.setMessage("数据上报成功");
+                LicensePlateValidateResponse.Data data = new LicensePlateValidateResponse.Data();
+                data.setTimestamp(resolveSecondTimestamp(request.getTimestamp()));
+                data.setScreen_message("数据上报成功");
+                data.setVoice_message("数据上报成功");
+                licensePlateValidateResponse.setData(data);
+                log.info("地磅数据保存成功 - ID: {}, 车牌: {}", record.getId(), record.getTruckNo());
+                return licensePlateValidateResponse;
+            } else {
+                log.error("地磅数据保存失败 - 车牌: {}", request.getLicensePlate());
+                return getLicensePlateValidateResponse(request, licensePlateValidateResponse);
+            }
+
+        } catch (Exception e) {
+            log.error("地磅数据上报处理异常", e);
+            return getLicensePlateValidateResponse(request, licensePlateValidateResponse);
+        }
+    }
+    private void updateImageUrls(WeighbridgePushRequest request, KwsWeighbridgeRecord record) {
+        MultipartFile[] images = request.getImages();
+        if (images == null || images.length == 0) {
+            return;
+        }
+
+        int validImageCount = (int) java.util.Arrays.stream(images)
+                .filter(Objects::nonNull)
+                .count();
+        if (validImageCount == 0) {
+            return;
+        }
+
+        // kws_weighbridge_record 当前没有图片URL字段,先只记录上传事实,避免保留无效的旧依赖代码。
+        log.info("地磅记录收到图片但当前未落库, recordId: {}, truckNo: {}, imageCount: {}",
+                record.getId(), request.getLicensePlate(), validImageCount);
+    }
+    private static LicensePlateValidateResponse getLicensePlateValidateResponse(WeighbridgePushRequest request, LicensePlateValidateResponse licensePlateValidateResponse) {
+
+        licensePlateValidateResponse.setStatus(false);
+        licensePlateValidateResponse.setCode(400);
+        licensePlateValidateResponse.setMessage("数据上报失败");
+        LicensePlateValidateResponse.Data data = new LicensePlateValidateResponse.Data();
+        String timestampStr = request.getTimestamp().toString();
+        if (timestampStr.length() == 10) {
+            // 秒级时间戳
+            data.setTimestamp(request.getTimestamp());
+        } else if (timestampStr.length() == 13) {
+            // 毫秒级时间戳
+            long l = request.getTimestamp() / 1000;
+            data.setTimestamp(l);
+        }
+
+        data.setVoice_message("数据上报失败");
+        licensePlateValidateResponse.setData(data);
+        return licensePlateValidateResponse;
+    }
+
+    private KwsWeighbridgeRecord buildWeighbridgeRecord(WeighbridgePushRequest request, Long weighbridgeId) {
+        Date now = new Date();
+        return new KwsWeighbridgeRecord()
+                .setWeighbridgeId(weighbridgeId)
+                .setTruckNo(request.getLicensePlate().trim())
+                .setWeight(request.getGrossWeight())
+                .setReceiveTime(resolveReceiveTime(request.getTimestamp()))
+                .setCreateBy(0L)
+                .setCreateTime(now)
+                .setUpdateBy(0L)
+                .setUpdateTime(now)
+                .setDelFlag(0);
+    }
+
+    private Date resolveReceiveTime(Long timestamp) {
+        long secondTimestamp = resolveSecondTimestamp(timestamp);
+        return new Date(secondTimestamp * 1000);
+    }
+
+    private long resolveSecondTimestamp(Long timestamp) {
+        if (timestamp == null) {
+            return System.currentTimeMillis() / 1000;
+        }
+        String timestampStr = timestamp.toString();
+        if (timestampStr.length() == 13) {
+            return timestamp / 1000;
+        }
+        return timestamp;
+    }
+
+}

+ 18 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/ValidateLicensePlateService.java

@@ -0,0 +1,18 @@
+package com.sckw.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sckw.system.model.ValidateLicensePlate;
+
+
+/**
+ * @Author: cxf
+ * @CreateTime: 2026-01-21
+ * @Description: 地磅过磅记录Service接口
+ * @Version: 1.0
+ */
+public interface ValidateLicensePlateService extends IService<ValidateLicensePlate> {
+
+    ValidateLicensePlate queryByLicensePlate(String licensePlate, String uuid);
+
+    ValidateLicensePlate queryByLicensePlate( String licensePlate);
+}

+ 36 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/ValidateLicensePlateServiceImpl.java

@@ -0,0 +1,36 @@
+package com.sckw.system.service;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import com.sckw.system.dao.ValidateLicensePlateMapper;
+import com.sckw.system.model.ValidateLicensePlate;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Author: cxf
+ * @CreateTime: 2026-01-21
+ * @Description: 地磅过磅记录Service实现类
+ * @Version: 1.0
+ */
+@Service
+public class ValidateLicensePlateServiceImpl extends ServiceImpl<ValidateLicensePlateMapper, ValidateLicensePlate> implements ValidateLicensePlateService {
+
+    @Override
+    public ValidateLicensePlate queryByLicensePlate(String licensePlate, String uuid) {
+        return getOne(Wrappers.<ValidateLicensePlate>lambdaQuery()
+                .eq(ValidateLicensePlate::getLicensePlate, licensePlate)
+                .eq(ValidateLicensePlate::getUuid, uuid)
+                .eq(ValidateLicensePlate::getStatus, 0)
+                .last("limit 1"));
+    }
+
+    @Override
+    public ValidateLicensePlate queryByLicensePlate(String licensePlate) {
+        return getOne(Wrappers.<ValidateLicensePlate>lambdaQuery()
+                .eq(ValidateLicensePlate::getLicensePlate, licensePlate)
+                .eq(ValidateLicensePlate::getStatus, 0)
+                .last("limit 1"));
+    }
+
+}

+ 0 - 259
sckw-modules/sckw-system/src/main/java/com/sckw/system/utils/DataPermissionHelper.java

@@ -1,259 +0,0 @@
-package com.sckw.system.utils;
-
-import com.sckw.core.utils.CollectionUtils;
-import com.sckw.core.web.context.LoginUserHolder;
-import com.sckw.system.api.model.dto.res.DataPermissionDTO;
-import com.sckw.system.model.KwsRole;
-import com.sckw.system.service.KwsDataPermissionService;
-import com.sckw.system.service.KwsRoleService;
-import lombok.Data;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.*;
-
-/**
- * 数据权限过滤工具类
- * <p>
- * 用于业务查询拦截,在查询贸易合同/订单/运单等业务数据时,
- * 根据当前用户角色的数据权限配置过滤可见数据范围。
- * <p>
- * 企业数据权限 + 个人数据权限为并集(组合)关系:
- * <ul>
- *     <li>企业数据权限:限制可见企业范围 → SQL: ent_id IN (已选企业)</li>
- *     <li>个人数据权限开启后:在企业范围内仅查看销售人员=本人的数据
- *         → SQL: ent_id IN (已选企业) AND salesman_id = 当前用户</li>
- *     <li>覆盖范围:贸易合同及其衍生的贸易订单、物流订单、运单</li>
- * </ul>
- * <p>
- *
- * @author system
- * @date 2026-03-26
- */
-@Slf4j
-@Component
-public class DataPermissionHelper {
-
-    @Autowired
-    private KwsDataPermissionService kwsDataPermissionService;
-
-    @Autowired
-    private KwsRoleService kwsRoleService;
-
-    /**
-     * 获取当前登录用户的数据权限过滤条件
-     * <p>
-     * 规则:
-     * - 平台端管理员:仅管理平台自身创建的角色,数据权限按角色配置
-     * - 客户端主账号:可管理本企业+下级企业的角色
-     * - 所有用户:企业数据权限 + 个人数据权限为并集关系
-     * - 个人数据权限开启后:在已勾选企业范围内,仅查看销售人员=当前用户的数据
-     *
-     * @return 数据权限过滤条件
-     */
-    public DataPermissionFilter getPermissionFilter() {
-        DataPermissionFilter filter = new DataPermissionFilter();
-        Long userId = LoginUserHolder.getUserId();
-        filter.setUserId(userId);
-
-        // 平台管理员默认可见全部
-        if (LoginUserHolder.isManager()) {
-            Long roleId = LoginUserHolder.getCurrentRoleId();
-            if (Objects.isNull(roleId)) {
-                filter.setAllVisible(true);
-                return filter;
-            }
-            return buildFilterByRole(roleId, userId);
-        }
-
-        // 客户端用户:获取当前使用的角色
-        Long roleId = LoginUserHolder.getCurrentRoleId();
-        if (Objects.isNull(roleId)) {
-            // 无角色,查询用户所有角色取并集
-            List<KwsRole> roles = kwsRoleService.queryRoleByUserId(userId);
-            if (CollectionUtils.isEmpty(roles)) {
-                filter.setAllVisible(false);
-                filter.setVisibleEntIds(Collections.emptySet());
-                return filter;
-            }
-            return buildFilterByRoles(roles, userId);
-        }
-
-        return buildFilterByRole(roleId, userId);
-    }
-
-    /**
-     * 根据单个角色构建过滤条件
-     */
-    private DataPermissionFilter buildFilterByRole(Long roleId, Long userId) {
-        DataPermissionFilter filter = new DataPermissionFilter();
-        filter.setUserId(userId);
-
-        Set<Long> entIds = kwsDataPermissionService.getCachedEntIds(roleId);
-        boolean personalFlag = kwsDataPermissionService.getCachedPersonalFlag(roleId);
-
-        if (CollectionUtils.isEmpty(entIds)) {
-            // 未配置企业数据权限,默认可见全部(兼容旧数据)
-            filter.setAllVisible(true);
-        } else {
-            filter.setAllVisible(false);
-            filter.setVisibleEntIds(entIds);
-        }
-
-        filter.setPersonalDataEnabled(personalFlag);
-        return filter;
-    }
-
-    /**
-     * 根据多个角色构建过滤条件(取并集)
-     */
-    private DataPermissionFilter buildFilterByRoles(List<KwsRole> roles, Long userId) {
-        DataPermissionFilter filter = new DataPermissionFilter();
-        filter.setUserId(userId);
-
-        Set<Long> allEntIds = new HashSet<>();
-        boolean anyPersonalFlag = false;
-
-        for (KwsRole role : roles) {
-            Set<Long> entIds = kwsDataPermissionService.getCachedEntIds(role.getId());
-            if (CollectionUtils.isNotEmpty(entIds)) {
-                allEntIds.addAll(entIds);
-            }
-            if (kwsDataPermissionService.getCachedPersonalFlag(role.getId())) {
-                anyPersonalFlag = true;
-            }
-        }
-
-        if (allEntIds.isEmpty()) {
-            filter.setAllVisible(true);
-        } else {
-            filter.setAllVisible(false);
-            filter.setVisibleEntIds(allEntIds);
-        }
-
-        filter.setPersonalDataEnabled(anyPersonalFlag);
-        return filter;
-    }
-
-    /**
-     * 将过滤条件应用到查询参数Map中
-     * <p>
-     * 业务Mapper XML中使用:
-     * <pre>
-     * &lt;if test="dataPermEntIds != null and dataPermEntIds.size() > 0"&gt;
-     *     AND ent_id IN
-     *     &lt;foreach collection="dataPermEntIds" item="item" open="(" close=")" separator=","&gt;
-     *         #{item}
-     *     &lt;/foreach&gt;
-     * &lt;/if&gt;
-     * &lt;if test="dataPermUserId != null"&gt;
-     *     AND salesman_id = #{dataPermUserId}
-     * &lt;/if&gt;
-     * </pre>
-     *
-     * @param params 查询参数Map
-     */
-    public void applyFilter(Map<String, Object> params) {
-        DataPermissionFilter filter = getPermissionFilter();
-
-        if (filter.isAllVisible() && !filter.isPersonalDataEnabled()) {
-            return;
-        }
-
-        if (!filter.isAllVisible() && CollectionUtils.isNotEmpty(filter.getVisibleEntIds())) {
-            params.put("dataPermEntIds", new ArrayList<>(filter.getVisibleEntIds()));
-        }
-
-        if (filter.isPersonalDataEnabled()) {
-            params.put("dataPermUserId", filter.getUserId());
-        }
-    }
-
-    /**
-     * 判断当前用户是否有权查看指定企业的数据
-     *
-     * @param targetEntId 目标企业ID
-     * @return true=有权限
-     */
-    public boolean hasEntPermission(Long targetEntId) {
-        DataPermissionFilter filter = getPermissionFilter();
-        if (filter.isAllVisible()) {
-            return true;
-        }
-        return CollectionUtils.isNotEmpty(filter.getVisibleEntIds())
-                && filter.getVisibleEntIds().contains(targetEntId);
-    }
-
-    // ==================== 跨模块调用支持(Dubbo) ====================
-
-    /**
-     * 根据显式参数获取数据权限过滤条件(不依赖LoginUserHolder,适用于Feign远程调用)
-     *
-     * @param userId    用户ID
-     * @param roleId    当前使用的角色ID,null时取用户所有角色的并集
-     * @param isManager 是否平台管理员
-     * @return 数据权限过滤条件
-     */
-    public DataPermissionFilter getPermissionFilter(Long userId, Long roleId, boolean isManager) {
-        DataPermissionFilter filter = new DataPermissionFilter();
-        filter.setUserId(userId);
-
-        if (isManager) {
-            if (Objects.isNull(roleId)) {
-                filter.setAllVisible(true);
-                return filter;
-            }
-            return buildFilterByRole(roleId, userId);
-        }
-
-        if (Objects.isNull(roleId)) {
-            List<KwsRole> roles = kwsRoleService.queryRoleByUserId(userId);
-            if (CollectionUtils.isEmpty(roles)) {
-                filter.setAllVisible(false);
-                filter.setVisibleEntIds(Collections.emptySet());
-                return filter;
-            }
-            return buildFilterByRoles(roles, userId);
-        }
-
-        return buildFilterByRole(roleId, userId);
-    }
-
-    /**
-     * 构建数据权限DTO(供Feign远程接口返回给其他模块)
-     * <p>
-     * 其他模块通过 DataPermissionFeignService.getDataPermissionFilter() 间接调用本方法,
-     * 获取可序列化的 DataPermissionDTO,然后调用 dto.applyTo(params) 注入查询参数。
-     *
-     * @param userId    用户ID
-     * @param roleId    当前使用的角色ID
-     * @param isManager 是否平台管理员
-     * @return 可序列化的数据权限过滤DTO
-     */
-    public DataPermissionDTO buildPermissionDTO(Long userId, Long roleId, boolean isManager) {
-        DataPermissionFilter filter = getPermissionFilter(userId, roleId, isManager);
-        DataPermissionDTO dto = new DataPermissionDTO();
-        dto.setUserId(filter.getUserId());
-        dto.setAllVisible(filter.isAllVisible());
-        dto.setVisibleEntIds(filter.getVisibleEntIds());
-        dto.setPersonalDataEnabled(filter.isPersonalDataEnabled());
-        return dto;
-    }
-
-    /**
-     * 数据权限过滤条件
-     */
-    @Data
-    public static class DataPermissionFilter {
-        /** 当前用户ID */
-        private Long userId;
-        /** 是否可见全部数据(未配置数据权限时为true) */
-        private boolean allVisible;
-        /** 可见的企业ID集合 */
-        private Set<Long> visibleEntIds;
-        /** 是否开启个人数据权限(仅查看本人作为销售的数据) */
-        private boolean personalDataEnabled;
-    }
-
-}

+ 10 - 0
sql/2026/04/2026_04_08_validate_license_plate.sql

@@ -0,0 +1,10 @@
+CREATE TABLE validate_License_Plate (
+                                        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+                                        license_plate VARCHAR(20) NOT NULL DEFAULT '' COMMENT '车牌号',
+                                        uuid VARCHAR(20) NOT NULL DEFAULT '' COMMENT '厂商来源标识',
+                                        status tinyint NOT NULL DEFAULT '0' COMMENT '状态,0=可用,1=不可用',
+                                        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
+                                        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
+                                        PRIMARY KEY (`id`),
+                                        KEY `idx_license_plate` (`license_plate`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车牌验证表';