diff --git a/docs/sql/migrations/2026-09-26-pl-vehicle-recharge-workflow.sql b/docs/sql/migrations/2026-09-26-pl-vehicle-recharge-workflow.sql new file mode 100644 index 000000000..ba1bb5c9f --- /dev/null +++ b/docs/sql/migrations/2026-09-26-pl-vehicle-recharge-workflow.sql @@ -0,0 +1,435 @@ +-- 车辆 ETC/加油充值:可配置审批与财务发放登记 +-- Spec: docs/superpowers/specs/2026-09-26-pl-vehicle-recharge-workflow-design.md +-- Plan Task 1: 结构 + 字典 + 菜单 + 流程种子 + 存量 pending 迁移 +-- 可重复执行(列/菜单/字典/角色授权均做存在性判断) + +SET NAMES utf8mb4; + +-- ============================================================================= +-- 1) bx_workflow_node.action_type +-- ============================================================================= +SET @db := DATABASE(); +SET @col_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db + AND TABLE_NAME = 'bx_workflow_node' + AND COLUMN_NAME = 'action_type' +); +SET @sql := IF( + @col_exists = 0, + 'ALTER TABLE `bx_workflow_node` + ADD COLUMN `action_type` varchar(20) NOT NULL DEFAULT ''approve'' + COMMENT ''approve-审批 disburse-发放登记'' AFTER `approver_name`', + 'SELECT ''bx_workflow_node.action_type already exists'' AS info' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- ============================================================================= +-- 2) pl_vehicle_recharge_apply:wf_id / current_node / approval_round + status 注释 +-- ============================================================================= +SET @col_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db + AND TABLE_NAME = 'pl_vehicle_recharge_apply' + AND COLUMN_NAME = 'wf_id' +); +SET @sql := IF( + @col_exists = 0, + 'ALTER TABLE `pl_vehicle_recharge_apply` + ADD COLUMN `wf_id` varchar(64) DEFAULT NULL COMMENT ''工作流定义ID'' AFTER `status`', + 'SELECT ''pl_vehicle_recharge_apply.wf_id already exists'' AS info' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @col_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db + AND TABLE_NAME = 'pl_vehicle_recharge_apply' + AND COLUMN_NAME = 'current_node' +); +SET @sql := IF( + @col_exists = 0, + 'ALTER TABLE `pl_vehicle_recharge_apply` + ADD COLUMN `current_node` varchar(64) DEFAULT NULL COMMENT ''当前节点ID'' AFTER `wf_id`', + 'SELECT ''pl_vehicle_recharge_apply.current_node already exists'' AS info' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @col_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db + AND TABLE_NAME = 'pl_vehicle_recharge_apply' + AND COLUMN_NAME = 'approval_round' +); +SET @sql := IF( + @col_exists = 0, + 'ALTER TABLE `pl_vehicle_recharge_apply` + ADD COLUMN `approval_round` int(11) NOT NULL DEFAULT 0 COMMENT ''当前审批轮次'' AFTER `current_node`', + 'SELECT ''pl_vehicle_recharge_apply.approval_round already exists'' AS info' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +ALTER TABLE `pl_vehicle_recharge_apply` + MODIFY COLUMN `status` varchar(20) NOT NULL DEFAULT 'draft' + COMMENT 'draft-草稿 in_approval-审批中 rejected-已驳回 disbursed-已发放 cancelled-已作废'; + +-- ============================================================================= +-- 3) 审批历史表 +-- ============================================================================= +CREATE TABLE IF NOT EXISTS `pl_vehicle_recharge_approval` ( + `id` varchar(64) NOT NULL COMMENT '主键ID', + `apply_id` varchar(64) NOT NULL COMMENT '申请单ID', + `approval_round` int(11) NOT NULL DEFAULT 1 COMMENT '审批轮次', + `node_id` varchar(64) DEFAULT NULL COMMENT '节点ID快照', + `node_name` varchar(128) DEFAULT NULL COMMENT '节点名称快照', + `approver_id` varchar(64) NOT NULL COMMENT '审批人用户ID', + `approver_name` varchar(64) NOT NULL COMMENT '审批人姓名', + `approval_status` varchar(32) NOT NULL COMMENT 'approved/rejected/disbursed', + `approval_opinion` varchar(1024) DEFAULT NULL COMMENT '审批意见', + `approval_time` datetime NOT NULL COMMENT '审批时间', + `tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号', + `order_num` int(11) DEFAULT NULL COMMENT '排序号', + `remark` varchar(512) DEFAULT NULL COMMENT '备注', + `create_by` varchar(64) DEFAULT NULL COMMENT '创建人id', + `create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(64) DEFAULT NULL COMMENT '更新人id', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` smallint(6) NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除', + PRIMARY KEY (`id`), + KEY `idx_apply_id` (`apply_id`), + KEY `idx_tenant` (`tenant_no`), + KEY `idx_approver` (`approver_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆充值申请审批历史'; + +-- ============================================================================= +-- 4) 流程定义 + 节点种子 +-- ============================================================================= +INSERT INTO `bx_workflow_def` ( + `id`, `biz_type`, `wf_name`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `del_flag` +) +VALUES ( + 'PL_WF_VEHICLE_RECHARGE', 'vehicle_recharge', 'ETC/加油充值审批流程', 1, + 'shanxia2.0', 0, '充值申请默认审批流程', 'system', '', NOW(), 0 +) +ON DUPLICATE KEY UPDATE + `wf_name` = VALUES(`wf_name`), + `biz_type` = VALUES(`biz_type`), + `status` = VALUES(`status`), + `del_flag` = 0, + `remark` = VALUES(`remark`); + +INSERT INTO `bx_workflow_node` ( + `id`, `wf_id`, `node_name`, `node_order`, `approver_id`, `approver_name`, + `action_type`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `del_flag` +) +VALUES +( + 'PL_WR_NODE_GM', 'PL_WF_VEHICLE_RECHARGE', '总经理审批', 1, + '56D1C9DA5F6747DDAD25C248A156A524', '吴九华', 'approve', + 'shanxia2.0', 0, NULL, 'system', '', NOW(), 0 +), +( + 'PL_WR_NODE_FIN', 'PL_WF_VEHICLE_RECHARGE', '财务发放', 2, + 'CC2EED9A86F44D11AFFD0AD381893093', '汪璇', 'disburse', + 'shanxia2.0', 0, NULL, 'system', '', NOW(), 0 +) +ON DUPLICATE KEY UPDATE + `wf_id` = VALUES(`wf_id`), + `node_name` = VALUES(`node_name`), + `node_order` = VALUES(`node_order`), + `approver_id` = VALUES(`approver_id`), + `approver_name` = VALUES(`approver_name`), + `action_type` = VALUES(`action_type`), + `del_flag` = 0; + +-- ============================================================================= +-- 5) 字典 pl_recharge_apply_status +-- 软停用旧 pending;补齐 draft/in_approval/rejected;保留 disbursed/cancelled +-- ============================================================================= +UPDATE `sys_dict_data` +SET `del_flag` = 1, + `remark` = '充值审批改造:pending(待发放)已废弃,存量已迁移为 in_approval', + `update_time` = NOW() +WHERE `id` = 'PL_RECHARGE_APPLY_PENDING' + AND `dict_type` = 'pl_recharge_apply_status' + AND `del_flag` = 0; + +INSERT INTO `sys_dict_data` ( + `id`, `dict_label`, `dict_value`, `tag_type`, `dict_type`, `tenant_no`, + `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `del_flag` +) +SELECT + 'PL_RECHARGE_APPLY_DRAFT', '草稿', 'draft', 'info', 'pl_recharge_apply_status', + 'shanxia2.0', 1, NULL, 'system', '', NOW(), 0 +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_dict_data` WHERE `id` = 'PL_RECHARGE_APPLY_DRAFT' +); + +INSERT INTO `sys_dict_data` ( + `id`, `dict_label`, `dict_value`, `tag_type`, `dict_type`, `tenant_no`, + `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `del_flag` +) +SELECT + 'PL_RECHARGE_APPLY_IN_APPROVAL', '审批中', 'in_approval', 'warning', 'pl_recharge_apply_status', + 'shanxia2.0', 2, NULL, 'system', '', NOW(), 0 +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_dict_data` WHERE `id` = 'PL_RECHARGE_APPLY_IN_APPROVAL' +); + +INSERT INTO `sys_dict_data` ( + `id`, `dict_label`, `dict_value`, `tag_type`, `dict_type`, `tenant_no`, + `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `del_flag` +) +SELECT + 'PL_RECHARGE_APPLY_REJECTED', '已驳回', 'rejected', 'danger', 'pl_recharge_apply_status', + 'shanxia2.0', 3, NULL, 'system', '', NOW(), 0 +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_dict_data` WHERE `id` = 'PL_RECHARGE_APPLY_REJECTED' +); + +UPDATE `sys_dict_data` +SET `dict_label` = '已发放', + `tag_type` = 'success', + `order_num` = 4, + `del_flag` = 0, + `update_time` = NOW() +WHERE `id` = 'PL_RECHARGE_APPLY_DISBURSED' + AND `dict_type` = 'pl_recharge_apply_status'; + +UPDATE `sys_dict_data` +SET `dict_label` = '已作废', + `tag_type` = 'info', + `order_num` = 5, + `del_flag` = 0, + `update_time` = NOW() +WHERE `id` = 'PL_RECHARGE_APPLY_CANCELLED' + AND `dict_type` = 'pl_recharge_apply_status'; + +-- 幂等:若上述 INSERT 因已存在跳过,仍校正文案/排序/启用态 +UPDATE `sys_dict_data` +SET `dict_label` = '草稿', `tag_type` = 'info', `order_num` = 1, `del_flag` = 0, `update_time` = NOW() +WHERE `id` = 'PL_RECHARGE_APPLY_DRAFT'; + +UPDATE `sys_dict_data` +SET `dict_label` = '审批中', `tag_type` = 'warning', `order_num` = 2, `del_flag` = 0, `update_time` = NOW() +WHERE `id` = 'PL_RECHARGE_APPLY_IN_APPROVAL'; + +UPDATE `sys_dict_data` +SET `dict_label` = '已驳回', `tag_type` = 'danger', `order_num` = 3, `del_flag` = 0, `update_time` = NOW() +WHERE `id` = 'PL_RECHARGE_APPLY_REJECTED'; + +-- ============================================================================= +-- 6) 菜单:充值审批 / 充值审批流程(挂 PL_GROUP_VEHICLE) +-- 排序:申请(1) → 审批(2) → 发放登记(3) → 流程配置(4) → 其余顺延 +-- 仅当发放登记仍为 order_num=2(首次)时整体后移,避免重复执行叠位 +-- ============================================================================= +SET @record_ord := ( + SELECT `order_num` FROM `sys_menu` + WHERE `id` = 'PL_VEH_RECHARGE_RECORD' AND `del_flag` = 0 + LIMIT 1 +); +UPDATE `sys_menu` +SET `order_num` = `order_num` + 2, + `update_time` = NOW() +WHERE @record_ord = 2 + AND `parent_id` = 'PL_GROUP_VEHICLE' + AND `order_num` >= 2 + AND `del_flag` = 0 + AND `id` NOT IN ('PL_VEH_RECHARGE_APPROVAL', 'PL_VEH_RECHARGE_WF'); + +UPDATE `sys_menu` +SET `order_num` = 3, `update_time` = NOW() +WHERE `id` = 'PL_VEH_RECHARGE_RECORD' AND `del_flag` = 0; + +-- 充值审批(待办) +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT + 'PL_VEH_RECHARGE_APPROVAL', '充值审批', 'PL_GROUP_VEHICLE', 'C', + 'vehicle-recharge-approval', 'pl/vehicle-recharge-approval/index', + 'pl:vehicle-recharge-approval:list', 1, '1', '1', 'shanxia2.0', 2, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VEH_RECHARGE_APPROVAL' AND `del_flag` = 0 +); + +UPDATE `sys_menu` +SET `menu_name` = '充值审批', + `path` = 'vehicle-recharge-approval', + `component` = 'pl/vehicle-recharge-approval/index', + `perms` = 'pl:vehicle-recharge-approval:list', + `order_num` = 2, + `visible` = '1', + `status` = '1', + `update_time` = NOW() +WHERE `id` = 'PL_VEH_RECHARGE_APPROVAL' AND `del_flag` = 0; + +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT 'PL_VRAP_Q', '查询', 'PL_VEH_RECHARGE_APPROVAL', 'F', '', '', + 'pl:vehicle-recharge-approval:query', 1, '1', '1', 'shanxia2.0', 1, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VRAP_Q' AND `del_flag` = 0); + +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT 'PL_VRAP_A', '通过', 'PL_VEH_RECHARGE_APPROVAL', 'F', '', '', + 'pl:vehicle-recharge-approval:approve', 1, '1', '1', 'shanxia2.0', 2, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VRAP_A' AND `del_flag` = 0); + +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT 'PL_VRAP_J', '驳回', 'PL_VEH_RECHARGE_APPROVAL', 'F', '', '', + 'pl:vehicle-recharge-approval:reject', 1, '1', '1', 'shanxia2.0', 3, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VRAP_J' AND `del_flag` = 0); + +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT 'PL_VRAP_D', '发放', 'PL_VEH_RECHARGE_APPROVAL', 'F', '', '', + 'pl:vehicle-recharge-approval:disburse', 1, '1', '1', 'shanxia2.0', 4, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VRAP_D' AND `del_flag` = 0); + +-- 充值审批流程(配置) +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT + 'PL_VEH_RECHARGE_WF', '充值审批流程', 'PL_GROUP_VEHICLE', 'C', + 'vehicle-recharge-workflow', 'pl/vehicle-recharge-workflow/index', + 'pl:vehicle-recharge-workflow:list', 1, '1', '1', 'shanxia2.0', 4, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VEH_RECHARGE_WF' AND `del_flag` = 0 +); + +UPDATE `sys_menu` +SET `menu_name` = '充值审批流程', + `path` = 'vehicle-recharge-workflow', + `component` = 'pl/vehicle-recharge-workflow/index', + `perms` = 'pl:vehicle-recharge-workflow:list', + `order_num` = 4, + `visible` = '1', + `status` = '1', + `update_time` = NOW() +WHERE `id` = 'PL_VEH_RECHARGE_WF' AND `del_flag` = 0; + +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT 'PL_VRWF_Q', '查询', 'PL_VEH_RECHARGE_WF', 'F', '', '', + 'pl:vehicle-recharge-workflow:query', 1, '1', '1', 'shanxia2.0', 1, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VRWF_Q' AND `del_flag` = 0); + +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT 'PL_VRWF_E', '编辑', 'PL_VEH_RECHARGE_WF', 'F', '', '', + 'pl:vehicle-recharge-workflow:edit', 1, '1', '1', 'shanxia2.0', 2, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VRWF_E' AND `del_flag` = 0); + +INSERT INTO `sys_menu` ( + `id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, + `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT 'PL_VRWF_S', '保存', 'PL_VEH_RECHARGE_WF', 'F', '', '', + 'pl:vehicle-recharge-workflow:save', 1, '1', '1', 'shanxia2.0', 3, NULL, + 'system', '', NOW(), NULL, NOW(), 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `id` = 'PL_VRWF_S' AND `del_flag` = 0); + +-- 角色授权:已拥有充值申请(C 菜单或查询按钮)的角色同步获得新菜单 +INSERT INTO `sys_role_menu` ( + `id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`, + `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` +) +SELECT + MD5(CONCAT(rm.role_id, '|', m.menu_id, '|pl-veh-recharge-wf|v1')), + rm.role_id, + m.menu_id, + IFNULL(NULLIF(rm.tenant_no, ''), 'shanxia2.0'), + 0, + '充值审批改造:同步授权', + 'system', + '', + NOW(), + NULL, + NOW(), + 0 +FROM ( + SELECT DISTINCT `role_id`, `tenant_no` + FROM `sys_role_menu` + WHERE `menu_id` IN ('PL_VEH_RECHARGE_APPLY', 'PL_VRA_Q') + AND `del_flag` = 0 +) rm +JOIN ( + SELECT 'PL_VEH_RECHARGE_APPROVAL' AS menu_id UNION ALL + SELECT 'PL_VRAP_Q' UNION ALL + SELECT 'PL_VRAP_A' UNION ALL + SELECT 'PL_VRAP_J' UNION ALL + SELECT 'PL_VRAP_D' UNION ALL + SELECT 'PL_VEH_RECHARGE_WF' UNION ALL + SELECT 'PL_VRWF_Q' UNION ALL + SELECT 'PL_VRWF_E' UNION ALL + SELECT 'PL_VRWF_S' +) m +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_role_menu` x + WHERE x.role_id = rm.role_id AND x.menu_id = m.menu_id AND x.del_flag = 0 +); + +-- ============================================================================= +-- 7) 存量:pending 且无发放记录 → 审批中(落在财务发放节点) +-- ============================================================================= +UPDATE `pl_vehicle_recharge_apply` a +LEFT JOIN `pl_vehicle_recharge_record` r + ON r.apply_id = a.id AND r.del_flag = 0 +SET a.status = 'in_approval', + a.wf_id = 'PL_WF_VEHICLE_RECHARGE', + a.current_node = 'PL_WR_NODE_FIN', + a.approval_round = 1, + a.update_time = NOW() +WHERE a.del_flag = 0 + AND a.status = 'pending' + AND r.id IS NULL; diff --git a/docs/superpowers/plans/2026-09-26-pl-vehicle-recharge-workflow.md b/docs/superpowers/plans/2026-09-26-pl-vehicle-recharge-workflow.md new file mode 100644 index 000000000..fd28b53e9 --- /dev/null +++ b/docs/superpowers/plans/2026-09-26-pl-vehicle-recharge-workflow.md @@ -0,0 +1,475 @@ +# 车辆充值可配置审批与财务登记 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 为 ETC/加油充值申请接入仿报销的可配置线性审批;总经理通过/驳回;财务在「充值审批」待办中驳回或发放登记归档;申请详情展示全部审批历史与发放信息(仅 Web)。 + +**Architecture:** 复用 `bx_workflow_def` / `bx_workflow_node`(`biz_type=vehicle_recharge`,节点 `action_type=approve|disburse`);申请表扩展 `wf_id`/`current_node`/`approval_round` 与新状态枚举;新建 `pl_vehicle_recharge_approval` 只追加历史;审批运行时在 `pl_vehicle_recharge_approval_service`;前端新增流程配置页与「充值审批」待办页,改造申请列表/详情。 + +**Tech Stack:** FastAPI + SQLAlchemy + Pydantic v2;Vue 3 + Element Plus;参考 `bx/workflow/reimburse`、`bx/reimburse` submit/approve/reject。 + +**Spec:** `docs/superpowers/specs/2026-09-26-pl-vehicle-recharge-workflow-design.md` + +**已知用户 ID(种子审批人,与现网一致):** + +| 姓名 | sys_user.id | +|------|-------------| +| 吴九华 | `56D1C9DA5F6747DDAD25C248A156A524` | +| 汪璇 | `CC2EED9A86F44D11AFFD0AD381893093` | + +--- + +## 文件结构 + +| 文件 | 职责 | +|------|------| +| `docs/sql/migrations/2026-09-26-pl-vehicle-recharge-workflow.sql` | DDL、字典、菜单、流程种子、存量 `pending` 迁移 | +| `backend/app/models/bx_models.py` | `BxWorkflowNode.action_type` | +| `backend/app/models/pl_models.py` | 申请字段扩展 + `PlVehicleRechargeApproval` | +| `backend/app/schemas/pl/vehicle_recharge.py` | 状态/提交/审批/发放 body;详情含 approvals/record | +| `backend/app/services/bx/workflow.py` | `_DEFAULT_WORKFLOWS` 增加 `vehicle_recharge`;保存节点校验 `action_type` | +| `backend/app/services/pl_vehicle_recharge_service.py` | 创建 draft、编辑限制、submit、详情组装、发放页互斥 | +| `backend/app/services/pl_vehicle_recharge_approval_service.py` | pending/done、approve、reject、disburse | +| `backend/app/api/pl/vehicle_recharge.py` | 新路由挂载 | +| `backend/tests/test_pl_vehicle_recharge_workflow.py` | 状态机与审批单元测试 | +| `web-admin/src/api/pl/index.ts` | API 方法 | +| `web-admin/src/utils/pl-display.ts` | 新状态文案/Tag | +| `web-admin/src/views/pl/vehicle-recharge-workflow/index.vue` | 流程配置(仿报销 + action_type) | +| `web-admin/src/views/pl/vehicle-recharge-approval/index.vue` | 待办/已办 | +| `web-admin/src/views/pl/vehicle-recharge-apply/index.vue` | 提交按钮、状态、详情分区 | +| `web-admin/src/views/pl/vehicle-recharge-record/index.vue` | 有申请走审批的禁止再发放 | +| `web-admin/src/router/modules/pl.ts` | 路由 | +| `web-admin/src/App.vue` | `panelTitleBarTextRules` 新标题 | + +**API 前缀(与现有一致):** `/api/transport/vehicle-recharge-apply/*`、`/api/transport/vehicle-recharge-record/*`;审批建议同文件或 `/api/transport/vehicle-recharge-approval/*`。 + +--- + +### Task 1: 迁移 SQL(结构 + 种子 + 存量) + +**Files:** +- Create: `docs/sql/migrations/2026-09-26-pl-vehicle-recharge-workflow.sql` + +- [ ] **Step 1: 编写迁移脚本核心内容** + +```sql +SET NAMES utf8mb4; + +-- 1) 节点动作 +ALTER TABLE `bx_workflow_node` + ADD COLUMN `action_type` varchar(20) NOT NULL DEFAULT 'approve' + COMMENT 'approve-审批 disburse-发放登记' AFTER `approver_name`; + +-- 2) 申请扩展 +ALTER TABLE `pl_vehicle_recharge_apply` + ADD COLUMN `wf_id` varchar(64) DEFAULT NULL COMMENT '工作流定义ID' AFTER `status`, + ADD COLUMN `current_node` varchar(64) DEFAULT NULL COMMENT '当前节点ID' AFTER `wf_id`, + ADD COLUMN `approval_round` int(11) NOT NULL DEFAULT 0 COMMENT '当前审批轮次' AFTER `current_node`; + +ALTER TABLE `pl_vehicle_recharge_apply` + MODIFY COLUMN `status` varchar(20) NOT NULL DEFAULT 'draft' + COMMENT 'draft/in_approval/rejected/disbursed/cancelled'; + +-- 3) 审批历史表 +CREATE TABLE IF NOT EXISTS `pl_vehicle_recharge_approval` ( + `id` varchar(64) NOT NULL, + `apply_id` varchar(64) NOT NULL, + `approval_round` int(11) NOT NULL DEFAULT 1, + `node_id` varchar(64) DEFAULT NULL, + `node_name` varchar(128) DEFAULT NULL, + `approver_id` varchar(64) NOT NULL, + `approver_name` varchar(64) NOT NULL, + `approval_status` varchar(32) NOT NULL COMMENT 'approved/rejected/disbursed', + `approval_opinion` varchar(1024) DEFAULT NULL, + `approval_time` datetime NOT NULL, + `tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0', + `order_num` int(11) DEFAULT NULL, + `remark` varchar(512) DEFAULT NULL, + `create_by` varchar(64) DEFAULT NULL, + `create_dept_id` varchar(64) NOT NULL DEFAULT '', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `del_flag` smallint(6) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_apply_id` (`apply_id`), + KEY `idx_tenant` (`tenant_no`), + KEY `idx_approver` (`approver_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆充值申请审批历史'; + +-- 4) 流程定义种子 +INSERT INTO `bx_workflow_def` (`id`, `biz_type`, `wf_name`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `del_flag`) +VALUES ('PL_WF_VEHICLE_RECHARGE', 'vehicle_recharge', 'ETC/加油充值审批流程', 1, 'shanxia2.0', 0, '充值申请默认审批流程', 'system', '', NOW(), 0) +ON DUPLICATE KEY UPDATE `wf_name`=VALUES(`wf_name`), `del_flag`=0; + +INSERT INTO `bx_workflow_node` (`id`, `wf_id`, `node_name`, `node_order`, `approver_id`, `approver_name`, `action_type`, `tenant_no`, `create_by`, `create_dept_id`, `create_time`, `del_flag`) +VALUES +('PL_WR_NODE_GM', 'PL_WF_VEHICLE_RECHARGE', '总经理审批', 1, '56D1C9DA5F6747DDAD25C248A156A524', '吴九华', 'approve', 'shanxia2.0', 'system', '', NOW(), 0), +('PL_WR_NODE_FIN', 'PL_WF_VEHICLE_RECHARGE', '财务发放', 2, 'CC2EED9A86F44D11AFFD0AD381893093', '汪璇', 'disburse', 'shanxia2.0', 'system', '', NOW(), 0) +ON DUPLICATE KEY UPDATE `action_type`=VALUES(`action_type`), `del_flag`=0; + +-- 5) 字典:停用旧 pending 文案或改值;插入 draft/in_approval/rejected(保留 disbursed/cancelled) +-- 按现有 pl_recharge_apply_status 字典 ID 约定写入(实施时查库对齐 ID) + +-- 6) 菜单:PL_VEH_RECHARGE_APPROVAL、PL_VEH_RECHARGE_WF 及按钮权限,parent=PL_GROUP_VEHICLE +-- order_num 插在申请与发放之间或之后,实施时看现有 order + +-- 7) 存量迁移 +UPDATE `pl_vehicle_recharge_apply` a +LEFT JOIN `pl_vehicle_recharge_record` r + ON r.apply_id = a.id AND r.del_flag = 0 +SET a.status = 'in_approval', + a.wf_id = 'PL_WF_VEHICLE_RECHARGE', + a.current_node = 'PL_WR_NODE_FIN', + a.approval_round = 1, + a.update_time = NOW() +WHERE a.del_flag = 0 + AND a.status = 'pending' + AND r.id IS NULL; +``` + +- [ ] **Step 2: 在测试库执行并核对** + +```bash +cd backend && ./venv/bin/python -c " +# 用 .env DATABASE_URL 执行上述 SQL 文件;SELECT 校验 action_type 列、种子节点、pending 迁移行数 +" +``` + +Expected: 列存在;两节点;原 pending 无发放单变为 `in_approval` + `PL_WR_NODE_FIN`。 + +- [ ] **Step 3: 生产库执行同一脚本**(用户确认后) + +- [ ] **Step 4: Commit** + +```bash +git add docs/sql/migrations/2026-09-26-pl-vehicle-recharge-workflow.sql +git commit -m "$(cat <<'EOF' +docs: add vehicle recharge workflow migration SQL + +EOF +)" +``` + +--- + +### Task 2: Model + Schema + 工作流默认类型 + +**Files:** +- Modify: `backend/app/models/bx_models.py`(`BxWorkflowNode` 加 `action_type`) +- Modify: `backend/app/models/pl_models.py`(申请三字段 + `PlVehicleRechargeApproval`) +- Modify: `backend/app/schemas/pl/vehicle_recharge.py` +- Modify: `backend/app/services/bx/workflow.py`(`_DEFAULT_WORKFLOWS` + 保存节点时校验至多一个 `disburse`) + +- [ ] **Step 1: 写失败测试(状态常量 / schema)** + +```python +# backend/tests/test_pl_vehicle_recharge_workflow.py +from app.schemas.pl.vehicle_recharge import APPLY_STATUSES, RECHARGE_TYPES + +def test_apply_statuses_include_workflow_states(): + assert {"draft", "in_approval", "rejected", "disbursed", "cancelled"} <= APPLY_STATUSES +``` + +- [ ] **Step 2: 跑测确认失败** + +```bash +cd backend && ./venv/bin/python -m pytest tests/test_pl_vehicle_recharge_workflow.py::test_apply_statuses_include_workflow_states -v +``` + +Expected: FAIL(`APPLY_STATUSES` 未定义或仍是旧集合) + +- [ ] **Step 3: 实现 schema 常量与 Read 扩展字段** + +在 `vehicle_recharge.py` 增加: + +```python +APPLY_STATUSES = {"draft", "in_approval", "rejected", "disbursed", "cancelled"} +NODE_ACTION_TYPES = {"approve", "disburse"} + +# VehicleRechargeApplyRead 增加: +# wfId, currentNode, approvalRound, approvals: list[ApprovalRead], record: Optional[RecordRead] +``` + +Model 字段与 spec §5 一致;`WorkflowService._DEFAULT_WORKFLOWS["vehicle_recharge"] = {...}`。 + +保存节点时: + +```python +disburse_count = sum(1 for n in nodes if (n.get("actionType") or n.get("action_type") or "approve") == "disburse") +if disburse_count > 1: + raise HTTPException(400, detail="发放登记节点至多一个") +``` + +- [ ] **Step 4: 跑测通过 + Commit** + +```bash +cd backend && ./venv/bin/python -m pytest tests/test_pl_vehicle_recharge_workflow.py -v +git add backend/app/models backend/app/schemas/pl/vehicle_recharge.py backend/app/services/bx/workflow.py backend/tests/test_pl_vehicle_recharge_workflow.py +git commit -m "$(cat <<'EOF' +feat: add recharge workflow models and schema statuses + +EOF +)" +``` + +--- + +### Task 3: 申请 submit / 编辑规则 / 详情含审批历史 + +**Files:** +- Modify: `backend/app/services/pl_vehicle_recharge_service.py` +- Modify: `backend/app/api/pl/vehicle_recharge.py` + +- [ ] **Step 1: 测试 submit 与再提交 round** + +```python +def test_submit_sets_in_approval_and_first_node(monkeypatch): + # 可用 sqlite + stub WorkflowService.ensure_by_biz_type 返回带 nodes 的 def + # 断言 status==in_approval, approval_round==1, current_node==首节点 id + ... + +def test_resubmit_increments_round_keeps_history(): + # 先 insert 一条 rejected 审批历史;resubmit 后 round==2 且历史仍在 + ... +``` + +- [ ] **Step 2: 实现** + +- `create_apply`:`status="draft"`,`approval_round=0`,`wf_id/current_node=None` +- `update_apply` / `cancel`:仅 `draft`/`rejected` +- `submit_apply(db, tenant, user, apply_id)`: + - 校验状态 ∈ {draft, rejected} + - `ensure_by_biz_type(..., "vehicle_recharge")`,取 `node_order` 最小节点 + - `approval_round = (row.approval_round or 0) + 1` + - `status=in_approval`,绑定 `wf_id`、`current_node` +- `get_apply` / `apply_to_read`:附带 `approvals`(按 round、time)与关联 `record` + +- [ ] **Step 3: 路由** + +```python +@router.post("/{apply_id}/submit") +def submit_apply(...): + return ok(svc.submit_apply(...)) +``` + +- [ ] **Step 4: pytest + Commit** + +```bash +cd backend && ./venv/bin/python -m pytest tests/test_pl_vehicle_recharge_workflow.py -v +git commit -m "$(cat <<'EOF' +feat: add vehicle recharge apply submit and detail approvals + +EOF +)" +``` + +--- + +### Task 4: 审批服务 approve / reject / disburse + 待办列表 + +**Files:** +- Create: `backend/app/services/pl_vehicle_recharge_approval_service.py` +- Modify: `backend/app/api/pl/vehicle_recharge.py`(或新建 `vehicle_recharge_approval.py` 并注册) + +- [ ] **Step 1: 测试** + +```python +def test_approve_advances_to_next_node(): ... +def test_reject_sets_rejected_and_appends_history(): ... +def test_disburse_writes_record_and_disbursed(): ... +def test_approve_forbidden_on_disburse_node(): ... +``` + +- [ ] **Step 2: 实现核心逻辑** + +```python +def _current_node_row(db, apply) -> BxWorkflowNode: + node = db.query(BxWorkflowNode).filter( + BxWorkflowNode.id == apply.current_node, + BxWorkflowNode.del_flag == 0, + ).first() + if not node or node.approver_id != user.id: + raise HTTPException(403, detail="无权审批或节点不存在") + return node + +def approve(...): + node = _current_node_row(...) + if (node.action_type or "approve") != "approve": + raise HTTPException(400, detail="当前节点请使用发放登记") + # 写 approval approved;找下一 node_order;无下一节点则报错(须有 disburse 收尾) + ... + +def reject(..., opinion: str): + # 写 rejected;apply.status=rejected;current_node=None + ... + +def disburse(..., payload: VehicleRechargeRecordCreate): + node = _current_node_row(...) + if node.action_type != "disburse": + raise HTTPException(400, detail="当前节点不是发放节点") + # 复用 create_record 核心(强制 apply_id=本单);写 approval_status=disbursed;apply.status=disbursed + ... + +def list_pending_for_me(...): + # join apply + node where status=in_approval and node.approver_id=me + ... + +def list_done_for_me(...): + # distinct apply_id from approval where approver_id=me + ... +``` + +发放页 `create_record`:若 `apply_id` 非空且申请 `status=in_approval`(走审批),拒绝,提示去「充值审批」处理。 + +- [ ] **Step 3: pytest + Commit** + +```bash +git commit -m "$(cat <<'EOF' +feat: add vehicle recharge approval pending approve reject disburse + +EOF +)" +``` + +--- + +### Task 5: 前端 API + 展示工具 + +**Files:** +- Modify: `web-admin/src/api/pl/index.ts` +- Modify: `web-admin/src/utils/pl-display.ts` + +- [ ] **Step 1: 类型与方法** + +```ts +// status: 'draft' | 'in_approval' | 'rejected' | 'disbursed' | 'cancelled' +submitVehicleRechargeApply: (id: string) => request.post(`/transport/vehicle-recharge-apply/${id}/submit`) +fetchRechargeApprovalPending: (params) => request.get('/transport/vehicle-recharge-approval/pending', { params }) +fetchRechargeApprovalDone: (params) => request.get('/transport/vehicle-recharge-approval/done', { params }) +approveVehicleRecharge: (id, data) => request.post(`/transport/vehicle-recharge-approval/${id}/approve`, data) +rejectVehicleRecharge: (id, data) => request.post(`/transport/vehicle-recharge-approval/${id}/reject`, data) +disburseVehicleRecharge: (id, data) => request.post(`/transport/vehicle-recharge-approval/${id}/disburse`, data) +// 流程:复用 bx workflow API bizType=vehicle_recharge,或 PL 封装 +``` + +- [ ] **Step 2: `getPlRechargeApplyStatusLabel` / TagType 映射新五态** + +- [ ] **Step 3: Commit** + +```bash +git commit -m "$(cat <<'EOF' +feat: add web-admin APIs for recharge workflow + +EOF +)" +``` + +--- + +### Task 6: 流程配置页 + +**Files:** +- Create: `web-admin/src/views/pl/vehicle-recharge-workflow/index.vue`(以 `bx/workflow/reimburse/index.vue` 为模板复制精简) +- Modify: `web-admin/src/router/modules/pl.ts` +- Modify: `web-admin/src/App.vue`(标题「充值审批流程」) + +- [ ] **Step 1: 复制报销配置页,改 `bizType` 为 `vehicle_recharge`** +- [ ] **Step 2: 节点表单增加「节点动作」:`approve` / `disburse`** +- [ ] **Step 3: 路由 `vehicle-recharge-workflow`,meta.title=`充值审批流程`** +- [ ] **Step 4: 本地打开配置页,保存两节点,刷新仍在** +- [ ] **Step 5: Commit** + +```bash +git commit -m "$(cat <<'EOF' +feat: add vehicle recharge workflow config page + +EOF +)" +``` + +--- + +### Task 7: 充值审批待办页 + +**Files:** +- Create: `web-admin/src/views/pl/vehicle-recharge-approval/index.vue` +- Modify: `web-admin/src/router/modules/pl.ts`、`App.vue` + +- [ ] **Step 1: P01 列表 + Tab「待我审批」「我已办」** +- [ ] **Step 2: 详情弹窗:申请信息 + 按轮次审批历史 + 发放信息(有则显示)** +- [ ] **Step 3: 待办操作区** + - `action_type=approve`:通过 / 驳回(意见) + - `action_type=disburse`:驳回 / 发放表单(字段对齐现 `vehicle-recharge-record` 弹窗) +- [ ] **Step 4: 角标(可选):`pl_menu_badge` 增加充值审批 pending 计数,或本页自行展示条数** +- [ ] **Step 5: Commit** + +```bash +git commit -m "$(cat <<'EOF' +feat: add vehicle recharge approval todo page + +EOF +)" +``` + +--- + +### Task 8: 改造申请页与发放页 + +**Files:** +- Modify: `web-admin/src/views/pl/vehicle-recharge-apply/index.vue` +- Modify: `web-admin/src/views/pl/vehicle-recharge-apply/columnFilterDefs.ts` +- Modify: `web-admin/src/views/pl/vehicle-recharge-record/index.vue` + +- [ ] **Step 1: 申请列表状态筛选项换新五态;工具栏增加「提交」(选中 draft/rejected)** +- [ ] **Step 2: 新建默认 draft;编辑仅 draft/rejected** +- [ ] **Step 3: 查看弹窗增加「审核记录」「发放信息」分区** +- [ ] **Step 4: 发放看板:对 `in_approval` 申请隐藏/禁用「发放」,提示走充值审批** +- [ ] **Step 5: Commit** + +```bash +git commit -m "$(cat <<'EOF' +feat: wire recharge apply submit and view approval history + +EOF +)" +``` + +--- + +### Task 9: 联调验收 + 图谱更新 + +- [ ] **Step 1: 按 spec §9 走通:提交 → 吴九华通过 → 汪璇登记 → 详情可见历史与发放** +- [ ] **Step 2: 驳回 → 改单再提 → 第 2 轮历史仍在** +- [ ] **Step 3: `./scripts/update-graph.sh all`(或 backend+frontend)** +- [ ] **Step 4: 将设计文档状态保持「已批准」;计划任务勾选完成** + +--- + +## Spec 覆盖自检 + +| Spec 项 | Task | +|---------|------| +| D1 仅 Web | 全任务无小程序 | +| D2 仿报销配置 | Task 2/6 | +| D3 共用流程 | `biz_type=vehicle_recharge` 一套 | +| D4 财务登记或驳回 | Task 4/7 | +| D5 独立充值审批菜单 | Task 1/7 | +| D6 驳回再提 | Task 3 | +| D7 历史保留 | Task 3/4 只追加 | +| D8 详情展示 | Task 7/8 | +| 存量 pending 迁移 | Task 1 | +| 发放页互斥 | Task 4/8 | + +--- + +## 执行交接 + +Plan complete and saved to `docs/superpowers/plans/2026-09-26-pl-vehicle-recharge-workflow.md`. + +**两种执行方式:** + +1. **Subagent-Driven(推荐)** — 每任务独立子代理,任务间复查 +2. **Inline Execution** — 本会话按 executing-plans 连续推进并设检查点 + +选哪种? diff --git a/docs/superpowers/specs/2026-09-26-pl-vehicle-recharge-workflow-design.md b/docs/superpowers/specs/2026-09-26-pl-vehicle-recharge-workflow-design.md new file mode 100644 index 000000000..fe982e2ad --- /dev/null +++ b/docs/superpowers/specs/2026-09-26-pl-vehicle-recharge-workflow-design.md @@ -0,0 +1,277 @@ +# 车辆 ETC/加油充值 — 可配置审批与财务登记 — 设计文档 + +> **日期**: 2026-09-26 +> **状态**: 已批准 +> **前置**: +> - `docs/superpowers/specs/2026-06-29-pl-vehicle-recharge-design.md`(原申请/发放,无审批) +> - `docs/superpowers/specs/2026-09-26-pl-vehicle-recharge-restore-fuel-design.md`(恢复加油类型) +> **界面模式**: 申请列表 P01;审批待办 P01;流程配置对齐报销工作流配置页;发放登记保留查询/补录 +> **参考实现**: `web-admin/src/views/bx/workflow/reimburse/index.vue`、`backend/app/services/bx/reimburse.py`(submit/approve/reject) + +--- + +## 1. 背景与目标 + +客户梳理的充值业务链路: + +1. 司机(或内勤代提)提交 **加油充值 / ETC 充值** 申请 +2. **总经理**审核:通过或驳回 +3. 总经理通过后,流转到 **财务**:可 **驳回**,或 **填写并发放登记**(登记成功即本节点通过并归档) +4. 申请人在申请详情中可见 **全部审核信息** 与 **发放信息**(含打回再提后的历史) + +### 已确认决策 + +| # | 项 | 决策 | +|---|-----|------| +| D1 | 实现范围 | **仅 Web**;小程序端本期不做 | +| D2 | 审批配置 | **仿报销单**:线性节点、每节点指定审批人,可增删改序 | +| D3 | ETC / 加油 | **共用一套**流程配置;类型仅体现在业务单 `recharge_type` | +| D4 | 财务末节点 | 「待我审批」同一入口:**驳回** 或 **填写发放登记**(成功=通过并归档) | +| D5 | 待办入口 | 运输管理下 **独立菜单「充值审批」**(不与其他业务审批混排) | +| D6 | 驳回后 | 原单可修改并 **重新提交**(回到流程起点) | +| D7 | 审批历史 | **只追加不删除**;再提交后历次审批(含驳回)均在详情展示 | +| D8 | 详情可见 | Web 申请详情展示:申请信息 + 审核记录 + 发放信息 | + +### 非目标(本期不做) + +- 小程序司机端申请/详情 +- 接入通用 `wf_*` 图形化引擎 / 条件分支 / 角色或 SQL 审批人 +- 与报销「统一待办中心」合并 +- 审批通过后「仅通过、另页登记」的两步财务模式 +- 物理删除历史审批或发放记录 + +--- + +## 2. 方案选型 + +| 方案 | 说明 | 结论 | +|------|------|------| +| A 仿报销线性工作流 | 复用 `bx_workflow_def` / `bx_workflow_node` 模式,新增 `biz_type=vehicle_recharge`;业务侧挂 `wf_id`/`current_node` + 审批历史表;末节点联动发放 | **采用** | +| B 通用 WorkflowEngine | 与请假同源,配置体验不同于报销 | 否 | +| C 硬编码两级审批人 | 不可配置 | 否 | + +**工作流表归属**:扩展现有 `bx_workflow_def.biz_type`,增加 `vehicle_recharge`;配置 UI 挂在运输管理下,API 可复用/扩展现有 `/workflows` 读写(按 `biz_type` 隔离),避免再造一套节点表。 + +--- + +## 3. 菜单与权限 + +侧栏:**运输管理 → 车辆管理**(或与现有充值菜单同级,实施时与现菜单树对齐)。 + +| 菜单名 | path(建议) | menuCode(建议) | 说明 | +|--------|--------------|------------------|------| +| ETC/加油充值申请 | `vehicle-recharge-apply` | `pl:vehicle-recharge-apply:*` | 已有;增加提交/再提交、详情展示审批与发放 | +| **充值审批** | `vehicle-recharge-approval` | `pl:vehicle-recharge-approval:list` 等 | **新建**;待我审批 / 已办 | +| ETC/加油充值发放登记 | `vehicle-recharge-record` | `pl:vehicle-recharge-record:*` | 已有;主路径改为审批末节点登记;本页保留查询与无申请补录 | +| **充值审批流程** | `vehicle-recharge-workflow` | `pl:vehicle-recharge-workflow:list` 等 | **新建**;仿报销配置页 | + +默认种子节点(可改): + +1. 总经理审批 — 审批人:吴九华 +2. 财务发放 — 审批人:汪璇(`action_type=disburse`) + +--- + +## 4. 状态机 + +### 4.1 申请单状态 `status` + +| 值 | 文案 | 说明 | +|----|------|------| +| `draft` | 草稿 | 已保存未提交(可选;若产品要求「新建即提交」可不暴露,仅内部用) | +| `in_approval` | 审批中 | 已提交,等待当前节点处理 | +| `rejected` | 已驳回 | 任一节点驳回;可编辑后重新提交 | +| `disbursed` | 已发放 | 末节点(发放型)登记成功 | +| `cancelled` | 已作废 | 手动作废 | + +> **废弃语义**:原 `pending`(待发放)不再作为主状态对外展示。存量迁移见 §8。 + +### 4.2 流转 + +``` +草稿/新建 ──提交──→ 审批中(current_node=首节点) + ├─ 普通节点「通过」──→ 下一节点(仍审批中) + ├─ 发放型节点「登记成功」──→ 已发放 + 写 pl_vehicle_recharge_record + ├─ 任一节点「驳回」──→ 已驳回(写审批历史) + └─(仅草稿/已驳回/未关联发放)作废 ──→ 已作废 + +已驳回 ──改单并重新提交──→ 审批中(回到首节点;审批历史保留,approval_round+1) +``` + +### 4.3 操作权限摘要 + +| 操作 | 允许状态 / 角色 | +|------|-----------------| +| 编辑申请 | `draft` / `rejected`;申请人或有编辑权限者 | +| 提交 / 再提交 | `draft` / `rejected`;须已配置启用中的流程且至少 1 个节点 | +| 作废 | `draft` / `rejected` /(无发放记录的)审批中策略:**建议仅 draft、rejected**,审批中用驳回 | +| 审批通过 | 「充值审批」待办;当前节点 `action_type=approve` 且审批人=本人 | +| 审批驳回 | 任意当前节点审批人=本人 | +| 发放登记(审批入口) | 当前节点 `action_type=disburse` 且审批人=本人;成功后申请→`disbursed` | +| 发放页无申请补录 | 保留原能力;不走审批流 | + +--- + +## 5. 数据模型 + +### 5.1 工作流定义(复用) + +`bx_workflow_def`:新增业务类型 `vehicle_recharge`(注释同步更新)。 + +`bx_workflow_node`:增加字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `action_type` | varchar(20) NOT NULL DEFAULT `'approve'` | `approve`-仅通过/驳回;`disburse`-驳回或填写发放登记 | + +约束:同一 `wf_id` 下 **至多一个** `action_type=disburse` 节点;建议配置为最后一序节点。若配置多个,提交/保存配置时校验失败。 + +### 5.2 申请表扩展 `pl_vehicle_recharge_apply` + +| 字段 | 说明 | +|------|------| +| `wf_id` | 提交时绑定的流程定义 ID | +| `current_node` | 当前节点 ID(`bx_workflow_node.id`)或空 | +| `approval_round` | 当前轮次,从 1 起;每次重新提交 +1 | +| `status` | 见 §4.1(迁移后不再用 `pending` 作主语义) | + +### 5.3 审批历史 `pl_vehicle_recharge_approval`(新建) + +对齐 `bx_reimburse_approval` 思路,**只插入不删**(业务软删标志仅用于运维异常,默认查询 `del_flag=0`)。 + +| 字段 | 说明 | +|------|------| +| `id` | UUID | +| `apply_id` | 申请单 ID | +| `approval_round` | 轮次(与提交时申请上的 round 一致) | +| `node_id` / `node_name` | 节点快照 | +| `approver_id` / `approver_name` | 审批人 | +| `approval_status` | `approved` / `rejected` / `disbursed`(发放型节点登记成功记 `disbursed`) | +| `approval_opinion` | 意见 | +| `approval_time` | 时间 | +| 公共字段 | tenant_no、create_*、del_flag 等 | + +详情按 `approval_round ASC, approval_time ASC` 展示;可用「第 N 轮」分组标题。 + +### 5.4 发放表 + +`pl_vehicle_recharge_record` 结构不变;审批末节点登记时写入并关联 `apply_id`(仍 1 申请 : 0~1 发放)。 + +### 5.5 字典 + +更新/新增 `pl_recharge_apply_status`:`draft` / `in_approval` / `rejected` / `disbursed` / `cancelled`(Tag 语义:warning / primary / danger / success / info)。 + +--- + +## 6. 后端设计 + +### 6.1 模块 + +| 能力 | 建议路径 | +|------|----------| +| 流程配置 | 扩展 `app/api/bx/workflow.py` 或 PL 薄封装读 `biz_type=vehicle_recharge` | +| 申请+提交/再提交 | `pl_vehicle_recharge_service` 扩展 | +| 审批待办/通过/驳回/发放 | 新建 `pl_vehicle_recharge_approval_service`(或同文件分区) | +| 审批历史查询 | 详情接口附带 `approvals[]` + 已有发放 `record` | + +### 6.2 关键 API(示意) + +**流程配置**(与报销同形,`bizType=vehicle_recharge`) + +- GET 定义 + 节点列表 +- PUT 保存节点(含 `action_type`、校验唯一 disburse) + +**申请** + +- POST `/` 创建(默认 `draft` 或直接提交由产品定;**推荐创建为 draft,显式「提交」**) +- POST `/{id}/submit` 提交/再提交 +- GET `/{id}` 含 `approvals`、`record`(发放) + +**充值审批** + +- GET `/pending` 待我审批(`status=in_approval` 且当前节点 `approver_id=me`) +- GET `/done` 我已办(审批历史中 `approver_id=me`) +- POST `/{id}/approve` 普通节点通过(body: opinion) +- POST `/{id}/reject` 驳回(body: opinion,必填建议) +- POST `/{id}/disburse` 发放型节点:body 同现有发放登记字段;事务内写 record + 审批历史 + 申请 `disbursed` + +租户隔离、数据权限沿用 PL 现有约定。 + +### 6.3 与发放登记页关系 + +| 场景 | 行为 | +|------|------| +| 正常主路径 | 财务在「充值审批」完成 `disburse` | +| 发放登记页 | 列表/详情/导出保留;**有申请且走审批的单**禁止在发放页重复「发放」抢状态(或仅允许查看) | +| 无申请补录 | 发放页仍可新建 `apply_id` 为空的记录 | + +--- + +## 7. 前端设计 + +### 7.1 充值审批流程配置 + +对标 `views/bx/workflow/reimburse/index.vue`:节点表格、增删、上移下移、保存;表单增加 **节点动作**(审批 / 发放登记)。 + +### 7.2 充值审批(待办) + +- Tab 或筛选项:**待我审批** / **我已办** +- 行点击打开详情抽屉/弹窗:申请信息 + 历史审批 +(待办时)操作区 + - 普通节点:通过 / 驳回 + - 发放节点:驳回 / 发放登记表单(字段对齐现发放弹窗) + +### 7.3 申请列表与详情 + +- 状态 Tag 换新枚举 +- 工具栏:提交(draft/rejected) +- `SystemViewDialog`(或现有查看弹窗):分区 + 1. 申请信息 + 2. 审核记录(按轮次;Tag 展示通过/驳回/发放) + 3. 发放信息(无则显示「暂无」) + +### 7.4 界面模式 + +- 列表:P01(参考 unit/order) +- 配置页:对齐报销工作流页 +- 状态/类型:`el-tag` 语义色(ui-conventions) + +--- + +## 8. 存量数据迁移 + +| 原状态 | 迁移策略(推荐) | +|--------|------------------| +| `pending` 且无发放记录 | 置为 `in_approval`,`current_node`=流程中 `action_type=disburse` 节点,`approval_round=1`,并绑定 `wf_id`;使财务可在「充值审批」继续处理 | +| `pending` 但已有异常数据 | 人工核对 | +| `disbursed` / `cancelled` | 保持;`wf_id`/`current_node` 可空 | +| 历史无审批记录 | 详情「审核记录」为空属预期;不补造假数据 | + +迁移脚本:`docs/sql/migrations/2026-09-26-pl-vehicle-recharge-workflow.sql`(含表结构、字典、菜单、默认流程种子、状态迁移)。 + +--- + +## 9. 验收要点 + +1. 可配置至少两级节点;更换审批人后新提交的单按新配置走(已提交单仍按其绑定的 `wf_id` 节点快照/ID 执行——节点改删时需约定:已绑定 `current_node` 若被删则报错或管理员修复;**建议配置页删除节点前检查无 in_approval 引用**)。 +2. 总经理可驳回;驳回后详情可见记录;改单再提交后出现第 2 轮,第 1 轮记录仍在。 +3. 财务待办可驳回或登记;登记后申请已发放,发放信息在详情可见。 +4. 「充值审批」菜单独立,不与其它业务待办混列表。 +5. 小程序无入口、无接口暴露要求(本期)。 + +--- + +## 10. 风险与开放项(实施计划阶段消化) + +| 项 | 说明 | +|----|------| +| 创建即提交 vs 草稿 | 设计推荐显式「提交」;若客户坚持新建即进审批,可去掉 draft 对外展示 | +| 配置变更影响在途单 | 见 §9.1;实施时加删除校验 | +| 发放页与审批双入口 | §6.3 需在实现时加互斥校验,避免双写 | + +--- + +## 修订记录 + +| 日期 | 说明 | +|------|------| +| 2026-09-26 | 初稿:基于客户流程图与确认项 D1–D8 |