feat: legacy mobile apps created
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:krow/core/application/clients/api/api_exception.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/features/invoice/data/models/invoice_decline_model.dart';
|
||||
import 'package:krow/features/invoice/data/models/invoice_model.dart';
|
||||
import 'package:krow/features/invoice/domain/invoices_repository.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
part 'invoice_details_event.dart';
|
||||
part 'invoice_details_state.dart';
|
||||
|
||||
class InvoiceDetailsBloc
|
||||
extends Bloc<InvoiceDetailsEvent, InvoiceDetailsState> {
|
||||
InvoiceDetailsBloc(InvoiceModel invoice)
|
||||
: super(InvoiceDetailsState(invoiceModel: invoice)) {
|
||||
on<InvoiceApproveEvent>(_onInvoiceApproveEvent);
|
||||
on<InvoiceDisputeEvent>(_onInvoiceDisputeEvent);
|
||||
}
|
||||
|
||||
void _onInvoiceApproveEvent(
|
||||
InvoiceApproveEvent event, Emitter<InvoiceDetailsState> emit) async {
|
||||
emit(state.copyWith(inLoading: true));
|
||||
try {
|
||||
await getIt<InvoicesRepository>().approveInvoice(
|
||||
invoiceId: state.invoiceModel?.id ?? '',
|
||||
);
|
||||
emit(state.copyWith(inLoading: false, success: true));
|
||||
return;
|
||||
} catch (e) {
|
||||
if (e is DisplayableException) {
|
||||
emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit(state.copyWith(inLoading: false));
|
||||
}
|
||||
|
||||
void _onInvoiceDisputeEvent(
|
||||
InvoiceDisputeEvent event, Emitter<InvoiceDetailsState> emit) async {
|
||||
emit(state.copyWith(inLoading: true));
|
||||
try {
|
||||
await getIt<InvoicesRepository>().disputeInvoice(
|
||||
invoiceId: state.invoiceModel?.id ?? '',
|
||||
reason: event.reason,
|
||||
comment: event.comment,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
inLoading: false,
|
||||
invoiceModel: state.invoiceModel?.copyWith(
|
||||
status: InvoiceStatus.disputed,
|
||||
dispute: InvoiceDeclineModel(
|
||||
id: '1',
|
||||
reason: event.reason,
|
||||
details: event.comment,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
if (e is DisplayableException) {
|
||||
emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit(state.copyWith(inLoading: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
part of 'invoice_details_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class InvoiceDetailsEvent {}
|
||||
|
||||
class InvoiceApproveEvent extends InvoiceDetailsEvent {
|
||||
InvoiceApproveEvent();
|
||||
}
|
||||
|
||||
class InvoiceDisputeEvent extends InvoiceDetailsEvent {
|
||||
final String reason;
|
||||
final String comment;
|
||||
|
||||
InvoiceDisputeEvent({
|
||||
required this.reason,
|
||||
required this.comment,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
part of 'invoice_details_bloc.dart';
|
||||
|
||||
@immutable
|
||||
class InvoiceDetailsState {
|
||||
final String? showErrorPopup;
|
||||
final bool inLoading;
|
||||
final bool success;
|
||||
final InvoiceModel? invoiceModel;
|
||||
|
||||
const InvoiceDetailsState( {this.showErrorPopup, this.inLoading = false,this.invoiceModel, this.success = false});
|
||||
|
||||
InvoiceDetailsState copyWith({String? showErrorPopup, bool? inLoading, InvoiceModel? invoiceModel, bool? success}) {
|
||||
return InvoiceDetailsState(
|
||||
showErrorPopup: showErrorPopup ?? this.showErrorPopup,
|
||||
inLoading: inLoading ?? false,
|
||||
success: success ?? this.success,
|
||||
invoiceModel: invoiceModel ?? this.invoiceModel,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
|
||||
import '../../invoice_entity.dart';
|
||||
import '../../invoices_repository.dart';
|
||||
import 'invoice_event.dart';
|
||||
import 'invoice_state.dart';
|
||||
|
||||
class InvoiceBloc extends Bloc<InvoiceEvent, InvoicesState> {
|
||||
var indexToStatus = <int, InvoiceStatusFilterType>{
|
||||
0: InvoiceStatusFilterType.all,
|
||||
1: InvoiceStatusFilterType.open,
|
||||
2: InvoiceStatusFilterType.disputed,
|
||||
3: InvoiceStatusFilterType.resolved,
|
||||
4: InvoiceStatusFilterType.paid,
|
||||
5: InvoiceStatusFilterType.overdue,
|
||||
6: InvoiceStatusFilterType.verified,
|
||||
};
|
||||
|
||||
InvoiceBloc()
|
||||
: super(const InvoicesState(tabs: {
|
||||
0: InvoiceTabState(items: [], inLoading: true),
|
||||
1: InvoiceTabState(items: []),
|
||||
2: InvoiceTabState(items: []),
|
||||
3: InvoiceTabState(items: []),
|
||||
4: InvoiceTabState(items: []),
|
||||
5: InvoiceTabState(items: []),
|
||||
6: InvoiceTabState(items: []),
|
||||
})) {
|
||||
on<InvoiceInitialEvent>(_onInitial);
|
||||
on<InvoiceTabChangedEvent>(_onTabChanged);
|
||||
on<LoadTabInvoiceEvent>(_onLoadTabItems);
|
||||
on<LoadMoreInvoiceEvent>(_onLoadMoreTabItems);
|
||||
|
||||
getIt<InvoicesRepository>().statusStream.listen((event) {
|
||||
add(LoadTabInvoiceEvent(status: event.index));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onInitial(InvoiceInitialEvent event, emit) async {
|
||||
add(const LoadTabInvoiceEvent(status: 0));
|
||||
}
|
||||
|
||||
Future<void> _onTabChanged(InvoiceTabChangedEvent event, emit) async {
|
||||
emit(state.copyWith(tabIndex: event.tabIndex));
|
||||
final currentTabState = state.tabs[event.tabIndex]!;
|
||||
if (currentTabState.items.isEmpty && !currentTabState.inLoading) {
|
||||
add(LoadTabInvoiceEvent(status: event.tabIndex));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadTabItems(LoadTabInvoiceEvent event, emit) async {
|
||||
await _fetchInvoices(event.status, null, emit);
|
||||
}
|
||||
|
||||
Future<void> _onLoadMoreTabItems(LoadMoreInvoiceEvent event, emit) async {
|
||||
final currentTabState = state.tabs[event.status]!;
|
||||
if (!currentTabState.hasMoreItems || currentTabState.inLoading) return;
|
||||
await _fetchInvoices(event.status, currentTabState.items, emit);
|
||||
}
|
||||
|
||||
_fetchInvoices(
|
||||
int tabIndex, List<InvoiceListEntity>? previousItems, emit) async {
|
||||
if (previousItems != null && previousItems.lastOrNull?.cursor == null) {
|
||||
return;
|
||||
}
|
||||
final currentTabState = state.tabs[tabIndex]!;
|
||||
|
||||
emit(state.copyWith(
|
||||
tabs: {
|
||||
...state.tabs,
|
||||
tabIndex: currentTabState.copyWith(inLoading: true),
|
||||
},
|
||||
));
|
||||
|
||||
try {
|
||||
var items = await getIt<InvoicesRepository>().getInvoices(
|
||||
statusFilter: indexToStatus[tabIndex]!,
|
||||
lastItemId: previousItems?.lastOrNull?.cursor,
|
||||
);
|
||||
|
||||
// if(items.isNotEmpty){
|
||||
// items = List.generate(20, (i)=>items[0]);
|
||||
// }
|
||||
emit(state.copyWith(
|
||||
tabs: {
|
||||
...state.tabs,
|
||||
tabIndex: currentTabState.copyWith(
|
||||
items: (previousItems ?? [])..addAll(items),
|
||||
hasMoreItems: items.isNotEmpty,
|
||||
inLoading: false,
|
||||
),
|
||||
},
|
||||
));
|
||||
} catch (e, s) {
|
||||
debugPrint(e.toString());
|
||||
debugPrint(s.toString());
|
||||
emit(state.copyWith(
|
||||
tabs: {
|
||||
...state.tabs,
|
||||
tabIndex: currentTabState.copyWith(inLoading: false),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
getIt<InvoicesRepository>().dispose();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
sealed class InvoiceEvent {
|
||||
const InvoiceEvent();
|
||||
}
|
||||
|
||||
class InvoiceInitialEvent extends InvoiceEvent {
|
||||
const InvoiceInitialEvent();
|
||||
}
|
||||
|
||||
class InvoiceTabChangedEvent extends InvoiceEvent {
|
||||
final int tabIndex;
|
||||
|
||||
const InvoiceTabChangedEvent({required this.tabIndex});
|
||||
}
|
||||
|
||||
class LoadTabInvoiceEvent extends InvoiceEvent {
|
||||
final int status;
|
||||
|
||||
const LoadTabInvoiceEvent({required this.status});
|
||||
}
|
||||
|
||||
class LoadMoreInvoiceEvent extends InvoiceEvent {
|
||||
final int status;
|
||||
|
||||
const LoadMoreInvoiceEvent({required this.status});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:krow/features/invoice/domain/invoice_entity.dart';
|
||||
|
||||
class InvoicesState {
|
||||
final bool inLoading;
|
||||
final int tabIndex;
|
||||
|
||||
final Map<int, InvoiceTabState> tabs;
|
||||
|
||||
const InvoicesState(
|
||||
{this.inLoading = false, this.tabIndex = 0, required this.tabs});
|
||||
|
||||
InvoicesState copyWith({
|
||||
bool? inLoading,
|
||||
int? tabIndex,
|
||||
Map<int, InvoiceTabState>? tabs,
|
||||
}) {
|
||||
return InvoicesState(
|
||||
inLoading: inLoading ?? this.inLoading,
|
||||
tabIndex: tabIndex ?? this.tabIndex,
|
||||
tabs: tabs ?? this.tabs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InvoiceTabState {
|
||||
final List<InvoiceListEntity> items;
|
||||
final bool inLoading;
|
||||
final bool hasMoreItems;
|
||||
|
||||
const InvoiceTabState({
|
||||
required this.items,
|
||||
this.inLoading = false,
|
||||
this.hasMoreItems = true,
|
||||
});
|
||||
|
||||
InvoiceTabState copyWith({
|
||||
List<InvoiceListEntity>? items,
|
||||
bool? inLoading,
|
||||
bool? hasMoreItems,
|
||||
}) {
|
||||
return InvoiceTabState(
|
||||
items: items ?? this.items,
|
||||
inLoading: inLoading ?? this.inLoading,
|
||||
hasMoreItems: hasMoreItems ?? this.hasMoreItems,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:krow/features/invoice/data/models/invoice_model.dart';
|
||||
|
||||
class InvoiceListEntity {
|
||||
final String id;
|
||||
final InvoiceStatus status;
|
||||
String? cursor;
|
||||
final String invoiceNumber;
|
||||
final String eventName;
|
||||
final int count;
|
||||
final double value;
|
||||
final DateTime date;
|
||||
final DateTime dueAt;
|
||||
final String? poNumber;
|
||||
|
||||
final InvoiceModel? invoiceModel;
|
||||
|
||||
InvoiceListEntity({
|
||||
required this.id,
|
||||
required this.status,
|
||||
required this.invoiceNumber,
|
||||
required this.eventName,
|
||||
required this.count,
|
||||
required this.value,
|
||||
required this.date,
|
||||
required this.dueAt,
|
||||
this.poNumber,
|
||||
this.cursor,
|
||||
this.invoiceModel,
|
||||
});
|
||||
|
||||
InvoiceListEntity copyWith({
|
||||
String? id,
|
||||
String? cursor,
|
||||
InvoiceStatus? status,
|
||||
String? invoiceNumber,
|
||||
String? eventName,
|
||||
int? count,
|
||||
double? value,
|
||||
DateTime? date,
|
||||
DateTime? dueAt,
|
||||
String? poNumber,
|
||||
}) {
|
||||
return InvoiceListEntity(
|
||||
id: id ?? this.id,
|
||||
cursor: cursor ?? this.cursor,
|
||||
status: status ?? this.status,
|
||||
invoiceNumber: invoiceNumber ?? this.invoiceNumber,
|
||||
eventName: eventName ?? this.eventName,
|
||||
count: count ?? this.count,
|
||||
value: value ?? this.value,
|
||||
date: date ?? this.date,
|
||||
dueAt: date ?? this.dueAt,
|
||||
);
|
||||
}
|
||||
|
||||
InvoiceListEntity.fromModel(InvoiceModel model, {this.cursor})
|
||||
: id = model.id,
|
||||
poNumber = model.event?.purchaseOrder,
|
||||
status = model.status,
|
||||
invoiceNumber = model.event?.id ?? '',
|
||||
eventName = model.event!.name,
|
||||
count = model.items?.length?? 0,
|
||||
value = (model.total ?? 0.0),
|
||||
date = DateTime.parse(model.event?.date ?? ''),
|
||||
dueAt = DateTime.parse(model.dueAt ?? ''),
|
||||
invoiceModel = model;
|
||||
|
||||
InvoiceListEntity.empty()
|
||||
: id = 'INV-20250113-12345',
|
||||
status = InvoiceStatus.open,
|
||||
invoiceNumber = 'INV-20250113-12345',
|
||||
eventName = 'Event Name',
|
||||
count = 16,
|
||||
value = 1230,
|
||||
poNumber = 'PO-123456',
|
||||
date = DateTime.now(),
|
||||
dueAt = DateTime.now(),
|
||||
invoiceModel = null;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:krow/features/invoice/domain/invoice_entity.dart';
|
||||
|
||||
enum InvoiceStatusFilterType {
|
||||
all,
|
||||
open,
|
||||
disputed,
|
||||
resolved,
|
||||
paid,
|
||||
overdue,
|
||||
verified
|
||||
}
|
||||
|
||||
abstract class InvoicesRepository {
|
||||
Stream<dynamic> get statusStream;
|
||||
|
||||
Future<List<InvoiceListEntity>> getInvoices(
|
||||
{String? lastItemId, required InvoiceStatusFilterType statusFilter});
|
||||
|
||||
void dispose();
|
||||
|
||||
Future<void> approveInvoice({required String invoiceId});
|
||||
|
||||
Future<void> disputeInvoice({
|
||||
required String invoiceId,
|
||||
required String reason,
|
||||
required String comment,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user