feat: sales analytic

This commit is contained in:
efrilm
2025-08-17 10:10:31 +07:00
parent e7525238fe
commit f84090c0e6
21 changed files with 3703 additions and 407 deletions
@@ -0,0 +1,8 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/analytic/analytic.dart';
part 'analytic_dtos.freezed.dart';
part 'analytic_dtos.g.dart';
part 'dto/sales_analytic_dto.dart';
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,89 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'analytic_dtos.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$SalesAnalyticDtoImpl _$$SalesAnalyticDtoImplFromJson(
Map<String, dynamic> json,
) => _$SalesAnalyticDtoImpl(
organizationId: json['organization_id'] as String?,
outletId: json['outlet_id'] as String?,
dateFrom: json['date_from'] == null
? null
: DateTime.parse(json['date_from'] as String),
dateTo: json['date_to'] == null
? null
: DateTime.parse(json['date_to'] as String),
groupBy: json['group_by'] as String?,
summary: json['summary'] == null
? null
: SalesAnalyticSummaryDto.fromJson(
json['summary'] as Map<String, dynamic>,
),
data: (json['data'] as List<dynamic>?)
?.map((e) => SalesAnalyticDataDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$SalesAnalyticDtoImplToJson(
_$SalesAnalyticDtoImpl instance,
) => <String, dynamic>{
'organization_id': instance.organizationId,
'outlet_id': instance.outletId,
'date_from': instance.dateFrom?.toIso8601String(),
'date_to': instance.dateTo?.toIso8601String(),
'group_by': instance.groupBy,
'summary': instance.summary,
'data': instance.data,
};
_$SalesAnalyticSummaryDtoImpl _$$SalesAnalyticSummaryDtoImplFromJson(
Map<String, dynamic> json,
) => _$SalesAnalyticSummaryDtoImpl(
totalSales: json['total_sales'] as num?,
totalOrders: json['total_orders'] as num?,
totalItems: json['total_items'] as num?,
averageOrderValue: json['average_order_value'] as num?,
totalTax: json['total_tax'] as num?,
totalDiscount: json['total_discount'] as num?,
netSales: json['net_sales'] as num?,
);
Map<String, dynamic> _$$SalesAnalyticSummaryDtoImplToJson(
_$SalesAnalyticSummaryDtoImpl instance,
) => <String, dynamic>{
'total_sales': instance.totalSales,
'total_orders': instance.totalOrders,
'total_items': instance.totalItems,
'average_order_value': instance.averageOrderValue,
'total_tax': instance.totalTax,
'total_discount': instance.totalDiscount,
'net_sales': instance.netSales,
};
_$SalesAnalyticDataDtoImpl _$$SalesAnalyticDataDtoImplFromJson(
Map<String, dynamic> json,
) => _$SalesAnalyticDataDtoImpl(
date: json['date'] == null ? null : DateTime.parse(json['date'] as String),
sales: json['sales'] as num?,
orders: json['orders'] as num?,
items: json['items'] as num?,
tax: json['tax'] as num?,
discount: json['discount'] as num?,
netSales: json['net_sales'] as num?,
);
Map<String, dynamic> _$$SalesAnalyticDataDtoImplToJson(
_$SalesAnalyticDataDtoImpl instance,
) => <String, dynamic>{
'date': instance.date?.toIso8601String(),
'sales': instance.sales,
'orders': instance.orders,
'items': instance.items,
'tax': instance.tax,
'discount': instance.discount,
'net_sales': instance.netSales,
};
@@ -0,0 +1,45 @@
import 'dart:developer';
import 'package:data_channel/data_channel.dart';
import 'package:injectable/injectable.dart';
import '../../../common/api/api_client.dart';
import '../../../common/api/api_failure.dart';
import '../../../common/extension/extension.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<DC<AnalyticFailure, SalesAnalyticDto>> fetchSales({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final response = await _apiClient.get(
ApiPath.salesAnalytic,
params: {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
);
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));
}
}
}
@@ -0,0 +1,85 @@
part of '../analytic_dtos.dart';
@freezed
class SalesAnalyticDto with _$SalesAnalyticDto {
const SalesAnalyticDto._();
const factory SalesAnalyticDto({
@JsonKey(name: 'organization_id') String? organizationId,
@JsonKey(name: 'outlet_id') String? outletId,
@JsonKey(name: 'date_from') DateTime? dateFrom,
@JsonKey(name: 'date_to') DateTime? dateTo,
@JsonKey(name: 'group_by') String? groupBy,
@JsonKey(name: 'summary') SalesAnalyticSummaryDto? summary,
@JsonKey(name: 'data') List<SalesAnalyticDataDto>? data,
}) = _SalesAnalyticDto;
factory SalesAnalyticDto.fromJson(Map<String, dynamic> json) =>
_$SalesAnalyticDtoFromJson(json);
SalesAnalytic toDomain() => SalesAnalytic(
organizationId: organizationId ?? '',
outletId: outletId ?? '',
dateFrom: dateFrom ?? DateTime.fromMillisecondsSinceEpoch(0),
dateTo: dateTo ?? DateTime.fromMillisecondsSinceEpoch(0),
groupBy: groupBy ?? '',
summary: summary?.toDomain() ?? SalesAnalyticSummary.empty(),
data: data?.map((e) => e.toDomain()).toList() ?? [],
);
}
@freezed
class SalesAnalyticSummaryDto with _$SalesAnalyticSummaryDto {
const SalesAnalyticSummaryDto._();
const factory SalesAnalyticSummaryDto({
@JsonKey(name: 'total_sales') num? totalSales,
@JsonKey(name: 'total_orders') num? totalOrders,
@JsonKey(name: 'total_items') num? totalItems,
@JsonKey(name: 'average_order_value') num? averageOrderValue,
@JsonKey(name: 'total_tax') num? totalTax,
@JsonKey(name: 'total_discount') num? totalDiscount,
@JsonKey(name: 'net_sales') num? netSales,
}) = _SalesAnalyticSummaryDto;
factory SalesAnalyticSummaryDto.fromJson(Map<String, dynamic> json) =>
_$SalesAnalyticSummaryDtoFromJson(json);
SalesAnalyticSummary toDomain() => SalesAnalyticSummary(
totalSales: totalSales?.toInt() ?? 0,
totalOrders: totalOrders?.toInt() ?? 0,
totalItems: totalItems?.toInt() ?? 0,
averageOrderValue: averageOrderValue?.toDouble() ?? 0,
totalTax: totalTax?.toInt() ?? 0,
totalDiscount: totalDiscount?.toInt() ?? 0,
netSales: netSales?.toInt() ?? 0,
);
}
@freezed
class SalesAnalyticDataDto with _$SalesAnalyticDataDto {
const SalesAnalyticDataDto._();
const factory SalesAnalyticDataDto({
@JsonKey(name: 'date') DateTime? date,
@JsonKey(name: 'sales') num? sales,
@JsonKey(name: 'orders') num? orders,
@JsonKey(name: 'items') num? items,
@JsonKey(name: 'tax') num? tax,
@JsonKey(name: 'discount') num? discount,
@JsonKey(name: 'net_sales') num? netSales,
}) = _SalesAnalyticDataDto;
factory SalesAnalyticDataDto.fromJson(Map<String, dynamic> json) =>
_$SalesAnalyticDataDtoFromJson(json);
SalesAnalyticData toDomain() => SalesAnalyticData(
date: date ?? DateTime.fromMillisecondsSinceEpoch(0),
sales: sales?.toInt() ?? 0,
orders: orders?.toInt() ?? 0,
items: items?.toInt() ?? 0,
tax: tax?.toInt() ?? 0,
discount: discount?.toInt() ?? 0,
netSales: netSales?.toInt() ?? 0,
);
}
@@ -0,0 +1,40 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/analytic/analytic.dart';
import '../../../domain/analytic/repositories/i_analytic_repository.dart';
import '../datasource/remote_data_provider.dart';
@Injectable(as: IAnalyticRepository)
class AnalyticRepository implements IAnalyticRepository {
final AnalyticRemoteDataProvider _dataProvider;
final String _logName = 'AnalyticRepository';
AnalyticRepository(this._dataProvider);
@override
Future<Either<AnalyticFailure, SalesAnalytic>> getSales({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final result = await _dataProvider.fetchSales(
dateFrom: dateFrom,
dateTo: dateTo,
);
if (result.hasError) {
return left(result.error!);
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) {
log('getSalesError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError());
}
}
}