report inventory

This commit is contained in:
efrilm
2025-11-04 00:21:15 +07:00
parent 2a44ce023e
commit dca0f546f9
20 changed files with 4865 additions and 2 deletions
@@ -11,3 +11,4 @@ part 'dtos/product_analytic_dto.dart';
part 'dtos/payment_method_analytic_dto.dart';
part 'dtos/profit_loss_analytic_dto.dart';
part 'dtos/category_analytic_dto.dart';
part 'dtos/inventory_analytic_dto.dart';
File diff suppressed because it is too large Load Diff
@@ -527,3 +527,146 @@ Map<String, dynamic> _$$CategoryAnalyticItemDtoImplToJson(
'product_count': instance.productCount,
'order_count': instance.orderCount,
};
_$InventoryAnalyticDtoImpl _$$InventoryAnalyticDtoImplFromJson(
Map<String, dynamic> json,
) => _$InventoryAnalyticDtoImpl(
summary: json['summary'] == null
? null
: InventoryAnalyticSummaryDto.fromJson(
json['summary'] as Map<String, dynamic>,
),
products: (json['products'] as List<dynamic>?)
?.map(
(e) =>
InventoryAnalyticProductItemDto.fromJson(e as Map<String, dynamic>),
)
.toList(),
ingredients: (json['ingredients'] as List<dynamic>?)
?.map(
(e) => InventoryAnalyticIngredientItemDto.fromJson(
e as Map<String, dynamic>,
),
)
.toList(),
);
Map<String, dynamic> _$$InventoryAnalyticDtoImplToJson(
_$InventoryAnalyticDtoImpl instance,
) => <String, dynamic>{
'summary': instance.summary,
'products': instance.products,
'ingredients': instance.ingredients,
};
_$InventoryAnalyticSummaryDtoImpl _$$InventoryAnalyticSummaryDtoImplFromJson(
Map<String, dynamic> json,
) => _$InventoryAnalyticSummaryDtoImpl(
totalProducts: (json['total_products'] as num?)?.toInt(),
totalIngredients: (json['total_ingredients'] as num?)?.toInt(),
totalValue: (json['total_value'] as num?)?.toInt(),
lowStockProducts: (json['low_stock_products'] as num?)?.toInt(),
lowStockIngredients: (json['low_stock_ingredients'] as num?)?.toInt(),
zeroStockProducts: (json['zero_stock_products'] as num?)?.toInt(),
zeroStockIngredients: (json['zero_stock_ingredients'] as num?)?.toInt(),
totalSoldProducts: (json['total_sold_products'] as num?)?.toInt(),
totalSoldIngredients: (json['total_sold_ingredients'] as num?)?.toInt(),
outletId: json['outlet_id'] as String?,
outletName: json['outlet_name'] as String?,
generatedAt: json['generated_at'] == null
? null
: DateTime.parse(json['generated_at'] as String),
);
Map<String, dynamic> _$$InventoryAnalyticSummaryDtoImplToJson(
_$InventoryAnalyticSummaryDtoImpl instance,
) => <String, dynamic>{
'total_products': instance.totalProducts,
'total_ingredients': instance.totalIngredients,
'total_value': instance.totalValue,
'low_stock_products': instance.lowStockProducts,
'low_stock_ingredients': instance.lowStockIngredients,
'zero_stock_products': instance.zeroStockProducts,
'zero_stock_ingredients': instance.zeroStockIngredients,
'total_sold_products': instance.totalSoldProducts,
'total_sold_ingredients': instance.totalSoldIngredients,
'outlet_id': instance.outletId,
'outlet_name': instance.outletName,
'generated_at': instance.generatedAt?.toIso8601String(),
};
_$InventoryAnalyticProductItemDtoImpl
_$$InventoryAnalyticProductItemDtoImplFromJson(Map<String, dynamic> json) =>
_$InventoryAnalyticProductItemDtoImpl(
id: json['id'] as String?,
productId: json['product_id'] as String?,
productName: json['product_name'] as String?,
categoryName: json['category_name'] as String?,
quantity: (json['quantity'] as num?)?.toInt(),
reorderLevel: (json['reorder_level'] as num?)?.toInt(),
unitCost: json['unit_cost'] as num?,
totalValue: (json['total_value'] as num?)?.toInt(),
totalIn: (json['total_in'] as num?)?.toInt(),
totalOut: (json['total_out'] as num?)?.toInt(),
isLowStock: json['is_low_stock'] as bool?,
isZeroStock: json['is_zero_stock'] as bool?,
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
);
Map<String, dynamic> _$$InventoryAnalyticProductItemDtoImplToJson(
_$InventoryAnalyticProductItemDtoImpl instance,
) => <String, dynamic>{
'id': instance.id,
'product_id': instance.productId,
'product_name': instance.productName,
'category_name': instance.categoryName,
'quantity': instance.quantity,
'reorder_level': instance.reorderLevel,
'unit_cost': instance.unitCost,
'total_value': instance.totalValue,
'total_in': instance.totalIn,
'total_out': instance.totalOut,
'is_low_stock': instance.isLowStock,
'is_zero_stock': instance.isZeroStock,
'updated_at': instance.updatedAt?.toIso8601String(),
};
_$InventoryAnalyticIngredientItemDtoImpl
_$$InventoryAnalyticIngredientItemDtoImplFromJson(Map<String, dynamic> json) =>
_$InventoryAnalyticIngredientItemDtoImpl(
id: json['id'] as String?,
ingredientId: json['ingredient_id'] as String?,
ingredientName: json['ingredient_name'] as String?,
unitName: json['unit_name'] as String?,
quantity: (json['quantity'] as num?)?.toInt(),
reorderLevel: (json['reorder_level'] as num?)?.toInt(),
unitCost: json['unit_cost'] as num?,
totalValue: (json['total_value'] as num?)?.toInt(),
totalIn: (json['total_in'] as num?)?.toInt(),
totalOut: (json['total_out'] as num?)?.toInt(),
isLowStock: json['is_low_stock'] as bool?,
isZeroStock: json['is_zero_stock'] as bool?,
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
);
Map<String, dynamic> _$$InventoryAnalyticIngredientItemDtoImplToJson(
_$InventoryAnalyticIngredientItemDtoImpl instance,
) => <String, dynamic>{
'id': instance.id,
'ingredient_id': instance.ingredientId,
'ingredient_name': instance.ingredientName,
'unit_name': instance.unitName,
'quantity': instance.quantity,
'reorder_level': instance.reorderLevel,
'unit_cost': instance.unitCost,
'total_value': instance.totalValue,
'total_in': instance.totalIn,
'total_out': instance.totalOut,
'is_low_stock': instance.isLowStock,
'is_zero_stock': instance.isZeroStock,
'updated_at': instance.updatedAt?.toIso8601String(),
};
@@ -9,6 +9,8 @@ import '../../../common/extension/extension.dart';
import '../../../common/function/app_function.dart';
import '../../../common/url/api_path.dart';
import '../../../domain/analytic/analytic.dart';
import '../../../injection.dart';
import '../../outlet/datasources/local_data_provider.dart';
import '../analytic_dtos.dart';
@injectable
@@ -192,4 +194,34 @@ class AnalyticRemoteDataProvider {
return DC.error(AnalyticFailure.serverError(e));
}
}
Future<DC<AnalyticFailure, InventoryAnalyticDto>> fetchInventory({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final user = await getIt<OutletLocalDatasource>().currentOutlet();
final response = await _apiClient.get(
'${ApiPath.analyticInventory}/${user.id}',
params: {
'date_from': dateFrom.toServerDate(),
'date_to': dateTo.toServerDate(),
},
headers: getAuthorizationHeader(),
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
}
final inventory = InventoryAnalyticDto.fromJson(
response.data['data'] as Map<String, dynamic>,
);
return DC.data(inventory);
} on ApiFailure catch (e, s) {
log('fetchInventory', name: _logName, error: e, stackTrace: s);
return DC.error(AnalyticFailure.serverError(e));
}
}
}
@@ -0,0 +1,146 @@
part of '../analytic_dtos.dart';
@freezed
class InventoryAnalyticDto with _$InventoryAnalyticDto {
const InventoryAnalyticDto._();
const factory InventoryAnalyticDto({
@JsonKey(name: "summary") InventoryAnalyticSummaryDto? summary,
@JsonKey(name: "products") List<InventoryAnalyticProductItemDto>? products,
@JsonKey(name: "ingredients")
List<InventoryAnalyticIngredientItemDto>? ingredients,
}) = _InventoryAnalyticDto;
factory InventoryAnalyticDto.fromJson(Map<String, dynamic> json) =>
_$InventoryAnalyticDtoFromJson(json);
// Mapper ke domain (opsional)
InventoryAnalytic toDomain() => InventoryAnalytic(
summary: summary?.toDomain() ?? InventoryAnalyticSummary.empty(),
products: products?.map((e) => e.toDomain()).toList() ?? [],
ingredients: ingredients?.map((e) => e.toDomain()).toList() ?? [],
);
}
@freezed
class InventoryAnalyticSummaryDto with _$InventoryAnalyticSummaryDto {
const InventoryAnalyticSummaryDto._();
const factory InventoryAnalyticSummaryDto({
@JsonKey(name: "total_products") int? totalProducts,
@JsonKey(name: "total_ingredients") int? totalIngredients,
@JsonKey(name: "total_value") int? totalValue,
@JsonKey(name: "low_stock_products") int? lowStockProducts,
@JsonKey(name: "low_stock_ingredients") int? lowStockIngredients,
@JsonKey(name: "zero_stock_products") int? zeroStockProducts,
@JsonKey(name: "zero_stock_ingredients") int? zeroStockIngredients,
@JsonKey(name: "total_sold_products") int? totalSoldProducts,
@JsonKey(name: "total_sold_ingredients") int? totalSoldIngredients,
@JsonKey(name: "outlet_id") String? outletId,
@JsonKey(name: "outlet_name") String? outletName,
@JsonKey(name: "generated_at") DateTime? generatedAt,
}) = _InventoryAnalyticSummaryDto;
factory InventoryAnalyticSummaryDto.fromJson(Map<String, dynamic> json) =>
_$InventoryAnalyticSummaryDtoFromJson(json);
// Optional mapper ke entity
InventoryAnalyticSummary toDomain() => InventoryAnalyticSummary(
totalProducts: totalProducts ?? 0,
totalIngredients: totalIngredients ?? 0,
totalValue: totalValue ?? 0,
lowStockProducts: lowStockProducts ?? 0,
lowStockIngredients: lowStockIngredients ?? 0,
zeroStockProducts: zeroStockProducts ?? 0,
zeroStockIngredients: zeroStockIngredients ?? 0,
totalSoldProducts: totalSoldProducts ?? 0,
totalSoldIngredients: totalSoldIngredients ?? 0,
outletId: outletId ?? '',
outletName: outletName ?? '',
generatedAt: generatedAt ?? DateTime.now(),
);
}
@freezed
class InventoryAnalyticProductItemDto with _$InventoryAnalyticProductItemDto {
const InventoryAnalyticProductItemDto._();
const factory InventoryAnalyticProductItemDto({
@JsonKey(name: "id") String? id,
@JsonKey(name: "product_id") String? productId,
@JsonKey(name: "product_name") String? productName,
@JsonKey(name: "category_name") String? categoryName,
@JsonKey(name: "quantity") int? quantity,
@JsonKey(name: "reorder_level") int? reorderLevel,
@JsonKey(name: "unit_cost") num? unitCost,
@JsonKey(name: "total_value") int? totalValue,
@JsonKey(name: "total_in") int? totalIn,
@JsonKey(name: "total_out") int? totalOut,
@JsonKey(name: "is_low_stock") bool? isLowStock,
@JsonKey(name: "is_zero_stock") bool? isZeroStock,
@JsonKey(name: "updated_at") DateTime? updatedAt,
}) = _InventoryAnalyticProductItemDto;
factory InventoryAnalyticProductItemDto.fromJson(Map<String, dynamic> json) =>
_$InventoryAnalyticProductItemDtoFromJson(json);
// Mapper ke domain (opsional)
InventoryAnalyticProductItem toDomain() => InventoryAnalyticProductItem(
id: id ?? '',
productId: productId ?? '',
productName: productName ?? '',
categoryName: categoryName ?? '',
quantity: quantity ?? 0,
reorderLevel: reorderLevel ?? 0,
unitCost: unitCost ?? 0,
totalValue: totalValue ?? 0,
totalIn: totalIn ?? 0,
totalOut: totalOut ?? 0,
isLowStock: isLowStock ?? false,
isZeroStock: isZeroStock ?? false,
updatedAt: updatedAt ?? DateTime.now(),
);
}
@freezed
class InventoryAnalyticIngredientItemDto
with _$InventoryAnalyticIngredientItemDto {
const InventoryAnalyticIngredientItemDto._();
const factory InventoryAnalyticIngredientItemDto({
@JsonKey(name: "id") String? id,
@JsonKey(name: "ingredient_id") String? ingredientId,
@JsonKey(name: "ingredient_name") String? ingredientName,
@JsonKey(name: "unit_name") String? unitName,
@JsonKey(name: "quantity") int? quantity,
@JsonKey(name: "reorder_level") int? reorderLevel,
@JsonKey(name: "unit_cost") num? unitCost,
@JsonKey(name: "total_value") int? totalValue,
@JsonKey(name: "total_in") int? totalIn,
@JsonKey(name: "total_out") int? totalOut,
@JsonKey(name: "is_low_stock") bool? isLowStock,
@JsonKey(name: "is_zero_stock") bool? isZeroStock,
@JsonKey(name: "updated_at") DateTime? updatedAt,
}) = _InventoryAnalyticIngredientItemDto;
factory InventoryAnalyticIngredientItemDto.fromJson(
Map<String, dynamic> json,
) => _$InventoryAnalyticIngredientItemDtoFromJson(json);
// Mapper ke domain (opsional)
InventoryAnalyticIngredientItem toDomain() => InventoryAnalyticIngredientItem(
id: id ?? '',
ingredientId: ingredientId ?? '',
ingredientName: ingredientName ?? '',
unitName: unitName ?? '',
quantity: quantity ?? 0,
reorderLevel: reorderLevel ?? 0,
unitCost: unitCost ?? 0,
totalValue: totalValue ?? 0,
totalIn: totalIn ?? 0,
totalOut: totalOut ?? 0,
isLowStock: isLowStock ?? false,
isZeroStock: isZeroStock ?? false,
updatedAt: updatedAt ?? DateTime.now(),
);
}
@@ -157,4 +157,28 @@ class AnalyticRepository implements IAnalyticRepository {
return left(const AnalyticFailure.unexpectedError());
}
}
@override
Future<Either<AnalyticFailure, InventoryAnalytic>> getInventory({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final result = await _dataProvider.fetchInventory(
dateFrom: dateFrom,
dateTo: dateTo,
);
if (result.hasError) {
return left(result.error!);
}
final inventory = result.data!.toDomain();
return right(inventory);
} catch (e) {
log('getInventoryError', name: _logName, error: e);
return left(const AnalyticFailure.unexpectedError());
}
}
}