Merge branch 'feat/waybill-driver-id-ownership' into feat/waybill-driver-id/02-backfill

This commit is contained in:
2026-10-27 20:55:10 +08:00
5 changed files with 193 additions and 102 deletions
@@ -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<Waybill> candidates = waybillService.list(scopedQuery(phone)
List<Waybill> 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<Waybill> pendingList = waybillService.list(scopedQuery(phone)
List<Waybill> pendingList = waybillService.list(scopedQuery(driver.getId())
.in(Waybill::getBusinessStatus, PENDING_STATUSES)
.orderByDesc(Waybill::getCreateTime));
List<Waybill> 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<Waybill> waybills = waybillService.list(scopedQuery(phone)
List<Waybill> 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<DriverWaybillCardVO> empty = new Page<>(pageNo, pageSize);
String phone = currentUserPhone();
if (Func.isEmpty(phone)) {
Driver driver = currentDriver();
if (driver == null) {
return empty;
}
LambdaQueryWrapper<Waybill> wrapper = scopedQuery(phone);
LambdaQueryWrapper<Waybill> 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)就是当前司机档案,
* 且该运单未被这个司机拒单。
* <p>
* driver_id 必须有值且大于 0 才算有主:存量数据的 -1 是哨兵值,与 0 一样等同无主。
* 运单上的 driver_phone 只是派单快照,不参与归属判定——司机改号不影响可见性。
* <p>
* 拒单后运单对司机彻底脱离——列表、计数、详情、打卡、接单一律不可达,等管理员重新派单。
* 运单上的司机姓名/手机/车牌不清空,调度重新派单时要知道原来派给谁。
*/
private LambdaQueryWrapper<Waybill> scopedQuery(String phone) {
return scopedQuery(phone, false);
private LambdaQueryWrapper<Waybill> scopedQuery(Long driverId) {
return scopedQuery(driverId, false);
}
/**
@@ -900,11 +898,10 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
* 拒单的运单不可能处于运输中,收口拦不到合法操作,
* 而保留原判据可避免「已拒单 + 进行中」的历史脏数据被误伤
*/
private LambdaQueryWrapper<Waybill> scopedQuery(String phone, boolean includeRejected) {
String normalizedPhone = Func.toStr(phone, "").trim();
LambdaQueryWrapper<Waybill> wrapper = Wrappers.<Waybill>lambdaQuery().and(w -> w
.eq(Waybill::getDriverPhone, normalizedPhone)
.or().apply("REPLACE(IFNULL(driver_phone,''),' ','') = {0}", normalizedPhone));
private LambdaQueryWrapper<Waybill> scopedQuery(Long driverId, boolean includeRejected) {
LambdaQueryWrapper<Waybill> wrapper = Wrappers.<Waybill>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 查司机档案,查不到即「无归属」。
* <p>
* 曾经查不到档案时按手机号回退匹配 driver.mobile,这让两个同号档案可以互相冒充,
* 已删除:手机号在归属链路上不再有任何作用。
*/
private Driver currentDriver() {
Long userId = AuthUtil.getUserId();
if (userId == null || userId <= 0) {
return null;
}
Driver driver = driverService.getOne(Wrappers.<Driver>lambdaQuery()
return driverService.getOne(Wrappers.<Driver>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.<Driver>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<MpUser> 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);
}
@@ -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> T runAsDriver(Supplier<T> 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<TripVO.TripWaybillVO> 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;
}
@@ -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;
/**
* 司机对运单的归属校验(异常上报等按运单操作的前置条件)。
* <p>
* 口径:当前用户手机号匹配运单 {@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<LambdaQueryWrapper<Driver>> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
verify(driverService, times(1)).getOne(captor.capture());
LambdaQueryWrapper<Driver> 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<LambdaQueryWrapper<Waybill>> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
verify(waybillService).getOne(captor.capture());
LambdaQueryWrapper<Waybill> 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<LambdaQueryWrapper<Waybill>> captor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
verify(waybillService).getOne(captor.capture());
LambdaQueryWrapper<Waybill> 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> 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;
}
@@ -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.<com.baomidou.mybatisplus.core.conditions.Wrapper<Waybill>>any()))
.thenReturn(List.of(waybill(7202L, "YD-20261001-0002", "pending", "pending")));
IPage<DriverWaybillCardVO> 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);
@@ -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<Waybill> 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> T runAsDriver(Supplier<T> action) {
@@ -167,6 +193,15 @@ class DriverWaybillTabStatusTest {
return statuses;
}
@SuppressWarnings("unchecked")
private LambdaQueryWrapper<Waybill> firstQueriedWrapper() {
assertEquals(1, queried.size(), "一次列表查询");
LambdaQueryWrapper<Waybill> wrapper = (LambdaQueryWrapper<Waybill>) queried.get(0);
// 参数槽由 SQL 片段构建时填充:不先取一次片段,参数表是空的
wrapper.getSqlSegment();
return wrapper;
}
@SuppressWarnings("unchecked")
private static Wrapper<Waybill> anyWrapper() {
return org.mockito.ArgumentMatchers.<Wrapper<Waybill>>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;
}