-- ============================================================ -- Provider Status Mapping Reference Table -- ============================================================ -- Maps provider-specific campaign statuses to platform statuses. -- Runtime normalization is config-driven (appsettings.json), -- but this table serves as: -- 1. Canonical reference / documentation -- 2. Admin-editable override (future phase) -- 3. Audit trail for mapping changes -- -- Platform statuses: draft, staged, pending, active, paused, -- completed, cancelled, error -- ============================================================ IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = 'tbProviderStatusMap') BEGIN CREATE TABLE dbo.tbProviderStatusMap ( psmId INT IDENTITY(1,1) PRIMARY KEY, psmChannelType VARCHAR(50) NOT NULL, -- google_ads, meta, tiktok psmProviderStatus VARCHAR(100) NOT NULL, -- raw provider value (ENABLED, DELIVERY_OK, etc.) psmPlatformStatus VARCHAR(20) NOT NULL, -- normalized platform value psmDescription NVARCHAR(200) NULL, -- human-readable explanation psmIsActive BIT NOT NULL DEFAULT 1, psmCreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(), psmUpdatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(), CONSTRAINT UQ_ProviderStatusMap_Channel_Status UNIQUE (psmChannelType, psmProviderStatus), CONSTRAINT CK_ProviderStatusMap_PlatformStatus CHECK (psmPlatformStatus IN ('draft','staged','pending','active','paused','completed','cancelled','error')) ); CREATE NONCLUSTERED INDEX IX_ProviderStatusMap_Channel ON dbo.tbProviderStatusMap (psmChannelType) INCLUDE (psmProviderStatus, psmPlatformStatus) WHERE psmIsActive = 1; END GO -- ============================================================ -- Seed: Google Ads -- ============================================================ MERGE dbo.tbProviderStatusMap AS tgt USING (VALUES ('google_ads', 'ENABLED', 'active', 'Campaign is serving ads'), ('google_ads', 'Enabled', 'active', 'Campaign is serving ads (camelCase variant)'), ('google_ads', 'PAUSED', 'paused', 'Campaign is paused by advertiser'), ('google_ads', 'Paused', 'paused', 'Campaign is paused (camelCase variant)'), ('google_ads', 'REMOVED', 'cancelled', 'Campaign has been removed'), ('google_ads', 'Removed', 'cancelled', 'Campaign has been removed (camelCase variant)'), ('google_ads', 'UNKNOWN', 'error', 'Unknown status from Google Ads API'), ('google_ads', 'UNSPECIFIED', 'error', 'Unspecified status from Google Ads API') ) AS src (channelType, providerStatus, platformStatus, description) ON tgt.psmChannelType = src.channelType AND tgt.psmProviderStatus = src.providerStatus WHEN NOT MATCHED THEN INSERT (psmChannelType, psmProviderStatus, psmPlatformStatus, psmDescription) VALUES (src.channelType, src.providerStatus, src.platformStatus, src.description); GO -- ============================================================ -- Seed: Meta (Facebook / Instagram) -- ============================================================ MERGE dbo.tbProviderStatusMap AS tgt USING (VALUES ('meta', 'ACTIVE', 'active', 'Campaign is delivering'), ('meta', 'PAUSED', 'paused', 'Campaign paused by advertiser'), ('meta', 'DELETED', 'cancelled', 'Campaign deleted'), ('meta', 'ARCHIVED', 'completed', 'Campaign archived after completion'), ('meta', 'IN_PROCESS', 'pending', 'Campaign is being processed'), ('meta', 'WITH_ISSUES', 'error', 'Campaign has delivery issues'), ('meta', 'CAMPAIGN_PAUSED', 'paused', 'Parent campaign is paused'), ('meta', 'ADSET_PAUSED', 'paused', 'Ad set level pause'), ('meta', 'DISAPPROVED', 'error', 'Ad/campaign disapproved by review'), ('meta', 'PREAPPROVED', 'pending', 'Preapproved, awaiting final review'), ('meta', 'PENDING_REVIEW', 'pending', 'Awaiting Meta ad review'), ('meta', 'PENDING_BILLING_INFO','error', 'Billing information required') ) AS src (channelType, providerStatus, platformStatus, description) ON tgt.psmChannelType = src.channelType AND tgt.psmProviderStatus = src.providerStatus WHEN NOT MATCHED THEN INSERT (psmChannelType, psmProviderStatus, psmPlatformStatus, psmDescription) VALUES (src.channelType, src.providerStatus, src.platformStatus, src.description); GO -- ============================================================ -- Seed: TikTok -- ============================================================ MERGE dbo.tbProviderStatusMap AS tgt USING (VALUES ('tiktok', 'ENABLE', 'active', 'Campaign is active and delivering'), ('tiktok', 'CAMPAIGN_STATUS_ENABLE', 'active', 'Campaign enabled (prefixed variant)'), ('tiktok', 'DISABLE', 'paused', 'Campaign disabled by advertiser'), ('tiktok', 'CAMPAIGN_STATUS_DISABLE', 'paused', 'Campaign disabled (prefixed variant)'), ('tiktok', 'DELETE', 'cancelled', 'Campaign deleted'), ('tiktok', 'CAMPAIGN_STATUS_DELETE', 'cancelled', 'Campaign deleted (prefixed variant)'), ('tiktok', 'BUDGET_EXCEED', 'paused', 'Budget limit exceeded'), ('tiktok', 'CAMPAIGN_STATUS_BUDGET_EXCEED', 'paused', 'Budget exceeded (prefixed variant)'), ('tiktok', 'ADVERTISER_AUDIT_DENY', 'error', 'Advertiser account audit denied'), ('tiktok', 'CAMPAIGN_STATUS_ADVERTISER_AUDIT_DENY','error', 'Audit denied (prefixed variant)'), ('tiktok', 'NOT_DELETE', 'active', 'Campaign exists and is not deleted'), ('tiktok', 'ADVERTISER_AUDIT', 'pending', 'Advertiser account under audit'), ('tiktok', 'CAMPAIGN_STATUS_ADVERTISER_AUDIT', 'pending', 'Under audit (prefixed variant)'), ('tiktok', 'REAUDIT', 'pending', 'Campaign under re-audit'), ('tiktok', 'ALL', 'active', 'TikTok ALL filter status (treat as active)') ) AS src (channelType, providerStatus, platformStatus, description) ON tgt.psmChannelType = src.channelType AND tgt.psmProviderStatus = src.providerStatus WHEN NOT MATCHED THEN INSERT (psmChannelType, psmProviderStatus, psmPlatformStatus, psmDescription) VALUES (src.channelType, src.providerStatus, src.platformStatus, src.description); GO -- ============================================================ -- Seed: Common / Internal (platform-generated statuses) -- ============================================================ MERGE dbo.tbProviderStatusMap AS tgt USING (VALUES ('_common', 'submitted', 'active', 'Successfully dispatched to provider'), ('_common', 'pending_review', 'pending', 'Awaiting provider review'), ('_common', 'stub_provider', 'pending', 'Stub provider — no real dispatch yet'), ('_common', 'approved', 'active', 'Provider approved the campaign'), ('_common', 'rejected', 'error', 'Provider rejected the campaign'), ('_common', 'suspended', 'paused', 'Campaign suspended by provider'), ('_common', 'budget_depleted', 'paused', 'Budget fully consumed'), ('_common', 'expired', 'completed', 'Campaign reached its end date'), ('_common', 'archived', 'completed', 'Campaign archived'), ('_common', 'deleted', 'cancelled', 'Campaign deleted'), ('_common', 'in_process', 'pending', 'Campaign is being processed'), ('_common', 'in_review', 'pending', 'Campaign is under review'), ('_common', 'learning', 'active', 'Campaign in learning/optimization phase'), ('_common', 'limited', 'active', 'Campaign serving but limited (budget, targeting)') ) AS src (channelType, providerStatus, platformStatus, description) ON tgt.psmChannelType = src.channelType AND tgt.psmProviderStatus = src.providerStatus WHEN NOT MATCHED THEN INSERT (psmChannelType, psmProviderStatus, psmPlatformStatus, psmDescription) VALUES (src.channelType, src.providerStatus, src.platformStatus, src.description); GO -- ============================================================ -- Stored Procedure: spProviderStatusMap -- ============================================================ -- Actions: list, get, upsert, delete -- Follows standard JSON request/response pattern. -- ============================================================ CREATE OR ALTER PROCEDURE dbo.spProviderStatusMap @Action VARCHAR(20), @Rqst NVARCHAR(MAX) = '{}', @Resp NVARCHAR(MAX) OUTPUT AS BEGIN SET NOCOUNT ON; -- ── LIST ── IF @Action = 'list' BEGIN DECLARE @filterChannel VARCHAR(50) = JSON_VALUE(@Rqst, '$.channelType'); SET @Resp = ( SELECT psmId AS id, psmChannelType AS channelType, psmProviderStatus AS providerStatus, psmPlatformStatus AS platformStatus, psmDescription AS [description], psmIsActive AS isActive FROM dbo.tbProviderStatusMap WHERE psmIsActive = 1 AND (@filterChannel IS NULL OR psmChannelType = @filterChannel) ORDER BY psmChannelType, psmProviderStatus FOR JSON PATH, ROOT('data') ); IF @Resp IS NULL SET @Resp = '{"data":[]}'; SET @Resp = '{"ok":true,' + SUBSTRING(@Resp, 2, LEN(@Resp)); RETURN; END -- ── GET ── IF @Action = 'get' BEGIN DECLARE @getId INT = JSON_VALUE(@Rqst, '$.id'); SET @Resp = ( SELECT psmId AS id, psmChannelType AS channelType, psmProviderStatus AS providerStatus, psmPlatformStatus AS platformStatus, psmDescription AS [description], psmIsActive AS isActive FROM dbo.tbProviderStatusMap WHERE psmId = @getId FOR JSON PATH, WITHOUT_ARRAY_WRAPPER ); IF @Resp IS NULL BEGIN SET @Resp = '{"ok":false,"error":"Mapping not found"}'; RETURN; END SET @Resp = '{"ok":true,"data":' + @Resp + '}'; RETURN; END -- ── UPSERT ── IF @Action = 'upsert' BEGIN DECLARE @uChannelType VARCHAR(50) = JSON_VALUE(@Rqst, '$.channelType'); DECLARE @uProviderStatus VARCHAR(100) = JSON_VALUE(@Rqst, '$.providerStatus'); DECLARE @uPlatformStatus VARCHAR(20) = JSON_VALUE(@Rqst, '$.platformStatus'); DECLARE @uDescription NVARCHAR(200) = JSON_VALUE(@Rqst, '$.description'); IF @uChannelType IS NULL OR @uProviderStatus IS NULL OR @uPlatformStatus IS NULL BEGIN SET @Resp = '{"ok":false,"error":"channelType, providerStatus, and platformStatus are required"}'; RETURN; END IF @uPlatformStatus NOT IN ('draft','staged','pending','active','paused','completed','cancelled','error') BEGIN SET @Resp = '{"ok":false,"error":"Invalid platformStatus. Must be: draft, staged, pending, active, paused, completed, cancelled, error"}'; RETURN; END MERGE dbo.tbProviderStatusMap AS tgt USING (SELECT @uChannelType, @uProviderStatus) AS src (ct, ps) ON tgt.psmChannelType = src.ct AND tgt.psmProviderStatus = src.ps WHEN MATCHED THEN UPDATE SET psmPlatformStatus = @uPlatformStatus, psmDescription = COALESCE(@uDescription, psmDescription), psmIsActive = 1, psmUpdatedAt = SYSUTCDATETIME() WHEN NOT MATCHED THEN INSERT (psmChannelType, psmProviderStatus, psmPlatformStatus, psmDescription) VALUES (@uChannelType, @uProviderStatus, @uPlatformStatus, @uDescription); SET @Resp = '{"ok":true,"message":"Mapping saved"}'; RETURN; END -- ── DELETE (soft) ── IF @Action = 'delete' BEGIN DECLARE @dId INT = JSON_VALUE(@Rqst, '$.id'); UPDATE dbo.tbProviderStatusMap SET psmIsActive = 0, psmUpdatedAt = SYSUTCDATETIME() WHERE psmId = @dId; SET @Resp = '{"ok":true,"message":"Mapping deactivated"}'; RETURN; END SET @Resp = '{"ok":false,"error":"Unknown action: ' + ISNULL(@Action,'null') + '"}'; END GO