========================================================================
hardware 定时任务迁移 daemon-quartz - sys_job 手工添加清单 (2026-09-28)
========================================================================

说明：以下内容需要管理员在对应环境手工添加（Quartz 任务管理页或直接执行 SQL）。
SQL 幂等，可重复执行。
注意库名：sys_job 在 `pjcloudx_job` 库（与 qrtz_* / sys_job_log 同库）。

迁移背景：
  hardware 原本地 @Scheduled 定时任务（6 个类共 8 个方法）已全部迁移到 daemon-quartz
  统一调度，集群部署下避免各实例重复触发，且可在任务管理页暂停/改cron/手动执行/看执行日志。
  原 job 包已整体删除：纯转发型 job（咖啡机/Outbox/打印）由 HardScheduleInnerController
  直调领域服务；聚合支付结算补偿编排平移到 HardAggregateOrderPayCompensator；
  自动退款扫描编排平移进 HardAutoRefundTaskService.retryDueTasks；消费机日志清理平移进
  HardXfjLogService.cleanupExpiredLogs。内部端点统一挂在 /hard-schedule/inner/** 下，
  由 daemon 的 hardScheduleTask bean（class_name=hardScheduleTask）通过 Feign 反射调用。
  HardApp 已移除 @EnableScheduling，hardware 不再有任何本地周期调度。

部署顺序（硬前提，顺序不能颠倒）：
  1. 部署 pjcloud-hardware-biz（新增内部端点、已移除本地 @Scheduled 与 @EnableScheduling）；
  2. 部署 pjcloud-daemon-quartz（新增 hardScheduleTask bean 与 hardware-api 依赖）；
  3. 执行下方 8 条 sys_job INSERT；
  4. 重启 daemon-quartz 自动加载（或调 POST /sys-job/refresh-jobs，权限 job_sys_job_refresh_job）。

空窗期说明（hardware 已部署、任务未生效期间补偿停摆的影响）：
  - 聚合支付结算：终端主动轮询仍是实时结算入口，补偿只是兜底；恢复后 1 小时内订单
    均会被遗留核对扫到，不丢单；
  - 自动退款 / Outbox / 打印任务：均为持久化任务表，恢复后按到期时间补执行；
  - 咖啡机通知：失败计数持久化（wuli_notify_count），恢复后继续重试；
  - 消费机日志清理：每日一次，无影响。
  结论：分钟级空窗只延迟补偿、不丢数据；但仍应尽快按顺序完成部署。

行为说明：
  原 fixedDelay 节奏改为固定 cron 节奏；Quartz 任务执行入口 PjcloudQuartzFactory 带
  @DisallowConcurrentExecution，单轮未结束时下一触发自动顺延，不会重叠。
  集群重复触发/多节点安全性：
  - 聚合支付结算：分布式订单锁 + 订单状态条件更新保证幂等；近期扫描在 hardware
    实例内还有轮询协调器退避门控，空闲时触发进来直接空转，不扫库。退避门控是实例内
    状态，多实例下负载均衡会把触发分摊到各实例，新订单唤醒后最迟 N×5 秒内会被扫到，
    实时结算仍以终端主动轮询为主、本任务只做兜底，不受影响；
  - 自动退款：订单 Redis 锁 + 任务行锁最终串行；
  - Outbox 投递/清理：条件更新（状态 CAS）领取，天然幂等；
  - 打印/咖啡机/消费机日志：任务表状态流转或条件删除，重复执行安全。
  misfire_policy 统一选 '3'（错过的触发不补偿执行，等下个周期），避免服务恢复后
  高频补偿任务雪崩式补跑。

------------------------------------------------------------------------
一、sys_job：聚合支付近期结算补偿（每5秒，对应原 fixedDelay=5s）
------------------------------------------------------------------------

INSERT INTO `pjcloudx_job`.`sys_job`
(`job_id`, `job_name`, `job_group`, `job_order`, `job_type`, `execute_path`, `class_name`, `method_name`,
 `method_params_value`, `cron_expression`, `misfire_policy`, `job_tenant_type`, `job_status`,
 `job_execute_status`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `remark`)
SELECT (SELECT GREATEST(COALESCE(MAX(`job_id`), 0), 2026092800000000000) FROM `pjcloudx_job`.`sys_job`) + 1,
       '聚合支付近期结算补偿', 'HARDWARE', '1', '2', NULL, 'hardScheduleTask', 'settlePendingAggregateOrders',
       NULL, '0/5 * * * * ?', '3', '1', '2',
       '0', 'system', NOW(), 'system', NOW(), 1, '每5秒补偿近15分钟未结算的聚合支付订单；空闲时硬件侧退避门控空转'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx_job`.`sys_job`
    WHERE `job_name` = '聚合支付近期结算补偿' AND `job_group` = 'HARDWARE' AND `tenant_id` = 1
);

------------------------------------------------------------------------
二、sys_job：聚合支付遗留核对与超时取消（每5分钟，对应原 fixedDelay=5min）
------------------------------------------------------------------------

INSERT INTO `pjcloudx_job`.`sys_job`
(`job_id`, `job_name`, `job_group`, `job_order`, `job_type`, `execute_path`, `class_name`, `method_name`,
 `method_params_value`, `cron_expression`, `misfire_policy`, `job_tenant_type`, `job_status`,
 `job_execute_status`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `remark`)
SELECT (SELECT GREATEST(COALESCE(MAX(`job_id`), 0), 2026092800000000000) FROM `pjcloudx_job`.`sys_job`) + 2,
       '聚合支付遗留核对与超时取消', 'HARDWARE', '2', '2', NULL, 'hardScheduleTask', 'settleStaleAggregateOrders',
       NULL, '0 0/5 * * * ?', '3', '1', '2',
       '0', 'system', NOW(), 'system', NOW(), 1, '每5分钟核对15-60分钟遗留订单并原子取消超1小时订单'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx_job`.`sys_job`
    WHERE `job_name` = '聚合支付遗留核对与超时取消' AND `job_group` = 'HARDWARE' AND `tenant_id` = 1
);

------------------------------------------------------------------------
三、sys_job：自动退款到期任务补偿（每30秒，对应原 fixedDelay=30s）
------------------------------------------------------------------------

INSERT INTO `pjcloudx_job`.`sys_job`
(`job_id`, `job_name`, `job_group`, `job_order`, `job_type`, `execute_path`, `class_name`, `method_name`,
 `method_params_value`, `cron_expression`, `misfire_policy`, `job_tenant_type`, `job_status`,
 `job_execute_status`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `remark`)
SELECT (SELECT GREATEST(COALESCE(MAX(`job_id`), 0), 2026092800000000000) FROM `pjcloudx_job`.`sys_job`) + 3,
       '自动退款到期任务补偿', 'HARDWARE', '3', '2', NULL, 'hardScheduleTask', 'retryDueAutoRefundTasks',
       NULL, '0/30 * * * * ?', '3', '1', '2',
       '0', 'system', NOW(), 'system', NOW(), 1, '每30秒扫描到期/暂停自动退款任务，订单Redis锁+任务行锁串行'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx_job`.`sys_job`
    WHERE `job_name` = '自动退款到期任务补偿' AND `job_group` = 'HARDWARE' AND `tenant_id` = 1
);

------------------------------------------------------------------------
四、sys_job：咖啡机通知失败补偿（每分钟，对应原 fixedDelay=60s）
------------------------------------------------------------------------

INSERT INTO `pjcloudx_job`.`sys_job`
(`job_id`, `job_name`, `job_group`, `job_order`, `job_type`, `execute_path`, `class_name`, `method_name`,
 `method_params_value`, `cron_expression`, `misfire_policy`, `job_tenant_type`, `job_status`,
 `job_execute_status`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `remark`)
SELECT (SELECT GREATEST(COALESCE(MAX(`job_id`), 0), 2026092800000000000) FROM `pjcloudx_job`.`sys_job`) + 4,
       '咖啡机通知失败补偿', 'HARDWARE', '4', '2', NULL, 'hardScheduleTask', 'retryCoffeeNotifyFailures',
       NULL, '0 * * * * ?', '3', '1', '2',
       '0', 'system', NOW(), 'system', NOW(), 1, '每分钟重试咖啡机支付回调/退款同步失败订单，按通知次数上限熔断'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx_job`.`sys_job`
    WHERE `job_name` = '咖啡机通知失败补偿' AND `job_group` = 'HARDWARE' AND `tenant_id` = 1
);

------------------------------------------------------------------------
五、sys_job：核销消息Outbox到期投递（每10秒，对应原 fixedDelay=10s）
------------------------------------------------------------------------

INSERT INTO `pjcloudx_job`.`sys_job`
(`job_id`, `job_name`, `job_group`, `job_order`, `job_type`, `execute_path`, `class_name`, `method_name`,
 `method_params_value`, `cron_expression`, `misfire_policy`, `job_tenant_type`, `job_status`,
 `job_execute_status`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `remark`)
SELECT (SELECT GREATEST(COALESCE(MAX(`job_id`), 0), 2026092800000000000) FROM `pjcloudx_job`.`sys_job`) + 5,
       '核销消息Outbox到期投递', 'HARDWARE', '5', '2', NULL, 'hardScheduleTask', 'processOutboxDueEvents',
       NULL, '0/10 * * * * ?', '3', '1', '2',
       '0', 'system', NOW(), 'system', NOW(), 1, '每10秒扫描到期核销消息Outbox事件，条件更新领取幂等'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx_job`.`sys_job`
    WHERE `job_name` = '核销消息Outbox到期投递' AND `job_group` = 'HARDWARE' AND `tenant_id` = 1
);

------------------------------------------------------------------------
六、sys_job：核销消息Outbox过期清理（每日 02:30，与原本地任务一致）
------------------------------------------------------------------------

INSERT INTO `pjcloudx_job`.`sys_job`
(`job_id`, `job_name`, `job_group`, `job_order`, `job_type`, `execute_path`, `class_name`, `method_name`,
 `method_params_value`, `cron_expression`, `misfire_policy`, `job_tenant_type`, `job_status`,
 `job_execute_status`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `remark`)
SELECT (SELECT GREATEST(COALESCE(MAX(`job_id`), 0), 2026092800000000000) FROM `pjcloudx_job`.`sys_job`) + 6,
       '核销消息Outbox过期清理', 'HARDWARE', '6', '2', NULL, 'hardScheduleTask', 'cleanupOutboxExpiredEvents',
       NULL, '0 30 2 * * ?', '3', '1', '2',
       '0', 'system', NOW(), 'system', NOW(), 1, '每日02:30分批清理超保留期终态核销Outbox'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx_job`.`sys_job`
    WHERE `job_name` = '核销消息Outbox过期清理' AND `job_group` = 'HARDWARE' AND `tenant_id` = 1
);

------------------------------------------------------------------------
七、sys_job：打印任务补偿扫描（每10秒，对应原 fixedDelay=10s）
------------------------------------------------------------------------

INSERT INTO `pjcloudx_job`.`sys_job`
(`job_id`, `job_name`, `job_group`, `job_order`, `job_type`, `execute_path`, `class_name`, `method_name`,
 `method_params_value`, `cron_expression`, `misfire_policy`, `job_tenant_type`, `job_status`,
 `job_execute_status`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `remark`)
SELECT (SELECT GREATEST(COALESCE(MAX(`job_id`), 0), 2026092800000000000) FROM `pjcloudx_job`.`sys_job`) + 7,
       '打印任务补偿扫描', 'HARDWARE', '7', '2', NULL, 'hardScheduleTask', 'processPrintDueTasks',
       NULL, '0/10 * * * * ?', '3', '1', '2',
       '0', 'system', NOW(), 'system', NOW(), 1, '每10秒扫描待生成/待下发持久化打印任务，恢复MQTT离线期遗留'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx_job`.`sys_job`
    WHERE `job_name` = '打印任务补偿扫描' AND `job_group` = 'HARDWARE' AND `tenant_id` = 1
);

------------------------------------------------------------------------
八、sys_job：消费机日志过期清理（每日 00:00，与原本地任务一致）
------------------------------------------------------------------------

INSERT INTO `pjcloudx_job`.`sys_job`
(`job_id`, `job_name`, `job_group`, `job_order`, `job_type`, `execute_path`, `class_name`, `method_name`,
 `method_params_value`, `cron_expression`, `misfire_policy`, `job_tenant_type`, `job_status`,
 `job_execute_status`, `create_by`, `create_time`, `update_by`, `update_time`, `tenant_id`, `remark`)
SELECT (SELECT GREATEST(COALESCE(MAX(`job_id`), 0), 2026092800000000000) FROM `pjcloudx_job`.`sys_job`) + 8,
       '消费机日志过期清理', 'HARDWARE', '8', '2', NULL, 'hardScheduleTask', 'cleanupXfjLog',
       NULL, '0 0 0 * * ?', '3', '1', '2',
       '0', 'system', NOW(), 'system', NOW(), 1, '每日00:00删除30天前消费机日志；累计不足1万条时跳过'
FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx_job`.`sys_job`
    WHERE `job_name` = '消费机日志过期清理' AND `job_group` = 'HARDWARE' AND `tenant_id` = 1
);

------------------------------------------------------------------------
九、内部端点与任务对应关系（勿对外暴露，@Inner + @NoToken 仅服务间调用）
------------------------------------------------------------------------

hardScheduleTask.settlePendingAggregateOrders:
  POST /hard-schedule/inner/aggregate-pay/settle-pending   聚合支付近期结算补偿
hardScheduleTask.settleStaleAggregateOrders:
  POST /hard-schedule/inner/aggregate-pay/settle-stale    聚合支付遗留核对与超时取消
hardScheduleTask.retryDueAutoRefundTasks:
  POST /hard-schedule/inner/auto-refund/retry-due         自动退款到期任务补偿
hardScheduleTask.retryCoffeeNotifyFailures:
  POST /hard-schedule/inner/coffee-notify/retry-failures  咖啡机通知失败补偿
hardScheduleTask.processOutboxDueEvents:
  POST /hard-schedule/inner/outbox/process-due            核销消息Outbox到期投递
hardScheduleTask.cleanupOutboxExpiredEvents:
  POST /hard-schedule/inner/outbox/cleanup                核销消息Outbox过期清理
hardScheduleTask.processPrintDueTasks:
  POST /hard-schedule/inner/print/process-due             打印任务补偿扫描
hardScheduleTask.cleanupXfjLog:
  POST /hard-schedule/inner/xfj-log/cleanup               消费机日志过期清理

------------------------------------------------------------------------
十、回滚说明
------------------------------------------------------------------------
  1. 停用任务：将 8 条 sys_job 的 job_status 改为 '3'（暂停）或 '4'（删除）并重启
     daemon-quartz（或调 POST /sys-job/refresh-jobs）；
  2. 恢复本地调度：回滚整个迁移提交（恢复 job 包 6 个类与 HardApp 的
     @EnableScheduling）并重新部署 hardware；
  3. 只想临时停某个补偿任务：单独暂停对应一条 sys_job 即可，其余任务不受影响。
