diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java index 6889c728b..91abc34d7 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java @@ -19,7 +19,8 @@ import java.util.Set; /** * bms财务结算系统feign调用接口 */ -@FeignClient(contextId = "BmsService", value = ServiceNameConstants.BMS_SERVICE, url = "http://10.33.0.109:8012",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) +// 目标地址由 nacos 配置项 feign.url.mhd-bms-service 指定,未配置时按服务名走服务发现 +@FeignClient(contextId = "BmsService", value = ServiceNameConstants.BMS_SERVICE, url = "http://10.33.0.109:8019",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) public interface BmsServiceFeign { diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java index 7c4b7082f..e78d253f3 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java @@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestParam; import java.util.List; import java.util.concurrent.CompletableFuture; +// 目标地址由 nacos 配置项 feign.url.mhd-oms-service 指定,未配置时按服务名走服务发现 @FeignClient(contextId = "OmsService", value = ServiceNameConstants.OMS_SERVICE,url = "http://10.33.0.99:8017",fallbackFactory = RemoteOmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) public interface OmsServiceFeign { diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java index aa3f1f09b..1935e8284 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java @@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; +// 目标地址由 nacos 配置项 feign.url.mhd-wlhy-service 指定,未配置时按服务名走服务发现 @FeignClient(contextId = "commonWlhyServiceFeign",value = ServiceNameConstants.WLHY_SERVICE,url = "http://10.33.0.129:8014",configuration = FeignAutoConfiguration.class) public interface WlhyServiceFeign { @@ -535,7 +536,12 @@ public interface WlhyServiceFeign { */ @PostMapping("/feign/order/add") public AjaxResult add(@RequestBody TmsOrderAddDTO tmsOrderAddDTO); - + /** + * 修改运单(OMS调整业务单后同步修改TMS订单) + * @param tmsOrderAddDTO 需携带 TMS 订单 id 定位 + */ + @PostMapping("/feign/order/update") + public AjaxResult update(@RequestBody TmsOrderAddDTO tmsOrderAddDTO); @GetMapping("/feign/order/cancelIssueDocuments") public AjaxResult cancelIssueDocuments(@RequestParam(name="executeOrderId") String executeOrderId); diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java index e7eaca654..121a73be9 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java @@ -21,7 +21,7 @@ import java.util.Map; * @description: TODO * @date 2024/5/10 8:58 **/ -@FeignClient(contextId = "remoteWmsServiceFeign",value = ServiceNameConstants.WMS_SERVICE,fallbackFactory = RemoteWmsFallbackFactory.class, configuration = FeignAutoConfiguration.class) +@FeignClient(contextId = "remoteWmsServiceFeign",value = ServiceNameConstants.WMS_SERVICE,url = "http://10.33.0.109:8016",fallbackFactory = RemoteWmsFallbackFactory.class, configuration = FeignAutoConfiguration.class) public interface WmsServiceFeign { /** diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/XxlJobServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/XxlJobServiceFeign.java index 7010ba691..74eef76d8 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/XxlJobServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/XxlJobServiceFeign.java @@ -13,7 +13,8 @@ import org.springframework.web.bind.annotation.*; 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.33.0.99:8020",fallbackFactory = RemoteXxlJobFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) public interface XxlJobServiceFeign { diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java index beba5b1af..5f616b8a6 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java @@ -9,6 +9,7 @@ import com.mhd.system.api.feign.FeignAutoConfiguration; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; +// 目标地址由 nacos 配置项 feign.url.mhd-yms-service 指定,未配置时按服务名走服务发现 @FeignClient(contextId = "YmsService", value = ServiceNameConstants.YMS_SERVICE,url = "http://10.33.0.99:8018",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) public interface YmsServiceFeign { diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/factory/RemoteWlhyFallbackFactory.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/factory/RemoteWlhyFallbackFactory.java index 7b401a6ad..9c3a362a2 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/factory/RemoteWlhyFallbackFactory.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/factory/RemoteWlhyFallbackFactory.java @@ -546,6 +546,11 @@ public class RemoteWlhyFallbackFactory implements FallbackFactory queryTaskLoadToday(); /** 今日预约-业务类型统计(按 businessName 分组统计今日预约数量) */ List queryTodayBusinessTypeStats(); + /** 今日作业准时率统计(total 今日预约总数;onTimeCount 今日准时车辆数) */ + RealOnTimeStatPO queryTodayOnTimeStats(); + + /** 任务类型统计(本月 YMS QMS_MAIN_BUSINESS 预约按业务类型 BUSINESS_NAME 分组) */ + List queryMonthTransportOrderByType(); + /** 月台监控-按楼层统计月台车辆(楼层、月台数量、今日预约车辆、当前作业车辆) */ List queryPlatformFloorStats(); diff --git a/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealMonthlyTaskDurationPO.java b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealMonthlyTaskDurationPO.java new file mode 100644 index 000000000..9f815b7ba --- /dev/null +++ b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealMonthlyTaskDurationPO.java @@ -0,0 +1,29 @@ +package com.mhd.bi.domain.biPlatformRealData.repository.po; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 本月任务平均时长 / 完成率统计(真实数据,来自 TEST_NGWL_YMS.QMS_MAIN_BUSINESS) + * avgDurationHours:累计 (EXIT_TIME - ENTRY_TIME) 小时数(仅 ENTRY_TIME/EXIT_TIME 都不为空时参与) + * durationCount:参与平均时长统计的有效条数 + * completedCount:已出场(EXIT_TIME IS NOT NULL)且预约日期在本月的数量 + * totalCount:本月(TARGET_DATE)预约总数量 + */ +@Data +public class RealMonthlyTaskDurationPO implements Serializable { + private static final long serialVersionUID = 1L; + + /** 平均作业时长(小时) */ + private Double avgDurationHours; + + /** 参与平均时长统计的有效条数 */ + private Long durationCount; + + /** 本月已出场数量 */ + private Long completedCount; + + /** 本月预约总数量(按 TARGET_DATE 统计) */ + private Long totalCount; +} \ No newline at end of file diff --git a/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealOnTimeStatPO.java b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealOnTimeStatPO.java new file mode 100644 index 000000000..0178405a7 --- /dev/null +++ b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealOnTimeStatPO.java @@ -0,0 +1,20 @@ +package com.mhd.bi.domain.biPlatformRealData.repository.po; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 今日作业准时率统计(真实数据,来自 TEST_NGWL_YMS.QMS_MAIN_BUSINESS) + * 准时率 = 今日实际进场时间在预约日期当天的车辆数 / 今日预约总车辆数 × 100% + */ +@Data +public class RealOnTimeStatPO implements Serializable { + private static final long serialVersionUID = 1L; + + /** 今日预约总车辆数 */ + private Long total; + + /** 今日准时车辆数(已进场且实际进场日期 = 预约日期) */ + private Long onTimeCount; +} \ No newline at end of file diff --git a/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealTaskTypeStatPO.java b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealTaskTypeStatPO.java new file mode 100644 index 000000000..bf73ec4b9 --- /dev/null +++ b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealTaskTypeStatPO.java @@ -0,0 +1,20 @@ +package com.mhd.bi.domain.biPlatformRealData.repository.po; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 任务类型统计(真实数据,来自 NGWL_TEST_YMS.QMS_MAIN_BUSINESS 预约登记表) + * 按本月预约车辆数(TARGET_DATE 在当前月)以 BUSINESS_NAME 业务类型分组 + */ +@Data +public class RealTaskTypeStatPO implements Serializable { + private static final long serialVersionUID = 1L; + + /** 业务类型名称(qms_main_business.BUSINESS_NAME) */ + private String businessName; + + /** 该业务类型本月预约车辆数 */ + private Long count; +} \ No newline at end of file diff --git a/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealVehicleStatusPO.java b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealVehicleStatusPO.java index 7f2f782a7..dabf754e9 100644 --- a/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealVehicleStatusPO.java +++ b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/repository/po/RealVehicleStatusPO.java @@ -16,6 +16,12 @@ public class RealVehicleStatusPO implements Serializable { /** 当前未签到 */ private Long notCheckedIn; + /** 当前等待作业(FLOW_STATUS IN '1','2','3' 已预约/已签到/已叫号) */ + private Long waiting; + + /** 叫号状态合计:已签到 + 已叫号 + 已过号(FLOW_STATUS IN '2','3','4',预约日期为今天) */ + private Long callStatusTotal; + /** 当前作业中 */ private Long operatingVehicles; diff --git a/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/service/BiPlatformRealDataService.java b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/service/BiPlatformRealDataService.java index 703c71e2a..d96d17022 100644 --- a/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/service/BiPlatformRealDataService.java +++ b/mhd-bi/src/main/java/com/mhd/bi/domain/biPlatformRealData/service/BiPlatformRealDataService.java @@ -9,12 +9,19 @@ import com.mhd.bi.domain.biPlatformRealData.repository.po.RealTaskLoadPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealBusinessTypeStatPO; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformFloorStatPO; 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.RealTaskTypeStatPO; +import com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyTaskDurationPO; import com.mhd.bi.domain.biPlatformSurveillance.repository.mapper.BiPlatformSurveillanceReportMapper; import com.mhd.bi.domain.biSnapshotSync.repository.mapper.BiSnapshotWriteMapper; import com.mhd.bi.domain.biSnapshotSync.support.BiSnapshotWeeklyRandomDataBuilder; 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.TaskLoadItemVO; +import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskTypeStatisticsVO; +import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.TaskTypeCountItemVO; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -91,16 +98,27 @@ public class BiPlatformRealDataService { if (vo.getPlatformOverview() != null) { fp.put("totalPlatforms", vo.getPlatformOverview().getTotalPlatforms()); fp.put("operatingPlatforms", vo.getPlatformOverview().getOperatingPlatforms()); + fp.put("currentOccupancyRate", vo.getPlatformOverview().getCurrentOccupancyRate()); fp.put("todayTasks", vo.getPlatformOverview().getTodayTasks()); } + if (vo.getEfficiencyStatistics() != null) { + fp.put("platformOnTimeRate", vo.getEfficiencyStatistics().getPlatformOnTimeRate()); + } if (vo.getVehicleStatus() != null) { fp.put("todayReservedVehicles", vo.getVehicleStatus().getTodayReservedVehicles()); fp.put("notCheckedIn", vo.getVehicleStatus().getNotCheckedIn()); + fp.put("waiting", vo.getVehicleStatus().getWaiting()); + fp.put("callStatusTotal", vo.getVehicleStatus().getCallStatusTotal()); fp.put("operatingVehicles", vo.getVehicleStatus().getOperatingVehicles()); + + fp.put("completedVehicles", vo.getVehicleStatus().getCompletedVehicles()); } + if (vo.getMonthlyTaskStatistics() != null) { fp.put("taskTotal", vo.getMonthlyTaskStatistics().getTaskTotal()); + fp.put("avgTaskDuration", vo.getMonthlyTaskStatistics().getAvgTaskDuration()); + fp.put("taskCompletionRate", vo.getMonthlyTaskStatistics().getTaskCompletionRate()); } if (vo.getVehicles() != null) { List> vehicles = new ArrayList<>(); @@ -126,6 +144,27 @@ public class BiPlatformRealDataService { } fp.put("taskLoadStatistics", loads); } + if (vo.getTaskTypeStatistics() != null) { + Map m = new LinkedHashMap<>(); + m.put("totalTasks", vo.getTaskTypeStatistics().getTotalTasks()); + if (vo.getTaskTypeStatistics().getTaskTypes() != null) { + List> types = new ArrayList<>(); + for (TaskTypeCountItemVO it : vo.getTaskTypeStatistics().getTaskTypes()) { + Map tm = new LinkedHashMap<>(); + tm.put("type", it.getType()); + tm.put("count", it.getCount()); + types.add(tm); + } + m.put("taskTypes", types); + } + 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); } @@ -166,12 +205,34 @@ public class BiPlatformRealDataService { } } + /** + * 今日作业准时率 = 今日准时车辆数 / 今日有入场时间的车辆数 × 100%(四舍五入取整) + * 准时:实际进入时间 < 预约日期(TARGET_DATE) + 预约结束时间(BOOKING_END_TIME)。 + * 分子分母都只统计有入场时间(ENTRY_TIME 不为空)的车辆;分母为 0 时返回 0。 + */ + public int calcTodayOnTimeRate() { + try { + RealOnTimeStatPO po = biPlatformRealDataMapper.queryTodayOnTimeStats(); + if (po == null) return 0; + long total = po.getTotal() == null ? 0L : po.getTotal(); + long onTime = po.getOnTimeCount() == null ? 0L : po.getOnTimeCount(); + if (total <= 0) return 0; + // 四舍五入到整数百分比(取整) + return (int) Math.round(((double) onTime / (double) total) * 100.0); + } catch (Exception e) { + log.error("【YMS】计算今日作业准时率失败: {}", e.getMessage(), e); + return 0; + } + } + public RealVehicleStatusPO queryVehicleStatusToday() { try { RealVehicleStatusPO po = biPlatformRealDataMapper.queryVehicleStatusToday(); if (po == null) po = new RealVehicleStatusPO(); if (po.getTodayReservedVehicles() == null) po.setTodayReservedVehicles(0L); if (po.getNotCheckedIn() == null) po.setNotCheckedIn(0L); + if (po.getWaiting() == null) po.setWaiting(0L); + if (po.getCallStatusTotal() == null) po.setCallStatusTotal(0L); if (po.getOperatingVehicles() == null) po.setOperatingVehicles(0L); if (po.getCompletedVehicles() == null) po.setCompletedVehicles(0L); return po; @@ -180,10 +241,14 @@ public class BiPlatformRealDataService { RealVehicleStatusPO po = new RealVehicleStatusPO(); po.setTodayReservedVehicles(0L); po.setNotCheckedIn(0L); + po.setWaiting(0L); + po.setCallStatusTotal(0L); po.setOperatingVehicles(0L); po.setCompletedVehicles(0L); return po; } + + } public long countMonthTasks() { @@ -196,6 +261,21 @@ public class BiPlatformRealDataService { } } + /** + * 本月任务平均时长 / 完成率统计: + * - avgDurationHours:累计(EXIT_TIME - ENTRY_TIME)小时数 / 参与数;任一为空的行不参与 + * - taskCompletionRate:已完成(EXIT_TIME 不为空 且 预约日期在本月)/ 本月预约总数量 ×100 + * 任一查询失败均返回 null,由调用方决定是否覆盖模板值。 + */ + public RealMonthlyTaskDurationPO queryMonthTaskStats() { + try { + return biPlatformRealDataMapper.queryMonthTaskStats(); + } catch (Exception e) { + log.error("【YMS】统计本月任务平均时长/完成率失败: {}", e.getMessage(), e); + return null; + } + } + public List queryTaskLoadToday() { try { return biPlatformRealDataMapper.queryTaskLoadToday(); @@ -205,6 +285,35 @@ public class BiPlatformRealDataService { } } + /** + * 任务类型统计:本月(YMS QMS_MAIN_BUSINESS)预约车辆按业务类型 BUSINESS_NAME 分组; + * 返回 {totalTasks: 当月总预约车辆数, taskTypes: [{type: 业务类型名称, count: 该类型预约数}]} + * 按真实 BUSINESS_NAME 分组输出,不固定兜底类别;当月无数据或查询失败时返回空列表。 + */ + public TaskTypeStatisticsVO queryMonthTransportOrderByType() { + List items = new ArrayList<>(); + int total = 0; + try { + List rows = biPlatformRealDataMapper.queryMonthTransportOrderByType(); + if (rows != null) { + for (RealTaskTypeStatPO r : rows) { + if (r == null || r.getBusinessName() == null || r.getCount() == null) { + continue; + } + int c = r.getCount().intValue(); + items.add(TaskTypeCountItemVO.builder().type(r.getBusinessName()).count(c).build()); + total += c; + } + } + } catch (Exception e) { + log.error("【YMS】统计当月预约业务类型失败: {}", e.getMessage(), e); + // 查询失败返回空列表(前端会显示空) + items = new ArrayList<>(); + total = 0; + } + return TaskTypeStatisticsVO.builder().totalTasks(total).taskTypes(items).build(); + } + /** * 今日预约-业务类型统计:按业务类型分组统计今日预约数量。 * 例:今日预约航空打板 3 条 → 返回 {businessName:"航空打板", count:3} @@ -271,29 +380,68 @@ public class BiPlatformRealDataService { template.setVehicles(vehicleVos); // 月台实时概览:真实数据源,无条件覆盖 + // 总月台 / 占用月台 来源:YMS 月台管理列表 QMS_WINDOW_INFO(JOB_STATUS=1 表示占用) + // 月台占用率 = 占用 / 全部月台 + // 今日作业任务 来源:QMS_MAIN_BUSINESS 按预约时间 TARGET_DATE = 今天 的全部数量 + // 作业准时率 = 今日(实际进场时间在预约日期当天的车辆数) / 今日预约车辆总数 × 100% if (template.getPlatformOverview() != null) { - RealPlatformOverviewPO ov = queryPlatformOverview(); - int total = ov.getTotalPlatforms().intValue(); - int occ = ov.getOperatingPlatforms().intValue(); + List platformList = queryPlatformInfoList(); + int total = platformList == null ? 0 : platformList.size(); + int occ = 0; + if (platformList != null) { + for (RealPlatformInfoPO p : platformList) { + if (p != null && p.getJobStatus() != null && p.getJobStatus() == 1) { + occ++; + } + } + } double rate = total > 0 ? Math.round(((double) occ / total) * 100.0 * 100.0) / 100.0 : 0d; + int todayTasks = (int) countTodayTasks(); template.getPlatformOverview().setTotalPlatforms(total); template.getPlatformOverview().setOperatingPlatforms(occ); template.getPlatformOverview().setCurrentOccupancyRate(rate); - template.getPlatformOverview().setTodayTasks((int) countTodayTasks()); + template.getPlatformOverview().setTodayTasks(todayTasks); + + // 作业准时率(实际进入时间 < 预约日期+预约结束时间 的车辆 / 今日预约总数 × 100% 取整) + if (template.getEfficiencyStatistics() != null) { + template.getEfficiencyStatistics().setPlatformOnTimeRate(calcTodayOnTimeRate()); + } } - // 作业车辆状态统计:真实数据源,无条件覆盖 + // 作业车辆状态统计:真实数据源,无条件覆盖(含等待作业 waiting = 今日预约且 已预约/已签到/已叫号) if (template.getVehicleStatus() != null) { RealVehicleStatusPO vs = queryVehicleStatusToday(); template.getVehicleStatus().setTodayReservedVehicles(vs.getTodayReservedVehicles().intValue()); template.getVehicleStatus().setNotCheckedIn(vs.getNotCheckedIn().intValue()); + template.getVehicleStatus().setWaiting(vs.getWaiting().intValue()); + // 叫号状态合计:已签到 + 已叫号 + 已过号(一个字段表示三个状态的总数量) + template.getVehicleStatus().setCallStatusTotal(vs.getCallStatusTotal().intValue()); template.getVehicleStatus().setOperatingVehicles(vs.getOperatingVehicles().intValue()); + + template.getVehicleStatus().setCompletedVehicles(vs.getCompletedVehicles().intValue()); } - // 本月任务统计:taskTotal 有真实数据源无条件覆盖;YoY 等无真实数据源保留模板写死值 + + // 本月任务统计:taskTotal / avgTaskDuration / taskCompletionRate 均有真实数据源,无条件覆盖;YoY 保留模板写死值 if (template.getMonthlyTaskStatistics() != null) { template.getMonthlyTaskStatistics().setTaskTotal((int) countMonthTasks()); + RealMonthlyTaskDurationPO monthStats = queryMonthTaskStats(); + if (monthStats != null) { + if (monthStats.getAvgDurationHours() != null) { + // 保留两位小数 + double v = monthStats.getAvgDurationHours(); + template.getMonthlyTaskStatistics().setAvgTaskDuration(Math.round(v * 100.0) / 100.0); + } + Long total = monthStats.getTotalCount(); + Long completed = monthStats.getCompletedCount(); + if (total != null && total > 0 && completed != null) { + int rate = (int) Math.round(((double) completed.longValue() / total.longValue()) * 100.0); + template.getMonthlyTaskStatistics().setTaskCompletionRate(rate); + } else { + template.getMonthlyTaskStatistics().setTaskCompletionRate(0); + } + } } // 任务负荷:有真实数据源;真实为空则清空不用模板模拟,有数据则按真实小时覆盖 @@ -313,6 +461,9 @@ public class BiPlatformRealDataService { } } + // 任务类型统计:本月 YMS QMS_MAIN_BUSINESS 按业务类型 BUSINESS_NAME 分组(真实业务数据) + template.setTaskTypeStatistics(queryMonthTransportOrderByType()); + return template; } diff --git a/mhd-bi/src/main/java/com/mhd/bi/domain/biReport/support/BiReportSnapshotMapperSupport.java b/mhd-bi/src/main/java/com/mhd/bi/domain/biReport/support/BiReportSnapshotMapperSupport.java index b3ac1ac64..914983d9c 100644 --- a/mhd-bi/src/main/java/com/mhd/bi/domain/biReport/support/BiReportSnapshotMapperSupport.java +++ b/mhd-bi/src/main/java/com/mhd/bi/domain/biReport/support/BiReportSnapshotMapperSupport.java @@ -87,7 +87,9 @@ public final class BiReportSnapshotMapperSupport { .platformOverview(PlatformOverviewVO.builder() .totalPlatforms(0).currentOccupancyRate(0d).operatingPlatforms(0).todayTasks(0).build()) .vehicleStatus(VehicleStatusVO.builder() - .todayReservedVehicles(0).notCheckedIn(0).operatingVehicles(0).completedVehicles(0).build()) + .todayReservedVehicles(0).notCheckedIn(0).waiting(0) + .callStatusTotal(0) + .operatingVehicles(0).completedVehicles(0).build()) .avgOperationDurationTrend(Collections.emptyList()) .monthlyTaskStatistics(MonthlyTaskStatisticsVO.builder() .taskTotal(0).taskTotalYoY(0).avgTaskDuration(0d).avgDurationYoY(0) diff --git a/mhd-bi/src/main/java/com/mhd/bi/domain/biSnapshotSync/service/BiReportSnapshotSyncService.java b/mhd-bi/src/main/java/com/mhd/bi/domain/biSnapshotSync/service/BiReportSnapshotSyncService.java index b682590a2..9843ae662 100644 --- a/mhd-bi/src/main/java/com/mhd/bi/domain/biSnapshotSync/service/BiReportSnapshotSyncService.java +++ b/mhd-bi/src/main/java/com/mhd/bi/domain/biSnapshotSync/service/BiReportSnapshotSyncService.java @@ -16,7 +16,7 @@ import com.mhd.bi.interfaces.facadeApi.biReport.vo.warehouseZone.WarehouseOperat import com.mhd.bi.interfaces.facadeApi.biReport.vo.platform.PlatformVehicleItemVO; import com.mhd.bi.domain.biPlatformRealData.service.BiPlatformRealDataService; import com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformVehiclePO; -import com.mhd.bi.domain.biPlatformRealData.repository.po.RealPlatformOverviewPO; +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.RealTaskLoadPO; import lombok.RequiredArgsConstructor; @@ -173,18 +173,31 @@ public class BiReportSnapshotSyncService { } // ====== 真实数据:月台实时概览 ====== - RealPlatformOverviewPO ov = biPlatformRealDataService.queryPlatformOverview(); + // 总月台 / 占用月台 来源:YMS 月台管理列表 QMS_WINDOW_INFO(JOB_STATUS=1 表示占用) + // 月台占用率 = 占用 / 全部月台 + // 今日作业任务 来源:QMS_MAIN_BUSINESS 按预约时间 TARGET_DATE = 今天 的全部数量 if (template.getPlatformOverview() != null) { - template.getPlatformOverview().setTotalPlatforms(ov.getTotalPlatforms().intValue()); - template.getPlatformOverview().setOperatingPlatforms(ov.getOperatingPlatforms().intValue()); - int total = ov.getTotalPlatforms().intValue(); - int occ = ov.getOperatingPlatforms().intValue(); + List platformList = biPlatformRealDataService.queryPlatformInfoList(); + int total = platformList == null ? 0 : platformList.size(); + int occ = 0; + if (platformList != null) { + for (RealPlatformInfoPO p : platformList) { + if (p != null && p.getJobStatus() != null && p.getJobStatus() == 1) { + occ++; + } + } + } double rate = total > 0 ? Math.round(((double) occ / total) * 100.0 * 100.0) / 100.0 : 0d; + int todayTasks = (int) biPlatformRealDataService.countTodayTasks(); + template.getPlatformOverview().setTotalPlatforms(total); + template.getPlatformOverview().setOperatingPlatforms(occ); template.getPlatformOverview().setCurrentOccupancyRate(rate); - } - long todayTasks = biPlatformRealDataService.countTodayTasks(); - if (template.getPlatformOverview() != null) { - template.getPlatformOverview().setTodayTasks((int) todayTasks); + template.getPlatformOverview().setTodayTasks(todayTasks); + + // 作业准时率 = 实际进入时间 < 预约日期+预约结束时间 的车辆 / 今日预约总数 × 100%(取整) + if (template.getEfficiencyStatistics() != null) { + template.getEfficiencyStatistics().setPlatformOnTimeRate(biPlatformRealDataService.calcTodayOnTimeRate()); + } } // ====== 真实数据:作业车辆状态统计 ====== @@ -192,14 +205,36 @@ public class BiReportSnapshotSyncService { if (template.getVehicleStatus() != null) { template.getVehicleStatus().setTodayReservedVehicles(vs.getTodayReservedVehicles().intValue()); template.getVehicleStatus().setNotCheckedIn(vs.getNotCheckedIn().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().setCompletedVehicles(vs.getCompletedVehicles().intValue()); + } - // ====== 真实数据:本月任务统计 taskTotal ====== + // ====== 真实数据:本月任务统计 taskTotal / avgTaskDuration / taskCompletionRate ====== long monthTasks = biPlatformRealDataService.countMonthTasks(); if (template.getMonthlyTaskStatistics() != null) { template.getMonthlyTaskStatistics().setTaskTotal((int) monthTasks); + com.mhd.bi.domain.biPlatformRealData.repository.po.RealMonthlyTaskDurationPO monthStats = + biPlatformRealDataService.queryMonthTaskStats(); + if (monthStats != null) { + if (monthStats.getAvgDurationHours() != null) { + double v = monthStats.getAvgDurationHours(); + template.getMonthlyTaskStatistics().setAvgTaskDuration(Math.round(v * 100.0) / 100.0); + } + Long total = monthStats.getTotalCount(); + Long completed = monthStats.getCompletedCount(); + if (total != null && total > 0 && completed != null) { + int rate = (int) Math.round(((double) completed.longValue() / total.longValue()) * 100.0); + template.getMonthlyTaskStatistics().setTaskCompletionRate(rate); + } else { + template.getMonthlyTaskStatistics().setTaskCompletionRate(0); + } + } } // ====== 真实数据:任务负荷(今日 24 小时) ====== @@ -218,6 +253,9 @@ public class BiReportSnapshotSyncService { } } + // ====== 真实数据:任务类型统计(本月 YMS QMS_MAIN_BUSINESS 按业务类型 BUSINESS_NAME 分组) ====== + template.setTaskTypeStatistics(biPlatformRealDataService.queryMonthTransportOrderByType()); + return template; } diff --git a/mhd-bi/src/main/java/com/mhd/bi/domain/biSnapshotSync/support/BiSnapshotWeeklyRandomDataBuilder.java b/mhd-bi/src/main/java/com/mhd/bi/domain/biSnapshotSync/support/BiSnapshotWeeklyRandomDataBuilder.java index 79f0cdd1a..7b1012c18 100644 --- a/mhd-bi/src/main/java/com/mhd/bi/domain/biSnapshotSync/support/BiSnapshotWeeklyRandomDataBuilder.java +++ b/mhd-bi/src/main/java/com/mhd/bi/domain/biSnapshotSync/support/BiSnapshotWeeklyRandomDataBuilder.java @@ -155,10 +155,13 @@ public final class BiSnapshotWeeklyRandomDataBuilder { VehicleStatusVO vs = VehicleStatusVO.builder() .todayReservedVehicles(Math.max(0, jitterInt(Baseline.P_RESV, 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))) .completedVehicles(Math.max(0, jitterInt(Baseline.P_DONE, r))) .build(); + List avgTrend = new ArrayList<>(); for (int i = 0; i < Baseline.P_MO_LABELS.length; i++) { avgTrend.add(AvgOperationDurationTrendItemVO.builder() @@ -185,13 +188,8 @@ public final class BiSnapshotWeeklyRandomDataBuilder { .build()); } + // 月台告警:按业务要求,全部置 0(趋势/统计/分布均不生成写死数据) List 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 typeCounts = new ArrayList<>(); for (int i = 0; i < Baseline.P_TT_TYPES.length; i++) { @@ -201,18 +199,11 @@ public final class BiSnapshotWeeklyRandomDataBuilder { .build()); } - int todayAl = Math.max(0, jitterInt(Baseline.P_AL_TOT, r)); - int handled = Math.max(0, jitterInt(Baseline.P_AL_H, r)); - handled = Math.min(handled, todayAl); - int unhandled = Math.max(0, todayAl - handled); + int todayAl = 0; + int handled = 0; + int unhandled = 0; List 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() .vehicles(vehicles) @@ -415,6 +406,8 @@ public final class BiSnapshotWeeklyRandomDataBuilder { static final int P_TASKS = 418; static final int P_RESV = 2_850; static final int P_NC = 920; + 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_DONE = 1_420; static final String[] P_MO_LABELS = {"5月", "6月", "7月"}; diff --git a/mhd-bi/src/main/java/com/mhd/bi/interfaces/facadeApi/biReport/vo/platform/VehicleStatusVO.java b/mhd-bi/src/main/java/com/mhd/bi/interfaces/facadeApi/biReport/vo/platform/VehicleStatusVO.java index f71aefe19..d4f53884a 100644 --- a/mhd-bi/src/main/java/com/mhd/bi/interfaces/facadeApi/biReport/vo/platform/VehicleStatusVO.java +++ b/mhd-bi/src/main/java/com/mhd/bi/interfaces/facadeApi/biReport/vo/platform/VehicleStatusVO.java @@ -20,6 +20,12 @@ public class VehicleStatusVO { @ApiModelProperty("当前未签到,单位:辆") private Integer notCheckedIn; + @ApiModelProperty("当前等待作业(已预约/已签到/已叫号),单位:辆") + private Integer waiting; + + @ApiModelProperty("已签到+已叫号+已过号(FLOW_STATUS IN 2/3/4),单位:辆") + private Integer callStatusTotal; + @ApiModelProperty("当前作业中,单位:辆") private Integer operatingVehicles; diff --git a/mhd-bi/src/main/resources/bootstrap.yml b/mhd-bi/src/main/resources/bootstrap.yml index 2572662c1..fa1d95e1a 100644 --- a/mhd-bi/src/main/resources/bootstrap.yml +++ b/mhd-bi/src/main/resources/bootstrap.yml @@ -19,14 +19,14 @@ spring: # username: nacos # password: manhuoda@2023 #线上正式环境 - server-addr: 10.102.192.30:6848 + server-addr: 10.102.192.31:6848 username: nacos password: manhuoda@2023 config: # server-addr: 127.0.0.1:8848 # 线上测试环境配置 容器名+端口号 # server-addr: 10.33.0.129:6010 - server-addr: 10.102.192.30:6848 + server-addr: 10.102.192.31:6848 username: nacos password: manhuoda@2023 #线上正式环境 diff --git a/mhd-bi/src/main/resources/mapper/biPlatformRealData/BiPlatformRealDataMapper.xml b/mhd-bi/src/main/resources/mapper/biPlatformRealData/BiPlatformRealDataMapper.xml index d3d0b3c12..578ae9b27 100644 --- a/mhd-bi/src/main/resources/mapper/biPlatformRealData/BiPlatformRealDataMapper.xml +++ b/mhd-bi/src/main/resources/mapper/biPlatformRealData/BiPlatformRealDataMapper.xml @@ -40,35 +40,85 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" FROM NGWL_TEST_YMS.QMS_WINDOW_INFO - + + + + - + + + - + + + + + + + + + + + + + AND TO_CHAR(a.CREATE_TIME, 'YYYY-MM') = #{billManageDO.billMonth} + AND date_format(a.create_time,'%Y-%m-%d') =]]> #{billManageDO.createTimeStart} diff --git a/mhd_bms/src/main/resources/mapper/BillOperationLogMapper.xml b/mhd_bms/src/main/resources/mapper/BillOperationLogMapper.xml index 7098fe232..1a55a853e 100644 --- a/mhd_bms/src/main/resources/mapper/BillOperationLogMapper.xml +++ b/mhd_bms/src/main/resources/mapper/BillOperationLogMapper.xml @@ -12,39 +12,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - and create_by_name like concat('%', #{createByName}, '%') + + and create_by_name like concat('%', #{billOperationLogDO.createByName}, '%') - - and update_by_name like concat('%', #{updateByName}, '%') + + and update_by_name like concat('%', #{billOperationLogDO.updateByName}, '%') - - and top_organization_id = #{topOrganizationId} + + and top_organization_id = #{billOperationLogDO.topOrganizationId} - - and organization_id = #{organizationId} + + and organization_id = #{billOperationLogDO.organizationId} - - and organization_name like concat('%', #{organizationName}, '%') + + and organization_name like concat('%', #{billOperationLogDO.organizationName}, '%') - - and bill_manage_id = #{billManageId} + + and bill_manage_id = #{billOperationLogDO.billManageId} - - and bill_number = #{billNumber} + + + + + and operation_type = #{billOperationLogDO.operationType} - - and operation_type = #{operationType} - - - and operation_info = #{operationInfo} - - - and operator = #{operator} - - - and operation_time = #{operationTime} + + and operation_info = #{billOperationLogDO.operationInfo} + + + + + + @@ -64,4 +64,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND bill_manage_id = #{billManageIds} ORDER BY create_time ASC - + \ No newline at end of file diff --git a/mhd_bms/src/main/resources/mapper/BillingStatementMapper.xml b/mhd_bms/src/main/resources/mapper/BillingStatementMapper.xml index 82ebab40d..83d62254d 100644 --- a/mhd_bms/src/main/resources/mapper/BillingStatementMapper.xml +++ b/mhd_bms/src/main/resources/mapper/BillingStatementMapper.xml @@ -275,7 +275,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" SELECT RATE as "taxRate" FROM - EXPENSE_ACCOUNT + NGWL_TEST_SYSTEM.EXPENSE_ACCOUNT WHERE SUBJECT_CODE = #{subjectCode} AND SUBJECT_NAME = #{subjectName} diff --git a/mhd_bms/src/main/resources/mapper/ReceiptManageMapper.xml b/mhd_bms/src/main/resources/mapper/ReceiptManageMapper.xml index 951e90838..3242d96cb 100644 --- a/mhd_bms/src/main/resources/mapper/ReceiptManageMapper.xml +++ b/mhd_bms/src/main/resources/mapper/ReceiptManageMapper.xml @@ -13,7 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WM_CONCAT(b.bill_number) AS billNumbers FROM receipt_manage a - LEFT JOIN receipt_detail b ON a.receipt_manage_id = b.receipt_manage_id + LEFT JOIN receipt_detail b ON a.receipt_manage_id = b.receipt_manage_id AND b.del_flag = 1 WHERE a.del_flag = 1 @@ -101,6 +101,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" left join PAYMENT_WALLET c on b.payment_card_id = c.PAYMENT_WALLET_ID WHERE a.bill_manage_id = #{billManageId} + AND a.del_flag = 1 \ No newline at end of file diff --git a/mhd_oms/src/main/java/com/mhd/oms/application/service/businessDocumentOrder/BusinessDocumentOrderAdjustService.java b/mhd_oms/src/main/java/com/mhd/oms/application/service/businessDocumentOrder/BusinessDocumentOrderAdjustService.java new file mode 100644 index 000000000..1411518ec --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/application/service/businessDocumentOrder/BusinessDocumentOrderAdjustService.java @@ -0,0 +1,826 @@ +package com.mhd.oms.application.service.businessDocumentOrder; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.mhd.common.core.domain.dto.wlhy.TmsAddressMultiDTO; +import com.mhd.common.core.domain.dto.wlhy.TmsCargoInfoDTO; +import com.mhd.common.core.domain.dto.wlhy.TmsOrderAccount; +import com.mhd.common.core.exception.ServiceException; +import com.mhd.common.core.web.domain.AjaxResult; +import com.mhd.common.security.utils.SecurityUtils; +import com.mhd.oms.domain.businessDocumentAddressMulti.entity.BusinessDocumentAddressMulti; +import com.mhd.oms.domain.businessDocumentAddressMulti.repository.mapper.BusinessDocumentAddressMultiMapper; +import com.mhd.oms.domain.businessDocumentCargoMulti.entity.BusinessDocumentCargoMulti; +import com.mhd.oms.domain.businessDocumentCargoMulti.repository.mapper.BusinessDocumentCargoMultiMapper; +import com.mhd.oms.domain.businessDocumentOrder.entity.BusinessDocumentOrder; +import com.mhd.oms.domain.businessDocumentOrder.repository.mapper.BusinessDocumentOrderMapper; +import com.mhd.oms.domain.businessDocumentOrderAdjust.entity.BusinessDocumentOrderAdjust; +import com.mhd.oms.domain.businessDocumentOrderAdjust.repository.mapper.BusinessDocumentOrderAdjustMapper; +import com.mhd.oms.domain.businessDocumentOrderAdjustDetail.entity.BusinessDocumentOrderAdjustDetail; +import com.mhd.oms.domain.businessDocumentOrderAdjustDetail.repository.mapper.BusinessDocumentOrderAdjustDetailMapper; +import com.mhd.oms.domain.businessOrderAccount.BusinessOrderAccount; +import com.mhd.oms.domain.businessOrderAccount.repository.mapper.BusinessOrderAccountMapper; +import com.mhd.oms.domain.executeOrder.entity.ExecuteOrder; +import com.mhd.oms.domain.executeOrder.repository.mapper.ExecuteOrderMapper; +import com.mhd.oms.interfaces.dto.businessDocumentAddressMulti.BusinessDocumentAddressMultiDTO; +import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustAuditDTO; +import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustDTO; +import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustQueryDTO; +import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustRecordDTO; +import com.mhd.system.api.WlhyServiceFeign; +import com.mhd.system.api.domain.TmsOrderAddDTO; +import com.mhd.system.api.model.LoginUser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 运输业务单「订单调整」应用服务 + * + * 规则(定稿): + * 1. 每次调整都生成:1 条主记录 + 1 条明细快照 + * 2. 费用数据源 = 前端回传的费用明细 business_order_account(feeDetailList); + * 主表金额 deliveryFreight 为其 amount 合计(冗余汇总,后端自动算)。 + * 3. 是否需审核:一旦任一「金额」发生变化 + * = feeDetailList 明细 amount 变化,或主表金额/费用列变化 -> 走业务主管审核; + * 否则(仅改非金额字段) -> 直通。 + * 「金额/费用」字段涵盖: deliveryFreight / collectedFreight / uncollectedFreight / + * collectLessFreight / transportationUnitPrice / infoFee / freightUnitPrice / + * cargoInsuranceAmount / freightCharges / freightFee / unloadingFee / customsFee / + * shortageFee / miscellaneousFee / otherFee / detentionFee,以及 settlementCurrency / + * paymentAccountId / accountName / accountInformation(结算币种+收款账户任一变化即视为费用变更)。 + * 判定口径: 以后端兜底比对为准(主表金额/费用列 + 明细 amount 合计), + * 前端 amountChanged 仅作提示;后端比对结果与前端 OR 运算,避免前端误传 false 绕过审核。 + * 4. 涉及金额提交 -> 仅把业务单 review_status 置为「调整待审核(3)」;主记录 AUDIT_STATUS=0(待审核); + * 不更新业务单数据,不影响其它状态(order_status / execute_status 不动)。 + * 5. 审核通过 -> 覆盖业务单(金额) + 替换费用明细;review_status 一律置为 1(已审核), + * 不影响 order_status / execute_status。 + * 审核驳回 -> 不更新业务单数据,review_status 置为「调整驳回(4)」,不影响 order_status/execute_status, + * 可再次调整提交审核。 + * 6. 直通(未改金额) -> 覆盖业务单可编辑字段 + deliveryFreight(明细合计),并替换 business_order_account + * (逻辑删旧 is_delete=1 + 插新 is_delete=0);不改动任何审核/执行状态。 + * + * 说明:review_status 原枚举 0未审/1通过/2驳回,新增 3=调整待审核;不改变原有用例。 + * + * @author dev + * @date 2026-09-09 + */ +@Slf4j +@Service +public class BusinessDocumentOrderAdjustService { + + private static final Integer AUDIT_WAIT = 0; // 调整记录:待审核 + private static final Integer AUDIT_PASS = 1; // 调整记录:审核通过 + private static final Integer AUDIT_REJECT = 2; // 调整记录:驳回 + + /** 业务单 review_status:调整待审核(提交涉及金额时置) */ + private static final Long REVIEW_ADJUST_WAIT = 3L; + /** 业务单 review_status:调整驳回(审核驳回时置,与普通驳回 2 区分) */ + private static final Long REVIEW_ADJUST_REJECT = 4L; + + /** 主表金额(费用)字段,任一变化触发审核 + * 含: 运费相关 8 项 + 其他费用 8 项 + 结算币种 + 收款账户(3 字段) + * 任意一项变化即需要审核(对应前端"费用合计 / 运费 / 装卸费 / 仓租及柜租(走明细) / + * 保险 / 压车(走明细) / 运输处理费(走明细) / 报关费 / 其他 / 费用合计 / 结算币种 / 收款账户") */ + private static final List FEE_FIELDS = Arrays.asList( + "deliveryFreight", "collectedFreight", "uncollectedFreight", "collectLessFreight", + "transportationUnitPrice", "infoFee", "freightUnitPrice", "cargoInsuranceAmount", + "freightCharges", "freightFee", "unloadingFee", "customsFee", "shortageFee", + "miscellaneousFee", "otherFee", "detentionFee", + // 结算币种 + 收款账户 任一变化也视为费用变更,需审核 + "settlementCurrency", "paymentAccountId", "accountName", "accountInformation"); + + /** 不允许被前端调整覆盖的系统字段 */ + private static final List PROTECTED_FIELDS = Arrays.asList( + "id", "goodsNumber", + "orderStatus", "reviewStatus", "reviewId", "review", "reviewTime", "reviewOpinion", + "salesManager", "auditStatus", "auditBy", "auditTime", + "executeStatus", "executeId", "executeTime", "executor", + "executeOrderId", "executeOrderNumber", + "isDelete", "delFlag", "createBy", "createByName", "createTime", + "updateBy", "updateByName", "updateTime", + "pushStatus", "pushTime", "pushBy", "pushByName", + "reviseApplyType", "reviseApplyStatus", "reviseApplyBy", "reviseApplyByName", + "reviseApplyReason", "reviseApplyTime", + "reservationOrderId", "reservationOrderNumber", + "mainOrderId", "mainOrderNum", "mergeOrderId", "mergeOrderNum", + "splicingOrderType", "splicingStatus", "deliveryProgress", + "orgId", "orgName", "organizationId", "organizationName", "topOrganizationId", + "realCreateBy", "realCreateName", "createCompanyId", "createUserId", + "szwlUserId" + ); + + @Autowired + private BusinessDocumentOrderMapper businessDocumentOrderMapper; + + @Autowired + private BusinessOrderAccountMapper businessOrderAccountMapper; + + @Autowired + private BusinessDocumentOrderAdjustMapper adjustMapper; + + @Autowired + private BusinessDocumentOrderAdjustDetailMapper adjustDetailMapper; + + @Autowired + private ExecuteOrderMapper executeOrderMapper; + + @Autowired + private BusinessDocumentCargoMultiMapper businessDocumentCargoMultiMapper; + + @Autowired + private BusinessDocumentAddressMultiMapper businessDocumentAddressMultiMapper; + + @Autowired + private WlhyServiceFeign wlhyServiceFeign; + + /** + * 提交调整 + * 返回 data = [是否需审核, 调整记录ID] + */ + @Transactional(rollbackFor = Exception.class) + public AjaxResult adjust(BusinessDocumentOrderAdjustDTO dto) { + if (dto == null || dto.getOrder() == null || dto.getOrder().getId() == null) { + throw new ServiceException("参数错误:缺少业务单ID(order.id)"); + } + BusinessDocumentOrder dbOrder = businessDocumentOrderMapper.selectById(dto.getOrder().getId()); + if (ObjectUtil.isNull(dbOrder)) { + throw new ServiceException("业务单不存在或已被删除"); + } + BusinessDocumentOrder front = dto.getOrder(); + + // 1. 当前库中费用明细 + List dbFeeList = businessOrderAccountMapper.selectList( + new LambdaQueryWrapper() + .eq(BusinessOrderAccount::getOrderId, dbOrder.getId()) + .eq(BusinessOrderAccount::getIsDelete, 0)); + + // 2. 调整后的费用明细(前端);为 null 则视为保持库中不变。 + // 兼容前端两种传法:顶层 feeDetailList,或整单回传 order.businessOrderAccountList + List newFeeList = dto.getFeeDetailList(); + if ((newFeeList == null || newFeeList.isEmpty()) && front.getBusinessOrderAccountList() != null) { + newFeeList = front.getBusinessOrderAccountList(); + } + + // 3. 生成"调整后"业务单对象:DB原值 + 前端可编辑字段覆盖(忽略null) + BusinessDocumentOrder afterOrder = buildAfterOrder(dbOrder, front); + + // 3.1 地址兼容:前端把地址放在 shipment/receipt 或 loadingAddressList/unloadAddressList, + // 映射到 loading*/unload* 扁平字段(与下单主流程 BusinessDocumentOrderDomainService 一致口径) + applyAddressMapping(afterOrder, front); + + // 4. 若提供了费用明细,则 deliveryFreight(合计)=明细 amount 之和,并联动到 afterOrder + boolean hasFeeDetail = newFeeList != null && !newFeeList.isEmpty(); + if (hasFeeDetail) { + BigDecimal sum = sumAmount(newFeeList); + afterOrder.setDeliveryFreight(sum); + } + + // 5. 判定是否修改了金额:以后端兜底比对为准,避免前端误传 false 绕过审核; + // 前端传 true 也尊重;后端比对结果与前端 OR 运算(任一为真则需审核)。 + boolean backendChanged = isAmountChanged(dbOrder, front, hasFeeDetail, dbFeeList, newFeeList); + boolean amountChanged = backendChanged || Boolean.TRUE.equals(dto.getAmountChanged()); + + // 6. 写主记录 + LoginUser loginUser = SecurityUtils.getLoginUser(); + Date now = new Date(); + BusinessDocumentOrderAdjust adjust = buildAdjust(dbOrder, loginUser, now, amountChanged, dto.getAdjustReason()); + adjustMapper.insert(adjust); + + // 7. 写明细快照(调整后业务单整行 + 费用明细JSON) + BusinessDocumentOrderAdjustDetail detail = new BusinessDocumentOrderAdjustDetail(); + copySnapshot(afterOrder, detail); + if (hasFeeDetail) { + detail.setFeeDetailJson(JSONUtil.toJsonStr(newFeeList)); + } + detail.setAdjustId(adjust.getId()); + detail.setOrderId(dbOrder.getId()); + detail.setGoodsNumber(dbOrder.getGoodsNumber()); + detail.setCreateBy(adjust.getAdjustBy()); + detail.setCreateName(adjust.getAdjustByName()); + detail.setCreateTime(now); + detail.setDelFlag(1); + adjustDetailMapper.insert(detail); + + if (!amountChanged) { + // 8a. 直通:覆盖主表可编辑字段(含 deliveryFreight 合计) + 替换费用明细;不改动任何审核/执行状态 + saveOrderAndFee(dbOrder.getId(), afterOrder, hasFeeDetail ? newFeeList : dbFeeList, loginUser, now, false, null); + updateAdjustPass(adjust.getId(), adjust.getAdjustBy(), adjust.getAdjustByName(), now, null); + syncTmsAsync(dbOrder.getGoodsNumber(), afterOrder); + } else { + // 8b. 涉及金额:仅把业务单 review_status 置为"调整待审核(3)",其余状态(exec/orderStatus)不动,不更新业务单数据 + BusinessDocumentOrder statusOnly = new BusinessDocumentOrder(); + statusOnly.setId(dbOrder.getId()); + statusOnly.setReviewStatus(REVIEW_ADJUST_WAIT); // 调整待审核 + businessDocumentOrderMapper.updateById(statusOnly); + } + + return AjaxResult.success(new Object[]{amountChanged, adjust.getId()}); + } + + /** + * 调整记录列表(管理页:查全部,可选按订单/业务单号/审核状态等筛选,配合分页使用) + */ + public List listAdjustRecord(BusinessDocumentOrderAdjustQueryDTO query) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(BusinessDocumentOrderAdjust::getDelFlag, 1); + + // ===== 组织隔离(参考业务单 queryList 口径) ===== + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser != null && loginUser.getUserPo() != null) { + Long loginOrgId = loginUser.getUserPo().getOrganizationId(); + Long loginTopOrgId = loginUser.getUserPo().getTopOrganizationId(); + // 一级组织非1(集团)时,按一级组织过滤(可看本一级组织下所有) + if (loginTopOrgId != null && loginTopOrgId != 1) { + wrapper.eq(BusinessDocumentOrderAdjust::getTopOrgId, String.valueOf(loginTopOrgId)); + } else if (loginOrgId != null) { + // 否则按当前组织过滤;若查询方显式指定了 organizationId 则以其为准 + Long effOrgId = (query != null && query.getOrganizationId() != null) + ? query.getOrganizationId() : loginOrgId; + if (effOrgId != null) { + wrapper.eq(BusinessDocumentOrderAdjust::getOrgId, String.valueOf(effOrgId)); + } + } + } else if (query != null && query.getOrganizationId() != null) { + wrapper.eq(BusinessDocumentOrderAdjust::getOrgId, String.valueOf(query.getOrganizationId())); + } + + if (query != null) { + if (query.getOrderId() != null) { + wrapper.eq(BusinessDocumentOrderAdjust::getOrderId, query.getOrderId()); + } + if (StrUtil.isNotBlank(query.getGoodsNumber())) { + wrapper.like(BusinessDocumentOrderAdjust::getGoodsNumber, query.getGoodsNumber()); + } + if (query.getIsAudit() != null) { + wrapper.eq(BusinessDocumentOrderAdjust::getIsAudit, query.getIsAudit()); + } + if (query.getAuditStatus() != null) { + wrapper.eq(BusinessDocumentOrderAdjust::getAuditStatus, query.getAuditStatus()); + } + if (StrUtil.isNotBlank(query.getAdjustByName())) { + wrapper.like(BusinessDocumentOrderAdjust::getAdjustByName, query.getAdjustByName()); + } + } + wrapper.orderByDesc(BusinessDocumentOrderAdjust::getAdjustTime); + List list = adjustMapper.selectList(wrapper); + return list.stream().map(a -> { + BusinessDocumentOrderAdjustRecordDTO vo = new BusinessDocumentOrderAdjustRecordDTO(); + BeanUtil.copyProperties(a, vo); + return vo; + }).collect(Collectors.toList()); + } + + /** + * 按业务单查询调整记录(供订单详情页"调整记录"Tab用) + * 每条记录同时带出调整后明细快照(detail),前端可自包含展示,无需再单独调详情接口 + */ + public List listAdjustRecordByOrder(Long orderId) { + BusinessDocumentOrderAdjustQueryDTO query = new BusinessDocumentOrderAdjustQueryDTO(); + query.setOrderId(orderId); + List list = listAdjustRecord(query); + if (list == null || list.isEmpty()) { + return list; + } + // 批量取明细快照按 adjust_id 一次性拼上,避免 N+1 + List adjustIds = list.stream() + .map(BusinessDocumentOrderAdjustRecordDTO::getId) + .collect(Collectors.toList()); + List details = adjustDetailMapper.selectList( + new LambdaQueryWrapper() + .in(BusinessDocumentOrderAdjustDetail::getAdjustId, adjustIds) + .eq(BusinessDocumentOrderAdjustDetail::getDelFlag, 1)); + Map detailMap = details.stream() + .collect(Collectors.toMap(BusinessDocumentOrderAdjustDetail::getAdjustId, d -> d, (a, b) -> a)); + list.forEach(vo -> vo.setDetail(detailMap.get(vo.getId()))); + return list; + } + + /** + * 调整记录详情(主记录 + 调整后明细快照) + */ + public BusinessDocumentOrderAdjustRecordDTO getAdjustRecord(Long adjustId) { + BusinessDocumentOrderAdjust adjust = adjustMapper.selectById(adjustId); + if (adjust == null) { + throw new ServiceException("调整记录不存在"); + } + BusinessDocumentOrderAdjustRecordDTO vo = new BusinessDocumentOrderAdjustRecordDTO(); + BeanUtil.copyProperties(adjust, vo); + BusinessDocumentOrderAdjustDetail detail = adjustDetailMapper.selectOne( + new LambdaQueryWrapper() + .eq(BusinessDocumentOrderAdjustDetail::getAdjustId, adjustId) + .eq(BusinessDocumentOrderAdjustDetail::getDelFlag, 1) + .last("LIMIT 1")); + vo.setDetail(detail); + return vo; + } + + /** + * 调整审核(通过/驳回),业务主管操作 + */ + @Transactional(rollbackFor = Exception.class) + public AjaxResult auditAdjust(BusinessDocumentOrderAdjustAuditDTO auditDTO) { + if (auditDTO == null || auditDTO.getAdjustId() == null) { + throw new ServiceException("参数错误:缺少调整记录ID"); + } + BusinessDocumentOrderAdjust adjust = adjustMapper.selectById(auditDTO.getAdjustId()); + if (adjust == null) { + throw new ServiceException("调整记录不存在"); + } + if (adjust.getAuditStatus() == null || adjust.getAuditStatus() != AUDIT_WAIT) { + throw new ServiceException("该调整记录非待审核状态,无法审核"); + } + Integer result = auditDTO.getAuditResult(); + if (result == null || (result != AUDIT_PASS && result != AUDIT_REJECT)) { + throw new ServiceException("审核结果非法"); + } + + LoginUser loginUser = SecurityUtils.getLoginUser(); + String auditBy = loginUser != null ? String.valueOf(loginUser.getUserid()) : null; + String auditByName = loginUser != null && loginUser.getWlhyLoginUser() != null ? loginUser.getWlhyLoginUser().getRealname() : null; + Date now = new Date(); + + if (result == AUDIT_PASS) { + BusinessDocumentOrder order = businessDocumentOrderMapper.selectById(adjust.getOrderId()); + if (ObjectUtil.isNull(order)) { + throw new ServiceException("业务单不存在"); + } + BusinessDocumentOrderAdjustDetail detail = adjustDetailMapper.selectOne( + new LambdaQueryWrapper() + .eq(BusinessDocumentOrderAdjustDetail::getAdjustId, adjust.getId()) + .eq(BusinessDocumentOrderAdjustDetail::getDelFlag, 1) + .last("LIMIT 1")); + + // 还原调整后业务单(从JSON整行快照) + BusinessDocumentOrder snapshot = null; + if (detail != null && StrUtil.isNotBlank(detail.getOrderSnapshotJson())) { + try { + snapshot = JSONUtil.toBean(detail.getOrderSnapshotJson(), BusinessDocumentOrder.class); + } catch (Exception e) { + log.warn("解析调整快照JSON失败", e); + } + } + BusinessDocumentOrder toSave = new BusinessDocumentOrder(); + toSave.setId(order.getId()); + if (snapshot != null) { + BeanUtil.copyProperties(snapshot, toSave, CopyOptions.create().setIgnoreNullValue(true) + .setIgnoreProperties(toArray())); + } else if (detail != null) { + BeanUtil.copyProperties(detail, toSave, CopyOptions.create().setIgnoreNullValue(true) + .setIgnoreProperties(toArray())); + } + + // 还原调整后费用明细(从 feeDetailJson) + List newFeeList = new ArrayList<>(); + if (detail != null && StrUtil.isNotBlank(detail.getFeeDetailJson())) { + try { + newFeeList = JSONUtil.toList(detail.getFeeDetailJson(), BusinessOrderAccount.class); + } catch (Exception e) { + log.warn("解析调整费用明细JSON失败", e); + } + } + // 若快照中有明细合计则保证 deliveryFreight 一致 + if (!newFeeList.isEmpty()) { + toSave.setDeliveryFreight(sumAmount(newFeeList)); + } + // 审核通过后 review_status 一律置为 1(已审核);不影响 order_status / execute_status + toSave.setReviewStatus(1L); + // 覆盖业务单 + 替换费用明细 + saveOrderAndFee(order.getId(), toSave, newFeeList, loginUser, now, false, null); + + updateAdjustPass(adjust.getId(), auditBy, auditByName, now, auditDTO.getAuditRemark()); + syncTmsAsync(order.getGoodsNumber(), toSave); + } else { + adjust.setAuditStatus(AUDIT_REJECT); + adjust.setIsAudit(1); // 走到审核即说明该调整需要审核,保持 is_audit 与 audit_status 语义一致 + adjust.setAuditBy(auditBy); + adjust.setAuditByName(auditByName); + adjust.setAuditTime(now); + adjust.setAuditRemark(auditDTO.getAuditRemark()); + adjust.setUpdateBy(auditBy); + adjust.setUpdateTime(now); + adjustMapper.updateById(adjust); + + // 驳回:不更新业务单数据,仅把 review_status 置为「调整驳回(4)」; + // 不影响 order_status / execute_status 等其它单据状态;可再次调整提交审核 + BusinessDocumentOrder statusOnly = new BusinessDocumentOrder(); + statusOnly.setId(adjust.getOrderId()); + statusOnly.setReviewStatus(REVIEW_ADJUST_REJECT); + businessDocumentOrderMapper.updateById(statusOnly); + } + return AjaxResult.success(); + } + + // ===================================================== + // 私有工具 + // ===================================================== + + private BusinessDocumentOrder buildAfterOrder(BusinessDocumentOrder dbOrder, BusinessDocumentOrder front) { + BusinessDocumentOrder after = new BusinessDocumentOrder(); + BeanUtil.copyProperties(dbOrder, after, CopyOptions.create().setIgnoreNullValue(true)); + BeanUtil.copyProperties(front, after, CopyOptions.create().setIgnoreNullValue(true) + .setIgnoreProperties(toArray())); + after.setId(dbOrder.getId()); + return after; + } + + /** + * 地址兼容映射:前端整单回传时地址在 shipment/receipt 或 loadingAddressList/unloadAddressList, + * 这里统一映射到 business_document_order 的 loading 与 unload 系列扁平字段。 + * 口径与 BusinessDocumentOrderDomainService 下单主流程保持一致: + * 装货取第一条地址,卸货取最后一条地址。 + */ + private void applyAddressMapping(BusinessDocumentOrder after, BusinessDocumentOrder front) { + BusinessDocumentAddressMultiDTO loading = null; + BusinessDocumentAddressMultiDTO unloading = null; + + // 优先取 shipment/receipt 单对象,其次取 loadingAddressList/unloadAddressList 集合 + if (front.getShipment() != null) { + loading = front.getShipment(); + } else if (front.getLoadingAddressList() != null && !front.getLoadingAddressList().isEmpty()) { + loading = front.getLoadingAddressList().get(0); + } + if (front.getReceipt() != null) { + unloading = front.getReceipt(); + } else if (front.getUnloadAddressList() != null && !front.getUnloadAddressList().isEmpty()) { + unloading = front.getUnloadAddressList().get(front.getUnloadAddressList().size() - 1); + } + + if (loading != null) { + after.setLoadingAreaId(loading.getAreaCode()); + after.setLoadingName(loading.getAreaName() != null ? loading.getAreaName() : loading.getAreaCode()); + after.setLoadingAddress(loading.getAddress()); + after.setLoadingLongitude(loading.getLongitude()); + after.setLoadingLatitude(loading.getLatitude()); + after.setFreighterName(loading.getContactName()); + after.setLoadingPhone(loading.getContactPhone()); + after.setLoadingDate(loading.getLayDate()); + } + if (unloading != null) { + after.setUnloadAreaId(unloading.getAreaCode()); + after.setUnloadName(unloading.getAreaName() != null ? unloading.getAreaName() : unloading.getAreaCode()); + after.setUnloadAddress(unloading.getAddress()); + after.setUnloadLongitude(unloading.getLongitude()); + after.setUnloadLatitude(unloading.getLatitude()); + after.setDischargerName(unloading.getContactName()); + after.setUnloadPhone(unloading.getContactPhone()); + after.setUnloadingData(unloading.getLayDate()); + } + } + + private BusinessDocumentOrderAdjust buildAdjust(BusinessDocumentOrder dbOrder, LoginUser u, Date now, + boolean amountChanged, String reason) { + BusinessDocumentOrderAdjust adjust = new BusinessDocumentOrderAdjust(); + adjust.setOrderId(dbOrder.getId()); + adjust.setGoodsNumber(dbOrder.getGoodsNumber()); + adjust.setAdjustBy(u != null ? String.valueOf(u.getUserid()) : null); + adjust.setAdjustByName(u != null && u.getWlhyLoginUser() != null ? u.getWlhyLoginUser().getRealname() : null); + adjust.setAdjustTime(now); + adjust.setAdjustReason(reason); + adjust.setIsAudit(amountChanged ? 1 : 0); + adjust.setAuditStatus(amountChanged ? AUDIT_WAIT : AUDIT_PASS); + adjust.setBeforeReviewStatus(dbOrder.getReviewStatus()); + // 组织信息:参考业务单口径,取当前登录用户所属组织(调整人归属) + if (u != null && u.getUserPo() != null) { + if (u.getUserPo().getOrganizationId() != null) { + adjust.setOrgId(String.valueOf(u.getUserPo().getOrganizationId())); + } else { + adjust.setOrgId(dbOrder.getOrgId()); + } + if (u.getUserPo().getTopOrganizationId() != null) { + adjust.setTopOrgId(String.valueOf(u.getUserPo().getTopOrganizationId())); + } else if (dbOrder.getTopOrganizationId() != null) { + adjust.setTopOrgId(String.valueOf(dbOrder.getTopOrganizationId())); + } + } else { + adjust.setOrgId(dbOrder.getOrgId()); + if (dbOrder.getTopOrganizationId() != null) { + adjust.setTopOrgId(String.valueOf(dbOrder.getTopOrganizationId())); + } + } + adjust.setCreateBy(adjust.getAdjustBy()); + adjust.setCreateName(adjust.getAdjustByName()); + adjust.setCreateTime(now); + adjust.setDelFlag(1); + return adjust; + } + + /** + * 是否修改了金额:主表金额/费用列变化 或 明细 amount 变化 + */ + private boolean isAmountChanged(BusinessDocumentOrder dbOrder, BusinessDocumentOrder front, + boolean hasFeeDetail, List dbFeeList, + List newFeeList) { + // a) 主表金额/费用列比对(金额列用 BigDecimal 比对,字符串列用 equals 比对) + boolean mainChanged = FEE_FIELDS.stream().anyMatch(field -> { + Object newVal = BeanUtil.getFieldValue(front, field); + if (newVal == null) { + return false; + } + Object oldVal = BeanUtil.getFieldValue(dbOrder, field); + if (newVal instanceof BigDecimal || oldVal instanceof BigDecimal) { + BigDecimal old = oldVal == null ? BigDecimal.ZERO : new BigDecimal(oldVal.toString()); + BigDecimal now = new BigDecimal(newVal.toString()); + return old.compareTo(now) != 0; + } + // 字符串字段(结算币种/收款账户等) + return !newVal.toString().equals(oldVal == null ? "" : oldVal.toString()); + }); + if (mainChanged) { + return true; + } + // b) 若前端未传明细,则只看主表;若传了明细,比较明细金额 + if (!hasFeeDetail) { + return false; + } + // 明细合计对比 + BigDecimal oldSum = sumAmount(dbFeeList); + BigDecimal newSum = sumAmount(newFeeList); + return oldSum.compareTo(newSum) != 0; + } + + private BigDecimal sumAmount(List list) { + BigDecimal sum = BigDecimal.ZERO; + if (list == null) { + return sum; + } + for (BusinessOrderAccount acc : list) { + if (acc.getAmount() != null) { + sum = sum.add(acc.getAmount()); + } + } + return sum; + } + + /** + * 覆盖业务单可编辑字段 + 替换费用明细(逻辑删旧 + 插新) + */ + /** + * 覆盖业务单可编辑字段 + 替换费用明细(逻辑删旧 + 插新) + * @param restoreReview 是否还原审核状态(审核通过/驳回后 true;直通 false 不改任何审核状态) + * @param beforeReviewStatus 调整前 review_status,用于还原 + */ + private void saveOrderAndFee(Long orderId, BusinessDocumentOrder toSave, + List newFeeList, + LoginUser loginUser, Date now, + boolean restoreReview, Long beforeReviewStatus) { + if (restoreReview) { + // 仅还原 review_status 到调整前值;不影响 order_status / execute_status 等其它状态 + toSave.setReviewStatus(beforeReviewStatus == null ? 0L : beforeReviewStatus); + } + businessDocumentOrderMapper.updateById(toSave); + + // 逻辑删旧明细 + List oldFeeList = businessOrderAccountMapper.selectList( + new LambdaQueryWrapper() + .eq(BusinessOrderAccount::getOrderId, orderId) + .eq(BusinessOrderAccount::getIsDelete, 0)); + if (oldFeeList != null) { + for (BusinessOrderAccount oldAcc : oldFeeList) { + oldAcc.setIsDelete(1); + oldAcc.setUpdateBy(loginUser != null ? String.valueOf(loginUser.getUserid()) : null); + oldAcc.setUpdateTime(now); + businessOrderAccountMapper.updateById(oldAcc); + } + } + // 插新明细 + if (newFeeList != null && !newFeeList.isEmpty()) { + for (BusinessOrderAccount acc : newFeeList) { + acc.setId(IdUtil.randomUUID()); + acc.setOrderId(String.valueOf(orderId)); + acc.setIsDelete(0); + acc.setCreateBy(loginUser != null ? String.valueOf(loginUser.getUserid()) : null); + acc.setCreateTime(now); + businessOrderAccountMapper.insert(acc); + } + } + + // 同步地址明细表(business_document_address_multi):逻辑删旧 + 按调整后扁平字段插新 + syncAddressDetail(orderId, toSave, loginUser, now); + } + + /** + * 同步地址明细表:把「调整后业务单」的装/卸货扁平字段写回 business_document_address_multi。 + * 与 applyAddressMapping 口径一致(装货取第一条、卸货取最后一条),此处各保留一条。 + */ + private void syncAddressDetail(Long orderId, BusinessDocumentOrder toSave, LoginUser loginUser, Date now) { + // 逻辑删旧 + List oldList = businessDocumentAddressMultiMapper.selectList( + new LambdaQueryWrapper() + .eq(BusinessDocumentAddressMulti::getOrderId, String.valueOf(orderId)) + .eq(BusinessDocumentAddressMulti::getDelFlag, 1)); + if (oldList != null) { + for (BusinessDocumentAddressMulti old : oldList) { + old.setDelFlag(0); + old.setUpdateBy(loginUser != null ? loginUser.getUserid() : null); + old.setUpdateTime(now); + businessDocumentAddressMultiMapper.updateById(old); + } + } + + Long userId = loginUser != null ? loginUser.getUserid() : null; + // 装货 + BusinessDocumentAddressMulti loading = new BusinessDocumentAddressMulti(); + loading.setOrderId(String.valueOf(orderId)); + loading.setAreaCode(toSave.getLoadingAreaId()); + loading.setAreaName(toSave.getLoadingName()); + loading.setAddress(toSave.getLoadingAddress()); + loading.setLongitude(toSave.getLoadingLongitude()); + loading.setLatitude(toSave.getLoadingLatitude()); + loading.setContactName(toSave.getFreighterName()); + loading.setContactPhone(toSave.getLoadingPhone()); + loading.setLayDate(toSave.getLoadingDate()); + loading.setLayType(1L); + loading.setDelFlag(1); + loading.setOrganizationId(toSave.getOrganizationId()); + loading.setOrganizationName(toSave.getOrganizationName()); + loading.setTopOrganizationId(toSave.getTopOrganizationId()); + loading.setCreateBy(userId); + loading.setCreateTime(now); + loading.setUpdateBy(userId); + loading.setUpdateTime(now); + businessDocumentAddressMultiMapper.insert(loading); + + // 卸货 + BusinessDocumentAddressMulti unload = new BusinessDocumentAddressMulti(); + unload.setOrderId(String.valueOf(orderId)); + unload.setAreaCode(toSave.getUnloadAreaId()); + unload.setAreaName(toSave.getUnloadName()); + unload.setAddress(toSave.getUnloadAddress()); + unload.setLongitude(toSave.getUnloadLongitude()); + unload.setLatitude(toSave.getUnloadLatitude()); + unload.setContactName(toSave.getDischargerName()); + unload.setContactPhone(toSave.getUnloadPhone()); + unload.setLayDate(toSave.getUnloadingData()); + unload.setLayType(2L); + unload.setDelFlag(1); + unload.setOrganizationId(toSave.getOrganizationId()); + unload.setOrganizationName(toSave.getOrganizationName()); + unload.setTopOrganizationId(toSave.getTopOrganizationId()); + unload.setCreateBy(userId); + unload.setCreateTime(now); + unload.setUpdateBy(userId); + unload.setUpdateTime(now); + businessDocumentAddressMultiMapper.insert(unload); + } + + private void copySnapshot(BusinessDocumentOrder order, BusinessDocumentOrderAdjustDetail detail) { + BeanUtil.copyProperties(order, detail, CopyOptions.create().setIgnoreNullValue(true) + .setIgnoreProperties("id", "adjustId", "orderId", "createBy", "createName", + "createTime", "updateBy", "updateTime", "delFlag", + "orderSnapshotJson", "feeDetailJson")); + try { + detail.setOrderSnapshotJson(JSONUtil.toJsonStr(order)); + } catch (Exception e) { + log.warn("生成调整快照JSON失败", e); + } + } + + private void updateAdjustPass(Long adjustId, String auditBy, String auditByName, Date auditTime, String remark) { + BusinessDocumentOrderAdjust adjust = new BusinessDocumentOrderAdjust(); + adjust.setId(adjustId); + adjust.setAuditStatus(AUDIT_PASS); + adjust.setAuditBy(auditBy); + adjust.setAuditByName(auditByName); + adjust.setAuditTime(auditTime); + adjust.setAuditRemark(remark); + adjust.setUpdateBy(auditBy); + adjust.setUpdateTime(auditTime); + adjustMapper.updateById(adjust); + } + + private String[] toArray() { + return PROTECTED_FIELDS.toArray(new String[0]); + } + + private void syncTmsAsync(String goodsNumber, BusinessDocumentOrder after) { + try { + List executeOrders = executeOrderMapper.selectList( + new LambdaQueryWrapper() + .eq(ExecuteOrder::getGoodsNumber, goodsNumber) + .eq(ExecuteOrder::getDelFlag, 1)); + if (executeOrders == null || executeOrders.isEmpty()) { + return; + } + for (ExecuteOrder executeOrder : executeOrders) { + // 仅已下发 TMS(存在 TMS 单号)的执行单需要同步修改 + if (StrUtil.isBlank(executeOrder.getTmsOrderNumber())) { + continue; + } + syncTmsOrder(executeOrder, after); + } + } catch (Exception e) { + log.error("同步TMS失败,不影响主流程", e); + } + } + + /** + * 同步修改单个 TMS 订单:按调整后业务单重新组装 TmsOrderAddDTO, + * 带 executeOrderNumber 定位 TMS 订单,调用 TMS /feign/order/update 接口。 + */ + private void syncTmsOrder(ExecuteOrder executeOrder, BusinessDocumentOrder after) { + TmsOrderAddDTO tmsOrderAddDTO = new TmsOrderAddDTO(); + BeanUtil.copyProperties(after, tmsOrderAddDTO); + // id 传 OMS 业务单主键:TMS 侧用作 tms_order_account.order_id 口径; + // TMS 的 update 在覆盖前会保留 tms_order 真实主键,不会被污染 + tmsOrderAddDTO.setId(String.valueOf(after.getId())); + // TMS 侧按执行单号定位订单 + tmsOrderAddDTO.setExecuteOrderId(String.valueOf(executeOrder.getId())); + tmsOrderAddDTO.setExecuteOrderNumber(executeOrder.getGoodsNumber()); + tmsOrderAddDTO.setGoodsNumber(after.getGoodsNumber()); + tmsOrderAddDTO.setOrderSource("0"); + + // TMS 侧 update 按 appointShipperId(业务单货主) 定位货主,需显式带上,口径与下单一致 + if (StrUtil.isNotBlank(after.getAppointShipperId())) { + tmsOrderAddDTO.setAppointShipperId(after.getAppointShipperId()); + } + if (after.getAppointShipper() != null) { + tmsOrderAddDTO.setAppointShipper(after.getAppointShipper()); + } + if (after.getSzwlUserId() != null) { + tmsOrderAddDTO.setSzwlUserId(after.getSzwlUserId()); + } + + // 货物信息 + List cargoList = businessDocumentCargoMultiMapper.selectList( + new LambdaQueryWrapper() + .eq(BusinessDocumentCargoMulti::getOrderId, String.valueOf(after.getId())) + .eq(BusinessDocumentCargoMulti::getDelFlag, 1)); + List cargoInfoList = new ArrayList<>(); + if (cargoList != null) { + for (BusinessDocumentCargoMulti cargo : cargoList) { + TmsCargoInfoDTO dto = new TmsCargoInfoDTO(); + BeanUtil.copyProperties(cargo, dto); + cargoInfoList.add(dto); + } + } + tmsOrderAddDTO.setCargoInfoList(cargoInfoList); + + // 装货地址:统一以「调整后业务单」的扁平字段为准,避免明细表(business_document_address_multi)旧值覆盖 + TmsAddressMultiDTO loadingDto = new TmsAddressMultiDTO(); + loadingDto.setAreaCode(after.getLoadingAreaId()); + loadingDto.setAreaName(after.getLoadingName()); + loadingDto.setAddress(after.getLoadingAddress()); + loadingDto.setLongitude(after.getLoadingLongitude()); + loadingDto.setLatitude(after.getLoadingLatitude()); + loadingDto.setContactName(after.getFreighterName()); + loadingDto.setContactPhone(after.getLoadingPhone()); + loadingDto.setLayDate(after.getLoadingDate()); + loadingDto.setLayType(1); + List loadingAddressList = new ArrayList<>(); + loadingAddressList.add(loadingDto); + tmsOrderAddDTO.setLoadingAddressList(loadingAddressList); + + // 卸货地址:同样以「调整后业务单」的扁平字段为准 + TmsAddressMultiDTO unloadDto = new TmsAddressMultiDTO(); + unloadDto.setAreaCode(after.getUnloadAreaId()); + unloadDto.setAreaName(after.getUnloadName()); + unloadDto.setAddress(after.getUnloadAddress()); + unloadDto.setLongitude(after.getUnloadLongitude()); + unloadDto.setLatitude(after.getUnloadLatitude()); + unloadDto.setContactName(after.getDischargerName()); + unloadDto.setContactPhone(after.getUnloadPhone()); + unloadDto.setLayDate(after.getUnloadingData()); + unloadDto.setLayType(2); + List unloadAddressList = new ArrayList<>(); + unloadAddressList.add(unloadDto); + tmsOrderAddDTO.setUnloadAddressList(unloadAddressList); + + // 费用明细 + List accountList = businessOrderAccountMapper.selectList( + new LambdaQueryWrapper() + .eq(BusinessOrderAccount::getOrderId, after.getId()) + .eq(BusinessOrderAccount::getIsDelete, 0)); + List tmsOrderAccountList = new ArrayList<>(); + if (accountList != null) { + for (BusinessOrderAccount acc : accountList) { + TmsOrderAccount dto = new TmsOrderAccount(); + BeanUtil.copyProperties(acc, dto); + tmsOrderAccountList.add(dto); + } + } + tmsOrderAddDTO.setTmsOrderAccountList(tmsOrderAccountList); + + AjaxResult result = wlhyServiceFeign.update(tmsOrderAddDTO); + if (result != null && "200".equals(String.valueOf(result.get("code")))) { + log.info("调整后同步修改TMS订单成功,执行单[{}],业务单[{}]", executeOrder.getGoodsNumber(), after.getGoodsNumber()); + } else { + log.error("调整后同步修改TMS订单失败,执行单[{}],返回: {}", executeOrder.getGoodsNumber(), + result == null ? "null" : result.get("msg")); + } + } +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrder/entity/BusinessDocumentOrder.java b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrder/entity/BusinessDocumentOrder.java index 5734bedad..e0b2a1bc0 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrder/entity/BusinessDocumentOrder.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrder/entity/BusinessDocumentOrder.java @@ -8,6 +8,7 @@ import com.mhd.common.core.web.domain.BaseVOEntity; import java.math.BigDecimal; import lombok.Data; import java.util.Date; +import java.util.List; import com.fasterxml.jackson.annotation.JsonFormat; import org.springframework.format.annotation.DateTimeFormat; @@ -659,7 +660,7 @@ public class BusinessDocumentOrder extends BaseVOEntity{ @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") private Date documentDate; - @ApiModelProperty( "审核状态 0-未审核 1-审核通过 2-已驳回") + @ApiModelProperty( "审核状态 0-未审核 1-审核通过 2-已驳回 3-调整待审核 4-调整驳回") private Long reviewStatus; @ApiModelProperty("审核时间") @@ -825,4 +826,35 @@ public class BusinessDocumentOrder extends BaseVOEntity{ @ApiModelProperty("车次") @Excel(name = "车次") private String shuttleNo; + + /** + * 前端整单回传时携带的费用明细(business_order_account),非表字段,仅用于「订单调整」入参接收。 + * 与顶层 feeDetailList 二选一,后端在 adjust() 中做回退兼容。 + */ + @com.baomidou.mybatisplus.annotation.TableField(exist = false) + private List businessOrderAccountList; + + /** + * 前端整单回传时的装货地址(单个),非表字段,仅用于「订单调整」入参接收与映射到 loading* 扁平字段。 + */ + @com.baomidou.mybatisplus.annotation.TableField(exist = false) + private com.mhd.oms.interfaces.dto.businessDocumentAddressMulti.BusinessDocumentAddressMultiDTO shipment; + + /** + * 前端整单回传时的卸货地址(单个),非表字段,仅用于「订单调整」入参接收与映射到 unload* 扁平字段。 + */ + @com.baomidou.mybatisplus.annotation.TableField(exist = false) + private com.mhd.oms.interfaces.dto.businessDocumentAddressMulti.BusinessDocumentAddressMultiDTO receipt; + + /** + * 前端整单回传时的装货地址集合,非表字段,仅用于「订单调整」入参接收与映射。 + */ + @com.baomidou.mybatisplus.annotation.TableField(exist = false) + private List loadingAddressList; + + /** + * 前端整单回传时的卸货地址集合,非表字段,仅用于「订单调整」入参接收与映射。 + */ + @com.baomidou.mybatisplus.annotation.TableField(exist = false) + private List unloadAddressList; } diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjust/entity/BusinessDocumentOrderAdjust.java b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjust/entity/BusinessDocumentOrderAdjust.java new file mode 100644 index 000000000..4ea7691f8 --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjust/entity/BusinessDocumentOrderAdjust.java @@ -0,0 +1,103 @@ +package com.mhd.oms.domain.businessDocumentOrderAdjust.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * 运输业务单 调整主记录 + * + * @author dev + * @date 2026-09-09 + */ +@Data +@TableName("business_document_order_adjust") +public class BusinessDocumentOrderAdjust implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键") + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + @ApiModelProperty("业务单ID") + private Long orderId; + + @ApiModelProperty("业务单号") + private String goodsNumber; + + @ApiModelProperty("调整人账号") + private String adjustBy; + + @ApiModelProperty("调整人姓名") + private String adjustByName; + + @ApiModelProperty("调整时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date adjustTime; + + @ApiModelProperty("调整原因") + private String adjustReason; + + @ApiModelProperty("是否需要审核 0-否 1-是") + private Integer isAudit; + + @ApiModelProperty("调整前业务单审核状态(review_status原值,用于调整审核通过/驳回后还原,避免影响其它)") + private Long beforeReviewStatus; + + @ApiModelProperty("审核状态 0-待审核 1-审核通过 2-驳回") + private Integer auditStatus; + + @ApiModelProperty("审核人账号") + private String auditBy; + + @ApiModelProperty("审核人姓名") + private String auditByName; + + @ApiModelProperty("审核时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date auditTime; + + @ApiModelProperty("审核/驳回意见") + private String auditRemark; + + @ApiModelProperty("组织ID") + private String orgId; + + @ApiModelProperty("一级组织ID") + private String topOrgId; + + @ApiModelProperty("创建人") + private String createBy; + + @ApiModelProperty("创建人姓名") + private String createName; + + @ApiModelProperty("创建时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + @ApiModelProperty("更新人") + private String updateBy; + + @ApiModelProperty("更新人姓名") + private String updateName; + + @ApiModelProperty("更新时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + @ApiModelProperty("删除标记 1-正常 0-删除") + private Integer delFlag; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjust/repository/mapper/BusinessDocumentOrderAdjustMapper.java b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjust/repository/mapper/BusinessDocumentOrderAdjustMapper.java new file mode 100644 index 000000000..ac740249a --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjust/repository/mapper/BusinessDocumentOrderAdjustMapper.java @@ -0,0 +1,12 @@ +package com.mhd.oms.domain.businessDocumentOrderAdjust.repository.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.mhd.oms.domain.businessDocumentOrderAdjust.entity.BusinessDocumentOrderAdjust; +import org.apache.ibatis.annotations.Mapper; + +/** + * 运输业务单 调整主记录 Mapper + */ +@Mapper +public interface BusinessDocumentOrderAdjustMapper extends BaseMapper { +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjustDetail/entity/BusinessDocumentOrderAdjustDetail.java b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjustDetail/entity/BusinessDocumentOrderAdjustDetail.java new file mode 100644 index 000000000..3aff8a13f --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjustDetail/entity/BusinessDocumentOrderAdjustDetail.java @@ -0,0 +1,189 @@ +package com.mhd.oms.domain.businessDocumentOrderAdjustDetail.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 运输业务单 调整明细(调整后业务单整行快照) + * + * @author dev + * @date 2026-09-09 + */ +@Data +@TableName("business_document_order_adjust_detail") +public class BusinessDocumentOrderAdjustDetail implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键") + @TableId(type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + @ApiModelProperty("关联调整主记录ID") + @JsonSerialize(using = ToStringSerializer.class) + private Long adjustId; + + @ApiModelProperty("业务单ID") + private Long orderId; + + @ApiModelProperty("业务单号") + private String goodsNumber; + + @ApiModelProperty("货物类型ID") + private String goodsTypeId; + + @ApiModelProperty("货物名称") + private String goodsName; + + @ApiModelProperty("车型") + private String carType; + + @ApiModelProperty("装货地") + private String loadingName; + + @ApiModelProperty("装货地址") + private String loadingAddress; + + @ApiModelProperty("装货时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date loadingDate; + + @ApiModelProperty("卸货地") + private String unloadName; + + @ApiModelProperty("卸货地址") + private String unloadAddress; + + @ApiModelProperty("卸货时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date unloadingData; + + @ApiModelProperty("发货人") + private String freighterName; + + @ApiModelProperty("装货电话") + private String loadingPhone; + + @ApiModelProperty("收货人") + private String dischargerName; + + @ApiModelProperty("卸货电话") + private String unloadPhone; + + @ApiModelProperty("备注") + private String remarks; + + @ApiModelProperty("线路") + private String route; + + @ApiModelProperty("单据日期") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date documentDate; + + @ApiModelProperty("组织ID") + private String orgId; + + @ApiModelProperty("组织ID") + private Long organizationId; + + @ApiModelProperty("组织名称") + private String organizationName; + + @ApiModelProperty("一级组织ID") + private Long topOrganizationId; + + // ==================== 金额/费用快照 ==================== + @ApiModelProperty("发货运费") + private BigDecimal deliveryFreight; + + @ApiModelProperty("已收运费") + private BigDecimal collectedFreight; + + @ApiModelProperty("未收运费") + private BigDecimal uncollectedFreight; + + @ApiModelProperty("少收运费") + private BigDecimal collectLessFreight; + + @ApiModelProperty("运费单价(元/吨)") + private BigDecimal transportationUnitPrice; + + @ApiModelProperty("信息费") + private BigDecimal infoFee; + + @ApiModelProperty("计费单价") + private BigDecimal freightUnitPrice; + + @ApiModelProperty("货物保险金额") + private BigDecimal cargoInsuranceAmount; + + @ApiModelProperty("运费") + private BigDecimal freightCharges; + + @ApiModelProperty("运费") + private BigDecimal freightFee; + + @ApiModelProperty("卸货费") + private BigDecimal unloadingFee; + + @ApiModelProperty("报关费") + private BigDecimal customsFee; + + @ApiModelProperty("差货费") + private BigDecimal shortageFee; + + @ApiModelProperty("杂费") + private BigDecimal miscellaneousFee; + + @ApiModelProperty("其他费用") + private BigDecimal otherFee; + + @ApiModelProperty("滞车费") + private BigDecimal detentionFee; + + @ApiModelProperty("税率") + private BigDecimal taxRate; + + @ApiModelProperty("创建人") + private String createBy; + + @ApiModelProperty("创建人姓名") + private String createName; + + @ApiModelProperty("创建时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + @ApiModelProperty("更新人") + private String updateBy; + + @ApiModelProperty("更新时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + @ApiModelProperty("删除标记 1-正常 0-删除") + private Integer delFlag; + + @ApiModelProperty("调整后业务单整行JSON快照(用于全字段精确还原)") + private String orderSnapshotJson; + + @ApiModelProperty("调整后费用明细JSON快照(business_order_account列表)") + private String feeDetailJson; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjustDetail/repository/mapper/BusinessDocumentOrderAdjustDetailMapper.java b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjustDetail/repository/mapper/BusinessDocumentOrderAdjustDetailMapper.java new file mode 100644 index 000000000..ca694765f --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/businessDocumentOrderAdjustDetail/repository/mapper/BusinessDocumentOrderAdjustDetailMapper.java @@ -0,0 +1,12 @@ +package com.mhd.oms.domain.businessDocumentOrderAdjustDetail.repository.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.mhd.oms.domain.businessDocumentOrderAdjustDetail.entity.BusinessDocumentOrderAdjustDetail; +import org.apache.ibatis.annotations.Mapper; + +/** + * 运输业务单 调整明细 Mapper + */ +@Mapper +public interface BusinessDocumentOrderAdjustDetailMapper extends BaseMapper { +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationOrder/service/OmsAddressMultiService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationOrder/service/OmsAddressMultiService.java index 3ddce8ecd..70652f315 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationOrder/service/OmsAddressMultiService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationOrder/service/OmsAddressMultiService.java @@ -610,6 +610,12 @@ public class OmsAddressMultiService implements IOmsAddressMultiService { List tmsOrderAccountList = tmsOrderAddDTO.getTmsOrderAccountList(); if (ObjectUtil.isNotNull(tmsOrderAccountList) && tmsOrderAccountList.size() > 0) { for (OmsOrderAccount tmsOrderAccount : tmsOrderAccountList) { + // 防御:account_id 为空或<=0 时跳过(避免唯一约束撞库 + 插入脏数据) + if (tmsOrderAccount.getAccountId() == null || tmsOrderAccount.getAccountId() <= 0) { + log.warn("订单费用科目 accountId 为空,已跳过该条, orderId={}, subjectName={}", + resevationOrder.getId(), tmsOrderAccount.getSubjectName()); + continue; + } tmsOrderAccount.setIsDelete(0); tmsOrderAccount.setOrderId(String.valueOf(resevationOrder.getId())); tmsOrderAccount.setCreateBy(String.valueOf(userPo.getUserId())); @@ -1116,14 +1122,20 @@ public class OmsAddressMultiService implements IOmsAddressMultiService { if (ObjectUtil.isNotNull(tmsOrderAccountList) && tmsOrderAccountList.size() > 0) { // 先删除旧的费用科目信息 tmsOrderAccountMapper.delete(new LambdaQueryWrapper().eq(OmsOrderAccount::getOrderId, String.valueOf(resevationOrder.getId()))); - + // 插入新的费用科目信息 for (OmsOrderAccount tmsOrderAccount : tmsOrderAccountList) { + // 防御:account_id 为空或<=0 时跳过 + if (tmsOrderAccount.getAccountId() == null || tmsOrderAccount.getAccountId() <= 0) { + log.warn("订单费用科目 accountId 为空,已跳过该条, orderId={}, subjectName={}", + resevationOrder.getId(), tmsOrderAccount.getSubjectName()); + continue; + } tmsOrderAccount.setIsDelete(0); tmsOrderAccount.setOrderId(String.valueOf(resevationOrder.getId())); tmsOrderAccount.setCreateBy(String.valueOf(userPo.getUserId())); tmsOrderAccount.setCreateTime(new Date()); - + // 设置组织信息 if (tmsOrderAddDTO.getOrganizationId() != null) { tmsOrderAccount.setOrganizationId(tmsOrderAddDTO.getOrganizationId()); @@ -1134,7 +1146,7 @@ public class OmsAddressMultiService implements IOmsAddressMultiService { if (tmsOrderAddDTO.getTopOrganizationId() != null) { tmsOrderAccount.setTopOrganizationId(tmsOrderAddDTO.getTopOrganizationId()); } - + tmsOrderAccountMapper.insert(tmsOrderAccount); } } diff --git a/mhd_oms/src/main/java/com/mhd/oms/infrastructure/feign/ThirdPartyServiceFeign.java b/mhd_oms/src/main/java/com/mhd/oms/infrastructure/feign/ThirdPartyServiceFeign.java index 49f1ebafb..3295b8fe1 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/infrastructure/feign/ThirdPartyServiceFeign.java +++ b/mhd_oms/src/main/java/com/mhd/oms/infrastructure/feign/ThirdPartyServiceFeign.java @@ -22,6 +22,7 @@ import java.util.Map; /** * 三方服务 Feign */ +// 目标地址由 nacos 配置项 feign.url.mhd-third-party-service 指定,未配置时按服务名走服务发现 @FeignClient(contextId = "thirdPartyServiceFeign",url = "http://10.33.0.129:8012", value = ServiceNameConstants.THIRDPARTY_CENTER, configuration = FeignAutoConfiguration.class) public interface ThirdPartyServiceFeign { diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustAuditDTO.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustAuditDTO.java new file mode 100644 index 000000000..f2db85d7d --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustAuditDTO.java @@ -0,0 +1,24 @@ +package com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 运输业务单 调整 审核入参 + */ +@Data +public class BusinessDocumentOrderAdjustAuditDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("调整记录ID(必传)") + private Long adjustId; + + @ApiModelProperty("审核结果 1-通过 2-驳回") + private Integer auditResult; + + @ApiModelProperty("审核/驳回意见") + private String auditRemark; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustDTO.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustDTO.java new file mode 100644 index 000000000..911af0260 --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustDTO.java @@ -0,0 +1,37 @@ +package com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust; + +import com.mhd.oms.domain.businessDocumentOrder.entity.BusinessDocumentOrder; +import com.mhd.oms.domain.businessOrderAccount.BusinessOrderAccount; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 运输业务单 调整提交入参 + * + * 前端整单回传 business_document_order 全部字段 + 费用明细 business_order_account: + * - amountChanged=true (本次修改了金额) -> 需要业务主管审核 + * - amountChanged=false (仅改其它字段) -> 无需审核,直接更新 + * + * order: 前端把「调整后」业务单整单字段回传(含 id 定位;deliveryFreight 合计可由后端按明细自动算)。 + * feeDetailList: 调整后费用明细(business_order_account),其 amount 之和 = 主表 deliveryFreight。 + */ +@Data +public class BusinessDocumentOrderAdjustDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("调整后业务单(整单字段,含 id 定位;金额合计 deliveryFreight 可由后端按明细自动算)") + private BusinessDocumentOrder order; + + @ApiModelProperty("调整后费用明细(business_order_account),amount 之和作为主表 deliveryFreight 合计") + private List feeDetailList; + + @ApiModelProperty("是否修改了金额(前端判定传入):true-需审核,false-直通") + private Boolean amountChanged; + + @ApiModelProperty("调整原因") + private String adjustReason; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustQueryDTO.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustQueryDTO.java new file mode 100644 index 000000000..c421705f8 --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustQueryDTO.java @@ -0,0 +1,36 @@ +package com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 运输业务单 调整记录 查询条件(管理列表用,可不按订单、查全部) + */ +@Data +public class BusinessDocumentOrderAdjustQueryDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("业务单号(模糊)") + private String goodsNumber; + + @ApiModelProperty("业务单ID") + private Long orderId; + + @ApiModelProperty("是否需要审核 0-否 1-是") + private Integer isAudit; + + @ApiModelProperty("审核状态 0-待审核 1-审核通过 2-驳回") + private Integer auditStatus; + + @ApiModelProperty("调整人姓名(模糊)") + private String adjustByName; + + @ApiModelProperty("所属组织ID(调整人/业务单所属组织)") + private Long organizationId; + + @ApiModelProperty("一级组织ID(组织隔离用)") + private Long topOrganizationId; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustRecordDTO.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustRecordDTO.java new file mode 100644 index 000000000..ebdacbe4c --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/dto/businessDocumentOrderAdjust/BusinessDocumentOrderAdjustRecordDTO.java @@ -0,0 +1,68 @@ +package com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.mhd.oms.domain.businessDocumentOrderAdjustDetail.entity.BusinessDocumentOrderAdjustDetail; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * 运输业务单 调整记录(主记录) 列表/详情返回 VO + */ +@Data +public class BusinessDocumentOrderAdjustRecordDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主记录ID") + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + @ApiModelProperty("业务单ID") + private Long orderId; + + @ApiModelProperty("业务单号") + private String goodsNumber; + + @ApiModelProperty("调整人账号") + private String adjustBy; + + @ApiModelProperty("调整人姓名") + private String adjustByName; + + @ApiModelProperty("调整时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date adjustTime; + + @ApiModelProperty("调整原因") + private String adjustReason; + + @ApiModelProperty("是否需要审核 0-否 1-是") + private Integer isAudit; + + @ApiModelProperty("审核状态 0-待审核 1-审核通过 2-驳回") + private Integer auditStatus; + + @ApiModelProperty("审核人账号") + private String auditBy; + + @ApiModelProperty("审核人姓名") + private String auditByName; + + @ApiModelProperty("审核时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date auditTime; + + @ApiModelProperty("审核/驳回意见") + private String auditRemark; + + @ApiModelProperty("调整后整行快照明细(详情查询时带出)") + private BusinessDocumentOrderAdjustDetail detail; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/businessDocumentOrder/BusinessDocumentOrderApi.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/businessDocumentOrder/BusinessDocumentOrderApi.java index abce60fdb..217435c2d 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/businessDocumentOrder/BusinessDocumentOrderApi.java +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/businessDocumentOrder/BusinessDocumentOrderApi.java @@ -10,7 +10,12 @@ import org.springframework.web.bind.annotation.*; import com.mhd.oms.interfaces.dto.businessDocumentOrder.BusinessDocumentOrderDTO; import com.mhd.oms.domain.businessDocumentOrder.repository.todo.BusinessDocumentOrderDO; import com.mhd.oms.domain.businessDocumentOrder.repository.po.BusinessDocumentOrderPO; +import com.mhd.oms.application.service.businessDocumentOrder.BusinessDocumentOrderAdjustService; import com.mhd.oms.application.service.businessDocumentOrder.BusinessDocumentOrderApplicationService; +import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustAuditDTO; +import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustDTO; +import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustQueryDTO; +import com.mhd.oms.interfaces.dto.businessDocumentOrderAdjust.BusinessDocumentOrderAdjustRecordDTO; import com.mhd.oms.interfaces.assembler.businessDocumentOrder.BusinessDocumentOrderAssembler; import com.github.pagehelper.PageHelper; import javax.annotation.Resource; @@ -33,6 +38,9 @@ public class BusinessDocumentOrderApi extends BaseController{ @Resource private BusinessDocumentOrderAssembler businessDocumentOrderAssembler; + @Resource + private BusinessDocumentOrderAdjustService businessDocumentOrderAdjustService; + /** * 分页查询【业务单】列表 */ @@ -161,7 +169,64 @@ public class BusinessDocumentOrderApi extends BaseController{ } } + /** + * 订单调整 - 提交调整 + * 若修改了金额字段则进入调整待审核,否则直通更新 + */ + @ApiOperation("订单调整 - 提交调整") + @PostMapping("/adjust") + public AjaxResult adjust(@RequestBody BusinessDocumentOrderAdjustDTO adjustDTO) { + try { + return businessDocumentOrderAdjustService.adjust(adjustDTO); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error(e.getMessage()); + } + } + /** + * 订单调整 - 调整记录列表(查全部,可分页/按业务单号或审核状态筛选) + */ + @ApiOperation("订单调整 - 调整记录列表") + @GetMapping("/listAdjustRecord") + public TableDataInfo listAdjustRecord(BusinessDocumentOrderAdjustQueryDTO query, + @RequestParam(value = "pageNo", required = false, defaultValue = "1") Integer pageNo, + @RequestParam(value = "pageSize", required = false, defaultValue = "20") Integer pageSize) { + PageHelper.startPage(pageNo, pageSize); + List list = businessDocumentOrderAdjustService.listAdjustRecord(query); + return getDataTable(list); + } + /** + * 订单调整 - 按业务单查询调整记录(供订单详情页展示) + */ + @ApiOperation("订单调整 - 按业务单查询调整记录") + @GetMapping("/listAdjustRecordByOrder/{orderId}") + public AjaxResult listAdjustRecordByOrder(@PathVariable("orderId") Long orderId) { + return AjaxResult.success(businessDocumentOrderAdjustService.listAdjustRecordByOrder(orderId)); + } + + /** + * 订单调整 - 调整记录详情 + */ + @ApiOperation("订单调整 - 调整记录详情") + @GetMapping("/getAdjustRecord/{adjustId}") + public AjaxResult getAdjustRecord(@PathVariable("adjustId") Long adjustId) { + return AjaxResult.success(businessDocumentOrderAdjustService.getAdjustRecord(adjustId)); + } + + /** + * 订单调整 - 审核(通过/驳回) + */ + @ApiOperation("订单调整 - 审核") + @PostMapping("/auditAdjust") + public AjaxResult auditAdjust(@RequestBody BusinessDocumentOrderAdjustAuditDTO auditDTO) { + try { + return businessDocumentOrderAdjustService.auditAdjust(auditDTO); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error(e.getMessage()); + } + } } diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/handoverTaskOrder/HandoverTaskOrderApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/handoverTaskOrder/HandoverTaskOrderApplicationService.java index 7b4c9919b..97f789e2f 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/application/service/handoverTaskOrder/HandoverTaskOrderApplicationService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/handoverTaskOrder/HandoverTaskOrderApplicationService.java @@ -33,6 +33,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.formula.functions.T; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.text.ParseException; import java.util.List; @@ -223,12 +224,13 @@ public class HandoverTaskOrderApplicationService { * @param handoverTaskOrderDO * @return Boolean */ + @Transactional(rollbackFor = Exception.class) public Boolean completeHandover(HandoverTaskOrderDO handoverTaskOrderDO){ Boolean result = handoverTaskOrderDomainService.completeHandover(handoverTaskOrderDO); List clearoutParamDOs = handoverTaskOrderDomainService.getLastClearoutParamDOs(); -// for (MaterialInventoryParamDO paramDO : clearoutParamDOs) { -// materialInventoryApplicationService.pushBillingRecordClearout(paramDO.getMaterialInventoryId(), paramDO.getQuantity()); -// } + for (MaterialInventoryParamDO paramDO : clearoutParamDOs) { + materialInventoryApplicationService.pushBillingRecordClearout(paramDO.getMaterialInventoryId(), paramDO.getQuantity()); + } return result; } diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java index 0a9729fd3..45db356c2 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java @@ -573,10 +573,11 @@ public class MaterialBaseInfoApplicationService { normalizeImportBarCodeWhenDuplicateOfHs(row); fillIdsFromExistingMasterData(row); fillOrganizationFromShipperIfNeeded(row); - if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) { + // 登录组织优先:以登录账号的组织为准,覆盖货主解析带来的组织,避免用货主组织导致包装规格等主数据查不到 + if (isValidOrgId(loginOrgId)) { row.setOrganizationId(loginOrgId); } - if (row.getTopOrganizationId() == null && isValidOrgId(loginTopOrgId)) { + if (isValidOrgId(loginTopOrgId)) { row.setTopOrganizationId(loginTopOrgId); } fillOrganizationName(row, loginUser); diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoAsyncService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoAsyncService.java index 5a3554478..d878d0d27 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoAsyncService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoAsyncService.java @@ -128,10 +128,11 @@ public class MaterialBaseInfoAsyncService { materialBaseInfoApplicationService.normalizeImportBarCodeWhenDuplicateOfHs(row); materialBaseInfoApplicationService.fillIdsFromExistingMasterData(row); materialBaseInfoApplicationService.fillOrganizationFromShipperIfNeeded(row); - if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) { + // 登录组织优先:以登录账号的组织为准,覆盖货主解析带来的组织,避免用货主组织(如2841)导致包装规格等主数据查不到 + if (isValidOrgId(loginOrgId)) { row.setOrganizationId(loginOrgId); } - if (row.getTopOrganizationId() == null && isValidOrgId(loginTopOrgId)) { + if (isValidOrgId(loginTopOrgId)) { row.setTopOrganizationId(loginTopOrgId); } materialBaseInfoApplicationService.fillOrganizationName(row, loginUser); diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/stockInOrder/StockInOrderApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/stockInOrder/StockInOrderApplicationService.java index d23cb6307..5420bfbf4 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/application/service/stockInOrder/StockInOrderApplicationService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/stockInOrder/StockInOrderApplicationService.java @@ -2075,21 +2075,71 @@ public class StockInOrderApplicationService { r.getMaterialCode(), topOrganizationId, shipperId); List materialList = materialBaseInfoService.queryList(queryDO); log.info("料号查询结果,查询到 {} 条记录", materialList != null ? materialList.size() : 0); - MaterialBaseInfoPO materialBaseInfoPO = null; - if (materialList != null && !materialList.isEmpty()) { - materialBaseInfoPO = materialList.get(0); + MaterialBaseInfoPO materialBaseInfoPO = pickMaterialByCode(materialList, r.getMaterialCode(), shipperId, order.getShipperName()); + // 兜底:按名称解析出的shipperId可能与物料档案shipper_id不一致(系统中存在同名货主账号, + // 按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 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(); - log.info("通过料号查询到物料基础信息,料号: {}, 物料ID: {}, 货主ID: {}", + log.info("通过料号查询到物料基础信息,料号: {}, 物料ID: {}, 货主ID: {}", r.getMaterialCode(), materialBaseInfoId, shipperId); } else { // 如果提供了料号但查询不到(或不属于该货主),直接报错,不允许导入 - String errorMsg = String.format("导入失败:该货主下不存在该料号(货主名称=%s,料号=%s)。请确保该料号已在物料管理中创建,且属于该货主", - order.getShipperName(), r.getMaterialCode()); + // 诊断对照查询(仅用于日志定位,不影响流程):去掉货主ID条件再查一次, + // 用于区分两类原因: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 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); throw new ServiceException(errorMsg); } // 校验料号与规格型号/SKU码一致性:按料号查到档案后逐项比对,任何一项不符直接报对应错误 - // 双方(trim后)必须完全一致:Excel留空而档案有值、或Excel填了值而档案为空,均视为不一致报错 + // 规格型号(trim后)双方必须完全一致:Excel留空而档案有值、或Excel填了值而档案为空,均视为不一致报错 + // SKU码:档案条码(barCode)为空时跳过比对(存量档案普遍未维护条码);档案有值时仍严格比对 String excelSpec = StringUtils.trimToEmpty(r.getSpecificationModel()); String dbSpec = StringUtils.trimToEmpty(materialBaseInfoPO.getSpecificationModel()); if (!excelSpec.equals(dbSpec)) { @@ -2099,7 +2149,7 @@ public class StockInOrderApplicationService { } String excelSku = StringUtils.trimToEmpty(r.getSkuCode()); 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)", r.getMaterialCode(), r.getMaterialName(), excelSku.isEmpty() ? "空" : excelSku, dbSku.isEmpty() ? "空" : dbSku)); @@ -2433,6 +2483,46 @@ public class StockInOrderApplicationService { return oid != null && loginOrganizationId.equals(oid); } + /** + * 导入校验:从按料号(SQL为LIKE模糊)查出的物料列表中挑选匹配记录。 + * 优先级:料号精确等值优先于模糊命中(避免子串料号误匹配); + * 同级内优先 shipperId 精确匹配,其次 shipperName 等值匹配,最后取排序第一条(与原 get(0) 行为一致)。 + */ + private MaterialBaseInfoPO pickMaterialByCode(List 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) * @param shipperName 货主名称 diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/persistence/MaterialInventoryImpl.java b/mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/persistence/MaterialInventoryImpl.java index e0f4bdf73..5b83a8e6c 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/persistence/MaterialInventoryImpl.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/persistence/MaterialInventoryImpl.java @@ -1032,7 +1032,7 @@ public class MaterialInventoryImpl extends ServiceImpl shelfMaterialDetailList = shelfMaterialDetailService.list(new QueryWrapper().lambda().eq(ShelfMaterialDetail::getShelfOrderNumber, stockShelfOrderPO.getShelfOrderNumber())); + List shelfMaterialDetailList = shelfMaterialDetailService.list(new QueryWrapper().lambda() + .eq(ShelfMaterialDetail::getShelfOrderNumber, stockShelfOrderPO.getShelfOrderNumber()) + .eq(ShelfMaterialDetail::getDelFlag, 1)); //修改库存 StockShelfOrderDO stockShelfOrderDO = new StockShelfOrderDO(); BeanUtils.copyProperties(stockShelfOrderPO, stockShelfOrderDO); diff --git a/mhd_wms/src/main/java/com/mhd/wms/util/XxlJobConfig.java b/mhd_wms/src/main/java/com/mhd/wms/util/XxlJobConfig.java index e17d38a46..9627d0e7b 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/util/XxlJobConfig.java +++ b/mhd_wms/src/main/java/com/mhd/wms/util/XxlJobConfig.java @@ -9,46 +9,46 @@ import org.springframework.context.annotation.Configuration; /** * XXL-JOB 配置类 */ -//@Configuration +@Configuration public class XxlJobConfig { -// private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); -// -// @Value("${xxl.job.admin.addresses}") -// private String adminAddresses; -// -// @Value("${xxl.job.accessToken}") -// private String accessToken; -// -// @Value("${xxl.job.executor.appname}") -// private String appname; -// -// @Value("${xxl.job.executor.address}") -// private String address; -// -// @Value("${xxl.job.executor.ip}") -// private String ip; -// -// @Value("${xxl.job.executor.port}") -// private int port; -// -// @Value("${xxl.job.executor.logpath}") -// private String logPath; -// -// @Value("${xxl.job.executor.logretentiondays}") -// private int logRetentionDays; -// -// @Bean -// public XxlJobSpringExecutor xxlJobExecutor() { -// logger.info(">>>>>>>>>>> xxl-job config init."); -// XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); -// xxlJobSpringExecutor.setAdminAddresses(adminAddresses); -// xxlJobSpringExecutor.setAppname(appname); -// xxlJobSpringExecutor.setAddress(address); -// xxlJobSpringExecutor.setIp(ip); -// xxlJobSpringExecutor.setPort(port); -// xxlJobSpringExecutor.setAccessToken(accessToken); -// xxlJobSpringExecutor.setLogPath(logPath); -// xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); -// return xxlJobSpringExecutor; -// } + private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); + + @Value("${xxl.job.admin.addresses}") + private String adminAddresses; + + @Value("${xxl.job.accessToken}") + private String accessToken; + + @Value("${xxl.job.executor.appname}") + private String appname; + + @Value("${xxl.job.executor.address}") + private String address; + + @Value("${xxl.job.executor.ip}") + private String ip; + + @Value("${xxl.job.executor.port}") + private int port; + + @Value("${xxl.job.executor.logpath}") + private String logPath; + + @Value("${xxl.job.executor.logretentiondays}") + private int logRetentionDays; + + @Bean + public XxlJobSpringExecutor xxlJobExecutor() { + logger.info(">>>>>>>>>>> xxl-job config init."); + XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); + xxlJobSpringExecutor.setAdminAddresses(adminAddresses); + xxlJobSpringExecutor.setAppname(appname); + xxlJobSpringExecutor.setAddress(address); + xxlJobSpringExecutor.setIp(ip); + xxlJobSpringExecutor.setPort(port); + xxlJobSpringExecutor.setAccessToken(accessToken); + xxlJobSpringExecutor.setLogPath(logPath); + xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); + return xxlJobSpringExecutor; + } } \ No newline at end of file diff --git a/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml b/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml index 68d752e32..cd75c81a2 100644 --- a/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml +++ b/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml @@ -1864,7 +1864,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" LEFT JOIN NGWL_TEST_BMS.SETTLEMENT_CUSTOMERS bms ON sys.SETTLEMENT_CUSTOMERS_ID = bms.SETTLEMENT_CUSTOMERS_ID WHERE 1 = 1 - AND CONTRACT_MANAGE_ID = #{contractManageId} AND DEL_FLAG = 1 + AND sys.CONTRACT_MANAGE_ID = #{contractManageId} AND sys.DEL_FLAG = 1