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
new file mode 100644
index 00000000..802564b0
--- /dev/null
+++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillTabStatusTest.java
@@ -0,0 +1,176 @@
+package org.springblade.transport.service.impl;
+
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockedStatic;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.system.feign.IMpUserClient;
+import org.springblade.transport.mapper.ExceptionDisposalMapper;
+import org.springblade.transport.mapper.WaybillEnroutePunchMapper;
+import org.springblade.transport.mapper.WaybillNodePunchMapper;
+import org.springblade.transport.pojo.entity.Driver;
+import org.springblade.transport.pojo.entity.Waybill;
+import org.springblade.transport.pojo.entity.WaybillNodePunch;
+import org.springblade.transport.pojo.vo.DriverWaybillCardVO;
+import org.springblade.transport.pojo.vo.DriverWaybillTabCountsVO;
+import org.springblade.transport.service.IDriverService;
+import org.springblade.transport.service.IProcessConfigService;
+import org.springblade.transport.service.ITransportVehicleService;
+import org.springblade.transport.service.ITripQueryService;
+import org.springblade.transport.service.IWaybillService;
+import org.springblade.transport.support.CertificationGate;
+import org.springblade.transport.support.WaybillProcessSupport;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * 司机端四个 Tab 的状态分档。
+ *
+ * 待回单确认是「等业务员动手」的进行中:数字态沿用 1(进行中),查询口径也必须认它。
+ * 漏掉的话司机打完末位卡,这张单从他的列表里彻底消失——不是展示问题,是单子不见了。
+ */
+class DriverWaybillTabStatusTest {
+
+ private static final Long WAYBILL_ID = 5901L;
+ private static final Long USER_ID = 7001L;
+ private static final String PLATE = "桂AAW3256";
+ private static final String PHONE = "13800138002";
+
+ private IWaybillService waybillService;
+ private IDriverService driverService;
+ private WaybillNodePunchMapper nodePunchMapper;
+ private ExceptionDisposalMapper exceptionDisposalMapper;
+ private DriverWaybillServiceImpl service;
+
+ @BeforeAll
+ static void initTableInfo() {
+ // 构造 LambdaQueryWrapper 需要 MyBatis-Plus 的实体元数据缓存
+ MybatisConfiguration configuration = new MybatisConfiguration();
+ MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
+ assistant.setCurrentNamespace("test");
+ TableInfoHelper.initTableInfo(assistant, Waybill.class);
+ TableInfoHelper.initTableInfo(assistant, Driver.class);
+ TableInfoHelper.initTableInfo(assistant, WaybillNodePunch.class);
+ }
+
+ @BeforeEach
+ void setUp() {
+ waybillService = mock(IWaybillService.class);
+ driverService = mock(IDriverService.class);
+ nodePunchMapper = mock(WaybillNodePunchMapper.class);
+ exceptionDisposalMapper = mock(ExceptionDisposalMapper.class);
+ service = new DriverWaybillServiceImpl(
+ waybillService,
+ driverService,
+ mock(IMpUserClient.class),
+ mock(IProcessConfigService.class),
+ mock(ITransportVehicleService.class),
+ mock(CertificationGate.class),
+ mock(WaybillEnroutePunchMapper.class),
+ nodePunchMapper,
+ exceptionDisposalMapper,
+ mock(ITripQueryService.class));
+
+ when(driverService.getOne(any())).thenReturn(driver());
+ when(waybillService.syncDriverAcceptState(any())).thenAnswer(invocation -> invocation.getArgument(0));
+ when(waybillService.list(anyWrapper())).thenReturn(List.of(pendingReturnConfirmWaybill()));
+ }
+
+ @Test
+ @DisplayName("待回单确认落在「进行中」档:列表查得到,数字态是 1")
+ void pendingReturnConfirmIsInProgressTier() {
+ IPage page = runAsDriver(() -> service.page(1, 10, 1, null));
+
+ assertEquals(1, page.getRecords().size(), "进行中档必须包含待回单确认,否则司机看不到这张单");
+ assertEquals(Integer.valueOf(1), page.getRecords().get(0).getStatus(), "数字态沿用 1(进行中),不新增档位");
+ }
+
+ @Test
+ @DisplayName("「进行中」查询口径在库层就带上待回单确认")
+ void inProgressQueryCarriesPendingReturnConfirm() {
+ runAsDriver(() -> service.page(1, 10, 1, null));
+
+ assertTrue(queriedStatuses().contains(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM));
+ }
+
+ @Test
+ @DisplayName("「全部」tab 不漏掉待回单确认的运单")
+ void allTabKeepsPendingReturnConfirm() {
+ IPage page = runAsDriver(() -> service.page(1, 10, null, null));
+
+ assertEquals(1, page.getRecords().size(), "打完末位卡的单不能从全部 tab 消失");
+ assertEquals(Integer.valueOf(1), page.getRecords().get(0).getStatus());
+ assertTrue(queriedStatuses().contains(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM));
+ }
+
+ @Test
+ @DisplayName("Tab 计数把它算进进行中,不算已完成")
+ void tabCountsCountsItAsDoing() {
+ DriverWaybillTabCountsVO counts = runAsDriver(service::tabCounts);
+
+ assertEquals(1, counts.getDoing(), "待回单确认仍是进行中,不是已完成");
+ assertEquals(0, counts.getDone());
+ assertEquals(1, counts.getAll());
+ }
+
+ // ------------------------------------------------------------ 夹具
+
+ private T runAsDriver(Supplier action) {
+ try (MockedStatic authUtil = mockStatic(AuthUtil.class)) {
+ authUtil.when(AuthUtil::getUserId).thenReturn(USER_ID);
+ return action.get();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Collection