Files
oneos-backend/docs/superpowers/plans/2026-03-13-rental-full-chain.md
kkfluous d5c3ed373f docs: add rental full-chain implementation plan
27 tasks across 6 chunks: inspection template system, prepare simplification,
delivery enhancement, replacement vehicle module, return order improvements,
BPM flows, event-driven linkage, and frontend changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:53:59 +08:00

46 KiB
Raw Blame History

租赁业务全链路实施计划

For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 让租赁全链路合同→备车→交车→替换车→还车按原型跑通含共享验车模板、BPM审批、事件驱动联动。

Architecture: 共享验车模板系统4张表放在 asset 模块,备车/交车/还车通过 inspection_record_id 关联。替换车模块从零构建。跨模块联动通过 Spring Event 解耦。BPM 审批继承现有 BpmProcessInstanceStatusEventListener 模式。

Tech Stack: Spring Boot, MyBatis-Plus (BaseMapperX, LambdaQueryWrapperX), MapStruct, Flowable BPM, Vue 3 + TypeScript + Ant Design Vue + VXE Table

Spec: docs/superpowers/specs/2026-03-13-rental-full-chain-design.md

Key paths:

  • Backend base: yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/ (below as $BE)
  • Backend API module: yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/ (below as $API)
  • Frontend base: ../oneos-frontend/apps/web-antd/src/ (below as $FE)
  • DB: 连接信息见 application-local.yaml 配置
  • ErrorCodeConstants 位于 $BE/enums/ErrorCodeConstants.javaserver 模块,非 API 模块)
  • 所有 DO 类遵循 ContractDO 注解模式:@TableName, @KeySequence, @Data, @EqualsAndHashCode(callSuper = true), @ToString(callSuper = true), @Builder, @NoArgsConstructor, @AllArgsConstructor, extends BaseDO

Chunk 1: 数据库 + 验车模板后端

Task 1: SQL 建表 — 验车模板 4 张表

Files:

  • Create: sql/2026-03-13-inspection-tables.sql

  • Step 1: 编写 SQL

-- 验车模板定义
CREATE TABLE IF NOT EXISTS `asset_inspection_template` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `code` varchar(50) NOT NULL COMMENT '模板编码',
  `name` varchar(100) NOT NULL COMMENT '模板名称',
  `biz_type` tinyint NOT NULL COMMENT '适用业务1=备车 2=交车 3=还车',
  `vehicle_type` varchar(50) DEFAULT NULL COMMENT '适用车辆类型nullable=通用)',
  `status` tinyint NOT NULL DEFAULT 1 COMMENT '0=禁用 1=启用',
  `remark` varchar(500) DEFAULT NULL,
  `creator` varchar(64) DEFAULT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updater` varchar(64) DEFAULT '',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted` bit(1) NOT NULL DEFAULT b'0',
  `tenant_id` bigint NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='验车模板定义';

-- 验车模板检查项
CREATE TABLE IF NOT EXISTS `asset_inspection_template_item` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `template_id` bigint NOT NULL COMMENT '关联模板',
  `category` varchar(50) NOT NULL COMMENT '分类',
  `item_name` varchar(100) NOT NULL COMMENT '检查项名称',
  `item_code` varchar(50) NOT NULL COMMENT '检查项编码',
  `input_type` varchar(20) NOT NULL DEFAULT 'checkbox' COMMENT '输入类型checkbox/number/text',
  `sort` int NOT NULL DEFAULT 0,
  `required` tinyint NOT NULL DEFAULT 1 COMMENT '0=否 1=是',
  `creator` varchar(64) DEFAULT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updater` varchar(64) DEFAULT '',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted` bit(1) NOT NULL DEFAULT b'0',
  `tenant_id` bigint NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_template_id` (`template_id`)
) ENGINE=InnoDB COMMENT='验车模板检查项';

-- 验车记录
CREATE TABLE IF NOT EXISTS `asset_inspection_record` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `record_code` varchar(50) NOT NULL COMMENT '记录编码',
  `template_id` bigint NOT NULL COMMENT '使用的模板',
  `source_type` tinyint NOT NULL COMMENT '来源1=备车 2=交车 3=还车',
  `source_id` bigint NOT NULL COMMENT '来源业务ID',
  `vehicle_id` bigint NOT NULL COMMENT '车辆ID',
  `inspector_name` varchar(50) DEFAULT NULL COMMENT '检查人',
  `inspection_time` datetime DEFAULT NULL COMMENT '检查时间',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '0=待检查 1=检查中 2=已完成',
  `overall_result` tinyint DEFAULT NULL COMMENT '1=合格 2=不合格',
  `remark` varchar(500) DEFAULT NULL,
  `cloned_from_id` bigint DEFAULT NULL COMMENT '克隆来源记录ID',
  `creator` varchar(64) DEFAULT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updater` varchar(64) DEFAULT '',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted` bit(1) NOT NULL DEFAULT b'0',
  `tenant_id` bigint NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_vehicle_source` (`vehicle_id`, `source_type`)
) ENGINE=InnoDB COMMENT='验车记录';

