diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java index cbdc9f69..912b2b17 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java @@ -35,10 +35,9 @@ import java.util.Set; * 运次查询条件 *

* 司机端用 {@link #forDriver(Long)} 按司机档案 ID 收口;管理端用 {@link #forAdmin()} 不按司机收口。 - * 两者都不给时收口为空集——收口一旦缺省就等于把别人的运次发出去,故缺省不放行。 + * 两者都不给时收口为空集。 *

- * 判据是司机档案 ID 而不是手机号:手机号在配载单/运单上只是派单时的快照,司机换号后并不跟随, - * 按号收口会让改过号的司机看不到自己手上的运次,号码被回收给别人时还会反向串号。 + * 归属判据只有司机档案 ID;driver_phone 是派单时的号码快照,不参与判定。 */ @Data @Schema(description = "运次查询条件") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java index 8aab3912..9433a569 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java @@ -34,7 +34,6 @@ import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.constant.BladeConstant; import org.springblade.core.tool.utils.Func; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -152,7 +151,6 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { private final IWaybillService waybillService; private final IDriverService driverService; - private final IMpUserClient mpUserClient; private final IProcessConfigService processConfigService; private final ITransportVehicleService transportVehicleService; private final CertificationGate certificationGate; @@ -923,8 +921,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { /** * 当前登录司机:只按登录用户 ID 查司机档案,查不到即「无归属」。 *

- * 曾经查不到档案时按手机号回退匹配 driver.mobile,这让两个同号档案可以互相冒充, - * 已删除:手机号在归属链路上不再有任何作用。 + * 归属判据:driver_id = 当前登录人的司机档案 id;driver_phone 不参与判定。 */ private Driver currentDriver() { Long userId = AuthUtil.getUserId(); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java index 13b6252e..63a9cbf4 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java @@ -4,6 +4,7 @@ */ package org.springblade.transport.service.impl; +import cn.hutool.core.util.DesensitizedUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -11,6 +12,7 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; +import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; @@ -143,16 +145,17 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds = new ArrayList<>(); // 新建暂存为草稿;编辑已有单据时保持原业务状态(如待执行),避免被改回草稿 String businessStatus = STATUS_DRAFT; - if (Func.isNotEmpty(loadingManage.getId())) { - LoadingManage oldRecord = loadEditable(loadingManage.getId(), true); - oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); - loadingManage.setLoadingNo(oldRecord.getLoadingNo()); - loadingManage.setDeptId(oldRecord.getDeptId()); - loadingManage.setDeptName(oldRecord.getDeptName()); - if (Func.isNotEmpty(oldRecord.getBusinessStatus())) { - businessStatus = oldRecord.getBusinessStatus(); + LoadingManage stored = Func.isEmpty(loadingManage.getId()) ? null : loadEditable(loadingManage.getId(), true); + if (stored != null) { + oldWaybillIds = waybillIds(stored.getWaybillIdsJson()); + loadingManage.setLoadingNo(stored.getLoadingNo()); + loadingManage.setDeptId(stored.getDeptId()); + loadingManage.setDeptName(stored.getDeptName()); + if (Func.isNotEmpty(stored.getBusinessStatus())) { + businessStatus = stored.getBusinessStatus(); } } + applyDriverOwnership(loadingManage, stored); prepareCreateOrUpdate(loadingManage); loadingManage.setBusinessStatus(businessStatus); if (Func.isEmpty(loadingManage.getLoadingNo())) { @@ -204,6 +207,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); loadingManage.setLoadingNo(oldRecord.getLoadingNo()); @@ -756,12 +761,6 @@ public class LoadingManageServiceImpl extends BaseServiceImpl0 才算有主,0 / -1 都是「无主」的哨兵写法,落库统一为 NULL - loadingManage.setDriverId(resolvedDriverId != null && resolvedDriverId > 0 ? resolvedDriverId : null); loadingManage.setEscortName(TransportBusinessSupport.trimToNull(loadingManage.getEscortName())); loadingManage.setEscortPhone(TransportBusinessSupport.trimToNull(loadingManage.getEscortPhone())); loadingManage.setCarrierType(TransportBusinessSupport.trimToNull(loadingManage.getCarrierType())); @@ -794,6 +793,32 @@ public class LoadingManageServiceImpl extends BaseServiceImpl + * 判定顺序:调用方带了正数档案 ID 就用它;更新时 driver_phone 与库中一致且库中 driver_id 非空就保留库中值; + * 其余情况(新建、换号、原本无主)按手机号在租户内唯一匹配司机档案,匹配不到落 null 并留 WARN,不阻断保存。 + */ + private void applyDriverOwnership(LoadingManage loadingManage, LoadingManage stored) { + Long driverId = loadingManage.getDriverId(); + if (driverId != null && driverId > 0) { + return; + } + if (stored != null && Func.isNotEmpty(stored.getDriverId()) + && Objects.equals(TransportBusinessSupport.trimToNull(stored.getDriverPhone()), loadingManage.getDriverPhone())) { + loadingManage.setDriverId(stored.getDriverId()); + return; + } + String tenantId = Func.isNotEmpty(loadingManage.getTenantId()) ? loadingManage.getTenantId() : AuthUtil.getTenantId(); + Long resolvedDriverId = driverService.resolveDriverIdByMobile(tenantId, loadingManage.getDriverPhone()); + loadingManage.setDriverId(resolvedDriverId); + if (resolvedDriverId == null) { + log.warn("配载单 {} 的司机手机号未唯一匹配司机档案,driver_id 落空。租户={} 手机号={}", + Func.isEmpty(loadingManage.getLoadingNo()) ? "(未编号)" : loadingManage.getLoadingNo(), + tenantId, DesensitizedUtil.mobilePhone(loadingManage.getDriverPhone())); + } + } + private void validate(LoadingManage loadingManage, boolean draftMode) { TransportBusinessSupport.validateRequired(loadingManage.getLoadingSubNos(), "配载子单号不能为空"); TransportBusinessSupport.validateRequired(loadingManage.getTransportType(), "运输类型不能为空"); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java index 1f054b21..d459c234 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java @@ -4,8 +4,10 @@ */ package org.springblade.transport.service.impl; +import cn.hutool.core.util.DesensitizedUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.extern.slf4j.Slf4j; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.tool.jackson.JsonUtil; @@ -51,6 +53,7 @@ import java.util.Objects; * @author Chill */ @Service +@Slf4j public class MasterOrderServiceImpl extends BaseServiceImpl implements IMasterOrderService { private static final String CARRIER_SELF = "自运"; @@ -266,11 +269,15 @@ public class MasterOrderServiceImpl extends BaseServiceImpl 0 ? resolvedDriverId : null; + driverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone()); + if (driverId == null) { + log.warn("总单 {} 派单的司机手机号未唯一匹配司机档案,driver_id 落空(不阻断派单)。租户={} 手机号={}", + masterOrder.getMasterNo(), waybill.getTenantId(), DesensitizedUtil.mobilePhone(waybill.getDriverPhone())); + } } waybill.setDriverId(driverId); waybill.setCaptainName(string(dispatch, "captainName")); waybill.setCabinNo(string(dispatch, "cabinNo")); waybill.setContainerNo(string(dispatch, "containerNo")); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripQueryServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripQueryServiceImpl.java index 7c35161c..ee63e0b3 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripQueryServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripQueryServiceImpl.java @@ -389,12 +389,10 @@ public class TripQueryServiceImpl implements ITripQueryService { } /** - * 运次可见范围:配载段按配载单的司机档案 ID 收口,未配载段按运单的司机档案 ID 收口。 + * 运次可见范围:已配载段按配载单的 driver_id 收口,未配载段按运单的 driver_id 收口。 *

- * 判据是司机档案 ID 而不是手机号:司机换号后档案 ID 不变,而派单时落在配载单/运单上的手机号 - * 是快照,按号收口会让改过号的司机看不到自己手上的运次;号码被回收给别人时又会反向串号。 - * 两段必须同时切:只切一段,另一段就会把不属于该司机的运次放进来。 - * 缺省收口(空、0、负数)不放行,由 {@link #scoped} 在外层挡掉。 + * 归属判据只有司机档案 ID(null 或非正数视为无主),driver_phone 不参与判定。 + * 无主的运次由 {@link #scoped} 在外层挡掉。 */ private void applyScope(LambdaQueryWrapper wrapper, TripQueryVO condition) { if (condition.isAllDrivers()) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java index f78533ab..5eabbc36 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java @@ -500,12 +500,12 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl 0) { return; } + // 解析入口只返回 null 或唯一命中的档案 ID,不需要再按哨兵归一 Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), waybill.getDriverPhone()); - if (resolvedDriverId != null && resolvedDriverId > 0) { + if (resolvedDriverId != null) { waybill.setDriverId(resolvedDriverId); return; } - // driver_id 只有 >0 才算有主,0 / -1 都按无主落空 waybill.setDriverId(null); log.warn("第{}行 运单 {} 的司机手机号未唯一匹配司机档案,driver_id 留空(不阻断导入)", rowNo, waybill.getWaybillNo()); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index aa3d3290..611b4e5f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -22,6 +22,7 @@ */ package org.springblade.transport.service.impl; +import cn.hutool.core.util.DesensitizedUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -1615,12 +1616,15 @@ public class WaybillServiceImpl extends BaseServiceImpl TransportBusinessSupport.validateRequired(vehicleNo, "车牌号不能为空"); // 归属主键是档案 ID,手机号只是派单时的号码快照:调用方带了档案 ID 就用它, - // 否则按手机号解析;承运商司机、未建档司机解析为空,派单照旧成功,只留 WARN + // 否则按手机号解析;解析入口只返回 null 或唯一命中的档案 ID,承运商司机、未建档司机 + // 解析为空,派单照旧成功,落空时留 WARN Long driverId = request.getDriverId(); if (driverId == null || driverId <= 0) { - Long resolvedDriverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), driverPhone); - // driver_id 只有 >0 才算有主,0 / -1 都是「无主」的哨兵写法,落库统一为 NULL - driverId = resolvedDriverId != null && resolvedDriverId > 0 ? resolvedDriverId : null; + driverId = driverService.resolveDriverIdByMobile(waybill.getTenantId(), driverPhone); + if (driverId == null) { + log.warn("运单 {} 的司机手机号未唯一匹配司机档案,driver_id 落空(不阻断派单)。租户={} 手机号={}", + waybill.getWaybillNo(), waybill.getTenantId(), DesensitizedUtil.mobilePhone(driverPhone)); + } } waybill.setDriverId(driverId); waybill.setDriverName(driverName); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverNodePunchPhotoTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverNodePunchPhotoTest.java index bc544d0a..98dcafb8 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverNodePunchPhotoTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverNodePunchPhotoTest.java @@ -12,7 +12,6 @@ import org.mockito.MockedStatic; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.jackson.JsonUtil; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -95,7 +94,6 @@ class DriverNodePunchPhotoTest { service = new DriverWaybillServiceImpl( waybillService, driverService, - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverPunchFinishFlagTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverPunchFinishFlagTest.java index 06667c50..132a3b98 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverPunchFinishFlagTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverPunchFinishFlagTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -92,7 +91,6 @@ class DriverPunchFinishFlagTest { service = new DriverWaybillServiceImpl( waybillService, mock(IDriverService.class), - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverPunchNodeCardTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverPunchNodeCardTest.java index 0009525f..25c4b5ed 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverPunchNodeCardTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverPunchNodeCardTest.java @@ -10,7 +10,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springblade.core.tool.jackson.JsonUtil; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -87,7 +86,6 @@ class DriverPunchNodeCardTest { service = new DriverWaybillServiceImpl( waybillService, mock(IDriverService.class), - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverResolveMobileTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverResolveMobileTest.java index 01db1969..89ba9503 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverResolveMobileTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverResolveMobileTest.java @@ -96,8 +96,9 @@ class DriverResolveMobileTest { when(driverMapper.selectList(any())).thenReturn(List.of(profile(9001L))); Long driverId = null; - try (var ignored = org.mockito.Mockito.mockStatic(org.springblade.core.secure.utils.AuthUtil.class)) { - ignored.when(org.springblade.core.secure.utils.AuthUtil::getTenantId).thenReturn(TENANT_ID); + try (org.mockito.MockedStatic authUtil + = org.mockito.Mockito.mockStatic(org.springblade.core.secure.utils.AuthUtil.class)) { + authUtil.when(org.springblade.core.secure.utils.AuthUtil::getTenantId).thenReturn(TENANT_ID); driverId = service.resolveDriverIdByMobile(null, MOBILE); } assertEquals(9001L, driverId); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripAcceptRejectTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripAcceptRejectTest.java index 8e8e474d..cad12c15 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripAcceptRejectTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripAcceptRejectTest.java @@ -14,7 +14,6 @@ import org.mockito.MockedStatic; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.secure.BladeUser; import org.springblade.core.secure.utils.AuthUtil; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -88,7 +87,6 @@ class DriverTripAcceptRejectTest { service = new DriverWaybillServiceImpl( waybillService, driverService, - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), certificationGate, diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripCardTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripCardTest.java index d783008e..c0034f02 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripCardTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripCardTest.java @@ -10,7 +10,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.secure.utils.AuthUtil; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -74,7 +73,6 @@ class DriverTripCardTest { private IWaybillService waybillService; private IDriverService driverService; - private IMpUserClient mpUserClient; private ITripQueryService tripQueryService; private WaybillEnroutePunchMapper enroutePunchMapper; private WaybillNodePunchMapper nodePunchMapper; @@ -94,14 +92,12 @@ class DriverTripCardTest { void setUp() { waybillService = mock(IWaybillService.class); driverService = mock(IDriverService.class); - mpUserClient = mock(IMpUserClient.class); tripQueryService = mock(ITripQueryService.class); enroutePunchMapper = mock(WaybillEnroutePunchMapper.class); nodePunchMapper = mock(WaybillNodePunchMapper.class); service = new DriverWaybillServiceImpl( waybillService, driverService, - mpUserClient, mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillAutoCompleteTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillAutoCompleteTest.java index 42b742fe..ab2bd8e5 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillAutoCompleteTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillAutoCompleteTest.java @@ -11,7 +11,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.secure.utils.AuthUtil; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -116,7 +115,6 @@ class DriverWaybillAutoCompleteTest { service = new DriverWaybillServiceImpl( waybillService, driverService, - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillCardLocationTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillCardLocationTest.java index 13ace47b..af981add 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillCardLocationTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillCardLocationTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -73,7 +72,6 @@ class DriverWaybillCardLocationTest { service = new DriverWaybillServiceImpl( waybillService, mock(IDriverService.class), - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillNodePunchTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillNodePunchTest.java index f1223d3c..8c4ad4ec 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillNodePunchTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillNodePunchTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -55,7 +54,6 @@ class DriverWaybillNodePunchTest { service = new DriverWaybillServiceImpl( mock(IWaybillService.class), mock(IDriverService.class), - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillOwnershipTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillOwnershipTest.java index ad19610d..2dc1e234 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillOwnershipTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillOwnershipTest.java @@ -12,7 +12,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.secure.utils.AuthUtil; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -76,7 +75,6 @@ class DriverWaybillOwnershipTest { service = new DriverWaybillServiceImpl( waybillService, driverService, - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillRejectDetachTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillRejectDetachTest.java index 228d5800..dc4b349c 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillRejectDetachTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillRejectDetachTest.java @@ -15,7 +15,6 @@ import org.mockito.ArgumentMatchers; import org.mockito.MockedStatic; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.secure.utils.AuthUtil; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -87,7 +86,6 @@ class DriverWaybillRejectDetachTest { service = new DriverWaybillServiceImpl( waybillService, driverService, - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), certificationGate, diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillTabStatusTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillTabStatusTest.java index 256eff6b..029adb95 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillTabStatusTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillTabStatusTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.springblade.core.secure.utils.AuthUtil; -import org.springblade.system.feign.IMpUserClient; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -86,7 +85,6 @@ class DriverWaybillTabStatusTest { service = new DriverWaybillServiceImpl( waybillService, driverService, - mock(IMpUserClient.class), mock(IProcessConfigService.class), mock(ITransportVehicleService.class), mock(CertificationGate.class), diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/LoadingManageChildStatusWriteTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/LoadingManageChildStatusWriteTest.java index 6087f36b..3213ac95 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/LoadingManageChildStatusWriteTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/LoadingManageChildStatusWriteTest.java @@ -1,5 +1,7 @@ package org.springblade.transport.service.impl; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -24,6 +26,7 @@ import org.springblade.transport.service.IContractManageService; import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.IProcessConfigService; import org.springblade.transport.service.IReceivablePayableDetailService; +import org.springblade.transport.support.LogCapture; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -35,6 +38,7 @@ import java.util.Map; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -66,6 +70,9 @@ class LoadingManageChildStatusWriteTest { private static final String DRIVER_PHONE = "13800138000"; private static final String TENANT_ID = "000000"; private static final Long RESOLVED_DRIVER_ID = 9321L; + private static final Long MANUAL_DRIVER_ID = 7001L; + private static final Long OTHER_PROFILE_ID = 8888L; + private static final String OTHER_DRIVER_PHONE = "13900139000"; private static final Long SOURCE_LOADING_ID = 1800000000000000002L; private static final Long NEED_ACCEPT_WAYBILL_ID = 3101L; @@ -394,6 +401,110 @@ class LoadingManageChildStatusWriteTest { assertEquals(RESOLVED_DRIVER_ID, capturedSaved().getDriverId()); } + @Test + @DisplayName("已归属的单子号码没变:无关字段的保存不重解析,归属原样保留") + void unrelatedEditKeepsStoredDriverId() { + LoadingManage record = runningRecord(); + record.setDriverId(RESOLVED_DRIVER_ID); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + // 这一次谁都解析不到:只要实现还按手机号重解析,归属就会被清空 + when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(null); + + assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID))); + + assertEquals(RESOLVED_DRIVER_ID, capturedLoading().getDriverId()); + assertEquals(RESOLVED_DRIVER_ID, statusSyncUpdates().get(0).setValue("driver_id")); + verify(driverService, never()).resolveDriverIdByMobile(any(), any()); + } + + @Test + @DisplayName("人工补过归属的单子:号码恰好命中别人的档案,保存也不改归属") + void unrelatedEditKeepsManualOwnership() { + LoadingManage record = runningRecord(); + record.setDriverId(MANUAL_DRIVER_ID); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(OTHER_PROFILE_ID); + + assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID))); + + assertEquals(MANUAL_DRIVER_ID, capturedLoading().getDriverId(), + "人工归属不能被一次无关保存改到同号的另一个档案上"); + assertEquals(MANUAL_DRIVER_ID, statusSyncUpdates().get(0).setValue("driver_id")); + } + + @Test + @DisplayName("-1 哨兵的归属:号码没变的保存原样保留,不抹成 NULL") + void sentinelDriverIdSurvivesUnrelatedEdit() { + LoadingManage record = runningRecord(); + record.setDriverId(-1L); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(null); + + assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID))); + + assertEquals(-1L, capturedLoading().getDriverId()); + } + + @Test + @DisplayName("换司机:号码变了就按新号码重新解析,归属跟着换") + void changingPhoneResolvesNewDriverId() { + LoadingManage record = pendingRecord(); + record.setDriverId(RESOLVED_DRIVER_ID); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + stubConfigs(config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON)); + when(driverService.resolveDriverIdByMobile(any(), eq(OTHER_DRIVER_PHONE))).thenReturn(OTHER_PROFILE_ID); + LoadingManage form = editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID); + form.setDriverPhone(OTHER_DRIVER_PHONE); + + assertTrue(service.submit(form)); + + assertEquals(OTHER_PROFILE_ID, capturedLoading().getDriverId()); + assertEquals(OTHER_PROFILE_ID, statusSyncUpdates().get(0).setValue("driver_id")); + } + + @Test + @DisplayName("号码改成一个解析不到的号:归属落空,子单同步落空") + void changingPhoneToUnresolvedNumberClearsDriverId() { + LoadingManage record = pendingRecord(); + record.setDriverId(RESOLVED_DRIVER_ID); + stubLoadingRecord(record); + stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID)); + stubConfigs(config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON)); + when(driverService.resolveDriverIdByMobile(any(), eq(OTHER_DRIVER_PHONE))).thenReturn(null); + LoadingManage form = editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID); + form.setDriverPhone(OTHER_DRIVER_PHONE); + + assertTrue(service.submit(form)); + + assertNull(capturedLoading().getDriverId()); + assertNull(statusSyncUpdates().get(0).setValue("driver_id")); + } + + @Test + @DisplayName("解析不到档案:留一条脱敏 WARN,保存照常成功") + void unresolvedPhoneLogsMaskedWarning() { + stubWaybills(waybill(NO_ACCEPT_WAYBILL_ID, NO_ACCEPT_PROJECT_ID)); + stubConfigs(config(NO_ACCEPT_PROJECT_ID, NO_ACCEPT_JSON)); + when(driverService.resolveDriverIdByMobile(any(), eq(DRIVER_PHONE))).thenReturn(null); + try (LogCapture logs = LogCapture.open(LoadingManageServiceImpl.class)) { + assertTrue(service.submit(loadingForm(NO_ACCEPT_WAYBILL_ID))); + + String warning = logs.events().stream() + .filter(event -> event.getLevel() == Level.WARN) + .map(ILoggingEvent::getFormattedMessage) + .filter(message -> message.contains("司机档案")) + .findFirst() + .orElseThrow(() -> new AssertionError("解析失败必须留 WARN,实际日志:" + logs.messages())); + assertTrue(warning.contains("138****8000"), "WARN 里的号码必须脱敏,实际:" + warning); + assertFalse(warning.contains(DRIVER_PHONE), "WARN 里不能出现完整号码,实际:" + warning); + assertTrue(warning.contains(LOADING_NO), "WARN 要带业务标识(配载单号),实际:" + warning); + } + } + // ------------------------------------------------------------ 夹具 private LoadingManage loadingForm(Long... waybillIds) { @@ -468,6 +579,14 @@ class LoadingManageChildStatusWriteTest { return record; } + private LoadingManage pendingRecord() { + LoadingManage record = loadingRecord("pending", NEED_ACCEPT_WAYBILL_ID); + record.setVehicleNo("桂A12345"); + record.setDriverName("张三"); + record.setDriverPhone(DRIVER_PHONE); + return record; + } + private LoadingManage copySource() { LoadingManage source = loadingRecord("pending", NEED_ACCEPT_WAYBILL_ID); source.setLoadingSubNos("YD-" + NEED_ACCEPT_WAYBILL_ID); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/MasterOrderDriverIdTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/MasterOrderDriverIdTest.java index 8872dd01..c9290709 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/MasterOrderDriverIdTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/MasterOrderDriverIdTest.java @@ -1,5 +1,7 @@ package org.springblade.transport.service.impl; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,6 +13,7 @@ import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.IProjectApplyService; import org.springblade.transport.service.ITransportPlanService; import org.springblade.transport.service.IWaybillService; +import org.springblade.transport.support.LogCapture; import java.lang.reflect.Method; import java.util.LinkedHashMap; @@ -18,6 +21,7 @@ import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -84,6 +88,25 @@ class MasterOrderDriverIdTest { verify(waybillService).submit(any(Waybill.class)); } + @Test + @DisplayName("解析不到档案:落空时留一条脱敏 WARN,派单照常成功") + void unresolvedProfileLogsMaskedWarning() throws Exception { + when(driverService.resolveDriverIdByMobile(any(), eq(DRIVER_PHONE))).thenReturn(null); + try (LogCapture logs = LogCapture.open(MasterOrderServiceImpl.class)) { + assertTrue(createWaybill(dispatch())); + + String warning = logs.events().stream() + .filter(event -> event.getLevel() == Level.WARN) + .map(ILoggingEvent::getFormattedMessage) + .filter(message -> message.contains("司机档案")) + .findFirst() + .orElseThrow(() -> new AssertionError("解析失败必须留 WARN,实际日志:" + logs.messages())); + assertTrue(warning.contains("138****8000"), "WARN 里的号码必须脱敏,实际:" + warning); + assertFalse(warning.contains(DRIVER_PHONE), "WARN 里不能出现完整号码,实际:" + warning); + assertTrue(warning.contains("ZD-20261027-001"), "WARN 要带业务标识(总单号),实际:" + warning); + } + } + // ------------------------------------------------------------ 夹具 private boolean createWaybill(Map dispatch) throws Exception { diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillImportDriverIdTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillImportDriverIdTest.java index d9581932..b7bf273b 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillImportDriverIdTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillImportDriverIdTest.java @@ -86,17 +86,6 @@ class WaybillImportDriverIdTest { assertNull(waybill.getDriverId()); } - @Test - @DisplayName("解析器返回 0(无主哨兵):按无主落空,不把 0 写进库") - void normalizesSentinelToNull() throws Exception { - when(driverService.resolveDriverIdByMobile(any(), eq(DRIVER_PHONE))).thenReturn(0L); - - Waybill waybill = importedWaybill(0L); - fillDriverOwnership(waybill, 3); - - assertNull(waybill.getDriverId()); - } - // ------------------------------------------------------------ 夹具 private void fillDriverOwnership(Waybill waybill, int rowNo) throws Exception { diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillReassignDriverIdTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillReassignDriverIdTest.java index 6ce47414..8646431a 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillReassignDriverIdTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillReassignDriverIdTest.java @@ -1,5 +1,7 @@ package org.springblade.transport.service.impl; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import org.apache.ibatis.builder.MapperBuilderAssistant; @@ -12,11 +14,13 @@ import org.springblade.transport.mapper.WaybillMapper; import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.service.IDriverService; import org.springblade.transport.service.IProcessConfigService; +import org.springblade.transport.support.LogCapture; import java.lang.reflect.Field; import java.util.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -108,6 +112,25 @@ class WaybillReassignDriverIdTest { assertNull(capturedWaybill().getDriverId()); } + @Test + @DisplayName("解析不到档案:落空时留一条脱敏 WARN,派单照常成功") + void unresolvedProfileLogsMaskedWarning() { + when(driverService.resolveDriverIdByMobile(TENANT_ID, DRIVER_PHONE)).thenReturn(null); + try (LogCapture logs = LogCapture.open(WaybillServiceImpl.class)) { + assertTrue(service.reassignWithoutDeptCheck(reassignRequest(null))); + + String warning = logs.events().stream() + .filter(event -> event.getLevel() == Level.WARN) + .map(ILoggingEvent::getFormattedMessage) + .filter(message -> message.contains("司机档案")) + .findFirst() + .orElseThrow(() -> new AssertionError("解析失败必须留 WARN,实际日志:" + logs.messages())); + assertTrue(warning.contains("138****8000"), "WARN 里的号码必须脱敏,实际:" + warning); + assertFalse(warning.contains(DRIVER_PHONE), "WARN 里不能出现完整号码,实际:" + warning); + assertTrue(warning.contains("YD-20261027-001"), "WARN 要带业务标识(运单号),实际:" + warning); + } + } + // ------------------------------------------------------------ 夹具 private void setUpAgain() throws Exception { @@ -131,6 +154,7 @@ class WaybillReassignDriverIdTest { waybill.setId(WAYBILL_ID); waybill.setIsDeleted(0); waybill.setTenantId(TENANT_ID); + waybill.setWaybillNo("YD-20261027-001"); waybill.setBusinessStatus("pending"); waybill.setTransportType("road"); waybill.setDriverName("张三"); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/LogCapture.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/LogCapture.java new file mode 100644 index 00000000..7fa8d76b --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/LogCapture.java @@ -0,0 +1,48 @@ +package org.springblade.transport.support; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * 抓取某个类的日志输出(logback ListAppender)。 + *

+ * 「解析失败时留一条脱敏 WARN」这类要求只看返回值验不出来,必须断言真实日志正文。 + */ +public final class LogCapture implements AutoCloseable { + + private final Logger logger; + private final ListAppender appender; + + private LogCapture(Logger logger, ListAppender appender) { + this.logger = logger; + this.appender = appender; + } + + public static LogCapture open(Class loggerType) { + Logger logger = (Logger) LoggerFactory.getLogger(loggerType); + ListAppender appender = new ListAppender<>(); + appender.start(); + logger.addAppender(appender); + return new LogCapture(logger, appender); + } + + /** 已按占位符填充好的日志正文,按发生顺序 */ + public List messages() { + return appender.list.stream().map(ILoggingEvent::getFormattedMessage).toList(); + } + + public List events() { + return List.copyOf(appender.list); + } + + @Override + public void close() { + logger.detachAppender(appender); + appender.stop(); + } + +} diff --git a/doc/sql/transport/blade_loading_manage_driver_id_backfill_20261028.sql b/doc/sql/transport/blade_loading_manage_driver_id_backfill_20261028.sql index 8aa2da8a..b2a42d36 100644 --- a/doc/sql/transport/blade_loading_manage_driver_id_backfill_20261028.sql +++ b/doc/sql/transport/blade_loading_manage_driver_id_backfill_20261028.sql @@ -22,8 +22,9 @@ -- ⑤ 逻辑删除的配载单同样回填:票面口径只有 driver_id IS NULL,不额外收窄范围; -- 这些单不会回到列表,回填对它们无副作用。 -- ⑥ 跨表比较显式 COLLATE:blade_loading_manage.driver_phone / tenant_id 是 utf8mb4_0900_ai_ci, --- blade_transport_driver.mobile / tenant_id 是 utf8mb4_general_ci; +-- blade_transport_driver.mobile / tenant_id 与存档表都是 utf8mb4_general_ci; -- 不显式 COLLATE 会抛 ERROR 1267 Illegal mix of collations,只有真跑库才会暴露。 +-- 第 2.4 节拿存档表比 driver_phone 同样要带 COLLATE utf8mb4_0900_ai_ci。 -- ⑦ 取数与写入全在 SQL 内完成,不经 JSON / 客户端中转:19 位雪花 ID 过 JSON 会丢精度。 -- ⑧ 分批执行(默认每批 500 行),每批独立提交,中断后重跑从剩余 NULL 行继续。 -- @@ -41,11 +42,11 @@ CREATE TABLE IF NOT EXISTS blade_loading_manage_driver_id_bak_20261028 ( loading_id bigint NOT NULL COMMENT '配载单ID', driver_id_before bigint DEFAULT NULL COMMENT '回填前的司机ID(本脚本只处理 NULL)', driver_id_after bigint DEFAULT NULL COMMENT '回填写入的司机档案ID(blade_transport_driver.id)', - driver_phone varchar(50) COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '回填前的手机号快照,用来证明没有被改写', + driver_phone varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '回填前的手机号快照,用来证明没有被改写', update_time_before datetime DEFAULT NULL COMMENT '回填前的更新时间,用来证明没有被改写', PRIMARY KEY (loading_id), KEY idx_driver_id_after (driver_id_after) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='20261028 配载单司机档案ID回填存档'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='20261028 配载单司机档案ID回填存档'; -- 命中映射:只收「号码唯一命中一条未删除档案」的 NULL 行。 -- INSERT IGNORE + WHERE driver_id IS NULL:第二次执行不会重复建映射,已经填过的行不会再进映射。 @@ -151,7 +152,7 @@ SELECT COUNT(*) AS wrong_driver_id SELECT COUNT(*) AS snapshot_rewritten FROM blade_loading_manage_driver_id_bak_20261028 b JOIN blade_loading_manage w ON w.id = b.loading_id - WHERE NOT (w.driver_phone <=> b.driver_phone) + WHERE NOT (w.driver_phone <=> b.driver_phone COLLATE utf8mb4_0900_ai_ci) OR NOT (w.update_time <=> b.update_time_before); -- 2.5 冲突清单:同号多档案,这些行被跳过(期望 0 行;有输出就是「为什么没填」的答案) diff --git a/doc/sql/transport/blade_tms_business.sql b/doc/sql/transport/blade_tms_business.sql index a91fa295..4dbf5c84 100644 --- a/doc/sql/transport/blade_tms_business.sql +++ b/doc/sql/transport/blade_tms_business.sql @@ -300,7 +300,8 @@ CREATE TABLE `blade_waybill` ( KEY `idx_waybill_dept` (`dept_id`) USING BTREE, KEY `idx_waybill_plan_id` (`plan_id`) USING BTREE, KEY `idx_waybill_carrier_contract_id` (`carrier_contract_id`) USING BTREE, - KEY `idx_waybill_create_time` (`create_time`) USING BTREE + KEY `idx_waybill_create_time` (`create_time`) USING BTREE, + KEY `idx_waybill_tenant_driver_id` (`tenant_id`,`driver_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运单管理'; -- ---------------------------- @@ -330,6 +331,7 @@ CREATE TABLE `blade_loading_manage` ( `trailer_vehicle_no` varchar(100) DEFAULT NULL COMMENT '挂车车牌号', `driver_name` varchar(100) DEFAULT NULL COMMENT '司机', `driver_phone` varchar(50) DEFAULT NULL COMMENT '联系电话', + `driver_id` bigint(20) DEFAULT NULL COMMENT '司机ID', `escort_name` varchar(100) DEFAULT NULL COMMENT '押运人', `escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号', `carrier_type` varchar(50) DEFAULT NULL COMMENT '承运类型', @@ -359,7 +361,8 @@ CREATE TABLE `blade_loading_manage` ( UNIQUE KEY `uk_loading_manage_no` (`loading_no`) USING BTREE, KEY `idx_loading_manage_carrier_contract_id` (`carrier_contract_id`) USING BTREE, KEY `idx_loading_manage_dept` (`dept_id`) USING BTREE, - KEY `idx_loading_manage_create_time` (`create_time`) USING BTREE + KEY `idx_loading_manage_create_time` (`create_time`) USING BTREE, + KEY `idx_loading_manage_tenant_driver_id` (`tenant_id`,`driver_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配载管理'; -- ---------------------------- diff --git a/doc/sql/transport/blade_waybill_driver_id_backfill_20261028.sql b/doc/sql/transport/blade_waybill_driver_id_backfill_20261028.sql index e42a82b9..22417a64 100644 --- a/doc/sql/transport/blade_waybill_driver_id_backfill_20261028.sql +++ b/doc/sql/transport/blade_waybill_driver_id_backfill_20261028.sql @@ -24,8 +24,9 @@ -- ⑤ 逻辑删除的运单同样回填:票面口径只有 driver_id IS NULL,不额外收窄范围; -- 这些单不会回到列表,回填对它们无副作用。 -- ⑥ 跨表比较显式 COLLATE:blade_waybill.driver_phone / tenant_id 是 utf8mb4_0900_ai_ci, --- blade_transport_driver.mobile / tenant_id 是 utf8mb4_general_ci; +-- blade_transport_driver.mobile / tenant_id 与存档表都是 utf8mb4_general_ci; -- 不显式 COLLATE 会抛 ERROR 1267 Illegal mix of collations,只有真跑库才会暴露。 +-- 第 2.4 节拿存档表比 driver_phone 同样要带 COLLATE utf8mb4_0900_ai_ci。 -- ⑦ 取数与写入全在 SQL 内完成,不经 JSON / 客户端中转:19 位雪花 ID 过 JSON 会丢精度。 -- ⑧ 分批执行(默认每批 500 行),每批独立提交,中断后重跑从剩余 NULL 行继续。 -- @@ -44,11 +45,11 @@ CREATE TABLE IF NOT EXISTS blade_waybill_driver_id_bak_20261028 ( waybill_id bigint NOT NULL COMMENT '运单ID', driver_id_before bigint DEFAULT NULL COMMENT '回填前的司机ID(本脚本只处理 NULL)', driver_id_after bigint DEFAULT NULL COMMENT '回填写入的司机档案ID(blade_transport_driver.id)', - driver_phone varchar(50) COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '回填前的手机号快照,用来证明没有被改写', + driver_phone varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '回填前的手机号快照,用来证明没有被改写', update_time_before datetime DEFAULT NULL COMMENT '回填前的更新时间,用来证明没有被改写', PRIMARY KEY (waybill_id), KEY idx_driver_id_after (driver_id_after) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='20261028 运单司机档案ID回填存档'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='20261028 运单司机档案ID回填存档'; -- 命中映射:只收「号码唯一命中一条未删除档案」的 NULL 行。 -- INSERT IGNORE + WHERE driver_id IS NULL:第二次执行不会重复建映射,已经填过的行不会再进映射。 @@ -154,7 +155,7 @@ SELECT COUNT(*) AS wrong_driver_id SELECT COUNT(*) AS snapshot_rewritten FROM blade_waybill_driver_id_bak_20261028 b JOIN blade_waybill w ON w.id = b.waybill_id - WHERE NOT (w.driver_phone <=> b.driver_phone) + WHERE NOT (w.driver_phone <=> b.driver_phone COLLATE utf8mb4_0900_ai_ci) OR NOT (w.update_time <=> b.update_time_before); -- 2.5 冲突清单:同号多档案,这些行被跳过(期望 0 行;有输出就是「为什么没填」的答案)