report inventory
This commit is contained in:
parent
2a44ce023e
commit
dca0f546f9
@ -0,0 +1,47 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
|
||||
part 'inventory_analytic_loader_event.dart';
|
||||
part 'inventory_analytic_loader_state.dart';
|
||||
part 'inventory_analytic_loader_bloc.freezed.dart';
|
||||
|
||||
@injectable
|
||||
class InventoryAnalyticLoaderBloc
|
||||
extends Bloc<InventoryAnalyticLoaderEvent, InventoryAnalyticLoaderState> {
|
||||
final IAnalyticRepository _analyticRepository;
|
||||
InventoryAnalyticLoaderBloc(this._analyticRepository)
|
||||
: super(InventoryAnalyticLoaderState.initial()) {
|
||||
on<InventoryAnalyticLoaderEvent>(_onInventoryAnalyticLoaderEvent);
|
||||
}
|
||||
|
||||
Future<void> _onInventoryAnalyticLoaderEvent(
|
||||
InventoryAnalyticLoaderEvent event,
|
||||
Emitter<InventoryAnalyticLoaderState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
fetched: (e) async {
|
||||
emit(state.copyWith(isFetching: true, failureOption: none()));
|
||||
|
||||
final result = await _analyticRepository.getInventory(
|
||||
dateFrom: e.startDate,
|
||||
dateTo: e.endDate,
|
||||
);
|
||||
|
||||
emit(
|
||||
result.fold(
|
||||
(l) => state.copyWith(isFetching: false, failureOption: some(l)),
|
||||
(r) => state.copyWith(
|
||||
isFetching: false,
|
||||
failureOption: none(),
|
||||
inventoryAnalytic: r,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,480 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'inventory_analytic_loader_bloc.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
/// @nodoc
|
||||
mixin _$InventoryAnalyticLoaderEvent {
|
||||
DateTime get startDate => throw _privateConstructorUsedError;
|
||||
DateTime get endDate => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime startDate, DateTime endDate) fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime startDate, DateTime endDate)? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime startDate, DateTime endDate)? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$InventoryAnalyticLoaderEventCopyWith<InventoryAnalyticLoaderEvent>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $InventoryAnalyticLoaderEventCopyWith<$Res> {
|
||||
factory $InventoryAnalyticLoaderEventCopyWith(
|
||||
InventoryAnalyticLoaderEvent value,
|
||||
$Res Function(InventoryAnalyticLoaderEvent) then,
|
||||
) =
|
||||
_$InventoryAnalyticLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
InventoryAnalyticLoaderEvent
|
||||
>;
|
||||
@useResult
|
||||
$Res call({DateTime startDate, DateTime endDate});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$InventoryAnalyticLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends InventoryAnalyticLoaderEvent
|
||||
>
|
||||
implements $InventoryAnalyticLoaderEventCopyWith<$Res> {
|
||||
_$InventoryAnalyticLoaderEventCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? startDate = null, Object? endDate = null}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
startDate: null == startDate
|
||||
? _value.startDate
|
||||
: startDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
endDate: null == endDate
|
||||
? _value.endDate
|
||||
: endDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedImplCopyWith<$Res>
|
||||
implements $InventoryAnalyticLoaderEventCopyWith<$Res> {
|
||||
factory _$$FetchedImplCopyWith(
|
||||
_$FetchedImpl value,
|
||||
$Res Function(_$FetchedImpl) then,
|
||||
) = __$$FetchedImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({DateTime startDate, DateTime endDate});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FetchedImplCopyWithImpl<$Res>
|
||||
extends _$InventoryAnalyticLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
|
||||
implements _$$FetchedImplCopyWith<$Res> {
|
||||
__$$FetchedImplCopyWithImpl(
|
||||
_$FetchedImpl _value,
|
||||
$Res Function(_$FetchedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? startDate = null, Object? endDate = null}) {
|
||||
return _then(
|
||||
_$FetchedImpl(
|
||||
startDate: null == startDate
|
||||
? _value.startDate
|
||||
: startDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
endDate: null == endDate
|
||||
? _value.endDate
|
||||
: endDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$FetchedImpl implements _Fetched {
|
||||
const _$FetchedImpl({required this.startDate, required this.endDate});
|
||||
|
||||
@override
|
||||
final DateTime startDate;
|
||||
@override
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InventoryAnalyticLoaderEvent.fetched(startDate: $startDate, endDate: $endDate)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FetchedImpl &&
|
||||
(identical(other.startDate, startDate) ||
|
||||
other.startDate == startDate) &&
|
||||
(identical(other.endDate, endDate) || other.endDate == endDate));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, startDate, endDate);
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$FetchedImplCopyWith<_$FetchedImpl> get copyWith =>
|
||||
__$$FetchedImplCopyWithImpl<_$FetchedImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime startDate, DateTime endDate) fetched,
|
||||
}) {
|
||||
return fetched(startDate, endDate);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime startDate, DateTime endDate)? fetched,
|
||||
}) {
|
||||
return fetched?.call(startDate, endDate);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime startDate, DateTime endDate)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched(startDate, endDate);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return fetched(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return fetched?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Fetched implements InventoryAnalyticLoaderEvent {
|
||||
const factory _Fetched({
|
||||
required final DateTime startDate,
|
||||
required final DateTime endDate,
|
||||
}) = _$FetchedImpl;
|
||||
|
||||
@override
|
||||
DateTime get startDate;
|
||||
@override
|
||||
DateTime get endDate;
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$FetchedImplCopyWith<_$FetchedImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$InventoryAnalyticLoaderState {
|
||||
InventoryAnalytic get inventoryAnalytic => throw _privateConstructorUsedError;
|
||||
Option<AnalyticFailure> get failureOption =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isFetching => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$InventoryAnalyticLoaderStateCopyWith<InventoryAnalyticLoaderState>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $InventoryAnalyticLoaderStateCopyWith<$Res> {
|
||||
factory $InventoryAnalyticLoaderStateCopyWith(
|
||||
InventoryAnalyticLoaderState value,
|
||||
$Res Function(InventoryAnalyticLoaderState) then,
|
||||
) =
|
||||
_$InventoryAnalyticLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
InventoryAnalyticLoaderState
|
||||
>;
|
||||
@useResult
|
||||
$Res call({
|
||||
InventoryAnalytic inventoryAnalytic,
|
||||
Option<AnalyticFailure> failureOption,
|
||||
bool isFetching,
|
||||
});
|
||||
|
||||
$InventoryAnalyticCopyWith<$Res> get inventoryAnalytic;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$InventoryAnalyticLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends InventoryAnalyticLoaderState
|
||||
>
|
||||
implements $InventoryAnalyticLoaderStateCopyWith<$Res> {
|
||||
_$InventoryAnalyticLoaderStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? inventoryAnalytic = null,
|
||||
Object? failureOption = null,
|
||||
Object? isFetching = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
inventoryAnalytic: null == inventoryAnalytic
|
||||
? _value.inventoryAnalytic
|
||||
: inventoryAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as InventoryAnalytic,
|
||||
failureOption: null == failureOption
|
||||
? _value.failureOption
|
||||
: failureOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<AnalyticFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$InventoryAnalyticCopyWith<$Res> get inventoryAnalytic {
|
||||
return $InventoryAnalyticCopyWith<$Res>(_value.inventoryAnalytic, (value) {
|
||||
return _then(_value.copyWith(inventoryAnalytic: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$InventoryAnalyticLoaderStateImplCopyWith<$Res>
|
||||
implements $InventoryAnalyticLoaderStateCopyWith<$Res> {
|
||||
factory _$$InventoryAnalyticLoaderStateImplCopyWith(
|
||||
_$InventoryAnalyticLoaderStateImpl value,
|
||||
$Res Function(_$InventoryAnalyticLoaderStateImpl) then,
|
||||
) = __$$InventoryAnalyticLoaderStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
InventoryAnalytic inventoryAnalytic,
|
||||
Option<AnalyticFailure> failureOption,
|
||||
bool isFetching,
|
||||
});
|
||||
|
||||
@override
|
||||
$InventoryAnalyticCopyWith<$Res> get inventoryAnalytic;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$InventoryAnalyticLoaderStateImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$InventoryAnalyticLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
_$InventoryAnalyticLoaderStateImpl
|
||||
>
|
||||
implements _$$InventoryAnalyticLoaderStateImplCopyWith<$Res> {
|
||||
__$$InventoryAnalyticLoaderStateImplCopyWithImpl(
|
||||
_$InventoryAnalyticLoaderStateImpl _value,
|
||||
$Res Function(_$InventoryAnalyticLoaderStateImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? inventoryAnalytic = null,
|
||||
Object? failureOption = null,
|
||||
Object? isFetching = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$InventoryAnalyticLoaderStateImpl(
|
||||
inventoryAnalytic: null == inventoryAnalytic
|
||||
? _value.inventoryAnalytic
|
||||
: inventoryAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as InventoryAnalytic,
|
||||
failureOption: null == failureOption
|
||||
? _value.failureOption
|
||||
: failureOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<AnalyticFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$InventoryAnalyticLoaderStateImpl
|
||||
implements _InventoryAnalyticLoaderState {
|
||||
_$InventoryAnalyticLoaderStateImpl({
|
||||
required this.inventoryAnalytic,
|
||||
required this.failureOption,
|
||||
this.isFetching = false,
|
||||
});
|
||||
|
||||
@override
|
||||
final InventoryAnalytic inventoryAnalytic;
|
||||
@override
|
||||
final Option<AnalyticFailure> failureOption;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetching;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InventoryAnalyticLoaderState(inventoryAnalytic: $inventoryAnalytic, failureOption: $failureOption, isFetching: $isFetching)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$InventoryAnalyticLoaderStateImpl &&
|
||||
(identical(other.inventoryAnalytic, inventoryAnalytic) ||
|
||||
other.inventoryAnalytic == inventoryAnalytic) &&
|
||||
(identical(other.failureOption, failureOption) ||
|
||||
other.failureOption == failureOption) &&
|
||||
(identical(other.isFetching, isFetching) ||
|
||||
other.isFetching == isFetching));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, inventoryAnalytic, failureOption, isFetching);
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$InventoryAnalyticLoaderStateImplCopyWith<
|
||||
_$InventoryAnalyticLoaderStateImpl
|
||||
>
|
||||
get copyWith =>
|
||||
__$$InventoryAnalyticLoaderStateImplCopyWithImpl<
|
||||
_$InventoryAnalyticLoaderStateImpl
|
||||
>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _InventoryAnalyticLoaderState
|
||||
implements InventoryAnalyticLoaderState {
|
||||
factory _InventoryAnalyticLoaderState({
|
||||
required final InventoryAnalytic inventoryAnalytic,
|
||||
required final Option<AnalyticFailure> failureOption,
|
||||
final bool isFetching,
|
||||
}) = _$InventoryAnalyticLoaderStateImpl;
|
||||
|
||||
@override
|
||||
InventoryAnalytic get inventoryAnalytic;
|
||||
@override
|
||||
Option<AnalyticFailure> get failureOption;
|
||||
@override
|
||||
bool get isFetching;
|
||||
|
||||
/// Create a copy of InventoryAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$InventoryAnalyticLoaderStateImplCopyWith<
|
||||
_$InventoryAnalyticLoaderStateImpl
|
||||
>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
part of 'inventory_analytic_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class InventoryAnalyticLoaderEvent with _$InventoryAnalyticLoaderEvent {
|
||||
const factory InventoryAnalyticLoaderEvent.fetched({
|
||||
required DateTime startDate,
|
||||
required DateTime endDate,
|
||||
}) = _Fetched;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
part of 'inventory_analytic_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class InventoryAnalyticLoaderState with _$InventoryAnalyticLoaderState {
|
||||
factory InventoryAnalyticLoaderState({
|
||||
required InventoryAnalytic inventoryAnalytic,
|
||||
required Option<AnalyticFailure> failureOption,
|
||||
@Default(false) bool isFetching,
|
||||
}) = _InventoryAnalyticLoaderState;
|
||||
|
||||
factory InventoryAnalyticLoaderState.initial() =>
|
||||
InventoryAnalyticLoaderState(
|
||||
inventoryAnalytic: InventoryAnalytic.empty(),
|
||||
failureOption: none(),
|
||||
);
|
||||
}
|
||||
@ -15,4 +15,5 @@ class ApiPath {
|
||||
'/api/v1/analytics/payment-methods';
|
||||
static const String analyticProfitLoss = '/api/v1/analytics/profit-loss';
|
||||
static const String analyticCategories = '/api/v1/analytics/categories';
|
||||
static const String analyticInventory = '/api/v1/inventory/report/details';
|
||||
}
|
||||
|
||||
@ -11,5 +11,6 @@ part 'entities/product_analytic_entity.dart';
|
||||
part 'entities/payment_method_analytic_entity.dart';
|
||||
part 'entities/profit_loss_analytic_entity.dart';
|
||||
part 'entities/category_analytic_entity.dart';
|
||||
part 'entities/inventory_analytic_entity.dart';
|
||||
part 'failures/analytic_failure.dart';
|
||||
part 'repositories/i_analytic_repository.dart';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
120
lib/domain/analytic/entities/inventory_analytic_entity.dart
Normal file
120
lib/domain/analytic/entities/inventory_analytic_entity.dart
Normal file
@ -0,0 +1,120 @@
|
||||
part of '../analytic.dart';
|
||||
|
||||
@freezed
|
||||
class InventoryAnalytic with _$InventoryAnalytic {
|
||||
const factory InventoryAnalytic({
|
||||
required InventoryAnalyticSummary summary,
|
||||
required List<InventoryAnalyticProductItem> products,
|
||||
required List<InventoryAnalyticIngredientItem> ingredients,
|
||||
}) = _InventoryAnalytic;
|
||||
|
||||
factory InventoryAnalytic.empty() => InventoryAnalytic(
|
||||
summary: InventoryAnalyticSummary.empty(),
|
||||
products: const [],
|
||||
ingredients: const [],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class InventoryAnalyticSummary with _$InventoryAnalyticSummary {
|
||||
const factory InventoryAnalyticSummary({
|
||||
required int totalProducts,
|
||||
required int totalIngredients,
|
||||
required int totalValue,
|
||||
required int lowStockProducts,
|
||||
required int lowStockIngredients,
|
||||
required int zeroStockProducts,
|
||||
required int zeroStockIngredients,
|
||||
required int totalSoldProducts,
|
||||
required int totalSoldIngredients,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required DateTime generatedAt,
|
||||
}) = _InventoryAnalyticSummary;
|
||||
|
||||
factory InventoryAnalyticSummary.empty() => InventoryAnalyticSummary(
|
||||
totalProducts: 0,
|
||||
totalIngredients: 0,
|
||||
totalValue: 0,
|
||||
lowStockProducts: 0,
|
||||
lowStockIngredients: 0,
|
||||
zeroStockProducts: 0,
|
||||
zeroStockIngredients: 0,
|
||||
totalSoldProducts: 0,
|
||||
totalSoldIngredients: 0,
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
generatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class InventoryAnalyticProductItem with _$InventoryAnalyticProductItem {
|
||||
const factory InventoryAnalyticProductItem({
|
||||
required String id,
|
||||
required String productId,
|
||||
required String productName,
|
||||
required String categoryName,
|
||||
required int quantity,
|
||||
required int reorderLevel,
|
||||
required num unitCost,
|
||||
required int totalValue,
|
||||
required int totalIn,
|
||||
required int totalOut,
|
||||
required bool isLowStock,
|
||||
required bool isZeroStock,
|
||||
required DateTime updatedAt,
|
||||
}) = _InventoryAnalyticProductItem;
|
||||
|
||||
factory InventoryAnalyticProductItem.empty() => InventoryAnalyticProductItem(
|
||||
id: '',
|
||||
productId: '',
|
||||
productName: '',
|
||||
categoryName: '',
|
||||
quantity: 0,
|
||||
reorderLevel: 0,
|
||||
unitCost: 0,
|
||||
totalValue: 0,
|
||||
totalIn: 0,
|
||||
totalOut: 0,
|
||||
isLowStock: false,
|
||||
isZeroStock: false,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class InventoryAnalyticIngredientItem with _$InventoryAnalyticIngredientItem {
|
||||
const factory InventoryAnalyticIngredientItem({
|
||||
required String id,
|
||||
required String ingredientId,
|
||||
required String ingredientName,
|
||||
required String unitName,
|
||||
required int quantity,
|
||||
required int reorderLevel,
|
||||
required num unitCost,
|
||||
required int totalValue,
|
||||
required int totalIn,
|
||||
required int totalOut,
|
||||
required bool isLowStock,
|
||||
required bool isZeroStock,
|
||||
required DateTime updatedAt,
|
||||
}) = _InventoryAnalyticIngredientItem;
|
||||
|
||||
factory InventoryAnalyticIngredientItem.empty() =>
|
||||
InventoryAnalyticIngredientItem(
|
||||
id: '',
|
||||
ingredientId: '',
|
||||
ingredientName: '',
|
||||
unitName: '',
|
||||
quantity: 0,
|
||||
reorderLevel: 0,
|
||||
unitCost: 0,
|
||||
totalValue: 0,
|
||||
totalIn: 0,
|
||||
totalOut: 0,
|
||||
isLowStock: false,
|
||||
isZeroStock: false,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
@ -25,4 +25,8 @@ abstract class IAnalyticRepository {
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
});
|
||||
Future<Either<AnalyticFailure, InventoryAnalytic>> getInventory({
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
146
lib/infrastructure/analytic/dtos/inventory_analytic_dto.dart
Normal file
146
lib/infrastructure/analytic/dtos/inventory_analytic_dto.dart
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ import 'package:apskel_pos_flutter_v2/application/analytic/category_analytic_loa
|
||||
as _i911;
|
||||
import 'package:apskel_pos_flutter_v2/application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart'
|
||||
as _i80;
|
||||
import 'package:apskel_pos_flutter_v2/application/analytic/inventory_analytic_loader/inventory_analytic_loader_bloc.dart'
|
||||
as _i651;
|
||||
import 'package:apskel_pos_flutter_v2/application/analytic/payment_method_analytic_loader/payment_method_analytic_loader_bloc.dart'
|
||||
as _i733;
|
||||
import 'package:apskel_pos_flutter_v2/application/analytic/product_analytic_loader/product_analytic_loader_bloc.dart'
|
||||
@ -320,6 +322,9 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh.factory<_i911.CategoryAnalyticLoaderBloc>(
|
||||
() => _i911.CategoryAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
|
||||
);
|
||||
gh.factory<_i651.InventoryAnalyticLoaderBloc>(
|
||||
() => _i651.InventoryAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../application/analytic/category_analytic_loader/category_analytic_loader_bloc.dart';
|
||||
import '../../../../../application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart';
|
||||
import '../../../../../application/analytic/inventory_analytic_loader/inventory_analytic_loader_bloc.dart';
|
||||
import '../../../../../application/analytic/payment_method_analytic_loader/payment_method_analytic_loader_bloc.dart';
|
||||
import '../../../../../application/analytic/product_analytic_loader/product_analytic_loader_bloc.dart';
|
||||
import '../../../../../application/analytic/profit_loss_analytic_loader/profit_loss_analytic_loader_bloc.dart';
|
||||
@ -19,6 +20,7 @@ import '../../../../components/spaces/space.dart';
|
||||
import '../../../../router/app_router.gr.dart';
|
||||
import 'sections/report_category_section.dart';
|
||||
import 'sections/report_dashboard_section.dart';
|
||||
import 'sections/report_inventory_section.dart';
|
||||
import 'sections/report_payment_method_section.dart';
|
||||
import 'sections/report_product_section.dart';
|
||||
import 'sections/report_profit_loss_section.dart';
|
||||
@ -185,7 +187,20 @@ class ReportPage extends StatelessWidget implements AutoRouteWrapper {
|
||||
);
|
||||
},
|
||||
),
|
||||
6 => Text(state.title),
|
||||
6 =>
|
||||
BlocBuilder<
|
||||
InventoryAnalyticLoaderBloc,
|
||||
InventoryAnalyticLoaderState
|
||||
>(
|
||||
builder: (context, inventory) {
|
||||
return ReportInventorySection(
|
||||
menu: reportMenus[state.selectedMenu],
|
||||
state: inventory,
|
||||
startDate: state.startDate,
|
||||
endDate: state.endDate,
|
||||
);
|
||||
},
|
||||
),
|
||||
7 =>
|
||||
BlocBuilder<
|
||||
CategoryAnalyticLoaderBloc,
|
||||
@ -255,6 +270,12 @@ class ReportPage extends StatelessWidget implements AutoRouteWrapper {
|
||||
),
|
||||
);
|
||||
case 6:
|
||||
return context.read<InventoryAnalyticLoaderBloc>().add(
|
||||
InventoryAnalyticLoaderEvent.fetched(
|
||||
startDate: state.startDate,
|
||||
endDate: state.endDate,
|
||||
),
|
||||
);
|
||||
case 7:
|
||||
return context.read<CategoryAnalyticLoaderBloc>().add(
|
||||
CategoryAnalyticLoaderEvent.fetched(
|
||||
@ -316,7 +337,6 @@ class ReportPage extends StatelessWidget implements AutoRouteWrapper {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
BlocProvider(
|
||||
create: (context) => getIt<CategoryAnalyticLoaderBloc>()
|
||||
..add(
|
||||
@ -326,6 +346,15 @@ class ReportPage extends StatelessWidget implements AutoRouteWrapper {
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => getIt<InventoryAnalyticLoaderBloc>()
|
||||
..add(
|
||||
InventoryAnalyticLoaderEvent.fetched(
|
||||
startDate: DateTime.now().subtract(const Duration(days: 30)),
|
||||
endDate: DateTime.now(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: this,
|
||||
);
|
||||
|
||||
@ -0,0 +1,171 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../../application/analytic/inventory_analytic_loader/inventory_analytic_loader_bloc.dart';
|
||||
import '../../../../../../common/data/report_menu.dart';
|
||||
import '../../../../../../common/extension/extension.dart';
|
||||
import '../../../../../../common/theme/theme.dart';
|
||||
import '../../../../../components/error/analytic_error_state_widget.dart';
|
||||
import '../../../../../components/loader/loader_with_text.dart';
|
||||
import '../../../../../components/spaces/space.dart';
|
||||
import '../../../../../components/widgets/report/report_header.dart';
|
||||
import '../../../../../components/widgets/report/report_summary_card.dart';
|
||||
import '../widgets/inventory/report_inventory_ingredient_tab.dart';
|
||||
import '../widgets/inventory/report_inventory_product_tab.dart';
|
||||
|
||||
class ReportInventorySection extends StatefulWidget {
|
||||
final ReportMenu menu;
|
||||
final InventoryAnalyticLoaderState state;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
const ReportInventorySection({
|
||||
super.key,
|
||||
required this.menu,
|
||||
required this.state,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ReportInventorySection> createState() => _ReportInventorySectionState();
|
||||
}
|
||||
|
||||
class _ReportInventorySectionState extends State<ReportInventorySection> {
|
||||
int _selectedTabIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.state.isFetching) {
|
||||
return const Center(child: LoaderWithText());
|
||||
}
|
||||
return widget.state.failureOption.fold(
|
||||
() => RefreshIndicator(
|
||||
onRefresh: () {
|
||||
context.read<InventoryAnalyticLoaderBloc>().add(
|
||||
InventoryAnalyticLoaderEvent.fetched(
|
||||
startDate: widget.startDate,
|
||||
endDate: widget.endDate,
|
||||
),
|
||||
);
|
||||
return Future.value();
|
||||
},
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16),
|
||||
children: [
|
||||
ReportHeader(
|
||||
menu: widget.menu,
|
||||
endDate: widget.endDate,
|
||||
startDate: widget.startDate,
|
||||
),
|
||||
_buildSummary(),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildTab('Produk', 0),
|
||||
SpaceWidth(12),
|
||||
_buildTab('Bahan Baku', 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
SpaceHeight(16),
|
||||
|
||||
_selectedTabIndex == 0
|
||||
? ReportInventoryProductTab(
|
||||
data: widget.state.inventoryAnalytic,
|
||||
)
|
||||
: ReportInventoryIngredientTab(
|
||||
data: widget.state.inventoryAnalytic,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
(f) => AnalyticErrorStateWidget(
|
||||
failure: f,
|
||||
menu: widget.menu,
|
||||
onRefresh: () {
|
||||
context.read<InventoryAnalyticLoaderBloc>().add(
|
||||
InventoryAnalyticLoaderEvent.fetched(
|
||||
startDate: widget.startDate,
|
||||
endDate: widget.endDate,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Padding _buildSummary() {
|
||||
return Padding(
|
||||
padding: EdgeInsetsGeometry.only(top: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.success,
|
||||
icon: Icons.inventory_2_outlined,
|
||||
title: 'Total Produk',
|
||||
value: widget.state.inventoryAnalytic.summary.totalProducts
|
||||
.toString(),
|
||||
),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.info,
|
||||
icon: Icons.list_alt_outlined,
|
||||
title: 'Total Bahan',
|
||||
value: widget.state.inventoryAnalytic.summary.totalIngredients
|
||||
.toString(),
|
||||
),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.primary,
|
||||
icon: Icons.monetization_on_outlined,
|
||||
title: 'Total Nilai',
|
||||
value: widget
|
||||
.state
|
||||
.inventoryAnalytic
|
||||
.summary
|
||||
.totalValue
|
||||
.currencyFormatRpV2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab(String title, int index) {
|
||||
bool isActive = _selectedTabIndex == index;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedTabIndex = index;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? AppColor.primary : AppColor.white,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
border: Border.all(
|
||||
color: isActive ? AppColor.primary : AppColor.border,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isActive ? AppColor.white : AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,210 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../../../common/theme/theme.dart';
|
||||
import '../../../../../../../domain/analytic/analytic.dart';
|
||||
import '../../../../../../components/spaces/space.dart';
|
||||
import '../../../../../../components/widgets/report/report_summary_card.dart';
|
||||
|
||||
class ReportInventoryIngredientTab extends StatelessWidget {
|
||||
final InventoryAnalytic data;
|
||||
const ReportInventoryIngredientTab({super.key, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.success,
|
||||
icon: Icons.warning_amber_outlined,
|
||||
title: 'Low Stok',
|
||||
value: data.summary.lowStockIngredients.toString(),
|
||||
),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.error,
|
||||
icon: Icons.block_outlined,
|
||||
title: 'Zero Stok',
|
||||
value: data.summary.zeroStockIngredients.toString(),
|
||||
),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.error,
|
||||
icon: Icons.shopping_cart_checkout_outlined,
|
||||
title: 'Total Sold',
|
||||
value: data.summary.totalSoldIngredients.toString(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary, // Purple color
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Table(
|
||||
columnWidths: const {
|
||||
0: FlexColumnWidth(2.5), // Name
|
||||
1: FlexColumnWidth(1), // Stock
|
||||
2: FlexColumnWidth(2), // Masuk
|
||||
3: FlexColumnWidth(2), // Keluar
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
_buildHeaderCell('Nama'),
|
||||
_buildHeaderCell('Stock'),
|
||||
_buildHeaderCell('Masuk'),
|
||||
_buildHeaderCell('Keluar'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(color: AppColor.white),
|
||||
child: Table(
|
||||
columnWidths: {
|
||||
0: FlexColumnWidth(2.5), // Name
|
||||
1: FlexColumnWidth(1), // Stock
|
||||
2: FlexColumnWidth(2), // Masuk
|
||||
3: FlexColumnWidth(2), // Keluar
|
||||
},
|
||||
children: data.ingredients
|
||||
.map(
|
||||
(item) => _buildIngredientsDataRow(
|
||||
item,
|
||||
data.ingredients.indexOf(item) % 2 == 0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary, // Purple color
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(8),
|
||||
bottomRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Table(
|
||||
columnWidths: const {
|
||||
0: FlexColumnWidth(2.5), // Name
|
||||
1: FlexColumnWidth(1), // Stock
|
||||
2: FlexColumnWidth(2), // Masuk
|
||||
3: FlexColumnWidth(2), // Keluar
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
_buildTotalCell('TOTAL'),
|
||||
_buildTotalCell(
|
||||
(data.ingredients.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.quantity),
|
||||
)).toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
(data.ingredients.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.totalIn),
|
||||
)).toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
(data.ingredients.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.totalOut),
|
||||
)).toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
TableRow _buildIngredientsDataRow(
|
||||
InventoryAnalyticIngredientItem item,
|
||||
bool isEven,
|
||||
) {
|
||||
return TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: item.isZeroStock
|
||||
? Colors.red.shade100
|
||||
: item.isLowStock
|
||||
? Colors.yellow.shade100
|
||||
: isEven
|
||||
? Colors.grey.shade50
|
||||
: AppColor.white,
|
||||
),
|
||||
children: [
|
||||
_buildDataCell(item.ingredientName, alignment: Alignment.centerLeft),
|
||||
_buildDataCell(item.quantity.toString()),
|
||||
_buildDataCell(item.totalIn.toString()),
|
||||
_buildDataCell(item.totalOut.toString()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDataCell(
|
||||
String text, {
|
||||
Alignment alignment = Alignment.center,
|
||||
Color? textColor,
|
||||
}) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
alignment: alignment,
|
||||
child: Text(
|
||||
text,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: textColor ?? AppColor.black,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
textAlign: alignment == Alignment.centerLeft
|
||||
? TextAlign.left
|
||||
: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderCell(String text) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: Text(
|
||||
text,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTotalCell(String text) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: Text(
|
||||
text,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,216 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../../../common/theme/theme.dart';
|
||||
import '../../../../../../../domain/analytic/analytic.dart';
|
||||
import '../../../../../../components/spaces/space.dart';
|
||||
import '../../../../../../components/widgets/report/report_summary_card.dart';
|
||||
|
||||
class ReportInventoryProductTab extends StatelessWidget {
|
||||
final InventoryAnalytic data;
|
||||
const ReportInventoryProductTab({super.key, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.success,
|
||||
icon: Icons.warning_amber_outlined,
|
||||
title: 'Low Stok',
|
||||
value: data.summary.lowStockProducts.toString(),
|
||||
),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.error,
|
||||
icon: Icons.block_outlined,
|
||||
title: 'Zero Stok',
|
||||
value: data.summary.zeroStockProducts.toString(),
|
||||
),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: ReportSummaryCard(
|
||||
color: AppColor.error,
|
||||
icon: Icons.shopping_cart_checkout_outlined,
|
||||
title: 'Total Sold',
|
||||
value: data.summary.totalSoldProducts.toString(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary, // Purple color
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Table(
|
||||
columnWidths: const {
|
||||
0: FlexColumnWidth(2.5), // Produk
|
||||
1: FlexColumnWidth(2), // Kategori
|
||||
2: FlexColumnWidth(1), // Stock
|
||||
3: FlexColumnWidth(2), // Masuk
|
||||
4: FlexColumnWidth(2), // Keluar
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
_buildHeaderCell('Nama'),
|
||||
_buildHeaderCell('Kategori'),
|
||||
_buildHeaderCell('Stock'),
|
||||
_buildHeaderCell('Masuk'),
|
||||
_buildHeaderCell('Keluar'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(color: AppColor.white),
|
||||
child: Table(
|
||||
columnWidths: {
|
||||
0: FlexColumnWidth(2.5), // Produk
|
||||
1: FlexColumnWidth(2), // Kategori
|
||||
2: FlexColumnWidth(1), // Stock
|
||||
3: FlexColumnWidth(2), // Masuk
|
||||
4: FlexColumnWidth(2), // Keluar
|
||||
},
|
||||
children: data.products
|
||||
.map(
|
||||
(item) => _buildProductDataRow(
|
||||
item,
|
||||
data.products.indexOf(item) % 2 == 0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary, // Purple color
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(8),
|
||||
bottomRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Table(
|
||||
columnWidths: const {
|
||||
0: FlexColumnWidth(2.5), // Produk
|
||||
1: FlexColumnWidth(2), // Kategori
|
||||
2: FlexColumnWidth(1), // Stock
|
||||
3: FlexColumnWidth(2), // Masuk
|
||||
4: FlexColumnWidth(2), // Keluar
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
_buildTotalCell('TOTAL'),
|
||||
_buildTotalCell(''),
|
||||
_buildTotalCell(
|
||||
(data.products.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.quantity),
|
||||
)).toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
(data.products.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.totalIn),
|
||||
)).toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
(data.products.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.totalOut),
|
||||
)).toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
TableRow _buildProductDataRow(
|
||||
InventoryAnalyticProductItem product,
|
||||
bool isEven,
|
||||
) {
|
||||
return TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: product.isZeroStock
|
||||
? Colors.red.shade100
|
||||
: product.isLowStock
|
||||
? Colors.yellow.shade100
|
||||
: isEven
|
||||
? Colors.grey.shade50
|
||||
: AppColor.white,
|
||||
),
|
||||
children: [
|
||||
_buildDataCell(product.productName, alignment: Alignment.centerLeft),
|
||||
_buildDataCell(product.categoryName, alignment: Alignment.centerLeft),
|
||||
_buildDataCell(product.quantity.toString()),
|
||||
_buildDataCell(product.totalIn.toString()),
|
||||
_buildDataCell(product.totalOut.toString()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDataCell(
|
||||
String text, {
|
||||
Alignment alignment = Alignment.center,
|
||||
Color? textColor,
|
||||
}) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
alignment: alignment,
|
||||
child: Text(
|
||||
text,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: textColor ?? AppColor.black,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
textAlign: alignment == Alignment.centerLeft
|
||||
? TextAlign.left
|
||||
: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderCell(String text) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: Text(
|
||||
text,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTotalCell(String text) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: Text(
|
||||
text,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user