feat: باشگاه امتیازات کارکنان (موتور امتیاز، جدول رتبه، فروشگاه پاداش، نشانها، پنل مدیریت و گزارش ROI)
- موتور رویدادمحور با شرط، محدودیت، تأیید مدیر و idempotency - اتصال به لید، مشتری، معامله، فعالیت، روتین، پیشفاکتور، فاکتور و تیکت - صفحه /club و /club/admin، اعلان زنده امتیاز، آیتم منو - انتقال فرمساز سرنخ به ابزارها Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,491 @@
|
||||
/** رویدادهای امتیازدهی — موتور پاداش فقط به همین رویدادها گوش میدهد */
|
||||
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<string, GamificationFieldMeta> = {
|
||||
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<GamificationEvent, GamificationEventMeta> = {
|
||||
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<GamificationEventMeta["group"], string> = {
|
||||
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<GamificationConditionOp, string> = {
|
||||
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<GamificationPeriod, string> = {
|
||||
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<GamificationRedemptionStatus, string> = {
|
||||
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<GamificationBadgeMetric, string> = {
|
||||
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<GamificationBonusKind, string> = {
|
||||
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[] };
|
||||
}
|
||||
@@ -139,7 +139,8 @@ export type OrgModuleKey =
|
||||
| "reservations"
|
||||
| "leads"
|
||||
| "conversations"
|
||||
| "routines";
|
||||
| "routines"
|
||||
| "gamification";
|
||||
|
||||
export const ORG_MODULE_KEYS: OrgModuleKey[] = [
|
||||
"customers",
|
||||
@@ -178,6 +179,7 @@ export const ORG_MODULE_KEYS: OrgModuleKey[] = [
|
||||
"reservations",
|
||||
"conversations",
|
||||
"routines",
|
||||
"gamification",
|
||||
];
|
||||
|
||||
export const ORG_MODULE_LABELS: Record<OrgModuleKey, string> = {
|
||||
@@ -217,6 +219,7 @@ export const ORG_MODULE_LABELS: Record<OrgModuleKey, string> = {
|
||||
reservations: "رزرو و نوبتدهی",
|
||||
conversations: "گفتگو",
|
||||
routines: "کارهای روتین",
|
||||
gamification: "باشگاه امتیازات",
|
||||
};
|
||||
|
||||
export const ORG_MODULE_GROUPS: Array<{
|
||||
@@ -236,7 +239,7 @@ export const ORG_MODULE_GROUPS: Array<{
|
||||
"sales_forecast",
|
||||
],
|
||||
},
|
||||
{ title: "فعالیتها", keys: ["activities", "routines", "notes"] },
|
||||
{ title: "فعالیتها", keys: ["activities", "routines", "notes", "gamification"] },
|
||||
{ title: "پشتیبانی", keys: ["tickets", "faq", "conversations"] },
|
||||
{
|
||||
title: "اتوماسیون",
|
||||
@@ -5211,3 +5214,7 @@ export interface RoutineUserSettingsDto {
|
||||
/** ساعت ارسال خلاصه پایان روز (۰ تا ۲۳) */
|
||||
summaryHour: number;
|
||||
}
|
||||
|
||||
/* ───────── باشگاه امتیازات (گیمیفیکیشن کارکنان) ───────── */
|
||||
|
||||
export * from "./gamification";
|
||||
|
||||
Reference in New Issue
Block a user