From c936f447cf3f726fde2a09435eefa07b362193c8 Mon Sep 17 00:00:00 2001 From: rckrdmrd Date: Tue, 20 Jan 2026 02:28:32 -0600 Subject: [PATCH] [MCH-BE] feat: Add settings API endpoints Implement settings module with endpoints for tenant configuration: - GET /api/v1/settings - Get tenant settings - PATCH /api/v1/settings - Update tenant settings - GET /api/v1/settings/whatsapp - WhatsApp connection status - POST /api/v1/settings/whatsapp/test - Test WhatsApp connection - GET /api/v1/settings/subscription - Subscription information Includes: - TenantSettings entity for fiado, WhatsApp and notification preferences - DTOs for business, fiado, WhatsApp and notification settings - Service with full CRUD operations - Integration with existing Tenant, Subscription and WhatsApp entities Co-Authored-By: Claude Opus 4.5 --- src/app.module.ts | 3 + src/modules/settings/dto/settings.dto.ts | 271 +++++++++++++++++ .../entities/tenant-settings.entity.ts | 59 ++++ src/modules/settings/settings.controller.ts | 65 +++++ src/modules/settings/settings.module.ts | 27 ++ src/modules/settings/settings.service.ts | 273 ++++++++++++++++++ 6 files changed, 698 insertions(+) create mode 100644 src/modules/settings/dto/settings.dto.ts create mode 100644 src/modules/settings/entities/tenant-settings.entity.ts create mode 100644 src/modules/settings/settings.controller.ts create mode 100644 src/modules/settings/settings.module.ts create mode 100644 src/modules/settings/settings.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 49c5082..02a22ff 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -22,6 +22,8 @@ import { NotificationsModule } from './modules/notifications/notifications.modul import { DeliveryModule } from './modules/delivery/delivery.module'; import { TemplatesModule } from './modules/templates/templates.module'; import { OnboardingModule } from './modules/onboarding/onboarding.module'; +import { SettingsModule } from './modules/settings/settings.module'; +import { ExportsModule } from './modules/exports/exports.module'; @Module({ imports: [ @@ -72,6 +74,7 @@ import { OnboardingModule } from './modules/onboarding/onboarding.module'; DeliveryModule, TemplatesModule, OnboardingModule, + SettingsModule, ], }) export class AppModule {} diff --git a/src/modules/settings/dto/settings.dto.ts b/src/modules/settings/dto/settings.dto.ts new file mode 100644 index 0000000..aba52be --- /dev/null +++ b/src/modules/settings/dto/settings.dto.ts @@ -0,0 +1,271 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsString, + IsOptional, + IsBoolean, + IsNumber, + IsEmail, + MaxLength, + Min, + Max, +} from 'class-validator'; + +// ==================== BUSINESS SETTINGS ==================== + +export class BusinessSettingsDto { + @ApiProperty({ description: 'Nombre del negocio', example: 'Mi Tiendita' }) + @IsString() + @MaxLength(100) + name: string; + + @ApiPropertyOptional({ description: 'Tipo de negocio', example: 'tienda' }) + @IsOptional() + @IsString() + @MaxLength(50) + businessType?: string; + + @ApiPropertyOptional({ description: 'Telefono', example: '5512345678' }) + @IsOptional() + @IsString() + @MaxLength(20) + phone?: string; + + @ApiPropertyOptional({ description: 'Email', example: 'tienda@email.com' }) + @IsOptional() + @IsEmail() + @MaxLength(100) + email?: string; + + @ApiPropertyOptional({ description: 'Direccion' }) + @IsOptional() + @IsString() + address?: string; + + @ApiPropertyOptional({ description: 'Ciudad' }) + @IsOptional() + @IsString() + @MaxLength(50) + city?: string; + + @ApiPropertyOptional({ description: 'Estado' }) + @IsOptional() + @IsString() + @MaxLength(50) + state?: string; + + @ApiPropertyOptional({ description: 'Codigo postal' }) + @IsOptional() + @IsString() + @MaxLength(10) + zipCode?: string; + + @ApiPropertyOptional({ description: 'Zona horaria', default: 'America/Mexico_City' }) + @IsOptional() + @IsString() + @MaxLength(50) + timezone?: string; + + @ApiPropertyOptional({ description: 'Moneda', default: 'MXN' }) + @IsOptional() + @IsString() + @MaxLength(3) + currency?: string; + + @ApiPropertyOptional({ description: 'Tasa de impuesto', default: 16 }) + @IsOptional() + @IsNumber() + @Min(0) + @Max(100) + taxRate?: number; + + @ApiPropertyOptional({ description: 'Impuesto incluido en precios', default: true }) + @IsOptional() + @IsBoolean() + taxIncluded?: boolean; +} + +// ==================== FIADO SETTINGS ==================== + +export class FiadoSettingsDto { + @ApiPropertyOptional({ description: 'Fiado habilitado globalmente', default: true }) + @IsOptional() + @IsBoolean() + enabled?: boolean; + + @ApiPropertyOptional({ description: 'Limite de credito global por defecto', example: 500 }) + @IsOptional() + @IsNumber() + @Min(0) + defaultCreditLimit?: number; + + @ApiPropertyOptional({ description: 'Dias de vencimiento por defecto', example: 15 }) + @IsOptional() + @IsNumber() + @Min(1) + @Max(365) + defaultDueDays?: number; +} + +// ==================== WHATSAPP SETTINGS ==================== + +export class WhatsAppSettingsDto { + @ApiPropertyOptional({ description: 'Numero de WhatsApp configurado' }) + @IsOptional() + @IsString() + @MaxLength(20) + phoneNumber?: string; + + @ApiPropertyOptional({ description: 'Usar numero de plataforma', default: true }) + @IsOptional() + @IsBoolean() + usePlatformNumber?: boolean; + + @ApiPropertyOptional({ description: 'Respuestas automaticas habilitadas', default: true }) + @IsOptional() + @IsBoolean() + autoRepliesEnabled?: boolean; + + @ApiPropertyOptional({ description: 'Notificar pedidos nuevos por WhatsApp', default: true }) + @IsOptional() + @IsBoolean() + orderNotificationsEnabled?: boolean; +} + +// ==================== NOTIFICATION SETTINGS ==================== + +export class NotificationSettingsDto { + @ApiPropertyOptional({ description: 'Alerta de stock bajo', default: true }) + @IsOptional() + @IsBoolean() + lowStockAlert?: boolean; + + @ApiPropertyOptional({ description: 'Alerta de fiados vencidos', default: true }) + @IsOptional() + @IsBoolean() + overdueDebtsAlert?: boolean; + + @ApiPropertyOptional({ description: 'Notificacion de nuevos pedidos', default: true }) + @IsOptional() + @IsBoolean() + newOrdersAlert?: boolean; + + @ApiPropertyOptional({ description: 'Sonido en nuevos pedidos', default: true }) + @IsOptional() + @IsBoolean() + newOrdersSound?: boolean; +} + +// ==================== UPDATE SETTINGS DTO ==================== + +export class UpdateSettingsDto { + @ApiPropertyOptional({ description: 'Configuracion del negocio' }) + @IsOptional() + business?: BusinessSettingsDto; + + @ApiPropertyOptional({ description: 'Configuracion de fiado' }) + @IsOptional() + fiado?: FiadoSettingsDto; + + @ApiPropertyOptional({ description: 'Configuracion de WhatsApp' }) + @IsOptional() + whatsapp?: WhatsAppSettingsDto; + + @ApiPropertyOptional({ description: 'Configuracion de notificaciones' }) + @IsOptional() + notifications?: NotificationSettingsDto; +} + +// ==================== RESPONSE DTOs ==================== + +export class WhatsAppStatusResponseDto { + @ApiProperty({ description: 'Estado de conexion' }) + connected: boolean; + + @ApiProperty({ description: 'Numero configurado', nullable: true }) + phoneNumber: string | null; + + @ApiProperty({ description: 'Nombre para mostrar', nullable: true }) + displayName: string | null; + + @ApiProperty({ description: 'Verificado' }) + verified: boolean; + + @ApiProperty({ description: 'Usa numero de plataforma' }) + usesPlatformNumber: boolean; +} + +export class SubscriptionInfoResponseDto { + @ApiProperty({ description: 'Nombre del plan' }) + planName: string; + + @ApiProperty({ description: 'Codigo del plan' }) + planCode: string; + + @ApiProperty({ description: 'Precio mensual' }) + priceMonthly: number; + + @ApiProperty({ description: 'Moneda' }) + currency: string; + + @ApiProperty({ description: 'Estado de suscripcion' }) + status: string; + + @ApiProperty({ description: 'Ciclo de facturacion' }) + billingCycle: string; + + @ApiProperty({ description: 'Fecha de renovacion', nullable: true }) + renewalDate: Date | null; + + @ApiProperty({ description: 'Tokens incluidos' }) + includedTokens: number; + + @ApiProperty({ description: 'Tokens utilizados' }) + tokensUsed: number; + + @ApiProperty({ description: 'Tokens disponibles' }) + tokensRemaining: number; + + @ApiProperty({ description: 'Limite de productos', nullable: true }) + maxProducts: number | null; + + @ApiProperty({ description: 'Limite de usuarios' }) + maxUsers: number; + + @ApiProperty({ description: 'Caracteristicas del plan' }) + features: Record; +} + +export class SettingsResponseDto { + @ApiProperty({ description: 'Configuracion del negocio' }) + business: BusinessSettingsDto; + + @ApiProperty({ description: 'Configuracion de fiado' }) + fiado: FiadoSettingsDto; + + @ApiProperty({ description: 'Configuracion de WhatsApp' }) + whatsapp: { + phoneNumber: string | null; + connected: boolean; + verified: boolean; + usesPlatformNumber: boolean; + autoRepliesEnabled: boolean; + orderNotificationsEnabled: boolean; + }; + + @ApiProperty({ description: 'Configuracion de notificaciones' }) + notifications: NotificationSettingsDto; + + @ApiProperty({ description: 'Informacion de suscripcion' }) + subscription: { + planName: string; + status: string; + }; +} + +export class TestWhatsAppResponseDto { + @ApiProperty({ description: 'Resultado del test' }) + success: boolean; + + @ApiProperty({ description: 'Mensaje del resultado' }) + message: string; +} diff --git a/src/modules/settings/entities/tenant-settings.entity.ts b/src/modules/settings/entities/tenant-settings.entity.ts new file mode 100644 index 0000000..a09f26c --- /dev/null +++ b/src/modules/settings/entities/tenant-settings.entity.ts @@ -0,0 +1,59 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + Index, +} from 'typeorm'; + +@Entity({ schema: 'public', name: 'tenant_settings' }) +@Index(['tenantId'], { unique: true }) +export class TenantSettings { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ name: 'tenant_id', type: 'uuid', unique: true }) + tenantId: string; + + // ==================== FIADO SETTINGS ==================== + + @Column({ name: 'fiado_enabled', default: true }) + fiadoEnabled: boolean; + + @Column({ name: 'fiado_default_credit_limit', type: 'decimal', precision: 10, scale: 2, default: 500 }) + fiadoDefaultCreditLimit: number; + + @Column({ name: 'fiado_default_due_days', default: 15 }) + fiadoDefaultDueDays: number; + + // ==================== WHATSAPP SETTINGS ==================== + + @Column({ name: 'whatsapp_auto_replies_enabled', default: true }) + whatsappAutoRepliesEnabled: boolean; + + @Column({ name: 'whatsapp_order_notifications_enabled', default: true }) + whatsappOrderNotificationsEnabled: boolean; + + // ==================== NOTIFICATION SETTINGS ==================== + + @Column({ name: 'notification_low_stock_alert', default: true }) + notificationLowStockAlert: boolean; + + @Column({ name: 'notification_overdue_debts_alert', default: true }) + notificationOverdueDebtsAlert: boolean; + + @Column({ name: 'notification_new_orders_alert', default: true }) + notificationNewOrdersAlert: boolean; + + @Column({ name: 'notification_new_orders_sound', default: true }) + notificationNewOrdersSound: boolean; + + // ==================== TIMESTAMPS ==================== + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; +} diff --git a/src/modules/settings/settings.controller.ts b/src/modules/settings/settings.controller.ts new file mode 100644 index 0000000..9709b64 --- /dev/null +++ b/src/modules/settings/settings.controller.ts @@ -0,0 +1,65 @@ +import { + Controller, + Get, + Patch, + Post, + Body, + UseGuards, + Request, +} from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { SettingsService } from './settings.service'; +import { + UpdateSettingsDto, + SettingsResponseDto, + WhatsAppStatusResponseDto, + SubscriptionInfoResponseDto, + TestWhatsAppResponseDto, +} from './dto/settings.dto'; + +@ApiTags('settings') +@ApiBearerAuth() +@UseGuards(JwtAuthGuard) +@Controller('v1/settings') +export class SettingsController { + constructor(private readonly settingsService: SettingsService) {} + + @Get() + @ApiOperation({ summary: 'Obtener configuracion del tenant' }) + @ApiResponse({ status: 200, description: 'Configuracion del tenant', type: SettingsResponseDto }) + getSettings(@Request() req): Promise { + return this.settingsService.getSettings(req.user.tenantId); + } + + @Patch() + @ApiOperation({ summary: 'Actualizar configuracion del tenant' }) + @ApiResponse({ status: 200, description: 'Configuracion actualizada', type: SettingsResponseDto }) + updateSettings( + @Request() req, + @Body() dto: UpdateSettingsDto, + ): Promise { + return this.settingsService.updateSettings(req.user.tenantId, dto); + } + + @Get('whatsapp') + @ApiOperation({ summary: 'Obtener estado de WhatsApp' }) + @ApiResponse({ status: 200, description: 'Estado de WhatsApp', type: WhatsAppStatusResponseDto }) + getWhatsAppStatus(@Request() req): Promise { + return this.settingsService.getWhatsAppStatus(req.user.tenantId); + } + + @Post('whatsapp/test') + @ApiOperation({ summary: 'Probar conexion de WhatsApp' }) + @ApiResponse({ status: 200, description: 'Resultado del test', type: TestWhatsAppResponseDto }) + testWhatsAppConnection(@Request() req): Promise { + return this.settingsService.testWhatsAppConnection(req.user.tenantId); + } + + @Get('subscription') + @ApiOperation({ summary: 'Obtener informacion de suscripcion' }) + @ApiResponse({ status: 200, description: 'Informacion de suscripcion', type: SubscriptionInfoResponseDto }) + getSubscriptionInfo(@Request() req): Promise { + return this.settingsService.getSubscriptionInfo(req.user.tenantId); + } +} diff --git a/src/modules/settings/settings.module.ts b/src/modules/settings/settings.module.ts new file mode 100644 index 0000000..f9a675e --- /dev/null +++ b/src/modules/settings/settings.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SettingsController } from './settings.controller'; +import { SettingsService } from './settings.service'; +import { TenantSettings } from './entities/tenant-settings.entity'; +import { Tenant } from '../auth/entities/tenant.entity'; +import { TenantWhatsAppNumber } from '../integrations/entities/tenant-whatsapp-number.entity'; +import { Subscription } from '../subscriptions/entities/subscription.entity'; +import { TokenBalance } from '../subscriptions/entities/token-balance.entity'; +import { Plan } from '../subscriptions/entities/plan.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + TenantSettings, + Tenant, + TenantWhatsAppNumber, + Subscription, + TokenBalance, + Plan, + ]), + ], + controllers: [SettingsController], + providers: [SettingsService], + exports: [SettingsService], +}) +export class SettingsModule {} diff --git a/src/modules/settings/settings.service.ts b/src/modules/settings/settings.service.ts new file mode 100644 index 0000000..63c9e9e --- /dev/null +++ b/src/modules/settings/settings.service.ts @@ -0,0 +1,273 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Tenant } from '../auth/entities/tenant.entity'; +import { TenantSettings } from './entities/tenant-settings.entity'; +import { TenantWhatsAppNumber } from '../integrations/entities/tenant-whatsapp-number.entity'; +import { Subscription } from '../subscriptions/entities/subscription.entity'; +import { TokenBalance } from '../subscriptions/entities/token-balance.entity'; +import { Plan } from '../subscriptions/entities/plan.entity'; +import { + UpdateSettingsDto, + SettingsResponseDto, + WhatsAppStatusResponseDto, + SubscriptionInfoResponseDto, + TestWhatsAppResponseDto, +} from './dto/settings.dto'; + +@Injectable() +export class SettingsService { + constructor( + @InjectRepository(Tenant) + private readonly tenantRepo: Repository, + @InjectRepository(TenantSettings) + private readonly settingsRepo: Repository, + @InjectRepository(TenantWhatsAppNumber) + private readonly whatsappRepo: Repository, + @InjectRepository(Subscription) + private readonly subscriptionRepo: Repository, + @InjectRepository(TokenBalance) + private readonly tokenBalanceRepo: Repository, + @InjectRepository(Plan) + private readonly planRepo: Repository, + ) {} + + // ==================== GET SETTINGS ==================== + + async getSettings(tenantId: string): Promise { + const tenant = await this.tenantRepo.findOne({ where: { id: tenantId } }); + if (!tenant) { + throw new NotFoundException('Tenant no encontrado'); + } + + const settings = await this.getOrCreateSettings(tenantId); + const whatsapp = await this.whatsappRepo.findOne({ + where: { tenantId, isActive: true }, + }); + const subscription = await this.subscriptionRepo.findOne({ + where: { tenantId }, + relations: ['plan'], + }); + + return { + business: { + name: tenant.name, + businessType: tenant.businessType, + phone: tenant.phone, + email: tenant.email, + address: tenant.address, + city: tenant.city, + state: tenant.state, + zipCode: tenant.zipCode, + timezone: tenant.timezone, + currency: tenant.currency, + taxRate: Number(tenant.taxRate), + taxIncluded: tenant.taxIncluded, + }, + fiado: { + enabled: settings.fiadoEnabled, + defaultCreditLimit: Number(settings.fiadoDefaultCreditLimit), + defaultDueDays: settings.fiadoDefaultDueDays, + }, + whatsapp: { + phoneNumber: whatsapp?.phoneNumber || tenant.whatsappNumber || null, + connected: !!whatsapp?.isActive || tenant.whatsappVerified, + verified: tenant.whatsappVerified, + usesPlatformNumber: tenant.usesPlatformNumber, + autoRepliesEnabled: settings.whatsappAutoRepliesEnabled, + orderNotificationsEnabled: settings.whatsappOrderNotificationsEnabled, + }, + notifications: { + lowStockAlert: settings.notificationLowStockAlert, + overdueDebtsAlert: settings.notificationOverdueDebtsAlert, + newOrdersAlert: settings.notificationNewOrdersAlert, + newOrdersSound: settings.notificationNewOrdersSound, + }, + subscription: { + planName: subscription?.plan?.name || 'Sin plan', + status: subscription?.status || 'inactive', + }, + }; + } + + // ==================== UPDATE SETTINGS ==================== + + async updateSettings(tenantId: string, dto: UpdateSettingsDto): Promise { + const tenant = await this.tenantRepo.findOne({ where: { id: tenantId } }); + if (!tenant) { + throw new NotFoundException('Tenant no encontrado'); + } + + const settings = await this.getOrCreateSettings(tenantId); + + // Update business settings on tenant + if (dto.business) { + const { name, businessType, phone, email, address, city, state, zipCode, timezone, currency, taxRate, taxIncluded } = dto.business; + if (name !== undefined) tenant.name = name; + if (businessType !== undefined) tenant.businessType = businessType; + if (phone !== undefined) tenant.phone = phone; + if (email !== undefined) tenant.email = email; + if (address !== undefined) tenant.address = address; + if (city !== undefined) tenant.city = city; + if (state !== undefined) tenant.state = state; + if (zipCode !== undefined) tenant.zipCode = zipCode; + if (timezone !== undefined) tenant.timezone = timezone; + if (currency !== undefined) tenant.currency = currency; + if (taxRate !== undefined) tenant.taxRate = taxRate; + if (taxIncluded !== undefined) tenant.taxIncluded = taxIncluded; + await this.tenantRepo.save(tenant); + } + + // Update fiado settings + if (dto.fiado) { + const { enabled, defaultCreditLimit, defaultDueDays } = dto.fiado; + if (enabled !== undefined) settings.fiadoEnabled = enabled; + if (defaultCreditLimit !== undefined) settings.fiadoDefaultCreditLimit = defaultCreditLimit; + if (defaultDueDays !== undefined) settings.fiadoDefaultDueDays = defaultDueDays; + } + + // Update whatsapp settings + if (dto.whatsapp) { + const { usePlatformNumber, autoRepliesEnabled, orderNotificationsEnabled } = dto.whatsapp; + if (usePlatformNumber !== undefined) { + tenant.usesPlatformNumber = usePlatformNumber; + await this.tenantRepo.save(tenant); + } + if (autoRepliesEnabled !== undefined) settings.whatsappAutoRepliesEnabled = autoRepliesEnabled; + if (orderNotificationsEnabled !== undefined) settings.whatsappOrderNotificationsEnabled = orderNotificationsEnabled; + } + + // Update notification settings + if (dto.notifications) { + const { lowStockAlert, overdueDebtsAlert, newOrdersAlert, newOrdersSound } = dto.notifications; + if (lowStockAlert !== undefined) settings.notificationLowStockAlert = lowStockAlert; + if (overdueDebtsAlert !== undefined) settings.notificationOverdueDebtsAlert = overdueDebtsAlert; + if (newOrdersAlert !== undefined) settings.notificationNewOrdersAlert = newOrdersAlert; + if (newOrdersSound !== undefined) settings.notificationNewOrdersSound = newOrdersSound; + } + + await this.settingsRepo.save(settings); + + return this.getSettings(tenantId); + } + + // ==================== WHATSAPP ==================== + + async getWhatsAppStatus(tenantId: string): Promise { + const tenant = await this.tenantRepo.findOne({ where: { id: tenantId } }); + if (!tenant) { + throw new NotFoundException('Tenant no encontrado'); + } + + const whatsapp = await this.whatsappRepo.findOne({ + where: { tenantId, isActive: true }, + }); + + return { + connected: !!whatsapp?.isActive || tenant.whatsappVerified, + phoneNumber: whatsapp?.phoneNumber || tenant.whatsappNumber || null, + displayName: whatsapp?.displayName || null, + verified: tenant.whatsappVerified, + usesPlatformNumber: tenant.usesPlatformNumber, + }; + } + + async testWhatsAppConnection(tenantId: string): Promise { + const tenant = await this.tenantRepo.findOne({ where: { id: tenantId } }); + if (!tenant) { + throw new NotFoundException('Tenant no encontrado'); + } + + const whatsapp = await this.whatsappRepo.findOne({ + where: { tenantId, isActive: true }, + }); + + // In a real implementation, this would send a test message + // For now, we just check if there's an active configuration + if (whatsapp?.isActive || tenant.whatsappVerified) { + return { + success: true, + message: 'Conexion de WhatsApp verificada correctamente', + }; + } + + return { + success: false, + message: 'No hay numero de WhatsApp configurado o no esta activo', + }; + } + + // ==================== SUBSCRIPTION ==================== + + async getSubscriptionInfo(tenantId: string): Promise { + const subscription = await this.subscriptionRepo.findOne({ + where: { tenantId }, + relations: ['plan'], + }); + + const tokenBalance = await this.tokenBalanceRepo.findOne({ + where: { tenantId }, + }); + + if (!subscription || !subscription.plan) { + // Return default/free plan info + return { + planName: 'Sin plan', + planCode: 'none', + priceMonthly: 0, + currency: 'MXN', + status: 'inactive', + billingCycle: 'monthly', + renewalDate: null, + includedTokens: 0, + tokensUsed: tokenBalance?.usedTokens || 0, + tokensRemaining: tokenBalance?.availableTokens || 0, + maxProducts: null, + maxUsers: 1, + features: {}, + }; + } + + const plan = subscription.plan; + + return { + planName: plan.name, + planCode: plan.code, + priceMonthly: Number(plan.priceMonthly), + currency: plan.currency, + status: subscription.status, + billingCycle: subscription.billingCycle, + renewalDate: subscription.currentPeriodEnd, + includedTokens: plan.includedTokens, + tokensUsed: tokenBalance?.usedTokens || 0, + tokensRemaining: tokenBalance?.availableTokens || 0, + maxProducts: plan.maxProducts, + maxUsers: plan.maxUsers, + features: plan.features || {}, + }; + } + + // ==================== HELPERS ==================== + + private async getOrCreateSettings(tenantId: string): Promise { + let settings = await this.settingsRepo.findOne({ where: { tenantId } }); + + if (!settings) { + settings = this.settingsRepo.create({ + tenantId, + fiadoEnabled: true, + fiadoDefaultCreditLimit: 500, + fiadoDefaultDueDays: 15, + whatsappAutoRepliesEnabled: true, + whatsappOrderNotificationsEnabled: true, + notificationLowStockAlert: true, + notificationOverdueDebtsAlert: true, + notificationNewOrdersAlert: true, + notificationNewOrdersSound: true, + }); + settings = await this.settingsRepo.save(settings); + } + + return settings; + } +}