From 5c88b90404ff294e6a43411ee169f70c188d1d4b Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Thu, 22 Oct 2026 13:42:41 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E7=94=A8=E8=AF=B7=E6=B1=82=E4=BD=93=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E8=BF=90=E5=8D=95=E4=B8=9A=E5=8A=A1=E7=8A=B6=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); + } + +}