aslan-server/.deploy/prod-deploy/sql/20260718_aslan_gift_challenge.sql

428 lines
20 KiB
SQL

-- Aslan Gift Challenge: non-lucky paid-gift ranking, daily tasks and reward settlement.
--
-- Performance review (DDL only; run through the normal deployment migration process):
-- 1. The gift hot path inserts one row through uq_aslan_gc_ledger_source_event, then updates one
-- overall PK row, one daily PK row and at most three daily-task rows. Every lookup is bounded
-- by activity/user/date keys and is O(log N); ranking never GROUP BYs the append-only ledger.
-- 2. Overall and daily Top-N queries bind activity_id (and stat_date for DAILY) before reading
-- score order from covering indexes. The same score/reached-time/user-id order supports a
-- deterministic rank query without sorting the entire activity in memory.
-- 3. Campaign lookup uses idx_aslan_gc_activity_window. start_time is the range column and
-- end_time is an index-covered residual filter; enabled campaigns per sys_origin are expected
-- to remain few and overlapping enabled windows are rejected by the application transaction.
-- 4. Period headers make an empty DAILY ranking settle exactly once and gate late writes after a
-- snapshot starts. Due headers and task-delivery recovery use status/time indexes; reward
-- reconciliation reads only one frozen owner and its configured group through unique keys.
-- 5. No UPDATE or DELETE below uses an unindexed predicate. This migration creates empty campaign
-- tables and idempotent RBAC rows only; it does not scan or backfill gift_give_running_water.
-- 6. Before production rollout, run EXPLAIN for display-activity, Top-N, my-rank, due-settlement
-- and recovery queries against production-like cardinality. Any gift-history backfill must use
-- a checked Mongo index and an id cursor; OFFSET/full collection scans are explicitly excluded.
-- 7. Enabling a campaign inserts at most 366 DAILY headers in one batched primary/unique-key write.
-- claim_request_id adds one nullable audit index updated only on the first successful task claim.
-- 8. Enabling also freezes at most 5000 reward rows in batches of 500. Reads always bind
-- activity_id + resource_group_id and follow idx_aslan_gc_reward_snapshot_group; pre-start
-- rebuild/delete binds activity_id and uses the same leftmost index prefix, so no table scan is
-- introduced when a shared reward group is edited later.
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_activity (
id BIGINT NOT NULL,
activity_code VARCHAR(64) NOT NULL,
activity_name VARCHAR(128) NOT NULL,
activity_desc VARCHAR(1000) NULL,
sys_origin VARCHAR(32) NOT NULL,
time_zone VARCHAR(64) NOT NULL DEFAULT 'Asia/Riyadh',
start_time DATETIME(3) NOT NULL,
end_time DATETIME(3) NOT NULL,
daily_settlement_delay_minutes INT NOT NULL DEFAULT 30,
overall_settlement_delay_minutes INT NOT NULL DEFAULT 30,
overall_settlement_time DATETIME(3) NOT NULL,
display_top_n SMALLINT NOT NULL DEFAULT 100,
enabled TINYINT(1) NOT NULL DEFAULT 0,
overall_settlement_status VARCHAR(32) NOT NULL DEFAULT 'NOT_STARTED',
version INT NOT NULL DEFAULT 0,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_activity_code (activity_code),
KEY idx_aslan_gc_activity_window (sys_origin, enabled, start_time, end_time),
KEY idx_aslan_gc_activity_settle (
overall_settlement_status, overall_settlement_time, end_time, id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Aslan paid non-lucky gift challenge campaign';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_task_config (
id BIGINT NOT NULL,
activity_id BIGINT NOT NULL,
task_code VARCHAR(64) NOT NULL,
task_type VARCHAR(32) NOT NULL COMMENT 'ENTER_PAGE or SEND_GIFT_GOLD',
task_title VARCHAR(128) NOT NULL,
task_desc VARCHAR(500) NULL,
target_value DECIMAL(24,2) NOT NULL,
resource_group_id BIGINT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
sort_order SMALLINT NOT NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_task_code (activity_id, task_code),
UNIQUE KEY uq_aslan_gc_task_sort (activity_id, sort_order),
KEY idx_aslan_gc_task_enabled (activity_id, enabled, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Exactly three activity-scoped daily task definitions';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_rank_reward (
id BIGINT NOT NULL,
activity_id BIGINT NOT NULL,
period_type VARCHAR(16) NOT NULL COMMENT 'DAILY or OVERALL',
start_rank SMALLINT NOT NULL,
end_rank SMALLINT NOT NULL,
resource_group_id BIGINT NOT NULL,
reward_name VARCHAR(128) NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_rank_range (activity_id, period_type, start_rank, end_rank),
KEY idx_aslan_gc_rank_start (activity_id, period_type, start_rank, end_rank)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Configurable DAILY and OVERALL rank reward ranges';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_reward_snapshot (
id BIGINT NOT NULL,
activity_id BIGINT NOT NULL,
resource_group_id BIGINT NOT NULL,
reward_config_id BIGINT NOT NULL,
reward_type VARCHAR(32) NOT NULL,
detail_type VARCHAR(64) NULL,
content VARCHAR(500) NULL,
quantity INT NULL,
sort_order INT NULL,
remark VARCHAR(500) NULL,
cover TEXT NULL,
source_url TEXT NULL,
display_name VARCHAR(500) NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_reward_snapshot (
activity_id, resource_group_id, reward_config_id
),
KEY idx_aslan_gc_reward_snapshot_group (
activity_id, resource_group_id, sort_order, reward_config_id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Immutable per-activity reward item templates captured before start';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_gift_ledger (
id BIGINT NOT NULL,
activity_id BIGINT NOT NULL,
source_event_track_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
gift_id BIGINT NOT NULL,
gift_tab VARCHAR(32) NULL,
amount DECIMAL(24,2) NOT NULL,
event_time DATETIME(3) NOT NULL,
stat_date DATE NOT NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_ledger_source_event (activity_id, source_event_track_id),
KEY idx_aslan_gc_ledger_user (activity_id, user_id, event_time),
KEY idx_aslan_gc_ledger_date (activity_id, stat_date, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Idempotent eligible paid-gift fact ledger';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_user_score (
activity_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
score DECIMAL(24,2) NOT NULL DEFAULT 0,
score_reached_time DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (activity_id, user_id),
KEY idx_aslan_gc_user_rank (
activity_id, score DESC, score_reached_time ASC, user_id ASC
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Activity-wide user score aggregate';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_user_daily_score (
activity_id BIGINT NOT NULL,
stat_date DATE NOT NULL,
user_id BIGINT NOT NULL,
score DECIMAL(24,2) NOT NULL DEFAULT 0,
score_reached_time DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (activity_id, stat_date, user_id),
KEY idx_aslan_gc_daily_rank (
activity_id, stat_date, score DESC, score_reached_time ASC, user_id ASC
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Activity-timezone daily user score aggregate';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_user_task_daily (
id BIGINT NOT NULL,
activity_id BIGINT NOT NULL,
stat_date DATE NOT NULL,
user_id BIGINT NOT NULL,
task_config_id BIGINT NOT NULL,
task_code VARCHAR(64) NOT NULL,
task_type VARCHAR(32) NOT NULL,
task_title VARCHAR(128) NOT NULL,
sort_order SMALLINT NOT NULL,
target_value DECIMAL(24,2) NOT NULL,
progress_value DECIMAL(24,2) NOT NULL DEFAULT 0,
completed_time DATETIME(3) NULL,
resource_group_id BIGINT NULL,
business_no VARCHAR(160) NOT NULL,
claim_request_id VARCHAR(128) NULL COMMENT 'First successful H5 claim request audit id',
delivery_status VARCHAR(32) NOT NULL DEFAULT 'NOT_CLAIMED'
COMMENT 'NOT_CLAIMED, PENDING, PROCESSING, SUCCESS or UNKNOWN',
retry_count INT NOT NULL DEFAULT 0,
failure_reason VARCHAR(500) NULL,
claimed_time DATETIME(3) NULL,
deliver_time DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_task_daily (activity_id, stat_date, user_id, task_code),
UNIQUE KEY uq_aslan_gc_task_business (business_no),
KEY idx_aslan_gc_task_user (activity_id, user_id, stat_date, sort_order),
KEY idx_aslan_gc_task_claim_request (claim_request_id),
KEY idx_aslan_gc_task_recover (delivery_status, update_time, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Daily task target/reward snapshot, progress and claim state';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_period_settlement (
id BIGINT NOT NULL,
activity_id BIGINT NOT NULL,
period_type VARCHAR(16) NOT NULL COMMENT 'DAILY or OVERALL',
period_key VARCHAR(16) NOT NULL COMMENT 'yyyy-MM-dd for DAILY, OVERALL otherwise',
stat_date DATE NULL,
snapshot_due_time DATETIME(3) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'NOT_STARTED'
COMMENT 'NOT_STARTED, PROCESSING, COMPLETED or RECONCILIATION_REQUIRED',
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_period (activity_id, period_type, period_key),
KEY idx_aslan_gc_period_due (status, snapshot_due_time, id),
KEY idx_aslan_gc_period_activity (activity_id, period_type, status, period_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Per-period snapshot gate including empty DAILY rankings';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_settlement (
id BIGINT NOT NULL,
activity_id BIGINT NOT NULL,
period_type VARCHAR(16) NOT NULL COMMENT 'DAILY or OVERALL',
period_key VARCHAR(16) NOT NULL COMMENT 'yyyy-MM-dd for DAILY, OVERALL otherwise',
stat_date DATE NULL,
user_id BIGINT NOT NULL,
rank_no SMALLINT NOT NULL,
score DECIMAL(24,2) NOT NULL,
rank_reward_id BIGINT NOT NULL,
resource_group_id BIGINT NOT NULL,
business_no VARCHAR(160) NOT NULL,
delivery_status VARCHAR(32) NOT NULL COMMENT 'PENDING, PROCESSING, SUCCESS or UNKNOWN',
retry_count INT NOT NULL DEFAULT 0,
failure_reason VARCHAR(500) NULL,
deliver_time DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_settlement_user (
activity_id, period_type, period_key, user_id
),
UNIQUE KEY uq_aslan_gc_settlement_rank (
activity_id, period_type, period_key, rank_no
),
UNIQUE KEY uq_aslan_gc_settlement_business (business_no),
KEY idx_aslan_gc_settlement_status (
activity_id, period_type, period_key, delivery_status, rank_no
),
KEY idx_aslan_gc_settlement_recover (delivery_status, update_time, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Frozen DAILY/OVERALL rank snapshots and delivery state';
CREATE TABLE IF NOT EXISTS aslan_gift_challenge_delivery_item (
id BIGINT NOT NULL,
owner_type VARCHAR(20) NOT NULL COMMENT 'TASK or SETTLEMENT',
owner_id BIGINT NOT NULL,
activity_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
reward_config_id BIGINT NOT NULL,
resource_group_id BIGINT NOT NULL,
reward_type VARCHAR(32) NOT NULL,
detail_type VARCHAR(64) NULL,
content VARCHAR(500) NULL,
quantity INT NULL,
sort_order INT NULL,
remark VARCHAR(500) NULL,
delivery_status VARCHAR(32) NOT NULL COMMENT 'PENDING, PROCESSING, SUCCESS or UNKNOWN',
retry_count INT NOT NULL DEFAULT 0,
failure_reason VARCHAR(500) NULL,
deliver_time DATETIME(3) NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uq_aslan_gc_delivery_config (owner_type, owner_id, reward_config_id),
KEY idx_aslan_gc_delivery_owner (owner_type, owner_id, delivery_status),
KEY idx_aslan_gc_delivery_activity (activity_id, delivery_status, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Per reward-config transactional delivery outbox';
-- RBAC is idempotent. The page is inherited only by roles that already own Operations.
INSERT INTO sys_resource (id, resource_name, mapping, method, auth_type, perm, update_time)
VALUES
('activity:gift-challenge:list', '礼物挑战活动列表', '/activity/gift-challenge/list', 'GET', 3, 'activity:gift-challenge:list', NOW()),
('activity:gift-challenge:detail', '礼物挑战活动详情', '/activity/gift-challenge/detail/**', 'GET', 3, 'activity:gift-challenge:detail', NOW()),
('activity:gift-challenge:save', '保存礼物挑战活动', '/activity/gift-challenge/save', 'POST', 3, 'activity:gift-challenge:save', NOW()),
('activity:gift-challenge:enable', '启停礼物挑战活动', '/activity/gift-challenge/enable/**', 'PUT', 3, 'activity:gift-challenge:enable', NOW()),
('activity:gift-challenge:tasks:get', '礼物挑战任务列表', '/activity/gift-challenge/tasks/**', 'GET', 3, 'activity:gift-challenge:tasks:get', NOW()),
('activity:gift-challenge:tasks:save', '保存礼物挑战任务', '/activity/gift-challenge/tasks/**', 'PUT', 3, 'activity:gift-challenge:tasks:save', NOW()),
('activity:gift-challenge:rank:get', '礼物挑战排名奖励', '/activity/gift-challenge/rank-rewards/**', 'GET', 3, 'activity:gift-challenge:rank:get', NOW()),
('activity:gift-challenge:rank:save', '保存礼物挑战排名奖励', '/activity/gift-challenge/rank-rewards/**', 'PUT', 3, 'activity:gift-challenge:rank:save', NOW()),
('activity:gift-challenge:ranking', '查看礼物挑战榜单', '/activity/gift-challenge/ranking/**', 'GET', 3, 'activity:gift-challenge:ranking', NOW()),
('activity:gift-challenge:settlement:get', '礼物挑战结算记录', '/activity/gift-challenge/settlement-records/**', 'GET', 3, 'activity:gift-challenge:settlement:get', NOW()),
('activity:gift-challenge:settlement:run', '执行礼物挑战结算', '/activity/gift-challenge/settlement/**', 'POST', 3, 'activity:gift-challenge:settlement:run', NOW()),
('activity:gift-challenge:delivery:resolve', '核账处理礼物挑战未知奖励', '/activity/gift-challenge/delivery-item/resolve/**', 'POST', 3, 'activity:gift-challenge:delivery:resolve', NOW())
ON DUPLICATE KEY UPDATE
resource_name = VALUES(resource_name),
mapping = VALUES(mapping),
method = VALUES(method),
auth_type = VALUES(auth_type),
perm = VALUES(perm),
update_time = NOW();
INSERT INTO sys_menu (
parent_id, menu_name, path, router, menu_type, icon, alias, sort, status,
create_user, update_user, create_time, update_time
)
SELECT
parent.id,
'礼物挑战活动',
'activity/gift-challenge/index',
'activity/gift-challenge',
2,
'gift',
'ActivityGiftChallenge',
89,
0,
'0',
'0',
NOW(),
NOW()
FROM sys_menu parent
LEFT JOIN sys_menu existing ON existing.alias = 'ActivityGiftChallenge'
WHERE (parent.alias = 'OperationManager' OR parent.menu_name = '运营管理')
AND parent.menu_type = 1
AND existing.id IS NULL
LIMIT 1;
UPDATE sys_menu campaign_menu
JOIN (
SELECT id
FROM sys_menu
WHERE (alias = 'OperationManager' OR menu_name = '运营管理')
AND menu_type = 1
LIMIT 1
) parent ON 1 = 1
SET
campaign_menu.parent_id = parent.id,
campaign_menu.menu_name = '礼物挑战活动',
campaign_menu.path = 'activity/gift-challenge/index',
campaign_menu.router = 'activity/gift-challenge',
campaign_menu.menu_type = 2,
campaign_menu.icon = 'gift',
campaign_menu.sort = 89,
campaign_menu.status = 0,
campaign_menu.update_user = '0',
campaign_menu.update_time = NOW()
WHERE campaign_menu.alias = 'ActivityGiftChallenge';
INSERT INTO sys_menu_resource (menu_id, resource_id)
SELECT menu.id, resource.id
FROM sys_menu menu
JOIN sys_resource resource ON resource.id LIKE 'activity:gift-challenge:%'
LEFT JOIN sys_menu_resource existing
ON existing.menu_id = menu.id AND existing.resource_id = resource.id
WHERE menu.alias = 'ActivityGiftChallenge'
AND existing.id IS NULL;
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT source_role.role_id, campaign_menu.id
FROM sys_role_menu source_role
JOIN sys_menu source_menu ON source_menu.id = source_role.menu_id
JOIN sys_menu campaign_menu ON campaign_menu.alias = 'ActivityGiftChallenge'
LEFT JOIN sys_role_menu existing
ON existing.role_id = source_role.role_id AND existing.menu_id = campaign_menu.id
WHERE (source_menu.alias = 'OperationManager' OR source_menu.menu_name = '运营管理')
AND source_menu.menu_type = 1
AND existing.id IS NULL;
-- Buttons are real menu_type=3 rows because controller-side hasButtonsAlias checks sys_menu.alias,
-- not sys_resource.perm. Existing page owners inherit all four buttons idempotently.
INSERT INTO sys_menu (
parent_id, menu_name, path, router, menu_type, icon, alias, sort, status,
create_user, update_user, create_time, update_time
)
SELECT
page.id,
button.menu_name,
'',
'',
3,
'',
button.alias,
button.sort_no,
0,
'0',
'0',
NOW(),
NOW()
FROM sys_menu page
JOIN (
SELECT '编辑礼物挑战配置' AS menu_name, 'activity:gift-challenge:edit' AS alias, 1 AS sort_no
UNION ALL
SELECT '启停礼物挑战活动', 'activity:gift-challenge:enable', 2
UNION ALL
SELECT '执行礼物挑战结算', 'activity:gift-challenge:settle', 3
UNION ALL
SELECT '核账礼物挑战未知奖励', 'activity:gift-challenge:reconcile', 4
) button ON 1 = 1
LEFT JOIN sys_menu existing ON existing.alias = button.alias
WHERE page.alias = 'ActivityGiftChallenge'
AND existing.id IS NULL;
UPDATE sys_menu button
JOIN sys_menu page ON page.alias = 'ActivityGiftChallenge'
SET
button.parent_id = page.id,
button.menu_type = 3,
button.status = 0,
button.update_user = '0',
button.update_time = NOW()
WHERE button.alias IN (
'activity:gift-challenge:edit',
'activity:gift-challenge:enable',
'activity:gift-challenge:settle',
'activity:gift-challenge:reconcile'
);
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT page_role.role_id, button.id
FROM sys_role_menu page_role
JOIN sys_menu page ON page.id = page_role.menu_id AND page.alias = 'ActivityGiftChallenge'
JOIN sys_menu button ON button.parent_id = page.id AND button.menu_type = 3
LEFT JOIN sys_role_menu existing
ON existing.role_id = page_role.role_id AND existing.menu_id = button.id
WHERE button.alias IN (
'activity:gift-challenge:edit',
'activity:gift-challenge:enable',
'activity:gift-challenge:settle',
'activity:gift-challenge:reconcile'
)
AND existing.id IS NULL;