34 Commits
Author SHA1 Message Date
秦鸿展 43b95f3f07 31 2026-09-15 17:17:43 +08:00
秦鸿展 44f2579e3f BI园区大屏指标修改 2026-09-15 17:13:49 +08:00
秦鸿展 ca5b6e4672 BI园区大屏指标修改 2026-09-15 17:13:30 +08:00
秦鸿展 6b991363c6 30 2026-09-15 11:41:39 +08:00
秦鸿展 84c7fa7647 BI月台指标字段修改 2026-09-15 11:34:59 +08:00
秦鸿展 c0d2faaaf1 BI月台指标字段修改 2026-09-15 11:29:46 +08:00
王奎兴 8324d8dc31 发31 2026-09-14 11:09:46 +08:00
王奎兴 6ce35b58e4 发30 2026-09-14 11:05:55 +08:00
王奎兴 700556ea63 发31 2026-09-14 10:58:00 +08:00
王奎兴 100a8d87ac 上架过滤已逻辑删除数据; 2026-09-14 10:54:02 +08:00
王奎兴 b0c9a41f07 Merge remote-tracking branch 'origin/dev' into dev 2026-09-14 09:33:14 +08:00
王奎兴 82d5eda139 上架过滤已逻辑删除数据; 2026-09-14 09:33:01 +08:00
秦鸿展 07393e43ff 31 2026-09-13 18:26:40 +08:00
秦鸿展 08858f24d6 30 2026-09-13 18:22:47 +08:00
秦鸿展 a994241400 BI月台指标新增字段 2026-09-13 18:21:26 +08:00
王奎兴 a9be03961a feign地址修改; 2026-09-12 17:57:49 +08:00
王奎兴 7f6006ca48 feign地址修改; 2026-09-12 17:20:01 +08:00
赵辉 defc8c3709 31 2026-09-12 10:03:33 +08:00
王奎兴 3c21c2c848 发30 2026-09-12 10:00:45 +08:00
王奎兴 71936b50a7 Merge remote-tracking branch 'origin/dev' into dev 2026-09-12 09:59:56 +08:00
赵辉 9cfffe1224 去除验证码校验 2026-09-12 09:59:39 +08:00
王奎兴 d7574d188a 客户月结报告; 2026-09-12 09:59:30 +08:00
赵辉 1c1b06c1bf 去除验证码校验 2026-09-12 09:24:11 +08:00
rcx 117019c55c 30 2026-09-11 17:46:07 +08:00
rcx 171909b68d 31 2026-09-11 17:44:15 +08:00
赵辉 31c27c16c6 user31 2026-09-11 17:28:40 +08:00
赵辉 8dfe9545aa user30 2026-09-11 17:22:50 +08:00
赵辉 028afb57d7 用户-yms同步新增与修改密码 2026-09-11 15:45:15 +08:00
rcx 37dd374bda fix(wms): 入库单导入"该货主下不存在该料号"误报修复
- 根因:Excel客户名称按USER表user_name解析货主ID,同名双账号(如3519/3525)解析到非档案归属账号,带错误shipperId查物料必为空
- 料号匹配:主查询(料号+顶级组织+解析shipperId)为空时,去掉货主条件按档案shipper_name与Excel客户名称等值兜底匹配,命中后以档案货主纠正单据shipperId/shipperCode/shipperName
- 新增pickMaterialByCode挑选:料号精确等值优先于LIKE模糊命中,避免子串料号截胡
- SKU校验:档案条码barCode为空时跳过比对,档案有值时仍严格校验
- 校验失败输出诊断日志并附解析shipperId/登录topOrganizationId,区分货主错配(原因A)与组织口径(原因B)
2026-09-11 15:14:46 +08:00
秦鸿展 d69af9ef20 30 2026-09-10 16:49:24 +08:00
秦鸿展 9704dbfb07 导入物料组织覆盖问题 2026-09-10 16:20:59 +08:00
秦鸿展 3276792bb5 告警修改 2026-09-10 13:20:07 +08:00
秦鸿展 b57db4b8cc 30 2026-09-10 11:49:34 +08:00
秦鸿展 8a31ed9974 BI月台指标检测修改 2026-09-10 11:46:34 +08:00
61 changed files with 1964 additions and 210 deletions
@@ -19,7 +19,8 @@ import java.util.Set;
/** /**
* bms财务结算系统feign调用接口 * bms财务结算系统feign调用接口
*/ */
@FeignClient(contextId = "BmsService", value = ServiceNameConstants.BMS_SERVICE, url = "http://10.102.192.32:8012",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) // 目标地址由 nacos 配置项 feign.url.mhd-bms-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "BmsService", value = ServiceNameConstants.BMS_SERVICE, url = "http://10.102.192.32:8019",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface BmsServiceFeign { public interface BmsServiceFeign {
@@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
// 目标地址由 nacos 配置项 feign.url.mhd-oms-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "OmsService", value = ServiceNameConstants.OMS_SERVICE,url = "http://10.102.192.33:8017",fallbackFactory = RemoteOmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) @FeignClient(contextId = "OmsService", value = ServiceNameConstants.OMS_SERVICE,url = "http://10.102.192.33:8017",fallbackFactory = RemoteOmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface OmsServiceFeign { public interface OmsServiceFeign {
@@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
// 目标地址由 nacos 配置项 feign.url.mhd-wlhy-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "commonWlhyServiceFeign",value = ServiceNameConstants.WLHY_SERVICE,url = "http://10.102.192.31:8014",configuration = FeignAutoConfiguration.class) @FeignClient(contextId = "commonWlhyServiceFeign",value = ServiceNameConstants.WLHY_SERVICE,url = "http://10.102.192.31:8014",configuration = FeignAutoConfiguration.class)
public interface WlhyServiceFeign { public interface WlhyServiceFeign {
@@ -21,6 +21,7 @@ import java.util.Map;
* @description: TODO * @description: TODO
* @date 2024/5/10 8:58 * @date 2024/5/10 8:58
**/ **/
// 目标地址由 nacos 配置项 feign.url.mhd-wms-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "remoteWmsServiceFeign",value = ServiceNameConstants.WMS_SERVICE,url = "http://10.102.192.32:8016",fallbackFactory = RemoteWmsFallbackFactory.class, configuration = FeignAutoConfiguration.class) @FeignClient(contextId = "remoteWmsServiceFeign",value = ServiceNameConstants.WMS_SERVICE,url = "http://10.102.192.32:8016",fallbackFactory = RemoteWmsFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface WmsServiceFeign { public interface WmsServiceFeign {
@@ -13,7 +13,8 @@ import org.springframework.web.bind.annotation.*;
import java.util.Map; import java.util.Map;
@FeignClient(contextId = "XxlJobService", value = ServiceNameConstants.XXL_JOB_SERVICE,url = "http://127.0.0.1:8020",fallbackFactory = RemoteXxlJobFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) // 目标地址由 nacos 配置项 feign.url.xxl-job-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "XxlJobService", value = ServiceNameConstants.XXL_JOB_SERVICE,url = "http://10.102.192.33:8020",fallbackFactory = RemoteXxlJobFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface XxlJobServiceFeign { public interface XxlJobServiceFeign {
@@ -9,6 +9,7 @@ import com.mhd.system.api.feign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
// 目标地址由 nacos 配置项 feign.url.mhd-yms-service 指定,未配置时按服务名走服务发现
@FeignClient(contextId = "YmsService", value = ServiceNameConstants.YMS_SERVICE,url = "http://10.102.192.33:8018",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) @FeignClient(contextId = "YmsService", value = ServiceNameConstants.YMS_SERVICE,url = "http://10.102.192.33:8018",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class)
public interface YmsServiceFeign { public interface YmsServiceFeign {
@@ -130,9 +130,11 @@ public class TokenLoginApplicationService {
//验证码登录校验验证码 //验证码登录校验验证码
String key = CacheConstants.SMS_LOGIN + userPhone; String key = CacheConstants.SMS_LOGIN + userPhone;
Object object = redisService.getCacheObject(key) == null ? "":redisService.getCacheObject(key); Object object = redisService.getCacheObject(key) == null ? "":redisService.getCacheObject(key);
if(!ObjectUtil.equal(String.valueOf(object),verificationCode) && !ObjectUtil.equal(verificationCode, SmsConstants.universalCode)){ if (loginDTO.getWechatFlag() != 1) {
recordLogininfor(userPhone, Constants.LOGIN_FAIL, "验证码错误", 1); // if (!ObjectUtil.equal(String.valueOf(object), verificationCode) && !ObjectUtil.equal(verificationCode, SmsConstants.universalCode)) {
throw new ServiceException("验证码错误"); // recordLogininfor(userPhone, Constants.LOGIN_FAIL, "验证码错误", 1);
throw new ServiceException("验证码错误");
// }
} }
//获取包名对应的业务(存在需要查询业务,不存在保存为无业务模式) //获取包名对应的业务(存在需要查询业务,不存在保存为无业务模式)
@@ -168,7 +170,13 @@ public class TokenLoginApplicationService {
throw new ServiceException("对不起,您的账号:" + userPhone + " 已停用"); throw new ServiceException("对不起,您的账号:" + userPhone + " 已停用");
} }
//校验验证码是否正确 //校验验证码是否正确
sysPasswordService.validate(userPo, verificationCode, 1, null); if (loginDTO.getWechatFlag() != 1) {
// if (!ObjectUtil.equal(String.valueOf(object), verificationCode) && !ObjectUtil.equal(verificationCode, SmsConstants.universalCode)) {
// recordLogininfor(userPhone, Constants.LOGIN_FAIL, "验证码错误", 1);
throw new ServiceException("验证码错误");
// }
}
// sysPasswordService.validate(userPo, verificationCode, 1, null);
} }
return userInfo; return userInfo;
} }
@@ -40,4 +40,7 @@ public class LoginDTO {
@ApiModelProperty(name = "组织名称(司机选择组织时传入)") @ApiModelProperty(name = "组织名称(司机选择组织时传入)")
private String organizationName; private String organizationName;
@ApiModelProperty(name = "微信标识")
private Integer wechatFlag;
} }
+4 -4
View File
@@ -12,10 +12,10 @@ spring:
cloud: cloud:
nacos: nacos:
discovery: discovery:
server-addr: 127.0.0.1:8848 # server-addr: 127.0.0.1:8848
# 线上测试环境配置 容器名+端口号 # 线上测试环境配置 容器名+端口号
# server-addr: 10.33.0.129:6010 # server-addr: 10.33.0.129:6010
# server-addr: 10.102.192.31:6848 server-addr: 10.102.192.31:6848
username: nacos username: nacos
password: manhuoda@2023 password: manhuoda@2023
#线上正式环境 #线上正式环境
@@ -23,8 +23,8 @@ spring:
# username: nacos # username: nacos
# password: manhuoda@2023 # password: manhuoda@2023
config: config:
server-addr: 127.0.0.1:8848 # server-addr: 127.0.0.1:8848
# server-addr: 10.102.192.31:6848 server-addr: 10.102.192.31:6848
# 测试环境配置 容器名+端口号 # 测试环境配置 容器名+端口号
# server-addr: 10.33.0.129:6010 # server-addr: 10.33.0.129:6010
# 线上测试环境配置 容器名+端口号 # 线上测试环境配置 容器名+端口号
@@ -0,0 +1,27 @@
package com.mhd.bi.domain.biComprehensive.repository.mapper;
import com.mhd.bi.domain.biComprehensive.repository.po.RealFuelStatPO;
/**
* 综合态势-真实业务数据 Mapper(跨库查询,与月台监测 BiPlatformRealDataMapper 同方案)
*
* <p>数据来源:</p>
* <ul>
* <li>运输订单量:NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDEROMS 运输业务单)</li>
* <li>百公里油耗:NGWL_TEST_WLHY.TMS_EXPENSE_REPORTTMS 运输作业单,字段 FUEL_CONSUMPTION / HEAVYLOAD_MILEAGE</li>
* </ul>
*/
public interface BiComprehensiveRealDataMapper {
/**
* 运输订单量:统计本年度(CREATE_TIME 在当前年)全部组织的运输业务单数量。
* 统计范围:全部组织,不按机构过滤。
*/
Long countCurrentYearTransportOrders();
/**
* 百公里油耗统计:统计本年度油耗量与重载里程均非空的记录,
* 返回 总油耗量 / 总重载里程 / 参与条数,由调用方计算百公里油耗。
*/
RealFuelStatPO queryCurrentYearFuelStat();
}
@@ -0,0 +1,26 @@
package com.mhd.bi.domain.biComprehensive.repository.po;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 百公里油耗统计(真实数据,来自 TMS 运输作业单 TMS_EXPENSE_REPORT
*
* <p>统计口径:本年度、全部组织、油耗量(FUEL_CONSUMPTION)与重载里程(HEAVYLOAD_MILEAGE)均非空的记录。</p>
* <p>百公里油耗 = 总油耗量 ÷ 总重载里程 × 100</p>
*/
@Data
public class RealFuelStatPO implements Serializable {
private static final long serialVersionUID = 1L;
/** 总油耗量(仅统计油耗量非空的记录) */
private BigDecimal totalOil;
/** 总重载里程(仅统计重载里程非空的记录,且油量非空) */
private BigDecimal totalHeavyMileage;
/** 参与统计的记录数 */
private Long totalCount;
}
@@ -0,0 +1,63 @@
package com.mhd.bi.domain.biComprehensive.service;
import com.mhd.bi.domain.biComprehensive.repository.mapper.BiComprehensiveRealDataMapper;
import com.mhd.bi.domain.biComprehensive.repository.po.RealFuelStatPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 综合态势-真实业务数据业务层(封装 OMS / TMS 跨库查询)
*
* <p>实现方案与「月台监测指标查询」一致:每次调用实时查库,查不到/查库异常返回 0,不影响其它字段。</p>
*/
@Slf4j
@Service
public class BiComprehensiveRealDataService {
@Resource
private BiComprehensiveRealDataMapper biComprehensiveRealDataMapper;
/**
* 运输订单量:本年度 OMS 运输业务单数量,范围全部组织。
* 查询失败或无数据返回 0。
*/
public long countCurrentYearTransportOrders() {
try {
Long n = biComprehensiveRealDataMapper.countCurrentYearTransportOrders();
return n == null ? 0L : n;
} catch (Exception e) {
log.error("【OMS】统计本年度运输订单量失败: {}", e.getMessage(), e);
return 0L;
}
}
/**
* 百公里油耗 = 总油耗量 ÷ 总重载里程 × 100,保留一位小数。
*
* <p>统计范围:TMS 运输作业单 TMS_EXPENSE_REPORT,本年度、全部组织、油耗量与重载里程均非空。</p>
* <p>总重载里程为 0 或查询失败时返回 0,避免除零。</p>
*/
public double calcCurrentYearFuelConsumptionPerHundredKm() {
try {
RealFuelStatPO po = biComprehensiveRealDataMapper.queryCurrentYearFuelStat();
if (po == null) {
return 0d;
}
BigDecimal totalOil = po.getTotalOil();
BigDecimal totalMileage = po.getTotalHeavyMileage();
if (totalOil == null || totalMileage == null || totalMileage.compareTo(BigDecimal.ZERO) <= 0) {
return 0d;
}
return totalOil.multiply(BigDecimal.valueOf(100))
.divide(totalMileage, 1, RoundingMode.HALF_UP)
.doubleValue();
} catch (Exception e) {
log.error("【TMS】计算本年度百公里油耗失败: {}", e.getMessage(), e);
return 0d;
}
}
}
@@ -1,21 +1,99 @@
package com.mhd.bi.domain.biComprehensive.service; package com.mhd.bi.domain.biComprehensive.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mhd.bi.domain.biComprehensive.repository.mapper.BiComprehensiveReportMapper; import com.mhd.bi.domain.biComprehensive.repository.mapper.BiComprehensiveReportMapper;
import com.mhd.bi.domain.biSnapshotSync.repository.mapper.BiSnapshotWriteMapper;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.ComprehensiveSituationVO; import com.mhd.bi.interfaces.facadeApi.biReport.vo.ComprehensiveSituationVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/** /**
* 综合态势:数据访问仅经 Mapper(含 XML 内 SQL 与 Mapper 默认方法组装)。 * 综合态势:数据访问仅经 Mapper(含 XML 内 SQL 与 Mapper 默认方法组装)。
*
* <p>其中「运输订单量」「百公里油耗」为实时查询业务库的真实数据,
* 每次调用实时组装并覆盖更新快照表最新一条,实现方案与「月台监测指标查询」一致;</p>
* <p>其余字段仍取自 BI_COMPREHENSIVE_SNAPSHOT 最新快照。</p>
*
* <p>仅当这两项实时数据发生变化时才覆盖更新快照,避免无意义的写库。</p>
*/ */
@Slf4j
@Service @Service
public class BiComprehensiveReportService { public class BiComprehensiveReportService {
private static final String REMARK_REALTIME = "realtime comprehensive";
@Resource @Resource
private BiComprehensiveReportMapper biComprehensiveReportMapper; private BiComprehensiveReportMapper biComprehensiveReportMapper;
@Resource
private BiSnapshotWriteMapper biSnapshotWriteMapper;
@Resource
private BiComprehensiveRealDataService biComprehensiveRealDataService;
@Resource
private ObjectMapper objectMapper;
/**
* 实时组装综合态势:快照其余字段 + 实时业务数据(运输订单量、百公里油耗);
* 仅当实时业务数据发生变化时,才覆盖更新 BI_COMPREHENSIVE_SNAPSHOT 快照表最新一条后返回。
* 表中尚无记录时插入一条兜底。
*/
public ComprehensiveSituationVO loadLatestComprehensive() { public ComprehensiveSituationVO loadLatestComprehensive() {
return biComprehensiveReportMapper.loadLatestComprehensive(); ComprehensiveSituationVO vo = biComprehensiveReportMapper.loadLatestComprehensive();
if (vo == null) {
vo = new ComprehensiveSituationVO();
}
// 实时业务数据:运输订单量(OMS 运输业务单,本年度、全部组织)
vo.setOrderCount(biComprehensiveRealDataService.countCurrentYearTransportOrders());
// 实时业务数据:百公里油耗(TMS 运输作业单,本年度、全部组织,油耗量与重载里程均非空)
vo.setFuelConsumptionPerHundredKm(
biComprehensiveRealDataService.calcCurrentYearFuelConsumptionPerHundredKm());
try {
String fullJson = objectMapper.writeValueAsString(vo);
String fingerprint = realDataFingerprint(vo);
String latestJson = biComprehensiveReportMapper.selectLatestPayloadJson();
if (StringUtils.isNotBlank(latestJson)) {
ComprehensiveSituationVO latestVo =
objectMapper.readValue(latestJson.trim(), ComprehensiveSituationVO.class);
if (Objects.equals(fingerprint, realDataFingerprint(latestVo))) {
log.info("BI 综合态势实时业务数据未变化,跳过落库");
return vo;
}
}
int rows = biSnapshotWriteMapper.updateLatestComprehensiveSnapshot(fullJson, REMARK_REALTIME);
if (rows < 1) {
// 表中无最新记录可更新,插入一条兜底
biSnapshotWriteMapper.insertComprehensiveSnapshot(fullJson, REMARK_REALTIME);
}
log.info("BI 综合态势实时数据已落库(覆盖最新, 数据变化), affectedRows={}", rows);
} catch (Exception e) {
log.error("BI 综合态势实时数据落库失败", e);
}
return vo;
}
/**
* 实时业务数据指纹:仅含实时查库得到的字段(运输订单量、百公里油耗),
* 用于判断是否需要覆盖更新快照;其余字段取自快照,不纳入指纹。
*/
private String realDataFingerprint(ComprehensiveSituationVO vo) throws Exception {
Map<String, Object> fp = new LinkedHashMap<>();
if (vo == null) {
return objectMapper.writeValueAsString(fp);
}
fp.put("orderCount", vo.getOrderCount());
fp.put("fuelConsumptionPerHundredKm", vo.getFuelConsumptionPerHundredKm());
return objectMapper.writeValueAsString(fp);
} }
} }
@@ -10,6 +10,15 @@ import com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformInfoPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealOnTimeStatPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealOnTimeStatPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskTypeStatPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskTypeStatPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyTaskDurationPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyTaskDurationPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyAvgDurationPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTodayAvgDurationPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealOvertimeStatPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealOvertimeRecordPO;
import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
@@ -29,7 +38,7 @@ public interface BiPlatformRealDataMapper {
/** 今日作业任务数 */ /** 今日作业任务数 */
Long countTodayTasks(); Long countTodayTasks();
/** 车辆状态统计(今日) */ /** 车辆状态统计(今日,含叫号状态合计 callStatusTotal = 已签到+已叫号+已过号 */
RealVehicleStatusPO queryVehicleStatusToday(); RealVehicleStatusPO queryVehicleStatusToday();
/** 本月任务量 */ /** 本月任务量 */
@@ -41,13 +50,34 @@ public interface BiPlatformRealDataMapper {
/** 任务负荷(今日 24 小时) */ /** 任务负荷(今日 24 小时) */
List<RealTaskLoadPO> queryTaskLoadToday(); List<RealTaskLoadPO> queryTaskLoadToday();
/** 近半年平均作业时长(按月分组:月台作业时长 / 园区作业时长 合计 + 任务数) */
List<RealMonthlyAvgDurationPO> queryMonthlyAvgDuration();
/** 今日平均作业时长(月台作业平均时长 / 园区平均时长,仅统计两端时间都非空的记录) */
RealTodayAvgDurationPO queryTodayAvgDuration();
/**
* 今日月台作业超时统计(超时记录数 / 有效记录总数)。
* 参考用时取自 YMS QMS_STATIC_FIELD.FIELD_CODE = 'platform_work_ref_minutes'
*/
RealOvertimeStatPO queryTodayOvertimeStat(@Param("refCode") String refCode);
/**
* 今日园区作业超时统计(超时记录数 / 有效记录总数)。
* 参考用时取自 YMS QMS_STATIC_FIELD.FIELD_CODE = 'yard_work_ref_minutes'
*/
RealOvertimeStatPO queryTodayYardOvertimeStat(@Param("refCode") String refCode);
/** 今日月台作业超时记录列表(月台名称 / 车牌号 / 超时时长) */
List<RealOvertimeRecordPO> queryTodayOvertimeList(@Param("refCode") String refCode);
/** 今日预约-业务类型统计(按 businessName 分组统计今日预约数量) */ /** 今日预约-业务类型统计(按 businessName 分组统计今日预约数量) */
List<RealBusinessTypeStatPO> queryTodayBusinessTypeStats(); List<RealBusinessTypeStatPO> queryTodayBusinessTypeStats();
/** 今日作业准时率统计(total 今日预约总数;onTimeCount 今日准时车辆数) */ /** 今日作业准时率统计(total 今日预约总数;onTimeCount 今日准时车辆数) */
RealOnTimeStatPO queryTodayOnTimeStats(); RealOnTimeStatPO queryTodayOnTimeStats();
/** 任务类型统计(OMS 运输订单按业务类型分组:1-跨境 2-国内 */ /** 任务类型统计(YMS QMS_MAIN_BUSINESS 预约按业务类型 BUSINESS_NAME 分组 */
List<RealTaskTypeStatPO> queryMonthTransportOrderByType(); List<RealTaskTypeStatPO> queryMonthTransportOrderByType();
/** 月台监控-按楼层统计月台车辆(楼层、月台数量、今日预约车辆、当前作业车辆) */ /** 月台监控-按楼层统计月台车辆(楼层、月台数量、今日预约车辆、当前作业车辆) */
@@ -0,0 +1,32 @@
package com.mhd.bi.domain.biPlatformRealData.repository.po;
import lombok.Data;
import java.io.Serializable;
/**
* 按月平均作业时长(真实数据,来自 TEST_NGWL_YMS.QMS_MAIN_BUSINESS
*
* <p>用于「平均作业时长」近半年趋势图,双系列:月台作业 / 园区作业。</p>
* <ul>
* <li>月台作业时长 = 离开月台时间(OUT_WINDOWS_TIME) - 进入月台时间(IN_WINDOWS_TIME)</li>
* <li>园区作业时长 = 实际出场时间(EXIT_TIME) - 实际进入时间(ENTRY_TIME)</li>
* </ul>
* <p>各月平均 = 该月有效记录的时长合计 ÷ 该月任务数。</p>
*/
@Data
public class RealMonthlyAvgDurationPO implements Serializable {
private static final long serialVersionUID = 1L;
/** 月份,格式 yyyy-MM */
private String monthOfYear;
/** 该月任务数(预约记录数,作为平均时长的分母) */
private Long taskCount;
/** 该月月台作业时长合计(小时);未进入月台计 0,已进入未离开按 SYSDATE - IN_WINDOWS_TIME */
private Double platformWorkHours;
/** 该月园区作业时长合计(小时);未进场计 0,已进场未出场按 SYSDATE - ENTRY_TIME */
private Double yardWorkHours;
}
@@ -6,16 +6,20 @@ import java.io.Serializable;
/** /**
* 本月任务平均时长 / 完成率统计(真实数据,来自 TEST_NGWL_YMS.QMS_MAIN_BUSINESS * 本月任务平均时长 / 完成率统计(真实数据,来自 TEST_NGWL_YMS.QMS_MAIN_BUSINESS
* avgDurationHours:累计 (EXIT_TIME - ENTRY_TIME) 小时数(仅 ENTRY_TIME/EXIT_TIME 都不为空时参与) * totalWorkHours:本月任务作业时长合计(小时),未进入月台计 0、已进入未离开按 SYSDATE - IN_WINDOWS_TIME
* durationCount:参与平均时长统计的有效条数
* completedCount:已出场(EXIT_TIME IS NOT NULL)且预约日期在本月的数量 * completedCount:已出场(EXIT_TIME IS NOT NULL)且预约日期在本月的数量
* totalCount:本月(TARGET_DATE)预约总数量 * totalCount:本月(TARGET_DATE)预约总数量(作为平均时长的分母)
*
* <p>任务平均作业时长 = totalWorkHours ÷ totalCount,由 Service 层计算。</p>
*/ */
@Data @Data
public class RealMonthlyTaskDurationPO implements Serializable { public class RealMonthlyTaskDurationPO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 平均作业时长(小时) */ /** 本月任务作业时长合计(小时) */
private Double totalWorkHours;
/** 平均作业时长(小时),由 Service 层按 totalWorkHours ÷ totalCount 计算 */
private Double avgDurationHours; private Double avgDurationHours;
/** 参与平均时长统计的有效条数 */ /** 参与平均时长统计的有效条数 */
@@ -26,4 +30,4 @@ public class RealMonthlyTaskDurationPO implements Serializable {
/** 本月预约总数量(按 TARGET_DATE 统计) */ /** 本月预约总数量(按 TARGET_DATE 统计) */
private Long totalCount; private Long totalCount;
} }
@@ -0,0 +1,30 @@
package com.mhd.bi.domain.biPlatformRealData.repository.po;
import lombok.Data;
import java.io.Serializable;
/**
* 今日月台作业超时记录(真实数据)
*
* <p>用于「超时记录列表」:月台名称 / 车牌号码 / 超时时长(分钟)。</p>
*/
@Data
public class RealOvertimeRecordPO implements Serializable {
private static final long serialVersionUID = 1L;
/** 月台名称 */
private String windowName;
/** 车牌号码 */
private String carNo;
/** 超时时长(分钟)= 月台作业时长 - 参考用时 */
private Long overtimeMinutes;
/** 月台作业参考用时(分钟) */
private Long refMinutes;
/** 月台作业时长(分钟) */
private Long platformMinutes;
}
@@ -0,0 +1,27 @@
package com.mhd.bi.domain.biPlatformRealData.repository.po;
import lombok.Data;
import java.io.Serializable;
/**
* 今日作业超时统计(月台作业 / 园区作业 通用)
*
* <p>分母只计入「能取到参考用时 且 作业时长两端时间都非空」的记录。</p>
* <ul>
* <li>月台作业:参考用时 code = platform_work_ref_minutes
* 时长 = OUT_WINDOWS_TIME - IN_WINDOWS_TIME</li>
* <li>园区作业:参考用时 code = yard_work_ref_minutes
* 时长 = EXIT_TIME - ENTRY_TIME</li>
* </ul>
*/
@Data
public class RealOvertimeStatPO implements Serializable {
private static final long serialVersionUID = 1L;
/** 超时记录数:作业时长 > 参考用时 */
private Long overtimeCount;
/** 有效记录总数(分母) */
private Long totalCount;
}
@@ -23,4 +23,8 @@ public class RealPlatformFloorStatPO implements Serializable {
/** 当前作业车辆数(FLOW_STATUS IN 5,6,7,8,9,10 */ /** 当前作业车辆数(FLOW_STATUS IN 5,6,7,8,9,10 */
private Long operatingVehicles; private Long operatingVehicles;
/** 该楼层月台作业平均时长(分钟)= SUM(该楼层有效月台作业时长) ÷ 该楼层有效记录数 */
private Double avgPlatformMinutes;
} }
@@ -4,15 +4,17 @@ import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
/** /**
* 任务负荷统计(按小时,来自 TEST_NGWL_YMS.QMS_MAIN_BUSINESS * 任务负荷统计(按 2 小时分组,来自 TEST_NGWL_YMS.QMS_MAIN_BUSINESS
*
* <p>「小时趋势」需求:每 2 小时分组统计一次,按车辆进入月台时间 IN_WINDOWS_TIME 分桶。</p>
*/ */
@Data @Data
public class RealTaskLoadPO implements Serializable { public class RealTaskLoadPO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 0-23 小时 */ /** 2 小时分组的起始小时,取值 0,2,4,...,22 */
private Integer hourOfDay; private Integer hourOfDay;
/** 该时段任务数 */ /** 该 2 小时时段任务数 */
private Long taskCount; private Long taskCount;
} }
@@ -5,16 +5,16 @@ import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
/** /**
* 任务类型统计(真实数据,来自 NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER 运输订单 * 任务类型统计(真实数据,来自 NGWL_TEST_YMS.QMS_MAIN_BUSINESS 预约登记表
* 按业务类型 businessCategory 分组统计当月数量:1-跨境运输 2-国内运输 * 按本月预约车辆数(TARGET_DATE 在当前月)以 BUSINESS_NAME 业务类型分组
*/ */
@Data @Data
public class RealTaskTypeStatPO implements Serializable { public class RealTaskTypeStatPO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 业务类型1-跨境 2-国内 */ /** 业务类型名称(qms_main_business.BUSINESS_NAME */
private Integer businessCategory; private String businessName;
/** 该类型当月单量 */ /** 该业务类型本月预约车辆数 */
private Long count; private Long count;
} }
@@ -0,0 +1,34 @@
package com.mhd.bi.domain.biPlatformRealData.repository.po;
import lombok.Data;
import java.io.Serializable;
/**
* 今日平均作业时长(真实数据,来自 TEST_NGWL_YMS.QMS_MAIN_BUSINESS
*
* <p>用于「效率统计(本日)」中的:</p>
* <ul>
* <li>月台作业平均时长 avgPlatformDurationHours</li>
* <li>园区平均时长 avgYardDurationHours</li>
* </ul>
*
* <p>统计范围:今日预约记录(TARGET_DATE = 今天),且只统计时长两端的字段都非空的记录:</p>
* <ul>
* <li>月台作业时长 = 离开月台时间(OUT_WINDOWS_TIME) - 进入月台时间(IN_WINDOWS_TIME)
* → 只统计 IN_WINDOWS_TIME 与 OUT_WINDOWS_TIME 都非空的记录</li>
* <li>园区作业时长 = 实际出场时间(EXIT_TIME) - 实际进入时间(ENTRY_TIME)
* → 只统计 ENTRY_TIME 与 EXIT_TIME 都非空的记录</li>
* </ul>
* <p>平均时长 = 有效记录时长合计 ÷ 有效记录数,由 Service 层或 SQL 直接完成。</p>
*/
@Data
public class RealTodayAvgDurationPO implements Serializable {
private static final long serialVersionUID = 1L;
/** 月台作业平均时长(小时),仅统计同时有进入与离开月台时间的记录 */
private Double avgPlatformDurationHours;
/** 园区平均时长(小时),仅统计同时有实际进入与实际出场时间的记录 */
private Double avgYardDurationHours;
}
@@ -19,6 +19,12 @@ public class RealVehicleStatusPO implements Serializable {
/** 当前等待作业(FLOW_STATUS IN '1','2','3' 已预约/已签到/已叫号) */ /** 当前等待作业(FLOW_STATUS IN '1','2','3' 已预约/已签到/已叫号) */
private Long waiting; private Long waiting;
/** 叫号状态合计:已签到 + 已叫号 + 已过号(FLOW_STATUS IN '2','3','4',预约日期为今天) */
private Long callStatusTotal;
/** 今日已出场(FLOW_STATUS = '11',预约日期为今天),用于月台周转率 */
private Long todayExited;
/** 当前作业中 */ /** 当前作业中 */
private Long operatingVehicles; private Long operatingVehicles;
@@ -12,11 +12,23 @@ import com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformInfoPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealOnTimeStatPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealOnTimeStatPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskTypeStatPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskTypeStatPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyTaskDurationPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyTaskDurationPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyAvgDurationPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTodayAvgDurationPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealOvertimeStatPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealOvertimeRecordPO;
import com.mhd.bi.domain.biPlatformSurveillance.repository.mapper.BiPlatformSurveillanceReportMapper; import com.mhd.bi.domain.biPlatformSurveillance.repository.mapper.BiPlatformSurveillanceReportMapper;
import com.mhd.bi.domain.biStaticField.service.BiStaticFieldService;
import com.mhd.bi.domain.biSnapshotSync.repository.mapper.BiSnapshotWriteMapper; import com.mhd.bi.domain.biSnapshotSync.repository.mapper.BiSnapshotWriteMapper;
import com.mhd.bi.domain.biSnapshotSync.support.BiSnapshotWeeklyRandomDataBuilder; import com.mhd.bi.domain.biSnapshotSync.support.BiSnapshotWeeklyRandomDataBuilder;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.AvgOperationDurationTrendItemVO;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.OvertimeRecordItemVO;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.PlatformSurveillanceVO; import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.PlatformSurveillanceVO;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.PlatformVehicleItemVO; import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.PlatformVehicleItemVO;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskLoadItemVO; import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskLoadItemVO;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskTypeStatisticsVO; import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskTypeStatisticsVO;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskTypeCountItemVO; import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskTypeCountItemVO;
@@ -98,17 +110,27 @@ public class BiPlatformRealDataService {
fp.put("operatingPlatforms", vo.getPlatformOverview().getOperatingPlatforms()); fp.put("operatingPlatforms", vo.getPlatformOverview().getOperatingPlatforms());
fp.put("currentOccupancyRate", vo.getPlatformOverview().getCurrentOccupancyRate()); fp.put("currentOccupancyRate", vo.getPlatformOverview().getCurrentOccupancyRate());
fp.put("todayTasks", vo.getPlatformOverview().getTodayTasks()); fp.put("todayTasks", vo.getPlatformOverview().getTodayTasks());
fp.put("platformTurnoverRate", vo.getPlatformOverview().getPlatformTurnoverRate());
} }
// 入园超时率/超时数量 按业务要求恒为 0,不纳入指纹
// 准时率与平均时长为实时业务数据,纳入指纹
if (vo.getEfficiencyStatistics() != null) { if (vo.getEfficiencyStatistics() != null) {
fp.put("platformOnTimeRate", vo.getEfficiencyStatistics().getPlatformOnTimeRate()); fp.put("platformOnTimeRate", vo.getEfficiencyStatistics().getPlatformOnTimeRate());
fp.put("yardOnTimeRate", vo.getEfficiencyStatistics().getYardOnTimeRate());
fp.put("avgPlatformDuration", vo.getEfficiencyStatistics().getAvgPlatformDuration());
fp.put("avgYardDuration", vo.getEfficiencyStatistics().getAvgYardDuration());
} }
if (vo.getVehicleStatus() != null) { if (vo.getVehicleStatus() != null) {
fp.put("todayReservedVehicles", vo.getVehicleStatus().getTodayReservedVehicles()); fp.put("todayReservedVehicles", vo.getVehicleStatus().getTodayReservedVehicles());
fp.put("notCheckedIn", vo.getVehicleStatus().getNotCheckedIn()); fp.put("notCheckedIn", vo.getVehicleStatus().getNotCheckedIn());
fp.put("waiting", vo.getVehicleStatus().getWaiting()); fp.put("waiting", vo.getVehicleStatus().getWaiting());
fp.put("callStatusTotal", vo.getVehicleStatus().getCallStatusTotal());
fp.put("operatingVehicles", vo.getVehicleStatus().getOperatingVehicles()); fp.put("operatingVehicles", vo.getVehicleStatus().getOperatingVehicles());
fp.put("completedVehicles", vo.getVehicleStatus().getCompletedVehicles()); fp.put("completedVehicles", vo.getVehicleStatus().getCompletedVehicles());
} }
if (vo.getMonthlyTaskStatistics() != null) { if (vo.getMonthlyTaskStatistics() != null) {
fp.put("taskTotal", vo.getMonthlyTaskStatistics().getTaskTotal()); fp.put("taskTotal", vo.getMonthlyTaskStatistics().getTaskTotal());
fp.put("avgTaskDuration", vo.getMonthlyTaskStatistics().getAvgTaskDuration()); fp.put("avgTaskDuration", vo.getMonthlyTaskStatistics().getAvgTaskDuration());
@@ -138,7 +160,34 @@ public class BiPlatformRealDataService {
} }
fp.put("taskLoadStatistics", loads); fp.put("taskLoadStatistics", loads);
} }
if (vo.getAvgOperationDurationTrend() != null) {
List<Map<String, Object>> trend = new ArrayList<>();
for (AvgOperationDurationTrendItemVO item : vo.getAvgOperationDurationTrend()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("month", item.getMonth());
m.put("yardDuration", item.getYardDuration());
m.put("platformDuration", item.getPlatformDuration());
trend.add(m);
}
fp.put("avgOperationDurationTrend", trend);
}
fp.put("taskOvertimeRate", vo.getTaskOvertimeRate());
if (vo.getOvertimeRecords() != null) {
List<Map<String, Object>> recs = new ArrayList<>();
for (OvertimeRecordItemVO r : vo.getOvertimeRecords()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("windowName", r.getWindowName());
m.put("carNo", r.getCarNo());
m.put("overtimeMinutes", r.getOvertimeMinutes());
recs.add(m);
}
fp.put("overtimeRecords", recs);
}
if (vo.getTaskTypeStatistics() != null) { if (vo.getTaskTypeStatistics() != null) {
Map<String, Object> m = new LinkedHashMap<>(); Map<String, Object> m = new LinkedHashMap<>();
m.put("totalTasks", vo.getTaskTypeStatistics().getTotalTasks()); m.put("totalTasks", vo.getTaskTypeStatistics().getTotalTasks());
if (vo.getTaskTypeStatistics().getTaskTypes() != null) { if (vo.getTaskTypeStatistics().getTaskTypes() != null) {
@@ -153,6 +202,12 @@ public class BiPlatformRealDataService {
} }
fp.put("taskTypeStatistics", m); fp.put("taskTypeStatistics", m);
} }
// 月台告警统计:按业务要求恒为 0,纳入指纹以确保旧快照(可能含非0写死值)能被覆盖更新为 0
if (vo.getPlatformAlarmStatistics() != null) {
fp.put("todayAlarmTotal", vo.getPlatformAlarmStatistics().getTodayAlarmTotal());
fp.put("handled", vo.getPlatformAlarmStatistics().getHandled());
fp.put("unhandled", vo.getPlatformAlarmStatistics().getUnhandled());
}
return objectMapper.writeValueAsString(fp); return objectMapper.writeValueAsString(fp);
} }
@@ -220,6 +275,8 @@ public class BiPlatformRealDataService {
if (po.getTodayReservedVehicles() == null) po.setTodayReservedVehicles(0L); if (po.getTodayReservedVehicles() == null) po.setTodayReservedVehicles(0L);
if (po.getNotCheckedIn() == null) po.setNotCheckedIn(0L); if (po.getNotCheckedIn() == null) po.setNotCheckedIn(0L);
if (po.getWaiting() == null) po.setWaiting(0L); if (po.getWaiting() == null) po.setWaiting(0L);
if (po.getCallStatusTotal() == null) po.setCallStatusTotal(0L);
if (po.getTodayExited() == null) po.setTodayExited(0L);
if (po.getOperatingVehicles() == null) po.setOperatingVehicles(0L); if (po.getOperatingVehicles() == null) po.setOperatingVehicles(0L);
if (po.getCompletedVehicles() == null) po.setCompletedVehicles(0L); if (po.getCompletedVehicles() == null) po.setCompletedVehicles(0L);
return po; return po;
@@ -229,13 +286,19 @@ public class BiPlatformRealDataService {
po.setTodayReservedVehicles(0L); po.setTodayReservedVehicles(0L);
po.setNotCheckedIn(0L); po.setNotCheckedIn(0L);
po.setWaiting(0L); po.setWaiting(0L);
po.setCallStatusTotal(0L);
po.setTodayExited(0L);
po.setOperatingVehicles(0L); po.setOperatingVehicles(0L);
po.setCompletedVehicles(0L); po.setCompletedVehicles(0L);
return po; return po;
} }
} }
public long countMonthTasks() { public long countMonthTasks() {
try { try {
Long n = biPlatformRealDataMapper.countMonthTasks(); Long n = biPlatformRealDataMapper.countMonthTasks();
return n == null ? 0L : n; return n == null ? 0L : n;
@@ -247,19 +310,31 @@ public class BiPlatformRealDataService {
/** /**
* 本月任务平均时长 / 完成率统计: * 本月任务平均时长 / 完成率统计:
* - avgDurationHours:累计(EXIT_TIME - ENTRY_TIME)小时数 / 参与数;任一为空的行不参与 * - totalWorkHours:本月任务作业时长合计(小时);未进入月台计 0,已进入未离开按 SYSDATE - IN_WINDOWS_TIME
* - avgDurationHours:任务平均作业时长 = totalWorkHours ÷ 本月任务数(totalCount),保留两位小数
* - taskCompletionRate:已完成(EXIT_TIME 不为空 且 预约日期在本月)/ 本月预约总数量 ×100 * - taskCompletionRate:已完成(EXIT_TIME 不为空 且 预约日期在本月)/ 本月预约总数量 ×100
* 任一查询失败均返回 null,由调用方决定是否覆盖模板值。 * 任一查询失败均返回 null,由调用方决定是否覆盖模板值。
*/ */
public RealMonthlyTaskDurationPO queryMonthTaskStats() { public RealMonthlyTaskDurationPO queryMonthTaskStats() {
try { try {
return biPlatformRealDataMapper.queryMonthTaskStats(); RealMonthlyTaskDurationPO po = biPlatformRealDataMapper.queryMonthTaskStats();
if (po == null) {
return null;
}
// 任务平均作业时长 = 本月作业时长合计 ÷ 本月任务数(分母为 0 时返回 0)
long totalCount = po.getTotalCount() == null ? 0L : po.getTotalCount();
double totalHours = po.getTotalWorkHours() == null ? 0d : po.getTotalWorkHours();
po.setAvgDurationHours(totalCount > 0
? Math.round((totalHours / (double) totalCount) * 100.0) / 100.0
: 0d);
return po;
} catch (Exception e) { } catch (Exception e) {
log.error("【YMS】统计本月任务平均时长/完成率失败: {}", e.getMessage(), e); log.error("【YMS】统计本月任务平均时长/完成率失败: {}", e.getMessage(), e);
return null; return null;
} }
} }
public List<RealTaskLoadPO> queryTaskLoadToday() { public List<RealTaskLoadPO> queryTaskLoadToday() {
try { try {
return biPlatformRealDataMapper.queryTaskLoadToday(); return biPlatformRealDataMapper.queryTaskLoadToday();
@@ -270,39 +345,32 @@ public class BiPlatformRealDataService {
} }
/** /**
* 任务类型统计:当月 OMS 运输订单按业务类型分组(1-跨境运输,2-国内运输)。 * 任务类型统计:本月(YMS QMS_MAIN_BUSINESS)预约车辆按业务类型 BUSINESS_NAME 分组;
* 返回 {totalTasks: 当月跨境+国内运输总单量, taskTypes: [{type, count}]} * 返回 {totalTasks: 当月总预约车辆数, taskTypes: [{type: 业务类型名称, count: 该类型预约数}]}
* 查询失败时返回空结构,不影响写死字段 * 按真实 BUSINESS_NAME 分组输出,不固定兜底类别;当月无数据或查询失败时返回空列表
*/
/**
* 任务类型统计:固定输出「跨境运输」「国内运输」两类,count 取当月 OMS 业务单真实单量;
* 当月无数据或查询失败时仍返回两类、count=0(不使用写死模板兜底)。
*/ */
public TaskTypeStatisticsVO queryMonthTransportOrderByType() { public TaskTypeStatisticsVO queryMonthTransportOrderByType() {
int crossBorder = 0; // 跨境运输 BUSINESS_CATEGORY=1 List<TaskTypeCountItemVO> items = new ArrayList<>();
int domestic = 0; // 国内运输 BUSINESS_CATEGORY=2 int total = 0;
try { try {
List<RealTaskTypeStatPO> rows = biPlatformRealDataMapper.queryMonthTransportOrderByType(); List<RealTaskTypeStatPO> rows = biPlatformRealDataMapper.queryMonthTransportOrderByType();
if (rows != null) { if (rows != null) {
for (RealTaskTypeStatPO r : rows) { for (RealTaskTypeStatPO r : rows) {
if (r == null || r.getBusinessCategory() == null || r.getCount() == null) { if (r == null || r.getBusinessName() == null || r.getCount() == null) {
continue; continue;
} }
if (r.getBusinessCategory() == 1) { int c = r.getCount().intValue();
crossBorder = r.getCount().intValue(); items.add(TaskTypeCountItemVO.builder().type(r.getBusinessName()).count(c).build());
} else if (r.getBusinessCategory() == 2) { total += c;
domestic = r.getCount().intValue();
}
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.error("OMS】统计当月运输订单业务类型失败: {}", e.getMessage(), e); log.error("YMS】统计当月预约业务类型失败: {}", e.getMessage(), e);
// 失败时不回退写死模板,保持两类 count=0 // 查询失败返回空列表(前端会显示空)
items = new ArrayList<>();
total = 0;
} }
List<TaskTypeCountItemVO> items = new ArrayList<>(); return TaskTypeStatisticsVO.builder().totalTasks(total).taskTypes(items).build();
items.add(TaskTypeCountItemVO.builder().type("跨境运输").count(crossBorder).build());
items.add(TaskTypeCountItemVO.builder().type("国内运输").count(domestic).build());
return TaskTypeStatisticsVO.builder().totalTasks(crossBorder + domestic).taskTypes(items).build();
} }
/** /**
@@ -393,31 +461,59 @@ public class BiPlatformRealDataService {
template.getPlatformOverview().setCurrentOccupancyRate(rate); template.getPlatformOverview().setCurrentOccupancyRate(rate);
template.getPlatformOverview().setTodayTasks(todayTasks); template.getPlatformOverview().setTodayTasks(todayTasks);
// 作业准时率(实际进入时间 < 预约日期+预约结束时间 的车辆 / 今日预约总数 × 100% 取整 // 作业准时率:任务超时率 + 月台作业准时率 = 100%(需求约定
if (template.getEfficiencyStatistics() != null) { // 月台作业准时率 = 100 - 月台作业超时率
template.getEfficiencyStatistics().setPlatformOnTimeRate(calcTodayOnTimeRate()); double platformOvertime = calcTodayTaskOvertimeRate();
} template.getEfficiencyStatistics().setPlatformOnTimeRate(
(int) Math.round(100.0 - platformOvertime));
// 园区作业准时率 = 100 - 园区作业超时率
double yardOvertime = calcTodayYardOvertimeRate();
template.getEfficiencyStatistics().setYardOnTimeRate(
(int) Math.round(100.0 - yardOvertime));
} }
// 平均作业时长(本日):月台作业平均时长 / 园区平均时长(真实数据,仅统计两端时间都非空的今日记录)
if (template.getEfficiencyStatistics() != null) {
RealTodayAvgDurationPO avg = queryTodayAvgDuration();
template.getEfficiencyStatistics().setAvgPlatformDuration(avg.getAvgPlatformDurationHours());
template.getEfficiencyStatistics().setAvgYardDuration(avg.getAvgYardDurationHours());
}
// 作业车辆状态统计:真实数据源,无条件覆盖(含等待作业 waiting = 今日预约且 已预约/已签到/已叫号) // 作业车辆状态统计:真实数据源,无条件覆盖(含等待作业 waiting = 今日预约且 已预约/已签到/已叫号)
if (template.getVehicleStatus() != null) { if (template.getVehicleStatus() != null) {
RealVehicleStatusPO vs = queryVehicleStatusToday(); RealVehicleStatusPO vs = queryVehicleStatusToday();
template.getVehicleStatus().setTodayReservedVehicles(vs.getTodayReservedVehicles().intValue()); template.getVehicleStatus().setTodayReservedVehicles(vs.getTodayReservedVehicles().intValue());
template.getVehicleStatus().setNotCheckedIn(vs.getNotCheckedIn().intValue()); template.getVehicleStatus().setNotCheckedIn(vs.getNotCheckedIn().intValue());
template.getVehicleStatus().setWaiting(vs.getWaiting().intValue()); template.getVehicleStatus().setWaiting(vs.getWaiting().intValue());
// 叫号状态合计:已签到 + 已叫号 + 已过号(一个字段表示三个状态的总数量)
template.getVehicleStatus().setCallStatusTotal(vs.getCallStatusTotal().intValue());
template.getVehicleStatus().setOperatingVehicles(vs.getOperatingVehicles().intValue()); template.getVehicleStatus().setOperatingVehicles(vs.getOperatingVehicles().intValue());
template.getVehicleStatus().setCompletedVehicles(vs.getCompletedVehicles().intValue()); template.getVehicleStatus().setCompletedVehicles(vs.getCompletedVehicles().intValue());
// 月台周转率 = 今日已出场记录数 ÷ 今日全部记录数 × 100%(保留两位小数)
if (template.getPlatformOverview() != null) {
long todayTotal = vs.getTodayReservedVehicles() == null ? 0L : vs.getTodayReservedVehicles();
long todayExited = vs.getTodayExited() == null ? 0L : vs.getTodayExited();
double turnover = todayTotal > 0
? Math.round(((double) todayExited / (double) todayTotal) * 100.0 * 100.0) / 100.0
: 0d;
template.getPlatformOverview().setPlatformTurnoverRate(turnover);
}
} }
// 本月任务统计:taskTotal / avgTaskDuration / taskCompletionRate 均有真实数据源,无条件覆盖;YoY 保留模板写死值 // 本月任务统计:taskTotal / avgTaskDuration / taskCompletionRate 均有真实数据源,无条件覆盖;YoY 保留模板写死值
if (template.getMonthlyTaskStatistics() != null) { if (template.getMonthlyTaskStatistics() != null) {
template.getMonthlyTaskStatistics().setTaskTotal((int) countMonthTasks()); template.getMonthlyTaskStatistics().setTaskTotal((int) countMonthTasks());
RealMonthlyTaskDurationPO monthStats = queryMonthTaskStats(); RealMonthlyTaskDurationPO monthStats = queryMonthTaskStats();
if (monthStats != null) { if (monthStats != null) {
if (monthStats.getAvgDurationHours() != null) { if (monthStats.getAvgDurationHours() != null) {
// 保留两位小数 // 任务平均作业时长 = 本月任务作业时长合计 ÷ 本月任务数(已在 Service 层算好),保留两位小数
double v = monthStats.getAvgDurationHours(); template.getMonthlyTaskStatistics().setAvgTaskDuration(monthStats.getAvgDurationHours());
template.getMonthlyTaskStatistics().setAvgTaskDuration(Math.round(v * 100.0) / 100.0);
} }
Long total = monthStats.getTotalCount(); Long total = monthStats.getTotalCount();
Long completed = monthStats.getCompletedCount(); Long completed = monthStats.getCompletedCount();
@@ -430,41 +526,208 @@ public class BiPlatformRealDataService {
} }
} }
// 任务负荷:有真实数据源;真实为空则清空不用模板模拟,有数据则按真实小时覆盖 // 平均装卸时长按业务要求恒为 0,不用真实值覆盖
// 小时趋势(今日,每 2 小时分组):有真实数据源则按真实分桶重建,无数据则清空
List<RealTaskLoadPO> loads = queryTaskLoadToday(); List<RealTaskLoadPO> loads = queryTaskLoadToday();
if (loads.isEmpty()) { if (loads.isEmpty()) {
template.setTaskLoadStatistics(new ArrayList<>()); template.setTaskLoadStatistics(new ArrayList<>());
} else if (template.getTaskLoadStatistics() != null) { } else {
Map<Integer, Long> hourMap = new HashMap<>(); Map<Integer, Long> bucketMap = new HashMap<>();
for (RealTaskLoadPO l : loads) { for (RealTaskLoadPO l : loads) {
hourMap.put(l.getHourOfDay(), l.getTaskCount()); if (l != null && l.getHourOfDay() != null) {
} bucketMap.put(l.getHourOfDay(), l.getTaskCount() == null ? 0L : l.getTaskCount());
for (TaskLoadItemVO item : template.getTaskLoadStatistics()) {
Integer hour = parseHourFromLabel(item.getTime());
if (hour != null && hourMap.containsKey(hour)) {
item.setTaskCount(hourMap.get(hour).intValue());
} }
} }
// 固定输出 0,2,4,...,22 共 12 个桶,无数据桶补 0,保证前端坐标轴连续
List<TaskLoadItemVO> items = new ArrayList<>();
for (int h = 0; h < 24; h += 2) {
Long cnt = bucketMap.get(h);
items.add(TaskLoadItemVO.builder()
.time(String.format("%02d:00", h))
.taskCount(cnt == null ? 0 : cnt.intValue())
.build());
}
template.setTaskLoadStatistics(items);
} }
// 任务类型统计:当月 OMS 业务单真实数据(固定跨境运输/国内运输两类,不使用写死模板兜底)
// 任务类型统计:本月 YMS QMS_MAIN_BUSINESS 按业务类型 BUSINESS_NAME 分组(真实业务数据)
template.setTaskTypeStatistics(queryMonthTransportOrderByType()); template.setTaskTypeStatistics(queryMonthTransportOrderByType());
// 平均作业时长趋势:近半年按月统计(月台作业平均时长 / 园区作业平均时长)
template.setAvgOperationDurationTrend(queryAvgOperationDurationTrend());
// 任务超时率 + 月台作业超时记录列表(依赖 YMS QMS_STATIC_FIELD 参考用时配置)
template.setTaskOvertimeRate(calcTodayTaskOvertimeRate());
template.setOvertimeRecords(queryTodayOvertimeRecords());
return template; return template;
} }
private static Integer parseHourFromLabel(String label) {
if (label == null) return null;
/**
* 今日平均作业时长(效率统计-本日),保留两位小数,单位:小时。
* - avgPlatformDurationHours:月台作业平均时长;仅统计同时有进入与离开月台时间的今日记录
* - avgYardDurationHours 园区平均时长;仅统计同时有实际进入与实际出场时间的今日记录
* 查询失败或无有效记录时返回 0。
*/
public RealTodayAvgDurationPO queryTodayAvgDuration() {
RealTodayAvgDurationPO po = new RealTodayAvgDurationPO();
po.setAvgPlatformDurationHours(0d);
po.setAvgYardDurationHours(0d);
try { try {
String s = label.replace("", "").trim(); RealTodayAvgDurationPO row = biPlatformRealDataMapper.queryTodayAvgDuration();
return Integer.parseInt(s); if (row != null) {
if (row.getAvgPlatformDurationHours() != null) {
po.setAvgPlatformDurationHours(
Math.round(row.getAvgPlatformDurationHours() * 100.0) / 100.0);
}
if (row.getAvgYardDurationHours() != null) {
po.setAvgYardDurationHours(
Math.round(row.getAvgYardDurationHours() * 100.0) / 100.0);
}
}
} catch (Exception e) { } catch (Exception e) {
return null; log.error("【YMS】统计今日平均作业时长失败: {}", e.getMessage(), e);
}
return po;
}
/**
* 今日任务超时率(%)= 超时记录数 ÷ 有效记录总数 × 100%,保留两位小数。
* 有效记录:月台已配置参考用时(platform_work_ref_minutes),且该记录同时有进入/离开月台时间。
* 无有效记录时返回 0。
*/
public double calcTodayTaskOvertimeRate() {
try {
RealOvertimeStatPO po = biPlatformRealDataMapper
.queryTodayOvertimeStat(BiStaticFieldService.CODE_PLATFORM_WORK_REF_MINUTES);
return calcRateFromStat(po);
} catch (Exception e) {
log.error("【YMS】统计今日任务超时率失败: {}", e.getMessage(), e);
return 0d;
} }
} }
/**
* 今日园区作业超时率(%)= 园区作业时长 > 园区参考用时的记录数 ÷ 有效记录总数 × 100%。
* 有效记录:能取到园区参考用时(yard_work_ref_minutes),且记录同时有实际进入/实际出场时间。
* 无有效记录时返回 0。
*/
public double calcTodayYardOvertimeRate() {
try {
RealOvertimeStatPO po = biPlatformRealDataMapper
.queryTodayYardOvertimeStat(BiStaticFieldService.CODE_YARD_WORK_REF_MINUTES);
return calcRateFromStat(po);
} catch (Exception e) {
log.error("【YMS】统计今日园区作业超时率失败: {}", e.getMessage(), e);
return 0d;
}
}
/** 由超时统计 PO 计算百分比,保留两位小数;无有效记录返回 0 */
private static double calcRateFromStat(RealOvertimeStatPO po) {
if (po == null) {
return 0d;
}
long total = po.getTotalCount() == null ? 0L : po.getTotalCount();
long overtime = po.getOvertimeCount() == null ? 0L : po.getOvertimeCount();
if (total <= 0) {
return 0d;
}
return Math.round(((double) overtime / (double) total) * 100.0 * 100.0) / 100.0;
}
/**
* 今日月台作业超时记录列表(月台名称 / 车牌号码 / 超时时长分钟)。
* 查询失败返回空列表。
*/
public List<OvertimeRecordItemVO> queryTodayOvertimeRecords() {
List<OvertimeRecordItemVO> result = new ArrayList<>();
try {
List<RealOvertimeRecordPO> rows = biPlatformRealDataMapper
.queryTodayOvertimeList(BiStaticFieldService.CODE_PLATFORM_WORK_REF_MINUTES);
if (rows == null) {
return result;
}
for (RealOvertimeRecordPO r : rows) {
if (r == null) {
continue;
}
result.add(OvertimeRecordItemVO.builder()
.windowName(r.getWindowName())
.carNo(r.getCarNo())
.overtimeMinutes(r.getOvertimeMinutes())
.refMinutes(r.getRefMinutes())
.platformMinutes(r.getPlatformMinutes())
.build());
}
} catch (Exception e) {
log.error("【YMS】查询今日月台作业超时记录失败: {}", e.getMessage(), e);
}
return result;
}
/**
* 近半年「平均作业时长」趋势(近 6 个月,按月正序,含无数据月份补 0):
* - platformDuration:月台作业平均时长 = SUM(月台作业时长) ÷ 该月任务数
* - yardDuration 园区作业平均时长 = SUM(园区作业时长) ÷ 该月任务数
* 均保留两位小数,单位:小时。
*/
public List<AvgOperationDurationTrendItemVO> queryAvgOperationDurationTrend() {
List<AvgOperationDurationTrendItemVO> result = new ArrayList<>();
Map<String, RealMonthlyAvgDurationPO> map = new HashMap<>();
try {
List<RealMonthlyAvgDurationPO> rows = biPlatformRealDataMapper.queryMonthlyAvgDuration();
if (rows != null) {
for (RealMonthlyAvgDurationPO r : rows) {
if (r != null && r.getMonthOfYear() != null) {
map.put(r.getMonthOfYear().trim(), r);
}
}
}
} catch (Exception e) {
log.error("【YMS】统计近半年平均作业时长失败: {}", e.getMessage(), e);
return result;
}
// 生成近 6 个月的标签(含当前月),保证无数据月份也返回一条(值为 0)
java.time.LocalDate now = java.time.LocalDate.now();
java.time.format.DateTimeFormatter keyFmt = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM");
for (int i = 5; i >= 0; i--) {
java.time.LocalDate m = now.minusMonths(i);
String key = m.format(keyFmt);
RealMonthlyAvgDurationPO po = map.get(key);
AvgOperationDurationTrendItemVO item = new AvgOperationDurationTrendItemVO();
// 展示用月份标签,如 "9月"
item.setMonth(m.getMonthValue() + "");
if (po != null) {
long cnt = po.getTaskCount() == null ? 0L : po.getTaskCount();
double platformHours = po.getPlatformWorkHours() == null ? 0d : po.getPlatformWorkHours();
double yardHours = po.getYardWorkHours() == null ? 0d : po.getYardWorkHours();
item.setPlatformDuration(cnt > 0
? Math.round((platformHours / (double) cnt) * 100.0) / 100.0 : 0d);
item.setYardDuration(cnt > 0
? Math.round((yardHours / (double) cnt) * 100.0) / 100.0 : 0d);
} else {
item.setPlatformDuration(0d);
item.setYardDuration(0d);
}
result.add(item);
}
return result;
}
/** /**
* FLOW_STATUS 数字转中文:1已预约 2已签到 3已叫号 4已过号 5已进场 6已称重 7已装车 8二次称重 9已结算 10已还卡 11已出场 12已取消 * FLOW_STATUS 数字转中文:1已预约 2已签到 3已叫号 4已过号 5已进场 6已称重 7已装车 8二次称重 9已结算 10已还卡 11已出场 12已取消
*/ */
private static String translateFlowStatus(String flowStatus) { private static String translateFlowStatus(String flowStatus) {
if (flowStatus == null) return null; if (flowStatus == null) return null;
@@ -43,9 +43,9 @@ public final class BiReportSnapshotMapperSupport {
return ComprehensiveSituationVO.builder() return ComprehensiveSituationVO.builder()
.valueAddedServiceIncome(0L) .valueAddedServiceIncome(0L)
.inventoryTurnover(0) .inventoryTurnover(0)
.orderCount(0) .orderCount(0L)
.vehicleUtilizationRate(0d) .vehicleUtilizationRate(0d)
.fuelConsumptionPerHundredKm(0) .fuelConsumptionPerHundredKm(0d)
.incomeTrend(Collections.emptyList()) .incomeTrend(Collections.emptyList())
.build(); .build();
} }
@@ -85,9 +85,12 @@ public final class BiReportSnapshotMapperSupport {
.entryOvertimeRate(0).overtimeCount(0).platformOnTimeRate(0) .entryOvertimeRate(0).overtimeCount(0).platformOnTimeRate(0)
.avgPlatformDuration(0d).yardOnTimeRate(0).avgYardDuration(0d).build()) .avgPlatformDuration(0d).yardOnTimeRate(0).avgYardDuration(0d).build())
.platformOverview(PlatformOverviewVO.builder() .platformOverview(PlatformOverviewVO.builder()
.totalPlatforms(0).currentOccupancyRate(0d).operatingPlatforms(0).todayTasks(0).build()) .totalPlatforms(0).currentOccupancyRate(0d).operatingPlatforms(0).todayTasks(0)
.platformTurnoverRate(0d).build())
.vehicleStatus(VehicleStatusVO.builder() .vehicleStatus(VehicleStatusVO.builder()
.todayReservedVehicles(0).notCheckedIn(0).waiting(0).operatingVehicles(0).completedVehicles(0).build()) .todayReservedVehicles(0).notCheckedIn(0).waiting(0)
.callStatusTotal(0)
.operatingVehicles(0).completedVehicles(0).build())
.avgOperationDurationTrend(Collections.emptyList()) .avgOperationDurationTrend(Collections.emptyList())
.monthlyTaskStatistics(MonthlyTaskStatisticsVO.builder() .monthlyTaskStatistics(MonthlyTaskStatisticsVO.builder()
.taskTotal(0).taskTotalYoY(0).avgTaskDuration(0d).avgDurationYoY(0) .taskTotal(0).taskTotalYoY(0).avgTaskDuration(0d).avgDurationYoY(0)
@@ -99,6 +102,8 @@ public final class BiReportSnapshotMapperSupport {
.platformAlarmStatistics(PlatformAlarmStatisticsVO.builder() .platformAlarmStatistics(PlatformAlarmStatisticsVO.builder()
.todayAlarmTotal(0).handled(0).unhandled(0).build()) .todayAlarmTotal(0).handled(0).unhandled(0).build())
.platformAlarmDistribution(Collections.emptyList()) .platformAlarmDistribution(Collections.emptyList())
.taskOvertimeRate(0d)
.overtimeRecords(Collections.emptyList())
.build(); .build();
} }
@@ -9,8 +9,13 @@ public interface BiSnapshotWriteMapper {
int insertComprehensiveSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark); int insertComprehensiveSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark);
int updateLatestComprehensiveSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark);
int insertWarehouseTransportSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark); int insertWarehouseTransportSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark);
int updateLatestWarehouseTransportSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark);
int insertPlatformSurveillanceSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark); int insertPlatformSurveillanceSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark);
int updateLatestPlatformSurveillanceSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark); int updateLatestPlatformSurveillanceSnapshot(@Param("payloadJson") String payloadJson, @Param("remark") String remark);
@@ -19,6 +19,8 @@ import com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformVehiclePO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformInfoPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformInfoPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealVehicleStatusPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealVehicleStatusPO;
import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskLoadPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskLoadPO;
import com.mhd.bi.domain.biWarehouseTransport.repository.po.RealWarehouseOrderStatPO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -45,6 +47,8 @@ public class BiReportSnapshotSyncService {
private final BiSnapshotWriteMapper biSnapshotWriteMapper; private final BiSnapshotWriteMapper biSnapshotWriteMapper;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final BiPlatformRealDataService biPlatformRealDataService; private final BiPlatformRealDataService biPlatformRealDataService;
private final com.mhd.bi.domain.biWarehouseTransport.service.BiWarehouseTransportRealDataService biWarehouseTransportRealDataService;
/** /**
* 依次同步各快照表;单表失败不影响其它表,只记日志。 * 依次同步各快照表;单表失败不影响其它表,只记日志。
@@ -143,11 +147,33 @@ public class BiReportSnapshotSyncService {
return BiSnapshotWeeklyRandomDataBuilder.comprehensive(new Random()); return BiSnapshotWeeklyRandomDataBuilder.comprehensive(new Random());
} }
/** TODO 业务落地:改为查询真实数据。临时实现见 {@link BiSnapshotWeeklyRandomDataBuilder#warehouseTransport(Random)}。 */ /**
* 仓储运输:订单量 / 今日订单 / 今日运输车次 / 客户贡献排名 用真实数据覆盖;
* 其余(趋势类、周转率、货量等)仍为写死模板。
*/
protected WarehouseTransportSituationVO buildWarehouseTransportSnapshot() { protected WarehouseTransportSituationVO buildWarehouseTransportSnapshot() {
return BiSnapshotWeeklyRandomDataBuilder.warehouseTransport(new Random()); WarehouseTransportSituationVO template =
BiSnapshotWeeklyRandomDataBuilder.warehouseTransport(new Random(20260825L));
// ====== 真实数据:仓储订单量(WMS 入库单 + 出库单,本年度/本季度/本月度,全部组织) ======
RealWarehouseOrderStatPO orderStat = biWarehouseTransportRealDataService.queryWarehouseOrderStat();
template.setCurrentYearOrders(orderStat.getCurrentYearOrders());
template.setCurrentQuarterOrders(orderStat.getCurrentQuarterOrders().intValue());
template.setCurrentMonthOrders(orderStat.getCurrentMonthOrders().intValue());
// ====== 真实数据:今日订单数量(OMS 运输业务单) ======
template.setOrderCount((int) biWarehouseTransportRealDataService.countTodayTransportOrders());
// ====== 真实数据:今日运输车次(TMS 运输单) ======
template.setTransportTrips((int) biWarehouseTransportRealDataService.countTodayTransportTrips());
// ====== 真实数据:客户贡献排名(OMS 近半年前 5 名货主) ======
template.setCustomerContributionRank(biWarehouseTransportRealDataService.queryCustomerContributionRank());
return template;
} }
/** TODO 业务落地:部分字段改为真实数据,其余仍写死。 */ /** TODO 业务落地:部分字段改为真实数据,其余仍写死。 */
protected PlatformSurveillanceVO buildPlatformSurveillanceSnapshot() { protected PlatformSurveillanceVO buildPlatformSurveillanceSnapshot() {
// 固定种子,保证写死字段每次生成值一致(真实字段随后实时覆盖) // 固定种子,保证写死字段每次生成值一致(真实字段随后实时覆盖)
@@ -194,22 +220,48 @@ public class BiReportSnapshotSyncService {
template.getPlatformOverview().setCurrentOccupancyRate(rate); template.getPlatformOverview().setCurrentOccupancyRate(rate);
template.getPlatformOverview().setTodayTasks(todayTasks); template.getPlatformOverview().setTodayTasks(todayTasks);
// 作业准时率 = 实际进入时间 < 预约日期+预约结束时间 的车辆 / 今日预约总数 × 100%(取整) // 作业准时率:任务超时率 + 月台作业准时率 = 100%;园区作业准时率 = 100 - 园区超时率
if (template.getEfficiencyStatistics() != null) { template.getEfficiencyStatistics().setPlatformOnTimeRate(
template.getEfficiencyStatistics().setPlatformOnTimeRate(biPlatformRealDataService.calcTodayOnTimeRate()); (int) Math.round(100.0 - biPlatformRealDataService.calcTodayTaskOvertimeRate()));
} template.getEfficiencyStatistics().setYardOnTimeRate(
(int) Math.round(100.0 - biPlatformRealDataService.calcTodayYardOvertimeRate()));
} }
// ====== 真实数据:平均作业时长(本日)月台作业平均时长 / 园区平均时长 ======
if (template.getEfficiencyStatistics() != null) {
com.mhd.bi.domain.biPlatformRealData.repository.po.RealTodayAvgDurationPO avg =
biPlatformRealDataService.queryTodayAvgDuration();
template.getEfficiencyStatistics().setAvgPlatformDuration(avg.getAvgPlatformDurationHours());
template.getEfficiencyStatistics().setAvgYardDuration(avg.getAvgYardDurationHours());
}
// ====== 真实数据:作业车辆状态统计 ====== // ====== 真实数据:作业车辆状态统计 ======
RealVehicleStatusPO vs = biPlatformRealDataService.queryVehicleStatusToday(); RealVehicleStatusPO vs = biPlatformRealDataService.queryVehicleStatusToday();
if (template.getVehicleStatus() != null) { if (template.getVehicleStatus() != null) {
template.getVehicleStatus().setTodayReservedVehicles(vs.getTodayReservedVehicles().intValue()); template.getVehicleStatus().setTodayReservedVehicles(vs.getTodayReservedVehicles().intValue());
template.getVehicleStatus().setNotCheckedIn(vs.getNotCheckedIn().intValue()); template.getVehicleStatus().setNotCheckedIn(vs.getNotCheckedIn().intValue());
template.getVehicleStatus().setWaiting(vs.getWaiting().intValue()); template.getVehicleStatus().setWaiting(vs.getWaiting().intValue());
// 叫号状态合计:已签到 + 已叫号 + 已过号(一个字段表示三个状态的总数量)
template.getVehicleStatus().setCallStatusTotal(vs.getCallStatusTotal() == null ? 0 : vs.getCallStatusTotal().intValue());
template.getVehicleStatus().setOperatingVehicles(vs.getOperatingVehicles().intValue()); template.getVehicleStatus().setOperatingVehicles(vs.getOperatingVehicles().intValue());
template.getVehicleStatus().setCompletedVehicles(vs.getCompletedVehicles().intValue()); template.getVehicleStatus().setCompletedVehicles(vs.getCompletedVehicles().intValue());
// 月台周转率 = 今日已出场记录数 ÷ 今日全部记录数 × 100%(保留两位小数)
if (template.getPlatformOverview() != null) {
long todayTotal = vs.getTodayReservedVehicles() == null ? 0L : vs.getTodayReservedVehicles();
long todayExited = vs.getTodayExited() == null ? 0L : vs.getTodayExited();
double turnover = todayTotal > 0
? Math.round(((double) todayExited / (double) todayTotal) * 100.0 * 100.0) / 100.0
: 0d;
template.getPlatformOverview().setPlatformTurnoverRate(turnover);
}
} }
// ====== 真实数据:本月任务统计 taskTotal / avgTaskDuration / taskCompletionRate ====== // ====== 真实数据:本月任务统计 taskTotal / avgTaskDuration / taskCompletionRate ======
long monthTasks = biPlatformRealDataService.countMonthTasks(); long monthTasks = biPlatformRealDataService.countMonthTasks();
if (template.getMonthlyTaskStatistics() != null) { if (template.getMonthlyTaskStatistics() != null) {
@@ -218,10 +270,11 @@ public class BiReportSnapshotSyncService {
biPlatformRealDataService.queryMonthTaskStats(); biPlatformRealDataService.queryMonthTaskStats();
if (monthStats != null) { if (monthStats != null) {
if (monthStats.getAvgDurationHours() != null) { if (monthStats.getAvgDurationHours() != null) {
double v = monthStats.getAvgDurationHours(); // 任务平均作业时长 = 本月作业时长合计 ÷ 本月任务数(Service 层已算好)
template.getMonthlyTaskStatistics().setAvgTaskDuration(Math.round(v * 100.0) / 100.0); template.getMonthlyTaskStatistics().setAvgTaskDuration(monthStats.getAvgDurationHours());
} }
Long total = monthStats.getTotalCount(); Long total = monthStats.getTotalCount();
Long completed = monthStats.getCompletedCount(); Long completed = monthStats.getCompletedCount();
if (total != null && total > 0 && completed != null) { if (total != null && total > 0 && completed != null) {
int rate = (int) Math.round(((double) completed.longValue() / total.longValue()) * 100.0); int rate = (int) Math.round(((double) completed.longValue() / total.longValue()) * 100.0);
@@ -232,42 +285,49 @@ public class BiReportSnapshotSyncService {
} }
} }
// ====== 真实数据:任务负荷(今日 24 小时) ====== // ====== 真实数据:小时趋势(今日,每 2 小时分组 ======
List<RealTaskLoadPO> loads = biPlatformRealDataService.queryTaskLoadToday(); List<RealTaskLoadPO> loads = biPlatformRealDataService.queryTaskLoadToday();
if (!loads.isEmpty() && template.getTaskLoadStatistics() != null) { if (loads.isEmpty()) {
Map<Integer, Long> hourMap = new HashMap<>(); template.setTaskLoadStatistics(new ArrayList<>());
} else {
Map<Integer, Long> bucketMap = new HashMap<>();
for (RealTaskLoadPO l : loads) { for (RealTaskLoadPO l : loads) {
hourMap.put(l.getHourOfDay(), l.getTaskCount()); if (l != null && l.getHourOfDay() != null) {
} bucketMap.put(l.getHourOfDay(), l.getTaskCount() == null ? 0L : l.getTaskCount());
for (com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskLoadItemVO item : template.getTaskLoadStatistics()) {
Integer hour = parseHourFromLabel(item.getTime());
if (hour != null) {
Long n = hourMap.get(hour);
if (n != null) item.setTaskCount(n.intValue());
} }
} }
// 固定输出 0,2,4,...,22 共 12 个桶,无数据桶补 0
List<com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskLoadItemVO> items = new ArrayList<>();
for (int h = 0; h < 24; h += 2) {
Long cnt = bucketMap.get(h);
items.add(com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskLoadItemVO.builder()
.time(String.format("%02d:00", h))
.taskCount(cnt == null ? 0 : cnt.intValue())
.build());
}
template.setTaskLoadStatistics(items);
} }
// ====== 真实数据:任务类型统计(当月 OMS 业务单,固定跨境运输/国内运输两类,不使用写死模板兜底) ======
// ====== 真实数据:任务类型统计(本月 YMS QMS_MAIN_BUSINESS 按业务类型 BUSINESS_NAME 分组) ======
template.setTaskTypeStatistics(biPlatformRealDataService.queryMonthTransportOrderByType()); template.setTaskTypeStatistics(biPlatformRealDataService.queryMonthTransportOrderByType());
// ====== 真实数据:平均作业时长趋势(近半年按月:月台作业平均时长 / 园区作业平均时长) ======
template.setAvgOperationDurationTrend(biPlatformRealDataService.queryAvgOperationDurationTrend());
// ====== 真实数据:任务超时率 + 月台作业超时记录列表(依赖 YMS QMS_STATIC_FIELD 参考用时) ======
template.setTaskOvertimeRate(biPlatformRealDataService.calcTodayTaskOvertimeRate());
template.setOvertimeRecords(biPlatformRealDataService.queryTodayOvertimeRecords());
return template; return template;
} }
private static Integer parseHourFromLabel(String label) {
if (label == null) return null;
// "15时" -> 15
try {
String s = label.replace("", "").trim();
return Integer.parseInt(s);
} catch (Exception e) {
return null;
}
}
/** /**
* TODO 业务落地:改为查询真实数据组装 VO。当前为 {@link WarehouseOperationZoneDemoData} 静态示例。 * TODO 业务落地:改为查询真实数据组装 VO。当前为 {@link WarehouseOperationZoneDemoData} 静态示例。
*/ */
protected WarehouseOperationZoneVO buildWarehouseOperationZoneSnapshot() { protected WarehouseOperationZoneVO buildWarehouseOperationZoneSnapshot() {
return WarehouseOperationZoneDemoData.build(); return WarehouseOperationZoneDemoData.build();
} }
@@ -79,9 +79,9 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
return ComprehensiveSituationVO.builder() return ComprehensiveSituationVO.builder()
.valueAddedServiceIncome(jitterLong(Baseline.COMP_INCOME, r)) .valueAddedServiceIncome(jitterLong(Baseline.COMP_INCOME, r))
.inventoryTurnover(Math.max(1, jitterInt(Baseline.COMP_TURNOVER, r))) .inventoryTurnover(Math.max(1, jitterInt(Baseline.COMP_TURNOVER, r)))
.orderCount(Math.max(0, jitterInt(Baseline.COMP_ORDERS, r))) .orderCount((long) Math.max(0, jitterInt(Baseline.COMP_ORDERS, r)))
.vehicleUtilizationRate(jitterPercentDouble(Baseline.COMP_VEHICLE_PCT, r)) .vehicleUtilizationRate(jitterPercentDouble(Baseline.COMP_VEHICLE_PCT, r))
.fuelConsumptionPerHundredKm(Math.max(1, jitterInt(Baseline.COMP_FUEL, r))) .fuelConsumptionPerHundredKm(Math.max(1.0, jitterDouble(Baseline.COMP_FUEL, r)))
.incomeTrend(jitterIntArray(Baseline.COMP_INCOME_TREND, r)) .incomeTrend(jitterIntArray(Baseline.COMP_INCOME_TREND, r))
.build(); .build();
} }
@@ -136,13 +136,14 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
.build()); .build());
} }
// 效率统计(本日):入园区超时率、超时数量、月台作业准时率、园区作业准时率 按业务要求恒为 0
EfficiencyStatisticsVO eff = EfficiencyStatisticsVO.builder() EfficiencyStatisticsVO eff = EfficiencyStatisticsVO.builder()
.entryOvertimeRate(jitterPercentInt(Baseline.P_ENT_OT, r)) .entryOvertimeRate(0)
.overtimeCount(Math.max(0, jitterInt(Baseline.P_OT_CNT, r))) .overtimeCount(0)
.platformOnTimeRate(jitterPercentInt(Baseline.P_PL_OT, r)) .platformOnTimeRate(0)
.avgPlatformDuration(Math.max(0.1, jitterDouble(Baseline.P_AVG_PL, r))) .avgPlatformDuration(0d)
.yardOnTimeRate(jitterPercentInt(Baseline.P_YD_OT, r)) .yardOnTimeRate(0)
.avgYardDuration(Math.max(0.1, jitterDouble(Baseline.P_AVG_YD, r))) .avgYardDuration(0d)
.build(); .build();
PlatformOverviewVO overview = PlatformOverviewVO.builder() PlatformOverviewVO overview = PlatformOverviewVO.builder()
@@ -150,15 +151,19 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
.currentOccupancyRate((double) jitterPercentInt(Baseline.P_OCC, r)) .currentOccupancyRate((double) jitterPercentInt(Baseline.P_OCC, r))
.operatingPlatforms(Math.max(0, jitterInt(Baseline.P_OP, r))) .operatingPlatforms(Math.max(0, jitterInt(Baseline.P_OP, r)))
.todayTasks(Math.max(0, jitterInt(Baseline.P_TASKS, r))) .todayTasks(Math.max(0, jitterInt(Baseline.P_TASKS, r)))
.platformTurnoverRate(jitterPercentDouble(Baseline.P_TURNOVER, r))
.build(); .build();
VehicleStatusVO vs = VehicleStatusVO.builder() VehicleStatusVO vs = VehicleStatusVO.builder()
.todayReservedVehicles(Math.max(0, jitterInt(Baseline.P_RESV, r))) .todayReservedVehicles(Math.max(0, jitterInt(Baseline.P_RESV, r)))
.notCheckedIn(Math.max(0, jitterInt(Baseline.P_NC, r))) .notCheckedIn(Math.max(0, jitterInt(Baseline.P_NC, r)))
.waiting(Math.max(0, jitterInt(Baseline.P_WAIT, r)))
.callStatusTotal(Math.max(0, jitterInt(Baseline.P_CALL_STATUS_TOTAL, r)))
.operatingVehicles(Math.max(0, jitterInt(Baseline.P_OPV, r))) .operatingVehicles(Math.max(0, jitterInt(Baseline.P_OPV, r)))
.completedVehicles(Math.max(0, jitterInt(Baseline.P_DONE, r))) .completedVehicles(Math.max(0, jitterInt(Baseline.P_DONE, r)))
.build(); .build();
List<AvgOperationDurationTrendItemVO> avgTrend = new ArrayList<>(); List<AvgOperationDurationTrendItemVO> avgTrend = new ArrayList<>();
for (int i = 0; i < Baseline.P_MO_LABELS.length; i++) { for (int i = 0; i < Baseline.P_MO_LABELS.length; i++) {
avgTrend.add(AvgOperationDurationTrendItemVO.builder() avgTrend.add(AvgOperationDurationTrendItemVO.builder()
@@ -185,13 +190,8 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
.build()); .build());
} }
// 月台告警:按业务要求,全部置 0(趋势/统计/分布均不生成写死数据)
List<PlatformAlarmTrendItemVO> alarmTrend = new ArrayList<>(); List<PlatformAlarmTrendItemVO> alarmTrend = new ArrayList<>();
for (int i = 0; i < Baseline.P_AL_D.length; i++) {
alarmTrend.add(PlatformAlarmTrendItemVO.builder()
.date(Baseline.P_AL_D[i])
.alarmCount(Math.max(0, jitterInt(Baseline.P_AL_C[i], r)))
.build());
}
List<TaskTypeCountItemVO> typeCounts = new ArrayList<>(); List<TaskTypeCountItemVO> typeCounts = new ArrayList<>();
for (int i = 0; i < Baseline.P_TT_TYPES.length; i++) { for (int i = 0; i < Baseline.P_TT_TYPES.length; i++) {
@@ -201,18 +201,11 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
.build()); .build());
} }
int todayAl = Math.max(0, jitterInt(Baseline.P_AL_TOT, r)); int todayAl = 0;
int handled = Math.max(0, jitterInt(Baseline.P_AL_H, r)); int handled = 0;
handled = Math.min(handled, todayAl); int unhandled = 0;
int unhandled = Math.max(0, todayAl - handled);
List<PlatformAlarmDistItemVO> dist = new ArrayList<>(); List<PlatformAlarmDistItemVO> dist = new ArrayList<>();
for (int i = 0; i < Baseline.P_DIST_T.length; i++) {
dist.add(PlatformAlarmDistItemVO.builder()
.type(Baseline.P_DIST_T[i])
.count(Math.max(0, jitterInt(Baseline.P_DIST_C[i], r)))
.build());
}
return PlatformSurveillanceVO.builder() return PlatformSurveillanceVO.builder()
.vehicles(vehicles) .vehicles(vehicles)
@@ -233,9 +226,13 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
.unhandled(unhandled) .unhandled(unhandled)
.build()) .build())
.platformAlarmDistribution(dist) .platformAlarmDistribution(dist)
// 任务超时率 / 超时记录列表:实时查库覆盖,模板给 0/空兜底
.taskOvertimeRate(0d)
.overtimeRecords(new ArrayList<>())
.build(); .build();
} }
/** /**
* 总体运营态势:与 {@code create_bi_overall_operation_dm.sql} 内置 PAYLOAD_JSON 一致(不随 Random 抖动)。 * 总体运营态势:与 {@code create_bi_overall_operation_dm.sql} 内置 PAYLOAD_JSON 一致(不随 Random 抖动)。
*/ */
@@ -371,7 +368,7 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
static final int COMP_TURNOVER = 18; static final int COMP_TURNOVER = 18;
static final int COMP_ORDERS = 50_120; static final int COMP_ORDERS = 50_120;
static final double COMP_VEHICLE_PCT = 93.2; static final double COMP_VEHICLE_PCT = 93.2;
static final int COMP_FUEL = 12; static final double COMP_FUEL = 12.5;
static final int[] COMP_INCOME_TREND = {195, 248, 302, 415, 366, 488}; static final int[] COMP_INCOME_TREND = {195, 248, 302, 415, 366, 488};
static final double W_WH_RATE = 96.8; static final double W_WH_RATE = 96.8;
@@ -403,19 +400,15 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
{"闽D99887", "作业中", "#07", "142"}, {"闽D99887", "作业中", "#07", "142"},
{"粤B66220", "已离园", "#29", "201"} {"粤B66220", "已离园", "#29", "201"}
}; };
static final int P_ENT_OT = 42;
static final int P_OT_CNT = 28;
static final int P_PL_OT = 88;
static final double P_AVG_PL = 3.8;
static final int P_YD_OT = 91;
static final double P_AVG_YD = 5.2;
static final int P_TOT_PL = 58; static final int P_TOT_PL = 58;
static final int P_OCC = 62; static final int P_OCC = 62;
static final int P_OP = 31; static final int P_OP = 31;
static final int P_TASKS = 418; static final int P_TASKS = 418;
static final double P_TURNOVER = 72.5;
static final int P_RESV = 2_850; static final int P_RESV = 2_850;
static final int P_NC = 920; static final int P_NC = 920;
static final int P_WAIT = 680; static final int P_WAIT = 680;
static final int P_CALL_STATUS_TOTAL = 680;
static final int P_OPV = 1_180; static final int P_OPV = 1_180;
static final int P_DONE = 1_420; static final int P_DONE = 1_420;
static final String[] P_MO_LABELS = {"5月", "6月", "7月"}; static final String[] P_MO_LABELS = {"5月", "6月", "7月"};
@@ -427,8 +420,10 @@ public final class BiSnapshotWeeklyRandomDataBuilder {
static final int P_MT_AVG_YOY = -3; static final int P_MT_AVG_YOY = -3;
static final int P_MT_CR = 97; static final int P_MT_CR = 97;
static final int P_MT_CR_YOY = 2; static final int P_MT_CR_YOY = 2;
static final String[] P_LOAD_T = {"8时", "10时", "14时"}; /** 小时趋势:每 2 小时一个桶,0:00 ~ 22:00 共 12 个 */
static final int[] P_LOAD_C = {45, 120, 156}; static final String[] P_LOAD_T = {"00:00", "02:00", "04:00", "06:00", "08:00", "10:00",
"12:00", "14:00", "16:00", "18:00", "20:00", "22:00"};
static final int[] P_LOAD_C = {5, 3, 2, 8, 45, 120, 130, 156, 110, 70, 30, 12};
static final String[] P_AL_D = {"1日", "2日", "3日"}; static final String[] P_AL_D = {"1日", "2日", "3日"};
static final int[] P_AL_C = {6, 9, 4}; static final int[] P_AL_C = {6, 9, 4};
static final int P_TT_TOT = 5_100; static final int P_TT_TOT = 5_100;
@@ -0,0 +1,23 @@
package com.mhd.bi.domain.biStaticField.repository.mapper;
import com.mhd.bi.domain.biStaticField.repository.po.BiStaticFieldPO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* BI 业务静态字段配置 MapperYMS 库 NGWL_TEST_YMS.QMS_STATIC_FIELD
*
* <p>SQL 见 BiStaticFieldMapper.xml。</p>
*/
public interface BiStaticFieldMapper {
/** 按字段编码查询全部有效配置(含 GLOBAL / FLOOR / WINDOW 各作用域,供程序按优先级取值) */
List<BiStaticFieldPO> queryByCode(@Param("fieldCode") String fieldCode);
/** 批量按字段编码查询有效配置 */
List<BiStaticFieldPO> queryByCodes(@Param("fieldCodes") List<String> fieldCodes);
/** 查询全部有效配置 */
List<BiStaticFieldPO> queryAll();
}
@@ -0,0 +1,38 @@
package com.mhd.bi.domain.biStaticField.repository.po;
import lombok.Data;
import java.io.Serializable;
/**
* 业务静态字段配置(来自 NGWL_TEST_YMS.QMS_STATIC_FIELD
*
* <p>集中存放指标计算所需的参考值/基准值,当前使用:</p>
* <ul>
* <li>platform_work_ref_minutes 月台作业参考用时(分钟)</li>
* <li>yard_work_ref_minutes 园区作业参考用时(分钟)</li>
* </ul>
* <p>后续新增静态字段只需往表里 INSERT,无需改代码结构。</p>
*/
@Data
public class BiStaticFieldPO implements Serializable {
private static final long serialVersionUID = 1L;
/** 静态字段编码 */
private String fieldCode;
/** 静态字段名称 */
private String fieldName;
/** 作用域类型:GLOBAL-全局 FLOOR-按楼层 WINDOW-按月台 */
private String scopeType;
/** 作用域编码:GLOBAL 时为 ALLFLOOR 时为楼层;WINDOW 时为月台名 */
private String scopeCode;
/** 字段值(字符串,程序按需转换) */
private String fieldValue;
/** 单位 */
private String unit;
}
@@ -0,0 +1,108 @@
package com.mhd.bi.domain.biStaticField.service;
import com.mhd.bi.domain.biStaticField.repository.mapper.BiStaticFieldMapper;
import com.mhd.bi.domain.biStaticField.repository.po.BiStaticFieldPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
/**
* BI 业务静态字段配置业务层(YMS 库 QMS_STATIC_FIELD
*
* <p>集中提供参考值/基准值等静态配置的读取能力,后续新增静态字段只需往表里 INSERT。</p>
*/
@Slf4j
@Service
public class BiStaticFieldService {
/** 月台作业参考用时(分钟) */
public static final String CODE_PLATFORM_WORK_REF_MINUTES = "platform_work_ref_minutes";
/** 园区作业参考用时(分钟) */
public static final String CODE_YARD_WORK_REF_MINUTES = "yard_work_ref_minutes";
/** 作用域:全局 */
private static final String SCOPE_GLOBAL = "GLOBAL";
/** 作用域:按月台 */
private static final String SCOPE_WINDOW = "WINDOW";
/** 作用域:按楼层 */
private static final String SCOPE_FLOOR = "FLOOR";
@Resource
private BiStaticFieldMapper biStaticFieldMapper;
/**
* 按字段编码查询全部有效配置;查询失败返回空列表。
*/
public List<BiStaticFieldPO> queryByCode(String fieldCode) {
try {
List<BiStaticFieldPO> list = biStaticFieldMapper.queryByCode(fieldCode);
return list == null ? Collections.emptyList() : list;
} catch (Exception e) {
log.error("【YMS】查询静态字段配置失败, fieldCode={}: {}", fieldCode, e.getMessage(), e);
return Collections.emptyList();
}
}
/**
* 按「WINDOW > FLOOR > GLOBAL」优先级取静态字段值(字符串)。取不到返回 null。
*
* @param fieldCode 字段编码
* @param windowName 月台名(可为空)
* @param floor 楼层(可为空)
*/
public String resolveValue(String fieldCode, String windowName, String floor) {
List<BiStaticFieldPO> list = queryByCode(fieldCode);
if (list.isEmpty()) {
return null;
}
String globalVal = null;
String floorVal = null;
for (BiStaticFieldPO po : list) {
if (po == null || po.getFieldValue() == null) {
continue;
}
String scopeType = po.getScopeType() == null ? "" : po.getScopeType().trim().toUpperCase();
String scopeCode = po.getScopeCode();
if (SCOPE_WINDOW.equals(scopeType) && windowName != null && windowName.equals(scopeCode)) {
return po.getFieldValue(); // 最高优先级,直接返回
}
if (SCOPE_FLOOR.equals(scopeType) && floor != null && floor.equals(scopeCode)) {
floorVal = po.getFieldValue();
}
if (SCOPE_GLOBAL.equals(scopeType)) {
globalVal = po.getFieldValue();
}
}
return floorVal != null ? floorVal : globalVal;
}
/**
* 取静态字段的数值(分钟等整数),取不到或非数字返回 null。
*/
public Long resolveLongValue(String fieldCode, String windowName, String floor) {
String v = resolveValue(fieldCode, windowName, floor);
if (v == null || v.trim().isEmpty()) {
return null;
}
try {
return Long.valueOf(v.trim());
} catch (Exception e) {
log.warn("【YMS】静态字段值非数字, fieldCode={}, value={}", fieldCode, v);
return null;
}
}
/** 月台作业参考用时(分钟),按月台/楼层解析;取不到返回 null */
public Long resolvePlatformWorkRefMinutes(String windowName, String floor) {
return resolveLongValue(CODE_PLATFORM_WORK_REF_MINUTES, windowName, floor);
}
/** 园区作业参考用时(分钟),按月台/楼层解析;取不到返回 null */
public Long resolveYardWorkRefMinutes(String windowName, String floor) {
return resolveLongValue(CODE_YARD_WORK_REF_MINUTES, windowName, floor);
}
}
@@ -0,0 +1,32 @@
package com.mhd.bi.domain.biWarehouseTransport.repository.mapper;
import com.mhd.bi.domain.biWarehouseTransport.repository.po.RealCustomerRankPO;
import com.mhd.bi.domain.biWarehouseTransport.repository.po.RealWarehouseOrderStatPO;
import java.util.List;
/**
* 仓储运输-真实业务数据 Mapper(跨库查询,与月台监测同方案)
*
* <p>数据来源:</p>
* <ul>
* <li>库存订单量:NGWL_TEST_WMS.STOCK_IN_ORDER + NGWL_TEST_WMS.STOCK_OUT_ORDER</li>
* <li>今日订单数量:NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER</li>
* <li>今日运输车次:NGWL_TEST_WLHY.TMS_TRANSPORT_NOTE</li>
* <li>客户贡献排名:NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER 按货主聚合近半年</li>
* </ul>
*/
public interface BiWarehouseTransportRealDataMapper {
/** 仓储订单量:本年度 / 本季度 / 本月度(WMS 入库单 + 出库单),全部组织 */
RealWarehouseOrderStatPO queryWarehouseOrderStat();
/** 今日订单数量:OMS 运输业务单,今日创建 */
Long countTodayTransportOrders();
/** 今日运输车次:TMS 运单(运输单),今日创建,一个单 = 一个车次 */
Long countTodayTransportTrips();
/** 客户贡献排名:OMS 运输业务单,近半年按货主聚合订单数,取前 5 名 */
List<RealCustomerRankPO> queryCustomerRankTop5();
}
@@ -0,0 +1,21 @@
package com.mhd.bi.domain.biWarehouseTransport.repository.po;
import lombok.Data;
import java.io.Serializable;
/**
* 客户贡献排名项(真实数据,来自 OMS 运输业务单)
*
* <p>统计范围:全部组织,近半年,按货主聚合作业量,取前 5 名。</p>
*/
@Data
public class RealCustomerRankPO implements Serializable {
private static final long serialVersionUID = 1L;
/** 货主/客户名称 */
private String customerName;
/** 订单数(单) */
private Long customerAmount;
}
@@ -0,0 +1,25 @@
package com.mhd.bi.domain.biWarehouseTransport.repository.po;
import lombok.Data;
import java.io.Serializable;
/**
* 仓储订单量统计(真实数据)
*
* <p>数据来源:WMS 入库业务单 NGWL_TEST_WMS.STOCK_IN_ORDER + 出库业务单 NGWL_TEST_WMS.STOCK_OUT_ORDER</p>
* <p>统计范围:全部组织</p>
*/
@Data
public class RealWarehouseOrderStatPO implements Serializable {
private static final long serialVersionUID = 1L;
/** 本年度订单数(入库 + 出库) */
private Long currentYearOrders;
/** 本季度订单数(入库 + 出库) */
private Long currentQuarterOrders;
/** 本月度订单数(入库 + 出库) */
private Long currentMonthOrders;
}
@@ -0,0 +1,99 @@
package com.mhd.bi.domain.biWarehouseTransport.service;
import com.mhd.bi.domain.biWarehouseTransport.repository.mapper.BiWarehouseTransportRealDataMapper;
import com.mhd.bi.domain.biWarehouseTransport.repository.po.RealCustomerRankPO;
import com.mhd.bi.domain.biWarehouseTransport.repository.po.RealWarehouseOrderStatPO;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.CustomerContributionRankVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 仓储运输-真实业务数据业务层(封装 WMS / OMS / TMS 跨库查询)
*
* <p>实现方案与「月台监测指标查询」一致:实时查库,查不到或异常返回 0/空,不影响其它字段。</p>
*/
@Slf4j
@Service
public class BiWarehouseTransportRealDataService {
@Resource
private BiWarehouseTransportRealDataMapper biWarehouseTransportRealDataMapper;
/**
* 仓储订单量:本年度 / 本季度 / 本月度(WMS 入库单 + 出库单),全部组织。
* 查询失败返回全 0。
*/
public RealWarehouseOrderStatPO queryWarehouseOrderStat() {
RealWarehouseOrderStatPO po = new RealWarehouseOrderStatPO();
po.setCurrentYearOrders(0L);
po.setCurrentQuarterOrders(0L);
po.setCurrentMonthOrders(0L);
try {
RealWarehouseOrderStatPO row = biWarehouseTransportRealDataMapper.queryWarehouseOrderStat();
if (row != null) {
po.setCurrentYearOrders(row.getCurrentYearOrders() == null ? 0L : row.getCurrentYearOrders());
po.setCurrentQuarterOrders(row.getCurrentQuarterOrders() == null ? 0L : row.getCurrentQuarterOrders());
po.setCurrentMonthOrders(row.getCurrentMonthOrders() == null ? 0L : row.getCurrentMonthOrders());
}
} catch (Exception e) {
log.error("【WMS】统计仓储订单量失败: {}", e.getMessage(), e);
}
return po;
}
/** 今日订单数量:OMS 运输业务单,今日创建;查询失败返回 0 */
public long countTodayTransportOrders() {
try {
Long n = biWarehouseTransportRealDataMapper.countTodayTransportOrders();
return n == null ? 0L : n;
} catch (Exception e) {
log.error("【OMS】统计今日订单数量失败: {}", e.getMessage(), e);
return 0L;
}
}
/** 今日运输车次:TMS 运输单,今日创建(一个单 = 一个车次);查询失败返回 0 */
public long countTodayTransportTrips() {
try {
Long n = biWarehouseTransportRealDataMapper.countTodayTransportTrips();
return n == null ? 0L : n;
} catch (Exception e) {
log.error("【TMS】统计今日运输车次失败: {}", e.getMessage(), e);
return 0L;
}
}
/**
* 客户贡献排名(前 5 名货主,近半年订单数)。
* 查询失败返回空数组结构的对象,前端可正常渲染。
*/
public CustomerContributionRankVO queryCustomerContributionRank() {
List<String> names = new ArrayList<>();
List<Integer> amounts = new ArrayList<>();
try {
List<RealCustomerRankPO> rows = biWarehouseTransportRealDataMapper.queryCustomerRankTop5();
if (rows != null) {
for (RealCustomerRankPO r : rows) {
if (r == null || r.getCustomerName() == null) {
continue;
}
names.add(r.getCustomerName());
amounts.add(r.getCustomerAmount() == null ? 0 : r.getCustomerAmount().intValue());
}
}
} catch (Exception e) {
log.error("【OMS】查询客户贡献排名失败: {}", e.getMessage(), e);
names = new ArrayList<>();
amounts = new ArrayList<>();
}
return CustomerContributionRankVO.builder()
.customerName(names.isEmpty() ? Collections.emptyList() : names)
.customerAmount(amounts.isEmpty() ? Collections.emptyList() : amounts)
.build();
}
}
@@ -1,18 +1,124 @@
package com.mhd.bi.domain.biWarehouseTransport.service; package com.mhd.bi.domain.biWarehouseTransport.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mhd.bi.domain.biSnapshotSync.repository.mapper.BiSnapshotWriteMapper;
import com.mhd.bi.domain.biWarehouseTransport.repository.mapper.BiWarehouseTransportReportMapper; import com.mhd.bi.domain.biWarehouseTransport.repository.mapper.BiWarehouseTransportReportMapper;
import com.mhd.bi.domain.biWarehouseTransport.repository.po.RealWarehouseOrderStatPO;
import com.mhd.bi.interfaces.facadeApi.biReport.vo.WarehouseTransportSituationVO; import com.mhd.bi.interfaces.facadeApi.biReport.vo.WarehouseTransportSituationVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/**
* 仓储运输:数据访问仅经 Mapper(含 XML 内 SQL 与 Mapper 默认方法组装)。
*
* <p>实现方案与「月台监测指标查询」完全一致:</p>
* <ol>
* <li>先读快照最新一条作为基底;</li>
* <li>实时查业务库,用真实数据覆盖以下字段:
* 本年度订单 / 本季度订单 / 本月度订单(WMS 入库业务单 + 出库业务单)、
* 今日订单数量(OMS 运输业务单)、今日运输车次(TMS 运单)、
* 客户贡献排名(OMS 近半年前 5 名货主);</li>
* <li>对「真实业务数据」计算指纹,与快照最新一条比对:
* 未变化则跳过落库直接返回;已变化则覆盖更新快照最新一条,无记录时插入兜底。</li>
* </ol>
* <p>其余字段(趋势类、周转率、货量等)仍取自快照模板值,不纳入指纹。</p>
*/
@Slf4j
@Service @Service
public class BiWarehouseTransportReportService { public class BiWarehouseTransportReportService {
private static final String REMARK_REALTIME = "realtime warehouse-transport";
@Resource @Resource
private BiWarehouseTransportReportMapper biWarehouseTransportReportMapper; private BiWarehouseTransportReportMapper biWarehouseTransportReportMapper;
@Resource
private BiWarehouseTransportRealDataService biWarehouseTransportRealDataService;
@Resource
private BiSnapshotWriteMapper biSnapshotWriteMapper;
@Resource
private ObjectMapper objectMapper;
/**
* 实时组装仓储运输数据:快照基底 + 实时业务数据覆盖;
* 仅当实时业务数据(WMS/OMS/TMS 查出的字段)发生变化时,才覆盖更新
* BI_WAREHOUSE_TRANSPORT_SNAPSHOT 快照表最新一条后返回。
* 表中尚无记录时插入一条兜底。
*/
public WarehouseTransportSituationVO loadLatestWarehouseTransport() { public WarehouseTransportSituationVO loadLatestWarehouseTransport() {
return biWarehouseTransportReportMapper.loadLatestWarehouseTransport(); WarehouseTransportSituationVO vo = biWarehouseTransportReportMapper.loadLatestWarehouseTransport();
if (vo == null) {
vo = new WarehouseTransportSituationVO();
}
// 仓储订单量:WMS 入库业务单 + 出库业务单(本年度 / 本季度 / 本月度,全部组织)
RealWarehouseOrderStatPO orderStat = biWarehouseTransportRealDataService.queryWarehouseOrderStat();
vo.setCurrentYearOrders(orderStat.getCurrentYearOrders());
vo.setCurrentQuarterOrders(orderStat.getCurrentQuarterOrders().intValue());
vo.setCurrentMonthOrders(orderStat.getCurrentMonthOrders().intValue());
// 今日订单数量:OMS 运输业务单
vo.setOrderCount((int) biWarehouseTransportRealDataService.countTodayTransportOrders());
// 今日运输车次:TMS 运输单(一个单 = 一个车次)
vo.setTransportTrips((int) biWarehouseTransportRealDataService.countTodayTransportTrips());
// 客户贡献排名:OMS 运输业务单,近半年前 5 名货主(订单数)
vo.setCustomerContributionRank(biWarehouseTransportRealDataService.queryCustomerContributionRank());
try {
String fullJson = objectMapper.writeValueAsString(vo);
String fingerprint = realDataFingerprint(vo);
String latestJson = biWarehouseTransportReportMapper.selectLatestPayloadJson();
if (StringUtils.isNotBlank(latestJson)) {
WarehouseTransportSituationVO latestVo =
objectMapper.readValue(latestJson.trim(), WarehouseTransportSituationVO.class);
if (Objects.equals(fingerprint, realDataFingerprint(latestVo))) {
log.info("BI 仓储运输实时业务数据未变化,跳过落库");
return vo;
}
}
int rows = biSnapshotWriteMapper.updateLatestWarehouseTransportSnapshot(fullJson, REMARK_REALTIME);
if (rows < 1) {
// 表中无最新记录可更新,插入一条兜底
biSnapshotWriteMapper.insertWarehouseTransportSnapshot(fullJson, REMARK_REALTIME);
}
log.info("BI 仓储运输实时数据已落库(覆盖最新, 数据变化), affectedRows={}", rows);
} catch (Exception e) {
log.error("BI 仓储运输实时数据落库失败", e);
}
return vo;
}
/**
* 实时业务数据指纹:仅含实时查库得到的字段(本年度/本季度/本月度订单、今日订单数量、
* 今日运输车次、客户贡献排名),用于判断是否需要覆盖更新快照;
* 其余字段取自快照模板,不纳入指纹,其变化不会触发更新。
*/
private String realDataFingerprint(WarehouseTransportSituationVO vo) throws Exception {
Map<String, Object> fp = new LinkedHashMap<>();
if (vo == null) {
return objectMapper.writeValueAsString(fp);
}
fp.put("currentYearOrders", vo.getCurrentYearOrders());
fp.put("currentQuarterOrders", vo.getCurrentQuarterOrders());
fp.put("currentMonthOrders", vo.getCurrentMonthOrders());
fp.put("orderCount", vo.getOrderCount());
fp.put("transportTrips", vo.getTransportTrips());
if (vo.getCustomerContributionRank() != null) {
fp.put("customerName", vo.getCustomerContributionRank().getCustomerName());
fp.put("customerAmount", vo.getCustomerContributionRank().getCustomerAmount());
}
return objectMapper.writeValueAsString(fp);
} }
} }
@@ -25,14 +25,14 @@ public class ComprehensiveSituationVO {
@ApiModelProperty("库存周转率,单位:次") @ApiModelProperty("库存周转率,单位:次")
private Integer inventoryTurnover; private Integer inventoryTurnover;
@ApiModelProperty("运输订单量,单位:单") @ApiModelProperty("运输订单量,单位:单(实时统计 OMS 运输业务单,本年度、全部组织)")
private Integer orderCount; private Long orderCount;
@ApiModelProperty("车辆利用率,单位:%") @ApiModelProperty("车辆利用率,单位:%")
private Double vehicleUtilizationRate; private Double vehicleUtilizationRate;
@ApiModelProperty("百公里油耗,单位:升") @ApiModelProperty("百公里油耗,单位:升(实时统计 TMS 运输作业单:总油耗量 ÷ 总重载里程 × 100)")
private Integer fuelConsumptionPerHundredKm; private Double fuelConsumptionPerHundredKm;
@ApiModelProperty("运输收入趋势,单位:万元(近半年,按月正序)") @ApiModelProperty("运输收入趋势,单位:万元(近半年,按月正序)")
private List<Integer> incomeTrend; private List<Integer> incomeTrend;
@@ -19,6 +19,6 @@ public class CustomerContributionRankVO {
@ApiModelProperty("客户名称") @ApiModelProperty("客户名称")
private List<String> customerName; private List<String> customerName;
@ApiModelProperty("客户金额,单位:万元") @ApiModelProperty("客户订单数,单位:")
private List<Integer> customerAmount; private List<Integer> customerAmount;
} }
@@ -0,0 +1,31 @@
package com.mhd.bi.interfaces.facadeApi.biReport.vo.platform;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("月台监测-月台作业超时记录")
public class OvertimeRecordItemVO {
@ApiModelProperty("月台名称")
private String windowName;
@ApiModelProperty("车牌号码")
private String carNo;
@ApiModelProperty("超时时长,单位:分钟(= 月台作业时长 - 参考用时)")
private Long overtimeMinutes;
@ApiModelProperty("月台作业参考用时,单位:分钟")
private Long refMinutes;
@ApiModelProperty("月台作业时长,单位:分钟")
private Long platformMinutes;
}
@@ -25,4 +25,7 @@ public class PlatformOverviewVO {
@ApiModelProperty("今日作业任务,单位:个") @ApiModelProperty("今日作业任务,单位:个")
private Integer todayTasks; private Integer todayTasks;
@ApiModelProperty("月台周转率,单位:%(今日已出场的记录数 ÷ 今日全部记录数 × 100)")
private Double platformTurnoverRate;
} }
@@ -48,4 +48,10 @@ public class PlatformSurveillanceVO {
@ApiModelProperty("月台告警分布") @ApiModelProperty("月台告警分布")
private List<PlatformAlarmDistItemVO> platformAlarmDistribution; private List<PlatformAlarmDistItemVO> platformAlarmDistribution;
@ApiModelProperty("任务超时率,单位:%(月台作业时长 > 参考用时的记录数 ÷ 总记录数 × 100)")
private Double taskOvertimeRate;
@ApiModelProperty("月台作业超时记录列表(月台名称 / 车牌号码 / 超时时长分钟)")
private List<OvertimeRecordItemVO> overtimeRecords;
} }
@@ -23,6 +23,9 @@ public class VehicleStatusVO {
@ApiModelProperty("当前等待作业(已预约/已签到/已叫号),单位:辆") @ApiModelProperty("当前等待作业(已预约/已签到/已叫号),单位:辆")
private Integer waiting; private Integer waiting;
@ApiModelProperty("已签到+已叫号+已过号(FLOW_STATUS IN 2/3/4),单位:辆")
private Integer callStatusTotal;
@ApiModelProperty("当前作业中,单位:辆") @ApiModelProperty("当前作业中,单位:辆")
private Integer operatingVehicles; private Integer operatingVehicles;
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mhd.bi.domain.biComprehensive.repository.mapper.BiComprehensiveRealDataMapper">
<!-- 运输订单量:本年度、全部组织
数据来源:OMS 运输业务单 NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER
统计口径:CREATE_TIME 在本年度;IS_DELETE = 0 表示未删除。
注:若该表删除标记实际为 DEL_FLAG,请将下方 IS_DELETE 替换为 DEL_FLAG -->
<select id="countCurrentYearTransportOrders" resultType="java.lang.Long">
SELECT COUNT(1)
FROM NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER
WHERE CREATE_TIME IS NOT NULL
AND TO_CHAR(CREATE_TIME, 'YYYY') = TO_CHAR(SYSDATE, 'YYYY')
AND IS_DELETE = 0
</select>
<!-- 百公里油耗:本年度、全部组织,油耗量与重载里程均非空的记录
数据来源:TMS 运输作业单 NGWL_TEST_WLHY.TMS_EXPENSE_REPORT
返回:总油耗量、总重载里程、参与条数(由 Service 层做除法)
公式:百公里油耗 = 总油耗量(FUEL_CONSUMPTION) ÷ 总重载里程(HEAVYLOAD_MILEAGE) × 100 -->
<select id="queryCurrentYearFuelStat" resultType="com.mhd.bi.domain.biComprehensive.repository.po.RealFuelStatPO">
SELECT
SUM(FUEL_CONSUMPTION) AS totalOil,
SUM(HEAVYLOAD_MILEAGE) AS totalHeavyMileage,
COUNT(1) AS totalCount
FROM NGWL_TEST_WLHY.TMS_EXPENSE_REPORT
WHERE FUEL_CONSUMPTION IS NOT NULL
AND HEAVYLOAD_MILEAGE IS NOT NULL
AND CREATE_TIME IS NOT NULL
AND TO_CHAR(CREATE_TIME, 'YYYY') = TO_CHAR(SYSDATE, 'YYYY')
</select>
</mapper>
@@ -8,7 +8,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
plateNumber <- qms_main_business.CAR_NO plateNumber <- qms_main_business.CAR_NO
status <- qms_main_business.FLOW_STATUS status <- qms_main_business.FLOW_STATUS
platformId <- qms_main_business.WINDOW_NAME(叫号月台,主业务表直接有) platformId <- qms_main_business.WINDOW_NAME(叫号月台,主业务表直接有)
platformDuration <- qms_main_business.EXIT_TIME - ENTRY_TIME(分钟) platformDuration <- 月台作业时长(分钟),用「进入月台 / 离开月台」时间:
未进入月台(IN_WINDOWS_TIME 为空) -> 0
已进入且已离开月台(OUT_WINDOWS_TIME 非空) -> OUT_WINDOWS_TIME - IN_WINDOWS_TIME
已进入但未离开月台 -> SYSDATE - IN_WINDOWS_TIME
appointmentTime <- qms_main_business.CREATE_TIME appointmentTime <- qms_main_business.CREATE_TIME
workFloor <- qms_window_info.PLATFORM_FLOOR(按 WINDOW_NAME 关联) workFloor <- qms_window_info.PLATFORM_FLOOR(按 WINDOW_NAME 关联)
--> -->
@@ -18,9 +21,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
mb.FLOW_STATUS AS status, mb.FLOW_STATUS AS status,
mb.WINDOW_NAME AS platformId, mb.WINDOW_NAME AS platformId,
CASE CASE
WHEN mb.EXIT_TIME IS NOT NULL AND mb.ENTRY_TIME IS NOT NULL WHEN mb.IN_WINDOWS_TIME IS NULL THEN 0
THEN CAST((mb.EXIT_TIME - mb.ENTRY_TIME) * 24 * 60 AS BIGINT) WHEN mb.OUT_WINDOWS_TIME IS NOT NULL
ELSE 0 THEN CAST((mb.OUT_WINDOWS_TIME - mb.IN_WINDOWS_TIME) * 24 * 60 AS BIGINT)
ELSE CAST((SYSDATE - mb.IN_WINDOWS_TIME) * 24 * 60 AS BIGINT)
END AS platformDuration, END AS platformDuration,
TO_CHAR(mb.CREATE_TIME, 'YYYY-MM-DD HH24:MI:SS') AS appointmentTime, TO_CHAR(mb.CREATE_TIME, 'YYYY-MM-DD HH24:MI:SS') AS appointmentTime,
wi.PLATFORM_FLOOR AS workFloor wi.PLATFORM_FLOOR AS workFloor
@@ -32,6 +36,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY mb.CREATE_TIME DESC ORDER BY mb.CREATE_TIME DESC
</select> </select>
<!-- 月台概览:totalPlatforms(总条数)、operatingPlatforms(作业状态=占用,JOB_STATUS: 0空闲 1占用) --> <!-- 月台概览:totalPlatforms(总条数)、operatingPlatforms(作业状态=占用,JOB_STATUS: 0空闲 1占用) -->
<select id="queryPlatformOverview" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformOverviewPO"> <select id="queryPlatformOverview" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformOverviewPO">
SELECT SELECT
@@ -70,18 +76,109 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!-- 车辆状态统计:今日的统计(按创建时间 CREATE_TIME / 流程状态) <!-- 车辆状态统计:今日的统计(按创建时间 CREATE_TIME / 流程状态)
todayReservedVehicles 改为按预约日期 TARGET_DATE(其它 3 项保持 CREATE_TIME todayReservedVehicles 改为按预约日期 TARGET_DATE(其它 3 项保持 CREATE_TIME
waiting 等待作业:FLOW_STATUS IN ('1','2','3') 已预约/已签到/已叫号 且 预约日期 = 今天 waiting 等待作业:FLOW_STATUS IN ('1','2','3') 已预约/已签到/已叫号 且 预约日期 = 今天
callStatusTotal 叫号状态合计:已签到 + 已叫号 + 已过号 = FLOW_STATUS IN ('2','3','4') 且 预约日期 = 今天
todayExited 今日已出场:FLOW_STATUS = '11' 且 预约日期 = 今天(月台周转率的分子)
completedVehicles 今日作业车辆总数:FLOW_STATUS = '11' 且 预约日期 = 今天
FLOW_STATUS: 1已预约 2已签到 3已叫号 4已过号 5已进场 6已称重 7已装车 8二次称重 9已结算 10已还卡 11已出场 12已取消 --> FLOW_STATUS: 1已预约 2已签到 3已叫号 4已过号 5已进场 6已称重 7已装车 8二次称重 9已结算 10已还卡 11已出场 12已取消 -->
<select id="queryVehicleStatusToday" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealVehicleStatusPO"> <select id="queryVehicleStatusToday" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealVehicleStatusPO">
SELECT SELECT
SUM(CASE WHEN TRUNC(TARGET_DATE) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS todayReservedVehicles, SUM(CASE WHEN TRUNC(TARGET_DATE) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS todayReservedVehicles,
SUM(CASE WHEN FLOW_STATUS = '1' AND TRUNC(CREATE_TIME) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS notCheckedIn, SUM(CASE WHEN FLOW_STATUS = '1' AND TRUNC(CREATE_TIME) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS notCheckedIn,
SUM(CASE WHEN FLOW_STATUS IN ('1','2','3') AND TRUNC(TARGET_DATE) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS waiting, SUM(CASE WHEN FLOW_STATUS IN ('1','2','3') AND TRUNC(TARGET_DATE) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS waiting,
SUM(CASE WHEN FLOW_STATUS IN ('2','3','4') AND TRUNC(TARGET_DATE) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS callStatusTotal,
SUM(CASE WHEN FLOW_STATUS = '11' AND TRUNC(TARGET_DATE) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS todayExited,
SUM(CASE WHEN FLOW_STATUS IN ('5','6','7','8','9','10') AND TRUNC(CREATE_TIME) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS operatingVehicles, SUM(CASE WHEN FLOW_STATUS IN ('5','6','7','8','9','10') AND TRUNC(CREATE_TIME) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS operatingVehicles,
SUM(CASE WHEN FLOW_STATUS = '11' AND TRUNC(CREATE_TIME) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS completedVehicles SUM(CASE WHEN FLOW_STATUS = '11' AND TRUNC(TARGET_DATE) = TRUNC(SYSDATE) THEN 1 ELSE 0 END) AS completedVehicles
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS
</select> </select>
<!-- ======================= 月台作业参考用时 / 超时相关 =======================
参考用时取自 YMS 库静态字段配置表 NGWL_TEST_YMS.QMS_STATIC_FIELD
FIELD_CODE = 'platform_work_ref_minutes'
取值优先级:WINDOW(按月台) > FLOOR(按楼层) > GLOBAL(全局)
下面用 LEFT JOIN 三级关联 + COALESCE 实现优先级取值,
REF_MINUTES 为空表示该月台未配置参考用时,其记录不计入超时率分母 -->
<!-- 可复用的参考用时关联条件(YMS 库静态字段配置表 QMS_STATIC_FIELD
取值优先级:WINDOW(按月台) > FLOOR(按楼层) > GLOBAL(全局) -->
<sql id="joinStaticRef">
INNER JOIN NGWL_TEST_YMS.QMS_STATIC_FIELD ref
ON ref.FIELD_CODE = #{refCode}
AND ref.DEL_FLAG = 1
AND ref.FIELD_VALUE IS NOT NULL
AND (
(ref.SCOPE_TYPE = 'WINDOW' AND ref.SCOPE_CODE = mb.WINDOW_NAME)
OR (ref.SCOPE_TYPE = 'FLOOR' AND ref.SCOPE_CODE = wi.PLATFORM_FLOOR)
OR (ref.SCOPE_TYPE = 'GLOBAL')
)
</sql>
<!-- 今日作业超时统计(通用:月台作业 / 园区作业,由 refCode + 时长表达式区分)
分母只计入「能取到参考用时 且 时长两端时间都非空」的记录
overtimeCount 超时记录数:作业时长(分钟) > 参考用时
totalCount 有效记录总数(分母)
超时率 = overtimeCount / totalCount × 100%,由 Service 层计算 -->
<select id="queryTodayOvertimeStat" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealOvertimeStatPO">
SELECT
SUM(CASE WHEN (mb.OUT_WINDOWS_TIME - mb.IN_WINDOWS_TIME) * 24 * 60 &gt; ref.REF_MINUTES
THEN 1 ELSE 0 END) AS overtimeCount,
COUNT(1) AS totalCount
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS mb
LEFT JOIN NGWL_TEST_YMS.QMS_WINDOW_INFO wi
ON wi.WINDOW_NAME = mb.WINDOW_NAME
<include refid="joinStaticRef"/>
WHERE mb.TARGET_DATE IS NOT NULL
AND TRUNC(mb.TARGET_DATE) = TRUNC(SYSDATE)
AND mb.IN_WINDOWS_TIME IS NOT NULL
AND mb.OUT_WINDOWS_TIME IS NOT NULL
</select>
<!-- 今日园区作业超时统计(时长 = 实际出场 - 实际进入;参考用时 yard_work_ref_minutes
用于计算「园区作业准时率 = 1 - 超时率」 -->
<select id="queryTodayYardOvertimeStat" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealOvertimeStatPO">
SELECT
SUM(CASE WHEN (mb.EXIT_TIME - mb.ENTRY_TIME) * 24 * 60 &gt; ref.REF_MINUTES
THEN 1 ELSE 0 END) AS overtimeCount,
COUNT(1) AS totalCount
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS mb
LEFT JOIN NGWL_TEST_YMS.QMS_WINDOW_INFO wi
ON wi.WINDOW_NAME = mb.WINDOW_NAME
<include refid="joinStaticRef"/>
WHERE mb.TARGET_DATE IS NOT NULL
AND TRUNC(mb.TARGET_DATE) = TRUNC(SYSDATE)
AND mb.ENTRY_TIME IS NOT NULL
AND mb.EXIT_TIME IS NOT NULL
</select>
<!-- 今日月台作业超时记录列表(月台名称 / 车牌号码 / 超时时长分钟):
超时时长 = 月台作业时长 - 参考用时 -->
<select id="queryTodayOvertimeList" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealOvertimeRecordPO">
SELECT
mb.WINDOW_NAME AS windowName,
mb.CAR_NO AS carNo,
CAST(((mb.OUT_WINDOWS_TIME - mb.IN_WINDOWS_TIME) * 24 * 60 - ref.REF_MINUTES) AS BIGINT) AS overtimeMinutes,
ref.REF_MINUTES AS refMinutes,
CAST((mb.OUT_WINDOWS_TIME - mb.IN_WINDOWS_TIME) * 24 * 60 AS BIGINT) AS platformMinutes
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS mb
LEFT JOIN NGWL_TEST_YMS.QMS_WINDOW_INFO wi
ON wi.WINDOW_NAME = mb.WINDOW_NAME
<include refid="joinStaticRef"/>
WHERE mb.TARGET_DATE IS NOT NULL
AND TRUNC(mb.TARGET_DATE) = TRUNC(SYSDATE)
AND mb.IN_WINDOWS_TIME IS NOT NULL
AND mb.OUT_WINDOWS_TIME IS NOT NULL
AND (mb.OUT_WINDOWS_TIME - mb.IN_WINDOWS_TIME) * 24 * 60 &gt; ref.REF_MINUTES
ORDER BY overtimeMinutes DESC
</select>
<!-- 本月任务量:按预约日期 TARGET_DATE(Y预约日期)统计本月预约总数量 --> <!-- 本月任务量:按预约日期 TARGET_DATE(Y预约日期)统计本月预约总数量 -->
<select id="countMonthTasks" resultType="java.lang.Long"> <select id="countMonthTasks" resultType="java.lang.Long">
SELECT COUNT(1) SELECT COUNT(1)
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS
@@ -89,21 +186,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND TO_CHAR(TARGET_DATE, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM') AND TO_CHAR(TARGET_DATE, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM')
</select> </select>
<!-- 本月任务平均作业时长 / 完成率统计: <!-- 本月任务平均作业时长 / 完成率统计(统计范围:本月 TARGET_DATE 在当月的记录)
avgDurationHours 平均时长(小时)= AVG((EXIT_TIME - ENTRY_TIME) * 24) totalWorkHours 本月任务作业时长合计(小时),用「进入月台 / 离开月台」时间:
仅 ENTRY_TIME 与 EXIT_TIME 都不为空时参与; 未进入月台(IN_WINDOWS_TIME 为空) -> 0
durationCount 参与平均时长统计的有效条数; 已进入且已离开月台(OUT_WINDOWS_TIME 非空) -> OUT_WINDOWS_TIME - IN_WINDOWS_TIME
completedCount 本月(TARGET_DATE)已出场(EXIT_TIME IS NOT NULL)的数量; 已进入但未离开月台 -> SYSDATE - IN_WINDOWS_TIME
totalCount 本月(TARGET_DATE)预约总数量 completedCount 本月已出场(EXIT_TIME IS NOT NULL)的数量
totalCount 本月预约总数量(作为平均时长的分母)。
avgDurationHours 由 Service 层计算 = totalWorkHours ÷ totalCount(任务平均作业时长)
--> -->
<select id="queryMonthTaskStats" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyTaskDurationPO"> <select id="queryMonthTaskStats" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyTaskDurationPO">
SELECT SELECT
AVG(CASE WHEN ENTRY_TIME IS NOT NULL AND EXIT_TIME IS NOT NULL SUM(CASE
THEN (EXIT_TIME - ENTRY_TIME) * 24 WHEN IN_WINDOWS_TIME IS NULL THEN 0
END) AS avgDurationHours, WHEN OUT_WINDOWS_TIME IS NOT NULL THEN (OUT_WINDOWS_TIME - IN_WINDOWS_TIME) * 24
SUM(CASE WHEN ENTRY_TIME IS NOT NULL AND EXIT_TIME IS NOT NULL ELSE (SYSDATE - IN_WINDOWS_TIME) * 24
AND TO_CHAR(ENTRY_TIME, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM') END) AS totalWorkHours,
THEN 1 ELSE 0 END) AS durationCount,
SUM(CASE WHEN EXIT_TIME IS NOT NULL SUM(CASE WHEN EXIT_TIME IS NOT NULL
AND TARGET_DATE IS NOT NULL AND TARGET_DATE IS NOT NULL
AND TO_CHAR(TARGET_DATE, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM') AND TO_CHAR(TARGET_DATE, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM')
@@ -112,8 +210,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND TO_CHAR(TARGET_DATE, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM') AND TO_CHAR(TARGET_DATE, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM')
THEN 1 ELSE 0 END) AS totalCount THEN 1 ELSE 0 END) AS totalCount
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS
WHERE TARGET_DATE IS NOT NULL
AND TO_CHAR(TARGET_DATE, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM')
</select> </select>
<!-- 今日预约-业务类型统计:按 BUSINESS_NAME(业务类型名称)分组统计今日预约数量,按预约日期 TARGET_DATE 过滤 --> <!-- 今日预约-业务类型统计:按 BUSINESS_NAME(业务类型名称)分组统计今日预约数量,按预约日期 TARGET_DATE 过滤 -->
<select id="queryTodayBusinessTypeStats" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealBusinessTypeStatPO"> <select id="queryTodayBusinessTypeStats" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealBusinessTypeStatPO">
SELECT SELECT
@@ -126,11 +227,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY COUNT(1) DESC ORDER BY COUNT(1) DESC
</select> </select>
<!-- 月台监控-按楼层统计:楼层从叫号月台 WINDOW_NAME 解析(如 S1-01 / N1-02 -> F1),不依赖字母前缀,只取 - 前面那段的数字 <!-- 月台监控-按楼层统计:
platformCount 来自 QMS_WINDOW_INFOtodayReservedVehicles/operatingVehicles 来自 QMS_MAIN_BUSINESS(按 TARGET_DATE=今天) --> floorNo 楼层号,取自月台表 QMS_WINDOW_INFO.PLATFORM_FLOOR
platformCount 该楼层月台数量(QMS_WINDOW_INFO
todayReservedVehicles 该楼层今日预约车辆数(按预约时已选的月台/叫号月台关联楼层)
operatingVehicles 该楼层当前作业车辆数(FLOW_STATUS IN 5,6,7,8,9,10
avgPlatformMinutes 该楼层月台作业平均时长(分钟)
公式:SUM(该楼层有效记录的月台作业时长) ÷ 该楼层有效记录数
有效记录:同时有进入月台(IN_WINDOWS_TIME)与离开月台(OUT_WINDOWS_TIME)时间
说明:楼层维度以「月台表」为准;车辆预约时若已选月台(WINDOW_NAME 非空)即计入对应楼层。 -->
<select id="queryPlatformFloorStats" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformFloorStatPO"> <select id="queryPlatformFloorStats" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformFloorStatPO">
SELECT SELECT
'F' || SUBSTR(W.WINDOW_NAME, INSTR(W.WINDOW_NAME,'-')-1, 1) AS floorNo, W.PLATFORM_FLOOR AS floorNo,
COUNT(DISTINCT W.WINDOW_NAME) AS platformCount, COUNT(DISTINCT W.WINDOW_NAME) AS platformCount,
COUNT(DISTINCT CASE COUNT(DISTINCT CASE
WHEN TRUNC(mb.TARGET_DATE) = TRUNC(SYSDATE) THEN mb.CAR_NO WHEN TRUNC(mb.TARGET_DATE) = TRUNC(SYSDATE) THEN mb.CAR_NO
@@ -139,42 +247,102 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHEN TRUNC(mb.TARGET_DATE) = TRUNC(SYSDATE) WHEN TRUNC(mb.TARGET_DATE) = TRUNC(SYSDATE)
AND mb.FLOW_STATUS IN ('5','6','7','8','9','10') AND mb.FLOW_STATUS IN ('5','6','7','8','9','10')
THEN mb.CAR_NO THEN mb.CAR_NO
END) AS operatingVehicles END) AS operatingVehicles,
ROUND(AVG(CASE
WHEN mb.IN_WINDOWS_TIME IS NOT NULL AND mb.OUT_WINDOWS_TIME IS NOT NULL
THEN (mb.OUT_WINDOWS_TIME - mb.IN_WINDOWS_TIME) * 24 * 60
END), 0) AS avgPlatformMinutes
FROM NGWL_TEST_YMS.QMS_WINDOW_INFO W FROM NGWL_TEST_YMS.QMS_WINDOW_INFO W
LEFT JOIN NGWL_TEST_YMS.QMS_MAIN_BUSINESS mb LEFT JOIN NGWL_TEST_YMS.QMS_MAIN_BUSINESS mb
ON mb.WINDOW_NAME = W.WINDOW_NAME ON mb.WINDOW_NAME = W.WINDOW_NAME
WHERE W.WINDOW_NAME LIKE '%-%' WHERE W.PLATFORM_FLOOR IS NOT NULL
GROUP BY SUBSTR(W.WINDOW_NAME, INSTR(W.WINDOW_NAME,'-')-1, 1) GROUP BY W.PLATFORM_FLOOR
ORDER BY 1 DESC ORDER BY W.PLATFORM_FLOOR DESC
</select> </select>
<!-- 任务类型统计(运输订单,来自 NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER):
当月(CREATE_TIME 在当前月)按业务类型 BUSINESS_CATEGORY 分组统计
BUSINESS_CATEGORY: 1-跨境运输 2-国内运输 --> <!-- 任务类型统计(来自 NGWL_TEST_YMS.QMS_MAIN_BUSINESS 预约登记表):
按本月预约车辆数(TARGET_DATE 在当前月)按 BUSINESS_NAME 业务类型分组统计 -->
<select id="queryMonthTransportOrderByType" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskTypeStatPO"> <select id="queryMonthTransportOrderByType" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskTypeStatPO">
SELECT SELECT
BUSINESS_CATEGORY AS businessCategory, BUSINESS_NAME AS businessName,
COUNT(1) AS count COUNT(1) AS count
FROM NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS
WHERE BUSINESS_CATEGORY IN (1, 2) WHERE BUSINESS_NAME IS NOT NULL
AND CREATE_TIME IS NOT NULL AND TARGET_DATE IS NOT NULL
AND TO_CHAR(CREATE_TIME, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM') AND TO_CHAR(TARGET_DATE, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM')
GROUP BY BUSINESS_CATEGORY GROUP BY BUSINESS_NAME
ORDER BY BUSINESS_CATEGORY ORDER BY COUNT(1) DESC
</select> </select>
<!-- 任务负荷(今日 24 小时时段):按 EXTRACT(HOUR FROM ENTRY_TIME) 统计 --> <!-- 今日平均作业时长(效率统计-本日):
avgPlatformDurationHours 月台作业平均时长(小时)
统计范围:今日预约记录中「同时有进入月台时间(IN_WINDOWS_TIME)和离开月台时间(OUT_WINDOWS_TIME)」的记录
公式:AVG((OUT_WINDOWS_TIME - IN_WINDOWS_TIME) * 24)
avgYardDurationHours 园区平均时长(小时)
统计范围:今日预约记录中「同时有实际进入时间(ENTRY_TIME)和实际出场时间(EXIT_TIME)」的记录
公式:AVG((EXIT_TIME - ENTRY_TIME) * 24) -->
<select id="queryTodayAvgDuration" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealTodayAvgDurationPO">
SELECT
AVG(CASE WHEN IN_WINDOWS_TIME IS NOT NULL AND OUT_WINDOWS_TIME IS NOT NULL
THEN (OUT_WINDOWS_TIME - IN_WINDOWS_TIME) * 24
END) AS avgPlatformDurationHours,
AVG(CASE WHEN ENTRY_TIME IS NOT NULL AND EXIT_TIME IS NOT NULL
THEN (EXIT_TIME - ENTRY_TIME) * 24
END) AS avgYardDurationHours
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS
WHERE TARGET_DATE IS NOT NULL
AND TRUNC(TARGET_DATE) = TRUNC(SYSDATE)
</select>
<!-- 近半年「平均作业时长」趋势(按月分组,取最近 6 个月):
taskCount 该月任务数(预约记录数,作为平均时长的分母);
platformWorkHours 该月月台作业时长合计(小时):
未进入月台(IN_WINDOWS_TIME 为空) -> 0
已进入且已离开月台(OUT_WINDOWS_TIME 非空) -> OUT_WINDOWS_TIME - IN_WINDOWS_TIME
已进入但未离开月台 -> SYSDATE - IN_WINDOWS_TIME
yardWorkHours 该月园区作业时长合计(小时):
未进场(ENTRY_TIME 为空) -> 0
已进场且已出场(EXIT_TIME 非空) -> EXIT_TIME - ENTRY_TIME
已进场但未出场 -> SYSDATE - ENTRY_TIME
平均时长由 Service 层计算 = 时长合计 ÷ taskCount -->
<select id="queryMonthlyAvgDuration" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyAvgDurationPO">
SELECT
TO_CHAR(TARGET_DATE, 'YYYY-MM') AS monthOfYear,
COUNT(1) AS taskCount,
SUM(CASE
WHEN IN_WINDOWS_TIME IS NULL THEN 0
WHEN OUT_WINDOWS_TIME IS NOT NULL THEN (OUT_WINDOWS_TIME - IN_WINDOWS_TIME) * 24
ELSE (SYSDATE - IN_WINDOWS_TIME) * 24
END) AS platformWorkHours,
SUM(CASE
WHEN ENTRY_TIME IS NULL THEN 0
WHEN EXIT_TIME IS NOT NULL THEN (EXIT_TIME - ENTRY_TIME) * 24
ELSE (SYSDATE - ENTRY_TIME) * 24
END) AS yardWorkHours
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS
WHERE TARGET_DATE IS NOT NULL
AND TARGET_DATE &gt;= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -5)
GROUP BY TO_CHAR(TARGET_DATE, 'YYYY-MM')
ORDER BY 1
</select>
<!-- 小时趋势(今日,每 2 小时分组统计一次):
按车辆进入月台时间 IN_WINDOWS_TIME 分桶,桶为 0,2,4,...,22 -->
<select id="queryTaskLoadToday" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskLoadPO"> <select id="queryTaskLoadToday" resultType="com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskLoadPO">
SELECT SELECT
EXTRACT(HOUR FROM ENTRY_TIME) AS hourOfDay, FLOOR(EXTRACT(HOUR FROM IN_WINDOWS_TIME) / 2) * 2 AS hourOfDay,
COUNT(1) AS taskCount COUNT(1) AS taskCount
FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS FROM NGWL_TEST_YMS.QMS_MAIN_BUSINESS
WHERE ENTRY_TIME IS NOT NULL WHERE IN_WINDOWS_TIME IS NOT NULL
AND TRUNC(ENTRY_TIME) = TRUNC(SYSDATE) AND TRUNC(IN_WINDOWS_TIME) = TRUNC(SYSDATE)
GROUP BY EXTRACT(HOUR FROM ENTRY_TIME) GROUP BY FLOOR(EXTRACT(HOUR FROM IN_WINDOWS_TIME) / 2) * 2
ORDER BY EXTRACT(HOUR FROM ENTRY_TIME) ORDER BY 1
</select> </select>
<!-- 月台管理列表:查询 YMS qms_window_info 月台/鹤位窗口 <!-- 月台管理列表:查询 YMS qms_window_info 月台/鹤位窗口
机构名称 deptName <- sys_dept.DEPT_NAMEd.dept_id = a.ORG_NO 关联) 机构名称 deptName <- sys_dept.DEPT_NAMEd.dept_id = a.ORG_NO 关联)
鹤位屏名称 berrNames <- qms_window_berr(WINDOW_ID/BERR_ID) join qms_berr_config(ID/BERR_NAME)WM_CONCAT 拼接 鹤位屏名称 berrNames <- qms_window_berr(WINDOW_ID/BERR_ID) join qms_berr_config(ID/BERR_NAME)WM_CONCAT 拼接
@@ -8,11 +8,33 @@
values (#{payloadJson}, SYSDATE, SYSDATE, 1, #{remark}) values (#{payloadJson}, SYSDATE, SYSDATE, 1, #{remark})
</insert> </insert>
<!-- 覆盖更新综合态势快照最新一条(实时组装后落库,同月台监测) -->
<update id="updateLatestComprehensiveSnapshot">
update NGWL_TEST_BI.BI_COMPREHENSIVE_SNAPSHOT
set PAYLOAD_JSON = #{payloadJson},
UPDATE_TIME = SYSDATE,
REMARK = #{remark}
where DEL_FLAG = 1
and ID = (select max(t.ID) from NGWL_TEST_BI.BI_COMPREHENSIVE_SNAPSHOT t where t.DEL_FLAG = 1)
</update>
<insert id="insertWarehouseTransportSnapshot"> <insert id="insertWarehouseTransportSnapshot">
insert into NGWL_TEST_BI.BI_WAREHOUSE_TRANSPORT_SNAPSHOT (PAYLOAD_JSON, CREATE_TIME, UPDATE_TIME, DEL_FLAG, REMARK) insert into NGWL_TEST_BI.BI_WAREHOUSE_TRANSPORT_SNAPSHOT (PAYLOAD_JSON, CREATE_TIME, UPDATE_TIME, DEL_FLAG, REMARK)
values (#{payloadJson}, SYSDATE, SYSDATE, 1, #{remark}) values (#{payloadJson}, SYSDATE, SYSDATE, 1, #{remark})
</insert> </insert>
<!-- 覆盖更新仓储运输快照最新一条(实时组装后落库,同月台监测) -->
<update id="updateLatestWarehouseTransportSnapshot">
update NGWL_TEST_BI.BI_WAREHOUSE_TRANSPORT_SNAPSHOT
set PAYLOAD_JSON = #{payloadJson},
UPDATE_TIME = SYSDATE,
REMARK = #{remark}
where DEL_FLAG = 1
and ID = (select max(t.ID) from NGWL_TEST_BI.BI_WAREHOUSE_TRANSPORT_SNAPSHOT t where t.DEL_FLAG = 1)
</update>
<insert id="insertPlatformSurveillanceSnapshot"> <insert id="insertPlatformSurveillanceSnapshot">
insert into NGWL_TEST_BI.BI_PLATFORM_SURVEILLANCE_SNAPSHOT (PAYLOAD_JSON, CREATE_TIME, UPDATE_TIME, DEL_FLAG, REMARK) insert into NGWL_TEST_BI.BI_PLATFORM_SURVEILLANCE_SNAPSHOT (PAYLOAD_JSON, CREATE_TIME, UPDATE_TIME, DEL_FLAG, REMARK)
values (#{payloadJson}, SYSDATE, SYSDATE, 1, #{remark}) values (#{payloadJson}, SYSDATE, SYSDATE, 1, #{remark})
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mhd.bi.domain.biStaticField.repository.mapper.BiStaticFieldMapper">
<!-- 业务静态字段配置(YMS 库):按字段编码查询全部有效配置
含 GLOBAL / FLOOR / WINDOW 各作用域,由程序按「WINDOW > FLOOR > GLOBAL」优先级取值 -->
<select id="queryByCode" resultType="com.mhd.bi.domain.biStaticField.repository.po.BiStaticFieldPO">
SELECT
FIELD_CODE AS fieldCode,
FIELD_NAME AS fieldName,
SCOPE_TYPE AS scopeType,
SCOPE_CODE AS scopeCode,
FIELD_VALUE AS fieldValue,
UNIT AS unit
FROM NGWL_TEST_YMS.QMS_STATIC_FIELD
WHERE DEL_FLAG = 1
AND FIELD_CODE = #{fieldCode}
</select>
<!-- 批量按字段编码查询 -->
<select id="queryByCodes" resultType="com.mhd.bi.domain.biStaticField.repository.po.BiStaticFieldPO">
SELECT
FIELD_CODE AS fieldCode,
FIELD_NAME AS fieldName,
SCOPE_TYPE AS scopeType,
SCOPE_CODE AS scopeCode,
FIELD_VALUE AS fieldValue,
UNIT AS unit
FROM NGWL_TEST_YMS.QMS_STATIC_FIELD
WHERE DEL_FLAG = 1
AND FIELD_CODE IN
<foreach collection="fieldCodes" item="c" open="(" separator="," close=")">
#{c}
</foreach>
</select>
<!-- 查询全部有效配置 -->
<select id="queryAll" resultType="com.mhd.bi.domain.biStaticField.repository.po.BiStaticFieldPO">
SELECT
FIELD_CODE AS fieldCode,
FIELD_NAME AS fieldName,
SCOPE_TYPE AS scopeType,
SCOPE_CODE AS scopeCode,
FIELD_VALUE AS fieldValue,
UNIT AS unit
FROM NGWL_TEST_YMS.QMS_STATIC_FIELD
WHERE DEL_FLAG = 1
</select>
</mapper>
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mhd.bi.domain.biWarehouseTransport.repository.mapper.BiWarehouseTransportRealDataMapper">
<!-- 仓储订单量:本年度 / 本季度 / 本月度 = 入库业务单 + 出库业务单 数量合计
统计范围:全部组织
口径:del_flag = 1(有效);本季度用「月份向上取整 / 3」判断同季度 -->
<select id="queryWarehouseOrderStat" resultType="com.mhd.bi.domain.biWarehouseTransport.repository.po.RealWarehouseOrderStatPO">
SELECT
(SELECT NVL(SUM(c), 0) FROM (
SELECT COUNT(1) AS c FROM NGWL_TEST_WMS.STOCK_IN_ORDER
WHERE del_flag = 1 AND TO_CHAR(CREATE_TIME, 'YYYY') = TO_CHAR(SYSDATE, 'YYYY')
UNION ALL
SELECT COUNT(1) FROM NGWL_TEST_WMS.STOCK_OUT_ORDER
WHERE del_flag = 1 AND TO_CHAR(CREATE_TIME, 'YYYY') = TO_CHAR(SYSDATE, 'YYYY')
)) AS currentYearOrders,
(SELECT NVL(SUM(c), 0) FROM (
SELECT COUNT(1) AS c FROM NGWL_TEST_WMS.STOCK_IN_ORDER
WHERE del_flag = 1 AND TO_CHAR(CREATE_TIME, 'YYYY') = TO_CHAR(SYSDATE, 'YYYY')
AND CEIL(TO_NUMBER(TO_CHAR(CREATE_TIME, 'MM')) / 3) = CEIL(TO_NUMBER(TO_CHAR(SYSDATE, 'MM')) / 3)
UNION ALL
SELECT COUNT(1) FROM NGWL_TEST_WMS.STOCK_OUT_ORDER
WHERE del_flag = 1 AND TO_CHAR(CREATE_TIME, 'YYYY') = TO_CHAR(SYSDATE, 'YYYY')
AND CEIL(TO_NUMBER(TO_CHAR(CREATE_TIME, 'MM')) / 3) = CEIL(TO_NUMBER(TO_CHAR(SYSDATE, 'MM')) / 3)
)) AS currentQuarterOrders,
(SELECT NVL(SUM(c), 0) FROM (
SELECT COUNT(1) AS c FROM NGWL_TEST_WMS.STOCK_IN_ORDER
WHERE del_flag = 1 AND TO_CHAR(CREATE_TIME, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM')
UNION ALL
SELECT COUNT(1) FROM NGWL_TEST_WMS.STOCK_OUT_ORDER
WHERE del_flag = 1 AND TO_CHAR(CREATE_TIME, 'YYYY-MM') = TO_CHAR(SYSDATE, 'YYYY-MM')
)) AS currentMonthOrders
FROM DUAL
</select>
<!-- 今日订单数量:OMS 运输业务单,今日创建,全部组织 -->
<select id="countTodayTransportOrders" resultType="java.lang.Long">
SELECT COUNT(1)
FROM NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER
WHERE CREATE_TIME IS NOT NULL
AND TRUNC(CREATE_TIME) = TRUNC(SYSDATE)
AND IS_DELETE = 0
</select>
<!-- 今日运输车次:TMS 运输单(运单),今日创建,全部组织
口径:一个单 = 一个车次,直接按单计数(不做单号去重,避免单号为空被漏计) -->
<select id="countTodayTransportTrips" resultType="java.lang.Long">
SELECT COUNT(1)
FROM NGWL_TEST_WLHY.TMS_TRANSPORT_NOTE
WHERE CREATE_TIME IS NOT NULL
AND TRUNC(CREATE_TIME) = TRUNC(SYSDATE)
AND (IS_DELETE IS NULL OR IS_DELETE = 0)
</select>
<!-- 客户贡献排名:OMS 运输业务单,近半年按货主(APPOINT_SHIPPER)聚合订单数,取前 5 名
统计范围:全部组织;口径:一个单 = 一单,COUNT(1) -->
<select id="queryCustomerRankTop5" resultType="com.mhd.bi.domain.biWarehouseTransport.repository.po.RealCustomerRankPO">
SELECT * FROM (
SELECT
APPOINT_SHIPPER AS customerName,
CEIL(COUNT(1)) AS customerAmount
FROM NGWL_TEST_OMS.BUSINESS_DOCUMENT_ORDER
WHERE CREATE_TIME IS NOT NULL
AND CREATE_TIME &gt;= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -5)
AND IS_DELETE = 0
AND APPOINT_SHIPPER IS NOT NULL
AND APPOINT_SHIPPER != ''
GROUP BY APPOINT_SHIPPER
ORDER BY COUNT(1) DESC
)
WHERE ROWNUM &lt;= 5
</select>
</mapper>
@@ -26,6 +26,7 @@ import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
// 目标地址由 nacos 配置项 feign.url.mhd-third-party-service 指定,未配置时按服务名走服务发现
@FeignClient(value = "mhd-third-party-service",url = "http://10.102.192.31:8012", configuration = FeignAutoConfiguration.class) @FeignClient(value = "mhd-third-party-service",url = "http://10.102.192.31:8012", configuration = FeignAutoConfiguration.class)
public interface ThirdPartyServiceFeign { public interface ThirdPartyServiceFeign {
@@ -13,16 +13,16 @@ spring:
cloud: cloud:
nacos: nacos:
discovery: discovery:
# server-addr: 127.0.0.1:8848 server-addr: 127.0.0.1:8848
server-addr: 10.33.0.129:6010 # server-addr: 10.33.0.129:6010
# server-addr: 10.102.192.31:6848 # server-addr: 10.102.192.31:6848
username: nacos username: nacos
password: manhuoda@2023 password: manhuoda@2023
#线上正式环境 #线上正式环境
# server-addr: 10.102.192.105:6848 # server-addr: 10.102.192.105:6848
config: config:
# server-addr: 127.0.0.1:8848 server-addr: 127.0.0.1:8848
server-addr: 10.33.0.129:6010 # server-addr: 10.33.0.129:6010
# server-addr: 10.102.192.31:6848 # server-addr: 10.102.192.31:6848
username: nacos username: nacos
password: manhuoda@2023 password: manhuoda@2023
@@ -97,6 +97,7 @@ import com.mhd.system.api.model.LoginUser;
import com.mhd.user.interfaces.dto.addDTO.UserDriverDTO; import com.mhd.user.interfaces.dto.addDTO.UserDriverDTO;
import com.mhd.user.interfaces.dto.queryDTO.UserDocumentWarningQueryDTO; import com.mhd.user.interfaces.dto.queryDTO.UserDocumentWarningQueryDTO;
import com.mhd.user.interfaces.dto.updateDTO.UpdateHasDefaultAddressDTO; import com.mhd.user.interfaces.dto.updateDTO.UpdateHasDefaultAddressDTO;
import com.mhd.user.interfaces.dto.updateDTO.PasswordChangeDTO;
import com.mhd.user.interfaces.facadeApi.Po.UserDriverCountPo; import com.mhd.user.interfaces.facadeApi.Po.UserDriverCountPo;
import com.mhd.user.interfaces.vo.QueryAllShipperInfoVo; import com.mhd.user.interfaces.vo.QueryAllShipperInfoVo;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -214,6 +215,61 @@ public class UserApplicationService {
@Autowired @Autowired
private UserShipperMapper userShipperMapper; private UserShipperMapper userShipperMapper;
/**
* 修改密码 - 同步 wlhy yms
*/
@Transactional(rollbackFor = Exception.class)
public void changePassword(PasswordChangeDTO passwordChangeDTO) {
// 1. 获取当前登录用户
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
throw new DigitalLogisticsException(UserError.TIMEOUT);
}
Long userId = loginUser.getUserPo().getUserId();
if (userId == null) {
throw new ServiceException("用户未登录");
}
// 2. 查询用户信息
UserPo userPO = userDomainService.selectByUserId(userId);
UserEntity userEntity = new UserEntity();
BeanUtils.copyProperties(userPO,userEntity);
if (ObjectUtil.isNull(userEntity)) {
throw new ServiceException("用户不存在");
}
// 3. 解密并加密新密码
String salt = userEntity.getUserSalt();
String newPassword = decrypt(passwordChangeDTO.getNewPassword());
String encodedPassword = PasswordUtil.encrypt(userEntity.getUserAccount(), newPassword, salt);
// 4. 更新本地密码
UserDO userDO = new UserDO();
userDO.setUserId(userId);
userDO.setUserPassword(encodedPassword);
userDomainService.updateUser(userDO);
// 5. 同步到网货平台wlhy
try {
userEntity.setUserPassword(encodedPassword);
wlhyDomainService.addOrEditPlatformUser(userEntity);
} catch (Exception e) {
log.warn("同步密码到网货平台失败,但不影响主流程:{}", e.getMessage());
}
// 6. 同步到 YMS 平台
try {
Integer isQueue = userMapper.getOrdIsQueue(userEntity.getOrganizationId());
if (isQueue != null && isQueue == 1) {
userEntity.setUserPassword(newPassword);
ymsDomainService.addOrEditYmsUser(userEntity);
}
} catch (Exception e) {
log.warn("同步密码到 YMS 平台失败,但不影响主流程:{}", e.getMessage());
}
}
/** /**
* @Description 查询用户列表 * @Description 查询用户列表
* @Author Alex * @Author Alex
@@ -1971,6 +2027,11 @@ public class UserApplicationService {
if (StrUtil.isEmpty(userDO.getUserName())) { if (StrUtil.isEmpty(userDO.getUserName())) {
userDO.setUserName("用户" + userDO.getUserPhone().substring(userDO.getUserPhone().length() - 4)); userDO.setUserName("用户" + userDO.getUserPhone().substring(userDO.getUserPhone().length() - 4));
} }
//密码为空则给默认密码
if (StrUtil.isEmpty(userDO.getUserPassword())) {
String defaultPassword = "Aa123456";
userDO.setUserPassword(PasswordUtil.encrypt(userDO.getUserAccount(), defaultPassword, userDO.getUserSalt()));
}
UserEntity userEntity = userDomainService.addUser(userDO); UserEntity userEntity = userDomainService.addUser(userDO);
//给小程序注册用户分配默认普通用户角色must本组织没有则找上级 //给小程序注册用户分配默认普通用户角色must本组织没有则找上级
userEntity.setUserPassword("Aa123456"); // 三方同步需要密码 userEntity.setUserPassword("Aa123456"); // 三方同步需要密码
@@ -100,6 +100,7 @@ public class UserYmsDomainService {
ymsSysUserDTO.setPhonenumber(userEntity.getUserPhone()); ymsSysUserDTO.setPhonenumber(userEntity.getUserPhone());
ymsSysUserDTO.setAvatar(userEntity.getAvatar()); ymsSysUserDTO.setAvatar(userEntity.getAvatar());
ymsSysUserDTO.setUserAccountType(userEntity.getUserAccountType()); ymsSysUserDTO.setUserAccountType(userEntity.getUserAccountType());
ymsSysUserDTO.setPassword(userEntity.getUserPassword());
if (StringUtils.isNotBlank(userEntity.getRoleCode())){ if (StringUtils.isNotBlank(userEntity.getRoleCode())){
ymsSysUserDTO.setRoleCodes(Arrays.asList(userEntity.getRoleCode().split(","))); ymsSysUserDTO.setRoleCodes(Arrays.asList(userEntity.getRoleCode().split(",")));
@@ -0,0 +1,20 @@
package com.mhd.user.interfaces.dto.updateDTO;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 修改密码 DTO
* @author AI
* @date 2026/09/10
*/
@Data
public class PasswordChangeDTO {
/**
* 新密码
*/
@NotBlank(message = "新密码不能为空")
private String newPassword;
}
@@ -32,6 +32,7 @@ import com.mhd.common.security.annotation.InnerAuth;
import com.mhd.common.security.service.TokenService; import com.mhd.common.security.service.TokenService;
import com.mhd.system.api.model.LoginUser; import com.mhd.system.api.model.LoginUser;
import com.mhd.user.interfaces.dto.queryDTO.UserDocumentWarningQueryDTO; import com.mhd.user.interfaces.dto.queryDTO.UserDocumentWarningQueryDTO;
import com.mhd.user.interfaces.dto.updateDTO.PasswordChangeDTO;
import com.mhd.user.interfaces.dto.updateDTO.UpdateHasDefaultAddressDTO; import com.mhd.user.interfaces.dto.updateDTO.UpdateHasDefaultAddressDTO;
import com.mhd.user.interfaces.vo.QueryAllShipperInfoVo; import com.mhd.user.interfaces.vo.QueryAllShipperInfoVo;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@@ -1037,4 +1038,16 @@ public class UserAPI extends BaseController {
public R<String> finShipperEnterpriseNamedByUserId(@PathVariable("userId") Long userId) { public R<String> finShipperEnterpriseNamedByUserId(@PathVariable("userId") Long userId) {
return R.ok(userApplicationService.finShipperEnterpriseNamedByUserId(userId)); return R.ok(userApplicationService.finShipperEnterpriseNamedByUserId(userId));
} }
/**
* 修改密码 - 同步 wlhy 与 yms
* 根据当前登录用户判断是谁修改,只传新密码,不需要校验旧密码
*/
@Log(title = "用户中台管理 - 修改密码", description ="修改当前登录用户密码",businessType = BusinessType.UPDATE)
@ApiOperation("修改密码")
@PostMapping("/changePassword")
public AjaxResult changePassword(@RequestBody PasswordChangeDTO passwordChangeDTO) {
userApplicationService.changePassword(passwordChangeDTO);
return AjaxResult.success();
}
} }
@@ -18,15 +18,15 @@ spring:
cloud: cloud:
nacos: nacos:
discovery: discovery:
# server-addr: 127.0.0.1:8848 server-addr: 127.0.0.1:8848
# server-addr: 10.33.0.129:6010 # server-addr: 10.33.0.129:6010
server-addr: 10.102.192.31:6848 # server-addr: 10.102.192.31:6848
username: nacos username: nacos
password: manhuoda@2023 password: manhuoda@2023
config: config:
# server-addr: 127.0.0.1:8848 server-addr: 127.0.0.1:8848
# server-addr: 10.33.0.129:6010 # server-addr: 10.33.0.129:6010
server-addr: 10.102.192.31:6848 # server-addr: 10.102.192.31:6848
username: nacos username: nacos
password: manhuoda@2023 password: manhuoda@2023
group: DEFAULT_GROUP # 默认分组就是DEFAULT_GROUP,如果使用默认分组可以不配置 group: DEFAULT_GROUP # 默认分组就是DEFAULT_GROUP,如果使用默认分组可以不配置
@@ -49,4 +49,8 @@ feign:
readTimeout: 120000 readTimeout: 120000
mhd-third-party-service: mhd-third-party-service:
connectTimeout: 10000 connectTimeout: 10000
readTimeout: 120000 readTimeout: 120000
#wechat:
# miniapp:
# appId: wxc9dd2810a77e4e30
# appSecret: 9354ff1215a82785ef14f2355fbf2185
@@ -22,6 +22,7 @@ import java.util.Map;
/** /**
* 三方服务 Feign * 三方服务 Feign
*/ */
// 目标地址由 nacos 配置项 feign.url.mhd-third-party-service 指定未配置时按服务名走服务发现
@FeignClient(contextId = "thirdPartyServiceFeign",url = "http://10.102.192.31:8012", value = ServiceNameConstants.THIRDPARTY_CENTER, configuration = FeignAutoConfiguration.class) @FeignClient(contextId = "thirdPartyServiceFeign",url = "http://10.102.192.31:8012", value = ServiceNameConstants.THIRDPARTY_CENTER, configuration = FeignAutoConfiguration.class)
public interface ThirdPartyServiceFeign { public interface ThirdPartyServiceFeign {
@@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T; import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.text.ParseException; import java.text.ParseException;
import java.util.List; import java.util.List;
@@ -205,6 +206,7 @@ public class HandoverTaskOrderApplicationService {
* @param handoverTaskOrderDO * @param handoverTaskOrderDO
* @return Boolean * @return Boolean
*/ */
@Transactional(rollbackFor = Exception.class)
public Boolean completeHandover(HandoverTaskOrderDO handoverTaskOrderDO){ public Boolean completeHandover(HandoverTaskOrderDO handoverTaskOrderDO){
Boolean result = handoverTaskOrderDomainService.completeHandover(handoverTaskOrderDO); Boolean result = handoverTaskOrderDomainService.completeHandover(handoverTaskOrderDO);
List<MaterialInventoryParamDO> clearoutParamDOs = handoverTaskOrderDomainService.getLastClearoutParamDOs(); List<MaterialInventoryParamDO> clearoutParamDOs = handoverTaskOrderDomainService.getLastClearoutParamDOs();
@@ -573,10 +573,11 @@ public class MaterialBaseInfoApplicationService {
normalizeImportBarCodeWhenDuplicateOfHs(row); normalizeImportBarCodeWhenDuplicateOfHs(row);
fillIdsFromExistingMasterData(row); fillIdsFromExistingMasterData(row);
fillOrganizationFromShipperIfNeeded(row); fillOrganizationFromShipperIfNeeded(row);
if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) { // 登录组织优先以登录账号的组织为准覆盖货主解析带来的组织避免用货主组织导致包装规格等主数据查不到
if (isValidOrgId(loginOrgId)) {
row.setOrganizationId(loginOrgId); row.setOrganizationId(loginOrgId);
} }
if (row.getTopOrganizationId() == null && isValidOrgId(loginTopOrgId)) { if (isValidOrgId(loginTopOrgId)) {
row.setTopOrganizationId(loginTopOrgId); row.setTopOrganizationId(loginTopOrgId);
} }
fillOrganizationName(row, loginUser); fillOrganizationName(row, loginUser);
@@ -128,10 +128,11 @@ public class MaterialBaseInfoAsyncService {
materialBaseInfoApplicationService.normalizeImportBarCodeWhenDuplicateOfHs(row); materialBaseInfoApplicationService.normalizeImportBarCodeWhenDuplicateOfHs(row);
materialBaseInfoApplicationService.fillIdsFromExistingMasterData(row); materialBaseInfoApplicationService.fillIdsFromExistingMasterData(row);
materialBaseInfoApplicationService.fillOrganizationFromShipperIfNeeded(row); materialBaseInfoApplicationService.fillOrganizationFromShipperIfNeeded(row);
if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) { // 登录组织优先以登录账号的组织为准覆盖货主解析带来的组织避免用货主组织(如2841)导致包装规格等主数据查不到
if (isValidOrgId(loginOrgId)) {
row.setOrganizationId(loginOrgId); row.setOrganizationId(loginOrgId);
} }
if (row.getTopOrganizationId() == null && isValidOrgId(loginTopOrgId)) { if (isValidOrgId(loginTopOrgId)) {
row.setTopOrganizationId(loginTopOrgId); row.setTopOrganizationId(loginTopOrgId);
} }
materialBaseInfoApplicationService.fillOrganizationName(row, loginUser); materialBaseInfoApplicationService.fillOrganizationName(row, loginUser);
@@ -2040,21 +2040,71 @@ public class StockInOrderApplicationService {
r.getMaterialCode(), topOrganizationId, shipperId); r.getMaterialCode(), topOrganizationId, shipperId);
List<MaterialBaseInfoPO> materialList = materialBaseInfoService.queryList(queryDO); List<MaterialBaseInfoPO> materialList = materialBaseInfoService.queryList(queryDO);
log.info("料号查询结果,查询到 {} 条记录", materialList != null ? materialList.size() : 0); log.info("料号查询结果,查询到 {} 条记录", materialList != null ? materialList.size() : 0);
MaterialBaseInfoPO materialBaseInfoPO = null; MaterialBaseInfoPO materialBaseInfoPO = pickMaterialByCode(materialList, r.getMaterialCode(), shipperId, order.getShipperName());
if (materialList != null && !materialList.isEmpty()) { // 兜底按名称解析出的shipperId可能与物料档案shipper_id不一致系统中存在同名货主账号
materialBaseInfoPO = materialList.get(0); // 按user_name精确匹配解析到了另一个同名列货主此时去掉货主ID条件再查一次
// 用物料档案冗余的shipper_name与Excel客户名称等值匹配
if (materialBaseInfoPO == null && StringUtils.isNotBlank(order.getShipperName())) {
MaterialBaseInfoDO fallbackDO = new MaterialBaseInfoDO();
fallbackDO.setMaterialCode(r.getMaterialCode());
if (topOrganizationId != null) {
fallbackDO.setTopOrganizationId(topOrganizationId);
}
List<MaterialBaseInfoPO> fallbackList = materialBaseInfoService.queryList(fallbackDO);
materialBaseInfoPO = pickMaterialByCode(fallbackList, r.getMaterialCode(), null, order.getShipperName());
if (materialBaseInfoPO != null) {
log.warn("导入料号校验:按解析shipperId未匹配到物料,已按档案货主名称兜底匹配。料号: [{}], 解析shipperId: {}, 档案shipperId: {}, 档案shipperName: {}",
r.getMaterialCode(), shipperId, materialBaseInfoPO.getShipperId(), materialBaseInfoPO.getShipperName());
// 以档案货主为准纠正单据货主信息避免后续shipperParam按错误的shipperId反查覆盖货主名称
if (materialBaseInfoPO.getShipperId() != null) {
shipperId = materialBaseInfoPO.getShipperId();
order.setShipperId(shipperId);
}
if (StringUtils.isNotBlank(materialBaseInfoPO.getShipperCode())) {
order.setShipperCode(materialBaseInfoPO.getShipperCode());
}
if (StringUtils.isNotBlank(materialBaseInfoPO.getShipperName())) {
order.setShipperName(materialBaseInfoPO.getShipperName());
}
}
}
if (materialBaseInfoPO != null) {
materialBaseInfoId = materialBaseInfoPO.getMaterialBaseInfoId(); materialBaseInfoId = materialBaseInfoPO.getMaterialBaseInfoId();
log.info("通过料号查询到物料基础信息,料号: {}, 物料ID: {}, 货主ID: {}", log.info("通过料号查询到物料基础信息,料号: {}, 物料ID: {}, 货主ID: {}",
r.getMaterialCode(), materialBaseInfoId, shipperId); r.getMaterialCode(), materialBaseInfoId, shipperId);
} else { } else {
// 如果提供了料号但查询不到或不属于该货主直接报错不允许导入 // 如果提供了料号但查询不到或不属于该货主直接报错不允许导入
String errorMsg = String.format("导入失败:该货主下不存在该料号(货主名称=%s,料号=%s)。请确保该料号已在物料管理中创建,且属于该货主", // 诊断对照查询仅用于日志定位不影响流程去掉货主ID条件再查一次
order.getShipperName(), r.getMaterialCode()); // 用于区分两类原因A-按名称解析出的shipperId与物料档案shipper_id不一致货主错配
// B-登录人topOrganizationId与物料档案top_organization_id不一致组织口径不一致
try {
MaterialBaseInfoDO diagDO = new MaterialBaseInfoDO();
diagDO.setMaterialCode(r.getMaterialCode());
if (topOrganizationId != null) {
diagDO.setTopOrganizationId(topOrganizationId);
}
List<MaterialBaseInfoPO> diagList = materialBaseInfoService.queryList(diagDO);
if (diagList != null && !diagList.isEmpty()) {
MaterialBaseInfoPO diagPo = diagList.get(0);
log.error("导入料号校验诊断:去掉货主条件后可查到物料,判定为货主解析错配(原因A)。料号: [{}], 解析shipperId: {}, 登录topOrganizationId: {}, 物料ID: {}, 档案shipperId: {}, 档案shipperName: {}, 档案topOrganizationId: {}",
r.getMaterialCode(), shipperId, topOrganizationId,
diagPo.getMaterialBaseInfoId(), diagPo.getShipperId(), diagPo.getShipperName(), diagPo.getTopOrganizationId());
} else {
log.error("导入料号校验诊断:去掉货主条件后仍查不到物料,判定为组织口径不一致(原因B)。料号: [{}], 解析shipperId: {}, 登录topOrganizationId: {}",
r.getMaterialCode(), shipperId, topOrganizationId);
}
} catch (Exception diagEx) {
log.warn("导入料号校验诊断查询异常,料号: [{}], 错误: {}", r.getMaterialCode(), diagEx.getMessage());
}
String errorMsg = String.format("导入失败:该货主下不存在该料号(货主名称=%s,料号=%s,解析shipperId=%s,登录topOrganizationId=%s)。请确保该料号已在物料管理中创建,且属于该货主",
order.getShipperName(), r.getMaterialCode(),
shipperId == null ? "" : shipperId, topOrganizationId == null ? "" : topOrganizationId);
log.error(errorMsg); log.error(errorMsg);
throw new ServiceException(errorMsg); throw new ServiceException(errorMsg);
} }
// 校验料号与规格型号/SKU码一致性按料号查到档案后逐项比对任何一项不符直接报对应错误 // 校验料号与规格型号/SKU码一致性按料号查到档案后逐项比对任何一项不符直接报对应错误
// 双方trim后必须完全一致Excel留空而档案有值或Excel填了值而档案为空均视为不一致报错 // 规格型号trim后双方必须完全一致Excel留空而档案有值或Excel填了值而档案为空均视为不一致报错
// SKU码档案条码barCode为空时跳过比对存量档案普遍未维护条码档案有值时仍严格比对
String excelSpec = StringUtils.trimToEmpty(r.getSpecificationModel()); String excelSpec = StringUtils.trimToEmpty(r.getSpecificationModel());
String dbSpec = StringUtils.trimToEmpty(materialBaseInfoPO.getSpecificationModel()); String dbSpec = StringUtils.trimToEmpty(materialBaseInfoPO.getSpecificationModel());
if (!excelSpec.equals(dbSpec)) { if (!excelSpec.equals(dbSpec)) {
@@ -2064,7 +2114,7 @@ public class StockInOrderApplicationService {
} }
String excelSku = StringUtils.trimToEmpty(r.getSkuCode()); String excelSku = StringUtils.trimToEmpty(r.getSkuCode());
String dbSku = StringUtils.trimToEmpty(materialBaseInfoPO.getBarCode()); String dbSku = StringUtils.trimToEmpty(materialBaseInfoPO.getBarCode());
if (!excelSku.equals(dbSku)) { if (StringUtils.isNotEmpty(dbSku) && !excelSku.equals(dbSku)) {
throw new ServiceException(String.format("导入失败:料号[%s](商品名称:%s)SKU码与物料档案不一致(Excel:%s / 档案:%s", throw new ServiceException(String.format("导入失败:料号[%s](商品名称:%s)SKU码与物料档案不一致(Excel:%s / 档案:%s",
r.getMaterialCode(), r.getMaterialName(), r.getMaterialCode(), r.getMaterialName(),
excelSku.isEmpty() ? "" : excelSku, dbSku.isEmpty() ? "" : dbSku)); excelSku.isEmpty() ? "" : excelSku, dbSku.isEmpty() ? "" : dbSku));
@@ -2398,6 +2448,46 @@ public class StockInOrderApplicationService {
return oid != null && loginOrganizationId.equals(oid); return oid != null && loginOrganizationId.equals(oid);
} }
/**
* 导入校验从按料号SQL为LIKE模糊查出的物料列表中挑选匹配记录
* 优先级料号精确等值优先于模糊命中避免子串料号误匹配
* 同级内优先 shipperId 精确匹配其次 shipperName 等值匹配最后取排序第一条与原 get(0) 行为一致
*/
private MaterialBaseInfoPO pickMaterialByCode(List<MaterialBaseInfoPO> materialList, String materialCode, Long shipperId, String shipperName) {
if (materialList == null || materialList.isEmpty()) {
return null;
}
String code = StringUtils.trimToEmpty(materialCode);
String name = StringUtils.trimToEmpty(shipperName);
MaterialBaseInfoPO exactCodeFirst = null;
MaterialBaseInfoPO anyFirst = null;
// 第一轮料号精确等值中优先 shipperId 精确匹配
for (MaterialBaseInfoPO po : materialList) {
if (anyFirst == null) {
anyFirst = po;
}
if (StringUtils.isEmpty(code) || !code.equals(StringUtils.trimToEmpty(po.getMaterialCode()))) {
continue;
}
if (exactCodeFirst == null) {
exactCodeFirst = po;
}
if (shipperId != null && shipperId.equals(po.getShipperId())) {
return po;
}
}
// 第二轮料号精确等值中按档案货主名称等值匹配
if (StringUtils.isNotEmpty(name)) {
for (MaterialBaseInfoPO po : materialList) {
if (StringUtils.isNotEmpty(code) && code.equals(StringUtils.trimToEmpty(po.getMaterialCode()))
&& name.equals(StringUtils.trimToEmpty(po.getShipperName()))) {
return po;
}
}
}
return exactCodeFirst != null ? exactCodeFirst : anyFirst;
}
/** /**
* @description 根据货主名称或编号获取货主ID用于导入时提前获取货主ID * @description 根据货主名称或编号获取货主ID用于导入时提前获取货主ID
* @param shipperName 货主名称 * @param shipperName 货主名称
@@ -994,7 +994,7 @@ public class MaterialInventoryImpl extends ServiceImpl<MaterialInventoryMapper,
MaterialInventory materialInventory = new MaterialInventory(); MaterialInventory materialInventory = new MaterialInventory();
materialInventoryPO.setUpdateTime(new Date()); materialInventoryPO.setUpdateTime(new Date());
com.mhd.common.core.utils.bean.BeanUtils.copyProperties(materialInventoryPO, materialInventory); com.mhd.common.core.utils.bean.BeanUtils.copyProperties(materialInventoryPO, materialInventory);
materialInventoryMapper.updateById(materialInventory); materialInventoryMapper.updateById(materialInventoryPO);
} }
} }
private void qxff(BigDecimal netWeight, BigDecimal grossWeight, BigDecimal volume, BigDecimal area, BigDecimal quantity, Long materialInventoryId) { private void qxff(BigDecimal netWeight, BigDecimal grossWeight, BigDecimal volume, BigDecimal area, BigDecimal quantity, Long materialInventoryId) {
@@ -1092,7 +1092,7 @@ public class MaterialInventoryImpl extends ServiceImpl<MaterialInventoryMapper,
materialInventoryPO.setUpdateTime(new Date()); materialInventoryPO.setUpdateTime(new Date());
MaterialInventory materialInventory = new MaterialInventory(); MaterialInventory materialInventory = new MaterialInventory();
com.mhd.common.core.utils.bean.BeanUtils.copyProperties(materialInventoryPO, materialInventory); com.mhd.common.core.utils.bean.BeanUtils.copyProperties(materialInventoryPO, materialInventory);
materialInventoryMapper.updateById(materialInventory); materialInventoryMapper.updateById(materialInventoryPO);
Long warehouseId = materialInventoryPO.getWarehouseId(); Long warehouseId = materialInventoryPO.getWarehouseId();
boolean needPush = false; boolean needPush = false;
@@ -860,7 +860,9 @@ public class StockShelfOrderDomainService {
//修改入库单状态: 1-已创建 2-已审核 3-收货中 4-上架中 5-已入库 //修改入库单状态: 1-已创建 2-已审核 3-收货中 4-上架中 5-已入库
updateStockInOrder(stockShelfOrderPO, 5); updateStockInOrder(stockShelfOrderPO, 5);
//上架修改物料库存 //上架修改物料库存
List<ShelfMaterialDetail> shelfMaterialDetailList = shelfMaterialDetailService.list(new QueryWrapper<ShelfMaterialDetail>().lambda().eq(ShelfMaterialDetail::getShelfOrderNumber, stockShelfOrderPO.getShelfOrderNumber())); List<ShelfMaterialDetail> shelfMaterialDetailList = shelfMaterialDetailService.list(new QueryWrapper<ShelfMaterialDetail>().lambda()
.eq(ShelfMaterialDetail::getShelfOrderNumber, stockShelfOrderPO.getShelfOrderNumber())
.eq(ShelfMaterialDetail::getDelFlag, 1));
//修改库存 //修改库存
StockShelfOrderDO stockShelfOrderDO = new StockShelfOrderDO(); StockShelfOrderDO stockShelfOrderDO = new StockShelfOrderDO();
BeanUtils.copyProperties(stockShelfOrderPO, stockShelfOrderDO); BeanUtils.copyProperties(stockShelfOrderPO, stockShelfOrderDO);