export type UserRole = "owner" | "admin" | "sales" | "support"; export const USER_ROLES: UserRole[] = ["owner", "admin", "sales", "support"]; export const USER_ROLE_LABELS: Record = { 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 = { trial: "دمو Û±Ûµ روزه", monthly: "ماهانه", quarterly: "سه‌ماهه", semiannual: "شش‌ماهه", yearly: "یکساله", custom: "سفارشی", }; /** مدت پیش‌فرض پلن‌ها (روز) — custom ندارد */ export const BILLING_PLAN_DAYS: Record, number> = { trial: 15, monthly: 30, quarterly: 90, semiannual: 180, yearly: 365, }; export const BILLING_PLAN_DEFAULT_MAX_ORGS: Record< Exclude, 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" | "voip" | "reservations" | "leads"; export const ORG_MODULE_KEYS: OrgModuleKey[] = [ "customers", "pipeline", "products", "invoices", "quotes", "commissions", "sales_forecast", "activities", "tickets", "workflow_automations", "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", "voip", "reservations", ]; export const ORG_MODULE_LABELS: Record = { customers: "مشتریان", pipeline: "کاریز فروش", products: "محصولات", invoices: "فاکتورها", quotes: "پیش‌فاکتورها", commissions: "پورسانت", sales_forecast: "هدف و پیش‌بینی", activities: "فعالیت‌ها", tickets: "تیکت‌ها", workflow_automations: "اتوماسیون گردش کار", message_automations: "اتوماسیون پیامک", lead_forms: "فرم‌ساز سرنخ", leads: "مدیریت لیدها", 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: "ووکامرس", voip: "تلفن سازمانی", reservations: "رزرو و نوبت‌دهی", }; export const ORG_MODULE_GROUPS: Array<{ title: string; keys: OrgModuleKey[]; }> = [ { title: "مشتریان", keys: ["customers"] }, { title: "لیدها", keys: ["leads", "lead_forms"] }, { 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: [ "project_boards", "inventory", "cheques", "attachments", "loyalty", "reports", "saved_views", "woocommerce", "voip", "reservations", ], }, { 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 = { 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; 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 = { 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; 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; ownerId?: string | null; } export type UpdateCompanyRequest = Partial; 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 = { 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; 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; ownerId?: string | null; } export type UpdatePersonRequest = Partial; export interface PaginatedResponse { 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; expectedCloseAt: string | null; position: number; pipelineId: string; stageId: string; person: PersonSummaryDto | null; company: CompanySummaryDto | null; owner: UserSummaryDto | null; /** مسئولان مشترک معامله (علاوه بر مالک اصلی) */ 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[]; } 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; assigneeIds?: string[]; customFields?: Record; 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; assigneeIds?: string[]; customFields?: Record; lineItems?: DealLineItemInput[]; } /** فیلتر ذخیره‌شدهٔ لیست اشخاص */ export interface PeopleViewFilters { q?: string; sort?: string; mood?: string; page?: number; } /** فیلتر ذخیره‌شدهٔ کاریز / معاملات */ 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"; 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; /** team = تیمی | personal = فقط نویسنده */ visibility: NoteVisibility; 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[]; /** تعداد نسخه‌های قبلی (برای نمایش دکمه تاریخچه) */ revisionCount?: number; createdAt: string; updatedAt: string; } export type NoteVisibility = "team" | "personal"; export const NOTE_VISIBILITIES: NoteVisibility[] = ["team", "personal"]; export const NOTE_VISIBILITY_LABELS: Record = { team: "تیمی", personal: "شخصی (فقط من)", }; 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; } 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; } 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; 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; personId: string | null; createdAt: string; } export interface SubmitLeadFormRequest { values: Record; } 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 = { 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; } /** گونه‌های drill-down گزارش عمیق (فاز Û± BI) */ 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; /** درصد تبدیل نسبت به مرحله قبل همان کاریز؛ null برای اولین مرحله */ conversionFromPrevPercent: number | null; /** سهم از کل معاملات باز همان کاریز */ shareOfPipelinePercent: number; } /** یک ردیف نرخ تبدیل (ورودی → موفق) مثل دیدار */ export interface ReportsConversionRateRowDto { key: string; label: string; incomingCount: number; wonCount: number; /** درصد؛ اگر ورودی صفر باشد Û° */ conversionPercent: number; } /** * نرخ تبدیل به سبک دیدار: * از معاملات تازه‌ساخته‌شده در بازه، چند درصد الان برنده شده‌اند. */ 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; /** اگر قابل کلیک برای لیست پرونده باشد */ 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[]; /** نرخ تبدیل ورودی→موفق (سبک دیدار) */ conversionRate: ReportsConversionRateDto; /** مقایسه صریح بازه فعلی با دوره قبل هم‌طول */ 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[]; } /** برد پروژه / کاریز کارت */ 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; 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; 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; 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; 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 = { 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 = { 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): 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; 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 = { 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; 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: "بس" | "بد" | string; } 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; 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" | "reports"; export const SAVED_VIEW_SCOPES: SavedViewScope[] = [ "deals", "people", "companies", "activities", "project_cards", "reports", ]; /** فیلترهای ذخیره‌شدهٔ صفحه گزارشات */ 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; }; } /** متریک‌های گزارش‌ساز ساده */ 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 > = { won_amount: "مبلغ معاملات برنده", won_count: "تعداد معاملات برنده", lost_count: "تعداد معاملات باخته", open_count: "تعداد معاملات باز", open_amount: "مبلغ معاملات باز", activities_done: "فعالیت انجام‌شده", people_created: "مشتریان جدید", invoice_amount: "مبلغ فاکتور", invoice_count: "تعداد فاکتور", }; /** ابعاد گروه‌بندی گزارش‌ساز */ 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 > = { none: "بدون گروه‌بندی (جمع کل)", owner: "کاربر / مسئول", team: "تیم", source: "منبع", stage: "مرحله کاریز", month: "ماه", status: "وضعیت معامله", }; 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; isShared: boolean; owner: UserSummaryDto | null; createdAt: string; updatedAt: string; } export interface CreateSavedViewRequest { name: string; scope: SavedViewScope; filters: Record; isShared?: boolean; } export type UpdateSavedViewRequest = Partial; /* ——— 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; 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; } /* ——— تلفن سازمانی / VoIP ——— */ export type VoipCallDirection = "inbound" | "outbound"; export type VoipCallStatus = | "ringing" | "answered" | "missed" | "completed" | "failed"; export interface VoipConnectionDto { id: string; label: string; enabled: boolean; webhookSecretSet: boolean; /** قالب URL برای شروع تماس؛ {number} جایگزین می‌شود */ clickToCallUrl: string | null; lastEventAt: string | null; lastError: string | null; callCount: number; createdAt: string; updatedAt: string; } export interface CreateVoipConnectionRequest { label: string; webhookSecret?: string; clickToCallUrl?: string | null; enabled?: boolean; } export interface UpdateVoipConnectionRequest { label?: string; webhookSecret?: string | null; clickToCallUrl?: string | null; 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 { /** شناسه یکتای تماس در سانترال */ callId: string; direction: VoipCallDirection; status: VoipCallStatus | string; from?: string; to?: string; startedAt?: string; endedAt?: string; durationSec?: number; /** لینک فایل ضبط (برای خلاصه خودکار) */ recordingUrl?: string; /** متن آمادهٔ تماس؛ اگر باشد STT رد می‌شود */ transcript?: string; /** برای سازگاری با فیلدهای رایج دیگر */ caller?: string; callee?: string; } /* ——— رزرو و نوبت‌دهی ——— */ export type ReservationStatus = | "pending" | "confirmed" | "canceled" | "done"; export const RESERVATION_STATUSES: ReservationStatus[] = [ "pending", "confirmed", "canceled", "done", ]; export const RESERVATION_STATUS_LABELS: Record = { pending: "در انتظار", confirmed: "تأیید شده", canceled: "لغو شده", done: "انجام شده", }; export interface ReservationServiceDto { id: string; name: string; description: string; durationMinutes: number; /** روزهای هفته Û°=یکشنبه … Û¶=شنبه (JS getDay) */ 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; 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; } /* ——— مدیریت لیدها ——— */ 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; mobile: string; phone: string | null; city: string | null; region: string | null; 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; phone?: string | null; city?: string | null; region?: string | null; trade?: string | null; source?: string | null; notes?: string | null; estimatedValue?: number | null; stageId?: string; assigneeId?: string | null; nextFollowUpAt?: string | null; } export interface UpdateLeadRequest { title?: string; mobile?: string; contactName?: string | null; phone?: string | null; city?: string | null; region?: string | null; 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; 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; leads: LeadDto[]; }