/** رویدادهای امتیازدهی — موتور پاداش فقط به همین رویدادها گوش می‌دهد */ export type GamificationEvent = | "LEAD_CREATED" | "LEAD_STAGE_CHANGED" | "LEAD_CONVERTED" | "CUSTOMER_CREATED" | "COMPANY_CREATED" | "DEAL_CREATED" | "DEAL_STAGE_CHANGED" | "DEAL_WON" | "DEAL_LOST" | "CALL_LOGGED" | "MEETING_CREATED" | "FOLLOW_UP_CREATED" | "NOTE_CREATED" | "ACTIVITY_CREATED" | "TASK_COMPLETED" | "ROUTINE_COMPLETED" | "QUOTE_CREATED" | "INVOICE_ISSUED" | "PAYMENT_REGISTERED" | "TICKET_CREATED" | "TICKET_RESOLVED"; export type GamificationFieldType = "number" | "string" | "boolean"; export interface GamificationFieldMeta { label: string; type: GamificationFieldType; /** مقادیر پیشنهادی برای فیلدهای متنی */ options?: Array<{ value: string; label: string }>; } /** فیلدهای قابل استفاده در شرط‌های Rule Builder */ export const GAMIFICATION_FIELDS: Record = { amount: { label: "مبلغ", type: "number" }, stageName: { label: "نام مرحله", type: "string" }, source: { label: "منبع", type: "string" }, activityType: { label: "نوع فعالیت", type: "string", options: [ { value: "call", label: "تماس" }, { value: "meeting", label: "جلسه" }, { value: "task", label: "کار" }, { value: "note", label: "یادداشت" }, ], }, typeName: { label: "عنوان نوع فعالیت", type: "string" }, onTime: { label: "انجام به‌موقع", type: "boolean" }, isMandatory: { label: "اجباری", type: "boolean" }, priority: { label: "اولویت", type: "string", options: [ { value: "very_low", label: "خیلی کم" }, { value: "low", label: "کم" }, { value: "medium", label: "متوسط" }, { value: "normal", label: "عادی (تیکت)" }, { value: "high", label: "زیاد" }, { value: "very_high", label: "خیلی زیاد" }, { value: "urgent", label: "فوری (تیکت)" }, ], }, hasDescription: { label: "توضیحات دارد", type: "boolean" }, hour: { label: "ساعت انجام (۰ تا ۲۳)", type: "number" }, weekday: { label: "روز هفته (۰=شنبه … ۶=جمعه)", type: "number" }, }; const COMMON_FIELDS = ["hour", "weekday"]; export interface GamificationEventMeta { label: string; /** متن کوتاه برای فید امتیازات، مثل «معامله برنده شد» */ feed: string; group: "leads" | "customers" | "sales" | "activities" | "finance" | "support"; fields: string[]; } export const GAMIFICATION_EVENTS: Record = { LEAD_CREATED: { label: "ثبت لید جدید", feed: "لید جدید ثبت شد", group: "leads", fields: ["source", "amount", ...COMMON_FIELDS] }, LEAD_STAGE_CHANGED: { label: "تغییر مرحله لید", feed: "لید به مرحله بعد رفت", group: "leads", fields: ["stageName", "source", ...COMMON_FIELDS] }, LEAD_CONVERTED: { label: "تبدیل لید به مشتری", feed: "لید به مشتری تبدیل شد", group: "leads", fields: ["source", "amount", ...COMMON_FIELDS] }, CUSTOMER_CREATED: { label: "ثبت مشتری جدید", feed: "مشتری جدید ثبت شد", group: "customers", fields: ["source", ...COMMON_FIELDS] }, COMPANY_CREATED: { label: "ثبت شرکت جدید", feed: "شرکت جدید ثبت شد", group: "customers", fields: [...COMMON_FIELDS] }, DEAL_CREATED: { label: "ایجاد معامله", feed: "معامله جدید ایجاد شد", group: "sales", fields: ["amount", ...COMMON_FIELDS] }, DEAL_STAGE_CHANGED: { label: "جابه‌جایی معامله در مسیر فروش", feed: "معامله به مرحله بعد رفت", group: "sales", fields: ["stageName", "amount", ...COMMON_FIELDS] }, DEAL_WON: { label: "برنده شدن معامله", feed: "معامله برنده شد", group: "sales", fields: ["amount", "stageName", ...COMMON_FIELDS] }, DEAL_LOST: { label: "از دست رفتن معامله", feed: "معامله بسته شد", group: "sales", fields: ["amount", ...COMMON_FIELDS] }, CALL_LOGGED: { label: "ثبت تماس", feed: "تماس ثبت شد", group: "activities", fields: ["hasDescription", ...COMMON_FIELDS] }, MEETING_CREATED: { label: "ثبت جلسه", feed: "جلسه ثبت شد", group: "activities", fields: ["hasDescription", ...COMMON_FIELDS] }, FOLLOW_UP_CREATED: { label: "ثبت پیگیری", feed: "پیگیری ثبت شد", group: "activities", fields: ["hasDescription", ...COMMON_FIELDS] }, NOTE_CREATED: { label: "ثبت یادداشت", feed: "یادداشت ثبت شد", group: "activities", fields: ["hasDescription", ...COMMON_FIELDS] }, ACTIVITY_CREATED: { label: "ثبت فعالیت", feed: "فعالیت ثبت شد", group: "activities", fields: ["activityType", "typeName", "priority", "hasDescription", ...COMMON_FIELDS] }, TASK_COMPLETED: { label: "انجام کار", feed: "کار انجام شد", group: "activities", fields: ["activityType", "typeName", "onTime", "priority", ...COMMON_FIELDS] }, ROUTINE_COMPLETED: { label: "انجام کار روتین", feed: "کار روتین انجام شد", group: "activities", fields: ["onTime", "isMandatory", "priority", ...COMMON_FIELDS] }, QUOTE_CREATED: { label: "ثبت پیش‌فاکتور", feed: "پیش‌فاکتور ثبت شد", group: "finance", fields: ["amount", ...COMMON_FIELDS] }, INVOICE_ISSUED: { label: "صدور فاکتور", feed: "فاکتور صادر شد", group: "finance", fields: ["amount", ...COMMON_FIELDS] }, PAYMENT_REGISTERED: { label: "ثبت پرداخت", feed: "پرداخت ثبت شد", group: "finance", fields: ["amount", ...COMMON_FIELDS] }, TICKET_CREATED: { label: "ثبت تیکت", feed: "تیکت ثبت شد", group: "support", fields: ["priority", ...COMMON_FIELDS] }, TICKET_RESOLVED: { label: "حل تیکت", feed: "تیکت حل شد", group: "support", fields: ["priority", ...COMMON_FIELDS] }, }; export const GAMIFICATION_EVENT_KEYS = Object.keys(GAMIFICATION_EVENTS) as GamificationEvent[]; export const GAMIFICATION_EVENT_GROUP_LABELS: Record = { leads: "لیدها", customers: "مشتریان", sales: "فروش", activities: "فعالیت‌ها", finance: "مالی", support: "پشتیبانی", }; export function isGamificationEvent(value: unknown): value is GamificationEvent { return typeof value === "string" && value in GAMIFICATION_EVENTS; } export type GamificationConditionOp = | "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "contains" | "in" | "is_true" | "is_false"; export const GAMIFICATION_CONDITION_OPS: Record = { eq: "برابر", neq: "مخالف", gt: "بیشتر از", gte: "حداقل", lt: "کمتر از", lte: "حداکثر", contains: "شامل", in: "یکی از (با ویرگول)", is_true: "بله باشد", is_false: "خیر باشد", }; export interface GamificationCondition { field: string; op: GamificationConditionOp; value?: string | number | null; } export type GamificationPeriod = "today" | "week" | "month" | "quarter" | "year" | "all"; export const GAMIFICATION_PERIOD_LABELS: Record = { today: "امروز", week: "این هفته", month: "این ماه", quarter: "این فصل", year: "امسال", all: "همه زمان‌ها", }; export type GamificationTxKind = "rule" | "bonus" | "achievement" | "adjust" | "redeem" | "refund"; export type GamificationTxStatus = "approved" | "pending" | "rejected"; export interface GamificationLevelDef { name: string; minScore: number; } export interface GamificationLevelDto { level: number; name: string; minScore: number; nextName: string | null; nextMinScore: number | null; /** درصد پیشرفت تا سطح بعد */ progress: number; toNext: number | null; } export interface GamificationRuleDto { id: string; name: string; description: string | null; event: GamificationEvent; score: number; coins: number; conditions: GamificationCondition[]; isActive: boolean; maxPerDay: number | null; maxCoinsPerDay: number | null; maxPerUser: number | null; cooldownMinutes: number | null; oncePerEntity: boolean; firstTimeOnly: boolean; requiresApproval: boolean; /** نقش‌های مشمول؛ خالی = همه */ roles: string[]; /** تیم‌های مشمول؛ خالی = همه */ teamIds: string[]; position: number; awardedCount: number; createdAt: string; updatedAt: string; } export interface SaveGamificationRuleRequest { name: string; description?: string | null; event: GamificationEvent; score: number; coins: number; conditions?: GamificationCondition[]; isActive?: boolean; maxPerDay?: number | null; maxCoinsPerDay?: number | null; maxPerUser?: number | null; cooldownMinutes?: number | null; oncePerEntity?: boolean; firstTimeOnly?: boolean; requiresApproval?: boolean; roles?: string[]; teamIds?: string[]; } export interface GamificationTxDto { id: string; userId: string; userName: string; kind: GamificationTxKind; event: GamificationEvent | null; title: string; score: number; coins: number; status: GamificationTxStatus; entityType: string | null; entityId: string | null; entityTitle: string | null; /** لینک پرونده مرتبط در CRM */ link: string | null; reason: string | null; createdByName: string | null; createdAt: string; } export interface GamificationLeaderEntryDto { rank: number; userId: string; fullName: string; hasAvatar: boolean; score: number; /** تغییر رتبه نسبت به ۲۴ ساعت قبل؛ مثبت = صعود */ change: number | null; levelName: string; isMe: boolean; } export interface GamificationLeaderboardDto { period: GamificationPeriod; from: string | null; totalUsers: number; items: GamificationLeaderEntryDto[]; /** کاربر جاری همیشه برمی‌گردد، حتی اگر در فهرست نباشد */ me: GamificationLeaderEntryDto | null; } export interface GamificationTeamEntryDto { rank: number; teamId: string; name: string; members: number; score: number; avgScore: number; isMine: boolean; } export interface GamificationRewardDto { id: string; name: string; description: string | null; cost: number; icon: string; color: string; stock: number | null; isActive: boolean; requiresApproval: boolean; position: number; } export interface SaveGamificationRewardRequest { name: string; description?: string | null; cost: number; icon?: string; color?: string; stock?: number | null; isActive?: boolean; requiresApproval?: boolean; } export type GamificationRedemptionStatus = "pending" | "approved" | "rejected" | "delivered"; export const GAMIFICATION_REDEMPTION_LABELS: Record = { pending: "در انتظار تأیید", approved: "تأیید شده", rejected: "رد شده", delivered: "تحویل شده", }; export interface GamificationRedemptionDto { id: string; userId: string; userName: string; rewardId: string; rewardName: string; rewardIcon: string; cost: number; status: GamificationRedemptionStatus; note: string | null; decisionNote: string | null; decidedByName: string | null; decidedAt: string | null; createdAt: string; } export type GamificationBadgeMetric = | "event_count" | "score_total" | "routine_streak" | "single_deal_amount"; export const GAMIFICATION_BADGE_METRICS: Record = { event_count: "تعداد دفعات یک رویداد", score_total: "مجموع امتیاز", routine_streak: "روزهای پیاپی انجام کامل روتین‌ها", single_deal_amount: "مبلغ یک معامله برنده", }; export interface GamificationBadgeDto { id: string; name: string; description: string | null; icon: string; color: string; metric: GamificationBadgeMetric; event: GamificationEvent | null; threshold: number; coins: number; isActive: boolean; earnedCount: number; } export interface SaveGamificationBadgeRequest { name: string; description?: string | null; icon?: string; color?: string; metric: GamificationBadgeMetric; event?: GamificationEvent | null; threshold: number; coins?: number; isActive?: boolean; } export interface GamificationAchievementDto extends GamificationBadgeDto { earned: boolean; earnedAt: string | null; /** مقدار فعلی کاربر برای این شاخص */ current: number; progress: number; } export type GamificationBonusKind = "event_count_period" | "routine_streak" | "top_of_month"; export const GAMIFICATION_BONUS_KINDS: Record = { event_count_period: "تعداد رویداد در بازه", routine_streak: "روزهای پیاپی انجام کامل روتین", top_of_month: "نفر اول امتیاز ماه", }; export interface GamificationBonusDto { id: string; name: string; description: string | null; kind: GamificationBonusKind; event: GamificationEvent | null; threshold: number; period: "day" | "week" | "month"; score: number; coins: number; isActive: boolean; awardedCount: number; } export interface SaveGamificationBonusRequest { name: string; description?: string | null; kind: GamificationBonusKind; event?: GamificationEvent | null; threshold: number; period?: "day" | "week" | "month"; score?: number; coins?: number; isActive?: boolean; } export interface GamificationSettingsDto { enabled: boolean; levels: GamificationLevelDef[]; /** هدف ماهانه امتیاز برای کارت «هدف بعدی» */ monthlyGoal: number; /** نمایش کامل جدول برای همه؛ اگر خاموش باشد فقط ۱۰ نفر اول و خود کاربر */ showFullLeaderboard: boolean; enabledAt: string | null; } export interface GamificationMeDto { user: { id: string; fullName: string; hasAvatar: boolean }; isManager: boolean; settings: GamificationSettingsDto; score: number; coins: number; period: GamificationPeriod; periodScore: number; todayScore: number; rank: number | null; totalUsers: number; /** فاصله تا نفر بالاتر */ gapToNext: number | null; nextRankName: string | null; gapToFirst: number | null; level: GamificationLevelDto; /** بهترین امتیاز ماهانه کاربر تا امروز */ personalBest: number; goal: { target: number; current: number; progress: number }; nextReward: { id: string; name: string; cost: number; icon: string; remaining: number } | null; streakDays: number; badgesEarned: number; pendingRedemptions: number; recent: GamificationTxDto[]; } export interface GamificationPulseDto { serverTime: string; score: number; coins: number; rank: number | null; items: GamificationTxDto[]; } export interface GamificationAdjustRequest { userId: string; amount: number; reason: string; } export interface GamificationReportUserRow { userId: string; fullName: string; value: number; } export interface GamificationRoiMetric { key: string; label: string; before: number; after: number; /** درصد تغییر؛ null وقتی مبنا صفر است */ changePct: number | null; } export interface GamificationReportDto { period: GamificationPeriod; totals: { score: number; coinsEarned: number; coinsSpent: number; activeUsers: number; totalUsers: number; transactions: number; pendingApprovals: number; pendingRedemptions: number; }; byEvent: Array<{ event: GamificationEvent; label: string; count: number; score: number; coins: number }>; topActive: GamificationReportUserRow[]; topGrowth: GamificationReportUserRow[]; topDeals: GamificationReportUserRow[]; topLeads: GamificationReportUserRow[]; topTasks: GamificationReportUserRow[]; inactive: GamificationReportUserRow[]; roi: { since: string | null; windowDays: number; metrics: GamificationRoiMetric[] }; }