feat: transaction report
This commit is contained in:
parent
1aa65d1732
commit
f07d07b3a8
@ -0,0 +1,91 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../domain/analytic/repositories/i_analytic_repository.dart';
|
||||
import '../../../domain/outlet/outlet.dart';
|
||||
|
||||
part 'transaction_report_event.dart';
|
||||
part 'transaction_report_state.dart';
|
||||
part 'transaction_report_bloc.freezed.dart';
|
||||
|
||||
@injectable
|
||||
class TransactionReportBloc
|
||||
extends Bloc<TransactionReportEvent, TransactionReportState> {
|
||||
final IAnalyticRepository _analyticRepository;
|
||||
final IOutletRepository _outletRepository;
|
||||
|
||||
TransactionReportBloc(this._analyticRepository, this._outletRepository)
|
||||
: super(TransactionReportState.initial()) {
|
||||
on<TransactionReportEvent>(_onTransactionReportEvent);
|
||||
}
|
||||
|
||||
Future<void> _onTransactionReportEvent(
|
||||
TransactionReportEvent event,
|
||||
Emitter<TransactionReportState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
fetchedOutlet: (e) async {
|
||||
emit(
|
||||
state.copyWith(isFetchingOutlet: true, failureOptionOutlet: none()),
|
||||
);
|
||||
|
||||
final result = await _outletRepository.currentOutlet();
|
||||
|
||||
var data = result.fold(
|
||||
(f) => state.copyWith(failureOptionOutlet: optionOf(f)),
|
||||
(currentOutlet) => state.copyWith(outlet: currentOutlet),
|
||||
);
|
||||
|
||||
emit(data.copyWith(isFetchingOutlet: false));
|
||||
},
|
||||
fetchedTransaction: (e) async {
|
||||
emit(state.copyWith(isFetching: true, failureOptionAnalytic: none()));
|
||||
|
||||
var newState = state;
|
||||
|
||||
final category = await _analyticRepository.getCategory(
|
||||
dateFrom: e.dateFrom,
|
||||
dateTo: e.dateTo,
|
||||
);
|
||||
final profitLoss = await _analyticRepository.getProfitLoss(
|
||||
dateFrom: e.dateFrom,
|
||||
dateTo: e.dateTo,
|
||||
);
|
||||
final paymentMethod = await _analyticRepository.getPaymentMethod(
|
||||
dateFrom: e.dateFrom,
|
||||
dateTo: e.dateTo,
|
||||
);
|
||||
final product = await _analyticRepository.getProduct(
|
||||
dateFrom: e.dateFrom,
|
||||
dateTo: e.dateTo,
|
||||
);
|
||||
|
||||
newState = category.fold(
|
||||
(f) => newState.copyWith(failureOptionAnalytic: optionOf(f)),
|
||||
(categoryAnalytic) =>
|
||||
newState.copyWith(categoryAnalytic: categoryAnalytic),
|
||||
);
|
||||
newState = profitLoss.fold(
|
||||
(f) => newState.copyWith(failureOptionAnalytic: optionOf(f)),
|
||||
(profitLossAnalytic) =>
|
||||
newState.copyWith(profitLossAnalytic: profitLossAnalytic),
|
||||
);
|
||||
newState = paymentMethod.fold(
|
||||
(f) => newState.copyWith(failureOptionAnalytic: optionOf(f)),
|
||||
(paymentMethodAnalytic) =>
|
||||
newState.copyWith(paymentMethodAnalytic: paymentMethodAnalytic),
|
||||
);
|
||||
newState = product.fold(
|
||||
(f) => newState.copyWith(failureOptionAnalytic: optionOf(f)),
|
||||
(productAnalytic) =>
|
||||
newState.copyWith(productAnalytic: productAnalytic),
|
||||
);
|
||||
|
||||
emit(newState.copyWith(isFetching: false));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,752 @@
|
||||
// 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 'transaction_report_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 _$TransactionReportEvent {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() fetchedOutlet,
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
fetchedTransaction,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? fetchedOutlet,
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? fetchedTransaction,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? fetchedOutlet,
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? fetchedTransaction,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_FetchedOutlet value) fetchedOutlet,
|
||||
required TResult Function(_FetchedTransaction value) fetchedTransaction,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_FetchedOutlet value)? fetchedOutlet,
|
||||
TResult? Function(_FetchedTransaction value)? fetchedTransaction,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_FetchedOutlet value)? fetchedOutlet,
|
||||
TResult Function(_FetchedTransaction value)? fetchedTransaction,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $TransactionReportEventCopyWith<$Res> {
|
||||
factory $TransactionReportEventCopyWith(
|
||||
TransactionReportEvent value,
|
||||
$Res Function(TransactionReportEvent) then,
|
||||
) = _$TransactionReportEventCopyWithImpl<$Res, TransactionReportEvent>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$TransactionReportEventCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends TransactionReportEvent
|
||||
>
|
||||
implements $TransactionReportEventCopyWith<$Res> {
|
||||
_$TransactionReportEventCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of TransactionReportEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedOutletImplCopyWith<$Res> {
|
||||
factory _$$FetchedOutletImplCopyWith(
|
||||
_$FetchedOutletImpl value,
|
||||
$Res Function(_$FetchedOutletImpl) then,
|
||||
) = __$$FetchedOutletImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FetchedOutletImplCopyWithImpl<$Res>
|
||||
extends _$TransactionReportEventCopyWithImpl<$Res, _$FetchedOutletImpl>
|
||||
implements _$$FetchedOutletImplCopyWith<$Res> {
|
||||
__$$FetchedOutletImplCopyWithImpl(
|
||||
_$FetchedOutletImpl _value,
|
||||
$Res Function(_$FetchedOutletImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of TransactionReportEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$FetchedOutletImpl implements _FetchedOutlet {
|
||||
const _$FetchedOutletImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TransactionReportEvent.fetchedOutlet()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$FetchedOutletImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() fetchedOutlet,
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
fetchedTransaction,
|
||||
}) {
|
||||
return fetchedOutlet();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? fetchedOutlet,
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? fetchedTransaction,
|
||||
}) {
|
||||
return fetchedOutlet?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? fetchedOutlet,
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? fetchedTransaction,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetchedOutlet != null) {
|
||||
return fetchedOutlet();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_FetchedOutlet value) fetchedOutlet,
|
||||
required TResult Function(_FetchedTransaction value) fetchedTransaction,
|
||||
}) {
|
||||
return fetchedOutlet(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_FetchedOutlet value)? fetchedOutlet,
|
||||
TResult? Function(_FetchedTransaction value)? fetchedTransaction,
|
||||
}) {
|
||||
return fetchedOutlet?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_FetchedOutlet value)? fetchedOutlet,
|
||||
TResult Function(_FetchedTransaction value)? fetchedTransaction,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetchedOutlet != null) {
|
||||
return fetchedOutlet(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _FetchedOutlet implements TransactionReportEvent {
|
||||
const factory _FetchedOutlet() = _$FetchedOutletImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedTransactionImplCopyWith<$Res> {
|
||||
factory _$$FetchedTransactionImplCopyWith(
|
||||
_$FetchedTransactionImpl value,
|
||||
$Res Function(_$FetchedTransactionImpl) then,
|
||||
) = __$$FetchedTransactionImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({DateTime dateFrom, DateTime dateTo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FetchedTransactionImplCopyWithImpl<$Res>
|
||||
extends _$TransactionReportEventCopyWithImpl<$Res, _$FetchedTransactionImpl>
|
||||
implements _$$FetchedTransactionImplCopyWith<$Res> {
|
||||
__$$FetchedTransactionImplCopyWithImpl(
|
||||
_$FetchedTransactionImpl _value,
|
||||
$Res Function(_$FetchedTransactionImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of TransactionReportEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? dateFrom = null, Object? dateTo = null}) {
|
||||
return _then(
|
||||
_$FetchedTransactionImpl(
|
||||
null == dateFrom
|
||||
? _value.dateFrom
|
||||
: dateFrom // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
null == dateTo
|
||||
? _value.dateTo
|
||||
: dateTo // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$FetchedTransactionImpl implements _FetchedTransaction {
|
||||
const _$FetchedTransactionImpl(this.dateFrom, this.dateTo);
|
||||
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TransactionReportEvent.fetchedTransaction(dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FetchedTransactionImpl &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, dateFrom, dateTo);
|
||||
|
||||
/// Create a copy of TransactionReportEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$FetchedTransactionImplCopyWith<_$FetchedTransactionImpl> get copyWith =>
|
||||
__$$FetchedTransactionImplCopyWithImpl<_$FetchedTransactionImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() fetchedOutlet,
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
fetchedTransaction,
|
||||
}) {
|
||||
return fetchedTransaction(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? fetchedOutlet,
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? fetchedTransaction,
|
||||
}) {
|
||||
return fetchedTransaction?.call(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? fetchedOutlet,
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? fetchedTransaction,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetchedTransaction != null) {
|
||||
return fetchedTransaction(dateFrom, dateTo);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_FetchedOutlet value) fetchedOutlet,
|
||||
required TResult Function(_FetchedTransaction value) fetchedTransaction,
|
||||
}) {
|
||||
return fetchedTransaction(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_FetchedOutlet value)? fetchedOutlet,
|
||||
TResult? Function(_FetchedTransaction value)? fetchedTransaction,
|
||||
}) {
|
||||
return fetchedTransaction?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_FetchedOutlet value)? fetchedOutlet,
|
||||
TResult Function(_FetchedTransaction value)? fetchedTransaction,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetchedTransaction != null) {
|
||||
return fetchedTransaction(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _FetchedTransaction implements TransactionReportEvent {
|
||||
const factory _FetchedTransaction(
|
||||
final DateTime dateFrom,
|
||||
final DateTime dateTo,
|
||||
) = _$FetchedTransactionImpl;
|
||||
|
||||
DateTime get dateFrom;
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of TransactionReportEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$FetchedTransactionImplCopyWith<_$FetchedTransactionImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$TransactionReportState {
|
||||
CategoryAnalytic get categoryAnalytic => throw _privateConstructorUsedError;
|
||||
ProfitLossAnalytic get profitLossAnalytic =>
|
||||
throw _privateConstructorUsedError;
|
||||
PaymentMethodAnalytic get paymentMethodAnalytic =>
|
||||
throw _privateConstructorUsedError;
|
||||
ProductAnalytic get productAnalytic => throw _privateConstructorUsedError;
|
||||
Option<AnalyticFailure> get failureOptionAnalytic =>
|
||||
throw _privateConstructorUsedError;
|
||||
Outlet get outlet => throw _privateConstructorUsedError;
|
||||
Option<OutletFailure> get failureOptionOutlet =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isFetching => throw _privateConstructorUsedError;
|
||||
bool get isFetchingOutlet => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$TransactionReportStateCopyWith<TransactionReportState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $TransactionReportStateCopyWith<$Res> {
|
||||
factory $TransactionReportStateCopyWith(
|
||||
TransactionReportState value,
|
||||
$Res Function(TransactionReportState) then,
|
||||
) = _$TransactionReportStateCopyWithImpl<$Res, TransactionReportState>;
|
||||
@useResult
|
||||
$Res call({
|
||||
CategoryAnalytic categoryAnalytic,
|
||||
ProfitLossAnalytic profitLossAnalytic,
|
||||
PaymentMethodAnalytic paymentMethodAnalytic,
|
||||
ProductAnalytic productAnalytic,
|
||||
Option<AnalyticFailure> failureOptionAnalytic,
|
||||
Outlet outlet,
|
||||
Option<OutletFailure> failureOptionOutlet,
|
||||
bool isFetching,
|
||||
bool isFetchingOutlet,
|
||||
});
|
||||
|
||||
$CategoryAnalyticCopyWith<$Res> get categoryAnalytic;
|
||||
$ProfitLossAnalyticCopyWith<$Res> get profitLossAnalytic;
|
||||
$PaymentMethodAnalyticCopyWith<$Res> get paymentMethodAnalytic;
|
||||
$ProductAnalyticCopyWith<$Res> get productAnalytic;
|
||||
$OutletCopyWith<$Res> get outlet;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$TransactionReportStateCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends TransactionReportState
|
||||
>
|
||||
implements $TransactionReportStateCopyWith<$Res> {
|
||||
_$TransactionReportStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? categoryAnalytic = null,
|
||||
Object? profitLossAnalytic = null,
|
||||
Object? paymentMethodAnalytic = null,
|
||||
Object? productAnalytic = null,
|
||||
Object? failureOptionAnalytic = null,
|
||||
Object? outlet = null,
|
||||
Object? failureOptionOutlet = null,
|
||||
Object? isFetching = null,
|
||||
Object? isFetchingOutlet = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
categoryAnalytic: null == categoryAnalytic
|
||||
? _value.categoryAnalytic
|
||||
: categoryAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as CategoryAnalytic,
|
||||
profitLossAnalytic: null == profitLossAnalytic
|
||||
? _value.profitLossAnalytic
|
||||
: profitLossAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitLossAnalytic,
|
||||
paymentMethodAnalytic: null == paymentMethodAnalytic
|
||||
? _value.paymentMethodAnalytic
|
||||
: paymentMethodAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as PaymentMethodAnalytic,
|
||||
productAnalytic: null == productAnalytic
|
||||
? _value.productAnalytic
|
||||
: productAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as ProductAnalytic,
|
||||
failureOptionAnalytic: null == failureOptionAnalytic
|
||||
? _value.failureOptionAnalytic
|
||||
: failureOptionAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as Option<AnalyticFailure>,
|
||||
outlet: null == outlet
|
||||
? _value.outlet
|
||||
: outlet // ignore: cast_nullable_to_non_nullable
|
||||
as Outlet,
|
||||
failureOptionOutlet: null == failureOptionOutlet
|
||||
? _value.failureOptionOutlet
|
||||
: failureOptionOutlet // ignore: cast_nullable_to_non_nullable
|
||||
as Option<OutletFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isFetchingOutlet: null == isFetchingOutlet
|
||||
? _value.isFetchingOutlet
|
||||
: isFetchingOutlet // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$CategoryAnalyticCopyWith<$Res> get categoryAnalytic {
|
||||
return $CategoryAnalyticCopyWith<$Res>(_value.categoryAnalytic, (value) {
|
||||
return _then(_value.copyWith(categoryAnalytic: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProfitLossAnalyticCopyWith<$Res> get profitLossAnalytic {
|
||||
return $ProfitLossAnalyticCopyWith<$Res>(_value.profitLossAnalytic, (
|
||||
value,
|
||||
) {
|
||||
return _then(_value.copyWith(profitLossAnalytic: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$PaymentMethodAnalyticCopyWith<$Res> get paymentMethodAnalytic {
|
||||
return $PaymentMethodAnalyticCopyWith<$Res>(_value.paymentMethodAnalytic, (
|
||||
value,
|
||||
) {
|
||||
return _then(_value.copyWith(paymentMethodAnalytic: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProductAnalyticCopyWith<$Res> get productAnalytic {
|
||||
return $ProductAnalyticCopyWith<$Res>(_value.productAnalytic, (value) {
|
||||
return _then(_value.copyWith(productAnalytic: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$OutletCopyWith<$Res> get outlet {
|
||||
return $OutletCopyWith<$Res>(_value.outlet, (value) {
|
||||
return _then(_value.copyWith(outlet: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$TransactionReportStateImplCopyWith<$Res>
|
||||
implements $TransactionReportStateCopyWith<$Res> {
|
||||
factory _$$TransactionReportStateImplCopyWith(
|
||||
_$TransactionReportStateImpl value,
|
||||
$Res Function(_$TransactionReportStateImpl) then,
|
||||
) = __$$TransactionReportStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
CategoryAnalytic categoryAnalytic,
|
||||
ProfitLossAnalytic profitLossAnalytic,
|
||||
PaymentMethodAnalytic paymentMethodAnalytic,
|
||||
ProductAnalytic productAnalytic,
|
||||
Option<AnalyticFailure> failureOptionAnalytic,
|
||||
Outlet outlet,
|
||||
Option<OutletFailure> failureOptionOutlet,
|
||||
bool isFetching,
|
||||
bool isFetchingOutlet,
|
||||
});
|
||||
|
||||
@override
|
||||
$CategoryAnalyticCopyWith<$Res> get categoryAnalytic;
|
||||
@override
|
||||
$ProfitLossAnalyticCopyWith<$Res> get profitLossAnalytic;
|
||||
@override
|
||||
$PaymentMethodAnalyticCopyWith<$Res> get paymentMethodAnalytic;
|
||||
@override
|
||||
$ProductAnalyticCopyWith<$Res> get productAnalytic;
|
||||
@override
|
||||
$OutletCopyWith<$Res> get outlet;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$TransactionReportStateImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$TransactionReportStateCopyWithImpl<$Res, _$TransactionReportStateImpl>
|
||||
implements _$$TransactionReportStateImplCopyWith<$Res> {
|
||||
__$$TransactionReportStateImplCopyWithImpl(
|
||||
_$TransactionReportStateImpl _value,
|
||||
$Res Function(_$TransactionReportStateImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? categoryAnalytic = null,
|
||||
Object? profitLossAnalytic = null,
|
||||
Object? paymentMethodAnalytic = null,
|
||||
Object? productAnalytic = null,
|
||||
Object? failureOptionAnalytic = null,
|
||||
Object? outlet = null,
|
||||
Object? failureOptionOutlet = null,
|
||||
Object? isFetching = null,
|
||||
Object? isFetchingOutlet = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$TransactionReportStateImpl(
|
||||
categoryAnalytic: null == categoryAnalytic
|
||||
? _value.categoryAnalytic
|
||||
: categoryAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as CategoryAnalytic,
|
||||
profitLossAnalytic: null == profitLossAnalytic
|
||||
? _value.profitLossAnalytic
|
||||
: profitLossAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitLossAnalytic,
|
||||
paymentMethodAnalytic: null == paymentMethodAnalytic
|
||||
? _value.paymentMethodAnalytic
|
||||
: paymentMethodAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as PaymentMethodAnalytic,
|
||||
productAnalytic: null == productAnalytic
|
||||
? _value.productAnalytic
|
||||
: productAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as ProductAnalytic,
|
||||
failureOptionAnalytic: null == failureOptionAnalytic
|
||||
? _value.failureOptionAnalytic
|
||||
: failureOptionAnalytic // ignore: cast_nullable_to_non_nullable
|
||||
as Option<AnalyticFailure>,
|
||||
outlet: null == outlet
|
||||
? _value.outlet
|
||||
: outlet // ignore: cast_nullable_to_non_nullable
|
||||
as Outlet,
|
||||
failureOptionOutlet: null == failureOptionOutlet
|
||||
? _value.failureOptionOutlet
|
||||
: failureOptionOutlet // ignore: cast_nullable_to_non_nullable
|
||||
as Option<OutletFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isFetchingOutlet: null == isFetchingOutlet
|
||||
? _value.isFetchingOutlet
|
||||
: isFetchingOutlet // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$TransactionReportStateImpl implements _TransactionReportState {
|
||||
const _$TransactionReportStateImpl({
|
||||
required this.categoryAnalytic,
|
||||
required this.profitLossAnalytic,
|
||||
required this.paymentMethodAnalytic,
|
||||
required this.productAnalytic,
|
||||
required this.failureOptionAnalytic,
|
||||
required this.outlet,
|
||||
required this.failureOptionOutlet,
|
||||
this.isFetching = false,
|
||||
this.isFetchingOutlet = false,
|
||||
});
|
||||
|
||||
@override
|
||||
final CategoryAnalytic categoryAnalytic;
|
||||
@override
|
||||
final ProfitLossAnalytic profitLossAnalytic;
|
||||
@override
|
||||
final PaymentMethodAnalytic paymentMethodAnalytic;
|
||||
@override
|
||||
final ProductAnalytic productAnalytic;
|
||||
@override
|
||||
final Option<AnalyticFailure> failureOptionAnalytic;
|
||||
@override
|
||||
final Outlet outlet;
|
||||
@override
|
||||
final Option<OutletFailure> failureOptionOutlet;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetching;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetchingOutlet;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TransactionReportState(categoryAnalytic: $categoryAnalytic, profitLossAnalytic: $profitLossAnalytic, paymentMethodAnalytic: $paymentMethodAnalytic, productAnalytic: $productAnalytic, failureOptionAnalytic: $failureOptionAnalytic, outlet: $outlet, failureOptionOutlet: $failureOptionOutlet, isFetching: $isFetching, isFetchingOutlet: $isFetchingOutlet)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$TransactionReportStateImpl &&
|
||||
(identical(other.categoryAnalytic, categoryAnalytic) ||
|
||||
other.categoryAnalytic == categoryAnalytic) &&
|
||||
(identical(other.profitLossAnalytic, profitLossAnalytic) ||
|
||||
other.profitLossAnalytic == profitLossAnalytic) &&
|
||||
(identical(other.paymentMethodAnalytic, paymentMethodAnalytic) ||
|
||||
other.paymentMethodAnalytic == paymentMethodAnalytic) &&
|
||||
(identical(other.productAnalytic, productAnalytic) ||
|
||||
other.productAnalytic == productAnalytic) &&
|
||||
(identical(other.failureOptionAnalytic, failureOptionAnalytic) ||
|
||||
other.failureOptionAnalytic == failureOptionAnalytic) &&
|
||||
(identical(other.outlet, outlet) || other.outlet == outlet) &&
|
||||
(identical(other.failureOptionOutlet, failureOptionOutlet) ||
|
||||
other.failureOptionOutlet == failureOptionOutlet) &&
|
||||
(identical(other.isFetching, isFetching) ||
|
||||
other.isFetching == isFetching) &&
|
||||
(identical(other.isFetchingOutlet, isFetchingOutlet) ||
|
||||
other.isFetchingOutlet == isFetchingOutlet));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
categoryAnalytic,
|
||||
profitLossAnalytic,
|
||||
paymentMethodAnalytic,
|
||||
productAnalytic,
|
||||
failureOptionAnalytic,
|
||||
outlet,
|
||||
failureOptionOutlet,
|
||||
isFetching,
|
||||
isFetchingOutlet,
|
||||
);
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$TransactionReportStateImplCopyWith<_$TransactionReportStateImpl>
|
||||
get copyWith =>
|
||||
__$$TransactionReportStateImplCopyWithImpl<_$TransactionReportStateImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class _TransactionReportState implements TransactionReportState {
|
||||
const factory _TransactionReportState({
|
||||
required final CategoryAnalytic categoryAnalytic,
|
||||
required final ProfitLossAnalytic profitLossAnalytic,
|
||||
required final PaymentMethodAnalytic paymentMethodAnalytic,
|
||||
required final ProductAnalytic productAnalytic,
|
||||
required final Option<AnalyticFailure> failureOptionAnalytic,
|
||||
required final Outlet outlet,
|
||||
required final Option<OutletFailure> failureOptionOutlet,
|
||||
final bool isFetching,
|
||||
final bool isFetchingOutlet,
|
||||
}) = _$TransactionReportStateImpl;
|
||||
|
||||
@override
|
||||
CategoryAnalytic get categoryAnalytic;
|
||||
@override
|
||||
ProfitLossAnalytic get profitLossAnalytic;
|
||||
@override
|
||||
PaymentMethodAnalytic get paymentMethodAnalytic;
|
||||
@override
|
||||
ProductAnalytic get productAnalytic;
|
||||
@override
|
||||
Option<AnalyticFailure> get failureOptionAnalytic;
|
||||
@override
|
||||
Outlet get outlet;
|
||||
@override
|
||||
Option<OutletFailure> get failureOptionOutlet;
|
||||
@override
|
||||
bool get isFetching;
|
||||
@override
|
||||
bool get isFetchingOutlet;
|
||||
|
||||
/// Create a copy of TransactionReportState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$TransactionReportStateImplCopyWith<_$TransactionReportStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
part of 'transaction_report_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class TransactionReportEvent with _$TransactionReportEvent {
|
||||
const factory TransactionReportEvent.fetchedOutlet() = _FetchedOutlet;
|
||||
const factory TransactionReportEvent.fetchedTransaction(
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
) = _FetchedTransaction;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
part of 'transaction_report_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class TransactionReportState with _$TransactionReportState {
|
||||
const factory TransactionReportState({
|
||||
required CategoryAnalytic categoryAnalytic,
|
||||
required ProfitLossAnalytic profitLossAnalytic,
|
||||
required PaymentMethodAnalytic paymentMethodAnalytic,
|
||||
required ProductAnalytic productAnalytic,
|
||||
required Option<AnalyticFailure> failureOptionAnalytic,
|
||||
required Outlet outlet,
|
||||
required Option<OutletFailure> failureOptionOutlet,
|
||||
@Default(false) bool isFetching,
|
||||
@Default(false) bool isFetchingOutlet,
|
||||
}) = _TransactionReportState;
|
||||
|
||||
factory TransactionReportState.initial() => TransactionReportState(
|
||||
failureOptionAnalytic: none(),
|
||||
outlet: Outlet.empty(),
|
||||
failureOptionOutlet: none(),
|
||||
categoryAnalytic: CategoryAnalytic.empty(),
|
||||
profitLossAnalytic: ProfitLossAnalytic.empty(),
|
||||
paymentMethodAnalytic: PaymentMethodAnalytic.empty(),
|
||||
productAnalytic: ProductAnalytic.empty(),
|
||||
);
|
||||
}
|
||||
@ -43,6 +43,8 @@ import 'package:apskel_owner_flutter/application/product/product_loader/product_
|
||||
as _i458;
|
||||
import 'package:apskel_owner_flutter/application/report/inventory_report/inventory_report_bloc.dart'
|
||||
as _i346;
|
||||
import 'package:apskel_owner_flutter/application/report/transaction_report/transaction_report_bloc.dart'
|
||||
as _i605;
|
||||
import 'package:apskel_owner_flutter/application/user/change_password_form/change_password_form_bloc.dart'
|
||||
as _i1030;
|
||||
import 'package:apskel_owner_flutter/application/user/user_edit_form/user_edit_form_bloc.dart'
|
||||
@ -265,6 +267,12 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh.factory<_i1030.ChangePasswordFormBloc>(
|
||||
() => _i1030.ChangePasswordFormBloc(gh<_i635.IUserRepository>()),
|
||||
);
|
||||
gh.factory<_i605.TransactionReportBloc>(
|
||||
() => _i605.TransactionReportBloc(
|
||||
gh<_i477.IAnalyticRepository>(),
|
||||
gh<_i197.IOutletRepository>(),
|
||||
),
|
||||
);
|
||||
gh.factory<_i346.InventoryReportBloc>(
|
||||
() => _i346.InventoryReportBloc(
|
||||
gh<_i477.IAnalyticRepository>(),
|
||||
|
||||
956
lib/presentation/components/report/transaction_report.dart
Normal file
956
lib/presentation/components/report/transaction_report.dart
Normal file
@ -0,0 +1,956 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/utils/pdf_service.dart';
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../domain/outlet/outlet.dart';
|
||||
|
||||
class TransactionReport {
|
||||
static final primaryColor = PdfColor.fromHex("36175e");
|
||||
|
||||
static Future<File> previewPdf({
|
||||
required Outlet outlet,
|
||||
required String searchDateFormatted,
|
||||
required CategoryAnalytic categoryAnalyticData,
|
||||
required ProfitLossAnalytic profitLossData,
|
||||
required PaymentMethodAnalytic paymentMethodAnalyticData,
|
||||
required ProductAnalytic productAnalyticData,
|
||||
}) async {
|
||||
final pdf = pw.Document();
|
||||
final ByteData dataImage = await rootBundle.load('assets/images/logo.png');
|
||||
final Uint8List bytes = dataImage.buffer.asUint8List();
|
||||
|
||||
final profitLossProductSummary = {
|
||||
'totalRevenue': profitLossData.productData.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.revenue),
|
||||
),
|
||||
'totalCost': profitLossData.productData.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.cost),
|
||||
),
|
||||
'totalGrossProfit': profitLossData.productData.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.grossProfit),
|
||||
),
|
||||
'totalQuantity': profitLossData.productData.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.quantitySold),
|
||||
),
|
||||
};
|
||||
|
||||
final categorySummary = {
|
||||
'totalRevenue': categoryAnalyticData.data.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.totalRevenue),
|
||||
),
|
||||
'orderCount': categoryAnalyticData.data.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.orderCount),
|
||||
),
|
||||
'productCount': categoryAnalyticData.data.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.productCount),
|
||||
),
|
||||
'totalQuantity': categoryAnalyticData.data.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.totalQuantity),
|
||||
),
|
||||
};
|
||||
|
||||
final productItemSummary = {
|
||||
'totalRevenue': productAnalyticData.data.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.revenue),
|
||||
),
|
||||
'orderCount': productAnalyticData.data.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.orderCount),
|
||||
),
|
||||
'totalQuantitySold': productAnalyticData.data.fold<num>(
|
||||
0,
|
||||
(sum, item) => sum + (item.quantitySold),
|
||||
),
|
||||
};
|
||||
|
||||
// Membuat objek Image dari gambar
|
||||
final image = pw.MemoryImage(bytes);
|
||||
pdf.addPage(
|
||||
pw.MultiPage(
|
||||
pageFormat: PdfPageFormat.a4,
|
||||
margin: pw.EdgeInsets.zero,
|
||||
build: (pw.Context context) {
|
||||
return [
|
||||
pw.Container(
|
||||
padding: pw.EdgeInsets.all(20),
|
||||
child: pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Bagian kiri - Logo dan Info Perusahaan
|
||||
pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Icon/Logo placeholder (bisa diganti dengan gambar logo)
|
||||
pw.Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: pw.Image(image),
|
||||
),
|
||||
pw.SizedBox(width: 15),
|
||||
pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Apskel',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text(
|
||||
outlet.name,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 16,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
outlet.address,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
color: PdfColors.grey600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// Bagian kanan - Info Laporan
|
||||
pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Laporan Transaksi',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.grey800,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Text(
|
||||
searchDateFormatted,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 14,
|
||||
color: PdfColors.grey600,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text(
|
||||
'Laporan',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
color: PdfColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
width: double.infinity,
|
||||
height: 3,
|
||||
color: primaryColor,
|
||||
),
|
||||
|
||||
// Summary
|
||||
pw.Container(
|
||||
padding: pw.EdgeInsets.all(20),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionWidget('1. Ringkasan'),
|
||||
pw.SizedBox(height: 30),
|
||||
pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Expanded(
|
||||
flex: 1,
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSummaryItem(
|
||||
'Total Penjualan (termasuk rasik)',
|
||||
(profitLossData.summary.totalRevenue)
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
),
|
||||
_buildSummaryItem(
|
||||
'Total Terjual',
|
||||
(profitLossData.summary.totalOrders).toString(),
|
||||
),
|
||||
_buildSummaryItem(
|
||||
'HPP',
|
||||
'${safeCurrency(profitLossData.summary.totalCost)} | ${safePercentage(profitLossData.summary.totalCost, profitLossData.summary.totalRevenue)}',
|
||||
),
|
||||
_buildSummaryItem(
|
||||
'Laba Kotor',
|
||||
'${safeCurrency(profitLossData.summary.grossProfit)} | ${safeRound(profitLossData.summary.grossProfitMargin)}%',
|
||||
valueStyle: pw.TextStyle(
|
||||
color: PdfColors.green800,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
labelStyle: pw.TextStyle(
|
||||
color: PdfColors.green800,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.SizedBox(width: 20),
|
||||
pw.Expanded(
|
||||
flex: 1,
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSummaryItem(
|
||||
'Biaya Lain lain',
|
||||
'${safeCurrency(profitLossData.summary.totalTax)} | ${safePercentage(profitLossData.summary.totalTax, profitLossData.summary.totalRevenue)}',
|
||||
),
|
||||
_buildSummaryItem(
|
||||
'Laba/Rugi',
|
||||
'${safeCurrency(profitLossData.summary.netProfit)} | ${safeRound(profitLossData.summary.netProfitMargin)}%',
|
||||
valueStyle: pw.TextStyle(
|
||||
color: PdfColors.blue800,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
labelStyle: pw.TextStyle(
|
||||
color: PdfColors.blue800,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
pw.SizedBox(height: 16),
|
||||
pw.Text(
|
||||
"Laba Rugi Perproduk",
|
||||
style: pw.TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 20),
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: primaryColor, // Purple color
|
||||
borderRadius: pw.BorderRadius.only(
|
||||
topLeft: pw.Radius.circular(8),
|
||||
topRight: pw.Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: pw.Table(
|
||||
columnWidths: const {
|
||||
0: pw.FlexColumnWidth(2.5), // Produk
|
||||
1: pw.FlexColumnWidth(1), // Qty
|
||||
2: pw.FlexColumnWidth(2.5), // Pendapatan
|
||||
3: pw.FlexColumnWidth(2), // HPP
|
||||
4: pw.FlexColumnWidth(2), // Laba Kotor
|
||||
5: pw.FlexColumnWidth(2), // Margin (%)
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildHeaderCell('Produk'),
|
||||
_buildHeaderCell('Qty'),
|
||||
_buildHeaderCell('Pendapatan'),
|
||||
_buildHeaderCell('HPP'),
|
||||
_buildHeaderCell('Laba Kotor'),
|
||||
_buildHeaderCell('Margin (%)'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(color: PdfColors.white),
|
||||
child: pw.Table(
|
||||
columnWidths: {
|
||||
0: pw.FlexColumnWidth(2.5), // Produk
|
||||
1: pw.FlexColumnWidth(1), // Qty
|
||||
2: pw.FlexColumnWidth(2.5), // Pendapatan
|
||||
3: pw.FlexColumnWidth(2), // HPP
|
||||
4: pw.FlexColumnWidth(2), // Laba Kotor
|
||||
5: pw.FlexColumnWidth(2), // Margin (%)
|
||||
},
|
||||
children: profitLossData.productData
|
||||
.map(
|
||||
(profitLoss) => _buildPerProductDataRow(
|
||||
product: profitLoss.productName,
|
||||
qty: profitLoss.quantitySold.toString(),
|
||||
pendapatan: profitLoss.revenue
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
hpp: profitLoss.cost
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
labaKotor: profitLoss.grossProfit
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
margin:
|
||||
'${safeRound(profitLoss.grossProfitMargin)}%',
|
||||
isEven:
|
||||
profitLossData.productData.indexOf(
|
||||
profitLoss,
|
||||
) %
|
||||
2 ==
|
||||
0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: primaryColor, // Purple color
|
||||
borderRadius: pw.BorderRadius.only(
|
||||
bottomLeft: pw.Radius.circular(8),
|
||||
bottomRight: pw.Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: pw.Table(
|
||||
columnWidths: const {
|
||||
0: pw.FlexColumnWidth(2.5), // Produk
|
||||
1: pw.FlexColumnWidth(1), // Qty
|
||||
2: pw.FlexColumnWidth(2.5), // Pendapatan
|
||||
3: pw.FlexColumnWidth(2), // HPP
|
||||
4: pw.FlexColumnWidth(2), // Laba Kotor
|
||||
5: pw.FlexColumnWidth(2), // Margin (%)
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildTotalCell('TOTAL'),
|
||||
_buildTotalCell(
|
||||
profitLossProductSummary['totalQuantity']
|
||||
.toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
profitLossProductSummary['totalRevenue']
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
),
|
||||
_buildTotalCell(
|
||||
profitLossProductSummary['totalCost']
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
),
|
||||
_buildTotalCell(
|
||||
profitLossProductSummary['totalGrossProfit']
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
),
|
||||
_buildTotalCell(''),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Summary Payment Method
|
||||
pw.Container(
|
||||
padding: pw.EdgeInsets.all(20),
|
||||
child: pw.Column(
|
||||
children: [
|
||||
pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionWidget('2. Ringkasan Metode Pembayaran'),
|
||||
pw.SizedBox(height: 30),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: primaryColor, // Purple color
|
||||
borderRadius: pw.BorderRadius.only(
|
||||
topLeft: pw.Radius.circular(8),
|
||||
topRight: pw.Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: pw.Table(
|
||||
columnWidths: const {
|
||||
0: pw.FlexColumnWidth(2.5), // Nama
|
||||
1: pw.FlexColumnWidth(1), // Tipe
|
||||
2: pw.FlexColumnWidth(2.5), // Jumlah Order
|
||||
3: pw.FlexColumnWidth(2), // Total Amount
|
||||
4: pw.FlexColumnWidth(2), // Presentase
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildHeaderCell('Nama'),
|
||||
_buildHeaderCell('Tipe'),
|
||||
_buildHeaderCell('Jumlah Order'),
|
||||
_buildHeaderCell('Total Amount'),
|
||||
_buildHeaderCell('Presentase'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(color: PdfColors.white),
|
||||
child: pw.Table(
|
||||
columnWidths: {
|
||||
0: pw.FlexColumnWidth(2.5), // Nama
|
||||
1: pw.FlexColumnWidth(1), // Tipe
|
||||
2: pw.FlexColumnWidth(2.5), // Jumlah Order
|
||||
3: pw.FlexColumnWidth(2), // Total Amount
|
||||
4: pw.FlexColumnWidth(2), // Presentase
|
||||
},
|
||||
children: paymentMethodAnalyticData.data
|
||||
.map(
|
||||
(payment) => _buildPaymentMethodDataRow(
|
||||
name: payment.paymentMethodName,
|
||||
tipe: payment.paymentMethodType.toTitleCase,
|
||||
jumlahOrder: payment.orderCount.toString(),
|
||||
totalAmount: payment.totalAmount
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
presentase:
|
||||
'${safeRound(payment.percentage)}%',
|
||||
isEven:
|
||||
paymentMethodAnalyticData.data.indexOf(
|
||||
payment,
|
||||
) %
|
||||
2 ==
|
||||
0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: primaryColor, // Purple color
|
||||
borderRadius: pw.BorderRadius.only(
|
||||
bottomLeft: pw.Radius.circular(8),
|
||||
bottomRight: pw.Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: pw.Table(
|
||||
columnWidths: const {
|
||||
0: pw.FlexColumnWidth(2.5), // Produk
|
||||
1: pw.FlexColumnWidth(1), // Qty
|
||||
2: pw.FlexColumnWidth(2.5), // Pendapatan
|
||||
3: pw.FlexColumnWidth(2), // HPP
|
||||
4: pw.FlexColumnWidth(2), // Laba Kotor
|
||||
5: pw.FlexColumnWidth(2), // Margin (%)
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildTotalCell('TOTAL'),
|
||||
_buildTotalCell(''),
|
||||
_buildTotalCell(
|
||||
(paymentMethodAnalyticData
|
||||
.summary
|
||||
.totalOrders)
|
||||
.toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
(paymentMethodAnalyticData
|
||||
.summary
|
||||
.totalAmount)
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
),
|
||||
_buildTotalCell(''),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Summary Category
|
||||
pw.Container(
|
||||
padding: pw.EdgeInsets.all(20),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionWidget('3. Ringkasan Kategori'),
|
||||
pw.SizedBox(height: 30),
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: primaryColor, // Purple color
|
||||
borderRadius: pw.BorderRadius.only(
|
||||
topLeft: pw.Radius.circular(8),
|
||||
topRight: pw.Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: pw.Table(
|
||||
columnWidths: const {
|
||||
0: pw.FlexColumnWidth(2.5), // Nama
|
||||
1: pw.FlexColumnWidth(2), // Total Product
|
||||
2: pw.FlexColumnWidth(1), // qty
|
||||
3: pw.FlexColumnWidth(2), // Jumlah Order
|
||||
4: pw.FlexColumnWidth(2.5), // Presentase
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildHeaderCell('Nama'),
|
||||
_buildHeaderCell('Total Produk'),
|
||||
_buildHeaderCell('Qty'),
|
||||
_buildHeaderCell('Jumlah Order'),
|
||||
_buildHeaderCell('Pendapatan'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(color: PdfColors.white),
|
||||
child: pw.Table(
|
||||
columnWidths: {
|
||||
0: pw.FlexColumnWidth(2.5), // Nama
|
||||
1: pw.FlexColumnWidth(2), // Total Product
|
||||
2: pw.FlexColumnWidth(1), // qty
|
||||
3: pw.FlexColumnWidth(2), // Jumlah Order
|
||||
4: pw.FlexColumnWidth(2.5), // Presentase
|
||||
},
|
||||
children: categoryAnalyticData.data
|
||||
.map(
|
||||
(category) => _buildCategoryDataRow(
|
||||
name: category.categoryName,
|
||||
totalProduct: category.productCount
|
||||
.toString(),
|
||||
qty: category.totalQuantity.toString(),
|
||||
jumlahOrder: category.orderCount.toString(),
|
||||
pendapatan: category.totalRevenue
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
isEven:
|
||||
categoryAnalyticData.data.indexOf(
|
||||
category,
|
||||
) %
|
||||
2 ==
|
||||
0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: primaryColor, // Purple color
|
||||
borderRadius: pw.BorderRadius.only(
|
||||
bottomLeft: pw.Radius.circular(8),
|
||||
bottomRight: pw.Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: pw.Table(
|
||||
columnWidths: const {
|
||||
0: pw.FlexColumnWidth(2.5), // Nama
|
||||
1: pw.FlexColumnWidth(2), // Total Product
|
||||
2: pw.FlexColumnWidth(1), // qty
|
||||
3: pw.FlexColumnWidth(2), // Jumlah Order
|
||||
4: pw.FlexColumnWidth(2.5), // Presentase
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildTotalCell('TOTAL'),
|
||||
_buildTotalCell(
|
||||
categorySummary['productCount'].toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
categorySummary['totalQuantity'].toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
categorySummary['orderCount'].toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
categorySummary['totalRevenue']
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Summary Item
|
||||
pw.Container(
|
||||
padding: pw.EdgeInsets.all(20),
|
||||
child: pw.Column(
|
||||
children: [
|
||||
pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionWidget('4. Ringkasan Item'),
|
||||
pw.SizedBox(height: 30),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: primaryColor, // Purple color
|
||||
borderRadius: pw.BorderRadius.only(
|
||||
topLeft: pw.Radius.circular(8),
|
||||
topRight: pw.Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: pw.Table(
|
||||
columnWidths: const {
|
||||
0: pw.FlexColumnWidth(2.5), // Produk
|
||||
1: pw.FlexColumnWidth(2), // Kategori
|
||||
2: pw.FlexColumnWidth(1), // qty
|
||||
3: pw.FlexColumnWidth(2), // Order
|
||||
4: pw.FlexColumnWidth(2), // Pendapatan
|
||||
5: pw.FlexColumnWidth(2), // Average
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildHeaderCell('Produk'),
|
||||
_buildHeaderCell('Kategori'),
|
||||
_buildHeaderCell('Qty'),
|
||||
_buildHeaderCell('Order'),
|
||||
_buildHeaderCell('Pendapatan'),
|
||||
_buildHeaderCell('Rata Rata'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(color: PdfColors.white),
|
||||
child: pw.Table(
|
||||
columnWidths: {
|
||||
0: pw.FlexColumnWidth(2.5), // Produk
|
||||
1: pw.FlexColumnWidth(2), // Kategori
|
||||
2: pw.FlexColumnWidth(1), // qty
|
||||
3: pw.FlexColumnWidth(2), // Order
|
||||
4: pw.FlexColumnWidth(2), // Pendapatan
|
||||
5: pw.FlexColumnWidth(2), // Average
|
||||
},
|
||||
children: productAnalyticData.data
|
||||
.map(
|
||||
(item) => _buildItemDataRow(
|
||||
product: item.productName,
|
||||
category: item.categoryName,
|
||||
qty: item.quantitySold.toString(),
|
||||
order: item.orderCount.toString(),
|
||||
pendapatan: item.revenue
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
average: safeCurrency(
|
||||
item.averagePrice.round(),
|
||||
),
|
||||
isEven:
|
||||
productAnalyticData.data.indexOf(item) %
|
||||
2 ==
|
||||
0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: primaryColor, // Purple color
|
||||
borderRadius: pw.BorderRadius.only(
|
||||
bottomLeft: pw.Radius.circular(8),
|
||||
bottomRight: pw.Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: pw.Table(
|
||||
columnWidths: const {
|
||||
0: pw.FlexColumnWidth(2.5), // Produk
|
||||
1: pw.FlexColumnWidth(2), // Kategori
|
||||
2: pw.FlexColumnWidth(1), // qty
|
||||
3: pw.FlexColumnWidth(2), // Order
|
||||
4: pw.FlexColumnWidth(2), // Pendapatan
|
||||
5: pw.FlexColumnWidth(2), // Average
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildTotalCell('TOTAL'),
|
||||
_buildTotalCell(''),
|
||||
_buildTotalCell(
|
||||
productItemSummary['totalQuantitySold']
|
||||
.toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
productItemSummary['orderCount'].toString(),
|
||||
),
|
||||
_buildTotalCell(
|
||||
productItemSummary['totalRevenue']
|
||||
.toString()
|
||||
.currencyFormatRpV2,
|
||||
),
|
||||
_buildTotalCell(''),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return HelperPdfService.saveDocument(
|
||||
name: 'Laporan Transaksi | $searchDateFormatted.pdf',
|
||||
pdf: pdf,
|
||||
);
|
||||
}
|
||||
|
||||
static String safePercentage(num numerator, num denominator) {
|
||||
if (denominator == 0 ||
|
||||
numerator.isInfinite ||
|
||||
numerator.isNaN ||
|
||||
denominator.isInfinite ||
|
||||
denominator.isNaN) {
|
||||
return '0%';
|
||||
}
|
||||
final result = (numerator / denominator) * 100;
|
||||
if (result.isInfinite || result.isNaN) {
|
||||
return '0%';
|
||||
}
|
||||
return '${result.round()}%';
|
||||
}
|
||||
|
||||
static String safeRound(num value) {
|
||||
if (value.isInfinite || value.isNaN) {
|
||||
return '0';
|
||||
}
|
||||
return value.round().toString();
|
||||
}
|
||||
|
||||
static String safeCurrency(num value) {
|
||||
if (value.isInfinite || value.isNaN) {
|
||||
return '0'.currencyFormatRpV2;
|
||||
}
|
||||
return value.toString().currencyFormatRpV2;
|
||||
}
|
||||
|
||||
static pw.Widget _buildSectionWidget(String title) {
|
||||
return pw.Text(
|
||||
title,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: primaryColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static pw.Widget _buildSummaryItem(
|
||||
String label,
|
||||
String value, {
|
||||
pw.TextStyle? valueStyle,
|
||||
pw.TextStyle? labelStyle,
|
||||
}) {
|
||||
return pw.Container(
|
||||
padding: pw.EdgeInsets.only(bottom: 8),
|
||||
margin: pw.EdgeInsets.only(bottom: 16),
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border(bottom: pw.BorderSide(color: PdfColors.grey300)),
|
||||
),
|
||||
child: pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Text(label, style: labelStyle),
|
||||
pw.Text(
|
||||
value,
|
||||
style: valueStyle ?? pw.TextStyle(fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static pw.Widget _buildHeaderCell(String text) {
|
||||
return pw.Container(
|
||||
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: pw.Text(
|
||||
text,
|
||||
style: pw.TextStyle(
|
||||
color: PdfColors.white,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
textAlign: pw.TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static pw.Widget _buildDataCell(
|
||||
String text, {
|
||||
pw.Alignment alignment = pw.Alignment.center,
|
||||
PdfColor? textColor,
|
||||
}) {
|
||||
return pw.Container(
|
||||
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
alignment: alignment,
|
||||
child: pw.Text(
|
||||
text,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
color: textColor ?? PdfColors.black,
|
||||
fontWeight: pw.FontWeight.normal,
|
||||
),
|
||||
textAlign: alignment == pw.Alignment.centerLeft
|
||||
? pw.TextAlign.left
|
||||
: pw.TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static pw.Widget _buildTotalCell(String text) {
|
||||
return pw.Container(
|
||||
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: pw.Text(
|
||||
text,
|
||||
style: pw.TextStyle(
|
||||
color: PdfColors.white,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
textAlign: pw.TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static pw.TableRow _buildPerProductDataRow({
|
||||
required String product,
|
||||
required String qty,
|
||||
required String pendapatan,
|
||||
required String hpp,
|
||||
required String labaKotor,
|
||||
required String margin,
|
||||
required bool isEven,
|
||||
}) {
|
||||
return pw.TableRow(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: isEven ? PdfColors.grey50 : PdfColors.white,
|
||||
),
|
||||
children: [
|
||||
_buildDataCell(product, alignment: pw.Alignment.centerLeft),
|
||||
_buildDataCell(qty),
|
||||
_buildDataCell(pendapatan),
|
||||
_buildDataCell(hpp, textColor: PdfColors.red600),
|
||||
_buildDataCell(labaKotor, textColor: PdfColors.green600),
|
||||
_buildDataCell(margin),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static pw.TableRow _buildPaymentMethodDataRow({
|
||||
required String name,
|
||||
required String tipe,
|
||||
required String jumlahOrder,
|
||||
required String totalAmount,
|
||||
required String presentase,
|
||||
required bool isEven,
|
||||
}) {
|
||||
return pw.TableRow(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: isEven ? PdfColors.grey50 : PdfColors.white,
|
||||
),
|
||||
children: [
|
||||
_buildDataCell(name, alignment: pw.Alignment.centerLeft),
|
||||
_buildDataCell(tipe),
|
||||
_buildDataCell(jumlahOrder),
|
||||
_buildDataCell(totalAmount),
|
||||
_buildDataCell(presentase),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static pw.TableRow _buildCategoryDataRow({
|
||||
required String name,
|
||||
required String totalProduct,
|
||||
required String qty,
|
||||
required String jumlahOrder,
|
||||
required String pendapatan,
|
||||
required bool isEven,
|
||||
}) {
|
||||
return pw.TableRow(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: isEven ? PdfColors.grey50 : PdfColors.white,
|
||||
),
|
||||
children: [
|
||||
_buildDataCell(name, alignment: pw.Alignment.centerLeft),
|
||||
_buildDataCell(totalProduct),
|
||||
_buildDataCell(qty),
|
||||
_buildDataCell(jumlahOrder),
|
||||
_buildDataCell(pendapatan),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static pw.TableRow _buildItemDataRow({
|
||||
required String product,
|
||||
required String category,
|
||||
required String qty,
|
||||
required String order,
|
||||
required String pendapatan,
|
||||
required String average,
|
||||
required bool isEven,
|
||||
}) {
|
||||
return pw.TableRow(
|
||||
decoration: pw.BoxDecoration(
|
||||
color: isEven ? PdfColors.grey50 : PdfColors.white,
|
||||
),
|
||||
children: [
|
||||
_buildDataCell(product, alignment: pw.Alignment.centerLeft),
|
||||
_buildDataCell(category, alignment: pw.Alignment.centerLeft),
|
||||
_buildDataCell(qty),
|
||||
_buildDataCell(order),
|
||||
_buildDataCell(pendapatan),
|
||||
_buildDataCell(average),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
|
||||
import '../../../application/report/inventory_report/inventory_report_bloc.dart';
|
||||
import '../../../application/report/transaction_report/transaction_report_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../common/utils/pdf_service.dart';
|
||||
@ -14,6 +15,7 @@ import '../../../injection.dart';
|
||||
import '../../components/appbar/appbar.dart';
|
||||
import '../../components/field/date_range_picker_field.dart';
|
||||
import '../../components/report/inventory_report.dart';
|
||||
import '../../components/report/transaction_report.dart';
|
||||
import '../../components/toast/flushbar.dart';
|
||||
|
||||
@RoutePage()
|
||||
@ -24,9 +26,19 @@ class DownloadReportPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
State<DownloadReportPage> createState() => _DownloadReportPageState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (context) =>
|
||||
getIt<InventoryReportBloc>()..add(InventoryReportEvent.fetchedOutlet()),
|
||||
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) =>
|
||||
getIt<InventoryReportBloc>()
|
||||
..add(InventoryReportEvent.fetchedOutlet()),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) =>
|
||||
getIt<TransactionReportBloc>()
|
||||
..add(TransactionReportEvent.fetchedOutlet()),
|
||||
),
|
||||
],
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
@ -88,17 +100,6 @@ class _DownloadReportPageState extends State<DownloadReportPage>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _downloadReport(
|
||||
String reportType,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
) {
|
||||
if (startDate == null || endDate == null) {
|
||||
AppFlushbar.showError(context, 'Please select both start and end dates');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -127,30 +128,75 @@ class _DownloadReportPageState extends State<DownloadReportPage>
|
||||
child: Column(
|
||||
children: [
|
||||
// Transaction Report Card
|
||||
_ReportOptionCard(
|
||||
title: 'Transaction Report',
|
||||
subtitle:
|
||||
'Export all transaction data with detailed analytics',
|
||||
icon: Icons.receipt_long_outlined,
|
||||
gradient: const [
|
||||
AppColor.primary,
|
||||
AppColor.primaryLight,
|
||||
],
|
||||
startDate: _transactionStartDate,
|
||||
endDate: _transactionEndDate,
|
||||
isLoading: false,
|
||||
onDateRangeChanged: (start, end) {
|
||||
setState(() {
|
||||
_transactionStartDate = start;
|
||||
_transactionEndDate = end;
|
||||
});
|
||||
BlocBuilder<
|
||||
TransactionReportBloc,
|
||||
TransactionReportState
|
||||
>(
|
||||
builder: (context, state) {
|
||||
return _ReportOptionCard(
|
||||
title: 'Transaction Report',
|
||||
subtitle:
|
||||
'Export all transaction data with detailed analytics',
|
||||
icon: Icons.receipt_long_outlined,
|
||||
gradient: const [
|
||||
AppColor.primary,
|
||||
AppColor.primaryLight,
|
||||
],
|
||||
startDate: _transactionStartDate,
|
||||
endDate: _transactionEndDate,
|
||||
isLoading: state.isFetching,
|
||||
onDateRangeChanged: (start, end) {
|
||||
setState(() {
|
||||
_transactionStartDate = start;
|
||||
_transactionEndDate = end;
|
||||
});
|
||||
if (start != null || end != null) {
|
||||
context.read<TransactionReportBloc>().add(
|
||||
TransactionReportEvent.fetchedTransaction(
|
||||
start!,
|
||||
end!,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onDownload: () async {
|
||||
try {
|
||||
final status = await PermessionHelper()
|
||||
.checkPermission();
|
||||
if (status) {
|
||||
final pdfFile =
|
||||
await TransactionReport.previewPdf(
|
||||
searchDateFormatted:
|
||||
"${_transactionStartDate?.toServerDate} - ${_transactionEndDate?.toServerDate}",
|
||||
outlet: state.outlet,
|
||||
categoryAnalyticData:
|
||||
state.categoryAnalytic,
|
||||
profitLossData:
|
||||
state.profitLossAnalytic,
|
||||
paymentMethodAnalyticData:
|
||||
state.paymentMethodAnalytic,
|
||||
productAnalyticData:
|
||||
state.productAnalytic,
|
||||
);
|
||||
log("pdfFile: $pdfFile");
|
||||
await HelperPdfService.openFile(pdfFile);
|
||||
} else {
|
||||
AppFlushbar.showError(
|
||||
context,
|
||||
'Storage permission is required to save PDF',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log("Error generating PDF: $e");
|
||||
AppFlushbar.showError(
|
||||
context,
|
||||
'Failed to generate PDF: $e',
|
||||
);
|
||||
}
|
||||
},
|
||||
delay: 200,
|
||||
);
|
||||
},
|
||||
onDownload: () => _downloadReport(
|
||||
'Transaction Report',
|
||||
_transactionStartDate,
|
||||
_transactionEndDate,
|
||||
),
|
||||
delay: 200,
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@ -139,7 +139,7 @@ class DownloadReportRoute extends _i26.PageRouteInfo<void> {
|
||||
static _i26.PageInfo page = _i26.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i5.DownloadReportPage();
|
||||
return _i26.WrappedRoute(child: const _i5.DownloadReportPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user