Files
CRM_Montzar/packages/shared/src/index.ts
T

3206 lines
72 KiB
TypeScript

export type UserRole = "owner" | "admin" | "sales" | "support";
export const USER_ROLES: UserRole[] = ["owner", "admin", "sales", "support"];
export const USER_ROLE_LABELS: Record<UserRole, string> = {
owner: "مالک",
admin: "مدیر",
sales: "فروش",
support: "پشتیبانی",
};
/** تنظیمات سازمانی و مدیریت کاربران */
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";
}
/** مشاهده همه پرونده‌های سازمان (نه فقط مالکیت خود) */
export function canViewAllRecords(role: UserRole | string | null | undefined): boolean {
return role === "owner" || role === "admin";
}
/** تغییر مالک پرونده */
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;
/** مالک محیط فعلی — اشتراک متعلق به مالک است نه عضو زیرمجموعه */
isOrgOwner?: boolean;
enabledModules?: OrgModuleKey[];
/** خلاصه اشتراک مالک محیط (برای اعضا فقط جهت نمایش/گیت ورود) */
subscription?: SubscriptionSummaryDto | null;
}
export type BillingPlanCode =
| "trial"
| "monthly"
| "quarterly"
| "semiannual"
| "yearly"
| "custom";
export type BillingAccountStatus = "active" | "expired" | "suspended";
export const BILLING_PLAN_CODES: BillingPlanCode[] = [
"trial",
"monthly",
"quarterly",
"semiannual",
"yearly",
"custom",
];
export const BILLING_PLAN_LABELS: Record<BillingPlanCode, string> = {
trial: "دمو ۱۵ روزه",
monthly: "ماهانه",
quarterly: "سه‌ماهه",
semiannual: "شش‌ماهه",
yearly: "یکساله",
custom: "سفارشی",
};
/** مدت پیش‌فرض پلن‌ها (روز) — custom ندارد */
export const BILLING_PLAN_DAYS: Record<Exclude<BillingPlanCode, "custom">, number> = {
trial: 15,
monthly: 30,
quarterly: 90,
semiannual: 180,
yearly: 365,
};
export const BILLING_PLAN_DEFAULT_MAX_ORGS: Record<
Exclude<BillingPlanCode, "custom">,
number
> = {
trial: 1,
monthly: 3,
quarterly: 5,
semiannual: 10,
yearly: 20,
};
export type OrgModuleKey =
| "customers"
| "pipeline"
| "products"
| "invoices"
| "quotes"
| "commissions"
| "sales_forecast"
| "activities"
| "tickets"
| "workflow_automations"
| "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";
export const ORG_MODULE_KEYS: OrgModuleKey[] = [
"customers",
"pipeline",
"products",
"invoices",
"quotes",
"commissions",
"sales_forecast",
"activities",
"tickets",
"workflow_automations",
"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",
];
export const ORG_MODULE_LABELS: Record<OrgModuleKey, string> = {
customers: "مشتریان",
pipeline: "کاریز فروش",
products: "محصولات",
invoices: "فاکتورها",
quotes: "پیش‌فاکتورها",
commissions: "پورسانت",
sales_forecast: "هدف و پیش‌بینی",
activities: "فعالیت‌ها",
tickets: "تیکت‌ها",
workflow_automations: "اتوماسیون گردش کار",
message_automations: "اتوماسیون پیامک",
lead_forms: "فرم‌ساز سرنخ",
project_boards: "کاریز پروژه",
inventory: "انبار",
cheques: "چک‌ها",
attachments: "پیوست‌ها",
loyalty: "باشگاه مشتریان",
reports: "گزارش‌ها",
notes: "یادداشت‌ها",
tahesab: "ته‌حساب",
custom_fields: "فیلدهای سفارشی",
currencies: "ارزها",
team_settings: "تیم و تنظیمات سازمان",
faq: "FAQ",
audit_logs: "لاگ فعالیت کاربران",
saved_views: "فیلترهای ذخیره‌شده",
public_api: "API و وب‌هوک",
sms_campaigns: "پیامک گروهی",
teams: "تیم‌ها و دیدپذیری",
woocommerce: "ووکامرس",
};
export const ORG_MODULE_GROUPS: Array<{
title: string;
keys: OrgModuleKey[];
}> = [
{ title: "مشتریان", keys: ["customers"] },
{
title: "فروش",
keys: [
"pipeline",
"products",
"invoices",
"quotes",
"commissions",
"sales_forecast",
],
},
{ title: "فعالیت‌ها", keys: ["activities", "notes"] },
{ title: "پشتیبانی", keys: ["tickets", "faq"] },
{
title: "اتوماسیون",
keys: [
"workflow_automations",
"message_automations",
"sms_campaigns",
],
},
{
title: "ابزارها",
keys: [
"lead_forms",
"project_boards",
"inventory",
"cheques",
"attachments",
"loyalty",
"reports",
"saved_views",
"woocommerce",
],
},
{
title: "تنظیمات",
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);
}
export function defaultEnabledModules(): OrgModuleKey[] {
return [...ORG_MODULE_KEYS];
}
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;
externalPaymentRef?: string | null;
notes?: string | null;
daysRemaining: number;
warn: boolean;
}
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;
/** own = فقط خود | team = هم‌تیمی‌ها | org = کل سازمان */
dataVisibility?: DataVisibility;
createdAt: string;
}
export type DataVisibility = "own" | "team" | "org";
export const DATA_VISIBILITIES: DataVisibility[] = ["own", "team", "org"];
export const DATA_VISIBILITY_LABELS: Record<DataVisibility, string> = {
own: "فقط پرونده‌های خود",
team: "تیم‌های عضو",
org: "کل سازمان",
};
export interface UpdateUserVisibilityRequest {
dataVisibility: DataVisibility;
}
export type RolePermissionKey =
| "customers"
| "sales"
| "billing"
| "products"
| "activities"
| "notes"
| "reports"
| "audit";
export const ROLE_PERMISSION_ITEMS: Array<{
key: RolePermissionKey;
label: string;
}> = [
{ key: "customers", label: "مشتریان" },
{ key: "sales", label: "فروش و معاملات" },
{ key: "billing", label: "پیش‌فاکتورها و فاکتورها" },
{ key: "products", label: "محصولات و خدمات" },
{ key: "activities", label: "فعالیت‌ها" },
{ key: "notes", label: "یادداشت‌ها" },
{ key: "reports", label: "گزارش‌ها" },
{ key: "audit", label: "لاگ فعالیت کاربران" },
];
export interface OrgRoleMemberDto {
id: string;
fullName: string;
hasAvatar: boolean;
}
export interface OrgRoleDto {
id: string;
name: string;
description: string;
color: string;
permissions: RolePermissionKey[];
systemKey: UserRole | null;
userCount: number;
members: OrgRoleMemberDto[];
}
export interface CreateOrgRoleRequest {
name: string;
description?: string;
color?: string;
permissions?: RolePermissionKey[];
}
export interface UpdateOrgRoleRequest {
name?: string;
description?: string;
color?: string;
permissions?: RolePermissionKey[];
}
export interface InviteUserRequest {
mobile: string;
fullName: string;
password?: string;
role?: Exclude<UserRole, "owner">;
orgRoleId?: string;
}
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 {
/** ایمیل یا شماره موبایل */
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;
/** فقط در محیط توسعه / AUTH_RETURN_OTP */
devCode?: string;
}
export interface RegisterChallengeResponse {
challengeId: string;
expiresInSeconds: number;
maskedEmail?: string;
maskedMobile?: string;
/** وقتی سرویس ایمیل خاموش است false — فقط OTP موبایل لازم است */
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[];
}
export interface UpdateOrganizationRequest {
name?: string;
businessField?: BusinessField;
}
/** زمینه فعالیت سازمان — هم‌تراز صنایع دیدار */
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> = {
education: "آموزشگاه و مؤسسات آموزشی",
beauty: "کلینیک زیبایی و آرایشگاه",
real_estate: "املاک و مشاورین مسکن",
construction: "صنعت ساختمان",
advertising: "تبلیغات و مشاوره کسب‌وکار",
insurance: "بیمه",
consulting: "شرکت‌های مشاور",
startup: "استارتاپ و کسب‌وکار آنلاین",
medical_devices: "تجهیزات پزشکی",
travel: "گردشگری و مهاجرت",
contractor: "پیمانکاری و پروژه‌ای",
trading: "بازرگانی",
manufacturing: "تولیدی و صنعتی",
personal: "کسب‌وکار شخصی و خانگی",
tech: "نرم‌افزار و سخت‌افزار",
legal: "وکلا و مشاورین حقوقی",
repair: "خدمات و تعمیرات",
retail: "خرده‌فروشی",
jewelry: "طلا و جواهر",
other: "سایر",
};
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[];
/** نرخ‌ها نسبت به ارز پایه؛ ارز پایه نیازی به نرخ ندارد */
rates?: Array<{ currency: string; rateToBase: number }>;
}
export const CURRENCY_CATALOG: Array<{
code: string;
labelFa: string;
suffix: string;
}> = [
{ code: "IRR", labelFa: "ریال ایران", suffix: "ریال" },
{ code: "USD", labelFa: "دلار آمریکا", suffix: "دلار" },
{ code: "EUR", labelFa: "یورو", suffix: "یورو" },
{ code: "AED", labelFa: "درهم امارات", suffix: "درهم" },
{ code: "TRY", labelFa: "لیر ترکیه", suffix: "لیر" },
{ code: "GBP", labelFa: "پوند انگلیس", suffix: "پوند" },
];
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;
}
export interface CompanySummaryDto {
id: string;
name: string;
}
export interface CompanyDto {
id: string;
name: string;
nationalId: string | null;
phone: string | null;
email: string | null;
website: string | null;
address: string | null;
notes: string | null;
tags: string[];
customFields: Record<string, unknown>;
peopleCount: number;
owner: UserSummaryDto | null;
createdAt: string;
updatedAt: string;
}
export interface CreateCompanyRequest {
name: string;
nationalId?: string;
phone?: string;
email?: string;
website?: string;
address?: string;
notes?: string;
tags?: string[];
customFields?: Record<string, unknown>;
ownerId?: string | null;
}
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> = {
newcomer: "مشتری تازه وارد",
happy: "مشتری خوشحال",
unhappy: "مشتری ناراضی",
special: "مشتری خاص",
};
export interface PersonDto {
id: string;
firstName: string;
lastName: string;
fullName: string;
email: string | null;
phone: string | null;
mobile: string | null;
position: string | null;
website: string | null;
referrer: string | null;
notes: string | null;
mood: PersonMood;
tags: string[];
customFields: Record<string, unknown>;
loyaltyPoints: number;
loyaltyTier: LoyaltyTierSummaryDto | null;
company: CompanySummaryDto | null;
owner: UserSummaryDto | null;
createdAt: string;
updatedAt: string;
}
export interface CreatePersonRequest {
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
mobile?: string;
position?: string;
website?: string;
referrer?: string;
notes?: string;
mood?: PersonMood;
tags?: string[];
companyId?: string | null;
customFields?: Record<string, unknown>;
ownerId?: string | null;
}
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[];
}
export interface DealDto {
id: string;
title: string;
amount: number | null;
currency: string;
status: DealStatus;
source: string | null;
lostReason: string | null;
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;
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[];
}
export interface CreateDealRequest {
title: string;
pipelineId?: string;
stageId?: string;
amount?: number | null;
currency?: string;
source?: string;
notes?: string;
tags?: string[];
isStale?: boolean;
isRotten?: boolean;
expectedCloseAt?: string | null;
personId?: string | null;
companyId?: string | null;
ownerId?: string | null;
customFields?: Record<string, unknown>;
lineItems?: DealLineItemInput[];
}
export interface UpdateDealRequest {
title?: string;
amount?: number | null;
currency?: string;
source?: string;
notes?: string;
tags?: string[];
isStale?: boolean;
isRotten?: boolean;
lostReason?: string | null;
expectedCloseAt?: string | null;
personId?: string | null;
companyId?: string | null;
ownerId?: string | null;
customFields?: Record<string, unknown>;
lineItems?: DealLineItemInput[];
}
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";
export type CustomFieldEntity = "person" | "company" | "deal";
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;
}
export interface ActivityDto {
id: string;
type: ActivityType;
status: ActivityStatus;
priority: ActivityPriority;
title: string;
description: string | null;
dueAt: string | null;
durationMinutes: number | null;
reminderAt: string | null;
reminderNotifyApp: boolean;
reminderNotifySms: boolean;
completedAt: string | null;
tags: string[];
person: PersonSummaryDto | null;
company: CompanySummaryDto | null;
deal: DealSummaryDto | null;
assignee: UserSummaryDto | null;
createdBy: UserSummaryDto | null;
createdAt: string;
updatedAt: string;
}
export interface CreateActivityRequest {
title: string;
type?: ActivityType;
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;
assigneeId?: string | null;
}
export interface UpdateActivityRequest {
title?: string;
type?: ActivityType;
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;
assigneeId?: string | null;
}
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;
pinned: boolean;
color: string | null;
tags: string[];
reminderAt: string | null;
checklist: NoteChecklistItemDto[];
locationText: string | null;
analyzeEnabled: boolean;
person: PersonSummaryDto | null;
company: CompanySummaryDto | null;
deal: DealSummaryDto | null;
activity: { id: string; title: string } | null;
author: UserSummaryDto | null;
attachments: NoteAttachmentDto[];
createdAt: string;
updatedAt: string;
}
export interface CreateNoteRequest {
title?: string;
body: string;
noteType?: NoteType | string;
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;
}
export interface UpdateNoteRequest {
title?: string | null;
body?: string;
noteType?: NoteType | string;
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;
}
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;
}
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[];
}
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;
}
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"
| "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"
| "notes"
| "custom";
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;
createPerson: boolean;
createDeal: boolean;
pipelineId: string | null;
source: string | null;
thankYouMessage: string | null;
redirectUrl: string | null;
assigneeIds: string[];
submitLabel: string;
submitColor: string;
hasCover: boolean;
fields: LeadFormFieldDto[];
submissionCount?: number;
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;
createPerson?: boolean;
createDeal?: boolean;
pipelineId?: string | null;
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;
createPerson?: boolean;
createDeal?: boolean;
pipelineId?: string | null;
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;
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;
createdAt: string;
}
export interface SubmitLeadFormRequest {
values: Record<string, string>;
}
export interface SubmitLeadFormResponse {
ok: true;
thankYouMessage: string | null;
redirectUrl: string | null;
}
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;
}
export interface UpdateChequeRequest {
number?: string;
bank?: string;
dueDate?: string;
status?: ChequeStatus | string;
notes?: string | null;
}
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;
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;
/** فقط مالک/مدیر؛ در غیر این صورت هدف برای خود کاربر */
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;
/** مجموع اهداف هم‌پوشان با بازه */
targetAmount: number;
/** معاملات برنده‌شده در بازه */
wonAmount: number;
/** معاملات باز فعلی */
openPipelineAmount: number;
/** پیش‌بینی: تحقق + کاریز باز × میانگین احتمال */
forecastAmount: number;
progressPercent: number | null;
openDealsCount: number;
wonDealsCount: number;
goals: SalesGoalDto[];
kpis: SalesForecastKpiDto[];
newCustomersCount: number;
/** معاملات ایجادشده در بازه */
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;
}
/** ساعات مهلت پاسخ اول بر اساس اولویت */
export const TICKET_SLA_HOURS: Record<TicketPriority, number> = {
urgent: 4,
high: 8,
normal: 24,
low: 48,
};
/* ——— پرتال مشتری (OTP) ——— */
export interface PortalOrgInfoDto {
name: string;
slug: string;
}
export interface PortalRequestOtpRequest {
mobile: string;
}
export interface PortalRequestOtpResponse {
ok: true;
/** فقط در حالت توسعه / دمو وقتی SMS واقعی وصل نیست */
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;
}
/* ——— گزارشات و داشبورد ——— */
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;
}
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: Array<{
stageId: string;
stageName: string;
pipelineName: string;
count: number;
amount: number;
}>;
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[];
}
/** برد پروژه / کاریز کارت */
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;
}
/** باشگاه مشتریان */
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;
}
/** اتوماسیون اگر/آنگاه */
export type AutomationTrigger =
| "person_created"
| "lead_submitted"
| "deal_created"
| "deal_stage_changed"
| "deal_won"
| "deal_lost"
| "ticket_created"
| "ticket_resolved"
| "invoice_issued"
| "invoice_paid"
| "activity_done";
export type AutomationAction =
| "create_activity"
| "create_notification"
| "add_loyalty_points";
export const AUTOMATION_TRIGGER_LABELS: Record<AutomationTrigger, string> = {
person_created: "مخاطب جدید ساخته شد",
lead_submitted: "فرم سرنخ ارسال شد",
deal_created: "معامله جدید ساخته شد",
deal_stage_changed: "مرحله معامله عوض شد",
deal_won: "معامله برنده شد",
deal_lost: "معامله باخت",
ticket_created: "تیکت جدید ساخته شد",
ticket_resolved: "تیکت حل شد",
invoice_issued: "فاکتور صادر شد",
invoice_paid: "پرداخت روی فاکتور ثبت شد",
activity_done: "فعالیت تمام شد",
};
export const AUTOMATION_ACTION_LABELS: Record<AutomationAction, string> = {
create_activity: "ساخت فعالیت",
create_notification: "ارسال اعلان",
add_loyalty_points: "افزودن امتیاز باشگاه",
};
export const MAX_AUTOMATION_STEPS = 8;
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",
] as const satisfies readonly AutomationTrigger[];
export const AUTOMATION_TRIGGER_GROUPS: {
label: string;
items: AutomationTrigger[];
}[] = [
{ label: "مخاطب", items: ["person_created", "lead_submitted"] },
{
label: "معامله",
items: ["deal_created", "deal_stage_changed", "deal_won", "deal_lost"],
},
{ label: "تیکت", items: ["ticket_created", "ticket_resolved"] },
{ label: "فعالیت", items: ["activity_done"] },
{ label: "مالی", items: ["invoice_issued", "invoice_paid"] },
];
export type AutomationStepKind = "action" | "condition";
export type AutomationConditionField =
| "deal_amount"
| "has_person"
| "has_company"
| "ticket_priority";
export type AutomationConditionOp =
| "gte"
| "lte"
| "eq"
| "neq"
| "is_set"
| "is_empty";
export type AutomationNotifyChannel = "app" | "sms" | "both";
export type AutomationNotifyTarget = "staff" | "person";
export const AUTOMATION_CONDITION_FIELD_LABELS: Record<
AutomationConditionField,
string
> = {
deal_amount: "مبلغ معامله",
has_person: "مخاطب دارد",
has_company: "شرکت دارد",
ticket_priority: "اولویت تیکت",
};
export const AUTOMATION_CONDITION_OP_LABELS: Record<
AutomationConditionOp,
string
> = {
gte: "بزرگ‌تر یا مساوی",
lte: "کوچک‌تر یا مساوی",
eq: "مساوی",
neq: "نامساوی",
is_set: "دارد",
is_empty: "ندارد",
};
export const AUTOMATION_NOTIFY_CHANNEL_LABELS: Record<
AutomationNotifyChannel,
string
> = {
app: "ناتیفیکیشن داخلی",
sms: "پیامک",
both: "هر دو",
};
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"];
}
return ["has_person", "has_company"];
}
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;
}
export interface AutomationRuleConfig {
title?: string;
body?: string;
assigneeId?: string | null;
notifyUserId?: string | null;
notifyChannel?: AutomationNotifyChannel;
notifyTarget?: AutomationNotifyTarget;
activityType?: "call" | "meeting" | "task" | "note";
points?: number;
/** زنجیرهٔ اعمال؛ اگر خالی باشد همان action تکی استفاده می‌شود */
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,
}));
}
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,
},
];
}
export function packAutomationConfig(steps: AutomationStep[]): AutomationRuleConfig {
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,
};
}
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,
};
}
// —— ته‌حساب / حسابداری ——
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> = {
buy_sale_gold: "خرید/فروش آبشده",
buy_sale_sekeh: "خرید/فروش سکه",
talab_bedehi: "طلب/بدهی",
vkh_gold: "واریز/خروج طلا",
vkh_bank: "واریز/خروج بانکی",
};
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;
createdAt: string;
updatedAt: string;
}
export interface CreateTahesabConnectionRequest {
label: string;
baseUrl: string;
dbName: string;
token: string;
enabled?: boolean;
isDefault?: boolean;
allowInsecureTls?: boolean;
}
export type UpdateTahesabConnectionRequest = Partial<CreateTahesabConnectionRequest>;
export interface PersonAccountingLinkDto {
id: string;
personId: string;
connectionId: string;
moshtariCode: number;
matchMethod: string;
linkedAt: string;
lastBalanceSyncedAt: string | null;
connectionLabel?: string;
dbName?: string;
isDefaultConnection?: boolean;
}
export interface MandehValueDto {
amount: string | number;
unit: string;
status: "بس" | "بد";
}
export interface MandehDetailItemDto {
Name: string;
values: MandehValueDto[];
}
export interface PersonAssetsDto {
connectionId: string;
connectionLabel: string;
dbName: string;
moshtariCode: number;
goldBalance: number | null;
rialBalance: number | null;
currencyBalance: number | null;
totalGoldEquiv: number | null;
mandehDetail: MandehDetailItemDto[];
fetchedAt: string;
}
// —— پیام خودکار (جدا از اتوماسیون گردش کار) ——
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"
| "activity_done";
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",
] as const satisfies readonly MessageAutomationTrigger[];
export const MESSAGE_AUTOMATION_TRIGGER_LABELS: Record<
MessageAutomationTrigger,
string
> = {
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: "فعالیت تمام شد",
};
export const MESSAGE_AUTOMATION_UNIT_LABELS: Record<
MessageAutomationDelayUnit,
string
> = {
minutes: "دقیقه",
hours: "ساعت",
days: "روز",
weeks: "هفته",
months: "ماه",
};
export const MESSAGE_AUTOMATION_FIELDS = [
{ token: "{نام}", label: "نام مشتری" },
{ token: "{شماره}", label: "شماره موبایل" },
{ token: "{تاریخ}", label: "تاریخ ارسال" },
{ token: "{عنوان}", label: "عنوان رکورد" },
{ token: "{شرکت}", label: "نام شرکت" },
] as const;
export const MESSAGE_AUTOMATION_SMS_SEGMENT = 70;
export const MESSAGE_AUTOMATION_CHANNEL_LABELS: Record<
MessageAutomationChannel,
string
> = {
app: "ناتیف داخل برنامه",
sms: "پیامک",
both: "پیامک و ناتیف",
};
export const MESSAGE_AUTOMATION_TARGET_LABELS: Record<
MessageAutomationTarget,
string
> = {
staff: "مسئول پرونده",
person: "مشتری",
both: "مشتری و مسئول",
};
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;
}
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[];
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[];
/** فقط اگر حداقل یک عضویت مالک فعال دارد */
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";
/* ——— لاگ فعالیت کاربران ——— */
export interface AuditLogDto {
id: string;
action: string;
method: string;
path: string;
entityType: string | null;
entityId: string | null;
summary: string;
actor: UserSummaryDto | null;
ip: string | null;
createdAt: string;
}
export interface AuditLogListResponse {
items: AuditLogDto[];
total: number;
page: number;
limit: number;
}
/* ——— فیلترهای ذخیره‌شده ——— */
export type SavedViewScope =
| "deals"
| "people"
| "companies"
| "activities"
| "project_cards";
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>;
/* ——— API عمومی و وب‌هوک ——— */
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 {
/** فقط یک‌بار هنگام ساخت برگردانده می‌شود */
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;
}
/** فقط هنگام ساخت؛ secret دوباره برنمی‌گردد */
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;
}
/* ——— پیامک گروهی ——— */
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;
}
export interface CreateSmsCampaignRequest {
name: string;
body: string;
personIds: string[];
scheduledAt?: string | null;
/** اگر true باشد بلافاصله صف می‌شود */
queueNow?: boolean;
}
export interface SmsCampaignListResponse {
items: SmsCampaignDto[];
total: number;
}
/* ——— تیم‌ها و دیدپذیری ——— */
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 }>;
}
/* ——— ووکامرس ——— */
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;
}