feat: routines module; super admin subscription badge, 7-day trial and lifetime plan

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-06 19:54:38 +03:30
co-authored by Cursor
parent 3de2d0fa67
commit b3784818fc
58 changed files with 13018 additions and 18 deletions
+484 -6
View File
@@ -50,6 +50,7 @@ export type BillingPlanCode =
| "quarterly"
| "semiannual"
| "yearly"
| "lifetime"
| "custom";
export type BillingAccountStatus = "active" | "expired" | "suspended";
@@ -60,27 +61,36 @@ export const BILLING_PLAN_CODES: BillingPlanCode[] = [
"quarterly",
"semiannual",
"yearly",
"lifetime",
"custom",
];
export const BILLING_PLAN_LABELS: Record<BillingPlanCode, string> = {
trial: "دمو ۱۵ روزه",
trial: "دمو ۷ روزه",
monthly: "ماهانه",
quarterly: "سه‌ماهه",
semiannual: "شش‌ماهه",
yearly: "یکساله",
lifetime: "دائمی",
custom: "سفارشی",
};
/** مدت پیش‌فرض پلن‌ها (روز) — custom ندارد */
export const BILLING_TRIAL_DAYS = 7;
/** مدت پیش‌فرض پلن‌ها (روز) — custom ندارد؛ lifetime عملاً بدون انقضا (۱۰۰ سال) */
export const BILLING_PLAN_DAYS: Record<Exclude<BillingPlanCode, "custom">, number> = {
trial: 15,
trial: BILLING_TRIAL_DAYS,
monthly: 30,
quarterly: 90,
semiannual: 180,
yearly: 365,
lifetime: 36500,
};
export function isPermanentBillingPlan(code: string | null | undefined): boolean {
return code === "lifetime";
}
export const BILLING_PLAN_DEFAULT_MAX_ORGS: Record<
Exclude<BillingPlanCode, "custom">,
number
@@ -90,6 +100,7 @@ export const BILLING_PLAN_DEFAULT_MAX_ORGS: Record<
quarterly: 5,
semiannual: 10,
yearly: 20,
lifetime: 20,
};
export type OrgModuleKey =
@@ -126,7 +137,8 @@ export type OrgModuleKey =
| "voip"
| "reservations"
| "leads"
| "conversations";
| "conversations"
| "routines";
export const ORG_MODULE_KEYS: OrgModuleKey[] = [
"customers",
@@ -163,6 +175,7 @@ export const ORG_MODULE_KEYS: OrgModuleKey[] = [
"voip",
"reservations",
"conversations",
"routines",
];
export const ORG_MODULE_LABELS: Record<OrgModuleKey, string> = {
@@ -200,6 +213,7 @@ export const ORG_MODULE_LABELS: Record<OrgModuleKey, string> = {
voip: "تلفن سازمانی",
reservations: "رزرو و نوبت‌دهی",
conversations: "گفتگو",
routines: "کارهای روتین",
};
export const ORG_MODULE_GROUPS: Array<{
@@ -219,7 +233,7 @@ export const ORG_MODULE_GROUPS: Array<{
"sales_forecast",
],
},
{ title: "فعالیت‌ها", keys: ["activities", "notes"] },
{ title: "فعالیت‌ها", keys: ["activities", "routines", "notes"] },
{ title: "پشتیبانی", keys: ["tickets", "faq", "conversations"] },
{
title: "اتوماسیون",
@@ -341,7 +355,8 @@ export type DataScope =
| "finance"
| "tickets"
| "notes"
| "projects";
| "projects"
| "routines";
export const DATA_SCOPES: DataScope[] = [
"contacts",
@@ -352,6 +367,7 @@ export const DATA_SCOPES: DataScope[] = [
"tickets",
"notes",
"projects",
"routines",
];
export const DATA_SCOPE_LABELS: Record<DataScope, string> = {
@@ -363,6 +379,7 @@ export const DATA_SCOPE_LABELS: Record<DataScope, string> = {
tickets: "تیکت‌ها",
notes: "یادداشت‌ها",
projects: "پروژه‌ها",
routines: "کارهای روتین",
};
export type DataVisibilityScopes = Partial<Record<DataScope, DataVisibility>>;
@@ -1658,6 +1675,10 @@ export type NotificationType =
| "cheque_due"
| "ticket_assigned"
| "automation"
| "routine_task_reminder"
| "routine_task_overdue"
| "routine_day_summary"
| "routine_task_assigned"
| "system";
export interface NotificationDto {
@@ -3381,6 +3402,8 @@ export interface PlatformOrganizationDto {
personCount: number;
dealCount: number;
enabledModules?: OrgModuleKey[];
/** اشتراک مالک اصلی محیط */
subscription?: SubscriptionSummaryDto | null;
members?: PlatformOrgMemberDto[];
}
@@ -4340,3 +4363,458 @@ export interface PlatformConversationStatsDto {
totalConfiguredOrgs: number;
totalRoomsTracked: number;
}
/* ───────────── کارهای روتین ───────────── */
export type RoutineTaskStatus =
| "todo"
| "in_progress"
| "completed"
| "overdue"
| "skipped"
| "cancelled";
export const ROUTINE_TASK_STATUSES: RoutineTaskStatus[] = [
"todo",
"in_progress",
"completed",
"overdue",
"skipped",
"cancelled",
];
export const ROUTINE_TASK_STATUS_LABELS: Record<RoutineTaskStatus, string> = {
todo: "در انتظار",
in_progress: "در حال انجام",
completed: "انجام شد",
overdue: "عقب‌افتاده",
skipped: "رد شده",
cancelled: "لغو شده",
};
export type RoutineRecurrence =
| "daily"
| "weekdays"
| "weekly"
| "biweekly"
| "monthly"
| "every_n_days"
| "specific_dates";
export const ROUTINE_RECURRENCES: RoutineRecurrence[] = [
"daily",
"weekdays",
"weekly",
"biweekly",
"monthly",
"every_n_days",
"specific_dates",
];
export const ROUTINE_RECURRENCE_LABELS: Record<RoutineRecurrence, string> = {
daily: "هر روز",
weekdays: "روزهای مشخص هفته",
weekly: "هر هفته",
biweekly: "هر دو هفته",
monthly: "هر ماه",
every_n_days: "هر چند روز یک‌بار",
specific_dates: "تاریخ‌های مشخص",
};
/** ۰ = شنبه … ۶ = جمعه */
export const ROUTINE_WEEKDAY_LABELS = [
"شنبه",
"یکشنبه",
"دوشنبه",
"سه‌شنبه",
"چهارشنبه",
"پنجشنبه",
"جمعه",
];
export type RoutineTaskSource =
| "routine"
| "manual"
| "follow_up"
| "lead"
| "customer"
| "deal";
export const ROUTINE_TASK_SOURCE_LABELS: Record<RoutineTaskSource, string> = {
routine: "روتین",
manual: "دستی",
follow_up: "پیگیری",
lead: "لید",
customer: "مشتری",
deal: "معامله",
};
/** اتصال Task به پرونده: none = ندارد | optional = اختیاری | lead/customer = الزامی */
export type RoutineEntityLink = "none" | "optional" | "lead" | "customer";
export const ROUTINE_ENTITY_LINK_LABELS: Record<RoutineEntityLink, string> = {
none: "بدون اتصال",
optional: "اختیاری",
lead: "الزامی به لید",
customer: "الزامی به مشتری",
};
/** وضعیت یک روز: کامل | ناقص | از دست رفته | بدون Task */
export type RoutineDayState = "complete" | "partial" | "missed" | "empty";
export interface RoutineCategoryDto {
id: string;
name: string;
color: string;
sortOrder: number;
}
export interface SaveRoutineCategoryRequest {
name: string;
color?: string;
sortOrder?: number;
}
export type RoutineEntityType =
| "lead"
| "person"
| "company"
| "deal"
| "invoice"
| "activity";
export interface RoutineEntityRefDto {
type: RoutineEntityType;
id: string;
label: string;
}
export interface RoutineTaskRulesDto {
allowSkip: boolean;
skipReasonRequired: boolean;
reportRequired: boolean;
allowAfterDeadline: boolean;
entityLink: RoutineEntityLink;
}
export interface RoutineTaskDto {
id: string;
routineId: string | null;
title: string;
description: string | null;
status: RoutineTaskStatus;
priority: ActivityPriority;
category: RoutineCategoryDto | null;
/** روز برنامه (YYYY-MM-DD به وقت تهران) */
scheduledFor: string;
dueAt: string | null;
startedAt: string | null;
completedAt: string | null;
resultNote: string | null;
skipReason: string | null;
source: RoutineTaskSource;
isMandatory: boolean;
rules: RoutineTaskRulesDto;
assignee: UserSummaryDto | null;
createdBy: UserSummaryDto | null;
entities: RoutineEntityRefDto[];
createdAt: string;
updatedAt: string;
}
export interface RoutineTaskEventDto {
id: string;
type: "created" | "status_changed" | "rescheduled" | "note" | "reassigned" | "updated";
fromStatus: RoutineTaskStatus | null;
toStatus: RoutineTaskStatus | null;
note: string | null;
actor: UserSummaryDto | null;
createdAt: string;
}
export interface RoutineTaskDetailDto extends RoutineTaskDto {
events: RoutineTaskEventDto[];
}
export interface RoutineDayStatsDto {
total: number;
completed: number;
remaining: number;
overdue: number;
/** null وقتی Task ندارد */
percent: number | null;
}
export interface RoutineScoreDto {
today: number | null;
yesterday: number | null;
week: number | null;
month: number | null;
/** روزهای متوالی با انجام همه Taskهای اجباری */
streak: number;
}
export interface RoutineTodayDto {
date: string;
tasks: RoutineTaskDto[];
overdue: RoutineTaskDto[];
stats: RoutineDayStatsDto;
score: RoutineScoreDto;
}
export interface CreateRoutineTaskRequest {
title: string;
description?: string | null;
/** ISO؛ اگر خالی باشد scheduledFor یا امروز */
dueAt?: string | null;
scheduledFor?: string | null;
priority?: ActivityPriority;
categoryId?: string | null;
assigneeId?: string | null;
isMandatory?: boolean;
source?: RoutineTaskSource;
leadId?: string | null;
personId?: string | null;
companyId?: string | null;
dealId?: string | null;
invoiceId?: string | null;
activityId?: string | null;
}
export type UpdateRoutineTaskRequest = Partial<
Omit<CreateRoutineTaskRequest, "source" | "activityId">
>;
export interface CompleteRoutineTaskRequest {
note?: string | null;
}
export interface SkipRoutineTaskRequest {
reason?: string | null;
}
export interface RescheduleRoutineTaskRequest {
dueAt: string;
}
export interface RoutineDto {
id: string;
title: string;
description: string | null;
assignee: UserSummaryDto | null;
team: { id: string; name: string } | null;
category: RoutineCategoryDto | null;
priority: ActivityPriority;
/** HH:mm */
timeOfDay: string;
durationMinutes: number | null;
startDate: string;
endDate: string | null;
recurrence: RoutineRecurrence;
weekdays: number[];
intervalDays: number | null;
monthDay: number | null;
specificDates: string[];
isMandatory: boolean;
allowSkip: boolean;
skipReasonRequired: boolean;
reportRequired: boolean;
allowAfterDeadline: boolean;
carryOverOverdue: boolean;
entityLink: RoutineEntityLink;
reminderMinutesBefore: number | null;
isActive: boolean;
recurrenceLabel: string;
/** درصد اجرا در ۳۰ روز اخیر */
executionRate: number | null;
executionTotal: number;
executionDone: number;
createdAt: string;
updatedAt: string;
}
export interface SaveRoutineRequest {
title: string;
description?: string | null;
assigneeId?: string | null;
teamId?: string | null;
categoryId?: string | null;
priority?: ActivityPriority;
timeOfDay: string;
durationMinutes?: number | null;
startDate: string;
endDate?: string | null;
recurrence: RoutineRecurrence;
weekdays?: number[];
intervalDays?: number | null;
monthDay?: number | null;
specificDates?: string[];
isMandatory?: boolean;
allowSkip?: boolean;
skipReasonRequired?: boolean;
reportRequired?: boolean;
allowAfterDeadline?: boolean;
carryOverOverdue?: boolean;
entityLink?: RoutineEntityLink;
reminderMinutesBefore?: number | null;
isActive?: boolean;
}
export interface RoutineContextDto {
isManager: boolean;
users: UserSummaryDto[];
teams: Array<{ id: string; name: string }>;
categories: RoutineCategoryDto[];
}
export interface RoutineCalendarDayDto {
date: string;
total: number;
completed: number;
overdue: number;
percent: number | null;
state: RoutineDayState;
}
export interface RoutineCalendarDto {
days: RoutineCalendarDayDto[];
tasks: RoutineTaskDto[];
}
export interface RoutineTrendPointDto {
date: string;
total: number;
completed: number;
percent: number | null;
}
export interface RoutineCategoryStatDto {
categoryId: string | null;
name: string;
color: string;
total: number;
completed: number;
percent: number | null;
}
export interface RoutineStatusBreakdownDto {
completed: number;
notDone: number;
overdue: number;
skipped: number;
cancelled: number;
}
export interface RoutinePerformanceDto {
from: string;
to: string;
total: number;
completed: number;
notDone: number;
overdue: number;
percent: number | null;
avgCompletionMinutes: number | null;
completeDays: number;
partialDays: number;
missedDays: number;
streak: number;
status: RoutineStatusBreakdownDto;
trend: RoutineTrendPointDto[];
byCategory: RoutineCategoryStatDto[];
}
export interface RoutineMemberStatDto {
user: UserSummaryDto;
total: number;
completed: number;
overdue: number;
skipped: number;
percent: number | null;
streak: number;
avgCompletionMinutes: number | null;
}
export interface RoutineTeamPerformanceDto {
from: string;
to: string;
total: number;
completed: number;
overdue: number;
percent: number | null;
status: RoutineStatusBreakdownDto;
trend: RoutineTrendPointDto[];
byCategory: RoutineCategoryStatDto[];
members: RoutineMemberStatDto[];
}
export interface RoutinePeriodTotalsDto {
total: number;
completed: number;
notDone: number;
overdue: number;
percent: number | null;
completeDays: number;
partialDays: number;
missedDays: number;
avgPercent: number | null;
}
export interface RoutineWeeklyReportDto {
from: string;
to: string;
days: RoutineCalendarDayDto[];
totals: RoutinePeriodTotalsDto;
previous: RoutinePeriodTotalsDto;
}
export interface RoutineMonthlyReportDto {
from: string;
to: string;
totals: RoutinePeriodTotalsDto;
previous: RoutinePeriodTotalsDto;
status: RoutineStatusBreakdownDto;
days: RoutineCalendarDayDto[];
byUser: RoutineMemberStatDto[];
}
export interface RoutineExecutionRowDto {
routineId: string;
title: string;
assigneeLabel: string;
recurrenceLabel: string;
occurrences: number;
completed: number;
notDone: number;
skipped: number;
percent: number | null;
}
export interface RoutineHistoryDayDto {
date: string;
total: number;
completed: number;
overdue: number;
skipped: number;
cancelled: number;
percent: number | null;
state: RoutineDayState;
}
export interface RoutineStatsQuery {
from?: string;
to?: string;
userId?: string;
teamId?: string;
categoryId?: string;
source?: RoutineTaskSource;
status?: RoutineTaskStatus;
}
export interface RoutineUserSettingsDto {
/** چند دقیقه قبل از Deadline یادآوری شود؛ null = خاموش */
remindBeforeMinutes: number | null;
endOfDaySummary: boolean;
overdueAlert: boolean;
/** ساعت ارسال خلاصه پایان روز (۰ تا ۲۳) */
summaryHour: number;
}