2026-10-29 18:46:10 +03:30
|
|
|
|
import type { PersonGender } from "./person-name";
|
|
|
|
|
|
export type { PersonGender } from "./person-name";
|
|
|
|
|
|
export {
|
|
|
|
|
|
PERSON_GENDERS,
|
|
|
|
|
|
PERSON_GENDER_LABELS,
|
2026-10-31 12:49:42 +03:30
|
|
|
|
applyLeadGenderName,
|
2026-10-29 18:46:10 +03:30
|
|
|
|
applyPersonGenderTitle,
|
2026-10-31 12:49:42 +03:30
|
|
|
|
isGenderQuestionLabel,
|
|
|
|
|
|
parsePersonGenderAnswer,
|
2026-10-29 18:46:10 +03:30
|
|
|
|
splitPersonHonorific,
|
|
|
|
|
|
} from "./person-name";
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type UserRole = "owner" | "admin" | "sales" | "support";
|
2026-09-27 18:00:59 +03:30
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export const USER_ROLES: UserRole[] = ["owner", "admin", "sales", "support"];
|
|
|
|
|
|
|
|
|
|
|
|
export const USER_ROLE_LABELS: Record<UserRole, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
owner: "مالک",
|
|
|
|
|
|
admin: "مدیر",
|
|
|
|
|
|
sales: "فروش",
|
|
|
|
|
|
support: "پشتیبانی",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** تنظیمات سازمانی و مدیریت کاربران */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export function canManageSettings(role: UserRole | string | null | undefined): boolean {
|
|
|
|
|
|
return role === "owner" || role === "admin";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function canManageUsers(role: UserRole | string | null | undefined): boolean {
|
|
|
|
|
|
return role === "owner" || role === "admin";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** مشاهده همه پروندههای سازمان (نه فقط مالکیت خود) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export function canViewAllRecords(role: UserRole | string | null | undefined): boolean {
|
|
|
|
|
|
return role === "owner" || role === "admin";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** تغییر مالک پرونده */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export function canReassignRecords(role: UserRole | string | null | undefined): boolean {
|
|
|
|
|
|
return role === "owner" || role === "admin";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AuthUserDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
mobile?: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
role: UserRole;
|
|
|
|
|
|
organizationId: string;
|
|
|
|
|
|
organizationName?: string;
|
|
|
|
|
|
hasAvatar?: boolean;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** مالک محیط فعلی — اشتراک متعلق به مالک است نه عضو زیرمجموعه */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
isOrgOwner?: boolean;
|
|
|
|
|
|
enabledModules?: OrgModuleKey[];
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** خلاصه اشتراک مالک محیط (برای اعضا فقط جهت نمایش/گیت ورود) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
subscription?: SubscriptionSummaryDto | null;
|
2026-11-02 15:44:36 +03:30
|
|
|
|
/** حجم فایل اشتراک مالک همین محیط */
|
|
|
|
|
|
storage?: StorageUsageDto | null;
|
2026-10-15 10:48:30 +03:30
|
|
|
|
/** اگر باشد، این کاربر سوئیچ «بایگانی اطلاعات قبل» را دارد */
|
2026-10-17 16:27:29 +03:30
|
|
|
|
priorArchive?: { lens: "current" | "archive"; cutoffAt: string | null } | null;
|
2026-10-17 19:57:26 +03:30
|
|
|
|
/**
|
|
|
|
|
|
* مالک در حال مشاهدهٔ فقطخواندنی بهجای این کاربر است.
|
|
|
|
|
|
* role/id مربوط به کاربر مشاهدهشده است.
|
|
|
|
|
|
*/
|
|
|
|
|
|
viewAs?: {
|
|
|
|
|
|
impersonatorId: string;
|
|
|
|
|
|
impersonatorFullName: string;
|
|
|
|
|
|
/** صفحهٔ تیم برای مالک واقعی باز بماند */
|
|
|
|
|
|
canOpenTeamSettings: boolean;
|
|
|
|
|
|
} | null;
|
2026-10-18 19:33:09 +03:30
|
|
|
|
/** مجوزهای نقش سازمانی؛ مالک و مدیر همهٔ بخشها را دارند */
|
|
|
|
|
|
permissions?: RolePermissionKey[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type BillingPlanCode =
|
|
|
|
|
|
| "trial"
|
|
|
|
|
|
| "monthly"
|
|
|
|
|
|
| "quarterly"
|
|
|
|
|
|
| "semiannual"
|
|
|
|
|
|
| "yearly"
|
2026-10-06 19:54:38 +03:30
|
|
|
|
| "lifetime"
|
2026-09-27 21:17:42 +03:30
|
|
|
|
| "custom";
|
|
|
|
|
|
|
|
|
|
|
|
export type BillingAccountStatus = "active" | "expired" | "suspended";
|
|
|
|
|
|
|
|
|
|
|
|
export const BILLING_PLAN_CODES: BillingPlanCode[] = [
|
|
|
|
|
|
"trial",
|
|
|
|
|
|
"monthly",
|
|
|
|
|
|
"quarterly",
|
|
|
|
|
|
"semiannual",
|
|
|
|
|
|
"yearly",
|
2026-10-06 19:54:38 +03:30
|
|
|
|
"lifetime",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
"custom",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const BILLING_PLAN_LABELS: Record<BillingPlanCode, string> = {
|
2026-10-06 19:54:38 +03:30
|
|
|
|
trial: "دمو ۷ روزه",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
monthly: "ماهانه",
|
|
|
|
|
|
quarterly: "سهماهه",
|
|
|
|
|
|
semiannual: "ششماهه",
|
|
|
|
|
|
yearly: "یکساله",
|
2026-10-06 19:54:38 +03:30
|
|
|
|
lifetime: "دائمی",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
custom: "سفارشی",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-06 19:54:38 +03:30
|
|
|
|
export const BILLING_TRIAL_DAYS = 7;
|
|
|
|
|
|
|
|
|
|
|
|
/** مدت پیشفرض پلنها (روز) — custom ندارد؛ lifetime عملاً بدون انقضا (۱۰۰ سال) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export const BILLING_PLAN_DAYS: Record<Exclude<BillingPlanCode, "custom">, number> = {
|
2026-10-06 19:54:38 +03:30
|
|
|
|
trial: BILLING_TRIAL_DAYS,
|
2026-09-27 21:17:42 +03:30
|
|
|
|
monthly: 30,
|
|
|
|
|
|
quarterly: 90,
|
|
|
|
|
|
semiannual: 180,
|
|
|
|
|
|
yearly: 365,
|
2026-10-06 19:54:38 +03:30
|
|
|
|
lifetime: 36500,
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-06 19:54:38 +03:30
|
|
|
|
export function isPermanentBillingPlan(code: string | null | undefined): boolean {
|
|
|
|
|
|
return code === "lifetime";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export const BILLING_PLAN_DEFAULT_MAX_ORGS: Record<
|
|
|
|
|
|
Exclude<BillingPlanCode, "custom">,
|
|
|
|
|
|
number
|
|
|
|
|
|
> = {
|
|
|
|
|
|
trial: 1,
|
|
|
|
|
|
monthly: 3,
|
|
|
|
|
|
quarterly: 5,
|
|
|
|
|
|
semiannual: 10,
|
|
|
|
|
|
yearly: 20,
|
2026-10-06 19:54:38 +03:30
|
|
|
|
lifetime: 20,
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type OrgModuleKey =
|
|
|
|
|
|
| "customers"
|
|
|
|
|
|
| "pipeline"
|
|
|
|
|
|
| "products"
|
|
|
|
|
|
| "invoices"
|
|
|
|
|
|
| "quotes"
|
|
|
|
|
|
| "commissions"
|
|
|
|
|
|
| "sales_forecast"
|
|
|
|
|
|
| "activities"
|
|
|
|
|
|
| "tickets"
|
|
|
|
|
|
| "workflow_automations"
|
2026-10-11 14:48:12 +03:30
|
|
|
|
| "workflow_automations_advanced"
|
2026-09-27 21:17:42 +03:30
|
|
|
|
| "message_automations"
|
|
|
|
|
|
| "lead_forms"
|
|
|
|
|
|
| "project_boards"
|
|
|
|
|
|
| "inventory"
|
|
|
|
|
|
| "cheques"
|
|
|
|
|
|
| "attachments"
|
|
|
|
|
|
| "loyalty"
|
|
|
|
|
|
| "reports"
|
|
|
|
|
|
| "notes"
|
|
|
|
|
|
| "tahesab"
|
|
|
|
|
|
| "custom_fields"
|
|
|
|
|
|
| "currencies"
|
|
|
|
|
|
| "team_settings"
|
|
|
|
|
|
| "faq"
|
|
|
|
|
|
| "audit_logs"
|
|
|
|
|
|
| "saved_views"
|
|
|
|
|
|
| "public_api"
|
|
|
|
|
|
| "sms_campaigns"
|
|
|
|
|
|
| "teams"
|
|
|
|
|
|
| "woocommerce"
|
2026-10-10 15:59:12 +03:30
|
|
|
|
| "montify"
|
2026-09-27 21:17:42 +03:30
|
|
|
|
| "voip"
|
|
|
|
|
|
| "reservations"
|
2026-09-29 10:34:01 +03:30
|
|
|
|
| "leads"
|
2026-10-06 19:54:38 +03:30
|
|
|
|
| "conversations"
|
2026-10-10 21:28:49 +03:30
|
|
|
|
| "routines"
|
2026-10-15 10:48:30 +03:30
|
|
|
|
| "gamification"
|
2026-10-18 19:33:09 +03:30
|
|
|
|
| "prior_data_archive"
|
2026-10-18 21:04:26 +03:30
|
|
|
|
| "treasury"
|
|
|
|
|
|
| "behavior";
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export const ORG_MODULE_KEYS: OrgModuleKey[] = [
|
|
|
|
|
|
"customers",
|
|
|
|
|
|
"pipeline",
|
|
|
|
|
|
"products",
|
|
|
|
|
|
"invoices",
|
|
|
|
|
|
"quotes",
|
|
|
|
|
|
"commissions",
|
|
|
|
|
|
"sales_forecast",
|
|
|
|
|
|
"activities",
|
|
|
|
|
|
"tickets",
|
|
|
|
|
|
"workflow_automations",
|
2026-10-11 14:48:12 +03:30
|
|
|
|
"workflow_automations_advanced",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
"message_automations",
|
|
|
|
|
|
"lead_forms",
|
|
|
|
|
|
"leads",
|
|
|
|
|
|
"project_boards",
|
|
|
|
|
|
"inventory",
|
|
|
|
|
|
"cheques",
|
|
|
|
|
|
"attachments",
|
|
|
|
|
|
"loyalty",
|
|
|
|
|
|
"reports",
|
|
|
|
|
|
"notes",
|
|
|
|
|
|
"tahesab",
|
|
|
|
|
|
"custom_fields",
|
|
|
|
|
|
"currencies",
|
|
|
|
|
|
"team_settings",
|
|
|
|
|
|
"faq",
|
|
|
|
|
|
"audit_logs",
|
|
|
|
|
|
"saved_views",
|
|
|
|
|
|
"public_api",
|
|
|
|
|
|
"sms_campaigns",
|
|
|
|
|
|
"teams",
|
|
|
|
|
|
"woocommerce",
|
2026-10-10 15:59:12 +03:30
|
|
|
|
"montify",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
"voip",
|
|
|
|
|
|
"reservations",
|
2026-09-29 10:34:01 +03:30
|
|
|
|
"conversations",
|
2026-10-06 19:54:38 +03:30
|
|
|
|
"routines",
|
2026-10-10 21:28:49 +03:30
|
|
|
|
"gamification",
|
2026-10-15 10:48:30 +03:30
|
|
|
|
"prior_data_archive",
|
2026-10-18 19:33:09 +03:30
|
|
|
|
"treasury",
|
2026-10-18 21:04:26 +03:30
|
|
|
|
"behavior",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const ORG_MODULE_LABELS: Record<OrgModuleKey, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
customers: "مشتریان",
|
2026-10-06 20:51:50 +03:30
|
|
|
|
pipeline: "مسیر فروش",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
products: "محصولات",
|
|
|
|
|
|
invoices: "فاکتورها",
|
|
|
|
|
|
quotes: "پیشفاکتورها",
|
|
|
|
|
|
commissions: "پورسانت",
|
|
|
|
|
|
sales_forecast: "هدف و پیشبینی",
|
|
|
|
|
|
activities: "فعالیتها",
|
|
|
|
|
|
tickets: "تیکتها",
|
|
|
|
|
|
workflow_automations: "اتوماسیون گردش کار",
|
2026-10-11 14:48:12 +03:30
|
|
|
|
workflow_automations_advanced: "اتوماسیون گردش کار حرفهای (چندشاخه)",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
message_automations: "اتوماسیون پیامک",
|
|
|
|
|
|
lead_forms: "فرمساز سرنخ",
|
|
|
|
|
|
leads: "مدیریت لیدها",
|
2026-10-06 20:51:50 +03:30
|
|
|
|
project_boards: "تابلوی پروژه",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
inventory: "انبار",
|
|
|
|
|
|
cheques: "چکها",
|
|
|
|
|
|
attachments: "پیوستها",
|
|
|
|
|
|
loyalty: "باشگاه مشتریان",
|
|
|
|
|
|
reports: "گزارشها",
|
|
|
|
|
|
notes: "یادداشتها",
|
2026-10-22 18:20:46 +03:30
|
|
|
|
tahesab: "تهحساب (اتصال و گزارش فروش)",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
custom_fields: "فیلدهای سفارشی",
|
|
|
|
|
|
currencies: "ارزها",
|
|
|
|
|
|
team_settings: "تیم و تنظیمات سازمان",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
faq: "FAQ",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
audit_logs: "لاگ فعالیت کاربران",
|
|
|
|
|
|
saved_views: "فیلترهای ذخیرهشده",
|
|
|
|
|
|
public_api: "API و وبهوک",
|
|
|
|
|
|
sms_campaigns: "پیامک گروهی",
|
|
|
|
|
|
teams: "تیمها و سطح دسترسی به پروندهها",
|
|
|
|
|
|
woocommerce: "ووکامرس",
|
2026-10-10 15:59:12 +03:30
|
|
|
|
montify: "فروشگاههای مونتیفای",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
voip: "تلفن سازمانی",
|
|
|
|
|
|
reservations: "رزرو و نوبتدهی",
|
2026-09-29 14:45:19 +03:30
|
|
|
|
conversations: "گفتگو",
|
2026-10-06 19:54:38 +03:30
|
|
|
|
routines: "کارهای روتین",
|
2026-10-10 21:28:49 +03:30
|
|
|
|
gamification: "باشگاه امتیازات",
|
2026-10-15 10:48:30 +03:30
|
|
|
|
prior_data_archive: "بایگانی اطلاعات قبل",
|
2026-10-18 19:33:09 +03:30
|
|
|
|
treasury: "مدیریت مالی",
|
2026-10-18 21:04:26 +03:30
|
|
|
|
behavior: "شخصیتشناسی و دستیار فروش",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const ORG_MODULE_GROUPS: Array<{
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
keys: OrgModuleKey[];
|
|
|
|
|
|
}> = [
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ title: "مشتریان", keys: ["customers"] },
|
|
|
|
|
|
{ title: "لیدها", keys: ["leads", "lead_forms"] },
|
2026-09-27 21:17:42 +03:30
|
|
|
|
{
|
2026-09-28 16:18:54 +03:30
|
|
|
|
title: "فروش",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
keys: [
|
|
|
|
|
|
"pipeline",
|
2026-10-18 21:04:26 +03:30
|
|
|
|
"behavior",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
"products",
|
|
|
|
|
|
"invoices",
|
|
|
|
|
|
"quotes",
|
|
|
|
|
|
"commissions",
|
|
|
|
|
|
"sales_forecast",
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
2026-10-10 21:28:49 +03:30
|
|
|
|
{ title: "فعالیتها", keys: ["activities", "routines", "notes", "gamification"] },
|
2026-09-29 10:34:01 +03:30
|
|
|
|
{ title: "پشتیبانی", keys: ["tickets", "faq", "conversations"] },
|
2026-09-27 21:17:42 +03:30
|
|
|
|
{
|
2026-09-28 16:18:54 +03:30
|
|
|
|
title: "اتوماسیون",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
keys: [
|
|
|
|
|
|
"workflow_automations",
|
|
|
|
|
|
"message_automations",
|
|
|
|
|
|
"sms_campaigns",
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-09-28 16:18:54 +03:30
|
|
|
|
title: "ابزارها",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
keys: [
|
|
|
|
|
|
"project_boards",
|
|
|
|
|
|
"inventory",
|
|
|
|
|
|
"cheques",
|
2026-10-18 19:33:09 +03:30
|
|
|
|
"treasury",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
"attachments",
|
|
|
|
|
|
"loyalty",
|
|
|
|
|
|
"reports",
|
|
|
|
|
|
"saved_views",
|
|
|
|
|
|
"woocommerce",
|
2026-10-10 15:59:12 +03:30
|
|
|
|
"montify",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
"voip",
|
|
|
|
|
|
"reservations",
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-09-28 16:18:54 +03:30
|
|
|
|
title: "تنظیمات",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
keys: [
|
|
|
|
|
|
"team_settings",
|
|
|
|
|
|
"teams",
|
|
|
|
|
|
"custom_fields",
|
|
|
|
|
|
"currencies",
|
|
|
|
|
|
"tahesab",
|
|
|
|
|
|
"audit_logs",
|
|
|
|
|
|
"public_api",
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export function isOrgModuleKey(value: string): value is OrgModuleKey {
|
|
|
|
|
|
return (ORG_MODULE_KEYS as readonly string[]).includes(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 14:48:12 +03:30
|
|
|
|
/** ماژولهایی که باید صراحتاً توسط سوپرادمین فعال شوند */
|
2026-10-15 10:48:30 +03:30
|
|
|
|
export const OPT_IN_ORG_MODULES: OrgModuleKey[] = [
|
|
|
|
|
|
"workflow_automations_advanced",
|
|
|
|
|
|
"prior_data_archive",
|
|
|
|
|
|
];
|
2026-10-11 14:48:12 +03:30
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export function defaultEnabledModules(): OrgModuleKey[] {
|
2026-10-11 14:48:12 +03:30
|
|
|
|
return ORG_MODULE_KEYS.filter((k) => !OPT_IN_ORG_MODULES.includes(k));
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-02 15:44:36 +03:30
|
|
|
|
export type StorageUsageLevel = "ok" | "warn" | "full" | "unlimited";
|
|
|
|
|
|
|
|
|
|
|
|
/** حجم فایل اشتراک مالک. quotaBytes خالی یعنی سقفی گذاشته نشده. */
|
|
|
|
|
|
export interface StorageUsageDto {
|
|
|
|
|
|
usedBytes: number;
|
|
|
|
|
|
quotaBytes: number | null;
|
|
|
|
|
|
percent: number | null;
|
|
|
|
|
|
level: StorageUsageLevel;
|
|
|
|
|
|
byOrganization?: Array<{ id: string; name: string; usedBytes: number }>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SetStorageQuotaRequest {
|
|
|
|
|
|
/** گیگابایت؛ null یعنی بدون سقف */
|
|
|
|
|
|
quotaGb: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-02 18:33:51 +03:30
|
|
|
|
/** سقفهای آمادهٔ تعداد کاربر. بالاتر از ۲۰ توافقی و با عدد جدا ذخیره میشود. */
|
|
|
|
|
|
export const BILLING_USER_CAPS = [3, 10, 20] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export interface OwnerSeatUsageDto {
|
|
|
|
|
|
usedUsers: number;
|
|
|
|
|
|
maxUsers: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SetUserCapRequest {
|
|
|
|
|
|
maxUsers: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface SubscriptionSummaryDto {
|
|
|
|
|
|
planCode: BillingPlanCode;
|
|
|
|
|
|
status: BillingAccountStatus;
|
|
|
|
|
|
startsAt: string;
|
|
|
|
|
|
expiresAt: string;
|
|
|
|
|
|
maxOrganizations: number;
|
|
|
|
|
|
daysRemaining: number;
|
|
|
|
|
|
warn: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface BillingAccountDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
ownerUserId: string;
|
|
|
|
|
|
planCode: BillingPlanCode;
|
|
|
|
|
|
status: BillingAccountStatus;
|
|
|
|
|
|
startsAt: string;
|
|
|
|
|
|
expiresAt: string;
|
|
|
|
|
|
maxOrganizations: number;
|
2026-11-02 18:33:51 +03:30
|
|
|
|
/** null یعنی سقف کاربر هنوز تعیین نشده */
|
|
|
|
|
|
maxUsers?: number | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
externalPaymentRef?: string | null;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
daysRemaining: number;
|
|
|
|
|
|
warn: boolean;
|
2026-11-02 15:44:36 +03:30
|
|
|
|
storage?: StorageUsageDto | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ApplyBillingPlanRequest {
|
|
|
|
|
|
planCode: BillingPlanCode;
|
|
|
|
|
|
maxOrganizations?: number;
|
|
|
|
|
|
expiresAt?: string;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
status?: BillingAccountStatus;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateOrgModulesRequest {
|
|
|
|
|
|
enabledModules: OrgModuleKey[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface OrgUserDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
mobile?: string | null;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
role: UserRole;
|
|
|
|
|
|
orgRoleId?: string | null;
|
|
|
|
|
|
orgRoleName?: string;
|
|
|
|
|
|
orgRoleColor?: string;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
lastLoginAt?: string | null;
|
|
|
|
|
|
hasAvatar?: boolean;
|
|
|
|
|
|
commissionPercent?: number;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** own = فقط خود | team = همتیمیها | org = کل سازمان */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
dataVisibility?: DataVisibility;
|
2026-10-06 15:04:46 +03:30
|
|
|
|
/** سطح دسترسی تفکیکی هر بخش (فقط بخشهایی که با پیشفرض فرق دارند) */
|
|
|
|
|
|
dataVisibilityScopes?: DataVisibilityScopes;
|
2026-10-22 11:53:26 +03:30
|
|
|
|
/**
|
|
|
|
|
|
* Override عمق داده نسبت به نقش؛ کلید غایب = ارث از نقش.
|
|
|
|
|
|
* اگر کل آبجکت خالی باشد و نقش هم خالی باشد → بدون محدودیت عمقی.
|
|
|
|
|
|
*/
|
|
|
|
|
|
dataAccessOverride?: DataAccessPolicy;
|
2026-11-04 10:02:31 +03:30
|
|
|
|
/**
|
|
|
|
|
|
* null = دسترسی مالی از نقش میآید.
|
|
|
|
|
|
* آبجکت = جایگزین بخشها و حسابهای مالی همین کاربر.
|
|
|
|
|
|
*/
|
|
|
|
|
|
treasuryAccessOverride?: UserTreasuryAccess | null;
|
2026-10-22 11:53:26 +03:30
|
|
|
|
/** سیاست مؤثر پس از resolve (نقش + override) — فقط برای نمایش UI */
|
|
|
|
|
|
effectiveDataAccess?: DataAccessPolicy;
|
|
|
|
|
|
/** @deprecated از effectiveDataAccess.customers استفاده کنید */
|
|
|
|
|
|
effectiveCustomersAccess?: SectionDataAccess;
|
2026-10-15 10:48:30 +03:30
|
|
|
|
/** تیک مالک: این کاربر سوئیچ بایگانی اطلاعات قبل را ببیند */
|
|
|
|
|
|
priorArchiveEnabled?: boolean;
|
2026-10-19 13:34:55 +03:30
|
|
|
|
/** ساعت خروج روزانه HH:mm به وقت تهران؛ خالی یعنی بدون محدودیت */
|
|
|
|
|
|
logoutTime?: string | null;
|
2026-10-21 14:20:15 +03:30
|
|
|
|
/** بعد از ساعت خروج تا پایان روز نمیتواند دوباره وارد شود */
|
|
|
|
|
|
blockLoginAfterLogout?: boolean;
|
2026-10-22 15:43:24 +03:30
|
|
|
|
/**
|
|
|
|
|
|
* ورود فقط با کد پیامکی؛ رمز عبور برای این کاربر در این محیط پذیرفته نمیشود
|
|
|
|
|
|
* (حتی اگر از قبل رمز داشته باشد).
|
|
|
|
|
|
*/
|
|
|
|
|
|
loginOtpOnly?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-22 15:43:24 +03:30
|
|
|
|
export interface UpdateUserLoginOtpOnlyRequest {
|
|
|
|
|
|
loginOtpOnly: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type DataVisibility = "own" | "team" | "org";
|
|
|
|
|
|
|
|
|
|
|
|
export const DATA_VISIBILITIES: DataVisibility[] = ["own", "team", "org"];
|
|
|
|
|
|
|
|
|
|
|
|
export const DATA_VISIBILITY_LABELS: Record<DataVisibility, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
own: "فقط پروندههای خود",
|
|
|
|
|
|
team: "تیمهای عضو",
|
|
|
|
|
|
org: "کل سازمان",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-06 15:04:46 +03:30
|
|
|
|
/** بخشهایی که سطح دسترسی به پروندهها برایشان جداگانه تعیین میشود */
|
|
|
|
|
|
export type DataScope =
|
|
|
|
|
|
| "contacts"
|
|
|
|
|
|
| "deals"
|
|
|
|
|
|
| "activities"
|
|
|
|
|
|
| "leads"
|
|
|
|
|
|
| "finance"
|
|
|
|
|
|
| "tickets"
|
|
|
|
|
|
| "notes"
|
2026-10-06 19:54:38 +03:30
|
|
|
|
| "projects"
|
|
|
|
|
|
| "routines";
|
2026-10-06 15:04:46 +03:30
|
|
|
|
|
|
|
|
|
|
export const DATA_SCOPES: DataScope[] = [
|
|
|
|
|
|
"contacts",
|
|
|
|
|
|
"deals",
|
|
|
|
|
|
"activities",
|
|
|
|
|
|
"leads",
|
|
|
|
|
|
"finance",
|
|
|
|
|
|
"tickets",
|
|
|
|
|
|
"notes",
|
|
|
|
|
|
"projects",
|
2026-10-06 19:54:38 +03:30
|
|
|
|
"routines",
|
2026-10-06 15:04:46 +03:30
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const DATA_SCOPE_LABELS: Record<DataScope, string> = {
|
|
|
|
|
|
contacts: "مشتریان (اشخاص و شرکتها)",
|
|
|
|
|
|
deals: "معاملات و اهداف فروش",
|
|
|
|
|
|
activities: "فعالیتها و کارها",
|
2026-10-21 13:45:16 +03:30
|
|
|
|
leads: "لیدها",
|
2026-10-06 15:04:46 +03:30
|
|
|
|
finance: "فاکتور، پیشفاکتور و چک",
|
|
|
|
|
|
tickets: "تیکتها",
|
|
|
|
|
|
notes: "یادداشتها",
|
|
|
|
|
|
projects: "پروژهها",
|
2026-10-06 19:54:38 +03:30
|
|
|
|
routines: "کارهای روتین",
|
2026-10-06 15:04:46 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type DataVisibilityScopes = Partial<Record<DataScope, DataVisibility>>;
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface UpdateUserVisibilityRequest {
|
2026-10-06 15:04:46 +03:30
|
|
|
|
/** سطح پیشفرض برای بخشهایی که جداگانه تنظیم نشدهاند */
|
|
|
|
|
|
dataVisibility?: DataVisibility;
|
|
|
|
|
|
/** سطح هر بخش؛ مقدار null یعنی «مثل پیشفرض» */
|
|
|
|
|
|
scopes?: Partial<Record<DataScope, DataVisibility | null>>;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-22 11:53:26 +03:30
|
|
|
|
/* ——— Deep data access (customers + leads + sales + activities + notes/routines/projects/tickets) ——— */
|
|
|
|
|
|
|
|
|
|
|
|
export type DataAccessOwnerKind = "self" | "team";
|
|
|
|
|
|
|
|
|
|
|
|
export type DataAccessMode = "all" | "rules";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* بخشهای عمق داده.
|
|
|
|
|
|
* customers/leads/sales/activities/notes ≈ RolePermissionKey؛
|
|
|
|
|
|
* routines/projects/tickets ≈ ماژول / DataScope (بدون مجوز نقش جدا).
|
|
|
|
|
|
*/
|
|
|
|
|
|
export type DataAccessSection =
|
|
|
|
|
|
| "customers"
|
|
|
|
|
|
| "leads"
|
|
|
|
|
|
| "sales"
|
|
|
|
|
|
| "activities"
|
|
|
|
|
|
| "notes"
|
|
|
|
|
|
| "routines"
|
|
|
|
|
|
| "projects"
|
|
|
|
|
|
| "tickets";
|
|
|
|
|
|
|
|
|
|
|
|
export const DATA_ACCESS_SECTIONS: DataAccessSection[] = [
|
|
|
|
|
|
"customers",
|
|
|
|
|
|
"leads",
|
|
|
|
|
|
"sales",
|
|
|
|
|
|
"activities",
|
|
|
|
|
|
"notes",
|
|
|
|
|
|
"routines",
|
|
|
|
|
|
"projects",
|
|
|
|
|
|
"tickets",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const DATA_ACCESS_SECTION_LABELS: Record<DataAccessSection, string> = {
|
|
|
|
|
|
customers: "مشتریان (اشخاص و شرکتها)",
|
|
|
|
|
|
leads: "لیدها",
|
|
|
|
|
|
sales: "فروش و معاملات",
|
|
|
|
|
|
activities: "فعالیتها",
|
|
|
|
|
|
notes: "یادداشتها",
|
|
|
|
|
|
routines: "کارهای روتین",
|
|
|
|
|
|
projects: "پروژهها",
|
|
|
|
|
|
tickets: "تیکتها",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** نگاشت بخش عمقداده ← کلید قدیمی DataScope برای مهاجرت */
|
|
|
|
|
|
export const DATA_ACCESS_LEGACY_SCOPE: Record<DataAccessSection, DataScope | null> = {
|
|
|
|
|
|
customers: "contacts",
|
|
|
|
|
|
leads: "leads",
|
|
|
|
|
|
sales: "deals",
|
|
|
|
|
|
activities: "activities",
|
|
|
|
|
|
notes: "notes",
|
|
|
|
|
|
routines: "routines",
|
|
|
|
|
|
projects: "projects",
|
|
|
|
|
|
tickets: "tickets",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* یک قانون: فیلدهای پرشده با AND.
|
|
|
|
|
|
* قوانین متعدد در SectionDataAccess با OR ترکیب میشوند.
|
|
|
|
|
|
* ابعاد بلااستفاده برای یک بخش نادیده گرفته میشوند.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export type DataAccessRule = {
|
|
|
|
|
|
/** self / team → مالک / مسئول / نویسنده (بسته به بخش) */
|
|
|
|
|
|
owners?: DataAccessOwnerKind[];
|
|
|
|
|
|
/** مشتریان: گروهها */
|
|
|
|
|
|
groupIds?: string[];
|
|
|
|
|
|
/** مشتریان: importSource/sourceRef · لیدها: source · یادداشت: noteType · تیکت: status */
|
|
|
|
|
|
sources?: string[];
|
|
|
|
|
|
/** مشتریان: حال */
|
|
|
|
|
|
moods?: PersonMood[];
|
|
|
|
|
|
/** لید / معامله / فعالیت / پروژه: مرحله */
|
|
|
|
|
|
stageIds?: string[];
|
|
|
|
|
|
/** معامله: پایپلاین */
|
|
|
|
|
|
pipelineIds?: string[];
|
|
|
|
|
|
/** فعالیت / پروژه: بورد */
|
|
|
|
|
|
boardIds?: string[];
|
|
|
|
|
|
/** فعالیت: typeDefId · روتین: categoryId */
|
|
|
|
|
|
typeIds?: string[];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** @deprecated نام قبلی؛ از DataAccessRule استفاده کنید */
|
|
|
|
|
|
export type ContactAccessRule = DataAccessRule;
|
|
|
|
|
|
|
|
|
|
|
|
export type SectionDataAccess = {
|
|
|
|
|
|
mode: DataAccessMode;
|
|
|
|
|
|
rules?: DataAccessRule[];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type DataAccessPolicy = Partial<Record<DataAccessSection, SectionDataAccess>>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateUserDataAccessRequest {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* null یعنی حذف override و برگشت به ارث از نقش.
|
|
|
|
|
|
* آبجکت یعنی جایگزینی کامل override (کلید غایب در آبجکت = آن بخش از override پاک میشود).
|
|
|
|
|
|
*/
|
|
|
|
|
|
dataAccessOverride?: DataAccessPolicy | null;
|
2026-11-04 10:02:31 +03:30
|
|
|
|
/**
|
|
|
|
|
|
* null = دسترسی مالی از نقش.
|
|
|
|
|
|
* نبودن فیلد یعنی این ذخیره به دسترسی مالی دست نمیزند.
|
|
|
|
|
|
*/
|
|
|
|
|
|
treasuryAccessOverride?: UserTreasuryAccess | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** دسترسی مالی اختصاصی یک کاربر؛ جایگزین نقش میشود */
|
|
|
|
|
|
export type UserTreasuryAccess = {
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
sections: RolePermissionKey[];
|
|
|
|
|
|
ownAccounts: boolean;
|
|
|
|
|
|
accountIds: string[];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export function parseUserTreasuryAccess(raw: unknown): UserTreasuryAccess | null {
|
|
|
|
|
|
if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
|
|
|
|
const obj = raw as Record<string, unknown>;
|
|
|
|
|
|
const sections = Array.isArray(obj.sections)
|
|
|
|
|
|
? obj.sections.filter(
|
|
|
|
|
|
(item): item is RolePermissionKey =>
|
|
|
|
|
|
typeof item === "string" && item.startsWith("treasury_"),
|
|
|
|
|
|
)
|
|
|
|
|
|
: [];
|
|
|
|
|
|
const accountIds = Array.isArray(obj.accountIds)
|
|
|
|
|
|
? obj.accountIds.filter((item): item is string => typeof item === "string" && item.trim().length > 0)
|
|
|
|
|
|
: [];
|
|
|
|
|
|
return {
|
|
|
|
|
|
enabled: obj.enabled === true,
|
|
|
|
|
|
sections: [...new Set(sections)],
|
|
|
|
|
|
ownAccounts: obj.ownAccounts === true,
|
|
|
|
|
|
accountIds: [...new Set(accountIds.map((id) => id.trim()))],
|
|
|
|
|
|
};
|
2026-10-22 11:53:26 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function emptySectionDataAccess(): SectionDataAccess {
|
|
|
|
|
|
return { mode: "all", rules: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function parseDataAccessPolicy(raw: unknown): DataAccessPolicy {
|
|
|
|
|
|
const out: DataAccessPolicy = {};
|
|
|
|
|
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out;
|
|
|
|
|
|
const obj = raw as Record<string, unknown>;
|
|
|
|
|
|
for (const section of DATA_ACCESS_SECTIONS) {
|
|
|
|
|
|
const parsed = parseSectionDataAccess(obj[section]);
|
|
|
|
|
|
if (parsed) out[section] = parsed;
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function parseSectionDataAccess(raw: unknown): SectionDataAccess | null {
|
|
|
|
|
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
|
|
|
|
const obj = raw as Record<string, unknown>;
|
|
|
|
|
|
const mode: DataAccessMode = obj.mode === "rules" ? "rules" : "all";
|
|
|
|
|
|
const rulesRaw = Array.isArray(obj.rules) ? obj.rules : [];
|
|
|
|
|
|
const rules: DataAccessRule[] = [];
|
|
|
|
|
|
for (const item of rulesRaw) {
|
|
|
|
|
|
const rule = parseDataAccessRule(item);
|
|
|
|
|
|
if (rule) rules.push(rule);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mode === "all") return { mode: "all", rules: [] };
|
|
|
|
|
|
return { mode: "rules", rules };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function parseStringIdList(raw: unknown): string[] | undefined {
|
|
|
|
|
|
if (!Array.isArray(raw)) return undefined;
|
|
|
|
|
|
const list = raw
|
|
|
|
|
|
.filter((v): v is string => typeof v === "string" && v.trim().length > 0)
|
|
|
|
|
|
.map((v) => v.trim());
|
|
|
|
|
|
return list.length ? [...new Set(list)] : undefined;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function parseDataAccessRule(raw: unknown): DataAccessRule | null {
|
|
|
|
|
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
|
|
|
|
const obj = raw as Record<string, unknown>;
|
|
|
|
|
|
const rule: DataAccessRule = {};
|
|
|
|
|
|
if (Array.isArray(obj.owners)) {
|
|
|
|
|
|
const owners = obj.owners.filter(
|
|
|
|
|
|
(v): v is DataAccessOwnerKind => v === "self" || v === "team",
|
|
|
|
|
|
);
|
|
|
|
|
|
if (owners.length) rule.owners = [...new Set(owners)];
|
|
|
|
|
|
}
|
|
|
|
|
|
const groupIds = parseStringIdList(obj.groupIds);
|
|
|
|
|
|
if (groupIds) rule.groupIds = groupIds;
|
|
|
|
|
|
const sources = parseStringIdList(obj.sources);
|
|
|
|
|
|
if (sources) rule.sources = sources;
|
|
|
|
|
|
if (Array.isArray(obj.moods)) {
|
|
|
|
|
|
const moods = obj.moods.filter(
|
|
|
|
|
|
(v): v is PersonMood =>
|
|
|
|
|
|
typeof v === "string" && (PERSON_MOODS as readonly string[]).includes(v),
|
|
|
|
|
|
);
|
|
|
|
|
|
if (moods.length) rule.moods = [...new Set(moods)];
|
|
|
|
|
|
}
|
|
|
|
|
|
const stageIds = parseStringIdList(obj.stageIds);
|
|
|
|
|
|
if (stageIds) rule.stageIds = stageIds;
|
|
|
|
|
|
const pipelineIds = parseStringIdList(obj.pipelineIds);
|
|
|
|
|
|
if (pipelineIds) rule.pipelineIds = pipelineIds;
|
|
|
|
|
|
const boardIds = parseStringIdList(obj.boardIds);
|
|
|
|
|
|
if (boardIds) rule.boardIds = boardIds;
|
|
|
|
|
|
const typeIds = parseStringIdList(obj.typeIds);
|
|
|
|
|
|
if (typeIds) rule.typeIds = typeIds;
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
!rule.owners &&
|
|
|
|
|
|
!rule.groupIds &&
|
|
|
|
|
|
!rule.sources &&
|
|
|
|
|
|
!rule.moods &&
|
|
|
|
|
|
!rule.stageIds &&
|
|
|
|
|
|
!rule.pipelineIds &&
|
|
|
|
|
|
!rule.boardIds &&
|
|
|
|
|
|
!rule.typeIds
|
|
|
|
|
|
) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return rule;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @deprecated */
|
|
|
|
|
|
export const parseContactAccessRule = parseDataAccessRule;
|
|
|
|
|
|
|
|
|
|
|
|
/** آیا این بخش محدودیت عمقی دارد؟ mode=all یا بدون قانون معتبر = بدون محدودیت */
|
|
|
|
|
|
export function sectionHasDataAccessLimits(section: SectionDataAccess | null | undefined): boolean {
|
|
|
|
|
|
if (!section || section.mode !== "rules") return false;
|
|
|
|
|
|
return Boolean(section.rules?.length);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* مهاجرت از مدل قدیمی own/team/org به SectionDataAccess.
|
|
|
|
|
|
* اگر scopeKey داده شود، سطح تفکیکی همان بخش اولویت دارد.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function migrateVisibilityToSectionAccess(
|
|
|
|
|
|
dataVisibility: DataVisibility | string | null | undefined,
|
|
|
|
|
|
scopes?: DataVisibilityScopes | null,
|
|
|
|
|
|
scopeKey?: DataScope | null,
|
|
|
|
|
|
): SectionDataAccess {
|
|
|
|
|
|
const scoped =
|
|
|
|
|
|
scopeKey && scopes
|
|
|
|
|
|
? scopes[scopeKey]
|
|
|
|
|
|
: undefined;
|
|
|
|
|
|
const level =
|
|
|
|
|
|
scoped === "own" || scoped === "team" || scoped === "org"
|
|
|
|
|
|
? scoped
|
|
|
|
|
|
: dataVisibility === "team" || dataVisibility === "org" || dataVisibility === "own"
|
|
|
|
|
|
? dataVisibility
|
|
|
|
|
|
: "own";
|
|
|
|
|
|
if (level === "org") return { mode: "all", rules: [] };
|
|
|
|
|
|
if (level === "team") return { mode: "rules", rules: [{ owners: ["self", "team"] }] };
|
|
|
|
|
|
return { mode: "rules", rules: [{ owners: ["self"] }] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @deprecated از migrateVisibilityToSectionAccess استفاده کنید */
|
|
|
|
|
|
export function migrateVisibilityToCustomersAccess(
|
|
|
|
|
|
dataVisibility: DataVisibility | string | null | undefined,
|
|
|
|
|
|
scopes?: DataVisibilityScopes | null,
|
|
|
|
|
|
): SectionDataAccess {
|
|
|
|
|
|
return migrateVisibilityToSectionAccess(dataVisibility, scopes, "contacts");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type RolePermissionKey =
|
|
|
|
|
|
| "customers"
|
2026-11-03 13:15:21 +03:30
|
|
|
|
| "assign_customers"
|
2026-10-21 13:45:16 +03:30
|
|
|
|
| "leads"
|
2026-11-03 13:15:21 +03:30
|
|
|
|
| "assign_leads"
|
2026-09-27 21:17:42 +03:30
|
|
|
|
| "sales"
|
2026-11-03 13:15:21 +03:30
|
|
|
|
| "assign_sales"
|
2026-09-27 21:17:42 +03:30
|
|
|
|
| "billing"
|
|
|
|
|
|
| "products"
|
|
|
|
|
|
| "activities"
|
2026-11-03 13:15:21 +03:30
|
|
|
|
| "assign_activities"
|
|
|
|
|
|
| "assign_routines"
|
|
|
|
|
|
| "assign_projects"
|
|
|
|
|
|
| "assign_tickets"
|
2026-09-27 21:17:42 +03:30
|
|
|
|
| "notes"
|
|
|
|
|
|
| "reports"
|
2026-09-29 10:34:01 +03:30
|
|
|
|
| "audit"
|
|
|
|
|
|
| "conversations"
|
2026-10-18 19:33:09 +03:30
|
|
|
|
| "conversations_groups"
|
|
|
|
|
|
| "treasury"
|
2026-11-04 10:02:31 +03:30
|
|
|
|
| "treasury_dashboard"
|
|
|
|
|
|
| "treasury_incomes"
|
|
|
|
|
|
| "treasury_expenses"
|
|
|
|
|
|
| "treasury_receivables"
|
|
|
|
|
|
| "treasury_accounts"
|
2026-10-18 21:04:26 +03:30
|
|
|
|
| "treasury_payroll"
|
2026-11-04 10:02:31 +03:30
|
|
|
|
| "treasury_recurring"
|
|
|
|
|
|
| "treasury_reports"
|
|
|
|
|
|
| "treasury_audit"
|
|
|
|
|
|
| "treasury_categories"
|
2026-10-18 21:04:26 +03:30
|
|
|
|
| "behavior";
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
2026-11-03 13:15:21 +03:30
|
|
|
|
/** بخشی که مسئولش را میشود به کاربر دیگری داد */
|
|
|
|
|
|
export type AssignSection =
|
|
|
|
|
|
| "customers"
|
|
|
|
|
|
| "leads"
|
|
|
|
|
|
| "sales"
|
|
|
|
|
|
| "activities"
|
|
|
|
|
|
| "routines"
|
|
|
|
|
|
| "projects"
|
|
|
|
|
|
| "tickets";
|
|
|
|
|
|
|
|
|
|
|
|
export const ASSIGN_SECTION_PERMISSION: Record<AssignSection, RolePermissionKey> = {
|
|
|
|
|
|
customers: "assign_customers",
|
|
|
|
|
|
leads: "assign_leads",
|
|
|
|
|
|
sales: "assign_sales",
|
|
|
|
|
|
activities: "assign_activities",
|
|
|
|
|
|
routines: "assign_routines",
|
|
|
|
|
|
projects: "assign_projects",
|
|
|
|
|
|
tickets: "assign_tickets",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export const ROLE_PERMISSION_ITEMS: Array<{
|
|
|
|
|
|
key: RolePermissionKey;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
}> = [
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ key: "customers", label: "مشتریان" },
|
2026-11-03 13:15:21 +03:30
|
|
|
|
{ key: "assign_customers", label: "تخصیص مشتری به دیگران" },
|
2026-10-21 13:45:16 +03:30
|
|
|
|
{ key: "leads", label: "لیدها" },
|
2026-11-03 13:15:21 +03:30
|
|
|
|
{ key: "assign_leads", label: "تخصیص لید به دیگران" },
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ key: "sales", label: "فروش و معاملات" },
|
2026-11-03 13:15:21 +03:30
|
|
|
|
{ key: "assign_sales", label: "تخصیص معامله به دیگران" },
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ key: "billing", label: "پیشفاکتورها و فاکتورها" },
|
|
|
|
|
|
{ key: "products", label: "محصولات و خدمات" },
|
|
|
|
|
|
{ key: "activities", label: "فعالیتها" },
|
2026-11-03 13:15:21 +03:30
|
|
|
|
{ key: "assign_activities", label: "تخصیص فعالیت به دیگران" },
|
|
|
|
|
|
{ key: "assign_routines", label: "تخصیص کار روتین به دیگران" },
|
|
|
|
|
|
{ key: "assign_projects", label: "تخصیص کارت پروژه به دیگران" },
|
|
|
|
|
|
{ key: "assign_tickets", label: "تخصیص تیکت به دیگران" },
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ key: "notes", label: "یادداشتها" },
|
|
|
|
|
|
{ key: "reports", label: "گزارشها" },
|
|
|
|
|
|
{ key: "audit", label: "لاگ فعالیت کاربران" },
|
2026-09-29 14:45:19 +03:30
|
|
|
|
{ key: "conversations", label: "گفتگو" },
|
2026-09-29 10:34:01 +03:30
|
|
|
|
{ key: "conversations_groups", label: "ساخت گروه گفتگو" },
|
2026-10-18 19:33:09 +03:30
|
|
|
|
{ key: "treasury", label: "مدیریت مالی" },
|
2026-11-04 10:02:31 +03:30
|
|
|
|
{ key: "treasury_dashboard", label: "مالی — داشبورد" },
|
|
|
|
|
|
{ key: "treasury_incomes", label: "مالی — دریافتها و درآمدها" },
|
|
|
|
|
|
{ key: "treasury_expenses", label: "مالی — پرداختها و هزینهها" },
|
|
|
|
|
|
{ key: "treasury_receivables", label: "مالی — اقساط و مطالبات" },
|
|
|
|
|
|
{ key: "treasury_accounts", label: "مالی — صندوق و حسابها" },
|
|
|
|
|
|
{ key: "treasury_payroll", label: "مالی — حقوق و دستمزد" },
|
|
|
|
|
|
{ key: "treasury_recurring", label: "مالی — تکرارشونده" },
|
|
|
|
|
|
{ key: "treasury_reports", label: "مالی — گزارش" },
|
|
|
|
|
|
{ key: "treasury_audit", label: "مالی — سوابق" },
|
2026-11-04 13:11:09 +03:30
|
|
|
|
{ key: "treasury_categories", label: "مالی — دستهبندی" },
|
2026-10-18 21:04:26 +03:30
|
|
|
|
{ key: "behavior", label: "شخصیتشناسی و دستیار فروش" },
|
2026-09-27 21:17:42 +03:30
|
|
|
|
];
|
|
|
|
|
|
|
2026-11-04 10:02:31 +03:30
|
|
|
|
/** بخشهای ریز مدیریت مالی. حقوق جداست و با تیک خودش باز میشود. */
|
|
|
|
|
|
export const TREASURY_SECTION_KEYS = [
|
|
|
|
|
|
"treasury_dashboard",
|
|
|
|
|
|
"treasury_incomes",
|
|
|
|
|
|
"treasury_expenses",
|
|
|
|
|
|
"treasury_receivables",
|
|
|
|
|
|
"treasury_accounts",
|
|
|
|
|
|
"treasury_payroll",
|
|
|
|
|
|
"treasury_recurring",
|
|
|
|
|
|
"treasury_reports",
|
|
|
|
|
|
"treasury_audit",
|
|
|
|
|
|
"treasury_categories",
|
|
|
|
|
|
] as const satisfies readonly RolePermissionKey[];
|
|
|
|
|
|
|
|
|
|
|
|
export type TreasurySectionKey = (typeof TREASURY_SECTION_KEYS)[number];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* دسترسی کلی «مدیریت مالی» در را باز میکند.
|
|
|
|
|
|
* اگر هیچ بخش ریزی انتخاب نشده باشد همه بخشها بهجز حقوق بازند.
|
|
|
|
|
|
* با انتخاب حتی یک بخش ریز، فقط همانها باز میمانند.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function treasurySectionAllowed(
|
|
|
|
|
|
role: string | null | undefined,
|
|
|
|
|
|
permissions: readonly string[] | null | undefined,
|
|
|
|
|
|
section: TreasurySectionKey | "treasury_documents",
|
|
|
|
|
|
): boolean {
|
|
|
|
|
|
if (role === "owner" || role === "admin") return true;
|
|
|
|
|
|
const perms = permissions ?? [];
|
|
|
|
|
|
if (section === "treasury_payroll") return perms.includes("treasury_payroll");
|
|
|
|
|
|
if (!perms.includes("treasury")) return false;
|
|
|
|
|
|
const fine = TREASURY_SECTION_KEYS.filter(
|
|
|
|
|
|
(key) => key !== "treasury_payroll" && perms.includes(key),
|
|
|
|
|
|
);
|
|
|
|
|
|
if (fine.length === 0) return true;
|
|
|
|
|
|
if (section === "treasury_documents") {
|
|
|
|
|
|
return perms.includes("treasury_incomes") || perms.includes("treasury_expenses");
|
|
|
|
|
|
}
|
|
|
|
|
|
return perms.includes(section);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface OrgRoleMemberDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
hasAvatar: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface OrgRoleDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
color: string;
|
|
|
|
|
|
permissions: RolePermissionKey[];
|
2026-10-22 11:53:26 +03:30
|
|
|
|
/** پیشفرض عمق داده برای اعضای این نقش */
|
|
|
|
|
|
dataAccessPolicy?: DataAccessPolicy;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
systemKey: UserRole | null;
|
2026-10-03 13:16:13 +03:30
|
|
|
|
/** نقش والد؛ null یعنی ریشه */
|
|
|
|
|
|
parentId: string | null;
|
|
|
|
|
|
/** نام نقش والد برای نمایش در UI */
|
|
|
|
|
|
parentName: string | null;
|
|
|
|
|
|
/** عمق در درخت (۰ = ریشه) */
|
|
|
|
|
|
depth: number;
|
|
|
|
|
|
/** تعداد زیرنقشهای مستقیم */
|
|
|
|
|
|
childCount: number;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
userCount: number;
|
|
|
|
|
|
members: OrgRoleMemberDto[];
|
2026-11-04 10:02:31 +03:30
|
|
|
|
/** حسابهای اضافی؛ خالی بهتنهایی یعنی هنوز محدود نشده */
|
|
|
|
|
|
treasuryAccountIds: string[];
|
|
|
|
|
|
/** true یعنی اعضای نقش حسابهایی را میبینند که خودشان مسئول آنها هستند */
|
|
|
|
|
|
treasuryOwnAccounts: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateOrgRoleRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
color?: string;
|
|
|
|
|
|
permissions?: RolePermissionKey[];
|
2026-10-22 11:53:26 +03:30
|
|
|
|
dataAccessPolicy?: DataAccessPolicy;
|
2026-10-03 13:16:13 +03:30
|
|
|
|
/** اگر ست شود، این نقش زیرمجموعهٔ نقش والد خواهد بود */
|
|
|
|
|
|
parentId?: string | null;
|
2026-11-04 10:02:31 +03:30
|
|
|
|
/** حسابهای اضافی، جدا از حسابهایی که خودش مسئول است */
|
|
|
|
|
|
treasuryAccountIds?: string[];
|
|
|
|
|
|
/** دسترسی به حسابهایی که نام خود عضو در مسئول ثبت شده */
|
|
|
|
|
|
treasuryOwnAccounts?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateOrgRoleRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
color?: string;
|
|
|
|
|
|
permissions?: RolePermissionKey[];
|
2026-10-22 11:53:26 +03:30
|
|
|
|
dataAccessPolicy?: DataAccessPolicy;
|
2026-10-03 13:16:13 +03:30
|
|
|
|
/** null یعنی جدا کردن از والد و تبدیل به ریشه */
|
|
|
|
|
|
parentId?: string | null;
|
2026-11-04 10:02:31 +03:30
|
|
|
|
/** حسابهای اضافی، جدا از حسابهایی که خودش مسئول است */
|
|
|
|
|
|
treasuryAccountIds?: string[];
|
|
|
|
|
|
/** دسترسی به حسابهایی که نام خود عضو در مسئول ثبت شده */
|
|
|
|
|
|
treasuryOwnAccounts?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface InviteUserRequest {
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
password?: string;
|
|
|
|
|
|
role?: Exclude<UserRole, "owner">;
|
|
|
|
|
|
orgRoleId?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 12:31:07 +03:30
|
|
|
|
/** ویرایش مشخصات عضو تیم (نام، موبایل، رمز) — نه نقش/دسترسی */
|
|
|
|
|
|
export interface UpdateOrgUserRequest {
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
/** در صورت ارسال، رمز جدید تنظیم میشود (حداقل ۸ کاراکتر) */
|
|
|
|
|
|
password?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface UpdateUserRoleRequest {
|
|
|
|
|
|
role?: UserRole;
|
|
|
|
|
|
orgRoleId?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateUserStatusRequest {
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateUserCommissionRequest {
|
|
|
|
|
|
commissionPercent: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateProfileRequest {
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type DealStatus = "open" | "won" | "lost";
|
|
|
|
|
|
|
|
|
|
|
|
export interface ApiErrorBody {
|
|
|
|
|
|
statusCode: number;
|
|
|
|
|
|
message: string | string[];
|
|
|
|
|
|
error?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoginRequest {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** ایمیل یا شماره موبایل */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
identifier: string;
|
|
|
|
|
|
password: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RegisterOrganizationRequest {
|
|
|
|
|
|
organizationName: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
password: string;
|
|
|
|
|
|
passwordConfirm: string;
|
|
|
|
|
|
businessField: BusinessField;
|
|
|
|
|
|
referralCode?: string;
|
|
|
|
|
|
acceptedTerms?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoginChallengeResponse {
|
|
|
|
|
|
challengeId: string;
|
|
|
|
|
|
channel: 'email' | 'mobile';
|
|
|
|
|
|
expiresInSeconds: number;
|
|
|
|
|
|
maskedDestination?: string;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** فقط در محیط توسعه / AUTH_RETURN_OTP */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
devCode?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RegisterChallengeResponse {
|
|
|
|
|
|
challengeId: string;
|
|
|
|
|
|
expiresInSeconds: number;
|
|
|
|
|
|
maskedEmail?: string;
|
|
|
|
|
|
maskedMobile?: string;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** وقتی سرویس ایمیل خاموش است false — فقط OTP موبایل لازم است */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
emailOtpRequired?: boolean;
|
|
|
|
|
|
devEmailCode?: string;
|
|
|
|
|
|
devMobileCode?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoginVerifyRequest {
|
|
|
|
|
|
challengeId: string;
|
|
|
|
|
|
code: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RegisterVerifyRequest {
|
|
|
|
|
|
challengeId: string;
|
|
|
|
|
|
emailCode?: string;
|
|
|
|
|
|
mobileCode: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoginResponse {
|
|
|
|
|
|
accessToken: string;
|
|
|
|
|
|
user: AuthUserDto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface OrganizationMembershipDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
role: UserRole;
|
|
|
|
|
|
orgRoleName?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoginOrganizationChoiceResponse {
|
|
|
|
|
|
requiresOrganizationChoice: true;
|
|
|
|
|
|
selectionToken: string;
|
|
|
|
|
|
organizations: OrganizationMembershipDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type LoginResult = LoginResponse | LoginOrganizationChoiceResponse;
|
|
|
|
|
|
|
|
|
|
|
|
export function isLoginOrganizationChoice(
|
|
|
|
|
|
data: LoginResult,
|
|
|
|
|
|
): data is LoginOrganizationChoiceResponse {
|
|
|
|
|
|
return (
|
|
|
|
|
|
typeof data === "object" &&
|
|
|
|
|
|
data !== null &&
|
|
|
|
|
|
"requiresOrganizationChoice" in data &&
|
|
|
|
|
|
data.requiresOrganizationChoice === true
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SelectOrganizationRequest {
|
|
|
|
|
|
selectionToken: string;
|
|
|
|
|
|
organizationId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SwitchOrganizationRequest {
|
|
|
|
|
|
organizationId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateOrganizationRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
businessField?: BusinessField;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface OrganizationDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
slug: string;
|
|
|
|
|
|
businessField: BusinessField | null;
|
|
|
|
|
|
baseCurrency: string;
|
|
|
|
|
|
enabledCurrencies: string[];
|
|
|
|
|
|
exchangeRates: ExchangeRateDto[];
|
|
|
|
|
|
enabledModules: OrgModuleKey[];
|
2026-10-17 16:27:29 +03:30
|
|
|
|
priorArchiveCutoffAt?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdatePriorArchiveCutoffRequest {
|
|
|
|
|
|
/** ISO datetime (start of selected Jalali day) or null to clear */
|
|
|
|
|
|
cutoffAt: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateOrganizationRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
businessField?: BusinessField;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** زمینه فعالیت سازمان — همتراز صنایع دیدار */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export const BUSINESS_FIELDS = [
|
|
|
|
|
|
"education",
|
|
|
|
|
|
"beauty",
|
|
|
|
|
|
"real_estate",
|
|
|
|
|
|
"construction",
|
|
|
|
|
|
"advertising",
|
|
|
|
|
|
"insurance",
|
|
|
|
|
|
"consulting",
|
|
|
|
|
|
"startup",
|
|
|
|
|
|
"medical_devices",
|
|
|
|
|
|
"travel",
|
|
|
|
|
|
"contractor",
|
|
|
|
|
|
"trading",
|
|
|
|
|
|
"manufacturing",
|
|
|
|
|
|
"personal",
|
|
|
|
|
|
"tech",
|
|
|
|
|
|
"legal",
|
|
|
|
|
|
"repair",
|
|
|
|
|
|
"retail",
|
|
|
|
|
|
"jewelry",
|
|
|
|
|
|
"other",
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type BusinessField = (typeof BUSINESS_FIELDS)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export const BUSINESS_FIELD_LABELS: Record<BusinessField, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
education: "آموزشگاه و مؤسسات آموزشی",
|
|
|
|
|
|
beauty: "کلینیک زیبایی و آرایشگاه",
|
|
|
|
|
|
real_estate: "املاک و مشاورین مسکن",
|
|
|
|
|
|
construction: "صنعت ساختمان",
|
|
|
|
|
|
advertising: "تبلیغات و مشاوره کسبوکار",
|
|
|
|
|
|
insurance: "بیمه",
|
|
|
|
|
|
consulting: "شرکتهای مشاور",
|
|
|
|
|
|
startup: "استارتاپ و کسبوکار آنلاین",
|
|
|
|
|
|
medical_devices: "تجهیزات پزشکی",
|
|
|
|
|
|
travel: "گردشگری و مهاجرت",
|
|
|
|
|
|
contractor: "پیمانکاری و پروژهای",
|
|
|
|
|
|
trading: "بازرگانی",
|
|
|
|
|
|
manufacturing: "تولیدی و صنعتی",
|
|
|
|
|
|
personal: "کسبوکار شخصی و خانگی",
|
|
|
|
|
|
tech: "نرمافزار و سختافزار",
|
|
|
|
|
|
legal: "وکلا و مشاورین حقوقی",
|
|
|
|
|
|
repair: "خدمات و تعمیرات",
|
|
|
|
|
|
retail: "خردهفروشی",
|
|
|
|
|
|
jewelry: "طلا و جواهر",
|
|
|
|
|
|
other: "سایر",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export function isBusinessField(value: string): value is BusinessField {
|
|
|
|
|
|
return (BUSINESS_FIELDS as readonly string[]).includes(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ExchangeRateDto {
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
rateToBase: number;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateOrgCurrenciesRequest {
|
|
|
|
|
|
baseCurrency?: string;
|
|
|
|
|
|
enabledCurrencies?: string[];
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** نرخها نسبت به ارز پایه؛ ارز پایه نیازی به نرخ ندارد */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
rates?: Array<{ currency: string; rateToBase: number }>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const CURRENCY_CATALOG: Array<{
|
|
|
|
|
|
code: string;
|
|
|
|
|
|
labelFa: string;
|
|
|
|
|
|
suffix: string;
|
|
|
|
|
|
}> = [
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ code: "IRR", labelFa: "ریال ایران", suffix: "ریال" },
|
|
|
|
|
|
{ code: "USD", labelFa: "دلار آمریکا", suffix: "دلار" },
|
|
|
|
|
|
{ code: "EUR", labelFa: "یورو", suffix: "یورو" },
|
|
|
|
|
|
{ code: "AED", labelFa: "درهم امارات", suffix: "درهم" },
|
|
|
|
|
|
{ code: "TRY", labelFa: "لیر ترکیه", suffix: "لیر" },
|
|
|
|
|
|
{ code: "GBP", labelFa: "پوند انگلیس", suffix: "پوند" },
|
2026-09-27 21:17:42 +03:30
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export function currencySuffix(code: string): string {
|
|
|
|
|
|
return CURRENCY_CATALOG.find((c) => c.code === code)?.suffix ?? code;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function formatMoneyAmount(amount: number, currency = "IRR"): string {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const n = new Intl.NumberFormat("fa-IR").format(amount);
|
|
|
|
|
|
return `${n} ${currencySuffix(currency)}`;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return `${amount} ${currency}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HealthResponse {
|
|
|
|
|
|
status: "ok";
|
|
|
|
|
|
service: string;
|
|
|
|
|
|
timestamp: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-21 17:16:31 +03:30
|
|
|
|
/* ───────── گروه مشتری (اشخاص و شرکتها) ───────── */
|
|
|
|
|
|
|
|
|
|
|
|
export const CUSTOMER_GROUP_COLORS = [
|
|
|
|
|
|
"teal",
|
|
|
|
|
|
"sky",
|
|
|
|
|
|
"violet",
|
|
|
|
|
|
"rose",
|
|
|
|
|
|
"amber",
|
|
|
|
|
|
"pink",
|
|
|
|
|
|
"slate",
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
export type CustomerGroupColor = (typeof CUSTOMER_GROUP_COLORS)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export interface CustomerGroupSummaryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
color: CustomerGroupColor | string;
|
|
|
|
|
|
/** manual | tahesab — فقط برای اشخاص */
|
|
|
|
|
|
source?: "manual" | "tahesab";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CustomerGroupDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
color: CustomerGroupColor | string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
/** گروه متناظر در تهحساب دارد */
|
|
|
|
|
|
fromTahesab: boolean;
|
|
|
|
|
|
personCount: number;
|
|
|
|
|
|
companyCount: number;
|
2026-10-26 11:05:34 +03:30
|
|
|
|
/** گروه اصلی؛ خالی یعنی خودش اصلی است */
|
|
|
|
|
|
parentId?: string | null;
|
|
|
|
|
|
parentName?: string | null;
|
2026-10-21 17:16:31 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateCustomerGroupRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
color?: CustomerGroupColor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateCustomerGroupRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
color?: CustomerGroupColor;
|
2026-10-26 11:05:34 +03:30
|
|
|
|
/** null = برداشتن گروه اصلی */
|
|
|
|
|
|
parentId?: string | null;
|
2026-10-21 17:16:31 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface CompanySummaryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
2026-10-17 20:54:32 +03:30
|
|
|
|
province?: string | null;
|
|
|
|
|
|
city?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CompanyDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
/** کد داخلی شرکت */
|
|
|
|
|
|
code: string | null;
|
|
|
|
|
|
importSource: PersonImportSource | string;
|
|
|
|
|
|
/** کد شرکت در مبدأ (مثلاً کد دیدار) */
|
|
|
|
|
|
sourceRef: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
nationalId: string | null;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
economicCode: string | null;
|
|
|
|
|
|
registrationNumber: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
phone: string | null;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
otherPhones: string | null;
|
|
|
|
|
|
fax: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
email: string | null;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
otherEmails: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
website: string | null;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
province: string | null;
|
|
|
|
|
|
city: string | null;
|
|
|
|
|
|
postalCode: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
address: string | null;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
groupName: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
notes: string | null;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
registeredAt: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
tags: string[];
|
|
|
|
|
|
customFields: Record<string, unknown>;
|
2026-10-21 17:16:31 +03:30
|
|
|
|
groups: CustomerGroupSummaryDto[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
peopleCount: number;
|
|
|
|
|
|
owner: UserSummaryDto | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateCompanyRequest {
|
|
|
|
|
|
name: string;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
code?: string | null;
|
|
|
|
|
|
importSource?: PersonImportSource | string;
|
|
|
|
|
|
sourceRef?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
nationalId?: string;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
economicCode?: string | null;
|
|
|
|
|
|
registrationNumber?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
phone?: string;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
otherPhones?: string | null;
|
|
|
|
|
|
fax?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
email?: string;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
otherEmails?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
website?: string;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
province?: string | null;
|
|
|
|
|
|
city?: string | null;
|
|
|
|
|
|
postalCode?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
address?: string;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
groupName?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
notes?: string;
|
2026-10-06 12:25:20 +03:30
|
|
|
|
registeredAt?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
tags?: string[];
|
|
|
|
|
|
customFields?: Record<string, unknown>;
|
|
|
|
|
|
ownerId?: string | null;
|
2026-10-21 17:16:31 +03:30
|
|
|
|
/** همهٔ گروههای شرکت (جایگزین کامل) */
|
|
|
|
|
|
groupIds?: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateCompanyRequest = Partial<CreateCompanyRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoyaltyTierSummaryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
minPoints: number;
|
|
|
|
|
|
color: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const PERSON_MOODS = ["newcomer", "happy", "unhappy", "special"] as const;
|
|
|
|
|
|
export type PersonMood = (typeof PERSON_MOODS)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export const PERSON_MOOD_LABELS: Record<PersonMood, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
newcomer: "مشتری تازه وارد",
|
|
|
|
|
|
happy: "مشتری خوشحال",
|
|
|
|
|
|
unhappy: "مشتری ناراضی",
|
|
|
|
|
|
special: "مشتری خاص",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-04 12:01:21 +03:30
|
|
|
|
/** منبع ورود شخص به CRM */
|
|
|
|
|
|
export const PERSON_IMPORT_SOURCES = [
|
|
|
|
|
|
"manual",
|
|
|
|
|
|
"tahesab",
|
|
|
|
|
|
"didar",
|
|
|
|
|
|
"excel",
|
|
|
|
|
|
"phone",
|
|
|
|
|
|
"lead_form",
|
2026-10-10 15:59:12 +03:30
|
|
|
|
"montify",
|
2026-10-04 12:01:21 +03:30
|
|
|
|
"other",
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
export type PersonImportSource = (typeof PERSON_IMPORT_SOURCES)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export const PERSON_IMPORT_SOURCE_LABELS: Record<PersonImportSource, string> = {
|
|
|
|
|
|
manual: "دستی",
|
|
|
|
|
|
tahesab: "تهحساب",
|
|
|
|
|
|
didar: "دیدار",
|
|
|
|
|
|
excel: "اکسل",
|
|
|
|
|
|
phone: "مخاطبین گوشی",
|
|
|
|
|
|
lead_form: "فرم لید",
|
2026-10-10 15:59:12 +03:30
|
|
|
|
montify: "فروشگاه مونتیفای",
|
2026-10-04 12:01:21 +03:30
|
|
|
|
other: "سایر",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface PersonImportSourceStatDto {
|
|
|
|
|
|
source: PersonImportSource | string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
count: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface PersonDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
firstName: string;
|
|
|
|
|
|
lastName: string;
|
2026-10-04 12:01:21 +03:30
|
|
|
|
previousFirstName: string | null;
|
|
|
|
|
|
previousLastName: string | null;
|
|
|
|
|
|
previousFullName: string | null;
|
|
|
|
|
|
importSource: PersonImportSource | string;
|
|
|
|
|
|
sourceRef: string | null;
|
2026-10-05 20:54:23 +03:30
|
|
|
|
/** منبع/کد قبل از آخرین ورود از منبع دیگر (مثلاً کد دیدار پیش از تهحساب) */
|
|
|
|
|
|
previousImportSource: PersonImportSource | string | null;
|
|
|
|
|
|
previousSourceRef: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
fullName: string;
|
2026-10-29 18:46:10 +03:30
|
|
|
|
gender: PersonGender;
|
|
|
|
|
|
showGenderInName: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
email: string | null;
|
|
|
|
|
|
phone: string | null;
|
|
|
|
|
|
mobile: string | null;
|
|
|
|
|
|
position: string | null;
|
|
|
|
|
|
website: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
instagram: string | null;
|
2026-10-13 18:04:53 +03:30
|
|
|
|
birthDate: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
referrer: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
addresses: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
notes: string | null;
|
|
|
|
|
|
mood: PersonMood;
|
|
|
|
|
|
tags: string[];
|
|
|
|
|
|
customFields: Record<string, unknown>;
|
2026-10-21 17:16:31 +03:30
|
|
|
|
groups: CustomerGroupSummaryDto[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
loyaltyPoints: number;
|
|
|
|
|
|
loyaltyTier: LoyaltyTierSummaryDto | null;
|
2026-10-18 21:04:26 +03:30
|
|
|
|
/** تیپ غالب یا ترکیبی تأییدشده؛ بدون مجوز رفتار خالی برمیگردد */
|
|
|
|
|
|
discLabel: string | null;
|
|
|
|
|
|
discReliable: boolean;
|
|
|
|
|
|
discAssessedAt: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
owner: UserSummaryDto | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreatePersonRequest {
|
|
|
|
|
|
firstName?: string;
|
|
|
|
|
|
lastName?: string;
|
2026-10-29 18:46:10 +03:30
|
|
|
|
gender?: PersonGender;
|
|
|
|
|
|
showGenderInName?: boolean;
|
2026-10-04 12:01:21 +03:30
|
|
|
|
previousFirstName?: string | null;
|
|
|
|
|
|
previousLastName?: string | null;
|
|
|
|
|
|
importSource?: PersonImportSource | string;
|
|
|
|
|
|
sourceRef?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
email?: string;
|
|
|
|
|
|
phone?: string;
|
|
|
|
|
|
mobile?: string;
|
|
|
|
|
|
position?: string;
|
|
|
|
|
|
website?: string;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
instagram?: string;
|
2026-10-13 18:04:53 +03:30
|
|
|
|
birthDate?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
referrer?: string;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
addresses?: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
notes?: string;
|
|
|
|
|
|
mood?: PersonMood;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
customFields?: Record<string, unknown>;
|
|
|
|
|
|
ownerId?: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
/** فقط برای ایمپورت اکسل — تاریخ ثبت مبدأ */
|
|
|
|
|
|
registeredAt?: string | null;
|
2026-10-21 17:16:31 +03:30
|
|
|
|
/** همهٔ گروههای شخص (جایگزین کامل؛ منبع گروههای تهحسابیِ باقیمانده حفظ میشود) */
|
|
|
|
|
|
groupIds?: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdatePersonRequest = Partial<CreatePersonRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface PaginatedResponse<T> {
|
|
|
|
|
|
items: T[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
limit: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PersonSummaryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UserSummaryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PipelineStageDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
isWon: boolean;
|
|
|
|
|
|
isLost: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PipelineDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isDefault: boolean;
|
|
|
|
|
|
stages: PipelineStageDto[];
|
2026-10-13 22:23:09 +03:30
|
|
|
|
/** تعداد معاملات؛ در فهرست کاریزها پر میشود */
|
|
|
|
|
|
dealCount?: number;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
/** کد دستی معامله */
|
|
|
|
|
|
code: string | null;
|
|
|
|
|
|
/** منبع ورود: manual | didar | excel */
|
|
|
|
|
|
importSource: string;
|
2026-10-17 16:27:29 +03:30
|
|
|
|
promotedFromPriorAt: string | null;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
/** شناسه در مبدأ (کد دیدار) */
|
|
|
|
|
|
sourceRef: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
amount: number | null;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
status: DealStatus;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
/** احتمال موفقیت ۰ تا ۱۰۰ */
|
|
|
|
|
|
probability: number | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
source: string | null;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
sourceDetail: string | null;
|
|
|
|
|
|
referrer: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
lostReason: string | null;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
lostNote: string | null;
|
|
|
|
|
|
closedAt: string | null;
|
|
|
|
|
|
createdBy: UserSummaryDto | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
notes: string | null;
|
|
|
|
|
|
tags: string[];
|
|
|
|
|
|
isStale: boolean;
|
|
|
|
|
|
isRotten: boolean;
|
|
|
|
|
|
customFields: Record<string, unknown>;
|
|
|
|
|
|
expectedCloseAt: string | null;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
pipelineId: string;
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
person: PersonSummaryDto | null;
|
|
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
owner: UserSummaryDto | null;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** مسئولان مشترک معامله (علاوه بر مالک اصلی) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
assignees: UserSummaryDto[];
|
|
|
|
|
|
lineItems: DealLineItemDto[];
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PipelineBoardStageDto extends PipelineStageDto {
|
|
|
|
|
|
deals: DealDto[];
|
|
|
|
|
|
totalAmount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PipelineBoardDto {
|
|
|
|
|
|
pipeline: PipelineDto;
|
|
|
|
|
|
stages: PipelineBoardStageDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreatePipelineRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreatePipelineStageRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdatePipelineStageRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReorderPipelineStagesRequest {
|
|
|
|
|
|
stageIds: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-12 20:15:11 +03:30
|
|
|
|
export type PipelineMergeLeadFormMode = 'retarget_all';
|
|
|
|
|
|
|
|
|
|
|
|
export interface PipelineMergePreviewStageDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isWon: boolean;
|
|
|
|
|
|
isLost: boolean;
|
|
|
|
|
|
dealCount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PipelineMergePreviewTargetDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isDefault: boolean;
|
|
|
|
|
|
stages: Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isWon: boolean;
|
|
|
|
|
|
isLost: boolean;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PipelineMergePreviewDto {
|
|
|
|
|
|
source: { id: string; name: string; isDefault: boolean };
|
|
|
|
|
|
stages: PipelineMergePreviewStageDto[];
|
|
|
|
|
|
dealCount: number;
|
|
|
|
|
|
leadForms: Array<{ id: string; name: string; isActive: boolean }>;
|
|
|
|
|
|
targets: PipelineMergePreviewTargetDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MergePipelineRequest {
|
|
|
|
|
|
targetPipelineId: string;
|
|
|
|
|
|
stageMap: Record<string, string>;
|
|
|
|
|
|
leadFormMode?: PipelineMergeLeadFormMode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MergePipelineResultDto {
|
|
|
|
|
|
ok: true;
|
|
|
|
|
|
targetPipelineId: string;
|
|
|
|
|
|
movedDeals: number;
|
|
|
|
|
|
retargetedLeadForms: number;
|
|
|
|
|
|
deletedPipelineId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface CreateDealRequest {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
pipelineId?: string;
|
|
|
|
|
|
stageId?: string;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
code?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
amount?: number | null;
|
|
|
|
|
|
currency?: string;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
probability?: number | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
source?: string;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
sourceDetail?: string | null;
|
|
|
|
|
|
referrer?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
notes?: string;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
isStale?: boolean;
|
|
|
|
|
|
isRotten?: boolean;
|
|
|
|
|
|
expectedCloseAt?: string | null;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
lostReason?: string | null;
|
|
|
|
|
|
lostNote?: string | null;
|
|
|
|
|
|
closedAt?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
ownerId?: string | null;
|
|
|
|
|
|
assigneeIds?: string[];
|
|
|
|
|
|
customFields?: Record<string, unknown>;
|
|
|
|
|
|
lineItems?: DealLineItemInput[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateDealRequest {
|
|
|
|
|
|
title?: string;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
code?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
amount?: number | null;
|
|
|
|
|
|
currency?: string;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
probability?: number | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
source?: string;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
sourceDetail?: string | null;
|
|
|
|
|
|
referrer?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
notes?: string;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
isStale?: boolean;
|
|
|
|
|
|
isRotten?: boolean;
|
|
|
|
|
|
lostReason?: string | null;
|
2026-10-06 16:36:04 +03:30
|
|
|
|
lostNote?: string | null;
|
|
|
|
|
|
closedAt?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
expectedCloseAt?: string | null;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
ownerId?: string | null;
|
|
|
|
|
|
assigneeIds?: string[];
|
|
|
|
|
|
customFields?: Record<string, unknown>;
|
|
|
|
|
|
lineItems?: DealLineItemInput[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-06 16:36:04 +03:30
|
|
|
|
/** ستونهای اکسل معامله — همترتیب با خروجی معاملات دیدار */
|
|
|
|
|
|
export const DEAL_EXCEL_COLUMNS: Array<{ header: string; key: string; width?: number }> = [
|
|
|
|
|
|
{ header: "_type", key: "_type", width: 8 },
|
|
|
|
|
|
{ header: "کد دیدار معامله", key: "didarId", width: 14 },
|
|
|
|
|
|
{ header: "کد دستی معامله", key: "code", width: 14 },
|
|
|
|
|
|
{ header: "عنوان معامله", key: "title", width: 30 },
|
|
|
|
|
|
{ header: "ارزش معامله", key: "amount", width: 16 },
|
|
|
|
|
|
{ header: "ارز معامله", key: "currency", width: 10 },
|
|
|
|
|
|
{ header: "توضیحات معامله", key: "notes", width: 32 },
|
|
|
|
|
|
{ header: "وضعیت معامله", key: "status", width: 12 },
|
|
|
|
|
|
{ header: "تاریخ انجام معامله", key: "closedAt", width: 14 },
|
|
|
|
|
|
{ header: "تاریخ ایجاد معامله", key: "createdAt", width: 14 },
|
|
|
|
|
|
{ header: "تاریخ احتمالی انجام معامله", key: "expectedCloseAt", width: 16 },
|
|
|
|
|
|
{ header: "مسئول معامله", key: "ownerName", width: 18 },
|
|
|
|
|
|
{ header: "ثبت کننده معامله", key: "creatorName", width: 18 },
|
|
|
|
|
|
{ header: "احتمال موفقیت معامله", key: "probability", width: 12 },
|
|
|
|
|
|
{ header: "دلیل شکست معامله", key: "lostReason", width: 18 },
|
|
|
|
|
|
{ header: "یادداشت شکست معامله", key: "lostNote", width: 24 },
|
|
|
|
|
|
{ header: "مجوز مشاهده معامله", key: "visibility", width: 18 },
|
|
|
|
|
|
{ header: "شیوه آشنایی معامله", key: "source", width: 16 },
|
|
|
|
|
|
{ header: "معرف معامله", key: "referrer", width: 16 },
|
|
|
|
|
|
{ header: "شیوه آشنایی دلخواه", key: "sourceDetail", width: 16 },
|
|
|
|
|
|
{ header: "برچسب", key: "tags", width: 16 },
|
2026-10-06 20:51:50 +03:30
|
|
|
|
{ header: "مسیر فروش معامله", key: "pipelineName", width: 20 },
|
|
|
|
|
|
{ header: "مرحله مسیر فروش معامله", key: "stageName", width: 20 },
|
2026-10-06 16:36:04 +03:30
|
|
|
|
{ header: "نام شخص معامله", key: "personFirstName", width: 16 },
|
|
|
|
|
|
{ header: "نام خانوادگی شخص معامله", key: "personName", width: 22 },
|
|
|
|
|
|
{ header: "کد دیدار شخص معامله", key: "personCode", width: 14 },
|
|
|
|
|
|
{ header: "موبایل شخص معامله", key: "personMobile", width: 14 },
|
|
|
|
|
|
{ header: "شرکت معامله", key: "companyName", width: 20 },
|
|
|
|
|
|
{ header: "کد دیدار شرکت معامله", key: "companyCode", width: 14 },
|
|
|
|
|
|
{ header: "شماره تلفن شرکت", key: "companyPhone", width: 14 },
|
|
|
|
|
|
{ header: "عنوان محصول", key: "productName", width: 20 },
|
|
|
|
|
|
{ header: "کد محصول", key: "productCode", width: 12 },
|
|
|
|
|
|
{ header: "دسته بندی محصول", key: "productCategory", width: 14 },
|
|
|
|
|
|
{ header: "تعداد محصول", key: "productQty", width: 10 },
|
|
|
|
|
|
{ header: "قیمت", key: "productPrice", width: 14 },
|
|
|
|
|
|
{ header: "واحد قیمت محصول", key: "productCurrency", width: 12 },
|
|
|
|
|
|
{ header: "میزان تخفیف محصول", key: "productDiscount", width: 12 },
|
|
|
|
|
|
{ header: "مبلغ نهایی", key: "productTotal", width: 14 },
|
|
|
|
|
|
{ header: "گروه محصول", key: "productGroup", width: 14 },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/** کلید ستونهای ناشناخته اکسل که بهعنوان فیلد سفارشی ذخیره میشوند: cf:<عنوان ستون> */
|
|
|
|
|
|
export const EXCEL_CUSTOM_FIELD_PREFIX = "cf:";
|
|
|
|
|
|
|
|
|
|
|
|
export function compactExcelHeader(raw: string): string {
|
|
|
|
|
|
return raw
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
|
.replace(/[_-]+/g, "")
|
|
|
|
|
|
.replace(/\u200c/g, "")
|
|
|
|
|
|
.replace(/\s+/g, "")
|
|
|
|
|
|
.replace(/[()]/g, "");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const DEAL_EXCEL_ALIASES: Record<string, string> = {
|
|
|
|
|
|
...Object.fromEntries(DEAL_EXCEL_COLUMNS.map((c) => [compactExcelHeader(c.header), c.key])),
|
|
|
|
|
|
type: "_type",
|
|
|
|
|
|
عنوان: "title",
|
|
|
|
|
|
مبلغمعامله: "amount",
|
|
|
|
|
|
مبلغ: "amount",
|
|
|
|
|
|
ارز: "currency",
|
|
|
|
|
|
وضعیت: "status",
|
|
|
|
|
|
توضیحات: "notes",
|
|
|
|
|
|
تاریخثبتمعامله: "createdAt",
|
|
|
|
|
|
کددیدارشخص: "personCode",
|
|
|
|
|
|
نامشرکتمعامله: "companyName",
|
|
|
|
|
|
شرکت: "companyName",
|
|
|
|
|
|
منبعمعامله: "source",
|
|
|
|
|
|
منبع: "source",
|
2026-10-06 20:51:50 +03:30
|
|
|
|
مسیرفروش: "pipelineName",
|
|
|
|
|
|
مسیر: "pipelineName",
|
|
|
|
|
|
// سرستونهای قدیمی (قبل از تغییر نام کاریز → مسیر فروش)
|
2026-10-06 16:36:04 +03:30
|
|
|
|
کاریز: "pipelineName",
|
2026-10-06 20:51:50 +03:30
|
|
|
|
کاریزمعامله: "pipelineName",
|
|
|
|
|
|
مرحلهکاریزمعامله: "stageName",
|
2026-10-06 16:36:04 +03:30
|
|
|
|
مرحله: "stageName",
|
|
|
|
|
|
};
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** فیلتر ذخیرهشدهٔ لیست اشخاص */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface PeopleViewFilters {
|
|
|
|
|
|
q?: string;
|
|
|
|
|
|
sort?: string;
|
|
|
|
|
|
mood?: string;
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-06 20:51:50 +03:30
|
|
|
|
/** فیلتر ذخیرهشدهٔ مسیر فروش / معاملات */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface DealsViewFilters {
|
|
|
|
|
|
q?: string;
|
|
|
|
|
|
amountMin?: string;
|
|
|
|
|
|
amountMax?: string;
|
|
|
|
|
|
pipelineId?: string;
|
|
|
|
|
|
viewMode?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
export interface MoveDealRequest {
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type ActivityType = "call" | "meeting" | "task" | "note";
|
|
|
|
|
|
export type ActivityStatus = "open" | "done" | "canceled";
|
|
|
|
|
|
export type ActivityPriority = "very_low" | "low" | "medium" | "high" | "very_high";
|
|
|
|
|
|
export type ActivityFilter = "today" | "overdue" | "upcoming" | "done" | "all";
|
|
|
|
|
|
|
2026-10-07 11:18:53 +03:30
|
|
|
|
export type ActivityStageColor = "green" | "orange" | "purple" | "blue" | "rose" | "slate";
|
|
|
|
|
|
|
|
|
|
|
|
export const ACTIVITY_STAGE_COLORS: ActivityStageColor[] = [
|
|
|
|
|
|
"green",
|
|
|
|
|
|
"orange",
|
|
|
|
|
|
"purple",
|
|
|
|
|
|
"blue",
|
|
|
|
|
|
"rose",
|
|
|
|
|
|
"slate",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const ACTIVITY_STAGE_COLOR_LABELS: Record<ActivityStageColor, string> = {
|
|
|
|
|
|
green: "سبز",
|
|
|
|
|
|
orange: "نارنجی",
|
|
|
|
|
|
purple: "بنفش",
|
|
|
|
|
|
blue: "آبی",
|
|
|
|
|
|
rose: "قرمز",
|
|
|
|
|
|
slate: "خاکستری",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** نوع مرحله = وضعیتی که فعالیت با رفتن به آن ستون میگیرد */
|
|
|
|
|
|
export const ACTIVITY_STAGE_KIND_LABELS: Record<ActivityStatus, string> = {
|
|
|
|
|
|
open: "باز (در جریان)",
|
|
|
|
|
|
done: "انجامشده",
|
|
|
|
|
|
canceled: "لغو شده",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-20 17:23:27 +03:30
|
|
|
|
/** گروه فعالیت (مثل مسیر فروش): هر گروه ستونهای خودش را دارد */
|
|
|
|
|
|
export interface ActivityBoardDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isDefault: boolean;
|
|
|
|
|
|
position: number;
|
2026-10-28 20:24:40 +03:30
|
|
|
|
/** خالی = گروه برای همهٔ اعضای محیط باز است */
|
|
|
|
|
|
memberIds: string[];
|
2026-10-20 17:23:27 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateActivityBoardRequest {
|
|
|
|
|
|
name: string;
|
2026-10-28 20:24:40 +03:30
|
|
|
|
memberIds?: string[];
|
2026-10-20 17:23:27 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateActivityBoardRequest {
|
|
|
|
|
|
name?: string;
|
2026-10-28 20:24:40 +03:30
|
|
|
|
memberIds?: string[];
|
2026-10-20 17:23:27 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-07 11:18:53 +03:30
|
|
|
|
export interface ActivityStageDto {
|
|
|
|
|
|
id: string;
|
2026-10-20 17:23:27 +03:30
|
|
|
|
boardId: string;
|
2026-10-07 11:18:53 +03:30
|
|
|
|
name: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
color: ActivityStageColor;
|
|
|
|
|
|
kind: ActivityStatus;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateActivityStageRequest {
|
2026-10-20 17:23:27 +03:30
|
|
|
|
/** خالی = گروه پیشفرض */
|
|
|
|
|
|
boardId?: string;
|
2026-10-07 11:18:53 +03:30
|
|
|
|
name: string;
|
|
|
|
|
|
color?: ActivityStageColor;
|
|
|
|
|
|
kind?: ActivityStatus;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateActivityStageRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
color?: ActivityStageColor;
|
|
|
|
|
|
kind?: ActivityStatus;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReorderActivityStagesRequest {
|
|
|
|
|
|
stageIds: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MoveActivityStageRequest {
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** شمارش فعالیتها به تفکیک مرحله با فیلترهای فعلی */
|
|
|
|
|
|
export interface ActivityStageCountsDto {
|
|
|
|
|
|
counts: Record<string, number>;
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-07 12:29:23 +03:30
|
|
|
|
/** برچسب نوع پایه (برای گزارشها و فعالیتهای بدون نوع سفارشی) */
|
|
|
|
|
|
export const ACTIVITY_TYPE_LABELS: Record<ActivityType, string> = {
|
|
|
|
|
|
call: "تماس",
|
|
|
|
|
|
meeting: "جلسه",
|
|
|
|
|
|
task: "وظیفه",
|
|
|
|
|
|
note: "پیام",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type ActivityFieldType =
|
|
|
|
|
|
| "text"
|
|
|
|
|
|
| "textarea"
|
|
|
|
|
|
| "number"
|
|
|
|
|
|
| "date"
|
|
|
|
|
|
| "url"
|
|
|
|
|
|
| "select"
|
|
|
|
|
|
| "multiselect"
|
|
|
|
|
|
| "checkbox"
|
|
|
|
|
|
| "checklist"
|
|
|
|
|
|
| "participants";
|
|
|
|
|
|
|
|
|
|
|
|
export const ACTIVITY_FIELD_TYPES: ActivityFieldType[] = [
|
|
|
|
|
|
"text",
|
|
|
|
|
|
"textarea",
|
|
|
|
|
|
"number",
|
|
|
|
|
|
"date",
|
|
|
|
|
|
"url",
|
|
|
|
|
|
"select",
|
|
|
|
|
|
"multiselect",
|
|
|
|
|
|
"checkbox",
|
|
|
|
|
|
"checklist",
|
|
|
|
|
|
"participants",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const ACTIVITY_FIELD_TYPE_LABELS: Record<ActivityFieldType, string> = {
|
|
|
|
|
|
text: "متن کوتاه",
|
|
|
|
|
|
textarea: "متن بلند",
|
|
|
|
|
|
number: "عدد",
|
|
|
|
|
|
date: "تاریخ",
|
|
|
|
|
|
url: "لینک",
|
|
|
|
|
|
select: "انتخاب از لیست",
|
|
|
|
|
|
multiselect: "چندانتخابی",
|
|
|
|
|
|
checkbox: "بله / خیر",
|
|
|
|
|
|
checklist: "چکلیست (لیست کارها)",
|
|
|
|
|
|
participants: "شرکتکنندگان",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** فیلدهایی که گزینه لازم دارند */
|
|
|
|
|
|
export const ACTIVITY_FIELD_TYPES_WITH_OPTIONS: ActivityFieldType[] = ["select", "multiselect"];
|
|
|
|
|
|
|
|
|
|
|
|
export interface ActivityFieldDef {
|
|
|
|
|
|
/** کلید پایدار (a-z، 0-9، _) — با تغییر برچسب عوض نمیشود */
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
type: ActivityFieldType;
|
|
|
|
|
|
options?: string[];
|
|
|
|
|
|
required?: boolean;
|
|
|
|
|
|
placeholder?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ActivityChecklistItem {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
done: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ActivityParticipantsValue {
|
|
|
|
|
|
/** کاربران سازمان — فعالیت در لیست آنها هم دیده میشود */
|
|
|
|
|
|
userIds: string[];
|
|
|
|
|
|
/** اشخاص (مشتریان) */
|
|
|
|
|
|
people: Array<{ id: string; name: string }>;
|
|
|
|
|
|
/** مهمانان آزاد (نام) */
|
|
|
|
|
|
guests: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type ActivityFieldValue =
|
|
|
|
|
|
| string
|
|
|
|
|
|
| number
|
|
|
|
|
|
| boolean
|
|
|
|
|
|
| string[]
|
|
|
|
|
|
| ActivityChecklistItem[]
|
|
|
|
|
|
| ActivityParticipantsValue
|
|
|
|
|
|
| null;
|
|
|
|
|
|
|
|
|
|
|
|
export type ActivityFieldValues = Record<string, ActivityFieldValue>;
|
|
|
|
|
|
|
|
|
|
|
|
export const ACTIVITY_TYPE_ICONS = [
|
|
|
|
|
|
"phone",
|
|
|
|
|
|
"users",
|
|
|
|
|
|
"check",
|
|
|
|
|
|
"chat",
|
|
|
|
|
|
"mail",
|
|
|
|
|
|
"building",
|
|
|
|
|
|
"calendar",
|
|
|
|
|
|
"star",
|
|
|
|
|
|
"flag",
|
|
|
|
|
|
"briefcase",
|
|
|
|
|
|
"document",
|
|
|
|
|
|
"target",
|
|
|
|
|
|
"truck",
|
|
|
|
|
|
"send",
|
|
|
|
|
|
"megaphone",
|
|
|
|
|
|
"pin",
|
|
|
|
|
|
"bell",
|
|
|
|
|
|
"sparkle",
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type ActivityTypeIcon = (typeof ACTIVITY_TYPE_ICONS)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export interface ActivityTypeSummaryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
icon: ActivityTypeIcon;
|
|
|
|
|
|
color: ActivityStageColor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ActivityTypeDefDto extends ActivityTypeSummaryDto {
|
|
|
|
|
|
/** نوع پایه — مبنای گزارشها و سازگاری */
|
|
|
|
|
|
baseType: ActivityType;
|
|
|
|
|
|
fields: ActivityFieldDef[];
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
/** چهار نوع اصلی — حذفشدنی نیستند */
|
|
|
|
|
|
isSystem: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateActivityTypeRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
baseType?: ActivityType;
|
|
|
|
|
|
icon?: ActivityTypeIcon;
|
|
|
|
|
|
color?: ActivityStageColor;
|
|
|
|
|
|
fields?: ActivityFieldDef[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateActivityTypeRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
baseType?: ActivityType;
|
|
|
|
|
|
icon?: ActivityTypeIcon;
|
|
|
|
|
|
color?: ActivityStageColor;
|
|
|
|
|
|
fields?: ActivityFieldDef[];
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReorderActivityTypesRequest {
|
|
|
|
|
|
typeIds: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-01 15:38:05 +03:30
|
|
|
|
export type CustomFieldEntity = "person" | "company" | "deal" | "note";
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type CustomFieldType = "text" | "number" | "date" | "boolean" | "select";
|
|
|
|
|
|
|
|
|
|
|
|
export interface CustomFieldDefinitionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
entityType: CustomFieldEntity;
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
fieldType: CustomFieldType;
|
|
|
|
|
|
options: string[];
|
|
|
|
|
|
required: boolean;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateCustomFieldRequest {
|
|
|
|
|
|
entityType: CustomFieldEntity;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
key?: string;
|
|
|
|
|
|
fieldType?: CustomFieldType;
|
|
|
|
|
|
options?: string[];
|
|
|
|
|
|
required?: boolean;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateCustomFieldRequest {
|
|
|
|
|
|
label?: string;
|
|
|
|
|
|
fieldType?: CustomFieldType;
|
|
|
|
|
|
options?: string[];
|
|
|
|
|
|
required?: boolean;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealSummaryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-27 21:30:45 +03:30
|
|
|
|
export interface ActivityImageDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fileName: string;
|
|
|
|
|
|
mimeType: string;
|
|
|
|
|
|
sizeBytes: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface ActivityDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
type: ActivityType;
|
|
|
|
|
|
status: ActivityStatus;
|
|
|
|
|
|
priority: ActivityPriority;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
description: string | null;
|
2026-10-27 21:30:45 +03:30
|
|
|
|
/** عکسهای یادداشت؛ فقط در جزئیات فعالیت پر میشود */
|
|
|
|
|
|
images?: ActivityImageDto[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
dueAt: string | null;
|
|
|
|
|
|
durationMinutes: number | null;
|
|
|
|
|
|
reminderAt: string | null;
|
|
|
|
|
|
reminderNotifyApp: boolean;
|
|
|
|
|
|
reminderNotifySms: boolean;
|
2026-10-06 13:49:03 +03:30
|
|
|
|
/** زمان ارسال یادآور (اگر ارسال شده باشد) */
|
|
|
|
|
|
reminderSentAt: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
completedAt: string | null;
|
2026-10-15 10:48:30 +03:30
|
|
|
|
/** manual یا didar؛ برای جدا کردن سابقهٔ قبلی */
|
|
|
|
|
|
importSource?: string;
|
2026-10-17 16:27:29 +03:30
|
|
|
|
promotedFromPriorAt?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
tags: string[];
|
|
|
|
|
|
person: PersonSummaryDto | null;
|
|
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
deal: DealSummaryDto | null;
|
2026-10-11 14:12:36 +03:30
|
|
|
|
/** لید مرتبط (مثلاً فعالیت ساختهشده از فرم) */
|
|
|
|
|
|
lead?: { id: string; title: string } | null;
|
2026-10-03 13:16:13 +03:30
|
|
|
|
/** مسئول اصلی (اولین مسئول) — سازگاری با فیلترها و یادآور */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
assignee: UserSummaryDto | null;
|
2026-10-03 13:16:13 +03:30
|
|
|
|
/** همه مسئولان انجام فعالیت */
|
|
|
|
|
|
assignees: UserSummaryDto[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
createdBy: UserSummaryDto | null;
|
2026-10-20 17:23:27 +03:30
|
|
|
|
/** گروه مؤثر فعالیت (گروه پیشفرض اگر گروهی انتخاب نشده باشد) */
|
|
|
|
|
|
boardId?: string | null;
|
2026-10-07 11:18:53 +03:30
|
|
|
|
/** مرحلهٔ مؤثر (ستون کانبان) */
|
|
|
|
|
|
stageId?: string | null;
|
2026-10-07 12:29:23 +03:30
|
|
|
|
/** نوع مؤثر (سفارشی یا نوع اصلیِ همان type) */
|
|
|
|
|
|
typeDef?: ActivityTypeSummaryDto | null;
|
|
|
|
|
|
/** مقادیر فیلدهای اختصاصی نوع */
|
|
|
|
|
|
fields?: ActivityFieldValues;
|
|
|
|
|
|
/** کاربران شرکتکننده (از فیلدهای شرکتکنندگان) */
|
|
|
|
|
|
participantIds?: string[];
|
|
|
|
|
|
/** نام کاربران شرکتکننده — فقط در جزئیات فعالیت */
|
|
|
|
|
|
participantUsers?: UserSummaryDto[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateActivityRequest {
|
|
|
|
|
|
title: string;
|
2026-10-20 17:23:27 +03:30
|
|
|
|
/** گروه فعالیت؛ خالی = گروه پیشفرض. اگر stageId بیاید، گروه از همان مرحله گرفته میشود */
|
|
|
|
|
|
boardId?: string | null;
|
2026-10-07 11:18:53 +03:30
|
|
|
|
/** مرحلهٔ اولیه؛ وضعیت فعالیت از نوع مرحله گرفته میشود */
|
|
|
|
|
|
stageId?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
type?: ActivityType;
|
2026-10-07 12:29:23 +03:30
|
|
|
|
/** نوع سفارشی؛ type از نوع پایهٔ آن گرفته میشود */
|
|
|
|
|
|
typeDefId?: string | null;
|
|
|
|
|
|
fields?: ActivityFieldValues;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
priority?: ActivityPriority;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
dueAt?: string | null;
|
|
|
|
|
|
durationMinutes?: number | null;
|
|
|
|
|
|
reminderAt?: string | null;
|
|
|
|
|
|
reminderNotifyApp?: boolean;
|
|
|
|
|
|
reminderNotifySms?: boolean;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
2026-11-03 16:19:32 +03:30
|
|
|
|
/** لید مرتبط؛ فعالیت در پروندهٔ همان لید میماند */
|
|
|
|
|
|
leadId?: string | null;
|
2026-10-03 13:16:13 +03:30
|
|
|
|
/** سازگاری — اگر assigneeIds نباشد از این استفاده میشود */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
assigneeId?: string | null;
|
2026-10-03 13:16:13 +03:30
|
|
|
|
/** چند مسئول؛ اولی بهعنوان assigneeId ذخیره میشود */
|
|
|
|
|
|
assigneeIds?: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateActivityRequest {
|
|
|
|
|
|
title?: string;
|
2026-10-20 17:23:27 +03:30
|
|
|
|
/** انتقال به گروه دیگر؛ فعالیت به ستون اولِ هموضعیت آن گروه میرود */
|
|
|
|
|
|
boardId?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
type?: ActivityType;
|
2026-10-07 12:29:23 +03:30
|
|
|
|
typeDefId?: string | null;
|
|
|
|
|
|
fields?: ActivityFieldValues;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
status?: ActivityStatus;
|
|
|
|
|
|
priority?: ActivityPriority;
|
|
|
|
|
|
description?: string | null;
|
|
|
|
|
|
dueAt?: string | null;
|
|
|
|
|
|
durationMinutes?: number | null;
|
|
|
|
|
|
reminderAt?: string | null;
|
|
|
|
|
|
reminderNotifyApp?: boolean;
|
|
|
|
|
|
reminderNotifySms?: boolean;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
2026-11-03 16:19:32 +03:30
|
|
|
|
leadId?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
assigneeId?: string | null;
|
2026-10-03 13:16:13 +03:30
|
|
|
|
assigneeIds?: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ActivitySummaryDto {
|
|
|
|
|
|
today: ActivityDto[];
|
|
|
|
|
|
overdue: ActivityDto[];
|
|
|
|
|
|
upcoming: ActivityDto[];
|
|
|
|
|
|
todayCount: number;
|
|
|
|
|
|
overdueCount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type NoteType =
|
|
|
|
|
|
| "general"
|
|
|
|
|
|
| "call_result"
|
|
|
|
|
|
| "meeting"
|
|
|
|
|
|
| "important"
|
|
|
|
|
|
| "warning"
|
|
|
|
|
|
| "next_action"
|
|
|
|
|
|
| "follow_up"
|
|
|
|
|
|
| "idea"
|
|
|
|
|
|
| "suggestion"
|
|
|
|
|
|
| "personal";
|
|
|
|
|
|
|
|
|
|
|
|
export interface NoteAttachmentDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fileName: string;
|
|
|
|
|
|
mimeType: string;
|
|
|
|
|
|
sizeBytes: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface NoteChecklistItemDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
done: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface NoteDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string | null;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
noteType: NoteType | string;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** team = تیمی | personal = فقط نویسنده */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
visibility: NoteVisibility;
|
|
|
|
|
|
pinned: boolean;
|
|
|
|
|
|
color: string | null;
|
|
|
|
|
|
tags: string[];
|
|
|
|
|
|
reminderAt: string | null;
|
|
|
|
|
|
checklist: NoteChecklistItemDto[];
|
|
|
|
|
|
locationText: string | null;
|
|
|
|
|
|
analyzeEnabled: boolean;
|
2026-11-01 15:38:05 +03:30
|
|
|
|
customFields: Record<string, unknown>;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
person: PersonSummaryDto | null;
|
|
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
deal: DealSummaryDto | null;
|
|
|
|
|
|
activity: { id: string; title: string } | null;
|
|
|
|
|
|
author: UserSummaryDto | null;
|
|
|
|
|
|
attachments: NoteAttachmentDto[];
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** تعداد نسخههای قبلی (برای نمایش دکمه تاریخچه) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
revisionCount?: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type NoteVisibility = "team" | "personal";
|
|
|
|
|
|
|
|
|
|
|
|
export const NOTE_VISIBILITIES: NoteVisibility[] = ["team", "personal"];
|
|
|
|
|
|
|
|
|
|
|
|
export const NOTE_VISIBILITY_LABELS: Record<NoteVisibility, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
team: "تیمی",
|
|
|
|
|
|
personal: "شخصی (فقط من)",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-11-01 16:34:34 +03:30
|
|
|
|
export type NoteCategoryTone =
|
|
|
|
|
|
| "general"
|
|
|
|
|
|
| "follow_up"
|
|
|
|
|
|
| "idea"
|
|
|
|
|
|
| "suggestion"
|
|
|
|
|
|
| "meeting"
|
|
|
|
|
|
| "personal";
|
|
|
|
|
|
|
|
|
|
|
|
export interface NoteCategoryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
tone: NoteCategoryTone | string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
showInBar: boolean;
|
|
|
|
|
|
isSystem: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateNoteCategoryRequest {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
tone?: NoteCategoryTone | string;
|
|
|
|
|
|
showInBar?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateNoteCategoryRequest {
|
|
|
|
|
|
label?: string;
|
|
|
|
|
|
tone?: NoteCategoryTone | string;
|
|
|
|
|
|
showInBar?: boolean;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReorderNoteCategoriesRequest {
|
|
|
|
|
|
ids: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface NoteRevisionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string | null;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
noteType: string;
|
|
|
|
|
|
visibility: NoteVisibility;
|
|
|
|
|
|
tags: string[];
|
|
|
|
|
|
checklist: NoteChecklistItemDto[];
|
|
|
|
|
|
editor: UserSummaryDto | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateNoteRequest {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
noteType?: NoteType | string;
|
|
|
|
|
|
visibility?: NoteVisibility;
|
|
|
|
|
|
pinned?: boolean;
|
|
|
|
|
|
color?: string;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
reminderAt?: string | null;
|
|
|
|
|
|
checklist?: NoteChecklistItemDto[];
|
|
|
|
|
|
locationText?: string | null;
|
|
|
|
|
|
analyzeEnabled?: boolean;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
activityId?: string | null;
|
2026-11-01 15:38:05 +03:30
|
|
|
|
customFields?: Record<string, unknown>;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateNoteRequest {
|
|
|
|
|
|
title?: string | null;
|
|
|
|
|
|
body?: string;
|
|
|
|
|
|
noteType?: NoteType | string;
|
|
|
|
|
|
visibility?: NoteVisibility;
|
|
|
|
|
|
pinned?: boolean;
|
|
|
|
|
|
color?: string | null;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
reminderAt?: string | null;
|
|
|
|
|
|
checklist?: NoteChecklistItemDto[];
|
|
|
|
|
|
locationText?: string | null;
|
|
|
|
|
|
analyzeEnabled?: boolean;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
activityId?: string | null;
|
2026-11-01 15:38:05 +03:30
|
|
|
|
customFields?: Record<string, unknown>;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type NoteAiSuggestionStatus =
|
|
|
|
|
|
| 'pending'
|
|
|
|
|
|
| 'applied'
|
|
|
|
|
|
| 'dismissed'
|
|
|
|
|
|
| 'failed';
|
|
|
|
|
|
|
|
|
|
|
|
export type MemorySourceType = 'note' | 'manual';
|
|
|
|
|
|
|
|
|
|
|
|
export interface NoteAiNextActionDto {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
dueAt?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface NoteAiMemoryExtractDto {
|
|
|
|
|
|
content: string;
|
|
|
|
|
|
category?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface NoteAiPayloadDto {
|
|
|
|
|
|
nextAction: NoteAiNextActionDto | null;
|
|
|
|
|
|
memoryItems: NoteAiMemoryExtractDto[];
|
|
|
|
|
|
summary?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface NoteAiSuggestionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
noteId: string;
|
|
|
|
|
|
status: NoteAiSuggestionStatus;
|
|
|
|
|
|
payload: NoteAiPayloadDto | null;
|
|
|
|
|
|
errorMessage: string | null;
|
|
|
|
|
|
analyzedAt: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ApplyNoteAiSuggestionRequest {
|
|
|
|
|
|
applyNextAction?: boolean;
|
|
|
|
|
|
memoryIndices?: number[];
|
|
|
|
|
|
nextAction?: NoteAiNextActionDto;
|
|
|
|
|
|
memoryItems?: Array<{ index: number; content: string }>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type AskMentoSourceType = 'person' | 'note' | 'activity' | 'deal' | 'memory';
|
|
|
|
|
|
|
|
|
|
|
|
export interface AskMentoSourceDto {
|
|
|
|
|
|
type: AskMentoSourceType;
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AskMentoRequest {
|
|
|
|
|
|
question: string;
|
|
|
|
|
|
personId?: string;
|
2026-09-30 16:30:41 +03:30
|
|
|
|
/** زمینهٔ اختیاری گفتگو برای پاسخ مرتبطتر داخل چت */
|
|
|
|
|
|
chatContext?: string;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AskMentoResponse {
|
|
|
|
|
|
answer: string;
|
|
|
|
|
|
sources: AskMentoSourceDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type VoiceCommandIntent = 'create_person' | 'create_activity' | 'none';
|
|
|
|
|
|
|
|
|
|
|
|
export interface VoicePersonDraft {
|
|
|
|
|
|
firstName: string;
|
|
|
|
|
|
lastName: string;
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
phone: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
website: string;
|
|
|
|
|
|
referrer: string;
|
|
|
|
|
|
position: string;
|
|
|
|
|
|
notes: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface VoiceActivityDraft {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
type: ActivityType;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
dueAt: string | null;
|
|
|
|
|
|
personName: string;
|
|
|
|
|
|
personId: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface VoiceCommandDraft {
|
|
|
|
|
|
intent: VoiceCommandIntent;
|
|
|
|
|
|
summary: string;
|
|
|
|
|
|
missing: string[];
|
|
|
|
|
|
canConfirm: boolean;
|
|
|
|
|
|
person: VoicePersonDraft | null;
|
|
|
|
|
|
activity: VoiceActivityDraft | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PersonAiSummaryDto {
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
cta: string;
|
|
|
|
|
|
ctaKind: 'call' | 'activity';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MemoryItemDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
content: string;
|
|
|
|
|
|
sourceType: MemorySourceType;
|
|
|
|
|
|
sourceId: string | null;
|
|
|
|
|
|
confirmedBy: UserSummaryDto | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateMemoryItemRequest {
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
content: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateMemoryItemRequest {
|
|
|
|
|
|
content: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ImportContactsResult {
|
|
|
|
|
|
created: number;
|
|
|
|
|
|
skipped: number;
|
|
|
|
|
|
errors: Array<{ row: number; message: string }>;
|
|
|
|
|
|
boardId?: string;
|
|
|
|
|
|
boards?: string[];
|
2026-10-03 15:01:01 +03:30
|
|
|
|
/** جلسه چندمرحلهای برای فایلهای بزرگ */
|
|
|
|
|
|
sessionId?: string;
|
|
|
|
|
|
/** تعداد کل ردیفهای دادهای فایل */
|
|
|
|
|
|
total?: number;
|
|
|
|
|
|
/** تا این آفست پردازش شده */
|
|
|
|
|
|
processed?: number;
|
|
|
|
|
|
/** مرحله بعدی وجود دارد */
|
|
|
|
|
|
hasMore?: boolean;
|
|
|
|
|
|
nextOffset?: number;
|
2026-10-03 18:14:17 +03:30
|
|
|
|
/** وضعیت آمادهسازی ناهمگام فایل بزرگ */
|
|
|
|
|
|
status?: "parsing" | "ready" | "error";
|
|
|
|
|
|
parsed?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ExcelImportPrepareStatus {
|
|
|
|
|
|
sessionId: string;
|
|
|
|
|
|
status: "parsing" | "ready" | "error";
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
parsed: number;
|
|
|
|
|
|
error?: string;
|
|
|
|
|
|
hasMore?: boolean;
|
|
|
|
|
|
nextOffset?: number;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ExcelFieldOption {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
required?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ExcelColumnPreview {
|
|
|
|
|
|
header: string;
|
|
|
|
|
|
index: number;
|
|
|
|
|
|
samples: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ExcelPreviewResult {
|
|
|
|
|
|
columns: ExcelColumnPreview[];
|
|
|
|
|
|
fields: ExcelFieldOption[];
|
|
|
|
|
|
suggested: Record<string, string>;
|
|
|
|
|
|
needsMapping: boolean;
|
2026-10-03 15:01:01 +03:30
|
|
|
|
/** تعداد ردیفهای غیرخالی (بدون هدر) */
|
|
|
|
|
|
rowCount: number;
|
|
|
|
|
|
/** پیشنهاد ایمپورت چندمرحلهای */
|
|
|
|
|
|
largeFile?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ProductDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
kind: "product" | "service";
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
sku: string | null;
|
|
|
|
|
|
description: string | null;
|
|
|
|
|
|
category: string | null;
|
|
|
|
|
|
unit: string | null;
|
|
|
|
|
|
stockQty: number | null;
|
|
|
|
|
|
brand: string | null;
|
|
|
|
|
|
model: string | null;
|
|
|
|
|
|
country: string | null;
|
|
|
|
|
|
tags: string[];
|
|
|
|
|
|
unitPrice: number | null;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateProductRequest {
|
|
|
|
|
|
kind?: "product" | "service";
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
sku?: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
category?: string;
|
|
|
|
|
|
unit?: string;
|
|
|
|
|
|
stockQty?: number | null;
|
|
|
|
|
|
brand?: string;
|
|
|
|
|
|
model?: string;
|
|
|
|
|
|
country?: string;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
unitPrice?: number | null;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateProductRequest {
|
|
|
|
|
|
kind?: "product" | "service";
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
sku?: string | null;
|
|
|
|
|
|
description?: string | null;
|
|
|
|
|
|
category?: string | null;
|
|
|
|
|
|
unit?: string | null;
|
|
|
|
|
|
stockQty?: number | null;
|
|
|
|
|
|
brand?: string | null;
|
|
|
|
|
|
model?: string | null;
|
|
|
|
|
|
country?: string | null;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
unitPrice?: number | null;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type StockMovementType =
|
|
|
|
|
|
| "opening"
|
|
|
|
|
|
| "purchase"
|
|
|
|
|
|
| "sale"
|
|
|
|
|
|
| "credit"
|
|
|
|
|
|
| "adjustment";
|
|
|
|
|
|
|
|
|
|
|
|
export interface InventoryStockRowDto {
|
|
|
|
|
|
productId: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
sku: string | null;
|
|
|
|
|
|
unit: string | null;
|
|
|
|
|
|
kind: "product" | "service";
|
|
|
|
|
|
stockQty: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
unitPrice: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface StockMovementDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
productId: string;
|
|
|
|
|
|
productName: string;
|
|
|
|
|
|
type: StockMovementType;
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
note: string | null;
|
|
|
|
|
|
invoiceId: string | null;
|
|
|
|
|
|
occurredAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface InventorySummaryDto {
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
warehouseName: string | null;
|
|
|
|
|
|
products: InventoryStockRowDto[];
|
|
|
|
|
|
cashTotal: number;
|
|
|
|
|
|
receivablesTotal: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateStockMovementRequest {
|
|
|
|
|
|
productId: string;
|
|
|
|
|
|
type: "purchase" | "adjustment";
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
note?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PartyBalanceDto {
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
openingAmount: number;
|
|
|
|
|
|
invoicedAmount: number;
|
|
|
|
|
|
paidAmount: number;
|
|
|
|
|
|
balance: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdatePartyOpeningRequest {
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
note?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealLineItemDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
productId: string | null;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
unitPrice: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
lineTotal: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealLineItemInput {
|
|
|
|
|
|
productId?: string | null;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
unitPrice: number;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type NotificationType =
|
|
|
|
|
|
| "activity_assigned"
|
|
|
|
|
|
| "activity_overdue"
|
|
|
|
|
|
| "activity_reminder"
|
|
|
|
|
|
| "deal_moved"
|
|
|
|
|
|
| "note_reminder"
|
|
|
|
|
|
| "cheque_due"
|
|
|
|
|
|
| "ticket_assigned"
|
|
|
|
|
|
| "automation"
|
2026-10-06 19:54:38 +03:30
|
|
|
|
| "routine_task_reminder"
|
|
|
|
|
|
| "routine_task_overdue"
|
|
|
|
|
|
| "routine_day_summary"
|
|
|
|
|
|
| "routine_task_assigned"
|
2026-09-27 21:17:42 +03:30
|
|
|
|
| "system";
|
|
|
|
|
|
|
|
|
|
|
|
export interface NotificationDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
type: NotificationType | string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
body: string | null;
|
|
|
|
|
|
link: string | null;
|
|
|
|
|
|
entityType: string | null;
|
|
|
|
|
|
entityId: string | null;
|
|
|
|
|
|
readAt: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface NotificationsListDto {
|
|
|
|
|
|
items: NotificationDto[];
|
|
|
|
|
|
unreadCount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const LEAD_FORM_FIELD_TYPES = [
|
|
|
|
|
|
"text",
|
|
|
|
|
|
"textarea",
|
|
|
|
|
|
"email",
|
|
|
|
|
|
"phone",
|
|
|
|
|
|
"select",
|
|
|
|
|
|
"radio",
|
|
|
|
|
|
"checkbox",
|
|
|
|
|
|
"date",
|
|
|
|
|
|
"number",
|
|
|
|
|
|
"file",
|
|
|
|
|
|
"rating",
|
|
|
|
|
|
"heading",
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
export type LeadFormFieldType = (typeof LEAD_FORM_FIELD_TYPES)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export type LeadFormMapTo =
|
|
|
|
|
|
| "person.firstName"
|
|
|
|
|
|
| "person.lastName"
|
|
|
|
|
|
| "person.fullName"
|
|
|
|
|
|
| "person.email"
|
|
|
|
|
|
| "person.mobile"
|
|
|
|
|
|
| "person.phone"
|
|
|
|
|
|
| "person.position"
|
|
|
|
|
|
| "deal.title"
|
|
|
|
|
|
| "deal.notes"
|
2026-09-28 13:39:44 +03:30
|
|
|
|
| "lead.title"
|
|
|
|
|
|
| "lead.contactName"
|
|
|
|
|
|
| "lead.mobile"
|
|
|
|
|
|
| "lead.phone"
|
|
|
|
|
|
| "lead.city"
|
|
|
|
|
|
| "lead.region"
|
|
|
|
|
|
| "lead.trade"
|
|
|
|
|
|
| "lead.notes"
|
|
|
|
|
|
| "lead.source"
|
2026-10-31 12:49:42 +03:30
|
|
|
|
| "lead.gender"
|
|
|
|
|
|
| "person.gender"
|
2026-09-27 21:17:42 +03:30
|
|
|
|
| "notes"
|
|
|
|
|
|
| "custom";
|
|
|
|
|
|
|
2026-09-28 13:39:44 +03:30
|
|
|
|
export type LeadFormDuplicateMode = "skip" | "update" | "activity";
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface LeadFormFieldDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
fieldType: LeadFormFieldType | string;
|
|
|
|
|
|
required: boolean;
|
|
|
|
|
|
mapTo: LeadFormMapTo | string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
placeholder: string | null;
|
|
|
|
|
|
options: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadFormDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description: string | null;
|
|
|
|
|
|
publicToken: string;
|
|
|
|
|
|
isActive: boolean;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
createLead: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
createPerson: boolean;
|
|
|
|
|
|
createDeal: boolean;
|
|
|
|
|
|
pipelineId: string | null;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
leadStageId: string | null;
|
|
|
|
|
|
duplicateMode: LeadFormDuplicateMode | string;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
source: string | null;
|
|
|
|
|
|
thankYouMessage: string | null;
|
|
|
|
|
|
redirectUrl: string | null;
|
|
|
|
|
|
assigneeIds: string[];
|
|
|
|
|
|
submitLabel: string;
|
|
|
|
|
|
submitColor: string;
|
|
|
|
|
|
hasCover: boolean;
|
|
|
|
|
|
fields: LeadFormFieldDto[];
|
|
|
|
|
|
submissionCount?: number;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
viewCount: number;
|
|
|
|
|
|
startCount: number;
|
|
|
|
|
|
submitCount: number;
|
|
|
|
|
|
leadCreateCount: number;
|
|
|
|
|
|
duplicateCount: number;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
publicPath: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadFormFieldInput {
|
|
|
|
|
|
key?: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
fieldType: LeadFormFieldType | string;
|
|
|
|
|
|
required?: boolean;
|
|
|
|
|
|
mapTo: LeadFormMapTo | string;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
placeholder?: string | null;
|
|
|
|
|
|
options?: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateLeadFormRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description?: string | null;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
createLead?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
createPerson?: boolean;
|
|
|
|
|
|
createDeal?: boolean;
|
|
|
|
|
|
pipelineId?: string | null;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
leadStageId?: string | null;
|
|
|
|
|
|
duplicateMode?: LeadFormDuplicateMode | string;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
source?: string | null;
|
|
|
|
|
|
thankYouMessage?: string | null;
|
|
|
|
|
|
redirectUrl?: string | null;
|
|
|
|
|
|
assigneeIds?: string[];
|
|
|
|
|
|
submitLabel?: string;
|
|
|
|
|
|
submitColor?: string;
|
|
|
|
|
|
fields?: LeadFormFieldInput[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateLeadFormRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
description?: string | null;
|
|
|
|
|
|
isActive?: boolean;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
createLead?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
createPerson?: boolean;
|
|
|
|
|
|
createDeal?: boolean;
|
|
|
|
|
|
pipelineId?: string | null;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
leadStageId?: string | null;
|
|
|
|
|
|
duplicateMode?: LeadFormDuplicateMode | string;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
source?: string | null;
|
|
|
|
|
|
thankYouMessage?: string | null;
|
|
|
|
|
|
redirectUrl?: string | null;
|
|
|
|
|
|
assigneeIds?: string[];
|
|
|
|
|
|
submitLabel?: string;
|
|
|
|
|
|
submitColor?: string;
|
|
|
|
|
|
fields?: LeadFormFieldInput[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PublicLeadFormDto {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description: string | null;
|
|
|
|
|
|
thankYouMessage: string | null;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
redirectUrl: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
submitLabel: string;
|
|
|
|
|
|
submitColor: string;
|
|
|
|
|
|
hasCover: boolean;
|
|
|
|
|
|
coverPath: string | null;
|
|
|
|
|
|
fields: Array<{
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
fieldType: LeadFormFieldType | string;
|
|
|
|
|
|
required: boolean;
|
|
|
|
|
|
placeholder: string | null;
|
|
|
|
|
|
options: string[];
|
|
|
|
|
|
}>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadFormSubmissionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
values: Record<string, string>;
|
|
|
|
|
|
personId: string | null;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
leadId: string | null;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
utm: Record<string, string> | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 13:39:44 +03:30
|
|
|
|
export interface LeadFormUtmDto {
|
|
|
|
|
|
source?: string;
|
|
|
|
|
|
medium?: string;
|
|
|
|
|
|
campaign?: string;
|
|
|
|
|
|
term?: string;
|
|
|
|
|
|
content?: string;
|
|
|
|
|
|
landingPage?: string;
|
|
|
|
|
|
referrer?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface SubmitLeadFormRequest {
|
|
|
|
|
|
values: Record<string, string>;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
utm?: LeadFormUtmDto;
|
|
|
|
|
|
pageUrl?: string;
|
|
|
|
|
|
referrer?: string;
|
|
|
|
|
|
/** honeypot — must be empty */
|
|
|
|
|
|
website?: string;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SubmitLeadFormResponse {
|
|
|
|
|
|
ok: true;
|
|
|
|
|
|
thankYouMessage: string | null;
|
|
|
|
|
|
redirectUrl: string | null;
|
2026-09-28 13:39:44 +03:30
|
|
|
|
outcome?: "created" | "updated" | "duplicate_skipped";
|
|
|
|
|
|
leadId?: string | null;
|
|
|
|
|
|
duplicate?: boolean;
|
|
|
|
|
|
message?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 13:39:44 +03:30
|
|
|
|
export type LeadFormPublicEvent = "view" | "start";
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type QuoteStatus =
|
|
|
|
|
|
| "draft"
|
|
|
|
|
|
| "sent"
|
|
|
|
|
|
| "accepted"
|
|
|
|
|
|
| "rejected"
|
|
|
|
|
|
| "expired";
|
|
|
|
|
|
|
|
|
|
|
|
export interface QuoteLineItemDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
productId: string | null;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
unitPrice: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
lineTotal: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface QuoteDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
number: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
status: QuoteStatus | string;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
discountAmount?: number;
|
|
|
|
|
|
taxPercent?: number;
|
|
|
|
|
|
issuedAt?: string | null;
|
|
|
|
|
|
validUntil: string | null;
|
|
|
|
|
|
totalAmount: number;
|
|
|
|
|
|
subtotal: number;
|
|
|
|
|
|
taxAmount: number;
|
|
|
|
|
|
payableAmount: number;
|
|
|
|
|
|
person: PersonSummaryDto | null;
|
|
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
deal: DealSummaryDto | null;
|
|
|
|
|
|
owner: UserSummaryDto | null;
|
|
|
|
|
|
lineItems: QuoteLineItemDto[];
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateQuoteRequest {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
status?: QuoteStatus | string;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
notes?: string;
|
|
|
|
|
|
discountAmount?: number;
|
|
|
|
|
|
taxPercent?: number;
|
|
|
|
|
|
issuedAt?: string | null;
|
|
|
|
|
|
validityDays?: number;
|
|
|
|
|
|
validUntil?: string | null;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
fromDealId?: string | null;
|
|
|
|
|
|
lineItems?: DealLineItemInput[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateQuoteRequest {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
status?: QuoteStatus | string;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
discountAmount?: number;
|
|
|
|
|
|
taxPercent?: number;
|
|
|
|
|
|
issuedAt?: string | null;
|
|
|
|
|
|
validUntil?: string | null;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
lineItems?: DealLineItemInput[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type InvoiceStatus =
|
|
|
|
|
|
| "draft"
|
|
|
|
|
|
| "issued"
|
|
|
|
|
|
| "partially_paid"
|
|
|
|
|
|
| "paid"
|
|
|
|
|
|
| "canceled";
|
|
|
|
|
|
|
|
|
|
|
|
export type PaymentMethod =
|
|
|
|
|
|
| "cash"
|
|
|
|
|
|
| "transfer"
|
|
|
|
|
|
| "card"
|
|
|
|
|
|
| "check"
|
|
|
|
|
|
| "other";
|
|
|
|
|
|
|
|
|
|
|
|
export type InstallmentStatus = "pending" | "paid" | "canceled";
|
|
|
|
|
|
|
|
|
|
|
|
export interface InvoiceLineItemDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
productId: string | null;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
unitPrice: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
lineTotal: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type ChequeStatus = "pending" | "cleared" | "bounced";
|
|
|
|
|
|
export type ChequeDirection = "in" | "out";
|
|
|
|
|
|
|
|
|
|
|
|
export interface ChequeDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
paymentId: string | null;
|
|
|
|
|
|
invoiceId: string | null;
|
|
|
|
|
|
invoiceNumber: string | null;
|
|
|
|
|
|
invoiceTitle: string | null;
|
|
|
|
|
|
personId: string | null;
|
|
|
|
|
|
personName: string | null;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
number: string;
|
|
|
|
|
|
bank: string;
|
|
|
|
|
|
dueDate: string;
|
|
|
|
|
|
status: ChequeStatus | string;
|
|
|
|
|
|
direction: ChequeDirection | string;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
paidAt: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ChequeInput {
|
|
|
|
|
|
number: string;
|
|
|
|
|
|
bank: string;
|
|
|
|
|
|
dueDate: string;
|
|
|
|
|
|
status?: ChequeStatus | string;
|
|
|
|
|
|
notes?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateChequeRequest {
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
number: string;
|
|
|
|
|
|
bank: string;
|
|
|
|
|
|
dueDate: string;
|
|
|
|
|
|
status?: ChequeStatus | string;
|
|
|
|
|
|
direction?: ChequeDirection | string;
|
|
|
|
|
|
notes?: string;
|
2026-10-18 19:33:09 +03:30
|
|
|
|
financialAccountId?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateChequeRequest {
|
|
|
|
|
|
number?: string;
|
|
|
|
|
|
bank?: string;
|
|
|
|
|
|
dueDate?: string;
|
|
|
|
|
|
status?: ChequeStatus | string;
|
|
|
|
|
|
notes?: string | null;
|
2026-10-18 19:33:09 +03:30
|
|
|
|
financialAccountId?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PaymentDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
method: PaymentMethod | string;
|
|
|
|
|
|
paidAt: string;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
installmentId: string | null;
|
|
|
|
|
|
cheque?: ChequeDto | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CommissionEntryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
userName: string;
|
|
|
|
|
|
invoiceId: string;
|
|
|
|
|
|
invoiceNumber: string;
|
|
|
|
|
|
invoiceTitle: string;
|
|
|
|
|
|
percent: number;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
status: "posted" | "void" | string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface InvoiceInstallmentDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
dueDate: string;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
status: InstallmentStatus | string;
|
|
|
|
|
|
paidAt: string | null;
|
|
|
|
|
|
paymentId: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface InvoiceDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
number: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
status: InvoiceStatus | string;
|
|
|
|
|
|
kind?: "invoice" | "credit";
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
discountAmount?: number;
|
|
|
|
|
|
taxPercent?: number;
|
|
|
|
|
|
issuedAt: string | null;
|
|
|
|
|
|
dueDate: string | null;
|
|
|
|
|
|
totalAmount: number;
|
|
|
|
|
|
subtotal: number;
|
|
|
|
|
|
taxAmount: number;
|
|
|
|
|
|
payableAmount: number;
|
|
|
|
|
|
paidAmount: number;
|
|
|
|
|
|
remainingAmount: number;
|
|
|
|
|
|
person: PersonSummaryDto | null;
|
|
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
deal: DealSummaryDto | null;
|
|
|
|
|
|
quote: { id: string; number: string; title: string } | null;
|
|
|
|
|
|
owner: UserSummaryDto | null;
|
|
|
|
|
|
lineItems: InvoiceLineItemDto[];
|
|
|
|
|
|
payments: PaymentDto[];
|
|
|
|
|
|
installments: InvoiceInstallmentDto[];
|
|
|
|
|
|
commissionPercent?: number | null;
|
|
|
|
|
|
commissionFixedAmount?: number | null;
|
|
|
|
|
|
commission?: {
|
|
|
|
|
|
percent: number;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
} | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface InvoiceInstallmentInput {
|
|
|
|
|
|
dueDate: string;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateInvoiceRequest {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
status?: InvoiceStatus | string;
|
|
|
|
|
|
kind?: "invoice" | "credit";
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
notes?: string;
|
|
|
|
|
|
discountAmount?: number;
|
|
|
|
|
|
taxPercent?: number;
|
|
|
|
|
|
issuedAt?: string | null;
|
|
|
|
|
|
dueDate?: string | null;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
quoteId?: string | null;
|
|
|
|
|
|
fromDealId?: string | null;
|
|
|
|
|
|
fromQuoteId?: string | null;
|
|
|
|
|
|
lineItems?: DealLineItemInput[];
|
|
|
|
|
|
installments?: InvoiceInstallmentInput[];
|
|
|
|
|
|
commissionPercent?: number | null;
|
|
|
|
|
|
commissionFixedAmount?: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateInvoiceRequest {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
status?: InvoiceStatus | string;
|
|
|
|
|
|
kind?: "invoice" | "credit";
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
discountAmount?: number;
|
|
|
|
|
|
taxPercent?: number;
|
|
|
|
|
|
issuedAt?: string | null;
|
|
|
|
|
|
dueDate?: string | null;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
quoteId?: string | null;
|
|
|
|
|
|
lineItems?: DealLineItemInput[];
|
|
|
|
|
|
installments?: InvoiceInstallmentInput[];
|
|
|
|
|
|
commissionPercent?: number | null;
|
|
|
|
|
|
commissionFixedAmount?: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreatePaymentRequest {
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
method?: PaymentMethod | string;
|
|
|
|
|
|
paidAt?: string;
|
|
|
|
|
|
notes?: string;
|
|
|
|
|
|
installmentId?: string | null;
|
2026-10-18 19:33:09 +03:30
|
|
|
|
/** اگر پر باشد، پرداخت غیرچکی یا چک وصولشده در این حساب صندوق مینشیند */
|
|
|
|
|
|
financialAccountId?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
cheque?: ChequeInput;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type SalesGoalType =
|
|
|
|
|
|
| "sales"
|
|
|
|
|
|
| "customer"
|
|
|
|
|
|
| "revenue"
|
|
|
|
|
|
| "deal"
|
|
|
|
|
|
| "activity"
|
|
|
|
|
|
| "quality";
|
|
|
|
|
|
|
|
|
|
|
|
export type SalesGoalStatus = "in_progress" | "completed" | "needs_attention";
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesGoalDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
type: SalesGoalType | string;
|
|
|
|
|
|
periodStart: string;
|
|
|
|
|
|
periodEnd: string;
|
|
|
|
|
|
targetAmount: number;
|
|
|
|
|
|
forecastAmount: number | null;
|
|
|
|
|
|
probability: number | null;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
user: UserSummaryDto | null;
|
|
|
|
|
|
currentAmount: number;
|
|
|
|
|
|
progressPercent: number;
|
|
|
|
|
|
status: SalesGoalStatus;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateSalesGoalRequest {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
periodStart: string;
|
|
|
|
|
|
periodEnd: string;
|
|
|
|
|
|
targetAmount: number;
|
|
|
|
|
|
type?: SalesGoalType | string;
|
|
|
|
|
|
forecastAmount?: number | null;
|
|
|
|
|
|
/** 0-100 */
|
|
|
|
|
|
probability?: number | null;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
notes?: string;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** فقط مالک/مدیر؛ در غیر این صورت هدف برای خود کاربر */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
userId?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateSalesGoalRequest {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
periodStart?: string;
|
|
|
|
|
|
periodEnd?: string;
|
|
|
|
|
|
targetAmount?: number;
|
|
|
|
|
|
type?: SalesGoalType | string;
|
|
|
|
|
|
forecastAmount?: number | null;
|
|
|
|
|
|
/** 0-100 */
|
|
|
|
|
|
probability?: number | null;
|
|
|
|
|
|
currency?: string;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
userId?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesForecastKpiDto {
|
|
|
|
|
|
key: "sales" | "customer" | "deal" | "forecast";
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
current: number;
|
|
|
|
|
|
target: number;
|
|
|
|
|
|
percent: number;
|
|
|
|
|
|
unit: "money" | "count";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesForecastDto {
|
|
|
|
|
|
periodStart: string;
|
|
|
|
|
|
periodEnd: string;
|
|
|
|
|
|
currency: string;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** مجموع اهداف همپوشان با بازه */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
targetAmount: number;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** معاملات برندهشده در بازه */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
wonAmount: number;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** معاملات باز فعلی */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
openPipelineAmount: number;
|
2026-10-06 20:51:50 +03:30
|
|
|
|
/** پیشبینی: تحقق + معاملات باز × میانگین احتمال */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
forecastAmount: number;
|
|
|
|
|
|
progressPercent: number | null;
|
|
|
|
|
|
openDealsCount: number;
|
|
|
|
|
|
wonDealsCount: number;
|
|
|
|
|
|
goals: SalesGoalDto[];
|
|
|
|
|
|
kpis: SalesForecastKpiDto[];
|
|
|
|
|
|
newCustomersCount: number;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** معاملات ایجادشده در بازه */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
newDealsCount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type TicketStatus =
|
|
|
|
|
|
| "open"
|
|
|
|
|
|
| "in_progress"
|
|
|
|
|
|
| "waiting"
|
|
|
|
|
|
| "resolved"
|
|
|
|
|
|
| "closed";
|
|
|
|
|
|
|
|
|
|
|
|
export type TicketPriority = "low" | "normal" | "high" | "urgent";
|
|
|
|
|
|
|
|
|
|
|
|
export interface TicketMessageDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
isInternal: boolean;
|
|
|
|
|
|
author: UserSummaryDto | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TicketDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
number: string;
|
|
|
|
|
|
subject: string;
|
|
|
|
|
|
description: string | null;
|
|
|
|
|
|
status: TicketStatus | string;
|
|
|
|
|
|
priority: TicketPriority | string;
|
|
|
|
|
|
person: PersonSummaryDto | null;
|
|
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
deal: DealSummaryDto | null;
|
|
|
|
|
|
assignee: UserSummaryDto | null;
|
|
|
|
|
|
reporter: UserSummaryDto | null;
|
|
|
|
|
|
resolvedAt: string | null;
|
|
|
|
|
|
slaDueAt: string | null;
|
|
|
|
|
|
firstRespondedAt: string | null;
|
|
|
|
|
|
slaBreached: boolean;
|
|
|
|
|
|
csatScore: number | null;
|
|
|
|
|
|
csatComment: string | null;
|
|
|
|
|
|
csatAt: string | null;
|
|
|
|
|
|
messages: TicketMessageDto[];
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateTicketRequest {
|
|
|
|
|
|
subject: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
status?: TicketStatus | string;
|
|
|
|
|
|
priority?: TicketPriority | string;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
assigneeId?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateTicketRequest {
|
|
|
|
|
|
subject?: string;
|
|
|
|
|
|
description?: string | null;
|
|
|
|
|
|
status?: TicketStatus | string;
|
|
|
|
|
|
priority?: TicketPriority | string;
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
assigneeId?: string | null;
|
|
|
|
|
|
csatScore?: number | null;
|
|
|
|
|
|
csatComment?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateTicketMessageRequest {
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
isInternal?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FaqArticleDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
question: string;
|
|
|
|
|
|
answer: string;
|
|
|
|
|
|
category: string | null;
|
|
|
|
|
|
isPublished: boolean;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateFaqArticleRequest {
|
|
|
|
|
|
question: string;
|
|
|
|
|
|
answer: string;
|
|
|
|
|
|
category?: string;
|
|
|
|
|
|
isPublished?: boolean;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateFaqArticleRequest {
|
|
|
|
|
|
question?: string;
|
|
|
|
|
|
answer?: string;
|
|
|
|
|
|
category?: string | null;
|
|
|
|
|
|
isPublished?: boolean;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** ساعات مهلت پاسخ اول بر اساس اولویت */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export const TICKET_SLA_HOURS: Record<TicketPriority, number> = {
|
|
|
|
|
|
urgent: 4,
|
|
|
|
|
|
high: 8,
|
|
|
|
|
|
normal: 24,
|
|
|
|
|
|
low: 48,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— پرتال مشتری (OTP) ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export interface PortalOrgInfoDto {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
slug: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalRequestOtpRequest {
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalRequestOtpResponse {
|
|
|
|
|
|
ok: true;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** فقط در حالت توسعه / دمو وقتی SMS واقعی وصل نیست */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
devCode?: string;
|
|
|
|
|
|
expiresInSeconds: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalVerifyOtpRequest {
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
code: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalPersonDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
mobile: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalLoginResponse {
|
|
|
|
|
|
accessToken: string;
|
|
|
|
|
|
person: PortalPersonDto;
|
|
|
|
|
|
organization: PortalOrgInfoDto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalCreateTicketRequest {
|
|
|
|
|
|
subject: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
priority?: TicketPriority | string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalCreateMessageRequest {
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalCsatRequest {
|
|
|
|
|
|
csatScore: number;
|
|
|
|
|
|
csatComment?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortalTicketDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
number: string;
|
|
|
|
|
|
subject: string;
|
|
|
|
|
|
description: string | null;
|
|
|
|
|
|
status: TicketStatus | string;
|
|
|
|
|
|
priority: TicketPriority | string;
|
|
|
|
|
|
resolvedAt: string | null;
|
|
|
|
|
|
csatScore: number | null;
|
|
|
|
|
|
csatComment: string | null;
|
|
|
|
|
|
messages: Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
fromStaff: boolean;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— گزارشات و داشبورد ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export interface ReportsKpiDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
format: "money" | "number";
|
|
|
|
|
|
changePercent: number | null;
|
|
|
|
|
|
changeLabel: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsSeriesPointDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsSliceDto {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
count: number;
|
|
|
|
|
|
percent: number;
|
|
|
|
|
|
amount?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsTopProductDto {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsTeamRowDto {
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
wonAmount: number;
|
|
|
|
|
|
wonCount: number;
|
|
|
|
|
|
activitiesDone: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** گونههای drill-down گزارش عمیق (فاز ۱ BI) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type ReportsDrillKind =
|
|
|
|
|
|
| "stage_open"
|
|
|
|
|
|
| "team_won"
|
|
|
|
|
|
| "period_won"
|
|
|
|
|
|
| "period_lost"
|
|
|
|
|
|
| "source"
|
|
|
|
|
|
| "lost_reason";
|
|
|
|
|
|
|
|
|
|
|
|
export const REPORTS_DRILL_KINDS: ReportsDrillKind[] = [
|
|
|
|
|
|
"stage_open",
|
|
|
|
|
|
"team_won",
|
|
|
|
|
|
"period_won",
|
|
|
|
|
|
"period_lost",
|
|
|
|
|
|
"source",
|
|
|
|
|
|
"lost_reason",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsFunnelStageDto {
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
stageName: string;
|
|
|
|
|
|
pipelineId: string;
|
|
|
|
|
|
pipelineName: string;
|
|
|
|
|
|
count: number;
|
|
|
|
|
|
amount: number;
|
2026-10-06 20:51:50 +03:30
|
|
|
|
/** درصد تبدیل نسبت به مرحله قبل همان مسیر فروش؛ null برای اولین مرحله */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
conversionFromPrevPercent: number | null;
|
2026-10-06 20:51:50 +03:30
|
|
|
|
/** سهم از کل معاملات باز همان مسیر فروش */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
shareOfPipelinePercent: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** یک ردیف نرخ تبدیل (ورودی → موفق) مثل دیدار */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface ReportsConversionRateRowDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
incomingCount: number;
|
|
|
|
|
|
wonCount: number;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** درصد؛ اگر ورودی صفر باشد ۰ */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
conversionPercent: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-09-28 16:18:54 +03:30
|
|
|
|
* نرخ تبدیل به سبک دیدار:
|
|
|
|
|
|
* از معاملات تازهساختهشده در بازه، چند درصد الان برنده شدهاند.
|
2026-09-27 21:17:42 +03:30
|
|
|
|
*/
|
|
|
|
|
|
export interface ReportsConversionRateDto {
|
|
|
|
|
|
incomingCount: number;
|
|
|
|
|
|
wonCount: number;
|
|
|
|
|
|
conversionPercent: number;
|
|
|
|
|
|
previousIncomingCount: number;
|
|
|
|
|
|
previousWonCount: number;
|
|
|
|
|
|
previousConversionPercent: number;
|
|
|
|
|
|
changePercent: number | null;
|
|
|
|
|
|
byTime: ReportsConversionRateRowDto[];
|
|
|
|
|
|
byUser: ReportsConversionRateRowDto[];
|
|
|
|
|
|
bySource: ReportsConversionRateRowDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsPeriodCompareRowDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
format: "money" | "number" | "percent";
|
|
|
|
|
|
current: number;
|
|
|
|
|
|
previous: number;
|
|
|
|
|
|
changePercent: number | null;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** اگر قابل کلیک برای لیست پرونده باشد */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
drillKind?: ReportsDrillKind;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsDrillItemDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
statusLabel: string;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
ownerName: string | null;
|
|
|
|
|
|
stageName: string | null;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
link: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsDrilldownDto {
|
|
|
|
|
|
kind: ReportsDrillKind;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
from: string;
|
|
|
|
|
|
to: string;
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
items: ReportsDrillItemDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsDashboardDto {
|
|
|
|
|
|
from: string;
|
|
|
|
|
|
to: string;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
kpis: ReportsKpiDto[];
|
|
|
|
|
|
salesTrend: ReportsSeriesPointDto[];
|
|
|
|
|
|
monthlyComparison: ReportsSeriesPointDto[];
|
|
|
|
|
|
customerGrowth: ReportsSeriesPointDto[];
|
|
|
|
|
|
sources: ReportsSliceDto[];
|
|
|
|
|
|
lostReasons: ReportsSliceDto[];
|
|
|
|
|
|
salesByCategory: ReportsSliceDto[];
|
|
|
|
|
|
topProducts: ReportsTopProductDto[];
|
|
|
|
|
|
customerStatus: ReportsSliceDto[];
|
|
|
|
|
|
activities: {
|
|
|
|
|
|
open: number;
|
|
|
|
|
|
done: number;
|
|
|
|
|
|
overdue: number;
|
|
|
|
|
|
byType: Array<{ type: string; label: string; count: number }>;
|
|
|
|
|
|
};
|
|
|
|
|
|
team: ReportsTeamRowDto[];
|
|
|
|
|
|
insights: string[];
|
|
|
|
|
|
sales: {
|
|
|
|
|
|
openCount: number;
|
|
|
|
|
|
wonCount: number;
|
|
|
|
|
|
lostCount: number;
|
|
|
|
|
|
openAmount: number;
|
|
|
|
|
|
wonAmount: number;
|
|
|
|
|
|
lostAmount: number;
|
|
|
|
|
|
winRate: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
funnel: ReportsFunnelStageDto[];
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** نرخ تبدیل ورودی→موفق (سبک دیدار) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
conversionRate: ReportsConversionRateDto;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** مقایسه صریح بازه فعلی با دوره قبل همطول */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
periodCompare: ReportsPeriodCompareRowDto[];
|
|
|
|
|
|
tickets: {
|
|
|
|
|
|
open: number;
|
|
|
|
|
|
resolved: number;
|
|
|
|
|
|
closed: number;
|
|
|
|
|
|
slaBreached: number;
|
|
|
|
|
|
avgCsat: number | null;
|
|
|
|
|
|
csatCount: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
peopleCreated: number;
|
|
|
|
|
|
invoiceCount: number;
|
|
|
|
|
|
invoiceAmount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HomeDashboardKpiDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
changePercent: number | null;
|
|
|
|
|
|
changeLabel: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HomeDashboardAlertDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
tone: 'urgent' | 'warning' | 'success' | 'info';
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
link?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HomeDashboardTrendPointDto {
|
|
|
|
|
|
monthKey: string;
|
|
|
|
|
|
monthLabel: string;
|
|
|
|
|
|
amount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HomeDashboardSourceDto {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
count: number;
|
|
|
|
|
|
percent: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HomeDashboardTopCustomerDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
dealCount: number;
|
|
|
|
|
|
rank: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HomeDashboardDto {
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
kpis: HomeDashboardKpiDto[];
|
|
|
|
|
|
alerts: HomeDashboardAlertDto[];
|
|
|
|
|
|
salesTrend: HomeDashboardTrendPointDto[];
|
|
|
|
|
|
sources: HomeDashboardSourceDto[];
|
|
|
|
|
|
topCustomers: HomeDashboardTopCustomerDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-06 20:51:50 +03:30
|
|
|
|
/** تابلوی پروژه / کارت */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface ProjectBoardStageDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ProjectBoardDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isDefault: boolean;
|
|
|
|
|
|
stages: ProjectBoardStageDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ProjectCardDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
boardId: string;
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
tags: string[];
|
|
|
|
|
|
person: PersonSummaryDto | null;
|
|
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
deal: { id: string; title: string } | null;
|
|
|
|
|
|
owner: UserSummaryDto | null;
|
|
|
|
|
|
assignees: UserSummaryDto[];
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ProjectBoardColumnDto extends ProjectBoardStageDto {
|
|
|
|
|
|
cards: ProjectCardDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ProjectBoardBoardDto {
|
|
|
|
|
|
board: ProjectBoardDto;
|
|
|
|
|
|
stages: ProjectBoardColumnDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateProjectBoardRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateProjectCardRequest {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
boardId?: string;
|
|
|
|
|
|
stageId?: string;
|
|
|
|
|
|
notes?: string;
|
|
|
|
|
|
tags?: string[];
|
|
|
|
|
|
personId?: string | null;
|
|
|
|
|
|
companyId?: string | null;
|
|
|
|
|
|
dealId?: string | null;
|
|
|
|
|
|
ownerId?: string | null;
|
|
|
|
|
|
assigneeIds?: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateProjectCardRequest = Partial<CreateProjectCardRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface MoveProjectCardRequest {
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** باشگاه مشتریان */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface LoyaltyTierDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
minPoints: number;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
color: string | null;
|
|
|
|
|
|
memberCount?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateLoyaltyTierRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
minPoints: number;
|
|
|
|
|
|
color?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateLoyaltyTierRequest = Partial<CreateLoyaltyTierRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface AdjustLoyaltyPointsRequest {
|
|
|
|
|
|
delta: number;
|
|
|
|
|
|
reason?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoyaltyMemberDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
mobile: string | null;
|
|
|
|
|
|
email: string | null;
|
|
|
|
|
|
loyaltyPoints: number;
|
|
|
|
|
|
loyaltyTier: LoyaltyTierSummaryDto | null;
|
|
|
|
|
|
company: CompanySummaryDto | null;
|
|
|
|
|
|
lastActivityAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoyaltyStatsDto {
|
|
|
|
|
|
memberCount: number;
|
|
|
|
|
|
memberDeltaPct: number | null;
|
|
|
|
|
|
pointsIssued: number;
|
|
|
|
|
|
pointsIssuedDeltaPct: number | null;
|
|
|
|
|
|
rewardsUsed: number;
|
|
|
|
|
|
rewardsUsedDeltaPct: number | null;
|
|
|
|
|
|
retentionRate: number;
|
|
|
|
|
|
retentionDeltaPct: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoyaltySettingsDto {
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
welcomePoints: number;
|
|
|
|
|
|
notifyOnAdjust: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateLoyaltySettingsRequest = Partial<LoyaltySettingsDto>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoyaltyRewardDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description: string | null;
|
|
|
|
|
|
pointsCost: number;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
color: string | null;
|
|
|
|
|
|
redemptionCount?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateLoyaltyRewardRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description?: string | null;
|
|
|
|
|
|
pointsCost: number;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
color?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateLoyaltyRewardRequest = Partial<CreateLoyaltyRewardRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoyaltyLedgerDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
delta: number;
|
|
|
|
|
|
reason: string | null;
|
|
|
|
|
|
source: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface BulkAdjustLoyaltyRequest {
|
|
|
|
|
|
personIds: string[];
|
|
|
|
|
|
delta: number;
|
|
|
|
|
|
reason?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoyaltyMessageRequest {
|
|
|
|
|
|
personIds: string[];
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoyaltyMessageResult {
|
|
|
|
|
|
sent: number;
|
|
|
|
|
|
skipped: number;
|
|
|
|
|
|
failed: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RedeemLoyaltyRewardRequest {
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** اتوماسیون اگر/آنگاه */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type AutomationTrigger =
|
|
|
|
|
|
| "person_created"
|
|
|
|
|
|
| "lead_submitted"
|
|
|
|
|
|
| "deal_created"
|
|
|
|
|
|
| "deal_stage_changed"
|
|
|
|
|
|
| "deal_won"
|
|
|
|
|
|
| "deal_lost"
|
|
|
|
|
|
| "ticket_created"
|
|
|
|
|
|
| "ticket_resolved"
|
|
|
|
|
|
| "invoice_issued"
|
|
|
|
|
|
| "invoice_paid"
|
2026-10-01 15:59:18 +03:30
|
|
|
|
| "activity_done"
|
|
|
|
|
|
| "sms_link_clicked";
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type AutomationAction =
|
|
|
|
|
|
| "create_activity"
|
|
|
|
|
|
| "create_notification"
|
|
|
|
|
|
| "add_loyalty_points";
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_TRIGGER_LABELS: Record<AutomationTrigger, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
person_created: "مخاطب جدید ساخته شد",
|
|
|
|
|
|
lead_submitted: "فرم سرنخ ارسال شد",
|
|
|
|
|
|
deal_created: "معامله جدید ساخته شد",
|
|
|
|
|
|
deal_stage_changed: "مرحله معامله عوض شد",
|
|
|
|
|
|
deal_won: "معامله برنده شد",
|
|
|
|
|
|
deal_lost: "معامله باخت",
|
|
|
|
|
|
ticket_created: "تیکت جدید ساخته شد",
|
|
|
|
|
|
ticket_resolved: "تیکت حل شد",
|
|
|
|
|
|
invoice_issued: "فاکتور صادر شد",
|
|
|
|
|
|
invoice_paid: "پرداخت روی فاکتور ثبت شد",
|
|
|
|
|
|
activity_done: "فعالیت تمام شد",
|
2026-10-01 15:59:18 +03:30
|
|
|
|
sms_link_clicked: "کلیک روی لینک پیامک",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_ACTION_LABELS: Record<AutomationAction, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
create_activity: "ساخت فعالیت",
|
|
|
|
|
|
create_notification: "ارسال اعلان",
|
|
|
|
|
|
add_loyalty_points: "افزودن امتیاز باشگاه",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-11 14:48:12 +03:30
|
|
|
|
export const MAX_AUTOMATION_STEPS = 20;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_ACTIONS = [
|
|
|
|
|
|
"create_activity",
|
|
|
|
|
|
"create_notification",
|
|
|
|
|
|
"add_loyalty_points",
|
|
|
|
|
|
] as const satisfies readonly AutomationAction[];
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_TRIGGERS = [
|
|
|
|
|
|
"person_created",
|
|
|
|
|
|
"lead_submitted",
|
|
|
|
|
|
"deal_created",
|
|
|
|
|
|
"deal_stage_changed",
|
|
|
|
|
|
"deal_won",
|
|
|
|
|
|
"deal_lost",
|
|
|
|
|
|
"ticket_created",
|
|
|
|
|
|
"ticket_resolved",
|
|
|
|
|
|
"invoice_issued",
|
|
|
|
|
|
"invoice_paid",
|
|
|
|
|
|
"activity_done",
|
2026-10-01 15:59:18 +03:30
|
|
|
|
"sms_link_clicked",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
] as const satisfies readonly AutomationTrigger[];
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_TRIGGER_GROUPS: {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
items: AutomationTrigger[];
|
|
|
|
|
|
}[] = [
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ label: "مخاطب", items: ["person_created", "lead_submitted"] },
|
2026-09-27 21:17:42 +03:30
|
|
|
|
{
|
2026-09-28 16:18:54 +03:30
|
|
|
|
label: "معامله",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
items: ["deal_created", "deal_stage_changed", "deal_won", "deal_lost"],
|
|
|
|
|
|
},
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ label: "تیکت", items: ["ticket_created", "ticket_resolved"] },
|
|
|
|
|
|
{ label: "فعالیت", items: ["activity_done"] },
|
|
|
|
|
|
{ label: "مالی", items: ["invoice_issued", "invoice_paid"] },
|
2026-10-01 15:59:18 +03:30
|
|
|
|
{ label: "پیامک", items: ["sms_link_clicked"] },
|
2026-09-27 21:17:42 +03:30
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export type AutomationStepKind = "action" | "condition";
|
|
|
|
|
|
|
|
|
|
|
|
export type AutomationConditionField =
|
|
|
|
|
|
| "deal_amount"
|
|
|
|
|
|
| "has_person"
|
|
|
|
|
|
| "has_company"
|
2026-10-11 14:12:36 +03:30
|
|
|
|
| "ticket_priority"
|
|
|
|
|
|
| "form_field";
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type AutomationConditionOp =
|
|
|
|
|
|
| "gte"
|
|
|
|
|
|
| "lte"
|
|
|
|
|
|
| "eq"
|
|
|
|
|
|
| "neq"
|
|
|
|
|
|
| "is_set"
|
2026-10-11 14:12:36 +03:30
|
|
|
|
| "is_empty"
|
|
|
|
|
|
| "contains"
|
2026-10-11 17:26:17 +03:30
|
|
|
|
| "contains_any"
|
2026-10-11 14:12:36 +03:30
|
|
|
|
| "not_contains";
|
|
|
|
|
|
|
|
|
|
|
|
/** stop: شرط ناموفق کل زنجیره را متوقف میکند — branches: هر شرط فقط عملهای بعدی خودش را تا شرط بعدی کنترل میکند */
|
|
|
|
|
|
export type AutomationChainMode = "stop" | "branches";
|
|
|
|
|
|
|
|
|
|
|
|
export type AutomationAssignMode =
|
|
|
|
|
|
| "owner"
|
|
|
|
|
|
| "user"
|
|
|
|
|
|
| "team_round_robin"
|
|
|
|
|
|
| "users_round_robin";
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_ASSIGN_MODE_LABELS: Record<AutomationAssignMode, string> = {
|
|
|
|
|
|
owner: "مسئول پیشفرض (مسئول پرونده)",
|
|
|
|
|
|
user: "کاربر مشخص",
|
|
|
|
|
|
team_round_robin: "نوبتی بین اعضای یک تیم",
|
|
|
|
|
|
users_round_robin: "نوبتی بین کاربران انتخابی",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_CHAIN_MODE_LABELS: Record<AutomationChainMode, string> = {
|
|
|
|
|
|
branches: "شاخهای — هر شرط فقط عملهای بعد از خودش را کنترل میکند",
|
|
|
|
|
|
stop: "توقف — شرط ناموفق کل زنجیره را متوقف میکند",
|
|
|
|
|
|
};
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type AutomationNotifyChannel = "app" | "sms" | "both";
|
|
|
|
|
|
export type AutomationNotifyTarget = "staff" | "person";
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_CONDITION_FIELD_LABELS: Record<
|
|
|
|
|
|
AutomationConditionField,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
deal_amount: "مبلغ معامله",
|
|
|
|
|
|
has_person: "مخاطب دارد",
|
|
|
|
|
|
has_company: "شرکت دارد",
|
|
|
|
|
|
ticket_priority: "اولویت تیکت",
|
2026-10-11 14:12:36 +03:30
|
|
|
|
form_field: "پاسخ فیلد فرم",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_CONDITION_OP_LABELS: Record<
|
|
|
|
|
|
AutomationConditionOp,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
gte: "بزرگتر یا مساوی",
|
|
|
|
|
|
lte: "کوچکتر یا مساوی",
|
|
|
|
|
|
eq: "مساوی",
|
|
|
|
|
|
neq: "نامساوی",
|
|
|
|
|
|
is_set: "دارد",
|
|
|
|
|
|
is_empty: "ندارد",
|
2026-10-11 14:12:36 +03:30
|
|
|
|
contains: "شامل",
|
2026-10-11 17:26:17 +03:30
|
|
|
|
contains_any: "شامل یکی از",
|
2026-10-11 14:12:36 +03:30
|
|
|
|
not_contains: "شامل نیست",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_NOTIFY_CHANNEL_LABELS: Record<
|
|
|
|
|
|
AutomationNotifyChannel,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
app: "ناتیفیکیشن داخلی",
|
|
|
|
|
|
sms: "پیامک",
|
|
|
|
|
|
both: "هر دو",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const AUTOMATION_NOTIFY_CHANNELS = [
|
|
|
|
|
|
"app",
|
|
|
|
|
|
"sms",
|
|
|
|
|
|
"both",
|
|
|
|
|
|
] as const satisfies readonly AutomationNotifyChannel[];
|
|
|
|
|
|
|
|
|
|
|
|
export function isAutomationCondition(step: Pick<AutomationStep, "kind">): boolean {
|
|
|
|
|
|
return step.kind === "condition";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function firstAutomationAction(
|
|
|
|
|
|
steps: AutomationStep[],
|
|
|
|
|
|
): AutomationAction | null {
|
|
|
|
|
|
const step = steps.find((s) => !isAutomationCondition(s) && s.action);
|
|
|
|
|
|
return step?.action ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function conditionFieldsForTrigger(
|
|
|
|
|
|
trigger: AutomationTrigger,
|
|
|
|
|
|
): AutomationConditionField[] {
|
|
|
|
|
|
if (
|
|
|
|
|
|
trigger === "deal_won" ||
|
|
|
|
|
|
trigger === "deal_lost" ||
|
|
|
|
|
|
trigger === "deal_created" ||
|
|
|
|
|
|
trigger === "deal_stage_changed"
|
|
|
|
|
|
) {
|
|
|
|
|
|
return ["deal_amount", "has_person", "has_company"];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (trigger === "ticket_resolved" || trigger === "ticket_created") {
|
|
|
|
|
|
return ["ticket_priority", "has_person", "has_company"];
|
|
|
|
|
|
}
|
2026-10-11 14:12:36 +03:30
|
|
|
|
if (trigger === "lead_submitted") {
|
|
|
|
|
|
return ["form_field", "has_person", "has_company"];
|
|
|
|
|
|
}
|
2026-09-27 21:17:42 +03:30
|
|
|
|
return ["has_person", "has_company"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 14:12:36 +03:30
|
|
|
|
export function conditionOpsForField(
|
|
|
|
|
|
field: AutomationConditionField | undefined,
|
|
|
|
|
|
): AutomationConditionOp[] {
|
|
|
|
|
|
if (field === "deal_amount") return ["gte", "lte", "eq", "neq"];
|
|
|
|
|
|
if (field === "ticket_priority") return ["eq", "neq"];
|
|
|
|
|
|
if (field === "form_field") {
|
2026-10-11 17:26:17 +03:30
|
|
|
|
return ["contains", "contains_any", "not_contains", "eq", "neq", "is_set", "is_empty"];
|
2026-10-11 14:12:36 +03:30
|
|
|
|
}
|
|
|
|
|
|
return ["is_set", "is_empty"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface AutomationStep {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
kind?: AutomationStepKind;
|
|
|
|
|
|
action?: AutomationAction;
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
body?: string;
|
|
|
|
|
|
assigneeId?: string | null;
|
|
|
|
|
|
notifyUserId?: string | null;
|
|
|
|
|
|
notifyChannel?: AutomationNotifyChannel;
|
|
|
|
|
|
notifyTarget?: AutomationNotifyTarget;
|
|
|
|
|
|
activityType?: "call" | "meeting" | "task" | "note";
|
|
|
|
|
|
points?: number;
|
|
|
|
|
|
conditionField?: AutomationConditionField;
|
|
|
|
|
|
conditionOp?: AutomationConditionOp;
|
|
|
|
|
|
conditionValue?: string | number;
|
2026-10-11 17:26:17 +03:30
|
|
|
|
/** چند گزینهٔ فیلد فرم (چندانتخابی)؛ اگر پر باشد بهجای conditionValue استفاده میشود */
|
|
|
|
|
|
conditionValues?: string[];
|
2026-10-11 14:12:36 +03:30
|
|
|
|
/** کلید فیلد فرم برای شرط form_field */
|
|
|
|
|
|
conditionKey?: string | null;
|
|
|
|
|
|
assignMode?: AutomationAssignMode;
|
|
|
|
|
|
assigneeTeamId?: string | null;
|
|
|
|
|
|
assigneeUserIds?: string[];
|
|
|
|
|
|
/** موعد فعالیت نسبت به زمان اجرا (دقیقه)؛ ۰ یا خالی = همان لحظه */
|
|
|
|
|
|
dueOffsetMinutes?: number | null;
|
|
|
|
|
|
activityTypeDefId?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AutomationRuleConfig {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
body?: string;
|
|
|
|
|
|
assigneeId?: string | null;
|
|
|
|
|
|
notifyUserId?: string | null;
|
|
|
|
|
|
notifyChannel?: AutomationNotifyChannel;
|
|
|
|
|
|
notifyTarget?: AutomationNotifyTarget;
|
|
|
|
|
|
activityType?: "call" | "meeting" | "task" | "note";
|
|
|
|
|
|
points?: number;
|
2026-10-11 14:12:36 +03:30
|
|
|
|
assignMode?: AutomationAssignMode;
|
|
|
|
|
|
assigneeTeamId?: string | null;
|
|
|
|
|
|
assigneeUserIds?: string[];
|
|
|
|
|
|
dueOffsetMinutes?: number | null;
|
|
|
|
|
|
activityTypeDefId?: string | null;
|
|
|
|
|
|
/** فیلتر اختیاری: فقط برای منبع خاص (مثلاً id کمپین SMS یا id فرم سرنخ) */
|
2026-10-01 15:59:18 +03:30
|
|
|
|
sourceId?: string | null;
|
2026-10-11 14:12:36 +03:30
|
|
|
|
/** نبودِ مقدار = stop (رفتار قدیمی) */
|
|
|
|
|
|
chainMode?: AutomationChainMode;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** زنجیرهٔ اعمال؛ اگر خالی باشد همان action تکی استفاده میشود */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
steps?: AutomationStep[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AutomationRuleDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
trigger: AutomationTrigger;
|
|
|
|
|
|
action: AutomationAction;
|
|
|
|
|
|
config: AutomationRuleConfig;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateAutomationRuleRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
trigger: AutomationTrigger;
|
|
|
|
|
|
action: AutomationAction;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
config?: AutomationRuleConfig;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateAutomationRuleRequest = Partial<CreateAutomationRuleRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export function normalizeAutomationSteps(
|
|
|
|
|
|
action: AutomationAction,
|
|
|
|
|
|
config?: AutomationRuleConfig | null,
|
|
|
|
|
|
): AutomationStep[] {
|
|
|
|
|
|
const cfg = config ?? {};
|
|
|
|
|
|
if (Array.isArray(cfg.steps) && cfg.steps.length) {
|
|
|
|
|
|
return cfg.steps
|
|
|
|
|
|
.filter((s) => s && (s.kind === "condition" || s.action))
|
|
|
|
|
|
.map((s, i) => ({
|
|
|
|
|
|
id: s.id || `step-${i}`,
|
|
|
|
|
|
kind: s.kind === "condition" ? "condition" : "action",
|
|
|
|
|
|
action: s.action,
|
|
|
|
|
|
title: s.title,
|
|
|
|
|
|
body: s.body,
|
|
|
|
|
|
assigneeId: s.assigneeId,
|
|
|
|
|
|
notifyUserId: s.notifyUserId,
|
|
|
|
|
|
notifyChannel: s.notifyChannel,
|
|
|
|
|
|
notifyTarget: s.notifyTarget,
|
|
|
|
|
|
activityType: s.activityType,
|
|
|
|
|
|
points: s.points,
|
|
|
|
|
|
conditionField: s.conditionField,
|
|
|
|
|
|
conditionOp: s.conditionOp,
|
|
|
|
|
|
conditionValue: s.conditionValue,
|
2026-10-11 17:26:17 +03:30
|
|
|
|
conditionValues: Array.isArray(s.conditionValues)
|
|
|
|
|
|
? s.conditionValues.map((v) => String(v).trim()).filter(Boolean)
|
|
|
|
|
|
: undefined,
|
2026-10-11 14:12:36 +03:30
|
|
|
|
conditionKey: s.conditionKey,
|
|
|
|
|
|
assignMode: s.assignMode,
|
|
|
|
|
|
assigneeTeamId: s.assigneeTeamId,
|
|
|
|
|
|
assigneeUserIds: Array.isArray(s.assigneeUserIds) ? s.assigneeUserIds : undefined,
|
|
|
|
|
|
dueOffsetMinutes: s.dueOffsetMinutes,
|
|
|
|
|
|
activityTypeDefId: s.activityTypeDefId,
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "step-0",
|
|
|
|
|
|
kind: "action",
|
|
|
|
|
|
action,
|
|
|
|
|
|
title: cfg.title,
|
|
|
|
|
|
body: cfg.body,
|
|
|
|
|
|
assigneeId: cfg.assigneeId,
|
|
|
|
|
|
notifyUserId: cfg.notifyUserId,
|
|
|
|
|
|
notifyChannel: cfg.notifyChannel,
|
|
|
|
|
|
notifyTarget: cfg.notifyTarget,
|
|
|
|
|
|
activityType: cfg.activityType,
|
|
|
|
|
|
points: cfg.points,
|
2026-10-11 14:12:36 +03:30
|
|
|
|
assignMode: cfg.assignMode,
|
|
|
|
|
|
assigneeTeamId: cfg.assigneeTeamId,
|
|
|
|
|
|
assigneeUserIds: cfg.assigneeUserIds,
|
|
|
|
|
|
dueOffsetMinutes: cfg.dueOffsetMinutes,
|
|
|
|
|
|
activityTypeDefId: cfg.activityTypeDefId,
|
2026-09-27 21:17:42 +03:30
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 14:12:36 +03:30
|
|
|
|
export function packAutomationConfig(
|
|
|
|
|
|
steps: AutomationStep[],
|
|
|
|
|
|
extra: Pick<AutomationRuleConfig, "sourceId" | "chainMode"> = {},
|
|
|
|
|
|
): AutomationRuleConfig {
|
2026-09-27 21:17:42 +03:30
|
|
|
|
const first =
|
|
|
|
|
|
steps.find((s) => (s.kind ?? "action") !== "condition") ?? steps[0];
|
|
|
|
|
|
return {
|
|
|
|
|
|
steps,
|
|
|
|
|
|
title: first?.title,
|
|
|
|
|
|
body: first?.body,
|
|
|
|
|
|
assigneeId: first?.assigneeId,
|
|
|
|
|
|
notifyUserId: first?.notifyUserId,
|
|
|
|
|
|
notifyChannel: first?.notifyChannel,
|
|
|
|
|
|
notifyTarget: first?.notifyTarget,
|
|
|
|
|
|
activityType: first?.activityType,
|
|
|
|
|
|
points: first?.points,
|
2026-10-11 14:12:36 +03:30
|
|
|
|
...(extra.sourceId !== undefined ? { sourceId: extra.sourceId || null } : {}),
|
|
|
|
|
|
...(extra.chainMode ? { chainMode: extra.chainMode } : {}),
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function automationStepToConfig(step: AutomationStep): AutomationRuleConfig {
|
|
|
|
|
|
return {
|
|
|
|
|
|
title: step.title,
|
|
|
|
|
|
body: step.body,
|
|
|
|
|
|
assigneeId: step.assigneeId,
|
|
|
|
|
|
notifyUserId: step.notifyUserId,
|
|
|
|
|
|
notifyChannel: step.notifyChannel,
|
|
|
|
|
|
notifyTarget: step.notifyTarget,
|
|
|
|
|
|
activityType: step.activityType,
|
|
|
|
|
|
points: step.points,
|
2026-10-11 14:12:36 +03:30
|
|
|
|
assignMode: step.assignMode,
|
|
|
|
|
|
assigneeTeamId: step.assigneeTeamId,
|
|
|
|
|
|
assigneeUserIds: step.assigneeUserIds,
|
|
|
|
|
|
dueOffsetMinutes: step.dueOffsetMinutes,
|
|
|
|
|
|
activityTypeDefId: step.activityTypeDefId,
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 14:12:36 +03:30
|
|
|
|
/**
|
|
|
|
|
|
* گامهای عملی که باید اجرا شوند.
|
|
|
|
|
|
* stop: اولین شرط ناموفق بقیهٔ زنجیره را قطع میکند.
|
|
|
|
|
|
* branches: هر دنبالهٔ پیاپی از شرطها (AND) فقط عملهای بعد از خودش را تا شرط بعدی کنترل میکند.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function planAutomationActions(
|
|
|
|
|
|
steps: AutomationStep[],
|
|
|
|
|
|
chainMode: AutomationChainMode | undefined,
|
|
|
|
|
|
evalCondition: (step: AutomationStep) => boolean,
|
|
|
|
|
|
): AutomationStep[] {
|
|
|
|
|
|
const out: AutomationStep[] = [];
|
|
|
|
|
|
if (chainMode !== "branches") {
|
|
|
|
|
|
for (const step of steps) {
|
|
|
|
|
|
if (isAutomationCondition(step)) {
|
|
|
|
|
|
if (!evalCondition(step)) break;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (step.action) out.push(step);
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
let gateOpen = true;
|
|
|
|
|
|
let prevWasCondition = false;
|
|
|
|
|
|
for (const step of steps) {
|
|
|
|
|
|
if (isAutomationCondition(step)) {
|
|
|
|
|
|
const ok = evalCondition(step);
|
|
|
|
|
|
gateOpen = prevWasCondition ? gateOpen && ok : ok;
|
|
|
|
|
|
prevWasCondition = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
prevWasCondition = false;
|
|
|
|
|
|
if (gateOpen && step.action) out.push(step);
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 14:48:12 +03:30
|
|
|
|
export interface AutomationBranchRange {
|
|
|
|
|
|
/** اندیس اولین شرط شاخه */
|
|
|
|
|
|
start: number;
|
|
|
|
|
|
/** اندیس بعد از آخرین گام شاخه */
|
|
|
|
|
|
end: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** گامهای بدون شرط ابتدای زنجیره و بازهٔ هر شاخه (در حالت branches) */
|
|
|
|
|
|
export function splitAutomationBranches(steps: AutomationStep[]): {
|
|
|
|
|
|
prefixEnd: number;
|
|
|
|
|
|
branches: AutomationBranchRange[];
|
|
|
|
|
|
} {
|
|
|
|
|
|
const branches: AutomationBranchRange[] = [];
|
|
|
|
|
|
let prefixEnd = steps.length;
|
|
|
|
|
|
steps.forEach((step, i) => {
|
|
|
|
|
|
if (!isAutomationCondition(step)) return;
|
|
|
|
|
|
const prev = steps[i - 1];
|
|
|
|
|
|
if (prev && isAutomationCondition(prev)) return;
|
|
|
|
|
|
if (!branches.length) prefixEnd = i;
|
|
|
|
|
|
else branches[branches.length - 1].end = i;
|
|
|
|
|
|
branches.push({ start: i, end: steps.length });
|
|
|
|
|
|
});
|
|
|
|
|
|
return { prefixEnd, branches };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** قانون از امکانات اتوماسیون حرفهای (شاخه، شرط فرم، تخصیص نوبتی) استفاده میکند */
|
|
|
|
|
|
export function automationUsesAdvanced(
|
|
|
|
|
|
steps: AutomationStep[],
|
|
|
|
|
|
chainMode: AutomationChainMode | undefined,
|
|
|
|
|
|
): boolean {
|
|
|
|
|
|
if (chainMode === "branches") return true;
|
|
|
|
|
|
return steps.some(
|
|
|
|
|
|
(s) =>
|
|
|
|
|
|
(isAutomationCondition(s) && s.conditionField === "form_field") ||
|
|
|
|
|
|
s.assignMode === "team_round_robin" ||
|
|
|
|
|
|
s.assignMode === "users_round_robin",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 14:12:36 +03:30
|
|
|
|
/** مقدار پاسخ فرم را به گزینهها میشکند (چندانتخابی با کاما ذخیره میشود) */
|
|
|
|
|
|
export function splitFormAnswer(raw: unknown): string[] {
|
|
|
|
|
|
if (raw == null) return [];
|
|
|
|
|
|
return String(raw)
|
|
|
|
|
|
.split(/[,،]/)
|
|
|
|
|
|
.map((s) => s.trim())
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 17:26:17 +03:30
|
|
|
|
/** گزینههای مورد انتظار شرط فرم: چندتایی اگر پر باشد، وگرنه مقدار تکی */
|
|
|
|
|
|
export function formConditionValues(
|
|
|
|
|
|
step: Pick<AutomationStep, "conditionValue" | "conditionValues">,
|
|
|
|
|
|
): string[] {
|
|
|
|
|
|
const many = (step.conditionValues ?? []).map((v) => String(v).trim()).filter(Boolean);
|
|
|
|
|
|
if (many.length) return [...new Set(many)];
|
|
|
|
|
|
const one = String(step.conditionValue ?? "").trim();
|
|
|
|
|
|
return one ? [one] : [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* contains: همهٔ گزینهها انتخاب شده باشند — contains_any: حداقل یکی —
|
|
|
|
|
|
* not_contains: هیچکدام — eq: دقیقاً همین گزینهها (نه بیشتر، نه کمتر)
|
|
|
|
|
|
*/
|
2026-10-11 14:12:36 +03:30
|
|
|
|
export function matchFormFieldCondition(
|
|
|
|
|
|
op: AutomationConditionOp | undefined,
|
|
|
|
|
|
raw: unknown,
|
2026-10-11 17:26:17 +03:30
|
|
|
|
expected: string | number | string[] | null | undefined,
|
2026-10-11 14:12:36 +03:30
|
|
|
|
): boolean {
|
|
|
|
|
|
const parts = splitFormAnswer(raw);
|
|
|
|
|
|
const whole = raw == null ? "" : String(raw).trim();
|
2026-10-11 17:26:17 +03:30
|
|
|
|
const values = (Array.isArray(expected) ? expected : [expected])
|
|
|
|
|
|
.map((v) => String(v ?? "").trim())
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
const exact = () =>
|
|
|
|
|
|
values.length === 1
|
|
|
|
|
|
? whole === values[0] || (parts.length === 1 && parts[0] === values[0])
|
|
|
|
|
|
: new Set(parts).size === values.length && values.every((v) => parts.includes(v));
|
2026-10-11 14:12:36 +03:30
|
|
|
|
switch (op ?? "contains") {
|
|
|
|
|
|
case "is_set":
|
|
|
|
|
|
return parts.length > 0;
|
|
|
|
|
|
case "is_empty":
|
|
|
|
|
|
return parts.length === 0;
|
|
|
|
|
|
case "contains":
|
2026-10-11 17:26:17 +03:30
|
|
|
|
return values.length > 0 && values.every((v) => parts.includes(v));
|
|
|
|
|
|
case "contains_any":
|
|
|
|
|
|
return values.some((v) => parts.includes(v));
|
2026-10-11 14:12:36 +03:30
|
|
|
|
case "not_contains":
|
2026-10-11 17:26:17 +03:30
|
|
|
|
return !values.some((v) => parts.includes(v));
|
2026-10-11 14:12:36 +03:30
|
|
|
|
case "eq":
|
2026-10-11 17:26:17 +03:30
|
|
|
|
return values.length > 0 && exact();
|
2026-10-11 14:12:36 +03:30
|
|
|
|
case "neq":
|
2026-10-11 17:26:17 +03:30
|
|
|
|
return !(values.length > 0 && exact());
|
2026-10-11 14:12:36 +03:30
|
|
|
|
default:
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
// —— تهحساب / حسابداری ——
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export const TAHSAB_SANAD_TYPES = [
|
|
|
|
|
|
"buy_sale_gold",
|
|
|
|
|
|
"buy_sale_sekeh",
|
|
|
|
|
|
"talab_bedehi",
|
|
|
|
|
|
"vkh_gold",
|
|
|
|
|
|
"vkh_bank",
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type TahesabSanadType = (typeof TAHSAB_SANAD_TYPES)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export const TAHSAB_SANAD_LABELS: Record<TahesabSanadType, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
buy_sale_gold: "خرید/فروش آبشده",
|
|
|
|
|
|
buy_sale_sekeh: "خرید/فروش سکه",
|
|
|
|
|
|
talab_bedehi: "طلب/بدهی",
|
|
|
|
|
|
vkh_gold: "واریز/خروج طلا",
|
|
|
|
|
|
vkh_bank: "واریز/خروج بانکی",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabConnectionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
organizationId: string;
|
|
|
|
|
|
provider: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
baseUrl: string;
|
|
|
|
|
|
dbName: string;
|
|
|
|
|
|
tokenMasked: string;
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
isDefault: boolean;
|
|
|
|
|
|
allowInsecureTls: boolean;
|
|
|
|
|
|
lastHealthAt: string | null;
|
|
|
|
|
|
lastHealthOk: boolean | null;
|
|
|
|
|
|
lastError: string | null;
|
2026-11-05 12:31:27 +03:30
|
|
|
|
/** ساعتهای ۰ تا ۲۳ به وقت تهران. خالی یعنی بروزرسانی خودکار خاموش است. */
|
|
|
|
|
|
customerSyncHours: number[];
|
|
|
|
|
|
customerSyncLastRunAt: string | null;
|
|
|
|
|
|
customerSyncLastSummary: string | null;
|
|
|
|
|
|
customerSyncLastError: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateTahesabConnectionRequest {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
baseUrl: string;
|
|
|
|
|
|
dbName: string;
|
|
|
|
|
|
token: string;
|
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
isDefault?: boolean;
|
|
|
|
|
|
allowInsecureTls?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-05 12:31:27 +03:30
|
|
|
|
export interface UpdateTahesabConnectionRequest
|
|
|
|
|
|
extends Partial<CreateTahesabConnectionRequest> {
|
|
|
|
|
|
/** آرایهٔ خالی بروزرسانی خودکار را خاموش میکند. */
|
|
|
|
|
|
customerSyncHours?: number[];
|
|
|
|
|
|
}
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export interface PersonAccountingLinkDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
connectionId: string;
|
|
|
|
|
|
moshtariCode: number;
|
|
|
|
|
|
matchMethod: string;
|
|
|
|
|
|
linkedAt: string;
|
|
|
|
|
|
lastBalanceSyncedAt: string | null;
|
|
|
|
|
|
connectionLabel?: string;
|
|
|
|
|
|
dbName?: string;
|
|
|
|
|
|
isDefaultConnection?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-22 18:20:46 +03:30
|
|
|
|
/** طبقهبندی تقریبی نوع سند از روی متن NO تهحساب */
|
|
|
|
|
|
export const TAHESAB_ASNAD_KINDS = [
|
|
|
|
|
|
"sale",
|
|
|
|
|
|
"return",
|
|
|
|
|
|
"purchase",
|
|
|
|
|
|
"other",
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
export type TahesabAsnadKind = (typeof TAHESAB_ASNAD_KINDS)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export const TAHESAB_ASNAD_KIND_LABELS: Record<TahesabAsnadKind, string> = {
|
|
|
|
|
|
sale: "فروش",
|
|
|
|
|
|
return: "مرجوعی",
|
|
|
|
|
|
purchase: "خرید",
|
|
|
|
|
|
other: "سایر",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const TAHESAB_JENS_FELEZ_LABELS: Record<number, string> = {
|
|
|
|
|
|
0: "طلا",
|
|
|
|
|
|
1: "پلاتین",
|
|
|
|
|
|
2: "نقره",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabAsnadSyncRequest {
|
|
|
|
|
|
connectionId?: string;
|
|
|
|
|
|
/** شمسی YYYY-MM-DD */
|
|
|
|
|
|
from: string;
|
|
|
|
|
|
/** شمسی YYYY-MM-DD */
|
|
|
|
|
|
to: string;
|
2026-11-03 10:09:40 +03:30
|
|
|
|
/** محدود کردن همگامسازی به یک شخص */
|
|
|
|
|
|
personId?: string;
|
|
|
|
|
|
/** محدود کردن همگامسازی به گروهها و زیرگروهها */
|
|
|
|
|
|
groupIds?: string[];
|
2026-10-22 18:20:46 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabAsnadSyncJobDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
status: "pending" | "running" | "done" | "failed" | string;
|
|
|
|
|
|
connectionId: string | null;
|
|
|
|
|
|
from: string;
|
|
|
|
|
|
to: string;
|
|
|
|
|
|
processed: number;
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
linesUpserted: number;
|
|
|
|
|
|
truncatedPersonIds: string[];
|
|
|
|
|
|
errorMessage: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabAsnadLineDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
connectionId: string;
|
|
|
|
|
|
personId: string | null;
|
|
|
|
|
|
personName: string | null;
|
|
|
|
|
|
moshtariCode: number;
|
|
|
|
|
|
tarikh: string;
|
|
|
|
|
|
noRaw: string;
|
|
|
|
|
|
kind: TahesabAsnadKind | string;
|
|
|
|
|
|
jensFelez: number | null;
|
|
|
|
|
|
noKar: string | null;
|
|
|
|
|
|
vazn: number | null;
|
|
|
|
|
|
tabdilVazn: number | null;
|
|
|
|
|
|
mali: number | null;
|
|
|
|
|
|
count: number | null;
|
|
|
|
|
|
shFactor: number | null;
|
|
|
|
|
|
factorCode: string | null;
|
|
|
|
|
|
groups: CustomerGroupSummaryDto[];
|
|
|
|
|
|
syncedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabAsnadReportQuery {
|
|
|
|
|
|
connectionId?: string;
|
|
|
|
|
|
from: string;
|
|
|
|
|
|
to: string;
|
|
|
|
|
|
groupIds?: string[];
|
|
|
|
|
|
personId?: string;
|
|
|
|
|
|
kinds?: TahesabAsnadKind[];
|
|
|
|
|
|
jensFelez?: number;
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabAsnadReportPageDto {
|
|
|
|
|
|
items: TahesabAsnadLineDto[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
pageSize: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabAsnadSummaryBucketDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
lineCount: number;
|
|
|
|
|
|
maliSum: number;
|
|
|
|
|
|
vaznSum: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabAsnadSummaryDto {
|
|
|
|
|
|
totalLines: number;
|
|
|
|
|
|
maliSum: number;
|
|
|
|
|
|
vaznSum: number;
|
|
|
|
|
|
byKind: TahesabAsnadSummaryBucketDto[];
|
|
|
|
|
|
byMetal: TahesabAsnadSummaryBucketDto[];
|
|
|
|
|
|
byGroup: TahesabAsnadSummaryBucketDto[];
|
|
|
|
|
|
distinctNos: string[];
|
|
|
|
|
|
lastSyncedAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-26 11:05:34 +03:30
|
|
|
|
export interface SalesPercentStatsDto {
|
|
|
|
|
|
first: number | null;
|
|
|
|
|
|
last: number | null;
|
|
|
|
|
|
min: number | null;
|
|
|
|
|
|
max: number | null;
|
|
|
|
|
|
avg: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesMovementStatsDto {
|
|
|
|
|
|
grossWeight: number;
|
|
|
|
|
|
returnWeight: number;
|
|
|
|
|
|
netWeight: number;
|
|
|
|
|
|
grossMali: number;
|
|
|
|
|
|
returnMali: number;
|
|
|
|
|
|
netMali: number;
|
|
|
|
|
|
grossCount: number;
|
|
|
|
|
|
returnCount: number;
|
|
|
|
|
|
netCount: number;
|
|
|
|
|
|
percent: SalesPercentStatsDto | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesFactorOverviewDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
sale: SalesMovementStatsDto;
|
|
|
|
|
|
purchase: SalesMovementStatsDto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesNamedMovementDto {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
sale: SalesMovementStatsDto;
|
|
|
|
|
|
purchase: SalesMovementStatsDto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesGroupChildDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
netSaleWeight: number;
|
|
|
|
|
|
netSaleMali: number;
|
|
|
|
|
|
saleReturnWeight: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesGroupOverviewDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
netSaleWeight: number;
|
|
|
|
|
|
netSaleMali: number;
|
|
|
|
|
|
saleReturnWeight: number;
|
|
|
|
|
|
children: SalesGroupChildDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesExpertOverviewDto {
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
percent: number;
|
|
|
|
|
|
silverWeight: number;
|
|
|
|
|
|
silverMali: number;
|
|
|
|
|
|
silverCommission: number;
|
|
|
|
|
|
investWeight: number;
|
|
|
|
|
|
investMali: number;
|
|
|
|
|
|
investCommission: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SalesInternetOverviewDto {
|
|
|
|
|
|
orderCount: number;
|
|
|
|
|
|
returnCount: number;
|
|
|
|
|
|
netGold: number;
|
|
|
|
|
|
netSilver: number;
|
|
|
|
|
|
returnGold: number;
|
|
|
|
|
|
returnSilver: number;
|
|
|
|
|
|
netMali: number;
|
|
|
|
|
|
returnMali: number;
|
|
|
|
|
|
linkedSkipped: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TahesabSalesOverviewDto {
|
|
|
|
|
|
factors: SalesFactorOverviewDto[];
|
|
|
|
|
|
karsakhte: SalesNamedMovementDto[];
|
|
|
|
|
|
groups: SalesGroupOverviewDto[];
|
|
|
|
|
|
experts: SalesExpertOverviewDto[];
|
|
|
|
|
|
internet: SalesInternetOverviewDto;
|
|
|
|
|
|
distinctNos: string[];
|
|
|
|
|
|
otherNos: string[];
|
|
|
|
|
|
percentReady: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-24 12:34:02 +03:30
|
|
|
|
/** تنظیمات ظاهر فاکتور PDF اسناد تهحساب (جدا از فاکتور CRM) */
|
|
|
|
|
|
export interface TahesabInvoiceSettingsDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
organizationId: string;
|
|
|
|
|
|
logoDataUrl: string | null;
|
|
|
|
|
|
companyName: string;
|
|
|
|
|
|
companySubtitle: string;
|
|
|
|
|
|
address: string;
|
|
|
|
|
|
phone: string;
|
|
|
|
|
|
headerText: string;
|
|
|
|
|
|
footerText: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateTahesabInvoiceSettingsRequest {
|
|
|
|
|
|
logoDataUrl?: string | null;
|
|
|
|
|
|
companyName?: string;
|
|
|
|
|
|
companySubtitle?: string;
|
|
|
|
|
|
address?: string;
|
|
|
|
|
|
phone?: string;
|
|
|
|
|
|
headerText?: string;
|
|
|
|
|
|
footerText?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface MandehValueDto {
|
|
|
|
|
|
amount: string | number;
|
|
|
|
|
|
unit: string;
|
2026-09-27 21:26:56 +03:30
|
|
|
|
/** بس = بستانکار | بد = بدهکار */
|
|
|
|
|
|
status: "بس" | "بد" | string;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MandehDetailItemDto {
|
|
|
|
|
|
Name: string;
|
|
|
|
|
|
values: MandehValueDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-08 13:49:54 +03:30
|
|
|
|
/** یک فیلد سطح بالای پاسخ GetMandeHesabByCode، دقیقاً همان مقدار تهحساب */
|
|
|
|
|
|
export interface TahesabMandehFieldDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
value: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** یک ردیف details پاسخ مانده، بدون هیچ تبدیل */
|
|
|
|
|
|
export interface TahesabMandehDetailRawDto {
|
|
|
|
|
|
Name: string;
|
|
|
|
|
|
Value: string | null;
|
|
|
|
|
|
Value1: string | null;
|
|
|
|
|
|
Value2: string | null;
|
|
|
|
|
|
Value3: string | null;
|
|
|
|
|
|
Value4: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface PersonAssetsDto {
|
|
|
|
|
|
connectionId: string;
|
|
|
|
|
|
connectionLabel: string;
|
|
|
|
|
|
dbName: string;
|
|
|
|
|
|
moshtariCode: number;
|
2026-10-08 13:49:54 +03:30
|
|
|
|
/** فیلدهای خام تهحساب به همان ترتیب پاسخ */
|
|
|
|
|
|
mandehFields?: TahesabMandehFieldDto[];
|
|
|
|
|
|
mandehDetailRaw?: TahesabMandehDetailRawDto[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
goldBalance: number | null;
|
|
|
|
|
|
rialBalance: number | null;
|
|
|
|
|
|
currencyBalance: number | null;
|
|
|
|
|
|
totalGoldEquiv: number | null;
|
|
|
|
|
|
mandehDetail: MandehDetailItemDto[];
|
|
|
|
|
|
fetchedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
// —— پیام خودکار (جدا از اتوماسیون گردش کار) ——
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type MessageAutomationTrigger =
|
|
|
|
|
|
| "person_created"
|
|
|
|
|
|
| "lead_submitted"
|
|
|
|
|
|
| "deal_created"
|
|
|
|
|
|
| "deal_stage_changed"
|
|
|
|
|
|
| "deal_won"
|
|
|
|
|
|
| "deal_lost"
|
|
|
|
|
|
| "ticket_created"
|
|
|
|
|
|
| "ticket_resolved"
|
|
|
|
|
|
| "invoice_issued"
|
|
|
|
|
|
| "invoice_paid"
|
|
|
|
|
|
| "activity_created"
|
2026-10-01 15:59:18 +03:30
|
|
|
|
| "activity_done"
|
|
|
|
|
|
| "sms_link_clicked";
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type MessageAutomationDelayUnit =
|
|
|
|
|
|
| "minutes"
|
|
|
|
|
|
| "hours"
|
|
|
|
|
|
| "days"
|
|
|
|
|
|
| "weeks"
|
|
|
|
|
|
| "months";
|
|
|
|
|
|
export type MessageAutomationChannel = "app" | "sms" | "both";
|
|
|
|
|
|
export type MessageAutomationTarget = "staff" | "person" | "both";
|
|
|
|
|
|
|
|
|
|
|
|
export const MESSAGE_AUTOMATION_TRIGGERS = [
|
|
|
|
|
|
"person_created",
|
|
|
|
|
|
"lead_submitted",
|
|
|
|
|
|
"deal_created",
|
|
|
|
|
|
"deal_stage_changed",
|
|
|
|
|
|
"deal_won",
|
|
|
|
|
|
"deal_lost",
|
|
|
|
|
|
"ticket_created",
|
|
|
|
|
|
"ticket_resolved",
|
|
|
|
|
|
"invoice_issued",
|
|
|
|
|
|
"invoice_paid",
|
|
|
|
|
|
"activity_created",
|
|
|
|
|
|
"activity_done",
|
2026-10-01 15:59:18 +03:30
|
|
|
|
"sms_link_clicked",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
] as const satisfies readonly MessageAutomationTrigger[];
|
|
|
|
|
|
|
|
|
|
|
|
export const MESSAGE_AUTOMATION_TRIGGER_LABELS: Record<
|
|
|
|
|
|
MessageAutomationTrigger,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
person_created: "مخاطب جدید ساخته شد",
|
|
|
|
|
|
lead_submitted: "فرم سرنخ ارسال شد",
|
|
|
|
|
|
deal_created: "معامله جدید ساخته شد",
|
|
|
|
|
|
deal_stage_changed: "مرحله معامله عوض شد",
|
|
|
|
|
|
deal_won: "معامله برنده شد",
|
|
|
|
|
|
deal_lost: "معامله باخت",
|
|
|
|
|
|
ticket_created: "تیکت جدید ساخته شد",
|
|
|
|
|
|
ticket_resolved: "تیکت حل شد",
|
|
|
|
|
|
invoice_issued: "فاکتور صادر شد",
|
|
|
|
|
|
invoice_paid: "پرداخت روی فاکتور ثبت شد",
|
|
|
|
|
|
activity_created: "فعالیت ثبت شد",
|
|
|
|
|
|
activity_done: "فعالیت تمام شد",
|
2026-10-01 15:59:18 +03:30
|
|
|
|
sms_link_clicked: "کلیک روی لینک پیامک",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const MESSAGE_AUTOMATION_UNIT_LABELS: Record<
|
|
|
|
|
|
MessageAutomationDelayUnit,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
minutes: "دقیقه",
|
|
|
|
|
|
hours: "ساعت",
|
|
|
|
|
|
days: "روز",
|
|
|
|
|
|
weeks: "هفته",
|
|
|
|
|
|
months: "ماه",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const MESSAGE_AUTOMATION_FIELDS = [
|
2026-09-28 16:18:54 +03:30
|
|
|
|
{ token: "{نام}", label: "نام مشتری" },
|
|
|
|
|
|
{ token: "{شماره}", label: "شماره موبایل" },
|
|
|
|
|
|
{ token: "{تاریخ}", label: "تاریخ ارسال" },
|
|
|
|
|
|
{ token: "{عنوان}", label: "عنوان رکورد" },
|
|
|
|
|
|
{ token: "{شرکت}", label: "نام شرکت" },
|
2026-09-27 21:17:42 +03:30
|
|
|
|
] as const;
|
|
|
|
|
|
|
2026-10-19 15:55:48 +03:30
|
|
|
|
/** First-part Persian SMS limit (legacy UI estimate). Prefer countSmsParts(). */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export const MESSAGE_AUTOMATION_SMS_SEGMENT = 70;
|
|
|
|
|
|
|
2026-10-19 15:55:48 +03:30
|
|
|
|
/** Hamkar-compatible Persian multipart limits: 70 / 64 / 67 */
|
|
|
|
|
|
export function countSmsParts(text: string): number {
|
|
|
|
|
|
const length = [...String(text ?? "").trim()].length;
|
|
|
|
|
|
if (length <= 0) return 0;
|
|
|
|
|
|
const first = 70;
|
|
|
|
|
|
const second = 64;
|
|
|
|
|
|
const rest = 67;
|
|
|
|
|
|
if (length <= first) return 1;
|
|
|
|
|
|
if (length <= first + second) return 2;
|
|
|
|
|
|
const thirdThreshold = first + second + rest;
|
|
|
|
|
|
if (length <= thirdThreshold) return 3;
|
|
|
|
|
|
return 3 + Math.ceil((length - thirdThreshold) / rest);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export const MESSAGE_AUTOMATION_CHANNEL_LABELS: Record<
|
|
|
|
|
|
MessageAutomationChannel,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
app: "ناتیف داخل برنامه",
|
|
|
|
|
|
sms: "پیامک",
|
|
|
|
|
|
both: "پیامک و ناتیف",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const MESSAGE_AUTOMATION_TARGET_LABELS: Record<
|
|
|
|
|
|
MessageAutomationTarget,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
staff: "مسئول پرونده",
|
|
|
|
|
|
person: "مشتری",
|
|
|
|
|
|
both: "مشتری و مسئول",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface MessageAutomationRuleDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
trigger: MessageAutomationTrigger;
|
|
|
|
|
|
startDelayValue: number;
|
|
|
|
|
|
startDelayUnit: MessageAutomationDelayUnit;
|
|
|
|
|
|
repeatCount: number;
|
|
|
|
|
|
intervalValue: number;
|
|
|
|
|
|
intervalUnit: MessageAutomationDelayUnit;
|
|
|
|
|
|
notifyTarget: MessageAutomationTarget;
|
|
|
|
|
|
channel: MessageAutomationChannel;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
description: string | null;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateMessageAutomationRuleRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
trigger: MessageAutomationTrigger;
|
|
|
|
|
|
startDelayValue?: number;
|
|
|
|
|
|
startDelayUnit?: MessageAutomationDelayUnit;
|
|
|
|
|
|
repeatCount?: number;
|
|
|
|
|
|
intervalValue?: number;
|
|
|
|
|
|
intervalUnit?: MessageAutomationDelayUnit;
|
|
|
|
|
|
notifyTarget?: MessageAutomationTarget;
|
|
|
|
|
|
channel?: MessageAutomationChannel;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
description?: string | null;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateMessageAutomationRuleRequest =
|
|
|
|
|
|
Partial<CreateMessageAutomationRuleRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformAdminDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
mobile?: string | null;
|
|
|
|
|
|
email?: string | null;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
lastLoginAt?: string | null;
|
|
|
|
|
|
createdAt?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformLoginRequest {
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
password: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformLoginResponse {
|
|
|
|
|
|
accessToken: string;
|
|
|
|
|
|
admin: PlatformAdminDto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreatePlatformAdminRequest {
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
password: string;
|
|
|
|
|
|
email?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ResetPlatformAdminPasswordRequest {
|
|
|
|
|
|
password: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-02 19:13:57 +03:30
|
|
|
|
export interface UpdatePlatformAdminRequest {
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
email?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformMasterPasswordStatusDto {
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
updatedAt?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SetPlatformMasterPasswordRequest {
|
|
|
|
|
|
/** null یعنی رمز کلی برداشته شود */
|
|
|
|
|
|
password: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface PlatformStatsDto {
|
|
|
|
|
|
organizationCount: number;
|
|
|
|
|
|
activeOrganizationCount: number;
|
|
|
|
|
|
userCount: number;
|
|
|
|
|
|
activeUserCount: number;
|
|
|
|
|
|
membershipCount: number;
|
|
|
|
|
|
expiringSoonCount?: number;
|
|
|
|
|
|
expiredBillingCount?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformOrgMemberDto {
|
|
|
|
|
|
membershipId: string;
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
mobile?: string | null;
|
|
|
|
|
|
role: UserRole;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
userIsActive: boolean;
|
|
|
|
|
|
isBillingOwner?: boolean;
|
|
|
|
|
|
billing?: BillingAccountDto | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformOrganizationDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
slug: string;
|
|
|
|
|
|
businessField?: string | null;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
memberCount: number;
|
|
|
|
|
|
personCount: number;
|
|
|
|
|
|
dealCount: number;
|
|
|
|
|
|
enabledModules?: OrgModuleKey[];
|
2026-10-06 19:54:38 +03:30
|
|
|
|
/** اشتراک مالک اصلی محیط */
|
|
|
|
|
|
subscription?: SubscriptionSummaryDto | null;
|
2026-11-02 19:13:57 +03:30
|
|
|
|
ownerUserId?: string | null;
|
|
|
|
|
|
ownerName?: string | null;
|
|
|
|
|
|
chatConfigured?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
members?: PlatformOrgMemberDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformUserMembershipDto {
|
|
|
|
|
|
membershipId: string;
|
|
|
|
|
|
organizationId: string;
|
|
|
|
|
|
organizationName: string;
|
|
|
|
|
|
organizationIsActive: boolean;
|
|
|
|
|
|
role: UserRole;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformUserDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
mobile?: string | null;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
lastLoginAt?: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
memberships: PlatformUserMembershipDto[];
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** فقط اگر حداقل یک عضویت مالک فعال دارد */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
isBillingOwner?: boolean;
|
|
|
|
|
|
billing?: BillingAccountDto | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdatePlatformStatusRequest {
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreatePlatformOrganizationRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
businessField?: string;
|
|
|
|
|
|
ownerFullName: string;
|
|
|
|
|
|
ownerEmail: string;
|
|
|
|
|
|
ownerPassword: string;
|
|
|
|
|
|
ownerMobile?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdatePlatformOrganizationRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
slug?: string;
|
|
|
|
|
|
businessField?: string | null;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdatePlatformMembershipRoleRequest {
|
|
|
|
|
|
role: UserRole;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ResetPlatformUserPasswordRequest {
|
|
|
|
|
|
password: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export { brand, APP_NAME, APP_NAME_FA, brandSuggestionFa } from "./brand";
|
|
|
|
|
|
export type { Brand } from "./brand";
|
|
|
|
|
|
export const API_PREFIX = "api";
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— لاگ فعالیت کاربران ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export interface AuditLogDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
action: string;
|
|
|
|
|
|
method: string;
|
|
|
|
|
|
path: string;
|
|
|
|
|
|
entityType: string | null;
|
|
|
|
|
|
entityId: string | null;
|
|
|
|
|
|
summary: string;
|
2026-10-14 19:47:32 +03:30
|
|
|
|
/** نام خوانای پرونده، مثل عنوان فعالیت یا نام مشتری */
|
|
|
|
|
|
label?: string | null;
|
|
|
|
|
|
/** چه چیزی ساخته یا عوض شده */
|
|
|
|
|
|
detail?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
actor: UserSummaryDto | null;
|
|
|
|
|
|
ip: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AuditLogListResponse {
|
|
|
|
|
|
items: AuditLogDto[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
limit: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-13 22:23:09 +03:30
|
|
|
|
export type DeletionRetention =
|
|
|
|
|
|
| 'month'
|
|
|
|
|
|
| 'two_months'
|
|
|
|
|
|
| 'three_months'
|
|
|
|
|
|
| 'six_months'
|
|
|
|
|
|
| 'year'
|
|
|
|
|
|
| 'never';
|
|
|
|
|
|
|
|
|
|
|
|
export interface DeletionArchiveDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
model: string;
|
|
|
|
|
|
modelLabel: string;
|
|
|
|
|
|
entityId: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
deletedById: string | null;
|
|
|
|
|
|
deletedByName: string | null;
|
|
|
|
|
|
deletedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DeletionArchiveListResponse {
|
|
|
|
|
|
items: DeletionArchiveDto[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
limit: number;
|
|
|
|
|
|
retention: DeletionRetention;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— فیلترهای ذخیرهشده ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type SavedViewScope =
|
|
|
|
|
|
| "deals"
|
|
|
|
|
|
| "people"
|
|
|
|
|
|
| "companies"
|
|
|
|
|
|
| "activities"
|
|
|
|
|
|
| "project_cards"
|
|
|
|
|
|
| "reports";
|
|
|
|
|
|
|
|
|
|
|
|
export const SAVED_VIEW_SCOPES: SavedViewScope[] = [
|
|
|
|
|
|
"deals",
|
|
|
|
|
|
"people",
|
|
|
|
|
|
"companies",
|
|
|
|
|
|
"activities",
|
|
|
|
|
|
"project_cards",
|
|
|
|
|
|
"reports",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** فیلترهای ذخیرهشدهٔ صفحه گزارشات */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface ReportsViewFilters {
|
|
|
|
|
|
/** today | week | month | quarter | year | custom */
|
|
|
|
|
|
period: string;
|
|
|
|
|
|
from?: string;
|
|
|
|
|
|
to?: string;
|
|
|
|
|
|
/** dashboard | builder */
|
|
|
|
|
|
mode?: string;
|
|
|
|
|
|
builder?: {
|
|
|
|
|
|
metric?: string;
|
|
|
|
|
|
dimension?: string;
|
|
|
|
|
|
userId?: string;
|
|
|
|
|
|
teamId?: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** متریکهای گزارشساز ساده */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type ReportsBuilderMetric =
|
|
|
|
|
|
| "won_amount"
|
|
|
|
|
|
| "won_count"
|
|
|
|
|
|
| "lost_count"
|
|
|
|
|
|
| "open_count"
|
|
|
|
|
|
| "open_amount"
|
|
|
|
|
|
| "activities_done"
|
|
|
|
|
|
| "people_created"
|
|
|
|
|
|
| "invoice_amount"
|
|
|
|
|
|
| "invoice_count";
|
|
|
|
|
|
|
|
|
|
|
|
export const REPORTS_BUILDER_METRICS: ReportsBuilderMetric[] = [
|
|
|
|
|
|
"won_amount",
|
|
|
|
|
|
"won_count",
|
|
|
|
|
|
"lost_count",
|
|
|
|
|
|
"open_count",
|
|
|
|
|
|
"open_amount",
|
|
|
|
|
|
"activities_done",
|
|
|
|
|
|
"people_created",
|
|
|
|
|
|
"invoice_amount",
|
|
|
|
|
|
"invoice_count",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const REPORTS_BUILDER_METRIC_LABELS: Record<
|
|
|
|
|
|
ReportsBuilderMetric,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
won_amount: "مبلغ معاملات برنده",
|
|
|
|
|
|
won_count: "تعداد معاملات برنده",
|
|
|
|
|
|
lost_count: "تعداد معاملات باخته",
|
|
|
|
|
|
open_count: "تعداد معاملات باز",
|
|
|
|
|
|
open_amount: "مبلغ معاملات باز",
|
|
|
|
|
|
activities_done: "فعالیت انجامشده",
|
|
|
|
|
|
people_created: "مشتریان جدید",
|
|
|
|
|
|
invoice_amount: "مبلغ فاکتور",
|
|
|
|
|
|
invoice_count: "تعداد فاکتور",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** ابعاد گروهبندی گزارشساز */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export type ReportsBuilderDimension =
|
|
|
|
|
|
| "none"
|
|
|
|
|
|
| "owner"
|
|
|
|
|
|
| "team"
|
|
|
|
|
|
| "source"
|
|
|
|
|
|
| "stage"
|
|
|
|
|
|
| "month"
|
|
|
|
|
|
| "status";
|
|
|
|
|
|
|
|
|
|
|
|
export const REPORTS_BUILDER_DIMENSIONS: ReportsBuilderDimension[] = [
|
|
|
|
|
|
"none",
|
|
|
|
|
|
"owner",
|
|
|
|
|
|
"team",
|
|
|
|
|
|
"source",
|
|
|
|
|
|
"stage",
|
|
|
|
|
|
"month",
|
|
|
|
|
|
"status",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const REPORTS_BUILDER_DIMENSION_LABELS: Record<
|
|
|
|
|
|
ReportsBuilderDimension,
|
|
|
|
|
|
string
|
|
|
|
|
|
> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
none: "بدون گروهبندی (جمع کل)",
|
|
|
|
|
|
owner: "کاربر / مسئول",
|
|
|
|
|
|
team: "تیم",
|
|
|
|
|
|
source: "منبع",
|
2026-10-06 20:51:50 +03:30
|
|
|
|
stage: "مرحله مسیر فروش",
|
2026-09-28 16:18:54 +03:30
|
|
|
|
month: "ماه",
|
|
|
|
|
|
status: "وضعیت معامله",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsBuilderQuery {
|
|
|
|
|
|
metric: ReportsBuilderMetric;
|
|
|
|
|
|
dimension: ReportsBuilderDimension;
|
|
|
|
|
|
from?: string;
|
|
|
|
|
|
to?: string;
|
|
|
|
|
|
userId?: string;
|
|
|
|
|
|
teamId?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsBuilderRowDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReportsBuilderResultDto {
|
|
|
|
|
|
metric: ReportsBuilderMetric;
|
|
|
|
|
|
metricLabel: string;
|
|
|
|
|
|
dimension: ReportsBuilderDimension;
|
|
|
|
|
|
dimensionLabel: string;
|
|
|
|
|
|
from: string;
|
|
|
|
|
|
to: string;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
format: "money" | "number";
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
rows: ReportsBuilderRowDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SavedViewDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
scope: SavedViewScope;
|
|
|
|
|
|
filters: Record<string, unknown>;
|
|
|
|
|
|
isShared: boolean;
|
|
|
|
|
|
owner: UserSummaryDto | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateSavedViewRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
scope: SavedViewScope;
|
|
|
|
|
|
filters: Record<string, unknown>;
|
|
|
|
|
|
isShared?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateSavedViewRequest = Partial<CreateSavedViewRequest>;
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— API عمومی و وبهوک ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export const WEBHOOK_EVENTS = [
|
|
|
|
|
|
"person.created",
|
|
|
|
|
|
"person.updated",
|
|
|
|
|
|
"deal.created",
|
|
|
|
|
|
"deal.updated",
|
|
|
|
|
|
"deal.won",
|
|
|
|
|
|
"deal.lost",
|
|
|
|
|
|
"invoice.paid",
|
|
|
|
|
|
"activity.created",
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type WebhookEvent = (typeof WEBHOOK_EVENTS)[number];
|
|
|
|
|
|
|
|
|
|
|
|
export interface OrganizationApiKeyDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
prefix: string;
|
|
|
|
|
|
lastUsedAt: string | null;
|
|
|
|
|
|
revokedAt: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateOrganizationApiKeyRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateOrganizationApiKeyResponse extends OrganizationApiKeyDto {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** فقط یکبار هنگام ساخت برگردانده میشود */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
rawKey: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface WebhookEndpointDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
url: string;
|
|
|
|
|
|
events: string[];
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateWebhookEndpointRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
url: string;
|
|
|
|
|
|
events: string[];
|
|
|
|
|
|
secret?: string;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** فقط هنگام ساخت؛ secret دوباره برنمیگردد */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface CreateWebhookEndpointResponse extends WebhookEndpointDto {
|
|
|
|
|
|
secret: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateWebhookEndpointRequest = Partial<CreateWebhookEndpointRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface WebhookDeliveryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
event: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
statusCode: number | null;
|
|
|
|
|
|
attempts: number;
|
|
|
|
|
|
lastError: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
deliveredAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-17 21:23:07 +03:30
|
|
|
|
/* ——— لاگ پیامک خروجی ——— */
|
|
|
|
|
|
|
|
|
|
|
|
export type OutboundSmsSource = "manual" | "campaign" | "automation";
|
|
|
|
|
|
export type OutboundSmsStatus = "sent" | "failed";
|
|
|
|
|
|
|
|
|
|
|
|
export const OUTBOUND_SMS_SOURCE_LABELS: Record<OutboundSmsSource, string> = {
|
|
|
|
|
|
manual: "دستی",
|
|
|
|
|
|
campaign: "کمپین",
|
|
|
|
|
|
automation: "اتوماسیون",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface OutboundSmsLogDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
source: OutboundSmsSource;
|
|
|
|
|
|
sourceLabel: string;
|
|
|
|
|
|
status: OutboundSmsStatus;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface OutboundSmsHistoryResponse {
|
|
|
|
|
|
items: OutboundSmsLogDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— پیامک گروهی ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type SmsCampaignStatus =
|
|
|
|
|
|
| "draft"
|
|
|
|
|
|
| "queued"
|
|
|
|
|
|
| "sending"
|
|
|
|
|
|
| "done"
|
|
|
|
|
|
| "cancelled";
|
|
|
|
|
|
|
|
|
|
|
|
export interface SmsCampaignDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
status: SmsCampaignStatus;
|
|
|
|
|
|
scheduledAt: string | null;
|
|
|
|
|
|
totalCount: number;
|
|
|
|
|
|
sentCount: number;
|
|
|
|
|
|
failedCount: number;
|
|
|
|
|
|
skippedCount: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-17 20:54:32 +03:30
|
|
|
|
/** حداکثر گیرنده در هر درخواست ساخت/افزودن کمپین پیامک */
|
|
|
|
|
|
export const SMS_CAMPAIGN_CHUNK_SIZE = 50;
|
|
|
|
|
|
|
|
|
|
|
|
/** سقف شناسههای قابل دریافت از /people/ids و /leads/ids */
|
|
|
|
|
|
export const FILTERED_IDS_MAX = 50_000;
|
|
|
|
|
|
|
|
|
|
|
|
export interface FilteredIdsResponse {
|
|
|
|
|
|
ids: string[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface CreateSmsCampaignRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
body: string;
|
2026-10-17 20:54:32 +03:30
|
|
|
|
/** دقیقاً یکی از personIds یا leadIds */
|
|
|
|
|
|
personIds?: string[];
|
|
|
|
|
|
leadIds?: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
scheduledAt?: string | null;
|
2026-10-17 20:54:32 +03:30
|
|
|
|
/** اگر true باشد بلافاصله صف میشود (فقط وقتی همه گیرندهها در همین درخواست باشند یا بعد از افزودن تکهها جداگانه queue شود) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
queueNow?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-17 20:54:32 +03:30
|
|
|
|
export interface AddSmsCampaignRecipientsRequest {
|
|
|
|
|
|
personIds?: string[];
|
|
|
|
|
|
leadIds?: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface SmsCampaignListResponse {
|
|
|
|
|
|
items: SmsCampaignDto[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-01 15:59:18 +03:30
|
|
|
|
export type SmsCampaignContactType = "customer" | "lead";
|
|
|
|
|
|
|
|
|
|
|
|
export interface SmsCampaignClickerDto {
|
|
|
|
|
|
contactType: SmsCampaignContactType;
|
|
|
|
|
|
personId: string | null;
|
|
|
|
|
|
leadId: string | null;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
mobile: string | null;
|
|
|
|
|
|
firstClickedAt: string | null;
|
|
|
|
|
|
lastClickedAt: string | null;
|
|
|
|
|
|
clickCount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SmsCampaignReportDto {
|
|
|
|
|
|
campaign: SmsCampaignDto;
|
|
|
|
|
|
totalCount: number;
|
|
|
|
|
|
sentCount: number;
|
|
|
|
|
|
failedCount: number;
|
|
|
|
|
|
skippedCount: number;
|
|
|
|
|
|
uniqueClickers: number;
|
|
|
|
|
|
totalClicks: number;
|
|
|
|
|
|
/** درصد یکتا نسبت به ارسالشده */
|
|
|
|
|
|
clickRate: number;
|
|
|
|
|
|
multiClickers: number;
|
|
|
|
|
|
clickedUsers: SmsCampaignClickerDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— تیمها و سطح دسترسی به پروندهها ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type TeamMemberRole = "member" | "manager";
|
|
|
|
|
|
|
|
|
|
|
|
export interface TeamMemberDto {
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
role: TeamMemberRole;
|
|
|
|
|
|
hasAvatar?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TeamDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
memberCount: number;
|
|
|
|
|
|
members: TeamMemberDto[];
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateTeamRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
memberIds?: string[];
|
|
|
|
|
|
managerIds?: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateTeamRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SetTeamMembersRequest {
|
|
|
|
|
|
members: Array<{ userId: string; role: TeamMemberRole }>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— ووکامرس ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export interface WooCommerceConnectionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
storeUrl: string;
|
|
|
|
|
|
consumerKeyMasked: string;
|
|
|
|
|
|
webhookSecretSet: boolean;
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
lastSyncAt: string | null;
|
|
|
|
|
|
lastHealthAt: string | null;
|
|
|
|
|
|
lastHealthOk: boolean | null;
|
|
|
|
|
|
lastError: string | null;
|
|
|
|
|
|
importedOrders: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateWooCommerceConnectionRequest {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
storeUrl: string;
|
|
|
|
|
|
consumerKey: string;
|
|
|
|
|
|
consumerSecret: string;
|
|
|
|
|
|
webhookSecret?: string;
|
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateWooCommerceConnectionRequest {
|
|
|
|
|
|
label?: string;
|
|
|
|
|
|
storeUrl?: string;
|
|
|
|
|
|
consumerKey?: string;
|
|
|
|
|
|
consumerSecret?: string;
|
|
|
|
|
|
webhookSecret?: string | null;
|
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface WooCommerceSyncResultDto {
|
|
|
|
|
|
fetched: number;
|
|
|
|
|
|
created: number;
|
|
|
|
|
|
updated: number;
|
|
|
|
|
|
skipped: number;
|
|
|
|
|
|
errors: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface WooCommerceOrderLinkDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
wooOrderId: string;
|
|
|
|
|
|
wooOrderNumber: string | null;
|
|
|
|
|
|
invoiceId: string;
|
|
|
|
|
|
invoiceNumber: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
syncedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 15:59:12 +03:30
|
|
|
|
/* ——— فروشگاههای Montify ——— */
|
|
|
|
|
|
|
|
|
|
|
|
export type MontifySyncStatus = "idle" | "running" | "ok" | "error";
|
|
|
|
|
|
|
|
|
|
|
|
export interface MontifySyncResultDto {
|
|
|
|
|
|
mode: "incremental" | "full";
|
|
|
|
|
|
customersFetched: number;
|
|
|
|
|
|
customersCreated: number;
|
|
|
|
|
|
customersUpdated: number;
|
|
|
|
|
|
ordersFetched: number;
|
|
|
|
|
|
transactionsFetched: number;
|
|
|
|
|
|
errors: string[];
|
|
|
|
|
|
startedAt: string;
|
|
|
|
|
|
finishedAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MontifyConnectionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
baseUrl: string;
|
|
|
|
|
|
storeId: string;
|
|
|
|
|
|
tokenMasked: string;
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
webhookRegistered: boolean;
|
|
|
|
|
|
webhookUrl: string;
|
|
|
|
|
|
syncStatus: MontifySyncStatus;
|
|
|
|
|
|
syncStartedAt: string | null;
|
|
|
|
|
|
lastSyncAt: string | null;
|
|
|
|
|
|
lastSyncResult: MontifySyncResultDto | null;
|
|
|
|
|
|
lastHealthAt: string | null;
|
|
|
|
|
|
lastHealthOk: boolean | null;
|
|
|
|
|
|
lastError: string | null;
|
|
|
|
|
|
lastWebhookAt: string | null;
|
|
|
|
|
|
linkedCustomers: number;
|
|
|
|
|
|
orders: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateMontifyConnectionRequest {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
baseUrl: string;
|
|
|
|
|
|
token: string;
|
|
|
|
|
|
storeId?: string;
|
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateMontifyConnectionRequest = Partial<CreateMontifyConnectionRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface MontifyTestResultDto {
|
|
|
|
|
|
ok: boolean;
|
|
|
|
|
|
totalCustomers: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 18:01:55 +03:30
|
|
|
|
export interface MontifyDiagnoseCheckDto {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
path: string;
|
|
|
|
|
|
status: number;
|
|
|
|
|
|
ok: boolean;
|
|
|
|
|
|
info: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MontifyDiagnoseDto {
|
|
|
|
|
|
customerId: string | null;
|
|
|
|
|
|
checks: MontifyDiagnoseCheckDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 15:59:12 +03:30
|
|
|
|
export interface MontifyOrderItemDto {
|
|
|
|
|
|
productId: string | null;
|
|
|
|
|
|
productName: string | null;
|
|
|
|
|
|
qty: number | null;
|
|
|
|
|
|
price: number | null;
|
|
|
|
|
|
weight: number | null;
|
|
|
|
|
|
totalPrice: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** سفارش فروشگاه — orderReference = code، orderStatus = sts */
|
|
|
|
|
|
export interface MontifyOrderDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
connectionId: string;
|
|
|
|
|
|
connectionLabel: string;
|
|
|
|
|
|
externalId: string;
|
|
|
|
|
|
customerExternalId: string | null;
|
|
|
|
|
|
personId: string | null;
|
|
|
|
|
|
personName: string | null;
|
|
|
|
|
|
orderReference: string | null;
|
|
|
|
|
|
orderStatus: string | null;
|
|
|
|
|
|
paymentStatus: string | null;
|
|
|
|
|
|
paymentType: string | null;
|
|
|
|
|
|
totalPrice: number | null;
|
|
|
|
|
|
discountTotalPrice: number | null;
|
|
|
|
|
|
totalDiscounts: number | null;
|
|
|
|
|
|
totalQty: number | null;
|
|
|
|
|
|
sumGold: number | null;
|
|
|
|
|
|
sumSilver: number | null;
|
|
|
|
|
|
note: string | null;
|
|
|
|
|
|
cancelReason: string | null;
|
|
|
|
|
|
cancelledAt: string | null;
|
|
|
|
|
|
closedAt: string | null;
|
|
|
|
|
|
items: MontifyOrderItemDto[];
|
|
|
|
|
|
createdAt: string | null;
|
|
|
|
|
|
updatedAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MontifyTransactionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
externalId: string;
|
|
|
|
|
|
type: string | null;
|
|
|
|
|
|
amount: number | null;
|
|
|
|
|
|
currency: string | null;
|
|
|
|
|
|
status: string | null;
|
|
|
|
|
|
referenceCode: string | null;
|
|
|
|
|
|
createdAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MontifyEngagementDto {
|
|
|
|
|
|
saved: number;
|
|
|
|
|
|
likes: number;
|
|
|
|
|
|
comments: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** پروفایل فروشگاهی یک شخص (wallet_balance / gold_wallet_balance طبق نگاشت مستند) */
|
|
|
|
|
|
export interface MontifyPersonStoreDto {
|
|
|
|
|
|
connectionId: string;
|
|
|
|
|
|
connectionLabel: string;
|
|
|
|
|
|
externalCustomerId: string;
|
|
|
|
|
|
fullName: string | null;
|
|
|
|
|
|
username: string | null;
|
|
|
|
|
|
customerType: string | null;
|
|
|
|
|
|
walletBalance: number | null;
|
|
|
|
|
|
goldWalletBalance: number | null;
|
|
|
|
|
|
totalOrders: number | null;
|
|
|
|
|
|
totalSpent: number | null;
|
|
|
|
|
|
engagement: MontifyEngagementDto | null;
|
|
|
|
|
|
orders: MontifyOrderDto[];
|
|
|
|
|
|
transactions: MontifyTransactionDto[];
|
|
|
|
|
|
syncedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— تلفن سازمانی / VoIP ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type VoipCallDirection = "inbound" | "outbound";
|
|
|
|
|
|
|
|
|
|
|
|
export type VoipCallStatus =
|
|
|
|
|
|
| "ringing"
|
|
|
|
|
|
| "answered"
|
|
|
|
|
|
| "missed"
|
|
|
|
|
|
| "completed"
|
|
|
|
|
|
| "failed";
|
|
|
|
|
|
|
2026-11-07 12:31:59 +03:30
|
|
|
|
export type VoipProvider = "generic" | "daftareshoma";
|
|
|
|
|
|
|
2026-09-27 21:17:42 +03:30
|
|
|
|
export interface VoipConnectionDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
label: string;
|
2026-11-07 12:31:59 +03:30
|
|
|
|
provider: VoipProvider;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
webhookSecretSet: boolean;
|
2026-11-07 12:31:59 +03:30
|
|
|
|
apiTokenSet: boolean;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** قالب URL برای شروع تماس؛ {number} جایگزین میشود */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
clickToCallUrl: string | null;
|
2026-11-07 12:31:59 +03:30
|
|
|
|
/** شماره مبدأ تماس خروجی (دفتر شما) */
|
|
|
|
|
|
fromNumber: string | null;
|
|
|
|
|
|
/** خط/داخلی سازمان (دفتر شما) */
|
|
|
|
|
|
callerExtension: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
lastEventAt: string | null;
|
|
|
|
|
|
lastError: string | null;
|
|
|
|
|
|
callCount: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateVoipConnectionRequest {
|
|
|
|
|
|
label: string;
|
2026-11-07 12:31:59 +03:30
|
|
|
|
provider?: VoipProvider;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
webhookSecret?: string;
|
|
|
|
|
|
clickToCallUrl?: string | null;
|
2026-11-07 12:31:59 +03:30
|
|
|
|
/** توکن Bearer API دفتر شما */
|
|
|
|
|
|
apiToken?: string;
|
|
|
|
|
|
fromNumber?: string | null;
|
|
|
|
|
|
callerExtension?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateVoipConnectionRequest {
|
|
|
|
|
|
label?: string;
|
|
|
|
|
|
webhookSecret?: string | null;
|
|
|
|
|
|
clickToCallUrl?: string | null;
|
2026-11-07 12:31:59 +03:30
|
|
|
|
apiToken?: string | null;
|
|
|
|
|
|
fromNumber?: string | null;
|
|
|
|
|
|
callerExtension?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type VoipSummaryStatus =
|
|
|
|
|
|
| "none"
|
|
|
|
|
|
| "pending"
|
|
|
|
|
|
| "processing"
|
|
|
|
|
|
| "done"
|
|
|
|
|
|
| "failed"
|
|
|
|
|
|
| "skipped";
|
|
|
|
|
|
|
|
|
|
|
|
export interface VoipCallLogDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
direction: VoipCallDirection;
|
|
|
|
|
|
status: VoipCallStatus | string;
|
|
|
|
|
|
fromNumber: string | null;
|
|
|
|
|
|
toNumber: string | null;
|
|
|
|
|
|
personId: string | null;
|
|
|
|
|
|
personName: string | null;
|
|
|
|
|
|
activityId: string | null;
|
|
|
|
|
|
durationSec: number | null;
|
|
|
|
|
|
startedAt: string;
|
|
|
|
|
|
syncedAt: string;
|
|
|
|
|
|
recordingUrl: string | null;
|
|
|
|
|
|
transcript: string | null;
|
|
|
|
|
|
summary: string | null;
|
|
|
|
|
|
summaryStatus: VoipSummaryStatus | string;
|
|
|
|
|
|
summaryNoteId: string | null;
|
|
|
|
|
|
summaryError: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface VoipClickToCallRequest {
|
|
|
|
|
|
number: string;
|
|
|
|
|
|
personId?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface VoipWebhookEvent {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** شناسه یکتای تماس در سانترال */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
callId: string;
|
|
|
|
|
|
direction: VoipCallDirection;
|
|
|
|
|
|
status: VoipCallStatus | string;
|
|
|
|
|
|
from?: string;
|
|
|
|
|
|
to?: string;
|
|
|
|
|
|
startedAt?: string;
|
|
|
|
|
|
endedAt?: string;
|
|
|
|
|
|
durationSec?: number;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** لینک فایل ضبط (برای خلاصه خودکار) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
recordingUrl?: string;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** متن آمادهٔ تماس؛ اگر باشد STT رد میشود */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
transcript?: string;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** برای سازگاری با فیلدهای رایج دیگر */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
caller?: string;
|
|
|
|
|
|
callee?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— رزرو و نوبتدهی ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type ReservationStatus =
|
|
|
|
|
|
| "pending"
|
|
|
|
|
|
| "confirmed"
|
|
|
|
|
|
| "canceled"
|
|
|
|
|
|
| "done";
|
|
|
|
|
|
|
|
|
|
|
|
export const RESERVATION_STATUSES: ReservationStatus[] = [
|
|
|
|
|
|
"pending",
|
|
|
|
|
|
"confirmed",
|
|
|
|
|
|
"canceled",
|
|
|
|
|
|
"done",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export const RESERVATION_STATUS_LABELS: Record<ReservationStatus, string> = {
|
2026-09-28 16:18:54 +03:30
|
|
|
|
pending: "در انتظار",
|
|
|
|
|
|
confirmed: "تأیید شده",
|
|
|
|
|
|
canceled: "لغو شده",
|
|
|
|
|
|
done: "انجام شده",
|
2026-09-27 21:17:42 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReservationServiceDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
durationMinutes: number;
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/** روزهای هفته ۰=یکشنبه … ۶=شنبه (JS getDay) */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
workDays: number[];
|
|
|
|
|
|
dayStartHour: number;
|
|
|
|
|
|
dayEndHour: number;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateReservationServiceRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
durationMinutes: number;
|
|
|
|
|
|
workDays?: number[];
|
|
|
|
|
|
dayStartHour?: number;
|
|
|
|
|
|
dayEndHour?: number;
|
|
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type UpdateReservationServiceRequest =
|
|
|
|
|
|
Partial<CreateReservationServiceRequest>;
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReservationAppointmentDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
serviceId: string;
|
|
|
|
|
|
serviceName: string;
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
personName: string;
|
|
|
|
|
|
assigneeId: string | null;
|
|
|
|
|
|
assigneeName: string | null;
|
|
|
|
|
|
startsAt: string;
|
|
|
|
|
|
endsAt: string;
|
|
|
|
|
|
status: ReservationStatus;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateReservationAppointmentRequest {
|
|
|
|
|
|
serviceId: string;
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
assigneeId?: string | null;
|
|
|
|
|
|
startsAt: string;
|
|
|
|
|
|
status?: ReservationStatus;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateReservationAppointmentRequest {
|
|
|
|
|
|
serviceId?: string;
|
|
|
|
|
|
personId?: string;
|
|
|
|
|
|
assigneeId?: string | null;
|
|
|
|
|
|
startsAt?: string;
|
|
|
|
|
|
status?: ReservationStatus;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReservationSlotDto {
|
|
|
|
|
|
startsAt: string;
|
|
|
|
|
|
endsAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReservationListResponse {
|
|
|
|
|
|
items: ReservationAppointmentDto[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-28 16:18:54 +03:30
|
|
|
|
/* ——— مدیریت لیدها ——— */
|
2026-09-27 21:17:42 +03:30
|
|
|
|
|
|
|
|
|
|
export type LeadAssignRule = "round_robin" | "equal_split";
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadStageDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
color: string;
|
|
|
|
|
|
showOnBoard: boolean;
|
|
|
|
|
|
isConverted: boolean;
|
|
|
|
|
|
isLost: boolean;
|
|
|
|
|
|
isUnqualified: boolean;
|
|
|
|
|
|
leadCount?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateLeadStageRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
color?: string;
|
|
|
|
|
|
showOnBoard?: boolean;
|
|
|
|
|
|
isConverted?: boolean;
|
|
|
|
|
|
isLost?: boolean;
|
|
|
|
|
|
isUnqualified?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateLeadStageRequest {
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
color?: string;
|
|
|
|
|
|
showOnBoard?: boolean;
|
|
|
|
|
|
isConverted?: boolean;
|
|
|
|
|
|
isLost?: boolean;
|
|
|
|
|
|
isUnqualified?: boolean;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
contactName: string | null;
|
2026-10-31 12:49:42 +03:30
|
|
|
|
gender: PersonGender;
|
|
|
|
|
|
showGenderInName: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
mobile: string;
|
|
|
|
|
|
phone: string | null;
|
|
|
|
|
|
city: string | null;
|
|
|
|
|
|
region: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
instagram: string | null;
|
2026-10-13 18:04:53 +03:30
|
|
|
|
birthDate: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
addresses: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
trade: string | null;
|
|
|
|
|
|
source: string | null;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
estimatedValue: number | null;
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
stageName: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
assigneeId: string | null;
|
|
|
|
|
|
assigneeName: string | null;
|
|
|
|
|
|
lastContactAt: string | null;
|
|
|
|
|
|
nextFollowUpAt: string | null;
|
|
|
|
|
|
convertedPersonId: string | null;
|
|
|
|
|
|
convertedAt: string | null;
|
|
|
|
|
|
archivedAt: string | null;
|
|
|
|
|
|
daysInStage: number;
|
|
|
|
|
|
hasLogo: boolean;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadEventDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
body: string | null;
|
|
|
|
|
|
actorId: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateLeadRequest {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
mobile: string;
|
|
|
|
|
|
contactName?: string | null;
|
2026-10-31 12:49:42 +03:30
|
|
|
|
gender?: PersonGender;
|
|
|
|
|
|
showGenderInName?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
phone?: string | null;
|
|
|
|
|
|
city?: string | null;
|
|
|
|
|
|
region?: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
instagram?: string | null;
|
2026-10-13 18:04:53 +03:30
|
|
|
|
birthDate?: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
addresses?: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
trade?: string | null;
|
|
|
|
|
|
source?: string | null;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
estimatedValue?: number | null;
|
|
|
|
|
|
stageId?: string;
|
|
|
|
|
|
assigneeId?: string | null;
|
|
|
|
|
|
nextFollowUpAt?: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
/** فقط برای ایمپورت اکسل — تاریخ ثبت مبدأ */
|
|
|
|
|
|
registeredAt?: string | null;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateLeadRequest {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
mobile?: string;
|
|
|
|
|
|
contactName?: string | null;
|
2026-10-31 12:49:42 +03:30
|
|
|
|
gender?: PersonGender;
|
|
|
|
|
|
showGenderInName?: boolean;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
phone?: string | null;
|
|
|
|
|
|
city?: string | null;
|
|
|
|
|
|
region?: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
instagram?: string | null;
|
2026-10-13 18:04:53 +03:30
|
|
|
|
birthDate?: string | null;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
addresses?: string[];
|
2026-09-27 21:17:42 +03:30
|
|
|
|
trade?: string | null;
|
|
|
|
|
|
source?: string | null;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
estimatedValue?: number | null;
|
|
|
|
|
|
stageId?: string;
|
|
|
|
|
|
assigneeId?: string | null;
|
|
|
|
|
|
nextFollowUpAt?: string | null;
|
|
|
|
|
|
lastContactAt?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MoveLeadRequest {
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
position?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AssignLeadsRequest {
|
|
|
|
|
|
leadIds: string[];
|
|
|
|
|
|
assigneeId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface BulkLeadStageRequest {
|
|
|
|
|
|
leadIds: string[];
|
|
|
|
|
|
stageId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AutoAssignLeadsRequest {
|
|
|
|
|
|
rule?: LeadAssignRule;
|
|
|
|
|
|
leadIds?: string[];
|
|
|
|
|
|
assigneeIds?: string[];
|
|
|
|
|
|
onlyUnassigned?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadCallLogRequest {
|
|
|
|
|
|
outcome?: string;
|
|
|
|
|
|
body?: string;
|
|
|
|
|
|
nextFollowUpAt?: string | null;
|
|
|
|
|
|
stageId?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadNoteRequest {
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadFollowUpRequest {
|
|
|
|
|
|
nextFollowUpAt: string;
|
|
|
|
|
|
body?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ConvertLeadRequest {
|
|
|
|
|
|
firstName?: string;
|
|
|
|
|
|
lastName?: string;
|
|
|
|
|
|
createIfMissing?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ConvertLeadResult {
|
|
|
|
|
|
lead: LeadDto;
|
|
|
|
|
|
personId: string;
|
|
|
|
|
|
createdPerson: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadDuplicateCheckResult {
|
|
|
|
|
|
exists: boolean;
|
|
|
|
|
|
kind: "lead" | "person" | null;
|
|
|
|
|
|
id: string | null;
|
|
|
|
|
|
title: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadImportJobDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fileName: string | null;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
totalCount: number;
|
|
|
|
|
|
processedCount: number;
|
|
|
|
|
|
successCount: number;
|
|
|
|
|
|
duplicateCount: number;
|
|
|
|
|
|
errorCount: number;
|
|
|
|
|
|
errorSamples: string[];
|
|
|
|
|
|
startedAt: string | null;
|
|
|
|
|
|
finishedAt: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadImportRow {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
contactName?: string;
|
|
|
|
|
|
mobile?: string;
|
|
|
|
|
|
phone?: string;
|
|
|
|
|
|
city?: string;
|
|
|
|
|
|
region?: string;
|
2026-10-03 12:29:11 +03:30
|
|
|
|
address?: string;
|
|
|
|
|
|
instagram?: string;
|
|
|
|
|
|
registeredAt?: string;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
trade?: string;
|
|
|
|
|
|
source?: string;
|
|
|
|
|
|
notes?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadStatsDto {
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
newCount: number;
|
|
|
|
|
|
unassigned: number;
|
|
|
|
|
|
byAssignee: Array<{ userId: string; fullName: string; count: number }>;
|
|
|
|
|
|
callsLogged: number;
|
|
|
|
|
|
withFollowUp: number;
|
|
|
|
|
|
interested: number;
|
|
|
|
|
|
converted: number;
|
|
|
|
|
|
lost: number;
|
|
|
|
|
|
conversionRate: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LeadBoardColumnDto {
|
|
|
|
|
|
stage: LeadStageDto;
|
2026-10-14 19:47:32 +03:30
|
|
|
|
/** تعداد واقعی لیدهای این مرحله، حتی اگر همهٔ کارتها برنگردد */
|
|
|
|
|
|
total: number;
|
2026-09-27 21:17:42 +03:30
|
|
|
|
leads: LeadDto[];
|
|
|
|
|
|
}
|
2026-09-29 10:34:01 +03:30
|
|
|
|
|
|
|
|
|
|
export interface ConversationSettingsDto {
|
|
|
|
|
|
chatHost: string;
|
|
|
|
|
|
uploadUrl: string;
|
|
|
|
|
|
downloadUrl: string;
|
|
|
|
|
|
apiKeySet: boolean;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateConversationSettingsRequest {
|
|
|
|
|
|
chatHost: string;
|
|
|
|
|
|
apiKey?: string;
|
|
|
|
|
|
uploadUrl?: string;
|
|
|
|
|
|
downloadUrl?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ConversationAccessDto {
|
|
|
|
|
|
canUse: boolean;
|
|
|
|
|
|
canCreateGroup: boolean;
|
2026-09-30 11:08:33 +03:30
|
|
|
|
/** ساخت کانال — فعلاً همان مجوز ساخت گروه */
|
|
|
|
|
|
canCreateChannel: boolean;
|
2026-09-29 10:34:01 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ConversationSessionDto {
|
|
|
|
|
|
chatUserId: string;
|
|
|
|
|
|
wsUrl: string;
|
|
|
|
|
|
messagesBaseUrl: string;
|
|
|
|
|
|
uploadUrl: string;
|
|
|
|
|
|
downloadUrl: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ConversationPeerDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
hasAvatar: boolean;
|
|
|
|
|
|
chatUserId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ConversationRoomDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
externalRoomId: string;
|
2026-09-30 11:08:33 +03:30
|
|
|
|
kind: "dm" | "group" | "channel";
|
2026-09-29 10:34:01 +03:30
|
|
|
|
name: string;
|
|
|
|
|
|
unreadCount: number;
|
|
|
|
|
|
lastMessagePreview: string | null;
|
|
|
|
|
|
lastMessageAt: string | null;
|
|
|
|
|
|
peerUserId: string | null;
|
2026-09-30 10:40:29 +03:30
|
|
|
|
/** سنجاق در لیست گفتگو — فقط برای کاربر جاری */
|
|
|
|
|
|
pinned?: boolean;
|
2026-09-30 14:58:44 +03:30
|
|
|
|
/** بیصدا برای کاربر جاری */
|
|
|
|
|
|
muted?: boolean;
|
|
|
|
|
|
/** در حالت mute، منشن همچنان اعلان میدهد */
|
|
|
|
|
|
muteAllowMentions?: boolean;
|
2026-09-30 11:08:33 +03:30
|
|
|
|
/** در کانال: آیا کاربر جاری میتواند پیام بفرستد */
|
|
|
|
|
|
canPost?: boolean;
|
2026-09-30 11:53:04 +03:30
|
|
|
|
/** آیا کاربر جاری میتواند اعضا را مدیریت کند */
|
|
|
|
|
|
canManageMembers?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ConversationRoomMemberDto {
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
fullName: string;
|
|
|
|
|
|
role: "admin" | "member";
|
|
|
|
|
|
hasAvatar: boolean;
|
|
|
|
|
|
isMe?: boolean;
|
2026-09-30 10:40:29 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ConversationPinnedMessageDto {
|
|
|
|
|
|
messageId: string;
|
|
|
|
|
|
preview: string;
|
|
|
|
|
|
pinnedByUserId: string;
|
|
|
|
|
|
createdAt: string;
|
2026-09-29 10:34:01 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-30 14:58:44 +03:30
|
|
|
|
export interface ConversationMessageReactionDto {
|
|
|
|
|
|
messageId: string;
|
|
|
|
|
|
emoji: string;
|
|
|
|
|
|
count: number;
|
|
|
|
|
|
me: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-29 10:34:01 +03:30
|
|
|
|
export interface CreateConversationDmRequest {
|
|
|
|
|
|
peerUserId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CreateConversationGroupRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
memberUserIds: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformConversationStatsDto {
|
|
|
|
|
|
organizationsWithChat: number;
|
|
|
|
|
|
totalConfiguredOrgs: number;
|
|
|
|
|
|
totalRoomsTracked: number;
|
|
|
|
|
|
}
|
2026-10-06 19:54:38 +03:30
|
|
|
|
|
|
|
|
|
|
/* ───────────── کارهای روتین ───────────── */
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2026-11-04 19:34:36 +03:30
|
|
|
|
/** سازندهٔ روتین؛ عضو عادی فقط روتین خودش را ویرایش میکند */
|
|
|
|
|
|
createdById: string | null;
|
2026-10-06 19:54:38 +03:30
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-10-10 21:28:49 +03:30
|
|
|
|
|
2026-10-18 18:49:49 +03:30
|
|
|
|
/* ───────── دفتر مالی داخلی ───────── */
|
|
|
|
|
|
|
|
|
|
|
|
export type FinancialAccountType = "bank" | "cash" | "petty";
|
|
|
|
|
|
export type FinancialDocKind = "income" | "expense";
|
|
|
|
|
|
export type FinancialDocStatus = "open" | "partial" | "settled" | "void";
|
2026-11-04 11:36:55 +03:30
|
|
|
|
export type FinancialPartyKind = "person" | "company" | "user" | "other";
|
2026-10-18 18:49:49 +03:30
|
|
|
|
export type FinancialPayMethod = "cash" | "card_to_card" | "pos" | "transfer" | "other";
|
|
|
|
|
|
|
|
|
|
|
|
export interface FinancialAccountDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
type: FinancialAccountType;
|
|
|
|
|
|
bankName: string | null;
|
|
|
|
|
|
accountRef: string | null;
|
|
|
|
|
|
responsibleName: string | null;
|
|
|
|
|
|
openingBalance: string;
|
|
|
|
|
|
openingDate: string;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
currency: string;
|
|
|
|
|
|
balance: string;
|
|
|
|
|
|
periodIn: string;
|
|
|
|
|
|
periodOut: string;
|
|
|
|
|
|
lastMovementAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FinancialCategoryDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
kind: FinancialDocKind;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FinancialDocumentDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
kind: FinancialDocKind;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
amount: string;
|
|
|
|
|
|
settledAmount: string;
|
|
|
|
|
|
remaining: string;
|
|
|
|
|
|
bookedAt: string;
|
|
|
|
|
|
dueAt: string | null;
|
|
|
|
|
|
categoryId: string | null;
|
|
|
|
|
|
categoryName: string | null;
|
2026-11-04 11:36:55 +03:30
|
|
|
|
partyKind: FinancialPartyKind | null;
|
2026-10-18 18:49:49 +03:30
|
|
|
|
personId: string | null;
|
2026-11-04 11:36:55 +03:30
|
|
|
|
companyId: string | null;
|
|
|
|
|
|
userId: string | null;
|
2026-10-18 18:49:49 +03:30
|
|
|
|
partyName: string | null;
|
|
|
|
|
|
accountId: string | null;
|
|
|
|
|
|
accountName: string | null;
|
|
|
|
|
|
method: string | null;
|
|
|
|
|
|
status: FinancialDocStatus;
|
|
|
|
|
|
reference: string | null;
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
createdAt: string;
|
2026-10-18 19:59:24 +03:30
|
|
|
|
attachments?: Array<{ id: string; fileName: string }>;
|
2026-10-18 18:49:49 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FinancialMovementDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
accountId: string;
|
|
|
|
|
|
direction: "in" | "out";
|
|
|
|
|
|
amount: string;
|
|
|
|
|
|
kind: string;
|
|
|
|
|
|
kindLabel: string;
|
|
|
|
|
|
transferGroupId: string | null;
|
|
|
|
|
|
documentId: string | null;
|
|
|
|
|
|
occurredAt: string;
|
|
|
|
|
|
memo: string | null;
|
|
|
|
|
|
reference: string | null;
|
|
|
|
|
|
balanceAfter: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type FinancialInstallmentStatus = "open" | "partial" | "settled" | "overdue";
|
|
|
|
|
|
|
|
|
|
|
|
export interface FinancialInstallmentDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
position: number;
|
|
|
|
|
|
dueDate: string;
|
|
|
|
|
|
amount: string;
|
|
|
|
|
|
paidAmount: string;
|
|
|
|
|
|
remaining: string;
|
|
|
|
|
|
status: FinancialInstallmentStatus;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FinancialReceivableDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
personId: string | null;
|
|
|
|
|
|
partyName: string | null;
|
|
|
|
|
|
totalAmount: string;
|
|
|
|
|
|
paidAmount: string;
|
|
|
|
|
|
remaining: string;
|
|
|
|
|
|
status: "open" | "settled" | "void";
|
|
|
|
|
|
notes: string | null;
|
|
|
|
|
|
installments: FinancialInstallmentDto[];
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-04 11:36:55 +03:30
|
|
|
|
export interface TreasuryPartyChoiceDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TreasuryPartySearchDto {
|
|
|
|
|
|
people: TreasuryPartyChoiceDto[];
|
|
|
|
|
|
companies: TreasuryPartyChoiceDto[];
|
|
|
|
|
|
users: TreasuryPartyChoiceDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TreasuryPartyBalanceDto {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
partyKind: FinancialPartyKind | null;
|
|
|
|
|
|
partyName: string;
|
|
|
|
|
|
amount: string;
|
|
|
|
|
|
settledAmount: string;
|
|
|
|
|
|
remaining: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-18 18:49:49 +03:30
|
|
|
|
export interface TreasuryDashboardDto {
|
|
|
|
|
|
bookedIncome: string;
|
|
|
|
|
|
actualReceipts: string;
|
|
|
|
|
|
bookedExpense: string;
|
|
|
|
|
|
actualPayments: string;
|
|
|
|
|
|
operatingProfit: string;
|
|
|
|
|
|
receivables: string;
|
|
|
|
|
|
payables: string;
|
|
|
|
|
|
totalBalance: string;
|
|
|
|
|
|
recent: FinancialDocumentDto[];
|
|
|
|
|
|
upcomingInstallments: FinancialInstallmentDto[];
|
|
|
|
|
|
upcomingPayables: FinancialDocumentDto[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-19 15:55:48 +03:30
|
|
|
|
/* ───────── کیفپول پیامک (صورتحساب محیط) ───────── */
|
|
|
|
|
|
|
|
|
|
|
|
export type SmsBalanceLevel = "healthy" | "warning" | "critical" | "unknown";
|
|
|
|
|
|
export type SmsPanelMode = "platform" | "own";
|
|
|
|
|
|
|
|
|
|
|
|
export interface SmsWalletSummaryDto {
|
|
|
|
|
|
billingEnabled: boolean;
|
|
|
|
|
|
balanceRials: number;
|
|
|
|
|
|
pricePerPartRials: number;
|
|
|
|
|
|
isBlocked: boolean;
|
|
|
|
|
|
autoBlocked: boolean;
|
|
|
|
|
|
balanceLevel: SmsBalanceLevel;
|
|
|
|
|
|
panelMode: SmsPanelMode | string;
|
|
|
|
|
|
chargeInfo: SmsChargeInfoDto | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SmsChargeInfoDto {
|
|
|
|
|
|
card: string;
|
|
|
|
|
|
iban: string;
|
|
|
|
|
|
accountHolder: string;
|
|
|
|
|
|
instruction: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformSmsTenantRowDto {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
slug: string;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
isBlocked: boolean;
|
|
|
|
|
|
panelMode: string;
|
|
|
|
|
|
sms: {
|
|
|
|
|
|
balanceRials: number;
|
|
|
|
|
|
pricePerPartRials: number;
|
|
|
|
|
|
autoBlockBelowRials: number | null;
|
|
|
|
|
|
balanceLevel: SmsBalanceLevel;
|
|
|
|
|
|
billingEnabled: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PlatformSmsOverviewDto {
|
|
|
|
|
|
organization: {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
slug: string;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
account: {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
organizationId: string;
|
|
|
|
|
|
balanceRials: number;
|
|
|
|
|
|
pricePerPartRials: number;
|
|
|
|
|
|
isBlocked: boolean;
|
|
|
|
|
|
autoBlockBelowRials: number | null;
|
|
|
|
|
|
alertCriticalRials: number;
|
|
|
|
|
|
alertWarningRials: number;
|
|
|
|
|
|
panelMode: SmsPanelMode;
|
|
|
|
|
|
ownPanelType: string;
|
|
|
|
|
|
ownPanelUsername: string | null;
|
|
|
|
|
|
ownPanelFrom: string | null;
|
|
|
|
|
|
hasOwnPanelPassword: boolean;
|
|
|
|
|
|
balanceLevel: SmsBalanceLevel;
|
|
|
|
|
|
billingEnabled: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
chargeInfo: SmsChargeInfoDto;
|
|
|
|
|
|
today: { messages: number; parts: number; amountRials: number };
|
|
|
|
|
|
month: { messages: number; parts: number; amountRials: number };
|
|
|
|
|
|
ledgers: Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
amountRials: number;
|
|
|
|
|
|
balanceAfterRials: number;
|
|
|
|
|
|
parts: number | null;
|
|
|
|
|
|
note: string | null;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
sendLogs: Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
receiver: string;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
parts: number;
|
|
|
|
|
|
totalAmountRials: number;
|
|
|
|
|
|
provider: string | null;
|
|
|
|
|
|
source: string | null;
|
|
|
|
|
|
isAlert: boolean;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 21:28:49 +03:30
|
|
|
|
/* ───────── باشگاه امتیازات (گیمیفیکیشن کارکنان) ───────── */
|
|
|
|
|
|
|
|
|
|
|
|
export * from "./gamification";
|
2026-10-18 21:04:26 +03:30
|
|
|
|
export * from "./behavior";
|