import 'dart:developer'; import 'package:data_channel/data_channel.dart'; import 'package:dio/dio.dart' show Response; import 'package:injectable/injectable.dart'; import '../../../common/api/api_client.dart'; import '../../../common/api/api_failure.dart'; import '../../../common/extension/extension.dart'; import '../../../common/function/app_function.dart'; import '../../../common/url/api_path.dart'; import '../../../domain/analytic/analytic.dart'; import '../analytic_dtos.dart'; @injectable class AnalyticRemoteDataProvider { final ApiClient _apiClient; final String _logName = "AnalyticRemoteDataProvider"; AnalyticRemoteDataProvider(this._apiClient); Future> fetchSales({ required DateTime dateFrom, required DateTime dateTo, String? outletId, }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _apiClient.get( ApiPath.salesAnalytic, params: params, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = SalesAnalyticDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchSalesError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchProfitLoss({ required DateTime dateFrom, required DateTime dateTo, String? outletId, }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _apiClient.get( ApiPath.profitLossAnalytic, params: params, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = ProfitLossAnalyticDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchProfitLossError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchCategory({ required DateTime dateFrom, required DateTime dateTo, String? outletId, }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _apiClient.get( ApiPath.categoryAnalytic, params: params, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = CategoryAnalyticDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchCategoryError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchInventory({ required String outletId, required DateTime dateFrom, required DateTime dateTo, }) async { try { final response = await _apiClient.get( '${ApiPath.inventoryReportDetail}/$outletId', params: { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = InventoryAnalyticDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchInventoryError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchDashboard({ required DateTime dateFrom, required DateTime dateTo, String? outletId, }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _apiClient.get( ApiPath.dashboardAnalytic, params: params, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = DashboardAnalyticDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchDashboardError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchProduct({ required DateTime dateFrom, required DateTime dateTo, String? outletId, }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _apiClient.get( ApiPath.productAnalytic, params: params, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = ProductAnalyticDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchProductError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchPaymentMethod({ required DateTime dateFrom, required DateTime dateTo, String? outletId, }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _apiClient.get( ApiPath.paymentMethodAnalytic, params: params, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = PaymentMethodAnalyticDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchPaymentMethodError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchPurchasing({ required DateTime dateFrom, required DateTime dateTo, String? outletId, String groupBy = 'day', }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, 'group_by': groupBy, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _apiClient.get( ApiPath.purchasingAnalytic, params: params, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = PurchasingAnalyticDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchPurchasingError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchExclusiveSummary({ required DateTime dateFrom, required DateTime dateTo, String? outletId, }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _apiClient.get( ApiPath.exclusiveSummaryAnalytic, params: params, headers: getAuthorizationHeader(), ); if (response.data['data'] == null) { return DC.error(AnalyticFailure.empty()); } final dto = ExclusiveSummaryDto.fromJson(response.data['data']); return DC.data(dto); } on ApiFailure catch (e, s) { log( 'fetchExclusiveSummaryError', name: _logName, error: e, stackTrace: s, ); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchProfitSharing({ required DateTime dateFrom, required DateTime dateTo, String? outletId, String groupBy = 'day', }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, 'group_by': groupBy, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _getProfitSharing('', params); final failure = _profitSharingFailure(response); if (failure != null) return DC.error(failure); final body = response.data; final data = body is Map ? body['data'] : null; if (data == null) { return DC.error(AnalyticFailure.empty()); } final dto = ProfitSharingDto.fromJson(data); return DC.data(dto); } on ApiFailure catch (e, s) { log('fetchProfitSharingError', name: _logName, error: e, stackTrace: s); return DC.error(AnalyticFailure.serverError(e)); } } Future> fetchProfitSharingDetail({ required String parentCategoryId, required DateTime dateFrom, required DateTime dateTo, String? outletId, }) async { try { final Map params = { 'date_from': dateFrom.toServerDate, 'date_to': dateTo.toServerDate, }; if (outletId != null) params['outlet_id'] = outletId; final response = await _getProfitSharing('/$parentCategoryId', params); final failure = _profitSharingFailure(response); if (failure != null) return DC.error(failure); final body = response.data; final data = body is Map ? body['data'] : null; if (data == null) { return DC.error(AnalyticFailure.empty()); } final dto = ProfitSharingDetailDto.fromJson(data); return DC.data(dto); } on ApiFailure catch (e, s) { log( 'fetchProfitSharingDetailError', name: _logName, error: e, stackTrace: s, ); return DC.error(AnalyticFailure.serverError(e)); } } /// GET ke endpoint profit sharing. Selama backend lama masih jalan path baru /// mengembalikan 404, jadi dicoba sekali lagi ke path lama. Future _getProfitSharing( String subPath, Map params, ) async { Future request(String basePath) => _apiClient.get( '$basePath$subPath', params: params, headers: getAuthorizationHeader(), // Status 5xx tidak dilempar supaya `cause` di body masih bisa dibaca. validateStatus: (status) => status != null && status < 600, ); final response = await request(ApiPath.profitSharingAnalytic); if (response.statusCode != 404) return response; return request(ApiPath.profitSharingAnalyticLegacy); } /// Failure dari status error endpoint profit sharing, null kalau berhasil. /// Detail kategori non-team ditolak 500 dengan `cause` "... is not a team". AnalyticFailure? _profitSharingFailure(Response response) { final status = response.statusCode ?? 0; if (status == 404) { return const AnalyticFailure.serverError(ApiFailure.notFound(null)); } if (status < 500) return null; final body = response.data; final errors = body is Map ? body['errors'] : null; final isNotTeam = errors is List && errors.any( (e) => e is Map && '${e['cause']}'.contains('is not a team'), ); if (isNotTeam) return const AnalyticFailure.notTeam(); return const AnalyticFailure.serverError(ApiFailure.internalServerError()); } }