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 750dd6d0..cbdc9f69 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 @@ -34,8 +34,11 @@ import java.util.Set; /** * 运次查询条件 *

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

+ * 判据是司机档案 ID 而不是手机号:手机号在配载单/运单上只是派单时的快照,司机换号后并不跟随, + * 按号收口会让改过号的司机看不到自己手上的运次,号码被回收给别人时还会反向串号。 */ @Data @Schema(description = "运次查询条件") @@ -47,8 +50,8 @@ public class TripQueryVO implements Serializable { @Schema(description = "运次主键(详情按它取单个运次)") private String tripId; - @Schema(description = "司机手机号") - private String driverPhone; + @Schema(description = "司机档案 ID(司机端收口判据,空或非正数一律不放行)") + private Long driverId; @Schema(description = "是否不按司机收口(管理端)") private boolean allDrivers; @@ -92,9 +95,9 @@ public class TripQueryVO implements Serializable { @Schema(description = "每页条数(按运次,不是运单)") private Integer size; - public static TripQueryVO forDriver(String driverPhone) { + public static TripQueryVO forDriver(Long driverId) { TripQueryVO query = new TripQueryVO(); - query.setDriverPhone(driverPhone); + query.setDriverId(driverId); return query; } 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 9bd09ed8..8aab3912 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 @@ -33,10 +33,8 @@ import org.springblade.core.secure.utils.AuthUtil; 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.api.R; import org.springblade.core.tool.utils.Func; import org.springblade.system.feign.IMpUserClient; -import org.springblade.system.pojo.entity.MpUser; import org.springblade.transport.mapper.ExceptionDisposalMapper; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -277,11 +275,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillCardVO tripCurrentTask() { - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { + Driver driver = currentDriver(); + if (driver == null) { return null; } - TripQueryVO query = TripQueryVO.forDriver(phone); + TripQueryVO query = TripQueryVO.forDriver(driver.getId()); // 待确认(待回单确认)的趟车司机仍需回到卡片上继续操作,与进行中并列作为当前任务 query.setStatuses(List.of(TRIP_STATUS_RUNNING, TRIP_STATUS_PENDING_RETURN_CONFIRM)); query.setCurrent(1); @@ -293,11 +291,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillPreviewVO tripPendingPreview(Integer size) { DriverWaybillPreviewVO preview = new DriverWaybillPreviewVO(); - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { + Driver driver = currentDriver(); + if (driver == null) { return preview; } - TripQueryVO query = TripQueryVO.forDriver(phone); + TripQueryVO query = TripQueryVO.forDriver(driver.getId()); query.setStatus(TRIP_STATUS_PENDING); query.setCurrent(1); query.setSize(normalizePreviewSize(size)); @@ -312,11 +310,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillTabCountsVO tripTabCounts() { DriverWaybillTabCountsVO counts = new DriverWaybillTabCountsVO(); - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { + Driver driver = currentDriver(); + if (driver == null) { return counts; } - TripTabCountsVO tripCounts = tripQueryService.tabCounts(TripQueryVO.forDriver(phone)); + TripTabCountsVO tripCounts = tripQueryService.tabCounts(TripQueryVO.forDriver(driver.getId())); counts.setPending(tripCounts.getPending()); counts.setDoing(tripCounts.getDoing()); counts.setConfirm(tripCounts.getConfirm()); @@ -327,11 +325,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public IPage tripPage(Integer current, Integer size, Integer status, String keyword) { - String phone = currentUserPhone(); - if (Func.isEmpty(phone)) { + Driver driver = currentDriver(); + if (driver == null) { return new Page<>(normalizePageNo(current), normalizePageSize(size)); } - TripQueryVO query = TripQueryVO.forDriver(phone); + TripQueryVO query = TripQueryVO.forDriver(driver.getId()); query.setStatus(status); query.setKeyword(keyword); query.setCurrent(current); @@ -346,8 +344,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { @Override public DriverWaybillCardVO tripDetail(String tripId) { - requireCurrentDriver(); - TripQueryVO query = TripQueryVO.forDriver(currentUserPhone()); + Driver driver = requireCurrentDriver(); + TripQueryVO query = TripQueryVO.forDriver(driver.getId()); query.setTripId(tripId); return toTripCard(tripQueryService.detail(query), true); } @@ -740,7 +738,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { * 子单集合是司机可见的那一批:已拒单的子单不在其中,否则接单会把本人拒掉的单重新拉回来。 */ private List tripChildren(String tripId) { - TripQueryVO query = TripQueryVO.forDriver(currentUserPhone()); + TripQueryVO query = TripQueryVO.forDriver(requireCurrentDriver().getId()); query.setTripId(tripId); TripVO trip = tripQueryService.detail(query); List children = trip.getWaybills() == null ? List.of() : trip.getWaybills(); @@ -922,28 +920,6 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { .or().like(Waybill::getVehicleNo, kw)); } - /** - * 当前用户手机号:司机档案 mobile 优先,其次小程序用户账号。 - *

- * 派单时运单 driver_phone 取自司机档案 mobile,故档案有手机号就以它为准。 - */ - private String currentUserPhone() { - Driver driver = currentDriver(); - if (driver != null && isMobileLike(driver.getMobile())) { - return driver.getMobile().trim(); - } - 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; - } - /** * 当前登录司机:只按登录用户 ID 查司机档案,查不到即「无归属」。 *

@@ -960,10 +936,6 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { .last("LIMIT 1")); } - private boolean isMobileLike(String value) { - return Func.isNotEmpty(value) && value.trim().matches("^1\\d{10,14}$"); - } - private List splitPlates(String drivingVehicle) { return VehiclePlateSupport.splitPlates(drivingVehicle); } 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 fcff3fdc..7c35161c 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,22 +389,24 @@ public class TripQueryServiceImpl implements ITripQueryService { } /** - * 运次可见范围:配载段按配载单的司机手机号收口,未配载段按运单的司机手机号收口。 + * 运次可见范围:配载段按配载单的司机档案 ID 收口,未配载段按运单的司机档案 ID 收口。 *

- * 手机号允许带空格,与运单列表的收口判据一致。 + * 判据是司机档案 ID 而不是手机号:司机换号后档案 ID 不变,而派单时落在配载单/运单上的手机号 + * 是快照,按号收口会让改过号的司机看不到自己手上的运次;号码被回收给别人时又会反向串号。 + * 两段必须同时切:只切一段,另一段就会把不属于该司机的运次放进来。 + * 缺省收口(空、0、负数)不放行,由 {@link #scoped} 在外层挡掉。 */ private void applyScope(LambdaQueryWrapper wrapper, TripQueryVO condition) { if (condition.isAllDrivers()) { return; } - String phone = Func.toStr(condition.getDriverPhone(), "").trim(); + Long driverId = condition.getDriverId(); wrapper.and(scope -> scope .apply("(IFNULL(loading_no, '') <> '' AND loading_no IN (SELECT lm.loading_no FROM blade_loading_manage lm" - + " WHERE lm.is_deleted = 0 AND lm.driver_phone = {0}))", phone) + + " WHERE lm.is_deleted = 0 AND lm.driver_id = {0}))", driverId) .or(lone -> lone .apply("IFNULL(loading_no, '') = ''") - .and(row -> row.eq(Waybill::getDriverPhone, phone) - .or().apply("REPLACE(IFNULL(driver_phone, ''), ' ', '') = {0}", phone)))); + .and(row -> row.eq(Waybill::getDriverId, driverId)))); } /** @@ -786,7 +788,7 @@ public class TripQueryServiceImpl implements ITripQueryService { } /** - * 按运次主键取配载单:主键命中配载单即配载运次,再按司机收口 + * 按运次主键取配载单:主键命中配载单即配载运次,再按配载单的司机档案 ID 收口 */ private LoadingManage visibleLoading(Long id, TripQueryVO condition) { LoadingManage loading = loadingManageService.getById(id); @@ -796,7 +798,7 @@ public class TripQueryServiceImpl implements ITripQueryService { if (condition.isAllDrivers()) { return loading; } - return samePhone(loading.getDriverPhone(), condition.getDriverPhone()) ? loading : null; + return ownedBy(loading.getDriverId(), condition.getDriverId()) ? loading : null; } /** @@ -813,19 +815,23 @@ public class TripQueryServiceImpl implements ITripQueryService { if (condition.isAllDrivers()) { return waybill; } - return samePhone(waybill.getDriverPhone(), condition.getDriverPhone()) ? waybill : null; + return ownedBy(waybill.getDriverId(), condition.getDriverId()) ? waybill : null; } + /** 收口条件是否成立:管理端不收口;司机端必须给出有效的档案 ID,缺省一律拒绝 */ private boolean scoped(TripQueryVO condition) { - return condition.isAllDrivers() || Func.isNotEmpty(Func.toStr(condition.getDriverPhone(), "").trim()); + Long driverId = condition.getDriverId(); + return condition.isAllDrivers() || (driverId != null && driverId > 0); } - private boolean samePhone(String left, String right) { - String expected = Func.toStr(right, "").trim().replace(" ", ""); - if (Func.isEmpty(expected)) { - return false; - } - return expected.equals(Func.toStr(left, "").trim().replace(" ", "")); + /** + * 归属判据:行的司机档案 ID 与收口条件相等。 + *

+ * 条件侧缺省(空、0、负数)一律不可见——没有有效档案 ID 就等于没有收口条件, + * 此时任何行都不能算作「本人」,否则业务主键未回填的历史数据会把运次漏给所有人。 + */ + private boolean ownedBy(Long ownerId, Long expectedId) { + return expectedId != null && expectedId > 0 && expectedId.equals(ownerId); } private TripQueryVO condition(TripQueryVO query) { @@ -835,7 +841,7 @@ public class TripQueryServiceImpl implements ITripQueryService { /** 去掉 Tab 条件、其余照搬:统计四档时不按某一档收窄 */ private TripQueryVO withoutTab(TripQueryVO source) { TripQueryVO copy = new TripQueryVO(); - copy.setDriverPhone(source.getDriverPhone()); + copy.setDriverId(source.getDriverId()); copy.setAllDrivers(source.isAllDrivers()); copy.setDeptIds(source.getDeptIds()); copy.setProjectIds(source.getProjectIds()); 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 4b18c922..8e8e474d 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 @@ -124,8 +124,8 @@ class DriverTripAcceptRejectTest { "两张子运单与接单司机一起下发:" + capturedUpdateParams()); assertTrue(capturedWhereSql().contains("(id IN ("), capturedWhereSql()); assertEquals(TRIP_ID, capturedQuery().getTripId()); - assertEquals(CURRENT_PHONE, capturedQuery().getDriverPhone(), - "运次侧本票不动:仍按档案当前手机号收口(票 04 统一改成档案 ID)"); + assertEquals(DRIVER_ID, capturedQuery().getDriverId(), + "运次查询按司机档案 ID 收口:手机号换了也认得出本人"); } @Test 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 8ac29d0f..d783008e 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 @@ -125,7 +125,7 @@ class DriverTripCardTest { IPage page = runAsDriver(() -> service.tripPage(1, 10, null, null)); given = capturedQuery(); - assertEquals(PHONE, given.getDriverPhone()); + assertEquals(DRIVER_ID, given.getDriverId(), "运次列表按司机档案 ID 收口"); assertEquals(1, given.getCurrent()); assertEquals(10, given.getSize(), "一页 10 条即 10 个运次"); assertNull(given.getStatus()); @@ -334,10 +334,9 @@ class DriverTripCardTest { } @Test - @DisplayName("收口缺失时不放行:空页、零统计、当前任务为空,且不查运次") - void missingPhoneIsRefused() { + @DisplayName("收口缺失时不放行:没有司机档案就空页、零统计、当前任务为空,且不查运次") + void missingDriverProfileIsRefused() { when(driverService.getOne(any())).thenReturn(null); - when(mpUserClient.userById(any())).thenReturn(null); IPage page = runAsDriver(() -> service.tripPage(null, null, null, null)); DriverWaybillPreviewVO preview = runAsDriver(() -> service.tripPendingPreview(null)); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripQueryServiceImplTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripQueryServiceImplTest.java index d717921a..b12f1624 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripQueryServiceImplTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripQueryServiceImplTest.java @@ -50,7 +50,10 @@ import static org.mockito.Mockito.when; */ class TripQueryServiceImplTest { + /** 派单时的司机手机号快照:只用于展示,归属判据是下面的档案 ID */ private static final String PHONE = "17587421208"; + private static final Long DRIVER_ID = 9001L; + private static final Long OTHER_DRIVER_ID = 9002L; private static final String LOADING_NO = "PZ-20261017-001"; private static final Long LOADING_ID = 2111350784070275123L; private static final Long WAYBILL_ID = 2110690671695380513L; @@ -85,7 +88,7 @@ class TripQueryServiceImplTest { @Test @DisplayName("段一在库层只做收口与行级筛选,分组与分档不在 SQL 里") void rowsAreFetchedWithoutDatabaseGrouping() { - TripQueryVO query = TripQueryVO.forDriver(PHONE); + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); query.setStatus(1); query.setCurrent(1); query.setSize(10); @@ -98,9 +101,9 @@ class TripQueryServiceImplTest { assertFalse(sql.contains("LIMIT"), "分页在服务层,不在 wrapper 里拼:" + sql); assertFalse(sql.contains("MAX(create_time)"), "排序键在服务层算:" + sql); assertTrue(sql.contains("blade_loading_manage"), "配载段按配载单的司机收口:" + sql); - assertTrue(sql.contains("lm.driver_phone = ?"), "司机手机号要作为参数下发:" + sql); - assertTrue(sql.contains("REPLACE(IFNULL(driver_phone, ''), ' ', '') = ?"), - "未配载段按运单司机收口,且容忍手机号带空格:" + sql); + assertTrue(sql.contains("lm.driver_id = ?"), "配载段按配载单的司机档案 ID 收口:" + sql); + assertTrue(sql.contains("driver_id = ?"), "未配载段按运单的司机档案 ID 收口:" + sql); + assertFalse(sql.contains("driver_phone"), "手机号不再是归属判据:" + sql); assertTrue(sql.contains("business_status IN"), "草稿子单不成运次:" + sql); assertTrue(sql.contains("driver_accept_status"), "被该司机拒单的子单不进运次:" + sql); } @@ -108,7 +111,7 @@ class TripQueryServiceImplTest { @Test @DisplayName("段一只 select 用得上的列,大 JSON 列不拖回来") void onlyNeededColumnsAreSelected() { - service.page(TripQueryVO.forDriver(PHONE)); + service.page(TripQueryVO.forDriver(DRIVER_ID)); String select = String.valueOf(capturedRowQuery().getSqlSelect()); assertTrue(select.contains("process_json"), "分档要按过程配置推导,必须取:" + select); @@ -124,7 +127,7 @@ class TripQueryServiceImplTest { @Test @DisplayName("组织收口给了就落在库层运单行上,分档与分页都由缩过的行集合算") void deptScopeIsAppliedOnRows() { - TripQueryVO query = TripQueryVO.forDriver(PHONE); + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); query.setDeptIds(List.of(1123598813738675202L, 1123598813738675201L)); service.page(query); @@ -134,7 +137,7 @@ class TripQueryServiceImplTest { Set values = new HashSet<>(capturedRowQuery().getParamNameValuePairs().values()); assertTrue(values.contains(1123598813738675202L) && values.contains(1123598813738675201L), "组织值按参数下发:" + values); - assertTrue(capturedRowSql().contains("driver_phone"), "组织收口不顶替司机收口:两种收口同时生效"); + assertTrue(capturedRowSql().contains("driver_id"), "组织收口不顶替司机收口:两种收口同时生效"); } @Test @@ -152,7 +155,7 @@ class TripQueryServiceImplTest { @Test @DisplayName("组织收口缺省时不下发组织条件:超管与司机端都按不传处理") void deptScopeIsOptional() { - service.page(TripQueryVO.forDriver(PHONE)); + service.page(TripQueryVO.forDriver(DRIVER_ID)); assertFalse(capturedRowSql().contains("dept_id"), "不给组织集合即不过滤:" + capturedRowSql()); } @@ -165,7 +168,7 @@ class TripQueryServiceImplTest { waybill(2L, "YD-2", null, "completed", "accepted", at(2000L), ACCEPT_NO), waybill(3L, "YD-3", null, "running", "accepted", at(1000L), ACCEPT_NO)); - TripQueryVO doing = TripQueryVO.forDriver(PHONE); + TripQueryVO doing = TripQueryVO.forDriver(DRIVER_ID); doing.setStatus(1); IPage doingPage = service.page(doing); @@ -175,11 +178,11 @@ class TripQueryServiceImplTest { assertTrue(doingPage.getRecords().stream().allMatch(trip -> Integer.valueOf(1).equals(trip.getStatus())), "每张卡的 status 都要等于这一档"); - TripQueryVO done = TripQueryVO.forDriver(PHONE); + TripQueryVO done = TripQueryVO.forDriver(DRIVER_ID); done.setStatus(2); assertEquals(List.of("2"), service.page(done).getRecords().stream().map(TripVO::getId).toList()); - TripQueryVO all = TripQueryVO.forDriver(PHONE); + TripQueryVO all = TripQueryVO.forDriver(DRIVER_ID); all.setStatus(null); assertEquals(3, service.page(all).getTotal(), "不传 Tab 为全部(不含已取消)"); } @@ -192,12 +195,12 @@ class TripQueryServiceImplTest { waybill(2L, "YD-2", null, "running", null, at(2000L), ACCEPT_YES), waybill(1L, "YD-1", null, "pending", "pending", at(1000L), ACCEPT_NO)); - TripQueryVO pending = TripQueryVO.forDriver(PHONE); + TripQueryVO pending = TripQueryVO.forDriver(DRIVER_ID); pending.setStatus(0); assertEquals(List.of("2"), service.page(pending).getRecords().stream().map(TripVO::getId).toList(), "库值 running 但要接单未接 → 落待接单档"); - TripQueryVO doing = TripQueryVO.forDriver(PHONE); + TripQueryVO doing = TripQueryVO.forDriver(DRIVER_ID); doing.setStatus(1); assertEquals(List.of("1"), service.page(doing).getRecords().stream().map(TripVO::getId).toList(), "库值 pending 但不需要接单 → 落进行中档"); @@ -212,8 +215,8 @@ class TripQueryServiceImplTest { List.of( waybill(1L, "YD-1", LOADING_NO, "running", null, at(3000L), ACCEPT_YES), waybill(2L, "YD-2", LOADING_NO, "running", null, at(2000L), ACCEPT_YES))); - when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(PHONE))); - TripQueryVO query = TripQueryVO.forDriver(PHONE); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); query.setKeyword("PZ-20261017"); TripVO trip = service.page(query).getRecords().get(0); @@ -232,7 +235,7 @@ class TripQueryServiceImplTest { void unloadedWaybillIsItsOwnTrip() { stubRows(waybill(WAYBILL_ID, "YD-20260920-0003", null, "running", null, at(1000L), ACCEPT_NO)); - IPage page = service.page(TripQueryVO.forDriver(PHONE)); + IPage page = service.page(TripQueryVO.forDriver(DRIVER_ID)); assertEquals(1, page.getTotal()); assertEquals(1, page.getRecords().size()); @@ -256,7 +259,7 @@ class TripQueryServiceImplTest { row.setMasterNo("DL-20261001-0001"); stubRows(row); - TripVO trip = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0); + TripVO trip = service.page(TripQueryVO.forDriver(DRIVER_ID)).getRecords().get(0); assertEquals(Boolean.TRUE, trip.getMultimodal(), "挂总单即含多式联运"); assertEquals("common", trip.getTransportOrgType(), "运输类型是组织形态维,与多式联运各算一个值"); @@ -270,9 +273,9 @@ class TripQueryServiceImplTest { stubRows( onMasterOrder, waybill(2092186187470467073L, "YD-2", LOADING_NO, "running", null, at(1000L), ACCEPT_NO)); - when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(PHONE))); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); - TripVO trip = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0); + TripVO trip = service.page(TripQueryVO.forDriver(DRIVER_ID)).getRecords().get(0); assertEquals("load", trip.getTransportOrgType()); assertEquals(Boolean.TRUE, trip.getMultimodal(), "两个事实叠加,不做互斥单选"); @@ -283,7 +286,7 @@ class TripQueryServiceImplTest { void unloadedTripStateComesFromFetchedWaybill() { stubRows(waybill(WAYBILL_ID, "YD-20260920-0003", null, "pending", "pending", at(1000L), ACCEPT_YES)); - TripVO trip = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0); + TripVO trip = service.page(TripQueryVO.forDriver(DRIVER_ID)).getRecords().get(0); assertEquals(Boolean.TRUE, trip.getRequireAccept()); assertEquals("pending", trip.getAcceptStatus()); @@ -300,7 +303,7 @@ class TripQueryServiceImplTest { row.setEstimatedEndTime(LocalDate.of(2026, 8, 24)); stubRows(row); - TripVO listed = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0); + TripVO listed = service.page(TripQueryVO.forDriver(DRIVER_ID)).getRecords().get(0); assertEquals(LocalDate.of(2026, 8, 7), listed.getEstimatedStartDate(), "列表卡片与详情同源,不能只在列表补"); assertEquals(LocalDate.of(2026, 8, 24), listed.getEstimatedEndDate()); @@ -319,7 +322,7 @@ class TripQueryServiceImplTest { void loadingMissingIsDropped() { stubRows(waybill(2092186187239780353L, "YD-20260825-0006", LOADING_NO, "running", null, at(1000L), ACCEPT_YES)); - IPage page = service.page(TripQueryVO.forDriver(PHONE)); + IPage page = service.page(TripQueryVO.forDriver(DRIVER_ID)); assertTrue(page.getRecords().isEmpty(), "配载单取不到就不给这一趟车"); assertEquals(1, page.getTotal(), "总数按运次算,卡片少一张时总数仍在"); @@ -335,9 +338,9 @@ class TripQueryServiceImplTest { waybill(2092186187470467073L, "YD-20260825-0007", LOADING_NO, "running", null, earlier, ACCEPT_NO), waybill(2092186187571130369L, "YD-20260825-0008", LOADING_NO, "running", null, earlier, ACCEPT_NO), waybill(2102780956493299713L, "YD-20260923-0008", LOADING_NO, "running", null, latest, ACCEPT_NO)); - when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(PHONE))); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); - TripVO trip = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0); + TripVO trip = service.page(TripQueryVO.forDriver(DRIVER_ID)).getRecords().get(0); assertEquals(String.valueOf(LOADING_ID), trip.getId()); assertEquals(LOADING_NO, trip.getLoadingNo()); @@ -358,9 +361,9 @@ class TripQueryServiceImplTest { stubRows( waybill(2092186187239780353L, "YD-1", LOADING_NO, "pending", "pending", at(2000L), ACCEPT_YES), waybill(2092186187470467073L, "YD-2", LOADING_NO, "running", null, at(1000L), ACCEPT_NO)); - when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(PHONE))); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); - TripVO trip = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0); + TripVO trip = service.page(TripQueryVO.forDriver(DRIVER_ID)).getRecords().get(0); assertEquals(Boolean.TRUE, trip.getRequireAccept(), "跨项目按 OR 合并:一张要接单整趟车都要接单"); assertEquals("pending", trip.getAcceptStatus()); @@ -372,8 +375,8 @@ class TripQueryServiceImplTest { void repeatedReadsDoNotDriftAndNeverWrite() { stubOneLoadedTrip(); - TripVO first = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0); - TripVO second = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0); + TripVO first = service.page(TripQueryVO.forDriver(DRIVER_ID)).getRecords().get(0); + TripVO second = service.page(TripQueryVO.forDriver(DRIVER_ID)).getRecords().get(0); assertEquals(first.getStatus(), second.getStatus()); assertEquals(first.getAcceptStatus(), second.getAcceptStatus()); @@ -412,19 +415,19 @@ class TripQueryServiceImplTest { waybill(2L, "YD-2", null, "running", null, at(2000L), ACCEPT_YES), waybill(1L, "YD-1", null, "running", null, at(1000L), ACCEPT_YES)); - TripQueryVO firstPage = TripQueryVO.forDriver(PHONE); + TripQueryVO firstPage = TripQueryVO.forDriver(DRIVER_ID); firstPage.setCurrent(1); firstPage.setSize(2); assertEquals(3, service.page(firstPage).getTotal(), "总数是过滤后的运次数,不是运单数"); assertEquals(List.of("3", "2"), service.page(firstPage).getRecords().stream().map(TripVO::getId).toList()); - TripQueryVO secondPage = TripQueryVO.forDriver(PHONE); + TripQueryVO secondPage = TripQueryVO.forDriver(DRIVER_ID); secondPage.setCurrent(2); secondPage.setSize(2); assertEquals(List.of("1"), service.page(secondPage).getRecords().stream().map(TripVO::getId).toList(), "第 2 页接着第 1 页,不重复不遗漏"); - TripQueryVO bySeven = TripQueryVO.forDriver(PHONE); + TripQueryVO bySeven = TripQueryVO.forDriver(DRIVER_ID); bySeven.setCurrent(1); bySeven.setSize(7); assertEquals(3, service.page(bySeven).getTotal(), "换每页条数总数不变"); @@ -440,7 +443,7 @@ class TripQueryServiceImplTest { waybill(2L, "YD-2", null, "running", null, at(2000L), ACCEPT_NO), waybill(1L, "YD-1", null, "completed", "accepted", at(1000L), ACCEPT_NO)); - TripTabCountsVO counts = service.tabCounts(TripQueryVO.forDriver(PHONE)); + TripTabCountsVO counts = service.tabCounts(TripQueryVO.forDriver(DRIVER_ID)); assertEquals(1, counts.getPending()); assertEquals(2, counts.getDoing()); @@ -476,7 +479,7 @@ class TripQueryServiceImplTest { stubRows( waybill(2L, "YD-2", LOADING_NO, "completed", "accepted", at(2000L), ACCEPT_NO), waybill(1L, "YD-1", LOADING_NO, "running", null, at(1000L), ACCEPT_NO)); - when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(PHONE))); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); TripQueryVO query = TripQueryVO.forAdmin(); query.setException("exception"); query.setExceptionWaybillIds(java.util.Set.of(2L)); @@ -572,7 +575,7 @@ class TripQueryServiceImplTest { @Test @DisplayName("详情与列表同口径:子单快照没有接单节点、项目生效配置要求接单时,详情也算待接单") void detailUsesLiveProcessConfig() { - when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(PHONE)); + when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(DRIVER_ID)); Waybill child = waybill(2092186187239780353L, "YD-20260915-0001", LOADING_NO, "running", "pending", at(2000L), ACCEPT_NO); child.setProjectId(7001L); @@ -590,7 +593,7 @@ class TripQueryServiceImplTest { @Test @DisplayName("详情仍是只读取数:生效配置只写回内存里的行,不落库") void detailStaysReadOnly() { - when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(PHONE)); + when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(DRIVER_ID)); Waybill child = waybill(2092186187239780353L, "YD-20260915-0001", LOADING_NO, "running", "pending", at(2000L), ACCEPT_NO); child.setProjectId(7001L); @@ -611,7 +614,7 @@ class TripQueryServiceImplTest { when(loadingManageService.getById(WAYBILL_ID)).thenReturn(null); when(waybillService.getById(WAYBILL_ID)) .thenReturn(waybill(WAYBILL_ID, "YD-20260920-0003", null, "running", null, at(1000L), ACCEPT_YES)); - when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(PHONE)); + when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(DRIVER_ID)); stubChildren(); TripVO lone = service.detail(tripQuery(String.valueOf(WAYBILL_ID))); @@ -627,13 +630,13 @@ class TripQueryServiceImplTest { @Test @DisplayName("详情的收口与列表一致:别人的运次、子单全被拒单的配载运次都不可达") void detailRefusesForeignTrips() { - when(loadingManageService.getById(LOADING_ID)).thenReturn(loading("13800000001")); + when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(OTHER_DRIVER_ID)); when(waybillService.getById(WAYBILL_ID)) .thenReturn(waybill(WAYBILL_ID, "YD-20260920-0003", null, "running", null, at(1000L), ACCEPT_YES)); assertThrows(ServiceException.class, () -> service.detail(tripQuery(String.valueOf(LOADING_ID)))); - when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(PHONE)); + when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(DRIVER_ID)); when(waybillService.list(anyWaybillWrapper())).thenReturn(List.of()); assertThrows(ServiceException.class, () -> service.detail(tripQuery(String.valueOf(LOADING_ID)))); @@ -647,14 +650,119 @@ class TripQueryServiceImplTest { void rejectedChildrenLeaveTheTrip() { // 段一的可见口径已经把被拒子单挡在库层,取回来是空则这趟车不存在 stubRows(); - when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(PHONE))); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); - IPage page = service.page(TripQueryVO.forDriver(PHONE)); + IPage page = service.page(TripQueryVO.forDriver(DRIVER_ID)); assertTrue(page.getRecords().isEmpty(), "可见子单为空时不给没有子单的运次"); assertEquals(0, page.getTotal()); } + // ------------------------------------------------------------ 收口判据:司机档案 ID + + @Test + @DisplayName("司机改号后运次仍可见:判据是档案 ID,派单快照号不再参与") + void driverScopeSurvivesPhoneChange() { + Waybill lone = waybill(7001L, "YD-LONE-0001", null, "running", null, at(1000L), ACCEPT_YES); + stubRows( + waybill(2092186187239780353L, "YD-20260825-0006", LOADING_NO, "running", null, at(2000L), ACCEPT_YES), + lone); + when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(DRIVER_ID)); + when(waybillService.getById(7001L)).thenReturn(lone); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); + + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); + query.setCurrent(1); + query.setSize(10); + IPage page = service.page(query); + + assertEquals(2, page.getTotal(), "档案换号不影响归属:已配载与未配载两段都要按档案 ID 命中"); + assertEquals(Set.of(LOADING_NO, "YD-LONE-0001"), + page.getRecords().stream().map(TripVO::getTripNo).collect(java.util.stream.Collectors.toSet())); + assertTrue(page.getRecords().stream().allMatch(trip -> PHONE.equals(trip.getDriverPhone())), + "响应里的司机手机号仍是派单快照,改号后不变"); + + assertEquals(LOADING_NO, service.detail(tripQuery(String.valueOf(LOADING_ID))).getTripNo()); + assertEquals(PHONE, service.detail(tripQuery(String.valueOf(LOADING_ID))).getDriverPhone(), + "已配载运次详情仍回派单快照号"); + assertEquals(PHONE, service.detail(tripQuery(String.valueOf(7001L))).getDriverPhone(), + "未配载运次详情仍回派单快照号"); + } + + @Test + @DisplayName("同号换人不放行:手机号相同、档案 ID 不同的运次仍然看不到") + void samePhoneDoesNotGrantAccess() { + // 派单快照号一模一样,档案 ID 却是别人 + Waybill foreignLone = waybill(7002L, "YD-LONE-0002", null, "running", null, at(1000L), ACCEPT_YES); + foreignLone.setDriverId(OTHER_DRIVER_ID); + when(waybillService.getById(7002L)).thenReturn(foreignLone); + + assertThrows(ServiceException.class, () -> service.detail(tripQuery(String.valueOf(7002L))), + "快照号相同不代表同一个司机"); + + // 已配载段同样只认配载单上的档案 ID + when(loadingManageService.getById(LOADING_ID)).thenReturn(loading(OTHER_DRIVER_ID)); + assertThrows(ServiceException.class, () -> service.detail(tripQuery(String.valueOf(LOADING_ID))), + "配载单归别人时详情同样拒绝"); + } + + @Test + @DisplayName("两段各按自己的 driver_id 判:已配载看配载单,未配载看运单") + void driverScopeReadsDriverIdOnBothSegments() { + Waybill loadedRow = waybill(2092186187239780353L, "YD-20260825-0006", LOADING_NO, "running", null, + at(2000L), ACCEPT_YES); + // 运单行归别人、配载单归本人:已配载段只认配载单 + loadedRow.setDriverId(OTHER_DRIVER_ID); + stubRows(loadedRow); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); + query.setCurrent(1); + query.setSize(10); + + assertEquals(1, service.page(query).getTotal(), "已配载段按配载单的 driver_id 收口,不看运单行的"); + + Waybill lone = waybill(7003L, "YD-LONE-0003", null, "running", null, at(1000L), ACCEPT_YES); + // 运单归本人、手机号却完全对不上:未配载段只认运单的 driver_id + lone.setDriverPhone("13800000003"); + stubRows(lone); + + IPage page = service.page(query); + assertEquals(1, page.getTotal(), "未配载段按运单的 driver_id 收口,不看手机号"); + assertEquals("13800000003", page.getRecords().get(0).getDriverPhone(), "展示的仍是运单上的派单快照号"); + } + + @Test + @DisplayName("管理端不按司机收口:带上任何 driverId 都照样全出") + void adminScopeIgnoresDriverId() { + stubRows( + waybill(2L, "YD-2", null, "running", null, at(2000L), ACCEPT_YES), + waybill(1L, "YD-1", null, "running", null, at(1000L), ACCEPT_YES)); + + for (Long driverId : java.util.Arrays.asList(null, 0L, -1L, OTHER_DRIVER_ID)) { + TripQueryVO admin = TripQueryVO.forAdmin(); + admin.setDriverId(driverId); + + assertEquals(2, service.page(admin).getTotal(), "管理端收口与司机无关:" + driverId); + } + } + + @Test + @DisplayName("收口缺省(空、0、负数)一律空集:不放行也不抛异常") + void missingDriverIdIsRefused() { + stubRows(waybill(1L, "YD-1", null, "running", null, at(1000L), ACCEPT_YES)); + + for (Long driverId : java.util.Arrays.asList(null, 0L, -1L, -9001L)) { + TripQueryVO query = TripQueryVO.forDriver(driverId); + + IPage page = service.page(query); + assertEquals(0, page.getTotal(), "收口缺失不能退回「不按司机收口」:" + driverId); + assertTrue(page.getRecords().isEmpty()); + assertEquals(0, service.tabCounts(query).getAll(), "统计同样为零:" + driverId); + assertThrows(ServiceException.class, () -> service.detail(query), "详情直接拒绝:" + driverId); + } + verify(waybillService, never()).list(anyWaybillWrapper()); + } + // ------------------------------------------------------------ 夹具 @SuppressWarnings("unchecked") @@ -712,7 +820,7 @@ class TripQueryServiceImplTest { } private List orgTypeTrips(String orgType) { - TripQueryVO query = TripQueryVO.forDriver(PHONE); + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); query.setTransportOrgType(orgType); query.setCurrent(1); query.setSize(50); @@ -737,7 +845,7 @@ class TripQueryServiceImplTest { stubRows( waybill(2092186187239780353L, "YD-20260825-0006", LOADING_NO, "running", null, at(2000L), ACCEPT_YES), waybill(2092186187470467073L, "YD-20260825-0007", LOADING_NO, "running", "pending", at(1000L), ACCEPT_NO)); - when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(PHONE))); + when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading(DRIVER_ID))); } private void stubChildren() { @@ -756,23 +864,25 @@ class TripQueryServiceImplTest { } private TripQueryVO tripQuery(String tripId) { - TripQueryVO query = TripQueryVO.forDriver(PHONE); + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); query.setTripId(tripId); return query; } private LoadingManage loading(String loadingNo, Long loadingId) { - LoadingManage loading = loading(PHONE); + LoadingManage loading = loading(DRIVER_ID); loading.setId(loadingId); loading.setLoadingNo(loadingNo); return loading; } - private LoadingManage loading(String driverPhone) { + private LoadingManage loading(Long driverId) { LoadingManage loading = new LoadingManage(); loading.setId(LOADING_ID); loading.setLoadingNo(LOADING_NO); - loading.setDriverPhone(driverPhone); + loading.setDriverId(driverId); + // 配载单上留的是派单时的手机号快照,档案改号后它不变 + loading.setDriverPhone(PHONE); loading.setDriverName("黄宇富"); loading.setVehicleNo("桂A32523"); loading.setProjectName("武宣建筑原料运输项目"); @@ -791,6 +901,8 @@ class TripQueryServiceImplTest { waybill.setLoadingNo(loadingNo); waybill.setBusinessStatus(businessStatus); waybill.setDriverAcceptStatus(acceptStatus); + waybill.setDriverId(DRIVER_ID); + // 运单上留的是派单时的手机号快照,档案改号后它不变 waybill.setDriverPhone(PHONE); waybill.setProcessJson(processJson); waybill.setCreateTime(createTime); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripReadServiceBoundaryTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripReadServiceBoundaryTest.java index ec37714d..e8432184 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripReadServiceBoundaryTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripReadServiceBoundaryTest.java @@ -45,6 +45,7 @@ import static org.mockito.Mockito.when; class TripReadServiceBoundaryTest { private static final String PHONE = "17587421208"; + private static final Long DRIVER_ID = 9001L; private static final String LOADING_NO = "PZ-20261017-001"; /** 19 位雪花号,按数字下发会被双精度取整 */ private static final Long LOADING_ID = 2111350784070275074L; @@ -100,7 +101,7 @@ class TripReadServiceBoundaryTest { child(CHILD_B, "cancelled", null, ACCEPT_NO, at(1787671422000L))); // 「全部」按定义不含已取消档,别在它上面找这趟车;这一趟车本身仍要算成已取消 - TripQueryVO query = TripQueryVO.forDriver(PHONE); + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); query.setStatus(3); TripVO trip = onlyTrip(query); @@ -205,7 +206,7 @@ class TripReadServiceBoundaryTest { void emptyChildrenDoNotThrow() { stubLoadedTrip(); - IPage page = service.page(TripQueryVO.forDriver(PHONE)); + IPage page = service.page(TripQueryVO.forDriver(DRIVER_ID)); assertTrue(page.getRecords().isEmpty(), "配载单在、可见子单为空时不给没有子单的运次"); @@ -239,6 +240,8 @@ class TripReadServiceBoundaryTest { bare.setBusinessStatus("pending"); bare.setDriverAcceptStatus("pending"); bare.setProcessJson(ACCEPT_YES); + bare.setDriverId(DRIVER_ID); + // 运单上留的是派单时的手机号快照,档案改号后它不变 bare.setDriverPhone(PHONE); when(waybillService.list(anyWaybillWrapper())).thenReturn(List.of(bare)); @@ -368,7 +371,7 @@ class TripReadServiceBoundaryTest { } private TripVO onlyTrip() { - return onlyTrip(TripQueryVO.forDriver(PHONE)); + return onlyTrip(TripQueryVO.forDriver(DRIVER_ID)); } private TripVO onlyTrip(TripQueryVO query) { @@ -378,7 +381,7 @@ class TripReadServiceBoundaryTest { } private TripQueryVO detailQuery() { - TripQueryVO query = TripQueryVO.forDriver(PHONE); + TripQueryVO query = TripQueryVO.forDriver(DRIVER_ID); query.setTripId(String.valueOf(LOADING_ID)); return query; } @@ -387,6 +390,8 @@ class TripReadServiceBoundaryTest { LoadingManage loading = new LoadingManage(); loading.setId(LOADING_ID); loading.setLoadingNo(LOADING_NO); + loading.setDriverId(DRIVER_ID); + // 配载单上留的是派单时的手机号快照 loading.setDriverPhone(PHONE); loading.setDriverName("黄宇富"); loading.setVehicleNo("桂A32523"); @@ -401,6 +406,8 @@ class TripReadServiceBoundaryTest { waybill.setLoadingNo(LOADING_NO); waybill.setBusinessStatus(businessStatus); waybill.setDriverAcceptStatus(acceptStatus); + waybill.setDriverId(DRIVER_ID); + // 运单上留的是派单时的手机号快照 waybill.setDriverPhone(PHONE); waybill.setProcessJson(processJson); waybill.setCreateTime(createTime);