diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverWaybillService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverWaybillService.java index 0d9d3f15..8f9cd52d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverWaybillService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverWaybillService.java @@ -130,9 +130,9 @@ public interface IDriverWaybillService { boolean isNodePunched(Long waybillId, String nodeCode); /** - * 断言当前登录司机对该运单有归属:运单车牌须落在司机档案自述绑定的车牌内。 + * 断言当前登录司机对该运单有归属:运单司机手机号须等于当前用户手机号。 *

- * 无司机档案、未绑定车辆、车牌不匹配都会抛异常。管理端与调度员账号没有司机档案, + * 无司机档案、无手机号、手机号不匹配都会抛异常。管理端与调度员账号没有司机档案, * 会停在「未找到当前登录司机档案」——这是刻意的,不提供代报。 */ void assertCurrentDriverOwnsWaybill(Long waybillId); 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 967738bd..57c8ceb4 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 @@ -142,12 +142,12 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillCardVO currentTask() { - List plates = currentBoundPlates(); - if (plates.isEmpty()) { + String phone = currentUserPhone(); + if (Func.isEmpty(phone)) { return null; } // 进行中:running,或无需确认接单但仍为 pending 的历史数据 - List candidates = waybillService.list(scopedQuery(plates) + List candidates = waybillService.list(scopedQuery(phone) .in(Waybill::getBusinessStatus, List.of(STATUS_RUNNING, STATUS_PENDING)) .orderByDesc(Waybill::getUpdateTime) .last("LIMIT 20")); @@ -163,12 +163,12 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillPreviewVO pendingPreview(Integer size) { DriverWaybillPreviewVO preview = new DriverWaybillPreviewVO(); - List plates = currentBoundPlates(); - if (plates.isEmpty()) { + String phone = currentUserPhone(); + if (Func.isEmpty(phone)) { return preview; } int limit = normalizePreviewSize(size); - List pendingList = waybillService.list(scopedQuery(plates) + List pendingList = waybillService.list(scopedQuery(phone) .in(Waybill::getBusinessStatus, PENDING_STATUSES) .orderByDesc(Waybill::getCreateTime)); List needAccept = pendingList.stream() @@ -183,11 +183,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillTabCountsVO tabCounts() { DriverWaybillTabCountsVO vo = new DriverWaybillTabCountsVO(); - List plates = currentBoundPlates(); - if (plates.isEmpty()) { + String phone = currentUserPhone(); + if (Func.isEmpty(phone)) { return vo; } - List waybills = waybillService.list(scopedQuery(plates) + List waybills = waybillService.list(scopedQuery(phone) .in(Waybill::getBusinessStatus, TAB_ALL_STATUSES)); long pending = 0; long doing = 0; @@ -219,12 +219,12 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { int pageSize = size == null || size < 1 ? DEFAULT_PAGE_SIZE : Math.min(size, MAX_PAGE_SIZE); Page empty = new Page<>(pageNo, pageSize); - List plates = currentBoundPlates(); - if (plates.isEmpty()) { + String phone = currentUserPhone(); + if (Func.isEmpty(phone)) { return empty; } - LambdaQueryWrapper wrapper = scopedQuery(plates); + LambdaQueryWrapper wrapper = scopedQuery(phone); // 先按 Tab 口径拉候选,再按过程配置校正 pending→running 后内存分页 applyStatusFilterForQuery(wrapper, status); applyKeyword(wrapper, keyword); @@ -247,15 +247,15 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillCardVO detail(Long id) { - Driver driver = requireCurrentDriver(); - Waybill waybill = loadDriverWaybill(id, currentBoundPlates(driver)); + requireCurrentDriver(); + Waybill waybill = loadDriverWaybill(id); return toCard(normalizeAcceptStatus(waybill), true); } @Override public WaybillTrackVO track(Long id) { - Driver driver = requireCurrentDriver(); - Waybill waybill = loadDriverWaybill(id, currentBoundPlates(driver)); + requireCurrentDriver(); + Waybill waybill = loadDriverWaybill(id); LocalDate[] range = resolveTrackDateRange(waybill); return waybillService.trackVehicle(waybill.getId(), range[0].toString(), range[1].toString()); } @@ -318,7 +318,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } Driver driver = requireCurrentDriver(); // 在途打卡单独保留原取单判据,不排除已拒单(见 scopedQuery 的重载说明) - Waybill waybill = loadDriverWaybill(dto.getWaybillId(), currentBoundPlates(driver), true); + Waybill waybill = loadDriverWaybill(dto.getWaybillId(), true); waybill = normalizeAcceptStatus(waybill); String processJson = resolveProcessJson(waybill); @@ -367,7 +367,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { throw new ServiceException("节点编码不能为空"); } Driver driver = requireCurrentDriver(); - Waybill waybill = loadDriverWaybill(dto.getWaybillId(), currentBoundPlates(driver)); + Waybill waybill = loadDriverWaybill(dto.getWaybillId()); waybill = normalizeAcceptStatus(waybill); String processJson = resolveProcessJson(waybill); @@ -443,7 +443,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { // 认证门槛:司机本人须已认证,且承运车辆须已认证。 // 判据是各自持有的生效快照指针,故「已认证的车重新提交认证」不会拦下接单。 assertCertified(driver); - Waybill waybill = loadDriverWaybill(id, currentBoundPlates(driver)); + Waybill waybill = loadDriverWaybill(id); waybillService.syncDriverAcceptState(waybill); assertAcceptable(waybill); Date now = new Date(); @@ -493,8 +493,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override @Transactional(rollbackFor = Exception.class) public boolean reject(Long id, String reason) { - Driver driver = requireCurrentDriver(); - Waybill waybill = loadDriverWaybill(id, currentBoundPlates(driver)); + requireCurrentDriver(); + Waybill waybill = loadDriverWaybill(id); waybillService.syncDriverAcceptState(waybill); assertRejectable(waybill); String rejectReason = Func.isEmpty(reason) ? null : reason.trim(); @@ -517,8 +517,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { if (id == null) { throw new ServiceException("运单ID不能为空"); } - Driver driver = requireCurrentDriver(); - Waybill waybill = loadDriverWaybill(id, currentBoundPlates(driver)); + requireCurrentDriver(); + Waybill waybill = loadDriverWaybill(id); waybill = normalizeAcceptStatus(waybill); if (!COMPLETABLE_STATUSES.contains(waybill.getBusinessStatus())) { throw new ServiceException("当前运单状态不允许完成"); @@ -557,19 +557,20 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } } - private Waybill loadDriverWaybill(Long id, List plates) { - return loadDriverWaybill(id, plates, false); + private Waybill loadDriverWaybill(Long id) { + return loadDriverWaybill(id, false); } /** @param includeRejected 仅 submitEnroute 传 true,见 scopedQuery 的重载说明 */ - private Waybill loadDriverWaybill(Long id, List plates, boolean includeRejected) { + private Waybill loadDriverWaybill(Long id, boolean includeRejected) { if (id == null) { throw new ServiceException("运单ID不能为空"); } - if (plates.isEmpty()) { - throw new ServiceException("当前司机未绑定车辆"); + String phone = currentUserPhone(); + if (Func.isEmpty(phone)) { + throw new ServiceException("当前用户手机号为空,无法查找运单"); } - Waybill waybill = waybillService.getOne(scopedQuery(plates, includeRejected) + Waybill waybill = waybillService.getOne(scopedQuery(phone, includeRejected) .eq(Waybill::getId, id).last("LIMIT 1")); if (waybill == null) { throw new ServiceException("运单不存在或无权操作"); @@ -626,14 +627,14 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } /** - * 司机可见运单范围:运单车牌(主车/挂车)落在当前司机绑定车牌内, - * 且该运单未被这个司机拒单。绑定来源:blade_transport_driver.driving_vehicle + * 司机可见运单范围:运单司机手机号(driver_phone)等于当前用户手机号, + * 且该运单未被这个司机拒单。 *

* 拒单后运单对司机彻底脱离——列表、计数、详情、打卡、接单一律不可达,等管理员重新派单。 * 运单上的司机姓名/手机/车牌不清空,调度重新派单时要知道原来派给谁。 */ - private LambdaQueryWrapper scopedQuery(List plates) { - return scopedQuery(plates, false); + private LambdaQueryWrapper scopedQuery(String phone) { + return scopedQuery(phone, false); } /** @@ -641,21 +642,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { * 拒单的运单不可能处于运输中,收口拦不到合法操作, * 而保留原判据可避免「已拒单 + 进行中」的历史脏数据被误伤 */ - private LambdaQueryWrapper scopedQuery(List plates, boolean includeRejected) { - LambdaQueryWrapper wrapper = Wrappers.lambdaQuery().and(w -> { - w.in(Waybill::getVehicleNo, plates) - .or().in(Waybill::getTrailerVehicleNo, plates); - for (String plate : plates) { - w.or().apply( - "REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(UPPER(IFNULL(vehicle_no,'')),'·',''),'•',''),'.',''),'-',''),' ','') = {0}", - plate - ); - w.or().apply( - "REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(UPPER(IFNULL(trailer_vehicle_no,'')),'·',''),'•',''),'.',''),'-',''),' ','') = {0}", - plate - ); - } - }); + private LambdaQueryWrapper scopedQuery(String phone, boolean includeRejected) { + String normalizedPhone = Func.toStr(phone, "").trim(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery().and(w -> w + .eq(Waybill::getDriverPhone, normalizedPhone) + .or().apply("REPLACE(IFNULL(driver_phone,''),' ','') = {0}", normalizedPhone)); if (!includeRejected) { wrapper.and(w -> w.isNull(Waybill::getDriverAcceptStatus) .or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED)); @@ -676,13 +667,26 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { .or().like(Waybill::getVehicleNo, kw)); } - /** 当前登录司机绑定的规范化车牌列表;无司机或无绑定车牌则空 */ - private List currentBoundPlates() { + /** + * 当前用户手机号:司机档案 mobile 优先,其次小程序用户账号。 + *

+ * 派单时运单 driver_phone 取自司机档案 mobile,故档案有手机号就以它为准。 + */ + private String currentUserPhone() { Driver driver = currentDriver(); - if (driver == null || Func.isEmpty(driver.getDrivingVehicle())) { - return List.of(); + if (driver != null && isMobileLike(driver.getMobile())) { + return driver.getMobile().trim(); } - return splitPlates(driver.getDrivingVehicle()); + Long userId = AuthUtil.getUserId(); + if (userId == null || userId <= 0) { + return null; + } + R result = mpUserClient.userById(userId); + if (result != null && R.isSuccess(result) && result.getData() != null + && isMobileLike(result.getData().getAccount())) { + return result.getData().getAccount().trim(); + } + return null; } /** @@ -928,9 +932,9 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public void assertCurrentDriverOwnsWaybill(Long waybillId) { - // 归属口径与司机打卡、司机接单一致:都按司机档案自述绑定的车牌判定 - Driver driver = requireCurrentDriver(); - loadDriverWaybill(waybillId, currentBoundPlates(driver)); + // 归属口径与司机打卡、司机接单一致:按当前用户手机号匹配运单司机手机号 + requireCurrentDriver(); + loadDriverWaybill(waybillId); } /** 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 86b9a3e7..12d00939 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 @@ -58,6 +58,7 @@ class DriverNodePunchPhotoTest { private static final Long WAYBILL_ID = 5601L; private static final Long USER_ID = 7001L; private static final String PLATE = "桂AAW3255"; + private static final String PHONE = "13800138003"; private static final String CLOCK_PHOTO = "https://oss/clock.jpg"; private static final String WAYBILL_PHOTO = "https://oss/waybill.jpg"; private static final String RECEIPT_PHOTO = "https://oss/receipt.jpg"; @@ -308,6 +309,7 @@ class DriverNodePunchPhotoTest { driver.setId(9001L); driver.setUserId(USER_ID); driver.setDrivingVehicle(PLATE); + driver.setMobile(PHONE); return driver; } @@ -316,6 +318,7 @@ class DriverNodePunchPhotoTest { waybill.setId(WAYBILL_ID); waybill.setWaybillNo("YD202610070001"); waybill.setVehicleNo(PLATE); + waybill.setDriverPhone(PHONE); waybill.setProcessJson(PROCESS_JSON); waybill.setBusinessStatus("running"); waybill.setIsDeleted(0); 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 1aef3f7c..91ecf020 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 @@ -49,6 +49,7 @@ class DriverWaybillAutoCompleteTest { private static final Long WAYBILL_ID = 5801L; private static final Long USER_ID = 7001L; private static final String PLATE = "桂AAW3255"; + private static final String PHONE = "13800138001"; private static final String CLOCK_PHOTO = "https://oss/clock.jpg"; /** 末位打卡节点是「签收」,没有回单节点 */ @@ -219,6 +220,7 @@ class DriverWaybillAutoCompleteTest { waybill.setId(WAYBILL_ID); waybill.setWaybillNo("YD202610080001"); waybill.setVehicleNo(PLATE); + waybill.setDriverPhone(PHONE); waybill.setProcessJson(processJson); waybill.setBusinessStatus(businessStatus); waybill.setIsDeleted(0); @@ -237,6 +239,7 @@ class DriverWaybillAutoCompleteTest { driver.setId(9001L); driver.setUserId(USER_ID); driver.setDrivingVehicle(PLATE); + driver.setMobile(PHONE); return driver; } 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 0a1a5df6..035a2db1 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 @@ -38,14 +38,14 @@ import static org.mockito.Mockito.when; /** * 司机对运单的归属校验(异常上报等按运单操作的前置条件)。 *

- * 口径与司机打卡、司机接单一致:当前登录司机档案自述绑定的车牌({@code driving_vehicle}) - * 覆盖运单主车或挂车。管理端与调度员账号没有司机档案,会停在这里,这是刻意的:不提供代报。 + * 口径:当前用户手机号匹配运单 {@code driverPhone}。 + * 管理端与调度员账号没有司机档案,会停在这里,这是刻意的:不提供代报。 */ class DriverWaybillOwnershipTest { private static final Long WAYBILL_ID = 5301L; private static final Long USER_ID = 7001L; - private static final String PLATE = "沪AE31312"; + private static final String PHONE = "13800138000"; private IDriverService driverService; private IWaybillService waybillService; @@ -88,21 +88,21 @@ class DriverWaybillOwnershipTest { } @Test - @DisplayName("司机档案没有绑定车辆时被拒") - void withoutBoundVehicleIsRejected() { + @DisplayName("司机档案与登录态都没有手机号时被拒") + void withoutPhoneIsRejected() { Driver driver = driver(null); when(driverService.getOne(any())).thenReturn(driver); ServiceException exception = assertThrows(ServiceException.class, () -> runOwnershipCheck(WAYBILL_ID)); - assertEquals("当前司机未绑定车辆", exception.getMessage()); + assertEquals("当前用户手机号为空,无法查找运单", exception.getMessage()); } @Test - @DisplayName("运单车牌不在当前司机绑定车牌内时被拒") - void waybillOfAnotherVehicleIsRejected() { - when(driverService.getOne(any())).thenReturn(driver(PLATE)); + @DisplayName("运单司机手机号与当前用户不一致时被拒") + void waybillOfAnotherPhoneIsRejected() { + when(driverService.getOne(any())).thenReturn(driver(PHONE)); when(waybillService.getOne(any())).thenReturn(null); ServiceException exception = assertThrows(ServiceException.class, @@ -112,9 +112,9 @@ class DriverWaybillOwnershipTest { } @Test - @DisplayName("本人绑定车牌的运单放行,且查询确实按车牌收敛") + @DisplayName("本人手机号运单放行,且查询确实按司机手机号收敛") void ownWaybillPasses() { - when(driverService.getOne(any())).thenReturn(driver(PLATE)); + when(driverService.getOne(any())).thenReturn(driver(PHONE)); when(waybillService.getOne(any())).thenReturn(waybill()); assertDoesNotThrow(() -> runOwnershipCheck(WAYBILL_ID)); @@ -122,17 +122,17 @@ class DriverWaybillOwnershipTest { ArgumentCaptor> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class); verify(waybillService).getOne(captor.capture()); LambdaQueryWrapper wrapper = captor.getValue(); - assertTrue(wrapper.getTargetSql().contains("vehicle_no"), - "归属查询必须落在运单主车/挂车车牌上:" + wrapper.getTargetSql()); + assertTrue(wrapper.getTargetSql().contains("driver_phone"), + "归属查询必须落在运单司机手机号上:" + wrapper.getTargetSql()); assertTrue(wrapper.getParamNameValuePairs().values().stream() - .anyMatch(PLATE::equals), - "归属查询必须带上当前司机绑定的车牌:" + wrapper.getParamNameValuePairs()); + .anyMatch(PHONE::equals), + "归属查询必须带上当前用户手机号:" + wrapper.getParamNameValuePairs()); } @Test @DisplayName("运单ID为空时被拒") void withoutWaybillIdIsRejected() { - when(driverService.getOne(any())).thenReturn(driver(PLATE)); + when(driverService.getOne(any())).thenReturn(driver(PHONE)); ServiceException exception = assertThrows(ServiceException.class, () -> runOwnershipCheck(null)); @@ -145,18 +145,17 @@ class DriverWaybillOwnershipTest { private void runOwnershipCheck(Long waybillId) { try (MockedStatic authUtil = mockStatic(AuthUtil.class)) { authUtil.when(AuthUtil::getUserId).thenReturn(USER_ID); - authUtil.when(AuthUtil::getUserAccount).thenReturn("13800138000"); + authUtil.when(AuthUtil::getUserAccount).thenReturn(PHONE); service.assertCurrentDriverOwnsWaybill(waybillId); } } - private Driver driver(String drivingVehicle) { + private Driver driver(String mobile) { Driver driver = new Driver(); driver.setId(9001L); driver.setUserId(USER_ID); driver.setDriverName("韦司机"); - driver.setMobile("13800138000"); - driver.setDrivingVehicle(drivingVehicle); + driver.setMobile(mobile); return driver; } @@ -164,7 +163,7 @@ class DriverWaybillOwnershipTest { Waybill waybill = new Waybill(); waybill.setId(WAYBILL_ID); waybill.setWaybillNo("YD202610070001"); - waybill.setVehicleNo(PLATE); + waybill.setDriverPhone(PHONE); waybill.setIsDeleted(0); return waybill; } 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 3553c9c3..10f6ccbb 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 @@ -57,6 +57,7 @@ class DriverWaybillRejectDetachTest { private static final Long USER_ID = 7401L; private static final Long DRIVER_ID = 9001L; private static final String PLATE = "沪AE31312"; + private static final String PHONE = "13800138002"; private static final Long REJECTED_ID = 7201L; private IDriverService driverService; @@ -97,7 +98,7 @@ class DriverWaybillRejectDetachTest { } @Test - @DisplayName("首页待接预览的取单条件排除已拒单,且排除子句与车牌条件并列") + @DisplayName("首页待接预览的取单条件排除已拒单,且排除子句与取单条件并列") void pendingPreviewQueryExcludesRejected() { when(waybillService.list(ArgumentMatchers.>any())).thenReturn(List.of()); @@ -107,7 +108,7 @@ class DriverWaybillRejectDetachTest { assertTrue(sql.contains("driver_accept_status"), "取单条件必须排除已拒单:" + sql); assertTrue(sql.toUpperCase().contains("AND"), - "排除子句不能与车牌条件「或」在一起,否则等于没排除:" + sql); + "排除子句不能与取单条件「或」在一起,否则等于没排除:" + sql); } @Test @@ -212,6 +213,7 @@ class DriverWaybillRejectDetachTest { driver.setUserId(USER_ID); driver.setDriverName("黄大富"); driver.setDrivingVehicle(PLATE); + driver.setMobile(PHONE); return driver; } @@ -224,6 +226,7 @@ class DriverWaybillRejectDetachTest { waybill.setId(id); waybill.setWaybillNo(no); waybill.setVehicleNo(PLATE); + waybill.setDriverPhone(PHONE); waybill.setBusinessStatus(businessStatus); waybill.setDriverAcceptStatus(acceptStatus); waybill.setIsDeleted(0);