diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java
index ca7ef54e..703119df 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverCertServiceImpl.java
@@ -123,21 +123,6 @@ public class DriverCertServiceImpl implements IDriverCertService {
}
}
- /**
- * 建档前置条件:车辆行的 {@code organization_name} 是 NOT NULL 且无默认值,缺了这条 INSERT
- * 直接报「Field 'organization_name' doesn't have a default value」,用户看到的是一串英文异常。
- *
- * 只在**新建车辆**时要求:重新认证不会写这一列(它由管理端维护,属于内部管理归属),
- * 拿它拦住重新认证只会让司机无从下手。
- *
- * @param driver 当前司机
- */
- private void requireOrganizationForNewVehicle(Driver driver) {
- if (Func.isEmpty(driver.getOrganizationName())) {
- throw new ServiceException("司机档案缺少所属组织,请联系管理员补充后再提交");
- }
- }
-
/**
* 文本必填 + 长度校验
*
@@ -474,15 +459,13 @@ public class DriverCertServiceImpl implements IDriverCertService {
*/
private TransportVehicle createVehicleForDriver(Driver driver, VehicleCertSubmitRequest request,
String normalizedPlate) {
- // 内容校验已在 submitVehicleCert 里统一做过;这里只剩「这条 INSERT 需要什么」的前置条件
- requireOrganizationForNewVehicle(driver);
-
+ // 内容校验已在 submitVehicleCert 里统一做过
TransportVehicle vehicle = new TransportVehicle();
vehicle.setPlateNo(normalizedPlate);
- // 所属组织取司机档案:车辆表的该列 NOT NULL 且无默认值,不填这条 INSERT 直接报
- // 「Field 'organization_name' doesn't have a default value」(严格模式下必错)。
- // 车辆归到司机所属组织名下,管理端按组织查看时才看得到这台车。
- vehicle.setOrganizationName(driver.getOrganizationName());
+ // 所属组织是建档时对司机档案取的一次**快照**,不是活关联:司机之后补上组织也不回填已建的车辆。
+ // 司机档案没有组织时写空串——该列 NOT NULL 且无默认值,落 null 这条 INSERT 直接报
+ // 「Field 'organization_name' doesn't have a default value」。
+ vehicle.setOrganizationName(trimToEmpty(driver.getOrganizationName()));
vehicle.setOwnerName(request.getOwnerName());
vehicle.setPlateColor(request.getPlateColor());
vehicle.setVehicleType(request.getVehicleType());
@@ -571,4 +554,8 @@ public class DriverCertServiceImpl implements IDriverCertService {
return driver;
}
+ private String trimToEmpty(String value) {
+ return value == null ? "" : value.trim();
+ }
+
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportVehicleServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportVehicleServiceImpl.java
index d9e1c734..4e6e2c00 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportVehicleServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportVehicleServiceImpl.java
@@ -264,9 +264,6 @@ public class TransportVehicleServiceImpl extends BaseServiceImpl service.submitVehicleCert(completeVehicleRequest("桂A32523")));
- assertTrue(exception.getMessage().contains("所属组织"));
+ service.submitVehicleCert(completeVehicleRequest("桂A32523"));
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(TransportVehicle.class);
+ verify(transportVehicleService).save(captor.capture());
+ assertEquals("", captor.getValue().getOrganizationName(),
+ "该列 NOT NULL 且无默认值:缺组织要落空串,落 null 这条 INSERT 就直接报错");
+ verify(certRecordService).save(any(TransportCertRecord.class));
}
@Test
@@ -689,8 +693,7 @@ class DriverCertServiceImplTest {
// 司机档案ID与用户ID是两个不同的ID:提交人字段要的是后者
driver.setUserId(USER_ID);
driver.setDriverName("张三");
- // 车辆表的 organization_name 是 NOT NULL 且无默认值:司机缺组织时建车必然失败,
- // 故建车链路要求它非空,fixture 也必须给
+ // 车辆行的 organization_name 是建档时从司机档案抄下来的快照
driver.setOrganizationName("广西现代物流集团有限公司");
driver.setDrivingVehicle(drivingVehicle);
return driver;
diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TransportVehicleServiceImplTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TransportVehicleServiceImplTest.java
new file mode 100644
index 00000000..393762bb
--- /dev/null
+++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TransportVehicleServiceImplTest.java
@@ -0,0 +1,148 @@
+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.AfterEach;
+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;
+import org.springblade.transport.mapper.DriverMapper;
+import org.springblade.transport.mapper.TransportVehicleMapper;
+import org.springblade.transport.pojo.entity.TransportVehicle;
+
+import java.lang.reflect.Field;
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * 车辆管理保存({@code submit} → {@code prepare} + {@code validate})的组织口径。
+ *
+ * 所属组织是车辆的档案属性,不是保存的前置条件:司机端建出来的车组织可能是空串,
+ * 管理端若仍要求必填,这辆车在后台就成了只读——打开能看、一保存就报错。
+ */
+class TransportVehicleServiceImplTest {
+
+ private static final String PLATE = "桂A32523";
+
+ private TransportVehicleServiceImpl service;
+ private TransportVehicleMapper vehicleMapper;
+ private MockedStatic authUtil;
+
+ @BeforeAll
+ static void initTableInfo() {
+ // saveOrUpdate / count 需要 MyBatis-Plus 的实体元数据缓存
+ MybatisConfiguration configuration = new MybatisConfiguration();
+ MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
+ assistant.setCurrentNamespace("test");
+ TableInfoHelper.initTableInfo(assistant, TransportVehicle.class);
+ }
+
+ @BeforeEach
+ void setUp() throws Exception {
+ vehicleMapper = mock(TransportVehicleMapper.class);
+ service = new TransportVehicleServiceImpl(mock(DriverMapper.class));
+ inject("baseMapper", vehicleMapper);
+ authUtil = mockStatic(AuthUtil.class);
+ }
+
+ @AfterEach
+ void tearDown() {
+ authUtil.close();
+ }
+
+ @Test
+ @DisplayName("保存车辆:所属组织为空也允许保存,落库是空串而不是 null(该列 NOT NULL)")
+ void submitAcceptsVehicleWithoutOrganization() {
+ when(vehicleMapper.selectCount(any(Wrapper.class))).thenReturn(0L);
+
+ TransportVehicle vehicle = completeVehicle();
+ vehicle.setOrganizationName(null);
+
+ assertDoesNotThrow(() -> service.submit(vehicle));
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(TransportVehicle.class);
+ verify(vehicleMapper).insert(captor.capture());
+ assertEquals("", captor.getValue().getOrganizationName(),
+ "organization_name 是 NOT NULL 且无默认值,null 会让这条 INSERT 直接报错");
+ }
+
+ @Test
+ @DisplayName("保存车辆:所属组织是空白串时归一成空串,不落一片空格")
+ void submitTrimsBlankOrganizationToEmpty() {
+ when(vehicleMapper.selectCount(any(Wrapper.class))).thenReturn(0L);
+
+ TransportVehicle vehicle = completeVehicle();
+ vehicle.setOrganizationName(" ");
+
+ assertDoesNotThrow(() -> service.submit(vehicle));
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(TransportVehicle.class);
+ verify(vehicleMapper).insert(captor.capture());
+ assertEquals("", captor.getValue().getOrganizationName());
+ }
+
+ @Test
+ @DisplayName("保存车辆:组织已不再必填,但其余必填项照旧拦住(证明校验本身还在跑)")
+ void submitStillValidatesOtherRequiredFields() {
+ TransportVehicle vehicle = completeVehicle();
+ vehicle.setPlateNo("");
+
+ ServiceException exception = assertThrows(ServiceException.class, () -> service.submit(vehicle));
+ assertEquals("车牌号不能为空", exception.getMessage());
+ verify(vehicleMapper, never()).insert(any(TransportVehicle.class));
+ }
+
+ // ------------------------------------------------------------ 夹具
+
+ /**
+ * 一份能通过全部校验的车辆内容;只有组织一项按用例需要改动。
+ */
+ private TransportVehicle completeVehicle() {
+ TransportVehicle vehicle = new TransportVehicle();
+ vehicle.setOrganizationName("测试组织");
+ vehicle.setPlateNo(PLATE);
+ vehicle.setVehicleType("重型货车");
+ vehicle.setBusinessRelation("管理挂靠");
+ vehicle.setEnergyType("柴油");
+ vehicle.setDrivingLicenseNo("450100123456");
+ vehicle.setDrivingLicenseEndDate(LocalDate.of(2028, 3, 9));
+ vehicle.setRoadTransportCertNo("桂交运管0001");
+ vehicle.setRoadTransportCertEndDate(LocalDate.of(2030, 1, 1));
+ vehicle.setAnnualReviewEndDate(LocalDate.of(2030, 1, 1));
+ vehicle.setRegistrationNo("450100123456");
+ vehicle.setApprovedLoadKg(18000);
+ return vehicle;
+ }
+
+ 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);
+ }
+
+}