Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixUI/hesabix_ui/lib/services/crm_service.dart

1351 lines
47 KiB
Dart
Executable file

import '../core/api_client.dart';
class CrmService {
final ApiClient _apiClient;
CrmService({ApiClient? apiClient}) : _apiClient = apiClient ?? ApiClient();
/// داده‌ی خروجی API را استخراج می‌کند. می‌تواند List یا Map باشد.
dynamic _extractData(dynamic response) {
if (response == null) return <String, dynamic>{};
final body = response is Map<String, dynamic> ? response : null;
if (body != null && body['data'] != null) return body['data'];
return body ?? <String, dynamic>{};
}
List<dynamic> _extractList(dynamic response) {
final data = _extractData(response);
if (data['data'] is List) return data['data'] as List<dynamic>;
if (data is List) return data;
return [];
}
/// پیگیری‌های امروز (سرنخ و فرصت با یادآور در امروز تا N روز آینده)
Future<Map<String, dynamic>> getFollowUpsToday({
required int businessId,
int daysAhead = 7,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/follow-ups-today',
query: {'days_ahead': daysAhead},
);
return _extractData(res.data) is Map
? Map<String, dynamic>.from(_extractData(res.data) as Map)
: <String, dynamic>{'leads': [], 'deals': []};
}
/// تاریخچه تغییرات سرنخ
Future<List<dynamic>> getLeadHistory({
required int businessId,
required int leadId,
int limit = 50,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/leads/$leadId/history',
query: {'limit': limit},
);
final data = _extractData(res.data);
return data is List ? data : [];
}
/// تاریخچه تغییرات فرصت فروش
Future<List<dynamic>> getDealHistory({
required int businessId,
required int dealId,
int limit = 50,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/deals/$dealId/history',
query: {'limit': limit},
);
final data = _extractData(res.data);
return data is List ? data : [];
}
/// جزئیات یک سرنخ
Future<Map<String, dynamic>> getLead({
required int businessId,
required int leadId,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/leads/$leadId',
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
/// جزئیات یک فرصت فروش
Future<Map<String, dynamic>> getDeal({
required int businessId,
required int dealId,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/deals/$dealId',
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
/// خلاصه CRM
Future<Map<String, dynamic>> getSummary({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/summary',
);
return _extractData(res.data);
}
/// گزارش پایپلاین فروش
Future<List<dynamic>> getPipelineReport({
required int businessId,
int? processDefinitionId,
String? fromDate,
String? toDate,
}) async {
final query = <String, dynamic>{};
if (processDefinitionId != null) query['process_definition_id'] = processDefinitionId;
if (fromDate != null && fromDate.isNotEmpty) query['from_date'] = fromDate;
if (toDate != null && toDate.isNotEmpty) query['to_date'] = toDate;
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/reports/pipeline',
query: query.isEmpty ? null : query,
);
final data = _extractData(res.data);
return data is List ? data : [];
}
/// گزارش قیف سرنخ
Future<List<dynamic>> getLeadFunnelReport({
required int businessId,
int? processDefinitionId,
String? fromDate,
String? toDate,
}) async {
final query = <String, dynamic>{};
if (processDefinitionId != null) query['process_definition_id'] = processDefinitionId;
if (fromDate != null && fromDate.isNotEmpty) query['from_date'] = fromDate;
if (toDate != null && toDate.isNotEmpty) query['to_date'] = toDate;
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/reports/lead-funnel',
query: query.isEmpty ? null : query,
);
final data = _extractData(res.data);
return data is List ? data : [];
}
/// پیش‌بینی درآمد (مبلغ موزون با احتمال)
Future<Map<String, dynamic>> getWeightedForecast({
required int businessId,
int? processDefinitionId,
}) async {
final query = processDefinitionId != null ? {'process_definition_id': processDefinitionId} : null;
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/reports/weighted-forecast',
query: query,
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
/// گزارش منبع سرنخ
Future<List<dynamic>> getLeadSourcesReport({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/reports/lead-sources',
);
final data = _extractData(res.data);
return data is List ? data : [];
}
/// گزارش عملکرد کارمندان
/// خروجی: Map با کلیدهای data (List) و restricted_to_self (bool)
Future<Map<String, dynamic>> getEmployeePerformanceReport({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/reports/employee-performance',
);
final body = res.data is Map ? Map<String, dynamic>.from(res.data as Map) : null;
final data = body != null && body['data'] != null ? body['data'] : <dynamic>[];
final list = data is List ? data : [];
final restricted = body?['restricted_to_self'] == true;
return {'data': list, 'restricted_to_self': restricted};
}
/// روند فروش در زمان
Future<List<dynamic>> getSalesTrendReport({
required int businessId,
String period = 'month',
int months = 6,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/reports/sales-trend',
query: {'period': period, 'months': months},
);
final data = _extractData(res.data);
return data is List ? data : [];
}
/// لیست فرایندهای CRM (خروجی: لیست یا map بسته به پاسخ API)
Future<dynamic> listProcessDefinitions({
required int businessId,
String? processType,
bool? isActive,
}) async {
final queryParams = <String, dynamic>{};
if (processType != null && processType.isNotEmpty) queryParams['process_type'] = processType;
if (isActive != null) queryParams['is_active'] = isActive;
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/process-definitions',
query: queryParams.isNotEmpty ? queryParams : null,
);
return _extractData(res.data);
}
/// ایجاد فرایند
Future<Map<String, dynamic>> createProcessDefinition({
required int businessId,
required String processType,
required String code,
required String name,
String? description,
bool isDefault = false,
bool isActive = true,
List<Map<String, dynamic>>? stages,
}) async {
final body = <String, dynamic>{
'process_type': processType,
'code': code,
'name': name,
'is_default': isDefault,
'is_active': isActive,
};
if (description != null) body['description'] = description;
if (stages != null && stages.isNotEmpty) body['stages'] = stages;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/process-definitions',
data: body,
);
return _extractData(res.data);
}
/// جزئیات فرایند
Future<Map<String, dynamic>> getProcessDefinition({
required int businessId,
required int definitionId,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/process-definitions/$definitionId',
);
return _extractData(res.data);
}
/// ویرایش فرایند
Future<Map<String, dynamic>> updateProcessDefinition({
required int businessId,
required int definitionId,
String? name,
String? description,
bool? isDefault,
bool? isActive,
}) async {
final body = <String, dynamic>{};
if (name != null) body['name'] = name;
if (description != null) body['description'] = description;
if (isDefault != null) body['is_default'] = isDefault;
if (isActive != null) body['is_active'] = isActive;
final res = await _apiClient.put<dynamic>(
'/api/v1/crm/businesses/$businessId/process-definitions/$definitionId',
data: body,
);
return _extractData(res.data);
}
/// حذف فرایند
Future<void> deleteProcessDefinition({
required int businessId,
required int definitionId,
}) async {
await _apiClient.delete(
'/api/v1/crm/businesses/$businessId/process-definitions/$definitionId',
);
}
/// لیست مراحل یک فرایند
Future<Map<String, dynamic>> listStages({
required int businessId,
required int definitionId,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/process-definitions/$definitionId/stages',
);
return _extractData(res.data);
}
/// افزودن مرحله
Future<Map<String, dynamic>> createStage({
required int businessId,
required int definitionId,
required String stageCode,
required String name,
int orderIndex = 0,
String? color,
bool isWin = false,
bool isLost = false,
List<String>? allowTransitionTo,
}) async {
final body = <String, dynamic>{
'stage_code': stageCode,
'name': name,
'order_index': orderIndex,
'is_win': isWin,
'is_lost': isLost,
};
if (color != null) body['color'] = color;
if (allowTransitionTo != null) body['allow_transition_to'] = allowTransitionTo;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/process-definitions/$definitionId/stages',
data: body,
);
return _extractData(res.data);
}
/// ویرایش مرحله
Future<Map<String, dynamic>> updateStage({
required int businessId,
required int definitionId,
required int stageId,
String? stageCode,
String? name,
int? orderIndex,
String? color,
bool? isWin,
bool? isLost,
List<String>? allowTransitionTo,
}) async {
final body = <String, dynamic>{};
if (stageCode != null) body['stage_code'] = stageCode;
if (name != null) body['name'] = name;
if (orderIndex != null) body['order_index'] = orderIndex;
if (color != null) body['color'] = color;
if (isWin != null) body['is_win'] = isWin;
if (isLost != null) body['is_lost'] = isLost;
if (allowTransitionTo != null) body['allow_transition_to'] = allowTransitionTo;
final res = await _apiClient.put<dynamic>(
'/api/v1/crm/businesses/$businessId/process-definitions/$definitionId/stages/$stageId',
data: body,
);
return _extractData(res.data);
}
/// حذف مرحله
Future<void> deleteStage({
required int businessId,
required int definitionId,
required int stageId,
}) async {
await _apiClient.delete(
'/api/v1/crm/businesses/$businessId/process-definitions/$definitionId/stages/$stageId',
);
}
// --- دستیار AI در CRM ---
/// خلاصه و پیشنهاد AI برای سرنخ
Future<Map<String, dynamic>> aiSummarizeLead({
required int businessId,
required int leadId,
}) async {
final res = await _apiClient.post<dynamic>(
'/api/v1/ai/crm/businesses/$businessId/summarize-lead',
data: {'lead_id': leadId},
);
return _extractData(res.data);
}
/// خلاصه و پیشنهاد AI برای فرصت فروش
Future<Map<String, dynamic>> aiSummarizeDeal({
required int businessId,
required int dealId,
}) async {
final res = await _apiClient.post<dynamic>(
'/api/v1/ai/crm/businesses/$businessId/summarize-deal',
data: {'deal_id': dealId},
);
return _extractData(res.data);
}
/// پیشنهاد متن فعالیت
Future<Map<String, dynamic>> aiSuggestActivityText({
required int businessId,
required int personId,
String activityType = 'note',
int? dealId,
}) async {
final data = <String, dynamic>{'person_id': personId, 'activity_type': activityType};
if (dealId != null) data['deal_id'] = dealId;
final res = await _apiClient.post<dynamic>(
'/api/v1/ai/crm/businesses/$businessId/suggest-activity-text',
data: data,
);
return _extractData(res.data);
}
/// پیشنهاد احتمال موفقیت فرصت فروش
Future<Map<String, dynamic>> aiSuggestDealProbability({
required int businessId,
required int dealId,
}) async {
final res = await _apiClient.post<dynamic>(
'/api/v1/ai/crm/businesses/$businessId/suggest-deal-probability',
data: {'deal_id': dealId},
);
return _extractData(res.data);
}
/// لیست سرنخ‌ها
Future<Map<String, dynamic>> listLeads({
required int businessId,
int? processDefinitionId,
int? stageId,
int? assignedToUserId,
String? search,
String? fromDate,
String? toDate,
bool? openOnly,
int page = 1,
int limit = 50,
}) async {
final queryParams = <String, dynamic>{'page': page, 'limit': limit};
if (processDefinitionId != null) queryParams['process_definition_id'] = processDefinitionId;
if (stageId != null) queryParams['stage_id'] = stageId;
if (assignedToUserId != null) queryParams['assigned_to_user_id'] = assignedToUserId;
if (search != null && search.trim().isNotEmpty) queryParams['search'] = search.trim();
if (fromDate != null && fromDate.isNotEmpty) queryParams['from_date'] = fromDate;
if (toDate != null && toDate.isNotEmpty) queryParams['to_date'] = toDate;
if (openOnly != null) queryParams['open_only'] = openOnly;
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/leads',
query: queryParams,
);
return _extractData(res.data);
}
/// ایجاد سرنخ
Future<Map<String, dynamic>> createLead({
required int businessId,
required int processDefinitionId,
required int stageId,
required String name,
String? code,
String? sourceCode,
String? companyName,
String? mobile,
String? email,
String? description,
int? assignedToUserId,
DateTime? nextFollowUpAt,
List<int>? tagIds,
Map<String, dynamic>? customFields,
}) async {
final body = <String, dynamic>{
'process_definition_id': processDefinitionId,
'stage_id': stageId,
'name': name,
};
if (code != null && code.trim().isNotEmpty) body['code'] = code.trim();
if (sourceCode != null) body['source_code'] = sourceCode;
if (companyName != null) body['company_name'] = companyName;
if (mobile != null) body['mobile'] = mobile;
if (email != null) body['email'] = email;
if (description != null) body['description'] = description;
if (assignedToUserId != null) body['assigned_to_user_id'] = assignedToUserId;
if (nextFollowUpAt != null) body['next_follow_up_at'] = nextFollowUpAt.toIso8601String();
if (tagIds != null) body['tag_ids'] = tagIds;
if (customFields != null) body['custom_fields'] = customFields;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/leads',
data: body,
);
return _extractData(res.data);
}
/// لیست فرصت‌های فروش
Future<Map<String, dynamic>> listDeals({
required int businessId,
int? processDefinitionId,
int? stageId,
int? personId,
int? assignedToUserId,
String? search,
String? fromDate,
String? toDate,
bool? openOnly,
int page = 1,
int limit = 50,
}) async {
final queryParams = <String, dynamic>{'page': page, 'limit': limit};
if (processDefinitionId != null) queryParams['process_definition_id'] = processDefinitionId;
if (stageId != null) queryParams['stage_id'] = stageId;
if (personId != null) queryParams['person_id'] = personId;
if (assignedToUserId != null) queryParams['assigned_to_user_id'] = assignedToUserId;
if (search != null && search.trim().isNotEmpty) queryParams['search'] = search.trim();
if (fromDate != null && fromDate.isNotEmpty) queryParams['from_date'] = fromDate;
if (toDate != null && toDate.isNotEmpty) queryParams['to_date'] = toDate;
if (openOnly != null) queryParams['open_only'] = openOnly;
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/deals',
query: queryParams,
);
return _extractData(res.data);
}
/// لیست فعالیت‌ها (برای یک شخص)
Future<Map<String, dynamic>> listActivities({
required int businessId,
int? personId,
int? leadId,
int? dealId,
String? activityType,
int page = 1,
int limit = 50,
}) async {
final queryParams = <String, dynamic>{'page': page, 'limit': limit};
if (personId != null) queryParams['person_id'] = personId;
if (leadId != null) queryParams['lead_id'] = leadId;
if (dealId != null) queryParams['deal_id'] = dealId;
if (activityType != null) queryParams['activity_type'] = activityType;
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/activities',
query: queryParams,
);
return _extractData(res.data);
}
/// ویرایش فعالیت
Future<Map<String, dynamic>> updateActivity({
required int businessId,
required int activityId,
String? code,
String? activityType,
String? subject,
String? description,
DateTime? activityDate,
int? dealId,
bool? isTask,
DateTime? dueAt,
String? status,
DateTime? completedAt,
int? assignedToUserId,
String? outcome,
String? priority,
Map<String, dynamic>? customFields,
}) async {
final body = <String, dynamic>{};
if (code != null && code.trim().isNotEmpty) body['code'] = code.trim();
if (activityType != null) body['activity_type'] = activityType;
if (subject != null) body['subject'] = subject;
if (description != null) body['description'] = description;
if (activityDate != null) body['activity_date'] = activityDate.toIso8601String();
if (dealId != null) body['deal_id'] = dealId;
if (isTask != null) body['is_task'] = isTask;
if (dueAt != null) body['due_at'] = dueAt.toIso8601String();
if (status != null) body['status'] = status;
if (completedAt != null) body['completed_at'] = completedAt.toIso8601String();
if (assignedToUserId != null) body['assigned_to_user_id'] = assignedToUserId;
if (outcome != null) body['outcome'] = outcome;
if (priority != null) body['priority'] = priority;
if (customFields != null) body['custom_fields'] = customFields;
final res = await _apiClient.put<dynamic>(
'/api/v1/crm/businesses/$businessId/activities/$activityId',
data: body,
);
return _extractData(res.data);
}
/// حذف فعالیت
Future<void> deleteActivity({
required int businessId,
required int activityId,
}) async {
await _apiClient.delete(
'/api/v1/crm/businesses/$businessId/activities/$activityId',
);
}
/// ثبت فعالیت
Future<Map<String, dynamic>> createActivity({
required int businessId,
int? personId,
int? leadId,
required String activityType,
String? code,
String? subject,
String? description,
required DateTime activityDate,
int? dealId,
bool? isTask,
DateTime? dueAt,
String? status,
int? assignedToUserId,
String? outcome,
String? priority,
Map<String, dynamic>? customFields,
}) async {
final body = <String, dynamic>{
'activity_type': activityType,
'activity_date': activityDate.toIso8601String(),
};
if (personId != null) body['person_id'] = personId;
if (leadId != null) body['lead_id'] = leadId;
if (code != null && code.trim().isNotEmpty) body['code'] = code.trim();
if (subject != null) body['subject'] = subject;
if (description != null) body['description'] = description;
if (dealId != null) body['deal_id'] = dealId;
if (isTask != null) body['is_task'] = isTask;
if (dueAt != null) body['due_at'] = dueAt.toIso8601String();
if (status != null) body['status'] = status;
if (assignedToUserId != null) body['assigned_to_user_id'] = assignedToUserId;
if (outcome != null) body['outcome'] = outcome;
if (priority != null) body['priority'] = priority;
if (customFields != null) body['custom_fields'] = customFields;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/activities',
data: body,
);
return _extractData(res.data);
}
/// حذف فرصت فروش
Future<void> deleteDeal({
required int businessId,
required int dealId,
}) async {
await _apiClient.delete(
'/api/v1/crm/businesses/$businessId/deals/$dealId',
);
}
/// لیست اسناد مرتبط با یک شخص (برای انتخاب در بستن معامله)
Future<List<Map<String, dynamic>>> listDocumentsForPerson({
required int businessId,
required int personId,
int limit = 100,
}) async {
try {
final res = await _apiClient.post<dynamic>(
'/api/v1/kardex/businesses/$businessId/lines',
data: {
'person_ids': [personId],
'result_scope': 'lines_matching',
'take': limit,
'skip': 0,
},
);
final body = res.data;
if (body == null || body['success'] != true) return [];
final data = body['data'];
if (data == null || data['items'] is! List) return [];
final items = data['items'] as List;
final seen = <int>{};
final docs = <Map<String, dynamic>>[];
for (final item in items) {
final m = item is Map ? item as Map<String, dynamic> : null;
if (m == null) continue;
final docId = (m['document_id'] as num?)?.toInt();
if (docId == null || seen.contains(docId)) continue;
seen.add(docId);
docs.add({
'id': docId,
'document_code': m['document_code']?.toString(),
'document_date': m['document_date']?.toString(),
'document_type': m['document_type']?.toString(),
'document_type_name': m['document_type_name']?.toString(),
});
}
return docs;
} catch (_) {
return [];
}
}
/// به‌روزرسانی فرصت فروش
Future<Map<String, dynamic>> updateDeal({
required int businessId,
required int dealId,
int? stageId,
String? code,
String? title,
num? amount,
int? currencyId,
int? probabilityPercent,
DateTime? expectedCloseDate,
DateTime? nextFollowUpAt,
int? assignedToUserId,
String? description,
int? documentId,
DateTime? closedAt,
String? wonReasonCode,
String? lostReasonCode,
String? competitorName,
List<int>? tagIds,
Map<String, dynamic>? customFields,
}) async {
final body = <String, dynamic>{};
if (stageId != null) body['stage_id'] = stageId;
if (code != null && code.trim().isNotEmpty) body['code'] = code.trim();
if (title != null) body['title'] = title;
if (amount != null) body['amount'] = amount;
if (currencyId != null) body['currency_id'] = currencyId;
if (probabilityPercent != null) body['probability_percent'] = probabilityPercent;
if (expectedCloseDate != null) body['expected_close_date'] = expectedCloseDate.toIso8601String().split('T')[0];
if (nextFollowUpAt != null) body['next_follow_up_at'] = nextFollowUpAt.toIso8601String();
if (assignedToUserId != null) body['assigned_to_user_id'] = assignedToUserId;
if (description != null) body['description'] = description;
if (documentId != null) body['document_id'] = documentId;
if (closedAt != null) body['closed_at'] = closedAt.toIso8601String();
if (wonReasonCode != null) body['won_reason_code'] = wonReasonCode;
if (lostReasonCode != null) body['lost_reason_code'] = lostReasonCode;
if (competitorName != null) body['competitor_name'] = competitorName;
if (tagIds != null) body['tag_ids'] = tagIds;
if (customFields != null) body['custom_fields'] = customFields;
final res = await _apiClient.put<dynamic>(
'/api/v1/crm/businesses/$businessId/deals/$dealId',
data: body,
);
return _extractData(res.data);
}
/// ایجاد فرصت فروش
Future<Map<String, dynamic>> createDeal({
required int businessId,
required int personId,
required int processDefinitionId,
required int stageId,
required String title,
required num amount,
String? code,
int? currencyId,
int? probabilityPercent,
DateTime? expectedCloseDate,
DateTime? nextFollowUpAt,
int? assignedToUserId,
String? description,
List<int>? tagIds,
Map<String, dynamic>? customFields,
}) async {
final body = <String, dynamic>{
'person_id': personId,
'process_definition_id': processDefinitionId,
'stage_id': stageId,
'title': title,
'amount': amount,
};
if (code != null && code.trim().isNotEmpty) body['code'] = code.trim();
if (currencyId != null) body['currency_id'] = currencyId;
if (probabilityPercent != null) body['probability_percent'] = probabilityPercent;
if (expectedCloseDate != null) body['expected_close_date'] = expectedCloseDate.toIso8601String().split('T')[0];
if (nextFollowUpAt != null) body['next_follow_up_at'] = nextFollowUpAt.toIso8601String();
if (assignedToUserId != null) body['assigned_to_user_id'] = assignedToUserId;
if (description != null) body['description'] = description;
if (tagIds != null) body['tag_ids'] = tagIds;
if (customFields != null) body['custom_fields'] = customFields;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/deals',
data: body,
);
return _extractData(res.data);
}
/// حذف سرنخ
Future<void> deleteLead({
required int businessId,
required int leadId,
}) async {
await _apiClient.delete(
'/api/v1/crm/businesses/$businessId/leads/$leadId',
);
}
/// تبدیل سرنخ به مشتری (با امکان ایجاد همزمان فرصت فروش)
Future<Map<String, dynamic>> convertLeadToCustomer({
required int businessId,
required int leadId,
Map<String, dynamic>? createDeal,
}) async {
final body = createDeal != null && createDeal.isNotEmpty ? {'create_deal': createDeal} : null;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/leads/$leadId/convert',
data: body,
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
/// به‌روزرسانی سرنخ
Future<Map<String, dynamic>> updateLead({
required int businessId,
required int leadId,
int? stageId,
String? code,
String? name,
String? sourceCode,
String? companyName,
String? mobile,
String? email,
String? description,
int? assignedToUserId,
DateTime? nextFollowUpAt,
List<int>? tagIds,
Map<String, dynamic>? customFields,
}) async {
final body = <String, dynamic>{};
if (stageId != null) body['stage_id'] = stageId;
if (code != null && code.trim().isNotEmpty) body['code'] = code.trim();
if (name != null) body['name'] = name;
if (sourceCode != null) body['source_code'] = sourceCode;
if (companyName != null) body['company_name'] = companyName;
if (mobile != null) body['mobile'] = mobile;
if (email != null) body['email'] = email;
if (description != null) body['description'] = description;
if (assignedToUserId != null) body['assigned_to_user_id'] = assignedToUserId;
if (nextFollowUpAt != null) body['next_follow_up_at'] = nextFollowUpAt.toIso8601String();
if (tagIds != null) body['tag_ids'] = tagIds;
if (customFields != null) body['custom_fields'] = customFields;
final res = await _apiClient.put<dynamic>(
'/api/v1/crm/businesses/$businessId/leads/$leadId',
data: body,
);
return _extractData(res.data);
}
/// انواع یادداشت CRM (چندزبانه در سرور)
Future<List<dynamic>> listCrmNoteTypes({required int businessId}) async {
final res = await _apiClient.get<dynamic>('/api/v1/crm/businesses/$businessId/note-types');
final data = _extractData(res.data);
if (data is Map && data['items'] is List) return data['items'] as List<dynamic>;
return [];
}
/// ایجاد نوع یادداشت سفارشی (code + title_i18n + scheduling_mode + allow_comments)
Future<Map<String, dynamic>> createCrmNoteType({
required int businessId,
required Map<String, dynamic> body,
}) async {
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/note-types',
data: body,
);
final data = _extractData(res.data);
return data is Map<String, dynamic> ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
/// یادداشت‌های بازه تقویم (from/to میلادی YYYY-MM-DD)
Future<List<dynamic>> listCrmNotes({
required int businessId,
required String fromDate,
required String toDate,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/notes',
query: {'from_date': fromDate, 'to_date': toDate},
);
final data = _extractData(res.data);
if (data is Map && data['items'] is List) return data['items'] as List<dynamic>;
return [];
}
Future<Map<String, dynamic>> getCrmNote({required int businessId, required int noteId}) async {
final res = await _apiClient.get<dynamic>('/api/v1/crm/businesses/$businessId/notes/$noteId');
final data = _extractData(res.data);
return data is Map<String, dynamic> ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
Future<Map<String, dynamic>> createCrmNote({
required int businessId,
required Map<String, dynamic> body,
}) async {
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/notes',
data: body,
);
final data = _extractData(res.data);
return data is Map<String, dynamic> ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
Future<Map<String, dynamic>> updateCrmNote({
required int businessId,
required int noteId,
required Map<String, dynamic> body,
}) async {
final res = await _apiClient.patch<dynamic>(
'/api/v1/crm/businesses/$businessId/notes/$noteId',
data: body,
);
final data = _extractData(res.data);
return data is Map<String, dynamic> ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
Future<void> deleteCrmNote({required int businessId, required int noteId}) async {
await _apiClient.delete<void>('/api/v1/crm/businesses/$businessId/notes/$noteId');
}
Future<List<dynamic>> listCrmNoteComments({required int businessId, required int noteId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/notes/$noteId/comments',
);
final data = _extractData(res.data);
if (data is Map && data['items'] is List) return data['items'] as List<dynamic>;
return [];
}
Future<void> addCrmNoteComment({
required int businessId,
required int noteId,
required String body,
}) async {
await _apiClient.post<void>(
'/api/v1/crm/businesses/$businessId/notes/$noteId/comments',
data: {'body': body},
);
}
Future<void> deleteCrmNoteComment({
required int businessId,
required int noteId,
required int commentId,
}) async {
await _apiClient.delete<void>(
'/api/v1/crm/businesses/$businessId/notes/$noteId/comments/$commentId',
);
}
Future<List<dynamic>> listCrmNoteAudit({required int businessId, required int noteId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/notes/$noteId/audit',
);
final data = _extractData(res.data);
if (data is Map && data['items'] is List) return data['items'] as List<dynamic>;
return [];
}
// ==========================================================================
// صف کاری من (My Work Queue)
// ==========================================================================
/// صف کاری کاربر: تسک‌های باز، پیگیری سرنخ/فرصت و نقض SLA
Future<Map<String, dynamic>> getMyWorkQueue({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/my-work-queue',
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
// ==========================================================================
// دید ۳۶۰ مشتری
// ==========================================================================
Future<Map<String, dynamic>> getCustomer360({
required int businessId,
required int personId,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/persons/$personId/customer-360',
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
// ==========================================================================
// برچسب‌ها (Tags)
// ==========================================================================
Future<List<Map<String, dynamic>>> listTags({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/tags',
);
final data = _extractData(res.data);
if (data is Map && data['items'] is List) {
return (data['items'] as List).map<Map<String, dynamic>>((e) => Map<String, dynamic>.from(e as Map)).toList();
}
return [];
}
Future<Map<String, dynamic>> createTag({
required int businessId,
required String name,
String? color,
int sortOrder = 0,
}) async {
final body = <String, dynamic>{'name': name, 'sort_order': sortOrder};
if (color != null && color.isNotEmpty) body['color'] = color;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/tags',
data: body,
);
return _extractData(res.data);
}
Future<Map<String, dynamic>> updateTag({
required int businessId,
required int tagId,
String? name,
String? color,
bool? isActive,
int? sortOrder,
}) async {
final body = <String, dynamic>{};
if (name != null) body['name'] = name;
if (color != null) body['color'] = color;
if (isActive != null) body['is_active'] = isActive;
if (sortOrder != null) body['sort_order'] = sortOrder;
final res = await _apiClient.patch<dynamic>(
'/api/v1/crm/businesses/$businessId/tags/$tagId',
data: body,
);
return _extractData(res.data);
}
Future<void> deleteTag({required int businessId, required int tagId}) async {
await _apiClient.delete<void>('/api/v1/crm/businesses/$businessId/tags/$tagId');
}
Future<List<dynamic>> setLeadTags({
required int businessId,
required int leadId,
required List<int> tagIds,
}) async {
final res = await _apiClient.put<dynamic>(
'/api/v1/crm/businesses/$businessId/leads/$leadId/tags',
data: {'tag_ids': tagIds},
);
final data = _extractData(res.data);
if (data is Map && data['tags'] is List) return data['tags'] as List<dynamic>;
return [];
}
Future<List<dynamic>> setDealTags({
required int businessId,
required int dealId,
required List<int> tagIds,
}) async {
final res = await _apiClient.put<dynamic>(
'/api/v1/crm/businesses/$businessId/deals/$dealId/tags',
data: {'tag_ids': tagIds},
);
final data = _extractData(res.data);
if (data is Map && data['tags'] is List) return data['tags'] as List<dynamic>;
return [];
}
// ==========================================================================
// دلایل بستن معامله (Close Reasons)
// ==========================================================================
Future<List<Map<String, dynamic>>> listCloseReasons({
required int businessId,
String? reasonType,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/close-reasons',
query: reasonType != null ? {'reason_type': reasonType} : null,
);
final data = _extractData(res.data);
if (data is Map && data['items'] is List) {
return (data['items'] as List).map<Map<String, dynamic>>((e) => Map<String, dynamic>.from(e as Map)).toList();
}
return [];
}
Future<Map<String, dynamic>> createCloseReason({
required int businessId,
required String reasonType,
required String code,
required String name,
int sortOrder = 0,
}) async {
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/close-reasons',
data: {
'reason_type': reasonType,
'code': code,
'name': name,
'sort_order': sortOrder,
},
);
return _extractData(res.data);
}
Future<Map<String, dynamic>> updateCloseReason({
required int businessId,
required int reasonId,
String? name,
bool? isActive,
int? sortOrder,
}) async {
final body = <String, dynamic>{};
if (name != null) body['name'] = name;
if (isActive != null) body['is_active'] = isActive;
if (sortOrder != null) body['sort_order'] = sortOrder;
final res = await _apiClient.patch<dynamic>(
'/api/v1/crm/businesses/$businessId/close-reasons/$reasonId',
data: body,
);
return _extractData(res.data);
}
Future<void> deleteCloseReason({required int businessId, required int reasonId}) async {
await _apiClient.delete<void>('/api/v1/crm/businesses/$businessId/close-reasons/$reasonId');
}
// ==========================================================================
// فیلدهای سفارشی (Custom Fields)
// ==========================================================================
Future<List<Map<String, dynamic>>> listCustomFields({
required int businessId,
String? entityType,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/custom-fields',
query: entityType != null ? {'entity_type': entityType} : null,
);
final data = _extractData(res.data);
if (data is Map && data['items'] is List) {
return (data['items'] as List).map<Map<String, dynamic>>((e) => Map<String, dynamic>.from(e as Map)).toList();
}
return [];
}
Future<Map<String, dynamic>> createCustomField({
required int businessId,
required String entityType,
required String fieldKey,
required String label,
required String fieldType,
List<String>? options,
bool isRequired = false,
int sortOrder = 0,
}) async {
final body = <String, dynamic>{
'entity_type': entityType,
'field_key': fieldKey,
'label': label,
'field_type': fieldType,
'is_required': isRequired,
'sort_order': sortOrder,
};
if (options != null) body['options'] = options;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/custom-fields',
data: body,
);
return _extractData(res.data);
}
Future<Map<String, dynamic>> updateCustomField({
required int businessId,
required int fieldId,
String? label,
List<String>? options,
bool? isRequired,
int? sortOrder,
bool? isActive,
}) async {
final body = <String, dynamic>{};
if (label != null) body['label'] = label;
if (options != null) body['options'] = options;
if (isRequired != null) body['is_required'] = isRequired;
if (sortOrder != null) body['sort_order'] = sortOrder;
if (isActive != null) body['is_active'] = isActive;
final res = await _apiClient.patch<dynamic>(
'/api/v1/crm/businesses/$businessId/custom-fields/$fieldId',
data: body,
);
return _extractData(res.data);
}
Future<void> deleteCustomField({required int businessId, required int fieldId}) async {
await _apiClient.delete<void>('/api/v1/crm/businesses/$businessId/custom-fields/$fieldId');
}
// ==========================================================================
// خطوط فرصت فروش (Deal Lines)
// ==========================================================================
Future<List<Map<String, dynamic>>> getDealLines({
required int businessId,
required int dealId,
}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/deals/$dealId/lines',
);
final data = _extractData(res.data);
if (data is Map && data['items'] is List) {
return (data['items'] as List).map<Map<String, dynamic>>((e) => Map<String, dynamic>.from(e as Map)).toList();
}
return [];
}
/// جایگزینی کامل خطوط فرصت فروش. هر خط: {product_id?, description?, quantity, unit_price, discount_percent?, sort_order?}
Future<Map<String, dynamic>> replaceDealLines({
required int businessId,
required int dealId,
required List<Map<String, dynamic>> lines,
}) async {
final res = await _apiClient.put<dynamic>(
'/api/v1/crm/businesses/$businessId/deals/$dealId/lines',
data: {'lines': lines},
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
// ==========================================================================
// تبدیل فرصت فروش به فاکتور/پیش‌فاکتور
// ==========================================================================
Future<Map<String, dynamic>> convertDealToInvoice({
required int businessId,
required int dealId,
bool isProforma = true,
bool closeAsWon = false,
String? wonReasonCode,
int? warehouseId,
}) async {
final body = <String, dynamic>{
'is_proforma': isProforma,
'close_as_won': closeAsWon,
};
if (wonReasonCode != null) body['won_reason_code'] = wonReasonCode;
if (warehouseId != null) body['warehouse_id'] = warehouseId;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/deals/$dealId/convert-to-invoice',
data: body,
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
// ==========================================================================
// گزارش دلایل برد/باخت
// ==========================================================================
Future<List<dynamic>> getLostReasonsReport({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/reports/lost-reasons',
);
final data = _extractData(res.data);
return data is List ? data : [];
}
Future<List<dynamic>> getWonReasonsReport({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/reports/won-reasons',
);
final data = _extractData(res.data);
return data is List ? data : [];
}
// ==========================================================================
// توالی‌های خودکار (Sequences)
// ==========================================================================
Future<List<Map<String, dynamic>>> listSequences({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/sequences',
);
final data = _extractData(res.data);
if (data is Map && data['items'] is List) {
return (data['items'] as List).map<Map<String, dynamic>>((e) => Map<String, dynamic>.from(e as Map)).toList();
}
return [];
}
Future<Map<String, dynamic>> createSequence({
required int businessId,
required String name,
String? description,
bool isActive = true,
List<Map<String, dynamic>>? steps,
}) async {
final body = <String, dynamic>{'name': name, 'is_active': isActive};
if (description != null) body['description'] = description;
if (steps != null) body['steps'] = steps;
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/sequences',
data: body,
);
return _extractData(res.data);
}
Future<Map<String, dynamic>> updateSequence({
required int businessId,
required int sequenceId,
String? name,
String? description,
bool? isActive,
List<Map<String, dynamic>>? steps,
}) async {
final body = <String, dynamic>{};
if (name != null) body['name'] = name;
if (description != null) body['description'] = description;
if (isActive != null) body['is_active'] = isActive;
if (steps != null) body['steps'] = steps;
final res = await _apiClient.patch<dynamic>(
'/api/v1/crm/businesses/$businessId/sequences/$sequenceId',
data: body,
);
return _extractData(res.data);
}
Future<void> deleteSequence({required int businessId, required int sequenceId}) async {
await _apiClient.delete<void>('/api/v1/crm/businesses/$businessId/sequences/$sequenceId');
}
Future<Map<String, dynamic>> enrollSequence({
required int businessId,
required int sequenceId,
required String entityType,
required int entityId,
}) async {
final res = await _apiClient.post<dynamic>(
'/api/v1/crm/businesses/$businessId/sequences/$sequenceId/enroll',
data: {'entity_type': entityType, 'entity_id': entityId},
);
return _extractData(res.data);
}
// ==========================================================================
// تنظیمات اتوماسیون CRM
// ==========================================================================
Future<Map<String, dynamic>> getAutomationSettings({required int businessId}) async {
final res = await _apiClient.get<dynamic>(
'/api/v1/crm/businesses/$businessId/automation-settings',
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
Future<Map<String, dynamic>> updateAutomationSettings({
required int businessId,
int? leadSlaHours,
bool? autoAssignEnabled,
List<int>? autoAssignUserIds,
bool? followUpNotifyEnabled,
int? staleDealDays,
Map<String, dynamic>? scoreRules,
}) async {
final body = <String, dynamic>{};
if (leadSlaHours != null) body['lead_sla_hours'] = leadSlaHours;
if (autoAssignEnabled != null) body['auto_assign_enabled'] = autoAssignEnabled;
if (autoAssignUserIds != null) body['auto_assign_user_ids'] = autoAssignUserIds;
if (followUpNotifyEnabled != null) body['follow_up_notify_enabled'] = followUpNotifyEnabled;
if (staleDealDays != null) body['stale_deal_days'] = staleDealDays;
if (scoreRules != null) body['score_rules'] = scoreRules;
final res = await _apiClient.patch<dynamic>(
'/api/v1/crm/businesses/$businessId/automation-settings',
data: body,
);
final data = _extractData(res.data);
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
}