feat: profit loss

This commit is contained in:
efrilm
2025-08-17 22:36:46 +07:00
parent beb9ead4da
commit 50b06da627
21 changed files with 4548 additions and 424 deletions
@@ -6,3 +6,4 @@ part 'analytic_dtos.freezed.dart';
part 'analytic_dtos.g.dart';
part 'dto/sales_analytic_dto.dart';
part 'dto/profit_loss_analytic_dto.dart';
File diff suppressed because it is too large Load Diff
@@ -87,3 +87,129 @@ Map<String, dynamic> _$$SalesAnalyticDataDtoImplToJson(
'discount': instance.discount,
'net_sales': instance.netSales,
};
_$ProfitLossAnalyticDtoImpl _$$ProfitLossAnalyticDtoImplFromJson(
Map<String, dynamic> json,
) => _$ProfitLossAnalyticDtoImpl(
organizationId: json['organization_id'] as String?,
dateFrom: json['date_from'] as String?,
dateTo: json['date_to'] as String?,
groupBy: json['group_by'] as String?,
summary: json['summary'] == null
? null
: ProfitLossSummaryDto.fromJson(json['summary'] as Map<String, dynamic>),
data: (json['data'] as List<dynamic>?)
?.map((e) => ProfitLossDailyDataDto.fromJson(e as Map<String, dynamic>))
.toList(),
productData: (json['product_data'] as List<dynamic>?)
?.map((e) => ProfitLossProductDataDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$ProfitLossAnalyticDtoImplToJson(
_$ProfitLossAnalyticDtoImpl instance,
) => <String, dynamic>{
'organization_id': instance.organizationId,
'date_from': instance.dateFrom,
'date_to': instance.dateTo,
'group_by': instance.groupBy,
'summary': instance.summary,
'data': instance.data,
'product_data': instance.productData,
};
_$ProfitLossSummaryDtoImpl _$$ProfitLossSummaryDtoImplFromJson(
Map<String, dynamic> json,
) => _$ProfitLossSummaryDtoImpl(
totalRevenue: (json['total_revenue'] as num?)?.toInt(),
totalCost: (json['total_cost'] as num?)?.toInt(),
grossProfit: (json['gross_profit'] as num?)?.toInt(),
grossProfitMargin: (json['gross_profit_margin'] as num?)?.toDouble(),
totalTax: (json['total_tax'] as num?)?.toInt(),
totalDiscount: (json['total_discount'] as num?)?.toInt(),
netProfit: (json['net_profit'] as num?)?.toInt(),
netProfitMargin: (json['net_profit_margin'] as num?)?.toDouble(),
totalOrders: (json['total_orders'] as num?)?.toInt(),
averageProfit: (json['average_profit'] as num?)?.toDouble(),
profitabilityRatio: (json['profitability_ratio'] as num?)?.toDouble(),
);
Map<String, dynamic> _$$ProfitLossSummaryDtoImplToJson(
_$ProfitLossSummaryDtoImpl instance,
) => <String, dynamic>{
'total_revenue': instance.totalRevenue,
'total_cost': instance.totalCost,
'gross_profit': instance.grossProfit,
'gross_profit_margin': instance.grossProfitMargin,
'total_tax': instance.totalTax,
'total_discount': instance.totalDiscount,
'net_profit': instance.netProfit,
'net_profit_margin': instance.netProfitMargin,
'total_orders': instance.totalOrders,
'average_profit': instance.averageProfit,
'profitability_ratio': instance.profitabilityRatio,
};
_$ProfitLossDailyDataDtoImpl _$$ProfitLossDailyDataDtoImplFromJson(
Map<String, dynamic> json,
) => _$ProfitLossDailyDataDtoImpl(
date: json['date'] as String?,
revenue: (json['revenue'] as num?)?.toInt(),
cost: (json['cost'] as num?)?.toInt(),
grossProfit: (json['gross_profit'] as num?)?.toInt(),
grossProfitMargin: (json['gross_profit_margin'] as num?)?.toDouble(),
tax: (json['tax'] as num?)?.toInt(),
discount: (json['discount'] as num?)?.toInt(),
netProfit: (json['net_profit'] as num?)?.toInt(),
netProfitMargin: (json['net_profit_margin'] as num?)?.toDouble(),
orders: (json['orders'] as num?)?.toInt(),
);
Map<String, dynamic> _$$ProfitLossDailyDataDtoImplToJson(
_$ProfitLossDailyDataDtoImpl instance,
) => <String, dynamic>{
'date': instance.date,
'revenue': instance.revenue,
'cost': instance.cost,
'gross_profit': instance.grossProfit,
'gross_profit_margin': instance.grossProfitMargin,
'tax': instance.tax,
'discount': instance.discount,
'net_profit': instance.netProfit,
'net_profit_margin': instance.netProfitMargin,
'orders': instance.orders,
};
_$ProfitLossProductDataDtoImpl _$$ProfitLossProductDataDtoImplFromJson(
Map<String, dynamic> json,
) => _$ProfitLossProductDataDtoImpl(
productId: json['product_id'] as String?,
productName: json['product_name'] as String?,
categoryId: json['category_id'] as String?,
categoryName: json['category_name'] as String?,
quantitySold: (json['quantity_sold'] as num?)?.toInt(),
revenue: (json['revenue'] as num?)?.toInt(),
cost: (json['cost'] as num?)?.toInt(),
grossProfit: (json['gross_profit'] as num?)?.toInt(),
grossProfitMargin: (json['gross_profit_margin'] as num?)?.toDouble(),
averagePrice: (json['average_price'] as num?)?.toInt(),
averageCost: (json['average_cost'] as num?)?.toInt(),
profitPerUnit: (json['profit_per_unit'] as num?)?.toInt(),
);
Map<String, dynamic> _$$ProfitLossProductDataDtoImplToJson(
_$ProfitLossProductDataDtoImpl instance,
) => <String, dynamic>{
'product_id': instance.productId,
'product_name': instance.productName,
'category_id': instance.categoryId,
'category_name': instance.categoryName,
'quantity_sold': instance.quantitySold,
'revenue': instance.revenue,
'cost': instance.cost,
'gross_profit': instance.grossProfit,
'gross_profit_margin': instance.grossProfitMargin,
'average_price': instance.averagePrice,
'average_cost': instance.averageCost,
'profit_per_unit': instance.profitPerUnit,
};
@@ -44,4 +44,31 @@ class AnalyticRemoteDataProvider {
return DC.error(AnalyticFailure.serverError(e));
}
}
Future<DC<AnalyticFailure, ProfitLossAnalyticDto>> fetchProfitLoss({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final response = await _apiClient.get(
ApiPath.profitLossAnalytic,
params: {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
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));
}
}
}
@@ -0,0 +1,137 @@
part of '../analytic_dtos.dart';
@freezed
class ProfitLossAnalyticDto with _$ProfitLossAnalyticDto {
const ProfitLossAnalyticDto._();
const factory ProfitLossAnalyticDto({
@JsonKey(name: 'organization_id') String? organizationId,
@JsonKey(name: 'date_from') String? dateFrom,
@JsonKey(name: 'date_to') String? dateTo,
@JsonKey(name: 'group_by') String? groupBy,
@JsonKey(name: 'summary') ProfitLossSummaryDto? summary,
@JsonKey(name: 'data') List<ProfitLossDailyDataDto>? data,
@JsonKey(name: 'product_data') List<ProfitLossProductDataDto>? productData,
}) = _ProfitLossAnalyticDto;
factory ProfitLossAnalyticDto.fromJson(Map<String, dynamic> json) =>
_$ProfitLossAnalyticDtoFromJson(json);
ProfitLossAnalytic toDomain() => ProfitLossAnalytic(
organizationId: organizationId ?? '',
dateFrom: dateFrom ?? '',
dateTo: dateTo ?? '',
groupBy: groupBy ?? '',
summary: summary?.toDomain() ?? ProfitLossSummary.empty(),
data: (data ?? []).map((e) => e.toDomain()).toList(),
productData: (productData ?? []).map((e) => e.toDomain()).toList(),
);
}
@freezed
class ProfitLossSummaryDto with _$ProfitLossSummaryDto {
const ProfitLossSummaryDto._();
const factory ProfitLossSummaryDto({
@JsonKey(name: 'total_revenue') int? totalRevenue,
@JsonKey(name: 'total_cost') int? totalCost,
@JsonKey(name: 'gross_profit') int? grossProfit,
@JsonKey(name: 'gross_profit_margin') double? grossProfitMargin,
@JsonKey(name: 'total_tax') int? totalTax,
@JsonKey(name: 'total_discount') int? totalDiscount,
@JsonKey(name: 'net_profit') int? netProfit,
@JsonKey(name: 'net_profit_margin') double? netProfitMargin,
@JsonKey(name: 'total_orders') int? totalOrders,
@JsonKey(name: 'average_profit') double? averageProfit,
@JsonKey(name: 'profitability_ratio') double? profitabilityRatio,
}) = _ProfitLossSummaryDto;
factory ProfitLossSummaryDto.fromJson(Map<String, dynamic> json) =>
_$ProfitLossSummaryDtoFromJson(json);
ProfitLossSummary toDomain() => ProfitLossSummary(
totalRevenue: totalRevenue ?? 0,
totalCost: totalCost ?? 0,
grossProfit: grossProfit ?? 0,
grossProfitMargin: grossProfitMargin ?? 0.0,
totalTax: totalTax ?? 0,
totalDiscount: totalDiscount ?? 0,
netProfit: netProfit ?? 0,
netProfitMargin: netProfitMargin ?? 0.0,
totalOrders: totalOrders ?? 0,
averageProfit: averageProfit ?? 0.0,
profitabilityRatio: profitabilityRatio ?? 0.0,
);
}
@freezed
class ProfitLossDailyDataDto with _$ProfitLossDailyDataDto {
const ProfitLossDailyDataDto._();
const factory ProfitLossDailyDataDto({
@JsonKey(name: 'date') String? date,
@JsonKey(name: 'revenue') int? revenue,
@JsonKey(name: 'cost') int? cost,
@JsonKey(name: 'gross_profit') int? grossProfit,
@JsonKey(name: 'gross_profit_margin') double? grossProfitMargin,
@JsonKey(name: 'tax') int? tax,
@JsonKey(name: 'discount') int? discount,
@JsonKey(name: 'net_profit') int? netProfit,
@JsonKey(name: 'net_profit_margin') double? netProfitMargin,
@JsonKey(name: 'orders') int? orders,
}) = _ProfitLossDailyDataDto;
factory ProfitLossDailyDataDto.fromJson(Map<String, dynamic> json) =>
_$ProfitLossDailyDataDtoFromJson(json);
ProfitLossDailyData toDomain() => ProfitLossDailyData(
date: date ?? '',
revenue: revenue ?? 0,
cost: cost ?? 0,
grossProfit: grossProfit ?? 0,
grossProfitMargin: grossProfitMargin ?? 0.0,
tax: tax ?? 0,
discount: discount ?? 0,
netProfit: netProfit ?? 0,
netProfitMargin: netProfitMargin ?? 0.0,
orders: orders ?? 0,
);
}
@freezed
class ProfitLossProductDataDto with _$ProfitLossProductDataDto {
const ProfitLossProductDataDto._();
const factory ProfitLossProductDataDto({
@JsonKey(name: 'product_id') String? productId,
@JsonKey(name: 'product_name') String? productName,
@JsonKey(name: 'category_id') String? categoryId,
@JsonKey(name: 'category_name') String? categoryName,
@JsonKey(name: 'quantity_sold') int? quantitySold,
@JsonKey(name: 'revenue') int? revenue,
@JsonKey(name: 'cost') int? cost,
@JsonKey(name: 'gross_profit') int? grossProfit,
@JsonKey(name: 'gross_profit_margin') double? grossProfitMargin,
@JsonKey(name: 'average_price') int? averagePrice,
@JsonKey(name: 'average_cost') int? averageCost,
@JsonKey(name: 'profit_per_unit') int? profitPerUnit,
}) = _ProfitLossProductDataDto;
factory ProfitLossProductDataDto.fromJson(Map<String, dynamic> json) =>
_$ProfitLossProductDataDtoFromJson(json);
ProfitLossProductData toDomain() => ProfitLossProductData(
productId: productId ?? '',
productName: productName ?? '',
categoryId: categoryId ?? '',
categoryName: categoryName ?? '',
quantitySold: quantitySold ?? 0,
revenue: revenue ?? 0,
cost: cost ?? 0,
grossProfit: grossProfit ?? 0,
grossProfitMargin: grossProfitMargin ?? 0.0,
averagePrice: averagePrice ?? 0,
averageCost: averageCost ?? 0,
profitPerUnit: profitPerUnit ?? 0,
);
}
@@ -37,4 +37,28 @@ class AnalyticRepository implements IAnalyticRepository {
return left(const AnalyticFailure.unexpectedError());
}
}
@override
Future<Either<AnalyticFailure, ProfitLossAnalytic>> getProfitLoss({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final result = await _dataProvider.fetchProfitLoss(
dateFrom: dateFrom,
dateTo: dateTo,
);
if (result.hasError) {
return left(result.error!);
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) {
log('getProfitLossError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError());
}
}
}