Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts). Signed-off-by: lions dev Team
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
/// BLoC pour la gestion des approbations de transactions
|
||||
library approval_bloc;
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../domain/usecases/get_pending_approvals.dart';
|
||||
import '../../domain/usecases/approve_transaction.dart';
|
||||
import '../../domain/usecases/reject_transaction.dart';
|
||||
import '../../domain/usecases/get_approval_by_id.dart';
|
||||
import 'approval_event.dart';
|
||||
import 'approval_state.dart';
|
||||
|
||||
@injectable
|
||||
class ApprovalBloc extends Bloc<ApprovalEvent, ApprovalState> {
|
||||
final GetPendingApprovals getPendingApprovals;
|
||||
final GetApprovalById getApprovalById;
|
||||
final ApproveTransaction approveTransaction;
|
||||
final RejectTransaction rejectTransaction;
|
||||
|
||||
ApprovalBloc({
|
||||
required this.getPendingApprovals,
|
||||
required this.getApprovalById,
|
||||
required this.approveTransaction,
|
||||
required this.rejectTransaction,
|
||||
}) : super(const ApprovalInitial()) {
|
||||
on<LoadPendingApprovals>(_onLoadPendingApprovals);
|
||||
on<LoadApprovalById>(_onLoadApprovalById);
|
||||
on<ApproveTransactionEvent>(_onApproveTransaction);
|
||||
on<RejectTransactionEvent>(_onRejectTransaction);
|
||||
on<RefreshApprovals>(_onRefreshApprovals);
|
||||
}
|
||||
|
||||
Future<void> _onLoadPendingApprovals(
|
||||
LoadPendingApprovals event,
|
||||
Emitter<ApprovalState> emit,
|
||||
) async {
|
||||
emit(const ApprovalsLoading());
|
||||
|
||||
final result = await getPendingApprovals(
|
||||
organizationId: event.organizationId,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(ApprovalError(failure.message)),
|
||||
(approvals) {
|
||||
if (approvals.isEmpty) {
|
||||
emit(const ApprovalsEmpty());
|
||||
} else {
|
||||
emit(ApprovalsLoaded(
|
||||
approvals: approvals,
|
||||
pendingCount: approvals.length,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLoadApprovalById(
|
||||
LoadApprovalById event,
|
||||
Emitter<ApprovalState> emit,
|
||||
) async {
|
||||
emit(const ApprovalsLoading());
|
||||
|
||||
final result = await getApprovalById(event.approvalId);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(ApprovalError(failure.message)),
|
||||
(approval) => emit(ApprovalDetailLoaded(approval)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onApproveTransaction(
|
||||
ApproveTransactionEvent event,
|
||||
Emitter<ApprovalState> emit,
|
||||
) async {
|
||||
emit(const ApprovalActionInProgress('approve'));
|
||||
|
||||
final result = await approveTransaction(
|
||||
approvalId: event.approvalId,
|
||||
comment: event.comment,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(ApprovalError(failure.message)),
|
||||
(approval) => emit(TransactionApproved(approval: approval)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onRejectTransaction(
|
||||
RejectTransactionEvent event,
|
||||
Emitter<ApprovalState> emit,
|
||||
) async {
|
||||
emit(const ApprovalActionInProgress('reject'));
|
||||
|
||||
final result = await rejectTransaction(
|
||||
approvalId: event.approvalId,
|
||||
reason: event.reason,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(ApprovalError(failure.message)),
|
||||
(approval) => emit(TransactionRejected(approval: approval)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onRefreshApprovals(
|
||||
RefreshApprovals event,
|
||||
Emitter<ApprovalState> emit,
|
||||
) async {
|
||||
// Keep current state while refreshing
|
||||
final result = await getPendingApprovals(
|
||||
organizationId: event.organizationId,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(ApprovalError(failure.message)),
|
||||
(approvals) {
|
||||
if (approvals.isEmpty) {
|
||||
emit(const ApprovalsEmpty());
|
||||
} else {
|
||||
emit(ApprovalsLoaded(
|
||||
approvals: approvals,
|
||||
pendingCount: approvals.length,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/// Événements pour le BLoC des approbations
|
||||
library approval_event;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class ApprovalEvent extends Equatable {
|
||||
const ApprovalEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// Charger les approbations en attente
|
||||
class LoadPendingApprovals extends ApprovalEvent {
|
||||
final String? organizationId;
|
||||
|
||||
const LoadPendingApprovals({this.organizationId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [organizationId];
|
||||
}
|
||||
|
||||
/// Charger une approbation spécifique
|
||||
class LoadApprovalById extends ApprovalEvent {
|
||||
final String approvalId;
|
||||
|
||||
const LoadApprovalById(this.approvalId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [approvalId];
|
||||
}
|
||||
|
||||
/// Approuver une transaction
|
||||
class ApproveTransactionEvent extends ApprovalEvent {
|
||||
final String approvalId;
|
||||
final String? comment;
|
||||
|
||||
const ApproveTransactionEvent({
|
||||
required this.approvalId,
|
||||
this.comment,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [approvalId, comment];
|
||||
}
|
||||
|
||||
/// Rejeter une transaction
|
||||
class RejectTransactionEvent extends ApprovalEvent {
|
||||
final String approvalId;
|
||||
final String reason;
|
||||
|
||||
const RejectTransactionEvent({
|
||||
required this.approvalId,
|
||||
required this.reason,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [approvalId, reason];
|
||||
}
|
||||
|
||||
/// Rafraîchir les approbations
|
||||
class RefreshApprovals extends ApprovalEvent {
|
||||
final String? organizationId;
|
||||
|
||||
const RefreshApprovals({this.organizationId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [organizationId];
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/// États pour le BLoC des approbations
|
||||
library approval_state;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../domain/entities/transaction_approval.dart';
|
||||
|
||||
abstract class ApprovalState extends Equatable {
|
||||
const ApprovalState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// État initial
|
||||
class ApprovalInitial extends ApprovalState {
|
||||
const ApprovalInitial();
|
||||
}
|
||||
|
||||
/// Chargement en cours
|
||||
class ApprovalsLoading extends ApprovalState {
|
||||
const ApprovalsLoading();
|
||||
}
|
||||
|
||||
/// Approbations chargées
|
||||
class ApprovalsLoaded extends ApprovalState {
|
||||
final List<TransactionApproval> approvals;
|
||||
final int pendingCount;
|
||||
|
||||
const ApprovalsLoaded({
|
||||
required this.approvals,
|
||||
required this.pendingCount,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [approvals, pendingCount];
|
||||
}
|
||||
|
||||
/// Approbation spécifique chargée
|
||||
class ApprovalDetailLoaded extends ApprovalState {
|
||||
final TransactionApproval approval;
|
||||
|
||||
const ApprovalDetailLoaded(this.approval);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [approval];
|
||||
}
|
||||
|
||||
/// Transaction approuvée avec succès
|
||||
class TransactionApproved extends ApprovalState {
|
||||
final TransactionApproval approval;
|
||||
final String message;
|
||||
|
||||
const TransactionApproved({
|
||||
required this.approval,
|
||||
this.message = 'Transaction approuvée avec succès',
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [approval, message];
|
||||
}
|
||||
|
||||
/// Transaction rejetée avec succès
|
||||
class TransactionRejected extends ApprovalState {
|
||||
final TransactionApproval approval;
|
||||
final String message;
|
||||
|
||||
const TransactionRejected({
|
||||
required this.approval,
|
||||
this.message = 'Transaction rejetée avec succès',
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [approval, message];
|
||||
}
|
||||
|
||||
/// Action en cours (approve/reject)
|
||||
class ApprovalActionInProgress extends ApprovalState {
|
||||
final String actionType; // 'approve' or 'reject'
|
||||
|
||||
const ApprovalActionInProgress(this.actionType);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [actionType];
|
||||
}
|
||||
|
||||
/// Erreur
|
||||
class ApprovalError extends ApprovalState {
|
||||
final String message;
|
||||
|
||||
const ApprovalError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
/// Liste vide
|
||||
class ApprovalsEmpty extends ApprovalState {
|
||||
final String message;
|
||||
|
||||
const ApprovalsEmpty({
|
||||
this.message = 'Aucune approbation en attente',
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
187
lib/features/finance_workflow/presentation/bloc/budget_bloc.dart
Normal file
187
lib/features/finance_workflow/presentation/bloc/budget_bloc.dart
Normal file
@@ -0,0 +1,187 @@
|
||||
/// BLoC pour la gestion des budgets
|
||||
library budget_bloc;
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../domain/usecases/get_budgets.dart';
|
||||
import '../../domain/usecases/get_budget_by_id.dart';
|
||||
import '../../domain/usecases/create_budget.dart';
|
||||
import '../../domain/usecases/get_budget_tracking.dart';
|
||||
import 'budget_event.dart';
|
||||
import 'budget_state.dart';
|
||||
|
||||
@injectable
|
||||
class BudgetBloc extends Bloc<BudgetEvent, BudgetState> {
|
||||
final GetBudgets getBudgets;
|
||||
final GetBudgetById getBudgetById;
|
||||
final CreateBudget createBudget;
|
||||
final GetBudgetTracking getBudgetTracking;
|
||||
|
||||
BudgetBloc({
|
||||
required this.getBudgets,
|
||||
required this.getBudgetById,
|
||||
required this.createBudget,
|
||||
required this.getBudgetTracking,
|
||||
}) : super(const BudgetInitial()) {
|
||||
on<LoadBudgets>(_onLoadBudgets);
|
||||
on<LoadBudgetById>(_onLoadBudgetById);
|
||||
on<CreateBudgetEvent>(_onCreateBudget);
|
||||
on<LoadBudgetTracking>(_onLoadBudgetTracking);
|
||||
on<RefreshBudgets>(_onRefreshBudgets);
|
||||
on<FilterBudgets>(_onFilterBudgets);
|
||||
}
|
||||
|
||||
Future<void> _onLoadBudgets(
|
||||
LoadBudgets event,
|
||||
Emitter<BudgetState> emit,
|
||||
) async {
|
||||
emit(const BudgetsLoading());
|
||||
|
||||
final result = await getBudgets(
|
||||
organizationId: event.organizationId,
|
||||
status: event.status,
|
||||
year: event.year,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(BudgetError(failure.message)),
|
||||
(budgets) {
|
||||
if (budgets.isEmpty) {
|
||||
emit(const BudgetsEmpty());
|
||||
} else {
|
||||
emit(BudgetsLoaded(
|
||||
budgets: budgets,
|
||||
filterStatus: event.status,
|
||||
filterYear: event.year,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLoadBudgetById(
|
||||
LoadBudgetById event,
|
||||
Emitter<BudgetState> emit,
|
||||
) async {
|
||||
emit(const BudgetsLoading());
|
||||
|
||||
final result = await getBudgetById(event.budgetId);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(BudgetError(failure.message)),
|
||||
(budget) => emit(BudgetDetailLoaded(budget)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onCreateBudget(
|
||||
CreateBudgetEvent event,
|
||||
Emitter<BudgetState> emit,
|
||||
) async {
|
||||
emit(const BudgetActionInProgress('create'));
|
||||
|
||||
final result = await createBudget(
|
||||
name: event.name,
|
||||
description: event.description,
|
||||
organizationId: event.organizationId,
|
||||
period: event.period,
|
||||
year: event.year,
|
||||
month: event.month,
|
||||
lines: event.lines,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(BudgetError(failure.message)),
|
||||
(budget) => emit(BudgetCreated(budget: budget)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLoadBudgetTracking(
|
||||
LoadBudgetTracking event,
|
||||
Emitter<BudgetState> emit,
|
||||
) async {
|
||||
emit(const BudgetsLoading());
|
||||
|
||||
// Load budget first
|
||||
final budgetResult = await getBudgetById(event.budgetId);
|
||||
|
||||
await budgetResult.fold(
|
||||
(failure) async => emit(BudgetError(failure.message)),
|
||||
(budget) async {
|
||||
// Then load tracking
|
||||
final trackingResult = await getBudgetTracking(budgetId: event.budgetId);
|
||||
|
||||
trackingResult.fold(
|
||||
(failure) => emit(BudgetError(failure.message)),
|
||||
(tracking) => emit(BudgetTrackingLoaded(
|
||||
budget: budget,
|
||||
tracking: tracking,
|
||||
)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onRefreshBudgets(
|
||||
RefreshBudgets event,
|
||||
Emitter<BudgetState> emit,
|
||||
) async {
|
||||
final result = await getBudgets(
|
||||
organizationId: event.organizationId,
|
||||
status: event.status,
|
||||
year: event.year,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(BudgetError(failure.message)),
|
||||
(budgets) {
|
||||
if (budgets.isEmpty) {
|
||||
emit(const BudgetsEmpty());
|
||||
} else {
|
||||
emit(BudgetsLoaded(
|
||||
budgets: budgets,
|
||||
filterStatus: event.status,
|
||||
filterYear: event.year,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onFilterBudgets(
|
||||
FilterBudgets event,
|
||||
Emitter<BudgetState> emit,
|
||||
) async {
|
||||
// Keep current organization if in loaded state
|
||||
String? organizationId;
|
||||
if (state is BudgetsLoaded) {
|
||||
final currentState = state as BudgetsLoaded;
|
||||
// Extract org ID from first budget if available
|
||||
if (currentState.budgets.isNotEmpty) {
|
||||
organizationId = currentState.budgets.first.organizationId;
|
||||
}
|
||||
}
|
||||
|
||||
emit(const BudgetsLoading());
|
||||
|
||||
final result = await getBudgets(
|
||||
organizationId: organizationId,
|
||||
status: event.status,
|
||||
year: event.year,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(BudgetError(failure.message)),
|
||||
(budgets) {
|
||||
if (budgets.isEmpty) {
|
||||
emit(const BudgetsEmpty());
|
||||
} else {
|
||||
emit(BudgetsLoaded(
|
||||
budgets: budgets,
|
||||
filterStatus: event.status,
|
||||
filterYear: event.year,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/// Événements pour le BLoC des budgets
|
||||
library budget_event;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../domain/entities/budget.dart';
|
||||
|
||||
abstract class BudgetEvent extends Equatable {
|
||||
const BudgetEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// Charger les budgets
|
||||
class LoadBudgets extends BudgetEvent {
|
||||
final String? organizationId;
|
||||
final BudgetStatus? status;
|
||||
final int? year;
|
||||
|
||||
const LoadBudgets({
|
||||
this.organizationId,
|
||||
this.status,
|
||||
this.year,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [organizationId, status, year];
|
||||
}
|
||||
|
||||
/// Charger un budget spécifique
|
||||
class LoadBudgetById extends BudgetEvent {
|
||||
final String budgetId;
|
||||
|
||||
const LoadBudgetById(this.budgetId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [budgetId];
|
||||
}
|
||||
|
||||
/// Créer un budget
|
||||
class CreateBudgetEvent extends BudgetEvent {
|
||||
final String name;
|
||||
final String? description;
|
||||
final String organizationId;
|
||||
final BudgetPeriod period;
|
||||
final int year;
|
||||
final int? month;
|
||||
final List<BudgetLine> lines;
|
||||
|
||||
const CreateBudgetEvent({
|
||||
required this.name,
|
||||
this.description,
|
||||
required this.organizationId,
|
||||
required this.period,
|
||||
required this.year,
|
||||
this.month,
|
||||
required this.lines,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
name,
|
||||
description,
|
||||
organizationId,
|
||||
period,
|
||||
year,
|
||||
month,
|
||||
lines,
|
||||
];
|
||||
}
|
||||
|
||||
/// Charger le suivi budgétaire
|
||||
class LoadBudgetTracking extends BudgetEvent {
|
||||
final String budgetId;
|
||||
|
||||
const LoadBudgetTracking(this.budgetId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [budgetId];
|
||||
}
|
||||
|
||||
/// Rafraîchir les budgets
|
||||
class RefreshBudgets extends BudgetEvent {
|
||||
final String? organizationId;
|
||||
final BudgetStatus? status;
|
||||
final int? year;
|
||||
|
||||
const RefreshBudgets({
|
||||
this.organizationId,
|
||||
this.status,
|
||||
this.year,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [organizationId, status, year];
|
||||
}
|
||||
|
||||
/// Filtrer les budgets
|
||||
class FilterBudgets extends BudgetEvent {
|
||||
final BudgetStatus? status;
|
||||
final int? year;
|
||||
|
||||
const FilterBudgets({
|
||||
this.status,
|
||||
this.year,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, year];
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/// États pour le BLoC des budgets
|
||||
library budget_state;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../domain/entities/budget.dart';
|
||||
|
||||
abstract class BudgetState extends Equatable {
|
||||
const BudgetState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// État initial
|
||||
class BudgetInitial extends BudgetState {
|
||||
const BudgetInitial();
|
||||
}
|
||||
|
||||
/// Chargement en cours
|
||||
class BudgetsLoading extends BudgetState {
|
||||
const BudgetsLoading();
|
||||
}
|
||||
|
||||
/// Budgets chargés
|
||||
class BudgetsLoaded extends BudgetState {
|
||||
final List<Budget> budgets;
|
||||
final BudgetStatus? filterStatus;
|
||||
final int? filterYear;
|
||||
|
||||
const BudgetsLoaded({
|
||||
required this.budgets,
|
||||
this.filterStatus,
|
||||
this.filterYear,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [budgets, filterStatus, filterYear];
|
||||
}
|
||||
|
||||
/// Budget spécifique chargé
|
||||
class BudgetDetailLoaded extends BudgetState {
|
||||
final Budget budget;
|
||||
|
||||
const BudgetDetailLoaded(this.budget);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [budget];
|
||||
}
|
||||
|
||||
/// Suivi budgétaire chargé
|
||||
class BudgetTrackingLoaded extends BudgetState {
|
||||
final Budget budget;
|
||||
final Map<String, dynamic> tracking;
|
||||
|
||||
const BudgetTrackingLoaded({
|
||||
required this.budget,
|
||||
required this.tracking,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [budget, tracking];
|
||||
}
|
||||
|
||||
/// Budget créé avec succès
|
||||
class BudgetCreated extends BudgetState {
|
||||
final Budget budget;
|
||||
final String message;
|
||||
|
||||
const BudgetCreated({
|
||||
required this.budget,
|
||||
this.message = 'Budget créé avec succès',
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [budget, message];
|
||||
}
|
||||
|
||||
/// Action en cours (create, update)
|
||||
class BudgetActionInProgress extends BudgetState {
|
||||
final String actionType;
|
||||
|
||||
const BudgetActionInProgress(this.actionType);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [actionType];
|
||||
}
|
||||
|
||||
/// Erreur
|
||||
class BudgetError extends BudgetState {
|
||||
final String message;
|
||||
|
||||
const BudgetError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
/// Liste vide
|
||||
class BudgetsEmpty extends BudgetState {
|
||||
final String message;
|
||||
|
||||
const BudgetsEmpty({
|
||||
this.message = 'Aucun budget trouvé',
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
Reference in New Issue
Block a user