template-saas-backend-v2/src/modules/goals/dto/definition.dto.ts
Adrian Flores Cortes 09ea4d51b4 [SAAS-022] feat: Implement Goals module backend
- Added 4 entities: DefinitionEntity, AssignmentEntity, ProgressLogEntity, MilestoneNotificationEntity
- Added DTOs for definitions and assignments
- Added services for definitions and assignments CRUD
- Added controllers with full REST API endpoints
- Added GoalsModule and registered in AppModule

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 06:25:44 -06:00

274 lines
7.1 KiB
TypeScript

import { IsString, IsOptional, IsEnum, IsNumber, IsDate, IsArray, IsObject, Min, ValidateNested, IsBoolean } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';
import { GoalType, MetricType, PeriodType, DataSource, GoalStatus, SourceConfig, Milestone } from '../entities/definition.entity';
// ─────────────────────────────────────────────
// Milestone DTO
// ─────────────────────────────────────────────
export class MilestoneDto {
@ApiProperty({ example: 50 })
@IsNumber()
@Min(0)
percentage: number;
@ApiProperty({ example: true })
@IsBoolean()
notify: boolean;
}
// ─────────────────────────────────────────────
// Source Config DTO
// ─────────────────────────────────────────────
export class SourceConfigDto {
@ApiPropertyOptional({ example: 'sales' })
@IsOptional()
@IsString()
module?: string;
@ApiPropertyOptional({ example: 'opportunities' })
@IsOptional()
@IsString()
entity?: string;
@ApiPropertyOptional({ example: { status: 'won' } })
@IsOptional()
@IsObject()
filter?: Record<string, unknown>;
@ApiPropertyOptional({ enum: ['sum', 'count', 'avg'] })
@IsOptional()
@IsString()
aggregation?: 'sum' | 'count' | 'avg';
@ApiPropertyOptional({ example: 'amount' })
@IsOptional()
@IsString()
field?: string;
}
// ─────────────────────────────────────────────
// Create Definition DTO
// ─────────────────────────────────────────────
export class CreateDefinitionDto {
@ApiProperty({ example: 'Q1 Sales Target' })
@IsString()
name: string;
@ApiPropertyOptional({ example: 'Achieve $100,000 in closed deals' })
@IsOptional()
@IsString()
description?: string;
@ApiPropertyOptional({ example: 'sales' })
@IsOptional()
@IsString()
category?: string;
@ApiPropertyOptional({ enum: GoalType, default: GoalType.TARGET })
@IsOptional()
@IsEnum(GoalType)
type?: GoalType;
@ApiPropertyOptional({ enum: MetricType, default: MetricType.NUMBER })
@IsOptional()
@IsEnum(MetricType)
metric?: MetricType;
@ApiProperty({ example: 100000 })
@IsNumber()
@Min(0)
targetValue: number;
@ApiPropertyOptional({ example: 'USD' })
@IsOptional()
@IsString()
unit?: string;
@ApiPropertyOptional({ enum: PeriodType, default: PeriodType.MONTHLY })
@IsOptional()
@IsEnum(PeriodType)
period?: PeriodType;
@ApiProperty({ example: '2026-01-01' })
@Type(() => Date)
@IsDate()
startsAt: Date;
@ApiProperty({ example: '2026-03-31' })
@Type(() => Date)
@IsDate()
endsAt: Date;
@ApiPropertyOptional({ enum: DataSource, default: DataSource.MANUAL })
@IsOptional()
@IsEnum(DataSource)
source?: DataSource;
@ApiPropertyOptional({ type: SourceConfigDto })
@IsOptional()
@ValidateNested()
@Type(() => SourceConfigDto)
sourceConfig?: SourceConfigDto;
@ApiPropertyOptional({ type: [MilestoneDto] })
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => MilestoneDto)
milestones?: MilestoneDto[];
@ApiPropertyOptional({ enum: GoalStatus, default: GoalStatus.DRAFT })
@IsOptional()
@IsEnum(GoalStatus)
status?: GoalStatus;
@ApiPropertyOptional({ type: [String], example: ['sales', 'q1'] })
@IsOptional()
@IsArray()
@IsString({ each: true })
tags?: string[];
}
// ─────────────────────────────────────────────
// Update Definition DTO
// ─────────────────────────────────────────────
export class UpdateDefinitionDto extends PartialType(CreateDefinitionDto) {}
// ─────────────────────────────────────────────
// Update Status DTO
// ─────────────────────────────────────────────
export class UpdateDefinitionStatusDto {
@ApiProperty({ enum: GoalStatus })
@IsEnum(GoalStatus)
status: GoalStatus;
}
// ─────────────────────────────────────────────
// Definition Response DTO
// ─────────────────────────────────────────────
export class DefinitionResponseDto {
@ApiProperty()
id: string;
@ApiProperty()
tenantId: string;
@ApiProperty()
name: string;
@ApiPropertyOptional()
description: string | null;
@ApiPropertyOptional()
category: string | null;
@ApiProperty({ enum: GoalType })
type: GoalType;
@ApiProperty({ enum: MetricType })
metric: MetricType;
@ApiProperty()
targetValue: number;
@ApiPropertyOptional()
unit: string | null;
@ApiProperty({ enum: PeriodType })
period: PeriodType;
@ApiProperty()
startsAt: Date;
@ApiProperty()
endsAt: Date;
@ApiProperty({ enum: DataSource })
source: DataSource;
@ApiProperty()
sourceConfig: SourceConfig;
@ApiProperty({ type: [MilestoneDto] })
milestones: Milestone[];
@ApiProperty({ enum: GoalStatus })
status: GoalStatus;
@ApiProperty({ type: [String] })
tags: string[];
@ApiProperty()
createdAt: Date;
@ApiProperty()
updatedAt: Date;
@ApiPropertyOptional()
createdBy: string | null;
@ApiPropertyOptional()
assignmentCount?: number;
}
// ─────────────────────────────────────────────
// Definition Filters DTO
// ─────────────────────────────────────────────
export class DefinitionFiltersDto {
@ApiPropertyOptional({ enum: GoalStatus })
@IsOptional()
@IsEnum(GoalStatus)
status?: GoalStatus;
@ApiPropertyOptional({ enum: PeriodType })
@IsOptional()
@IsEnum(PeriodType)
period?: PeriodType;
@ApiPropertyOptional()
@IsOptional()
@IsString()
category?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
search?: string;
@ApiPropertyOptional()
@IsOptional()
@Type(() => Date)
@IsDate()
activeOn?: Date;
@ApiPropertyOptional({ example: 'createdAt' })
@IsOptional()
@IsString()
sortBy?: string;
@ApiPropertyOptional({ enum: ['ASC', 'DESC'], default: 'DESC' })
@IsOptional()
@IsString()
sortOrder?: 'ASC' | 'DESC';
@ApiPropertyOptional({ default: 1 })
@IsOptional()
@Type(() => Number)
@IsNumber()
page?: number;
@ApiPropertyOptional({ default: 20 })
@IsOptional()
@Type(() => Number)
@IsNumber()
limit?: number;
}