🔀 合并票 03 车辆侧取消所属组织必填
This commit is contained in:
+9
-22
@@ -123,21 +123,6 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 建档前置条件:车辆行的 {@code organization_name} 是 NOT NULL 且无默认值,缺了这条 INSERT
|
|
||||||
* 直接报「Field 'organization_name' doesn't have a default value」,用户看到的是一串英文异常。
|
|
||||||
* <p>
|
|
||||||
* 只在**新建车辆**时要求:重新认证不会写这一列(它由管理端维护,属于内部管理归属),
|
|
||||||
* 拿它拦住重新认证只会让司机无从下手。
|
|
||||||
*
|
|
||||||
* @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,
|
private TransportVehicle createVehicleForDriver(Driver driver, VehicleCertSubmitRequest request,
|
||||||
String normalizedPlate) {
|
String normalizedPlate) {
|
||||||
// 内容校验已在 submitVehicleCert 里统一做过;这里只剩「这条 INSERT 需要什么」的前置条件
|
// 内容校验已在 submitVehicleCert 里统一做过
|
||||||
requireOrganizationForNewVehicle(driver);
|
|
||||||
|
|
||||||
TransportVehicle vehicle = new TransportVehicle();
|
TransportVehicle vehicle = new TransportVehicle();
|
||||||
vehicle.setPlateNo(normalizedPlate);
|
vehicle.setPlateNo(normalizedPlate);
|
||||||
// 所属组织取司机档案:车辆表的该列 NOT NULL 且无默认值,不填这条 INSERT 直接报
|
// 所属组织是建档时对司机档案取的一次**快照**,不是活关联:司机之后补上组织也不回填已建的车辆。
|
||||||
// 「Field 'organization_name' doesn't have a default value」(严格模式下必错)。
|
// 司机档案没有组织时写空串——该列 NOT NULL 且无默认值,落 null 这条 INSERT 直接报
|
||||||
// 车辆归到司机所属组织名下,管理端按组织查看时才看得到这台车。
|
// 「Field 'organization_name' doesn't have a default value」。
|
||||||
vehicle.setOrganizationName(driver.getOrganizationName());
|
vehicle.setOrganizationName(trimToEmpty(driver.getOrganizationName()));
|
||||||
vehicle.setOwnerName(request.getOwnerName());
|
vehicle.setOwnerName(request.getOwnerName());
|
||||||
vehicle.setPlateColor(request.getPlateColor());
|
vehicle.setPlateColor(request.getPlateColor());
|
||||||
vehicle.setVehicleType(request.getVehicleType());
|
vehicle.setVehicleType(request.getVehicleType());
|
||||||
@@ -571,4 +554,8 @@ public class DriverCertServiceImpl implements IDriverCertService {
|
|||||||
return driver;
|
return driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String trimToEmpty(String value) {
|
||||||
|
return value == null ? "" : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-3
@@ -264,9 +264,6 @@ public class TransportVehicleServiceImpl extends BaseServiceImpl<TransportVehicl
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validate(TransportVehicle vehicle) {
|
private void validate(TransportVehicle vehicle) {
|
||||||
if (Func.isEmpty(vehicle.getOrganizationName())) {
|
|
||||||
throw new ServiceException("所属组织不能为空");
|
|
||||||
}
|
|
||||||
if (Func.isEmpty(vehicle.getPlateNo())) {
|
if (Func.isEmpty(vehicle.getPlateNo())) {
|
||||||
throw new ServiceException("车牌号不能为空");
|
throw new ServiceException("车牌号不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-7
@@ -581,17 +581,21 @@ class DriverCertServiceImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("司机建车:没有所属组织时明确拒绝,不落一辆查不到归属的车")
|
@DisplayName("司机建车:司机档案没有组织也允许建车,车辆组织落空串而不是 null")
|
||||||
void submitVehicleRejectsDriverWithoutOrganization() {
|
void submitVehicleCreatesVehicleForDriverWithoutOrganization() {
|
||||||
Driver driver = driver(null);
|
Driver driver = driver(null);
|
||||||
driver.setOrganizationName(null);
|
driver.setOrganizationName(null);
|
||||||
when(driverAppService.currentDriverEntity()).thenReturn(driver);
|
when(driverAppService.currentDriverEntity()).thenReturn(driver);
|
||||||
when(transportVehicleService.list(any(LambdaQueryWrapper.class))).thenReturn(List.of());
|
when(transportVehicleService.list(any(LambdaQueryWrapper.class))).thenReturn(List.of());
|
||||||
when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null);
|
when(certRecordService.selectPendingRecord(any(), anyLong())).thenReturn(null);
|
||||||
|
|
||||||
ServiceException exception = assertThrows(ServiceException.class,
|
service.submitVehicleCert(completeVehicleRequest("桂A32523"));
|
||||||
() -> service.submitVehicleCert(completeVehicleRequest("桂A32523")));
|
|
||||||
assertTrue(exception.getMessage().contains("所属组织"));
|
ArgumentCaptor<TransportVehicle> 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
|
@Test
|
||||||
@@ -689,8 +693,7 @@ class DriverCertServiceImplTest {
|
|||||||
// 司机档案ID与用户ID是两个不同的ID:提交人字段要的是后者
|
// 司机档案ID与用户ID是两个不同的ID:提交人字段要的是后者
|
||||||
driver.setUserId(USER_ID);
|
driver.setUserId(USER_ID);
|
||||||
driver.setDriverName("张三");
|
driver.setDriverName("张三");
|
||||||
// 车辆表的 organization_name 是 NOT NULL 且无默认值:司机缺组织时建车必然失败,
|
// 车辆行的 organization_name 是建档时从司机档案抄下来的快照
|
||||||
// 故建车链路要求它非空,fixture 也必须给
|
|
||||||
driver.setOrganizationName("广西现代物流集团有限公司");
|
driver.setOrganizationName("广西现代物流集团有限公司");
|
||||||
driver.setDrivingVehicle(drivingVehicle);
|
driver.setDrivingVehicle(drivingVehicle);
|
||||||
return driver;
|
return driver;
|
||||||
|
|||||||
+148
@@ -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})的组织口径。
|
||||||
|
* <p>
|
||||||
|
* 所属组织是车辆的档案属性,不是保存的前置条件:司机端建出来的车组织可能是空串,
|
||||||
|
* 管理端若仍要求必填,这辆车在后台就成了只读——打开能看、一保存就报错。
|
||||||
|
*/
|
||||||
|
class TransportVehicleServiceImplTest {
|
||||||
|
|
||||||
|
private static final String PLATE = "桂A32523";
|
||||||
|
|
||||||
|
private TransportVehicleServiceImpl service;
|
||||||
|
private TransportVehicleMapper vehicleMapper;
|
||||||
|
private MockedStatic<AuthUtil> 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<TransportVehicle> 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<TransportVehicle> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user