🐛 配载单保存按需解析司机归属,不再抹掉已有归属
原实现每次保存都按手机号重解析 driver_id,与司机无关的保存会把归属清空并同步到子运单。改为:单据带正数 driver_id 直接沿用;更新时号码与库中一致且库中已有归属则保留;其余情况(新建、换号、原本无主)才按手机号在租户内唯一匹配,匹配不到落 null 并留脱敏 WARN,不阻断保存。复制配载单直接带源单归属。
This commit is contained in:
+41
-16
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
@@ -11,6 +12,7 @@ import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
@@ -143,16 +145,17 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
List<Long> oldWaybillIds = new ArrayList<>();
|
||||
// 新建暂存为草稿;编辑已有单据时保持原业务状态(如待执行),避免被改回草稿
|
||||
String businessStatus = STATUS_DRAFT;
|
||||
if (Func.isNotEmpty(loadingManage.getId())) {
|
||||
LoadingManage oldRecord = loadEditable(loadingManage.getId(), true);
|
||||
oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson());
|
||||
loadingManage.setLoadingNo(oldRecord.getLoadingNo());
|
||||
loadingManage.setDeptId(oldRecord.getDeptId());
|
||||
loadingManage.setDeptName(oldRecord.getDeptName());
|
||||
if (Func.isNotEmpty(oldRecord.getBusinessStatus())) {
|
||||
businessStatus = oldRecord.getBusinessStatus();
|
||||
LoadingManage stored = Func.isEmpty(loadingManage.getId()) ? null : loadEditable(loadingManage.getId(), true);
|
||||
if (stored != null) {
|
||||
oldWaybillIds = waybillIds(stored.getWaybillIdsJson());
|
||||
loadingManage.setLoadingNo(stored.getLoadingNo());
|
||||
loadingManage.setDeptId(stored.getDeptId());
|
||||
loadingManage.setDeptName(stored.getDeptName());
|
||||
if (Func.isNotEmpty(stored.getBusinessStatus())) {
|
||||
businessStatus = stored.getBusinessStatus();
|
||||
}
|
||||
}
|
||||
applyDriverOwnership(loadingManage, stored);
|
||||
prepareCreateOrUpdate(loadingManage);
|
||||
loadingManage.setBusinessStatus(businessStatus);
|
||||
if (Func.isEmpty(loadingManage.getLoadingNo())) {
|
||||
@@ -204,6 +207,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
loadingManage.setDeptId(oldRecord.getDeptId());
|
||||
loadingManage.setDeptName(oldRecord.getDeptName());
|
||||
}
|
||||
applyDriverOwnership(loadingManage, oldRecord);
|
||||
prepareCreateOrUpdate(loadingManage);
|
||||
if (Func.isEmpty(loadingManage.getLoadingNo())) {
|
||||
loadingManage.setLoadingNo(nextCode());
|
||||
@@ -483,6 +487,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
target.setTrailerVehicleNo(source.getTrailerVehicleNo());
|
||||
target.setDriverName(source.getDriverName());
|
||||
target.setDriverPhone(source.getDriverPhone());
|
||||
target.setDriverId(source.getDriverId());
|
||||
target.setEscortName(source.getEscortName());
|
||||
target.setEscortPhone(source.getEscortPhone());
|
||||
target.setCarrierType(source.getCarrierType());
|
||||
@@ -510,8 +515,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
target.setBusinessStatus(STATUS_DRAFT);
|
||||
target.setLoadingNo(nextCode());
|
||||
prepare(target);
|
||||
// 源单已有归属就沿用(手机号可能已被改过);没有才用刚解析出的值
|
||||
target.setDriverId(source.getDriverId() != null ? source.getDriverId() : target.getDriverId());
|
||||
applyDriverOwnership(target, null);
|
||||
validate(target, true);
|
||||
save(target);
|
||||
return detail(target.getId());
|
||||
@@ -528,6 +532,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
throw new ServiceException("仅司机拒绝接单后允许重新派单");
|
||||
}
|
||||
prepare(loadingManage);
|
||||
applyDriverOwnership(loadingManage, oldRecord);
|
||||
validate(loadingManage, false);
|
||||
List<Long> oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson());
|
||||
loadingManage.setLoadingNo(oldRecord.getLoadingNo());
|
||||
@@ -756,12 +761,6 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
loadingManage.setTrailerVehicleNo(TransportBusinessSupport.trimToNull(loadingManage.getTrailerVehicleNo()));
|
||||
loadingManage.setDriverName(TransportBusinessSupport.trimToNull(loadingManage.getDriverName()));
|
||||
loadingManage.setDriverPhone(TransportBusinessSupport.trimToNull(loadingManage.getDriverPhone()));
|
||||
// 归属主键是档案 ID,手机号只是号码快照:每次保存都按手机号重解析,
|
||||
// 避免改派后残留旧归属;解析不到(承运商司机)留空,不阻断保存
|
||||
Long resolvedDriverId = driverService.resolveDriverIdByMobile(
|
||||
loadingManage.getTenantId(), loadingManage.getDriverPhone());
|
||||
// driver_id 只有 >0 才算有主,0 / -1 都是「无主」的哨兵写法,落库统一为 NULL
|
||||
loadingManage.setDriverId(resolvedDriverId != null && resolvedDriverId > 0 ? resolvedDriverId : null);
|
||||
loadingManage.setEscortName(TransportBusinessSupport.trimToNull(loadingManage.getEscortName()));
|
||||
loadingManage.setEscortPhone(TransportBusinessSupport.trimToNull(loadingManage.getEscortPhone()));
|
||||
loadingManage.setCarrierType(TransportBusinessSupport.trimToNull(loadingManage.getCarrierType()));
|
||||
@@ -794,6 +793,32 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写司机归属:档案 ID 只在需要时解析,已落在单据上的归属不会被无关保存抹掉。
|
||||
* <p>
|
||||
* 判定顺序:调用方带了正数档案 ID 就用它;更新时 driver_phone 与库中一致且库中 driver_id 非空就保留库中值;
|
||||
* 其余情况(新建、换号、原本无主)按手机号在租户内唯一匹配司机档案,匹配不到落 null 并留 WARN,不阻断保存。
|
||||
*/
|
||||
private void applyDriverOwnership(LoadingManage loadingManage, LoadingManage stored) {
|
||||
Long driverId = loadingManage.getDriverId();
|
||||
if (driverId != null && driverId > 0) {
|
||||
return;
|
||||
}
|
||||
if (stored != null && Func.isNotEmpty(stored.getDriverId())
|
||||
&& Objects.equals(TransportBusinessSupport.trimToNull(stored.getDriverPhone()), loadingManage.getDriverPhone())) {
|
||||
loadingManage.setDriverId(stored.getDriverId());
|
||||
return;
|
||||
}
|
||||
String tenantId = Func.isNotEmpty(loadingManage.getTenantId()) ? loadingManage.getTenantId() : AuthUtil.getTenantId();
|
||||
Long resolvedDriverId = driverService.resolveDriverIdByMobile(tenantId, loadingManage.getDriverPhone());
|
||||
loadingManage.setDriverId(resolvedDriverId);
|
||||
if (resolvedDriverId == null) {
|
||||
log.warn("配载单 {} 的司机手机号未唯一匹配司机档案,driver_id 落空。租户={} 手机号={}",
|
||||
Func.isEmpty(loadingManage.getLoadingNo()) ? "(未编号)" : loadingManage.getLoadingNo(),
|
||||
tenantId, DesensitizedUtil.mobilePhone(loadingManage.getDriverPhone()));
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(LoadingManage loadingManage, boolean draftMode) {
|
||||
TransportBusinessSupport.validateRequired(loadingManage.getLoadingSubNos(), "配载子单号不能为空");
|
||||
TransportBusinessSupport.validateRequired(loadingManage.getTransportType(), "运输类型不能为空");
|
||||
|
||||
+119
@@ -1,5 +1,7 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
@@ -24,6 +26,7 @@ import org.springblade.transport.service.IContractManageService;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
import org.springblade.transport.service.IReceivablePayableDetailService;
|
||||
import org.springblade.transport.support.LogCapture;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -35,6 +38,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -66,6 +70,9 @@ class LoadingManageChildStatusWriteTest {
|
||||
private static final String DRIVER_PHONE = "13800138000";
|
||||
private static final String TENANT_ID = "000000";
|
||||
private static final Long RESOLVED_DRIVER_ID = 9321L;
|
||||
private static final Long MANUAL_DRIVER_ID = 7001L;
|
||||
private static final Long OTHER_PROFILE_ID = 8888L;
|
||||
private static final String OTHER_DRIVER_PHONE = "13900139000";
|
||||
private static final Long SOURCE_LOADING_ID = 1800000000000000002L;
|
||||
|
||||
private static final Long NEED_ACCEPT_WAYBILL_ID = 3101L;
|
||||
@@ -394,6 +401,110 @@ class LoadingManageChildStatusWriteTest {
|
||||
assertEquals(RESOLVED_DRIVER_ID, capturedSaved().getDriverId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("已归属的单子号码没变:无关字段的保存不重解析,归属原样保留")
|
||||
void unrelatedEditKeepsStoredDriverId() {
|
||||
LoadingManage record = runningRecord();
|
||||
record.setDriverId(RESOLVED_DRIVER_ID);
|
||||
stubLoadingRecord(record);
|
||||
stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID));
|
||||
// 这一次谁都解析不到:只要实现还按手机号重解析,归属就会被清空
|
||||
when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(null);
|
||||
|
||||
assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID)));
|
||||
|
||||
assertEquals(RESOLVED_DRIVER_ID, capturedLoading().getDriverId());
|
||||
assertEquals(RESOLVED_DRIVER_ID, statusSyncUpdates().get(0).setValue("driver_id"));
|
||||
verify(driverService, never()).resolveDriverIdByMobile(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("人工补过归属的单子:号码恰好命中别人的档案,保存也不改归属")
|
||||
void unrelatedEditKeepsManualOwnership() {
|
||||
LoadingManage record = runningRecord();
|
||||
record.setDriverId(MANUAL_DRIVER_ID);
|
||||
stubLoadingRecord(record);
|
||||
stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID));
|
||||
when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(OTHER_PROFILE_ID);
|
||||
|
||||
assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID)));
|
||||
|
||||
assertEquals(MANUAL_DRIVER_ID, capturedLoading().getDriverId(),
|
||||
"人工归属不能被一次无关保存改到同号的另一个档案上");
|
||||
assertEquals(MANUAL_DRIVER_ID, statusSyncUpdates().get(0).setValue("driver_id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("-1 哨兵的归属:号码没变的保存原样保留,不抹成 NULL")
|
||||
void sentinelDriverIdSurvivesUnrelatedEdit() {
|
||||
LoadingManage record = runningRecord();
|
||||
record.setDriverId(-1L);
|
||||
stubLoadingRecord(record);
|
||||
stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID));
|
||||
when(driverService.resolveDriverIdByMobile(any(), any())).thenReturn(null);
|
||||
|
||||
assertTrue(service.submit(editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID)));
|
||||
|
||||
assertEquals(-1L, capturedLoading().getDriverId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("换司机:号码变了就按新号码重新解析,归属跟着换")
|
||||
void changingPhoneResolvesNewDriverId() {
|
||||
LoadingManage record = pendingRecord();
|
||||
record.setDriverId(RESOLVED_DRIVER_ID);
|
||||
stubLoadingRecord(record);
|
||||
stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID));
|
||||
stubConfigs(config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON));
|
||||
when(driverService.resolveDriverIdByMobile(any(), eq(OTHER_DRIVER_PHONE))).thenReturn(OTHER_PROFILE_ID);
|
||||
LoadingManage form = editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID);
|
||||
form.setDriverPhone(OTHER_DRIVER_PHONE);
|
||||
|
||||
assertTrue(service.submit(form));
|
||||
|
||||
assertEquals(OTHER_PROFILE_ID, capturedLoading().getDriverId());
|
||||
assertEquals(OTHER_PROFILE_ID, statusSyncUpdates().get(0).setValue("driver_id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("号码改成一个解析不到的号:归属落空,子单同步落空")
|
||||
void changingPhoneToUnresolvedNumberClearsDriverId() {
|
||||
LoadingManage record = pendingRecord();
|
||||
record.setDriverId(RESOLVED_DRIVER_ID);
|
||||
stubLoadingRecord(record);
|
||||
stubWaybills(waybill(NEED_ACCEPT_WAYBILL_ID, NEED_ACCEPT_PROJECT_ID));
|
||||
stubConfigs(config(NEED_ACCEPT_PROJECT_ID, NEED_ACCEPT_JSON));
|
||||
when(driverService.resolveDriverIdByMobile(any(), eq(OTHER_DRIVER_PHONE))).thenReturn(null);
|
||||
LoadingManage form = editForm(LOADING_ID, NEED_ACCEPT_WAYBILL_ID);
|
||||
form.setDriverPhone(OTHER_DRIVER_PHONE);
|
||||
|
||||
assertTrue(service.submit(form));
|
||||
|
||||
assertNull(capturedLoading().getDriverId());
|
||||
assertNull(statusSyncUpdates().get(0).setValue("driver_id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("解析不到档案:留一条脱敏 WARN,保存照常成功")
|
||||
void unresolvedPhoneLogsMaskedWarning() {
|
||||
stubWaybills(waybill(NO_ACCEPT_WAYBILL_ID, NO_ACCEPT_PROJECT_ID));
|
||||
stubConfigs(config(NO_ACCEPT_PROJECT_ID, NO_ACCEPT_JSON));
|
||||
when(driverService.resolveDriverIdByMobile(any(), eq(DRIVER_PHONE))).thenReturn(null);
|
||||
try (LogCapture logs = LogCapture.open(LoadingManageServiceImpl.class)) {
|
||||
assertTrue(service.submit(loadingForm(NO_ACCEPT_WAYBILL_ID)));
|
||||
|
||||
String warning = logs.events().stream()
|
||||
.filter(event -> event.getLevel() == Level.WARN)
|
||||
.map(ILoggingEvent::getFormattedMessage)
|
||||
.filter(message -> message.contains("司机档案"))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("解析失败必须留 WARN,实际日志:" + logs.messages()));
|
||||
assertTrue(warning.contains("138****8000"), "WARN 里的号码必须脱敏,实际:" + warning);
|
||||
assertFalse(warning.contains(DRIVER_PHONE), "WARN 里不能出现完整号码,实际:" + warning);
|
||||
assertTrue(warning.contains(LOADING_NO), "WARN 要带业务标识(配载单号),实际:" + warning);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 夹具
|
||||
|
||||
private LoadingManage loadingForm(Long... waybillIds) {
|
||||
@@ -468,6 +579,14 @@ class LoadingManageChildStatusWriteTest {
|
||||
return record;
|
||||
}
|
||||
|
||||
private LoadingManage pendingRecord() {
|
||||
LoadingManage record = loadingRecord("pending", NEED_ACCEPT_WAYBILL_ID);
|
||||
record.setVehicleNo("桂A12345");
|
||||
record.setDriverName("张三");
|
||||
record.setDriverPhone(DRIVER_PHONE);
|
||||
return record;
|
||||
}
|
||||
|
||||
private LoadingManage copySource() {
|
||||
LoadingManage source = loadingRecord("pending", NEED_ACCEPT_WAYBILL_ID);
|
||||
source.setLoadingSubNos("YD-" + NEED_ACCEPT_WAYBILL_ID);
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package org.springblade.transport.support;
|
||||
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.read.ListAppender;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 抓取某个类的日志输出(logback ListAppender)。
|
||||
* <p>
|
||||
* 「解析失败时留一条脱敏 WARN」这类要求只看返回值验不出来,必须断言真实日志正文。
|
||||
*/
|
||||
public final class LogCapture implements AutoCloseable {
|
||||
|
||||
private final Logger logger;
|
||||
private final ListAppender<ILoggingEvent> appender;
|
||||
|
||||
private LogCapture(Logger logger, ListAppender<ILoggingEvent> appender) {
|
||||
this.logger = logger;
|
||||
this.appender = appender;
|
||||
}
|
||||
|
||||
public static LogCapture open(Class<?> loggerType) {
|
||||
Logger logger = (Logger) LoggerFactory.getLogger(loggerType);
|
||||
ListAppender<ILoggingEvent> appender = new ListAppender<>();
|
||||
appender.start();
|
||||
logger.addAppender(appender);
|
||||
return new LogCapture(logger, appender);
|
||||
}
|
||||
|
||||
/** 已按占位符填充好的日志正文,按发生顺序 */
|
||||
public List<String> messages() {
|
||||
return appender.list.stream().map(ILoggingEvent::getFormattedMessage).toList();
|
||||
}
|
||||
|
||||
public List<ILoggingEvent> events() {
|
||||
return List.copyOf(appender.list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
logger.detachAppender(appender);
|
||||
appender.stop();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user