import 'package:dio/dio.dart'; import '../core/api_client.dart'; // duplicate removed import '../core/fiscal_year_controller.dart'; import '../models/business_dashboard_models.dart'; class BusinessDashboardService { final ApiClient _apiClient; final FiscalYearController? fiscalYearController; BusinessDashboardService(this._apiClient, {this.fiscalYearController}); /// دریافت داشبورد کسب و کار Future getDashboard(int businessId) async { try { final response = await _apiClient.post>( '/api/v1/business/$businessId/dashboard', options: Options(headers: { if (fiscalYearController?.fiscalYearId != null) 'X-Fiscal-Year-ID': fiscalYearController!.fiscalYearId.toString(), }), ); if (response.data?['success'] == true) { return BusinessDashboardResponse.fromJson(response.data!['data']); } else { throw Exception('Failed to load dashboard: ${response.data?['message']}'); } } on DioException catch (e) { if (e.response?.statusCode == 403) { throw Exception('دسترسی غیرمجاز به این کسب و کار'); } else if (e.response?.statusCode == 404) { throw Exception('کسب و کار یافت نشد'); } else { throw Exception('خطا در بارگذاری داشبورد: ${e.message}'); } } catch (e) { throw Exception('خطا در بارگذاری داشبورد: $e'); } } /// دریافت لیست اعضای کسب و کار Future getMembers(int businessId) async { try { final response = await _apiClient.post>( '/api/v1/business/$businessId/members', ); if (response.data?['success'] == true) { return BusinessMembersResponse.fromJson(response.data!['data']); } else { throw Exception('Failed to load members: ${response.data?['message']}'); } } on DioException catch (e) { if (e.response?.statusCode == 403) { throw Exception('دسترسی غیرمجاز به این کسب و کار'); } else if (e.response?.statusCode == 404) { throw Exception('کسب و کار یافت نشد'); } else { throw Exception('خطا در بارگذاری اعضا: ${e.message}'); } } catch (e) { throw Exception('خطا در بارگذاری اعضا: $e'); } } /// دریافت آمار کسب و کار Future> getStatistics(int businessId) async { try { final response = await _apiClient.post>( '/api/v1/business/$businessId/statistics', options: Options(headers: { if (fiscalYearController?.fiscalYearId != null) 'X-Fiscal-Year-ID': fiscalYearController!.fiscalYearId.toString(), }), ); if (response.data?['success'] == true) { return response.data!['data']; } else { throw Exception('Failed to load statistics: ${response.data?['message']}'); } } on DioException catch (e) { if (e.response?.statusCode == 403) { throw Exception('دسترسی غیرمجاز به این کسب و کار'); } else if (e.response?.statusCode == 404) { throw Exception('کسب و کار یافت نشد'); } else { throw Exception('خطا در بارگذاری آمار: ${e.message}'); } } catch (e) { throw Exception('خطا در بارگذاری آمار: $e'); } } Future>> listFiscalYears(int businessId) async { final res = await _apiClient.get>('/api/v1/business/$businessId/fiscal-years'); final data = res.data as Map; final items = (data['data']?['items'] as List?) ?? const []; return items.cast>(); } /// دریافت لیست کسب و کارهای کاربر (مالک + عضو) Future> getUserBusinesses() async { try { // دریافت کسب و کارهای کاربر (مالک + عضو) با POST request final response = await _apiClient.post>( '/api/v1/businesses/list', data: { 'take': 100, 'skip': 0, 'sort_by': 'created_at', 'sort_desc': true, 'search': null, }, ); List businesses = []; if (response.data?['success'] == true) { final items = response.data!['data']['items'] as List; businesses.addAll( items.map((item) => BusinessWithPermission.fromJson(item)), ); } return businesses; } on DioException catch (e) { if (e.response?.statusCode == 401) { throw Exception('لطفاً ابتدا وارد شوید'); } else { throw Exception('خطا در بارگذاری کسب و کارها: ${e.message}'); } } catch (e) { throw Exception('خطا در بارگذاری کسب و کارها: $e'); } } /// دریافت اطلاعات کسب و کار همراه با دسترسی‌های کاربر Future getBusinessWithPermissions(int businessId) async { print('=== getBusinessWithPermissions START ==='); print('Business ID: $businessId'); try { print('Calling API: /api/v1/business/$businessId/info-with-permissions'); final response = await _apiClient.post>( '/api/v1/business/$businessId/info-with-permissions', ); print('API Response received:'); print(' - Success: ${response.data?['success']}'); print(' - Message: ${response.data?['message']}'); print(' - Data: ${response.data?['data']}'); if (response.data?['success'] == true) { final data = response.data!['data'] as Map; // تبدیل اطلاعات کسب و کار final businessInfo = data['business_info'] as Map; // نرمال‌سازی دسترسی‌ها: هم Map پشتیبانی می‌شود و هم List مثل 'inventory.read' final dynamic userPermissionsRaw = data['user_permissions']; final Map userPermissions = {}; if (userPermissionsRaw is Map) { userPermissions.addAll(userPermissionsRaw); } else if (userPermissionsRaw is List) { for (final item in userPermissionsRaw) { if (item is String) { final parts = item.split('.'); if (parts.length >= 2) { final String section = parts[0]; final String action = parts[1]; final Map sectionPerms = (userPermissions[section] as Map?) ?? {}; sectionPerms[action] = true; userPermissions[section] = sectionPerms; } } } } final isOwner = data['is_owner'] as bool? ?? false; final role = data['role'] as String? ?? 'عضو'; final hasAccess = data['has_access'] as bool? ?? false; print('Parsed data:'); print(' - Business Info: $businessInfo'); print(' - User Permissions: $userPermissions'); print(' - Is Owner: $isOwner'); print(' - Role: $role'); print(' - Has Access: $hasAccess'); if (!hasAccess) { print('Access denied by API'); throw Exception('دسترسی غیرمجاز به این کسب و کار'); } // ساخت یک Map ترکیبی از اطلاعات کسب و کار + متادیتاهای دسترسی کاربر final Map combined = { ...businessInfo, 'is_owner': isOwner, 'role': role, 'permissions': userPermissions, }; // اگر سرور ارز پیش‌فرض یا لیست ارزها را نیز ارسال کرد، اضافه کنیم if (data.containsKey('default_currency')) { combined['default_currency'] = data['default_currency']; } if (data.containsKey('currencies')) { combined['currencies'] = data['currencies']; } // استفاده از fromJson برای مدیریت امن انواع (مثلاً created_at می‌تواند String یا Map باشد) final businessWithPermission = BusinessWithPermission.fromJson( Map.from(combined), ); print('BusinessWithPermission created:'); print(' - Name: ${businessWithPermission.name}'); print(' - ID: ${businessWithPermission.id}'); print(' - Is Owner: ${businessWithPermission.isOwner}'); print(' - Role: ${businessWithPermission.role}'); print(' - Permissions: ${businessWithPermission.permissions}'); print('=== getBusinessWithPermissions END ==='); return businessWithPermission; } else { print('API returned error: ${response.data?['message']}'); throw Exception('Failed to load business info: ${response.data?['message']}'); } } on DioException catch (e) { print('DioException occurred:'); print(' - Status Code: ${e.response?.statusCode}'); print(' - Response Data: ${e.response?.data}'); print(' - Message: ${e.message}'); if (e.response?.statusCode == 403) { throw Exception('دسترسی غیرمجاز به این کسب و کار'); } else if (e.response?.statusCode == 404) { throw Exception('کسب و کار یافت نشد'); } else { throw Exception('خطا در بارگذاری اطلاعات کسب و کار: ${e.message}'); } } catch (e) { print('General Exception: $e'); throw Exception('خطا در بارگذاری اطلاعات کسب و کار: $e'); } } // ===== Dashboard V2 (Responsive Widgets) ===== Future getWidgetDefinitions(int businessId) async { final res = await _apiClient.get>( '/api/v1/business/$businessId/dashboard/widgets/definitions', ); final data = res.data?['data'] as Map? ?? const {}; return DashboardDefinitionsResponse.fromJson(data); } Future getLayoutProfile({ required int businessId, required String breakpoint, }) async { final res = await _apiClient.get>( '/api/v1/business/$businessId/dashboard/layout', query: {'breakpoint': breakpoint}, ); final data = res.data?['data'] as Map? ?? const {}; return DashboardLayoutProfile.fromJson(data); } Future putLayoutProfile({ required int businessId, required String breakpoint, required List items, }) async { final body = { 'breakpoint': breakpoint, 'items': items.map((e) => e.toJson()).toList(), }; final res = await _apiClient.put>( '/api/v1/business/$businessId/dashboard/layout', data: body, ); final data = res.data?['data'] as Map? ?? const {}; return DashboardLayoutProfile.fromJson(data); } Future> getWidgetsBatchData({ required int businessId, required List widgetKeys, Map? filters, }) async { final res = await _api_client_postJson( '/api/v1/business/$businessId/dashboard/data', { 'widget_keys': widgetKeys, 'filters': filters ?? const {}, }, ); final data = (res['data'] as Map?) ?? const {}; return Map.from(data); } // Business default layout (owner can publish) Future getBusinessDefaultLayout({ required int businessId, required String breakpoint, }) async { final res = await _apiClient.get>( '/api/v1/business/$businessId/dashboard/layout/default', query: {'breakpoint': breakpoint}, ); final data = res.data?['data'] as Map? ?? const {}; if (data.isEmpty) return null; return DashboardLayoutProfile.fromJson(data); } Future putBusinessDefaultLayout({ required int businessId, required String breakpoint, required List items, }) async { final body = { 'breakpoint': breakpoint, 'items': items.map((e) => e.toJson()).toList(), }; final res = await _apiClient.put>( '/api/v1/business/$businessId/dashboard/layout/default', data: body, ); final data = res.data?['data'] as Map? ?? const {}; return DashboardLayoutProfile.fromJson(data); } Future> _api_client_postJson(String path, Map body) async { final response = await _apiClient.post>( path, data: body, options: Options(headers: { if (fiscalYearController?.fiscalYearId != null) 'X-Fiscal-Year-ID': fiscalYearController!.fiscalYearId.toString(), }), ); if (response.data?['success'] == true) { return Map.from(response.data!); } throw Exception(response.data?['message'] ?? 'API error'); } }