Files
CRM_Montzar/apps/api/src/gamification/dto.ts
T
MahdiandCursor 60d797f184 feat: باشگاه امتیازات کارکنان (موتور امتیاز، جدول رتبه، فروشگاه پاداش، نشان‌ها، پنل مدیریت و گزارش ROI)
- موتور رویدادمحور با شرط، محدودیت، تأیید مدیر و idempotency
- اتصال به لید، مشتری، معامله، فعالیت، روتین، پیش‌فاکتور، فاکتور و تیکت
- صفحه /club و /club/admin، اعلان زنده امتیاز، آیتم منو
- انتقال فرم‌ساز سرنخ به ابزارها

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 21:28:49 +03:30

342 lines
5.2 KiB
TypeScript

import { Type } from 'class-transformer';
import {
ArrayMaxSize,
IsArray,
IsBoolean,
IsIn,
IsInt,
IsOptional,
IsString,
Max,
MaxLength,
Min,
MinLength,
ValidateIf,
} from 'class-validator';
import type { GamificationCondition, GamificationLevelDef } from '@mento/shared';
import { GAMIFICATION_EVENT_KEYS } from '@mento/shared';
const nullable = () => ValidateIf((_, v) => v !== '' && v != null);
export class SaveRuleDto {
@IsString()
@MinLength(2)
@MaxLength(120)
name!: string;
@IsOptional()
@nullable()
@IsString()
@MaxLength(500)
description?: string | null;
@IsIn(GAMIFICATION_EVENT_KEYS)
event!: (typeof GAMIFICATION_EVENT_KEYS)[number];
@Type(() => Number)
@IsInt()
@Min(-10000)
@Max(100000)
score!: number;
@Type(() => Number)
@IsInt()
@Min(0)
@Max(100000)
coins!: number;
@IsOptional()
@IsArray()
@ArrayMaxSize(10)
conditions?: GamificationCondition[];
@IsOptional()
@IsBoolean()
isActive?: boolean;
@IsOptional()
@nullable()
@Type(() => Number)
@IsInt()
@Min(1)
maxPerDay?: number | null;
@IsOptional()
@nullable()
@Type(() => Number)
@IsInt()
@Min(1)
maxCoinsPerDay?: number | null;
@IsOptional()
@nullable()
@Type(() => Number)
@IsInt()
@Min(1)
maxPerUser?: number | null;
@IsOptional()
@nullable()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100000)
cooldownMinutes?: number | null;
@IsOptional()
@IsBoolean()
oncePerEntity?: boolean;
@IsOptional()
@IsBoolean()
firstTimeOnly?: boolean;
@IsOptional()
@IsBoolean()
requiresApproval?: boolean;
@IsOptional()
@IsArray()
@ArrayMaxSize(10)
@IsString({ each: true })
roles?: string[];
@IsOptional()
@IsArray()
@ArrayMaxSize(50)
@IsString({ each: true })
teamIds?: string[];
}
export class ToggleDto {
@IsBoolean()
isActive!: boolean;
}
export class AdjustDto {
@IsString()
userId!: string;
@Type(() => Number)
@IsInt()
@Min(-1000000)
@Max(1000000)
amount!: number;
@IsString()
@MinLength(2)
@MaxLength(500)
reason!: string;
}
export class DecisionDto {
@IsOptional()
@nullable()
@IsString()
@MaxLength(500)
note?: string | null;
}
export class RedeemDto {
@IsString()
rewardId!: string;
@IsOptional()
@nullable()
@IsString()
@MaxLength(500)
note?: string | null;
}
export class SaveRewardDto {
@IsString()
@MinLength(2)
@MaxLength(120)
name!: string;
@IsOptional()
@nullable()
@IsString()
@MaxLength(500)
description?: string | null;
@Type(() => Number)
@IsInt()
@Min(1)
@Max(10000000)
cost!: number;
@IsOptional()
@IsString()
@MaxLength(40)
icon?: string;
@IsOptional()
@IsString()
@MaxLength(20)
color?: string;
@IsOptional()
@nullable()
@Type(() => Number)
@IsInt()
@Min(0)
stock?: number | null;
@IsOptional()
@IsBoolean()
isActive?: boolean;
@IsOptional()
@IsBoolean()
requiresApproval?: boolean;
}
export class SaveBadgeDto {
@IsString()
@MinLength(2)
@MaxLength(120)
name!: string;
@IsOptional()
@nullable()
@IsString()
@MaxLength(500)
description?: string | null;
@IsOptional()
@IsString()
@MaxLength(40)
icon?: string;
@IsOptional()
@IsString()
@MaxLength(20)
color?: string;
@IsIn(['event_count', 'score_total', 'routine_streak', 'single_deal_amount'])
metric!: 'event_count' | 'score_total' | 'routine_streak' | 'single_deal_amount';
@IsOptional()
@nullable()
@IsIn(GAMIFICATION_EVENT_KEYS)
event?: (typeof GAMIFICATION_EVENT_KEYS)[number] | null;
@Type(() => Number)
@IsInt()
@Min(1)
threshold!: number;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
@Max(1000000)
coins?: number;
@IsOptional()
@IsBoolean()
isActive?: boolean;
}
export class SaveBonusDto {
@IsString()
@MinLength(2)
@MaxLength(120)
name!: string;
@IsOptional()
@nullable()
@IsString()
@MaxLength(500)
description?: string | null;
@IsIn(['event_count_period', 'routine_streak', 'top_of_month'])
kind!: 'event_count_period' | 'routine_streak' | 'top_of_month';
@IsOptional()
@nullable()
@IsIn(GAMIFICATION_EVENT_KEYS)
event?: (typeof GAMIFICATION_EVENT_KEYS)[number] | null;
@Type(() => Number)
@IsInt()
@Min(1)
threshold!: number;
@IsOptional()
@IsIn(['day', 'week', 'month'])
period?: 'day' | 'week' | 'month';
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
@Max(1000000)
score?: number;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
@Max(1000000)
coins?: number;
@IsOptional()
@IsBoolean()
isActive?: boolean;
}
export class UpdateSettingsDto {
@IsOptional()
@IsBoolean()
enabled?: boolean;
@IsOptional()
@IsArray()
@ArrayMaxSize(20)
levels?: GamificationLevelDef[];
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(10000000)
monthlyGoal?: number;
@IsOptional()
@IsBoolean()
showFullLeaderboard?: boolean;
}
export class TxQueryDto {
@IsOptional()
@IsString()
userId?: string;
@IsOptional()
@IsString()
kind?: string;
@IsOptional()
@IsString()
event?: string;
@IsOptional()
@IsString()
status?: string;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
limit?: number;
}