[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 <noreply@anthropic.com>
This commit is contained in:
parent
45ec3ec09a
commit
c936f447cf
@ -22,6 +22,8 @@ import { NotificationsModule } from './modules/notifications/notifications.modul
|
|||||||
import { DeliveryModule } from './modules/delivery/delivery.module';
|
import { DeliveryModule } from './modules/delivery/delivery.module';
|
||||||
import { TemplatesModule } from './modules/templates/templates.module';
|
import { TemplatesModule } from './modules/templates/templates.module';
|
||||||
import { OnboardingModule } from './modules/onboarding/onboarding.module';
|
import { OnboardingModule } from './modules/onboarding/onboarding.module';
|
||||||
|
import { SettingsModule } from './modules/settings/settings.module';
|
||||||
|
import { ExportsModule } from './modules/exports/exports.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -72,6 +74,7 @@ import { OnboardingModule } from './modules/onboarding/onboarding.module';
|
|||||||
DeliveryModule,
|
DeliveryModule,
|
||||||
TemplatesModule,
|
TemplatesModule,
|
||||||
OnboardingModule,
|
OnboardingModule,
|
||||||
|
SettingsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
271
src/modules/settings/dto/settings.dto.ts
Normal file
271
src/modules/settings/dto/settings.dto.ts
Normal file
@ -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<string, boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
59
src/modules/settings/entities/tenant-settings.entity.ts
Normal file
59
src/modules/settings/entities/tenant-settings.entity.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
65
src/modules/settings/settings.controller.ts
Normal file
65
src/modules/settings/settings.controller.ts
Normal file
@ -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<SettingsResponseDto> {
|
||||||
|
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<SettingsResponseDto> {
|
||||||
|
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<WhatsAppStatusResponseDto> {
|
||||||
|
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<TestWhatsAppResponseDto> {
|
||||||
|
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<SubscriptionInfoResponseDto> {
|
||||||
|
return this.settingsService.getSubscriptionInfo(req.user.tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/modules/settings/settings.module.ts
Normal file
27
src/modules/settings/settings.module.ts
Normal file
@ -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 {}
|
||||||
273
src/modules/settings/settings.service.ts
Normal file
273
src/modules/settings/settings.service.ts
Normal file
@ -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<Tenant>,
|
||||||
|
@InjectRepository(TenantSettings)
|
||||||
|
private readonly settingsRepo: Repository<TenantSettings>,
|
||||||
|
@InjectRepository(TenantWhatsAppNumber)
|
||||||
|
private readonly whatsappRepo: Repository<TenantWhatsAppNumber>,
|
||||||
|
@InjectRepository(Subscription)
|
||||||
|
private readonly subscriptionRepo: Repository<Subscription>,
|
||||||
|
@InjectRepository(TokenBalance)
|
||||||
|
private readonly tokenBalanceRepo: Repository<TokenBalance>,
|
||||||
|
@InjectRepository(Plan)
|
||||||
|
private readonly planRepo: Repository<Plan>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// ==================== GET SETTINGS ====================
|
||||||
|
|
||||||
|
async getSettings(tenantId: string): Promise<SettingsResponseDto> {
|
||||||
|
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<SettingsResponseDto> {
|
||||||
|
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<WhatsAppStatusResponseDto> {
|
||||||
|
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<TestWhatsAppResponseDto> {
|
||||||
|
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<SubscriptionInfoResponseDto> {
|
||||||
|
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<TenantSettings> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user