import db from '../config/database'; import { DomainEventBus } from '../core/runtime/DomainEventBus'; import { logger } from '../utils/logger'; export interface MarketingChannel { id: string; tenantId: string; name: string; type: 'email' | 'social' | 'sms' | 'push' | 'ads'; config: Record; status: 'active' | 'inactive'; createdAt: Date; } export interface MarketingCampaign { id: string; tenantId: string; name: string; channels: string[]; targetAudience: Record; content: Record; schedule?: Date; status: 'draft' | 'scheduled' | 'running' | 'completed' | 'paused'; metrics?: CampaignMetrics; createdAt: Date; } export interface CampaignMetrics { impressions: number; clicks: number; conversions: number; revenue: number; cost: number; } export interface AutomationRule { id: string; tenantId: string; name: string; trigger: { type: 'user_action' | 'time_based' | 'event'; conditions: Record; }; actions: Array<{ type: string; config: Record; }>; status: 'active' | 'inactive'; createdAt: Date; } export class OmnichannelMarketingService { private static CHANNEL_TABLE = 'cf_marketing_channel'; private static CAMPAIGN_TABLE = 'cf_marketing_campaign'; private static RULE_TABLE = 'cf_automation_rule'; static async initTables(): Promise { await this.initChannelTable(); await this.initCampaignTable(); await this.initRuleTable(); } private static async initChannelTable(): Promise { const hasTable = await db.schema.hasTable(this.CHANNEL_TABLE); if (!hasTable) { logger.info(`[OmnichannelMarketing] Creating ${this.CHANNEL_TABLE} table...`); await db.schema.createTable(this.CHANNEL_TABLE, (table) => { table.string('id', 36).primary(); table.string('tenant_id', 36).notNullable().index(); table.string('name', 128).notNullable(); table.enum('type', ['email', 'social', 'sms', 'push', 'ads']).notNullable(); table.json('config'); table.enum('status', ['active', 'inactive']).defaultTo('active'); table.datetime('created_at').notNullable().defaultTo(db.fn.now()); table.index(['tenant_id', 'type'], 'idx_tenant_type'); }); logger.info(`[OmnichannelMarketing] Table ${this.CHANNEL_TABLE} created`); } } private static async initCampaignTable(): Promise { const hasTable = await db.schema.hasTable(this.CAMPAIGN_TABLE); if (!hasTable) { logger.info(`[OmnichannelMarketing] Creating ${this.CAMPAIGN_TABLE} table...`); await db.schema.createTable(this.CAMPAIGN_TABLE, (table) => { table.string('id', 36).primary(); table.string('tenant_id', 36).notNullable().index(); table.string('name', 128).notNullable(); table.json('channels'); table.json('target_audience'); table.json('content'); table.datetime('schedule'); table.enum('status', ['draft', 'scheduled', 'running', 'completed', 'paused']).defaultTo('draft'); table.json('metrics'); table.datetime('created_at').notNullable().defaultTo(db.fn.now()); }); logger.info(`[OmnichannelMarketing] Table ${this.CAMPAIGN_TABLE} created`); } } private static async initRuleTable(): Promise { const hasTable = await db.schema.hasTable(this.RULE_TABLE); if (!hasTable) { logger.info(`[OmnichannelMarketing] Creating ${this.RULE_TABLE} table...`); await db.schema.createTable(this.RULE_TABLE, (table) => { table.string('id', 36).primary(); table.string('tenant_id', 36).notNullable().index(); table.string('name', 128).notNullable(); table.json('trigger'); table.json('actions'); table.enum('status', ['active', 'inactive']).defaultTo('active'); table.datetime('created_at').notNullable().defaultTo(db.fn.now()); }); logger.info(`[OmnichannelMarketing] Table ${this.RULE_TABLE} created`); } } static async integrateChannels( tenantId: string, channels: Array<{ name: string; type: string; config: Record }> ): Promise { const integrated: MarketingChannel[] = []; for (const channel of channels) { const id = this.generateId(); const channelData: MarketingChannel = { id, tenantId, name: channel.name, type: channel.type as MarketingChannel['type'], config: channel.config, status: 'active', createdAt: new Date(), }; await db(this.CHANNEL_TABLE).insert({ id: channelData.id, tenant_id: channelData.tenantId, name: channelData.name, type: channelData.type, config: JSON.stringify(channelData.config), status: channelData.status, created_at: channelData.createdAt, }); integrated.push(channelData); } logger.info(`[OmnichannelMarketing] Integrated ${integrated.length} channels`); return integrated; } static async createAutomation( tenantId: string, rule: Omit ): Promise { const id = this.generateId(); const newRule: AutomationRule = { ...rule, id, createdAt: new Date(), }; await db(this.RULE_TABLE).insert({ id: newRule.id, tenant_id: newRule.tenantId, name: newRule.name, trigger: JSON.stringify(newRule.trigger), actions: JSON.stringify(newRule.actions), status: newRule.status, created_at: newRule.createdAt, }); await DomainEventBus.publish({ type: 'marketing.automation.created', tenantId, data: { ruleId: id, name: rule.name }, timestamp: new Date(), }); logger.info(`[OmnichannelMarketing] Created automation rule ${id}`); return newRule; } static async executeAutomation( tenantId: string, userBehavior: Record, triggerRules: AutomationRule[] ): Promise { for (const rule of triggerRules) { const { trigger, actions } = rule; let shouldExecute = false; if (trigger.type === 'user_action') { shouldExecute = this.matchConditions(userBehavior, trigger.conditions); } if (shouldExecute) { for (const action of actions) { await this.executeAction(tenantId, action); } logger.info(`[OmnichannelMarketing] Executed automation rule ${rule.id}`); } } } private static matchConditions(behavior: Record, conditions: Record): boolean { for (const [key, value] of Object.entries(conditions)) { if (behavior[key] !== value) { return false; } } return true; } private static async executeAction(tenantId: string, action: { type: string; config: Record }): Promise { logger.info(`[OmnichannelMarketing] Executing action: ${action.type}`); await DomainEventBus.publish({ type: 'marketing.action.executed', tenantId, data: { actionType: action.type, config: action.config }, timestamp: new Date(), }); } static async analyzeCampaignEffect( tenantId: string, campaignId: string, timeRange: { start: Date; end: Date } ): Promise { const campaign = await db(this.CAMPAIGN_TABLE) .where({ tenant_id: tenantId, id: campaignId }) .first(); const metrics: CampaignMetrics = campaign?.metrics ? JSON.parse(campaign.metrics) : { impressions: Math.floor(Math.random() * 10000), clicks: Math.floor(Math.random() * 1000), conversions: Math.floor(Math.random() * 100), revenue: Math.floor(Math.random() * 10000), cost: Math.floor(Math.random() * 1000), }; await db(this.CAMPAIGN_TABLE) .where({ tenant_id: tenantId, id: campaignId }) .update({ metrics: JSON.stringify(metrics) }); logger.info(`[OmnichannelMarketing] Analyzed campaign ${campaignId}`); return metrics; } static async runABTest( tenantId: string, testParams: { name: string; variants: Array<{ id: string; content: Record }>; trafficSplit: number[]; } ): Promise<{ winner: string; results: Record }> { const results: Record = {}; for (let i = 0; i < testParams.variants.length; i++) { const variant = testParams.variants[i]; results[variant.id] = { impressions: Math.floor(Math.random() * 1000 * testParams.trafficSplit[i]), conversions: Math.floor(Math.random() * 100 * testParams.trafficSplit[i]), conversionRate: Math.random() * 0.1 + 0.01, }; } let winner = testParams.variants[0].id; let bestRate = 0; for (const [variantId, data] of Object.entries(results)) { if (data.conversionRate > bestRate) { bestRate = data.conversionRate; winner = variantId; } } logger.info(`[OmnichannelMarketing] A/B test completed, winner: ${winner}`); return { winner, results }; } private static generateId(): string { return `mkt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } }