Match desktop/mobile mockups and compute current amounts, status, and KPIs from real CRM data. Co-authored-by: Cursor <cursoragent@cursor.com>
173 lines
2.4 KiB
TypeScript
173 lines
2.4 KiB
TypeScript
import {
|
|
IsIn,
|
|
IsNumber,
|
|
IsOptional,
|
|
IsString,
|
|
Max,
|
|
MaxLength,
|
|
Min,
|
|
MinLength,
|
|
} from 'class-validator';
|
|
import { Type } from 'class-transformer';
|
|
|
|
const SALES_GOAL_TYPES = [
|
|
'sales',
|
|
'customer',
|
|
'revenue',
|
|
'deal',
|
|
'activity',
|
|
'quality',
|
|
] as const;
|
|
|
|
const SALES_GOAL_STATUSES = [
|
|
'in_progress',
|
|
'completed',
|
|
'needs_attention',
|
|
] as const;
|
|
|
|
export class CreateSalesGoalDto {
|
|
@IsString()
|
|
@MinLength(2)
|
|
@MaxLength(200)
|
|
title!: string;
|
|
|
|
@IsString()
|
|
periodStart!: string;
|
|
|
|
@IsString()
|
|
periodEnd!: string;
|
|
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
targetAmount!: number;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsIn(SALES_GOAL_TYPES)
|
|
type?: string;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
forecastAmount?: number | null;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
@Max(100)
|
|
probability?: number | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(8)
|
|
currency?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(2000)
|
|
notes?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
userId?: string | null;
|
|
}
|
|
|
|
export class UpdateSalesGoalDto {
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(2)
|
|
@MaxLength(200)
|
|
title?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
periodStart?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
periodEnd?: string;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
targetAmount?: number;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsIn(SALES_GOAL_TYPES)
|
|
type?: string;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
forecastAmount?: number | null;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
@Max(100)
|
|
probability?: number | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(8)
|
|
currency?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(2000)
|
|
notes?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
userId?: string | null;
|
|
}
|
|
|
|
export class ForecastQueryDto {
|
|
@IsOptional()
|
|
@IsString()
|
|
from?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
to?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
userId?: string;
|
|
}
|
|
|
|
export class ListGoalsQueryDto {
|
|
@IsOptional()
|
|
@IsString()
|
|
from?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
to?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
userId?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
q?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsIn(SALES_GOAL_STATUSES)
|
|
status?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsIn(SALES_GOAL_TYPES)
|
|
type?: string;
|
|
}
|