-- 验车记录检查项结果
CREATE TABLE IF NOT EXISTS `asset_inspection_record_item` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `record_id` bigint NOT NULL COMMENT '关联记录',
  `item_code` varchar(50) NOT NULL COMMENT '检查项编码',
  `category` varchar(50) NOT NULL COMMENT '分类',
  `item_name` varchar(100) NOT NULL COMMENT '检查项名称',
  `input_type` varchar(20) NOT NULL DEFAULT 'checkbox' COMMENT '输入类型',
  `result` tinyint DEFAULT NULL COMMENT '1=合格 2=不合格 3=不适用',
  `value` varchar(200) DEFAULT NULL COMMENT '数值/文本输入值',
  `remark` varchar(500) DEFAULT NULL,
  `image_urls` varchar(2000) DEFAULT NULL COMMENT '图片URL JSON数组',
  `creator` varchar(64) DEFAULT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updater` varchar(64) DEFAULT '',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted` bit(1) NOT NULL DEFAULT b'0',
  `tenant_id` bigint NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_record_id` (`record_id`)
) ENGINE=InnoDB COMMENT='验车记录检查项结果';
  • Step 2: 执行 SQL

通过 PyMySQL 连接数据库执行(连接信息见 application-local.yaml

  • Step 3: Commit
git add sql/2026-03-13-inspection-tables.sql
git commit -m "sql: create inspection template and record tables"

Task 2: 验车模板枚举类

Files:

  • Create: $API/enums/inspection/InspectionSourceTypeEnum.java

  • Create: $API/enums/inspection/InspectionStatusEnum.java

  • Create: $API/enums/inspection/InspectionResultEnum.java

  • Step 1: 创建 InspectionSourceTypeEnum

package cn.iocoder.yudao.module.asset.enums.inspection;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum InspectionSourceTypeEnum {
    PREPARE(1, "备车"),
    DELIVERY(2, "交车"),
    RETURN(3, "还车");

    private final Integer type;
    private final String name;
}
  • Step 2: 创建 InspectionStatusEnum
package cn.iocoder.yudao.module.asset.enums.inspection;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum InspectionStatusEnum {
    PENDING(0, "待检查"),
    IN_PROGRESS(1, "检查中"),
    COMPLETED(2, "已完成");

    private final Integer status;
    private final String name;
}
  • Step 3: 创建 InspectionResultEnum
package cn.iocoder.yudao.module.asset.enums.inspection;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum InspectionResultEnum {
    PASS(1, "合格"),
    FAIL(2, "不合格"),
    NA(3, "不适用");

    private final Integer result;
    private final String name;
}
  • Step 4: Commit
git add yudao-module-asset/yudao-module-asset-api/
git commit -m "feat: add inspection enums"

Task 3: 验车模板 DO + Mapper

Files:

  • Create: $BE/dal/dataobject/inspection/InspectionTemplateDO.java

  • Create: $BE/dal/dataobject/inspection/InspectionTemplateItemDO.java

  • Create: $BE/dal/dataobject/inspection/InspectionRecordDO.java

  • Create: $BE/dal/dataobject/inspection/InspectionRecordItemDO.java

  • Create: $BE/dal/mysql/inspection/InspectionTemplateMapper.java

  • Create: $BE/dal/mysql/inspection/InspectionTemplateItemMapper.java

  • Create: $BE/dal/mysql/inspection/InspectionRecordMapper.java

  • Create: $BE/dal/mysql/inspection/InspectionRecordItemMapper.java

  • Step 1: 创建 InspectionTemplateDO

Follow ContractDO pattern: @TableName("asset_inspection_template"), @KeySequence("asset_inspection_template_seq"), @Data, @EqualsAndHashCode(callSuper = true), @Builder, @NoArgsConstructor, @AllArgsConstructor, extends BaseDO.

Fields: id(@TableId), code, name, bizType(Integer), vehicleType, status(Integer), remark.

  • Step 2: 创建 InspectionTemplateItemDO

@TableName("asset_inspection_template_item"). Fields: id, templateId, category, itemName, itemCode, inputType, sort(Integer), required(Integer).

  • Step 3: 创建 InspectionRecordDO

@TableName("asset_inspection_record"). Fields: id, recordCode, templateId, sourceType(Integer), sourceId, vehicleId, inspectorName, inspectionTime(LocalDateTime), status(Integer), overallResult(Integer), remark, clonedFromId.

  • Step 4: 创建 InspectionRecordItemDO

@TableName("asset_inspection_record_item"). Fields: id, recordId, itemCode, category, itemName, inputType, result(Integer), value, remark, imageUrls.

  • Step 5: 创建 4 个 Mapper

Each extends BaseMapperX<XxxDO> with @Mapper annotation.

InspectionTemplateMapper — add selectPage + match methods:

default PageResult<InspectionTemplateDO> selectPage(InspectionTemplatePageReqVO reqVO) {
    return selectPage(reqVO, new LambdaQueryWrapperX<InspectionTemplateDO>()
            .likeIfPresent(InspectionTemplateDO::getCode, reqVO.getCode())
            .likeIfPresent(InspectionTemplateDO::getName, reqVO.getName())
            .eqIfPresent(InspectionTemplateDO::getBizType, reqVO.getBizType())
            .eqIfPresent(InspectionTemplateDO::getStatus, reqVO.getStatus())
            .orderByDesc(InspectionTemplateDO::getId));
}

default InspectionTemplateDO selectByBizTypeAndVehicleType(Integer bizType, String vehicleType) {
    return selectOne(new LambdaQueryWrapperX<InspectionTemplateDO>()
            .eq(InspectionTemplateDO::getBizType, bizType)
            .eqIfPresent(InspectionTemplateDO::getVehicleType, vehicleType)
            .eq(InspectionTemplateDO::getStatus, 1)
            .orderByDesc(InspectionTemplateDO::getId)
            .last("LIMIT 1"));
}

default InspectionTemplateDO selectDefaultByBizType(Integer bizType) {
    return selectOne(new LambdaQueryWrapperX<InspectionTemplateDO>()
            .eq(InspectionTemplateDO::getBizType, bizType)
            .isNull(InspectionTemplateDO::getVehicleType)
            .eq(InspectionTemplateDO::getStatus, 1)
            .orderByDesc(InspectionTemplateDO::getId)
            .last("LIMIT 1"));
}

InspectionTemplateItemMapper — add:

default List<InspectionTemplateItemDO> selectByTemplateId(Long templateId) {
    return selectList(new LambdaQueryWrapperX<InspectionTemplateItemDO>()
            .eq(InspectionTemplateItemDO::getTemplateId, templateId)
            .orderByAsc(InspectionTemplateItemDO::getSort));
}

InspectionRecordMapper — add:

default InspectionRecordDO selectLatestByVehicleAndSourceType(Long vehicleId, Integer sourceType) {
    return selectOne(new LambdaQueryWrapperX<InspectionRecordDO>()
            .eq(InspectionRecordDO::getVehicleId, vehicleId)
            .eq(InspectionRecordDO::getSourceType, sourceType)
            .eq(InspectionRecordDO::getStatus, 2) // 已完成
            .orderByDesc(InspectionRecordDO::getId)
            .last("LIMIT 1"));
}

InspectionRecordItemMapper — add:

default List<InspectionRecordItemDO> selectByRecordId(Long recordId) {
    return selectList(new LambdaQueryWrapperX<InspectionRecordItemDO>()
            .eq(InspectionRecordItemDO::getRecordId, recordId)
            .orderByAsc(InspectionRecordItemDO::getId));
}

default void deleteByRecordId(Long recordId) {
    delete(new LambdaQueryWrapperX<InspectionRecordItemDO>()
            .eq(InspectionRecordItemDO::getRecordId, recordId));
}
  • Step 6: Compile 验证
mvn compile -pl yudao-module-asset/yudao-module-asset-server -am -DskipTests
  • Step 7: Commit
git add yudao-module-asset/
git commit -m "feat: add inspection template and record DO/Mapper"

Task 4: 验车模板 VO + Convert

Files:

  • Create: $BE/controller/admin/inspection/vo/InspectionTemplateSaveReqVO.java

  • Create: $BE/controller/admin/inspection/vo/InspectionTemplateRespVO.java

  • Create: $BE/controller/admin/inspection/vo/InspectionTemplatePageReqVO.java

  • Create: $BE/controller/admin/inspection/vo/InspectionTemplateItemVO.java

  • Create: $BE/controller/admin/inspection/vo/InspectionRecordDetailVO.java

  • Create: $BE/controller/admin/inspection/vo/InspectionRecordItemUpdateReqVO.java

  • Create: $BE/convert/inspection/InspectionConvert.java

  • Step 1: 创建 VO 类

InspectionTemplateSaveReqVO — Fields: id(Long), code(@NotBlank), name(@NotBlank), bizType(@NotNull Integer), vehicleType, status(Integer), remark, items(List).

InspectionTemplateItemVO — Fields: id, category(@NotBlank), itemName(@NotBlank), itemCode(@NotBlank), inputType, sort, required.

InspectionTemplateRespVO — Fields: id, code, name, bizType, vehicleType, status, remark, createTime, items(List).

InspectionTemplatePageReqVO extends PageParam — Fields: code, name, bizType, vehicleType, status.

InspectionRecordDetailVO — Fields: id, recordCode, templateId, sourceType, sourceId, vehicleId, inspectorName, inspectionTime, status, overallResult, remark, clonedFromId, createTime, items(List).

Inner InspectionRecordItemVO — Fields: id, itemCode, category, itemName, inputType, result, value, remark, imageUrls.

InspectionRecordItemUpdateReqVO — Fields: id(@NotNull), result(Integer), value, remark, imageUrls.

  • Step 2: 创建 InspectionConvert
@Mapper
public interface InspectionConvert {
    InspectionConvert INSTANCE = Mappers.getMapper(InspectionConvert.class);

    InspectionTemplateDO convert(InspectionTemplateSaveReqVO bean);
    InspectionTemplateRespVO convert(InspectionTemplateDO bean);
    PageResult<InspectionTemplateRespVO> convertPage(PageResult<InspectionTemplateDO> page);
    InspectionTemplateItemDO convertItem(InspectionTemplateItemVO bean);
    List<InspectionTemplateItemDO> convertItemList(List<InspectionTemplateItemVO> list);
    List<InspectionTemplateItemVO> convertItemVOList(List<InspectionTemplateItemDO> list);
    InspectionRecordDetailVO convertRecord(InspectionRecordDO bean);
    InspectionRecordDetailVO.InspectionRecordItemVO convertRecordItem(InspectionRecordItemDO bean);
    List<InspectionRecordDetailVO.InspectionRecordItemVO> convertRecordItemList(List<InspectionRecordItemDO> list);
}
  • Step 3: Compile 验证
  • Step 4: Commit

Task 5: 验车记录 Service

Files:

  • Create: $BE/service/inspection/InspectionTemplateService.java

  • Create: $BE/service/inspection/InspectionTemplateServiceImpl.java

  • Create: $BE/service/inspection/InspectionRecordService.java

  • Create: $BE/service/inspection/InspectionRecordServiceImpl.java

  • Step 1: 创建 InspectionTemplateService 接口

Methods: createTemplate, updateTemplate, deleteTemplate, getTemplate, getTemplatePage, matchTemplate(Integer bizType, String vehicleType).

  • Step 2: 实现 InspectionTemplateServiceImpl

matchTemplate 逻辑: 先按 bizType+vehicleType 精确匹配 → 无结果则 bizType+vehicleType IS NULL 匹配通用模板。CRUD 标准实现。保存时同步保存/更新 items先删后插

  • Step 3: 创建 InspectionRecordService 接口
public interface InspectionRecordService {
    Long createRecord(Long templateId, Integer sourceType, Long sourceId, Long vehicleId);
    Long cloneRecord(Long sourceRecordId, Integer newSourceType, Long newSourceId);
    void updateRecordItem(InspectionRecordItemUpdateReqVO updateReqVO);
    void completeRecord(Long recordId, String inspectorName);
    InspectionRecordDetailVO getRecordDetail(Long recordId);
    InspectionRecordDO getLatestRecord(Long vehicleId, Integer sourceType);
}
  • Step 4: 实现 InspectionRecordServiceImpl

createRecord: 根据 templateId 查询所有 item → 创建 record生成 recordCode → 批量创建 record_item从 template_item 复制 code/category/name/inputTyperesult/value 为 null

recordCode 生成: {prefix}-{yyyyMMdd}-{seq}prefix 根据 sourceType 决定BC/JC/HC

cloneRecord: 查源 record + items → 创建新 recordclonedFromId=源id新 sourceType/sourceId→ 复制所有 items保留 result/value/remark/imageUrls

completeRecord: 校验 record 存在 → 更新 status=2, inspectorName, inspectionTime=now()。检查必填项是否都有 result没有则抛异常。

getRecordDetail: 查 record + items → 组装 VO。

  • Step 5: Compile 验证
  • Step 6: Commit

Task 6: 验车模板 Controller + 错误码

Files:

  • Create: $BE/controller/admin/inspection/InspectionTemplateController.java

  • Create: $BE/controller/admin/inspection/InspectionRecordController.java

  • Modify: $BE/enums/ErrorCodeConstants.java

  • Step 1: 添加错误码

ErrorCodeConstants.java 末尾追加:

// ========== 验车模板管理 1-008-011-000 ==========
ErrorCode INSPECTION_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_011_000, "验车模板不存在");
ErrorCode INSPECTION_RECORD_NOT_EXISTS = new ErrorCode(1_008_011_001, "验车记录不存在");
ErrorCode INSPECTION_RECORD_ALREADY_COMPLETED = new ErrorCode(1_008_011_002, "验车记录已完成");
ErrorCode INSPECTION_RECORD_REQUIRED_ITEMS_INCOMPLETE = new ErrorCode(1_008_011_003, "必填检查项未全部完成");
ErrorCode INSPECTION_TEMPLATE_MATCH_FAILED = new ErrorCode(1_008_011_004, "未找到匹配的验车模板");

// ========== 替换车管理 1-008-012-000 ==========
ErrorCode VEHICLE_REPLACEMENT_NOT_EXISTS = new ErrorCode(1_008_012_000, "替换车申请不存在");
ErrorCode VEHICLE_REPLACEMENT_STATUS_NOT_ALLOW_UPDATE = new ErrorCode(1_008_012_001, "当前状态不允许修改");
ErrorCode VEHICLE_REPLACEMENT_STATUS_NOT_ALLOW_DELETE = new ErrorCode(1_008_012_002, "当前状态不允许删除");
ErrorCode VEHICLE_REPLACEMENT_STATUS_NOT_ALLOW_SUBMIT = new ErrorCode(1_008_012_003, "当前状态不允许提交审批");
ErrorCode VEHICLE_REPLACEMENT_STATUS_NOT_ALLOW_WITHDRAW = new ErrorCode(1_008_012_004, "当前状态不允许撤回");
ErrorCode VEHICLE_REPLACEMENT_STATUS_NOT_ALLOW_CONFIRM = new ErrorCode(1_008_012_005, "当前状态不允许确认换回");
  • Step 2: 创建 InspectionTemplateController

@RequestMapping("/asset/inspection-template")。标准 CRUD 端点create, update, delete, get, page。权限前缀 asset:inspection-template:xxx

  • Step 3: 创建 InspectionRecordController

@RequestMapping("/asset/inspection-record")。端点:

  • GET /get?id= — 获取详情

  • PUT /update-item — 更新检查项结果

  • POST /complete?id=&inspectorName= — 完成验车

  • Step 4: Compile 验证

  • Step 5: Commit

git add yudao-module-asset/
git commit -m "feat: add inspection template/record service and controller"

Chunk 2: 备车精简 + 交车增强(后端)

Task 7: 备车表结构变更 SQL

Files:

  • Create: sql/2026-03-13-prepare-simplify.sql

  • Step 1: 编写并执行 SQL

-- 备车表新增 inspection_record_id移除废弃字段
ALTER TABLE asset_vehicle_prepare
  ADD COLUMN inspection_record_id bigint DEFAULT NULL COMMENT '关联验车记录' AFTER defect_photos;

ALTER TABLE asset_vehicle_prepare
  DROP COLUMN IF EXISTS check_list,
  DROP COLUMN IF EXISTS contract_id,
  DROP COLUMN IF EXISTS contract_code,
  DROP COLUMN IF EXISTS enlarged_text_photo;

注意:DROP COLUMN IF EXISTS 在 MySQL 8.0.13+ 支持。若版本较低,先用 SHOW COLUMNS 检查后再 DROP。

  • Step 2: Commit

Task 8: 更新 VehiclePrepareDO + Mapper + Service

Files:

  • Modify: $BE/dal/dataobject/prepare/VehiclePrepareDO.java — 移除 contractId, contractCode, checkList, enlargedTextPhoto,新增 inspectionRecordId

  • Modify: $BE/dal/mysql/prepare/VehiclePrepareMapper.java — 移除 contractId 相关查询条件

  • Modify: $BE/service/prepare/VehiclePrepareServiceImpl.java — createPrepare 时调用 InspectionRecordService.createRecord()completePrepare 时调用 completeRecord()

  • Modify: $BE/controller/admin/prepare/vo/ — 对应 VO 移除废弃字段,新增 inspectionRecordId

  • Step 1: 更新 VehiclePrepareDO

删除字段: contractId, contractCode, checkList, enlargedTextPhoto。 新增字段: private Long inspectionRecordId;

  • Step 2: 更新 VOs

SaveReqVO/RespVO 同步移除废弃字段,新增 inspectionRecordId

  • Step 3: 更新 Service — 创建时生成验车记录

VehiclePrepareServiceImpl.createPrepare() 方法中,vehiclePrepareMapper.insert(prepare) 之后添加:

@Resource
private InspectionRecordService inspectionRecordService;
@Resource
private InspectionTemplateService inspectionTemplateService;

// 创建备车单后,自动创建验车记录
InspectionTemplateDO template = inspectionTemplateService.matchTemplate(
    InspectionSourceTypeEnum.PREPARE.getType(), vehicleType);
if (template != null) {
    Long recordId = inspectionRecordService.createRecord(
        template.getId(), InspectionSourceTypeEnum.PREPARE.getType(),
        prepare.getId(), prepare.getVehicleId());
    prepare.setInspectionRecordId(recordId);
    vehiclePrepareMapper.updateById(prepare);
}
  • Step 4: Compile 验证
  • Step 5: Commit

Task 9: 交车表结构变更 + 后端更新

Files:

  • Create: sql/2026-03-13-delivery-order-inspection.sql

  • Modify: $BE/dal/dataobject/delivery/DeliveryOrderDO.java — 新增 inspectionRecordId

  • Modify: $BE/service/delivery/DeliveryOrderServiceImpl.java — 创建交车单时克隆备车验车记录

  • Step 1: SQL

ALTER TABLE asset_delivery_order
  ADD COLUMN inspection_record_id bigint DEFAULT NULL COMMENT '关联验车记录' AFTER cost_list;
  • Step 2: 更新 DeliveryOrderDO

新增 private Long inspectionRecordId;。不删除 inspectionData 字段(废弃但保留兼容)。

注意: 当前交车单结构中 inspectionData 在主表上,inspection_record_id 也放在主表以保持一致。若交车单需支持多车各自验车,后续可迁移到 DeliveryOrderVehicleDO 子表。

  • Step 3: 更新 Service — 创建时克隆备车验车记录

DeliveryOrderServiceImpl 的创建交车单方法中(createDeliveryOrder 或类似方法),deliveryOrderMapper.insert(deliveryOrder) 之后添加:

// 查找该车最近的备车验车记录
InspectionRecordDO prepareRecord = inspectionRecordService.getLatestRecord(
    vehicleId, InspectionSourceTypeEnum.PREPARE.getType());
if (prepareRecord != null) {
    Long recordId = inspectionRecordService.cloneRecord(
        prepareRecord.getId(), InspectionSourceTypeEnum.DELIVERY.getType(), deliveryOrder.getId());
    deliveryOrder.setInspectionRecordId(recordId);
    deliveryOrderMapper.updateById(deliveryOrder);
}
  • Step 4: Compile 验证
  • Step 5: Commit

Chunk 3: 替换车模块(后端完整)

Task 10: 替换车 SQL

Files:

  • Create: sql/2026-03-13-vehicle-replacement.sql

  • Step 1: 编写并执行 SQL

CREATE TABLE IF NOT EXISTS `asset_vehicle_replacement` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `replacement_code` varchar(50) NOT NULL COMMENT '替换单编码',
  `replacement_type` tinyint NOT NULL COMMENT '1=临时 2=永久',
  `contract_id` bigint NOT NULL,
  `contract_code` varchar(50) DEFAULT NULL,
  `customer_id` bigint DEFAULT NULL,
  `customer_name` varchar(100) DEFAULT NULL,
  `delivery_order_id` bigint DEFAULT NULL COMMENT '来源交车单ID',
  `original_vehicle_id` bigint NOT NULL,
  `original_plate_no` varchar(20) DEFAULT NULL,
  `original_vin` varchar(50) DEFAULT NULL,
  `new_vehicle_id` bigint DEFAULT NULL,
  `new_plate_no` varchar(20) DEFAULT NULL,
  `new_vin` varchar(50) DEFAULT NULL,
  `replacement_reason` varchar(500) DEFAULT NULL,
  `expected_date` date DEFAULT NULL,
  `actual_date` date DEFAULT NULL,
  `return_date` date DEFAULT NULL COMMENT '临时替换预计归还日期',
  `actual_return_date` date DEFAULT NULL,
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '0=草稿 1=审批中 2=审批通过 3=执行中 4=已完成 5=审批驳回 6=已撤回',
  `approval_status` tinyint NOT NULL DEFAULT 0 COMMENT '审批状态(与 ContractDO 保持一致的双字段模式status 跟踪业务生命周期approval_status 跟踪 BPM 审批状态)',
  `bpm_instance_id` varchar(64) DEFAULT NULL,
  `remark` varchar(500) DEFAULT NULL,
  `creator` varchar(64) DEFAULT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updater` varchar(64) DEFAULT '',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted` bit(1) NOT NULL DEFAULT b'0',
  `tenant_id` bigint NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='替换车申请';
  • Step 2: Commit

Task 11: 替换车枚举

Files:

  • Create: $API/enums/replacement/ReplacementTypeEnum.java — TEMPORARY(1), PERMANENT(2)

  • Create: $API/enums/replacement/ReplacementStatusEnum.java — DRAFT(0), APPROVING(1), APPROVED(2), EXECUTING(3), COMPLETED(4), REJECTED(5), WITHDRAWN(6)

  • Step 1: 创建枚举类

  • Step 2: Commit


Task 12: 替换车 DO + Mapper + VO + Convert

Files:

  • Create: $BE/dal/dataobject/replacement/VehicleReplacementDO.java

  • Create: $BE/dal/mysql/replacement/VehicleReplacementMapper.java

  • Create: $BE/controller/admin/replacement/vo/VehicleReplacementSaveReqVO.java

  • Create: $BE/controller/admin/replacement/vo/VehicleReplacementRespVO.java

  • Create: $BE/controller/admin/replacement/vo/VehicleReplacementPageReqVO.java

  • Create: $BE/convert/replacement/VehicleReplacementConvert.java

  • Step 1: 创建 VehicleReplacementDO

@TableName("asset_vehicle_replacement"). 所有字段按 spec 的表结构。

  • Step 2: 创建 Mapper
@Mapper
public interface VehicleReplacementMapper extends BaseMapperX<VehicleReplacementDO> {
    default PageResult<VehicleReplacementDO> selectPage(VehicleReplacementPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<VehicleReplacementDO>()
                .likeIfPresent(VehicleReplacementDO::getReplacementCode, reqVO.getReplacementCode())
                .eqIfPresent(VehicleReplacementDO::getReplacementType, reqVO.getReplacementType())
                .eqIfPresent(VehicleReplacementDO::getContractId, reqVO.getContractId())
                .likeIfPresent(VehicleReplacementDO::getCustomerName, reqVO.getCustomerName())
                .eqIfPresent(VehicleReplacementDO::getStatus, reqVO.getStatus())
                .orderByDesc(VehicleReplacementDO::getId));
    }
}
  • Step 3: 创建 VOs

SaveReqVO: id, replacementType(@NotNull), contractId(@NotNull), contractCode, customerId, customerName, deliveryOrderId, originalVehicleId(@NotNull), originalPlateNo, originalVin, newVehicleId, newPlateNo, newVin, replacementReason, expectedDate, returnDate, remark.

RespVO: 所有 SaveReqVO 字段 + replacementCode, actualDate, actualReturnDate, status, approvalStatus, bpmInstanceId, createTime, creator.

PageReqVO extends PageParam: replacementCode, replacementType, contractId, customerName, status.

  • Step 4: 创建 Convert

标准 MapStruct convert: convert(SaveReqVO)→DO, convert(DO)→RespVO, convertPage.

  • Step 5: Compile 验证
  • Step 6: Commit

Task 13: 替换车 Service + BPM Listener + 事件类

Files:

  • Create: $BE/service/replacement/VehicleReplacementService.java

  • Create: $BE/service/replacement/VehicleReplacementServiceImpl.java

  • Create: $BE/service/replacement/listener/ReplacementBpmListener.java

  • Create: $BE/service/replacement/event/ReplacementApprovedEvent.java

  • Create: $BE/service/replacement/event/ReplacementReturnConfirmedEvent.java

  • Step 1: 创建事件类

ReplacementApprovedEvent:

@Getter
@AllArgsConstructor
public class ReplacementApprovedEvent {
    private final Long replacementId;
    private final Integer replacementType;
    private final Long contractId;
    private final Long originalVehicleId;
}

ReplacementReturnConfirmedEvent:

@Getter
@AllArgsConstructor
public class ReplacementReturnConfirmedEvent {
    private final Long replacementId;
    private final Long originalVehicleId;
}
  • Step 2: 创建 Service 接口

Methods: createReplacement, updateReplacement, deleteReplacement, getReplacement, getReplacementPage, submitApproval, withdrawApproval, updateApprovalStatus(Long id, Integer bpmStatus), confirmReturn.

  • Step 3: 实现 ServiceImpl

关键方法:

createReplacement: 生成 replacementCode (TH-yyyyMMdd-seq),设 status=DRAFT。

submitApproval: 校验 status=DRAFT/REJECTED → 调用 bpmProcessInstanceApi.createProcessInstance() → 设 status=APPROVING。

updateApprovalStatus: BPM 回调。APPROVE → status=APPROVED → 发布 ReplacementApprovedEvent → status=EXECUTING。REJECT → status=REJECTED。CANCEL → status=WITHDRAWN。

confirmReturn: 校验 status=EXECUTING + type=TEMPORARY → status=COMPLETED → 发布 ReplacementReturnConfirmedEvent

  • Step 4: 创建 ReplacementBpmListener
@Component
public class ReplacementBpmListener extends BpmProcessInstanceStatusEventListener {
    public static final String PROCESS_KEY = "asset_vehicle_replacement";

    @Resource
    private VehicleReplacementService replacementService;

    @Override
    protected String getProcessDefinitionKey() { return PROCESS_KEY; }

    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        replacementService.updateApprovalStatus(
            Long.parseLong(event.getBusinessKey()), event.getStatus());
    }
}
  • Step 5: Compile 验证
  • Step 6: Commit

Task 14: 替换车 Controller

Files:

  • Create: $BE/controller/admin/replacement/VehicleReplacementController.java

  • Step 1: 创建 Controller

@RequestMapping("/asset/vehicle-replacement")。端点create, update, delete, get, page, submit, withdraw, confirm-return。权限前缀 asset:vehicle-replacement:xxx

  • Step 2: Compile 验证
  • Step 3: Commit

Chunk 4: 还车模块完善 + 事件驱动(后端)

Task 15: 还车表结构变更 SQL

Files:

  • Create: sql/2026-03-13-return-order-enhance.sql

  • Step 1: 编写并执行 SQL

ALTER TABLE asset_return_order
  ADD COLUMN source_type tinyint DEFAULT NULL COMMENT '来源1=手动 2=替换车触发' AFTER bpm_instance_id,
  ADD COLUMN source_id bigint DEFAULT NULL COMMENT '来源业务ID' AFTER source_type,
  ADD COLUMN delivery_order_id bigint DEFAULT NULL COMMENT '关联交车单ID' AFTER source_id;

ALTER TABLE asset_return_order_vehicle
  ADD COLUMN inspection_record_id bigint DEFAULT NULL COMMENT '关联验车记录' AFTER defect_photos;
  • Step 2: Commit

Task 16: 更新还车 DO + Service

Files:

  • Modify: $BE/dal/dataobject/returnorder/ReturnOrderDO.java — 新增 sourceType, sourceId, deliveryOrderId

  • Modify: $BE/dal/dataobject/returnorder/ReturnOrderVehicleDO.java — 新增 inspectionRecordId

  • Modify: $BE/service/returnorder/ReturnOrderService.java — 新增 createFromDelivery, createFromReplacement, startVehicleInspection, completeVehicleInspection, submitApproval, withdrawApproval, updateApprovalStatus

  • Modify: $BE/service/returnorder/ReturnOrderServiceImpl.java

  • Create: $BE/service/returnorder/listener/ReturnOrderBpmListener.java

  • Create: $BE/service/returnorder/event/ReturnApprovedEvent.java

  • Step 1: 更新 DO 字段

ReturnOrderDO 新增: private Integer sourceType;, private Long sourceId;, private Long deliveryOrderId; ReturnOrderVehicleDO 新增: private Long inspectionRecordId;

  • Step 2: 创建 ReturnApprovedEvent
@Getter
@AllArgsConstructor
public class ReturnApprovedEvent {
    private final Long returnOrderId;
    private final List<Long> vehicleIds;
}
  • Step 3: 新增 Service 方法

createFromDelivery(deliveryOrderId, vehicleIds[]):

  • 查交车单获取合同/客户信息
  • 创建还车单主记录sourceType=1, deliveryOrderId=deliveryOrderId
  • 生成 orderCode
  • 为每个 vehicleId 创建 ReturnOrderVehicleDO 子记录
  • 返回还车单ID

createFromReplacement(replacementId, contractId, vehicleId):

  • 创建还车单主记录sourceType=2, sourceId=replacementId
  • 创建一条车辆子记录
  • 返回还车单ID

startVehicleInspection(returnOrderVehicleId):

  • 查找该车最近的交车 inspection_record → 克隆为还车 record
  • 更新 ReturnOrderVehicleDO.inspectionRecordId

completeVehicleInspection(returnOrderVehicleId):

  • 调用 inspectionRecordService.completeRecord()
  • 检查该还车单所有车辆是否都验车完成 → 是则更新还车单 status=1(验车完成)

submitApproval(id): 校验 status=1(验车完成) → 调用 BPM → approvalStatus=1

updateApprovalStatus: BPM 回调。APPROVE → approvalStatus=2 → 发布 ReturnApprovedEvent。

  • Step 4: 创建 ReturnOrderBpmListener

同 ReplacementBpmListener 模式PROCESS_KEY = "asset_return_order"。

  • Step 5: 更新还车 Controller

新增端点:create-from-delivery, start-inspection, complete-inspection, submit, withdraw

  • Step 6: Compile 验证
  • Step 7: Commit

Task 17: 事件监听器 — 跨模块联动

Files:

  • Create: $BE/service/delivery/event/DeliveryCompletedEvent.java

  • Create: $BE/service/event/VehicleStatusEventListener.java

  • Create: $BE/service/event/ReturnOrderEventListener.java

  • Step 1: 创建 DeliveryCompletedEvent

@Getter
@AllArgsConstructor
public class DeliveryCompletedEvent {
    private final Long deliveryOrderId;
    private final Long vehicleId;
}
  • Step 2: 创建 VehicleStatusEventListener
@Component
@Slf4j
public class VehicleStatusEventListener {

    @Resource
    private VehicleBaseMapper vehicleBaseMapper; // 或对应的车辆 Service

    @TransactionalEventListener
    public void onDeliveryCompleted(DeliveryCompletedEvent event) {
        log.info("交车完成,更新车辆状态: vehicleId={}", event.getVehicleId());
        // 更新车辆状态为已交付
    }

    @TransactionalEventListener
    public void onReturnApproved(ReturnApprovedEvent event) {
        log.info("还车审批通过,更新车辆状态: vehicleIds={}", event.getVehicleIds());
        for (Long vehicleId : event.getVehicleIds()) {
            // 更新车辆状态为可用
        }
    }

    @TransactionalEventListener
    public void onReplacementReturnConfirmed(ReplacementReturnConfirmedEvent event) {
        log.info("临时替换换回,恢复原车状态: vehicleId={}", event.getOriginalVehicleId());
        // 更新车辆状态为可用
    }
}
  • Step 3: 创建 ReturnOrderEventListener
@Component
@Slf4j
public class ReturnOrderEventListener {

    @Resource
    private ReturnOrderService returnOrderService;

    @EventListener // 非 @TransactionalEventListener确保同事务
    public void onReplacementApproved(ReplacementApprovedEvent event) {
        if (event.getReplacementType().equals(ReplacementTypeEnum.PERMANENT.getType())) {
            log.info("永久替换审批通过,自动创建还车单: replacementId={}", event.getReplacementId());
            returnOrderService.createFromReplacement(
                event.getReplacementId(),
                event.getContractId(),
                event.getOriginalVehicleId()
            );
        }
    }
}
  • Step 4: 在交车完成时发布事件

DeliveryOrderServiceImplcompleteDeliveryOrder() 方法(或标记交车单为已完成的方法)中,状态更新为已完成之后添加:

@Resource
private ApplicationEventPublisher eventPublisher;

// 交车完成后发布事件
eventPublisher.publishEvent(new DeliveryCompletedEvent(deliveryOrder.getId(), vehicleId));
  • Step 5: Compile 验证
  • Step 6: Commit

Task 18: BPM 流程定义 XML + 部署

注意: BPMN XML 文件需在应用启动前就位。Task 13/16 的 BPM Listener 和本 Task 的 XML 必须一起部署,不要在中间重启应用。

Files:

  • Create: $BE/../resources/processes/asset_vehicle_replacement.bpmn20.xml

  • Create: $BE/../resources/processes/asset_return_order.bpmn20.xml

  • Step 1: 创建替换车审批流程 XML

参照现有 asset_contract.bpmn20.xmlprocess id=asset_vehicle_replacement。 流程: startEvent → 部门主管 → gateway(永久替换?) → 总经理审批 → endEvent。

  • Step 2: 创建还车审批流程 XML

process id=asset_return_order。简单串行: startEvent → 部门主管 → endEventApprove / endEventReject。

  • Step 3: Compile 验证
  • Step 4: Commit
git add .
git commit -m "feat: add replacement/return BPM process definitions"

Task 19: 后端整体编译验证

  • Step 1: Full compile
mvn compile -DskipTests

修复所有编译错误直到通过。

  • Step 2: Commit 修复

Chunk 5: 前端 — API + 共享组件

Task 20: 前端 API 文件

Files:

  • Create: $FE/api/asset/inspection.ts

  • Create: $FE/api/asset/vehicle-replacement.ts

  • Modify: $FE/api/asset/return-order.ts — 新增接口

  • Step 1: 创建 inspection.ts

import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';

export namespace InspectionApi {
  export interface Template {
    id?: number;
    code: string;
    name: string;
    bizType: number;
    vehicleType?: string;
    status: number;
    remark?: string;
    items?: TemplateItem[];
  }
  export interface TemplateItem {
    id?: number;
    category: string;
    itemName: string;
    itemCode: string;
    inputType: string;
    sort: number;
    required: number;
  }
  export interface RecordDetail {
    id: number;
    recordCode: string;
    templateId: number;
    sourceType: number;
    sourceId: number;
    vehicleId: number;
    inspectorName?: string;
    inspectionTime?: string;
    status: number;
    overallResult?: number;
    remark?: string;
    items: RecordItem[];
  }
  export interface RecordItem {
    id: number;
    itemCode: string;
    category: string;
    itemName: string;
    inputType: string;
    result?: number;
    value?: string;
    remark?: string;
    imageUrls?: string;
  }
}

export function getInspectionTemplatePage(params: PageParam) {
  return requestClient.get<PageResult<InspectionApi.Template>>('/asset/inspection-template/page', { params });
}
export function getInspectionTemplate(id: number) {
  return requestClient.get<InspectionApi.Template>(`/asset/inspection-template/get?id=${id}`);
}
export function createInspectionTemplate(data: InspectionApi.Template) {
  return requestClient.post('/asset/inspection-template/create', data);
}
export function updateInspectionTemplate(data: InspectionApi.Template) {
  return requestClient.put('/asset/inspection-template/update', data);
}
export function deleteInspectionTemplate(id: number) {
  return requestClient.delete(`/asset/inspection-template/delete?id=${id}`);
}
export function getInspectionRecord(id: number) {
  return requestClient.get<InspectionApi.RecordDetail>(`/asset/inspection-record/get?id=${id}`);
}
export function updateInspectionRecordItem(data: { id: number; result?: number; value?: string; remark?: string; imageUrls?: string }) {
  return requestClient.put('/asset/inspection-record/update-item', data);
}
export function completeInspection(id: number, inspectorName: string) {
  return requestClient.post(`/asset/inspection-record/complete?id=${id}&inspectorName=${inspectorName}`);
}
  • Step 2: 创建 vehicle-replacement.ts

标准 CRUD + submit/withdraw/confirmReturn。参照 customer.ts 模式。

  • Step 3: 更新 return-order.ts

新增: createFromDelivery(deliveryOrderId, vehicleIds), startVehicleInspection(returnOrderVehicleId), completeVehicleInspection(returnOrderVehicleId), submitReturnOrder(id), withdrawReturnOrder(id).

  • Step 4: Commit

Task 21: 共享验车组件 InspectionForm.vue

Files:

  • Create: $FE/views/asset/components/InspectionForm.vue

  • Step 1: 创建组件

Props: recordId(number), readonly(boolean), onComplete(callback)。

功能:

  • 调用 getInspectionRecord(recordId) 获取数据

  • category 分组,使用 a-collapse 展示

  • 每个 item 根据 inputType 渲染:

    • checkboxa-radio-group(合格/不合格/不适用)
    • numbera-input-number
    • texta-input
  • 每项有备注 input + 图片上传

  • 编辑模式下 onChange 调用 updateInspectionRecordItem 保存

  • readonly 模式禁用所有输入

  • "完成验车"按钮调用 completeInspection

  • Step 2: Commit


Chunk 6: 前端 — 各模块页面

Task 22: 替换车前端页面

Files:

  • Create: $FE/views/asset/vehicle-replacement/index.vue

  • Create: $FE/views/asset/vehicle-replacement/data.ts

  • Create: $FE/views/asset/vehicle-replacement/modules/form.vue

  • Step 1: 创建 data.ts

搜索表单: replacementCode, replacementType, customerName, status。 列定义: replacementCode, replacementType, contractCode, customerName, originalPlateNo, newPlateNo, status, expectedDate, createTime, 操作。 状态常量: REPLACEMENT_TYPE_OPTIONS, REPLACEMENT_STATUS_OPTIONS

  • Step 2: 创建 index.vue

参照 supplier/index.vue 模式useVbenVxeGrid + 搜索 + 操作列(查看/编辑/删除/提交审批/撤回/确认换回,按 status 条件显示)。

  • Step 3: 创建 form.vue

三模式 (create/edit/view)。使用 useVbenModal + useVbenForm。 字段: replacementType(Select), contractId(Select), originalVehicleId(Select 带车辆列表), newVehicleId(Select), replacementReason(TextArea), expectedDate(DatePicker), returnDate(DatePicker, 仅临时显示), remark。

create 模式: 检查 URL query params (contractId, vehicleId, deliveryOrderId),有则自动填充并 readonly。

  • Step 4: Commit

Task 23: 备车前端改造

Files:

  • Modify: $FE/views/asset/vehicle-prepare/modules/form.vue — 移除硬编码 checkList改用 InspectionForm 组件

  • Modify: $FE/views/asset/vehicle-prepare/data.ts — 移除 contractId 相关字段

  • Modify: $FE/api/asset/vehicle-prepare.ts — 更新接口定义

  • Step 1: 更新 API 定义

VehiclePrepare 接口移除 contractId, contractCode, checkList,新增 inspectionRecordId

  • Step 2: 更新 data.ts

移除 contractId/contractCode 搜索字段和列。

  • Step 3: 改造 form.vue

  • 移除 checkList 相关的表格/抽屉代码

  • 在表单末尾添加 <InspectionForm :record-id="formData.inspectionRecordId" :readonly="isView" /> 组件

  • 如果 inspectionRecordId 不存在(旧数据),显示提示信息

  • Step 4: Commit


Task 24: 交车前端改造

Files:

  • Modify: $FE/views/asset/delivery-order/index.vue — 操作列增加"还车"和"替换车"按钮

  • Modify: $FE/views/asset/delivery-order/modules/form.vue — 验车区域改用 InspectionForm

  • Step 1: 操作列扩展

在 index.vue 的 getRowActions 中,对 status=已完成 的记录增加:

{ label: '还车', onClick: () => handleReturn(row) },
{ label: '替换车', onClick: () => handleReplacement(row) },

handleReturn: 弹窗显示交车单关联车辆列表checkbox 多选)→ 确认后调用 createFromDelivery(row.id, selectedVehicleIds) → 跳转还车单详情。

handleReplacement: 弹窗显示交车单关联车辆列表(单选)→ 确认后 router.push({ path: '/asset/vehicle-replacement', query: { contractId, vehicleId, deliveryOrderId } })

  • Step 2: 验车区域改造

form.vue 中如果有 inspectionRecordId使用 InspectionForm 组件替代原有 inspectionData JSON 表单。

  • Step 3: Commit

Task 25: 还车前端改造

Files:

  • Modify: $FE/views/asset/return-order/index.vue — 操作列增加"提交审批"、"撤回"

  • Modify: $FE/views/asset/return-order/modules/form.vue — 每辆车增加"开始验车"按钮 + InspectionForm

  • Step 1: 更新 index.vue 操作列

按 status 和 approvalStatus 条件显示: 查看(始终)、编辑(待验车)、删除(待验车)、提交审批(验车完成+未提交)、撤回(审批中)。

  • Step 2: 改造 form.vue 验车区域

车辆子表格中每行增加:

  • "开始验车"按钮(当 inspectionRecordId 为空时显示)→ 调用 startVehicleInspection

  • InspectionForm 组件(当 inspectionRecordId 存在时显示)

  • "完成验车"按钮 → 调用 completeVehicleInspection

  • Step 3: Commit


Task 26: 前端构建验证

  • Step 1: Build
cd ../oneos-frontend && pnpm run build:antd

修复所有 TypeScript/构建错误直到通过。

  • Step 2: Commit 修复

Task 27: 全链路端到端验证

  • Step 1: 后端编译
cd ../oneos-backend && mvn compile -DskipTests
  • Step 2: 前端构建
cd ../oneos-frontend && pnpm run build:antd
  • Step 3: 功能检查清单

验证以下流程可正常操作:

  1. 验车模板 CRUD — 创建模板+检查项
  2. 备车 — 创建备车单 → 自动生成验车记录 → 完成验车
  3. 交车 — 创建交车单 → 克隆备车验车记录 → 完成验车 → 操作列显示还车/替换车
  4. 替换车 — 从交车页面触发 → 自动填充 → 提交审批
  5. 还车 — 从交车页面触发 → 创建壳子 → 开始验车(克隆交车记录) → 完成验车 → 提交审批
  6. 永久替换审批通过 → 自动创建还车单
  • Step 4: Final commit
git add .
git commit -m "feat: complete rental full-chain implementation"