[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 { 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 {}
|
||||
|
||||
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