272 lines
6.8 KiB
Dart
272 lines
6.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:pshared/models/payment/payment.dart';
|
|
import 'package:pshared/provider/organizations.dart';
|
|
import 'package:pshared/provider/resource.dart';
|
|
import 'package:pshared/service/payment/service.dart';
|
|
import 'package:pshared/utils/exception.dart';
|
|
|
|
class PaymentsProvider with ChangeNotifier {
|
|
OrganizationsProvider? _organizations;
|
|
String? _loadedOrganizationRef;
|
|
|
|
Resource<List<Payment>> _resource = Resource(data: []);
|
|
bool _isLoaded = false;
|
|
bool _isLoadingMore = false;
|
|
String? _nextCursor;
|
|
int? _limit;
|
|
String? _quotationRef;
|
|
DateTime? _createdFrom;
|
|
DateTime? _createdTo;
|
|
List<String>? _states;
|
|
|
|
int _opSeq = 0;
|
|
|
|
Resource<List<Payment>> get resource => _resource;
|
|
List<Payment> get payments => _resource.data ?? [];
|
|
bool get isLoading => _resource.isLoading;
|
|
Exception? get error => _resource.error;
|
|
bool get isReady =>
|
|
_isLoaded && !_resource.isLoading && _resource.error == null;
|
|
|
|
bool get isLoadingMore => _isLoadingMore;
|
|
String? get nextCursor => _nextCursor;
|
|
bool get canLoadMore => _nextCursor != null && _nextCursor!.isNotEmpty;
|
|
|
|
void update(OrganizationsProvider organizations) {
|
|
_organizations = organizations;
|
|
if (!organizations.isOrganizationSet) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
final orgRef = organizations.current.id;
|
|
if (_loadedOrganizationRef != orgRef) {
|
|
_loadedOrganizationRef = orgRef;
|
|
unawaited(refresh());
|
|
}
|
|
}
|
|
|
|
Future<void> refresh({
|
|
int? limit,
|
|
String? quotationRef,
|
|
DateTime? createdFrom,
|
|
DateTime? createdTo,
|
|
List<String>? states,
|
|
}) async {
|
|
await _refresh(
|
|
limit: limit,
|
|
quotationRef: quotationRef,
|
|
createdFrom: createdFrom,
|
|
createdTo: createdTo,
|
|
states: states,
|
|
showLoading: true,
|
|
updateError: true,
|
|
);
|
|
}
|
|
|
|
Future<void> refreshSilently({
|
|
int? limit,
|
|
String? quotationRef,
|
|
DateTime? createdFrom,
|
|
DateTime? createdTo,
|
|
List<String>? states,
|
|
}) async {
|
|
await _refresh(
|
|
limit: limit,
|
|
quotationRef: quotationRef,
|
|
createdFrom: createdFrom,
|
|
createdTo: createdTo,
|
|
states: states,
|
|
showLoading: false,
|
|
updateError: false,
|
|
);
|
|
}
|
|
|
|
Future<void> refreshCurrentQuerySilently() async {
|
|
await _refresh(
|
|
limit: _limit,
|
|
quotationRef: _quotationRef,
|
|
createdFrom: _createdFrom,
|
|
createdTo: _createdTo,
|
|
states: _states,
|
|
showLoading: false,
|
|
updateError: false,
|
|
);
|
|
}
|
|
|
|
void mergePayments(List<Payment> incoming) {
|
|
if (incoming.isEmpty) return;
|
|
final existing = List<Payment>.from(_resource.data ?? const []);
|
|
final combined = <Payment>[...incoming, ...existing];
|
|
final seen = <String>{};
|
|
final merged = <Payment>[];
|
|
|
|
for (final payment in combined) {
|
|
final key = _paymentKey(payment);
|
|
if (key == null || key.isEmpty) {
|
|
merged.add(payment);
|
|
continue;
|
|
}
|
|
if (seen.contains(key)) continue;
|
|
seen.add(key);
|
|
merged.add(payment);
|
|
}
|
|
|
|
_applyResource(_resource.copyWith(data: merged), notify: true);
|
|
}
|
|
|
|
Future<void> _refresh({
|
|
int? limit,
|
|
String? quotationRef,
|
|
DateTime? createdFrom,
|
|
DateTime? createdTo,
|
|
List<String>? states,
|
|
required bool showLoading,
|
|
required bool updateError,
|
|
}) async {
|
|
final org = _organizations;
|
|
if (org == null || !org.isOrganizationSet) return;
|
|
|
|
_limit = limit;
|
|
_quotationRef = _normalize(quotationRef);
|
|
_createdFrom = createdFrom?.toUtc();
|
|
_createdTo = createdTo?.toUtc();
|
|
_states = _normalizeStates(states);
|
|
_nextCursor = null;
|
|
_isLoadingMore = false;
|
|
|
|
final seq = ++_opSeq;
|
|
|
|
if (showLoading) {
|
|
_applyResource(
|
|
_resource.copyWith(isLoading: true, error: null),
|
|
notify: true,
|
|
);
|
|
}
|
|
|
|
try {
|
|
final page = await PaymentService.listPage(
|
|
org.current.id,
|
|
limit: _limit,
|
|
cursor: null,
|
|
quotationRef: _quotationRef,
|
|
createdFrom: _createdFrom,
|
|
createdTo: _createdTo,
|
|
states: _states,
|
|
);
|
|
|
|
if (seq != _opSeq) return;
|
|
|
|
_isLoaded = true;
|
|
_nextCursor = _normalize(page.nextCursor);
|
|
_applyResource(
|
|
Resource(data: page.items, isLoading: false, error: null),
|
|
notify: true,
|
|
);
|
|
} catch (e) {
|
|
if (seq != _opSeq) return;
|
|
if (updateError) {
|
|
_applyResource(
|
|
_resource.copyWith(isLoading: false, error: toException(e)),
|
|
notify: true,
|
|
);
|
|
} else if (showLoading) {
|
|
_applyResource(_resource.copyWith(isLoading: false), notify: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> loadMore() async {
|
|
final org = _organizations;
|
|
if (org == null || !org.isOrganizationSet) return;
|
|
if (_isLoadingMore || _resource.isLoading) return;
|
|
|
|
final cursor = _normalize(_nextCursor);
|
|
if (cursor == null) return;
|
|
|
|
final seq = _opSeq;
|
|
|
|
_isLoadingMore = true;
|
|
_applyResource(_resource.copyWith(error: null), notify: false);
|
|
notifyListeners();
|
|
|
|
try {
|
|
final page = await PaymentService.listPage(
|
|
org.current.id,
|
|
limit: _limit,
|
|
cursor: cursor,
|
|
quotationRef: _quotationRef,
|
|
createdFrom: _createdFrom,
|
|
createdTo: _createdTo,
|
|
states: _states,
|
|
);
|
|
|
|
if (seq != _opSeq) return;
|
|
|
|
final combined = List<Payment>.from(payments)..addAll(page.items);
|
|
_nextCursor = _normalize(page.nextCursor);
|
|
|
|
_applyResource(
|
|
_resource.copyWith(data: combined, error: null),
|
|
notify: false,
|
|
);
|
|
} catch (e) {
|
|
if (seq != _opSeq) return;
|
|
|
|
_applyResource(_resource.copyWith(error: toException(e)), notify: false);
|
|
} finally {
|
|
if (seq == _opSeq) {
|
|
_isLoadingMore = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
}
|
|
|
|
void reset() {
|
|
_opSeq++;
|
|
_isLoaded = false;
|
|
_isLoadingMore = false;
|
|
_nextCursor = null;
|
|
_limit = null;
|
|
_quotationRef = null;
|
|
_createdFrom = null;
|
|
_createdTo = null;
|
|
_states = null;
|
|
_resource = Resource(data: []);
|
|
notifyListeners();
|
|
}
|
|
|
|
void _applyResource(
|
|
Resource<List<Payment>> newResource, {
|
|
required bool notify,
|
|
}) {
|
|
_resource = newResource;
|
|
if (notify) notifyListeners();
|
|
}
|
|
|
|
String? _normalize(String? value) {
|
|
final trimmed = value?.trim();
|
|
if (trimmed == null || trimmed.isEmpty) return null;
|
|
return trimmed;
|
|
}
|
|
|
|
String? _paymentKey(Payment payment) {
|
|
final ref = _normalize(payment.paymentRef);
|
|
if (ref != null) return ref;
|
|
return _normalize(payment.idempotencyKey);
|
|
}
|
|
|
|
List<String>? _normalizeStates(List<String>? states) {
|
|
if (states == null || states.isEmpty) return null;
|
|
final normalized = states
|
|
.map((state) => state.trim())
|
|
.where((state) => state.isNotEmpty)
|
|
.toList();
|
|
if (normalized.isEmpty) return null;
|
|
return normalized;
|
|
}
|
|
}
|