From 911393fb091fb2dfdc2962a47cec37ec3902cca2 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:33:45 +0800 Subject: [PATCH 01/25] =?UTF-8?q?:sparkles:=20=E8=BF=90=E5=8D=95=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=BE=85=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=B8=B8=E9=87=8F=E5=B9=B6=E7=BA=B3=E5=85=A5=E7=BB=88?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接单归一在需要确认接单的配置下会把非终态运单改成待执行/进行中,落库后即抹掉待回单确认。 --- .../transport/support/WaybillProcessSupport.java | 5 ++++- .../support/WaybillProcessSupportTest.java | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java index 4462c3a3..3d5ca5a7 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java @@ -49,6 +49,8 @@ public final class WaybillProcessSupport { public static final String STATUS_PENDING = "pending"; public static final String STATUS_RUNNING = "running"; + /** 司机已打完末位卡、回单需业务员确认,尚未生成应收应付 */ + public static final String STATUS_PENDING_RETURN_CONFIRM = "pending_return_confirm"; public static final String ACCEPT_PENDING = "pending"; public static final String ACCEPT_ACCEPTED = "accepted"; public static final String ACCEPT_REJECTED = "rejected"; @@ -213,7 +215,8 @@ public final class WaybillProcessSupport { || "completed".equals(businessStatus) || "cancelled".equals(businessStatus) || "waiting_dispatch".equals(businessStatus) - || "dispatching".equals(businessStatus); + || "dispatching".equals(businessStatus) + || STATUS_PENDING_RETURN_CONFIRM.equals(businessStatus); } /** diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java index b09c82c4..8cd89e3c 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java @@ -334,6 +334,20 @@ class WaybillProcessSupportTest { "TRANSPORT_JSON 的回单启用且没有 confirmMode:缺字段当没配「无需确认完成」,否则会跳过本该人工确认的回单"); } + // ------------------------------------------------------------ 待回单确认 + + @Test + @DisplayName("待回单确认落库字面量为 pending_return_confirm,且接单归一不得改写它") + void pendingReturnConfirmIsTerminalStatus() { + assertEquals("pending_return_confirm", WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, + "库里存的是这个字面量,web 与小程序也按它判断,不能改名"); + assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, + WaybillProcessSupport.normalizeBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, TRANSPORT_JSON), + "TRANSPORT_JSON 要求确认接单,未接单时归一结果本是 pending;运单已跑完全程,不能被改回待执行"); + assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, + WaybillProcessSupport.normalizeBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, TRANSPORT_JSON, "accepted")); + } + // ------------------------------------------------------------ 结束门禁 @Test From e0c65f44805ebf507824aeb495b9def78060c66c Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:35:03 +0800 Subject: [PATCH 02/25] =?UTF-8?q?:sparkles:=20=E5=BE=85=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E8=BF=90=E5=8D=95=E5=BD=92=E5=85=A5=E5=B7=B2?= =?UTF-8?q?=E7=BB=93=E6=9D=9F=EF=BC=8C=E6=89=93=E5=8D=A1=E9=97=A8=E7=A6=81?= =?UTF-8?q?=E7=BB=99=E5=87=BA=E4=B8=93=E9=97=A8=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/support/WaybillProcessSupport.java | 11 ++++++++--- .../transport/support/WaybillProcessSupportTest.java | 9 ++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java index 3d5ca5a7..aad6b3e5 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java @@ -136,14 +136,16 @@ public final class WaybillProcessSupport { } /** - * 运单已结束:已完成或已取消。此后不再接受司机打卡。 + * 运单已结束:已完成、已取消,或已打完末位卡停在待回单确认。此后不再接受司机打卡。 *

- * 只认这两个状态,大小写不敏感。草稿、待执行、待调度、调度中的运单司机仍要能打卡, + * 只认这三个状态,大小写不敏感。草稿、待执行、待调度、调度中的运单司机仍要能打卡, * 所以不能按 {@link #isTerminalBusinessStatus} 的「终态或调度中间态」口径判定。 */ public static boolean isPunchEnded(String businessStatus) { String status = stringVal(businessStatus); - return "completed".equalsIgnoreCase(status) || "cancelled".equalsIgnoreCase(status); + return "completed".equalsIgnoreCase(status) + || "cancelled".equalsIgnoreCase(status) + || STATUS_PENDING_RETURN_CONFIRM.equalsIgnoreCase(status); } /** @@ -157,6 +159,9 @@ public final class WaybillProcessSupport { if ("cancelled".equalsIgnoreCase(status)) { return "运单已取消"; } + if (STATUS_PENDING_RETURN_CONFIRM.equalsIgnoreCase(status)) { + return "运单待业务员确认回单"; + } return null; } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java index 8cd89e3c..2398cf2d 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java @@ -351,12 +351,14 @@ class WaybillProcessSupportTest { // ------------------------------------------------------------ 结束门禁 @Test - @DisplayName("结束门禁:只认已完成与已取消,大小写不敏感") - void punchEndedOnlyForCompletedAndCancelled() { + @DisplayName("结束门禁:已完成、已取消与待回单确认都算结束,大小写不敏感") + void punchEndedForCompletedCancelledAndPendingReturnConfirm() { assertTrue(WaybillProcessSupport.isPunchEnded("completed")); assertTrue(WaybillProcessSupport.isPunchEnded("COMPLETED")); assertTrue(WaybillProcessSupport.isPunchEnded("cancelled")); assertTrue(WaybillProcessSupport.isPunchEnded("Cancelled")); + assertTrue(WaybillProcessSupport.isPunchEnded("pending_return_confirm"), + "司机已打完末位卡,不能再打卡"); } @Test @@ -372,11 +374,12 @@ class WaybillProcessSupportTest { } @Test - @DisplayName("结束门禁文案:完成与取消各一句,非结束态没有文案") + @DisplayName("结束门禁文案:完成、取消与待回单确认各一句,非结束态没有文案") void punchEndedReasonOnlyForEndedStatuses() { assertEquals("运单已完成", WaybillProcessSupport.punchEndedReason("completed")); assertEquals("运单已完成", WaybillProcessSupport.punchEndedReason(" COMPLETED ")); assertEquals("运单已取消", WaybillProcessSupport.punchEndedReason("cancelled")); + assertEquals("运单待业务员确认回单", WaybillProcessSupport.punchEndedReason("pending_return_confirm")); assertNull(WaybillProcessSupport.punchEndedReason(null)); assertNull(WaybillProcessSupport.punchEndedReason("")); assertNull(WaybillProcessSupport.punchEndedReason(" ")); From 3c230eafed674c39f5666e30e3ad9e659a653325 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:36:01 +0800 Subject: [PATCH 03/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E5=BE=85=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=E8=BF=90?= =?UTF-8?q?=E5=8D=95=E7=9A=84=E4=B8=8A=E6=8A=A5=E5=BC=82=E5=B8=B8=E9=97=A8?= =?UTF-8?q?=E7=A6=81=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springblade/transport/support/WaybillProcessSupport.java | 2 +- .../transport/support/WaybillProcessSupportTest.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java index aad6b3e5..3b9d920e 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillProcessSupport.java @@ -202,7 +202,7 @@ public final class WaybillProcessSupport { * 上报异常前的运单状态门禁原因;可上报时返回 null。 *

* 与打卡共用「已开始运输」判据,但文案说的是能不能报,不是能不能打。 - * 结束优先于未开始:已完成/已取消的运单先说结束,不再提「等你上路」。 + * 结束优先于未开始:已结束的运单先说结束,不再提「等你上路」。 */ public static String reportBlockedReason(String businessStatus) { String endedReason = punchEndedReason(businessStatus); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java index 2398cf2d..568372be 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java @@ -472,9 +472,11 @@ class WaybillProcessSupportTest { assertNull(WaybillProcessSupport.reportBlockedReason("running")); assertEquals("运单尚未开始运输,暂不可上报异常", WaybillProcessSupport.reportBlockedReason("pending")); assertEquals("运单尚未开始运输,暂不可上报异常", WaybillProcessSupport.reportBlockedReason("dispatching")); - // 结束优先:已完成/已取消先说结束,不提「等你上路」 + // 结束优先:已结束态先说结束,不提「等你上路」 assertEquals("运单已完成,不能上报异常", WaybillProcessSupport.reportBlockedReason("completed")); assertEquals("运单已取消,不能上报异常", WaybillProcessSupport.reportBlockedReason("cancelled")); + assertEquals("运单待业务员确认回单,不能上报异常", + WaybillProcessSupport.reportBlockedReason("pending_return_confirm")); } } From 646d8513d2eb7ee2212eaa888b7a9e1d710e1a0b Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:36:36 +0800 Subject: [PATCH 04/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E9=94=81?= =?UTF-8?q?=E5=AE=9A=E5=BE=85=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=E8=BF=90?= =?UTF-8?q?=E5=8D=95=E7=9A=84=E3=80=8C=E6=9C=AA=E5=BC=80=E5=A7=8B=E8=BF=90?= =?UTF-8?q?=E8=BE=93=E3=80=8D=E6=96=87=E6=A1=88=E4=B8=8E=E5=9C=A8=E9=80=94?= =?UTF-8?q?=E5=8D=A1=E4=B8=8D=E5=8F=AF=E6=89=93=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两处都由 isPunchEnded 自动覆盖,测试只锁住它们不得回到「尚未开始运输」分支。 --- .../support/WaybillProcessSupportTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java index 568372be..9408bfd6 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java @@ -438,6 +438,22 @@ class WaybillProcessSupportTest { // 结束态由结束文案负责,两句话不能抢 assertNull(WaybillProcessSupport.punchNotStartedReason("completed")); assertNull(WaybillProcessSupport.punchNotStartedReason("cancelled")); + assertNull(WaybillProcessSupport.punchNotStartedReason("pending_return_confirm"), + "司机已打完末位卡,不能再被说成「尚未开始运输」"); + } + + @Test + @DisplayName("在途卡:待回单确认的运单与已完成同样不可打卡") + void transitCardOnPendingReturnConfirmWaybillIsVisibleButNotPunchable() { + LocalDateTime now = LocalDateTime.of(2026, 10, 20, 12, 0); + + WaybillProcessSupport.TransitCheckinDecision decision = + WaybillProcessSupport.evaluateTransitCheckin(TRANSPORT_JSON, "pending_return_confirm", null, now); + + assertTrue(decision.visible(), "卡片仍要下发,司机能回看"); + assertFalse(decision.punchEnabled(), "末位卡已打完,在途卡不能再提交"); + assertFalse(decision.doneToday()); + assertFalse(decision.dueToday()); } @Test From 5c88b90404ff294e6a43411ee169f70c188d1d4b Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:42:41 +0800 Subject: [PATCH 05/25] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E4=BF=9D=E5=AD=98=E7=94=A8=E8=AF=B7=E6=B1=82=E4=BD=93?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E8=BF=90=E5=8D=95=E4=B8=9A=E5=8A=A1=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/WaybillServiceImpl.java | 16 +- .../WaybillEditSaveBusinessStatusTest.java | 184 ++++++++++++++++++ 2 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 9f40356f..36ebf07f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -1062,10 +1062,11 @@ public class WaybillServiceImpl extends BaseServiceImpl waybill.setMasterNo(null); waybill.setMileageRemark(null); } - // 新建若已带业务状态(如复制提交已完成)则在过程校正后回写,保证与原运单一致 + // 新建若已带业务状态(如复制提交已完成)则在过程校正后回写,保证与原运单一致; + // 编辑保存只改资料,business_status 以库里的值为准,请求体里的值不回写 String preservedBusinessStatus = created ? TransportBusinessSupport.trimToNull(waybill.getBusinessStatus()) - : null; + : editPreservedBusinessStatus(oldRecord); if (isCompletedBusinessStatus(preservedBusinessStatus)) { preservedBusinessStatus = "completed"; } @@ -1087,6 +1088,17 @@ public class WaybillServiceImpl extends BaseServiceImpl } } + /** + * 编辑保存要回写的业务状态:取库里的值。 + * 草稿返回 null —— submit 即正式提交,草稿须由过程校正提升为待执行 / 进行中。 + */ + private String editPreservedBusinessStatus(Waybill oldRecord) { + String currentStatus = oldRecord == null + ? null + : TransportBusinessSupport.trimToNull(oldRecord.getBusinessStatus()); + return STATUS_DRAFT.equals(currentStatus) ? null : currentStatus; + } + private void fillCustomerName(Waybill waybill) { if (Func.isNotEmpty(waybill.getContractId())) { ContractManage contract = contractManageService.getById(waybill.getContractId()); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java new file mode 100644 index 00000000..3e1cf371 --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java @@ -0,0 +1,184 @@ +package org.springblade.transport.service.impl; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +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.MockedStatic; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.transport.mapper.WaybillMapper; +import org.springblade.transport.pojo.entity.Waybill; + +import java.lang.reflect.Field; +import java.math.BigDecimal; + +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.when; + +/** + * 运单编辑保存的业务状态来源测试。 + *

+ * 编辑保存只改运单资料,business_status 一律以库里的值为准:请求体不回传(null / 空串) + * 不得把库里的待执行、进行中、已完成改写成别的状态,回传陈旧值也不得覆盖。 + * 唯一例外是草稿——submit 即正式提交,草稿须按过程配置提升为待执行 / 进行中。 + *

+ * 过程配置用「无需确认接单」的快照({@code []}):无过程校正时待执行会被提升为进行中, + * 正是编辑保存最容易把库里状态写坏的场景。 + */ +class WaybillEditSaveBusinessStatusTest { + + private static final Long WAYBILL_ID = 7001L; + private static final Long DEPT_ID = 100L; + /** 无接单节点的过程配置:normalizeBusinessStatus 会把空 / pending 提升为 running */ + private static final String PROCESS_JSON_NO_ACCEPT = "[]"; + + private WaybillMapper waybillMapper; + private WaybillServiceImpl service; + + @BeforeAll + static void initTableInfo() { + // saveOrUpdate 判主键需要 MyBatis-Plus 的实体元数据缓存 + MybatisConfiguration configuration = new MybatisConfiguration(); + MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, ""); + assistant.setCurrentNamespace("test"); + TableInfoHelper.initTableInfo(assistant, Waybill.class); + } + + @BeforeEach + void setUp() throws Exception { + waybillMapper = mock(WaybillMapper.class); + service = new WaybillServiceImpl(); + inject("baseMapper", waybillMapper); + when(waybillMapper.updateById(any(Waybill.class))).thenReturn(1); + } + + // ------------------------------------------------------------ 编辑保存:以库里的状态为准 + + @Test + @DisplayName("编辑保存:请求体不回传业务状态,库里的待执行不得被提升为进行中") + void editSaveKeepsPendingWhenRequestBodyOmitsStatus() { + Waybill request = editableRequest(); + + submit(request, "pending"); + + assertEquals("pending", request.getBusinessStatus(), + "编辑保存时请求体没有 business_status,不得按过程配置把库里的待执行改写成进行中"); + } + + @Test + @DisplayName("编辑保存:请求体不回传业务状态,库里的进行中不得被改写") + void editSaveKeepsRunningWhenRequestBodyOmitsStatus() { + Waybill request = editableRequest(); + + submit(request, "running"); + + assertEquals("running", request.getBusinessStatus()); + } + + @Test + @DisplayName("编辑保存:请求体不回传业务状态,库里的已完成不得被改写") + void editSaveKeepsCompletedWhenRequestBodyOmitsStatus() { + Waybill request = editableRequest(); + + submit(request, "completed"); + + assertEquals("completed", request.getBusinessStatus()); + } + + @Test + @DisplayName("编辑保存:请求体回传陈旧状态,仍以库里的值为准") + void editSaveKeepsDatabaseStatusWhenRequestBodyCarriesStaleValue() { + Waybill request = editableRequest(); + request.setBusinessStatus("running"); + + submit(request, "completed"); + + assertEquals("completed", request.getBusinessStatus(), + "库里已完成,请求体带的是打开表单时的旧值,不得据此回退状态"); + } + + @Test + @DisplayName("编辑保存:请求体回传空串,库里的状态不得被清空") + void editSaveKeepsDatabaseStatusWhenRequestBodyIsBlank() { + Waybill request = editableRequest(); + request.setBusinessStatus(""); + + submit(request, "running"); + + assertEquals("running", request.getBusinessStatus()); + } + + // ------------------------------------------------------------ 草稿例外 + + @Test + @DisplayName("提交草稿:草稿仍按过程配置提升为进行中") + void submitDraftStillPromotesToRunning() { + Waybill request = editableRequest(); + + submit(request, "draft"); + + assertEquals("running", request.getBusinessStatus(), + "submit 即正式提交,草稿必须脱离 draft,不能被「以库里的值为准」锁死"); + } + + // ------------------------------------------------------------ 夹具 + + private Waybill editableRequest() { + Waybill waybill = new Waybill(); + waybill.setId(WAYBILL_ID); + waybill.setProjectName("项目A"); + waybill.setContractName("客户合同A"); + waybill.setCustomerName("客户A"); + waybill.setTransportType("公路运输"); + waybill.setCargoName("钢材"); + waybill.setCargoType("普货"); + waybill.setQuantityUnit("吨"); + waybill.setCarrierType("自运"); + waybill.setVehicleNo("桂A12345"); + waybill.setDriverName("张三"); + waybill.setCarrierJson("{}"); + waybill.setQuantity(BigDecimal.TEN); + waybill.setProcessJson(PROCESS_JSON_NO_ACCEPT); + return waybill; + } + + /** + * 让库里的同一条运单处于 {@code databaseStatus},再走一次编辑保存(submit)。 + * 司机与车牌保持一致,避免接单记录被判定为「换了司机」而清空。 + */ + private void submit(Waybill request, String databaseStatus) { + Waybill oldRecord = editableRequest(); + oldRecord.setBusinessStatus(databaseStatus); + oldRecord.setDeptId(DEPT_ID); + oldRecord.setDeptName("测试组织"); + when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(oldRecord); + + try (MockedStatic authUtil = mockStatic(AuthUtil.class)) { + authUtil.when(AuthUtil::getDeptId).thenReturn(DEPT_ID); + assertTrue(service.submit(request)); + } + } + + private void inject(String field, Object value) throws Exception { + Class type = service.getClass(); + while (type != null) { + try { + Field declared = type.getDeclaredField(field); + declared.setAccessible(true); + declared.set(service, value); + return; + } catch (NoSuchFieldException ignored) { + type = type.getSuperclass(); + } + } + throw new IllegalStateException("未找到字段:" + field); + } + +} From 6de80d2bb233dd41c7dfc6377ce36b4ed0eb7d4a Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:44:06 +0800 Subject: [PATCH 06/25] =?UTF-8?q?:bug:=20=E5=BE=85=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E4=B8=8B=E6=8B=92=E7=BB=9D=E8=A1=A5=E5=BD=95?= =?UTF-8?q?=E6=89=93=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/WaybillServiceImpl.java | 6 +++ .../impl/WaybillPunchRecordSaveTest.java | 54 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 9f40356f..1f54276c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -531,6 +531,12 @@ public class WaybillServiceImpl extends BaseServiceImpl } // 只有显式传 false 才是取消打卡,未传按已打卡处理 boolean punched = !Boolean.FALSE.equals(request.getPunched()); + // 待回单确认的运单司机已无待打卡节点,再补一条打卡流水与状态含义矛盾; + // 漏打的出路是先取消该节点的打卡退回进行中(punched=false),再重新补录。 + if (punched && WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM + .equalsIgnoreCase(waybill.getBusinessStatus())) { + throw new ServiceException("运单待业务员确认回单,不能补录打卡"); + } if (ENROUTE_PUNCH_TYPE.equalsIgnoreCase(TransportBusinessSupport.trimToEmpty(request.getType()))) { return saveEnroutePunchRecord(waybill, request, punched); } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java index 6b41a1ef..1778a850 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java @@ -18,6 +18,7 @@ import org.springblade.transport.pojo.dto.WaybillPunchRecordSaveRequest; import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.entity.WaybillEnroutePunch; import org.springblade.transport.pojo.entity.WaybillNodePunch; +import org.springblade.transport.support.WaybillProcessSupport; import java.lang.reflect.Field; import java.util.ArrayList; @@ -165,6 +166,59 @@ class WaybillPunchRecordSaveTest { verify(nodePunchMapper, never()).updateById(any(WaybillNodePunch.class)); } + @Test + @DisplayName("待回单确认:补录打卡被拒——司机已无待打卡,漏打应先取消打卡退回进行中") + void rejectBackfillOnPendingReturnConfirm() { + Waybill waybill = waybill(); + waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); + when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill); + when(nodePunchMapper.selectList(any())).thenReturn(List.of()); + + WaybillPunchRecordSaveRequest request = request("node", "load", "装货"); + request.setPunched(true); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.savePunchRecord(request)); + + assertEquals("运单待业务员确认回单,不能补录打卡", exception.getMessage()); + verify(nodePunchMapper, never()).insert(any(WaybillNodePunch.class)); + } + + @Test + @DisplayName("待回单确认:取消打卡仍放行——那是退回进行中的唯一路径") + void keepCancelPunchOnPendingReturnConfirm() { + Waybill waybill = waybill(); + waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); + when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill); + when(nodePunchMapper.selectList(any())).thenReturn(List.of(nodePunch(7001L, "load", "装货"))); + + WaybillPunchRecordSaveRequest request = request("node", "load", "装货"); + request.setPunched(false); + + assertTrue(service.savePunchRecord(request)); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(Collection.class); + verify(nodePunchMapper).deleteBatchIds(captor.capture()); + assertEquals(Set.of(7001L), new HashSet<>(captor.getValue())); + } + + @Test + @DisplayName("已完成运单仍可补录——门禁只对待回单确认生效") + void keepBackfillOnCompletedWaybill() { + Waybill waybill = waybill(); + waybill.setBusinessStatus("completed"); + when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill); + when(nodePunchMapper.selectList(any())).thenReturn(List.of()); + + WaybillPunchRecordSaveRequest request = request("node", "load", "装货"); + request.setPunched(true); + request.setPunchTime("2026-10-06 09:30:00"); + + assertTrue(service.savePunchRecord(request)); + + verify(nodePunchMapper).insert(any(WaybillNodePunch.class)); + } + @Test @DisplayName("接单节点:补录与取消打卡都拒绝——它的完成由运单的接单事实决定") void rejectAcceptNodePunchEdit() { From 3e60c0720bee09191e29260588e12b5385a5bb38 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:46:03 +0800 Subject: [PATCH 07/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E9=94=81?= =?UTF-8?q?=E5=AE=9A=E6=9C=AB=E4=BD=8D=E6=89=93=E5=8D=A1=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E9=9C=80=E7=A1=AE=E8=AE=A4=E6=97=B6=E8=BF=9B=E5=85=A5=E5=BE=85?= =?UTF-8?q?=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/pojo/vo/DriverNodePunchVO.java | 3 + .../impl/DriverWaybillAutoCompleteTest.java | 106 ++++++++++++++++-- 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverNodePunchVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverNodePunchVO.java index 691cb573..d5d10b60 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverNodePunchVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverNodePunchVO.java @@ -52,4 +52,7 @@ public class DriverNodePunchVO implements Serializable { @Schema(description = "本次打卡是否使运单自动完成") private Boolean waybillCompleted; + @Schema(description = "本次打卡后的运单业务状态:running / pending_return_confirm / completed") + private String waybillBusinessStatus; + } 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 f2eeac76..3c1f83fe 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 @@ -7,6 +7,7 @@ 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.log.exception.ServiceException; import org.springblade.core.secure.utils.AuthUtil; @@ -28,6 +29,7 @@ import org.springblade.transport.service.IWaybillService; import org.springblade.transport.service.ITripQueryService; import org.springblade.transport.support.CertificationGate; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -35,6 +37,7 @@ 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.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; @@ -42,11 +45,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * 司机打完末位打卡节点后运单自动完成。 + * 司机打完末位打卡节点后运单的去向。 *

- * 命中要四个条件同时成立:本次打卡的是「司机可打卡节点」的末位、该节点是到货/卸货/签收、 - * 回单无需确认完成、运单本身允许完成。任一不成立都只落打卡流水, - * 响应里的 waybillCompleted 始终取完成接口的返回值,前端据此决定是否提示「运单已完成」。 + * 末位打卡是到货/卸货/签收、运单本身允许完成时,回单是否需业务员确认决定去向: + * 无需确认则本次一并完成运单,需确认则把运单置为待回单确认等业务员动手。 + * 任一条件不成立都只落打卡流水。 + * 响应里的 waybillCompleted 始终取完成接口的返回值,前端据此决定是否提示「运单已完成」; + * 待回单确认不是已完成,只能靠 waybillBusinessStatus 分辨。 */ class DriverWaybillAutoCompleteTest { @@ -72,6 +77,12 @@ class DriverWaybillAutoCompleteTest { [{"key":"dispatch","name":"发货","type":"cargo","enabled":true,"punch":true}] """; + /** 末位司机打卡节点是「发货」,回单启用且需确认完成 */ + private static final String DISPATCH_LAST_RETURN_CONFIRM_JSON = """ + [{"key":"dispatch","name":"发货","type":"cargo","enabled":true,"punch":true}, + {"key":"return","name":"回单","type":"confirm","enabled":true,"punch":true,"confirmMode":"yes"}] + """; + private IWaybillService waybillService; private IDriverService driverService; private WaybillNodePunchMapper nodePunchMapper; @@ -118,7 +129,7 @@ class DriverWaybillAutoCompleteTest { // ------------------------------------------------------------ 命中 @Test - @DisplayName("末位签收打卡且回单无需确认时调完成接口,响应标记 waybillCompleted") + @DisplayName("末位签收打卡且回单无需确认时调完成接口,响应标记 waybillCompleted 与 completed") void lastDeliveryPunchCompletesWaybill() { givenWaybill(SIGN_LAST_JSON, "running"); when(waybillService.completeWithoutDeptCheck(WAYBILL_ID)).thenReturn(true); @@ -127,6 +138,7 @@ class DriverWaybillAutoCompleteTest { verify(waybillService).completeWithoutDeptCheck(WAYBILL_ID); assertTrue(vo.getWaybillCompleted(), "完成接口返回成功,响应要告诉前端运单已完成"); + assertEquals("completed", vo.getWaybillBusinessStatus(), "完成后运单业务状态要如实回报"); } @Test @@ -139,6 +151,25 @@ class DriverWaybillAutoCompleteTest { verify(waybillService).completeWithoutDeptCheck(WAYBILL_ID); assertFalse(vo.getWaybillCompleted(), "完成接口自己说不算完成时,前端不能提示运单已完成"); + assertEquals("running", vo.getWaybillBusinessStatus(), "完成没落库,运单仍在进行中"); + } + + // ------------------------------------------------------------ 待回单确认 + + @Test + @DisplayName("回单需业务员确认时末位打卡进入待回单确认,落库并回报,且不调完成接口") + void returnNeedingConfirmationEntersPendingReturnConfirm() { + givenWaybill(SIGN_LAST_RETURN_CONFIRM_JSON, "running"); + + DriverNodePunchVO vo = submit("sign"); + + verify(waybillService, never()).completeWithoutDeptCheck(any()); + verify(nodePunchMapper).insert(any(WaybillNodePunch.class)); + assertFalse(vo.getWaybillCompleted(), "待回单确认不是已完成"); + assertEquals("pending_return_confirm", vo.getWaybillBusinessStatus(), + "客户端要知道打卡已提交、等业务员确认回单"); + assertEquals("pending_return_confirm", writtenBusinessStatus(), + "状态要落库,否则下次查询又回到进行中"); } // ------------------------------------------------------------ 不命中 @@ -153,18 +184,31 @@ class DriverWaybillAutoCompleteTest { verify(waybillService, never()).completeWithoutDeptCheck(any()); verify(nodePunchMapper).insert(any(WaybillNodePunch.class)); assertFalse(vo.getWaybillCompleted()); + assertEquals("running", vo.getWaybillBusinessStatus()); } @Test - @DisplayName("回单启用且需确认完成时只打卡不完成") - void returnNeedingConfirmationDoesNotComplete() { + @DisplayName("回单需确认但不是末位打卡:运单留在进行中") + void nonLastPunchDoesNotEnterReturnConfirmation() { givenWaybill(SIGN_LAST_RETURN_CONFIRM_JSON, "running"); - DriverNodePunchVO vo = submit("sign"); + DriverNodePunchVO vo = submit("load"); + + verify(nodePunchMapper).insert(any(WaybillNodePunch.class)); + assertEquals("running", vo.getWaybillBusinessStatus(), "后面还有签收要打,不能提前停在待回单确认"); + assertEquals("running", writtenBusinessStatus()); + } + + @Test + @DisplayName("回单需确认但末位不是到货/卸货/签收:运单留在进行中") + void nonDeliveryLastNodeDoesNotEnterReturnConfirmation() { + givenWaybill(DISPATCH_LAST_RETURN_CONFIRM_JSON, "running"); + + DriverNodePunchVO vo = submit("dispatch"); verify(waybillService, never()).completeWithoutDeptCheck(any()); - verify(nodePunchMapper).insert(any(WaybillNodePunch.class)); - assertFalse(vo.getWaybillCompleted()); + assertEquals("running", vo.getWaybillBusinessStatus(), "发货不是到货类节点,打完不该停在等待回单"); + assertEquals("running", writtenBusinessStatus()); } // ------------------------------------------------------------ 前置:运单本身能否完成 @@ -208,8 +252,50 @@ class DriverWaybillAutoCompleteTest { assertFalse(vo.getWaybillCompleted()); } + @Test + @DisplayName("有未了结异常时回单需确认也不进待回单确认——四条进入条件共同成立才进") + void unresolvedExceptionBlocksEnteringReturnConfirmation() { + givenWaybill(SIGN_LAST_RETURN_CONFIRM_JSON, "running"); + when(exceptionDisposalMapper.selectList(any())).thenReturn(List.of(unresolvedException("unload"))); + + DriverNodePunchVO vo = submit("sign"); + + assertEquals("running", vo.getWaybillBusinessStatus(), "异常未了结,不能把运单停在等待回单"); + assertEquals("running", writtenBusinessStatus()); + } + + // ------------------------------------------------------------ 并发:只从进行中进入 + + @Test + @DisplayName("打卡期间运单已被推到终态时不再写待回单确认——只从进行中进入") + void concurrentTerminalStatusPreventsEnteringReturnConfirmation() { + givenWaybill(SIGN_LAST_RETURN_CONFIRM_JSON, "running"); + List statusesWritten = new ArrayList<>(); + // 模拟另一事务在这次打卡推进过程节点时把运单置为已完成(管理端强制完成) + when(waybillService.updateById(any())).thenAnswer(invocation -> { + Waybill entity = invocation.getArgument(0); + statusesWritten.add(entity.getBusinessStatus()); + entity.setBusinessStatus("completed"); + return true; + }); + + DriverNodePunchVO vo = submit("sign"); + + assertFalse(statusesWritten.contains("pending_return_confirm"), + "运单已被完成,不能再改写回待回单确认"); + assertEquals("completed", vo.getWaybillBusinessStatus()); + } + // ------------------------------------------------------------ 夹具 + /** 打卡流程最后一次落库时运单的业务状态;状态写库走 updateById,各次传的是同一个实体引用 */ + private String writtenBusinessStatus() { + ArgumentCaptor captor = ArgumentCaptor.forClass(Waybill.class); + verify(waybillService, atLeastOnce()).updateById(captor.capture()); + List written = captor.getAllValues(); + return written.get(written.size() - 1).getBusinessStatus(); + } + private DriverNodePunchVO submit(String nodeCode) { NodeSubmitDTO dto = new NodeSubmitDTO(); dto.setWaybillId(WAYBILL_ID); From 570331321148b922a28a672e2533520edf57efab Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:47:32 +0800 Subject: [PATCH 08/25] =?UTF-8?q?:sparkles:=20=E6=9C=AB=E4=BD=8D=E6=89=93?= =?UTF-8?q?=E5=8D=A1=E5=9B=9E=E5=8D=95=E9=9C=80=E7=A1=AE=E8=AE=A4=E6=97=B6?= =?UTF-8?q?=E8=BF=9B=E5=85=A5=E5=BE=85=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/DriverWaybillServiceImpl.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) 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 ae825828..9590a442 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 @@ -520,19 +520,32 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { nodePunchMapper.insert(punch); advanceCurrentProcessNode(waybill, nodeCfg); - // 末位打卡节点是到货/卸货/签收、且回单无需确认完成时,本次打卡一并把运单置为完成; + // 末位打卡节点是到货/卸货/签收、且运单本身允许完成时,本次打卡就是运单的去向决定点: + // 回单无需确认 → 一并完成;回单需业务员确认 → 停在待回单确认等业务员确认。 // isLastDriverPunchNode 与 returnNeedsNoConfirmation 传的就是上面那份 processJson,未再调 resolveProcessJson 取过程配置。 String nodeKey = WaybillProcessSupport.nodeKey(nodeCfg); String nodeName = WaybillProcessSupport.nodeName(nodeCfg); + String waybillBusinessStatus = waybill.getBusinessStatus(); boolean completed = false; if (WaybillProcessSupport.isLastDriverPunchNode(processJson, nodeKey) && WaybillProcessSupport.isDeliveryNode(nodeKey, nodeName) - && WaybillProcessSupport.returnNeedsNoConfirmation(processJson) && canAutoCompleteWaybill(waybill)) { - completed = waybillService.completeWithoutDeptCheck(waybill.getId()); + if (WaybillProcessSupport.returnNeedsNoConfirmation(processJson)) { + completed = waybillService.completeWithoutDeptCheck(waybill.getId()); + if (completed) { + waybillBusinessStatus = STATUS_COMPLETED; + } + } else if (WaybillProcessSupport.STATUS_RUNNING.equals(waybill.getBusinessStatus())) { + // 只有进行中能进入:canAutoCompleteWaybill 的状态口径含待执行/待调度等,不能替代这一条; + // 待回单确认在 isPunchEnded 里算已结束,不能从结束态重进。 + waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); + waybillService.updateById(waybill); + waybillBusinessStatus = WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM; + } } DriverNodePunchVO vo = toNodePunchVO(punch); vo.setWaybillCompleted(completed); + vo.setWaybillBusinessStatus(waybillBusinessStatus); return vo; } From 6412769ced6de4d27a905162a20c0fcf67fedc5f Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:49:19 +0800 Subject: [PATCH 09/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E5=BE=85=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=B8=8D=E8=A2=AB=E7=BC=96=E8=BE=91=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E6=94=B9=E5=86=99=E7=9A=84=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/WaybillEditSaveBusinessStatusTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java index 3e1cf371..626d8886 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java @@ -11,6 +11,7 @@ import org.mockito.MockedStatic; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.transport.mapper.WaybillMapper; import org.springblade.transport.pojo.entity.Waybill; +import org.springblade.transport.support.WaybillProcessSupport; import java.lang.reflect.Field; import java.math.BigDecimal; @@ -115,6 +116,19 @@ class WaybillEditSaveBusinessStatusTest { assertEquals("running", request.getBusinessStatus()); } + // ------------------------------------------------------------ 新增状态 + + @Test + @DisplayName("编辑保存:待回单确认的运单不得被编辑保存改成进行中") + void editSaveKeepsPendingReturnConfirm() { + Waybill request = editableRequest(); + + submit(request, WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); + + assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, request.getBusinessStatus(), + "司机已打完末位卡,编辑一次运单资料不得把它退回进行中"); + } + // ------------------------------------------------------------ 草稿例外 @Test From 5f53a8671d158fc9ddb146d5b0bee5872d493654 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:49:55 +0800 Subject: [PATCH 10/25] =?UTF-8?q?:fire:=20=E5=88=A0=E9=99=A4=E5=8F=B8?= =?UTF-8?q?=E6=9C=BA=E7=AB=AF=E5=AE=8C=E6=88=90=E8=BF=90=E5=8D=95=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DriverWaybillController 去掉 POST /complete - IDriverWaybillService/DriverWaybillServiceImpl 去掉 complete 及其独占的 assertNoUnresolvedException - 末位打卡自动完成仍用 COMPLETABLE_STATUSES 与 canAutoCompleteWaybill - 新增 WaybillCompleteEndpointTest:司机端完成端点已 404,管理端完成接口仍在 --- .../controller/DriverWaybillController.java | 7 -- .../service/IDriverWaybillService.java | 5 -- .../impl/DriverWaybillServiceImpl.java | 32 +-------- .../WaybillCompleteEndpointTest.java | 71 +++++++++++++++++++ 4 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/controller/WaybillCompleteEndpointTest.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java index bb9b4693..dfba67e8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java @@ -201,13 +201,6 @@ public class DriverWaybillController extends BladeController { return R.data(driverWaybillService.submitNode(dto)); } - @PostMapping("/complete") - @ApiOperationSupport(order = 10) - @Operation(summary = "司机完成运单", description = "校验司机归属后改状态为已完成,并检查生成应收应付明细(与管理端一致)") - public R complete(@Parameter(description = "运单ID", required = true) @RequestParam Long id) { - return R.status(driverWaybillService.complete(id)); - } - /** 前端可能传空字符串表示「全部」 */ private Integer parseStatus(String status) { if (Func.isEmpty(status)) { 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 9ad5f543..0cc4d89b 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 @@ -157,11 +157,6 @@ public interface IDriverWaybillService { */ DriverNodePunchVO submitNode(NodeSubmitDTO dto); - /** - * 司机完成运单:校验归属后改状态为已完成,并走与管理端相同的应收应付明细生成逻辑。 - */ - boolean complete(Long id); - /** * 按运单与节点 key 取该运单实际生效的过程节点配置;节点不存在时返回 null。 *

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 ae825828..cec6c927 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 @@ -611,24 +611,6 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { return waybillService.updateById(waybill); } - @Override - @Transactional(rollbackFor = Exception.class) - public boolean complete(Long id) { - if (id == null) { - throw new ServiceException("运单ID不能为空"); - } - requireCurrentDriver(); - Waybill waybill = loadDriverWaybill(id); - waybill = normalizeAcceptStatus(waybill); - if (!COMPLETABLE_STATUSES.contains(waybill.getBusinessStatus())) { - throw new ServiceException("当前运单状态不允许完成"); - } - // 有未了结异常时不允许完成——完成接口不要求节点全部打卡,不挡住就能绕过打卡门禁 - assertNoUnresolvedException(waybill.getId()); - // 与管理端完成逻辑一致:改状态 + 生成应收应付明细 + 尝试完成配载单 - return waybillService.completeWithoutDeptCheck(waybill.getId()); - } - private void assertAcceptable(Waybill waybill) { if (WaybillProcessSupport.isTerminalBusinessStatus(waybill.getBusinessStatus())) { throw new ServiceException("当前运单状态不允许接单"); @@ -1519,7 +1501,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } /** - * 自动完成的前置:与司机端「完成运单」同一套——业务状态允许完成,且没有未了结异常。 + * 自动完成的前置:业务状态允许完成,且没有未了结异常。 *

* 不满足返回 false,由调用方静默跳过,打卡不受影响。 */ @@ -1528,18 +1510,6 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { && unresolvedExceptionNodeCodeList(waybill.getId()).isEmpty(); } - /** - * 断言该运单没有未了结的异常。 - *

- * 完成运单前必须校验:完成接口本身不要求节点全部打卡,不挡住它就能从 - * 「完成运单」绕过打卡门禁。 - */ - private void assertNoUnresolvedException(Long waybillId) { - if (!unresolvedExceptionNodeCodeList(waybillId).isEmpty()) { - throw new ServiceException("该运单存在未处理异常,处理完成后才能完成运单"); - } - } - /** * 组装过程配置 punch=是 的打卡节点列表。 *

diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/controller/WaybillCompleteEndpointTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/controller/WaybillCompleteEndpointTest.java new file mode 100644 index 00000000..fae4e73d --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/controller/WaybillCompleteEndpointTest.java @@ -0,0 +1,71 @@ +package org.springblade.transport.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springblade.transport.service.IContractManageService; +import org.springblade.transport.service.ICustomerArchiveService; +import org.springblade.transport.service.IDriverWaybillService; +import org.springblade.transport.service.IProjectApplyService; +import org.springblade.transport.service.ITransportPlanService; +import org.springblade.transport.service.IWaybillImportBatchService; +import org.springblade.transport.service.IWaybillService; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * 运单「完成运单」接口的边界:司机端不再有完成入口,管理端强制完成仍可达。 + *

+ * 走真实 HTTP 路由:司机端那条链删没删干净由 404 说话,管理端保持可用由 200 + 落到 + * {@code waybillService.complete} 说话。 + */ +class WaybillCompleteEndpointTest { + + private IWaybillService waybillService; + private MockMvc driverMvc; + private MockMvc manageMvc; + + @BeforeEach + void setUp() { + waybillService = mock(IWaybillService.class); + DriverWaybillController driverController = new DriverWaybillController(mock(IDriverWaybillService.class)); + driverMvc = MockMvcBuilders.standaloneSetup(driverController).build(); + WaybillController manageController = new WaybillController( + waybillService, + mock(IProjectApplyService.class), + mock(IContractManageService.class), + mock(ICustomerArchiveService.class), + mock(ITransportPlanService.class), + mock(IWaybillImportBatchService.class)); + manageMvc = MockMvcBuilders.standaloneSetup(manageController).build(); + } + + @Test + @DisplayName("POST /blade-transport/waybill/complete:司机端完成运单路由已下线") + void driverCompleteRouteIsGone() throws Exception { + driverMvc.perform(post("/blade-transport/waybill/complete").param("id", "6001")) + .andExpect(status().isNotFound()); + driverMvc.perform(post("/waybill/complete").param("id", "6001")) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("POST /waybill-manage/complete:管理端强制完成仍可达") + void manageCompleteRouteIsKept() throws Exception { + when(waybillService.complete(6001L)).thenReturn(true); + + manageMvc.perform(post("/waybill-manage/complete").param("id", "6001")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)); + + verify(waybillService).complete(6001L); + } + +} From f0872c78821ac62fbf933a29efbf45eb0cff6344 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:53:21 +0800 Subject: [PATCH 11/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E5=9B=9E=E5=8D=95=E6=94=B9=E7=9C=8B=E8=BF=90=E5=8D=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E3=80=81=E5=8F=96=E6=B6=88=E6=89=93=E5=8D=A1?= =?UTF-8?q?=E5=9B=9E=E9=80=80=E4=B8=8E=E5=8F=96=E6=B6=88=E8=BF=90=E5=8D=95?= =?UTF-8?q?=E7=9A=84=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/WaybillConfirmReturnTest.java | 100 +++++++++++++++--- .../impl/WaybillPunchRecordListTest.java | 14 +++ .../impl/WaybillPunchRecordSaveTest.java | 57 ++++++++++ 3 files changed, 157 insertions(+), 14 deletions(-) diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillConfirmReturnTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillConfirmReturnTest.java index 3cfe106f..b753a03b 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillConfirmReturnTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillConfirmReturnTest.java @@ -23,6 +23,7 @@ import org.springblade.transport.pojo.entity.WaybillNodePunch; import org.springblade.transport.service.ILoadingManageService; import org.springblade.transport.service.IProcessConfigService; import org.springblade.transport.service.IReceivablePayableDetailService; +import org.springblade.transport.support.WaybillProcessSupport; import java.lang.reflect.Field; import java.util.List; @@ -37,15 +38,17 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * 确认回单:回单节点要求业务员确认时,内部人员确认后把运单置为已完成。 + * 确认回单:停在待回单确认的运单,内部人员确认后把运单置为已完成。 *

- * 完整入口不校验节点打卡,故这里必须自己挡住两件事——该运单的司机打卡节点(在途除外)全部已打卡、 - * 无未了结异常;不挡住就成了绕过打卡门禁与异常门禁的通道。回单不要求确认时不能走这个入口。 + * 完整入口不校验节点打卡,故这里必须自己挡住三件事——运单确实停在待回单确认、该运单的司机打卡节点 + * (在途除外)全部已打卡、无未了结异常;不挡住就成了绕过打卡门禁与异常门禁的通道。 + * 判据只看运单状态,不看回单配置当前值。 */ class WaybillConfirmReturnTest { private static final Long WAYBILL_ID = 5701L; private static final String DEPT_ID = "1123598813738675201"; + private static final String PENDING_RETURN_CONFIRM = WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM; private static final String RETURN_NEED_CONFIRM_JSON = """ [{"key":"load","name":"装货","type":"cargo","enabled":true,"punch":true}, @@ -118,9 +121,9 @@ class WaybillConfirmReturnTest { } @Test - @DisplayName("回单要求确认且节点都打完:置为已完成,并照常生成应收应付、尝试完成配载单") + @DisplayName("待回单确认且节点都打完:置为已完成,并照常生成应收应付、尝试完成配载单") void confirmsReturnAndCompletesWaybill() { - stubWaybill(RETURN_NEED_CONFIRM_JSON); + stubWaybill(RETURN_NEED_CONFIRM_JSON, PENDING_RETURN_CONFIRM); stubPunches("load", "sign"); boolean result = service.confirmReturnReceipt(WAYBILL_ID); @@ -135,16 +138,57 @@ class WaybillConfirmReturnTest { @Test @DisplayName("在途节点没打卡也放行:它不是必经一步") void transitNodeDoesNotBlockConfirmation() { - stubWaybill(WITH_TRANSIT_JSON); + stubWaybill(WITH_TRANSIT_JSON, PENDING_RETURN_CONFIRM); stubPunches("load", "sign"); assertEquals(true, service.confirmReturnReceipt(WAYBILL_ID)); } + @Test + @DisplayName("进行中的运单不能确认回单:状态校验先于打卡校验") + void runningWaybillCannotConfirmReturn() { + stubWaybill(RETURN_NEED_CONFIRM_JSON, "running"); + // 故意一条打卡都不补:若状态校验排在打卡校验之后,这里报的会是缺哪个节点 + stubPunches(); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.confirmReturnReceipt(WAYBILL_ID)); + + assertEquals("当前状态不允许确认回单", exception.getMessage()); + verify(waybillMapper, never()).updateById(any(Waybill.class)); + verify(receivablePayableDetailService, never()).generateForCompletedWaybills(any()); + } + + @Test + @DisplayName("已完成的运单不能确认回单") + void completedWaybillCannotConfirmReturn() { + stubWaybill(RETURN_NEED_CONFIRM_JSON, "completed"); + stubPunches("load", "sign"); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.confirmReturnReceipt(WAYBILL_ID)); + + assertEquals("当前状态不允许确认回单", exception.getMessage()); + verify(waybillMapper, never()).updateById(any(Waybill.class)); + } + + @Test + @DisplayName("待执行的运单不能确认回单") + void pendingWaybillCannotConfirmReturn() { + stubWaybill(RETURN_NEED_CONFIRM_JSON, "pending"); + stubPunches("load", "sign"); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.confirmReturnReceipt(WAYBILL_ID)); + + assertEquals("当前状态不允许确认回单", exception.getMessage()); + verify(waybillMapper, never()).updateById(any(Waybill.class)); + } + @Test @DisplayName("还有节点没打卡:拒绝并点名缺哪个节点,不改状态") void unfinishedPunchNodeBlocksConfirmation() { - stubWaybill(RETURN_NEED_CONFIRM_JSON); + stubWaybill(RETURN_NEED_CONFIRM_JSON, PENDING_RETURN_CONFIRM); stubPunches("load"); ServiceException exception = assertThrows(ServiceException.class, @@ -157,7 +201,7 @@ class WaybillConfirmReturnTest { @Test @DisplayName("有未了结异常:拒绝,不改状态") void unresolvedExceptionBlocksConfirmation() { - stubWaybill(RETURN_NEED_CONFIRM_JSON); + stubWaybill(RETURN_NEED_CONFIRM_JSON, PENDING_RETURN_CONFIRM); stubPunches("load", "sign"); when(exceptionDisposalMapper.selectCount(any())).thenReturn(1L); @@ -169,31 +213,59 @@ class WaybillConfirmReturnTest { } @Test - @DisplayName("回单不要求业务员确认:这个入口不适用,说清而不是静默完成") - void returnWithoutConfirmationIsRejected() { - stubWaybill(RETURN_NO_CONFIRM_JSON); + @DisplayName("回单配置已改成无需确认、运单停在待回单确认:仍能确认回单") + void confirmStillWorksAfterConfigSwitchedToNoConfirm() { + stubWaybill(RETURN_NO_CONFIRM_JSON, PENDING_RETURN_CONFIRM); + stubPunches("load", "sign"); + + assertEquals(true, service.confirmReturnReceipt(WAYBILL_ID)); + + verify(waybillMapper).updateById(org.mockito.ArgumentMatchers.argThat(waybill -> + "completed".equals(waybill.getBusinessStatus()))); + verify(receivablePayableDetailService).generateForCompletedWaybills(List.of(WAYBILL_ID)); + } + + @Test + @DisplayName("回单配置无需确认且运单还在进行中:这个入口不适用,报状态不符") + void noConfirmConfigRunningWaybillCannotConfirmReturn() { + stubWaybill(RETURN_NO_CONFIRM_JSON, "running"); stubPunches("load", "sign"); ServiceException exception = assertThrows(ServiceException.class, () -> service.confirmReturnReceipt(WAYBILL_ID)); - assertEquals("该运单的回单不需要业务员确认", exception.getMessage()); + assertEquals("当前状态不允许确认回单", exception.getMessage()); verify(waybillMapper, never()).updateById(any(Waybill.class)); } + @Test + @DisplayName("待回单确认可以取消:置为已取消,不生成应收应付") + void cancelPendingReturnConfirmWaybill() { + // 取消接口不允许已配载的运单 + stubWaybill(RETURN_NEED_CONFIRM_JSON, PENDING_RETURN_CONFIRM).setLoadingNo(null); + + assertEquals(true, service.cancel(WAYBILL_ID)); + + verify(waybillMapper).updateById(org.mockito.ArgumentMatchers.argThat(waybill -> + "cancelled".equals(waybill.getBusinessStatus()) && WAYBILL_ID.equals(waybill.getId()))); + verify(receivablePayableDetailService, never()).generateForCompletedWaybills(any()); + verify(loadingManageService, never()).completeIfAllWaybillsCompleted(any()); + } + // ------------------------------------------------------------ 夹具 - private void stubWaybill(String processJson) { + private Waybill stubWaybill(String processJson, String businessStatus) { Waybill waybill = new Waybill(); waybill.setId(WAYBILL_ID); waybill.setWaybillNo("YD202610180002"); waybill.setDeptId(Long.valueOf(DEPT_ID)); waybill.setProjectId(7001L); waybill.setProcessJson(processJson); - waybill.setBusinessStatus("running"); + waybill.setBusinessStatus(businessStatus); waybill.setLoadingNo("PZ-20261018-001"); waybill.setIsDeleted(0); when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill); + return waybill; } private void stubPunches(String... nodeCodes) { diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordListTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordListTest.java index c61dd1ec..6f055f13 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordListTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordListTest.java @@ -14,6 +14,7 @@ import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.entity.WaybillEnroutePunch; import org.springblade.transport.pojo.entity.WaybillNodePunch; import org.springblade.transport.pojo.vo.WaybillPunchRecordItemVO; +import org.springblade.transport.support.WaybillProcessSupport; import java.lang.reflect.Field; import java.text.ParseException; @@ -126,6 +127,19 @@ class WaybillPunchRecordListTest { assertEquals("", accept.getPunchTime(), "没有接单动作就没有完成时刻,不得拿别的字段编一个"); } + @Test + @DisplayName("待回单确认的无需确认接单运单:接单节点已打卡,但没有完成时刻") + void acceptNodePunchedWhenPendingReturnConfirm() { + stubWaybill(waybill(NO_CONFIRM_ACCEPT_JSON, + WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, null)); + + WaybillPunchRecordItemVO accept = acceptRecord(); + + assertTrue(accept.getPunched()); + assertEquals("已打卡", accept.getStatusName()); + assertEquals("", accept.getPunchTime()); + } + @Test @DisplayName("无需确认接单但仍是草稿:接单节点未打卡") void acceptNodeUnpunchedWhenDraft() { diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java index 1778a850..fc7b0f38 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillPunchRecordSaveTest.java @@ -68,6 +68,7 @@ class WaybillPunchRecordSaveTest { assistant.setCurrentNamespace("test"); TableInfoHelper.initTableInfo(assistant, WaybillNodePunch.class); TableInfoHelper.initTableInfo(assistant, WaybillEnroutePunch.class); + TableInfoHelper.initTableInfo(assistant, Waybill.class); } @BeforeEach @@ -79,6 +80,7 @@ class WaybillPunchRecordSaveTest { inject("baseMapper", waybillMapper); inject("waybillNodePunchMapper", nodePunchMapper); inject("waybillEnroutePunchMapper", enroutePunchMapper); + when(waybillMapper.updateById(any(Waybill.class))).thenReturn(1); } // ------------------------------------------------------------ 过程节点打卡 @@ -202,6 +204,61 @@ class WaybillPunchRecordSaveTest { assertEquals(Set.of(7001L), new HashSet<>(captor.getValue())); } + @Test + @DisplayName("待回单确认:取消打卡后退回进行中——进入该状态的前提已被撤掉") + void revertToRunningWhenCancelPunchOnPendingReturnConfirm() { + Waybill waybill = waybill(); + waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); + when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill); + when(nodePunchMapper.selectList(any())).thenReturn(List.of(nodePunch(7001L, "load", "装货"))); + + WaybillPunchRecordSaveRequest request = request("node", "load", "装货"); + request.setPunched(false); + + assertTrue(service.savePunchRecord(request)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Waybill.class); + verify(waybillMapper).updateById(captor.capture()); + assertEquals(WaybillProcessSupport.STATUS_RUNNING, captor.getValue().getBusinessStatus()); + assertEquals(WAYBILL_ID, captor.getValue().getId()); + } + + @Test + @DisplayName("进行中:取消打卡不动运单状态") + void keepStatusWhenCancelPunchOnRunningWaybill() { + Waybill waybill = waybill(); + waybill.setBusinessStatus(WaybillProcessSupport.STATUS_RUNNING); + when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill); + when(nodePunchMapper.selectList(any())).thenReturn(List.of(nodePunch(7001L, "load", "装货"))); + + WaybillPunchRecordSaveRequest request = request("node", "load", "装货"); + request.setPunched(false); + + assertTrue(service.savePunchRecord(request)); + + verify(waybillMapper, never()).updateById(any(Waybill.class)); + } + + @Test + @DisplayName("待回单确认:取消在途打卡同样退回进行中") + void revertToRunningWhenCancelEnroutePunchOnPendingReturnConfirm() { + Waybill waybill = waybill(); + waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); + when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(waybill); + WaybillEnroutePunch existed = new WaybillEnroutePunch(); + existed.setId(8001L); + when(enroutePunchMapper.selectList(any())).thenReturn(List.of(existed)); + + WaybillPunchRecordSaveRequest request = request("enroute", "transit", "在途"); + request.setPunched(false); + + assertTrue(service.savePunchRecord(request)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Waybill.class); + verify(waybillMapper).updateById(captor.capture()); + assertEquals(WaybillProcessSupport.STATUS_RUNNING, captor.getValue().getBusinessStatus()); + } + @Test @DisplayName("已完成运单仍可补录——门禁只对待回单确认生效") void keepBackfillOnCompletedWaybill() { From 5d84bf1fffe4b8696d344d428abdc1a04f84c4b6 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:55:10 +0800 Subject: [PATCH 12/25] =?UTF-8?q?:sparkles:=20=E7=A1=AE=E8=AE=A4=E5=9B=9E?= =?UTF-8?q?=E5=8D=95=E6=94=B9=E7=9C=8B=E8=BF=90=E5=8D=95=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=B9=B6=E6=94=AF=E6=8C=81=E5=8F=96=E6=B6=88=E6=89=93=E5=8D=A1?= =?UTF-8?q?=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/WaybillServiceImpl.java | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index b934d41a..a3ca5791 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -538,9 +538,26 @@ public class WaybillServiceImpl extends BaseServiceImpl throw new ServiceException("运单待业务员确认回单,不能补录打卡"); } if (ENROUTE_PUNCH_TYPE.equalsIgnoreCase(TransportBusinessSupport.trimToEmpty(request.getType()))) { - return saveEnroutePunchRecord(waybill, request, punched); + return revertIfReturnConfirmPreconditionLost(waybill, punched, + saveEnroutePunchRecord(waybill, request, punched)); } - return saveNodePunchRecord(waybill, request, punched); + return revertIfReturnConfirmPreconditionLost(waybill, punched, + saveNodePunchRecord(waybill, request, punched)); + } + + /** + * 取消打卡撤掉了「司机已无待打卡节点」这个前提,停在待回单确认的运单退回进行中。 + *

+ * 只认取消打卡后的待回单确认,其它状态一律不动。 + */ + private boolean revertIfReturnConfirmPreconditionLost(Waybill waybill, boolean punched, boolean saved) { + if (!saved || punched + || !WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM + .equalsIgnoreCase(waybill.getBusinessStatus())) { + return saved; + } + waybill.setBusinessStatus(WaybillProcessSupport.STATUS_RUNNING); + return updateById(waybill); } /** @@ -968,13 +985,16 @@ public class WaybillServiceImpl extends BaseServiceImpl /** * 运单是否已越过接单阶段。 *

- * 正向列举进行中/已完成/已取消,不能用「不是终态」取反:终态里还包含草稿、待调度、 + * 正向列举进行中/已完成/已取消/待回单确认,不能用「不是终态」取反:终态里还包含草稿、待调度、 * 调度中这些并未越过接单的阶段,取反会把它们判成已接单。 + *

+ * 待回单确认下司机已打完末位卡,无需接单配置时接单节点不该再显示未打卡。 */ private static boolean passedAcceptStage(String businessStatus) { return "running".equals(businessStatus) || "completed".equals(businessStatus) - || "cancelled".equals(businessStatus); + || "cancelled".equals(businessStatus) + || WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM.equals(businessStatus); } private String formatPunchTime(Date time) { @@ -1525,14 +1545,14 @@ public class WaybillServiceImpl extends BaseServiceImpl @Transactional(rollbackFor = Exception.class) public boolean confirmReturnReceipt(Long id) { Waybill waybill = loadEditable(id, true); + // 判据只看运单当前状态,不看回单配置当前值:配置改回「无需确认」后, + // 已停在待回单确认的运单仍要能确认回单 + if (!WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM.equalsIgnoreCase(waybill.getBusinessStatus())) { + throw new ServiceException("当前状态不允许确认回单"); + } String processJson = LiveProcessConfigSupport.resolve( LiveProcessConfigSupport.byProject(processConfigService, waybill.getProjectId()), waybill.getProcessJson()); - if (WaybillProcessSupport.returnNeedsNoConfirmation(processJson)) { - // 不要求确认时司机打完末位卡就已经完成了,运单不会停在进行中等确认;配置对不上要说清, - // 否则用户会以为是确认没生效 - throw new ServiceException("该运单的回单不需要业务员确认"); - } assertPunchNodesFinished(waybill.getId(), processJson); assertNoUnresolvedException(waybill.getId()); return doComplete(waybill); From 47fdff55a22b8a7e902d143bf8e37a9286fe2436 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:57:58 +0800 Subject: [PATCH 13/25] =?UTF-8?q?:memo:=20completeWithoutDeptCheck=20?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=94=B9=E4=B8=BA=E6=8F=8F=E8=BF=B0=E7=8E=B0?= =?UTF-8?q?=E7=8A=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/springblade/transport/service/IWaybillService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java index d43242fd..e87bc79a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java @@ -108,7 +108,7 @@ public interface IWaybillService extends BaseService { boolean complete(Long id); /** - * 司机端完成运单:跳过管理端部门校验,其余逻辑与 {@link #complete(Long)} 一致 + * 末位打卡自动完成:跳过管理端部门校验,其余逻辑与 {@link #complete(Long)} 一致 * (改状态 + 生成应收应付明细 + 尝试完成配载单)。 */ boolean completeWithoutDeptCheck(Long id); From 654a7776b720bb7da009223f210bbd6673ff2355 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:05:42 +0800 Subject: [PATCH 14/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E5=BE=85?= =?UTF-8?q?=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=EF=BC=9A=E8=A1=A5=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=88=86=E6=A1=A3=E3=80=81=E6=95=B0=E5=AD=97=E6=80=81?= =?UTF-8?q?=E3=80=81=E4=B8=A4=E5=A5=97=E5=90=8D=E7=A7=B0=E8=A1=A8=E4=B8=8E?= =?UTF-8?q?=E5=B7=B2=E5=88=B0=E8=BE=BE=E5=8F=A3=E5=BE=84=E7=9A=84=E7=BA=A2?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/DriverWaybillTabStatusTest.java | 176 ++++++++++++++++++ .../service/impl/MasterOrderProgressTest.java | 150 +++++++++++++++ .../service/impl/TripAggregationTest.java | 17 ++ .../impl/WaybillBusinessStatusNameTest.java | 65 +++++++ 4 files changed, 408 insertions(+) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverWaybillTabStatusTest.java create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/MasterOrderProgressTest.java create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillBusinessStatusNameTest.java 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 queriedStatuses() { + ArgumentCaptor> captor = ArgumentCaptor.forClass(Wrapper.class); + verify(waybillService).list(captor.capture()); + return ((LambdaQueryWrapper) captor.getValue()).getParamNameValuePairs().values(); + } + + @SuppressWarnings("unchecked") + private static Wrapper anyWrapper() { + return org.mockito.ArgumentMatchers.>any(); + } + + private Waybill pendingReturnConfirmWaybill() { + Waybill waybill = new Waybill(); + waybill.setId(WAYBILL_ID); + waybill.setWaybillNo("YD202610080002"); + waybill.setVehicleNo(PLATE); + waybill.setDriverPhone(PHONE); + waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); + waybill.setIsDeleted(0); + return waybill; + } + + private Driver driver() { + Driver driver = new Driver(); + 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/MasterOrderProgressTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/MasterOrderProgressTest.java new file mode 100644 index 00000000..f7d77747 --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/MasterOrderProgressTest.java @@ -0,0 +1,150 @@ +package org.springblade.transport.service.impl; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +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.springblade.transport.mapper.MasterOrderMapper; +import org.springblade.transport.pojo.entity.MasterOrder; +import org.springblade.transport.pojo.entity.TransportPlan; +import org.springblade.transport.pojo.entity.Waybill; +import org.springblade.transport.pojo.vo.MasterOrderVO; +import org.springblade.transport.service.IContractManageService; +import org.springblade.transport.service.IProjectApplyService; +import org.springblade.transport.service.ITransportPlanService; +import org.springblade.transport.service.IWaybillService; +import org.springblade.transport.support.WaybillProcessSupport; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * 多联总单分段进度的「已到达数量」口径。 + *

+ * 到货但回单待确认的运单算到达:货物确实到了,回单只是单据。 + * 不算会让下一段被「调度总量不能大于上一段已到达数量」挡住,整条线路卡在第二段。 + */ +class MasterOrderProgressTest { + + private static final Long MASTER_ID = 6101L; + private static final String MASTER_NO = "ZD20261008001"; + private static final String ROUTE_JSON = """ + [{"segmentNo":"1","transportType":"公路运输","departureName":"南宁","arrivalName":"北海"}, + {"segmentNo":"2","transportType":"铁路运输","departureName":"北海","arrivalName":"昆明"}] + """; + + private MasterOrderMapper masterOrderMapper; + private IWaybillService waybillService; + private ITransportPlanService transportPlanService; + private MasterOrderServiceImpl 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, TransportPlan.class); + TableInfoHelper.initTableInfo(assistant, MasterOrder.class); + } + + @BeforeEach + void setUp() throws Exception { + masterOrderMapper = mock(MasterOrderMapper.class); + waybillService = mock(IWaybillService.class); + transportPlanService = mock(ITransportPlanService.class); + service = new MasterOrderServiceImpl( + waybillService, + transportPlanService, + mock(IProjectApplyService.class), + mock(IContractManageService.class)); + inject("baseMapper", masterOrderMapper); + + when(transportPlanService.list(anyWrapper())).thenReturn(List.of()); + } + + @Test + @DisplayName("到货但回单待确认的运单计入「已到达数量」") + void arrivedQuantityCountsPendingReturnConfirm() { + when(masterOrderMapper.selectById(MASTER_ID)).thenReturn(masterOrder()); + when(waybillService.list(anyWrapper())).thenReturn(List.of( + child(1L, "1", WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, "8"))); + + MasterOrderVO vo = service.detail(MASTER_ID); + + assertEquals(0, decimalOf(vo.getRouteProgress().get(0).get("arrivedQuantity")) + .compareTo(new BigDecimal("8")), "回单待确认不等于没到货"); + assertEquals(0, decimalOf(vo.getRouteProgress().get(1).get("arrivedQuantity")).signum(), + "第二段还没有到货的运单"); + } + + @Test + @DisplayName("已到达口径:已完成 + 待回单确认,进行中不算") + void arrivedQuantityIgnoresRunning() { + when(masterOrderMapper.selectById(MASTER_ID)).thenReturn(masterOrder()); + when(waybillService.list(anyWrapper())).thenReturn(List.of( + child(1L, "1", "running", "8"), + child(2L, "1", "completed", "5"), + child(3L, "1", WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, "3"))); + + MasterOrderVO vo = service.detail(MASTER_ID); + + assertEquals(0, decimalOf(vo.getRouteProgress().get(0).get("arrivedQuantity")) + .compareTo(new BigDecimal("8")), "5 + 3:进行中的 8 吨还没到,不能算到达"); + } + + // ------------------------------------------------------------ 夹具 + + private MasterOrder masterOrder() { + MasterOrder masterOrder = new MasterOrder(); + masterOrder.setId(MASTER_ID); + masterOrder.setMasterNo(MASTER_NO); + masterOrder.setRouteJson(ROUTE_JSON); + return masterOrder; + } + + private Waybill child(Long id, String segmentNo, String businessStatus, String quantity) { + Waybill waybill = new Waybill(); + waybill.setId(id); + waybill.setMasterNo(MASTER_NO); + waybill.setRelationNo(segmentNo); + waybill.setBusinessStatus(businessStatus); + waybill.setGoodsJson("[{\"cargoName\":\"钢材\",\"quantity\":" + quantity + "}]"); + return waybill; + } + + private BigDecimal decimalOf(Object value) { + return value == null ? BigDecimal.ZERO : (BigDecimal) value; + } + + @SuppressWarnings("unchecked") + private static Wrapper anyWrapper() { + return org.mockito.ArgumentMatchers.>any(); + } + + private void inject(String field, Object value) throws Exception { + Class type = service.getClass(); + while (type != null) { + try { + Field declared = type.getDeclaredField(field); + declared.setAccessible(true); + declared.set(service, value); + return; + } catch (NoSuchFieldException ignored) { + type = type.getSuperclass(); + } + } + throw new IllegalStateException("未找到字段:" + field); + } + +} diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripAggregationTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripAggregationTest.java index a5375241..1bee8fd5 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripAggregationTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripAggregationTest.java @@ -193,6 +193,23 @@ class TripAggregationTest { assertTrue(TripAggregation.CHILD_STATUSES.containsAll(TripAggregation.TERMINAL_STATUSES)); } + @Test + @DisplayName("待回单确认的子单仍进运次,且不会把运次判成已完成") + void pendingReturnConfirmChildKeepsTripRunning() { + String pendingReturnConfirm = org.springblade.transport.support.WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM; + + assertTrue(TripAggregation.CHILD_STATUSES.contains(pendingReturnConfirm), + "漏掉这个状态,子单会从运次读服务消失,配载单被当成全部完成"); + + // 与一张已完成的子单拼载:还有子单没走完,运次只能是运输中 + List children = List.of( + waybill("completed", null, null), + waybill(pendingReturnConfirm, null, null)); + String status = TripAggregation.statusOf(children, false, false); + assertEquals("running", status); + assertEquals(1, TripAggregation.appStatus(status), "运次不进已完成档"); + } + private Waybill waybill(String businessStatus, String acceptStatus, String processJson) { Waybill waybill = new Waybill(); waybill.setBusinessStatus(businessStatus); diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillBusinessStatusNameTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillBusinessStatusNameTest.java new file mode 100644 index 00000000..392c0550 --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillBusinessStatusNameTest.java @@ -0,0 +1,65 @@ +package org.springblade.transport.service.impl; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springblade.transport.mapper.MasterOrderMapper; +import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper; +import org.springblade.transport.mapper.ReceivablePayableChangeRecordMapper; +import org.springblade.transport.service.IContractManageService; +import org.springblade.transport.service.ICommonAddressService; +import org.springblade.transport.service.IFormalSettlementService; +import org.springblade.transport.service.IPreSettlementService; +import org.springblade.transport.service.IWaybillService; +import org.springblade.transport.support.WaybillProcessSupport; +import org.springblade.transport.wrapper.WaybillWrapper; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +/** + * 运单业务状态的中文名,两套表并存(历史结构,不合并):运单管理一套、应收应付一套。 + *

+ * 两套都要认待回单确认:漏一套就在页面上露出原始英文码。 + */ +class WaybillBusinessStatusNameTest { + + private static final String PENDING_RETURN_CONFIRM = WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM; + + @Test + @DisplayName("运单管理名称表给出「待回单确认」——导出与详情共用这张表") + void waybillWrapperNamesPendingReturnConfirm() { + assertEquals("待回单确认", WaybillWrapper.businessStatusName(PENDING_RETURN_CONFIRM)); + // 相邻档位不受影响 + assertEquals("进行中", WaybillWrapper.businessStatusName("running")); + assertEquals("已完成", WaybillWrapper.businessStatusName("completed")); + } + + @Test + @DisplayName("应收应付的第二套名称表同步给出「待回单确认」") + void receivablePayableNamesPendingReturnConfirm() throws Exception { + assertEquals("待回单确认", receivableStatusName(PENDING_RETURN_CONFIRM)); + } + + /** + * 应收应付那张表只被 waybillMap 用,而 waybillMap 当前没有调用方, + * 没有公共接缝;它是纯函数,按既有做法直接取私有方法(同 LoadingManageChildStatusWriteTest)。 + */ + private String receivableStatusName(String status) throws Exception { + ReceivablePayableDetailServiceImpl service = new ReceivablePayableDetailServiceImpl( + mock(ReceivablePayableCargoFeeMapper.class), + mock(ReceivablePayableChangeRecordMapper.class), + mock(MasterOrderMapper.class), + mock(IWaybillService.class), + mock(IContractManageService.class), + mock(ICommonAddressService.class), + mock(IPreSettlementService.class), + mock(IFormalSettlementService.class)); + Method method = ReceivablePayableDetailServiceImpl.class + .getDeclaredMethod("waybillStatusName", String.class); + method.setAccessible(true); + return (String) method.invoke(service, status); + } + +} From 53f208cf05c7fbdacd80b4206fbfc9bd76c150ad Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:08:22 +0800 Subject: [PATCH 15/25] =?UTF-8?q?:sparkles:=20=E5=BE=85=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=EF=BC=9A=E5=8F=B8=E6=9C=BA=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=88=86=E6=A1=A3=E3=80=81=E6=95=B0=E5=AD=97=E6=80=81=E3=80=81?= =?UTF-8?q?=E4=B8=A4=E5=A5=97=E5=90=8D=E7=A7=B0=E8=A1=A8=E3=80=81=E8=BF=90?= =?UTF-8?q?=E6=AC=A1=E5=AD=90=E5=8D=95=E4=B8=8E=E6=80=BB=E5=8D=95=E5=B7=B2?= =?UTF-8?q?=E5=88=B0=E8=BE=BE=E5=8F=A3=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/DriverWaybillServiceImpl.java | 23 +++++++++++-------- .../service/impl/MasterOrderServiceImpl.java | 14 +++++++++-- .../ReceivablePayableDetailServiceImpl.java | 2 ++ .../service/impl/TripAggregation.java | 4 +++- .../transport/wrapper/WaybillWrapper.java | 1 + 5 files changed, 32 insertions(+), 12 deletions(-) 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 16466128..d2369c3f 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 @@ -118,13 +118,18 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { private static final List PENDING_STATUSES = Arrays.asList( STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING ); - /** Tab「全部」统计口径:待接 + 运输中 + 已完成(不含已取消) */ + /** Tab「全部」统计口径:待接 + 运输中 + 待回单确认 + 已完成(不含已取消) */ private static final List TAB_ALL_STATUSES = Arrays.asList( - STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING, STATUS_RUNNING, STATUS_COMPLETED + STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING, STATUS_RUNNING, + WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, STATUS_COMPLETED ); - /** 允许完成运单的业务状态 */ - private static final List COMPLETABLE_STATUSES = Arrays.asList( - STATUS_RUNNING, STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING + /** + * 进行中口径:司机手上还没走完的单。 + * 待回单确认也算进行中——司机侧已打完末位卡,等业务员确认回单才变已完成。 + */ + private static final List ACTIVE_STATUSES = Arrays.asList( + STATUS_RUNNING, STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING, + WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM ); private static final int DEFAULT_PREVIEW_SIZE = 2; @@ -838,7 +843,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } switch (status) { case 0 -> wrapper.in(Waybill::getBusinessStatus, PENDING_STATUSES); - case 1 -> wrapper.in(Waybill::getBusinessStatus, COMPLETABLE_STATUSES); + case 1 -> wrapper.in(Waybill::getBusinessStatus, ACTIVE_STATUSES); case 2 -> wrapper.eq(Waybill::getBusinessStatus, STATUS_COMPLETED); case 3 -> wrapper.eq(Waybill::getBusinessStatus, STATUS_CANCELLED); default -> wrapper.in(Waybill::getBusinessStatus, TAB_ALL_STATUSES); @@ -1514,12 +1519,12 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } /** - * 自动完成的前置:业务状态允许完成,且没有未了结异常。 + * 自动完成的前置:业务状态在进行中口径内,且没有未了结异常。 *

* 不满足返回 false,由调用方静默跳过,打卡不受影响。 */ private boolean canAutoCompleteWaybill(Waybill waybill) { - return COMPLETABLE_STATUSES.contains(waybill.getBusinessStatus()) + return ACTIVE_STATUSES.contains(waybill.getBusinessStatus()) && unresolvedExceptionNodeCodeList(waybill.getId()).isEmpty(); } @@ -1986,7 +1991,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { } return switch (businessStatus) { case STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING -> 0; - case STATUS_RUNNING -> 1; + case STATUS_RUNNING, WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM -> 1; case STATUS_COMPLETED -> 2; case STATUS_CANCELLED -> 3; default -> null; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java index 51491538..3c3dc747 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java @@ -362,7 +362,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl dispatchedGoods = dispatchedGoods(routeWaybills, routePlans); route.put("dispatchedQuantity", dispatchedGoods.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add)); route.put("dispatchedGoods", dispatchedGoods); - route.put("arrivedQuantity", routeWaybills.stream().filter(item -> "completed".equals(item.getBusinessStatus())).flatMap(item -> waybillGoods(item).stream()).map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add)); + route.put("arrivedQuantity", routeWaybills.stream().filter(item -> arrived(item.getBusinessStatus())).flatMap(item -> waybillGoods(item).stream()).map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add)); route.put("waybills", routeWaybills); route.put("transportPlans", routePlans); } @@ -541,12 +541,22 @@ public class MasterOrderServiceImpl extends BaseServiceImpl route) { return waybillsByMasterNo(masterNo).stream() - .filter(item -> "completed".equals(item.getBusinessStatus())) + .filter(item -> arrived(item.getBusinessStatus())) .filter(item -> belongsToRoute(item, route)) .flatMap(item -> waybillGoods(item).stream()) .map(item -> decimal(item, "quantity")) .reduce(BigDecimal.ZERO, BigDecimal::add); } + + /** + * 已到达口径:待回单确认也算到达——货物确实到了,回单只是单据。 + * 不计入会按「调度总量不能大于上一段已到达数量」把多联总单的下一段挡在门外。 + */ + static boolean arrived(String businessStatus) { + return "completed".equals(businessStatus) + || WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM.equals(businessStatus); + } + private Map availableGoods(MasterOrder masterOrder) { Map available = new LinkedHashMap<>(); for (Map goods : parseArray(masterOrder.getGoodsJson())) available.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java index 8ce2f5f7..7a757025 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java @@ -61,6 +61,7 @@ import org.springblade.transport.service.IFormalSettlementService; import org.springblade.transport.service.IPreSettlementService; import org.springblade.transport.service.IReceivablePayableDetailService; import org.springblade.transport.service.IWaybillService; +import org.springblade.transport.support.WaybillProcessSupport; import org.springblade.transport.wrapper.ReceivablePayableDetailWrapper; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -3295,6 +3296,7 @@ public class ReceivablePayableDetailServiceImpl case "completed" -> "已完成"; case "processing", "running", "in_progress", "inProgress" -> "进行中"; case "pending", "created" -> "待执行"; + case WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM -> "待回单确认"; case "cancelled", "canceled" -> "已取消"; default -> status; }; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripAggregation.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripAggregation.java index d55d700a..6eb4689a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripAggregation.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripAggregation.java @@ -45,6 +45,7 @@ final class TripAggregation { static final String RUNNING = "running"; static final String COMPLETED = "completed"; static final String CANCELLED = "cancelled"; + static final String PENDING_RETURN_CONFIRM = WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM; /** 待接单档:与运单侧 Tab「待接单」取同一组状态 */ static final List PENDING_STATUSES = List.of(PENDING, WAITING_DISPATCH, DISPATCHING); @@ -53,9 +54,10 @@ final class TripAggregation { /** * 进运次的子单状态:草稿尚未派单、不成运次;已取消要进, * 否则「子单全已取消」会被算成运输中而不是已取消。 + * 待回单确认要进:子单漏在运次外,剩下那几张会被判成整趟车已走完。 */ static final List CHILD_STATUSES = List.of( - PENDING, WAITING_DISPATCH, DISPATCHING, RUNNING, COMPLETED, CANCELLED + PENDING, WAITING_DISPATCH, DISPATCHING, RUNNING, PENDING_RETURN_CONFIRM, COMPLETED, CANCELLED ); /** 待重新派单的口径:子单被拒且这趟车还没结束 */ diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/WaybillWrapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/WaybillWrapper.java index 1dab5a5b..d0257f86 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/WaybillWrapper.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/WaybillWrapper.java @@ -122,6 +122,7 @@ public class WaybillWrapper extends BaseEntityWrapper { case "waiting_dispatch" -> "待调度"; case "dispatching" -> "调度中"; case "running", "processing", "in_progress", "inProgress" -> "进行中"; + case WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM -> "待回单确认"; case "completed" -> "已完成"; case "cancelled" -> "已取消"; case "closed" -> "已关闭"; From 67480098f35f5a0eb7f38c6aa0d8a2a4e4607420 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:11:18 +0800 Subject: [PATCH 16/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E5=BE=85?= =?UTF-8?q?=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=EF=BC=9A=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=96=AD=E8=A8=80=E6=94=B9=E4=B8=BA=20Answer?= =?UTF-8?q?=20=E6=8D=95=E8=8E=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DriverWaybillTabStatusTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 802564b0..c556a398 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 @@ -10,7 +10,6 @@ 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; @@ -30,6 +29,7 @@ import org.springblade.transport.service.IWaybillService; import org.springblade.transport.support.CertificationGate; import org.springblade.transport.support.WaybillProcessSupport; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.function.Supplier; @@ -39,7 +39,6 @@ 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; /** @@ -59,6 +58,7 @@ class DriverWaybillTabStatusTest { private IDriverService driverService; private WaybillNodePunchMapper nodePunchMapper; private ExceptionDisposalMapper exceptionDisposalMapper; + private final List> queried = new ArrayList<>(); private DriverWaybillServiceImpl service; @BeforeAll @@ -92,7 +92,10 @@ class DriverWaybillTabStatusTest { when(driverService.getOne(any())).thenReturn(driver()); when(waybillService.syncDriverAcceptState(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(waybillService.list(anyWrapper())).thenReturn(List.of(pendingReturnConfirmWaybill())); + when(waybillService.list(anyWrapper())).thenAnswer(invocation -> { + queried.add(invocation.getArgument(0)); + return List.of(pendingReturnConfirmWaybill()); + }); } @Test @@ -143,9 +146,8 @@ class DriverWaybillTabStatusTest { @SuppressWarnings("unchecked") private Collection queriedStatuses() { - ArgumentCaptor> captor = ArgumentCaptor.forClass(Wrapper.class); - verify(waybillService).list(captor.capture()); - return ((LambdaQueryWrapper) captor.getValue()).getParamNameValuePairs().values(); + assertEquals(1, queried.size(), "一次列表查询"); + return ((LambdaQueryWrapper) queried.get(0)).getParamNameValuePairs().values(); } @SuppressWarnings("unchecked") From 66a4a4945211f4a9fc8a54c3eff9d45f6e8b62f2 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:14:19 +0800 Subject: [PATCH 17/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E5=BE=85?= =?UTF-8?q?=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=EF=BC=9A=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E4=B8=8E=E5=8D=A1=E7=89=87=E4=B8=8B=E5=8F=91=E5=8E=9F=E5=A7=8B?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E7=8A=B6=E6=80=81=E7=9A=84=E7=BA=A2=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/DriverWaybillTabStatusTest.java | 5 + .../impl/ManageWaybillBusinessStatusTest.java | 116 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/ManageWaybillBusinessStatusTest.java 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 c556a398..5de8ac1d 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 @@ -105,6 +105,9 @@ class DriverWaybillTabStatusTest { assertEquals(1, page.getRecords().size(), "进行中档必须包含待回单确认,否则司机看不到这张单"); assertEquals(Integer.valueOf(1), page.getRecords().get(0).getStatus(), "数字态沿用 1(进行中),不新增档位"); + assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, + page.getRecords().get(0).getBusinessStatus(), + "原始业务状态随卡片下发:数字态恒为 1,前端靠它区分运输中与待回单确认"); } @Test @@ -122,6 +125,8 @@ class DriverWaybillTabStatusTest { assertEquals(1, page.getRecords().size(), "打完末位卡的单不能从全部 tab 消失"); assertEquals(Integer.valueOf(1), page.getRecords().get(0).getStatus()); + assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, + page.getRecords().get(0).getBusinessStatus()); assertTrue(queriedStatuses().contains(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM)); } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/ManageWaybillBusinessStatusTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/ManageWaybillBusinessStatusTest.java new file mode 100644 index 00000000..e7f39d3d --- /dev/null +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/ManageWaybillBusinessStatusTest.java @@ -0,0 +1,116 @@ +package org.springblade.transport.service.impl; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +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.springblade.transport.pojo.entity.ExceptionDisposal; +import org.springblade.transport.pojo.entity.Waybill; +import org.springblade.transport.pojo.vo.AdminWaybillCardVO; +import org.springblade.transport.pojo.vo.AdminWaybillDetailVO; +import org.springblade.transport.service.IDriverService; +import org.springblade.transport.service.IDriverWaybillService; +import org.springblade.transport.service.IExceptionDisposalService; +import org.springblade.transport.service.ILoadingManageService; +import org.springblade.transport.service.IRiskDisposalService; +import org.springblade.transport.service.ITripQueryService; +import org.springblade.transport.service.IWaybillService; +import org.springblade.transport.support.WaybillProcessSupport; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * 调度端运单详情的原始业务状态。 + *

+ * 数字态只有 0/1/2/3:待回单确认落进行中(1),与运输中同档。 + * 调度端要区分这两者只能看原始状态码,所以详情与卡片都得带上它。 + */ +class ManageWaybillBusinessStatusTest { + + private static final Long WAYBILL_ID = 6201L; + private static final String PENDING_RETURN_CONFIRM = WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM; + + private ManageWaybillServiceImpl service; + private IWaybillService waybillService; + + @BeforeAll + static void initTableInfo() { + MybatisConfiguration configuration = new MybatisConfiguration(); + MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, ""); + assistant.setCurrentNamespace("test"); + TableInfoHelper.initTableInfo(assistant, Waybill.class); + TableInfoHelper.initTableInfo(assistant, ExceptionDisposal.class); + } + + @BeforeEach + void setUp() { + waybillService = mock(IWaybillService.class); + IExceptionDisposalService exceptionDisposalService = mock(IExceptionDisposalService.class); + service = new ManageWaybillServiceImpl( + waybillService, + mock(IDriverService.class), + exceptionDisposalService, + mock(IRiskDisposalService.class), + mock(IDriverWaybillService.class), + mock(ITripQueryService.class), + mock(ILoadingManageService.class)); + when(exceptionDisposalService.list(anyWrapper())).thenReturn(new ArrayList<>()); + } + + @Test + @DisplayName("运单详情:数字态 1,原始业务状态是待回单确认") + void detailCarriesRawBusinessStatus() { + when(waybillService.getById(WAYBILL_ID)).thenReturn(waybill()); + + AdminWaybillDetailVO detail = service.detail(String.valueOf(WAYBILL_ID)); + + assertEquals(Integer.valueOf(1), detail.getStatus(), "数字态仍落进行中,未新增档位"); + assertEquals(PENDING_RETURN_CONFIRM, detail.getBusinessStatus()); + } + + @Test + @DisplayName("待重新派单卡片:原始业务状态随卡片下发") + void reassignCardCarriesRawBusinessStatus() { + when(waybillService.page(any(), any())).thenAnswer(invocation -> { + Page rows = invocation.getArgument(0); + rows.setRecords(List.of(waybill())); + rows.setTotal(1); + return rows; + }); + + IPage page = service.pendingList(1, 10, null, true); + + assertEquals(1, page.getRecords().size()); + assertEquals(PENDING_RETURN_CONFIRM, page.getRecords().get(0).getBusinessStatus()); + } + + // ------------------------------------------------------------ 夹具 + + private Waybill waybill() { + Waybill waybill = new Waybill(); + waybill.setId(WAYBILL_ID); + waybill.setWaybillNo("YD202610080003"); + waybill.setBusinessStatus(PENDING_RETURN_CONFIRM); + waybill.setDriverAcceptStatus("rejected"); + waybill.setIsDeleted(0); + return waybill; + } + + @SuppressWarnings("unchecked") + private static Wrapper anyWrapper() { + return org.mockito.ArgumentMatchers.>any(); + } + +} From d2f7d6aed0009e94c5d1b57a393738b1a21a45aa Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:15:33 +0800 Subject: [PATCH 18/25] =?UTF-8?q?:sparkles:=20=E5=BE=85=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=EF=BC=9A=E5=8F=B8=E6=9C=BA=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E4=B8=8E=E8=B0=83=E5=BA=A6=E7=AB=AF=E8=AF=A6=E6=83=85/?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E4=B8=8B=E5=8F=91=E5=8E=9F=E5=A7=8B=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/springblade/transport/pojo/vo/AdminWaybillCardVO.java | 3 +++ .../springblade/transport/pojo/vo/AdminWaybillDetailVO.java | 3 +++ .../org/springblade/transport/pojo/vo/DriverWaybillCardVO.java | 3 +++ .../transport/service/impl/DriverWaybillServiceImpl.java | 1 + .../transport/service/impl/ManageWaybillServiceImpl.java | 2 ++ 5 files changed, 12 insertions(+) diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java index b0dca1a4..aa0dbfb5 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java @@ -65,6 +65,9 @@ public class AdminWaybillCardVO implements Serializable { @Schema(description = "状态:0待接单/1运输中/2已完成/3已取消") private Integer status; + @Schema(description = "原始业务状态:pending / running / pending_return_confirm / completed / cancelled") + private String businessStatus; + @Schema(description = "承运方") private String carrierName; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java index ff601f14..e874df68 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java @@ -48,6 +48,9 @@ public class AdminWaybillDetailVO implements Serializable { @Schema(description = "状态:0待接单/1运输中/2已完成/3已取消") private Integer status; + @Schema(description = "原始业务状态:pending / running / pending_return_confirm / completed / cancelled") + private String businessStatus; + @Schema(description = "起点名称") private String fromName; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java index 23432c6a..a4be3625 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java @@ -76,6 +76,9 @@ public class DriverWaybillCardVO implements Serializable { @Schema(description = "状态:0待接单/1运输中/2已完成/3已取消") private Integer status; + @Schema(description = "原始业务状态:pending / running / pending_return_confirm / completed / cancelled") + private String businessStatus; + @Schema(description = "创建时间") private String createTime; 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 d2369c3f..4348f65b 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 @@ -1011,6 +1011,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { WaybillCargoSupport.Quantity weighing = WaybillCargoSupport.weighing(cargos); card.setWeight(weighing == null ? "" : formatWeight(weighing.value(), weighing.unit())); card.setStatus(toAppStatus(waybill.getBusinessStatus())); + card.setBusinessStatus(waybill.getBusinessStatus()); String createTime = formatDateTime(waybill.getCreateTime()); card.setCreateTime(createTime); card.setPublishTime(createTime); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java index 4b1e5c93..0e303dcc 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java @@ -744,6 +744,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService { detail.setId(waybill.getId()); detail.setWaybillNo(waybill.getWaybillNo()); detail.setStatus(toAppStatus(waybill.getBusinessStatus())); + detail.setBusinessStatus(waybill.getBusinessStatus()); String mode = Func.toStr(waybill.getTransportType(), ""); detail.setTransportMode(mode); @@ -937,6 +938,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService { card.setPlanTime(formatLocalDate(waybill.getEstimatedStartTime())); card.setPlanTimeEnd(formatLocalDate(waybill.getEstimatedEndTime())); card.setStatus(toAppStatus(waybill.getBusinessStatus())); + card.setBusinessStatus(waybill.getBusinessStatus()); card.setCarrierName(Func.toStr(waybill.getCarrierName(), "")); card.setDriverName(Func.toStr(waybill.getDriverName(), "")); card.setVehicleNo(Func.toStr(waybill.getVehicleNo(), "")); From 86da6f32dcef31c23f4e6722e537a725b29c5d4e Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:18:34 +0800 Subject: [PATCH 19/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E5=BE=85?= =?UTF-8?q?=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=EF=BC=9A=E9=94=81=E4=BD=8F?= =?UTF-8?q?=E8=B0=83=E5=BA=A6=E7=AB=AF=E6=95=B0=E5=AD=97=E6=80=81=E8=90=BD?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/service/impl/ManageWaybillBusinessStatusTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/ManageWaybillBusinessStatusTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/ManageWaybillBusinessStatusTest.java index e7f39d3d..ad43545b 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/ManageWaybillBusinessStatusTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/ManageWaybillBusinessStatusTest.java @@ -93,6 +93,8 @@ class ManageWaybillBusinessStatusTest { IPage page = service.pendingList(1, 10, null, true); assertEquals(1, page.getRecords().size()); + assertEquals(Integer.valueOf(1), page.getRecords().get(0).getStatus(), + "数字态落进行中:null 到前端会掉进「非 0/1/2 即已取消」的展示分支"); assertEquals(PENDING_RETURN_CONFIRM, page.getRecords().get(0).getBusinessStatus()); } From 833c3e247e2e0ce5a5dbf50cb15b8df9612534e1 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:19:06 +0800 Subject: [PATCH 20/25] =?UTF-8?q?:bug:=20=E5=BE=85=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=EF=BC=9A=E8=B0=83=E5=BA=A6=E7=AB=AF=E6=95=B0?= =?UTF-8?q?=E5=AD=97=E6=80=81=E8=90=BD=E8=BF=9B=E8=A1=8C=E4=B8=AD=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E8=BF=94=E5=9B=9E=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/service/impl/ManageWaybillServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java index 0e303dcc..0f692983 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java @@ -996,7 +996,9 @@ public class ManageWaybillServiceImpl implements IManageWaybillService { } return switch (businessStatus) { case STATUS_PENDING, "waiting_dispatch", "dispatching" -> 0; - case STATUS_RUNNING -> 1; + // 与司机端 toAppStatus 同一份数字枚举(0 待接单 / 1 运输中 / 2 已完成 / 3 已取消),待回单确认属进行中: + // 两处对同一状态必须给同一个数字,给 null 前端只能落到「未知」兜底 + case STATUS_RUNNING, WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM -> 1; case STATUS_COMPLETED -> 2; case STATUS_CANCELLED -> 3; default -> null; From 6673ed38f319d92596268f0986507b94e3cfb391 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:47:33 +0800 Subject: [PATCH 21/25] =?UTF-8?q?:recycle:=20=E5=BE=85=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=EF=BC=9A=E6=B3=A8=E9=87=8A=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E9=99=88=E8=BF=B0=E4=BA=8B=E5=AE=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DriverWaybillServiceImpl.java | 10 +++++----- .../service/impl/ManageWaybillServiceImpl.java | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) 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 4348f65b..94d11e3f 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 @@ -124,8 +124,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, STATUS_COMPLETED ); /** - * 进行中口径:司机手上还没走完的单。 - * 待回单确认也算进行中——司机侧已打完末位卡,等业务员确认回单才变已完成。 + * 进行中口径:司机手上还没走完的单,含待回单确认。 + * 待回单确认指司机已打完末位卡、等业务员确认回单,此时业务状态还不是 completed。 */ private static final List ACTIVE_STATUSES = Arrays.asList( STATUS_RUNNING, STATUS_PENDING, STATUS_WAITING_DISPATCH, STATUS_DISPATCHING, @@ -525,9 +525,9 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { nodePunchMapper.insert(punch); advanceCurrentProcessNode(waybill, nodeCfg); - // 末位打卡节点是到货/卸货/签收、且运单本身允许完成时,本次打卡就是运单的去向决定点: - // 回单无需确认 → 一并完成;回单需业务员确认 → 停在待回单确认等业务员确认。 - // isLastDriverPunchNode 与 returnNeedsNoConfirmation 传的就是上面那份 processJson,未再调 resolveProcessJson 取过程配置。 + // 末位打卡节点为到货/卸货/签收、且运单允许完成时:回单无需确认则本次打卡一并完成运单, + // 回单需业务员确认则停在待回单确认。 + // isLastDriverPunchNode 与 returnNeedsNoConfirmation 传的是上面那份 processJson,未再调 resolveProcessJson 取过程配置。 String nodeKey = WaybillProcessSupport.nodeKey(nodeCfg); String nodeName = WaybillProcessSupport.nodeName(nodeCfg); String waybillBusinessStatus = waybill.getBusinessStatus(); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java index 0f692983..a0ee73f3 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java @@ -996,8 +996,8 @@ public class ManageWaybillServiceImpl implements IManageWaybillService { } return switch (businessStatus) { case STATUS_PENDING, "waiting_dispatch", "dispatching" -> 0; - // 与司机端 toAppStatus 同一份数字枚举(0 待接单 / 1 运输中 / 2 已完成 / 3 已取消),待回单确认属进行中: - // 两处对同一状态必须给同一个数字,给 null 前端只能落到「未知」兜底 + // 数字枚举与司机端 toAppStatus 同源:0 待接单 / 1 运输中 / 2 已完成 / 3 已取消; + // 待回单确认属进行中,落 1。给 null 时前端显示「未知」。 case STATUS_RUNNING, WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM -> 1; case STATUS_COMPLETED -> 2; case STATUS_CANCELLED -> 3; From 35127760df7b35da52b2be16c9509846330afd35 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:48:20 +0800 Subject: [PATCH 22/25] =?UTF-8?q?:bug:=20=E5=BE=85=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=EF=BC=9A=E4=B8=9A=E5=8A=A1=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=AF=94=E8=BE=83=E7=BB=9F=E4=B8=80=E4=B8=BA=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/service/impl/DriverWaybillServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 94d11e3f..27226429 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 @@ -540,9 +540,9 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { if (completed) { waybillBusinessStatus = STATUS_COMPLETED; } - } else if (WaybillProcessSupport.STATUS_RUNNING.equals(waybill.getBusinessStatus())) { - // 只有进行中能进入:canAutoCompleteWaybill 的状态口径含待执行/待调度等,不能替代这一条; - // 待回单确认在 isPunchEnded 里算已结束,不能从结束态重进。 + } else if (WaybillProcessSupport.STATUS_RUNNING.equalsIgnoreCase(waybill.getBusinessStatus())) { + // 当前业务状态为 running 才置为待回单确认;canAutoCompleteWaybill 的状态口径还含待执行/待调度。 + // 待回单确认在 isPunchEnded 里与已完成、已取消同属结束态。 waybill.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); waybillService.updateById(waybill); waybillBusinessStatus = WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM; From e0c8c988052cb5619c0f634913950ccbc6ef99df Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:48:48 +0800 Subject: [PATCH 23/25] =?UTF-8?q?:sparkles:=20=E5=BE=85=E5=9B=9E=E5=8D=95?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=EF=BC=9A=E8=BF=90=E6=AC=A1=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E4=B8=8B=E5=8F=91=E5=94=AF=E4=B8=80=E5=8E=9F=E5=A7=8B=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/DriverWaybillServiceImpl.java | 7 +++++ .../service/impl/DriverTripCardTest.java | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+) 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 27226429..428c4398 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 @@ -1113,6 +1113,13 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { card.setId(parseId(children.isEmpty() ? trip.getId() : children.get(0).getId())); card.setWaybillNo(children.isEmpty() ? card.getTripNo() : Func.toStr(children.get(0).getWaybillNo(), "")); card.setStatus(trip.getStatus()); + // 原始业务状态只在整趟车子单状态唯一时下发:配载车混状态时一趟车没有唯一原始状态,给 null 由前端按数字态展示 + List childBusinessStatuses = children.stream() + .map(TripVO.TripWaybillVO::getBusinessStatus) + .filter(Func::isNotBlank) + .distinct() + .collect(Collectors.toList()); + card.setBusinessStatus(childBusinessStatuses.size() == 1 ? childBusinessStatuses.get(0) : null); card.setRequireAccept(trip.getRequireAccept()); card.setAcceptStatus(trip.getAcceptStatus()); card.setTransportOrgType(trip.getTransportOrgType()); 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 7bdcbba1..41189b41 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 @@ -30,6 +30,7 @@ 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.math.BigDecimal; import java.util.Date; @@ -147,6 +148,34 @@ class DriverTripCardTest { assertEquals("桂A32523", card.getVehicleNo()); } + @Test + @DisplayName("运次卡片带唯一原始业务状态:子单都在待回单确认时下发 pending_return_confirm,数字态仍是 1") + void tripCardCarriesUniqueChildBusinessStatus() { + TripVO trip = loadingTrip(); + trip.getWaybills().forEach(child -> child.setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM)); + stubTripPage(1, trip); + + IPage page = runAsDriver(() -> service.tripPage(1, 10, null, null)); + + DriverWaybillCardVO card = page.getRecords().get(0); + assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, card.getBusinessStatus()); + assertEquals(trip.getStatus(), card.getStatus(), "运次数字态口径不变,仍取运次状态"); + } + + @Test + @DisplayName("运次卡片原始业务状态:配载车混状态时给 null,由前端回退数字态") + void tripCardWithoutUniqueChildBusinessStatusGivesNull() { + TripVO trip = loadingTrip(); + trip.getWaybills().get(0).setBusinessStatus(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM); + stubTripPage(1, trip); + + IPage page = runAsDriver(() -> service.tripPage(1, 10, null, null)); + + DriverWaybillCardVO card = page.getRecords().get(0); + assertNull(card.getBusinessStatus(), "一趟车里两种原始状态,没有唯一值可下发"); + assertEquals(1, card.getStatus(), "混状态不影响运次数字态"); + } + @Test @DisplayName("运次列表不因配载卡去回查运单,也不写库") void tripPageDoesNotLookUpWaybillsOrWrite() { From c47268adfdb8b5d5b39d299255666ee4dc754cfe Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 14:48:48 +0800 Subject: [PATCH 24/25] =?UTF-8?q?:white=5Fcheck=5Fmark:=20=E5=BE=85?= =?UTF-8?q?=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=EF=BC=9A=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=90=8D=E4=B8=8E=E6=96=AD=E8=A8=80=E5=AF=B9?= =?UTF-8?q?=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/support/WaybillProcessSupportTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java index 9408bfd6..d8830c8a 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/support/WaybillProcessSupportTest.java @@ -337,8 +337,8 @@ class WaybillProcessSupportTest { // ------------------------------------------------------------ 待回单确认 @Test - @DisplayName("待回单确认落库字面量为 pending_return_confirm,且接单归一不得改写它") - void pendingReturnConfirmIsTerminalStatus() { + @DisplayName("待回单确认落库字面量为 pending_return_confirm,且接单归一不改写它") + void pendingReturnConfirmLiteralSurvivesAcceptanceNormalization() { assertEquals("pending_return_confirm", WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, "库里存的是这个字面量,web 与小程序也按它判断,不能改名"); assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, From b31b81bedf84a03fe75af28ef0cd1924c8a3e110 Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 15:30:26 +0800 Subject: [PATCH 25/25] =?UTF-8?q?:bug:=20=E4=BF=AE=E6=AD=A3=E5=BE=85?= =?UTF-8?q?=E5=9B=9E=E5=8D=95=E7=A1=AE=E8=AE=A4=E5=8D=95=E6=B5=8B=E7=9A=84?= =?UTF-8?q?=E6=A1=A9=E8=BF=94=E5=9B=9E=E7=B1=BB=E5=9E=8B=E4=B8=8E=E5=A4=B9?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DriverWaybillAutoCompleteTest.java | 9 ++++++++- .../service/impl/DriverWaybillTabStatusTest.java | 13 ++++++++++++- .../impl/WaybillEditSaveBusinessStatusTest.java | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) 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 3c1f83fe..6a23c1ac 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 @@ -72,6 +72,13 @@ class DriverWaybillAutoCompleteTest { {"key":"return","name":"回单","type":"confirm","enabled":true,"punch":true,"confirmMode":"yes"}] """; + /** 装货 → 签收两个司机打卡节点,末位是签收,回单需确认完成 */ + private static final String LOAD_SIGN_RETURN_CONFIRM_JSON = """ + [{"key":"load","name":"装货","type":"cargo","enabled":true,"punch":true}, + {"key":"sign","name":"签收","type":"cargo","enabled":true,"punch":true}, + {"key":"return","name":"回单","type":"confirm","enabled":true,"punch":true,"confirmMode":"yes"}] + """; + /** 末位打卡节点是「发货」,不是到货/卸货/签收 */ private static final String DISPATCH_LAST_JSON = """ [{"key":"dispatch","name":"发货","type":"cargo","enabled":true,"punch":true}] @@ -190,7 +197,7 @@ class DriverWaybillAutoCompleteTest { @Test @DisplayName("回单需确认但不是末位打卡:运单留在进行中") void nonLastPunchDoesNotEnterReturnConfirmation() { - givenWaybill(SIGN_LAST_RETURN_CONFIRM_JSON, "running"); + givenWaybill(LOAD_SIGN_RETURN_CONFIRM_JSON, "running"); DriverNodePunchVO vo = submit("load"); 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 5de8ac1d..668dc965 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 @@ -152,7 +152,18 @@ class DriverWaybillTabStatusTest { @SuppressWarnings("unchecked") private Collection queriedStatuses() { assertEquals(1, queried.size(), "一次列表查询"); - return ((LambdaQueryWrapper) queried.get(0)).getParamNameValuePairs().values(); + LambdaQueryWrapper wrapper = (LambdaQueryWrapper) queried.get(0); + // 参数槽由 SQL 片段构建时填充:不先取一次片段,参数表是空的 + wrapper.getSqlSegment(); + Collection statuses = new ArrayList<>(); + wrapper.getParamNameValuePairs().values().forEach(value -> { + if (value instanceof Collection items) { + statuses.addAll(items); + } else { + statuses.add(value); + } + }); + return statuses; } @SuppressWarnings("unchecked") diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java index 626d8886..db84583a 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/WaybillEditSaveBusinessStatusTest.java @@ -175,7 +175,7 @@ class WaybillEditSaveBusinessStatusTest { when(waybillMapper.selectById(WAYBILL_ID)).thenReturn(oldRecord); try (MockedStatic authUtil = mockStatic(AuthUtil.class)) { - authUtil.when(AuthUtil::getDeptId).thenReturn(DEPT_ID); + authUtil.when(AuthUtil::getDeptId).thenReturn(String.valueOf(DEPT_ID)); assertTrue(service.submit(request)); } }