🐛 运次级接单与拒单补上更新痕迹,与单票路径一致

单票接单/拒单走 updateById,写前会把 update_time 刷成提交时刻、
update_user 填成当前用户(无登录态不填);运次级原来用 wrapper 一次写完,
这两列保持旧值,同一动作的两条路径在库里留下不同痕迹。
现显式补上同样两列,时刻取与接单/拒单时刻同一个 Date。
This commit is contained in:
2026-10-17 18:34:30 +08:00
parent 1de48db233
commit 751b246d8e
2 changed files with 92 additions and 3 deletions
@@ -28,6 +28,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.DateUtil;
@@ -651,7 +652,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
List<TripVO.TripWaybillVO> children = tripChildren(tripId);
assertTripAcceptable(children);
List<Long> pendingIds = writeCandidateIds(children, "当前运单状态不允许接单");
// 更新痕迹与单票接单一致:单票走 updateById,写前会把 update_time 刷成提交时刻、
// 把 update_user 填成当前用户(无登录态则不填);这里一次写完,显式补上同样两列,
// 时刻取与接单时刻同一个 now,操作人取同一个登录用户
Date now = new Date();
Long operatorId = currentOperatorId();
return waybillService.update(Wrappers.<Waybill>lambdaUpdate()
.set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_ACCEPTED)
.set(Waybill::getDriverAcceptTime, now)
@@ -659,6 +664,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
.set(Waybill::getDriverRejectTime, null)
.set(Waybill::getDriverRejectReason, null)
.set(Waybill::getBusinessStatus, STATUS_RUNNING)
.set(Waybill::getUpdateTime, now)
.set(operatorId != null, Waybill::getUpdateUser, operatorId)
.in(Waybill::getId, pendingIds));
}
@@ -673,8 +680,9 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
throw new ServiceException("拒绝原因不能超过200字");
}
List<Long> pendingIds = writeCandidateIds(children, "当前运单状态不允许拒绝接单");
// 整单拒绝落在每张未终态子运单上,一次一条语句写完,拒单时刻与更新痕迹全趟车一致
Date now = new Date();
// 整单拒绝落在每张未终态子运单上,一次一条语句写完,接单时刻与拒单时刻全趟车一致
Long operatorId = currentOperatorId();
return waybillService.update(Wrappers.<Waybill>lambdaUpdate()
.set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED)
.set(Waybill::getDriverAcceptTime, null)
@@ -682,9 +690,19 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
.set(Waybill::getDriverRejectTime, now)
.set(Waybill::getDriverRejectReason, rejectReason)
.set(Waybill::getBusinessStatus, STATUS_PENDING)
.set(Waybill::getUpdateTime, now)
.set(operatorId != null, Waybill::getUpdateUser, operatorId)
.in(Waybill::getId, pendingIds));
}
/**
* 当前登录用户主键:与框架写入时填充更新人的口径一致——没有登录态就不填
*/
private Long currentOperatorId() {
BladeUser operator = AuthUtil.getUser();
return operator == null ? null : operator.getUserId();
}
/**
* 运次 → 子运单集合:配载的运次走运次读服务解析(配载单主键命中后按配载单号取子运单),
* 未配载的就是那张运单本身。收口与列表一致——别人的运次在这里已经被挡住。
@@ -12,6 +12,7 @@ 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.BladeUser;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.system.feign.IMpUserClient;
import org.springblade.transport.mapper.ExceptionDisposalMapper;
@@ -29,11 +30,13 @@ import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.CertificationGate;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -110,7 +113,9 @@ class DriverTripAcceptRejectTest {
"driver_accept_driver_id",
"driver_reject_time",
"driver_reject_reason",
"business_status"), updatedColumns(), "只写接单相关的列,司机字段一概不动");
"business_status",
"update_time",
"update_user"), updatedColumns(), "只写接单相关的列与刷新痕迹,司机字段一概不动");
assertTrue(capturedUpdateParams().containsAll(List.of(PENDING_ID, SECOND_ID, DRIVER_ID)),
"两张子运单与接单司机一起下发:" + capturedUpdateParams());
assertTrue(capturedWhereSql().contains("(id IN ("), capturedWhereSql());
@@ -118,6 +123,49 @@ class DriverTripAcceptRejectTest {
assertEquals(PHONE, capturedQuery().getDriverPhone(), "归属仍按司机手机号收口");
}
@Test
@DisplayName("运次接单的更新时间与接单时刻是同一个 now、更新人是当前操作人")
void tripAcceptRefreshesUpdateTrace() {
stubTrip(requireAcceptChild(PENDING_ID, "pending", "pending"));
runAsDriver(() -> service.tripAccept(TRIP_ID));
List<Date> dates = capturedUpdateDates();
assertEquals(2, dates.size(), "接单时刻与更新时间各占一列");
assertSame(dates.get(0), dates.get(1), "update_time 与接单时刻必须是同一个 now");
assertTrue(capturedUpdateParams().contains(USER_ID), "update_user 就是当前操作人:" + capturedUpdateParams());
}
@Test
@DisplayName("没有登录态时不写 update_user,但 update_time 仍随操作刷新")
void tripAcceptWithoutLoginSkipsUpdateUser() {
stubTrip(requireAcceptChild(PENDING_ID, "pending", "pending"));
runWithoutLoginUser(() -> service.tripAccept(TRIP_ID));
assertEquals(List.of(
"driver_accept_status",
"driver_accept_time",
"driver_accept_driver_id",
"driver_reject_time",
"driver_reject_reason",
"business_status",
"update_time"), updatedColumns(), "无登录态时与框架填充口径一致:只刷 update_time");
}
@Test
@DisplayName("运次拒单的更新时间与拒单时刻是同一个 now、更新人是当前操作人")
void tripRejectRefreshesUpdateTrace() {
stubTrip(requireAcceptChild(PENDING_ID, "pending", "pending"));
runAsDriver(() -> service.tripReject(TRIP_ID, "不要了"));
List<Date> dates = capturedUpdateDates();
assertEquals(2, dates.size(), "拒单时刻与更新时间各占一列");
assertSame(dates.get(0), dates.get(1), "update_time 与拒单时刻必须是同一个 now");
assertTrue(capturedUpdateParams().contains(USER_ID), "update_user 就是当前操作人:" + capturedUpdateParams());
}
@Test
@DisplayName("任一子单已接单 → 整单已接单,不再重复写")
void tripAcceptRefusesSecondCall() {
@@ -204,7 +252,9 @@ class DriverTripAcceptRejectTest {
"driver_accept_driver_id",
"driver_reject_time",
"driver_reject_reason",
"business_status"), updatedColumns(), "只写拒单相关的列,司机字段一概不动");
"business_status",
"update_time",
"update_user"), updatedColumns(), "只写拒单相关的列与刷新痕迹,司机字段一概不动");
assertTrue(capturedUpdateParams().contains("车坏了"), "拒单原因原样下发(去首尾空格):" + capturedUpdateParams());
assertTrue(capturedUpdateParams().containsAll(List.of(PENDING_ID, SECOND_ID)));
}
@@ -294,10 +344,27 @@ class DriverTripAcceptRejectTest {
private <T> T runAsDriver(Supplier<T> action) {
try (MockedStatic<AuthUtil> authUtil = mockStatic(AuthUtil.class)) {
authUtil.when(AuthUtil::getUserId).thenReturn(USER_ID);
authUtil.when(AuthUtil::getUser).thenReturn(bladeUser());
return action.get();
}
}
/** 框架填充更新人看的是 getUserId() 背后的登录用户:为空就不填,这里把这一口径钉住 */
private <T> T runWithoutLoginUser(Supplier<T> action) {
try (MockedStatic<AuthUtil> authUtil = mockStatic(AuthUtil.class)) {
authUtil.when(AuthUtil::getUserId).thenReturn(USER_ID);
authUtil.when(AuthUtil::getUser).thenReturn(null);
return action.get();
}
}
private BladeUser bladeUser() {
BladeUser user = new BladeUser();
user.setUserId(USER_ID);
user.setUserName("黄宇富");
return user;
}
private void stubTrip(TripVO.TripWaybillVO... children) {
stubTrip(new TripVO.TripWaybillVO[0], children);
}
@@ -360,6 +427,10 @@ class DriverTripAcceptRejectTest {
.toList();
}
private List<Date> capturedUpdateDates() {
return capturedUpdateParams().stream().filter(Date.class::isInstance).map(Date.class::cast).toList();
}
/** SET 与 WHERE 的参数都是渲染时才进参数表的,取值前先把两段都渲染一遍 */
private Collection<Object> capturedUpdateParams() {
LambdaUpdateWrapper<Waybill> update = capturedUpdate();