-- 每日菜谱预约订单自动退款。 -- 本脚本只新增自动退款所需表、订单快照字段、钱包幂等索引和技术重试权限点, -- 不回填历史订单,不创建跨库外键,也不自动向已有角色授予技术重试权限。 -- 执行唯一索引变更前应确认本查询返回 0 行;发现重复时必须先人工核对资金记录,禁止自动清理。 SELECT pay_refund_order_id, COUNT(*) AS duplicate_count FROM pjcloudx_wallet.wallet_recharge WHERE pay_refund_order_id IS NOT NULL GROUP BY pay_refund_order_id HAVING COUNT(*) > 1; -- Wallet按售后单ID保证退款到账幂等;MySQL唯一索引允许保留多条NULL历史记录。 -- 将该变更放在其他DDL之前,重复检测不通过时由唯一索引直接阻止脚本继续执行。 ALTER TABLE `pjcloudx_wallet`.`wallet_recharge` ADD UNIQUE KEY `uk_wallet_recharge_refund_order` (`pay_refund_order_id`); -- 商户自动退款配置。父店和分店按各自 mch_id 独立配置,每个订单类型只保留一条配置记录。 CREATE TABLE `pjcloudx_wallet`.`mch_auto_refund_config` ( `id` bigint(20) NOT NULL COMMENT '配置主键,由应用使用雪花算法生成', `mch_id` bigint(20) NOT NULL COMMENT '商户ID,父店和分店互不继承配置', `order_type` char(10) NOT NULL COMMENT '硬件订单类型编码,首期仅支持2预约订单', `enabled` char(1) NOT NULL DEFAULT '0' COMMENT '自动退款开关,0关闭,1开启', `rule_type` varchar(32) NOT NULL COMMENT '自动退款规则,PAY_TIME_LIMIT支付后限时,APPT_CYCLE_CLOSE周期预约截止', `pay_time_limit_hours` int(11) DEFAULT NULL COMMENT '支付后可自动退款小时数,仅PAY_TIME_LIMIT使用,业务允许1至168', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `update_time` datetime DEFAULT NULL COMMENT '修改时间', `del_flag` char(1) DEFAULT '0' COMMENT '逻辑删除标记,0有效,1删除;本功能不提供删除接口', `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', PRIMARY KEY (`id`), UNIQUE KEY `uk_mch_auto_refund_config` (`tenant_id`, `mch_id`, `order_type`), KEY `idx_mch_auto_refund_config_mch` (`tenant_id`, `mch_id`, `del_flag`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '商户自动退款配置'; -- 订单保存下单时的配置与规则快照;三个关键字段任一为空均不具备免审资格,历史订单保持为空。 ALTER TABLE `pjcloudx_hardware`.`hard_order` ADD COLUMN `auto_refund_config_id` bigint(20) DEFAULT NULL COMMENT '下单时命中的Wallet自动退款配置ID,为空表示不具备免审资格' AFTER `appt_rule_id`, ADD COLUMN `auto_refund_rule_type` varchar(32) DEFAULT NULL COMMENT '下单时自动退款规则快照,PAY_TIME_LIMIT或APPT_CYCLE_CLOSE' AFTER `auto_refund_config_id`, ADD COLUMN `auto_refund_deadline` datetime DEFAULT NULL COMMENT '免审退款申请截止时间,闭区间判断为申请时间小于等于该值' AFTER `auto_refund_rule_type`; -- 自动退款持久化任务。一个售后单最多一条任务,失败后由该记录驱动定时恢复。 CREATE TABLE `pjcloudx_hardware`.`hard_auto_refund_task` ( `id` bigint(20) NOT NULL COMMENT '任务主键,由应用使用雪花算法生成', `after_sale_id` bigint(20) NOT NULL COMMENT '售后单ID,同时作为钱包退款幂等键', `order_id` bigint(20) NOT NULL COMMENT '原硬件订单ID,用于订单级加锁和退款状态汇总', `mch_id` bigint(20) NOT NULL COMMENT '订单所属商户ID,用于数据范围和配置复核', `config_id` bigint(20) NOT NULL COMMENT '下单时命中的Wallet自动退款配置ID', `rule_type` varchar(32) NOT NULL COMMENT '自动退款规则快照,任务重试期间不随配置规则变化', `eligibility_deadline` datetime NOT NULL COMMENT '申请免审资格截止时间,任务受理后跨过该时间仍继续重试', `status` char(10) NOT NULL COMMENT '任务状态,10待执行,20等待重试,30开关关闭暂停,31配置不可用暂停,40重试耗尽,50成功', `auto_attempt_count` int(11) NOT NULL DEFAULT 0 COMMENT '已消耗自动尝试次数,含首次执行,最大8次', `total_attempt_count` int(11) NOT NULL DEFAULT 0 COMMENT '全部尝试序号计数,包含自动、人工和开关关闭跳过', `next_retry_time` datetime DEFAULT NULL COMMENT '下次自动重试时间,暂停、耗尽和成功状态为空', `last_attempt_time` datetime DEFAULT NULL COMMENT '最近一次实际尝试或开关关闭跳过的开始时间', `success_time` datetime DEFAULT NULL COMMENT '钱包到账且售后与订单状态提交成功的时间', `last_error` varchar(2000) DEFAULT NULL COMMENT '最近一次失败诊断信息,不保存资金凭证等敏感数据', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `update_time` datetime DEFAULT NULL COMMENT '修改时间', `del_flag` char(1) DEFAULT '0' COMMENT '逻辑删除标记,0有效,1删除;任务不提供删除接口', `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', PRIMARY KEY (`id`), UNIQUE KEY `uk_hard_auto_refund_task_after_sale` (`after_sale_id`), KEY `idx_hard_auto_refund_task_scan` (`tenant_id`, `status`, `next_retry_time`, `del_flag`), KEY `idx_hard_auto_refund_task_order` (`tenant_id`, `order_id`, `del_flag`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '硬件订单自动退款任务'; -- 自动退款单次尝试日志。日志只在一次尝试内从执行中更新为最终结果,完成后只读。 CREATE TABLE `pjcloudx_hardware`.`hard_auto_refund_attempt_log` ( `id` bigint(20) NOT NULL COMMENT '日志主键,由应用使用雪花算法生成', `task_id` bigint(20) NOT NULL COMMENT '自动退款任务ID', `after_sale_id` bigint(20) NOT NULL COMMENT '售后单ID,用于直接查询完整尝试历史', `attempt_no` int(11) NOT NULL COMMENT '任务内递增尝试序号,包含开关关闭跳过记录', `trigger_type` char(10) NOT NULL COMMENT '触发来源,10首次同步执行,20定时重试,30人工技术重试', `result_status` char(10) NOT NULL COMMENT '尝试结果,10执行中,20成功,30失败,40因开关关闭跳过', `refund_code` varchar(128) NOT NULL COMMENT '稳定钱包退款业务号,格式hard_{orderId}_{afterSaleId}', `operator_user_id` bigint(20) DEFAULT NULL COMMENT '人工技术重试操作用户ID,自动触发为空', `started_time` datetime NOT NULL COMMENT '本次尝试开始时间', `finished_time` datetime DEFAULT NULL COMMENT '本次尝试完成时间,执行中记录为空', `error_message` varchar(2000) DEFAULT NULL COMMENT '失败诊断信息,成功或开关关闭跳过时为空', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `update_time` datetime DEFAULT NULL COMMENT '修改时间', `del_flag` char(1) DEFAULT '0' COMMENT '逻辑删除标记,0有效,1删除;日志不提供删除接口', `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', PRIMARY KEY (`id`), UNIQUE KEY `uk_hard_auto_refund_attempt` (`task_id`, `attempt_no`), KEY `idx_hard_auto_refund_attempt_after_sale` (`tenant_id`, `after_sale_id`, `create_time`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '硬件订单自动退款尝试日志'; -- 为每个已有“售后订单-查看”权限所在租户注册技术重试按钮。 -- 菜单ID从迁移日期种子或现有最大ID之后分配;脚本不写sys_role_menu,需由角色管理显式授权。 SET @hard_auto_refund_menu_id_base = ( SELECT GREATEST(COALESCE(MAX(menu_id), 0), 2026081400000000000) FROM pjcloudx.sys_menu ); INSERT INTO `pjcloudx`.`sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) SELECT @hard_auto_refund_menu_id_base + ROW_NUMBER() OVER (ORDER BY source_menu.tenant_id, source_menu.menu_id), '自动退款重试', 'hard_autoRefundTask_retry', NULL, NULL, source_menu.parent_id, '1', '1', COALESCE(source_menu.sort_order, 0) + 1, '0', NULL, source_menu.menu_type, 'system', NOW(), 'system', NOW(), '0', source_menu.tenant_id FROM pjcloudx.sys_menu source_menu WHERE source_menu.permission = 'hard_hardAfterSale_view' AND source_menu.del_flag = '0' AND NOT EXISTS ( SELECT 1 FROM pjcloudx.sys_menu existing_menu WHERE existing_menu.permission = 'hard_autoRefundTask_retry' AND existing_menu.tenant_id <=> source_menu.tenant_id AND existing_menu.del_flag = '0' ); SET @hard_auto_refund_menu_id_base = NULL;