🔀 合并编辑保存以库为准

This commit is contained in:
2026-10-22 13:49:58 +08:00
2 changed files with 212 additions and 2 deletions
@@ -1068,10 +1068,11 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
waybill.setMasterNo(null);
waybill.setMileageRemark(null);
}
// 新建若已带业务状态(如复制提交已完成)则在过程校正后回写,保证与原运单一致
// 新建若已带业务状态(如复制提交已完成)则在过程校正后回写,保证与原运单一致;
// 编辑保存只改资料,business_status 以库里的值为准,请求体里的值不回写
String preservedBusinessStatus = created
? TransportBusinessSupport.trimToNull(waybill.getBusinessStatus())
: null;
: editPreservedBusinessStatus(oldRecord);
if (isCompletedBusinessStatus(preservedBusinessStatus)) {
preservedBusinessStatus = "completed";
}
@@ -1093,6 +1094,17 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
}
}
/**
* 编辑保存要回写的业务状态:取库里的值。
* 草稿返回 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());
@@ -0,0 +1,198 @@
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 org.springblade.transport.support.WaybillProcessSupport;
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;
/**
* 运单编辑保存的业务状态来源测试。
* <p>
* 编辑保存只改运单资料,business_status 一律以库里的值为准:请求体不回传(null / 空串)
* 不得把库里的待执行、进行中、已完成改写成别的状态,回传陈旧值也不得覆盖。
* 唯一例外是草稿——submit 即正式提交,草稿须按过程配置提升为待执行 / 进行中。
* <p>
* 过程配置用「无需确认接单」的快照({@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 editSaveKeepsPendingReturnConfirm() {
Waybill request = editableRequest();
submit(request, WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM);
assertEquals(WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, 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> 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);
}
}