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 afd82e56..9bd09ed8 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 @@ -166,12 +166,12 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillCardVO currentTask() { - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { + Driver driver = currentDriver(); + if (driver == null) { return null; } // 进行中:running,或无需确认接单但仍为 pending 的历史数据 - List candidates = waybillService.list(scopedQuery(phone) + List candidates = waybillService.list(scopedQuery(driver.getId()) .in(Waybill::getBusinessStatus, List.of(STATUS_RUNNING, STATUS_PENDING)) .orderByDesc(Waybill::getUpdateTime) .last("LIMIT 20")); @@ -187,12 +187,12 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillPreviewVO pendingPreview(Integer size) { DriverWaybillPreviewVO preview = new DriverWaybillPreviewVO(); - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { + Driver driver = currentDriver(); + if (driver == null) { return preview; } int limit = normalizePreviewSize(size); - List pendingList = waybillService.list(scopedQuery(phone) + List pendingList = waybillService.list(scopedQuery(driver.getId()) .in(Waybill::getBusinessStatus, PENDING_STATUSES) .orderByDesc(Waybill::getCreateTime)); List needAccept = pendingList.stream() @@ -207,11 +207,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillTabCountsVO tabCounts() { DriverWaybillTabCountsVO vo = new DriverWaybillTabCountsVO(); - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { + Driver driver = currentDriver(); + if (driver == null) { return vo; } - List waybills = waybillService.list(scopedQuery(phone) + List waybills = waybillService.list(scopedQuery(driver.getId()) .in(Waybill::getBusinessStatus, TAB_ALL_STATUSES)); long pending = 0; long doing = 0; @@ -243,12 +243,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); - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { + Driver driver = currentDriver(); + if (driver == null) { return empty; } - LambdaQueryWrapper wrapper = scopedQuery(phone); + LambdaQueryWrapper wrapper = scopedQuery(driver.getId()); // 先按 Tab 口径拉候选,再按过程配置校正 pending→running 后内存分页 applyStatusFilterForQuery(wrapper, status); applyKeyword(wrapper, keyword); @@ -271,7 +271,6 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillCardVO detail(Long id) { - requireCurrentDriver(); Waybill waybill = loadDriverWaybill(id); return toCard(normalizeAcceptStatus(waybill), true); } @@ -355,7 +354,6 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public WaybillTrackVO track(Long id) { - requireCurrentDriver(); Waybill waybill = loadDriverWaybill(id); LocalDate[] range = resolveTrackDateRange(waybill); return waybillService.trackVehicle(waybill.getId(), range[0].toString(), range[1].toString()); @@ -822,13 +820,10 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { if (id == null) { throw new ServiceException("运单ID不能为空"); } - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { - throw new ServiceException("当前用户手机号为空,无法查找运单"); - } + Driver driver = requireCurrentDriver(); // MySQL 的 FOR UPDATE 必须跟在 LIMIT 之后 String tail = forUpdate ? "LIMIT 1 FOR UPDATE" : "LIMIT 1"; - Waybill waybill = waybillService.getOne(scopedQuery(phone, includeRejected) + Waybill waybill = waybillService.getOne(scopedQuery(driver.getId(), includeRejected) .eq(Waybill::getId, id).last(tail)); if (waybill == null) { throw new ServiceException("运单不存在或无权操作"); @@ -885,14 +880,17 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } /** - * 司机可见运单范围:运单司机手机号(driver_phone)等于当前用户手机号, + * 司机可见运单范围:运单归属档案(driver_id)就是当前司机档案, * 且该运单未被这个司机拒单。 *

+ * driver_id 必须有值且大于 0 才算有主:存量数据的 -1 是哨兵值,与 0 一样等同无主。 + * 运单上的 driver_phone 只是派单快照,不参与归属判定——司机改号不影响可见性。 + *

* 拒单后运单对司机彻底脱离——列表、计数、详情、打卡、接单一律不可达,等管理员重新派单。 * 运单上的司机姓名/手机/车牌不清空,调度重新派单时要知道原来派给谁。 */ - private LambdaQueryWrapper scopedQuery(String phone) { - return scopedQuery(phone, false); + private LambdaQueryWrapper scopedQuery(Long driverId) { + return scopedQuery(driverId, false); } /** @@ -900,11 +898,10 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { * 拒单的运单不可能处于运输中,收口拦不到合法操作, * 而保留原判据可避免「已拒单 + 进行中」的历史脏数据被误伤 */ - 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)); + private LambdaQueryWrapper scopedQuery(Long driverId, boolean includeRejected) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() + .gt(Waybill::getDriverId, 0) + .eq(Waybill::getDriverId, driverId); if (!includeRejected) { wrapper.and(w -> w.isNull(Waybill::getDriverAcceptStatus) .or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED)); @@ -948,43 +945,19 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } /** - * 当前登录司机:优先 userId,其次手机号(JWT 账号 / 小程序用户 account)匹配 driver.mobile + * 当前登录司机:只按登录用户 ID 查司机档案,查不到即「无归属」。 + *

+ * 曾经查不到档案时按手机号回退匹配 driver.mobile,这让两个同号档案可以互相冒充, + * 已删除:手机号在归属链路上不再有任何作用。 */ private Driver currentDriver() { Long userId = AuthUtil.getUserId(); if (userId == null || userId <= 0) { return null; } - Driver driver = driverService.getOne(Wrappers.lambdaQuery() + return driverService.getOne(Wrappers.lambdaQuery() .eq(Driver::getUserId, userId) .last("LIMIT 1")); - if (driver != null) { - return driver; - } - String phone = resolveSelfPhone(userId); - if (Func.isNotEmpty(phone)) { - return driverService.getOne(Wrappers.lambdaQuery() - .eq(Driver::getMobile, phone) - .orderByDesc(Driver::getUpdateTime) - .last("LIMIT 1")); - } - return null; - } - - /** - * 解析当前小程序用户可用于匹配司机档案的手机号 - */ - private String resolveSelfPhone(Long userId) { - String account = AuthUtil.getUserAccount(); - if (isMobileLike(account)) { - return account.trim(); - } - R result = mpUserClient.userById(userId); - if (result != null && R.isSuccess(result) && result.getData() != null - && isMobileLike(result.getData().getAccount())) { - return result.getData().getAccount().trim(); - } - return Func.isEmpty(account) ? null : account.trim(); } private boolean isMobileLike(String value) { @@ -1308,8 +1281,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public void assertCurrentDriverOwnsWaybill(Long waybillId) { - // 归属口径与司机打卡、司机接单一致:按当前用户手机号匹配运单司机手机号 - requireCurrentDriver(); + // 归属口径与司机打卡、司机接单一致:运单 driver_id 必须是当前登录司机的档案(查不到档案即无归属) loadDriverWaybill(waybillId); } 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 05c3af55..4b18c922 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 @@ -57,7 +57,10 @@ class DriverTripAcceptRejectTest { private static final Long USER_ID = 7401L; private static final Long DRIVER_ID = 9001L; - private static final String PHONE = "17587421208"; + /** 派单时落在运次上的号码快照。 */ + private static final String SNAPSHOT_PHONE = "17587421208"; + /** 司机改号后档案与登录账号上的号码。 */ + private static final String CURRENT_PHONE = "17687421208"; private static final String TRIP_ID = "2101146192739459074"; private static final Long PENDING_ID = 2101144166395699202L; private static final Long SECOND_ID = 2101145869819994113L; @@ -121,7 +124,8 @@ class DriverTripAcceptRejectTest { "两张子运单与接单司机一起下发:" + capturedUpdateParams()); assertTrue(capturedWhereSql().contains("(id IN ("), capturedWhereSql()); assertEquals(TRIP_ID, capturedQuery().getTripId()); - assertEquals(PHONE, capturedQuery().getDriverPhone(), "归属仍按司机手机号收口"); + assertEquals(CURRENT_PHONE, capturedQuery().getDriverPhone(), + "运次侧本票不动:仍按档案当前手机号收口(票 04 统一改成档案 ID)"); } @Test @@ -340,6 +344,16 @@ class DriverTripAcceptRejectTest { verify(waybillService, never()).update(any()); } + @Test + @DisplayName("没有司机档案的账号不能运次接单:不按手机号回退、不提供代报") + void tripAcceptWithoutDriverProfileIsRefused() { + when(driverService.getOne(any())).thenReturn(null); + + assertEquals("未找到当前登录司机档案", assertThrows(ServiceException.class, + () -> runAsDriver(() -> service.tripAccept(TRIP_ID))).getMessage()); + verify(waybillService, never()).update(any()); + } + // ------------------------------------------------------------ 夹具 private T runAsDriver(Supplier action) { @@ -373,7 +387,7 @@ class DriverTripAcceptRejectTest { private void stubTrip(TripVO.TripWaybillVO[] leading, TripVO.TripWaybillVO... children) { TripVO trip = new TripVO(); trip.setId(TRIP_ID); - trip.setDriverPhone(PHONE); + trip.setDriverPhone(SNAPSHOT_PHONE); List all = new java.util.ArrayList<>(List.of(leading)); all.addAll(List.of(children)); trip.setWaybills(all); @@ -400,7 +414,7 @@ class DriverTripAcceptRejectTest { driver.setUserId(USER_ID); driver.setDriverName("黄宇富"); driver.setDrivingVehicle("桂A32523"); - driver.setMobile(PHONE); + driver.setMobile(CURRENT_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 7f3f6cae..ad19610d 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 @@ -27,26 +27,34 @@ import org.springblade.transport.support.CertificationGate; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * 司机对运单的归属校验(异常上报等按运单操作的前置条件)。 *

- * 口径:当前用户手机号匹配运单 {@code driverPhone}。 + * 口径:登录用户按 {@code user_id} 解析到司机档案,运单归属键是运单上的 {@code driver_id}; + * {@code driver_phone} 只是派单快照,不参与判定。 * 管理端与调度员账号没有司机档案,会停在这里,这是刻意的:不提供代报。 */ class DriverWaybillOwnershipTest { private static final Long WAYBILL_ID = 5301L; private static final Long USER_ID = 7001L; - private static final String PHONE = "13800138000"; + private static final Long DRIVER_ID = 9001L; + private static final Long OTHER_DRIVER_ID = 9002L; + /** 派单时落在运单上的号码快照。 */ + private static final String SNAPSHOT_PHONE = "13800138000"; + /** 司机改号后档案与登录账号上的号码。 */ + private static final String CURRENT_PHONE = "13900139000"; private IDriverService driverService; private IWaybillService waybillService; @@ -58,6 +66,7 @@ class DriverWaybillOwnershipTest { MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, ""); assistant.setCurrentNamespace("test"); TableInfoHelper.initTableInfo(assistant, Waybill.class); + TableInfoHelper.initTableInfo(assistant, Driver.class); } @BeforeEach @@ -91,33 +100,26 @@ class DriverWaybillOwnershipTest { } @Test - @DisplayName("司机档案与登录态都没有手机号时被拒") - void withoutPhoneIsRejected() { - Driver driver = driver(null); - when(driverService.getOne(any())).thenReturn(driver); + @DisplayName("司机档案只按登录用户 ID 解析,不按手机号回退") + void driverProfileResolvedByUserIdOnly() { + when(driverService.getOne(any())).thenReturn(driver(CURRENT_PHONE)); + when(waybillService.getOne(any())).thenReturn(waybill()); - ServiceException exception = assertThrows(ServiceException.class, - () -> runOwnershipCheck(WAYBILL_ID)); + assertDoesNotThrow(() -> runOwnershipCheck(WAYBILL_ID)); - assertEquals("当前用户手机号为空,无法查找运单", exception.getMessage()); + ArgumentCaptor> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class); + verify(driverService, times(1)).getOne(captor.capture()); + LambdaQueryWrapper wrapper = captor.getValue(); + assertTrue(wrapper.getTargetSql().contains("user_id"), + "司机档案必须按登录用户 ID 解析:" + wrapper.getTargetSql()); + assertFalse(wrapper.getTargetSql().contains("mobile"), + "司机档案不允许再按手机号回退:" + wrapper.getTargetSql()); } @Test - @DisplayName("运单司机手机号与当前用户不一致时被拒") - void waybillOfAnotherPhoneIsRejected() { - when(driverService.getOne(any())).thenReturn(driver(PHONE)); - when(waybillService.getOne(any())).thenReturn(null); - - ServiceException exception = assertThrows(ServiceException.class, - () -> runOwnershipCheck(WAYBILL_ID)); - - assertEquals("运单不存在或无权操作", exception.getMessage()); - } - - @Test - @DisplayName("本人手机号运单放行,且查询确实按司机手机号收敛") - void ownWaybillPasses() { - when(driverService.getOne(any())).thenReturn(driver(PHONE)); + @DisplayName("司机改号后本人运单仍放行,归属只认 driver_id") + void ownWaybillPassesAfterNumberChange() { + when(driverService.getOne(any())).thenReturn(driver(CURRENT_PHONE)); when(waybillService.getOne(any())).thenReturn(waybill()); assertDoesNotThrow(() -> runOwnershipCheck(WAYBILL_ID)); @@ -125,17 +127,55 @@ class DriverWaybillOwnershipTest { ArgumentCaptor> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class); verify(waybillService).getOne(captor.capture()); LambdaQueryWrapper wrapper = captor.getValue(); - assertTrue(wrapper.getTargetSql().contains("driver_phone"), - "归属查询必须落在运单司机手机号上:" + wrapper.getTargetSql()); - assertTrue(wrapper.getParamNameValuePairs().values().stream() - .anyMatch(PHONE::equals), - "归属查询必须带上当前用户手机号:" + wrapper.getParamNameValuePairs()); + // 护栏:归属查询必须落在运单 driver_id 上,不能再出现手机号比较,否则改号即失效。 + assertTrue(wrapper.getTargetSql().contains("driver_id"), + "归属查询必须落在运单司机档案 ID 上:" + wrapper.getTargetSql()); + assertFalse(wrapper.getTargetSql().contains("driver_phone"), + "归属查询不能再比较运单司机手机号快照:" + wrapper.getTargetSql()); + assertTrue(wrapper.getParamNameValuePairs().values().contains(DRIVER_ID), + "归属查询必须带上当前司机档案 ID:" + wrapper.getParamNameValuePairs()); + assertFalse(wrapper.getParamNameValuePairs().values().contains(SNAPSHOT_PHONE), + "快照号不参与归属判定:" + wrapper.getParamNameValuePairs()); + assertFalse(wrapper.getParamNameValuePairs().values().contains(CURRENT_PHONE), + "档案手机号不参与归属判定:" + wrapper.getParamNameValuePairs()); + } + + @Test + @DisplayName("同号换人:同手机号但不同档案看不到对方的运单") + void waybillOwnedByAnotherDriverWithSamePhoneIsRejected() { + when(driverService.getOne(any())).thenReturn(driver(CURRENT_PHONE)); + // 库里存在该运单,但归属档案是另一个同号档案,按 driver_id 收口后查不到。 + when(waybillService.getOne(any())).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> runOwnershipCheck(WAYBILL_ID)); + + assertEquals("运单不存在或无权操作", exception.getMessage()); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class); + verify(waybillService).getOne(captor.capture()); + LambdaQueryWrapper wrapper = captor.getValue(); + // 参数槽由 SQL 片段构建时填充:不先取一次片段,参数表是空的 + wrapper.getTargetSql(); + assertTrue(wrapper.getParamNameValuePairs().values().contains(DRIVER_ID), + "收口条件必须是当前档案 ID:" + wrapper.getParamNameValuePairs()); + assertFalse(wrapper.getParamNameValuePairs().values().contains(OTHER_DRIVER_ID), + "不能把别人的档案 ID 当查询条件:" + wrapper.getParamNameValuePairs()); + } + + @Test + @DisplayName("司机档案没有手机号也能按档案 ID 认领自己的运单") + void profileWithoutPhoneStillOwnsByDriverId() { + when(driverService.getOne(any())).thenReturn(driver(null)); + when(waybillService.getOne(any())).thenReturn(waybill()); + + assertDoesNotThrow(() -> runOwnershipCheck(WAYBILL_ID)); } @Test @DisplayName("运单ID为空时被拒") void withoutWaybillIdIsRejected() { - when(driverService.getOne(any())).thenReturn(driver(PHONE)); + when(driverService.getOne(any())).thenReturn(driver(CURRENT_PHONE)); ServiceException exception = assertThrows(ServiceException.class, () -> runOwnershipCheck(null)); @@ -148,14 +188,14 @@ 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(PHONE); + authUtil.when(AuthUtil::getUserAccount).thenReturn(CURRENT_PHONE); service.assertCurrentDriverOwnsWaybill(waybillId); } } private Driver driver(String mobile) { Driver driver = new Driver(); - driver.setId(9001L); + driver.setId(DRIVER_ID); driver.setUserId(USER_ID); driver.setDriverName("韦司机"); driver.setMobile(mobile); @@ -166,7 +206,8 @@ class DriverWaybillOwnershipTest { Waybill waybill = new Waybill(); waybill.setId(WAYBILL_ID); waybill.setWaybillNo("YD202610070001"); - waybill.setDriverPhone(PHONE); + waybill.setDriverId(DRIVER_ID); + waybill.setDriverPhone(SNAPSHOT_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 49502666..228d5800 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 @@ -34,6 +34,7 @@ import org.springblade.transport.support.CertificationGate; import java.util.List; import java.util.function.Supplier; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -58,7 +59,10 @@ 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 String SNAPSHOT_PHONE = "13800138002"; + /** 司机改号后档案与登录账号上的号码。 */ + private static final String CURRENT_PHONE = "13900139002"; private static final Long REJECTED_ID = 7201L; private IDriverService driverService; @@ -155,6 +159,29 @@ class DriverWaybillRejectDetachTest { assertEquals(2, page.getRecords().size()); } + @Test + @DisplayName("司机改号后运单仍可见,卡片仍展示派单时的号码快照") + void numberChangeKeepsWaybillsVisible() { + // 同一个档案:档案与登录账号已换成新号,运单快照还是派单时的旧号 + when(waybillService.list(ArgumentMatchers.>any())) + .thenReturn(List.of(waybill(7202L, "YD-20261001-0002", "pending", "pending"))); + + IPage page = runAsDriver(() -> service.page(1, 10, null, null)); + + assertEquals(1, page.getTotal(), "改号不影响本人运单的可见性"); + assertEquals(SNAPSHOT_PHONE, page.getRecords().get(0).getDriverPhone(), + "卡片展示的仍是运单上的号码快照,不改成本人新号"); + } + + @Test + @DisplayName("重新派回后运单恢复可见:归属看新的 driver_id,不看历史拒单") + void reassignedWaybillBecomesVisibleAgain() { + when(waybillService.getOne(any())) + .thenReturn(waybill(REJECTED_ID, "YD-20261010-0001", "pending", "pending")); + + assertDoesNotThrow(() -> runAsDriver(() -> service.detail(REJECTED_ID))); + } + @Test @DisplayName("已拒单运单的详情不可达:取单条件已把它挡住") void detailOfRejectedIsUnreachable() { @@ -218,7 +245,7 @@ class DriverWaybillRejectDetachTest { driver.setUserId(USER_ID); driver.setDriverName("黄大富"); driver.setDrivingVehicle(PLATE); - driver.setMobile(PHONE); + driver.setMobile(CURRENT_PHONE); return driver; } @@ -231,7 +258,8 @@ class DriverWaybillRejectDetachTest { waybill.setId(id); waybill.setWaybillNo(no); waybill.setVehicleNo(PLATE); - waybill.setDriverPhone(PHONE); + waybill.setDriverId(DRIVER_ID); + waybill.setDriverPhone(SNAPSHOT_PHONE); waybill.setBusinessStatus(businessStatus); waybill.setDriverAcceptStatus(acceptStatus); waybill.setIsDeleted(0); 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 1789c892..256eff6b 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 @@ -35,6 +35,7 @@ import java.util.List; import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -51,8 +52,12 @@ class DriverWaybillTabStatusTest { private static final Long WAYBILL_ID = 5901L; private static final Long USER_ID = 7001L; + private static final Long DRIVER_ID = 9001L; private static final String PLATE = "桂AAW3256"; - private static final String PHONE = "13800138002"; + /** 派单时落在运单上的号码快照。 */ + private static final String SNAPSHOT_PHONE = "13800138002"; + /** 司机改号后档案与登录账号上的号码。 */ + private static final String CURRENT_PHONE = "13900139002"; private IWaybillService waybillService; private IDriverService driverService; @@ -141,6 +146,27 @@ class DriverWaybillTabStatusTest { assertEquals(1, counts.getAll()); } + @Test + @DisplayName("司机改号后三档计数不变,计数查询按 driver_id 收口") + void tabCountsAfterNumberChangeScopedByDriverId() { + DriverWaybillTabCountsVO counts = runAsDriver(service::tabCounts); + + assertEquals(1, counts.getDoing(), "改号不影响本人运单的分档"); + assertEquals(1, counts.getAll()); + + LambdaQueryWrapper wrapper = firstQueriedWrapper(); + // 护栏:计数查询必须落在运单 driver_id 上;比对手机号的话,改号后司机的数字会清零 + String sql = wrapper.getTargetSql(); + assertTrue(sql.contains("driver_id"), "计数查询必须按司机档案 ID 收口:" + sql); + assertFalse(sql.contains("driver_phone"), "计数查询不能再比较运单手机号快照:" + sql); + assertTrue(wrapper.getParamNameValuePairs().values().contains(DRIVER_ID), + "计数查询必须带上当前司机档案 ID:" + wrapper.getParamNameValuePairs()); + assertFalse(wrapper.getParamNameValuePairs().values().contains(SNAPSHOT_PHONE), + "快照号不参与计数:" + wrapper.getParamNameValuePairs()); + assertFalse(wrapper.getParamNameValuePairs().values().contains(CURRENT_PHONE), + "档案手机号不参与计数:" + wrapper.getParamNameValuePairs()); + } + // ------------------------------------------------------------ 夹具 private T runAsDriver(Supplier action) { @@ -167,6 +193,15 @@ class DriverWaybillTabStatusTest { return statuses; } + @SuppressWarnings("unchecked") + private LambdaQueryWrapper firstQueriedWrapper() { + assertEquals(1, queried.size(), "一次列表查询"); + LambdaQueryWrapper wrapper = (LambdaQueryWrapper) queried.get(0); + // 参数槽由 SQL 片段构建时填充:不先取一次片段,参数表是空的 + wrapper.getSqlSegment(); + return wrapper; + } + @SuppressWarnings("unchecked") private static Wrapper anyWrapper() { return org.mockito.ArgumentMatchers.>any(); @@ -177,7 +212,8 @@ class DriverWaybillTabStatusTest { waybill.setId(WAYBILL_ID); waybill.setWaybillNo("YD202610080002"); waybill.setVehicleNo(PLATE); - waybill.setDriverPhone(PHONE); + waybill.setDriverId(DRIVER_ID); + waybill.setDriverPhone(SNAPSHOT_PHONE); waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); waybill.setIsDeleted(0); return waybill; @@ -185,10 +221,10 @@ class DriverWaybillTabStatusTest { private Driver driver() { Driver driver = new Driver(); - driver.setId(9001L); + driver.setId(DRIVER_ID); driver.setUserId(USER_ID); driver.setDrivingVehicle(PLATE); - driver.setMobile(PHONE); + driver.setMobile(CURRENT_PHONE); return driver; }