fix: 恢复旧通道报废催办投递

This commit is contained in:
WuYongJie 2026-07-17 13:55:55 +08:00
parent f670ccb5d5
commit 2e316a7104
2 changed files with 96 additions and 0 deletions

View File

@ -0,0 +1,57 @@
-- 统一通知中心切换前,旧 work_notice 催办任务可能在零次投递时就把报废单标记为已催办。
-- 保留旧任务用于审计,但仅对“仍待审批、从未尝试/发送、且尚无新通道任务”的记录开放一次重新催办。
START TRANSACTION;
UPDATE `disposal_record` AS `disposal`
SET `disposal`.`reminded_at` = NULL
WHERE `disposal`.`status` = 'pending'
AND `disposal`.`reminded_at` IS NOT NULL
AND EXISTS (
SELECT 1
FROM `dingtalk_delivery` AS `legacy_delivery`
WHERE `legacy_delivery`.`business_type` = 'disposal'
AND `legacy_delivery`.`business_id` = `disposal`.`id`
AND `legacy_delivery`.`event_type` = 'disposal_overdue_reminder'
AND `legacy_delivery`.`provider_type` = 'work_notice'
AND `legacy_delivery`.`status` IN ('pending', 'retrying', 'sending')
AND `legacy_delivery`.`attempt_count` = 0
AND `legacy_delivery`.`dingtalk_task_id` IS NULL
AND `legacy_delivery`.`sent_at` IS NULL
AND `legacy_delivery`.`read_at` IS NULL
)
AND NOT EXISTS (
SELECT 1
FROM `dingtalk_delivery` AS `current_delivery`
WHERE `current_delivery`.`business_type` = 'disposal'
AND `current_delivery`.`business_id` = `disposal`.`id`
AND `current_delivery`.`event_type` = 'disposal_overdue_reminder'
AND `current_delivery`.`provider_type` = 'notification_center'
);
UPDATE `dingtalk_delivery` AS `legacy_delivery`
JOIN `disposal_record` AS `disposal`
ON `disposal`.`id` = `legacy_delivery`.`business_id`
LEFT JOIN `dingtalk_delivery` AS `current_delivery`
ON `current_delivery`.`business_type` = 'disposal'
AND `current_delivery`.`business_id` = `legacy_delivery`.`business_id`
AND `current_delivery`.`event_type` = 'disposal_overdue_reminder'
AND `current_delivery`.`provider_type` = 'notification_center'
SET `legacy_delivery`.`status` = 'failed',
`legacy_delivery`.`next_attempt_at` = NOW(3),
`legacy_delivery`.`lease_owner` = NULL,
`legacy_delivery`.`lease_expires_at` = NULL,
`legacy_delivery`.`last_error_code` = 'LEGACY_CHANNEL_RETIRED',
`legacy_delivery`.`last_error_message` = '历史工作通知通道未实际投递,已开放由统一通知中心重新生成',
`legacy_delivery`.`failure_notified_at` = NOW(3)
WHERE `legacy_delivery`.`business_type` = 'disposal'
AND `legacy_delivery`.`event_type` = 'disposal_overdue_reminder'
AND `legacy_delivery`.`provider_type` = 'work_notice'
AND `legacy_delivery`.`status` IN ('pending', 'retrying', 'sending')
AND `legacy_delivery`.`attempt_count` = 0
AND `legacy_delivery`.`dingtalk_task_id` IS NULL
AND `legacy_delivery`.`sent_at` IS NULL
AND `legacy_delivery`.`read_at` IS NULL
AND `disposal`.`status` = 'pending'
AND `current_delivery`.`id` IS NULL;
COMMIT;

View File

@ -0,0 +1,39 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import path from 'node:path'
import test from 'node:test'
const migrationPath = path.resolve(
__dirname,
'../../../prisma/migrations/20260717_recover_legacy_disposal_reminders/migration.sql'
)
test('旧通道零次投递的报废催办可重新生成统一通知中心任务', () => {
const sql = readFileSync(migrationPath, 'utf8')
assert.match(sql, /START TRANSACTION;/i)
assert.match(sql, /UPDATE `disposal_record` AS `disposal`/i)
assert.match(sql, /SET `disposal`\.`reminded_at` = NULL/i)
assert.match(sql, /`disposal`\.`status` = 'pending'/i)
assert.match(sql, /`legacy_delivery`\.`provider_type` = 'work_notice'/i)
assert.match(sql, /`legacy_delivery`\.`attempt_count` = 0/i)
assert.match(sql, /`legacy_delivery`\.`sent_at` IS NULL/i)
assert.match(sql, /`legacy_delivery`\.`read_at` IS NULL/i)
assert.match(sql, /`current_delivery`\.`provider_type` = 'notification_center'/i)
assert.match(sql, /SET `legacy_delivery`\.`status` = 'failed'/i)
assert.match(sql, /LEGACY_CHANNEL_RETIRED/i)
assert.match(sql, /`legacy_delivery`\.`failure_notified_at` = NOW\(3\)/i)
assert.match(sql, /LEFT JOIN `dingtalk_delivery` AS `current_delivery`/i)
assert.match(sql, /`current_delivery`\.`id` IS NULL/i)
assert.match(sql, /COMMIT;/i)
assert.doesNotMatch(sql, /DELETE\s+FROM\s+`?dingtalk_delivery`?/i)
})
test('已发送、已读或已有统一通知中心任务的催办不会被迁移重放', () => {
const sql = readFileSync(migrationPath, 'utf8')
assert.match(sql, /`legacy_delivery`\.`dingtalk_task_id` IS NULL/i)
assert.match(sql, /NOT EXISTS \([\s\S]*`current_delivery`\.`event_type` = 'disposal_overdue_reminder'[\s\S]*`current_delivery`\.`provider_type` = 'notification_center'/i)
assert.doesNotMatch(sql, /`sent_at`\s*=\s*NULL/i)
assert.doesNotMatch(sql, /`read_at`\s*=\s*NULL/i)
})