293 lines
10 KiB
Dart
293 lines
10 KiB
Dart
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:equatable/equatable.dart';
|
|
import 'package:injectable/injectable.dart';
|
|
import '../data/datasources/souscription_datasource.dart';
|
|
import '../data/models/formule_model.dart';
|
|
import '../data/models/souscription_status_model.dart';
|
|
import '../../../../core/utils/logger.dart';
|
|
|
|
// ─────────────────────────────────────────────────────────────── Events ──────
|
|
|
|
abstract class OnboardingEvent extends Equatable {
|
|
const OnboardingEvent();
|
|
@override
|
|
List<Object?> get props => [];
|
|
}
|
|
|
|
/// Démarre le workflow (charge les formules + état courant si souscription existante)
|
|
class OnboardingStarted extends OnboardingEvent {
|
|
final String? existingSouscriptionId;
|
|
final String initialState; // NO_SUBSCRIPTION | AWAITING_PAYMENT | PAYMENT_INITIATED | AWAITING_VALIDATION
|
|
const OnboardingStarted({required this.initialState, this.existingSouscriptionId});
|
|
@override
|
|
List<Object?> get props => [initialState, existingSouscriptionId];
|
|
}
|
|
|
|
/// L'utilisateur a sélectionné une formule et une plage
|
|
class OnboardingFormuleSelected extends OnboardingEvent {
|
|
final String codeFormule;
|
|
final String plage;
|
|
const OnboardingFormuleSelected({required this.codeFormule, required this.plage});
|
|
@override
|
|
List<Object?> get props => [codeFormule, plage];
|
|
}
|
|
|
|
/// L'utilisateur a sélectionné la période et le type d'organisation
|
|
class OnboardingPeriodeSelected extends OnboardingEvent {
|
|
final String typePeriode;
|
|
final String typeOrganisation;
|
|
final String organisationId;
|
|
const OnboardingPeriodeSelected({
|
|
required this.typePeriode,
|
|
required this.typeOrganisation,
|
|
required this.organisationId,
|
|
});
|
|
@override
|
|
List<Object?> get props => [typePeriode, typeOrganisation, organisationId];
|
|
}
|
|
|
|
/// Confirme la demande et crée la souscription en base
|
|
class OnboardingDemandeConfirmee extends OnboardingEvent {
|
|
const OnboardingDemandeConfirmee();
|
|
}
|
|
|
|
/// Initie le paiement Wave
|
|
class OnboardingPaiementInitie extends OnboardingEvent {
|
|
const OnboardingPaiementInitie();
|
|
}
|
|
|
|
/// L'utilisateur est revenu dans l'app après le paiement Wave
|
|
class OnboardingRetourDepuisWave extends OnboardingEvent {
|
|
const OnboardingRetourDepuisWave();
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────── States ──────
|
|
|
|
abstract class OnboardingState extends Equatable {
|
|
const OnboardingState();
|
|
@override
|
|
List<Object?> get props => [];
|
|
}
|
|
|
|
class OnboardingInitial extends OnboardingState {}
|
|
class OnboardingLoading extends OnboardingState {}
|
|
|
|
class OnboardingError extends OnboardingState {
|
|
final String message;
|
|
const OnboardingError(this.message);
|
|
@override
|
|
List<Object?> get props => [message];
|
|
}
|
|
|
|
/// Étape 1 : choix formule + plage (affiche la grille des formules)
|
|
class OnboardingStepFormule extends OnboardingState {
|
|
final List<FormuleModel> formules;
|
|
const OnboardingStepFormule(this.formules);
|
|
@override
|
|
List<Object?> get props => [formules];
|
|
}
|
|
|
|
/// Étape 2 : choix période + type organisation (formule/plage déjà sélectionnés)
|
|
class OnboardingStepPeriode extends OnboardingState {
|
|
final String codeFormule;
|
|
final String plage;
|
|
final List<FormuleModel> formules;
|
|
const OnboardingStepPeriode({
|
|
required this.codeFormule,
|
|
required this.plage,
|
|
required this.formules,
|
|
});
|
|
@override
|
|
List<Object?> get props => [codeFormule, plage, formules];
|
|
}
|
|
|
|
/// Étape 3 : récapitulatif avec montant calculé (avant paiement)
|
|
class OnboardingStepSummary extends OnboardingState {
|
|
final SouscriptionStatusModel souscription;
|
|
const OnboardingStepSummary(this.souscription);
|
|
@override
|
|
List<Object?> get props => [souscription];
|
|
}
|
|
|
|
/// Étape 4 : paiement Wave — URL à ouvrir dans le navigateur
|
|
class OnboardingStepPaiement extends OnboardingState {
|
|
final SouscriptionStatusModel souscription;
|
|
final String waveLaunchUrl;
|
|
const OnboardingStepPaiement({required this.souscription, required this.waveLaunchUrl});
|
|
@override
|
|
List<Object?> get props => [souscription, waveLaunchUrl];
|
|
}
|
|
|
|
/// Étape 5 : en attente de validation SuperAdmin
|
|
class OnboardingStepAttente extends OnboardingState {
|
|
final SouscriptionStatusModel? souscription;
|
|
const OnboardingStepAttente({this.souscription});
|
|
@override
|
|
List<Object?> get props => [souscription];
|
|
}
|
|
|
|
/// Souscription rejetée par le SuperAdmin
|
|
class OnboardingRejected extends OnboardingState {
|
|
final String? commentaire;
|
|
const OnboardingRejected({this.commentaire});
|
|
@override
|
|
List<Object?> get props => [commentaire];
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────── BLoC ────────
|
|
|
|
@injectable
|
|
class OnboardingBloc extends Bloc<OnboardingEvent, OnboardingState> {
|
|
final SouscriptionDatasource _datasource;
|
|
|
|
// Données accumulées au fil du wizard
|
|
List<FormuleModel> _formules = [];
|
|
String? _codeFormule;
|
|
String? _plage;
|
|
String? _typePeriode;
|
|
String? _typeOrganisation;
|
|
String? _organisationId;
|
|
SouscriptionStatusModel? _souscription;
|
|
|
|
OnboardingBloc(this._datasource) : super(OnboardingInitial()) {
|
|
on<OnboardingStarted>(_onStarted);
|
|
on<OnboardingFormuleSelected>(_onFormuleSelected);
|
|
on<OnboardingPeriodeSelected>(_onPeriodeSelected);
|
|
on<OnboardingDemandeConfirmee>(_onDemandeConfirmee);
|
|
on<OnboardingPaiementInitie>(_onPaiementInitie);
|
|
on<OnboardingRetourDepuisWave>(_onRetourDepuisWave);
|
|
}
|
|
|
|
Future<void> _onStarted(OnboardingStarted event, Emitter<OnboardingState> emit) async {
|
|
emit(OnboardingLoading());
|
|
try {
|
|
_formules = await _datasource.getFormules();
|
|
|
|
switch (event.initialState) {
|
|
case 'NO_SUBSCRIPTION':
|
|
emit(OnboardingStepFormule(_formules));
|
|
|
|
case 'AWAITING_PAYMENT':
|
|
final sosc = await _datasource.getMaSouscription();
|
|
if (sosc != null) {
|
|
_souscription = sosc;
|
|
emit(OnboardingStepSummary(sosc));
|
|
} else {
|
|
emit(OnboardingStepFormule(_formules));
|
|
}
|
|
|
|
case 'PAYMENT_INITIATED':
|
|
final sosc = await _datasource.getMaSouscription();
|
|
if (sosc != null && sosc.waveLaunchUrl != null) {
|
|
_souscription = sosc;
|
|
emit(OnboardingStepPaiement(
|
|
souscription: sosc,
|
|
waveLaunchUrl: sosc.waveLaunchUrl!,
|
|
));
|
|
} else {
|
|
emit(OnboardingStepAttente(souscription: sosc));
|
|
}
|
|
|
|
case 'AWAITING_VALIDATION':
|
|
final sosc = await _datasource.getMaSouscription();
|
|
_souscription = sosc;
|
|
emit(OnboardingStepAttente(souscription: sosc));
|
|
|
|
case 'REJECTED':
|
|
final sosc = await _datasource.getMaSouscription();
|
|
emit(OnboardingRejected(commentaire: sosc?.statutValidation));
|
|
|
|
default:
|
|
emit(OnboardingStepFormule(_formules));
|
|
}
|
|
} catch (e) {
|
|
AppLogger.error('OnboardingBloc._onStarted: $e');
|
|
emit(const OnboardingError('Impossible de charger les formules. Vérifiez votre connexion.'));
|
|
}
|
|
}
|
|
|
|
void _onFormuleSelected(OnboardingFormuleSelected event, Emitter<OnboardingState> emit) {
|
|
_codeFormule = event.codeFormule;
|
|
_plage = event.plage;
|
|
emit(OnboardingStepPeriode(
|
|
codeFormule: event.codeFormule,
|
|
plage: event.plage,
|
|
formules: _formules,
|
|
));
|
|
}
|
|
|
|
void _onPeriodeSelected(OnboardingPeriodeSelected event, Emitter<OnboardingState> emit) {
|
|
_typePeriode = event.typePeriode;
|
|
_typeOrganisation = event.typeOrganisation;
|
|
_organisationId = event.organisationId;
|
|
}
|
|
|
|
Future<void> _onDemandeConfirmee(
|
|
OnboardingDemandeConfirmee event, Emitter<OnboardingState> emit) async {
|
|
if (_codeFormule == null || _plage == null || _typePeriode == null ||
|
|
_typeOrganisation == null || _organisationId == null) {
|
|
emit(const OnboardingError('Données manquantes. Recommencez depuis le début.'));
|
|
return;
|
|
}
|
|
emit(OnboardingLoading());
|
|
try {
|
|
final sosc = await _datasource.creerDemande(
|
|
typeFormule: _codeFormule!,
|
|
plageMembres: _plage!,
|
|
typePeriode: _typePeriode!,
|
|
typeOrganisation: _typeOrganisation!,
|
|
organisationId: _organisationId!,
|
|
);
|
|
if (sosc != null) {
|
|
_souscription = sosc;
|
|
emit(OnboardingStepSummary(sosc));
|
|
} else {
|
|
emit(const OnboardingError('Erreur lors de la création de la demande.'));
|
|
}
|
|
} catch (e) {
|
|
emit(OnboardingError('Erreur: $e'));
|
|
}
|
|
}
|
|
|
|
Future<void> _onPaiementInitie(
|
|
OnboardingPaiementInitie event, Emitter<OnboardingState> emit) async {
|
|
final souscId = _souscription?.souscriptionId;
|
|
if (souscId == null) {
|
|
emit(const OnboardingError('Souscription introuvable.'));
|
|
return;
|
|
}
|
|
emit(OnboardingLoading());
|
|
try {
|
|
final updated = await _datasource.initierPaiement(souscId);
|
|
if (updated?.waveLaunchUrl != null) {
|
|
_souscription = updated;
|
|
emit(OnboardingStepPaiement(
|
|
souscription: updated!,
|
|
waveLaunchUrl: updated.waveLaunchUrl!,
|
|
));
|
|
} else {
|
|
emit(const OnboardingError('Impossible d\'initier le paiement Wave.'));
|
|
}
|
|
} catch (e) {
|
|
emit(OnboardingError('Erreur paiement: $e'));
|
|
}
|
|
}
|
|
|
|
Future<void> _onRetourDepuisWave(
|
|
OnboardingRetourDepuisWave event, Emitter<OnboardingState> emit) async {
|
|
emit(OnboardingLoading());
|
|
try {
|
|
final souscId = _souscription?.souscriptionId;
|
|
if (souscId != null) {
|
|
await _datasource.confirmerPaiement(souscId);
|
|
}
|
|
final sosc = await _datasource.getMaSouscription();
|
|
_souscription = sosc;
|
|
emit(OnboardingStepAttente(souscription: sosc));
|
|
} catch (e) {
|
|
// En cas d'erreur, on affiche quand même l'écran d'attente
|
|
emit(OnboardingStepAttente(souscription: _souscription));
|
|
}
|
|
}
|
|
}
|