diff --git a/pom.xml b/pom.xml
index 9cb03ac..6f20803 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,6 +19,7 @@
yudao-module-bpm
yudao-module-asset
yudao-module-ocr
+ yudao-module-energy
${project.artifactId}
diff --git a/sql/energy/2026-03-16-add-hydrogen-station-table.sql b/sql/energy/2026-03-16-add-hydrogen-station-table.sql
new file mode 100644
index 0000000..445b0c3
--- /dev/null
+++ b/sql/energy/2026-03-16-add-hydrogen-station-table.sql
@@ -0,0 +1,62 @@
+-- ==========================================
+-- 2026-03-16 Energy 模块优化
+-- 新增加氢站基础信息表(asset 模块)
+-- 去掉 energy_station_config 表
+-- ==========================================
+
+-- ----------------------------
+-- 1. 在 asset 模块创建加氢站表
+-- ----------------------------
+DROP TABLE IF EXISTS `asset_hydrogen_station`;
+CREATE TABLE `asset_hydrogen_station` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `name` varchar(100) NOT NULL COMMENT '站点名称',
+ `short_name` varchar(50) DEFAULT NULL COMMENT '简称',
+ `station_no` varchar(50) DEFAULT NULL COMMENT '站点编码',
+ `city` varchar(50) DEFAULT NULL COMMENT '所属城市',
+ `address` varchar(255) DEFAULT NULL COMMENT '站点地址',
+ `longitude` varchar(50) DEFAULT NULL COMMENT '经度',
+ `latitude` varchar(50) DEFAULT NULL COMMENT '纬度',
+ `contact` varchar(50) DEFAULT NULL COMMENT '联系人',
+ `phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
+ `station_type` tinyint DEFAULT NULL COMMENT '站点类型(字典)',
+ `cooperation_type` tinyint NOT NULL DEFAULT 0 COMMENT '合作类型(0=合作 1=非合作)',
+ `auto_deduct` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否自动扣款(1=是 0=否)',
+ `booking_required` tinyint(1) DEFAULT 0 COMMENT '是否需要预约(1=是 0=否)',
+ `station_status` tinyint NOT NULL DEFAULT 1 COMMENT '站点状态(0=停用 1=启用)',
+ `start_business` time DEFAULT NULL COMMENT '开始营业时间',
+ `end_business` time DEFAULT NULL COMMENT '结束营业时间',
+ `billing_method` tinyint DEFAULT NULL COMMENT '结算方式(字典)',
+ `cooperation_term` date DEFAULT NULL COMMENT '合作期限',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_station_no` (`station_no`),
+ KEY `idx_name` (`name`),
+ KEY `idx_city` (`city`),
+ KEY `idx_station_status` (`station_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢站基础信息';
+
+-- ----------------------------
+-- 2. 删除 energy_station_config 表
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_station_config`;
+
+-- ----------------------------
+-- 3. 插入测试数据
+-- ----------------------------
+INSERT INTO `asset_hydrogen_station`
+(`id`, `name`, `short_name`, `station_no`, `city`, `address`, `contact`, `phone`,
+ `cooperation_type`, `auto_deduct`, `station_status`, `start_business`, `end_business`, `remark`)
+VALUES
+(1, '嘉兴嘉燃经开站', '经开站', 'JX001', '嘉兴', '浙江省嘉兴市经济开发区岗山路', '张三', '13800138000',
+ 0, 1, 1, '08:00:00', '18:00:00', '合作站点,自动扣款'),
+(2, '上海临港加氢站', '临港站', 'SH001', '上海', '上海市浦东新区临港新城', '李四', '13900139000',
+ 0, 1, 1, '07:00:00', '19:00:00', '合作站点,自动扣款'),
+(3, '杭州萧山加氢站', '萧山站', 'HZ001', '杭州', '浙江省杭州市萧山区', '王五', '13700137000',
+ 1, 0, 1, '08:30:00', '17:30:00', '非合作站点,审核后扣款');
diff --git a/sql/energy/check_tables.py b/sql/energy/check_tables.py
new file mode 100644
index 0000000..dcc6c94
--- /dev/null
+++ b/sql/energy/check_tables.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+"""
+检查数据库表结构
+"""
+import pymysql
+
+def check_tables(host, port, user, password, database):
+ """检查表是否存在及字段"""
+ try:
+ connection = pymysql.connect(
+ host=host,
+ port=port,
+ user=user,
+ password=password,
+ database=database,
+ charset='utf8mb4',
+ cursorclass=pymysql.cursors.DictCursor
+ )
+
+ print(f"✓ 连接数据库成功: {database}")
+
+ tables = [
+ 'asset_parking',
+ 'asset_customer',
+ 'asset_vehicle_base',
+ 'asset_vehicle_business',
+ 'asset_contract'
+ ]
+
+ with connection.cursor() as cursor:
+ for table in tables:
+ # 检查表是否存在
+ cursor.execute(f"SHOW TABLES LIKE '{table}'")
+ result = cursor.fetchone()
+
+ if result:
+ print(f"\n✓ 表 {table} 存在")
+
+ # 显示字段
+ cursor.execute(f"DESCRIBE {table}")
+ columns = cursor.fetchall()
+ print(f" 字段: {', '.join([col['Field'] for col in columns[:10]])}")
+ if len(columns) > 10:
+ print(f" ... 共 {len(columns)} 个字段")
+ else:
+ print(f"\n✗ 表 {table} 不存在")
+
+ connection.close()
+
+ except Exception as e:
+ print(f"✗ 错误: {e}")
+
+if __name__ == '__main__':
+ DB_HOST = '47.103.115.36'
+ DB_PORT = 3306
+ DB_USER = 'root'
+ DB_PASSWORD = 'Passw0rd2026'
+
+ print("检查 oneos_asset 数据库:")
+ check_tables(DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, 'oneos_asset')
+
+ print("\n" + "="*60)
+ print("检查 oneos_energy 数据库:")
+ check_tables(DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, 'oneos_energy')
diff --git a/sql/energy/energy_create_tables.sql b/sql/energy/energy_create_tables.sql
new file mode 100644
index 0000000..6f22d7a
--- /dev/null
+++ b/sql/energy/energy_create_tables.sql
@@ -0,0 +1,282 @@
+-- ==========================================
+-- 能源账单模块建表 SQL(331 一期,9 张表)
+-- ==========================================
+
+-- ----------------------------
+-- 1. energy_hydrogen_record — 加氢原始记录
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_hydrogen_record`;
+CREATE TABLE `energy_hydrogen_record` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `station_id` bigint NOT NULL COMMENT '加氢站 ID(关联 asset 模块)',
+ `plate_number` varchar(20) NOT NULL COMMENT '车牌号',
+ `hydrogen_date` date NOT NULL COMMENT '加氢日期',
+ `hydrogen_quantity` decimal(10,2) NOT NULL COMMENT '加氢量(KG)',
+ `unit_price` decimal(10,2) NOT NULL COMMENT '单价(元/KG)',
+ `amount` decimal(12,2) NOT NULL COMMENT '金额(以数据源原始值为准,不重新计算)',
+ `mileage` decimal(12,2) DEFAULT NULL COMMENT '里程数',
+ `source_type` tinyint NOT NULL COMMENT '数据来源(1=Excel 2=Web 3=API 4=OCR)',
+ `match_status` tinyint NOT NULL DEFAULT 0 COMMENT '匹配状态(0=未匹配 1=已匹配 2=无法匹配)',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '匹配到的车辆 ID(匹配后填充)',
+ `customer_id` bigint DEFAULT NULL COMMENT '匹配到的客户 ID(匹配后填充)',
+ `upload_batch_no` varchar(64) DEFAULT NULL COMMENT '导入批次号(幂等去重)',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_station_id` (`station_id`),
+ KEY `idx_plate_number` (`plate_number`),
+ KEY `idx_hydrogen_date` (`hydrogen_date`),
+ KEY `idx_match_status` (`match_status`),
+ KEY `idx_upload_batch_no` (`upload_batch_no`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢原始记录';
+
+-- ----------------------------
+-- 2. energy_hydrogen_detail — 加氢明细
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_hydrogen_detail`;
+CREATE TABLE `energy_hydrogen_detail` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `record_id` bigint NOT NULL COMMENT '关联原始记录 ID',
+ `station_id` bigint NOT NULL COMMENT '加氢站 ID',
+ `vehicle_id` bigint NOT NULL COMMENT '车辆 ID',
+ `plate_number` varchar(20) NOT NULL COMMENT '车牌号(冗余)',
+ `hydrogen_date` date NOT NULL COMMENT '加氢日期',
+ `hydrogen_quantity` decimal(10,2) NOT NULL COMMENT '加氢量(KG)',
+ `cost_price` decimal(10,2) NOT NULL COMMENT '成本单价(元/KG)',
+ `cost_amount` decimal(12,2) NOT NULL COMMENT '成本金额',
+ `customer_price` decimal(10,2) NOT NULL COMMENT '对客单价(元/KG)',
+ `customer_amount` decimal(12,2) NOT NULL COMMENT '对客金额',
+ `contract_id` bigint NOT NULL COMMENT '合同 ID',
+ `customer_id` bigint NOT NULL COMMENT '客户 ID',
+ `project_name` varchar(100) DEFAULT NULL COMMENT '项目名称(冗余)',
+ `cost_bearer` tinyint NOT NULL COMMENT '费用承担方(1=客户承担 2=羚牛承担 3=自行结算)',
+ `pay_method` tinyint NOT NULL COMMENT '支付方式(1=预充值 2=月结算)',
+ `audit_status` tinyint NOT NULL DEFAULT 0 COMMENT '审核状态(0=待审核 1=已审核 2=已驳回)',
+ `audit_remark` varchar(500) DEFAULT NULL COMMENT '审核备注',
+ `deduction_status` tinyint NOT NULL DEFAULT 0 COMMENT '扣款状态(0=未扣款 1=已扣款)',
+ `settlement_status` tinyint NOT NULL DEFAULT 0 COMMENT '结算状态(0=未结算 1=已结算)',
+ `bill_id` bigint DEFAULT NULL COMMENT '关联账单 ID',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_record_id` (`record_id`),
+ KEY `idx_station_id` (`station_id`),
+ KEY `idx_vehicle_id` (`vehicle_id`),
+ KEY `idx_customer_id` (`customer_id`),
+ KEY `idx_contract_id` (`contract_id`),
+ KEY `idx_bill_id` (`bill_id`),
+ KEY `idx_audit_status` (`audit_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢明细';
+
+-- ----------------------------
+-- 3. energy_bill — 能源账单
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_bill`;
+CREATE TABLE `energy_bill` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `bill_code` varchar(32) NOT NULL COMMENT '账单编号(唯一)',
+ `energy_type` tinyint NOT NULL COMMENT '能源类型(1=氢 2=电 3=ETC)',
+ `customer_id` bigint NOT NULL COMMENT '客户 ID',
+ `customer_name` varchar(100) DEFAULT NULL COMMENT '客户名称(冗余)',
+ `contract_id` bigint NOT NULL COMMENT '合同 ID',
+ `station_id` bigint DEFAULT NULL COMMENT '加氢站 ID',
+ `station_name` varchar(100) DEFAULT NULL COMMENT '站点名称(冗余)',
+ `bill_period_start` date NOT NULL COMMENT '账单周期开始日期',
+ `bill_period_end` date NOT NULL COMMENT '账单周期结束日期',
+ `receivable_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '应收总额',
+ `actual_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '实收总额(receivable_amount + adjustment_amount)',
+ `adjustment_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '调整总额(可正可负)',
+ `paid_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '已收金额',
+ `total_quantity` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '总加氢量/总度数',
+ `detail_count` int NOT NULL DEFAULT 0 COMMENT '明细条数',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '账单状态(0=草稿 1=已生成 2=已作废)',
+ `audit_status` tinyint NOT NULL DEFAULT 0 COMMENT '审核状态(0=待审核 1=已审核 2=已驳回)',
+ `submit_status` tinyint NOT NULL DEFAULT 0 COMMENT '提交状态(0=未提交 1=已提交 2=已驳回)',
+ `payment_status` tinyint NOT NULL DEFAULT 0 COMMENT '支付状态(0=未支付 1=部分支付 2=已结清)',
+ `auditor_id` bigint DEFAULT NULL COMMENT '审核人 ID',
+ `audit_time` datetime DEFAULT NULL COMMENT '审核时间',
+ `audit_remark` varchar(500) DEFAULT NULL COMMENT '审核备注',
+ `submit_time` datetime DEFAULT NULL COMMENT '提交时间',
+ `generate_time` datetime DEFAULT NULL COMMENT '账单生成时间',
+ `yos_bill_code` varchar(64) DEFAULT NULL COMMENT 'YOS 账单编号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_bill_code` (`bill_code`),
+ KEY `idx_customer_id` (`customer_id`),
+ KEY `idx_contract_id` (`contract_id`),
+ KEY `idx_station_id` (`station_id`),
+ KEY `idx_bill_period` (`bill_period_start`, `bill_period_end`),
+ KEY `idx_status` (`status`),
+ KEY `idx_audit_status` (`audit_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='能源账单';
+
+-- ----------------------------
+-- 4. energy_bill_adjustment — 账单调整记录
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_bill_adjustment`;
+CREATE TABLE `energy_bill_adjustment` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `bill_id` bigint NOT NULL COMMENT '关联账单 ID',
+ `detail_id` bigint DEFAULT NULL COMMENT '关联明细 ID(可选)',
+ `adjustment_type` tinyint NOT NULL COMMENT '调整类型(1=增加 2=减少)',
+ `amount` decimal(12,2) NOT NULL COMMENT '调整金额(正数)',
+ `reason` varchar(500) DEFAULT NULL COMMENT '调整原因',
+ `attachment_urls` varchar(1000) DEFAULT NULL COMMENT '附件 URL(JSON 数组)',
+ `operator_id` bigint DEFAULT NULL COMMENT '操作人 ID',
+ `operate_time` datetime DEFAULT NULL COMMENT '操作时间',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_bill_id` (`bill_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='账单调整记录';
+
+-- ----------------------------
+-- 5. energy_account — 客户能源总账户
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_account`;
+CREATE TABLE `energy_account` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `customer_id` bigint NOT NULL COMMENT '客户 ID(唯一)',
+ `balance` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '当前余额(可为负数)',
+ `init_balance` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '初始余额',
+ `accumulated_recharge` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计充值',
+ `accumulated_hydrogen` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计氢费',
+ `accumulated_electric` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计电费',
+ `accumulated_consume` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计消费(所有能源类型合计)',
+ `reminder_threshold` decimal(14,2) DEFAULT NULL COMMENT '提醒阈值(低于此值触发预警)',
+ `account_status` tinyint NOT NULL DEFAULT 0 COMMENT '账户状态(0=正常 1=预警 2=欠费)',
+ `last_recharge_date` date DEFAULT NULL COMMENT '最后充值日期',
+ `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_customer_id` (`customer_id`),
+ KEY `idx_account_status` (`account_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户能源总账户';
+
+-- ----------------------------
+-- 6. energy_project_account — 项目账户
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_project_account`;
+CREATE TABLE `energy_project_account` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `account_id` bigint NOT NULL COMMENT '关联总账户 ID',
+ `contract_id` bigint NOT NULL COMMENT '合同 ID(唯一)',
+ `project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
+ `project_balance` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '项目余额',
+ `project_remit_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计划账金额',
+ `project_hydrogen_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计氢费',
+ `project_electric_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计电费',
+ `project_consume_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计消费',
+ `reminder_threshold` decimal(14,2) DEFAULT NULL COMMENT '提醒阈值',
+ `account_status` tinyint NOT NULL DEFAULT 0 COMMENT '账户状态(0=正常 1=预警 2=欠费)',
+ `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_contract_id` (`contract_id`),
+ KEY `idx_account_id` (`account_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目账户';
+
+-- ----------------------------
+-- 7. energy_account_flow — 统一余额变更流水
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_account_flow`;
+CREATE TABLE `energy_account_flow` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `account_id` bigint NOT NULL COMMENT '关联总账户 ID',
+ `project_account_id` bigint DEFAULT NULL COMMENT '关联项目账户 ID(可选)',
+ `flow_type` tinyint NOT NULL COMMENT '流水类型(1=充值 2=扣款 3=冲正 4=划账 5=退款)',
+ `amount` decimal(14,2) NOT NULL COMMENT '变动金额',
+ `balance_before` decimal(14,2) NOT NULL COMMENT '变动前余额(总账户级别)',
+ `balance_after` decimal(14,2) NOT NULL COMMENT '变动后余额(总账户级别)',
+ `project_balance_before` decimal(14,2) DEFAULT NULL COMMENT '变动前项目余额(仅项目账户操作时填写)',
+ `project_balance_after` decimal(14,2) DEFAULT NULL COMMENT '变动后项目余额(仅项目账户操作时填写)',
+ `biz_type` tinyint NOT NULL COMMENT '业务类型(1=加氢扣款 2=账单结算 3=手动调整 ...)',
+ `biz_id` bigint DEFAULT NULL COMMENT '关联单据 ID',
+ `biz_code` varchar(64) DEFAULT NULL COMMENT '关联单据编号',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `operator_id` bigint DEFAULT NULL COMMENT '操作人 ID',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_account_id` (`account_id`),
+ KEY `idx_project_account_id` (`project_account_id`),
+ KEY `idx_biz_type_biz_id` (`biz_type`, `biz_id`),
+ KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='统一余额变更流水';
+
+-- ----------------------------
+-- 8. energy_station_price — 加氢站客户价格
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_station_price`;
+CREATE TABLE `energy_station_price` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `station_id` bigint NOT NULL COMMENT '加氢站 ID',
+ `customer_id` bigint NOT NULL COMMENT '客户 ID',
+ `cost_price` decimal(10,2) NOT NULL COMMENT '成本价(元/KG)',
+ `customer_price` decimal(10,2) NOT NULL COMMENT '对客价(元/KG)',
+ `effective_date` date NOT NULL COMMENT '生效日期',
+ `expiry_date` date DEFAULT NULL COMMENT '失效日期(可空,空=永久生效)',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0=生效中 1=已失效)',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_station_customer_date` (`station_id`, `customer_id`, `effective_date`),
+ KEY `idx_station_id` (`station_id`),
+ KEY `idx_customer_id` (`customer_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢站客户价格';
+
+-- ----------------------------
+-- 9. energy_station_config — 加氢站扣款配置
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_station_config`;
+CREATE TABLE `energy_station_config` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `station_id` bigint NOT NULL COMMENT '加氢站 ID(唯一,每站一条记录)',
+ `auto_deduct` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否自动扣款(1=是 0=否)',
+ `cooperation_type` tinyint NOT NULL DEFAULT 0 COMMENT '合作类型(0=合作 1=非合作)',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_station_id` (`station_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢站扣款配置';
diff --git a/sql/energy/energy_init_all.sql b/sql/energy/energy_init_all.sql
new file mode 100644
index 0000000..7afc3e6
--- /dev/null
+++ b/sql/energy/energy_init_all.sql
@@ -0,0 +1,432 @@
+-- ==========================================
+-- Energy 模块一键初始化脚本
+-- 使用方式:
+-- 1. 用数据库工具连接 47.103.115.36:3306 (root/Passw0rd2026)
+-- 2. 直接执行本脚本(脚本内部会切换数据库)
+-- ==========================================
+
+-- ==========================================
+-- 第一部分: 创建 oneos_energy 数据库 + 建表
+-- ==========================================
+CREATE DATABASE IF NOT EXISTS `oneos_energy` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE `oneos_energy`;
+
+-- ----------------------------
+-- 1. energy_hydrogen_record — 加氢原始记录
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_hydrogen_record`;
+CREATE TABLE `energy_hydrogen_record` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `station_id` bigint NOT NULL COMMENT '加氢站 ID(关联 asset 模块)',
+ `plate_number` varchar(20) NOT NULL COMMENT '车牌号',
+ `hydrogen_date` date NOT NULL COMMENT '加氢日期',
+ `hydrogen_quantity` decimal(10,2) NOT NULL COMMENT '加氢量(KG)',
+ `unit_price` decimal(10,2) NOT NULL COMMENT '单价(元/KG)',
+ `amount` decimal(12,2) NOT NULL COMMENT '金额(以数据源原始值为准,不重新计算)',
+ `mileage` decimal(12,2) DEFAULT NULL COMMENT '里程数',
+ `source_type` tinyint NOT NULL COMMENT '数据来源(1=Excel 2=Web 3=API 4=OCR)',
+ `match_status` tinyint NOT NULL DEFAULT 0 COMMENT '匹配状态(0=未匹配 1=已匹配 2=无法匹配)',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '匹配到的车辆 ID(匹配后填充)',
+ `customer_id` bigint DEFAULT NULL COMMENT '匹配到的客户 ID(匹配后填充)',
+ `upload_batch_no` varchar(64) DEFAULT NULL COMMENT '导入批次号(幂等去重)',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_station_id` (`station_id`),
+ KEY `idx_plate_number` (`plate_number`),
+ KEY `idx_hydrogen_date` (`hydrogen_date`),
+ KEY `idx_match_status` (`match_status`),
+ KEY `idx_upload_batch_no` (`upload_batch_no`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢原始记录';
+
+-- ----------------------------
+-- 2. energy_hydrogen_detail — 加氢明细
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_hydrogen_detail`;
+CREATE TABLE `energy_hydrogen_detail` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `record_id` bigint NOT NULL COMMENT '关联原始记录 ID',
+ `station_id` bigint NOT NULL COMMENT '加氢站 ID',
+ `vehicle_id` bigint NOT NULL COMMENT '车辆 ID',
+ `plate_number` varchar(20) NOT NULL COMMENT '车牌号(冗余)',
+ `hydrogen_date` date NOT NULL COMMENT '加氢日期',
+ `hydrogen_quantity` decimal(10,2) NOT NULL COMMENT '加氢量(KG)',
+ `cost_price` decimal(10,2) NOT NULL COMMENT '成本单价(元/KG)',
+ `cost_amount` decimal(12,2) NOT NULL COMMENT '成本金额',
+ `customer_price` decimal(10,2) NOT NULL COMMENT '对客单价(元/KG)',
+ `customer_amount` decimal(12,2) NOT NULL COMMENT '对客金额',
+ `contract_id` bigint NOT NULL COMMENT '合同 ID',
+ `customer_id` bigint NOT NULL COMMENT '客户 ID',
+ `project_name` varchar(100) DEFAULT NULL COMMENT '项目名称(冗余)',
+ `cost_bearer` tinyint NOT NULL COMMENT '费用承担方(1=客户承担 2=羚牛承担 3=自行结算)',
+ `pay_method` tinyint NOT NULL COMMENT '支付方式(1=预充值 2=月结算)',
+ `audit_status` tinyint NOT NULL DEFAULT 0 COMMENT '审核状态(0=待审核 1=已审核 2=已驳回)',
+ `audit_remark` varchar(500) DEFAULT NULL COMMENT '审核备注',
+ `deduction_status` tinyint NOT NULL DEFAULT 0 COMMENT '扣款状态(0=未扣款 1=已扣款)',
+ `settlement_status` tinyint NOT NULL DEFAULT 0 COMMENT '结算状态(0=未结算 1=已结算)',
+ `bill_id` bigint DEFAULT NULL COMMENT '关联账单 ID',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_record_id` (`record_id`),
+ KEY `idx_station_id` (`station_id`),
+ KEY `idx_vehicle_id` (`vehicle_id`),
+ KEY `idx_customer_id` (`customer_id`),
+ KEY `idx_contract_id` (`contract_id`),
+ KEY `idx_bill_id` (`bill_id`),
+ KEY `idx_audit_status` (`audit_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢明细';
+
+-- ----------------------------
+-- 3. energy_bill — 能源账单
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_bill`;
+CREATE TABLE `energy_bill` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `bill_code` varchar(32) NOT NULL COMMENT '账单编号(唯一)',
+ `energy_type` tinyint NOT NULL COMMENT '能源类型(1=氢 2=电 3=ETC)',
+ `customer_id` bigint NOT NULL COMMENT '客户 ID',
+ `customer_name` varchar(100) DEFAULT NULL COMMENT '客户名称(冗余)',
+ `contract_id` bigint NOT NULL COMMENT '合同 ID',
+ `station_id` bigint DEFAULT NULL COMMENT '加氢站 ID',
+ `station_name` varchar(100) DEFAULT NULL COMMENT '站点名称(冗余)',
+ `cooperation_type` tinyint DEFAULT NULL COMMENT '合作模式(1=预充值 2=月结算)',
+ `bill_period_start` date NOT NULL COMMENT '账单周期开始日期',
+ `bill_period_end` date NOT NULL COMMENT '账单周期结束日期',
+ `receivable_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '应收总额',
+ `actual_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '实收总额(receivable_amount + adjustment_amount)',
+ `adjustment_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '调整总额(可正可负)',
+ `paid_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '已收金额',
+ `total_quantity` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '总加氢量/总度数',
+ `detail_count` int NOT NULL DEFAULT 0 COMMENT '明细条数',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '账单状态(0=草稿 1=已生成 2=已作废)',
+ `audit_status` tinyint NOT NULL DEFAULT 0 COMMENT '审核状态(0=待审核 1=已审核 2=已驳回)',
+ `submit_status` tinyint NOT NULL DEFAULT 0 COMMENT '提交状态(0=未提交 1=已提交 2=已驳回)',
+ `payment_status` tinyint NOT NULL DEFAULT 0 COMMENT '支付状态(0=未支付 1=部分支付 2=已结清)',
+ `auditor_id` bigint DEFAULT NULL COMMENT '审核人 ID',
+ `audit_time` datetime DEFAULT NULL COMMENT '审核时间',
+ `audit_remark` varchar(500) DEFAULT NULL COMMENT '审核备注',
+ `submit_time` datetime DEFAULT NULL COMMENT '提交时间',
+ `generate_time` datetime DEFAULT NULL COMMENT '账单生成时间',
+ `yos_bill_code` varchar(64) DEFAULT NULL COMMENT 'YOS 账单编号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_bill_code` (`bill_code`),
+ KEY `idx_customer_id` (`customer_id`),
+ KEY `idx_contract_id` (`contract_id`),
+ KEY `idx_station_id` (`station_id`),
+ KEY `idx_bill_period` (`bill_period_start`, `bill_period_end`),
+ KEY `idx_status` (`status`),
+ KEY `idx_audit_status` (`audit_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='能源账单';
+
+-- ----------------------------
+-- 4. energy_bill_adjustment — 账单调整记录
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_bill_adjustment`;
+CREATE TABLE `energy_bill_adjustment` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `bill_id` bigint NOT NULL COMMENT '关联账单 ID',
+ `detail_id` bigint DEFAULT NULL COMMENT '关联明细 ID(可选)',
+ `adjustment_type` tinyint NOT NULL COMMENT '调整类型(1=增加 2=减少)',
+ `amount` decimal(12,2) NOT NULL COMMENT '调整金额(正数)',
+ `reason` varchar(500) DEFAULT NULL COMMENT '调整原因',
+ `attachment_urls` varchar(1000) DEFAULT NULL COMMENT '附件 URL(JSON 数组)',
+ `operator_id` bigint DEFAULT NULL COMMENT '操作人 ID',
+ `operate_time` datetime DEFAULT NULL COMMENT '操作时间',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_bill_id` (`bill_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='账单调整记录';
+
+-- ----------------------------
+-- 5. energy_account — 客户能源总账户
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_account`;
+CREATE TABLE `energy_account` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `customer_id` bigint NOT NULL COMMENT '客户 ID(唯一)',
+ `balance` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '当前余额(可为负数)',
+ `init_balance` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '初始余额',
+ `accumulated_recharge` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计充值',
+ `accumulated_hydrogen` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计氢费',
+ `accumulated_electric` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计电费',
+ `accumulated_consume` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计消费(所有能源类型合计)',
+ `reminder_threshold` decimal(14,2) DEFAULT NULL COMMENT '提醒阈值(低于此值触发预警)',
+ `account_status` tinyint NOT NULL DEFAULT 0 COMMENT '账户状态(0=正常 1=预警 2=欠费)',
+ `last_recharge_date` date DEFAULT NULL COMMENT '最后充值日期',
+ `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_customer_id` (`customer_id`),
+ KEY `idx_account_status` (`account_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户能源总账户';
+
+-- ----------------------------
+-- 6. energy_project_account — 项目账户
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_project_account`;
+CREATE TABLE `energy_project_account` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `account_id` bigint NOT NULL COMMENT '关联总账户 ID',
+ `contract_id` bigint NOT NULL COMMENT '合同 ID(唯一)',
+ `project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
+ `project_balance` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '项目余额',
+ `project_remit_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计划账金额',
+ `project_hydrogen_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计氢费',
+ `project_electric_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计电费',
+ `project_consume_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '累计消费',
+ `reminder_threshold` decimal(14,2) DEFAULT NULL COMMENT '提醒阈值',
+ `account_status` tinyint NOT NULL DEFAULT 0 COMMENT '账户状态(0=正常 1=预警 2=欠费)',
+ `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_contract_id` (`contract_id`),
+ KEY `idx_account_id` (`account_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目账户';
+
+-- ----------------------------
+-- 7. energy_account_flow — 统一余额变更流水
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_account_flow`;
+CREATE TABLE `energy_account_flow` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `account_id` bigint NOT NULL COMMENT '关联总账户 ID',
+ `project_account_id` bigint DEFAULT NULL COMMENT '关联项目账户 ID(可选)',
+ `flow_type` tinyint NOT NULL COMMENT '流水类型(1=充值 2=扣款 3=冲正 4=划账 5=退款)',
+ `amount` decimal(14,2) NOT NULL COMMENT '变动金额',
+ `balance_before` decimal(14,2) NOT NULL COMMENT '变动前余额(总账户级别)',
+ `balance_after` decimal(14,2) NOT NULL COMMENT '变动后余额(总账户级别)',
+ `project_balance_before` decimal(14,2) DEFAULT NULL COMMENT '变动前项目余额(仅项目账户操作时填写)',
+ `project_balance_after` decimal(14,2) DEFAULT NULL COMMENT '变动后项目余额(仅项目账户操作时填写)',
+ `biz_type` tinyint NOT NULL COMMENT '业务类型(1=加氢扣款 2=账单结算 3=手动调整 ...)',
+ `biz_id` bigint DEFAULT NULL COMMENT '关联单据 ID',
+ `biz_code` varchar(64) DEFAULT NULL COMMENT '关联单据编号',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `operator_id` bigint DEFAULT NULL COMMENT '操作人 ID',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_account_id` (`account_id`),
+ KEY `idx_project_account_id` (`project_account_id`),
+ KEY `idx_biz_type_biz_id` (`biz_type`, `biz_id`),
+ KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='统一余额变更流水';
+
+-- ----------------------------
+-- 8. energy_station_price — 加氢站客户价格
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_station_price`;
+CREATE TABLE `energy_station_price` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `station_id` bigint NOT NULL COMMENT '加氢站 ID',
+ `customer_id` bigint NOT NULL COMMENT '客户 ID',
+ `cost_price` decimal(10,2) NOT NULL COMMENT '成本价(元/KG)',
+ `customer_price` decimal(10,2) NOT NULL COMMENT '对客价(元/KG)',
+ `effective_date` date NOT NULL COMMENT '生效日期',
+ `expiry_date` date DEFAULT NULL COMMENT '失效日期(可空,空=永久生效)',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0=生效中 1=已失效)',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_station_customer_date` (`station_id`, `customer_id`, `effective_date`),
+ KEY `idx_station_id` (`station_id`),
+ KEY `idx_customer_id` (`customer_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢站客户价格';
+
+-- ----------------------------
+-- 9. energy_station_config — 加氢站扣款配置
+-- ----------------------------
+DROP TABLE IF EXISTS `energy_station_config`;
+CREATE TABLE `energy_station_config` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `station_id` bigint NOT NULL COMMENT '加氢站 ID(唯一,每站一条记录)',
+ `auto_deduct` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否自动扣款(1=是 0=否)',
+ `cooperation_type` tinyint NOT NULL DEFAULT 0 COMMENT '合作类型(0=合作 1=非合作)',
+ `auto_match` tinyint DEFAULT 1 COMMENT '自动匹配开关(0=关闭 1=开启)',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_station_id` (`station_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢站扣款配置';
+
+
+-- ==========================================
+-- 第二部分: 菜单权限(在 oneos_system 数据库执行)
+-- ==========================================
+USE `oneos_system`;
+
+-- 一级菜单: 能源管理(目录,type=1)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5200, '能源管理', '', 1, 50, 0, '/energy', 'ep:lightning', NULL, NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 二级菜单(type=2)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5201, '加氢记录管理', 'energy:hydrogen-record:query', 2, 1, 5200, 'hydrogen-record', 'ep:document', 'energy/hydrogen-record/index', 'EnergyHydrogenRecord', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5202, '加氢明细管理', 'energy:hydrogen-detail:query', 2, 2, 5200, 'hydrogen-detail', 'ep:list', 'energy/hydrogen-detail/index', 'EnergyHydrogenDetail', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5203, '能源账单管理', 'energy:bill:query', 2, 3, 5200, 'bill', 'ep:tickets', 'energy/bill/index', 'EnergyBill', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5204, '能源账户管理', 'energy:account:query', 2, 4, 5200, 'account', 'ep:wallet', 'energy/account/index', 'EnergyAccount', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5205, '价格管理', 'energy:station-price:query', 2, 5, 5200, 'station-price', 'ep:price-tag', 'energy/station-price/index', 'EnergyStationPrice', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5206, '加氢站配置', 'energy:station-config:query', 2, 6, 5200, 'station-config', 'ep:setting', 'energy/station-config/index', 'EnergyStationConfig', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 三级按钮/权限(type=3)
+
+-- 加氢记录管理(parent=5201)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5210, '加氢记录查询', 'energy:hydrogen-record:query', 3, 1, 5201, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5211, '加氢记录创建', 'energy:hydrogen-record:create', 3, 2, 5201, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5212, '加氢记录更新', 'energy:hydrogen-record:update', 3, 3, 5201, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5213, '加氢记录删除', 'energy:hydrogen-record:delete', 3, 4, 5201, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5214, '加氢记录导出', 'energy:hydrogen-record:export', 3, 5, 5201, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5215, '加氢记录导入', 'energy:hydrogen-record:import', 3, 6, 5201, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 加氢明细管理(parent=5202)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5220, '加氢明细查询', 'energy:hydrogen-detail:query', 3, 1, 5202, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5221, '加氢明细创建', 'energy:hydrogen-detail:create', 3, 2, 5202, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5222, '加氢明细更新', 'energy:hydrogen-detail:update', 3, 3, 5202, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5223, '加氢明细删除', 'energy:hydrogen-detail:delete', 3, 4, 5202, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5224, '加氢明细导出', 'energy:hydrogen-detail:export', 3, 5, 5202, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5225, '加氢明细审核', 'energy:hydrogen-detail:audit', 3, 6, 5202, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 能源账单管理(parent=5203)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5230, '能源账单查询', 'energy:bill:query', 3, 1, 5203, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5231, '能源账单创建', 'energy:bill:create', 3, 2, 5203, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5232, '能源账单更新', 'energy:bill:update', 3, 3, 5203, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5233, '能源账单删除', 'energy:bill:delete', 3, 4, 5203, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5234, '能源账单导出', 'energy:bill:export', 3, 5, 5203, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5235, '能源账单审核', 'energy:bill:audit', 3, 6, 5203, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 能源账户管理(parent=5204)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5240, '能源账户查询', 'energy:account:query', 3, 1, 5204, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5241, '能源账户创建', 'energy:account:create', 3, 2, 5204, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5242, '能源账户更新', 'energy:account:update', 3, 3, 5204, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5243, '能源账户删除', 'energy:account:delete', 3, 4, 5204, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5244, '能源账户导出', 'energy:account:export', 3, 5, 5204, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5245, '能源账户充值', 'energy:account:recharge', 3, 6, 5204, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 价格管理(parent=5205)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5250, '价格查询', 'energy:station-price:query', 3, 1, 5205, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5251, '价格创建', 'energy:station-price:create', 3, 2, 5205, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5252, '价格更新', 'energy:station-price:update', 3, 3, 5205, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5253, '价格删除', 'energy:station-price:delete', 3, 4, 5205, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5254, '价格导出', 'energy:station-price:export', 3, 5, 5205, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 加氢站配置(parent=5206)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5260, '加氢站配置查询', 'energy:station-config:query', 3, 1, 5206, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5261, '加氢站配置创建', 'energy:station-config:create', 3, 2, 5206, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5262, '加氢站配置更新', 'energy:station-config:update', 3, 3, 5206, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5263, '加氢站配置删除', 'energy:station-config:delete', 3, 4, 5206, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5264, '加氢站配置导出', 'energy:station-config:export', 3, 5, 5206, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
diff --git a/sql/energy/energy_menu.sql b/sql/energy/energy_menu.sql
new file mode 100644
index 0000000..eb809cd
--- /dev/null
+++ b/sql/energy/energy_menu.sql
@@ -0,0 +1,153 @@
+-- ==========================================
+-- 能源管理模块菜单权限 SQL
+-- 生成日期: 2026-03-15
+-- 说明: ID 从 5100 起步,避免与现有菜单冲突
+-- parent_id=0 表示顶级目录
+-- type: 1=目录, 2=菜单, 3=按钮
+-- status: 0=开启
+-- ==========================================
+
+-- ----------------------------
+-- 一级菜单: 能源管理(目录,type=1)
+-- ----------------------------
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5100, '能源管理', '', 1, 50, 0, '/energy', 'ep:lightning', NULL, NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- ----------------------------
+-- 二级菜单(type=2)
+-- 5101: 加氢记录管理
+-- 5102: 加氢明细管理
+-- 5103: 能源账单管理
+-- 5104: 能源账户管理
+-- 5105: 价格管理
+-- 5106: 加氢站配置
+-- ----------------------------
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5101, '加氢记录管理', 'energy:hydrogen-record:query', 2, 1, 5100, 'hydrogen-record', 'ep:document', 'energy/hydrogen-record/index', 'EnergyHydrogenRecord', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5102, '加氢明细管理', 'energy:hydrogen-detail:query', 2, 2, 5100, 'hydrogen-detail', 'ep:list', 'energy/hydrogen-detail/index', 'EnergyHydrogenDetail', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5103, '能源账单管理', 'energy:bill:query', 2, 3, 5100, 'bill', 'ep:tickets', 'energy/bill/index', 'EnergyBill', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5104, '能源账户管理', 'energy:account:query', 2, 4, 5100, 'account', 'ep:wallet', 'energy/account/index', 'EnergyAccount', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5105, '价格管理', 'energy:station-price:query', 2, 5, 5100, 'station-price', 'ep:price-tag', 'energy/station-price/index', 'EnergyStationPrice', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5106, '加氢站配置', 'energy:station-config:query', 2, 6, 5100, 'station-config', 'ep:setting', 'energy/station-config/index', 'EnergyStationConfig', 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- ----------------------------
+-- 三级按钮/权限(type=3)
+-- ----------------------------
+
+-- 加氢记录管理(parent=5101): 查询、创建、更新、删除、导出、导入
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5110, '加氢记录查询', 'energy:hydrogen-record:query', 3, 1, 5101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5111, '加氢记录创建', 'energy:hydrogen-record:create', 3, 2, 5101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5112, '加氢记录更新', 'energy:hydrogen-record:update', 3, 3, 5101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5113, '加氢记录删除', 'energy:hydrogen-record:delete', 3, 4, 5101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5114, '加氢记录导出', 'energy:hydrogen-record:export', 3, 5, 5101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5115, '加氢记录导入', 'energy:hydrogen-record:import', 3, 6, 5101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 加氢明细管理(parent=5102): 查询、创建、更新、删除、导出、审核
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5120, '加氢明细查询', 'energy:hydrogen-detail:query', 3, 1, 5102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5121, '加氢明细创建', 'energy:hydrogen-detail:create', 3, 2, 5102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5122, '加氢明细更新', 'energy:hydrogen-detail:update', 3, 3, 5102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5123, '加氢明细删除', 'energy:hydrogen-detail:delete', 3, 4, 5102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5124, '加氢明细导出', 'energy:hydrogen-detail:export', 3, 5, 5102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5125, '加氢明细审核', 'energy:hydrogen-detail:audit', 3, 6, 5102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 能源账单管理(parent=5103): 查询、创建、更新、删除、导出、审核
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5130, '能源账单查询', 'energy:bill:query', 3, 1, 5103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5131, '能源账单创建', 'energy:bill:create', 3, 2, 5103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5132, '能源账单更新', 'energy:bill:update', 3, 3, 5103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5133, '能源账单删除', 'energy:bill:delete', 3, 4, 5103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5134, '能源账单导出', 'energy:bill:export', 3, 5, 5103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5135, '能源账单审核', 'energy:bill:audit', 3, 6, 5103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 能源账户管理(parent=5104): 查询、创建、更新、删除、导出、充值
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5140, '能源账户查询', 'energy:account:query', 3, 1, 5104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5141, '能源账户创建', 'energy:account:create', 3, 2, 5104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5142, '能源账户更新', 'energy:account:update', 3, 3, 5104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5143, '能源账户删除', 'energy:account:delete', 3, 4, 5104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5144, '能源账户导出', 'energy:account:export', 3, 5, 5104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5145, '能源账户充值', 'energy:account:recharge', 3, 6, 5104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 价格管理(parent=5105): 查询、创建、更新、删除、导出
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5150, '价格查询', 'energy:station-price:query', 3, 1, 5105, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5151, '价格创建', 'energy:station-price:create', 3, 2, 5105, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5152, '价格更新', 'energy:station-price:update', 3, 3, 5105, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5153, '价格删除', 'energy:station-price:delete', 3, 4, 5105, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5154, '价格导出', 'energy:station-price:export', 3, 5, 5105, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 加氢站配置(parent=5106): 查询、创建、更新、删除、导出
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5160, '加氢站配置查询', 'energy:station-config:query', 3, 1, 5106, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5161, '加氢站配置创建', 'energy:station-config:create', 3, 2, 5106, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5162, '加氢站配置更新', 'energy:station-config:update', 3, 3, 5106, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5163, '加氢站配置删除', 'energy:station-config:delete', 3, 4, 5106, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5164, '加氢站配置导出', 'energy:station-config:export', 3, 5, 5106, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0');
diff --git a/sql/energy/energy_mock_data.sql b/sql/energy/energy_mock_data.sql
new file mode 100644
index 0000000..bfa76ef
--- /dev/null
+++ b/sql/energy/energy_mock_data.sql
@@ -0,0 +1,230 @@
+-- ==========================================
+-- Energy 模块 Mock 数据
+-- 执行方式: 用数据库工具连接 47.103.115.36:3306 直接执行
+-- 脚本会自动切换数据库
+-- ==========================================
+
+-- ==========================================
+-- 第一部分: 在 oneos_asset 中插入关联 mock 数据
+-- (如果已有真实数据,请跳过此部分,并修改下方 energy 数据中的 ID)
+-- ==========================================
+USE `oneos_asset`;
+
+-- 3 个加氢站(停车场)
+INSERT INTO `asset_parking` (`id`, `name`, `address`, `capacity`, `parked_amount`, `tenant_id`, `deleted`, `creator`, `create_time`, `updater`, `update_time`)
+VALUES
+(1001, '上海松江加氢站', '上海市松江区新桥镇莘砖公路368号', 50, 0, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(1002, '上海嘉定加氢站', '上海市嘉定区安亭镇墨玉南路888号', 30, 0, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(1003, '江苏昆山加氢站', '江苏省昆山市千灯镇石浦路199号', 40, 0, 0, b'0', 'admin', NOW(), 'admin', NOW())
+ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);
+
+-- 3 个客户
+INSERT INTO `asset_customer` (`id`, `customer_name`, `customer_code`, `coop_status`, `province`, `city`, `contact`, `contact_mobile`, `tenant_id`, `deleted`, `creator`, `create_time`, `updater`, `update_time`)
+VALUES
+(2001, '上海绿能物流有限公司', 'CUST-2026-001', '合作中', '上海', '上海', '张经理', '13800001001', 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(2002, '江苏氢动力运输有限公司', 'CUST-2026-002', '合作中', '江苏', '苏州', '李经理', '13800001002', 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(2003, '浙江新能源科技有限公司', 'CUST-2026-003', '合作中', '浙江', '杭州', '王经理', '13800001003', 0, b'0', 'admin', NOW(), 'admin', NOW())
+ON DUPLICATE KEY UPDATE `customer_name` = VALUES(`customer_name`);
+
+-- 6 辆车
+INSERT INTO `asset_vehicle_base` (`id`, `vin`, `plate_no`, `vehicle_no`, `color`, `year`, `tenant_id`, `deleted`, `creator`, `create_time`, `updater`, `update_time`)
+VALUES
+(3001, 'LVBV1234567890001', '沪A12345', 'VH-001', '白色', '2025', 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3002, 'LVBV1234567890002', '沪B67890', 'VH-002', '蓝色', '2025', 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3003, 'LVBV1234567890003', '苏A11111', 'VH-003', '白色', '2024', 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3004, 'LVBV1234567890004', '苏B22222', 'VH-004', '绿色', '2024', 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3005, 'LVBV1234567890005', '浙A33333', 'VH-005', '白色', '2025', 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3006, 'LVBV1234567890006', '浙B44444', 'VH-006', '银色', '2025', 0, b'0', 'admin', NOW(), 'admin', NOW())
+ON DUPLICATE KEY UPDATE `plate_no` = VALUES(`plate_no`);
+
+-- 车辆业务关联
+INSERT INTO `asset_vehicle_business` (`id`, `vehicle_id`, `customer_id`, `tenant_id`, `deleted`, `creator`, `create_time`, `updater`, `update_time`)
+VALUES
+(3001, 3001, 2001, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3002, 3002, 2001, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3003, 3003, 2002, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3004, 3004, 2002, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3005, 3005, 2003, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(3006, 3006, 2003, 0, b'0', 'admin', NOW(), 'admin', NOW())
+ON DUPLICATE KEY UPDATE `customer_id` = VALUES(`customer_id`);
+
+-- 3 个合同
+INSERT INTO `asset_contract` (`id`, `contract_code`, `contract_type`, `project_name`, `start_date`, `end_date`, `payment_method`, `payment_cycle`, `signing_company`, `delivery_province`, `delivery_city`, `delivery_location`, `customer_id`, `customer_name`, `approval_status`, `contract_status`, `tenant_id`, `deleted`, `creator`, `create_time`, `updater`, `update_time`)
+VALUES
+(4001, 'HT-2026-001', 2, '绿能物流氢能车辆租赁项目', '2026-01-01', '2026-12-31', '预充值', '按次', '上海氢能科技有限公司', '上海', '上海', '上海市松江区', 2001, '上海绿能物流有限公司', 2, 2, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(4002, 'HT-2026-002', 2, '氢动力城配物流项目', '2026-01-01', '2026-12-31', '月结算', '月结', '上海氢能科技有限公司', '江苏', '苏州', '江苏省苏州市昆山市', 2002, '江苏氢动力运输有限公司', 2, 2, 0, b'0', 'admin', NOW(), 'admin', NOW()),
+(4003, 'HT-2026-003', 2, '新能源冷链运输项目', '2026-02-01', '2027-01-31', '预充值', '按次', '上海氢能科技有限公司', '浙江', '杭州', '浙江省杭州市西湖区', 2003, '浙江新能源科技有限公司', 2, 2, 0, b'0', 'admin', NOW(), 'admin', NOW())
+ON DUPLICATE KEY UPDATE `project_name` = VALUES(`project_name`);
+
+
+-- ==========================================
+-- 第二部分: Energy 模块 mock 数据
+-- ==========================================
+USE `oneos_energy`;
+
+-- ----------------------------
+-- 1. 加氢站配置(3 站)
+-- ----------------------------
+INSERT INTO `energy_station_config` (`id`, `station_id`, `auto_deduct`, `cooperation_type`, `auto_match`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+(1, 1001, 1, 0, 1, '松江站,自动扣款+自动匹配', 'admin', NOW(), 'admin', NOW(), b'0', 0),
+(2, 1002, 1, 0, 1, '嘉定站,自动扣款+自动匹配', 'admin', NOW(), 'admin', NOW(), b'0', 0),
+(3, 1003, 0, 1, 0, '昆山站,非合作站,手动匹配', 'admin', NOW(), 'admin', NOW(), b'0', 0);
+
+-- ----------------------------
+-- 2. 加氢站客户价格(6 条)
+-- ----------------------------
+INSERT INTO `energy_station_price` (`id`, `station_id`, `customer_id`, `cost_price`, `customer_price`, `effective_date`, `expiry_date`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+-- 松江站价格
+(1, 1001, 2001, 28.00, 35.00, '2026-01-01', NULL, 0, 'admin', NOW(), 'admin', NOW(), b'0', 0),
+(2, 1001, 2002, 28.00, 34.00, '2026-01-01', NULL, 0, 'admin', NOW(), 'admin', NOW(), b'0', 0),
+-- 嘉定站价格
+(3, 1002, 2001, 27.50, 35.00, '2026-01-01', NULL, 0, 'admin', NOW(), 'admin', NOW(), b'0', 0),
+(4, 1002, 2002, 27.50, 33.50, '2026-01-01', NULL, 0, 'admin', NOW(), 'admin', NOW(), b'0', 0),
+-- 昆山站价格
+(5, 1003, 2003, 29.00, 36.00, '2026-02-01', NULL, 0, 'admin', NOW(), 'admin', NOW(), b'0', 0),
+-- 已失效的旧价格
+(6, 1001, 2001, 26.00, 33.00, '2025-06-01', '2025-12-31', 1, 'admin', NOW(), 'admin', NOW(), b'0', 0);
+
+-- ----------------------------
+-- 3. 加氢原始记录(20 条,覆盖多种状态)
+-- ----------------------------
+INSERT INTO `energy_hydrogen_record` (`id`, `station_id`, `plate_number`, `hydrogen_date`, `hydrogen_quantity`, `unit_price`, `amount`, `mileage`, `source_type`, `match_status`, `vehicle_id`, `customer_id`, `upload_batch_no`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+-- 已匹配记录(松江站,客户2001)
+( 1, 1001, '沪A12345', '2026-03-01', 12.50, 35.00, 437.50, 15230.00, 1, 1, 3001, 2001, 'IMP20260305001A', 'admin', '2026-03-05 10:00:00', 'admin', '2026-03-05 10:00:00', b'0', 0),
+( 2, 1001, '沪A12345', '2026-03-03', 15.00, 35.00, 525.00, 15580.00, 1, 1, 3001, 2001, 'IMP20260305001A', 'admin', '2026-03-05 10:00:00', 'admin', '2026-03-05 10:00:00', b'0', 0),
+( 3, 1001, '沪B67890', '2026-03-02', 10.80, 35.00, 378.00, 8920.00, 1, 1, 3002, 2001, 'IMP20260305001A', 'admin', '2026-03-05 10:00:00', 'admin', '2026-03-05 10:00:00', b'0', 0),
+( 4, 1001, '沪B67890', '2026-03-05', 13.20, 35.00, 462.00, 9200.00, 1, 1, 3002, 2001, 'IMP20260305001A', 'admin', '2026-03-05 10:00:00', 'admin', '2026-03-05 10:00:00', b'0', 0),
+-- 已匹配记录(嘉定站,客户2002)
+( 5, 1002, '苏A11111', '2026-03-01', 18.00, 33.50, 603.00, 22100.00, 1, 1, 3003, 2002, 'IMP20260306002B', 'admin', '2026-03-06 09:00:00', 'admin', '2026-03-06 09:00:00', b'0', 0),
+( 6, 1002, '苏A11111', '2026-03-04', 16.50, 33.50, 552.75, 22450.00, 1, 1, 3003, 2002, 'IMP20260306002B', 'admin', '2026-03-06 09:00:00', 'admin', '2026-03-06 09:00:00', b'0', 0),
+( 7, 1002, '苏B22222', '2026-03-02', 20.00, 33.50, 670.00, 18600.00, 1, 1, 3004, 2002, 'IMP20260306002B', 'admin', '2026-03-06 09:00:00', 'admin', '2026-03-06 09:00:00', b'0', 0),
+( 8, 1002, '苏B22222', '2026-03-06', 14.80, 33.50, 495.80, 19010.00, 1, 1, 3004, 2002, 'IMP20260306002B', 'admin', '2026-03-06 09:00:00', 'admin', '2026-03-06 09:00:00', b'0', 0),
+-- 已匹配记录(昆山站,客户2003)
+( 9, 1003, '浙A33333', '2026-03-03', 22.00, 36.00, 792.00, 31200.00, 1, 1, 3005, 2003, 'IMP20260307003C', 'admin', '2026-03-07 14:00:00', 'admin', '2026-03-07 14:00:00', b'0', 0),
+(10, 1003, '浙A33333', '2026-03-07', 19.50, 36.00, 702.00, 31800.00, 1, 1, 3005, 2003, 'IMP20260307003C', 'admin', '2026-03-07 14:00:00', 'admin', '2026-03-07 14:00:00', b'0', 0),
+(11, 1003, '浙B44444', '2026-03-05', 17.00, 36.00, 612.00, 27500.00, 1, 1, 3006, 2003, 'IMP20260307003C', 'admin', '2026-03-07 14:00:00', 'admin', '2026-03-07 14:00:00', b'0', 0),
+-- 未匹配记录(车牌在系统中不存在)
+(12, 1001, '沪C99999', '2026-03-08', 11.00, 35.00, 385.00, NULL, 1, 0, NULL, NULL, 'IMP20260310004D', 'admin', '2026-03-10 11:00:00', 'admin', '2026-03-10 11:00:00', b'0', 0),
+(13, 1001, '沪D88888', '2026-03-08', 9.50, 35.00, 332.50, NULL, 1, 0, NULL, NULL, 'IMP20260310004D', 'admin', '2026-03-10 11:00:00', 'admin', '2026-03-10 11:00:00', b'0', 0),
+(14, 1002, '苏C77777', '2026-03-09', 14.00, 33.50, 469.00, NULL, 1, 0, NULL, NULL, 'IMP20260310004D', 'admin', '2026-03-10 11:00:00', 'admin', '2026-03-10 11:00:00', b'0', 0),
+-- 无法匹配记录
+(15, 1003, '京A00001', '2026-03-10', 8.00, 36.00, 288.00, NULL, 1, 2, NULL, NULL, 'IMP20260311005E', 'admin', '2026-03-11 09:00:00', 'admin', '2026-03-11 09:00:00', b'0', 0),
+-- 手动录入(source_type=2)
+(16, 1001, '沪A12345', '2026-03-10', 14.00, 35.00, 490.00, 16100.00, 2, 1, 3001, 2001, NULL, 'admin', '2026-03-10 16:00:00', 'admin', '2026-03-10 16:00:00', b'0', 0),
+(17, 1002, '苏A11111', '2026-03-10', 17.50, 33.50, 586.25, 23100.00, 2, 1, 3003, 2002, NULL, 'admin', '2026-03-10 16:30:00', 'admin', '2026-03-10 16:30:00', b'0', 0),
+-- 最新批次(未处理)
+(18, 1001, '沪A12345', '2026-03-12', 11.80, 35.00, 413.00, 16500.00, 1, 1, 3001, 2001, 'IMP20260313006F', 'admin', '2026-03-13 10:00:00', 'admin', '2026-03-13 10:00:00', b'0', 0),
+(19, 1001, '沪B67890', '2026-03-13', 16.00, 35.00, 560.00, 9800.00, 1, 1, 3002, 2001, 'IMP20260313006F', 'admin', '2026-03-13 10:00:00', 'admin', '2026-03-13 10:00:00', b'0', 0),
+(20, 1002, '苏B22222', '2026-03-14', 13.00, 33.50, 435.50, 19500.00, 1, 1, 3004, 2002, 'IMP20260314007G', 'admin', '2026-03-14 08:00:00', 'admin', '2026-03-14 08:00:00', b'0', 0);
+
+-- ----------------------------
+-- 4. 加氢明细(15 条,对应已匹配的记录,覆盖不同审核/扣款/结算状态)
+-- ----------------------------
+INSERT INTO `energy_hydrogen_detail` (`id`, `record_id`, `station_id`, `vehicle_id`, `plate_number`, `hydrogen_date`, `hydrogen_quantity`, `cost_price`, `cost_amount`, `customer_price`, `customer_amount`, `contract_id`, `customer_id`, `project_name`, `cost_bearer`, `pay_method`, `audit_status`, `audit_remark`, `deduction_status`, `settlement_status`, `bill_id`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+-- 客户2001 明细(预充值,已审核+已扣款+已结算,关联账单1)
+(1, 1, 1001, 3001, '沪A12345', '2026-03-01', 12.50, 28.00, 350.00, 35.00, 437.50, 4001, 2001, '绿能物流氢能车辆租赁项目', 1, 1, 1, '审核通过', 1, 1, 1, NULL, 'admin', '2026-03-05 10:30:00', 'admin', '2026-03-08 10:00:00', b'0', 0),
+(2, 2, 1001, 3001, '沪A12345', '2026-03-03', 15.00, 28.00, 420.00, 35.00, 525.00, 4001, 2001, '绿能物流氢能车辆租赁项目', 1, 1, 1, '审核通过', 1, 1, 1, NULL, 'admin', '2026-03-05 10:30:00', 'admin', '2026-03-08 10:00:00', b'0', 0),
+(3, 3, 1001, 3002, '沪B67890', '2026-03-02', 10.80, 28.00, 302.40, 35.00, 378.00, 4001, 2001, '绿能物流氢能车辆租赁项目', 1, 1, 1, '审核通过', 1, 1, 1, NULL, 'admin', '2026-03-05 10:30:00', 'admin', '2026-03-08 10:00:00', b'0', 0),
+(4, 4, 1001, 3002, '沪B67890', '2026-03-05', 13.20, 28.00, 369.60, 35.00, 462.00, 4001, 2001, '绿能物流氢能车辆租赁项目', 1, 1, 1, '审核通过', 1, 1, 1, NULL, 'admin', '2026-03-05 10:30:00', 'admin', '2026-03-08 10:00:00', b'0', 0),
+
+-- 客户2002 明细(月结算,已审核+未扣款+未结算,关联账单2)
+(5, 5, 1002, 3003, '苏A11111', '2026-03-01', 18.00, 27.50, 495.00, 33.50, 603.00, 4002, 2002, '氢动力城配物流项目', 1, 2, 1, '审核通过', 0, 0, 2, NULL, 'admin', '2026-03-06 09:30:00', 'admin', '2026-03-09 09:00:00', b'0', 0),
+(6, 6, 1002, 3003, '苏A11111', '2026-03-04', 16.50, 27.50, 453.75, 33.50, 552.75, 4002, 2002, '氢动力城配物流项目', 1, 2, 1, '审核通过', 0, 0, 2, NULL, 'admin', '2026-03-06 09:30:00', 'admin', '2026-03-09 09:00:00', b'0', 0),
+(7, 7, 1002, 3004, '苏B22222', '2026-03-02', 20.00, 27.50, 550.00, 33.50, 670.00, 4002, 2002, '氢动力城配物流项目', 1, 2, 1, '审核通过', 0, 0, 2, NULL, 'admin', '2026-03-06 09:30:00', 'admin', '2026-03-09 09:00:00', b'0', 0),
+(8, 8, 1002, 3004, '苏B22222', '2026-03-06', 14.80, 27.50, 407.00, 33.50, 495.80, 4002, 2002, '氢动力城配物流项目', 1, 2, 1, '审核通过', 0, 0, 2, NULL, 'admin', '2026-03-06 09:30:00', 'admin', '2026-03-09 09:00:00', b'0', 0),
+
+-- 客户2003 明细(预充值,待审核,无账单)
+( 9, 9, 1003, 3005, '浙A33333', '2026-03-03', 22.00, 29.00, 638.00, 36.00, 792.00, 4003, 2003, '新能源冷链运输项目', 1, 1, 0, NULL, 0, 0, NULL, NULL, 'admin', '2026-03-07 14:30:00', 'admin', '2026-03-07 14:30:00', b'0', 0),
+(10, 10, 1003, 3005, '浙A33333', '2026-03-07', 19.50, 29.00, 565.50, 36.00, 702.00, 4003, 2003, '新能源冷链运输项目', 1, 1, 0, NULL, 0, 0, NULL, NULL, 'admin', '2026-03-07 14:30:00', 'admin', '2026-03-07 14:30:00', b'0', 0),
+(11, 11, 1003, 3006, '浙B44444', '2026-03-05', 17.00, 29.00, 493.00, 36.00, 612.00, 4003, 2003, '新能源冷链运输项目', 1, 1, 0, NULL, 0, 0, NULL, NULL, 'admin', '2026-03-07 14:30:00', 'admin', '2026-03-07 14:30:00', b'0', 0),
+
+-- 客户2001 最新明细(已审核,未结算,无账单 — 可用于生成新账单测试)
+(12, 16, 1001, 3001, '沪A12345', '2026-03-10', 14.00, 28.00, 392.00, 35.00, 490.00, 4001, 2001, '绿能物流氢能车辆租赁项目', 1, 1, 1, '审核通过', 1, 0, NULL, NULL, 'admin', '2026-03-10 16:30:00', 'admin', '2026-03-11 10:00:00', b'0', 0),
+(13, 18, 1001, 3001, '沪A12345', '2026-03-12', 11.80, 28.00, 330.40, 35.00, 413.00, 4001, 2001, '绿能物流氢能车辆租赁项目', 1, 1, 1, '审核通过', 1, 0, NULL, NULL, 'admin', '2026-03-13 10:30:00', 'admin', '2026-03-14 10:00:00', b'0', 0),
+(14, 19, 1001, 3002, '沪B67890', '2026-03-13', 16.00, 28.00, 448.00, 35.00, 560.00, 4001, 2001, '绿能物流氢能车辆租赁项目', 1, 1, 1, '审核通过', 1, 0, NULL, NULL, 'admin', '2026-03-13 10:30:00', 'admin', '2026-03-14 10:00:00', b'0', 0),
+
+-- 客户2002 已审核驳回明细
+(15, 17, 1002, 3003, '苏A11111', '2026-03-10', 17.50, 27.50, 481.25, 33.50, 586.25, 4002, 2002, '氢动力城配物流项目', 1, 2, 2, '金额异常,请核实', 0, 0, NULL, '驳回后待重新审核', 'admin', '2026-03-10 17:00:00', 'admin', '2026-03-11 15:00:00', b'0', 0);
+
+-- ----------------------------
+-- 5. 能源账单(3 条,覆盖不同状态)
+-- ----------------------------
+INSERT INTO `energy_bill` (`id`, `bill_code`, `energy_type`, `customer_id`, `customer_name`, `contract_id`, `station_id`, `station_name`, `cooperation_type`, `bill_period_start`, `bill_period_end`, `receivable_amount`, `actual_amount`, `adjustment_amount`, `paid_amount`, `total_quantity`, `detail_count`, `status`, `audit_status`, `submit_status`, `payment_status`, `auditor_id`, `audit_time`, `audit_remark`, `generate_time`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+-- 账单1: 客户2001,已生成+已审核+已支付(完结状态)
+(1, 'EB20260308140000A1B2', 0, 2001, '上海绿能物流有限公司', 4001, 1001, '上海松江加氢站', 1,
+ '2026-03-01', '2026-03-07', 1802.50, 1802.50, 0.00, 1802.50, 51.50, 4, 1, 1, 1, 2,
+ 1, '2026-03-09 10:00:00', '审核通过,金额核对无误', '2026-03-08 14:00:00',
+ 'admin', '2026-03-08 14:00:00', 'admin', '2026-03-10 10:00:00', b'0', 0),
+
+-- 账单2: 客户2002,已生成+待审核(月结算)
+(2, 'EB20260309100000C3D4', 0, 2002, '江苏氢动力运输有限公司', 4002, 1002, '上海嘉定加氢站', 2,
+ '2026-03-01', '2026-03-07', 2321.55, 2221.55, -100.00, 0.00, 69.30, 4, 1, 0, 0, 0,
+ NULL, NULL, NULL, '2026-03-09 10:00:00',
+ 'admin', '2026-03-09 10:00:00', 'admin', '2026-03-09 10:00:00', b'0', 0),
+
+-- 账单3: 客户2001 草稿(还没提交审核)
+(3, 'EB20260314120000E5F6', 0, 2001, '上海绿能物流有限公司', 4001, 1001, '上海松江加氢站', 1,
+ '2026-03-08', '2026-03-14', 1463.00, 1463.00, 0.00, 0.00, 41.80, 3, 0, 0, 0, 0,
+ NULL, NULL, NULL, '2026-03-14 12:00:00',
+ 'admin', '2026-03-14 12:00:00', 'admin', '2026-03-14 12:00:00', b'0', 0);
+
+-- ----------------------------
+-- 6. 账单调整记录(2 条,关联账单2)
+-- ----------------------------
+INSERT INTO `energy_bill_adjustment` (`id`, `bill_id`, `detail_id`, `adjustment_type`, `amount`, `reason`, `attachment_urls`, `operator_id`, `operate_time`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+(1, 2, 8, 2, 80.00, '3月6日苏B22222加氢量数据偏高,与站点核对后减少80元', NULL, 1, '2026-03-10 11:00:00', 'admin', '2026-03-10 11:00:00', 'admin', '2026-03-10 11:00:00', b'0', 0),
+(2, 2, NULL, 2, 20.00, '整单优惠减免20元', NULL, 1, '2026-03-10 11:30:00', 'admin', '2026-03-10 11:30:00', 'admin', '2026-03-10 11:30:00', b'0', 0);
+
+-- ----------------------------
+-- 7. 客户能源账户(3 个客户)
+-- ----------------------------
+INSERT INTO `energy_account` (`id`, `customer_id`, `balance`, `init_balance`, `accumulated_recharge`, `accumulated_hydrogen`, `accumulated_electric`, `accumulated_consume`, `reminder_threshold`, `account_status`, `last_recharge_date`, `version`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+-- 客户2001: 预充值模式,余额正常
+(1, 2001, 47197.50, 0.00, 50000.00, 2802.50, 0.00, 2802.50, 5000.00, 0, '2026-03-01', 5,
+ 'admin', '2026-01-15 10:00:00', 'admin', '2026-03-14 12:00:00', b'0', 0),
+-- 客户2002: 月结算模式,余额为零(月结不需要预充)
+(2, 2002, 0.00, 0.00, 0.00, 2321.55, 0.00, 2321.55, NULL, 0, NULL, 2,
+ 'admin', '2026-01-15 10:00:00', 'admin', '2026-03-09 10:00:00', b'0', 0),
+-- 客户2003: 预充值模式,余额预警
+(3, 2003, 3894.00, 0.00, 6000.00, 2106.00, 0.00, 2106.00, 5000.00, 1, '2026-02-20', 3,
+ 'admin', '2026-02-01 10:00:00', 'admin', '2026-03-07 15:00:00', b'0', 0);
+
+-- ----------------------------
+-- 8. 项目账户(3 个项目,对应3个合同)
+-- ----------------------------
+INSERT INTO `energy_project_account` (`id`, `account_id`, `contract_id`, `project_name`, `project_balance`, `project_remit_amount`, `project_hydrogen_amount`, `project_electric_amount`, `project_consume_amount`, `reminder_threshold`, `account_status`, `version`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+(1, 1, 4001, '绿能物流氢能车辆租赁项目', 47197.50, 50000.00, 2802.50, 0.00, 2802.50, 5000.00, 0, 5,
+ 'admin', '2026-01-15 10:00:00', 'admin', '2026-03-14 12:00:00', b'0', 0),
+(2, 2, 4002, '氢动力城配物流项目', 0.00, 0.00, 2321.55, 0.00, 2321.55, NULL, 0, 2,
+ 'admin', '2026-01-15 10:00:00', 'admin', '2026-03-09 10:00:00', b'0', 0),
+(3, 3, 4003, '新能源冷链运输项目', 3894.00, 6000.00, 2106.00, 0.00, 2106.00, 5000.00, 1, 3,
+ 'admin', '2026-02-01 10:00:00', 'admin', '2026-03-07 15:00:00', b'0', 0);
+
+-- ----------------------------
+-- 9. 账户流水(12 条,覆盖充值/扣款场景)
+-- ----------------------------
+INSERT INTO `energy_account_flow` (`id`, `account_id`, `project_account_id`, `flow_type`, `amount`, `balance_before`, `balance_after`, `project_balance_before`, `project_balance_after`, `biz_type`, `biz_id`, `biz_code`, `remark`, `operator_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES
+-- 客户2001 流水
+(1, 1, 1, 1, 50000.00, 0.00, 50000.00, 0.00, 50000.00, 3, NULL, NULL, '首次充值', 1, 'admin', '2026-03-01 10:00:00', 'admin', '2026-03-01 10:00:00', b'0', 0),
+(2, 1, 1, 2, -437.50, 50000.00, 49562.50, 50000.00, 49562.50, 1, 1, NULL, '沪A12345 2026-03-01 加氢扣款', NULL, 'admin', '2026-03-05 11:00:00', 'admin', '2026-03-05 11:00:00', b'0', 0),
+(3, 1, 1, 2, -525.00, 49562.50, 49037.50, 49562.50, 49037.50, 1, 2, NULL, '沪A12345 2026-03-03 加氢扣款', NULL, 'admin', '2026-03-05 11:01:00', 'admin', '2026-03-05 11:01:00', b'0', 0),
+(4, 1, 1, 2, -378.00, 49037.50, 48659.50, 49037.50, 48659.50, 1, 3, NULL, '沪B67890 2026-03-02 加氢扣款', NULL, 'admin', '2026-03-05 11:02:00', 'admin', '2026-03-05 11:02:00', b'0', 0),
+(5, 1, 1, 2, -462.00, 48659.50, 48197.50, 48659.50, 48197.50, 1, 4, NULL, '沪B67890 2026-03-05 加氢扣款', NULL, 'admin', '2026-03-05 11:03:00', 'admin', '2026-03-05 11:03:00', b'0', 0),
+(6, 1, 1, 2, -490.00, 48197.50, 47707.50, 48197.50, 47707.50, 1, 12, NULL, '沪A12345 2026-03-10 加氢扣款', NULL, 'admin', '2026-03-11 10:00:00', 'admin', '2026-03-11 10:00:00', b'0', 0),
+(7, 1, 1, 2, -413.00, 47707.50, 47294.50, 47707.50, 47294.50, 1, 13, NULL, '沪A12345 2026-03-12 加氢扣款', NULL, 'admin', '2026-03-14 10:00:00', 'admin', '2026-03-14 10:00:00', b'0', 0),
+(8, 1, 1, 2, -97.00, 47294.50, 47197.50, 47294.50, 47197.50, 1, 14, NULL, '沪B67890 2026-03-13 加氢扣款(部分)', NULL, 'admin', '2026-03-14 10:01:00', 'admin', '2026-03-14 10:01:00', b'0', 0),
+
+-- 客户2003 流水
+( 9, 3, 3, 1, 6000.00, 0.00, 6000.00, 0.00, 6000.00, 3, NULL, NULL, '首次充值', 1, 'admin', '2026-02-20 10:00:00', 'admin', '2026-02-20 10:00:00', b'0', 0),
+(10, 3, 3, 2, -792.00, 6000.00, 5208.00, 6000.00, 5208.00, 1, 9, NULL, '浙A33333 2026-03-03 加氢扣款', NULL, 'admin', '2026-03-07 15:00:00', 'admin', '2026-03-07 15:00:00', b'0', 0),
+(11, 3, 3, 2, -702.00, 5208.00, 4506.00, 5208.00, 4506.00, 1, 10, NULL, '浙A33333 2026-03-07 加氢扣款', NULL, 'admin', '2026-03-07 15:01:00', 'admin', '2026-03-07 15:01:00', b'0', 0),
+(12, 3, 3, 2, -612.00, 4506.00, 3894.00, 4506.00, 3894.00, 1, 11, NULL, '浙B44444 2026-03-05 加氢扣款', NULL, 'admin', '2026-03-07 15:02:00', 'admin', '2026-03-07 15:02:00', b'0', 0);
diff --git a/sql/energy/import_sql.py b/sql/energy/import_sql.py
new file mode 100644
index 0000000..e6ae8ea
--- /dev/null
+++ b/sql/energy/import_sql.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+"""
+SQL 导入工具
+"""
+import pymysql
+import sys
+
+def execute_sql_file(host, port, user, password, sql_file):
+ """执行 SQL 文件"""
+ try:
+ # 读取 SQL 文件
+ with open(sql_file, 'r', encoding='utf-8') as f:
+ sql_content = f.read()
+
+ # 连接数据库(不指定数据库,让 SQL 中的 USE 语句生效)
+ connection = pymysql.connect(
+ host=host,
+ port=port,
+ user=user,
+ password=password,
+ charset='utf8mb4',
+ cursorclass=pymysql.cursors.DictCursor,
+ autocommit=False
+ )
+
+ print(f"✓ 连接数据库成功: {host}:{port}")
+
+ # 分割 SQL 语句(按分号分割,但要处理存储过程等特殊情况)
+ statements = []
+ current_statement = []
+ in_delimiter = False
+
+ for line in sql_content.split('\n'):
+ line = line.strip()
+
+ # 跳过注释和空行
+ if not line or line.startswith('--') or line.startswith('#'):
+ continue
+
+ # 处理 DELIMITER
+ if line.upper().startswith('DELIMITER'):
+ in_delimiter = not in_delimiter
+ continue
+
+ current_statement.append(line)
+
+ # 判断语句结束
+ if not in_delimiter and line.endswith(';'):
+ statements.append(' '.join(current_statement))
+ current_statement = []
+
+ # 执行 SQL 语句
+ with connection.cursor() as cursor:
+ success_count = 0
+ error_count = 0
+
+ for i, statement in enumerate(statements, 1):
+ if not statement.strip():
+ continue
+
+ try:
+ cursor.execute(statement)
+ connection.commit()
+ success_count += 1
+
+ # 每 10 条打印一次进度
+ if success_count % 10 == 0:
+ print(f" 已执行 {success_count} 条语句...")
+
+ except Exception as e:
+ error_count += 1
+ print(f"✗ 语句 {i} 执行失败: {str(e)[:100]}")
+ # 继续执行下一条
+
+ print(f"\n✓ 执行完成: 成功 {success_count} 条, 失败 {error_count} 条")
+
+ connection.close()
+ return True
+
+ except Exception as e:
+ print(f"✗ 错误: {e}")
+ return False
+
+if __name__ == '__main__':
+ # 数据库配置
+ DB_HOST = '47.103.115.36'
+ DB_PORT = 3306
+ DB_USER = 'root'
+ DB_PASSWORD = 'Passw0rd2026'
+
+ # SQL 文件列表
+ sql_files = [
+ 'energy_init_all.sql',
+ 'energy_mock_data.sql'
+ ]
+
+ print("=" * 60)
+ print("OneOS Energy 模块数据导入")
+ print("=" * 60)
+
+ for sql_file in sql_files:
+ print(f"\n>>> 导入 {sql_file}")
+ success = execute_sql_file(DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, sql_file)
+ if not success:
+ print(f"✗ {sql_file} 导入失败")
+ sys.exit(1)
+
+ print("\n" + "=" * 60)
+ print("✓ 所有数据导入完成")
+ print("=" * 60)
diff --git a/sql/execute-all-updates.sql b/sql/execute-all-updates.sql
new file mode 100644
index 0000000..ae0e2f5
--- /dev/null
+++ b/sql/execute-all-updates.sql
@@ -0,0 +1,209 @@
+-- ==================== 数据库更新脚本汇总 ====================
+-- 执行日期:2026-03-12
+-- 说明:包含 parking、vehiclemodel、customer、vehicleregistration 模块的所有更新
+--
+-- 执行顺序:
+-- 1. 车型参数表字段补全(asset 库)
+-- 2. 车辆类型字典数据(system 库)
+-- 3. 车辆上牌记录表创建(asset 库)
+-- 4. 菜单和权限配置(system 库)
+
+-- ==================== 使用说明 ====================
+-- 各个模块是分库的,请按照以下顺序执行:
+--
+-- 1. 连接到 asset 库,执行第一部分和第三部分
+-- 2. 连接到 system 库(或主库),执行第二部分和第四部分
+
+-- ==================== 第一部分:车型参数表字段补全(asset 库)====================
+USE `asset`;
+
+-- 检查字段是否已存在
+SELECT 'Checking asset_vehicle_model table...' AS status;
+
+-- 添加车型字段
+ALTER TABLE `asset_vehicle_model`
+ ADD COLUMN IF NOT EXISTS `vehicle_type` int DEFAULT NULL COMMENT '车辆类型(字典)' AFTER `model`,
+ ADD COLUMN IF NOT EXISTS `plate_color` varchar(50) DEFAULT NULL COMMENT '车牌颜色(如:绿牌、蓝牌、黄牌)' AFTER `notice_model`,
+ ADD COLUMN IF NOT EXISTS `battery_type` varchar(100) DEFAULT NULL COMMENT '电池类型(如:磷酸铁锂、三元锂)' AFTER `battery_factory`,
+ ADD COLUMN IF NOT EXISTS `hydrogen_factory` varchar(200) DEFAULT NULL COMMENT '供氢系统厂家' AFTER `refrigerator_factory`;
+
+-- 添加索引(如果不存在)
+ALTER TABLE `asset_vehicle_model`
+ ADD INDEX IF NOT EXISTS `idx_vehicle_type` (`vehicle_type`) COMMENT '车辆类型查询',
+ ADD INDEX IF NOT EXISTS `idx_brand_model` (`brand`, `model`) COMMENT '品牌型号组合查询';
+
+SELECT 'Vehicle model fields updated successfully!' AS status;
+
+-- ==================== 第二部分:车辆类型字典数据(system 库或主库)====================
+-- 注意:如果 system_dict_type 和 system_dict_data 在主库,请切换到主库
+-- USE `ruoyi-vue-pro`; -- 或者你的主库名称
+
+-- 检查字典类型是否已存在
+SELECT 'Checking vehicle type dictionary...' AS status;
+
+-- 创建车辆类型字典类型(如果不存在)
+INSERT INTO `system_dict_type` (
+ `name`,
+ `type`,
+ `status`,
+ `remark`,
+ `creator`,
+ `create_time`,
+ `updater`,
+ `update_time`,
+ `deleted`,
+ `deleted_time`
+)
+SELECT
+ '车辆类型',
+ 'asset_vehicle_type',
+ 0,
+ '车辆资产管理-车辆类型分类',
+ 'admin',
+ NOW(),
+ 'admin',
+ NOW(),
+ 0,
+ NULL
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `system_dict_type` WHERE `type` = 'asset_vehicle_type'
+);
+
+-- 创建车辆类型字典数据(如果不存在)
+INSERT INTO `system_dict_data` (
+ `sort`,
+ `label`,
+ `value`,
+ `dict_type`,
+ `status`,
+ `color_type`,
+ `css_class`,
+ `remark`,
+ `creator`,
+ `create_time`,
+ `updater`,
+ `update_time`,
+ `deleted`
+)
+SELECT * FROM (
+ SELECT 1 AS sort, '小型轿车' AS label, '1' AS value, 'asset_vehicle_type' AS dict_type, 0 AS status, 'primary' AS color_type, '' AS css_class, '5座以下的小型乘用车' AS remark, 'admin' AS creator, NOW() AS create_time, 'admin' AS updater, NOW() AS update_time, 0 AS deleted UNION ALL
+ SELECT 2, 'SUV', '2', 'asset_vehicle_type', 0, 'success', '', '运动型多用途车', 'admin', NOW(), 'admin', NOW(), 0 UNION ALL
+ SELECT 3, '厢式货车', '3', 'asset_vehicle_type', 0, 'info', '', '封闭式货运车辆', 'admin', NOW(), 'admin', NOW(), 0 UNION ALL
+ SELECT 4, '18吨双飞翼货车', '4', 'asset_vehicle_type', 0, 'warning', '', '大型货运车辆', 'admin', NOW(), 'admin', NOW(), 0 UNION ALL
+ SELECT 5, '轻型货车', '5', 'asset_vehicle_type', 0, 'default', '', '4.5吨以下货车', 'admin', NOW(), 'admin', NOW(), 0 UNION ALL
+ SELECT 6, '中型货车', '6', 'asset_vehicle_type', 0, 'default', '', '4.5-12吨货车', 'admin', NOW(), 'admin', NOW(), 0 UNION ALL
+ SELECT 7, '重型货车', '7', 'asset_vehicle_type', 0, 'danger', '', '12吨以上货车', 'admin', NOW(), 'admin', NOW(), 0 UNION ALL
+ SELECT 8, '客车', '8', 'asset_vehicle_type', 0, 'primary', '', '大中型客运车辆', 'admin', NOW(), 'admin', NOW(), 0 UNION ALL
+ SELECT 9, '专用车', '9', 'asset_vehicle_type', 0, 'info', '', '特种用途车辆', 'admin', NOW(), 'admin', NOW(), 0
+) AS tmp
+WHERE NOT EXISTS (
+ SELECT 1 FROM `system_dict_data` WHERE `dict_type` = 'asset_vehicle_type' AND `value` = tmp.value
+);
+
+SELECT 'Vehicle type dictionary created successfully!' AS status;
+
+-- ==================== 第三部分:车辆上牌记录表创建(asset 库)====================
+USE `asset`;
+
+-- 创建车辆上牌记录表
+CREATE TABLE IF NOT EXISTS `asset_vehicle_registration` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `vehicle_id` BIGINT NOT NULL COMMENT '车辆ID',
+ `vin` VARCHAR(50) NOT NULL COMMENT '车辆识别代号(VIN)',
+ `plate_no` VARCHAR(20) NOT NULL COMMENT '车牌号',
+ `plate_date` DATE NOT NULL COMMENT '上牌日期',
+ `operator` VARCHAR(50) COMMENT '操作员',
+
+ -- OCR 识别信息
+ `recognized_brand` VARCHAR(100) COMMENT 'OCR识别的品牌型号',
+ `recognized_model` VARCHAR(100) COMMENT 'OCR识别的车型',
+ `vehicle_type` VARCHAR(50) COMMENT '车辆类型',
+ `owner` VARCHAR(100) COMMENT '所有人',
+ `use_character` VARCHAR(50) COMMENT '使用性质',
+ `engine_no` VARCHAR(50) COMMENT '发动机号码',
+ `register_date` DATE COMMENT '注册日期',
+ `issue_date` DATE COMMENT '发证日期',
+ `inspection_record` VARCHAR(50) COMMENT '检验记录',
+ `scrap_date` DATE COMMENT '强制报废期止',
+ `curb_weight` VARCHAR(20) COMMENT '整备质量(kg)',
+ `total_mass` VARCHAR(20) COMMENT '总质量(kg)',
+ `approved_passenger_capacity` VARCHAR(20) COMMENT '核定载人数',
+
+ -- 匹配信息
+ `vehicle_model_id` BIGINT COMMENT '匹配的车型ID',
+ `match_confidence` DECIMAL(5,2) COMMENT '匹配置信度(0-100)',
+ `match_method` VARCHAR(20) COMMENT '匹配方式(exact/fuzzy/manual)',
+
+ -- 照片信息
+ `photo_url` VARCHAR(500) COMMENT '行驶证照片URL',
+ `photo_size` INT COMMENT '照片大小(字节)',
+
+ -- OCR 信息
+ `ocr_provider` VARCHAR(50) COMMENT 'OCR厂商',
+ `ocr_cost_time` INT COMMENT 'OCR识别耗时(毫秒)',
+ `ocr_raw_result` TEXT COMMENT 'OCR原始结果(JSON)',
+
+ -- 状态信息
+ `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态(0-待确认 1-已确认 2-已作废)',
+ `remark` VARCHAR(500) COMMENT '备注',
+
+ -- 审计字段
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_vehicle_plate` (`vehicle_id`, `plate_no`, `deleted`),
+ INDEX `idx_vin` (`vin`),
+ INDEX `idx_plate_no` (`plate_no`),
+ INDEX `idx_plate_date` (`plate_date`),
+ INDEX `idx_status` (`status`),
+ INDEX `idx_create_time` (`create_time`),
+ INDEX `idx_tenant_id` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆上牌记录表';
+
+SELECT 'Vehicle registration table created successfully!' AS status;
+
+-- ==================== 第四部分:菜单和权限配置(system 库或主库)====================
+-- 注意:需要先找到"车辆管理"或"资产管理"的父菜单ID
+-- 如果没有父菜单,请先创建或手动指定 parent_id
+
+-- 查询可能的父菜单
+SELECT id, name, parent_id, path
+FROM system_menu
+WHERE name LIKE '%车辆%' OR name LIKE '%资产%' OR path LIKE '%vehicle%' OR path LIKE '%asset%'
+ORDER BY id;
+
+-- 手动执行:将下面的 @parent_id 替换为实际的父菜单ID
+-- SET @parent_id = YOUR_PARENT_MENU_ID;
+
+-- 插入上牌管理菜单(请手动替换 @parent_id)
+-- INSERT INTO system_menu(
+-- name, permission, type, sort, parent_id,
+-- path, icon, component, status, component_name
+-- )
+-- VALUES (
+-- '上牌管理', '', 2, 3, @parent_id,
+-- 'registration', 'form', 'asset/vehicle/registration/index', 0, 'VehicleRegistration'
+-- );
+
+-- 获取刚插入的菜单ID
+-- SET @menuId = LAST_INSERT_ID();
+
+-- 上牌管理按钮权限
+-- INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+-- VALUES
+-- ('上牌记录查询', 'asset:vehicle-registration:query', 3, 1, @menuId, '', '', '', 0),
+-- ('上牌记录创建', 'asset:vehicle-registration:create', 3, 2, @menuId, '', '', '', 0),
+-- ('上牌记录更新', 'asset:vehicle-registration:update', 3, 3, @menuId, '', '', '', 0),
+-- ('上牌记录删除', 'asset:vehicle-registration:delete', 3, 4, @menuId, '', '', '', 0),
+-- ('行驶证识别', 'asset:vehicle-registration:recognize', 3, 5, @menuId, '', '', '', 0);
+
+SELECT '========================================' AS '';
+SELECT 'All updates completed!' AS status;
+SELECT '========================================' AS '';
+SELECT '注意:菜单部分需要手动执行,请先查询父菜单ID' AS note;
diff --git a/sql/rebuild/asset_ddl.sql b/sql/rebuild/asset_ddl.sql
new file mode 100644
index 0000000..86a6957
--- /dev/null
+++ b/sql/rebuild/asset_ddl.sql
@@ -0,0 +1,836 @@
+-- ============================================================
+-- Asset Module DDL - Generated from DO classes
+-- Database: oneos_asset
+-- Generated: 2026-03-16
+-- ============================================================
+
+-- 1. asset_parking (停车场)
+DROP TABLE IF EXISTS `asset_parking`;
+CREATE TABLE `asset_parking` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `name` varchar(255) DEFAULT NULL COMMENT '停车场名称',
+ `capacity` int DEFAULT NULL COMMENT '容量',
+ `manager_name` varchar(255) DEFAULT NULL COMMENT '管理员姓名',
+ `manager_phone` varchar(64) DEFAULT NULL COMMENT '管理员电话',
+ `contact_name` varchar(255) DEFAULT NULL COMMENT '联系人姓名',
+ `contact_phone` varchar(64) DEFAULT NULL COMMENT '联系人电话',
+ `parked_amount` int DEFAULT NULL COMMENT '已停数量',
+ `province` varchar(64) DEFAULT NULL COMMENT '省份',
+ `city` varchar(64) DEFAULT NULL COMMENT '城市',
+ `address` varchar(500) DEFAULT NULL COMMENT '详细地址',
+ `longitude` varchar(32) DEFAULT NULL COMMENT '经度',
+ `latitude` varchar(32) DEFAULT NULL COMMENT '纬度',
+ `lease_start_date` date DEFAULT NULL COMMENT '租赁开始日期',
+ `lease_end_date` date DEFAULT NULL COMMENT '租赁结束日期',
+ `rent_fee` decimal(18,2) DEFAULT NULL COMMENT '租金',
+ `contract_file_url` varchar(500) DEFAULT NULL COMMENT '合同文件URL',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='停车场';
+
+-- 2. asset_vehicle_model (车型参数)
+DROP TABLE IF EXISTS `asset_vehicle_model`;
+CREATE TABLE `asset_vehicle_model` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `brand` varchar(128) DEFAULT NULL COMMENT '品牌',
+ `model` varchar(128) DEFAULT NULL COMMENT '型号',
+ `vehicle_type` varchar(64) DEFAULT NULL COMMENT '车辆类型',
+ `model_label` varchar(128) DEFAULT NULL COMMENT '车型标签',
+ `fuel_type` varchar(64) DEFAULT NULL COMMENT '燃料类型',
+ `plate_color` varchar(32) DEFAULT NULL COMMENT '车牌颜色',
+ `truck_size_x` varchar(32) DEFAULT NULL COMMENT '货箱尺寸X',
+ `truck_size_y` varchar(32) DEFAULT NULL COMMENT '货箱尺寸Y',
+ `truck_size_z` varchar(32) DEFAULT NULL COMMENT '货箱尺寸Z',
+ `tire_size` varchar(64) DEFAULT NULL COMMENT '轮胎尺寸',
+ `tire_number` int DEFAULT NULL COMMENT '轮胎数量',
+ `battery_type` varchar(64) DEFAULT NULL COMMENT '电池类型',
+ `battery_factory` varchar(128) DEFAULT NULL COMMENT '电池厂家',
+ `reserve_electricity` decimal(18,2) DEFAULT NULL COMMENT '储备电量',
+ `electricity_mileage` int DEFAULT NULL COMMENT '纯电续航里程',
+ `hydrogen_capacity` int DEFAULT NULL COMMENT '储氢容量',
+ `hydrogen_factory` varchar(128) DEFAULT NULL COMMENT '氢系统厂家',
+ `hydrogen_unit` varchar(32) DEFAULT NULL COMMENT '氢气单位',
+ `hydrogen_mileage` int DEFAULT NULL COMMENT '氢续航里程',
+ `refrigerator_factory` varchar(128) DEFAULT NULL COMMENT '冷机厂家',
+ `online_spread_enterprise` varchar(255) DEFAULT NULL COMMENT '线上传播企业',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车型参数';
+
+-- 3. asset_vehicle_model_maintain_item (车型保养项目)
+DROP TABLE IF EXISTS `asset_vehicle_model_maintain_item`;
+CREATE TABLE `asset_vehicle_model_maintain_item` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `vehicle_model_id` bigint DEFAULT NULL COMMENT '车型ID',
+ `maintain_item` varchar(255) DEFAULT NULL COMMENT '保养项目',
+ `kilometer_cycle` int DEFAULT NULL COMMENT '公里周期',
+ `time_cycle` int DEFAULT NULL COMMENT '时间周期(月)',
+ `hour_fee` decimal(18,2) DEFAULT NULL COMMENT '工时费',
+ `material_fee` decimal(18,2) DEFAULT NULL COMMENT '材料费',
+ `total_fee` decimal(18,2) DEFAULT NULL COMMENT '总费用',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车型保养项目';
+
+-- 4. asset_vehicle_base (车辆基础信息)
+DROP TABLE IF EXISTS `asset_vehicle_base`;
+CREATE TABLE `asset_vehicle_base` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `vin` varchar(64) DEFAULT NULL COMMENT 'VIN码',
+ `plate_no` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `vehicle_no` varchar(64) DEFAULT NULL COMMENT '车辆编号',
+ `vehicle_model_id` bigint DEFAULT NULL COMMENT '车型ID',
+ `region_province` varchar(64) DEFAULT NULL COMMENT '所属省份',
+ `region_city` varchar(64) DEFAULT NULL COMMENT '所属城市',
+ `engine_no` varchar(64) DEFAULT NULL COMMENT '发动机号',
+ `register_date` date DEFAULT NULL COMMENT '注册日期',
+ `purchase_date` date DEFAULT NULL COMMENT '购买日期',
+ `purchase_price` decimal(18,2) DEFAULT NULL COMMENT '购买价格',
+ `color` varchar(32) DEFAULT NULL COMMENT '颜色',
+ `year` varchar(16) DEFAULT NULL COMMENT '年款',
+ `mileage` int DEFAULT NULL COMMENT '里程数',
+ `inspect_expire` varchar(32) DEFAULT NULL COMMENT '年检到期',
+ `scrap_date` date DEFAULT NULL COMMENT '报废日期',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_vin` (`vin`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆基础信息';
+
+-- 5. asset_vehicle_location (车辆位置信息)
+DROP TABLE IF EXISTS `asset_vehicle_location`;
+CREATE TABLE `asset_vehicle_location` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `parking_id` bigint DEFAULT NULL COMMENT '停车场ID',
+ `parking_name` varchar(255) DEFAULT NULL COMMENT '停车场名称',
+ `parking_space` varchar(64) DEFAULT NULL COMMENT '车位号',
+ `location` varchar(500) DEFAULT NULL COMMENT '位置描述',
+ `latitude` decimal(18,8) DEFAULT NULL COMMENT '纬度',
+ `longitude` decimal(18,8) DEFAULT NULL COMMENT '经度',
+ `gps_time` datetime DEFAULT NULL COMMENT 'GPS时间',
+ `entry_time` datetime DEFAULT NULL COMMENT '进场时间',
+ `exit_time` datetime DEFAULT NULL COMMENT '出场时间',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_vehicle_id` (`vehicle_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆位置信息';
+
+-- 6. asset_vehicle_business (车辆业务信息)
+DROP TABLE IF EXISTS `asset_vehicle_business`;
+CREATE TABLE `asset_vehicle_business` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `department_id` bigint DEFAULT NULL COMMENT '部门ID',
+ `manager_id` bigint DEFAULT NULL COMMENT '管理人ID',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `ownership` varchar(64) DEFAULT NULL COMMENT '产权归属',
+ `delivery_date` date DEFAULT NULL COMMENT '交车日期',
+ `return_date` date DEFAULT NULL COMMENT '还车日期',
+ `monthly_rent` decimal(18,2) DEFAULT NULL COMMENT '月租金',
+ `deposit` decimal(18,2) DEFAULT NULL COMMENT '押金',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_vehicle_id` (`vehicle_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆业务信息';
+
+-- 7. asset_vehicle_status (车辆状态信息)
+DROP TABLE IF EXISTS `asset_vehicle_status`;
+CREATE TABLE `asset_vehicle_status` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `status` int DEFAULT NULL COMMENT '车辆状态',
+ `operate_status` int DEFAULT NULL COMMENT '运营状态',
+ `storage_status` int DEFAULT NULL COMMENT '仓储状态',
+ `out_status` int DEFAULT NULL COMMENT '出库状态',
+ `preempt_status` int DEFAULT NULL COMMENT '占用状态',
+ `prepare_status` int DEFAULT NULL COMMENT '备车状态',
+ `transfer_status` int DEFAULT NULL COMMENT '调拨状态',
+ `repair_status` int DEFAULT NULL COMMENT '维修状态',
+ `license_status` int DEFAULT NULL COMMENT '牌照状态',
+ `scrap_status` int DEFAULT NULL COMMENT '报废状态',
+ `online_status` int DEFAULT NULL COMMENT '在线状态',
+ `is_prepared` bit(1) DEFAULT NULL COMMENT '是否已备车',
+ `is_delivered` bit(1) DEFAULT NULL COMMENT '是否已交车',
+ `is_returned` bit(1) DEFAULT NULL COMMENT '是否已还车',
+ `last_maintain_date` date DEFAULT NULL COMMENT '上次保养日期',
+ `next_maintain_date` date DEFAULT NULL COMMENT '下次保养日期',
+ `last_inspect_date` date DEFAULT NULL COMMENT '上次年检日期',
+ `next_inspect_date` date DEFAULT NULL COMMENT '下次年检日期',
+ `insurance_expire_date` date DEFAULT NULL COMMENT '保险到期日期',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_vehicle_id` (`vehicle_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆状态信息';
+
+-- 8. asset_customer (客户)
+DROP TABLE IF EXISTS `asset_customer`;
+CREATE TABLE `asset_customer` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `customer_code` varchar(64) DEFAULT NULL COMMENT '客户编码',
+ `coop_status` varchar(32) DEFAULT NULL COMMENT '合作状态',
+ `customer_name` varchar(255) DEFAULT NULL COMMENT '客户名称',
+ `province` varchar(64) DEFAULT NULL COMMENT '省份',
+ `city` varchar(64) DEFAULT NULL COMMENT '城市',
+ `address` varchar(500) DEFAULT NULL COMMENT '地址',
+ `region` varchar(64) DEFAULT NULL COMMENT '区域',
+ `contact` varchar(64) DEFAULT NULL COMMENT '联系人',
+ `contact_mobile` varchar(32) DEFAULT NULL COMMENT '联系手机',
+ `contact_phone` varchar(32) DEFAULT NULL COMMENT '联系电话',
+ `email` varchar(128) DEFAULT NULL COMMENT '邮箱',
+ `credit_code_or_id` varchar(64) DEFAULT NULL COMMENT '统一社会信用代码/身份证',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `tax_id` varchar(64) DEFAULT NULL COMMENT '税号',
+ `invoice_address` varchar(500) DEFAULT NULL COMMENT '开票地址',
+ `invoice_phone` varchar(32) DEFAULT NULL COMMENT '开票电话',
+ `account` varchar(64) DEFAULT NULL COMMENT '银行账号',
+ `opening_bank` varchar(255) DEFAULT NULL COMMENT '开户行',
+ `mailing_address` varchar(500) DEFAULT NULL COMMENT '邮寄地址',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户';
+
+-- 9. asset_customer_business_manager (客户商务经理)
+DROP TABLE IF EXISTS `asset_customer_business_manager`;
+CREATE TABLE `asset_customer_business_manager` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `business_manager_name` varchar(64) DEFAULT NULL COMMENT '商务经理名称',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户商务经理';
+
+-- 10. asset_supplier (供应商)
+DROP TABLE IF EXISTS `asset_supplier`;
+CREATE TABLE `asset_supplier` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `supplier_code` varchar(64) DEFAULT NULL COMMENT '供应商编码',
+ `coop_status` varchar(32) DEFAULT NULL COMMENT '合作状态',
+ `supplier_name` varchar(255) DEFAULT NULL COMMENT '供应商名称',
+ `type` varchar(64) DEFAULT NULL COMMENT '类型',
+ `province` varchar(64) DEFAULT NULL COMMENT '省份',
+ `city` varchar(64) DEFAULT NULL COMMENT '城市',
+ `address` varchar(500) DEFAULT NULL COMMENT '地址',
+ `region` varchar(64) DEFAULT NULL COMMENT '区域',
+ `contact` varchar(64) DEFAULT NULL COMMENT '联系人',
+ `contact_mobile` varchar(32) DEFAULT NULL COMMENT '联系手机',
+ `contact_phone` varchar(32) DEFAULT NULL COMMENT '联系电话',
+ `email` varchar(128) DEFAULT NULL COMMENT '邮箱',
+ `credit_code_or_id` varchar(64) DEFAULT NULL COMMENT '统一社会信用代码',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `tax_id` varchar(64) DEFAULT NULL COMMENT '税号',
+ `invoice_address` varchar(500) DEFAULT NULL COMMENT '开票地址',
+ `invoice_phone` varchar(32) DEFAULT NULL COMMENT '开票电话',
+ `account` varchar(64) DEFAULT NULL COMMENT '银行账号',
+ `opening_bank` varchar(255) DEFAULT NULL COMMENT '开户行',
+ `mailing_address` varchar(500) DEFAULT NULL COMMENT '邮寄地址',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商';
+
+-- 11. asset_vehicle_registration (车辆行驶证)
+DROP TABLE IF EXISTS `asset_vehicle_registration`;
+CREATE TABLE `asset_vehicle_registration` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `vin` varchar(64) DEFAULT NULL COMMENT 'VIN码',
+ `plate_no` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `plate_date` date DEFAULT NULL COMMENT '上牌日期',
+ `operator` varchar(64) DEFAULT NULL COMMENT '经办人',
+ `recognized_brand` varchar(128) DEFAULT NULL COMMENT '识别品牌',
+ `recognized_model` varchar(128) DEFAULT NULL COMMENT '识别型号',
+ `vehicle_type` varchar(64) DEFAULT NULL COMMENT '车辆类型',
+ `owner` varchar(255) DEFAULT NULL COMMENT '所有人',
+ `use_character` varchar(64) DEFAULT NULL COMMENT '使用性质',
+ `engine_no` varchar(64) DEFAULT NULL COMMENT '发动机号',
+ `register_date` date DEFAULT NULL COMMENT '注册日期',
+ `issue_date` date DEFAULT NULL COMMENT '发证日期',
+ `inspection_record` varchar(500) DEFAULT NULL COMMENT '检验记录',
+ `scrap_date` date DEFAULT NULL COMMENT '报废日期',
+ `curb_weight` varchar(32) DEFAULT NULL COMMENT '整备质量',
+ `total_mass` varchar(32) DEFAULT NULL COMMENT '总质量',
+ `approved_passenger_capacity` varchar(32) DEFAULT NULL COMMENT '核定载客',
+ `vehicle_model_id` bigint DEFAULT NULL COMMENT '车型ID',
+ `match_confidence` decimal(5,2) DEFAULT NULL COMMENT '匹配置信度',
+ `match_method` varchar(32) DEFAULT NULL COMMENT '匹配方式',
+ `photo_url` varchar(500) DEFAULT NULL COMMENT '照片URL',
+ `photo_size` int DEFAULT NULL COMMENT '照片大小',
+ `ocr_provider` varchar(64) DEFAULT NULL COMMENT 'OCR提供商',
+ `ocr_cost_time` int DEFAULT NULL COMMENT 'OCR耗时(ms)',
+ `ocr_raw_result` text COMMENT 'OCR原始结果',
+ `status` int DEFAULT NULL COMMENT '状态',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆行驶证';
+
+-- 12. asset_contract (合同)
+DROP TABLE IF EXISTS `asset_contract`;
+CREATE TABLE `asset_contract` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `contract_code` varchar(64) DEFAULT NULL COMMENT '合同编号',
+ `contract_type` int DEFAULT NULL COMMENT '合同类型',
+ `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
+ `start_date` date DEFAULT NULL COMMENT '开始日期',
+ `end_date` date DEFAULT NULL COMMENT '结束日期',
+ `payment_method` varchar(64) DEFAULT NULL COMMENT '付款方式',
+ `payment_cycle` varchar(64) DEFAULT NULL COMMENT '付款周期',
+ `signing_company` varchar(255) DEFAULT NULL COMMENT '签约公司',
+ `delivery_province` varchar(64) DEFAULT NULL COMMENT '交付省份',
+ `delivery_city` varchar(64) DEFAULT NULL COMMENT '交付城市',
+ `delivery_location` varchar(500) DEFAULT NULL COMMENT '交付地点',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `customer_name` varchar(255) DEFAULT NULL COMMENT '客户名称',
+ `third_party_enabled` bit(1) DEFAULT NULL COMMENT '是否启用第三方',
+ `third_party_customer_id` bigint DEFAULT NULL COMMENT '第三方客户ID',
+ `third_party_name` varchar(255) DEFAULT NULL COMMENT '第三方名称',
+ `business_dept_id` bigint DEFAULT NULL COMMENT '商务部门ID',
+ `business_manager_id` bigint DEFAULT NULL COMMENT '商务经理ID',
+ `approval_status` int DEFAULT NULL COMMENT '审批状态',
+ `bpm_instance_id` varchar(64) DEFAULT NULL COMMENT '流程实例ID',
+ `contract_status` int DEFAULT NULL COMMENT '合同状态',
+ `effective_time` datetime DEFAULT NULL COMMENT '生效时间',
+ `terminate_time` datetime DEFAULT NULL COMMENT '终止时间',
+ `terminate_reason` varchar(500) DEFAULT NULL COMMENT '终止原因',
+ `renewed_contract_id` bigint DEFAULT NULL COMMENT '续约合同ID',
+ `original_contract_id` bigint DEFAULT NULL COMMENT '原始合同ID',
+ `hydrogen_bearer` varchar(64) DEFAULT NULL COMMENT '氢费承担方',
+ `hydrogen_payment_method` varchar(64) DEFAULT NULL COMMENT '氢费支付方式',
+ `hydrogen_prepay` decimal(18,2) DEFAULT NULL COMMENT '氢费预付金额',
+ `hydrogen_return_price` decimal(18,2) DEFAULT NULL COMMENT '氢费返还单价',
+ `billing_method` varchar(64) DEFAULT NULL COMMENT '计费方式',
+ `main_vehicle_type` varchar(64) DEFAULT NULL COMMENT '主要车型',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同';
+
+-- 13. asset_contract_vehicle (合同车辆)
+DROP TABLE IF EXISTS `asset_contract_vehicle`;
+CREATE TABLE `asset_contract_vehicle` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `brand` varchar(128) DEFAULT NULL COMMENT '品牌',
+ `model` varchar(128) DEFAULT NULL COMMENT '型号',
+ `plate_no` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `vin` varchar(64) DEFAULT NULL COMMENT 'VIN码',
+ `month_rent` decimal(18,2) DEFAULT NULL COMMENT '月租金',
+ `deposit` decimal(18,2) DEFAULT NULL COMMENT '押金',
+ `vehicle_status` int DEFAULT NULL COMMENT '车辆状态',
+ `actual_delivery_time` datetime DEFAULT NULL COMMENT '实际交付时间',
+ `delivery_person` varchar(64) DEFAULT NULL COMMENT '交付人',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同车辆';
+
+-- 14. asset_contract_vehicle_service (合同车辆服务)
+DROP TABLE IF EXISTS `asset_contract_vehicle_service`;
+CREATE TABLE `asset_contract_vehicle_service` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `contract_vehicle_id` bigint DEFAULT NULL COMMENT '合同车辆ID',
+ `service_name` varchar(255) DEFAULT NULL COMMENT '服务名称',
+ `service_fee` decimal(18,2) DEFAULT NULL COMMENT '服务费',
+ `effective_date` date DEFAULT NULL COMMENT '生效日期',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同车辆服务';
+
+-- 15. asset_contract_authorized (合同授权人)
+DROP TABLE IF EXISTS `asset_contract_authorized`;
+CREATE TABLE `asset_contract_authorized` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `name` varchar(64) DEFAULT NULL COMMENT '姓名',
+ `phone` varchar(32) DEFAULT NULL COMMENT '电话',
+ `id_card` varchar(32) DEFAULT NULL COMMENT '身份证号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同授权人';
+
+-- 16. asset_contract_attachment (合同附件)
+DROP TABLE IF EXISTS `asset_contract_attachment`;
+CREATE TABLE `asset_contract_attachment` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `attachment_type` int DEFAULT NULL COMMENT '附件类型',
+ `file_id` bigint DEFAULT NULL COMMENT '文件ID',
+ `file_name` varchar(255) DEFAULT NULL COMMENT '文件名',
+ `file_url` varchar(500) DEFAULT NULL COMMENT '文件URL',
+ `file_size` bigint DEFAULT NULL COMMENT '文件大小',
+ `upload_time` datetime DEFAULT NULL COMMENT '上传时间',
+ `uploader` varchar(64) DEFAULT NULL COMMENT '上传人',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同附件';
+
+-- 17. asset_contract_change_history (合同变更历史)
+DROP TABLE IF EXISTS `asset_contract_change_history`;
+CREATE TABLE `asset_contract_change_history` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `change_type` varchar(64) DEFAULT NULL COMMENT '变更类型',
+ `change_content` text COMMENT '变更内容',
+ `operator` varchar(64) DEFAULT NULL COMMENT '操作人',
+ `operate_time` datetime DEFAULT NULL COMMENT '操作时间',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同变更历史';
+
+-- 18. asset_inspection_template (验车模板)
+DROP TABLE IF EXISTS `asset_inspection_template`;
+CREATE TABLE `asset_inspection_template` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `code` varchar(64) DEFAULT NULL COMMENT '模板编码',
+ `name` varchar(255) DEFAULT NULL COMMENT '模板名称',
+ `biz_type` int DEFAULT NULL COMMENT '业务类型',
+ `vehicle_type` varchar(64) DEFAULT NULL COMMENT '车辆类型',
+ `status` int DEFAULT NULL COMMENT '状态',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='验车模板';
+
+-- 19. asset_inspection_template_item (验车模板项)
+DROP TABLE IF EXISTS `asset_inspection_template_item`;
+CREATE TABLE `asset_inspection_template_item` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `template_id` bigint DEFAULT NULL COMMENT '模板ID',
+ `category` varchar(64) DEFAULT NULL COMMENT '分类',
+ `item_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
+ `item_code` varchar(64) DEFAULT NULL COMMENT '项目编码',
+ `input_type` varchar(32) DEFAULT NULL COMMENT '输入类型',
+ `sort` int DEFAULT NULL COMMENT '排序',
+ `required` int DEFAULT NULL COMMENT '是否必填',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='验车模板项';
+
+-- 20. asset_inspection_record (验车记录)
+DROP TABLE IF EXISTS `asset_inspection_record`;
+CREATE TABLE `asset_inspection_record` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `record_code` varchar(64) DEFAULT NULL COMMENT '记录编码',
+ `template_id` bigint DEFAULT NULL COMMENT '模板ID',
+ `source_type` int DEFAULT NULL COMMENT '来源类型',
+ `source_id` bigint DEFAULT NULL COMMENT '来源ID',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `inspector_name` varchar(64) DEFAULT NULL COMMENT '检查人',
+ `inspection_time` datetime DEFAULT NULL COMMENT '检查时间',
+ `status` int DEFAULT NULL COMMENT '状态',
+ `overall_result` int DEFAULT NULL COMMENT '总体结果',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `cloned_from_id` bigint DEFAULT NULL COMMENT '克隆自ID',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='验车记录';
+
+-- 21. asset_inspection_record_item (验车记录项)
+DROP TABLE IF EXISTS `asset_inspection_record_item`;
+CREATE TABLE `asset_inspection_record_item` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `record_id` bigint DEFAULT NULL COMMENT '记录ID',
+ `item_code` varchar(64) DEFAULT NULL COMMENT '项目编码',
+ `category` varchar(64) DEFAULT NULL COMMENT '分类',
+ `item_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
+ `input_type` varchar(32) DEFAULT NULL COMMENT '输入类型',
+ `result` int DEFAULT NULL COMMENT '结果',
+ `value` varchar(500) DEFAULT NULL COMMENT '值',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `image_urls` varchar(2000) DEFAULT NULL COMMENT '图片URL列表',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='验车记录项';
+
+-- 22. asset_vehicle_prepare (备车)
+DROP TABLE IF EXISTS `asset_vehicle_prepare`;
+CREATE TABLE `asset_vehicle_prepare` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `plate_no` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `vin` varchar(64) DEFAULT NULL COMMENT 'VIN码',
+ `vehicle_model_id` bigint DEFAULT NULL COMMENT '车型ID',
+ `brand` varchar(128) DEFAULT NULL COMMENT '品牌',
+ `model` varchar(128) DEFAULT NULL COMMENT '型号',
+ `vehicle_type` varchar(64) DEFAULT NULL COMMENT '车辆类型',
+ `parking_lot` varchar(255) DEFAULT NULL COMMENT '停车场',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `contract_code` varchar(64) DEFAULT NULL COMMENT '合同编号',
+ `preparation_type` varchar(64) DEFAULT NULL COMMENT '备车类型',
+ `mileage` int DEFAULT NULL COMMENT '里程',
+ `hydrogen_remaining` decimal(18,2) DEFAULT NULL COMMENT '剩余氢量',
+ `hydrogen_unit` varchar(32) DEFAULT NULL COMMENT '氢气单位',
+ `battery_remaining` decimal(18,2) DEFAULT NULL COMMENT '剩余电量',
+ `has_body_ad` bit(1) DEFAULT NULL COMMENT '是否有车身广告',
+ `body_ad_photos` varchar(2000) DEFAULT NULL COMMENT '车身广告照片',
+ `enlarged_text_photo` varchar(500) DEFAULT NULL COMMENT '放大字照片',
+ `has_tail_lift` bit(1) DEFAULT NULL COMMENT '是否有尾板',
+ `spare_tire_depth` decimal(18,2) DEFAULT NULL COMMENT '备胎花纹深度',
+ `spare_tire_photo` varchar(500) DEFAULT NULL COMMENT '备胎照片',
+ `trailer_plate_no` varchar(32) DEFAULT NULL COMMENT '挂车牌号',
+ `defect_photos` varchar(2000) DEFAULT NULL COMMENT '缺陷照片',
+ `inspection_record_id` bigint DEFAULT NULL COMMENT '验车记录ID',
+ `check_list` text COMMENT '检查清单JSON',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `status` int DEFAULT NULL COMMENT '状态',
+ `complete_time` datetime DEFAULT NULL COMMENT '完成时间',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='备车';
+
+-- 23. asset_delivery_task (交车任务)
+DROP TABLE IF EXISTS `asset_delivery_task`;
+CREATE TABLE `asset_delivery_task` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `task_code` varchar(64) DEFAULT NULL COMMENT '任务编码',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `contract_code` varchar(64) DEFAULT NULL COMMENT '合同编号',
+ `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `customer_name` varchar(255) DEFAULT NULL COMMENT '客户名称',
+ `expected_delivery_date_start` date DEFAULT NULL COMMENT '预计交付开始日期',
+ `expected_delivery_date_end` date DEFAULT NULL COMMENT '预计交付结束日期',
+ `billing_start_date` date DEFAULT NULL COMMENT '计费开始日期',
+ `delivery_province` varchar(64) DEFAULT NULL COMMENT '交付省份',
+ `delivery_city` varchar(64) DEFAULT NULL COMMENT '交付城市',
+ `delivery_location` varchar(500) DEFAULT NULL COMMENT '交付地点',
+ `vehicle_count` int DEFAULT NULL COMMENT '车辆数量',
+ `task_status` int DEFAULT NULL COMMENT '任务状态',
+ `delivery_status` int DEFAULT NULL COMMENT '交付状态',
+ `need_return` bit(1) DEFAULT NULL COMMENT '是否需要还车',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交车任务';
+
+-- 24. asset_delivery_task_vehicle (交车任务车辆)
+DROP TABLE IF EXISTS `asset_delivery_task_vehicle`;
+CREATE TABLE `asset_delivery_task_vehicle` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `task_id` bigint DEFAULT NULL COMMENT '任务ID',
+ `contract_vehicle_id` bigint DEFAULT NULL COMMENT '合同车辆ID',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `plate_no` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `vin` varchar(64) DEFAULT NULL COMMENT 'VIN码',
+ `brand` varchar(128) DEFAULT NULL COMMENT '品牌',
+ `model` varchar(128) DEFAULT NULL COMMENT '型号',
+ `month_rent` decimal(18,2) DEFAULT NULL COMMENT '月租金',
+ `deposit` decimal(18,2) DEFAULT NULL COMMENT '押金',
+ `actual_delivery_date` datetime DEFAULT NULL COMMENT '实际交付日期',
+ `delivery_person` varchar(64) DEFAULT NULL COMMENT '交付人',
+ `is_delivered` bit(1) DEFAULT NULL COMMENT '是否已交付',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交车任务车辆';
+
+-- 25. asset_delivery_order (交车单)
+DROP TABLE IF EXISTS `asset_delivery_order`;
+CREATE TABLE `asset_delivery_order` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `order_code` varchar(64) DEFAULT NULL COMMENT '交车单号',
+ `task_id` bigint DEFAULT NULL COMMENT '任务ID',
+ `task_code` varchar(64) DEFAULT NULL COMMENT '任务编码',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `contract_code` varchar(64) DEFAULT NULL COMMENT '合同编号',
+ `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `customer_name` varchar(255) DEFAULT NULL COMMENT '客户名称',
+ `delivery_date` datetime DEFAULT NULL COMMENT '交车日期',
+ `delivery_person` varchar(64) DEFAULT NULL COMMENT '交车人',
+ `delivery_location` varchar(500) DEFAULT NULL COMMENT '交车地点',
+ `authorized_person_id` bigint DEFAULT NULL COMMENT '授权人ID',
+ `authorized_person_name` varchar(64) DEFAULT NULL COMMENT '授权人姓名',
+ `authorized_person_phone` varchar(32) DEFAULT NULL COMMENT '授权人电话',
+ `authorized_person_id_card` varchar(32) DEFAULT NULL COMMENT '授权人身份证',
+ `esign_flow_id` varchar(128) DEFAULT NULL COMMENT '电子签章流程ID',
+ `esign_status` int DEFAULT NULL COMMENT '电子签章状态',
+ `delivery_photos` varchar(2000) DEFAULT NULL COMMENT '交车照片',
+ `driver_name` varchar(64) DEFAULT NULL COMMENT '司机姓名',
+ `driver_id_card` varchar(32) DEFAULT NULL COMMENT '司机身份证',
+ `driver_phone` varchar(32) DEFAULT NULL COMMENT '司机电话',
+ `inspection_data` text COMMENT '验车数据JSON',
+ `cost_list` text COMMENT '费用清单JSON',
+ `inspection_record_id` bigint DEFAULT NULL COMMENT '验车记录ID',
+ `status` int DEFAULT NULL COMMENT '状态',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交车单';
+
+-- 26. asset_delivery_order_vehicle (交车单车辆)
+DROP TABLE IF EXISTS `asset_delivery_order_vehicle`;
+CREATE TABLE `asset_delivery_order_vehicle` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `order_id` bigint DEFAULT NULL COMMENT '交车单ID',
+ `task_vehicle_id` bigint DEFAULT NULL COMMENT '任务车辆ID',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `plate_no` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `vin` varchar(64) DEFAULT NULL COMMENT 'VIN码',
+ `brand` varchar(128) DEFAULT NULL COMMENT '品牌',
+ `model` varchar(128) DEFAULT NULL COMMENT '型号',
+ `mileage` int DEFAULT NULL COMMENT '里程',
+ `hydrogen_level` decimal(18,2) DEFAULT NULL COMMENT '氢量',
+ `battery_level` decimal(18,2) DEFAULT NULL COMMENT '电量',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交车单车辆';
+
+-- 27. asset_vehicle_replacement (替换车)
+DROP TABLE IF EXISTS `asset_vehicle_replacement`;
+CREATE TABLE `asset_vehicle_replacement` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `replacement_code` varchar(64) DEFAULT NULL COMMENT '替换编号',
+ `replacement_type` int DEFAULT NULL COMMENT '替换类型',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `contract_code` varchar(64) DEFAULT NULL COMMENT '合同编号',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `customer_name` varchar(255) DEFAULT NULL COMMENT '客户名称',
+ `delivery_order_id` bigint DEFAULT NULL COMMENT '交车单ID',
+ `original_vehicle_id` bigint DEFAULT NULL COMMENT '原车辆ID',
+ `original_plate_no` varchar(32) DEFAULT NULL COMMENT '原车牌号',
+ `original_vin` varchar(64) DEFAULT NULL COMMENT '原VIN码',
+ `new_vehicle_id` bigint DEFAULT NULL COMMENT '新车辆ID',
+ `new_plate_no` varchar(32) DEFAULT NULL COMMENT '新车牌号',
+ `new_vin` varchar(64) DEFAULT NULL COMMENT '新VIN码',
+ `replacement_reason` varchar(500) DEFAULT NULL COMMENT '替换原因',
+ `expected_date` date DEFAULT NULL COMMENT '预计替换日期',
+ `actual_date` date DEFAULT NULL COMMENT '实际替换日期',
+ `return_date` date DEFAULT NULL COMMENT '预计归还日期',
+ `actual_return_date` date DEFAULT NULL COMMENT '实际归还日期',
+ `status` int DEFAULT NULL COMMENT '状态',
+ `approval_status` int DEFAULT NULL COMMENT '审批状态',
+ `bpm_instance_id` varchar(64) DEFAULT NULL COMMENT '流程实例ID',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='替换车';
+
+-- 28. asset_return_order (还车单)
+DROP TABLE IF EXISTS `asset_return_order`;
+CREATE TABLE `asset_return_order` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `order_code` varchar(64) DEFAULT NULL COMMENT '还车单号',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `contract_code` varchar(64) DEFAULT NULL COMMENT '合同编号',
+ `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `customer_name` varchar(255) DEFAULT NULL COMMENT '客户名称',
+ `return_date` datetime DEFAULT NULL COMMENT '还车日期',
+ `return_person` varchar(64) DEFAULT NULL COMMENT '还车人',
+ `return_location` varchar(500) DEFAULT NULL COMMENT '还车地点',
+ `return_reason` varchar(64) DEFAULT NULL COMMENT '还车原因',
+ `return_reason_desc` varchar(500) DEFAULT NULL COMMENT '还车原因描述',
+ `total_refund_amount` decimal(18,2) DEFAULT NULL COMMENT '退款总额',
+ `deposit_refund` decimal(18,2) DEFAULT NULL COMMENT '押金退款',
+ `hydrogen_refund` decimal(18,2) DEFAULT NULL COMMENT '氢费退款',
+ `other_charges` decimal(18,2) DEFAULT NULL COMMENT '其他费用',
+ `return_photos` varchar(2000) DEFAULT NULL COMMENT '还车照片',
+ `status` int DEFAULT NULL COMMENT '状态',
+ `approval_status` int DEFAULT NULL COMMENT '审批状态',
+ `bpm_instance_id` varchar(64) DEFAULT NULL COMMENT '流程实例ID',
+ `source_type` int DEFAULT NULL COMMENT '来源类型',
+ `source_id` bigint DEFAULT NULL COMMENT '来源ID',
+ `delivery_order_id` bigint DEFAULT NULL COMMENT '交车单ID',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='还车单';
+
+-- 29. asset_return_order_vehicle (还车单车辆)
+DROP TABLE IF EXISTS `asset_return_order_vehicle`;
+CREATE TABLE `asset_return_order_vehicle` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `return_order_id` bigint DEFAULT NULL COMMENT '还车单ID',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `plate_no` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `vin` varchar(64) DEFAULT NULL COMMENT 'VIN码',
+ `brand` varchar(128) DEFAULT NULL COMMENT '品牌',
+ `model` varchar(128) DEFAULT NULL COMMENT '型号',
+ `return_mileage` int DEFAULT NULL COMMENT '还车里程',
+ `return_hydrogen_level` decimal(18,2) DEFAULT NULL COMMENT '还车氢量',
+ `delivery_hydrogen_level` decimal(18,2) DEFAULT NULL COMMENT '交车氢量',
+ `hydrogen_diff` decimal(18,2) DEFAULT NULL COMMENT '氢量差值',
+ `hydrogen_unit_price` decimal(18,2) DEFAULT NULL COMMENT '氢单价',
+ `hydrogen_refund_amount` decimal(18,2) DEFAULT NULL COMMENT '氢费退款金额',
+ `check_list` text COMMENT '检查清单JSON',
+ `defect_photos` varchar(2000) DEFAULT NULL COMMENT '缺陷照片',
+ `vehicle_damage_fee` decimal(18,2) DEFAULT NULL COMMENT '车辆损坏费',
+ `tool_damage_fee` decimal(18,2) DEFAULT NULL COMMENT '工具损坏费',
+ `unpaid_maintenance_fee` decimal(18,2) DEFAULT NULL COMMENT '未付保养费',
+ `unpaid_repair_fee` decimal(18,2) DEFAULT NULL COMMENT '未付维修费',
+ `violation_fee` decimal(18,2) DEFAULT NULL COMMENT '违章费',
+ `other_fee` decimal(18,2) DEFAULT NULL COMMENT '其他费用',
+ `inspection_record_id` bigint DEFAULT NULL COMMENT '验车记录ID',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='还车单车辆';
+
+-- ============================================================
+-- 车辆全量视图 (view)
+-- ============================================================
+DROP VIEW IF EXISTS `v_asset_vehicle_full`;
+CREATE VIEW `v_asset_vehicle_full` AS
+SELECT
+ b.id, b.vin, b.plate_no, b.vehicle_no, b.vehicle_model_id,
+ b.region_province, b.region_city, b.engine_no, b.register_date,
+ b.purchase_date, b.purchase_price, b.color, b.year, b.mileage,
+ b.inspect_expire, b.scrap_date,
+ b.creator, b.create_time, b.updater, b.update_time, b.deleted,
+ l.parking_id, l.parking_name, l.parking_space, l.location,
+ l.latitude, l.longitude, l.gps_time, l.entry_time, l.exit_time,
+ biz.customer_id, biz.department_id, biz.manager_id, biz.contract_id,
+ biz.ownership, biz.delivery_date, biz.return_date, biz.monthly_rent, biz.deposit,
+ s.status, s.operate_status, s.storage_status, s.out_status,
+ s.preempt_status, s.prepare_status, s.transfer_status,
+ s.repair_status, s.license_status, s.scrap_status, s.online_status,
+ s.is_prepared, s.is_delivered, s.is_returned,
+ s.last_maintain_date, s.next_maintain_date,
+ s.last_inspect_date, s.next_inspect_date, s.insurance_expire_date
+FROM asset_vehicle_base b
+LEFT JOIN asset_vehicle_location l ON b.id = l.vehicle_id AND l.deleted = 0
+LEFT JOIN asset_vehicle_business biz ON b.id = biz.vehicle_id AND biz.deleted = 0
+LEFT JOIN asset_vehicle_status s ON b.id = s.vehicle_id AND s.deleted = 0
+WHERE b.deleted = 0;
diff --git a/sql/rebuild/energy_ddl.sql b/sql/rebuild/energy_ddl.sql
new file mode 100644
index 0000000..1315733
--- /dev/null
+++ b/sql/rebuild/energy_ddl.sql
@@ -0,0 +1,236 @@
+-- ============================================================
+-- Energy Module DDL - Generated from DO classes
+-- Database: oneos_energy
+-- Generated: 2026-03-16
+-- ============================================================
+
+-- 1. energy_hydrogen_record (加氢记录)
+DROP TABLE IF EXISTS `energy_hydrogen_record`;
+CREATE TABLE `energy_hydrogen_record` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT '租户编号',
+ `station_id` bigint DEFAULT NULL COMMENT '加氢站ID',
+ `plate_number` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `hydrogen_date` date DEFAULT NULL COMMENT '加氢日期',
+ `hydrogen_quantity` decimal(18,2) DEFAULT NULL COMMENT '加氢量(kg)',
+ `unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价',
+ `amount` decimal(18,2) DEFAULT NULL COMMENT '金额',
+ `mileage` decimal(18,2) DEFAULT NULL COMMENT '里程(km)',
+ `source_type` int DEFAULT NULL COMMENT '来源类型',
+ `match_status` int DEFAULT NULL COMMENT '匹配状态',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `upload_batch_no` varchar(64) DEFAULT NULL COMMENT '上传批次号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢记录';
+
+-- 2. energy_hydrogen_detail (加氢明细)
+DROP TABLE IF EXISTS `energy_hydrogen_detail`;
+CREATE TABLE `energy_hydrogen_detail` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `record_id` bigint DEFAULT NULL COMMENT '加氢记录ID',
+ `station_id` bigint DEFAULT NULL COMMENT '加氢站ID',
+ `vehicle_id` bigint DEFAULT NULL COMMENT '车辆ID',
+ `plate_number` varchar(32) DEFAULT NULL COMMENT '车牌号',
+ `hydrogen_date` date DEFAULT NULL COMMENT '加氢日期',
+ `hydrogen_quantity` decimal(18,2) DEFAULT NULL COMMENT '加氢量(kg)',
+ `cost_price` decimal(18,2) DEFAULT NULL COMMENT '成本价',
+ `cost_amount` decimal(18,2) DEFAULT NULL COMMENT '成本金额',
+ `customer_price` decimal(18,2) DEFAULT NULL COMMENT '客户价',
+ `customer_amount` decimal(18,2) DEFAULT NULL COMMENT '客户金额',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
+ `cost_bearer` int DEFAULT NULL COMMENT '费用承担方',
+ `pay_method` int DEFAULT NULL COMMENT '支付方式',
+ `audit_status` int DEFAULT NULL COMMENT '审核状态',
+ `audit_remark` varchar(500) DEFAULT NULL COMMENT '审核备注',
+ `deduction_status` int DEFAULT NULL COMMENT '扣费状态',
+ `settlement_status` int DEFAULT NULL COMMENT '结算状态',
+ `bill_id` bigint DEFAULT NULL COMMENT '账单ID',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='加氢明细';
+
+-- 3. energy_bill (能源账单)
+DROP TABLE IF EXISTS `energy_bill`;
+CREATE TABLE `energy_bill` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `bill_code` varchar(64) DEFAULT NULL COMMENT '账单编号',
+ `energy_type` int DEFAULT NULL COMMENT '能源类型',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `customer_name` varchar(255) DEFAULT NULL COMMENT '客户名称',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `station_id` bigint DEFAULT NULL COMMENT '站点ID',
+ `station_name` varchar(255) DEFAULT NULL COMMENT '站点名称',
+ `cooperation_type` int DEFAULT NULL COMMENT '合作模式(1=预充值 2=月结算)',
+ `bill_period_start` date DEFAULT NULL COMMENT '账单周期开始',
+ `bill_period_end` date DEFAULT NULL COMMENT '账单周期结束',
+ `receivable_amount` decimal(18,2) DEFAULT NULL COMMENT '应收金额',
+ `actual_amount` decimal(18,2) DEFAULT NULL COMMENT '实收金额',
+ `adjustment_amount` decimal(18,2) DEFAULT NULL COMMENT '调整金额',
+ `paid_amount` decimal(18,2) DEFAULT NULL COMMENT '已付金额',
+ `total_quantity` decimal(18,2) DEFAULT NULL COMMENT '总用量',
+ `detail_count` int DEFAULT NULL COMMENT '明细条数',
+ `status` int DEFAULT NULL COMMENT '账单状态',
+ `audit_status` int DEFAULT NULL COMMENT '审核状态',
+ `submit_status` int DEFAULT NULL COMMENT '提交状态',
+ `payment_status` int DEFAULT NULL COMMENT '付款状态',
+ `auditor_id` bigint DEFAULT NULL COMMENT '审核人ID',
+ `audit_time` datetime DEFAULT NULL COMMENT '审核时间',
+ `audit_remark` varchar(500) DEFAULT NULL COMMENT '审核备注',
+ `submit_time` datetime DEFAULT NULL COMMENT '提交时间',
+ `generate_time` datetime DEFAULT NULL COMMENT '生成时间',
+ `yos_bill_code` varchar(64) DEFAULT NULL COMMENT 'YOS账单编号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='能源账单';
+
+-- 4. energy_bill_adjustment (账单调整)
+DROP TABLE IF EXISTS `energy_bill_adjustment`;
+CREATE TABLE `energy_bill_adjustment` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `bill_id` bigint DEFAULT NULL COMMENT '账单ID',
+ `detail_id` bigint DEFAULT NULL COMMENT '明细ID',
+ `adjustment_type` int DEFAULT NULL COMMENT '调整类型',
+ `amount` decimal(18,2) DEFAULT NULL COMMENT '调整金额',
+ `reason` varchar(500) DEFAULT NULL COMMENT '调整原因',
+ `attachment_urls` varchar(2000) DEFAULT NULL COMMENT '附件URLs',
+ `operator_id` bigint DEFAULT NULL COMMENT '操作人ID',
+ `operate_time` datetime DEFAULT NULL COMMENT '操作时间',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='账单调整';
+
+-- 5. energy_station_config (站点配置)
+DROP TABLE IF EXISTS `energy_station_config`;
+CREATE TABLE `energy_station_config` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `station_id` bigint DEFAULT NULL COMMENT '站点ID',
+ `auto_deduct` bit(1) DEFAULT NULL COMMENT '是否自动扣费',
+ `cooperation_type` int DEFAULT NULL COMMENT '合作类型',
+ `auto_match` bit(1) DEFAULT NULL COMMENT '自动匹配开关',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站点配置';
+
+-- 6. energy_station_price (站点价格)
+DROP TABLE IF EXISTS `energy_station_price`;
+CREATE TABLE `energy_station_price` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `station_id` bigint DEFAULT NULL COMMENT '站点ID',
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `cost_price` decimal(18,2) DEFAULT NULL COMMENT '成本价',
+ `customer_price` decimal(18,2) DEFAULT NULL COMMENT '客户价',
+ `effective_date` date DEFAULT NULL COMMENT '生效日期',
+ `expiry_date` date DEFAULT NULL COMMENT '失效日期',
+ `status` int DEFAULT NULL COMMENT '状态',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站点价格';
+
+-- 7. energy_account (能源账户)
+DROP TABLE IF EXISTS `energy_account`;
+CREATE TABLE `energy_account` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `customer_id` bigint DEFAULT NULL COMMENT '客户ID',
+ `balance` decimal(18,2) DEFAULT NULL COMMENT '余额',
+ `init_balance` decimal(18,2) DEFAULT NULL COMMENT '初始余额',
+ `accumulated_recharge` decimal(18,2) DEFAULT NULL COMMENT '累计充值金额',
+ `accumulated_hydrogen` decimal(18,2) DEFAULT NULL COMMENT '累计加氢金额',
+ `accumulated_electric` decimal(18,2) DEFAULT NULL COMMENT '累计充电金额',
+ `accumulated_consume` decimal(18,2) DEFAULT NULL COMMENT '累计消费金额',
+ `reminder_threshold` decimal(18,2) DEFAULT NULL COMMENT '余额预警阈值',
+ `account_status` int DEFAULT NULL COMMENT '账户状态',
+ `last_recharge_date` date DEFAULT NULL COMMENT '最后充值日期',
+ `version` int DEFAULT 0 COMMENT '乐观锁版本号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='能源账户';
+
+-- 8. energy_project_account (项目账户)
+DROP TABLE IF EXISTS `energy_project_account`;
+CREATE TABLE `energy_project_account` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `account_id` bigint DEFAULT NULL COMMENT '账户ID',
+ `contract_id` bigint DEFAULT NULL COMMENT '合同ID',
+ `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
+ `project_balance` decimal(18,2) DEFAULT NULL COMMENT '项目余额',
+ `project_remit_amount` decimal(18,2) DEFAULT NULL COMMENT '项目汇款总额',
+ `project_hydrogen_amount` decimal(18,2) DEFAULT NULL COMMENT '项目加氢金额',
+ `project_electric_amount` decimal(18,2) DEFAULT NULL COMMENT '项目充电金额',
+ `project_consume_amount` decimal(18,2) DEFAULT NULL COMMENT '项目消费总额',
+ `reminder_threshold` decimal(18,2) DEFAULT NULL COMMENT '余额预警阈值',
+ `account_status` int DEFAULT NULL COMMENT '账户状态',
+ `version` int DEFAULT 0 COMMENT '乐观锁版本号',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目账户';
+
+-- 9. energy_account_flow (账户流水)
+DROP TABLE IF EXISTS `energy_account_flow`;
+CREATE TABLE `energy_account_flow` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL DEFAULT 1 COMMENT 租户编号,
+ `account_id` bigint DEFAULT NULL COMMENT '账户ID',
+ `project_account_id` bigint DEFAULT NULL COMMENT '项目账户ID',
+ `flow_type` int DEFAULT NULL COMMENT '流水类型',
+ `amount` decimal(18,2) DEFAULT NULL COMMENT '金额',
+ `balance_before` decimal(18,2) DEFAULT NULL COMMENT '变更前余额',
+ `balance_after` decimal(18,2) DEFAULT NULL COMMENT '变更后余额',
+ `project_balance_before` decimal(18,2) DEFAULT NULL COMMENT '变更前项目余额',
+ `project_balance_after` decimal(18,2) DEFAULT NULL COMMENT '变更后项目余额',
+ `biz_type` int DEFAULT NULL COMMENT '业务类型',
+ `biz_id` bigint DEFAULT NULL COMMENT '业务ID',
+ `biz_code` varchar(64) DEFAULT NULL COMMENT '业务编号',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `operator_id` bigint DEFAULT NULL COMMENT '操作人ID',
+ `creator` varchar(64) DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='账户流水';
diff --git a/sql/rebuild/mock_data.sql b/sql/rebuild/mock_data.sql
new file mode 100644
index 0000000..a812439
--- /dev/null
+++ b/sql/rebuild/mock_data.sql
@@ -0,0 +1,197 @@
+-- ============================================================
+-- Mock Data for Asset & Energy Modules
+-- Part 1: Asset data → oneos_asset
+-- Part 2: Energy data → oneos_energy
+-- Generated: 2026-03-16
+-- ============================================================
+
+-- ===================== PART 1: ASSET =====================
+
+-- ---------- 停车场 ----------
+INSERT INTO `asset_parking` (`id`, `name`, `capacity`, `manager_name`, `manager_phone`, `contact_name`, `contact_phone`, `parked_amount`, `province`, `city`, `address`, `longitude`, `latitude`, `lease_start_date`, `lease_end_date`, `rent_fee`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, '上海嘉定氢能停车场', 200, '张伟', '13800001001', '李敏', '13800001002', 45, '上海', '上海市', '嘉定区安亭镇曹安公路5555号', '121.1856', '31.3856', '2025-01-01', '2027-12-31', 50000.00, 'admin', NOW(), 'admin', NOW(), 0),
+(2, '上海临港停车场', 150, '王强', '13800002001', '赵丽', '13800002002', 30, '上海', '上海市', '浦东新区临港大道888号', '121.9300', '30.8900', '2025-03-01', '2027-02-28', 40000.00, 'admin', NOW(), 'admin', NOW(), 0),
+(3, '佛山南海停车场', 100, '陈刚', '13800003001', '周红', '13800003002', 20, '广东', '佛山市', '南海区桂城街道海五路28号', '113.1500', '23.0200', '2025-06-01', '2027-05-31', 30000.00, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 车型参数 ----------
+INSERT INTO `asset_vehicle_model` (`id`, `brand`, `model`, `vehicle_type`, `model_label`, `fuel_type`, `plate_color`, `hydrogen_capacity`, `hydrogen_factory`, `hydrogen_unit`, `hydrogen_mileage`, `battery_type`, `battery_factory`, `reserve_electricity`, `electricity_mileage`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, '飞驰', 'FCEV-C1', '冷藏车', '18吨氢能冷藏车', '氢燃料', '黄色', 30, '国鸿氢能', 'kg', 350, '磷酸铁锂', '宁德时代', 100.00, 200, 'admin', NOW(), 'admin', NOW(), 0),
+(2, '东风', 'EQ5180', '厢式货车', '18吨氢能厢式货车', '氢燃料', '黄色', 25, '亿华通', 'kg', 300, '磷酸铁锂', '比亚迪', 80.00, 150, 'admin', NOW(), 'admin', NOW(), 0),
+(3, '福田', 'BJ5120', '冷藏车', '12吨氢能冷藏车', '氢燃料', '黄色', 20, '国鸿氢能', 'kg', 280, '三元锂', '国轩高科', 60.00, 120, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 车型保养项目 ----------
+INSERT INTO `asset_vehicle_model_maintain_item` (`id`, `vehicle_model_id`, `maintain_item`, `kilometer_cycle`, `time_cycle`, `hour_fee`, `material_fee`, `total_fee`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, '燃料电池系统检查', 10000, 3, 200.00, 500.00, 700.00, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 1, '储氢瓶检测', 20000, 6, 300.00, 0.00, 300.00, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 2, '燃料电池系统检查', 10000, 3, 200.00, 500.00, 700.00, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 客户 ----------
+INSERT INTO `asset_customer` (`id`, `customer_code`, `coop_status`, `customer_name`, `province`, `city`, `address`, `region`, `contact`, `contact_mobile`, `email`, `credit_code_or_id`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 'CUS-2025-001', '合作中', '上海鲜生冷链物流有限公司', '上海', '上海市', '浦东新区张江路100号', '华东', '刘总', '13900001001', 'liu@xiansheng.com', '91310000MA1FY3XY23', '大客户', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 'CUS-2025-002', '合作中', '广州顺达运输有限公司', '广东', '广州市', '天河区天河路200号', '华南', '黄经理', '13900002001', 'huang@shunda.com', '91440100MA5CXKQR0L', NULL, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 'CUS-2025-003', '合作中', '深圳绿能科技有限公司', '广东', '深圳市', '南山区科技园南路50号', '华南', '郑总', '13900003001', 'zheng@lvneng.com', '91440300MA5FKP4Y28', '氢能源客户', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 客户商务经理 ----------
+INSERT INTO `asset_customer_business_manager` (`id`, `customer_id`, `business_manager_name`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, '张三', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 2, '李四', 'admin', NOW(), 'admin', NOW(), 0),
+(3, 3, '王五', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 供应商 ----------
+INSERT INTO `asset_supplier` (`id`, `supplier_code`, `coop_status`, `supplier_name`, `type`, `province`, `city`, `address`, `contact`, `contact_mobile`, `credit_code_or_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 'SUP-2025-001', '合作中', '飞驰科技有限公司', '车辆供应商', '广东', '佛山市', '南海区狮山镇科技路1号', '孙总', '13800010001', '91440600MA4W9F1M0K', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 'SUP-2025-002', '合作中', '国鸿氢能科技有限公司', '配件供应商', '广东', '云浮市', '云城区工业园8号', '钱经理', '13800010002', '91445300MA4WFPQY0T', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 车辆基础信息 (8辆) ----------
+INSERT INTO `asset_vehicle_base` (`id`, `vin`, `plate_no`, `vehicle_no`, `vehicle_model_id`, `region_province`, `region_city`, `engine_no`, `register_date`, `purchase_date`, `purchase_price`, `color`, `year`, `mileage`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 'LFCEV001202500001', '沪A12345', 'VH-2025-001', 1, '上海', '上海市', 'ENG001', '2025-01-15', '2025-01-10', 680000.00, '白色', '2025', 12000, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 'LFCEV001202500002', '沪A12346', 'VH-2025-002', 1, '上海', '上海市', 'ENG002', '2025-01-20', '2025-01-15', 680000.00, '白色', '2025', 15000, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 'LFCEV001202500003', '沪A12347', 'VH-2025-003', 2, '上海', '上海市', 'ENG003', '2025-02-10', '2025-02-05', 550000.00, '蓝色', '2025', 8000, 'admin', NOW(), 'admin', NOW(), 0),
+(4, 'LFCEV001202500004', '粤B88001', 'VH-2025-004', 2, '广东', '广州市', 'ENG004', '2025-03-01', '2025-02-25', 550000.00, '白色', '2025', 20000, 'admin', NOW(), 'admin', NOW(), 0),
+(5, 'LFCEV001202500005', '粤B88002', 'VH-2025-005', 3, '广东', '广州市', 'ENG005', '2025-03-15', '2025-03-10', 450000.00, '蓝色', '2025', 5000, 'admin', NOW(), 'admin', NOW(), 0),
+(6, 'LFCEV001202500006', '粤B88003', 'VH-2025-006', 1, '广东', '深圳市', 'ENG006', '2025-04-01', '2025-03-25', 680000.00, '白色', '2025', 3000, 'admin', NOW(), 'admin', NOW(), 0),
+(7, 'LFCEV001202500007', '沪A12348', 'VH-2025-007', 3, '上海', '上海市', 'ENG007', '2025-04-15', '2025-04-10', 450000.00, '白色', '2025', 1000, 'admin', NOW(), 'admin', NOW(), 0),
+(8, 'LFCEV001202500008', '沪A12349', 'VH-2025-008', 2, '上海', '上海市', 'ENG008', '2025-05-01', '2025-04-25', 550000.00, '蓝色', '2025', 500, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 车辆位置信息 ----------
+INSERT INTO `asset_vehicle_location` (`id`, `vehicle_id`, `parking_id`, `parking_name`, `parking_space`, `location`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 1, '上海嘉定氢能停车场', 'A-001', '已交车-客户运营中', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 2, 1, '上海嘉定氢能停车场', 'A-002', '已交车-客户运营中', 'admin', NOW(), 'admin', NOW(), 0),
+(3, 3, 1, '上海嘉定氢能停车场', 'B-001', '已交车-客户运营中', 'admin', NOW(), 'admin', NOW(), 0),
+(4, 4, 3, '佛山南海停车场', 'C-001', '已交车-客户运营中', 'admin', NOW(), 'admin', NOW(), 0),
+(5, 5, 3, '佛山南海停车场', 'C-002', '已交车-客户运营中', 'admin', NOW(), 'admin', NOW(), 0),
+(6, 6, 2, '上海临港停车场', 'D-001', '待交车', 'admin', NOW(), 'admin', NOW(), 0),
+(7, 7, 1, '上海嘉定氢能停车场', 'A-003', '库存待分配', 'admin', NOW(), 'admin', NOW(), 0),
+(8, 8, 2, '上海临港停车场', 'D-002', '库存待分配', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 车辆业务信息 ----------
+INSERT INTO `asset_vehicle_business` (`id`, `vehicle_id`, `customer_id`, `contract_id`, `ownership`, `delivery_date`, `monthly_rent`, `deposit`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 1, 1, '公司自有', '2025-02-01', 15000.00, 30000.00, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 2, 1, 1, '公司自有', '2025-02-01', 15000.00, 30000.00, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 3, 1, 1, '公司自有', '2025-03-01', 12000.00, 25000.00, 'admin', NOW(), 'admin', NOW(), 0),
+(4, 4, 2, 2, '公司自有', '2025-04-01', 12000.00, 25000.00, 'admin', NOW(), 'admin', NOW(), 0),
+(5, 5, 2, 2, '公司自有', '2025-04-15', 10000.00, 20000.00, 'admin', NOW(), 'admin', NOW(), 0),
+(6, 6, 3, 3, '公司自有', NULL, NULL, NULL, 'admin', NOW(), 'admin', NOW(), 0),
+(7, 7, NULL, NULL, '公司自有', NULL, NULL, NULL, 'admin', NOW(), 'admin', NOW(), 0),
+(8, 8, NULL, NULL, '公司自有', NULL, NULL, NULL, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 车辆状态信息 ----------
+INSERT INTO `asset_vehicle_status` (`id`, `vehicle_id`, `status`, `operate_status`, `storage_status`, `is_prepared`, `is_delivered`, `is_returned`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 1, 1, 2, 1, 1, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 2, 1, 1, 2, 1, 1, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 3, 1, 1, 2, 1, 1, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(4, 4, 1, 1, 2, 1, 1, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(5, 5, 1, 1, 2, 1, 1, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(6, 6, 1, 0, 1, 1, 0, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(7, 7, 0, 0, 1, 0, 0, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(8, 8, 0, 0, 1, 0, 0, 0, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 合同 ----------
+INSERT INTO `asset_contract` (`id`, `contract_code`, `contract_type`, `project_name`, `start_date`, `end_date`, `payment_method`, `payment_cycle`, `signing_company`, `delivery_province`, `delivery_city`, `delivery_location`, `customer_id`, `customer_name`, `approval_status`, `contract_status`, `effective_time`, `hydrogen_bearer`, `hydrogen_payment_method`, `hydrogen_prepay`, `billing_method`, `main_vehicle_type`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 'HT-2025-001', 1, '上海鲜生冷链配送项目', '2025-02-01', '2027-01-31', '月付', '月', '辽宁新能源汽车有限公司', '上海', '上海市', '嘉定区安亭镇', 1, '上海鲜生冷链物流有限公司', 2, 1, '2025-02-01 00:00:00', '客户', '预充值', 100000.00, '按月计费', '冷藏车', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 'HT-2025-002', 1, '广州顺达城配项目', '2025-04-01', '2027-03-31', '月付', '月', '辽宁新能源汽车有限公司', '广东', '广州市', '天河区天河路', 2, '广州顺达运输有限公司', 2, 1, '2025-04-01 00:00:00', '公司', '月结算', NULL, '按月计费', '厢式货车', 'admin', NOW(), 'admin', NOW(), 0),
+(3, 'HT-2025-003', 1, '深圳绿能示范项目', '2025-06-01', '2027-05-31', '月付', '月', '辽宁新能源汽车有限公司', '广东', '深圳市', '南山区科技园', 3, '深圳绿能科技有限公司', 1, 0, NULL, '客户', '预充值', 50000.00, '按月计费', '冷藏车', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 合同车辆 ----------
+INSERT INTO `asset_contract_vehicle` (`id`, `contract_id`, `vehicle_id`, `brand`, `model`, `plate_no`, `vin`, `month_rent`, `deposit`, `vehicle_status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 1, '飞驰', 'FCEV-C1', '沪A12345', 'LFCEV001202500001', 15000.00, 30000.00, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 1, 2, '飞驰', 'FCEV-C1', '沪A12346', 'LFCEV001202500002', 15000.00, 30000.00, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 1, 3, '东风', 'EQ5180', '沪A12347', 'LFCEV001202500003', 12000.00, 25000.00, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(4, 2, 4, '东风', 'EQ5180', '粤B88001', 'LFCEV001202500004', 12000.00, 25000.00, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(5, 2, 5, '福田', 'BJ5120', '粤B88002', 'LFCEV001202500005', 10000.00, 20000.00, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(6, 3, 6, '飞驰', 'FCEV-C1', '粤B88003', 'LFCEV001202500006', 15000.00, 30000.00, 0, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 合同授权人 ----------
+INSERT INTO `asset_contract_authorized` (`id`, `contract_id`, `name`, `phone`, `id_card`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, '刘志强', '13900001001', '310101199001011234', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 1, '王小明', '13900001002', '310101199205051234', 'admin', NOW(), 'admin', NOW(), 0),
+(3, 2, '黄大成', '13900002001', '440103199501011234', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 验车模板 ----------
+INSERT INTO `asset_inspection_template` (`id`, `code`, `name`, `biz_type`, `vehicle_type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 'TPL-DELIVERY-001', '交车验车模板-冷藏车', 1, '冷藏车', 1, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 'TPL-RETURN-001', '还车验车模板-通用', 2, NULL, 1, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 验车模板项 ----------
+INSERT INTO `asset_inspection_template_item` (`id`, `template_id`, `category`, `item_name`, `item_code`, `input_type`, `sort`, `required`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, '外观检查', '车身外观', 'BODY_APPEARANCE', 'radio', 1, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 1, '外观检查', '车灯', 'LIGHTS', 'radio', 2, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 1, '功能检查', '制动系统', 'BRAKE', 'radio', 3, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(4, 1, '功能检查', '氢系统', 'HYDROGEN_SYSTEM', 'radio', 4, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(5, 1, '功能检查', '冷机系统', 'REFRIGERATOR', 'radio', 5, 1, 'admin', NOW(), 'admin', NOW(), 0);
+
+
+-- ===================== PART 2: ENERGY =====================
+-- NOTE: This section runs against oneos_energy database
+
+-- ---------- 站点配置 (加氢站) ----------
+INSERT INTO `energy_station_config` (`id`, `station_id`, `auto_deduct`, `cooperation_type`, `auto_match`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1001, 1, 1, 1, '上海嘉定加氢站', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 1002, 1, 1, 1, '上海临港加氢站', 'admin', NOW(), 'admin', NOW(), 0),
+(3, 1003, 0, 2, 1, '佛山南海加氢站', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 站点价格 ----------
+INSERT INTO `energy_station_price` (`id`, `station_id`, `customer_id`, `cost_price`, `customer_price`, `effective_date`, `expiry_date`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1001, 1, 25.00, 35.00, '2025-01-01', '2027-12-31', 1, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 1001, 2, 25.00, 38.00, '2025-01-01', '2027-12-31', 1, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 1002, 1, 28.00, 38.00, '2025-01-01', '2027-12-31', 1, 'admin', NOW(), 'admin', NOW(), 0),
+(4, 1003, 2, 26.00, 36.00, '2025-04-01', '2027-03-31', 1, 'admin', NOW(), 'admin', NOW(), 0),
+(5, 1003, 3, 26.00, 35.00, '2025-06-01', '2027-05-31', 1, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 能源账户 ----------
+INSERT INTO `energy_account` (`id`, `customer_id`, `balance`, `init_balance`, `accumulated_recharge`, `accumulated_hydrogen`, `accumulated_electric`, `accumulated_consume`, `reminder_threshold`, `account_status`, `last_recharge_date`, `version`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 52350.00, 0.00, 200000.00, 147650.00, 0.00, 147650.00, 10000.00, 1, '2026-02-15', 0, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 2, 0.00, 0.00, 0.00, 85600.00, 0.00, 85600.00, 5000.00, 1, NULL, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 3, 35000.00, 0.00, 50000.00, 15000.00, 0.00, 15000.00, 5000.00, 1, '2026-01-10', 0, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 项目账户 ----------
+INSERT INTO `energy_project_account` (`id`, `account_id`, `contract_id`, `project_name`, `project_balance`, `project_remit_amount`, `project_hydrogen_amount`, `project_electric_amount`, `project_consume_amount`, `reminder_threshold`, `account_status`, `version`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 1, '上海鲜生冷链配送项目', 52350.00, 200000.00, 147650.00, 0.00, 147650.00, 10000.00, 1, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 2, 2, '广州顺达城配项目', 0.00, 0.00, 85600.00, 0.00, 85600.00, 5000.00, 1, 0, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 3, 3, '深圳绿能示范项目', 35000.00, 50000.00, 15000.00, 0.00, 15000.00, 5000.00, 1, 0, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 加氢记录 ----------
+INSERT INTO `energy_hydrogen_record` (`id`, `station_id`, `plate_number`, `hydrogen_date`, `hydrogen_quantity`, `unit_price`, `amount`, `mileage`, `source_type`, `match_status`, `vehicle_id`, `customer_id`, `upload_batch_no`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1001, '沪A12345', '2026-03-01', 8.50, 35.00, 297.50, 12500, 1, 1, 1, 1, 'BATCH-20260301-001', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 1001, '沪A12346', '2026-03-01', 10.20, 35.00, 357.00, 15800, 1, 1, 2, 1, 'BATCH-20260301-001', 'admin', NOW(), 'admin', NOW(), 0),
+(3, 1001, '沪A12347', '2026-03-02', 7.80, 35.00, 273.00, 8500, 1, 1, 3, 1, 'BATCH-20260302-001', 'admin', NOW(), 'admin', NOW(), 0),
+(4, 1003, '粤B88001', '2026-03-02', 9.00, 36.00, 324.00, 20800, 1, 1, 4, 2, 'BATCH-20260302-002', 'admin', NOW(), 'admin', NOW(), 0),
+(5, 1003, '粤B88002', '2026-03-03', 6.50, 36.00, 234.00, 5500, 1, 1, 5, 2, 'BATCH-20260303-001', 'admin', NOW(), 'admin', NOW(), 0),
+(6, 1001, '沪A12345', '2026-03-05', 9.20, 35.00, 322.00, 12900, 1, 1, 1, 1, 'BATCH-20260305-001', 'admin', NOW(), 'admin', NOW(), 0),
+(7, 1001, '沪A12346', '2026-03-06', 11.00, 35.00, 385.00, 16300, 1, 1, 2, 1, 'BATCH-20260306-001', 'admin', NOW(), 'admin', NOW(), 0),
+(8, 1002, '沪A12345', '2026-03-08', 8.00, 38.00, 304.00, 13400, 1, 1, 1, 1, 'BATCH-20260308-001', 'admin', NOW(), 'admin', NOW(), 0),
+(9, 1003, '粤B88001', '2026-03-08', 10.50, 36.00, 378.00, 21500, 1, 1, 4, 2, 'BATCH-20260308-002', 'admin', NOW(), 'admin', NOW(), 0),
+(10, 1001, '沪A12347', '2026-03-10', 8.30, 35.00, 290.50, 9200, 1, 1, 3, 1, 'BATCH-20260310-001', 'admin', NOW(), 'admin', NOW(), 0),
+-- 未匹配记录
+(11, 1001, '沪C99999', '2026-03-10', 5.00, 35.00, 175.00, NULL, 1, 0, NULL, NULL, 'BATCH-20260310-001', 'admin', NOW(), 'admin', NOW(), 0),
+(12, 1002, '粤Z11111', '2026-03-11', 7.00, 38.00, 266.00, NULL, 2, 0, NULL, NULL, 'BATCH-20260311-001', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 加氢明细 ----------
+INSERT INTO `energy_hydrogen_detail` (`id`, `record_id`, `station_id`, `vehicle_id`, `plate_number`, `hydrogen_date`, `hydrogen_quantity`, `cost_price`, `cost_amount`, `customer_price`, `customer_amount`, `contract_id`, `customer_id`, `project_name`, `cost_bearer`, `pay_method`, `audit_status`, `deduction_status`, `settlement_status`, `bill_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 1001, 1, '沪A12345', '2026-03-01', 8.50, 25.00, 212.50, 35.00, 297.50, 1, 1, '上海鲜生冷链配送项目', 1, 1, 1, 1, 1, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(2, 2, 1001, 2, '沪A12346', '2026-03-01', 10.20, 25.00, 255.00, 35.00, 357.00, 1, 1, '上海鲜生冷链配送项目', 1, 1, 1, 1, 1, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(3, 3, 1001, 3, '沪A12347', '2026-03-02', 7.80, 25.00, 195.00, 35.00, 273.00, 1, 1, '上海鲜生冷链配送项目', 1, 1, 1, 1, 1, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(4, 4, 1003, 4, '粤B88001', '2026-03-02', 9.00, 26.00, 234.00, 36.00, 324.00, 2, 2, '广州顺达城配项目', 2, 2, 1, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0),
+(5, 5, 1003, 5, '粤B88002', '2026-03-03', 6.50, 26.00, 169.00, 36.00, 234.00, 2, 2, '广州顺达城配项目', 2, 2, 1, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0),
+(6, 6, 1001, 1, '沪A12345', '2026-03-05', 9.20, 25.00, 230.00, 35.00, 322.00, 1, 1, '上海鲜生冷链配送项目', 1, 1, 1, 1, 1, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(7, 7, 1001, 2, '沪A12346', '2026-03-06', 11.00, 25.00, 275.00, 35.00, 385.00, 1, 1, '上海鲜生冷链配送项目', 1, 1, 1, 1, 1, 1, 'admin', NOW(), 'admin', NOW(), 0),
+(8, 8, 1002, 1, '沪A12345', '2026-03-08', 8.00, 28.00, 224.00, 38.00, 304.00, 1, 1, '上海鲜生冷链配送项目', 1, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0),
+(9, 9, 1003, 4, '粤B88001', '2026-03-08', 10.50, 26.00, 273.00, 36.00, 378.00, 2, 2, '广州顺达城配项目', 2, 2, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0),
+(10, 10, 1001, 3, '沪A12347', '2026-03-10', 8.30, 25.00, 207.50, 35.00, 290.50, 1, 1, '上海鲜生冷链配送项目', 1, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 能源账单 ----------
+INSERT INTO `energy_bill` (`id`, `bill_code`, `energy_type`, `customer_id`, `customer_name`, `contract_id`, `station_id`, `station_name`, `cooperation_type`, `bill_period_start`, `bill_period_end`, `receivable_amount`, `actual_amount`, `adjustment_amount`, `paid_amount`, `total_quantity`, `detail_count`, `status`, `audit_status`, `submit_status`, `payment_status`, `generate_time`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 'BILL-2026-03-001', 1, 1, '上海鲜生冷链物流有限公司', 1, 1001, '上海嘉定加氢站', 1, '2026-03-01', '2026-03-15', 1634.50, 1634.50, 0.00, 1634.50, 46.70, 5, 3, 1, 1, 1, '2026-03-16 00:00:00', 'admin', NOW(), 'admin', NOW(), 0),
+(2, 'BILL-2026-03-002', 1, 2, '广州顺达运输有限公司', 2, 1003, '佛山南海加氢站', 2, '2026-03-01', '2026-03-15', 936.00, NULL, 0.00, 0.00, 26.00, 3, 1, 0, 0, 0, '2026-03-16 00:00:00', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 账单调整 ----------
+INSERT INTO `energy_bill_adjustment` (`id`, `bill_id`, `detail_id`, `adjustment_type`, `amount`, `reason`, `operator_id`, `operate_time`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 1, 1, -10.00, '加氢站计量偏差修正', 1, '2026-03-15 10:00:00', 'admin', NOW(), 'admin', NOW(), 0);
+
+-- ---------- 账户流水 ----------
+INSERT INTO `energy_account_flow` (`id`, `account_id`, `project_account_id`, `flow_type`, `amount`, `balance_before`, `balance_after`, `project_balance_before`, `project_balance_after`, `biz_type`, `biz_id`, `biz_code`, `remark`, `operator_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1, 1, 1, 1, 100000.00, 0.00, 100000.00, 0.00, 100000.00, 1, NULL, NULL, '首次充值', 1, 'admin', '2025-02-01 10:00:00', 'admin', '2025-02-01 10:00:00', 0),
+(2, 1, 1, 1, 100000.00, 100000.00, 200000.00, 100000.00, 200000.00, 1, NULL, NULL, '二次充值', 1, 'admin', '2026-02-15 10:00:00', 'admin', '2026-02-15 10:00:00', 0),
+(3, 1, 1, 2, -297.50, 200000.00, 199702.50, 200000.00, 199702.50, 2, 1, 'BILL-2026-03-001', '加氢扣费-沪A12345', NULL, 'admin', '2026-03-01 18:00:00', 'admin', '2026-03-01 18:00:00', 0),
+(4, 1, 1, 2, -357.00, 199702.50, 199345.50, 199702.50, 199345.50, 2, 1, 'BILL-2026-03-001', '加氢扣费-沪A12346', NULL, 'admin', '2026-03-01 18:05:00', 'admin', '2026-03-01 18:05:00', 0),
+(5, 3, 3, 1, 50000.00, 0.00, 50000.00, 0.00, 50000.00, 1, NULL, NULL, '首次充值', 1, 'admin', '2026-01-10 10:00:00', 'admin', '2026-01-10 10:00:00', 0);
diff --git a/sql/rebuild/rebuild_all.py b/sql/rebuild/rebuild_all.py
new file mode 100644
index 0000000..143e41c
--- /dev/null
+++ b/sql/rebuild/rebuild_all.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+"""
+重建 Asset 和 Energy 模块数据表并插入 Mock 数据
+- Asset 表 → oneos_asset 数据库
+- Energy 表 → oneos_energy 数据库
+- 使用 pymysql 连接远程 MySQL(规避本地 mysql 客户端 auth plugin 问题)
+"""
+
+import os
+import sys
+import re
+
+try:
+ import pymysql
+except ImportError:
+ print("pymysql 未安装,正在安装...")
+ os.system(f"{sys.executable} -m pip install pymysql")
+ import pymysql
+
+# 数据库连接配置
+DB_CONFIG = {
+ "host": "47.103.115.36",
+ "port": 3306,
+ "user": "root",
+ "password": "Passw0rd2026",
+ "charset": "utf8mb4",
+}
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+
+
+def read_sql_file(filename):
+ """读取 SQL 文件内容"""
+ filepath = os.path.join(SCRIPT_DIR, filename)
+ with open(filepath, "r", encoding="utf-8") as f:
+ return f.read()
+
+
+def split_statements(sql_text):
+ """
+ 将 SQL 文本拆分为独立语句。
+ 处理 CREATE VIEW 等多行语句,跳过注释和空行。
+ """
+ statements = []
+ current = []
+ for line in sql_text.split("\n"):
+ stripped = line.strip()
+ # 跳过纯注释行和空行(但保留语句中间的注释)
+ if not stripped or stripped.startswith("--"):
+ if current:
+ # 如果当前语句已经开始,保留注释行(可能是语句内的注释)
+ pass
+ continue
+ current.append(line)
+ if stripped.endswith(";"):
+ stmt = "\n".join(current).strip()
+ if stmt:
+ statements.append(stmt)
+ current = []
+ # 处理没有分号结尾的最后一条语句
+ if current:
+ stmt = "\n".join(current).strip()
+ if stmt:
+ statements.append(stmt)
+ return statements
+
+
+def execute_sql(cursor, sql_text, label=""):
+ """执行一段 SQL(可包含多条语句)"""
+ statements = split_statements(sql_text)
+ success = 0
+ errors = 0
+ for i, stmt in enumerate(statements, 1):
+ # 提取语句摘要用于日志
+ first_line = stmt.split("\n")[0][:80]
+ try:
+ cursor.execute(stmt)
+ success += 1
+ except pymysql.err.OperationalError as e:
+ code = e.args[0]
+ # 1051: Unknown table (DROP 不存在的表) - 忽略
+ if code == 1051:
+ success += 1
+ else:
+ errors += 1
+ print(f" ❌ [{label}] 语句 {i} 失败: {e}")
+ print(f" SQL: {first_line}...")
+ except Exception as e:
+ errors += 1
+ print(f" ❌ [{label}] 语句 {i} 失败: {e}")
+ print(f" SQL: {first_line}...")
+ return success, errors
+
+
+def main():
+ print("=" * 60)
+ print("ONE-OS 数据表重建工具")
+ print("=" * 60)
+
+ # 读取 SQL 文件
+ print("\n📄 读取 SQL 文件...")
+ asset_ddl = read_sql_file("asset_ddl.sql")
+ energy_ddl = read_sql_file("energy_ddl.sql")
+ mock_data = read_sql_file("mock_data.sql")
+
+ # 拆分 mock_data 为 asset 和 energy 部分
+ parts = mock_data.split("-- ===================== PART 2: ENERGY =====================")
+ if len(parts) != 2:
+ print("❌ mock_data.sql 格式错误:找不到 PART 2 分隔符")
+ sys.exit(1)
+ asset_mock = parts[0]
+ energy_mock = parts[1]
+
+ # 从 asset_mock 中去掉文件头注释和 PART 1 标记
+ # 保留实际的 INSERT 语句
+
+ total_success = 0
+ total_errors = 0
+
+ # ========== 1. 处理 Asset 数据库 ==========
+ print("\n" + "=" * 60)
+ print("🔧 [1/3] 重建 oneos_asset 数据库表...")
+ print("=" * 60)
+
+ conn_asset = pymysql.connect(**DB_CONFIG, database="oneos_asset")
+ conn_asset.autocommit(True)
+ cursor_asset = conn_asset.cursor()
+
+ # 创建 Asset 表
+ print("\n📋 执行 Asset DDL(29张表 + 1个视图)...")
+ s, e = execute_sql(cursor_asset, asset_ddl, "Asset DDL")
+ total_success += s
+ total_errors += e
+ print(f" ✅ Asset DDL: {s} 成功, {e} 失败")
+
+ # 插入 Asset Mock 数据
+ print("\n📋 插入 Asset Mock 数据...")
+ s, e = execute_sql(cursor_asset, asset_mock, "Asset Mock")
+ total_success += s
+ total_errors += e
+ print(f" ✅ Asset Mock: {s} 成功, {e} 失败")
+
+ cursor_asset.close()
+ conn_asset.close()
+
+ # ========== 2. 处理 Energy 数据库 ==========
+ print("\n" + "=" * 60)
+ print("🔧 [2/3] 重建 oneos_energy 数据库表...")
+ print("=" * 60)
+
+ # 先确保 oneos_energy 数据库存在
+ conn_init = pymysql.connect(**DB_CONFIG)
+ conn_init.autocommit(True)
+ cursor_init = conn_init.cursor()
+ cursor_init.execute("CREATE DATABASE IF NOT EXISTS `oneos_energy` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
+ cursor_init.close()
+ conn_init.close()
+
+ conn_energy = pymysql.connect(**DB_CONFIG, database="oneos_energy")
+ conn_energy.autocommit(True)
+ cursor_energy = conn_energy.cursor()
+
+ # 创建 Energy 表
+ print("\n📋 执行 Energy DDL(9张表)...")
+ s, e = execute_sql(cursor_energy, energy_ddl, "Energy DDL")
+ total_success += s
+ total_errors += e
+ print(f" ✅ Energy DDL: {s} 成功, {e} 失败")
+
+ # 插入 Energy Mock 数据
+ print("\n📋 插入 Energy Mock 数据...")
+ s, e = execute_sql(cursor_energy, energy_mock, "Energy Mock")
+ total_success += s
+ total_errors += e
+ print(f" ✅ Energy Mock: {s} 成功, {e} 失败")
+
+ cursor_energy.close()
+ conn_energy.close()
+
+ # ========== 3. 验证 ==========
+ print("\n" + "=" * 60)
+ print("🔍 [3/3] 验证表结构和数据...")
+ print("=" * 60)
+
+ # 验证 Asset
+ conn_v = pymysql.connect(**DB_CONFIG, database="oneos_asset")
+ cursor_v = conn_v.cursor()
+ cursor_v.execute("SELECT TABLE_NAME, TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'oneos_asset' AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME")
+ asset_tables = cursor_v.fetchall()
+ print(f"\n📊 oneos_asset 数据库: {len(asset_tables)} 张表")
+ for table_name, row_count in asset_tables:
+ cursor_v.execute(f"SELECT COUNT(*) FROM `{table_name}`")
+ actual_count = cursor_v.fetchone()[0]
+ print(f" {table_name}: {actual_count} 行")
+ cursor_v.close()
+ conn_v.close()
+
+ # 验证 Energy
+ conn_v2 = pymysql.connect(**DB_CONFIG, database="oneos_energy")
+ cursor_v2 = conn_v2.cursor()
+ cursor_v2.execute("SELECT TABLE_NAME, TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'oneos_energy' AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME")
+ energy_tables = cursor_v2.fetchall()
+ print(f"\n📊 oneos_energy 数据库: {len(energy_tables)} 张表")
+ for table_name, row_count in energy_tables:
+ cursor_v2.execute(f"SELECT COUNT(*) FROM `{table_name}`")
+ actual_count = cursor_v2.fetchone()[0]
+ print(f" {table_name}: {actual_count} 行")
+ cursor_v2.close()
+ conn_v2.close()
+
+ # ========== 总结 ==========
+ print("\n" + "=" * 60)
+ print(f"🎉 完成! 总计: {total_success} 成功, {total_errors} 失败")
+ print("=" * 60)
+
+ if total_errors > 0:
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sql/update-2026-03-13-contract-new-fields.sql b/sql/update-2026-03-13-contract-new-fields.sql
new file mode 100644
index 0000000..701c923
--- /dev/null
+++ b/sql/update-2026-03-13-contract-new-fields.sql
@@ -0,0 +1,8 @@
+-- 合同表新增6个字段
+ALTER TABLE asset_contract
+ ADD COLUMN hydrogen_bearer VARCHAR(20) COMMENT '氢费承担方',
+ ADD COLUMN hydrogen_payment_method VARCHAR(20) COMMENT '氢气付款方式',
+ ADD COLUMN hydrogen_prepay DECIMAL(12,2) COMMENT '氢气预付款',
+ ADD COLUMN hydrogen_return_price DECIMAL(12,2) COMMENT '退还车氢气单价',
+ ADD COLUMN billing_method VARCHAR(50) COMMENT '账单计算方式',
+ ADD COLUMN main_vehicle_type VARCHAR(50) COMMENT '主车型';
diff --git a/yudao-module-asset/sql/mysql/contract-menu-dict.sql b/yudao-module-asset/sql/mysql/contract-menu-dict.sql
new file mode 100644
index 0000000..6d9b205
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/contract-menu-dict.sql
@@ -0,0 +1,67 @@
+-- 合同管理菜单和权限初始化脚本
+
+-- 1. 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '合同管理', '', 2, 5, 5055,
+ 'contract', 'documentation', 'asset/contract/index', 0, 'Contract'
+);
+
+-- 获取刚插入的菜单ID
+SET @menuId = LAST_INSERT_ID();
+
+-- 合同管理按钮权限
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES
+ ('合同查询', 'asset:contract:query', 3, 1, @menuId, '', '', '', 0),
+ ('合同创建', 'asset:contract:create', 3, 2, @menuId, '', '', '', 0),
+ ('合同更新', 'asset:contract:update', 3, 3, @menuId, '', '', '', 0),
+ ('合同删除', 'asset:contract:delete', 3, 4, @menuId, '', '', '', 0),
+ ('合同导出', 'asset:contract:export', 3, 5, @menuId, '', '', '', 0),
+ ('提交审批', 'asset:contract:submit-approval', 3, 6, @menuId, '', '', '', 0),
+ ('撤回审批', 'asset:contract:withdraw-approval', 3, 7, @menuId, '', '', '', 0),
+ ('终止合同', 'asset:contract:terminate', 3, 8, @menuId, '', '', '', 0),
+ ('续签合同', 'asset:contract:renew', 3, 9, @menuId, '', '', '', 0);
+
+-- 2. 字典类型
+INSERT INTO system_dict_type (name, type, status, remark, creator, create_time, updater, update_time, deleted)
+VALUES
+ ('合同类型', 'asset_contract_type', 0, '车辆租赁合同类型', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('合同审批状态', 'asset_contract_approval_status', 0, '合同审批状态', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('合同状态', 'asset_contract_status', 0, '合同业务状态', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('车辆订单状态', 'asset_contract_vehicle_status', 0, '合同车辆订单状态', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 3. 字典数据 - 合同类型
+INSERT INTO system_dict_data (dict_type, label, value, sort, status, remark, creator, create_time, updater, update_time, deleted)
+VALUES
+ ('asset_contract_type', '试用合同', '1', 1, 0, '试用期合同', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_type', '正式合同', '2', 2, 0, '正式租赁合同', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 4. 字典数据 - 合同审批状态
+INSERT INTO system_dict_data (dict_type, label, value, sort, status, color_type, remark, creator, create_time, updater, update_time, deleted)
+VALUES
+ ('asset_contract_approval_status', '草稿', '0', 1, 0, 'info', '草稿状态', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_approval_status', '审批中', '1', 2, 0, 'warning', '审批中', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_approval_status', '审批通过', '2', 3, 0, 'success', '审批通过', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_approval_status', '审批拒绝', '3', 4, 0, 'danger', '审批拒绝', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_approval_status', '已撤回', '4', 5, 0, 'info', '已撤回', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 5. 字典数据 - 合同状态
+INSERT INTO system_dict_data (dict_type, label, value, sort, status, color_type, remark, creator, create_time, updater, update_time, deleted)
+VALUES
+ ('asset_contract_status', '草稿', '0', 1, 0, 'info', '草稿状态', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_status', '待生效', '1', 2, 0, 'warning', '待生效', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_status', '进行中', '2', 3, 0, 'success', '进行中', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_status', '已到期', '3', 4, 0, 'info', '已到期', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_status', '已终止', '4', 5, 0, 'danger', '已终止', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_status', '已续签', '5', 6, 0, 'primary', '已续签', 'admin', NOW(), 'admin', NOW(), b'0');
+
+-- 6. 字典数据 - 车辆订单状态
+INSERT INTO system_dict_data (dict_type, label, value, sort, status, color_type, remark, creator, create_time, updater, update_time, deleted)
+VALUES
+ ('asset_contract_vehicle_status', '待交车', '0', 1, 0, 'warning', '待交车', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_vehicle_status', '已交车', '1', 2, 0, 'success', '已交车', 'admin', NOW(), 'admin', NOW(), b'0'),
+ ('asset_contract_vehicle_status', '已退车', '2', 3, 0, 'info', '已退车', 'admin', NOW(), 'admin', NOW(), b'0');
diff --git a/yudao-module-asset/sql/mysql/contract-tables-supplement.sql b/yudao-module-asset/sql/mysql/contract-tables-supplement.sql
new file mode 100644
index 0000000..c7cc128
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/contract-tables-supplement.sql
@@ -0,0 +1,74 @@
+-- 合同管理补充表(合同被授权人、变更历史、附件、车辆服务项目)
+
+-- 3. 合同被授权人表
+CREATE TABLE IF NOT EXISTS `asset_contract_authorized` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `contract_id` BIGINT NOT NULL COMMENT '合同ID',
+ `name` VARCHAR(100) NOT NULL COMMENT '姓名',
+ `phone` VARCHAR(20) COMMENT '电话',
+ `id_card` VARCHAR(30) COMMENT '身份证号',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_contract_id` (`contract_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同被授权人表';
+
+-- 4. 合同变更历史表
+CREATE TABLE IF NOT EXISTS `asset_contract_change_history` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `contract_id` BIGINT NOT NULL COMMENT '合同ID',
+ `change_type` VARCHAR(50) NOT NULL COMMENT '变更类型',
+ `change_content` TEXT COMMENT '变更内容',
+ `operator` VARCHAR(64) COMMENT '操作人',
+ `operate_time` DATETIME COMMENT '操作时间',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_contract_id` (`contract_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同变更历史表';
+
+-- 5. 合同附件表
+CREATE TABLE IF NOT EXISTS `asset_contract_attachment` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `contract_id` BIGINT NOT NULL COMMENT '合同ID',
+ `attachment_type` INT COMMENT '附件类型(1=合同原件 2=盖章合同)',
+ `file_id` BIGINT COMMENT '文件ID',
+ `file_name` VARCHAR(255) COMMENT '文件名称',
+ `file_url` VARCHAR(500) COMMENT '文件URL',
+ `file_size` BIGINT COMMENT '文件大小(字节)',
+ `upload_time` DATETIME COMMENT '上传时间',
+ `uploader` VARCHAR(64) COMMENT '上传人',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_contract_id` (`contract_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同附件表';
+
+-- 6. 合同车辆服务项目表
+CREATE TABLE IF NOT EXISTS `asset_contract_vehicle_service` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `contract_vehicle_id` BIGINT NOT NULL COMMENT '合同车辆ID',
+ `service_name` VARCHAR(200) NOT NULL COMMENT '服务项目名称',
+ `service_fee` DECIMAL(10,2) COMMENT '服务费用(元)',
+ `effective_date` DATE COMMENT '生效日期',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_contract_vehicle_id` (`contract_vehicle_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同车辆服务项目表';
diff --git a/yudao-module-asset/sql/mysql/contract-tables.sql b/yudao-module-asset/sql/mysql/contract-tables.sql
new file mode 100644
index 0000000..f714a67
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/contract-tables.sql
@@ -0,0 +1,73 @@
+-- 合同管理表结构
+
+-- 1. 车辆租赁合同表
+CREATE TABLE IF NOT EXISTS `asset_contract` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `contract_code` VARCHAR(50) NOT NULL COMMENT '合同编码',
+ `contract_type` TINYINT NOT NULL COMMENT '合同类型(1=试用 2=正式)',
+ `project_name` VARCHAR(200) NOT NULL COMMENT '项目名称',
+ `start_date` DATE NOT NULL COMMENT '生效日期',
+ `end_date` DATE NOT NULL COMMENT '结束日期',
+ `payment_method` VARCHAR(100) NOT NULL COMMENT '付款方式',
+ `payment_cycle` VARCHAR(100) NOT NULL COMMENT '付款周期',
+ `signing_company` VARCHAR(200) NOT NULL COMMENT '签约公司(乙方)',
+ `delivery_province` VARCHAR(50) NOT NULL COMMENT '交车省份',
+ `delivery_city` VARCHAR(50) NOT NULL COMMENT '交车城市',
+ `delivery_location` VARCHAR(255) NOT NULL COMMENT '交车地点',
+ `remark` VARCHAR(500) COMMENT '备注',
+ `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+ `customer_name` VARCHAR(100) COMMENT '客户名称(冗余)',
+ `third_party_enabled` BIT(1) DEFAULT b'0' COMMENT '是否三方合同',
+ `third_party_customer_id` BIGINT COMMENT '丙方客户ID',
+ `third_party_name` VARCHAR(100) COMMENT '丙方名称',
+ `business_dept_id` BIGINT COMMENT '业务部门ID',
+ `business_manager_id` BIGINT COMMENT '业务负责人ID',
+ `approval_status` TINYINT NOT NULL DEFAULT 0 COMMENT '审批状态(0=草稿 1=审批中 2=审批通过 3=审批拒绝 4=已撤回)',
+ `bpm_instance_id` VARCHAR(64) COMMENT 'BPM流程实例ID',
+ `contract_status` TINYINT NOT NULL DEFAULT 0 COMMENT '合同状态(0=草稿 1=待生效 2=进行中 3=已到期 4=已终止 5=已续签)',
+ `effective_time` DATETIME COMMENT '实际生效时间',
+ `terminate_time` DATETIME COMMENT '终止时间',
+ `terminate_reason` VARCHAR(500) COMMENT '终止原因',
+ `renewed_contract_id` BIGINT COMMENT '续签后的新合同ID',
+ `original_contract_id` BIGINT COMMENT '原合同ID(如果是续签合同)',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_contract_code` (`contract_code`, `deleted`),
+ KEY `idx_customer_id` (`customer_id`),
+ KEY `idx_approval_status` (`approval_status`),
+ KEY `idx_contract_status` (`contract_status`),
+ KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆租赁合同表';
+
+-- 2. 合同车辆租赁订单表
+CREATE TABLE IF NOT EXISTS `asset_contract_vehicle` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `contract_id` BIGINT NOT NULL COMMENT '合同ID',
+ `vehicle_id` BIGINT COMMENT '车辆ID(关联 asset_vehicle_base)',
+ `brand` VARCHAR(50) NOT NULL COMMENT '品牌',
+ `model` VARCHAR(100) NOT NULL COMMENT '型号',
+ `plate_no` VARCHAR(20) COMMENT '车牌号',
+ `vin` VARCHAR(50) COMMENT 'VIN码',
+ `month_rent` DECIMAL(10,2) NOT NULL COMMENT '月租金(元)',
+ `deposit` DECIMAL(10,2) NOT NULL COMMENT '保证金(元)',
+ `vehicle_status` TINYINT NOT NULL DEFAULT 0 COMMENT '车辆状态(0=待交车 1=已交车 2=已退车)',
+ `actual_delivery_time` DATETIME COMMENT '实际交车时间',
+ `delivery_person` VARCHAR(50) COMMENT '交车人',
+ `remark` VARCHAR(500) COMMENT '备注',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ KEY `idx_contract_id` (`contract_id`),
+ KEY `idx_vehicle_id` (`vehicle_id`),
+ KEY `idx_plate_no` (`plate_no`),
+ KEY `idx_vin` (`vin`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同车辆租赁订单表';
diff --git a/yudao-module-asset/sql/mysql/contract.sql b/yudao-module-asset/sql/mysql/contract.sql
new file mode 100644
index 0000000..f54443e
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/contract.sql
@@ -0,0 +1,27 @@
+-- 合同管理菜单和权限初始化脚本
+
+-- 1. 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '合同管理', '', 2, 6, 5055,
+ 'contract', 'file-text', 'asset/contract/index', 0, 'Contract'
+);
+
+-- 获取刚插入的菜单ID
+SET @menuId = LAST_INSERT_ID();
+
+-- 合同管理按钮权限
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES
+ ('合同查询', 'asset:contract:query', 3, 1, @menuId, '', '', '', 0),
+ ('合同创建', 'asset:contract:create', 3, 2, @menuId, '', '', '', 0),
+ ('合同更新', 'asset:contract:update', 3, 3, @menuId, '', '', '', 0),
+ ('合同删除', 'asset:contract:delete', 3, 4, @menuId, '', '', '', 0),
+ ('合同导出', 'asset:contract:export', 3, 5, @menuId, '', '', '', 0),
+ ('提交审批', 'asset:contract:submit-approval', 3, 6, @menuId, '', '', '', 0),
+ ('撤回审批', 'asset:contract:withdraw-approval', 3, 7, @menuId, '', '', '', 0),
+ ('终止合同', 'asset:contract:terminate', 3, 8, @menuId, '', '', '', 0),
+ ('续签合同', 'asset:contract:renew', 3, 9, @menuId, '', '', '', 0);
diff --git a/yudao-module-asset/sql/mysql/customer.sql b/yudao-module-asset/sql/mysql/customer.sql
new file mode 100644
index 0000000..1149593
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/customer.sql
@@ -0,0 +1,23 @@
+-- 客户管理菜单和权限初始化脚本
+
+-- 1. 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '客户管理', '', 2, 2, 5055,
+ 'customer', 'user', 'asset/customer/index', 0, 'Customer'
+);
+
+-- 获取刚插入的菜单ID
+SET @menuId = LAST_INSERT_ID();
+
+-- 客户管理按钮权限
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES
+ ('客户查询', 'asset:customer:query', 3, 1, @menuId, '', '', '', 0),
+ ('客户创建', 'asset:customer:create', 3, 2, @menuId, '', '', '', 0),
+ ('客户更新', 'asset:customer:update', 3, 3, @menuId, '', '', '', 0),
+ ('客户删除', 'asset:customer:delete', 3, 4, @menuId, '', '', '', 0),
+ ('客户导出', 'asset:customer:export', 3, 5, @menuId, '', '', '', 0);
diff --git a/yudao-module-asset/sql/mysql/customer_rebuild.sql b/yudao-module-asset/sql/mysql/customer_rebuild.sql
new file mode 100644
index 0000000..cee4c94
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/customer_rebuild.sql
@@ -0,0 +1,44 @@
+-- ==================== 客户信息表重建脚本 ====================
+-- 说明:原表列名与 Java DO 不匹配,重建以对齐
+-- 日期:2026-03-13
+-- 注意:如果 asset_customer 已有数据,请先备份!
+
+-- 1. 备份旧表(如果存在)
+-- CREATE TABLE asset_customer_bak AS SELECT * FROM asset_customer;
+
+-- 2. 删除旧表
+DROP TABLE IF EXISTS asset_customer;
+
+-- 3. 创建新表(与 CustomerDO 字段完全对齐)
+CREATE TABLE `asset_customer` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `customer_code` varchar(50) DEFAULT NULL COMMENT '客户编号',
+ `coop_status` varchar(20) DEFAULT NULL COMMENT '合作状态',
+ `customer_name` varchar(100) NOT NULL COMMENT '客户名称',
+ `province` varchar(50) DEFAULT NULL COMMENT '省份',
+ `city` varchar(50) DEFAULT NULL COMMENT '城市',
+ `address` varchar(255) DEFAULT NULL COMMENT '地址',
+ `region` varchar(20) DEFAULT NULL COMMENT '区域',
+ `contact` varchar(50) DEFAULT NULL COMMENT '联系人',
+ `contact_mobile` varchar(20) DEFAULT NULL COMMENT '联系人手机',
+ `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系人座机',
+ `email` varchar(100) DEFAULT NULL COMMENT '电子邮箱',
+ `credit_code_or_id` varchar(50) DEFAULT NULL COMMENT '统一社会信用代码/身份证',
+ `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+ `tax_id` varchar(50) DEFAULT NULL COMMENT '纳税人识别号',
+ `invoice_address` varchar(255) DEFAULT NULL COMMENT '发票地址',
+ `invoice_phone` varchar(20) DEFAULT NULL COMMENT '发票电话',
+ `account` varchar(50) DEFAULT NULL COMMENT '银行账号',
+ `opening_bank` varchar(100) DEFAULT NULL COMMENT '开户行',
+ `mailing_address` varchar(255) DEFAULT NULL COMMENT '邮寄地址',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除(0=未删除 1=已删除)',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `idx_customer_code` (`customer_code`) USING BTREE COMMENT '客户编号查询',
+ KEY `idx_customer_name` (`customer_name`) USING BTREE COMMENT '客户名称查询',
+ KEY `idx_tenant_deleted` (`tenant_id`, `deleted`) USING BTREE COMMENT '租户隔离 + 逻辑删除'
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户信息表';
diff --git a/yudao-module-asset/sql/mysql/parking.sql b/yudao-module-asset/sql/mysql/parking.sql
new file mode 100644
index 0000000..c9f1ab5
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/parking.sql
@@ -0,0 +1,23 @@
+-- 停车场管理菜单和权限初始化脚本
+
+-- 1. 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '停车场管理', '', 2, 1, 5055,
+ 'parking', 'car', 'asset/parking/index', 0, 'Parking'
+);
+
+-- 获取刚插入的菜单ID
+SET @menuId = LAST_INSERT_ID();
+
+-- 停车场管理按钮权限
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES
+ ('停车场查询', 'asset:parking:query', 3, 1, @menuId, '', '', '', 0),
+ ('停车场创建', 'asset:parking:create', 3, 2, @menuId, '', '', '', 0),
+ ('停车场更新', 'asset:parking:update', 3, 3, @menuId, '', '', '', 0),
+ ('停车场删除', 'asset:parking:delete', 3, 4, @menuId, '', '', '', 0),
+ ('停车场导出', 'asset:parking:export', 3, 5, @menuId, '', '', '', 0);
diff --git a/yudao-module-asset/sql/mysql/supplier-menu.sql b/yudao-module-asset/sql/mysql/supplier-menu.sql
new file mode 100644
index 0000000..d1d5183
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/supplier-menu.sql
@@ -0,0 +1,23 @@
+-- 供应商管理菜单和权限初始化脚本
+
+-- 1. 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '供应商管理', '', 2, 3, 5055,
+ 'supplier', 'user', 'asset/supplier/index', 0, 'Supplier'
+);
+
+-- 获取刚插入的菜单ID
+SET @menuId = LAST_INSERT_ID();
+
+-- 供应商管理按钮权限
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES
+ ('供应商查询', 'asset:supplier:query', 3, 1, @menuId, '', '', '', 0),
+ ('供应商创建', 'asset:supplier:create', 3, 2, @menuId, '', '', '', 0),
+ ('供应商更新', 'asset:supplier:update', 3, 3, @menuId, '', '', '', 0),
+ ('供应商删除', 'asset:supplier:delete', 3, 4, @menuId, '', '', '', 0),
+ ('供应商导出', 'asset:supplier:export', 3, 5, @menuId, '', '', '', 0);
diff --git a/yudao-module-asset/sql/mysql/vehicle-model.sql b/yudao-module-asset/sql/mysql/vehicle-model.sql
new file mode 100644
index 0000000..62020a3
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/vehicle-model.sql
@@ -0,0 +1,23 @@
+-- 车型参数管理菜单和权限初始化脚本
+
+-- 1. 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '车型参数', '', 2, 4, 5055,
+ 'vehicle-model', 'car', 'asset/vehicle-model/index', 0, 'VehicleModel'
+);
+
+-- 获取刚插入的菜单ID
+SET @menuId = LAST_INSERT_ID();
+
+-- 车型参数管理按钮权限
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES
+ ('车型参数查询', 'asset:vehicle-model:query', 3, 1, @menuId, '', '', '', 0),
+ ('车型参数创建', 'asset:vehicle-model:create', 3, 2, @menuId, '', '', '', 0),
+ ('车型参数更新', 'asset:vehicle-model:update', 3, 3, @menuId, '', '', '', 0),
+ ('车型参数删除', 'asset:vehicle-model:delete', 3, 4, @menuId, '', '', '', 0),
+ ('车型参数导出', 'asset:vehicle-model:export', 3, 5, @menuId, '', '', '', 0);
diff --git a/yudao-module-asset/sql/mysql/vehicle-registration.sql b/yudao-module-asset/sql/mysql/vehicle-registration.sql
new file mode 100644
index 0000000..d302167
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/vehicle-registration.sql
@@ -0,0 +1,25 @@
+-- 车辆上牌管理菜单和权限初始化脚本
+
+-- 1. 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '车辆上牌', '', 2, 5, 5055,
+ 'vehicle-registration', 'file-text', 'asset/vehicle-registration/index', 0, 'VehicleRegistration'
+);
+
+-- 获取刚插入的菜单ID
+SET @menuId = LAST_INSERT_ID();
+
+-- 车辆上牌管理按钮权限
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES
+ ('车辆上牌查询', 'asset:vehicle-registration:query', 3, 1, @menuId, '', '', '', 0),
+ ('车辆上牌创建', 'asset:vehicle-registration:create', 3, 2, @menuId, '', '', '', 0),
+ ('车辆上牌更新', 'asset:vehicle-registration:update', 3, 3, @menuId, '', '', '', 0),
+ ('车辆上牌删除', 'asset:vehicle-registration:delete', 3, 4, @menuId, '', '', '', 0),
+ ('车辆上牌导出', 'asset:vehicle-registration:export', 3, 5, @menuId, '', '', '', 0),
+ ('车辆上牌确认', 'asset:vehicle-registration:confirm', 3, 6, @menuId, '', '', '', 0),
+ ('车辆上牌作废', 'asset:vehicle-registration:void', 3, 7, @menuId, '', '', '', 0);
diff --git a/yudao-module-asset/sql/mysql/vehicle-rental-flow-alter.sql b/yudao-module-asset/sql/mysql/vehicle-rental-flow-alter.sql
new file mode 100644
index 0000000..932cb9f
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/vehicle-rental-flow-alter.sql
@@ -0,0 +1,35 @@
+-- =============================================
+-- 车辆租赁业务流程表结构增量变更
+-- 对应前端新增字段
+-- =============================================
+
+-- 1. 备车记录表 - 新增字段
+ALTER TABLE `asset_vehicle_prepare`
+ ADD COLUMN `vehicle_type` VARCHAR(50) COMMENT '车辆类型' AFTER `model`,
+ ADD COLUMN `parking_lot` VARCHAR(100) COMMENT '停车场' AFTER `vehicle_type`,
+ ADD COLUMN `preparation_type` VARCHAR(50) COMMENT '整备类型' AFTER `contract_code`,
+ ADD COLUMN `mileage` INT COMMENT '里程(km)' AFTER `preparation_type`,
+ ADD COLUMN `hydrogen_remaining` DECIMAL(10,2) COMMENT '剩余氢量' AFTER `mileage`,
+ ADD COLUMN `hydrogen_unit` VARCHAR(10) DEFAULT '%' COMMENT '氢量单位(%/MPa/kg)' AFTER `hydrogen_remaining`,
+ ADD COLUMN `battery_remaining` DECIMAL(10,2) COMMENT '剩余电量(%)' AFTER `hydrogen_unit`,
+ ADD COLUMN `enlarged_text_photo` VARCHAR(1000) COMMENT '放大字照片(JSON数组)' AFTER `body_ad_photos`,
+ ADD COLUMN `spare_tire_depth` DECIMAL(10,2) COMMENT '备胎胎纹深度(mm)' AFTER `has_tail_lift`,
+ ADD COLUMN `spare_tire_photo` VARCHAR(1000) COMMENT '备胎照片(JSON数组)' AFTER `spare_tire_depth`,
+ ADD COLUMN `remark` VARCHAR(500) COMMENT '备注' AFTER `check_list`,
+ ADD COLUMN `complete_time` DATETIME COMMENT '完成时间' AFTER `status`;
+
+-- 2. 交车单表 - 新增司机信息、交检清单、费用信息
+ALTER TABLE `asset_delivery_order`
+ ADD COLUMN `driver_name` VARCHAR(64) COMMENT '司机姓名' AFTER `delivery_photos`,
+ ADD COLUMN `driver_id_card` VARCHAR(18) COMMENT '司机身份证' AFTER `driver_name`,
+ ADD COLUMN `driver_phone` VARCHAR(20) COMMENT '司机手机号' AFTER `driver_id_card`,
+ ADD COLUMN `inspection_data` TEXT COMMENT '交检清单(JSON)' AFTER `driver_phone`,
+ ADD COLUMN `cost_list` TEXT COMMENT '费用信息(JSON)' AFTER `inspection_data`;
+
+-- 3. 交车单车辆表 - 新增电量
+ALTER TABLE `asset_delivery_order_vehicle`
+ ADD COLUMN `battery_level` DECIMAL(10,2) COMMENT '交车时电量(%)' AFTER `hydrogen_level`;
+
+-- 4. 还车车辆表 - 新增违章费用
+ALTER TABLE `asset_return_order_vehicle`
+ ADD COLUMN `violation_fee` DECIMAL(10,2) DEFAULT 0 COMMENT '违章费用' AFTER `unpaid_repair_fee`;
diff --git a/yudao-module-asset/sql/mysql/vehicle-rental-flow.sql b/yudao-module-asset/sql/mysql/vehicle-rental-flow.sql
new file mode 100644
index 0000000..78f7a02
--- /dev/null
+++ b/yudao-module-asset/sql/mysql/vehicle-rental-flow.sql
@@ -0,0 +1,222 @@
+-- =============================================
+-- 车辆租赁业务流程表结构
+-- 包含:备车、交车任务、交车单、还车单
+-- =============================================
+
+-- 1. 备车记录表
+CREATE TABLE IF NOT EXISTS `asset_vehicle_prepare` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `vehicle_id` BIGINT NOT NULL COMMENT '车辆ID',
+ `plate_no` VARCHAR(20) COMMENT '车牌号',
+ `vin` VARCHAR(50) NOT NULL COMMENT '车辆识别代码',
+ `vehicle_model_id` BIGINT NOT NULL COMMENT '车型ID',
+ `brand` VARCHAR(100) COMMENT '品牌',
+ `model` VARCHAR(100) COMMENT '型号',
+ `contract_id` BIGINT COMMENT '关联合同ID',
+ `contract_code` VARCHAR(50) COMMENT '合同编码',
+ `has_body_ad` BIT(1) DEFAULT b'0' COMMENT '是否有车身广告',
+ `body_ad_photos` VARCHAR(1000) COMMENT '广告照片(JSON数组)',
+ `has_tail_lift` BIT(1) DEFAULT b'0' COMMENT '是否有尾板',
+ `trailer_plate_no` VARCHAR(20) COMMENT '挂车牌号',
+ `defect_photos` VARCHAR(1000) COMMENT '瑕疵照片(JSON数组)',
+ `check_list` TEXT COMMENT '备车检查清单(JSON)',
+ `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态(0=待提交 1=已完成)',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ INDEX `idx_vehicle_id` (`vehicle_id`),
+ INDEX `idx_contract_id` (`contract_id`),
+ INDEX `idx_status` (`status`),
+ INDEX `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='备车记录表';
+
+-- 2. 交车任务表
+CREATE TABLE IF NOT EXISTS `asset_delivery_task` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `task_code` VARCHAR(50) NOT NULL COMMENT '交车任务编码',
+ `contract_id` BIGINT NOT NULL COMMENT '合同ID',
+ `contract_code` VARCHAR(50) NOT NULL COMMENT '合同编码',
+ `project_name` VARCHAR(200) NOT NULL COMMENT '项目名称',
+ `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+ `customer_name` VARCHAR(200) NOT NULL COMMENT '客户名称',
+ `expected_delivery_date_start` DATE COMMENT '预计交车开始日期',
+ `expected_delivery_date_end` DATE COMMENT '预计交车结束日期',
+ `billing_start_date` DATE NOT NULL COMMENT '开始计费日期',
+ `delivery_province` VARCHAR(50) COMMENT '交车省份',
+ `delivery_city` VARCHAR(50) COMMENT '交车城市',
+ `delivery_location` VARCHAR(255) COMMENT '交车地点',
+ `vehicle_count` INT NOT NULL DEFAULT 0 COMMENT '交车数量',
+ `task_status` TINYINT NOT NULL DEFAULT 0 COMMENT '任务状态(0=激活 1=挂起)',
+ `delivery_status` TINYINT NOT NULL DEFAULT 0 COMMENT '交车状态(0=未交车 1=已交车)',
+ `need_return` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否需要还车',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_task_code` (`task_code`, `deleted`),
+ INDEX `idx_contract_id` (`contract_id`),
+ INDEX `idx_customer_id` (`customer_id`),
+ INDEX `idx_task_status` (`task_status`),
+ INDEX `idx_delivery_status` (`delivery_status`),
+ INDEX `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交车任务表';
+
+-- 3. 交车任务车辆表
+CREATE TABLE IF NOT EXISTS `asset_delivery_task_vehicle` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `task_id` BIGINT NOT NULL COMMENT '交车任务ID',
+ `contract_vehicle_id` BIGINT NOT NULL COMMENT '合同车辆ID',
+ `vehicle_id` BIGINT NOT NULL COMMENT '车辆ID',
+ `plate_no` VARCHAR(20) COMMENT '车牌号',
+ `vin` VARCHAR(50) NOT NULL COMMENT '车辆识别代码',
+ `brand` VARCHAR(100) NOT NULL COMMENT '品牌',
+ `model` VARCHAR(100) NOT NULL COMMENT '型号',
+ `month_rent` DECIMAL(10,2) NOT NULL COMMENT '月租金',
+ `deposit` DECIMAL(10,2) NOT NULL COMMENT '保证金',
+ `actual_delivery_date` DATETIME COMMENT '实际交车日期',
+ `delivery_person` VARCHAR(64) COMMENT '交车人',
+ `is_delivered` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否已交车',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ INDEX `idx_task_id` (`task_id`),
+ INDEX `idx_vehicle_id` (`vehicle_id`),
+ INDEX `idx_contract_vehicle_id` (`contract_vehicle_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交车任务车辆表';
+
+-- 4. 交车单表
+CREATE TABLE IF NOT EXISTS `asset_delivery_order` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `order_code` VARCHAR(50) NOT NULL COMMENT '交车单编码',
+ `task_id` BIGINT NOT NULL COMMENT '交车任务ID',
+ `task_code` VARCHAR(50) NOT NULL COMMENT '交车任务编码',
+ `contract_id` BIGINT NOT NULL COMMENT '合同ID',
+ `contract_code` VARCHAR(50) NOT NULL COMMENT '合同编码',
+ `project_name` VARCHAR(200) NOT NULL COMMENT '项目名称',
+ `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+ `customer_name` VARCHAR(200) NOT NULL COMMENT '客户名称',
+ `delivery_date` DATETIME NOT NULL COMMENT '交车日期',
+ `delivery_person` VARCHAR(64) NOT NULL COMMENT '交车人',
+ `delivery_location` VARCHAR(255) COMMENT '交车地点',
+ `authorized_person_id` BIGINT COMMENT '被授权人ID',
+ `authorized_person_name` VARCHAR(100) COMMENT '被授权人姓名',
+ `authorized_person_phone` VARCHAR(20) COMMENT '被授权人电话',
+ `authorized_person_id_card` VARCHAR(18) COMMENT '被授权人身份证',
+ `esign_flow_id` VARCHAR(100) COMMENT 'E签宝流程ID',
+ `esign_status` TINYINT DEFAULT 0 COMMENT 'E签宝状态(0=未签 1=已签)',
+ `delivery_photos` VARCHAR(1000) COMMENT '交车照片(JSON数组)',
+ `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态(0=待完成 1=已完成)',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_order_code` (`order_code`, `deleted`),
+ INDEX `idx_task_id` (`task_id`),
+ INDEX `idx_contract_id` (`contract_id`),
+ INDEX `idx_status` (`status`),
+ INDEX `idx_delivery_date` (`delivery_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交车单表';
+
+-- 5. 交车单车辆表
+CREATE TABLE IF NOT EXISTS `asset_delivery_order_vehicle` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `order_id` BIGINT NOT NULL COMMENT '交车单ID',
+ `task_vehicle_id` BIGINT NOT NULL COMMENT '交车任务车辆ID',
+ `vehicle_id` BIGINT NOT NULL COMMENT '车辆ID',
+ `plate_no` VARCHAR(20) COMMENT '车牌号',
+ `vin` VARCHAR(50) NOT NULL COMMENT '车辆识别代码',
+ `brand` VARCHAR(100) NOT NULL COMMENT '品牌',
+ `model` VARCHAR(100) NOT NULL COMMENT '型号',
+ `mileage` INT COMMENT '交车时里程',
+ `hydrogen_level` DECIMAL(10,2) COMMENT '交车时氢气量(kg)',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ INDEX `idx_order_id` (`order_id`),
+ INDEX `idx_vehicle_id` (`vehicle_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交车单车辆表';
+
+-- 6. 还车单表
+CREATE TABLE IF NOT EXISTS `asset_return_order` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `order_code` VARCHAR(50) NOT NULL COMMENT '还车单编码',
+ `contract_id` BIGINT NOT NULL COMMENT '合同ID',
+ `contract_code` VARCHAR(50) NOT NULL COMMENT '合同编码',
+ `project_name` VARCHAR(200) NOT NULL COMMENT '项目名称',
+ `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+ `customer_name` VARCHAR(200) NOT NULL COMMENT '客户名称',
+ `return_date` DATETIME NOT NULL COMMENT '还车日期',
+ `return_person` VARCHAR(64) NOT NULL COMMENT '还车验收人',
+ `return_location` VARCHAR(255) COMMENT '还车地点',
+ `return_reason` VARCHAR(50) COMMENT '还车原因',
+ `return_reason_desc` VARCHAR(500) COMMENT '还车原因说明',
+ `total_refund_amount` DECIMAL(10,2) DEFAULT 0 COMMENT '退还总金额',
+ `deposit_refund` DECIMAL(10,2) DEFAULT 0 COMMENT '退还保证金',
+ `hydrogen_refund` DECIMAL(10,2) DEFAULT 0 COMMENT '氢气退款',
+ `other_charges` DECIMAL(10,2) DEFAULT 0 COMMENT '其他费用',
+ `return_photos` VARCHAR(1000) COMMENT '还车照片(JSON数组)',
+ `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态(0=待验车 1=验车完成 2=已结算)',
+ `approval_status` TINYINT NOT NULL DEFAULT 0 COMMENT '审批状态(0=草稿 1=审批中 2=审批通过 3=审批拒绝)',
+ `bpm_instance_id` VARCHAR(64) COMMENT 'BPM流程实例ID',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_order_code` (`order_code`, `deleted`),
+ INDEX `idx_contract_id` (`contract_id`),
+ INDEX `idx_status` (`status`),
+ INDEX `idx_approval_status` (`approval_status`),
+ INDEX `idx_return_date` (`return_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='还车单表';
+
+-- 7. 还车车辆表
+CREATE TABLE IF NOT EXISTS `asset_return_order_vehicle` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `return_order_id` BIGINT NOT NULL COMMENT '还车单ID',
+ `vehicle_id` BIGINT NOT NULL COMMENT '车辆ID',
+ `plate_no` VARCHAR(20) COMMENT '车牌号',
+ `vin` VARCHAR(50) NOT NULL COMMENT '车辆识别代码',
+ `brand` VARCHAR(100) NOT NULL COMMENT '品牌',
+ `model` VARCHAR(100) NOT NULL COMMENT '型号',
+ `return_mileage` INT COMMENT '还车时里程',
+ `return_hydrogen_level` DECIMAL(10,2) COMMENT '还车时氢气量(kg)',
+ `delivery_hydrogen_level` DECIMAL(10,2) COMMENT '交车时氢气量(kg)',
+ `hydrogen_diff` DECIMAL(10,2) COMMENT '氢气差值(kg)',
+ `hydrogen_unit_price` DECIMAL(10,2) COMMENT '氢气单价(元/kg)',
+ `hydrogen_refund_amount` DECIMAL(10,2) COMMENT '氢气退款金额',
+ `check_list` TEXT COMMENT '还车检查清单(JSON)',
+ `defect_photos` VARCHAR(1000) COMMENT '瑕疵照片(JSON数组)',
+ `vehicle_damage_fee` DECIMAL(10,2) DEFAULT 0 COMMENT '车损费',
+ `tool_damage_fee` DECIMAL(10,2) DEFAULT 0 COMMENT '工具损坏费',
+ `unpaid_maintenance_fee` DECIMAL(10,2) DEFAULT 0 COMMENT '未结算保养费',
+ `unpaid_repair_fee` DECIMAL(10,2) DEFAULT 0 COMMENT '未结算维修费',
+ `other_fee` DECIMAL(10,2) DEFAULT 0 COMMENT '其他费用',
+ `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`),
+ INDEX `idx_return_order_id` (`return_order_id`),
+ INDEX `idx_vehicle_id` (`vehicle_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='还车车辆表';
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractApprovalStatusEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractApprovalStatusEnum.java
new file mode 100644
index 0000000..60496b1
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractApprovalStatusEnum.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.asset.enums.contract;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 合同审批状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ContractApprovalStatusEnum {
+
+ DRAFT(0, "草稿"),
+ APPROVING(1, "审批中"),
+ APPROVED(2, "审批通过"),
+ REJECTED(3, "审批拒绝"),
+ WITHDRAWN(4, "已撤回");
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ public static ContractApprovalStatusEnum valueOf(Integer status) {
+ return Arrays.stream(values())
+ .filter(item -> item.getStatus().equals(status))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractStatusEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractStatusEnum.java
new file mode 100644
index 0000000..b3270cf
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractStatusEnum.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.asset.enums.contract;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 合同状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ContractStatusEnum {
+
+ DRAFT(0, "草稿"),
+ PENDING(1, "待生效"),
+ IN_PROGRESS(2, "进行中"),
+ EXPIRED(3, "已到期"),
+ TERMINATED(4, "已终止"),
+ RENEWED(5, "已续签");
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ public static ContractStatusEnum valueOf(Integer status) {
+ return Arrays.stream(values())
+ .filter(item -> item.getStatus().equals(status))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractTypeEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractTypeEnum.java
new file mode 100644
index 0000000..987ccd4
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/contract/ContractTypeEnum.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.asset.enums.contract;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 合同类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ContractTypeEnum {
+
+ TRIAL(1, "试用合同"),
+ FORMAL(2, "正式合同");
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ public static ContractTypeEnum valueOf(Integer type) {
+ return Arrays.stream(values())
+ .filter(item -> item.getType().equals(type))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryOrderStatusEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryOrderStatusEnum.java
new file mode 100644
index 0000000..bfea52b
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryOrderStatusEnum.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.asset.enums.delivery;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 交车单状态枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum DeliveryOrderStatusEnum {
+
+ PENDING(0, "待完成"),
+ COMPLETED(1, "已完成");
+
+ private final Integer status;
+ private final String name;
+
+ public static DeliveryOrderStatusEnum valueOf(Integer status) {
+ return Arrays.stream(values())
+ .filter(item -> item.getStatus().equals(status))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryStatusEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryStatusEnum.java
new file mode 100644
index 0000000..893cd23
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryStatusEnum.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.asset.enums.delivery;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 交车状态枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum DeliveryStatusEnum {
+
+ NOT_DELIVERED(0, "未交车"),
+ DELIVERED(1, "已交车");
+
+ private final Integer status;
+ private final String name;
+
+ public static DeliveryStatusEnum valueOf(Integer status) {
+ return Arrays.stream(values())
+ .filter(item -> item.getStatus().equals(status))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryTaskStatusEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryTaskStatusEnum.java
new file mode 100644
index 0000000..ba6dc21
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/delivery/DeliveryTaskStatusEnum.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.asset.enums.delivery;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 交车任务状态枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum DeliveryTaskStatusEnum {
+
+ ACTIVE(0, "激活"),
+ SUSPENDED(1, "挂起");
+
+ private final Integer status;
+ private final String name;
+
+ public static DeliveryTaskStatusEnum valueOf(Integer status) {
+ return Arrays.stream(values())
+ .filter(item -> item.getStatus().equals(status))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/prepare/VehiclePrepareStatusEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/prepare/VehiclePrepareStatusEnum.java
new file mode 100644
index 0000000..0824320
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/prepare/VehiclePrepareStatusEnum.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.asset.enums.prepare;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 备车状态枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum VehiclePrepareStatusEnum {
+
+ DRAFT(0, "待提交"),
+ COMPLETED(1, "已完成");
+
+ private final Integer status;
+ private final String name;
+
+ public static VehiclePrepareStatusEnum valueOf(Integer status) {
+ return Arrays.stream(values())
+ .filter(item -> item.getStatus().equals(status))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/returnorder/ReturnOrderStatusEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/returnorder/ReturnOrderStatusEnum.java
new file mode 100644
index 0000000..e72ab40
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/returnorder/ReturnOrderStatusEnum.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.enums.returnorder;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 还车单状态枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum ReturnOrderStatusEnum {
+
+ PENDING_INSPECTION(0, "待验车"),
+ INSPECTION_COMPLETED(1, "验车完成"),
+ SETTLED(2, "已结算");
+
+ private final Integer status;
+ private final String name;
+
+ public static ReturnOrderStatusEnum valueOf(Integer status) {
+ return Arrays.stream(values())
+ .filter(item -> item.getStatus().equals(status))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/returnorder/ReturnReasonEnum.java b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/returnorder/ReturnReasonEnum.java
new file mode 100644
index 0000000..224d2d7
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-api/src/main/java/cn/iocoder/yudao/module/asset/enums/returnorder/ReturnReasonEnum.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.enums.returnorder;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 还车原因枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum ReturnReasonEnum {
+
+ CONTRACT_EXPIRED("CONTRACT_EXPIRED", "合同到期"),
+ CUSTOMER_REQUEST("CUSTOMER_REQUEST", "客户申请"),
+ CONTRACT_TERMINATED("CONTRACT_TERMINATED", "合同终止");
+
+ private final String code;
+ private final String name;
+
+ public static ReturnReasonEnum valueOfCode(String code) {
+ return Arrays.stream(values())
+ .filter(item -> item.getCode().equals(code))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/AssetServerApplication.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/AssetServerApplication.java
index b49b60f..9545c51 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/AssetServerApplication.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/AssetServerApplication.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.asset;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 资产管理模块 Application
@@ -9,6 +10,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author 芋道源码
*/
@SpringBootApplication
+@EnableFeignClients(basePackages = {
+ "cn.iocoder.yudao.module.system.api", // System 模块 API
+ "cn.iocoder.yudao.module.ocr.api", // OCR 模块 API
+ "cn.iocoder.yudao.module.infra.api", // Infra 模块 API
+ "cn.iocoder.yudao.module.bpm.api" // BPM 模块 API
+})
public class AssetServerApplication {
public static void main(String[] args) {
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/ContractController.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/ContractController.java
new file mode 100644
index 0000000..4df8d69
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/ContractController.java
@@ -0,0 +1,212 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.contract.vo.*;
+import cn.iocoder.yudao.module.asset.convert.contract.ContractConvert;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.*;
+import cn.iocoder.yudao.module.asset.dal.mysql.contract.*;
+import cn.iocoder.yudao.module.asset.service.contract.ContractService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 车辆租赁合同 Controller
+ *
+ * @author 芋道源码
+ */
+@Tag(name = "管理后台 - 车辆租赁合同管理")
+@RestController
+@RequestMapping("/asset/contract")
+@Validated
+public class ContractController {
+
+ @Resource
+ private ContractService contractService;
+
+ @Resource
+ private ContractVehicleMapper contractVehicleMapper;
+
+ @Resource
+ private ContractVehicleServiceMapper contractVehicleServiceMapper;
+
+ @Resource
+ private ContractAuthorizedMapper contractAuthorizedMapper;
+
+ @Resource
+ private ContractChangeHistoryMapper contractChangeHistoryMapper;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建合同")
+ @PreAuthorize("@ss.hasPermission('asset:contract:create')")
+ public CommonResult createContract(@Valid @RequestBody ContractSaveReqVO createReqVO) {
+ return success(contractService.createContract(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新合同")
+ @PreAuthorize("@ss.hasPermission('asset:contract:update')")
+ public CommonResult updateContract(@Valid @RequestBody ContractSaveReqVO updateReqVO) {
+ contractService.updateContract(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除合同")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:contract:delete')")
+ public CommonResult deleteContract(@RequestParam("id") Long id) {
+ contractService.deleteContract(id);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete-list")
+ @Operation(summary = "批量删除合同")
+ @Parameter(name = "ids", description = "编号列表", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:contract:delete')")
+ public CommonResult deleteContractList(@RequestParam("ids") List ids) {
+ for (Long id : ids) {
+ contractService.deleteContract(id);
+ }
+ return success(true);
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出合同 Excel")
+ @PreAuthorize("@ss.hasPermission('asset:contract:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportContractExcel(HttpServletResponse response, @Valid ContractPageReqVO exportReqVO) throws IOException {
+ exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List list = contractService.getContractPage(exportReqVO).getList();
+ // 输出
+ ExcelUtils.write(response, "车辆租赁合同.xls", "合同数据", ContractExcelVO.class,
+ BeanUtils.toBean(list, ContractExcelVO.class));
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得合同详情")
+ @Parameter(name = "id", description = "编号", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('asset:contract:query')")
+ public CommonResult getContract(@RequestParam("id") Long id) {
+ return success(contractService.getContractDetail(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得合同分页")
+ @PreAuthorize("@ss.hasPermission('asset:contract:query')")
+ public CommonResult> getContractPage(@Valid ContractPageReqVO pageReqVO) {
+ PageResult pageResult = contractService.getContractPage(pageReqVO);
+ PageResult voPage = ContractConvert.INSTANCE.convertPage(pageResult);
+ // 填充车辆数和已交车辆数
+ for (ContractRespVO vo : voPage.getList()) {
+ List vehicles = contractVehicleMapper.selectListByContractId(vo.getId());
+ vo.setVehicleCount(vehicles.size());
+ vo.setDeliveredCount((int) vehicles.stream()
+ .filter(v -> Integer.valueOf(1).equals(v.getVehicleStatus()))
+ .count());
+ }
+ return success(voPage);
+ }
+
+ @GetMapping("/change-history")
+ @Operation(summary = "获得合同变更历史")
+ @Parameter(name = "contractId", description = "合同ID", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('asset:contract:query')")
+ public CommonResult> getChangeHistory(@RequestParam("contractId") Long contractId) {
+ return success(contractChangeHistoryMapper.selectListByContractId(contractId));
+ }
+
+ @PostMapping("/submit-approval")
+ @Operation(summary = "提交合同审批")
+ @Parameter(name = "id", description = "合同ID", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('asset:contract:update')")
+ public CommonResult submitApproval(@RequestParam("id") Long id) {
+ return success(contractService.submitContractApproval(id));
+ }
+
+ @PostMapping("/withdraw-approval")
+ @Operation(summary = "撤回合同审批")
+ @Parameter(name = "id", description = "合同ID", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('asset:contract:update')")
+ public CommonResult withdrawApproval(@RequestParam("id") Long id) {
+ contractService.withdrawContractApproval(id);
+ return success(true);
+ }
+
+ @PostMapping("/terminate")
+ @Operation(summary = "终止合同")
+ @PreAuthorize("@ss.hasPermission('asset:contract:update')")
+ public CommonResult terminateContract(@RequestParam("id") Long id,
+ @RequestParam("reason") String reason) {
+ contractService.terminateContract(id, reason);
+ return success(true);
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得合同精简列表", description = "用于下拉选择")
+ public CommonResult> getContractSimpleList() {
+ List list = contractService.getContractSimpleList();
+ return success(BeanUtils.toBean(list, ContractSimpleRespVO.class));
+ }
+
+ @PostMapping("/renew")
+ @Operation(summary = "续签合同")
+ @PreAuthorize("@ss.hasPermission('asset:contract:create')")
+ public CommonResult renewContract(@RequestParam("id") Long id,
+ @Valid @RequestBody ContractSaveReqVO newContractReqVO) {
+ return success(contractService.renewContract(id, newContractReqVO));
+ }
+
+ @PostMapping("/convert-to-third-party")
+ @Operation(summary = "变更为三方合同")
+ @PreAuthorize("@ss.hasPermission('asset:contract:update')")
+ public CommonResult convertToThirdParty(@RequestParam("id") Long id,
+ @Valid @RequestBody ContractSaveReqVO newContractReqVO) {
+ return success(contractService.convertToThirdParty(id, newContractReqVO));
+ }
+
+ @PostMapping("/convert-to-formal")
+ @Operation(summary = "试用合同转正式")
+ @PreAuthorize("@ss.hasPermission('asset:contract:update')")
+ public CommonResult convertToFormal(@RequestParam("id") Long id,
+ @Valid @RequestBody ContractSaveReqVO newContractReqVO) {
+ return success(contractService.convertToFormal(id, newContractReqVO));
+ }
+
+ @PostMapping("/add-vehicle")
+ @Operation(summary = "往现有合同追加车辆")
+ @PreAuthorize("@ss.hasPermission('asset:contract:update')")
+ public CommonResult addVehicle(@RequestParam("id") Long id,
+ @Valid @RequestBody List vehicles) {
+ contractService.addVehiclesToContract(id, vehicles);
+ return success(true);
+ }
+
+ @PostMapping("/upload-seal")
+ @Operation(summary = "上传盖章合同附件")
+ @PreAuthorize("@ss.hasPermission('asset:contract:update')")
+ public CommonResult uploadSeal(@RequestParam("id") Long id,
+ @RequestParam("fileUrl") String fileUrl,
+ @RequestParam("fileName") String fileName) {
+ contractService.uploadSealedContract(id, fileUrl, fileName);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractAttachmentVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractAttachmentVO.java
new file mode 100644
index 0000000..48ef47e
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractAttachmentVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 合同附件 VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class ContractAttachmentVO {
+
+ @Schema(description = "主键ID", example = "1")
+ private Long id;
+
+ @Schema(description = "附件类型(1=合同原件 2=盖章合同)", example = "1")
+ private Integer attachmentType;
+
+ @Schema(description = "文件名称", example = "合同扫描件.pdf")
+ private String fileName;
+
+ @Schema(description = "文件URL", example = "https://xxx.com/contract.pdf")
+ private String fileUrl;
+
+ @Schema(description = "文件大小(字节)", example = "1024000")
+ private Long fileSize;
+
+ @Schema(description = "上传时间", example = "2026-01-01 00:00:00")
+ private LocalDateTime uploadTime;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractAuthorizedVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractAuthorizedVO.java
new file mode 100644
index 0000000..af4856b
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractAuthorizedVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 合同被授权人 VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class ContractAuthorizedVO {
+
+ @Schema(description = "主键ID", example = "1")
+ private Long id;
+
+ @Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+ private String name;
+
+ @Schema(description = "电话", requiredMode = Schema.RequiredMode.REQUIRED, example = "13800138000")
+ private String phone;
+
+ @Schema(description = "身份证号", requiredMode = Schema.RequiredMode.REQUIRED, example = "310101199001011234")
+ private String idCard;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractBaseVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractBaseVO.java
new file mode 100644
index 0000000..af79ff3
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractBaseVO.java
@@ -0,0 +1,101 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotBlank;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 车辆租赁合同 Base VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class ContractBaseVO {
+
+ @Schema(description = "合同编码(新增时自动生成)", example = "HT-2026-001")
+ private String contractCode;
+
+ @Schema(description = "合同类型(1=试用 2=正式)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "合同类型不能为空")
+ private Integer contractType;
+
+ @Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张江高科技园区车辆租赁项目")
+ @NotBlank(message = "项目名称不能为空")
+ private String projectName;
+
+ @Schema(description = "生效日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-01-01")
+ @NotNull(message = "生效日期不能为空")
+ private LocalDate startDate;
+
+ @Schema(description = "结束日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-12-31")
+ @NotNull(message = "结束日期不能为空")
+ private LocalDate endDate;
+
+ @Schema(description = "付款方式", example = "月付")
+ private String paymentMethod;
+
+ @Schema(description = "付款周期", example = "每月1日")
+ private String paymentCycle;
+
+ @Schema(description = "签约公司(乙方)", example = "上海某某汽车租赁有限公司")
+ private String signingCompany;
+
+ @Schema(description = "交车省份", example = "上海市")
+ private String deliveryProvince;
+
+ @Schema(description = "交车城市", example = "浦东新区")
+ private String deliveryCity;
+
+ @Schema(description = "交车地点", example = "张江高科技园区")
+ private String deliveryLocation;
+
+ @Schema(description = "备注", example = "特殊要求说明")
+ private String remark;
+
+ @Schema(description = "客户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "客户ID不能为空")
+ private Long customerId;
+
+ @Schema(description = "客户名称(由后端根据客户ID自动填充)", example = "上海某某科技有限公司")
+ private String customerName;
+
+ @Schema(description = "是否三方合同", example = "false")
+ private Boolean thirdPartyEnabled;
+
+ @Schema(description = "丙方客户ID", example = "2")
+ private Long thirdPartyCustomerId;
+
+ @Schema(description = "丙方名称", example = "北京某某公司")
+ private String thirdPartyName;
+
+ @Schema(description = "业务部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "业务部门ID不能为空")
+ private Long businessDeptId;
+
+ @Schema(description = "业务负责人ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "业务负责人ID不能为空")
+ private Long businessManagerId;
+
+ @Schema(description = "氢费承担方", example = "客户")
+ private String hydrogenBearer;
+
+ @Schema(description = "氢气付款方式", example = "预付")
+ private String hydrogenPaymentMethod;
+
+ @Schema(description = "氢气预付款", example = "5000.00")
+ private BigDecimal hydrogenPrepay;
+
+ @Schema(description = "退还车氢气单价", example = "35.00")
+ private BigDecimal hydrogenReturnPrice;
+
+ @Schema(description = "账单计算方式", example = "按自然月结算")
+ private String billingMethod;
+
+ @Schema(description = "主车型", example = "氢能重卡")
+ private String mainVehicleType;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractDetailRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractDetailRespVO.java
new file mode 100644
index 0000000..e432d29
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractDetailRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.List;
+
+/**
+ * 车辆租赁合同详情 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - 车辆租赁合同详情 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ContractDetailRespVO extends ContractRespVO {
+
+ @Schema(description = "车辆订单列表")
+ private List vehicles;
+
+ @Schema(description = "被授权人列表")
+ private List authorizedPersons;
+
+ @Schema(description = "附件列表")
+ private List attachments;
+
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ @ToString(callSuper = true)
+ public static class ContractVehicleDetailVO extends ContractVehicleVO {
+
+ @Schema(description = "服务项目列表")
+ private List services;
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractExcelVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractExcelVO.java
new file mode 100644
index 0000000..dfed989
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractExcelVO.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 车辆租赁合同 Excel 导出 VO
+ *
+ * @author 芋道源码
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class ContractExcelVO {
+
+ @ExcelProperty("合同编码")
+ private String contractCode;
+
+ @ExcelProperty("合同类型")
+ private Integer contractType;
+
+ @ExcelProperty("项目名称")
+ private String projectName;
+
+ @ExcelProperty("客户名称")
+ private String customerName;
+
+ @ExcelProperty("签约公司")
+ private String signingCompany;
+
+ @ExcelProperty("生效日期")
+ private LocalDate startDate;
+
+ @ExcelProperty("结束日期")
+ private LocalDate endDate;
+
+ @ExcelProperty("付款方式")
+ private String paymentMethod;
+
+ @ExcelProperty("付款周期")
+ private String paymentCycle;
+
+ @ExcelProperty("交车省份")
+ private String deliveryProvince;
+
+ @ExcelProperty("交车城市")
+ private String deliveryCity;
+
+ @ExcelProperty("交车地点")
+ private String deliveryLocation;
+
+ @ExcelProperty("主车型")
+ private String mainVehicleType;
+
+ @ExcelProperty("审批状态")
+ private Integer approvalStatus;
+
+ @ExcelProperty("合同状态")
+ private Integer contractStatus;
+
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+ @ExcelProperty("备注")
+ private String remark;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractPageReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractPageReqVO.java
new file mode 100644
index 0000000..7e2661a
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractPageReqVO.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+
+/**
+ * 车辆租赁合同分页查询 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - 车辆租赁合同分页查询 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ContractPageReqVO extends PageParam {
+
+ @Schema(description = "合同编码", example = "HT-2026-001")
+ private String contractCode;
+
+ @Schema(description = "合同类型(1=试用 2=正式)", example = "1")
+ private Integer contractType;
+
+ @Schema(description = "项目名称(模糊搜索)", example = "张江高科技园区")
+ private String projectName;
+
+ @Schema(description = "客户ID", example = "1")
+ private Long customerId;
+
+ @Schema(description = "客户名称(模糊搜索)", example = "上海某某科技")
+ private String customerName;
+
+ @Schema(description = "审批状态(0=草稿 1=审批中 2=审批通过 3=审批拒绝 4=已撤回)", example = "0")
+ private Integer approvalStatus;
+
+ @Schema(description = "合同状态(0=草稿 1=待生效 2=进行中 3=已到期 4=已终止 5=已续签)", example = "0")
+ private Integer contractStatus;
+
+ @Schema(description = "生效日期", example = "[2026-01-01, 2026-12-31]")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+ private LocalDate[] startDate;
+
+ @Schema(description = "结束日期", example = "[2026-01-01, 2026-12-31]")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+ private LocalDate[] endDate;
+
+ @Schema(description = "业务部门ID", example = "1")
+ private Long businessDeptId;
+
+ @Schema(description = "签约公司", example = "嘉兴羚牛")
+ private String signingCompany;
+
+ @Schema(description = "业务负责人ID", example = "1")
+ private Long businessManagerId;
+
+ @Schema(description = "创建人", example = "admin")
+ private String creator;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractRespVO.java
new file mode 100644
index 0000000..a957273
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractRespVO.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+/**
+ * 车辆租赁合同 Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - 车辆租赁合同 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ContractRespVO extends ContractBaseVO {
+
+ @Schema(description = "主键ID", example = "1")
+ private Long id;
+
+ @Schema(description = "审批状态(0=草稿 1=审批中 2=审批通过 3=审批拒绝 4=已撤回)", example = "0")
+ private Integer approvalStatus;
+
+ @Schema(description = "BPM流程实例ID", example = "123456")
+ private String bpmInstanceId;
+
+ @Schema(description = "合同状态(0=草稿 1=待生效 2=进行中 3=已到期 4=已终止 5=已续签)", example = "0")
+ private Integer contractStatus;
+
+ @Schema(description = "实际生效时间", example = "2026-01-01 00:00:00")
+ private LocalDateTime effectiveTime;
+
+ @Schema(description = "终止时间", example = "2026-12-31 23:59:59")
+ private LocalDateTime terminateTime;
+
+ @Schema(description = "终止原因", example = "客户要求终止")
+ private String terminateReason;
+
+ @Schema(description = "续签后的新合同ID", example = "2")
+ private Long renewedContractId;
+
+ @Schema(description = "原合同ID(如果是续签合同)", example = "1")
+ private Long originalContractId;
+
+ @Schema(description = "创建时间", example = "2026-01-01 00:00:00")
+ private LocalDateTime createTime;
+
+ @Schema(description = "创建者", example = "admin")
+ private String creator;
+
+ @Schema(description = "租赁车辆数", example = "5")
+ private Integer vehicleCount;
+
+ @Schema(description = "已交车辆数", example = "3")
+ private Integer deliveredCount;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractSaveReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractSaveReqVO.java
new file mode 100644
index 0000000..5d9e63a
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractSaveReqVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 车辆租赁合同创建/更新 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - 车辆租赁合同创建/更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ContractSaveReqVO extends ContractBaseVO {
+
+ @Schema(description = "主键ID", example = "1")
+ private Long id;
+
+ @Schema(description = "车辆订单列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "车辆订单列表不能为空")
+ @Valid
+ private List vehicles;
+
+ @Schema(description = "被授权人列表")
+ @Valid
+ private List authorizedPersons;
+
+ @Data
+ public static class ContractVehicleSaveVO extends ContractVehicleVO {
+
+ @Schema(description = "服务项目列表")
+ @Valid
+ private List services;
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractSimpleRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractSimpleRespVO.java
new file mode 100644
index 0000000..3e8eede
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractSimpleRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 合同精简 Response VO")
+@Data
+public class ContractSimpleRespVO {
+
+ @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "合同编码", example = "HT-2026-0001")
+ private String contractCode;
+
+ @Schema(description = "项目名称", example = "XX物流项目")
+ private String projectName;
+
+ @Schema(description = "客户名称", example = "XX公司")
+ private String customerName;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractVehicleServiceVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractVehicleServiceVO.java
new file mode 100644
index 0000000..705ed33
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractVehicleServiceVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 合同车辆服务项目 VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class ContractVehicleServiceVO {
+
+ @Schema(description = "主键ID", example = "1")
+ private Long id;
+
+ @Schema(description = "服务项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "保险")
+ private String serviceName;
+
+ @Schema(description = "服务费用(元)", requiredMode = Schema.RequiredMode.REQUIRED, example = "3000.00")
+ private BigDecimal serviceFee;
+
+ @Schema(description = "生效日期", example = "2026-01-01")
+ private LocalDate effectiveDate;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractVehicleVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractVehicleVO.java
new file mode 100644
index 0000000..9758cd3
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/contract/vo/ContractVehicleVO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.asset.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 合同车辆 VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class ContractVehicleVO {
+
+ @Schema(description = "主键ID", example = "1")
+ private Long id;
+
+ @Schema(description = "车辆ID", example = "1")
+ private Long vehicleId;
+
+ @Schema(description = "品牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "丰田")
+ private String brand;
+
+ @Schema(description = "型号", requiredMode = Schema.RequiredMode.REQUIRED, example = "凯美瑞")
+ private String model;
+
+ @Schema(description = "车牌号", example = "沪A12345")
+ private String plateNo;
+
+ @Schema(description = "VIN码", example = "LVSHCAMB1CE012345")
+ private String vin;
+
+ @Schema(description = "月租金(元)", requiredMode = Schema.RequiredMode.REQUIRED, example = "5000.00")
+ private BigDecimal monthRent;
+
+ @Schema(description = "保证金(元)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000.00")
+ private BigDecimal deposit;
+
+ @Schema(description = "车辆状态(0=待交车 1=已交车 2=已退车)", example = "0")
+ private Integer vehicleStatus;
+
+ @Schema(description = "备注", example = "特殊要求")
+ private String remark;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/customer/CustomerController.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/customer/CustomerController.java
index 7bdcf7e..caaab61 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/customer/CustomerController.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/customer/CustomerController.java
@@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
+import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -75,4 +76,11 @@ public class CustomerController {
return success(CustomerConvert.INSTANCE.convertPage(pageResult));
}
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得客户精简列表", description = "用于下拉选择")
+ public CommonResult> getCustomerSimpleList() {
+ List list = customerService.getCustomerSimpleList();
+ return success(CustomerConvert.INSTANCE.convertList(list));
+ }
+
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/DeliveryOrderController.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/DeliveryOrderController.java
new file mode 100644
index 0000000..cd0e279
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/DeliveryOrderController.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.*;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryOrderDO;
+import cn.iocoder.yudao.module.asset.service.delivery.DeliveryOrderService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 交车单")
+@RestController
+@RequestMapping("/asset/delivery-order")
+@Validated
+public class DeliveryOrderController {
+
+ @Resource
+ private DeliveryOrderService deliveryOrderService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建交车单")
+ @PreAuthorize("@ss.hasPermission('asset:delivery-order:create')")
+ public CommonResult createDeliveryOrder(@Valid @RequestBody DeliveryOrderSaveReqVO createReqVO) {
+ return success(deliveryOrderService.createDeliveryOrder(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新交车单")
+ @PreAuthorize("@ss.hasPermission('asset:delivery-order:update')")
+ public CommonResult updateDeliveryOrder(@Valid @RequestBody DeliveryOrderSaveReqVO updateReqVO) {
+ deliveryOrderService.updateDeliveryOrder(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除交车单")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:delivery-order:delete')")
+ public CommonResult deleteDeliveryOrder(@RequestParam("id") Long id) {
+ deliveryOrderService.deleteDeliveryOrder(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得交车单详情")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:delivery-order:query')")
+ public CommonResult getDeliveryOrder(@RequestParam("id") Long id) {
+ return success(deliveryOrderService.getDeliveryOrderDetail(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得交车单分页")
+ @PreAuthorize("@ss.hasPermission('asset:delivery-order:query')")
+ public CommonResult> getDeliveryOrderPage(@Valid DeliveryOrderPageReqVO pageReqVO) {
+ PageResult pageResult = deliveryOrderService.getDeliveryOrderPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, DeliveryOrderRespVO.class));
+ }
+
+ @PutMapping("/complete")
+ @Operation(summary = "完成交车")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:delivery-order:update')")
+ public CommonResult completeDeliveryOrder(@RequestParam("id") Long id) {
+ deliveryOrderService.completeDeliveryOrder(id);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/DeliveryTaskController.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/DeliveryTaskController.java
new file mode 100644
index 0000000..f7451c7
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/DeliveryTaskController.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.*;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryTaskDO;
+import cn.iocoder.yudao.module.asset.service.delivery.DeliveryTaskService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 交车任务")
+@RestController
+@RequestMapping("/asset/delivery-task")
+@Validated
+public class DeliveryTaskController {
+
+ @Resource
+ private DeliveryTaskService deliveryTaskService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建交车任务")
+ @PreAuthorize("@ss.hasPermission('asset:delivery-task:create')")
+ public CommonResult createDeliveryTask(@Valid @RequestBody DeliveryTaskSaveReqVO createReqVO) {
+ return success(deliveryTaskService.createDeliveryTask(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新交车任务")
+ @PreAuthorize("@ss.hasPermission('asset:delivery-task:update')")
+ public CommonResult updateDeliveryTask(@Valid @RequestBody DeliveryTaskSaveReqVO updateReqVO) {
+ deliveryTaskService.updateDeliveryTask(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除交车任务")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:delivery-task:delete')")
+ public CommonResult deleteDeliveryTask(@RequestParam("id") Long id) {
+ deliveryTaskService.deleteDeliveryTask(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得交车任务详情")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:delivery-task:query')")
+ public CommonResult getDeliveryTask(@RequestParam("id") Long id) {
+ return success(deliveryTaskService.getDeliveryTaskDetail(id));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得交车任务分页")
+ @PreAuthorize("@ss.hasPermission('asset:delivery-task:query')")
+ public CommonResult> getDeliveryTaskPage(@Valid DeliveryTaskPageReqVO pageReqVO) {
+ PageResult pageResult = deliveryTaskService.getDeliveryTaskPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, DeliveryTaskRespVO.class));
+ }
+
+ @GetMapping("/contract-page")
+ @Operation(summary = "获得交车任务按合同分组分页")
+ @PreAuthorize("@ss.hasPermission('asset:delivery-task:query')")
+ public CommonResult> getDeliveryTaskContractPage(
+ @Valid DeliveryTaskPageReqVO pageReqVO) {
+ return success(deliveryTaskService.getDeliveryTaskContractPage(pageReqVO));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得交车任务精简列表", description = "用于下拉选择")
+ public CommonResult> getDeliveryTaskSimpleList() {
+ java.util.List list = deliveryTaskService.getDeliveryTaskSimpleList();
+ return success(BeanUtils.toBean(list, DeliveryTaskSimpleRespVO.class));
+ }
+
+ @PutMapping("/suspend")
+ @Operation(summary = "挂起交车任务")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:delivery-task:update')")
+ public CommonResult suspendDeliveryTask(@RequestParam("id") Long id) {
+ deliveryTaskService.suspendDeliveryTask(id);
+ return success(true);
+ }
+
+ @PutMapping("/activate")
+ @Operation(summary = "激活交车任务")
+ @PreAuthorize("@ss.hasPermission('asset:delivery-task:update')")
+ public CommonResult activateDeliveryTask(@Valid @RequestBody DeliveryTaskActivateReqVO reqVO) {
+ deliveryTaskService.activateDeliveryTask(reqVO);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderPageReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderPageReqVO.java
new file mode 100644
index 0000000..5bcaf8b
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderPageReqVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 交车单分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class DeliveryOrderPageReqVO extends PageParam {
+
+ @Schema(description = "交车单编码")
+ private String orderCode;
+
+ @Schema(description = "任务编码")
+ private String taskCode;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "项目名称")
+ private String projectName;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "交车人")
+ private String deliveryPerson;
+
+ @Schema(description = "交车地区")
+ private String deliveryRegion;
+
+ @Schema(description = "状态")
+ private Integer status;
+
+ @Schema(description = "交车日期")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] deliveryDate;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderSaveReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderSaveReqVO.java
new file mode 100644
index 0000000..8231a63
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderSaveReqVO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 交车单创建/更新 Request VO")
+@Data
+public class DeliveryOrderSaveReqVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "交车任务ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "交车任务ID不能为空")
+ private Long taskId;
+
+ @Schema(description = "交车日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "交车日期不能为空")
+ private LocalDateTime deliveryDate;
+
+ @Schema(description = "交车人", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "交车人不能为空")
+ private String deliveryPerson;
+
+ @Schema(description = "交车地点")
+ private String deliveryLocation;
+
+ @Schema(description = "被授权人ID")
+ private Long authorizedPersonId;
+
+ @Schema(description = "被授权人姓名")
+ private String authorizedPersonName;
+
+ @Schema(description = "被授权人电话")
+ private String authorizedPersonPhone;
+
+ @Schema(description = "被授权人身份证")
+ private String authorizedPersonIdCard;
+
+ @Schema(description = "交车照片")
+ private String deliveryPhotos;
+
+ @Schema(description = "司机姓名")
+ private String driverName;
+
+ @Schema(description = "司机身份证")
+ private String driverIdCard;
+
+ @Schema(description = "司机手机号")
+ private String driverPhone;
+
+ @Schema(description = "交检清单JSON")
+ private String inspectionData;
+
+ @Schema(description = "费用信息JSON")
+ private String costList;
+
+ @Schema(description = "车辆列表")
+ private List vehicles;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderVehicleVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderVehicleVO.java
new file mode 100644
index 0000000..75235df
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryOrderVehicleVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "交车单车辆 VO")
+@Data
+public class DeliveryOrderVehicleVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "交车任务车辆ID")
+ private Long taskVehicleId;
+
+ @Schema(description = "车辆ID")
+ private Long vehicleId;
+
+ @Schema(description = "车牌号")
+ private String plateNo;
+
+ @Schema(description = "VIN码")
+ private String vin;
+
+ @Schema(description = "品牌")
+ private String brand;
+
+ @Schema(description = "型号")
+ private String model;
+
+ @Schema(description = "交车时里程")
+ private Integer mileage;
+
+ @Schema(description = "交车时氢气量(kg)")
+ private BigDecimal hydrogenLevel;
+
+ @Schema(description = "交车时电量(%)")
+ private BigDecimal batteryLevel;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskActivateReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskActivateReqVO.java
new file mode 100644
index 0000000..3937c18
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskActivateReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Schema(description = "管理后台 - 交车任务激活 Request VO")
+@Data
+public class DeliveryTaskActivateReqVO {
+
+ @Schema(description = "任务ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "任务ID不能为空")
+ private Long id;
+
+ @Schema(description = "预计交车开始日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "预计交车开始日期不能为空")
+ private LocalDate expectedDeliveryDateStart;
+
+ @Schema(description = "预计交车结束日期")
+ private LocalDate expectedDeliveryDateEnd;
+
+ @Schema(description = "开始计费日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "开始计费日期不能为空")
+ private LocalDate billingStartDate;
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskContractGroupRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskContractGroupRespVO.java
new file mode 100644
index 0000000..f4701c1
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskContractGroupRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@Schema(description = "管理后台 - 交车任务按合同分组 Response VO")
+@Data
+public class DeliveryTaskContractGroupRespVO {
+
+ @Schema(description = "合同ID")
+ private Long contractId;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "项目名称")
+ private String projectName;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "业务部门名称")
+ private String businessDeptName;
+
+ @Schema(description = "业务负责人名称")
+ private String businessManagerName;
+
+ @Schema(description = "合同生效日期")
+ private LocalDate startDate;
+
+ @Schema(description = "合同结束日期")
+ private LocalDate endDate;
+
+ @Schema(description = "交车任务列表")
+ private List tasks;
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskPageReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskPageReqVO.java
new file mode 100644
index 0000000..7cc4e3e
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskPageReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 交车任务分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class DeliveryTaskPageReqVO extends PageParam {
+
+ @Schema(description = "任务编码")
+ private String taskCode;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "项目名称")
+ private String projectName;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "任务状态")
+ private Integer taskStatus;
+
+ @Schema(description = "交车状态")
+ private Integer deliveryStatus;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskRespVO.java
new file mode 100644
index 0000000..198a383
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskRespVO.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 交车任务 Response VO")
+@Data
+public class DeliveryTaskRespVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "任务编码")
+ private String taskCode;
+
+ @Schema(description = "合同ID")
+ private Long contractId;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "项目名称")
+ private String projectName;
+
+ @Schema(description = "客户ID")
+ private Long customerId;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "预计交车开始日期")
+ private LocalDate expectedDeliveryDateStart;
+
+ @Schema(description = "预计交车结束日期")
+ private LocalDate expectedDeliveryDateEnd;
+
+ @Schema(description = "开始计费日期")
+ private LocalDate billingStartDate;
+
+ @Schema(description = "交车省份")
+ private String deliveryProvince;
+
+ @Schema(description = "交车城市")
+ private String deliveryCity;
+
+ @Schema(description = "交车地点")
+ private String deliveryLocation;
+
+ @Schema(description = "交车数量")
+ private Integer vehicleCount;
+
+ @Schema(description = "任务状态")
+ private Integer taskStatus;
+
+ @Schema(description = "交车状态")
+ private Integer deliveryStatus;
+
+ @Schema(description = "是否需要还车")
+ private Boolean needReturn;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+ @Schema(description = "车辆列表")
+ private List vehicles;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskSaveReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskSaveReqVO.java
new file mode 100644
index 0000000..2bf18a6
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskSaveReqVO.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@Schema(description = "管理后台 - 交车任务创建/更新 Request VO")
+@Data
+public class DeliveryTaskSaveReqVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "合同ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "合同ID不能为空")
+ private Long contractId;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "项目名称")
+ private String projectName;
+
+ @Schema(description = "客户ID")
+ private Long customerId;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "预计交车开始日期")
+ private LocalDate expectedDeliveryDateStart;
+
+ @Schema(description = "预计交车结束日期")
+ private LocalDate expectedDeliveryDateEnd;
+
+ @Schema(description = "开始计费日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "开始计费日期不能为空")
+ private LocalDate billingStartDate;
+
+ @Schema(description = "交车省份")
+ private String deliveryProvince;
+
+ @Schema(description = "交车城市")
+ private String deliveryCity;
+
+ @Schema(description = "交车地点")
+ private String deliveryLocation;
+
+ @Schema(description = "是否需要还车")
+ private Boolean needReturn;
+
+ @Schema(description = "车辆列表")
+ private List vehicles;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskSimpleRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskSimpleRespVO.java
new file mode 100644
index 0000000..e10e1fb
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskSimpleRespVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 交车任务精简 Response VO")
+@Data
+public class DeliveryTaskSimpleRespVO {
+
+ @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "任务编码", example = "HT-2026-0001JC0001")
+ private String taskCode;
+
+ @Schema(description = "合同编码", example = "HT-2026-0001")
+ private String contractCode;
+
+ @Schema(description = "项目名称", example = "XX物流项目")
+ private String projectName;
+
+ @Schema(description = "客户名称", example = "XX公司")
+ private String customerName;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskVehicleVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskVehicleVO.java
new file mode 100644
index 0000000..fd88aa7
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/delivery/vo/DeliveryTaskVehicleVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.asset.controller.admin.delivery.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "交车任务车辆 VO")
+@Data
+public class DeliveryTaskVehicleVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "合同车辆ID")
+ private Long contractVehicleId;
+
+ @Schema(description = "车辆ID")
+ private Long vehicleId;
+
+ @Schema(description = "车牌号")
+ private String plateNo;
+
+ @Schema(description = "VIN码")
+ private String vin;
+
+ @Schema(description = "品牌")
+ private String brand;
+
+ @Schema(description = "型号")
+ private String model;
+
+ @Schema(description = "月租金")
+ private BigDecimal monthRent;
+
+ @Schema(description = "保证金")
+ private BigDecimal deposit;
+
+ @Schema(description = "是否已交车")
+ private Boolean isDelivered;
+
+ @Schema(description = "实际交车日期")
+ private java.time.LocalDateTime actualDeliveryDate;
+
+ @Schema(description = "交车人")
+ private String deliveryPerson;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/VehiclePrepareController.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/VehiclePrepareController.java
new file mode 100644
index 0000000..a5bbd09
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/VehiclePrepareController.java
@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.asset.controller.admin.prepare;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.prepare.vo.*;
+import cn.iocoder.yudao.module.asset.dal.dataobject.prepare.VehiclePrepareDO;
+import cn.iocoder.yudao.module.asset.service.prepare.VehiclePrepareService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 备车管理")
+@RestController
+@RequestMapping("/asset/vehicle-prepare")
+@Validated
+public class VehiclePrepareController {
+
+ @Resource
+ private VehiclePrepareService vehiclePrepareService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建备车记录")
+ @PreAuthorize("@ss.hasPermission('asset:vehicle-prepare:create')")
+ public CommonResult createVehiclePrepare(@Valid @RequestBody VehiclePrepareSaveReqVO createReqVO) {
+ return success(vehiclePrepareService.createVehiclePrepare(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新备车记录")
+ @PreAuthorize("@ss.hasPermission('asset:vehicle-prepare:update')")
+ public CommonResult updateVehiclePrepare(@Valid @RequestBody VehiclePrepareSaveReqVO updateReqVO) {
+ vehiclePrepareService.updateVehiclePrepare(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除备车记录")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:vehicle-prepare:delete')")
+ public CommonResult deleteVehiclePrepare(@RequestParam("id") Long id) {
+ vehiclePrepareService.deleteVehiclePrepare(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得备车记录")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:vehicle-prepare:query')")
+ public CommonResult getVehiclePrepare(@RequestParam("id") Long id) {
+ VehiclePrepareDO prepare = vehiclePrepareService.getVehiclePrepare(id);
+ return success(BeanUtils.toBean(prepare, VehiclePrepareRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得备车记录分页")
+ @PreAuthorize("@ss.hasPermission('asset:vehicle-prepare:query')")
+ public CommonResult> getVehiclePreparePage(@Valid VehiclePreparePageReqVO pageReqVO) {
+ PageResult pageResult = vehiclePrepareService.getVehiclePreparePage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, VehiclePrepareRespVO.class));
+ }
+
+ @PutMapping("/complete")
+ @Operation(summary = "完成备车")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:vehicle-prepare:update')")
+ public CommonResult completeVehiclePrepare(@RequestParam("id") Long id) {
+ vehiclePrepareService.completeVehiclePrepare(id);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/vo/VehiclePreparePageReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/vo/VehiclePreparePageReqVO.java
new file mode 100644
index 0000000..4bbffe4
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/vo/VehiclePreparePageReqVO.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.asset.controller.admin.prepare.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 备车记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class VehiclePreparePageReqVO extends PageParam {
+
+ @Schema(description = "车牌号")
+ private String plateNo;
+
+ @Schema(description = "VIN码")
+ private String vin;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "车辆类型")
+ private String vehicleType;
+
+ @Schema(description = "品牌")
+ private String brand;
+
+ @Schema(description = "停车场")
+ private String parkingLot;
+
+ @Schema(description = "状态")
+ private Integer status;
+
+ @Schema(description = "备车日期")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] completeTime;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/vo/VehiclePrepareSaveReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/vo/VehiclePrepareSaveReqVO.java
new file mode 100644
index 0000000..b804a03
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/prepare/vo/VehiclePrepareSaveReqVO.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.asset.controller.admin.prepare.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "管理后台 - 备车记录创建/更新 Request VO")
+@Data
+public class VehiclePrepareSaveReqVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "车辆ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "车辆ID不能为空")
+ private Long vehicleId;
+
+ @Schema(description = "车牌号")
+ private String plateNo;
+
+ @Schema(description = "VIN码", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "VIN码不能为空")
+ private String vin;
+
+ @Schema(description = "车型ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "车型ID不能为空")
+ private Long vehicleModelId;
+
+ @Schema(description = "品牌")
+ private String brand;
+
+ @Schema(description = "型号")
+ private String model;
+
+ @Schema(description = "车辆类型")
+ private String vehicleType;
+
+ @Schema(description = "停车场")
+ private String parkingLot;
+
+ @Schema(description = "合同ID")
+ private Long contractId;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "整备类型")
+ private String preparationType;
+
+ @Schema(description = "里程(km)")
+ private Integer mileage;
+
+ @Schema(description = "剩余氢量")
+ private BigDecimal hydrogenRemaining;
+
+ @Schema(description = "氢量单位")
+ private String hydrogenUnit;
+
+ @Schema(description = "剩余电量(%)")
+ private BigDecimal batteryRemaining;
+
+ @Schema(description = "是否有车身广告")
+ private Boolean hasBodyAd;
+
+ @Schema(description = "广告照片")
+ private String bodyAdPhotos;
+
+ @Schema(description = "放大字照片")
+ private String enlargedTextPhoto;
+
+ @Schema(description = "是否有尾板")
+ private Boolean hasTailLift;
+
+ @Schema(description = "备胎胎纹深度(mm)")
+ private BigDecimal spareTireDepth;
+
+ @Schema(description = "备胎照片")
+ private String spareTirePhoto;
+
+ @Schema(description = "挂车牌号")
+ private String trailerPlateNo;
+
+ @Schema(description = "瑕疵照片")
+ private String defectPhotos;
+
+ @Schema(description = "检查清单JSON")
+ private String checkList;
+
+ @Schema(description = "备注")
+ private String remark;
+
+ @Schema(description = "状态")
+ private Integer status;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderPageReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderPageReqVO.java
new file mode 100644
index 0000000..132c0d5
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderPageReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.asset.controller.admin.returnorder.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 还车单分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ReturnOrderPageReqVO extends PageParam {
+
+ @Schema(description = "还车单编码")
+ private String orderCode;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "状态")
+ private Integer status;
+
+ @Schema(description = "还车原因")
+ private String returnReason;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderRespVO.java
new file mode 100644
index 0000000..a4b03e2
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderRespVO.java
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.asset.controller.admin.returnorder.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 还车单 Response VO")
+@Data
+public class ReturnOrderRespVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "还车单编码")
+ private String orderCode;
+
+ @Schema(description = "合同ID")
+ private Long contractId;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "项目名称")
+ private String projectName;
+
+ @Schema(description = "客户ID")
+ private Long customerId;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "还车日期")
+ private LocalDateTime returnDate;
+
+ @Schema(description = "还车验收人")
+ private String returnPerson;
+
+ @Schema(description = "还车地点")
+ private String returnLocation;
+
+ @Schema(description = "还车原因")
+ private String returnReason;
+
+ @Schema(description = "还车原因说明")
+ private String returnReasonDesc;
+
+ @Schema(description = "退还总金额")
+ private BigDecimal totalRefundAmount;
+
+ @Schema(description = "退还保证金")
+ private BigDecimal depositRefund;
+
+ @Schema(description = "氢气退款")
+ private BigDecimal hydrogenRefund;
+
+ @Schema(description = "其他费用")
+ private BigDecimal otherCharges;
+
+ @Schema(description = "还车照片")
+ private String returnPhotos;
+
+ @Schema(description = "状态")
+ private Integer status;
+
+ @Schema(description = "审批状态")
+ private Integer approvalStatus;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+ @Schema(description = "车辆列表")
+ private List vehicles;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderSaveReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderSaveReqVO.java
new file mode 100644
index 0000000..6b31117
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderSaveReqVO.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.asset.controller.admin.returnorder.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 还车单创建/更新 Request VO")
+@Data
+public class ReturnOrderSaveReqVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "合同ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "合同ID不能为空")
+ private Long contractId;
+
+ @Schema(description = "合同编码")
+ private String contractCode;
+
+ @Schema(description = "项目名称")
+ private String projectName;
+
+ @Schema(description = "客户ID")
+ private Long customerId;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "还车日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "还车日期不能为空")
+ private LocalDateTime returnDate;
+
+ @Schema(description = "还车验收人", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "还车验收人不能为空")
+ private String returnPerson;
+
+ @Schema(description = "还车地点")
+ private String returnLocation;
+
+ @Schema(description = "还车原因")
+ private String returnReason;
+
+ @Schema(description = "还车原因说明")
+ private String returnReasonDesc;
+
+ @Schema(description = "退还保证金")
+ private BigDecimal depositRefund;
+
+ @Schema(description = "氢气退款")
+ private BigDecimal hydrogenRefund;
+
+ @Schema(description = "其他费用")
+ private BigDecimal otherCharges;
+
+ @Schema(description = "还车照片")
+ private String returnPhotos;
+
+ @Schema(description = "车辆列表")
+ private List vehicles;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderVehicleVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderVehicleVO.java
new file mode 100644
index 0000000..c716e46
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/returnorder/vo/ReturnOrderVehicleVO.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.asset.controller.admin.returnorder.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "还车车辆 VO")
+@Data
+public class ReturnOrderVehicleVO {
+
+ @Schema(description = "主键")
+ private Long id;
+
+ @Schema(description = "车辆ID")
+ private Long vehicleId;
+
+ @Schema(description = "车牌号")
+ private String plateNo;
+
+ @Schema(description = "VIN码")
+ private String vin;
+
+ @Schema(description = "品牌")
+ private String brand;
+
+ @Schema(description = "型号")
+ private String model;
+
+ @Schema(description = "还车时里程")
+ private Integer returnMileage;
+
+ @Schema(description = "还车时氢气量")
+ private BigDecimal returnHydrogenLevel;
+
+ @Schema(description = "交车时氢气量")
+ private BigDecimal deliveryHydrogenLevel;
+
+ @Schema(description = "氢气差值")
+ private BigDecimal hydrogenDiff;
+
+ @Schema(description = "氢气单价")
+ private BigDecimal hydrogenUnitPrice;
+
+ @Schema(description = "氢气退款金额")
+ private BigDecimal hydrogenRefundAmount;
+
+ @Schema(description = "检查清单JSON")
+ private String checkList;
+
+ @Schema(description = "瑕疵照片")
+ private String defectPhotos;
+
+ @Schema(description = "车损费")
+ private BigDecimal vehicleDamageFee;
+
+ @Schema(description = "工具损坏费")
+ private BigDecimal toolDamageFee;
+
+ @Schema(description = "未结算保养费")
+ private BigDecimal unpaidMaintenanceFee;
+
+ @Schema(description = "未结算维修费")
+ private BigDecimal unpaidRepairFee;
+
+ @Schema(description = "违章费用")
+ private BigDecimal violationFee;
+
+ @Schema(description = "其他费用")
+ private BigDecimal otherFee;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/HydrogenStationController.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/HydrogenStationController.java
new file mode 100644
index 0000000..12d8a5e
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/HydrogenStationController.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.asset.controller.admin.station;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.station.vo.*;
+import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
+import cn.iocoder.yudao.module.asset.service.station.HydrogenStationService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 加氢站管理 Controller
+ *
+ * @author 芋道源码
+ */
+@Tag(name = "管理后台 - 加氢站管理")
+@RestController
+@RequestMapping("/asset/hydrogen-station")
+@Validated
+public class HydrogenStationController {
+
+ @Resource
+ private HydrogenStationService hydrogenStationService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建加氢站")
+ @PreAuthorize("@ss.hasPermission('asset:hydrogen-station:create')")
+ public CommonResult createHydrogenStation(@Valid @RequestBody HydrogenStationSaveReqVO createReqVO) {
+ return success(hydrogenStationService.createHydrogenStation(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新加氢站")
+ @PreAuthorize("@ss.hasPermission('asset:hydrogen-station:update')")
+ public CommonResult updateHydrogenStation(@Valid @RequestBody HydrogenStationSaveReqVO updateReqVO) {
+ hydrogenStationService.updateHydrogenStation(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除加氢站")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:hydrogen-station:delete')")
+ public CommonResult deleteHydrogenStation(@RequestParam("id") Long id) {
+ hydrogenStationService.deleteHydrogenStation(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得加氢站")
+ @Parameter(name = "id", description = "编号", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('asset:hydrogen-station:query')")
+ public CommonResult getHydrogenStation(@RequestParam("id") Long id) {
+ HydrogenStationDO hydrogenStation = hydrogenStationService.getHydrogenStation(id);
+ return success(BeanUtils.toBean(hydrogenStation, HydrogenStationRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得加氢站分页")
+ @PreAuthorize("@ss.hasPermission('asset:hydrogen-station:query')")
+ public CommonResult> getHydrogenStationPage(@Valid HydrogenStationPageReqVO pageReqVO) {
+ PageResult pageResult = hydrogenStationService.getHydrogenStationPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, HydrogenStationRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得加氢站精简列表", description = "用于下拉选择")
+ public CommonResult> getHydrogenStationSimpleList() {
+ List list = hydrogenStationService.getHydrogenStationSimpleList();
+ return success(BeanUtils.toBean(list, HydrogenStationSimpleRespVO.class));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationBaseVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationBaseVO.java
new file mode 100644
index 0000000..ebb316a
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationBaseVO.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.asset.controller.admin.station.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import java.time.LocalDate;
+import java.time.LocalTime;
+
+/**
+ * 加氢站 Base VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class HydrogenStationBaseVO {
+
+ @Schema(description = "站点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "嘉兴嘉燃经开站")
+ @NotBlank(message = "站点名称不能为空")
+ private String name;
+
+ @Schema(description = "简称", example = "经开站")
+ private String shortName;
+
+ @Schema(description = "站点编码", example = "JX001")
+ private String stationNo;
+
+ @Schema(description = "所属城市", example = "嘉兴")
+ private String city;
+
+ @Schema(description = "站点地址", example = "浙江省嘉兴市经济开发区岗山路")
+ private String address;
+
+ @Schema(description = "经度", example = "120.123456")
+ @Pattern(regexp = "^-?((0|[1-9]\\d?|1[0-7]\\d)(\\.\\d{1,6})?|180(\\.0{1,6})?)$", message = "经度格式不正确,范围:-180~180")
+ private String longitude;
+
+ @Schema(description = "纬度", example = "30.123456")
+ @Pattern(regexp = "^-?((0|[1-8]?\\d)(\\.\\d{1,6})?|90(\\.0{1,6})?)$", message = "纬度格式不正确,范围:-90~90")
+ private String latitude;
+
+ @Schema(description = "联系人", example = "张三")
+ private String contact;
+
+ @Schema(description = "联系电话", example = "13800138000")
+ @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+ private String phone;
+
+ @Schema(description = "站点类型(字典)", example = "1")
+ private Integer stationType;
+
+ @Schema(description = "合作类型(0=合作 1=非合作)", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "合作类型不能为空")
+ private Integer cooperationType;
+
+ @Schema(description = "是否自动扣款(1=是 0=否)", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "是否自动扣款不能为空")
+ private Boolean autoDeduct;
+
+ @Schema(description = "是否需要预约(1=是 0=否)", example = "false")
+ private Boolean bookingRequired;
+
+ @Schema(description = "站点状态(0=停用 1=启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "站点状态不能为空")
+ private Integer stationStatus;
+
+ @Schema(description = "开始营业时间", example = "08:00:00")
+ private LocalTime startBusiness;
+
+ @Schema(description = "结束营业时间", example = "18:00:00")
+ private LocalTime endBusiness;
+
+ @Schema(description = "结算方式(字典)", example = "1")
+ private Integer billingMethod;
+
+ @Schema(description = "合作期限", example = "2027-12-31")
+ private LocalDate cooperationTerm;
+
+ @Schema(description = "备注", example = "合作站点,自动扣款")
+ private String remark;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationPageReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationPageReqVO.java
new file mode 100644
index 0000000..a55d6d9
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationPageReqVO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.asset.controller.admin.station.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 加氢站 - 分页查询 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - 加氢站分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HydrogenStationPageReqVO extends PageParam {
+
+ @Schema(description = "站点名称", example = "嘉兴嘉燃经开站")
+ private String name;
+
+ @Schema(description = "站点编码", example = "JX001")
+ private String stationNo;
+
+ @Schema(description = "所属城市", example = "嘉兴")
+ private String city;
+
+ @Schema(description = "合作类型(0=合作 1=非合作)", example = "0")
+ private Integer cooperationType;
+
+ @Schema(description = "站点状态(0=停用 1=启用)", example = "1")
+ private Integer stationStatus;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationRespVO.java
new file mode 100644
index 0000000..d394477
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.asset.controller.admin.station.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+/**
+ * 加氢站 - Response VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - 加氢站 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HydrogenStationRespVO extends HydrogenStationBaseVO {
+
+ @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationSaveReqVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationSaveReqVO.java
new file mode 100644
index 0000000..1bad52b
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationSaveReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.asset.controller.admin.station.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * 加氢站 - 创建/更新 Request VO
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - 加氢站创建/更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HydrogenStationSaveReqVO extends HydrogenStationBaseVO {
+
+ @Schema(description = "主键", example = "1")
+ private Long id;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationSimpleRespVO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationSimpleRespVO.java
new file mode 100644
index 0000000..eac3b17
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/station/vo/HydrogenStationSimpleRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.asset.controller.admin.station.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 加氢站 - 精简信息 Response VO
+ * 用于下拉选择
+ *
+ * @author 芋道源码
+ */
+@Schema(description = "管理后台 - 加氢站精简信息 Response VO")
+@Data
+public class HydrogenStationSimpleRespVO {
+
+ @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "站点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "嘉兴嘉燃经开站")
+ private String name;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/vehicleregistration/VehicleRegistrationController.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/vehicleregistration/VehicleRegistrationController.java
index b69c892..a89efcc 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/vehicleregistration/VehicleRegistrationController.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/controller/admin/vehicleregistration/VehicleRegistrationController.java
@@ -21,6 +21,9 @@ import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -58,6 +61,23 @@ public class VehicleRegistrationController {
return success(result);
}
+ @PostMapping("/ocr")
+ @Operation(summary = "通过文件URL识别行驶证")
+ @PreAuthorize("@ss.hasPermission('asset:vehicle-registration:recognize')")
+ public CommonResult ocrVehicleLicense(
+ @RequestBody Map body) throws IOException {
+ String fileUrl = body.get("fileUrl");
+ if (fileUrl == null || fileUrl.isBlank()) {
+ throw new IllegalArgumentException("fileUrl 不能为空");
+ }
+ log.info("[ocrVehicleLicense][通过URL识别行驶证:{}]", fileUrl);
+ try (InputStream is = URI.create(fileUrl).toURL().openStream()) {
+ byte[] imageData = is.readAllBytes();
+ VehicleLicenseRecognizeRespVO result = vehicleRegistrationService.recognizeVehicleLicense(imageData);
+ return success(result);
+ }
+ }
+
@PostMapping("/create")
@Operation(summary = "创建车辆上牌记录")
@PreAuthorize("@ss.hasPermission('asset:vehicle-registration:create')")
@@ -99,7 +119,7 @@ public class VehicleRegistrationController {
return success(VehicleRegistrationConvert.INSTANCE.convertPage(pageResult));
}
- @PostMapping("/confirm")
+ @PutMapping("/confirm")
@Operation(summary = "确认上牌记录(更新车辆信息)")
@Parameter(name = "id", description = "上牌记录ID", required = true)
@PreAuthorize("@ss.hasPermission('asset:vehicle-registration:update')")
@@ -108,4 +128,13 @@ public class VehicleRegistrationController {
return success(true);
}
+ @PutMapping("/void")
+ @Operation(summary = "作废上牌记录")
+ @Parameter(name = "id", description = "上牌记录ID", required = true)
+ @PreAuthorize("@ss.hasPermission('asset:vehicle-registration:update')")
+ public CommonResult voidRegistration(@RequestParam("id") Long id) {
+ vehicleRegistrationService.voidRegistration(id);
+ return success(true);
+ }
+
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/contract/ContractConvert.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/contract/ContractConvert.java
new file mode 100644
index 0000000..a5b67f8
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/contract/ContractConvert.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.asset.convert.contract;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.contract.vo.*;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.*;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 车辆租赁合同 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ContractConvert {
+
+ ContractConvert INSTANCE = Mappers.getMapper(ContractConvert.class);
+
+ ContractDO convert(ContractSaveReqVO bean);
+
+ ContractRespVO convert(ContractDO bean);
+
+ ContractDetailRespVO convertDetail(ContractDO bean);
+
+ PageResult convertPage(PageResult page);
+
+ List convertVehicleList(List list);
+
+ ContractVehicleDO convertVehicle(ContractSaveReqVO.ContractVehicleSaveVO bean);
+
+ List convertServiceList(List list);
+
+ ContractVehicleServiceDO convertService(ContractVehicleServiceVO bean);
+
+ List convertAuthorizedList(List list);
+
+ ContractAuthorizedDO convertAuthorized(ContractAuthorizedVO bean);
+
+ List convertVehicleDetailList(List list);
+
+ ContractDetailRespVO.ContractVehicleDetailVO convertVehicleDetail(ContractVehicleDO bean);
+
+ List convertServiceVOList(List list);
+
+ List convertAuthorizedVOList(List list);
+
+ List convertAttachmentVOList(List list);
+
+ ContractAttachmentDO convertAttachment(ContractAttachmentVO bean);
+
+ List convertAttachmentList(List list);
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/customer/CustomerConvert.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/customer/CustomerConvert.java
index 07cb1fc..acd5150 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/customer/CustomerConvert.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/customer/CustomerConvert.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.asset.dal.dataobject.customer.CustomerDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
+import java.util.List;
+
/**
* 客户 Convert
*
@@ -23,4 +25,6 @@ public interface CustomerConvert {
PageResult convertPage(PageResult page);
+ List convertList(List list);
+
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/station/HydrogenStationConvert.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/station/HydrogenStationConvert.java
new file mode 100644
index 0000000..19ea54d
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/convert/station/HydrogenStationConvert.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.asset.convert.station;
+
+import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationRespVO;
+import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationSimpleRespVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 加氢站 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface HydrogenStationConvert {
+
+ HydrogenStationConvert INSTANCE = Mappers.getMapper(HydrogenStationConvert.class);
+
+ /**
+ * 转换为响应 VO
+ */
+ HydrogenStationRespVO convert(HydrogenStationDO bean);
+
+ /**
+ * 转换为精简响应 VO
+ */
+ HydrogenStationSimpleRespVO convertSimple(HydrogenStationDO bean);
+
+ /**
+ * 批量转换为精简响应 VO
+ */
+ List convertSimpleList(List list);
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractAttachmentDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractAttachmentDO.java
new file mode 100644
index 0000000..e3999aa
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractAttachmentDO.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 合同附件 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("asset_contract_attachment")
+@KeySequence("asset_contract_attachment_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ContractAttachmentDO extends BaseDO {
+
+ /**
+ * 主键ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 合同ID
+ */
+ private Long contractId;
+
+ /**
+ * 附件类型(1=合同原件 2=盖章合同)
+ */
+ private Integer attachmentType;
+
+ /**
+ * 文件ID(关联 infra_file)
+ */
+ private Long fileId;
+
+ /**
+ * 文件名称
+ */
+ private String fileName;
+
+ /**
+ * 文件URL
+ */
+ private String fileUrl;
+
+ /**
+ * 文件大小(字节)
+ */
+ private Long fileSize;
+
+ /**
+ * 上传时间
+ */
+ private LocalDateTime uploadTime;
+
+ /**
+ * 上传人
+ */
+ private String uploader;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractAuthorizedDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractAuthorizedDO.java
new file mode 100644
index 0000000..ea9d163
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractAuthorizedDO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 合同被授权人 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("asset_contract_authorized")
+@KeySequence("asset_contract_authorized_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ContractAuthorizedDO extends BaseDO {
+
+ /**
+ * 主键ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 合同ID
+ */
+ private Long contractId;
+
+ /**
+ * 姓名
+ */
+ private String name;
+
+ /**
+ * 电话
+ */
+ private String phone;
+
+ /**
+ * 身份证号
+ */
+ private String idCard;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractChangeHistoryDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractChangeHistoryDO.java
new file mode 100644
index 0000000..6936aa0
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractChangeHistoryDO.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 合同变更历史 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("asset_contract_change_history")
+@KeySequence("asset_contract_change_history_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ContractChangeHistoryDO extends BaseDO {
+
+ /**
+ * 主键ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 合同ID
+ */
+ private Long contractId;
+
+ /**
+ * 变更类型
+ */
+ private String changeType;
+
+ /**
+ * 变更内容
+ */
+ private String changeContent;
+
+ /**
+ * 操作人
+ */
+ private String operator;
+
+ /**
+ * 操作时间
+ */
+ private LocalDateTime operateTime;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractDO.java
new file mode 100644
index 0000000..a2d1946
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractDO.java
@@ -0,0 +1,199 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 车辆租赁合同 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("asset_contract")
+@KeySequence("asset_contract_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ContractDO extends BaseDO {
+
+ /**
+ * 主键ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 合同编码
+ */
+ private String contractCode;
+
+ /**
+ * 合同类型(1=试用 2=正式)
+ */
+ private Integer contractType;
+
+ /**
+ * 项目名称
+ */
+ private String projectName;
+
+ /**
+ * 生效日期
+ */
+ private LocalDate startDate;
+
+ /**
+ * 结束日期
+ */
+ private LocalDate endDate;
+
+ /**
+ * 付款方式
+ */
+ private String paymentMethod;
+
+ /**
+ * 付款周期
+ */
+ private String paymentCycle;
+
+ /**
+ * 签约公司(乙方)
+ */
+ private String signingCompany;
+
+ /**
+ * 交车省份
+ */
+ private String deliveryProvince;
+
+ /**
+ * 交车城市
+ */
+ private String deliveryCity;
+
+ /**
+ * 交车地点
+ */
+ private String deliveryLocation;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 客户ID
+ */
+ private Long customerId;
+
+ /**
+ * 客户名称(冗余)
+ */
+ private String customerName;
+
+ /**
+ * 是否三方合同
+ */
+ private Boolean thirdPartyEnabled;
+
+ /**
+ * 丙方客户ID
+ */
+ private Long thirdPartyCustomerId;
+
+ /**
+ * 丙方名称
+ */
+ private String thirdPartyName;
+
+ /**
+ * 业务部门ID
+ */
+ private Long businessDeptId;
+
+ /**
+ * 业务负责人ID
+ */
+ private Long businessManagerId;
+
+ /**
+ * 审批状态(0=草稿 1=审批中 2=审批通过 3=审批拒绝 4=已撤回)
+ */
+ private Integer approvalStatus;
+
+ /**
+ * BPM流程实例ID
+ */
+ private String bpmInstanceId;
+
+ /**
+ * 合同状态(0=草稿 1=待生效 2=进行中 3=已到期 4=已终止 5=已续签)
+ */
+ private Integer contractStatus;
+
+ /**
+ * 实际生效时间
+ */
+ private LocalDateTime effectiveTime;
+
+ /**
+ * 终止时间
+ */
+ private LocalDateTime terminateTime;
+
+ /**
+ * 终止原因
+ */
+ private String terminateReason;
+
+ /**
+ * 续签后的新合同ID
+ */
+ private Long renewedContractId;
+
+ /**
+ * 原合同ID(如果是续签合同)
+ */
+ private Long originalContractId;
+
+ /**
+ * 氢费承担方
+ */
+ private String hydrogenBearer;
+
+ /**
+ * 氢气付款方式
+ */
+ private String hydrogenPaymentMethod;
+
+ /**
+ * 氢气预付款
+ */
+ private BigDecimal hydrogenPrepay;
+
+ /**
+ * 退还车氢气单价
+ */
+ private BigDecimal hydrogenReturnPrice;
+
+ /**
+ * 账单计算方式
+ */
+ private String billingMethod;
+
+ /**
+ * 主车型
+ */
+ private String mainVehicleType;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractVehicleDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractVehicleDO.java
new file mode 100644
index 0000000..69afe40
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractVehicleDO.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 合同车辆租赁订单 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("asset_contract_vehicle")
+@KeySequence("asset_contract_vehicle_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ContractVehicleDO extends BaseDO {
+
+ /**
+ * 主键ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 合同ID
+ */
+ private Long contractId;
+
+ /**
+ * 车辆ID(关联 asset_vehicle_base)
+ */
+ private Long vehicleId;
+
+ /**
+ * 品牌
+ */
+ private String brand;
+
+ /**
+ * 型号
+ */
+ private String model;
+
+ /**
+ * 车牌号
+ */
+ private String plateNo;
+
+ /**
+ * VIN码
+ */
+ private String vin;
+
+ /**
+ * 月租金(元)
+ */
+ private BigDecimal monthRent;
+
+ /**
+ * 保证金(元)
+ */
+ private BigDecimal deposit;
+
+ /**
+ * 车辆状态(0=待交车 1=已交车 2=已退车)
+ */
+ private Integer vehicleStatus;
+
+ /**
+ * 实际交车时间
+ */
+ private LocalDateTime actualDeliveryTime;
+
+ /**
+ * 交车人
+ */
+ private String deliveryPerson;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractVehicleServiceDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractVehicleServiceDO.java
new file mode 100644
index 0000000..ff91502
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/contract/ContractVehicleServiceDO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 合同车辆服务项目 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("asset_contract_vehicle_service")
+@KeySequence("asset_contract_vehicle_service_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ContractVehicleServiceDO extends BaseDO {
+
+ /**
+ * 主键ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 合同车辆ID
+ */
+ private Long contractVehicleId;
+
+ /**
+ * 服务项目名称
+ */
+ private String serviceName;
+
+ /**
+ * 服务费用(元)
+ */
+ private BigDecimal serviceFee;
+
+ /**
+ * 生效日期
+ */
+ private LocalDate effectiveDate;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryOrderVehicleDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryOrderVehicleDO.java
new file mode 100644
index 0000000..3b5c30f
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryOrderVehicleDO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.delivery;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * 交车单车辆 DO
+ */
+@TableName("asset_delivery_order_vehicle")
+@KeySequence("asset_delivery_order_vehicle_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeliveryOrderVehicleDO extends BaseDO {
+
+ @TableId
+ private Long id;
+ private Long orderId;
+ private Long taskVehicleId;
+ private Long vehicleId;
+ private String plateNo;
+ private String vin;
+ private String brand;
+ private String model;
+ private Integer mileage;
+ private BigDecimal hydrogenLevel;
+ private BigDecimal batteryLevel;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryTaskDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryTaskDO.java
new file mode 100644
index 0000000..70572be
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryTaskDO.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.delivery;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDate;
+
+/**
+ * 交车任务 DO
+ */
+@TableName("asset_delivery_task")
+@KeySequence("asset_delivery_task_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeliveryTaskDO extends BaseDO {
+
+ @TableId
+ private Long id;
+ private String taskCode;
+ private Long contractId;
+ private String contractCode;
+ private String projectName;
+ private Long customerId;
+ private String customerName;
+ private LocalDate expectedDeliveryDateStart;
+ private LocalDate expectedDeliveryDateEnd;
+ private LocalDate billingStartDate;
+ private String deliveryProvince;
+ private String deliveryCity;
+ private String deliveryLocation;
+ private Integer vehicleCount;
+ /**
+ * 任务状态(0=激活 1=挂起)
+ */
+ private Integer taskStatus;
+ /**
+ * 交车状态(0=未交车 1=已交车)
+ */
+ private Integer deliveryStatus;
+ private Boolean needReturn;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryTaskVehicleDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryTaskVehicleDO.java
new file mode 100644
index 0000000..9321acb
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/delivery/DeliveryTaskVehicleDO.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.delivery;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 交车任务车辆 DO
+ */
+@TableName("asset_delivery_task_vehicle")
+@KeySequence("asset_delivery_task_vehicle_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeliveryTaskVehicleDO extends BaseDO {
+
+ @TableId
+ private Long id;
+ private Long taskId;
+ private Long contractVehicleId;
+ private Long vehicleId;
+ private String plateNo;
+ private String vin;
+ private String brand;
+ private String model;
+ private BigDecimal monthRent;
+ private BigDecimal deposit;
+ private LocalDateTime actualDeliveryDate;
+ private String deliveryPerson;
+ private Boolean isDelivered;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/station/HydrogenStationDO.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/station/HydrogenStationDO.java
new file mode 100644
index 0000000..bf952f2
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/dataobject/station/HydrogenStationDO.java
@@ -0,0 +1,128 @@
+package cn.iocoder.yudao.module.asset.dal.dataobject.station;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+
+/**
+ * 加氢站 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("asset_hydrogen_station")
+@KeySequence("asset_hydrogen_station_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HydrogenStationDO extends BaseDO {
+
+ /**
+ * 主键
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 站点名称
+ */
+ private String name;
+
+ /**
+ * 简称
+ */
+ private String shortName;
+
+ /**
+ * 站点编码
+ */
+ private String stationNo;
+
+ /**
+ * 所属城市
+ */
+ private String city;
+
+ /**
+ * 站点地址
+ */
+ private String address;
+
+ /**
+ * 经度
+ */
+ private String longitude;
+
+ /**
+ * 纬度
+ */
+ private String latitude;
+
+ /**
+ * 联系人
+ */
+ private String contact;
+
+ /**
+ * 联系电话
+ */
+ private String phone;
+
+ /**
+ * 站点类型(字典)
+ */
+ private Integer stationType;
+
+ /**
+ * 合作类型(0=合作 1=非合作)
+ */
+ private Integer cooperationType;
+
+ /**
+ * 是否自动扣款(1=是 0=否)
+ */
+ private Boolean autoDeduct;
+
+ /**
+ * 是否需要预约(1=是 0=否)
+ */
+ private Boolean bookingRequired;
+
+ /**
+ * 站点状态(0=停用 1=启用)
+ */
+ private Integer stationStatus;
+
+ /**
+ * 开始营业时间
+ */
+ private LocalTime startBusiness;
+
+ /**
+ * 结束营业时间
+ */
+ private LocalTime endBusiness;
+
+ /**
+ * 结算方式(字典)
+ */
+ private Integer billingMethod;
+
+ /**
+ * 合作期限
+ */
+ private LocalDate cooperationTerm;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractAttachmentMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractAttachmentMapper.java
new file mode 100644
index 0000000..2a14fc8
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractAttachmentMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractAttachmentDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 合同附件 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ContractAttachmentMapper extends BaseMapperX {
+
+ default List selectListByContractId(Long contractId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ContractAttachmentDO::getContractId, contractId)
+ .orderByAsc(ContractAttachmentDO::getId));
+ }
+
+ default int deleteByContractId(Long contractId) {
+ return delete(new LambdaQueryWrapperX()
+ .eq(ContractAttachmentDO::getContractId, contractId));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractAuthorizedMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractAuthorizedMapper.java
new file mode 100644
index 0000000..4edd95b
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractAuthorizedMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractAuthorizedDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 合同被授权人 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ContractAuthorizedMapper extends BaseMapperX {
+
+ default List selectListByContractId(Long contractId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ContractAuthorizedDO::getContractId, contractId)
+ .orderByAsc(ContractAuthorizedDO::getId));
+ }
+
+ default int deleteByContractId(Long contractId) {
+ return delete(new LambdaQueryWrapperX()
+ .eq(ContractAuthorizedDO::getContractId, contractId));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractChangeHistoryMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractChangeHistoryMapper.java
new file mode 100644
index 0000000..64c1ac8
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractChangeHistoryMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractChangeHistoryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 合同变更历史 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ContractChangeHistoryMapper extends BaseMapperX {
+
+ default List selectListByContractId(Long contractId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ContractChangeHistoryDO::getContractId, contractId)
+ .orderByDesc(ContractChangeHistoryDO::getOperateTime));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractMapper.java
new file mode 100644
index 0000000..85e4bca
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractMapper.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.contract;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.controller.admin.contract.vo.ContractPageReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.time.LocalDate;
+
+/**
+ * 车辆租赁合同 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ContractMapper extends BaseMapperX {
+
+ default PageResult selectPage(ContractPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(ContractDO::getContractCode, reqVO.getContractCode())
+ .eqIfPresent(ContractDO::getContractType, reqVO.getContractType())
+ .likeIfPresent(ContractDO::getProjectName, reqVO.getProjectName())
+ .eqIfPresent(ContractDO::getCustomerId, reqVO.getCustomerId())
+ .likeIfPresent(ContractDO::getCustomerName, reqVO.getCustomerName())
+ .eqIfPresent(ContractDO::getApprovalStatus, reqVO.getApprovalStatus())
+ .eqIfPresent(ContractDO::getContractStatus, reqVO.getContractStatus())
+ .betweenIfPresent(ContractDO::getStartDate, reqVO.getStartDate())
+ .betweenIfPresent(ContractDO::getEndDate, reqVO.getEndDate())
+ .eqIfPresent(ContractDO::getBusinessDeptId, reqVO.getBusinessDeptId())
+ .eqIfPresent(ContractDO::getSigningCompany, reqVO.getSigningCompany())
+ .eqIfPresent(ContractDO::getBusinessManagerId, reqVO.getBusinessManagerId())
+ .likeIfPresent(ContractDO::getCreator, reqVO.getCreator())
+ .orderByDesc(ContractDO::getId));
+ }
+
+ @Select("SELECT contract_code FROM asset_contract WHERE contract_code LIKE CONCAT(#{prefix}, '%') AND deleted = 0 ORDER BY contract_code DESC LIMIT 1")
+ String selectMaxContractCodeByPrefix(@Param("prefix") String prefix);
+
+ /**
+ * 检查车辆在指定时间段内是否有有效合同
+ */
+ @Select("SELECT COUNT(*) FROM asset_contract c " +
+ "INNER JOIN asset_contract_vehicle cv ON c.id = cv.contract_id " +
+ "WHERE cv.vehicle_id = #{vehicleId} " +
+ "AND c.contract_status IN (1, 2) " +
+ "AND c.deleted = 0 AND cv.deleted = 0 " +
+ "AND NOT (c.end_date < #{startDate} OR c.start_date > #{endDate})")
+ int countActiveContractsByVehicleAndDateRange(@Param("vehicleId") Long vehicleId,
+ @Param("startDate") LocalDate startDate,
+ @Param("endDate") LocalDate endDate);
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractVehicleMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractVehicleMapper.java
new file mode 100644
index 0000000..4ff23be
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractVehicleMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractVehicleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 合同车辆租赁订单 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ContractVehicleMapper extends BaseMapperX {
+
+ default List selectListByContractId(Long contractId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ContractVehicleDO::getContractId, contractId)
+ .orderByAsc(ContractVehicleDO::getId));
+ }
+
+ default int deleteByContractId(Long contractId) {
+ return delete(new LambdaQueryWrapperX()
+ .eq(ContractVehicleDO::getContractId, contractId));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractVehicleServiceMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractVehicleServiceMapper.java
new file mode 100644
index 0000000..9e64c11
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/contract/ContractVehicleServiceMapper.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractVehicleServiceDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 合同车辆服务项目 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ContractVehicleServiceMapper extends BaseMapperX {
+
+ default List selectListByContractVehicleId(Long contractVehicleId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ContractVehicleServiceDO::getContractVehicleId, contractVehicleId)
+ .orderByAsc(ContractVehicleServiceDO::getId));
+ }
+
+ default int deleteByContractVehicleId(Long contractVehicleId) {
+ return delete(new LambdaQueryWrapperX()
+ .eq(ContractVehicleServiceDO::getContractVehicleId, contractVehicleId));
+ }
+
+ default int deleteByContractVehicleIds(List contractVehicleIds) {
+ return delete(new LambdaQueryWrapperX()
+ .in(ContractVehicleServiceDO::getContractVehicleId, contractVehicleIds));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryOrderMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryOrderMapper.java
new file mode 100644
index 0000000..36706a2
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryOrderMapper.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.delivery;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryOrderPageReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryOrderDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface DeliveryOrderMapper extends BaseMapperX {
+
+ default PageResult selectPage(DeliveryOrderPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(DeliveryOrderDO::getOrderCode, reqVO.getOrderCode())
+ .likeIfPresent(DeliveryOrderDO::getTaskCode, reqVO.getTaskCode())
+ .likeIfPresent(DeliveryOrderDO::getContractCode, reqVO.getContractCode())
+ .likeIfPresent(DeliveryOrderDO::getProjectName, reqVO.getProjectName())
+ .likeIfPresent(DeliveryOrderDO::getCustomerName, reqVO.getCustomerName())
+ .likeIfPresent(DeliveryOrderDO::getDeliveryPerson, reqVO.getDeliveryPerson())
+ .eqIfPresent(DeliveryOrderDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(DeliveryOrderDO::getDeliveryDate, reqVO.getDeliveryDate())
+ .betweenIfPresent(DeliveryOrderDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(DeliveryOrderDO::getId));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryOrderVehicleMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryOrderVehicleMapper.java
new file mode 100644
index 0000000..35f0bc9
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryOrderVehicleMapper.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.delivery;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryOrderVehicleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface DeliveryOrderVehicleMapper extends BaseMapperX {
+
+ default List selectListByOrderId(Long orderId) {
+ return selectList(DeliveryOrderVehicleDO::getOrderId, orderId);
+ }
+
+ default void deleteByOrderId(Long orderId) {
+ delete(DeliveryOrderVehicleDO::getOrderId, orderId);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryTaskMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryTaskMapper.java
new file mode 100644
index 0000000..da4512c
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryTaskMapper.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.delivery;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskPageReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryTaskDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface DeliveryTaskMapper extends BaseMapperX {
+
+ default PageResult selectPage(DeliveryTaskPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(DeliveryTaskDO::getTaskCode, reqVO.getTaskCode())
+ .likeIfPresent(DeliveryTaskDO::getContractCode, reqVO.getContractCode())
+ .likeIfPresent(DeliveryTaskDO::getProjectName, reqVO.getProjectName())
+ .likeIfPresent(DeliveryTaskDO::getCustomerName, reqVO.getCustomerName())
+ .eqIfPresent(DeliveryTaskDO::getTaskStatus, reqVO.getTaskStatus())
+ .eqIfPresent(DeliveryTaskDO::getDeliveryStatus, reqVO.getDeliveryStatus())
+ .betweenIfPresent(DeliveryTaskDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(DeliveryTaskDO::getId));
+ }
+
+ default long selectCountByContractId(Long contractId) {
+ return selectCount(DeliveryTaskDO::getContractId, contractId);
+ }
+
+ default List selectListByFilters(DeliveryTaskPageReqVO reqVO) {
+ return selectList(new LambdaQueryWrapperX()
+ .likeIfPresent(DeliveryTaskDO::getTaskCode, reqVO.getTaskCode())
+ .likeIfPresent(DeliveryTaskDO::getContractCode, reqVO.getContractCode())
+ .likeIfPresent(DeliveryTaskDO::getProjectName, reqVO.getProjectName())
+ .likeIfPresent(DeliveryTaskDO::getCustomerName, reqVO.getCustomerName())
+ .eqIfPresent(DeliveryTaskDO::getTaskStatus, reqVO.getTaskStatus())
+ .eqIfPresent(DeliveryTaskDO::getDeliveryStatus, reqVO.getDeliveryStatus())
+ .betweenIfPresent(DeliveryTaskDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(DeliveryTaskDO::getId));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryTaskVehicleMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryTaskVehicleMapper.java
new file mode 100644
index 0000000..d9ab106
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/delivery/DeliveryTaskVehicleMapper.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.delivery;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryTaskVehicleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface DeliveryTaskVehicleMapper extends BaseMapperX {
+
+ default List selectListByTaskId(Long taskId) {
+ return selectList(DeliveryTaskVehicleDO::getTaskId, taskId);
+ }
+
+ default void deleteByTaskId(Long taskId) {
+ delete(DeliveryTaskVehicleDO::getTaskId, taskId);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/prepare/VehiclePrepareMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/prepare/VehiclePrepareMapper.java
new file mode 100644
index 0000000..9da985f
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/prepare/VehiclePrepareMapper.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.prepare;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.controller.admin.prepare.vo.VehiclePreparePageReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.prepare.VehiclePrepareDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface VehiclePrepareMapper extends BaseMapperX {
+
+ default PageResult selectPage(VehiclePreparePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(VehiclePrepareDO::getPlateNo, reqVO.getPlateNo())
+ .likeIfPresent(VehiclePrepareDO::getVin, reqVO.getVin())
+ .likeIfPresent(VehiclePrepareDO::getContractCode, reqVO.getContractCode())
+ .eqIfPresent(VehiclePrepareDO::getVehicleType, reqVO.getVehicleType())
+ .likeIfPresent(VehiclePrepareDO::getBrand, reqVO.getBrand())
+ .likeIfPresent(VehiclePrepareDO::getParkingLot, reqVO.getParkingLot())
+ .eqIfPresent(VehiclePrepareDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(VehiclePrepareDO::getCompleteTime, reqVO.getCompleteTime())
+ .betweenIfPresent(VehiclePrepareDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(VehiclePrepareDO::getId));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/returnorder/ReturnOrderMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/returnorder/ReturnOrderMapper.java
new file mode 100644
index 0000000..7d3b9ab
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/returnorder/ReturnOrderMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.returnorder;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.controller.admin.returnorder.vo.ReturnOrderPageReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.returnorder.ReturnOrderDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ReturnOrderMapper extends BaseMapperX {
+
+ default PageResult selectPage(ReturnOrderPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(ReturnOrderDO::getOrderCode, reqVO.getOrderCode())
+ .likeIfPresent(ReturnOrderDO::getContractCode, reqVO.getContractCode())
+ .likeIfPresent(ReturnOrderDO::getCustomerName, reqVO.getCustomerName())
+ .eqIfPresent(ReturnOrderDO::getStatus, reqVO.getStatus())
+ .eqIfPresent(ReturnOrderDO::getReturnReason, reqVO.getReturnReason())
+ .betweenIfPresent(ReturnOrderDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(ReturnOrderDO::getId));
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/returnorder/ReturnOrderVehicleMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/returnorder/ReturnOrderVehicleMapper.java
new file mode 100644
index 0000000..b71b928
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/returnorder/ReturnOrderVehicleMapper.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.returnorder;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.asset.dal.dataobject.returnorder.ReturnOrderVehicleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ReturnOrderVehicleMapper extends BaseMapperX {
+
+ default List selectListByReturnOrderId(Long returnOrderId) {
+ return selectList(ReturnOrderVehicleDO::getReturnOrderId, returnOrderId);
+ }
+
+ default void deleteByReturnOrderId(Long returnOrderId) {
+ delete(ReturnOrderVehicleDO::getReturnOrderId, returnOrderId);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/station/HydrogenStationMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/station/HydrogenStationMapper.java
new file mode 100644
index 0000000..89aa481
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/station/HydrogenStationMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.asset.dal.mysql.station;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationPageReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 加氢站 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface HydrogenStationMapper extends BaseMapperX {
+
+ default PageResult selectPage(HydrogenStationPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(HydrogenStationDO::getName, reqVO.getName())
+ .likeIfPresent(HydrogenStationDO::getStationNo, reqVO.getStationNo())
+ .likeIfPresent(HydrogenStationDO::getCity, reqVO.getCity())
+ .eqIfPresent(HydrogenStationDO::getCooperationType, reqVO.getCooperationType())
+ .eqIfPresent(HydrogenStationDO::getStationStatus, reqVO.getStationStatus())
+ .betweenIfPresent(HydrogenStationDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(HydrogenStationDO::getId));
+ }
+
+ default List selectSimpleList() {
+ return selectList();
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/vehicle/VehicleStatusMapper.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/vehicle/VehicleStatusMapper.java
index a908239..f039ba1 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/vehicle/VehicleStatusMapper.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/dal/mysql/vehicle/VehicleStatusMapper.java
@@ -20,4 +20,9 @@ public interface VehicleStatusMapper extends BaseMapperX {
.in(VehicleStatusDO::getVehicleId, vehicleIds));
}
+ default VehicleStatusDO selectByVehicleId(Long vehicleId) {
+ return selectOne(new LambdaQueryWrapperX()
+ .eq(VehicleStatusDO::getVehicleId, vehicleId));
+ }
+
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/enums/ErrorCodeConstants.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/enums/ErrorCodeConstants.java
index eb49df4..7e44a80 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/enums/ErrorCodeConstants.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/enums/ErrorCodeConstants.java
@@ -38,7 +38,8 @@ public interface ErrorCodeConstants {
ErrorCode CONTRACT_STATUS_NOT_ALLOW_ADD_VEHICLE = new ErrorCode(1_008_005_011, "当前合同状态不允许新增车辆");
ErrorCode CONTRACT_TYPE_NOT_TRIAL = new ErrorCode(1_008_005_012, "只有试用合同才能转正式");
- // ========== 车辆登记 1-008-006-000 ==========
+ // ========== 车辆基础信息 1-008-006-000 ==========
+ ErrorCode VEHICLE_NOT_EXISTS = new ErrorCode(1_008_006_001, "车辆不存在");
ErrorCode VEHICLE_REGISTRATION_NOT_EXISTS = new ErrorCode(1_008_006_000, "车辆登记不存在");
// ========== 备车管理 1-008-007-000 ==========
@@ -78,4 +79,8 @@ public interface ErrorCodeConstants {
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, "当前状态不允许确认换回");
+ // ========== 加氢站管理 1-008-013-000 ==========
+ ErrorCode HYDROGEN_STATION_NOT_EXISTS = new ErrorCode(1_008_013_000, "加氢站不存在");
+ ErrorCode HYDROGEN_STATION_END_BUSINESS_BEFORE_START_BUSINESS = new ErrorCode(1_008_013_001, "结束营业时间不能早于开始营业时间");
+
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/job/ContractStatusJob.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/job/ContractStatusJob.java
new file mode 100644
index 0000000..75192c4
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/job/ContractStatusJob.java
@@ -0,0 +1,106 @@
+package cn.iocoder.yudao.module.asset.job;
+
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractDO;
+import cn.iocoder.yudao.module.asset.dal.mysql.contract.ContractMapper;
+import cn.iocoder.yudao.module.asset.enums.contract.ContractStatusEnum;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import jakarta.annotation.Resource;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 合同状态自动流转定时任务
+ *
+ * 功能:
+ * 1. 将待生效的合同自动转为进行中(当前日期 >= 生效日期)
+ * 2. 将进行中的合同自动转为已到期(当前日期 > 结束日期)
+ *
+ * @author 芋道源码
+ */
+@Component
+@Slf4j
+public class ContractStatusJob {
+
+ @Resource
+ private ContractMapper contractMapper;
+
+ @XxlJob("contractStatusJob")
+ @TenantJob
+ public void execute() {
+ log.info("[contractStatusJob] 开始执行合同状态自动流转");
+
+ try {
+ // 1. 处理待生效 -> 进行中
+ int activatedCount = activatePendingContracts();
+ log.info("[contractStatusJob] 激活待生效合同数量: {}", activatedCount);
+
+ // 2. 处理进行中 -> 已到期
+ int expiredCount = expireActiveContracts();
+ log.info("[contractStatusJob] 到期合同数量: {}", expiredCount);
+
+ log.info("[contractStatusJob] 执行完成,激活: {}, 到期: {}", activatedCount, expiredCount);
+ } catch (Exception e) {
+ log.error("[contractStatusJob] 执行失败", e);
+ }
+ }
+
+ /**
+ * 激活待生效的合同
+ */
+ private int activatePendingContracts() {
+ LocalDate today = LocalDate.now();
+
+ // 查询待生效且生效日期 <= 今天的合同
+ List contracts = contractMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(ContractDO::getContractStatus, ContractStatusEnum.PENDING.getStatus())
+ .le(ContractDO::getStartDate, today)
+ );
+
+ int count = 0;
+ for (ContractDO contract : contracts) {
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(contract.getId());
+ updateObj.setContractStatus(ContractStatusEnum.IN_PROGRESS.getStatus());
+ updateObj.setEffectiveTime(LocalDateTime.now());
+ contractMapper.updateById(updateObj);
+ count++;
+ log.info("[contractStatusJob] 合同 {} 已激活", contract.getContractCode());
+ }
+
+ return count;
+ }
+
+ /**
+ * 将进行中的合同标记为已到期
+ */
+ private int expireActiveContracts() {
+ LocalDate today = LocalDate.now();
+
+ // 查询进行中且结束日期 < 今天的合同
+ List contracts = contractMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(ContractDO::getContractStatus, ContractStatusEnum.IN_PROGRESS.getStatus())
+ .lt(ContractDO::getEndDate, today)
+ );
+
+ int count = 0;
+ for (ContractDO contract : contracts) {
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(contract.getId());
+ updateObj.setContractStatus(ContractStatusEnum.EXPIRED.getStatus());
+ contractMapper.updateById(updateObj);
+ count++;
+ log.info("[contractStatusJob] 合同 {} 已到期", contract.getContractCode());
+ }
+
+ return count;
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/ContractService.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/ContractService.java
new file mode 100644
index 0000000..f699d5f
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/ContractService.java
@@ -0,0 +1,145 @@
+package cn.iocoder.yudao.module.asset.service.contract;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.contract.vo.*;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractDO;
+
+import jakarta.validation.Valid;
+import java.util.List;
+
+/**
+ * 车辆租赁合同 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface ContractService {
+
+ /**
+ * 创建合同
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createContract(@Valid ContractSaveReqVO createReqVO);
+
+ /**
+ * 更新合同
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateContract(@Valid ContractSaveReqVO updateReqVO);
+
+ /**
+ * 删除合同
+ *
+ * @param id 编号
+ */
+ void deleteContract(Long id);
+
+ /**
+ * 获得合同
+ *
+ * @param id 编号
+ * @return 合同
+ */
+ ContractDO getContract(Long id);
+
+ /**
+ * 获得合同分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 合同分页
+ */
+ PageResult getContractPage(ContractPageReqVO pageReqVO);
+
+ /**
+ * 提交合同审批
+ *
+ * @param id 合同ID
+ * @return 流程实例ID
+ */
+ String submitContractApproval(Long id);
+
+ /**
+ * 更新合同审批状态
+ *
+ * @param id 合同ID
+ * @param status 审批状态
+ */
+ void updateContractApprovalStatus(Long id, Integer status);
+
+ /**
+ * 撤回合同审批
+ *
+ * @param id 合同ID
+ */
+ void withdrawContractApproval(Long id);
+
+ /**
+ * 终止合同
+ *
+ * @param id 合同ID
+ * @param reason 终止原因
+ */
+ void terminateContract(Long id, String reason);
+
+ /**
+ * 续签合同
+ *
+ * @param id 原合同ID
+ * @param newContractReqVO 新合同信息
+ * @return 新合同ID
+ */
+ Long renewContract(Long id, ContractSaveReqVO newContractReqVO);
+
+ /**
+ * 获取合同详情(包含关联数据)
+ *
+ * @param id 合同ID
+ * @return 合同详情
+ */
+ ContractDetailRespVO getContractDetail(Long id);
+
+ /**
+ * 获得合同精简列表(用于下拉选择)
+ *
+ * @return 合同列表
+ */
+ List getContractSimpleList();
+
+ /**
+ * 变更为三方合同
+ *
+ * @param id 原合同ID
+ * @param newContractReqVO 新合同信息
+ * @return 新合同ID
+ */
+ Long convertToThirdParty(Long id, @Valid ContractSaveReqVO newContractReqVO);
+
+ /**
+ * 试用合同转正式
+ *
+ * @param id 原合同ID
+ * @param newContractReqVO 新合同信息
+ * @return 新合同ID
+ */
+ Long convertToFormal(Long id, @Valid ContractSaveReqVO newContractReqVO);
+
+ /**
+ * 往现有合同追加车辆
+ *
+ * @param id 合同ID
+ * @param vehicles 车辆列表
+ */
+ void addVehiclesToContract(Long id, List vehicles);
+
+ /**
+ * 上传盖章合同附件
+ *
+ * @param id 合同ID
+ * @param fileUrl 文件URL
+ * @param fileName 文件名
+ */
+ void uploadSealedContract(Long id, String fileUrl, String fileName);
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/ContractServiceImpl.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/ContractServiceImpl.java
new file mode 100644
index 0000000..08b3a9e
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/ContractServiceImpl.java
@@ -0,0 +1,609 @@
+package cn.iocoder.yudao.module.asset.service.contract;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.contract.vo.*;
+import cn.iocoder.yudao.module.asset.convert.contract.ContractConvert;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.*;
+import cn.iocoder.yudao.module.asset.dal.mysql.contract.*;
+import cn.iocoder.yudao.module.asset.enums.contract.ContractApprovalStatusEnum;
+import cn.iocoder.yudao.module.asset.enums.contract.ContractStatusEnum;
+import cn.iocoder.yudao.module.asset.service.contract.listener.ContractBpmListener;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCancelReqDTO;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import jakarta.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.asset.enums.ErrorCodeConstants.*;
+
+/**
+ * 车辆租赁合同 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+@Slf4j
+public class ContractServiceImpl implements ContractService {
+
+ @Resource
+ private ContractMapper contractMapper;
+
+ @Resource
+ private ContractVehicleMapper contractVehicleMapper;
+
+ @Resource
+ private ContractVehicleServiceMapper contractVehicleServiceMapper;
+
+ @Resource
+ private ContractAuthorizedMapper contractAuthorizedMapper;
+
+ @Resource
+ private ContractAttachmentMapper contractAttachmentMapper;
+
+ @Resource
+ private ContractChangeHistoryMapper contractChangeHistoryMapper;
+
+ @Resource
+ private BpmProcessInstanceApi bpmProcessInstanceApi;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createContract(ContractSaveReqVO createReqVO) {
+ // 校验合同日期
+ validateContractDate(createReqVO.getStartDate(), createReqVO.getEndDate());
+
+ // 转换并插入合同主表
+ ContractDO contract = ContractConvert.INSTANCE.convert(createReqVO);
+
+ // 生成合同编码
+ if (contract.getContractCode() == null || contract.getContractCode().isEmpty()) {
+ contract.setContractCode(generateContractCode());
+ }
+
+ // 设置初始状态
+ contract.setApprovalStatus(ContractApprovalStatusEnum.DRAFT.getStatus());
+ contract.setContractStatus(0); // 草稿
+
+ contractMapper.insert(contract);
+
+ // 保存车辆订单
+ saveContractVehicles(contract.getId(), createReqVO.getVehicles());
+
+ // 保存被授权人
+ saveContractAuthorized(contract.getId(), createReqVO.getAuthorizedPersons());
+
+ // 记录变更历史
+ saveChangeHistory(contract.getId(), "创建合同", "创建合同:" + contract.getContractCode());
+
+ return contract.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateContract(ContractSaveReqVO updateReqVO) {
+ // 校验存在
+ ContractDO existContract = validateContractExists(updateReqVO.getId());
+
+ // 校验状态:只有草稿、审批拒绝、已撤回状态才能修改
+ if (!canEdit(existContract.getApprovalStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_UPDATE);
+ }
+
+ // 校验合同日期
+ validateContractDate(updateReqVO.getStartDate(), updateReqVO.getEndDate());
+
+ // 转换并更新合同主表
+ ContractDO updateObj = ContractConvert.INSTANCE.convert(updateReqVO);
+ contractMapper.updateById(updateObj);
+
+ // 更新车辆订单:先删除旧的,再插入新的
+ List oldVehicles = contractVehicleMapper.selectListByContractId(updateReqVO.getId());
+ List oldVehicleIds = oldVehicles.stream().map(ContractVehicleDO::getId).collect(Collectors.toList());
+ if (!oldVehicleIds.isEmpty()) {
+ contractVehicleServiceMapper.deleteByContractVehicleIds(oldVehicleIds);
+ }
+ contractVehicleMapper.deleteByContractId(updateReqVO.getId());
+ saveContractVehicles(updateReqVO.getId(), updateReqVO.getVehicles());
+
+ // 更新被授权人:先删除旧的,再插入新的
+ contractAuthorizedMapper.deleteByContractId(updateReqVO.getId());
+ saveContractAuthorized(updateReqVO.getId(), updateReqVO.getAuthorizedPersons());
+
+ // 记录变更历史
+ saveChangeHistory(updateReqVO.getId(), "更新合同", "更新合同信息");
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteContract(Long id) {
+ // 校验存在
+ ContractDO contract = validateContractExists(id);
+
+ // 校验状态:只有草稿、审批拒绝、已撤回状态才能删除
+ if (!canEdit(contract.getApprovalStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_DELETE);
+ }
+
+ // 删除合同主表
+ contractMapper.deleteById(id);
+
+ // 删除关联数据
+ List vehicles = contractVehicleMapper.selectListByContractId(id);
+ List vehicleIds = vehicles.stream().map(ContractVehicleDO::getId).collect(Collectors.toList());
+ if (!vehicleIds.isEmpty()) {
+ contractVehicleServiceMapper.deleteByContractVehicleIds(vehicleIds);
+ }
+ contractVehicleMapper.deleteByContractId(id);
+ contractAuthorizedMapper.deleteByContractId(id);
+ contractAttachmentMapper.deleteByContractId(id);
+
+ // 记录变更历史
+ saveChangeHistory(id, "删除合同", "删除合同:" + contract.getContractCode());
+ }
+
+ @Override
+ public ContractDO getContract(Long id) {
+ return contractMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getContractPage(ContractPageReqVO pageReqVO) {
+ return contractMapper.selectPage(pageReqVO);
+ }
+
+ // ==================== 私有方法 ====================
+
+ /**
+ * 校验合同是否存在
+ */
+ private ContractDO validateContractExists(Long id) {
+ ContractDO contract = contractMapper.selectById(id);
+ if (contract == null) {
+ throw exception(CONTRACT_NOT_EXISTS);
+ }
+ return contract;
+ }
+
+ /**
+ * 校验合同日期
+ */
+ private void validateContractDate(java.time.LocalDate startDate, java.time.LocalDate endDate) {
+ if (startDate != null && endDate != null && endDate.isBefore(startDate)) {
+ throw exception(CONTRACT_END_DATE_BEFORE_START_DATE);
+ }
+ }
+
+ /**
+ * 判断是否可以编辑
+ */
+ private boolean canEdit(Integer approvalStatus) {
+ return ContractApprovalStatusEnum.DRAFT.getStatus().equals(approvalStatus)
+ || ContractApprovalStatusEnum.REJECTED.getStatus().equals(approvalStatus)
+ || ContractApprovalStatusEnum.WITHDRAWN.getStatus().equals(approvalStatus);
+ }
+
+ /**
+ * 生成合同编码
+ */
+ private String generateContractCode() {
+ // 格式:HT-YYYY-NNNN
+ String year = String.valueOf(java.time.LocalDate.now().getYear());
+
+ // 查询当年最大序号
+ String prefix = "HT-" + year + "-";
+ String maxCode = contractMapper.selectMaxContractCodeByPrefix(prefix);
+
+ int nextSeq = 1;
+ if (maxCode != null && maxCode.startsWith(prefix)) {
+ try {
+ String seqStr = maxCode.substring(prefix.length());
+ nextSeq = Integer.parseInt(seqStr) + 1;
+ } catch (Exception e) {
+ log.warn("解析合同编码失败: {}", maxCode, e);
+ }
+ }
+
+ return prefix + String.format("%04d", nextSeq);
+ }
+
+ /**
+ * 保存车辆订单
+ */
+ private void saveContractVehicles(Long contractId, List vehicles) {
+ if (vehicles == null || vehicles.isEmpty()) {
+ return;
+ }
+
+ // 获取合同信息用于校验
+ ContractDO contract = contractMapper.selectById(contractId);
+
+ for (ContractSaveReqVO.ContractVehicleSaveVO vehicleVO : vehicles) {
+ // 校验车辆时间冲突
+ if (vehicleVO.getVehicleId() != null && contract != null) {
+ validateVehicleTimeConflict(vehicleVO.getVehicleId(), contract.getStartDate(), contract.getEndDate(), contractId);
+ }
+
+ // 保存车辆订单
+ ContractVehicleDO vehicle = ContractConvert.INSTANCE.convertVehicle(vehicleVO);
+ vehicle.setContractId(contractId);
+ contractVehicleMapper.insert(vehicle);
+
+ // 保存服务项目
+ if (vehicleVO.getServices() != null && !vehicleVO.getServices().isEmpty()) {
+ List services = ContractConvert.INSTANCE.convertServiceList(vehicleVO.getServices());
+ for (ContractVehicleServiceDO service : services) {
+ service.setContractVehicleId(vehicle.getId());
+ contractVehicleServiceMapper.insert(service);
+ }
+ }
+ }
+ }
+
+ /**
+ * 校验车辆时间冲突
+ */
+ private void validateVehicleTimeConflict(Long vehicleId, java.time.LocalDate startDate, java.time.LocalDate endDate, Long excludeContractId) {
+ int count = contractMapper.countActiveContractsByVehicleAndDateRange(vehicleId, startDate, endDate);
+ if (count > 0) {
+ throw exception(CONTRACT_VEHICLE_TIME_CONFLICT);
+ }
+ }
+
+ /**
+ * 保存被授权人
+ */
+ private void saveContractAuthorized(Long contractId, List authorizedPersons) {
+ if (authorizedPersons == null || authorizedPersons.isEmpty()) {
+ return;
+ }
+
+ List authorizedList = ContractConvert.INSTANCE.convertAuthorizedList(authorizedPersons);
+ for (ContractAuthorizedDO authorized : authorizedList) {
+ authorized.setContractId(contractId);
+ contractAuthorizedMapper.insert(authorized);
+ }
+ }
+
+ /**
+ * 保存变更历史
+ */
+ private void saveChangeHistory(Long contractId, String changeType, String changeContent) {
+ ContractChangeHistoryDO history = new ContractChangeHistoryDO();
+ history.setContractId(contractId);
+ history.setChangeType(changeType);
+ history.setChangeContent(changeContent);
+
+ // 获取当前登录用户
+ Long userId = SecurityFrameworkUtils.getLoginUserId();
+ history.setOperator(userId != null ? String.valueOf(userId) : "system");
+ history.setOperateTime(LocalDateTime.now());
+
+ contractChangeHistoryMapper.insert(history);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String submitContractApproval(Long id) {
+ // 校验存在
+ ContractDO contract = validateContractExists(id);
+
+ // 校验状态:只有草稿、审批拒绝、已撤回状态才能提交审批
+ if (!canEdit(contract.getApprovalStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_SUBMIT);
+ }
+
+ // 创建流程变量
+ Map variables = new HashMap<>();
+ variables.put("contractCode", contract.getContractCode());
+ variables.put("projectName", contract.getProjectName());
+ variables.put("customerName", contract.getCustomerName());
+ variables.put("startDate", contract.getStartDate());
+ variables.put("endDate", contract.getEndDate());
+
+ // 创建流程实例
+ BpmProcessInstanceCreateReqDTO reqDTO = new BpmProcessInstanceCreateReqDTO();
+ reqDTO.setProcessDefinitionKey(ContractBpmListener.PROCESS_KEY);
+ reqDTO.setBusinessKey(String.valueOf(id));
+ reqDTO.setVariables(variables);
+
+ Long userId = SecurityFrameworkUtils.getLoginUserId();
+ String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, reqDTO).getData();
+
+ // 更新合同状态
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(id);
+ updateObj.setApprovalStatus(ContractApprovalStatusEnum.APPROVING.getStatus());
+ updateObj.setBpmInstanceId(processInstanceId);
+ contractMapper.updateById(updateObj);
+
+ // 记录变更历史
+ saveChangeHistory(id, "提交审批", "提交合同审批,流程实例ID:" + processInstanceId);
+
+ return processInstanceId;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateContractApprovalStatus(Long id, Integer status) {
+ // 校验存在
+ ContractDO contract = validateContractExists(id);
+
+ // 更新审批状态
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(id);
+
+ // 根据 BPM 状态映射到合同审批状态
+ if (BpmProcessInstanceStatusEnum.RUNNING.getStatus().equals(status)) {
+ updateObj.setApprovalStatus(ContractApprovalStatusEnum.APPROVING.getStatus());
+ } else if (BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(status)) {
+ updateObj.setApprovalStatus(ContractApprovalStatusEnum.APPROVED.getStatus());
+ updateObj.setContractStatus(ContractStatusEnum.PENDING.getStatus()); // 待生效
+ updateObj.setEffectiveTime(LocalDateTime.now());
+ } else if (BpmProcessInstanceStatusEnum.REJECT.getStatus().equals(status)) {
+ updateObj.setApprovalStatus(ContractApprovalStatusEnum.REJECTED.getStatus());
+ } else if (BpmProcessInstanceStatusEnum.CANCEL.getStatus().equals(status)) {
+ updateObj.setApprovalStatus(ContractApprovalStatusEnum.WITHDRAWN.getStatus());
+ }
+
+ contractMapper.updateById(updateObj);
+
+ // 记录变更历史
+ String changeContent = "审批状态变更:" + getApprovalStatusName(updateObj.getApprovalStatus());
+ saveChangeHistory(id, "审批状态变更", changeContent);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void withdrawContractApproval(Long id) {
+ // 校验存在
+ ContractDO contract = validateContractExists(id);
+
+ // 校验状态:只有审批中状态才能撤回
+ if (!ContractApprovalStatusEnum.APPROVING.getStatus().equals(contract.getApprovalStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_WITHDRAW);
+ }
+
+ // 调用 BPM API 取消流程实例
+ if (contract.getBpmInstanceId() != null) {
+ BpmProcessInstanceCancelReqDTO cancelReqDTO = new BpmProcessInstanceCancelReqDTO();
+ cancelReqDTO.setId(contract.getBpmInstanceId());
+ cancelReqDTO.setReason("合同审批撤回");
+ Long userId = SecurityFrameworkUtils.getLoginUserId();
+ bpmProcessInstanceApi.cancelProcessInstance(userId, cancelReqDTO);
+ }
+
+ // 更新合同状态
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(id);
+ updateObj.setApprovalStatus(ContractApprovalStatusEnum.WITHDRAWN.getStatus());
+ contractMapper.updateById(updateObj);
+
+ // 记录变更历史
+ saveChangeHistory(id, "撤回审批", "撤回合同审批");
+ }
+
+ /**
+ * 获取审批状态名称
+ */
+ private String getApprovalStatusName(Integer status) {
+ ContractApprovalStatusEnum statusEnum = ContractApprovalStatusEnum.valueOf(status);
+ return statusEnum != null ? statusEnum.getName() : "未知";
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void terminateContract(Long id, String reason) {
+ // 校验存在
+ ContractDO contract = validateContractExists(id);
+
+ // 校验状态:只有进行中的合同才能终止
+ if (!ContractStatusEnum.IN_PROGRESS.getStatus().equals(contract.getContractStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_TERMINATE);
+ }
+
+ // 更新合同状态
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(id);
+ updateObj.setContractStatus(ContractStatusEnum.TERMINATED.getStatus());
+ updateObj.setTerminateTime(LocalDateTime.now());
+ updateObj.setTerminateReason(reason);
+ contractMapper.updateById(updateObj);
+
+ // 记录变更历史
+ saveChangeHistory(id, "终止合同", "终止合同,原因:" + reason);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long renewContract(Long id, ContractSaveReqVO newContractReqVO) {
+ // 校验原合同存在
+ ContractDO oldContract = validateContractExists(id);
+
+ // 校验状态:只有进行中或已到期的合同才能续签
+ if (!ContractStatusEnum.IN_PROGRESS.getStatus().equals(oldContract.getContractStatus())
+ && !ContractStatusEnum.EXPIRED.getStatus().equals(oldContract.getContractStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_RENEW);
+ }
+
+ // 创建新合同
+ Long newContractId = createContract(newContractReqVO);
+
+ // 更新原合同状态
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(id);
+ updateObj.setContractStatus(ContractStatusEnum.RENEWED.getStatus());
+ updateObj.setRenewedContractId(newContractId);
+ contractMapper.updateById(updateObj);
+
+ // 更新新合同的原合同ID
+ ContractDO newContract = new ContractDO();
+ newContract.setId(newContractId);
+ newContract.setOriginalContractId(id);
+ contractMapper.updateById(newContract);
+
+ // 记录变更历史
+ saveChangeHistory(id, "续签合同", "续签合同,新合同ID:" + newContractId);
+ saveChangeHistory(newContractId, "续签合同", "由合同ID " + id + " 续签而来");
+
+ return newContractId;
+ }
+
+ @Override
+ public List getContractSimpleList() {
+ return contractMapper.selectList();
+ }
+
+ @Override
+ public ContractDetailRespVO getContractDetail(Long id) {
+ ContractDO contract = validateContractExists(id);
+ ContractDetailRespVO result = ContractConvert.INSTANCE.convertDetail(contract);
+
+ // 查询车辆订单
+ List vehicles = contractVehicleMapper.selectListByContractId(id);
+ List vehicleDetails = ContractConvert.INSTANCE.convertVehicleDetailList(vehicles);
+
+ // 查询每个车辆的服务项目
+ for (int i = 0; i < vehicles.size(); i++) {
+ List services = contractVehicleServiceMapper.selectListByContractVehicleId(vehicles.get(i).getId());
+ vehicleDetails.get(i).setServices(ContractConvert.INSTANCE.convertServiceVOList(services));
+ }
+ result.setVehicles(vehicleDetails);
+
+ // 查询被授权人
+ List authorized = contractAuthorizedMapper.selectListByContractId(id);
+ result.setAuthorizedPersons(ContractConvert.INSTANCE.convertAuthorizedVOList(authorized));
+
+ // 查询附件
+ List attachments = contractAttachmentMapper.selectListByContractId(id);
+ result.setAttachments(ContractConvert.INSTANCE.convertAttachmentVOList(attachments));
+
+ return result;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long convertToThirdParty(Long id, ContractSaveReqVO newContractReqVO) {
+ // 校验原合同存在且进行中
+ ContractDO oldContract = validateContractExists(id);
+ if (!ContractStatusEnum.IN_PROGRESS.getStatus().equals(oldContract.getContractStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_CONVERT);
+ }
+
+ // 设置三方合同标记
+ newContractReqVO.setThirdPartyEnabled(true);
+
+ // 创建新合同
+ Long newContractId = createContract(newContractReqVO);
+
+ // 更新新合同的原合同ID
+ ContractDO newContract = new ContractDO();
+ newContract.setId(newContractId);
+ newContract.setOriginalContractId(id);
+ contractMapper.updateById(newContract);
+
+ // 终止原合同
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(id);
+ updateObj.setContractStatus(ContractStatusEnum.TERMINATED.getStatus());
+ updateObj.setTerminateTime(LocalDateTime.now());
+ updateObj.setTerminateReason("变更为三方合同");
+ contractMapper.updateById(updateObj);
+
+ // 记录变更历史
+ saveChangeHistory(id, "变更为三方", "变更为三方合同,新合同ID:" + newContractId);
+ saveChangeHistory(newContractId, "变更为三方", "由合同ID " + id + " 变更而来");
+
+ return newContractId;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long convertToFormal(Long id, ContractSaveReqVO newContractReqVO) {
+ // 校验原合同存在且进行中
+ ContractDO oldContract = validateContractExists(id);
+ if (!ContractStatusEnum.IN_PROGRESS.getStatus().equals(oldContract.getContractStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_CONVERT);
+ }
+ // 校验原合同为试用合同
+ if (!Integer.valueOf(1).equals(oldContract.getContractType())) {
+ throw exception(CONTRACT_TYPE_NOT_TRIAL);
+ }
+
+ // 设置为正式合同
+ newContractReqVO.setContractType(2);
+
+ // 创建新合同
+ Long newContractId = createContract(newContractReqVO);
+
+ // 更新新合同的原合同ID
+ ContractDO newContract = new ContractDO();
+ newContract.setId(newContractId);
+ newContract.setOriginalContractId(id);
+ contractMapper.updateById(newContract);
+
+ // 终止原合同
+ ContractDO updateObj = new ContractDO();
+ updateObj.setId(id);
+ updateObj.setContractStatus(ContractStatusEnum.TERMINATED.getStatus());
+ updateObj.setTerminateTime(LocalDateTime.now());
+ updateObj.setTerminateReason("转正式合同");
+ contractMapper.updateById(updateObj);
+
+ // 记录变更历史
+ saveChangeHistory(id, "转正式", "转正式合同,新合同ID:" + newContractId);
+ saveChangeHistory(newContractId, "转正式", "由试用合同ID " + id + " 转正式而来");
+
+ return newContractId;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void addVehiclesToContract(Long id, List vehicles) {
+ // 校验合同存在且进行中
+ ContractDO contract = validateContractExists(id);
+ if (!ContractStatusEnum.IN_PROGRESS.getStatus().equals(contract.getContractStatus())) {
+ throw exception(CONTRACT_STATUS_NOT_ALLOW_ADD_VEHICLE);
+ }
+
+ // 追加车辆
+ saveContractVehicles(id, vehicles);
+
+ // 记录变更历史
+ saveChangeHistory(id, "新增车辆", "追加 " + vehicles.size() + " 辆车辆");
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void uploadSealedContract(Long id, String fileUrl, String fileName) {
+ // 校验合同存在
+ validateContractExists(id);
+
+ // 创建附件记录
+ ContractAttachmentDO attachment = new ContractAttachmentDO();
+ attachment.setContractId(id);
+ attachment.setAttachmentType(2); // 盖章合同
+ attachment.setFileName(fileName);
+ attachment.setFileUrl(fileUrl);
+ attachment.setUploadTime(LocalDateTime.now());
+ Long userId = SecurityFrameworkUtils.getLoginUserId();
+ attachment.setUploader(userId != null ? String.valueOf(userId) : "system");
+ contractAttachmentMapper.insert(attachment);
+
+ // 记录变更历史
+ saveChangeHistory(id, "上传盖章合同", "上传盖章合同附件:" + fileName);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/listener/ContractBpmListener.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/listener/ContractBpmListener.java
new file mode 100644
index 0000000..9130194
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/contract/listener/ContractBpmListener.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.asset.service.contract.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.asset.service.contract.ContractService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 车辆租赁合同审批状态监听器
+ *
+ * @author 芋道源码
+ */
+@Component
+public class ContractBpmListener extends BpmProcessInstanceStatusEventListener {
+
+ /**
+ * 流程定义 Key
+ */
+ public static final String PROCESS_KEY = "asset_contract";
+
+ @Resource
+ private ContractService contractService;
+
+ @Override
+ protected String getProcessDefinitionKey() {
+ return PROCESS_KEY;
+ }
+
+ @Override
+ protected void onEvent(BpmProcessInstanceStatusEvent event) {
+ contractService.updateContractApprovalStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/customer/CustomerService.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/customer/CustomerService.java
index b11f2a8..d04d609 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/customer/CustomerService.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/customer/CustomerService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.asset.controller.admin.customer.vo.CustomerSaveRe
import cn.iocoder.yudao.module.asset.dal.dataobject.customer.CustomerDO;
import jakarta.validation.Valid;
+import java.util.List;
/**
* 客户信息 Service 接口
@@ -52,4 +53,11 @@ public interface CustomerService {
*/
PageResult getCustomerPage(CustomerPageReqVO pageReqVO);
+ /**
+ * 获得客户精简列表(用于下拉选择)
+ *
+ * @return 客户列表
+ */
+ List getCustomerSimpleList();
+
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/customer/CustomerServiceImpl.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/customer/CustomerServiceImpl.java
index b64f3a6..6c19266 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/customer/CustomerServiceImpl.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/customer/CustomerServiceImpl.java
@@ -98,6 +98,11 @@ public class CustomerServiceImpl implements CustomerService {
return customerMapper.selectPage(pageReqVO);
}
+ @Override
+ public List getCustomerSimpleList() {
+ return customerMapper.selectList();
+ }
+
/**
* 生成客户编号
*/
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryOrderService.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryOrderService.java
new file mode 100644
index 0000000..44801d3
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryOrderService.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.asset.service.delivery;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryOrderPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryOrderRespVO;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryOrderSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryOrderDO;
+
+import jakarta.validation.Valid;
+
+public interface DeliveryOrderService {
+
+ Long createDeliveryOrder(@Valid DeliveryOrderSaveReqVO createReqVO);
+
+ void updateDeliveryOrder(@Valid DeliveryOrderSaveReqVO updateReqVO);
+
+ void deleteDeliveryOrder(Long id);
+
+ DeliveryOrderDO getDeliveryOrder(Long id);
+
+ DeliveryOrderRespVO getDeliveryOrderDetail(Long id);
+
+ PageResult getDeliveryOrderPage(DeliveryOrderPageReqVO pageReqVO);
+
+ void completeDeliveryOrder(Long id);
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryOrderServiceImpl.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryOrderServiceImpl.java
index c77498f..b013435 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryOrderServiceImpl.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryOrderServiceImpl.java
@@ -8,6 +8,8 @@ import cn.iocoder.yudao.module.asset.dal.dataobject.inspection.InspectionRecordD
import cn.iocoder.yudao.module.asset.dal.mysql.delivery.*;
import cn.iocoder.yudao.module.asset.enums.inspection.InspectionSourceTypeEnum;
import cn.iocoder.yudao.module.asset.service.inspection.InspectionRecordService;
+import cn.iocoder.yudao.module.asset.service.delivery.event.DeliveryCompletedEvent;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -37,6 +39,9 @@ public class DeliveryOrderServiceImpl implements DeliveryOrderService {
@Resource
private InspectionRecordService inspectionRecordService;
+ @Resource
+ private ApplicationEventPublisher eventPublisher;
+
@Override
@Transactional(rollbackFor = Exception.class)
public Long createDeliveryOrder(DeliveryOrderSaveReqVO createReqVO) {
@@ -169,6 +174,11 @@ public class DeliveryOrderServiceImpl implements DeliveryOrderService {
deliveryTaskMapper.updateById(DeliveryTaskDO.builder().id(task.getId()).deliveryStatus(1).build());
}
}
+
+ // Publish delivery completed events for each vehicle
+ for (DeliveryOrderVehicleDO ov : orderVehicles) {
+ eventPublisher.publishEvent(new DeliveryCompletedEvent(id, ov.getVehicleId()));
+ }
}
private DeliveryOrderDO validateDeliveryOrderExists(Long id) {
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryTaskService.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryTaskService.java
new file mode 100644
index 0000000..4c4c689
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryTaskService.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.asset.service.delivery;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskActivateReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskContractGroupRespVO;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskRespVO;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryTaskDO;
+
+import jakarta.validation.Valid;
+import java.util.List;
+
+public interface DeliveryTaskService {
+
+ Long createDeliveryTask(@Valid DeliveryTaskSaveReqVO createReqVO);
+
+ void updateDeliveryTask(@Valid DeliveryTaskSaveReqVO updateReqVO);
+
+ void deleteDeliveryTask(Long id);
+
+ DeliveryTaskDO getDeliveryTask(Long id);
+
+ DeliveryTaskRespVO getDeliveryTaskDetail(Long id);
+
+ PageResult getDeliveryTaskPage(DeliveryTaskPageReqVO pageReqVO);
+
+ void suspendDeliveryTask(Long id);
+
+ void activateDeliveryTask(Long id);
+
+ /**
+ * 激活交车任务(带日期参数)
+ */
+ void activateDeliveryTask(DeliveryTaskActivateReqVO reqVO);
+
+ /**
+ * 获得按合同分组的交车任务分页
+ */
+ PageResult getDeliveryTaskContractPage(DeliveryTaskPageReqVO pageReqVO);
+
+ List getDeliveryTaskSimpleList();
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryTaskServiceImpl.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryTaskServiceImpl.java
new file mode 100644
index 0000000..36bcef2
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/delivery/DeliveryTaskServiceImpl.java
@@ -0,0 +1,264 @@
+package cn.iocoder.yudao.module.asset.service.delivery;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.*;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskActivateReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskContractGroupRespVO;
+import cn.iocoder.yudao.module.asset.controller.admin.delivery.vo.DeliveryTaskVehicleVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractDO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryTaskDO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.delivery.DeliveryTaskVehicleDO;
+import cn.iocoder.yudao.module.asset.dal.mysql.contract.ContractMapper;
+import cn.iocoder.yudao.module.asset.dal.mysql.delivery.DeliveryTaskMapper;
+import cn.iocoder.yudao.module.asset.dal.mysql.delivery.DeliveryTaskVehicleMapper;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import jakarta.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.asset.enums.ErrorCodeConstants.*;
+
+@Service
+@Validated
+public class DeliveryTaskServiceImpl implements DeliveryTaskService {
+
+ @Resource
+ private DeliveryTaskMapper deliveryTaskMapper;
+
+ @Resource
+ private DeliveryTaskVehicleMapper deliveryTaskVehicleMapper;
+
+ @Resource
+ private ContractMapper contractMapper;
+
+ @Resource
+ private DeptApi deptApi;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createDeliveryTask(DeliveryTaskSaveReqVO createReqVO) {
+ // 校验合同是否存在
+ if (contractMapper.selectById(createReqVO.getContractId()) == null) {
+ throw exception(CONTRACT_NOT_EXISTS);
+ }
+
+ // Generate task code: contractCode + JC + sequence
+ String taskCode = generateTaskCode(createReqVO.getContractId(), createReqVO.getContractCode());
+
+ DeliveryTaskDO task = BeanUtils.toBean(createReqVO, DeliveryTaskDO.class);
+ task.setTaskCode(taskCode);
+ task.setTaskStatus(0); // active
+ task.setDeliveryStatus(0); // not delivered
+ task.setVehicleCount(createReqVO.getVehicles() != null ? createReqVO.getVehicles().size() : 0);
+ deliveryTaskMapper.insert(task);
+
+ // Insert vehicles
+ if (createReqVO.getVehicles() != null) {
+ for (DeliveryTaskVehicleVO vehicleVO : createReqVO.getVehicles()) {
+ DeliveryTaskVehicleDO vehicle = BeanUtils.toBean(vehicleVO, DeliveryTaskVehicleDO.class);
+ vehicle.setTaskId(task.getId());
+ vehicle.setIsDelivered(false);
+ deliveryTaskVehicleMapper.insert(vehicle);
+ }
+ }
+
+ return task.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateDeliveryTask(DeliveryTaskSaveReqVO updateReqVO) {
+ DeliveryTaskDO task = validateDeliveryTaskExists(updateReqVO.getId());
+ if (task.getDeliveryStatus().equals(1)) {
+ throw exception(DELIVERY_TASK_ALREADY_DELIVERED);
+ }
+
+ DeliveryTaskDO updateObj = BeanUtils.toBean(updateReqVO, DeliveryTaskDO.class);
+ updateObj.setVehicleCount(updateReqVO.getVehicles() != null ? updateReqVO.getVehicles().size() : 0);
+ deliveryTaskMapper.updateById(updateObj);
+
+ // Update vehicles: delete old, insert new
+ if (updateReqVO.getVehicles() != null) {
+ deliveryTaskVehicleMapper.deleteByTaskId(updateReqVO.getId());
+ for (DeliveryTaskVehicleVO vehicleVO : updateReqVO.getVehicles()) {
+ DeliveryTaskVehicleDO vehicle = BeanUtils.toBean(vehicleVO, DeliveryTaskVehicleDO.class);
+ vehicle.setTaskId(updateReqVO.getId());
+ vehicle.setIsDelivered(false);
+ deliveryTaskVehicleMapper.insert(vehicle);
+ }
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteDeliveryTask(Long id) {
+ validateDeliveryTaskExists(id);
+ deliveryTaskMapper.deleteById(id);
+ deliveryTaskVehicleMapper.deleteByTaskId(id);
+ }
+
+ @Override
+ public DeliveryTaskDO getDeliveryTask(Long id) {
+ return deliveryTaskMapper.selectById(id);
+ }
+
+ @Override
+ public DeliveryTaskRespVO getDeliveryTaskDetail(Long id) {
+ DeliveryTaskDO task = validateDeliveryTaskExists(id);
+ DeliveryTaskRespVO respVO = BeanUtils.toBean(task, DeliveryTaskRespVO.class);
+ List vehicles = deliveryTaskVehicleMapper.selectListByTaskId(id);
+ respVO.setVehicles(BeanUtils.toBean(vehicles, DeliveryTaskVehicleVO.class));
+ return respVO;
+ }
+
+ @Override
+ public PageResult getDeliveryTaskPage(DeliveryTaskPageReqVO pageReqVO) {
+ return deliveryTaskMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public void suspendDeliveryTask(Long id) {
+ DeliveryTaskDO task = validateDeliveryTaskExists(id);
+ if (task.getDeliveryStatus().equals(1)) {
+ throw exception(DELIVERY_TASK_ALREADY_DELIVERED);
+ }
+ deliveryTaskMapper.updateById(DeliveryTaskDO.builder().id(id).taskStatus(1).build());
+ }
+
+ @Override
+ public void activateDeliveryTask(Long id) {
+ validateDeliveryTaskExists(id);
+ deliveryTaskMapper.updateById(DeliveryTaskDO.builder().id(id).taskStatus(0).build());
+ }
+
+ @Override
+ public void activateDeliveryTask(DeliveryTaskActivateReqVO reqVO) {
+ validateDeliveryTaskExists(reqVO.getId());
+ DeliveryTaskDO updateObj = DeliveryTaskDO.builder()
+ .id(reqVO.getId())
+ .taskStatus(0)
+ .expectedDeliveryDateStart(reqVO.getExpectedDeliveryDateStart())
+ .expectedDeliveryDateEnd(reqVO.getExpectedDeliveryDateEnd())
+ .billingStartDate(reqVO.getBillingStartDate())
+ .build();
+ deliveryTaskMapper.updateById(updateObj);
+ }
+
+ @Override
+ public PageResult getDeliveryTaskContractPage(DeliveryTaskPageReqVO pageReqVO) {
+ // 1. 查询所有匹配的交车任务
+ List allTasks = deliveryTaskMapper.selectListByFilters(pageReqVO);
+ if (allTasks.isEmpty()) {
+ return new PageResult<>(Collections.emptyList(), 0L);
+ }
+
+ // 2. 按合同 ID 分组
+ Map> groupedByContract = allTasks.stream()
+ .collect(Collectors.groupingBy(DeliveryTaskDO::getContractId,
+ LinkedHashMap::new, Collectors.toList()));
+
+ // 3. 对合同分组进行分页
+ List contractIds = new ArrayList<>(groupedByContract.keySet());
+ long total = contractIds.size();
+ int start = (pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize();
+ int end = Math.min(start + pageReqVO.getPageSize(), contractIds.size());
+ if (start >= contractIds.size()) {
+ return new PageResult<>(Collections.emptyList(), total);
+ }
+ List pageContractIds = contractIds.subList(start, end);
+
+ // 4. 批量查询合同信息
+ List contracts = contractMapper.selectBatchIds(pageContractIds);
+ Map contractMap = contracts.stream()
+ .collect(Collectors.toMap(ContractDO::getId, c -> c, (a, b) -> a));
+
+ // 5. 批量查询部门和用户名称
+ Set deptIds = new HashSet<>();
+ Set userIds = new HashSet<>();
+ contracts.forEach(c -> {
+ if (c.getBusinessDeptId() != null) deptIds.add(c.getBusinessDeptId());
+ if (c.getBusinessManagerId() != null) userIds.add(c.getBusinessManagerId());
+ });
+ Map deptMap = deptIds.isEmpty() ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
+ Map userMap = userIds.isEmpty() ? Collections.emptyMap() : adminUserApi.getUserMap(userIds);
+
+ // 6. 批量查询车辆信息(用于 popover 展示)
+ List taskIds = pageContractIds.stream()
+ .flatMap(cid -> groupedByContract.get(cid).stream())
+ .map(DeliveryTaskDO::getId)
+ .toList();
+ List allVehicles = taskIds.isEmpty() ? Collections.emptyList()
+ : deliveryTaskVehicleMapper.selectList(
+ new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX()
+ .in(DeliveryTaskVehicleDO::getTaskId, taskIds));
+ Map> vehiclesByTask = allVehicles.stream()
+ .collect(Collectors.groupingBy(DeliveryTaskVehicleDO::getTaskId));
+
+ // 7. 组装响应
+ List result = new ArrayList<>();
+ for (Long contractId : pageContractIds) {
+ DeliveryTaskContractGroupRespVO group = new DeliveryTaskContractGroupRespVO();
+ ContractDO contract = contractMap.get(contractId);
+ if (contract != null) {
+ group.setContractId(contract.getId());
+ group.setContractCode(contract.getContractCode());
+ group.setProjectName(contract.getProjectName());
+ group.setCustomerName(contract.getCustomerName());
+ group.setStartDate(contract.getStartDate());
+ group.setEndDate(contract.getEndDate());
+ DeptRespDTO dept = deptMap.get(contract.getBusinessDeptId());
+ group.setBusinessDeptName(dept != null ? dept.getName() : null);
+ AdminUserRespDTO user = userMap.get(contract.getBusinessManagerId());
+ group.setBusinessManagerName(user != null ? user.getNickname() : null);
+ } else {
+ DeliveryTaskDO firstTask = groupedByContract.get(contractId).get(0);
+ group.setContractId(contractId);
+ group.setContractCode(firstTask.getContractCode());
+ group.setProjectName(firstTask.getProjectName());
+ group.setCustomerName(firstTask.getCustomerName());
+ }
+
+ List taskVOs = groupedByContract.get(contractId).stream().map(task -> {
+ DeliveryTaskRespVO vo = BeanUtils.toBean(task, DeliveryTaskRespVO.class);
+ List vehicles = vehiclesByTask.getOrDefault(task.getId(), Collections.emptyList());
+ vo.setVehicles(BeanUtils.toBean(vehicles, DeliveryTaskVehicleVO.class));
+ return vo;
+ }).toList();
+ group.setTasks(taskVOs);
+ result.add(group);
+ }
+
+ return new PageResult<>(result, total);
+ }
+
+ @Override
+ public List getDeliveryTaskSimpleList() {
+ return deliveryTaskMapper.selectList();
+ }
+
+ private DeliveryTaskDO validateDeliveryTaskExists(Long id) {
+ DeliveryTaskDO task = deliveryTaskMapper.selectById(id);
+ if (task == null) {
+ throw exception(DELIVERY_TASK_NOT_EXISTS);
+ }
+ return task;
+ }
+
+ private String generateTaskCode(Long contractId, String contractCode) {
+ long count = deliveryTaskMapper.selectCountByContractId(contractId);
+ return contractCode + "JC" + String.format("%04d", count + 1);
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/event/VehicleStatusEventListener.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/event/VehicleStatusEventListener.java
index 7589082..ea6bae2 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/event/VehicleStatusEventListener.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/event/VehicleStatusEventListener.java
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.asset.service.event;
+import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleStatusDO;
+import cn.iocoder.yudao.module.asset.dal.mysql.vehicle.VehicleStatusMapper;
import cn.iocoder.yudao.module.asset.service.delivery.event.DeliveryCompletedEvent;
import cn.iocoder.yudao.module.asset.service.replacement.event.ReplacementReturnConfirmedEvent;
import cn.iocoder.yudao.module.asset.service.returnorder.event.ReturnApprovedEvent;
@@ -7,6 +9,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
+import jakarta.annotation.Resource;
+import java.util.List;
+
/**
* 车辆状态事件监听器
* 负责在业务事件发生时更新车辆状态
@@ -17,22 +22,68 @@ import org.springframework.transaction.event.TransactionalEventListener;
@Slf4j
public class VehicleStatusEventListener {
+ @Resource
+ private VehicleStatusMapper vehicleStatusMapper;
+
+ /**
+ * 交车完成:更新车辆库位状态为「已交付车-租赁交车」,标记已交车
+ */
@TransactionalEventListener
public void onDeliveryCompleted(DeliveryCompletedEvent event) {
- log.info("[onDeliveryCompleted] vehicleId={}", event.getVehicleId());
- // TODO: update vehicle status to DELIVERED when vehicle status management is implemented
+ log.info("[onDeliveryCompleted] deliveryOrderId={}, vehicleId={}", event.getDeliveryOrderId(), event.getVehicleId());
+ VehicleStatusDO vehicleStatus = vehicleStatusMapper.selectByVehicleId(event.getVehicleId());
+ if (vehicleStatus == null) {
+ log.warn("[onDeliveryCompleted] vehicle status not found, vehicleId={}", event.getVehicleId());
+ return;
+ }
+ vehicleStatusMapper.updateById(VehicleStatusDO.builder()
+ .id(vehicleStatus.getId())
+ .storageStatus(5) // 已交付车-租赁交车
+ .operateStatus(2) // 租赁
+ .isDelivered(true)
+ .build());
}
+ /**
+ * 还车审批通过:更新所有还车车辆的库位状态为「库存车-可交付车」,标记已还车
+ */
@TransactionalEventListener
public void onReturnApproved(ReturnApprovedEvent event) {
- log.info("[onReturnApproved] vehicleIds={}", event.getVehicleIds());
- // TODO: update vehicle status to AVAILABLE when vehicle status management is implemented
+ log.info("[onReturnApproved] returnOrderId={}, vehicleIds={}", event.getReturnOrderId(), event.getVehicleIds());
+ List vehicleIds = event.getVehicleIds();
+ if (vehicleIds == null || vehicleIds.isEmpty()) {
+ return;
+ }
+ List vehicleStatuses = vehicleStatusMapper.selectByVehicleIds(vehicleIds);
+ for (VehicleStatusDO vehicleStatus : vehicleStatuses) {
+ vehicleStatusMapper.updateById(VehicleStatusDO.builder()
+ .id(vehicleStatus.getId())
+ .storageStatus(2) // 库存车-可交付车
+ .operateStatus(1) // 库存
+ .isReturned(true)
+ .isDelivered(false)
+ .build());
+ }
}
+ /**
+ * 替换车换回确认:更新原车辆库位状态为「库存车-可交付车」
+ */
@TransactionalEventListener
public void onReplacementReturnConfirmed(ReplacementReturnConfirmedEvent event) {
- log.info("[onReplacementReturnConfirmed] vehicleId={}", event.getOriginalVehicleId());
- // TODO: update vehicle status to AVAILABLE
+ log.info("[onReplacementReturnConfirmed] replacementId={}, originalVehicleId={}",
+ event.getReplacementId(), event.getOriginalVehicleId());
+ VehicleStatusDO vehicleStatus = vehicleStatusMapper.selectByVehicleId(event.getOriginalVehicleId());
+ if (vehicleStatus == null) {
+ log.warn("[onReplacementReturnConfirmed] vehicle status not found, vehicleId={}", event.getOriginalVehicleId());
+ return;
+ }
+ vehicleStatusMapper.updateById(VehicleStatusDO.builder()
+ .id(vehicleStatus.getId())
+ .storageStatus(2) // 库存车-可交付车
+ .operateStatus(1) // 库存
+ .isDelivered(false)
+ .build());
}
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/prepare/VehiclePrepareService.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/prepare/VehiclePrepareService.java
new file mode 100644
index 0000000..ad26dd2
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/prepare/VehiclePrepareService.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.asset.service.prepare;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.prepare.vo.VehiclePreparePageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.prepare.vo.VehiclePrepareSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.prepare.VehiclePrepareDO;
+
+import jakarta.validation.Valid;
+
+public interface VehiclePrepareService {
+
+ Long createVehiclePrepare(@Valid VehiclePrepareSaveReqVO createReqVO);
+
+ void updateVehiclePrepare(@Valid VehiclePrepareSaveReqVO updateReqVO);
+
+ void deleteVehiclePrepare(Long id);
+
+ VehiclePrepareDO getVehiclePrepare(Long id);
+
+ PageResult getVehiclePreparePage(VehiclePreparePageReqVO pageReqVO);
+
+ void completeVehiclePrepare(Long id);
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/prepare/VehiclePrepareServiceImpl.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/prepare/VehiclePrepareServiceImpl.java
index df93d32..4a3630b 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/prepare/VehiclePrepareServiceImpl.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/prepare/VehiclePrepareServiceImpl.java
@@ -6,7 +6,9 @@ import cn.iocoder.yudao.module.asset.controller.admin.prepare.vo.VehiclePrepareP
import cn.iocoder.yudao.module.asset.controller.admin.prepare.vo.VehiclePrepareSaveReqVO;
import cn.iocoder.yudao.module.asset.dal.dataobject.inspection.InspectionTemplateDO;
import cn.iocoder.yudao.module.asset.dal.dataobject.prepare.VehiclePrepareDO;
+import cn.iocoder.yudao.module.asset.dal.mysql.contract.ContractMapper;
import cn.iocoder.yudao.module.asset.dal.mysql.prepare.VehiclePrepareMapper;
+import cn.iocoder.yudao.module.asset.dal.mysql.vehicle.VehicleBaseMapper;
import cn.iocoder.yudao.module.asset.enums.inspection.InspectionSourceTypeEnum;
import cn.iocoder.yudao.module.asset.service.inspection.InspectionRecordService;
import cn.iocoder.yudao.module.asset.service.inspection.InspectionTemplateService;
@@ -27,6 +29,12 @@ public class VehiclePrepareServiceImpl implements VehiclePrepareService {
@Resource
private VehiclePrepareMapper vehiclePrepareMapper;
+ @Resource
+ private ContractMapper contractMapper;
+
+ @Resource
+ private VehicleBaseMapper vehicleBaseMapper;
+
@Resource
private InspectionTemplateService inspectionTemplateService;
@@ -35,6 +43,19 @@ public class VehiclePrepareServiceImpl implements VehiclePrepareService {
@Override
public Long createVehiclePrepare(VehiclePrepareSaveReqVO createReqVO) {
+ // 校验合同是否存在
+ if (createReqVO.getContractId() != null) {
+ if (contractMapper.selectById(createReqVO.getContractId()) == null) {
+ throw exception(CONTRACT_NOT_EXISTS);
+ }
+ }
+ // 校验车辆是否存在
+ if (createReqVO.getVehicleId() != null) {
+ if (vehicleBaseMapper.selectById(createReqVO.getVehicleId()) == null) {
+ throw exception(VEHICLE_NOT_EXISTS);
+ }
+ }
+
VehiclePrepareDO prepare = BeanUtils.toBean(createReqVO, VehiclePrepareDO.class);
vehiclePrepareMapper.insert(prepare);
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/returnorder/ReturnOrderServiceImpl.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/returnorder/ReturnOrderServiceImpl.java
index 289b35c..9151817 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/returnorder/ReturnOrderServiceImpl.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/returnorder/ReturnOrderServiceImpl.java
@@ -11,12 +11,16 @@ import cn.iocoder.yudao.module.asset.dal.dataobject.inspection.InspectionRecordD
import cn.iocoder.yudao.module.asset.dal.dataobject.inspection.InspectionTemplateDO;
import cn.iocoder.yudao.module.asset.dal.dataobject.returnorder.ReturnOrderDO;
import cn.iocoder.yudao.module.asset.dal.dataobject.returnorder.ReturnOrderVehicleDO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleBaseDO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.vehiclemodel.VehicleModelDO;
import cn.iocoder.yudao.module.asset.dal.mysql.contract.ContractMapper;
import cn.iocoder.yudao.module.asset.dal.mysql.delivery.DeliveryOrderMapper;
import cn.iocoder.yudao.module.asset.dal.mysql.inspection.InspectionRecordMapper;
import cn.iocoder.yudao.module.asset.dal.mysql.delivery.DeliveryOrderVehicleMapper;
import cn.iocoder.yudao.module.asset.dal.mysql.returnorder.ReturnOrderMapper;
import cn.iocoder.yudao.module.asset.dal.mysql.returnorder.ReturnOrderVehicleMapper;
+import cn.iocoder.yudao.module.asset.dal.mysql.vehicle.VehicleBaseMapper;
+import cn.iocoder.yudao.module.asset.dal.mysql.vehiclemodel.VehicleModelMapper;
import cn.iocoder.yudao.module.asset.enums.inspection.InspectionSourceTypeEnum;
import cn.iocoder.yudao.module.asset.service.inspection.InspectionRecordService;
import cn.iocoder.yudao.module.asset.service.inspection.InspectionTemplateService;
@@ -61,6 +65,12 @@ public class ReturnOrderServiceImpl implements ReturnOrderService {
@Resource
private ContractMapper contractMapper;
+ @Resource
+ private VehicleBaseMapper vehicleBaseMapper;
+
+ @Resource
+ private VehicleModelMapper vehicleModelMapper;
+
@Resource
private InspectionRecordMapper inspectionRecordMapper;
@@ -238,10 +248,26 @@ public class ReturnOrderServiceImpl implements ReturnOrderService {
returnOrderMapper.insert(order);
// 创建还车车辆(原车辆)
- // TODO: 查询车辆信息获取 plateNo/vin/brand/model,暂时只设置 vehicleId
+ VehicleBaseDO vehicleBase = vehicleBaseMapper.selectById(vehicleId);
+ if (vehicleBase == null) {
+ throw exception(VEHICLE_NOT_EXISTS);
+ }
+
ReturnOrderVehicleDO vehicle = new ReturnOrderVehicleDO();
vehicle.setReturnOrderId(order.getId());
vehicle.setVehicleId(vehicleId);
+ vehicle.setPlateNo(vehicleBase.getPlateNo());
+ vehicle.setVin(vehicleBase.getVin());
+
+ // 通过车型ID查询品牌和型号
+ if (vehicleBase.getVehicleModelId() != null) {
+ VehicleModelDO vehicleModel = vehicleModelMapper.selectById(vehicleBase.getVehicleModelId());
+ if (vehicleModel != null) {
+ vehicle.setBrand(vehicleModel.getBrand());
+ vehicle.setModel(vehicleModel.getModel());
+ }
+ }
+
returnOrderVehicleMapper.insert(vehicle);
return order.getId();
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/station/HydrogenStationService.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/station/HydrogenStationService.java
new file mode 100644
index 0000000..b37fa87
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/station/HydrogenStationService.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.asset.service.station;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
+
+import jakarta.validation.Valid;
+import java.util.List;
+
+/**
+ * 加氢站 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface HydrogenStationService {
+
+ /**
+ * 创建加氢站
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createHydrogenStation(@Valid HydrogenStationSaveReqVO createReqVO);
+
+ /**
+ * 更新加氢站
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateHydrogenStation(@Valid HydrogenStationSaveReqVO updateReqVO);
+
+ /**
+ * 删除加氢站
+ *
+ * @param id 编号
+ */
+ void deleteHydrogenStation(Long id);
+
+ /**
+ * 获得加氢站
+ *
+ * @param id 编号
+ * @return 加氢站
+ */
+ HydrogenStationDO getHydrogenStation(Long id);
+
+ /**
+ * 获得加氢站分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 加氢站分页
+ */
+ PageResult getHydrogenStationPage(HydrogenStationPageReqVO pageReqVO);
+
+ /**
+ * 获得加氢站精简列表
+ *
+ * @return 加氢站列表
+ */
+ List getHydrogenStationSimpleList();
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/station/HydrogenStationServiceImpl.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/station/HydrogenStationServiceImpl.java
new file mode 100644
index 0000000..a13446c
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/station/HydrogenStationServiceImpl.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.asset.service.station;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationPageReqVO;
+import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationSaveReqVO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
+import cn.iocoder.yudao.module.asset.dal.mysql.station.HydrogenStationMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.asset.enums.ErrorCodeConstants.*;
+
+/**
+ * 加氢站 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class HydrogenStationServiceImpl implements HydrogenStationService {
+
+ @Resource
+ private HydrogenStationMapper hydrogenStationMapper;
+
+ @Override
+ public Long createHydrogenStation(HydrogenStationSaveReqVO createReqVO) {
+ // 业务校验
+ validateHydrogenStationBusiness(createReqVO);
+
+ HydrogenStationDO hydrogenStation = BeanUtils.toBean(createReqVO, HydrogenStationDO.class);
+ hydrogenStationMapper.insert(hydrogenStation);
+ return hydrogenStation.getId();
+ }
+
+ @Override
+ public void updateHydrogenStation(HydrogenStationSaveReqVO updateReqVO) {
+ validateHydrogenStationExists(updateReqVO.getId());
+
+ // 业务校验
+ validateHydrogenStationBusiness(updateReqVO);
+
+ HydrogenStationDO updateObj = BeanUtils.toBean(updateReqVO, HydrogenStationDO.class);
+ hydrogenStationMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteHydrogenStation(Long id) {
+ validateHydrogenStationExists(id);
+ hydrogenStationMapper.deleteById(id);
+ }
+
+ private void validateHydrogenStationExists(Long id) {
+ if (hydrogenStationMapper.selectById(id) == null) {
+ throw exception(HYDROGEN_STATION_NOT_EXISTS);
+ }
+ }
+
+ /**
+ * 业务校验
+ */
+ private void validateHydrogenStationBusiness(HydrogenStationSaveReqVO reqVO) {
+ // 校验营业时间
+ if (reqVO.getStartBusiness() != null && reqVO.getEndBusiness() != null) {
+ if (reqVO.getEndBusiness().isBefore(reqVO.getStartBusiness())) {
+ throw exception(HYDROGEN_STATION_END_BUSINESS_BEFORE_START_BUSINESS);
+ }
+ }
+ }
+
+ @Override
+ public HydrogenStationDO getHydrogenStation(Long id) {
+ return hydrogenStationMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getHydrogenStationPage(HydrogenStationPageReqVO pageReqVO) {
+ return hydrogenStationMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List getHydrogenStationSimpleList() {
+ return hydrogenStationMapper.selectSimpleList();
+ }
+
+}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/vehicleregistration/VehicleRegistrationService.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/vehicleregistration/VehicleRegistrationService.java
index 9b7f06b..8309d4c 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/vehicleregistration/VehicleRegistrationService.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/vehicleregistration/VehicleRegistrationService.java
@@ -68,4 +68,11 @@ public interface VehicleRegistrationService {
*/
void confirmRegistration(Long id);
+ /**
+ * 作废上牌记录
+ *
+ * @param id 上牌记录ID
+ */
+ void voidRegistration(Long id);
+
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/vehicleregistration/VehicleRegistrationServiceImpl.java b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/vehicleregistration/VehicleRegistrationServiceImpl.java
index 782aae0..a6bffc1 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/vehicleregistration/VehicleRegistrationServiceImpl.java
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/java/cn/iocoder/yudao/module/asset/service/vehicleregistration/VehicleRegistrationServiceImpl.java
@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.asset.dal.mysql.vehicleregistration.VehicleRegist
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.ocr.api.OcrApi;
+import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseReqDTO;
import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseRespDTO;
import cn.hutool.core.codec.Base64;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -55,7 +56,9 @@ public class VehicleRegistrationServiceImpl implements VehicleRegistrationServic
String imageDataBase64 = Base64.encode(imageData);
// 调用 OCR API 识别
- CommonResult ocrResult = ocrApi.recognizeVehicleLicense(imageDataBase64, null);
+ VehicleLicenseReqDTO reqDTO = new VehicleLicenseReqDTO();
+ reqDTO.setImageData(imageDataBase64);
+ CommonResult ocrResult = ocrApi.recognizeVehicleLicense(reqDTO);
long costTime = System.currentTimeMillis() - startTime;
@@ -100,11 +103,12 @@ public class VehicleRegistrationServiceImpl implements VehicleRegistrationServic
@Override
@Transactional(rollbackFor = Exception.class)
public Long createVehicleRegistration(VehicleRegistrationSaveReqVO createReqVO) {
- // 验证车辆是否存在
- VehicleBaseDO vehicle = validateVehicleExists(createReqVO.getVehicleId());
+ // 通过 VIN 验证车辆是否存在
+ VehicleBaseDO vehicle = validateVehicleExistsByVin(createReqVO.getVin());
// 插入
VehicleRegistrationDO registration = VehicleRegistrationConvert.INSTANCE.convert(createReqVO);
+ registration.setVehicleId(vehicle.getId());
registration.setStatus(0); // 待确认
vehicleRegistrationMapper.insert(registration);
@@ -120,9 +124,10 @@ public class VehicleRegistrationServiceImpl implements VehicleRegistrationServic
// 校验存在
validateVehicleRegistrationExists(updateReqVO.getId());
- // 验证车辆是否存在
- if (updateReqVO.getVehicleId() != null) {
- validateVehicleExists(updateReqVO.getVehicleId());
+ // 通过 VIN 验证车辆是否存在
+ if (StrUtil.isNotBlank(updateReqVO.getVin())) {
+ VehicleBaseDO vehicle = validateVehicleExistsByVin(updateReqVO.getVin());
+ updateReqVO.setVehicleId(vehicle.getId());
}
// 更新
@@ -192,6 +197,20 @@ public class VehicleRegistrationServiceImpl implements VehicleRegistrationServic
log.info("[confirmRegistration][确认上牌记录成功,ID:{},车辆ID:{}]", id, vehicle.getId());
}
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void voidRegistration(Long id) {
+ VehicleRegistrationDO registration = validateVehicleRegistrationExists(id);
+ if (registration.getStatus() != 0) {
+ throw exception(new ErrorCode(400, "上牌记录已确认或已作废"));
+ }
+ VehicleRegistrationDO updateObj = new VehicleRegistrationDO();
+ updateObj.setId(id);
+ updateObj.setStatus(2); // 2=已作废
+ vehicleRegistrationMapper.updateById(updateObj);
+ log.info("[voidRegistration][作废上牌记录成功,ID:{}]", id);
+ }
+
// ==================== 私有方法 ====================
private VehicleRegistrationDO validateVehicleRegistrationExists(Long id) {
@@ -210,4 +229,17 @@ public class VehicleRegistrationServiceImpl implements VehicleRegistrationServic
return vehicle;
}
+ private VehicleBaseDO validateVehicleExistsByVin(String vin) {
+ if (StrUtil.isBlank(vin)) {
+ throw exception(new ErrorCode(400, "VIN不能为空"));
+ }
+ VehicleBaseDO vehicle = vehicleBaseMapper.selectOne(new LambdaQueryWrapper()
+ .eq(VehicleBaseDO::getVin, vin)
+ .last("LIMIT 1"));
+ if (vehicle == null) {
+ throw exception(new ErrorCode(400, "车辆不存在,请先录入车辆信息"));
+ }
+ return vehicle;
+ }
+
}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/resources/application-local.yaml b/yudao-module-asset/yudao-module-asset-server/src/main/resources/application-local.yaml
index 3056a2d..b8e2da6 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/resources/application-local.yaml
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/resources/application-local.yaml
@@ -109,3 +109,14 @@ knife4j:
enable: true
setting:
language: zh_cn
+
+# XXL-Job 配置
+xxl:
+ job:
+ enabled: false # 本地开发禁用
+ admin:
+ addresses: http://47.103.115.36:9090/xxl-job-admin
+ executor:
+ appname: asset-server
+ logpath: ./logs/xxl-job
+ logretentiondays: 30
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/resources/application.yaml b/yudao-module-asset/yudao-module-asset-server/src/main/resources/application.yaml
index 0d234fe..b5361ff 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/main/resources/application.yaml
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/resources/application.yaml
@@ -3,7 +3,7 @@ spring:
name: asset-server
profiles:
- active: dev
+ active: local
# 允许 Bean 覆盖
main:
diff --git a/yudao-module-asset/yudao-module-asset-server/src/main/resources/processes/asset_contract.bpmn20.xml b/yudao-module-asset/yudao-module-asset-server/src/main/resources/processes/asset_contract.bpmn20.xml
new file mode 100644
index 0000000..c068d2c
--- /dev/null
+++ b/yudao-module-asset/yudao-module-asset-server/src/main/resources/processes/asset_contract.bpmn20.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${approved == true}
+
+
+
+ ${approved == false}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${approved == true}
+
+
+
+ ${approved == false}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-asset/yudao-module-asset-server/src/test/java/cn/iocoder/yudao/module/asset/service/customer/CustomerServiceImplTest.java b/yudao-module-asset/yudao-module-asset-server/src/test/java/cn/iocoder/yudao/module/asset/service/customer/CustomerServiceImplTest.java
deleted file mode 100644
index a8f1020..0000000
--- a/yudao-module-asset/yudao-module-asset-server/src/test/java/cn/iocoder/yudao/module/asset/service/customer/CustomerServiceImplTest.java
+++ /dev/null
@@ -1,228 +0,0 @@
-package cn.iocoder.yudao.module.asset.service.customer;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-import cn.iocoder.yudao.module.asset.controller.admin.customer.vo.CustomerPageReqVO;
-import cn.iocoder.yudao.module.asset.controller.admin.customer.vo.CustomerSaveReqVO;
-import cn.iocoder.yudao.module.asset.dal.dataobject.customer.CustomerDO;
-import cn.iocoder.yudao.module.asset.dal.mysql.customer.CustomerMapper;
-import org.junit.jupiter.api.Test;
-import org.springframework.context.annotation.Import;
-
-import jakarta.annotation.Resource;
-import java.math.BigDecimal;
-
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.module.asset.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * 客户管理 Service 测试类
- *
- * @author 芋道源码
- */
-@Import(CustomerServiceImpl.class)
-public class CustomerServiceImplTest extends BaseDbUnitTest {
-
- @Resource
- private CustomerServiceImpl customerService;
-
- @Resource
- private CustomerMapper customerMapper;
-
- @Test
- public void testCreateCustomer_success() {
- // 准备参数
- CustomerSaveReqVO createReqVO = randomPojo(CustomerSaveReqVO.class, o -> {
- o.setCustomerName("测试客户");
- o.setCustomerType(0);
- o.setContactPhone("13800138000");
- o.setContactEmail("test@example.com");
- o.setIdCardNo("310101199001011234");
- o.setCreditLevel(1);
- o.setDepositAmount(new BigDecimal("5000.00"));
- o.setStatus(0);
- });
-
- // 调用
- Long customerId = customerService.createCustomer(createReqVO);
-
- // 断言
- assertNotNull(customerId);
- CustomerDO customer = customerMapper.selectById(customerId);
- assertNotNull(customer);
- assertEquals("测试客户", customer.getCustomerName());
- assertEquals(0, customer.getCustomerType());
- assertEquals("13800138000", customer.getContactPhone());
- assertNotNull(customer.getCustomerNo());
- assertTrue(customer.getCustomerNo().startsWith("CUST"));
- }
-
- @Test
- public void testUpdateCustomer_success() {
- // mock 数据
- CustomerDO dbCustomer = new CustomerDO();
- dbCustomer.setCustomerNo("CUST000001");
- dbCustomer.setCustomerName("原客户名");
- dbCustomer.setCustomerType(0);
- dbCustomer.setContactPhone("13800138000");
- dbCustomer.setCreditLevel(1);
- dbCustomer.setDepositAmount(new BigDecimal("5000.00"));
- dbCustomer.setStatus(0);
- customerMapper.insert(dbCustomer);
-
- // 准备参数
- CustomerSaveReqVO updateReqVO = new CustomerSaveReqVO();
- updateReqVO.setId(dbCustomer.getId());
- updateReqVO.setCustomerName("新客户名");
- updateReqVO.setCustomerType(0);
- updateReqVO.setContactPhone("13900139000");
- updateReqVO.setCreditLevel(2);
- updateReqVO.setDepositAmount(new BigDecimal("8000.00"));
- updateReqVO.setStatus(0);
-
- // 调用
- customerService.updateCustomer(updateReqVO);
-
- // 断言
- CustomerDO customer = customerMapper.selectById(updateReqVO.getId());
- assertEquals("新客户名", customer.getCustomerName());
- assertEquals("13900139000", customer.getContactPhone());
- assertEquals(2, customer.getCreditLevel());
- }
-
- @Test
- public void testUpdateCustomer_notExists() {
- // 准备参数
- CustomerSaveReqVO updateReqVO = randomPojo(CustomerSaveReqVO.class, o -> {
- o.setId(999999L);
- o.setCustomerName("不存在的客户");
- o.setCustomerType(0);
- o.setStatus(0);
- });
-
- // 调用并断言异常
- assertServiceException(() -> customerService.updateCustomer(updateReqVO), CUSTOMER_NOT_EXISTS);
- }
-
- @Test
- public void testDeleteCustomer_success() {
- // mock 数据
- CustomerDO dbCustomer = new CustomerDO();
- dbCustomer.setCustomerNo("CUST000001");
- dbCustomer.setCustomerName("测试客户");
- dbCustomer.setCustomerType(0);
- dbCustomer.setCreditLevel(1);
- dbCustomer.setDepositAmount(new BigDecimal("5000.00"));
- dbCustomer.setStatus(0);
- customerMapper.insert(dbCustomer);
-
- // 调用
- customerService.deleteCustomer(dbCustomer.getId());
-
- // 断言
- assertNull(customerMapper.selectById(dbCustomer.getId()));
- }
-
- @Test
- public void testDeleteCustomer_notExists() {
- // 调用并断言异常
- assertServiceException(() -> customerService.deleteCustomer(999999L), CUSTOMER_NOT_EXISTS);
- }
-
- @Test
- public void testGetCustomer() {
- // mock 数据
- CustomerDO dbCustomer = new CustomerDO();
- dbCustomer.setCustomerNo("CUST000001");
- dbCustomer.setCustomerName("测试客户");
- dbCustomer.setCustomerType(0);
- dbCustomer.setCreditLevel(1);
- dbCustomer.setDepositAmount(new BigDecimal("5000.00"));
- dbCustomer.setStatus(0);
- customerMapper.insert(dbCustomer);
-
- // 调用
- CustomerDO customer = customerService.getCustomer(dbCustomer.getId());
-
- // 断言
- assertNotNull(customer);
- assertEquals("测试客户", customer.getCustomerName());
- }
-
- @Test
- public void testGetCustomerPage() {
- // mock 数据
- CustomerDO dbCustomer1 = new CustomerDO();
- dbCustomer1.setCustomerNo("CUST000001");
- dbCustomer1.setCustomerName("张三");
- dbCustomer1.setCustomerType(0);
- dbCustomer1.setContactPhone("13800138001");
- dbCustomer1.setCreditLevel(1);
- dbCustomer1.setDepositAmount(new BigDecimal("5000.00"));
- dbCustomer1.setStatus(0);
- customerMapper.insert(dbCustomer1);
-
- CustomerDO dbCustomer2 = new CustomerDO();
- dbCustomer2.setCustomerNo("CUST000002");
- dbCustomer2.setCustomerName("李四");
- dbCustomer2.setCustomerType(1);
- dbCustomer2.setContactPhone("13800138002");
- dbCustomer2.setCreditLevel(1);
- dbCustomer2.setDepositAmount(new BigDecimal("5000.00"));
- dbCustomer2.setStatus(0);
- customerMapper.insert(dbCustomer2);
-
- CustomerDO dbCustomer3 = new CustomerDO();
- dbCustomer3.setCustomerNo("CUST000003");
- dbCustomer3.setCustomerName("王五");
- dbCustomer3.setCustomerType(0);
- dbCustomer3.setContactPhone("13800138003");
- dbCustomer3.setCreditLevel(1);
- dbCustomer3.setDepositAmount(new BigDecimal("5000.00"));
- dbCustomer3.setStatus(1);
- customerMapper.insert(dbCustomer3);
-
- // 测试 customerName 模糊查询
- CustomerPageReqVO reqVO = new CustomerPageReqVO();
- reqVO.setCustomerName("张");
- reqVO.setPageNo(1);
- reqVO.setPageSize(10);
-
- PageResult pageResult = customerService.getCustomerPage(reqVO);
- assertEquals(1, pageResult.getTotal());
- assertEquals("张三", pageResult.getList().get(0).getCustomerName());
-
- // 测试 customerType 筛选
- reqVO = new CustomerPageReqVO();
- reqVO.setCustomerType(1);
- reqVO.setPageNo(1);
- reqVO.setPageSize(10);
-
- pageResult = customerService.getCustomerPage(reqVO);
- assertEquals(1, pageResult.getTotal());
- assertEquals("李四", pageResult.getList().get(0).getCustomerName());
-
- // 测试 status 筛选
- reqVO = new CustomerPageReqVO();
- reqVO.setStatus(1);
- reqVO.setPageNo(1);
- reqVO.setPageSize(10);
-
- pageResult = customerService.getCustomerPage(reqVO);
- assertEquals(1, pageResult.getTotal());
- assertEquals("王五", pageResult.getList().get(0).getCustomerName());
-
- // 测试 contactPhone 模糊查询
- reqVO = new CustomerPageReqVO();
- reqVO.setContactPhone("138001");
- reqVO.setPageNo(1);
- reqVO.setPageSize(10);
-
- pageResult = customerService.getCustomerPage(reqVO);
- assertEquals(3, pageResult.getTotal());
- }
-
-}
diff --git a/yudao-module-asset/yudao-module-asset-server/src/test/resources/sql/create_tables.sql b/yudao-module-asset/yudao-module-asset-server/src/test/resources/sql/create_tables.sql
index 1e5f3e7..8ba6a79 100644
--- a/yudao-module-asset/yudao-module-asset-server/src/test/resources/sql/create_tables.sql
+++ b/yudao-module-asset/yudao-module-asset-server/src/test/resources/sql/create_tables.sql
@@ -1,30 +1,29 @@
-- 客户信息表
CREATE TABLE IF NOT EXISTS asset_customer (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
- customer_no VARCHAR(50) NOT NULL,
- customer_name VARCHAR(100) NOT NULL,
- customer_type TINYINT NOT NULL DEFAULT 0,
- contact_person VARCHAR(50),
- contact_phone VARCHAR(20),
- contact_email VARCHAR(100),
- contact_address VARCHAR(255),
- id_card_no VARCHAR(18),
- id_card_front_url VARCHAR(255),
- id_card_back_url VARCHAR(255),
- driver_license_no VARCHAR(50),
- driver_license_url VARCHAR(255),
- company_name VARCHAR(200),
- unified_social_credit_code VARCHAR(50),
- business_license_url VARCHAR(255),
- legal_person VARCHAR(50),
- credit_level TINYINT DEFAULT 0,
- deposit_amount DECIMAL(10,2) DEFAULT 0.00,
- remark VARCHAR(500),
- status TINYINT NOT NULL DEFAULT 0,
- creator VARCHAR(64) DEFAULT '',
- create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updater VARCHAR(64) DEFAULT '',
- update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- deleted TINYINT NOT NULL DEFAULT 0,
- tenant_id BIGINT NOT NULL DEFAULT 0
+ customer_code VARCHAR(50) COMMENT '客户编号',
+ coop_status VARCHAR(20) COMMENT '合作状态',
+ customer_name VARCHAR(100) NOT NULL COMMENT '客户名称',
+ province VARCHAR(50) COMMENT '省份',
+ city VARCHAR(50) COMMENT '城市',
+ address VARCHAR(255) COMMENT '地址',
+ region VARCHAR(20) COMMENT '区域',
+ contact VARCHAR(50) COMMENT '联系人',
+ contact_mobile VARCHAR(20) COMMENT '联系人手机',
+ contact_phone VARCHAR(20) COMMENT '联系人座机',
+ email VARCHAR(100) COMMENT '电子邮箱',
+ credit_code_or_id VARCHAR(50) COMMENT '统一社会信用代码/身份证',
+ remark VARCHAR(500) COMMENT '备注',
+ tax_id VARCHAR(50) COMMENT '纳税人识别号',
+ invoice_address VARCHAR(255) COMMENT '发票地址',
+ invoice_phone VARCHAR(20) COMMENT '发票电话',
+ account VARCHAR(50) COMMENT '银行账号',
+ opening_bank VARCHAR(100) COMMENT '开户行',
+ mailing_address VARCHAR(255) COMMENT '邮寄地址',
+ creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
+ deleted TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
+ tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号'
);
diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java
index 1e3d158..0fab11e 100644
--- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java
+++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java
@@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.bpm.api.task;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCancelReqDTO;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
@@ -25,4 +27,10 @@ public interface BpmProcessInstanceApi {
CommonResult createProcessInstance(@RequestParam("userId") Long userId,
@Valid @RequestBody BpmProcessInstanceCreateReqDTO reqDTO);
+ @DeleteMapping(PREFIX + "/cancel")
+ @Operation(summary = "取消流程实例(提供给内部)")
+ @Parameter(name = "userId", description = "用户编号", required = true, example = "1")
+ CommonResult cancelProcessInstance(@RequestParam("userId") Long userId,
+ @Valid @RequestBody BpmProcessInstanceCancelReqDTO reqDTO);
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCancelReqDTO.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCancelReqDTO.java
new file mode 100644
index 0000000..d897b55
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCancelReqDTO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.bpm.api.task.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+
+@Schema(description = "RPC 服务 - 流程实例的取消 Request DTO")
+@Data
+public class BpmProcessInstanceCancelReqDTO {
+
+ @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "流程实例的编号不能为空")
+ private String id;
+
+ @Schema(description = "取消原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "撤回审批")
+ @NotEmpty(message = "取消原因不能为空")
+ private String reason;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java
index d058a11..6a1134c 100644
--- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java
+++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java
@@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.bpm.api.task;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCancelReqDTO;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@@ -29,4 +31,13 @@ public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi {
return success(processInstanceService.createProcessInstance(userId, reqDTO));
}
+ @Override
+ public CommonResult cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqDTO reqDTO) {
+ BpmProcessInstanceCancelReqVO cancelReqVO = new BpmProcessInstanceCancelReqVO();
+ cancelReqVO.setId(reqDTO.getId());
+ cancelReqVO.setReason(reqDTO.getReason());
+ processInstanceService.cancelProcessInstanceByStartUser(userId, cancelReqVO);
+ return success(true);
+ }
+
}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/EnergyServerApplication.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/EnergyServerApplication.java
new file mode 100644
index 0000000..8d9ffe4
--- /dev/null
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/EnergyServerApplication.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.energy;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+/**
+ * 能源管理服务启动类
+ *
+ * @author 芋道源码
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients(basePackages = {"cn.iocoder.yudao.module.infra.api"})
+public class EnergyServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(EnergyServerApplication.class, args);
+ }
+
+}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/EnergyStationConfigController.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/EnergyStationConfigController.java
deleted file mode 100644
index ff5c9a8..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/EnergyStationConfigController.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package cn.iocoder.yudao.module.energy.controller.admin.config;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigPageReqVO;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigRespVO;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigSaveReqVO;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigSimpleVO;
-import cn.iocoder.yudao.module.energy.convert.config.EnergyStationConfigConvert;
-import cn.iocoder.yudao.module.energy.dal.dataobject.config.EnergyStationConfigDO;
-import cn.iocoder.yudao.module.energy.service.config.EnergyStationConfigService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.annotation.Resource;
-import jakarta.validation.Valid;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "管理后台 - 加氢站配置")
-@RestController
-@RequestMapping("/energy/station-config")
-@Validated
-public class EnergyStationConfigController {
-
- @Resource
- private EnergyStationConfigService stationConfigService;
-
- @PostMapping("/create")
- @Operation(summary = "创建站点配置")
- @PreAuthorize("@ss.hasPermission('energy:station-config:create')")
- public CommonResult createConfig(@Valid @RequestBody EnergyStationConfigSaveReqVO createReqVO) {
- return success(stationConfigService.createConfig(createReqVO));
- }
-
- @PutMapping("/update")
- @Operation(summary = "更新站点配置")
- @PreAuthorize("@ss.hasPermission('energy:station-config:update')")
- public CommonResult updateConfig(@Valid @RequestBody EnergyStationConfigSaveReqVO updateReqVO) {
- stationConfigService.updateConfig(updateReqVO);
- return success(true);
- }
-
- @GetMapping("/get")
- @Operation(summary = "获得站点配置")
- @Parameter(name = "id", description = "编号", required = true)
- @PreAuthorize("@ss.hasPermission('energy:station-config:query')")
- public CommonResult getConfig(@RequestParam("id") Long id) {
- EnergyStationConfigDO config = stationConfigService.getConfig(id);
- return success(EnergyStationConfigConvert.INSTANCE.convert(config));
- }
-
- @GetMapping("/page")
- @Operation(summary = "获得站点配置分页")
- @PreAuthorize("@ss.hasPermission('energy:station-config:query')")
- public CommonResult> getConfigPage(@Valid EnergyStationConfigPageReqVO pageReqVO) {
- PageResult pageResult = stationConfigService.getConfigPage(pageReqVO);
- return success(EnergyStationConfigConvert.INSTANCE.convertPage(pageResult));
- }
-
- @GetMapping("/simple-list")
- @Operation(summary = "获得站点配置简单列表")
- public CommonResult> getSimpleList() {
- List list = stationConfigService.getConfigList();
- return success(EnergyStationConfigConvert.INSTANCE.convertSimpleList(list));
- }
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigPageReqVO.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigPageReqVO.java
deleted file mode 100644
index faa556c..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigPageReqVO.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.module.energy.controller.admin.config.vo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - 加氢站配置分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class EnergyStationConfigPageReqVO extends PageParam {
- @Schema(description = "加氢站ID")
- private Long stationId;
- @Schema(description = "是否自动扣款")
- private Boolean autoDeduct;
- @Schema(description = "合作类型")
- private Integer cooperationType;
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigRespVO.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigRespVO.java
deleted file mode 100644
index 51fc640..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigRespVO.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.energy.controller.admin.config.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import java.time.LocalDateTime;
-
-@Schema(description = "管理后台 - 加氢站配置 Response VO")
-@Data
-public class EnergyStationConfigRespVO {
- @Schema(description = "主键ID")
- private Long id;
- @Schema(description = "加氢站ID")
- private Long stationId;
- @Schema(description = "是否自动扣款")
- private Boolean autoDeduct;
- @Schema(description = "合作类型")
- private Integer cooperationType;
- @Schema(description = "站点名称")
- private String stationName;
- @Schema(description = "自动匹配开关")
- private Boolean autoMatch;
- @Schema(description = "备注")
- private String remark;
- @Schema(description = "创建时间")
- private LocalDateTime createTime;
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigSaveReqVO.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigSaveReqVO.java
deleted file mode 100644
index aaa4d13..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigSaveReqVO.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package cn.iocoder.yudao.module.energy.controller.admin.config.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
-import lombok.Data;
-
-@Schema(description = "管理后台 - 加氢站配置创建/更新 Request VO")
-@Data
-public class EnergyStationConfigSaveReqVO {
- @Schema(description = "主键ID")
- private Long id;
- @Schema(description = "加氢站ID", requiredMode = Schema.RequiredMode.REQUIRED)
- @NotNull(message = "加氢站不能为空")
- private Long stationId;
- @Schema(description = "是否自动扣款", requiredMode = Schema.RequiredMode.REQUIRED)
- @NotNull(message = "是否自动扣款不能为空")
- private Boolean autoDeduct;
- @Schema(description = "合作类型")
- private Integer cooperationType;
- @Schema(description = "自动匹配开关")
- private Boolean autoMatch;
- @Schema(description = "备注")
- private String remark;
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigSimpleVO.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigSimpleVO.java
deleted file mode 100644
index c8f327d..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/controller/admin/config/vo/EnergyStationConfigSimpleVO.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package cn.iocoder.yudao.module.energy.controller.admin.config.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Schema(description = "管理后台 - 加氢站配置简单 Response VO")
-@Data
-public class EnergyStationConfigSimpleVO {
- @Schema(description = "配置ID")
- private Long id;
- @Schema(description = "站点ID")
- private Long stationId;
- @Schema(description = "站点名称")
- private String stationName;
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/convert/config/EnergyStationConfigConvert.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/convert/config/EnergyStationConfigConvert.java
deleted file mode 100644
index 1cf4362..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/convert/config/EnergyStationConfigConvert.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.module.energy.convert.config;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigRespVO;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigSaveReqVO;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigSimpleVO;
-import cn.iocoder.yudao.module.energy.dal.dataobject.config.EnergyStationConfigDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-
-@Mapper
-public interface EnergyStationConfigConvert {
- EnergyStationConfigConvert INSTANCE = Mappers.getMapper(EnergyStationConfigConvert.class);
- EnergyStationConfigDO convert(EnergyStationConfigSaveReqVO bean);
- EnergyStationConfigRespVO convert(EnergyStationConfigDO bean);
- PageResult convertPage(PageResult page);
- List convertSimpleList(List list);
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/dal/dataobject/config/EnergyStationConfigDO.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/dal/dataobject/config/EnergyStationConfigDO.java
deleted file mode 100644
index 4a24f49..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/dal/dataobject/config/EnergyStationConfigDO.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package cn.iocoder.yudao.module.energy.dal.dataobject.config;
-
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.*;
-
-/**
- * 站点配置 DO
- */
-@TableName("energy_station_config")
-@KeySequence("energy_station_config_seq")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class EnergyStationConfigDO extends BaseDO {
-
- /**
- * 主键
- */
- @TableId
- private Long id;
-
- /**
- * 站点ID
- */
- private Long stationId;
-
- /**
- * 是否自动扣费
- */
- private Boolean autoDeduct;
-
- /**
- * 合作类型
- */
- private Integer cooperationType;
-
- /**
- * 自动匹配开关
- */
- private Boolean autoMatch;
-
- /**
- * 备注
- */
- private String remark;
-
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/dal/mysql/config/EnergyStationConfigMapper.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/dal/mysql/config/EnergyStationConfigMapper.java
deleted file mode 100644
index 6fa9481..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/dal/mysql/config/EnergyStationConfigMapper.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package cn.iocoder.yudao.module.energy.dal.mysql.config;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigPageReqVO;
-import cn.iocoder.yudao.module.energy.dal.dataobject.config.EnergyStationConfigDO;
-import org.apache.ibatis.annotations.Mapper;
-
-@Mapper
-public interface EnergyStationConfigMapper extends BaseMapperX {
-
- default EnergyStationConfigDO selectByStationId(Long stationId) {
- return selectOne(EnergyStationConfigDO::getStationId, stationId);
- }
-
- default PageResult selectPage(EnergyStationConfigPageReqVO reqVO) {
- return selectPage(reqVO, new LambdaQueryWrapperX()
- .eqIfPresent(EnergyStationConfigDO::getStationId, reqVO.getStationId())
- .eqIfPresent(EnergyStationConfigDO::getAutoDeduct, reqVO.getAutoDeduct())
- .eqIfPresent(EnergyStationConfigDO::getCooperationType, reqVO.getCooperationType())
- .orderByDesc(EnergyStationConfigDO::getId));
- }
-
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillApprovedEvent.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillAuditPassedEvent.java
similarity index 76%
rename from yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillApprovedEvent.java
rename to yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillAuditPassedEvent.java
index c7a3dcd..ae1804f 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillApprovedEvent.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillAuditPassedEvent.java
@@ -5,9 +5,12 @@ import lombok.Getter;
import java.util.List;
+/**
+ * 账单审核通过事件
+ */
@Getter
@AllArgsConstructor
-public class BillApprovedEvent {
+public class BillAuditPassedEvent {
private final Long billId;
private final List detailIds;
}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillCreatedEvent.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillCreatedEvent.java
deleted file mode 100644
index 3114600..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/BillCreatedEvent.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package cn.iocoder.yudao.module.energy.event;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.List;
-
-@Getter
-@AllArgsConstructor
-public class BillCreatedEvent {
- private final Long billId;
- private final List detailIds;
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DeductionCompletedEvent.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DeductionCompletedEvent.java
deleted file mode 100644
index d865a74..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DeductionCompletedEvent.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package cn.iocoder.yudao.module.energy.event;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-@Getter
-@AllArgsConstructor
-public class DeductionCompletedEvent {
- private final Long detailId;
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailCreatedEvent.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailAuditPassedEvent.java
similarity index 82%
rename from yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailCreatedEvent.java
rename to yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailAuditPassedEvent.java
index 0732db0..5f66923 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailCreatedEvent.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailAuditPassedEvent.java
@@ -5,9 +5,12 @@ import lombok.Getter;
import java.math.BigDecimal;
+/**
+ * 明细审核通过事件
+ */
@Getter
@AllArgsConstructor
-public class DetailCreatedEvent {
+public class DetailAuditPassedEvent {
private final Long detailId;
private final Long stationId;
private final Long customerId;
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailAuditedEvent.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailAuditedEvent.java
deleted file mode 100644
index ab3f2c0..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/DetailAuditedEvent.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.energy.event;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.math.BigDecimal;
-
-@Getter
-@AllArgsConstructor
-public class DetailAuditedEvent {
- private final Long detailId;
- private final Long stationId;
- private final Long customerId;
- private final Long contractId;
- private final BigDecimal customerAmount;
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/RecordMatchedEvent.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/RecordMatchedEvent.java
deleted file mode 100644
index 1394757..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/event/RecordMatchedEvent.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.energy.event;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-@Getter
-@AllArgsConstructor
-public class RecordMatchedEvent {
- private final Long recordId;
- private final Long stationId;
- private final Long vehicleId;
- private final Long customerId;
- private final String plateNumber;
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/AccountEventListener.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/AccountEventListener.java
index 5368552..5980e1e 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/AccountEventListener.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/AccountEventListener.java
@@ -1,10 +1,7 @@
package cn.iocoder.yudao.module.energy.listener;
-import cn.iocoder.yudao.module.energy.dal.dataobject.config.EnergyStationConfigDO;
-import cn.iocoder.yudao.module.energy.dal.mysql.config.EnergyStationConfigMapper;
import cn.iocoder.yudao.module.energy.enums.DeductionStatusEnum;
-import cn.iocoder.yudao.module.energy.event.DetailAuditedEvent;
-import cn.iocoder.yudao.module.energy.event.DetailCreatedEvent;
+import cn.iocoder.yudao.module.energy.event.DetailAuditPassedEvent;
import cn.iocoder.yudao.module.energy.service.account.EnergyAccountService;
import cn.iocoder.yudao.module.energy.service.detail.HydrogenDetailService;
import jakarta.annotation.Resource;
@@ -20,37 +17,23 @@ public class AccountEventListener {
@Resource
private EnergyAccountService accountService;
@Resource
- private EnergyStationConfigMapper stationConfigMapper;
- @Resource
private HydrogenDetailService detailService;
/**
- * 明细创建完成 → 若配置自动扣款则扣款
+ * 明细审核通过 → 执行扣款
* BEFORE_COMMIT: 资金操作,必须同事务
+ *
+ * 注意:此监听器处理审核后扣款场景(站点配置 auto_deduct=false)
+ * 自动扣款场景(auto_deduct=true)在明细创建时已完成
*/
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
- public void onDetailCreated(DetailCreatedEvent event) {
- EnergyStationConfigDO config = stationConfigMapper.selectByStationId(event.getStationId());
- if (config != null && Boolean.TRUE.equals(config.getAutoDeduct())) {
- log.info("[onDetailCreated] auto deduct: detailId={}, amount={}", event.getDetailId(), event.getCustomerAmount());
- accountService.deduct(event.getCustomerId(), event.getContractId(),
- event.getCustomerAmount(), event.getDetailId(), null, "加氢自动扣款");
- detailService.updateDeductionStatus(event.getDetailId(), DeductionStatusEnum.DEDUCTED.getStatus());
- }
- }
-
- /**
- * 明细审核通过 → 若配置审核后扣款则扣款
- * BEFORE_COMMIT: 资金操作,必须同事务
- */
- @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
- public void onDetailAudited(DetailAuditedEvent event) {
- EnergyStationConfigDO config = stationConfigMapper.selectByStationId(event.getStationId());
- if (config != null && !Boolean.TRUE.equals(config.getAutoDeduct())) {
- log.info("[onDetailAudited] audit-then-deduct: detailId={}, amount={}", event.getDetailId(), event.getCustomerAmount());
- accountService.deduct(event.getCustomerId(), event.getContractId(),
- event.getCustomerAmount(), event.getDetailId(), null, "审核后扣款");
- detailService.updateDeductionStatus(event.getDetailId(), DeductionStatusEnum.DEDUCTED.getStatus());
- }
+ public void onDetailAuditPassed(DetailAuditPassedEvent event) {
+ // TODO: 需要从 asset 模块获取站点配置判断是否需要扣款
+ // 当前简化实现:审核通过后统一扣款
+ log.info("[onDetailAuditPassed] deduct: detailId={}, amount={}",
+ event.getDetailId(), event.getCustomerAmount());
+ accountService.deduct(event.getCustomerId(), event.getContractId(),
+ event.getCustomerAmount(), event.getDetailId(), null, "审核后扣款");
+ detailService.updateDeductionStatus(event.getDetailId(), DeductionStatusEnum.DEDUCTED.getStatus());
}
}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/BillEventListener.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/BillEventListener.java
index 898735d..77c107e 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/BillEventListener.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/BillEventListener.java
@@ -1,8 +1,7 @@
package cn.iocoder.yudao.module.energy.listener;
import cn.iocoder.yudao.module.energy.enums.SettlementStatusEnum;
-import cn.iocoder.yudao.module.energy.event.BillApprovedEvent;
-import cn.iocoder.yudao.module.energy.event.BillCreatedEvent;
+import cn.iocoder.yudao.module.energy.event.BillAuditPassedEvent;
import cn.iocoder.yudao.module.energy.service.detail.HydrogenDetailService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -16,23 +15,15 @@ public class BillEventListener {
@Resource
private HydrogenDetailService detailService;
- /**
- * 账单生成完成 → 更新明细的 billId
- * AFTER_COMMIT: 非资金操作
- */
- @TransactionalEventListener
- public void onBillCreated(BillCreatedEvent event) {
- log.info("[onBillCreated] billId={}, detailCount={}", event.getBillId(), event.getDetailIds().size());
- detailService.updateBillId(event.getDetailIds(), event.getBillId());
- }
-
/**
* 账单审核通过 → 更新明细结算状态
* AFTER_COMMIT: 非资金操作
+ *
+ * 预留二期财务对接
*/
@TransactionalEventListener
- public void onBillApproved(BillApprovedEvent event) {
- log.info("[onBillApproved] billId={}", event.getBillId());
+ public void onBillAuditPassed(BillAuditPassedEvent event) {
+ log.info("[onBillAuditPassed] billId={}", event.getBillId());
detailService.updateSettlementStatus(event.getDetailIds(), SettlementStatusEnum.SETTLED.getStatus());
}
}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/DetailEventListener.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/DetailEventListener.java
index fff088e..5090706 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/DetailEventListener.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/DetailEventListener.java
@@ -1,13 +1,30 @@
package cn.iocoder.yudao.module.energy.listener;
+import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
+import cn.iocoder.yudao.module.asset.service.station.HydrogenStationService;
+import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
+import cn.iocoder.yudao.module.energy.dal.dataobject.price.EnergyStationPriceDO;
+import cn.iocoder.yudao.module.energy.dal.dataobject.record.EnergyHydrogenRecordDO;
+import cn.iocoder.yudao.module.energy.dal.mysql.detail.EnergyHydrogenDetailMapper;
+import cn.iocoder.yudao.module.energy.dal.mysql.record.EnergyHydrogenRecordMapper;
+import cn.iocoder.yudao.module.energy.enums.DeductionStatusEnum;
import cn.iocoder.yudao.module.energy.event.RecordImportedEvent;
-import cn.iocoder.yudao.module.energy.event.RecordMatchedEvent;
+import cn.iocoder.yudao.module.energy.service.account.EnergyAccountService;
import cn.iocoder.yudao.module.energy.service.detail.HydrogenDetailService;
+import cn.iocoder.yudao.module.energy.service.match.HydrogenMatchService;
+import cn.iocoder.yudao.module.energy.service.match.dto.MatchResultDTO;
+import cn.iocoder.yudao.module.energy.service.price.EnergyStationPriceService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+
@Component
@Slf4j
public class DetailEventListener {
@@ -15,26 +32,136 @@ public class DetailEventListener {
@Resource
private HydrogenDetailService hydrogenDetailService;
+ @Resource
+ private HydrogenMatchService hydrogenMatchService;
+
+ @Resource
+ private EnergyHydrogenRecordMapper hydrogenRecordMapper;
+
+ @Resource
+ private EnergyHydrogenDetailMapper hydrogenDetailMapper;
+
+ @Resource
+ private HydrogenStationService hydrogenStationService;
+
+ @Resource
+ private EnergyStationPriceService stationPriceService;
+
+ @Resource
+ private EnergyAccountService accountService;
+
/**
- * 记录导入完成 → 触发自动匹配(占位,后续实现批量自动匹配)
+ * 记录导入完成 → 触发自动匹配+生成明细
* AFTER_COMMIT: 非资金操作
*/
@TransactionalEventListener
+ @Transactional(rollbackFor = Exception.class)
public void onRecordImported(RecordImportedEvent event) {
- log.info("[onRecordImported] stationId={}, recordCount={}, batchNo={}",
+ log.info("[onRecordImported] 开始处理导入记录,stationId={}, recordCount={}, batchNo={}",
event.getStationId(), event.getRecordIds().size(), event.getBatchNo());
- // TODO: 后续接入 asset 模块实现批量自动匹配
- // hydrogenRecordService.batchMatch(event.getRecordIds());
+
+ // 1. 批量匹配
+ MatchResultDTO matchResult = hydrogenMatchService.batchMatch(event.getRecordIds());
+ log.info("[onRecordImported] 匹配完成,成功={}, 失败={}",
+ matchResult.getSuccessCount(), matchResult.getFailCount());
+
+ // 2. 批量生成明细(只为匹配成功的记录生成)
+ if (matchResult.getSuccessCount() > 0) {
+ List matchedRecords = hydrogenRecordMapper.selectBatchIds(matchResult.getSuccessIds());
+ List details = new ArrayList<>();
+
+ for (EnergyHydrogenRecordDO record : matchedRecords) {
+ EnergyHydrogenDetailDO detail = createDetailFromRecord(record, event.getStationId());
+ if (detail != null) {
+ details.add(detail);
+ }
+ }
+
+ if (!details.isEmpty()) {
+ // 批量插入明细
+ for (EnergyHydrogenDetailDO detail : details) {
+ hydrogenDetailMapper.insert(detail);
+ }
+ log.info("[onRecordImported] 生成明细完成,count={}", details.size());
+
+ // 3. 检查站点配置,决定是否自动扣款
+ HydrogenStationDO station = hydrogenStationService.getHydrogenStation(event.getStationId());
+ if (station != null && Boolean.TRUE.equals(station.getAutoDeduct())) {
+ log.info("[onRecordImported] 站点配置自动扣款,开始扣款流程");
+ for (EnergyHydrogenDetailDO detail : details) {
+ try {
+ accountService.deduct(
+ detail.getCustomerId(),
+ detail.getContractId(),
+ detail.getCustomerAmount(),
+ detail.getId(),
+ null,
+ "加氢自动扣款"
+ );
+ hydrogenDetailService.updateDeductionStatus(
+ detail.getId(),
+ DeductionStatusEnum.DEDUCTED.getStatus()
+ );
+ log.info("[onRecordImported] 扣款成功,detailId={}, amount={}",
+ detail.getId(), detail.getCustomerAmount());
+ } catch (Exception e) {
+ log.error("[onRecordImported] 扣款失败,detailId={}, error={}",
+ detail.getId(), e.getMessage(), e);
+ }
+ }
+ }
+ }
+ }
+
+ log.info("[onRecordImported] 处理完成");
}
/**
- * 记录匹配完成 → 创建明细
- * AFTER_COMMIT: 明细创建在独立事务中
+ * 从记录创建明细
*/
- @TransactionalEventListener
- public void onRecordMatched(RecordMatchedEvent event) {
- log.info("[onRecordMatched] recordId={}, vehicleId={}, customerId={}",
- event.getRecordId(), event.getVehicleId(), event.getCustomerId());
- hydrogenDetailService.createFromRecord(event);
+ private EnergyHydrogenDetailDO createDetailFromRecord(EnergyHydrogenRecordDO record, Long stationId) {
+ // 获取价格配置(使用客户ID和日期)
+ EnergyStationPriceDO price = stationPriceService.getEffectivePrice(
+ stationId,
+ record.getCustomerId(),
+ record.getHydrogenDate()
+ );
+ if (price == null) {
+ log.warn("[createDetailFromRecord] 未找到有效价格配置,recordId={}, stationId={}, customerId={}, date={}",
+ record.getId(), stationId, record.getCustomerId(), record.getHydrogenDate());
+ return null;
+ }
+
+ // 重新匹配获取合同ID(因为批量匹配时已经更新了record,但需要获取contractId)
+ MatchResultVO matchResult = hydrogenMatchService.matchRecord(record);
+
+ // 计算金额
+ BigDecimal costAmount = record.getHydrogenQuantity()
+ .multiply(price.getCostPrice())
+ .setScale(2, RoundingMode.HALF_UP);
+ BigDecimal customerAmount = record.getHydrogenQuantity()
+ .multiply(price.getCustomerPrice())
+ .setScale(2, RoundingMode.HALF_UP);
+
+ return EnergyHydrogenDetailDO.builder()
+ .recordId(record.getId())
+ .stationId(stationId)
+ .vehicleId(record.getVehicleId())
+ .plateNumber(record.getPlateNumber())
+ .hydrogenDate(record.getHydrogenDate())
+ .hydrogenQuantity(record.getHydrogenQuantity())
+ .costPrice(price.getCostPrice())
+ .costAmount(costAmount)
+ .customerPrice(price.getCustomerPrice())
+ .customerAmount(customerAmount)
+ .contractId(matchResult.getContractId()) // 从匹配结果获取合同ID
+ .customerId(record.getCustomerId())
+ .projectName(null) // 可以从合同获取,暂时为空
+ .costBearer(1) // 默认客户承担,可以从合同配置获取
+ .payMethod(1) // 默认预付,可以从合同配置获取
+ .auditStatus(0) // 待审核
+ .deductionStatus(0) // 未扣款
+ .settlementStatus(0) // 未结算
+ .build();
}
}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/bill/EnergyBillServiceImpl.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/bill/EnergyBillServiceImpl.java
index 8b3ca14..1ae4b97 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/bill/EnergyBillServiceImpl.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/bill/EnergyBillServiceImpl.java
@@ -12,8 +12,7 @@ import cn.iocoder.yudao.module.energy.dal.mysql.bill.EnergyBillAdjustmentMapper;
import cn.iocoder.yudao.module.energy.dal.mysql.bill.EnergyBillMapper;
import cn.iocoder.yudao.module.energy.dal.mysql.detail.EnergyHydrogenDetailMapper;
import cn.iocoder.yudao.module.energy.enums.*;
-import cn.iocoder.yudao.module.energy.event.BillApprovedEvent;
-import cn.iocoder.yudao.module.energy.event.BillCreatedEvent;
+import cn.iocoder.yudao.module.energy.event.BillAuditPassedEvent;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
@@ -91,9 +90,11 @@ public class EnergyBillServiceImpl implements EnergyBillService {
.build();
billMapper.insert(bill);
- // 4. Publish event to update detail billIds
+ // 4. Update detail billIds directly (no event needed)
List detailIds = details.stream().map(EnergyHydrogenDetailDO::getId).toList();
- eventPublisher.publishEvent(new BillCreatedEvent(bill.getId(), detailIds));
+ detailMapper.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper()
+ .set(EnergyHydrogenDetailDO::getBillId, bill.getId())
+ .in(EnergyHydrogenDetailDO::getId, detailIds));
return bill.getId();
}
@@ -160,7 +161,7 @@ public class EnergyBillServiceImpl implements EnergyBillService {
if (approved) {
List details = detailMapper.selectListByBillId(id);
List detailIds = details.stream().map(EnergyHydrogenDetailDO::getId).toList();
- eventPublisher.publishEvent(new BillApprovedEvent(id, detailIds));
+ eventPublisher.publishEvent(new BillAuditPassedEvent(id, detailIds));
}
}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/config/EnergyStationConfigService.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/config/EnergyStationConfigService.java
deleted file mode 100644
index 067250d..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/config/EnergyStationConfigService.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package cn.iocoder.yudao.module.energy.service.config;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigPageReqVO;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigSaveReqVO;
-import cn.iocoder.yudao.module.energy.dal.dataobject.config.EnergyStationConfigDO;
-
-import java.util.List;
-
-public interface EnergyStationConfigService {
- Long createConfig(EnergyStationConfigSaveReqVO createReqVO);
- void updateConfig(EnergyStationConfigSaveReqVO updateReqVO);
- EnergyStationConfigDO getConfig(Long id);
- PageResult getConfigPage(EnergyStationConfigPageReqVO pageReqVO);
- EnergyStationConfigDO getByStationId(Long stationId);
- List getConfigList();
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/config/EnergyStationConfigServiceImpl.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/config/EnergyStationConfigServiceImpl.java
deleted file mode 100644
index 5832e1b..0000000
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/config/EnergyStationConfigServiceImpl.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package cn.iocoder.yudao.module.energy.service.config;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigPageReqVO;
-import cn.iocoder.yudao.module.energy.controller.admin.config.vo.EnergyStationConfigSaveReqVO;
-import cn.iocoder.yudao.module.energy.convert.config.EnergyStationConfigConvert;
-import cn.iocoder.yudao.module.energy.dal.dataobject.config.EnergyStationConfigDO;
-import cn.iocoder.yudao.module.energy.dal.mysql.config.EnergyStationConfigMapper;
-import jakarta.annotation.Resource;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.energy.enums.ErrorCodeConstants.*;
-
-@Service
-@Validated
-public class EnergyStationConfigServiceImpl implements EnergyStationConfigService {
-
- @Resource
- private EnergyStationConfigMapper stationConfigMapper;
-
- @Override
- public Long createConfig(EnergyStationConfigSaveReqVO createReqVO) {
- // 校验站点唯一
- EnergyStationConfigDO existing = stationConfigMapper.selectByStationId(createReqVO.getStationId());
- if (existing != null) {
- throw exception(STATION_CONFIG_DUPLICATE);
- }
- EnergyStationConfigDO config = EnergyStationConfigConvert.INSTANCE.convert(createReqVO);
- stationConfigMapper.insert(config);
- return config.getId();
- }
-
- @Override
- public void updateConfig(EnergyStationConfigSaveReqVO updateReqVO) {
- validateConfigExists(updateReqVO.getId());
- EnergyStationConfigDO updateObj = EnergyStationConfigConvert.INSTANCE.convert(updateReqVO);
- stationConfigMapper.updateById(updateObj);
- }
-
- @Override
- public EnergyStationConfigDO getConfig(Long id) {
- return stationConfigMapper.selectById(id);
- }
-
- @Override
- public PageResult getConfigPage(EnergyStationConfigPageReqVO pageReqVO) {
- return stationConfigMapper.selectPage(pageReqVO);
- }
-
- @Override
- public EnergyStationConfigDO getByStationId(Long stationId) {
- return stationConfigMapper.selectByStationId(stationId);
- }
-
- @Override
- public List getConfigList() {
- return stationConfigMapper.selectList();
- }
-
- private void validateConfigExists(Long id) {
- if (stationConfigMapper.selectById(id) == null) {
- throw exception(STATION_CONFIG_NOT_EXISTS);
- }
- }
-}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/detail/HydrogenDetailService.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/detail/HydrogenDetailService.java
index f5dddac..06bc7ae 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/detail/HydrogenDetailService.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/detail/HydrogenDetailService.java
@@ -4,11 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailPageReqVO;
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailSaveReqVO;
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
-import cn.iocoder.yudao.module.energy.event.RecordMatchedEvent;
import java.util.List;
public interface HydrogenDetailService {
- void createFromRecord(RecordMatchedEvent event);
void updateDetail(HydrogenDetailSaveReqVO reqVO);
PageResult getDetailPage(HydrogenDetailPageReqVO pageReqVO);
EnergyHydrogenDetailDO getDetail(Long id);
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/detail/HydrogenDetailServiceImpl.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/detail/HydrogenDetailServiceImpl.java
index bc30c45..fe97d22 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/detail/HydrogenDetailServiceImpl.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/detail/HydrogenDetailServiceImpl.java
@@ -12,9 +12,7 @@ import cn.iocoder.yudao.module.energy.enums.AuditStatusEnum;
import cn.iocoder.yudao.module.energy.enums.CostBearerEnum;
import cn.iocoder.yudao.module.energy.enums.DeductionStatusEnum;
import cn.iocoder.yudao.module.energy.enums.PayMethodEnum;
-import cn.iocoder.yudao.module.energy.event.DetailAuditedEvent;
-import cn.iocoder.yudao.module.energy.event.DetailCreatedEvent;
-import cn.iocoder.yudao.module.energy.event.RecordMatchedEvent;
+import cn.iocoder.yudao.module.energy.event.DetailAuditPassedEvent;
import cn.iocoder.yudao.module.energy.service.price.EnergyStationPriceService;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
@@ -44,54 +42,6 @@ public class HydrogenDetailServiceImpl implements HydrogenDetailService {
@Resource
private ApplicationEventPublisher eventPublisher;
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void createFromRecord(RecordMatchedEvent event) {
- // 1. Get original record
- EnergyHydrogenRecordDO record = recordMapper.selectById(event.getRecordId());
- if (record == null) {
- log.warn("[createFromRecord] record not found: {}", event.getRecordId());
- return;
- }
-
- // 2. Get price
- EnergyStationPriceDO price = stationPriceService.getEffectivePrice(
- event.getStationId(), event.getCustomerId(), record.getHydrogenDate());
- BigDecimal costPrice = price != null ? price.getCostPrice() : record.getUnitPrice();
- BigDecimal customerPrice = price != null ? price.getCustomerPrice() : record.getUnitPrice();
-
- // 3. Calculate amounts
- BigDecimal costAmount = record.getHydrogenQuantity().multiply(costPrice).setScale(2, RoundingMode.HALF_UP);
- BigDecimal customerAmount = record.getHydrogenQuantity().multiply(customerPrice).setScale(2, RoundingMode.HALF_UP);
-
- // 4. Build detail - NOTE: contractId, costBearer, payMethod would normally come from asset/rental API
- // For now, use defaults. TODO: integrate with asset module for contract lookup
- EnergyHydrogenDetailDO detail = EnergyHydrogenDetailDO.builder()
- .recordId(event.getRecordId())
- .stationId(event.getStationId())
- .vehicleId(event.getVehicleId())
- .plateNumber(event.getPlateNumber())
- .hydrogenDate(record.getHydrogenDate())
- .hydrogenQuantity(record.getHydrogenQuantity())
- .costPrice(costPrice)
- .costAmount(costAmount)
- .customerPrice(customerPrice)
- .customerAmount(customerAmount)
- .customerId(event.getCustomerId())
- .costBearer(CostBearerEnum.CUSTOMER.getType())
- .payMethod(PayMethodEnum.PREPAID.getType())
- .auditStatus(AuditStatusEnum.PENDING.getStatus())
- .deductionStatus(DeductionStatusEnum.NOT_DEDUCTED.getStatus())
- .settlementStatus(0) // NOT_SETTLED
- .build();
- detailMapper.insert(detail);
-
- // 5. Publish event (BEFORE_COMMIT listener will handle auto-deduction if configured)
- eventPublisher.publishEvent(new DetailCreatedEvent(
- detail.getId(), event.getStationId(), event.getCustomerId(),
- detail.getContractId(), customerAmount));
- }
-
@Override
public void updateDetail(HydrogenDetailSaveReqVO reqVO) {
validateDetailExists(reqVO.getId());
@@ -134,7 +84,7 @@ public class HydrogenDetailServiceImpl implements HydrogenDetailService {
// If approved, publish event for deduction
if (approved) {
- eventPublisher.publishEvent(new DetailAuditedEvent(
+ eventPublisher.publishEvent(new DetailAuditPassedEvent(
id, detail.getStationId(), detail.getCustomerId(),
detail.getContractId(), detail.getCustomerAmount()));
}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/HydrogenMatchService.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/HydrogenMatchService.java
new file mode 100644
index 0000000..7b4d227
--- /dev/null
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/HydrogenMatchService.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.energy.service.match;
+
+import cn.iocoder.yudao.module.energy.dal.dataobject.record.EnergyHydrogenRecordDO;
+import cn.iocoder.yudao.module.energy.service.match.dto.MatchResultDTO;
+import cn.iocoder.yudao.module.energy.service.match.vo.MatchResultVO;
+
+import java.util.List;
+
+/**
+ * 加氢记录匹配服务
+ *
+ * @author 芋道源码
+ */
+public interface HydrogenMatchService {
+
+ /**
+ * 批量自动匹配记录
+ *
+ * @param recordIds 记录ID列表
+ * @return 匹配结果统计(成功数、失败数)
+ */
+ MatchResultDTO batchMatch(List recordIds);
+
+ /**
+ * 单条记录匹配
+ *
+ * @param record 原始记录
+ * @return 匹配结果(vehicleId, customerId, contractId)
+ */
+ MatchResultVO matchRecord(EnergyHydrogenRecordDO record);
+
+}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/HydrogenMatchServiceImpl.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/HydrogenMatchServiceImpl.java
new file mode 100644
index 0000000..8e5fffa
--- /dev/null
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/HydrogenMatchServiceImpl.java
@@ -0,0 +1,150 @@
+package cn.iocoder.yudao.module.energy.service.match;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractDO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.customer.CustomerDO;
+import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleBaseDO;
+import cn.iocoder.yudao.module.asset.dal.mysql.contract.ContractMapper;
+import cn.iocoder.yudao.module.asset.dal.mysql.customer.CustomerMapper;
+import cn.iocoder.yudao.module.asset.dal.mysql.vehicle.VehicleBaseMapper;
+import cn.iocoder.yudao.module.energy.dal.dataobject.record.EnergyHydrogenRecordDO;
+import cn.iocoder.yudao.module.energy.dal.mysql.record.EnergyHydrogenRecordMapper;
+import cn.iocoder.yudao.module.energy.service.match.dto.MatchResultDTO;
+import cn.iocoder.yudao.module.energy.service.match.vo.MatchResultVO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 加氢记录匹配服务实现
+ *
+ * @author 芋道源码
+ */
+@Service
+@Slf4j
+public class HydrogenMatchServiceImpl implements HydrogenMatchService {
+
+ @Resource
+ private EnergyHydrogenRecordMapper hydrogenRecordMapper;
+
+ @Resource
+ private VehicleBaseMapper vehicleBaseMapper;
+
+ @Resource
+ private CustomerMapper customerMapper;
+
+ @Resource
+ private ContractMapper contractMapper;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public MatchResultDTO batchMatch(List recordIds) {
+ MatchResultDTO result = new MatchResultDTO();
+
+ for (Long recordId : recordIds) {
+ EnergyHydrogenRecordDO record = hydrogenRecordMapper.selectById(recordId);
+ if (record == null) {
+ log.warn("[batchMatch] 记录不存在,recordId={}", recordId);
+ result.setFailCount(result.getFailCount() + 1);
+ result.getFailIds().add(recordId);
+ continue;
+ }
+
+ // 执行匹配
+ MatchResultVO matchResult = matchRecord(record);
+
+ // 更新记录
+ EnergyHydrogenRecordDO updateRecord = new EnergyHydrogenRecordDO();
+ updateRecord.setId(recordId);
+ updateRecord.setVehicleId(matchResult.getVehicleId());
+ updateRecord.setCustomerId(matchResult.getCustomerId());
+ updateRecord.setMatchStatus(matchResult.getMatchStatus());
+ updateRecord.setUpdateTime(LocalDateTime.now());
+
+ hydrogenRecordMapper.updateById(updateRecord);
+
+ // 统计结果
+ if (matchResult.getMatchStatus() == 0) {
+ result.setSuccessCount(result.getSuccessCount() + 1);
+ result.getSuccessIds().add(recordId);
+ log.info("[batchMatch] 匹配成功,recordId={}, vehicleId={}, customerId={}, contractId={}",
+ recordId, matchResult.getVehicleId(), matchResult.getCustomerId(), matchResult.getContractId());
+ } else {
+ result.setFailCount(result.getFailCount() + 1);
+ result.getFailIds().add(recordId);
+ log.warn("[batchMatch] 匹配失败,recordId={}, status={}, message={}",
+ recordId, matchResult.getMatchStatus(), matchResult.getMatchMessage());
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public MatchResultVO matchRecord(EnergyHydrogenRecordDO record) {
+ MatchResultVO result = new MatchResultVO();
+
+ // 1. 匹配车辆(通过车牌号)
+ if (StrUtil.isNotBlank(record.getPlateNumber())) {
+ VehicleBaseDO vehicle = vehicleBaseMapper.selectOne(
+ VehicleBaseDO::getPlateNo, record.getPlateNumber()
+ );
+ if (vehicle != null) {
+ result.setVehicleId(vehicle.getId());
+ log.debug("[matchRecord] 车辆匹配成功,plateNumber={}, vehicleId={}",
+ record.getPlateNumber(), vehicle.getId());
+ }
+ }
+
+ // 2. 匹配客户
+ // 2.1 如果车辆未匹配,尝试通过客户名称模糊匹配
+ if (result.getCustomerId() == null && StrUtil.isNotBlank(record.getPlateNumber())) {
+ // 注意:这里假设 plateNumber 字段可能包含客户信息,实际需要根据业务调整
+ // 如果 Record 有独立的 customerName 字段,应该使用那个字段
+ CustomerDO customer = customerMapper.selectOne(
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper()
+ .like(CustomerDO::getCustomerName, record.getPlateNumber())
+ .last("LIMIT 1")
+ );
+ if (customer != null) {
+ result.setCustomerId(customer.getId());
+ log.debug("[matchRecord] 客户匹配成功(通过名称),customerId={}", customer.getId());
+ }
+ }
+
+ // 3. 匹配合同(通过客户ID)
+ if (result.getCustomerId() != null) {
+ ContractDO contract = contractMapper.selectOne(
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper()
+ .eq(ContractDO::getCustomerId, result.getCustomerId())
+ .eq(ContractDO::getContractStatus, 2) // 2=进行中
+ .gt(ContractDO::getEndDate, record.getHydrogenDate())
+ .orderByDesc(ContractDO::getStartDate)
+ .last("LIMIT 1")
+ );
+ if (contract != null) {
+ result.setContractId(contract.getId());
+ log.debug("[matchRecord] 合同匹配成功,contractId={}", contract.getId());
+ }
+ }
+
+ // 4. 判断匹配状态
+ if (result.getVehicleId() != null && result.getCustomerId() != null && result.getContractId() != null) {
+ result.setMatchStatus(0); // 完全匹配
+ result.setMatchMessage("自动匹配成功");
+ } else if (result.getCustomerId() != null) {
+ result.setMatchStatus(1); // 部分匹配
+ result.setMatchMessage("客户匹配成功,但缺少车辆或合同信息");
+ } else {
+ result.setMatchStatus(2); // 未匹配
+ result.setMatchMessage("未找到匹配的客户");
+ }
+
+ return result;
+ }
+
+}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/dto/MatchResultDTO.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/dto/MatchResultDTO.java
new file mode 100644
index 0000000..c825621
--- /dev/null
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/dto/MatchResultDTO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.energy.service.match.dto;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 批量匹配结果 DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class MatchResultDTO {
+
+ /**
+ * 成功数量
+ */
+ private Integer successCount = 0;
+
+ /**
+ * 失败数量
+ */
+ private Integer failCount = 0;
+
+ /**
+ * 成功的记录ID列表
+ */
+ private List successIds = new ArrayList<>();
+
+ /**
+ * 失败的记录ID列表
+ */
+ private List failIds = new ArrayList<>();
+
+}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/vo/MatchResultVO.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/vo/MatchResultVO.java
new file mode 100644
index 0000000..9de9f90
--- /dev/null
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/vo/MatchResultVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.energy.service.match.vo;
+
+import lombok.Data;
+
+/**
+ * 匹配结果 VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class MatchResultVO {
+
+ /**
+ * 车辆ID
+ */
+ private Long vehicleId;
+
+ /**
+ * 客户ID
+ */
+ private Long customerId;
+
+ /**
+ * 合同ID
+ */
+ private Long contractId;
+
+ /**
+ * 匹配状态(0=完全匹配,1=部分匹配,2=未匹配)
+ */
+ private Integer matchStatus;
+
+ /**
+ * 匹配说明
+ */
+ private String matchMessage;
+
+}
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/record/HydrogenRecordServiceImpl.java b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/record/HydrogenRecordServiceImpl.java
index 8c44272..34c76e4 100644
--- a/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/record/HydrogenRecordServiceImpl.java
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/record/HydrogenRecordServiceImpl.java
@@ -14,7 +14,6 @@ import cn.iocoder.yudao.module.energy.dal.mysql.record.EnergyHydrogenRecordMappe
import cn.iocoder.yudao.module.energy.enums.MatchStatusEnum;
import cn.iocoder.yudao.module.energy.enums.SourceTypeEnum;
import cn.iocoder.yudao.module.energy.event.RecordImportedEvent;
-import cn.iocoder.yudao.module.energy.event.RecordMatchedEvent;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
@@ -132,8 +131,7 @@ public class HydrogenRecordServiceImpl implements HydrogenRecordService {
.customerId(customerId)
.matchStatus(MatchStatusEnum.MATCHED.getStatus())
.build());
- // Publish event
- eventPublisher.publishEvent(new RecordMatchedEvent(id, record.getStationId(), vehicleId, customerId, record.getPlateNumber()));
+ // 手动匹配功能保留,批量匹配和明细生成由 RecordImportedEvent 统一处理
}
@Override
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/resources/application-local.yaml b/yudao-module-energy/yudao-module-energy-server/src/main/resources/application-local.yaml
new file mode 100644
index 0000000..4ad5a20
--- /dev/null
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/resources/application-local.yaml
@@ -0,0 +1,107 @@
+server:
+ port: 48085
+
+spring:
+ application:
+ name: energy-server
+
+ # 允许 Bean 覆盖
+ main:
+ allow-bean-definition-overriding: true
+
+ # 禁用 Nacos 配置中心
+ cloud:
+ nacos:
+ config:
+ enabled: false
+ import-check:
+ enabled: false
+
+ # 数据源配置
+ datasource:
+ druid:
+ url: jdbc:mysql://47.103.115.36:3306/oneos_energy?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+ username: root
+ password: Passw0rd2026
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ initial-size: 5
+ min-idle: 5
+ max-active: 20
+ max-wait: 60000
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+
+ # Redis 配置
+ data:
+ redis:
+ host: 47.103.115.36
+ port: 6379
+ database: 0
+ password: Passw0rd2026
+ timeout: 10s
+ lettuce:
+ pool:
+ max-active: 8
+ max-idle: 8
+ min-idle: 0
+ max-wait: -1ms
+
+# MyBatis Plus 配置
+mybatis-plus:
+ configuration:
+ map-underscore-to-camel-case: true
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+ global-config:
+ db-config:
+ id-type: auto
+ logic-delete-field: deleted
+ logic-delete-value: 1
+ logic-not-delete-value: 0
+ type-aliases-package: cn.iocoder.yudao.module.energy.dal.dataobject
+
+# 日志配置
+logging:
+ level:
+ root: INFO
+ cn.iocoder.yudao.module.energy: DEBUG
+ cn.iocoder.yudao.framework: INFO
+ pattern:
+ console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'
+
+# 芋道配置
+yudao:
+ info:
+ version: 1.0.0
+ base-package: cn.iocoder.yudao.module.energy
+ web:
+ admin-api:
+ prefix: /admin-api
+ controller: '**.controller.admin.**'
+ admin-ui:
+ url: http://localhost:3000
+ security:
+ permit-all_urls: []
+ xss:
+ enable: false
+ access-log:
+ enable: true
+ error-code:
+ enable: true
+ demo: false
+ tenant:
+ enable: false
+
+# Swagger 配置
+springdoc:
+ api-docs:
+ enabled: true
+ path: /v3/api-docs
+ swagger-ui:
+ enabled: true
+ path: /swagger-ui.html
+
+knife4j:
+ enable: true
+ setting:
+ language: zh_cn
diff --git a/yudao-module-energy/yudao-module-energy-server/src/main/resources/application.yaml b/yudao-module-energy/yudao-module-energy-server/src/main/resources/application.yaml
new file mode 100644
index 0000000..ac178d2
--- /dev/null
+++ b/yudao-module-energy/yudao-module-energy-server/src/main/resources/application.yaml
@@ -0,0 +1,27 @@
+spring:
+ application:
+ name: energy-server
+
+ profiles:
+ active: dev
+
+ # 允许 Bean 覆盖
+ main:
+ allow-bean-definition-overriding: true
+
+ config:
+ import:
+ - optional:nacos:common-dev.yaml
+ - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
+
+ cloud:
+ nacos:
+ server-addr: ${NACOS_ADDR:localhost:8848}
+ namespace: ${NACOS_NAMESPACE:dev}
+ username: ${NACOS_USERNAME:nacos}
+ password: ${NACOS_PASSWORD:nacos}
+ discovery:
+ namespace: ${NACOS_NAMESPACE:dev}
+ config:
+ namespace: ${NACOS_NAMESPACE:dev}
+ file-extension: yaml
diff --git a/yudao-module-ocr/yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApi.java b/yudao-module-ocr/yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApi.java
index 27172ee..cb97198 100644
--- a/yudao-module-ocr/yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApi.java
+++ b/yudao-module-ocr/yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApi.java
@@ -1,14 +1,14 @@
package cn.iocoder.yudao.module.ocr.api;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseReqDTO;
import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseRespDTO;
import cn.iocoder.yudao.module.ocr.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestBody;
/**
* OCR 识别 API 接口
@@ -23,10 +23,7 @@ public interface OcrApi {
@PostMapping(PREFIX + "/vehicle-license")
@Operation(summary = "识别行驶证(提供给内部模块)")
- @Parameter(name = "imageData", description = "图片数据(Base64编码)", required = true)
- @Parameter(name = "provider", description = "OCR厂商(可选,默认使用配置的默认厂商)", example = "baidu")
CommonResult recognizeVehicleLicense(
- @RequestParam("imageData") String imageData,
- @RequestParam(value = "provider", required = false) String provider);
+ @RequestBody VehicleLicenseReqDTO reqDTO);
}
diff --git a/yudao-module-ocr/yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/dto/VehicleLicenseReqDTO.java b/yudao-module-ocr/yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/dto/VehicleLicenseReqDTO.java
new file mode 100644
index 0000000..44209e9
--- /dev/null
+++ b/yudao-module-ocr/yudao-module-ocr-api/src/main/java/cn/iocoder/yudao/module/ocr/api/dto/VehicleLicenseReqDTO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.ocr.api.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class VehicleLicenseReqDTO implements Serializable {
+
+ /** 图片数据(Base64编码) */
+ private String imageData;
+
+ /** OCR厂商(可选) */
+ private String provider;
+
+}
diff --git a/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApiImpl.java b/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApiImpl.java
index eda14fe..83853b0 100644
--- a/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApiImpl.java
+++ b/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/api/OcrApiImpl.java
@@ -1,25 +1,22 @@
package cn.iocoder.yudao.module.ocr.api;
-import cn.hutool.core.codec.Base64;
-import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseReqDTO;
import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseRespDTO;
-import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.OcrClient;
-import cn.iocoder.yudao.module.ocr.framework.ocr.core.client.OcrClientFactory;
+import cn.iocoder.yudao.module.ocr.convert.ocr.OcrConvert;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
+import cn.iocoder.yudao.module.ocr.service.ocr.OcrService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
+import java.util.Base64;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* OCR 识别 API 实现类
- *
- * @author 芋道源码
*/
@RestController
@Validated
@@ -27,37 +24,23 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
public class OcrApiImpl implements OcrApi {
@Resource
- private OcrClientFactory ocrClientFactory;
+ private OcrService ocrService;
@Override
- public CommonResult recognizeVehicleLicense(String imageData, String provider) {
- long startTime = System.currentTimeMillis();
+ public CommonResult recognizeVehicleLicense(VehicleLicenseReqDTO reqDTO) {
+ try {
+ // Base64 解码图片数据
+ byte[] imageBytes = Base64.getDecoder().decode(reqDTO.getImageData());
- log.info("[recognizeVehicleLicense][开始识别行驶证,provider:{},数据长度:{}]",
- provider, imageData != null ? imageData.length() : 0);
+ // 调用识别服务
+ VehicleLicenseResult result = ocrService.recognizeVehicleLicense(imageBytes, reqDTO.getProvider());
- // Base64 解码图片数据
- byte[] imageBytes = Base64.decode(imageData);
-
- // 获取 OCR 客户端
- OcrClient ocrClient;
- if (StrUtil.isNotBlank(provider)) {
- ocrClient = ocrClientFactory.getClient(provider);
- } else {
- ocrClient = ocrClientFactory.getDefaultClient();
+ // 转换并返回
+ return success(OcrConvert.INSTANCE.convertDto(result));
+ } catch (Exception e) {
+ log.error("[recognizeVehicleLicense][识别行驶证失败]", e);
+ throw new RuntimeException("识别行驶证失败: " + e.getMessage());
}
-
- // 调用识别
- VehicleLicenseResult result = ocrClient.recognizeVehicleLicense(imageBytes);
-
- // 转换为 DTO
- VehicleLicenseRespDTO respDTO = BeanUtils.toBean(result, VehicleLicenseRespDTO.class);
-
- long costTime = System.currentTimeMillis() - startTime;
- log.info("[recognizeVehicleLicense][识别完成,耗时:{}ms,VIN:{},车牌号:{}]",
- costTime, respDTO.getVin(), respDTO.getPlateNo());
-
- return success(respDTO);
}
}
diff --git a/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/convert/ocr/OcrConvert.java b/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/convert/ocr/OcrConvert.java
index bfef112..5e1a087 100644
--- a/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/convert/ocr/OcrConvert.java
+++ b/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/convert/ocr/OcrConvert.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.ocr.convert.ocr;
+import cn.iocoder.yudao.module.ocr.api.dto.VehicleLicenseRespDTO;
import cn.iocoder.yudao.module.ocr.controller.admin.ocr.vo.VehicleLicenseRespVO;
import cn.iocoder.yudao.module.ocr.framework.ocr.core.result.VehicleLicenseResult;
import org.mapstruct.Mapper;
@@ -14,8 +15,13 @@ public interface OcrConvert {
OcrConvert INSTANCE = Mappers.getMapper(OcrConvert.class);
/**
- * 转换行驶证识别结果
+ * 转换行驶证识别结果(Controller 使用)
*/
VehicleLicenseRespVO convert(VehicleLicenseResult result);
+ /**
+ * 转换行驶证识别结果(API 使用)
+ */
+ VehicleLicenseRespDTO convertDto(VehicleLicenseResult result);
+
}
diff --git a/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/framework/security/config/SecurityConfiguration.java b/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/framework/security/config/SecurityConfiguration.java
new file mode 100644
index 0000000..77de959
--- /dev/null
+++ b/yudao-module-ocr/yudao-module-ocr-server/src/main/java/cn/iocoder/yudao/module/ocr/framework/security/config/SecurityConfiguration.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.ocr.framework.security.config;
+
+import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
+
+/**
+ * OCR 模块的 Security 配置
+ */
+@Configuration
+public class SecurityConfiguration {
+
+ @Bean
+ public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
+ return new AuthorizeRequestsCustomizer() {
+ @Override
+ public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) {
+ // OCR 识别接口需要认证
+ registry.requestMatchers("/admin-api/ocr/**").authenticated();
+ }
+ };
+ }
+
+}
diff --git a/yudao-module-ocr/yudao-module-ocr-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-ocr/yudao-module-ocr-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..76ac1f3
--- /dev/null
+++ b/yudao-module-ocr/yudao-module-ocr-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+cn.iocoder.yudao.module.ocr.framework.ocr.config.OcrAutoConfiguration
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index b7d46d0..8dd3763 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -115,6 +115,13 @@
+
+
+ cn.iocoder.cloud
+ yudao-module-energy-server
+ ${revision}
+
+
org.springframework.boot