✨ 新增小程序端司机与车辆档案管理接口

- /archive/manage/driver|vehicle 的 page/detail/submit/status 八个接口,仅登录态 + 显式 admin 校验,
  权限不足回业务 403(小程序把 401 当会话失效,不能回 401)
- 分页支持 keyword(司机:姓名/手机号;车辆:车牌/VIN/所有人)与 expireStatus、status
- 停用真正生效:/driver/vehicles 只回启用车辆、停用车辆不可提交车辆认证、
  派单检索排除停用司机、选车列表改查车辆档案表并排除停用车辆
- 管理端保存不再重置车辆认证状态(此前每存一次就造出「审核中但没有待审记录」的死状态)
This commit is contained in:
2026-10-28 17:36:04 +08:00
parent 29bce66d63
commit 85a3416b6e
11 changed files with 458 additions and 16 deletions
@@ -48,6 +48,12 @@ public class DriverVO extends Driver {
@Serial
private static final long serialVersionUID = 1L;
/**
* 搜索关键字:同时模糊匹配姓名与手机号(小程序列表用,管理端不传)
*/
@TableField(exist = false)
@Schema(description = "搜索关键字(姓名 / 手机号)")
private String keyword;
/**
* 证件有效期状态:within30-30天内到期,expired-已到期
*/
@@ -48,6 +48,12 @@ public class TransportVehicleVO extends TransportVehicle {
@Serial
private static final long serialVersionUID = 1L;
/**
* 搜索关键字:同时模糊匹配车牌、VIN 与所有人(小程序列表用,管理端不传)
*/
@TableField(exist = false)
@Schema(description = "搜索关键字(车牌 / VIN / 所有人)")
private String keyword;
/**
* 证件有效期状态:within30-30天内到期,expired-已到期
*/
@@ -30,6 +30,7 @@ import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.tool.api.R;
import org.springblade.transport.pojo.vo.ArchiveManageStatsVO;
import org.springblade.transport.service.IManageArchiveService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -53,6 +54,7 @@ import org.springframework.web.bind.annotation.RestController;
public class ManageArchiveController extends BladeController {
private final IManageArchiveService manageArchiveService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
@GetMapping("/stats")
@ApiOperationSupport(order = 1)
@@ -61,4 +63,12 @@ public class ManageArchiveController extends BladeController {
return R.data(manageArchiveService.stats());
}
// 本接口不挂 @PreAuth;无权限返回 false,不抛 403,供小程序决定空态。
@GetMapping("/capability")
@ApiOperationSupport(order = 2)
@Operation(summary = "档案管理能力判定", description = "当前登录是否为调度端管理员;false 时小程序显示无权限空态,不踢登录")
public R<Boolean> capability() {
return R.data(mpUserDataScopeSupport.isMpDispatcher());
}
}
@@ -0,0 +1,171 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.transport.pojo.entity.Driver;
import org.springblade.transport.pojo.vo.DriverVO;
import org.springblade.transport.service.IDriverService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.wrapper.DriverWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDate;
/**
* 调度端司机档案管理(小程序)
* <p>
* 对外完整路径:{@code /api/blade-transport/archive/manage/driver/**}
* (网关 StripPrefix 去掉 {@code blade-transport} 后落入 {@code /archive/manage/driver/**}),
* 同时兼容未去前缀直连。
* <p>
* 为什么不复用管理端的 {@code /driver/**}:那些接口挂在 {@code @PreAuth(menu = "driver")} 下,
* 小程序令牌的 roleId 是占位值 "0"、查不到 blade_role_menu,对任何小程序账号都必然被拒。
* 这里不挂 {@code @PreAuth},改成方法内显式判定管理员身份,且**权限不足必须回业务 403**:
* 小程序请求层把 401 一律当会话失效(清令牌 + 回登录页),回 401 会把越权访问变成被踢下线。
*
* @author Chill
*/
@RestController
@AllArgsConstructor
@RequestMapping({"/archive/manage/driver", "/blade-transport/archive/manage/driver"})
@Tag(name = "调度端司机档案", description = "小程序调度端司机管理")
public class ManageArchiveDriverController extends BladeController {
private static final int DEFAULT_CURRENT = 1;
private static final int DEFAULT_SIZE = 10;
private static final int MAX_SIZE = 100;
/**
* 权限不足的业务码。小程序请求层把 401 当会话失效,接口一律不得回 401。
*/
private static final int FORBIDDEN_CODE = 403;
private static final String FORBIDDEN_MSG = "仅调度端管理员可管理司机档案";
private final IDriverService driverService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
/**
* 分页
*/
@GetMapping("/page")
@ApiOperationSupport(order = 1)
@Operation(summary = "司机档案分页", description = "keyword 同时模糊匹配姓名与手机号;status 不传为启用停用全量;expireStatus 取 within30 / expired")
public R<IPage<DriverVO>> page(DriverVO driver, Query query) {
if (!canManage()) {
return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG);
}
fillExpiryDate(driver);
return R.data(driverService.selectDriverPage(Condition.getPage(normalizeQuery(query)), driver));
}
/**
* 详情
*/
@GetMapping("/detail")
@ApiOperationSupport(order = 2)
@Operation(summary = "司机档案详情")
public R<DriverVO> detail(@Parameter(description = "司机主键", required = true) @RequestParam Long id) {
if (!canManage()) {
return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG);
}
Driver query = new Driver();
query.setId(id);
return R.data(DriverWrapper.build().entityVO(driverService.getOne(Condition.getQueryWrapper(query))));
}
/**
* 新增或修改
*/
@PostMapping("/submit")
@ApiOperationSupport(order = 3)
@Operation(summary = "新增或修改司机档案", description = "直接写档案、立即生效,不生成待审快照")
public R submit(@Valid @RequestBody Driver driver) {
if (!canManage()) {
return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG);
}
// 生效指针只由认证审核流水写(通过才建立生效快照),不接受管理端写入
driver.setEffectiveCertRecordId(null);
return R.status(driverService.submit(driver));
}
/**
* 启停用
*/
@PostMapping("/status")
@ApiOperationSupport(order = 4)
@Operation(summary = "启用 / 停用司机", description = "status:1 启用,2 停用。停用司机不进调度检索,但仍可登录")
public R status(@Parameter(description = "主键", required = true) @RequestParam Long id,
@Parameter(description = "状态:1 启用,2 停用", required = true) @RequestParam Integer status) {
if (!canManage()) {
return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG);
}
return R.status(driverService.changeStatus(id, status));
}
private boolean canManage() {
return mpUserDataScopeSupport.isMpDispatcher();
}
private Query normalizeQuery(Query query) {
if (query == null) {
query = new Query();
}
if (query.getCurrent() == null || query.getCurrent() < DEFAULT_CURRENT) {
query.setCurrent(DEFAULT_CURRENT);
}
if (query.getSize() == null || query.getSize() <= 0) {
query.setSize(DEFAULT_SIZE);
}
if (query.getSize() > MAX_SIZE) {
query.setSize(MAX_SIZE);
}
return query;
}
private void fillExpiryDate(DriverVO driver) {
if (driver.getToday() == null) {
driver.setToday(LocalDate.now());
}
if (driver.getWarningDate() == null) {
driver.setWarningDate(driver.getToday().plusDays(30));
}
}
}
@@ -0,0 +1,169 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.transport.pojo.entity.TransportVehicle;
import org.springblade.transport.pojo.vo.TransportVehicleVO;
import org.springblade.transport.service.ITransportVehicleService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.wrapper.TransportVehicleWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDate;
/**
* 调度端车辆档案管理(小程序)
* <p>
* 对外完整路径:{@code /api/blade-transport/archive/manage/vehicle/**}
* (网关 StripPrefix 去掉 {@code blade-transport} 后落入 {@code /archive/manage/vehicle/**}),
* 同时兼容未去前缀直连。
* <p>
* 与管理端 {@code /transport-vehicle/**} 的关系、以及为什么必须回业务 403 而不是 401,
* 见 {@link ManageArchiveDriverController} 的类注释。
*
* @author Chill
*/
@RestController
@AllArgsConstructor
@RequestMapping({"/archive/manage/vehicle", "/blade-transport/archive/manage/vehicle"})
@Tag(name = "调度端车辆档案", description = "小程序调度端车辆管理")
public class ManageArchiveVehicleController extends BladeController {
private static final int DEFAULT_CURRENT = 1;
private static final int DEFAULT_SIZE = 10;
private static final int MAX_SIZE = 100;
/**
* 权限不足的业务码。小程序请求层把 401 当会话失效,接口一律不得回 401。
*/
private static final int FORBIDDEN_CODE = 403;
private static final String FORBIDDEN_MSG = "仅调度端管理员可管理车辆档案";
private final ITransportVehicleService transportVehicleService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
/**
* 分页
*/
@GetMapping("/page")
@ApiOperationSupport(order = 1)
@Operation(summary = "车辆档案分页", description = "keyword 同时模糊匹配车牌、VIN 与所有人;status 不传为启用停用全量;expireStatus 取 within30 / expired")
public R<IPage<TransportVehicleVO>> page(TransportVehicleVO vehicle, Query query) {
if (!canManage()) {
return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG);
}
fillExpiryDate(vehicle);
return R.data(transportVehicleService.selectTransportVehiclePage(Condition.getPage(normalizeQuery(query)), vehicle));
}
/**
* 详情
*/
@GetMapping("/detail")
@ApiOperationSupport(order = 2)
@Operation(summary = "车辆档案详情")
public R<TransportVehicleVO> detail(@Parameter(description = "车辆主键", required = true) @RequestParam Long id) {
if (!canManage()) {
return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG);
}
TransportVehicle query = new TransportVehicle();
query.setId(id);
return R.data(TransportVehicleWrapper.build().entityVO(transportVehicleService.getOne(Condition.getQueryWrapper(query))));
}
/**
* 新增或修改
*/
@PostMapping("/submit")
@ApiOperationSupport(order = 3)
@Operation(summary = "新增或修改车辆档案", description = "直接写档案、立即生效,不生成待审快照,也不改动认证状态")
public R submit(@Valid @RequestBody TransportVehicle vehicle) {
if (!canManage()) {
return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG);
}
// 生效指针只由认证审核流水写(通过才建立生效快照),不接受管理端写入
vehicle.setEffectiveCertRecordId(null);
return R.status(transportVehicleService.submit(vehicle));
}
/**
* 启停用
*/
@PostMapping("/status")
@ApiOperationSupport(order = 4)
@Operation(summary = "启用 / 停用车辆", description = "status:1 启用,2 停用。停用车辆不进车辆检索、司机端车辆列表不再出现")
public R status(@Parameter(description = "主键", required = true) @RequestParam Long id,
@Parameter(description = "状态:1 启用,2 停用", required = true) @RequestParam Integer status) {
if (!canManage()) {
return R.fail(FORBIDDEN_CODE, FORBIDDEN_MSG);
}
return R.status(transportVehicleService.changeStatus(id, status));
}
private boolean canManage() {
return mpUserDataScopeSupport.isMpDispatcher();
}
private Query normalizeQuery(Query query) {
if (query == null) {
query = new Query();
}
if (query.getCurrent() == null || query.getCurrent() < DEFAULT_CURRENT) {
query.setCurrent(DEFAULT_CURRENT);
}
if (query.getSize() == null || query.getSize() <= 0) {
query.setSize(DEFAULT_SIZE);
}
if (query.getSize() > MAX_SIZE) {
query.setSize(MAX_SIZE);
}
return query;
}
private void fillExpiryDate(TransportVehicleVO vehicle) {
if (vehicle.getToday() == null) {
vehicle.setToday(LocalDate.now());
}
if (vehicle.getWarningDate() == null) {
vehicle.setWarningDate(vehicle.getToday().plusDays(30));
}
}
}
@@ -147,6 +147,11 @@
<sql id="QueryCondition">
is_deleted = 0
<!-- 小程序列表的单框搜索:姓名或手机号命中其一即可。管理端不传 keyword,条件不生效。 -->
<if test="driver.keyword != null and driver.keyword != ''">
<bind name="driverKeywordLike" value="'%' + driver.keyword + '%'"/>
AND (driver_name LIKE #{driverKeywordLike} OR mobile LIKE #{driverKeywordLike})
</if>
<if test="driver.driverName != null and driver.driverName != ''">
<bind name="driverNameLike" value="'%' + driver.driverName + '%'"/>
AND driver_name LIKE #{driverNameLike}
@@ -116,6 +116,11 @@
<sql id="QueryCondition">
is_deleted = 0
<!-- 小程序列表的单框搜索:车牌、VIN 或所有人命中其一即可。管理端不传 keyword,条件不生效。 -->
<if test="vehicle.keyword != null and vehicle.keyword != ''">
<bind name="vehicleKeywordLike" value="'%' + vehicle.keyword + '%'"/>
AND (plate_no LIKE #{vehicleKeywordLike} OR vin LIKE #{vehicleKeywordLike} OR owner_name LIKE #{vehicleKeywordLike})
</if>
<if test="vehicle.organizationName != null and vehicle.organizationName != ''">
<!-- 「未分配组织」哨兵值定义在 TransportBusiConstant.UNASSIGNED_ORG_FILTER 与前端
tms-erp-web/src/mixins/organization-search.js,改这里必须同步常量与 DriverMapper.xml 的同名分支。
@@ -52,6 +52,12 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class DriverAppServiceImpl implements IDriverAppService {
/**
* 启用状态。框架基类只有 status 列与 BladeConstant.DB_STATUS_NORMAL = 1,没有枚举;
* 2 是「停用」,业务侧一律写字面量常量。
*/
private static final int ENABLED_STATUS = 1;
private final IDriverService driverService;
private final ITransportVehicleService transportVehicleService;
@@ -74,6 +80,8 @@ public class DriverAppServiceImpl implements IDriverAppService {
// 精确 + 规范化双条件匹配,统一走共享实现(避免各调用点各自实现而漏掉写法差异)
LambdaQueryWrapper<TransportVehicle> plateQuery = Wrappers.lambdaQuery();
VehiclePlateSupport.applyPlateMatch(plateQuery, plates);
// 停用车辆不再出现在司机端:管理员停用即意味着这辆车不该再被司机拿去接单
plateQuery.eq(TransportVehicle::getStatus, ENABLED_STATUS);
List<TransportVehicle> vehicles = transportVehicleService.list(
plateQuery.orderByDesc(TransportVehicle::getUpdateTime));
// 去重(精确与规范化可能命中同一条)
@@ -271,6 +271,10 @@ public class DriverCertServiceImpl implements IDriverCertService {
if (!VehiclePlateSupport.isBound(requireCurrentDriver().getDrivingVehicle(), vehicle.getPlateNo())) {
throw new ServiceException("无权查看该车辆的认证信息");
}
// 停用车辆不再对司机端开放:司机列表里已经不显示它,详情/认证入口也不该能进
if (Func.isNotEmpty(vehicle.getStatus()) && vehicle.getStatus() != 1) {
throw new ServiceException("该车辆已停用,暂不可查看,请联系调度启用");
}
TransportCertRecord effective = findEffectiveRecord(vehicle.getEffectiveCertRecordId());
return effective == null
? null
@@ -408,6 +412,7 @@ public class DriverCertServiceImpl implements IDriverCertService {
if (!VehiclePlateSupport.isBound(driver.getDrivingVehicle(), vehicle.getPlateNo())) {
throw new ServiceException("无权操作该车辆的认证");
}
requireEnabledVehicle(vehicle);
return vehicle;
}
@@ -428,11 +433,24 @@ public class DriverCertServiceImpl implements IDriverCertService {
// 已登记但不在本人名下,可能属于其他司机 —— 拒绝,不能顺手改归属
throw new ServiceException("该车牌已归属其他司机,请联系管理员");
}
requireEnabledVehicle(existing);
return existing;
}
return createVehicleForDriver(driver, request, normalized);
}
/**
* 停用车辆不接受司机的认证提交
* <p>
* 停用是调度端的管理动作(车辆停驶、报废待办),司机端列表已不再显示停用车;
* 若这里放行,司机仍能凭车牌把停用车提交认证,等于绕过停用。
*/
private void requireEnabledVehicle(TransportVehicle vehicle) {
if (vehicle != null && Func.isNotEmpty(vehicle.getStatus()) && vehicle.getStatus() != 1) {
throw new ServiceException("该车辆已停用,请联系调度启用后再认证");
}
}
/**
* 按车牌(容忍写法差异)查已登记的车辆
*
@@ -38,6 +38,7 @@ import org.springblade.transport.pojo.entity.Driver;
import org.springblade.transport.pojo.entity.ExceptionDisposal;
import org.springblade.transport.pojo.entity.LoadingManage;
import org.springblade.transport.pojo.entity.RiskDisposal;
import org.springblade.transport.pojo.entity.TransportVehicle;
import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.AdminDriverOptionVO;
import org.springblade.transport.pojo.vo.AdminHomeBadgesVO;
@@ -58,6 +59,7 @@ import org.springblade.transport.service.IExceptionDisposalService;
import org.springblade.transport.service.ILoadingManageService;
import org.springblade.transport.service.IManageWaybillService;
import org.springblade.transport.service.IRiskDisposalService;
import org.springblade.transport.service.ITransportVehicleService;
import org.springblade.transport.service.ITripQueryService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.MpUserDataScopeSupport;
@@ -122,6 +124,9 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
private static final String TRANSPORT_COMMON = "common";
private static final String TRANSPORT_LOAD = "load";
/** 启用状态(base_entity.status):停用的司机不进派单检索、停用的车不进选车列表 */
private static final int ENABLED_STATUS = 1;
private static final int FEED_LIMIT = 20;
private static final int DEFAULT_PAGE_SIZE = 10;
private static final int MAX_PAGE_SIZE = 50;
@@ -141,6 +146,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
private final IDriverWaybillService driverWaybillService;
private final ITripQueryService tripQueryService;
private final ILoadingManageService loadingManageService;
private final ITransportVehicleService transportVehicleService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
@Override
@@ -859,6 +865,8 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
String key = Func.toStr(keyword, "").trim();
LambdaQueryWrapper<Driver> wrapper = Wrappers.<Driver>lambdaQuery()
.eq(Driver::getIsDeleted, 0)
// 停用司机不可派单:不进调度检索。停用只拦派单,不拦登录(司机仍能进小程序看自己的运单)
.eq(Driver::getStatus, ENABLED_STATUS)
.orderByDesc(Driver::getUpdateTime)
.last("LIMIT 20");
if (Func.isNotBlank(key)) {
@@ -877,27 +885,63 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
@Override
public List<AdminVehicleOptionVO> searchVehicles(String keyword) {
String key = Func.toStr(keyword, "").trim();
LambdaQueryWrapper<Driver> wrapper = Wrappers.<Driver>lambdaQuery()
.eq(Driver::getIsDeleted, 0)
.isNotNull(Driver::getDrivingVehicle)
.ne(Driver::getDrivingVehicle, "")
.orderByDesc(Driver::getUpdateTime)
// 权威来源是车辆档案表:此前查的是司机表的 driving_vehicle 文本,既看不见没有司机绑定的车,
// 也无从判断「是否停用」——停用的车会照样出现在派单选车列表里。
LambdaQueryWrapper<TransportVehicle> wrapper = Wrappers.<TransportVehicle>lambdaQuery()
.eq(TransportVehicle::getIsDeleted, 0)
.eq(TransportVehicle::getStatus, ENABLED_STATUS)
.orderByDesc(TransportVehicle::getUpdateTime)
.last("LIMIT 30");
if (Func.isNotBlank(key)) {
wrapper.like(Driver::getDrivingVehicle, key);
wrapper.like(TransportVehicle::getPlateNo, key);
}
java.util.LinkedHashMap<String, AdminVehicleOptionVO> map = new java.util.LinkedHashMap<>();
for (Driver d : driverService.list(wrapper)) {
String plate = Func.toStr(d.getDrivingVehicle(), "").trim();
if (Func.isBlank(plate) || map.containsKey(plate)) {
List<TransportVehicle> vehicles = transportVehicleService.list(wrapper);
if (Func.isEmpty(vehicles)) {
return List.of();
}
Map<String, String> driverNameByPlate = driverNamesByPlate(vehicles);
List<AdminVehicleOptionVO> options = new ArrayList<>();
for (TransportVehicle vehicle : vehicles) {
String plate = Func.toStr(vehicle.getPlateNo(), "").trim();
if (Func.isBlank(plate)) {
continue;
}
AdminVehicleOptionVO vo = new AdminVehicleOptionVO();
vo.setVehicleNo(plate);
vo.setDriverName(Func.toStr(d.getDriverName(), ""));
map.put(plate, vo);
vo.setDriverName(Func.toStr(driverNameByPlate.get(plate.toUpperCase()), ""));
options.add(vo);
}
return new java.util.ArrayList<>(map.values());
return options;
}
/**
* 车牌 → 绑定司机姓名
* <p>
* 绑定关系是司机侧按车牌文本声明的({@code driver.driving_vehicle}),车辆行不存司机,
* 所以选车列表要回到司机表补名字;多车牌串的司机匹配不到,这条既有缺陷不在本次改动内。
*/
private Map<String, String> driverNamesByPlate(List<TransportVehicle> vehicles) {
List<String> plates = vehicles.stream()
.map(TransportVehicle::getPlateNo)
.filter(Func::isNotEmpty)
.map(String::toUpperCase)
.distinct()
.toList();
if (plates.isEmpty()) {
return Map.of();
}
List<Driver> drivers = driverService.list(Wrappers.<Driver>lambdaQuery()
.eq(Driver::getIsDeleted, 0)
.in(Driver::getDrivingVehicle, plates)
.select(Driver::getDriverName, Driver::getDrivingVehicle));
Map<String, String> names = new LinkedHashMap<>();
for (Driver driver : drivers) {
if (Func.isEmpty(driver.getDrivingVehicle()) || Func.isEmpty(driver.getDriverName())) {
continue;
}
names.putIfAbsent(driver.getDrivingVehicle().toUpperCase(), driver.getDriverName());
}
return names;
}
/** 运输方式字典值 → 展示文案 */
@@ -71,7 +71,6 @@ public class TransportVehicleServiceImpl extends BaseServiceImpl<TransportVehicl
private static final int REMARK_MAX_LENGTH = 200;
private static final int DEFAULT_ENABLED_STATUS = 1;
private static final int DEFAULT_FALSE = 0;
private static final int CERTIFICATION_PENDING = 0;
private static final int CERTIFICATION_APPROVED = 1;
private static final int CERTIFICATION_REJECTED = 2;
@@ -133,8 +132,9 @@ public class TransportVehicleServiceImpl extends BaseServiceImpl<TransportVehicl
prepare(vehicle);
validate(vehicle);
checkUniquePlateNo(vehicle);
vehicle.setCertificationStatus(CERTIFICATION_PENDING);
vehicle.setCertificationRejectReason(null);
// 保存档案不碰认证状态:认证状态与生效快照只由认证审核流水维护。
// 这里曾强制置 certification_status = 0(审核中),于是管理端每存一次档案,
// 已认证车辆就被改回「审核中」,而审核页并没有对应的待审记录——一个退不出去的死状态。
return saveOrUpdate(vehicle);
}