Files
qipai/database/migrations/2026081006_m09c_cleaning_settlement_integrity.up.sql
T

137 lines
6.1 KiB
SQL

CREATE TABLE IF NOT EXISTS qipai_cleaning_settlement_reversals (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
reversal_no VARCHAR(64) NOT NULL,
settlement_id BIGINT UNSIGNED NOT NULL,
amount_delta_cents BIGINT NOT NULL,
reason VARCHAR(512) NOT NULL,
reversed_by BIGINT UNSIGNED NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT fk_qipai_cleaning_settlement_reversal_tenant FOREIGN KEY (tenant_id)
REFERENCES qipai_tenants(id),
CONSTRAINT fk_qipai_cleaning_settlement_reversal_settlement FOREIGN KEY (settlement_id)
REFERENCES qipai_cleaning_settlements(id),
CONSTRAINT fk_qipai_cleaning_settlement_reversal_actor FOREIGN KEY (reversed_by)
REFERENCES qipai_users(id),
CONSTRAINT chk_qipai_cleaning_settlement_reversal_amount CHECK (amount_delta_cents <= 0),
UNIQUE KEY uq_qipai_cleaning_settlement_reversal_no (tenant_id, reversal_no),
UNIQUE KEY uq_qipai_cleaning_settlement_reversal_parent (tenant_id, settlement_id),
KEY idx_qipai_cleaning_settlement_reversal_created (tenant_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS qipai_cleaning_settlement_events (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
settlement_id BIGINT UNSIGNED NOT NULL,
from_status VARCHAR(32) NULL,
to_status VARCHAR(32) NOT NULL,
action VARCHAR(32) NOT NULL,
actor_id BIGINT UNSIGNED NOT NULL,
actor_type VARCHAR(16) NOT NULL DEFAULT 'USER',
trace_id VARCHAR(64) NOT NULL,
note VARCHAR(512) NOT NULL DEFAULT '',
metadata JSON NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT fk_qipai_cleaning_settlement_event_tenant FOREIGN KEY (tenant_id)
REFERENCES qipai_tenants(id),
CONSTRAINT fk_qipai_cleaning_settlement_event_parent FOREIGN KEY (settlement_id)
REFERENCES qipai_cleaning_settlements(id),
CONSTRAINT fk_qipai_cleaning_settlement_event_actor FOREIGN KEY (actor_id)
REFERENCES qipai_users(id),
CONSTRAINT chk_qipai_cleaning_settlement_event_actor_type CHECK (
actor_type IN ('USER', 'SYSTEM')
),
UNIQUE KEY uq_qipai_cleaning_settlement_event_trace
(tenant_id, settlement_id, action, trace_id),
KEY idx_qipai_cleaning_settlement_event_history
(tenant_id, settlement_id, created_at, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE qipai_cleaning_settlements
ADD COLUMN cancelled_by BIGINT UNSIGNED NULL AFTER paid_by,
ADD COLUMN cancelled_at DATETIME(3) NULL AFTER paid_at,
ADD COLUMN payout_request_no VARCHAR(64) NOT NULL DEFAULT '' AFTER payout_channel,
ADD COLUMN payout_request_key VARCHAR(128)
GENERATED ALWAYS AS (
CASE WHEN payout_request_no <> ''
THEN CONCAT(payout_channel, ':', payout_request_no)
ELSE NULL
END
) STORED AFTER payout_request_no,
ADD CONSTRAINT fk_qipai_cleaning_settlements_cancelled_by FOREIGN KEY (cancelled_by)
REFERENCES qipai_users(id),
ADD KEY idx_qipai_cleaning_settlement_cancelled (tenant_id, status, cancelled_at),
ADD UNIQUE KEY uq_qipai_cleaning_settlement_payout_request
(tenant_id, payout_request_key);
ALTER TABLE qipai_cleaning_settlement_items
DROP INDEX uq_qipai_cleaning_settlement_item_task_user,
ADD COLUMN reversal_id BIGINT UNSIGNED NULL AFTER reward_cents,
ADD COLUMN reversed_at DATETIME(3) NULL AFTER reversal_id,
ADD COLUMN active_share_key VARCHAR(128)
GENERATED ALWAYS AS (
CASE WHEN reversed_at IS NULL
THEN CONCAT(CAST(task_id AS CHAR), ':', CAST(cleaner_user_id AS CHAR))
ELSE NULL
END
) STORED AFTER reversed_at,
ADD CONSTRAINT fk_qipai_cleaning_settlement_items_reversal FOREIGN KEY (reversal_id)
REFERENCES qipai_cleaning_settlement_reversals(id),
ADD UNIQUE KEY uq_qipai_cleaning_settlement_active_share (tenant_id, active_share_key),
ADD KEY idx_qipai_cleaning_settlement_item_reversal (tenant_id, reversal_id);
UPDATE qipai_cleaning_settlements
SET cancelled_by = generated_by,
cancelled_at = COALESCE(cancelled_at, updated_at)
WHERE status = 'CANCELLED' AND cancelled_by IS NULL;
INSERT IGNORE INTO qipai_cleaning_settlement_reversals
(tenant_id, reversal_no, settlement_id, amount_delta_cents, reason, reversed_by, created_at)
SELECT s.tenant_id, CONCAT('CLR-M09C-', s.id), s.id, -CAST(s.total_reward_cents AS SIGNED),
'M09-C historical cancellation baseline', s.cancelled_by,
COALESCE(s.cancelled_at, s.updated_at)
FROM qipai_cleaning_settlements s
WHERE s.status = 'CANCELLED' AND s.deleted_at IS NULL;
UPDATE qipai_cleaning_settlement_items i
INNER JOIN qipai_cleaning_settlement_reversals r
ON r.tenant_id = i.tenant_id AND r.settlement_id = i.settlement_id
SET i.reversal_id = r.id,
i.reversed_at = r.created_at
WHERE i.reversed_at IS NULL;
UPDATE qipai_cleaning_task_members m
INNER JOIN qipai_cleaning_settlement_items i
ON i.tenant_id = m.tenant_id AND i.task_id = m.task_id AND i.cleaner_user_id = m.user_id
INNER JOIN qipai_cleaning_settlements s
ON s.tenant_id = i.tenant_id AND s.id = i.settlement_id AND s.deleted_at IS NULL
SET m.settled_at = CASE
WHEN s.status = 'PAID' AND i.reversed_at IS NULL
THEN COALESCE(s.paid_at, m.settled_at, UTC_TIMESTAMP(3))
ELSE NULL
END
WHERE i.id IS NOT NULL;
UPDATE qipai_cleaning_tasks t
SET t.status = 'COMPLETED', t.settled_at = NULL
WHERE t.status = 'SETTLED'
AND EXISTS (
SELECT 1
FROM qipai_cleaning_task_members pending
WHERE pending.tenant_id = t.tenant_id AND pending.task_id = t.id
AND pending.removed_at IS NULL AND pending.reward_cents > 0
AND pending.settled_at IS NULL
);
INSERT IGNORE INTO qipai_cleaning_settlement_events
(tenant_id, settlement_id, from_status, to_status, action, actor_id,
actor_type, trace_id, note, metadata, created_at)
SELECT s.tenant_id, s.id, NULL, s.status, 'MIGRATE', s.generated_by, 'SYSTEM',
CONCAT('m09c-', s.id), 'M09-C settlement history baseline',
JSON_OBJECT('source', '2026081006'), s.created_at
FROM qipai_cleaning_settlements s
WHERE s.deleted_at IS NULL;
INSERT IGNORE INTO qipai_schema_migrations (version, name)
VALUES ('2026081006', 'm09c_cleaning_settlement_integrity');