Files
sendico/frontend/pshared/lib/provider/recipient/methods_cache.dart
2026-01-29 19:22:30 +03:00

155 lines
5.8 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/organization/bound.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/methods/type.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/permissions/bound.dart';
import 'package:pshared/models/recipient/payment_method_draft.dart';
import 'package:pshared/models/storable.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pshared/service/recipient/pmethods.dart';
import 'package:pshared/utils/exception.dart';
class RecipientMethodsCacheProvider extends ChangeNotifier {
late OrganizationsProvider _organizations;
final Map<String, List<PaymentMethod>> _methodsByRecipient = {};
final Map<String, bool> _loadingByRecipient = {};
final Map<String, Object?> _errorByRecipient = {};
Set<String> _trackedRecipients = {};
List<PaymentMethod> methodsForRecipient(String recipientId) =>
List<PaymentMethod>.unmodifiable(_methodsByRecipient[recipientId] ?? const []);
bool isLoadingFor(String recipientId) => _loadingByRecipient[recipientId] == true;
Object? errorFor(String recipientId) => _errorByRecipient[recipientId];
bool hasMethodsFor(String recipientId) => _methodsByRecipient.containsKey(recipientId);
bool isTrackingRecipient(String recipientId) => _trackedRecipients.contains(recipientId);
void updateProviders(OrganizationsProvider organizations, RecipientsProvider recipients) {
_organizations = organizations;
if (!_organizations.isOrganizationSet) return;
final nextRecipients = recipients.items.map((r) => r.id).toSet();
if (setEquals(_trackedRecipients, nextRecipients)) return;
final removed = _trackedRecipients.difference(nextRecipients);
for (final recipientId in removed) {
_methodsByRecipient.remove(recipientId);
_loadingByRecipient.remove(recipientId);
_errorByRecipient.remove(recipientId);
}
final added = nextRecipients.difference(_trackedRecipients);
_trackedRecipients = nextRecipients;
for (final recipientId in added) {
unawaited(_loadRecipientMethods(recipientId));
}
}
Future<void> refreshRecipient(String recipientId) => _loadRecipientMethods(recipientId, force: true);
Future<void> syncRecipientMethods({
required String recipientId,
required List<RecipientMethodDraft> methods,
required Map<PaymentType, String> names,
}) async {
await _ensureLoaded(recipientId);
final current = List<PaymentMethod>.from(_methodsByRecipient[recipientId] ?? const []);
final currentById = {for (final method in current) method.id: method};
final desired = methods.where((m) => m.data != null).toList();
final desiredExisting = desired.where((m) => m.existing != null).toList();
final desiredExistingIds = desiredExisting.map((m) => m.existing!.id).toSet();
for (final method in current.toList()) {
if (!desiredExistingIds.contains(method.id)) {
await PaymentMethodService.delete(method);
current.removeWhere((m) => m.id == method.id);
}
}
for (final entry in desiredExisting) {
final existing = entry.existing;
final data = entry.data;
if (existing == null || data == null) continue;
final currentMethod = currentById[existing.id] ?? existing;
final updated = currentMethod.copyWith(data: data);
final updatedList = await PaymentMethodService.update(updated);
final updatedMethod = updatedList.firstWhereOrNull((m) => m.id == updated.id) ?? updated;
final index = current.indexWhere((m) => m.id == updatedMethod.id);
if (index != -1) {
current[index] = updatedMethod;
}
}
for (final entry in desired.where((m) => m.existing == null)) {
final data = entry.data;
if (data == null) continue;
final type = entry.type;
final created = await _createMethod(
recipientId: recipientId,
data: data,
name: names[type] ?? type.name,
);
current.add(created);
}
_methodsByRecipient[recipientId] = _sortedMethods(current);
notifyListeners();
}
Future<void> _ensureLoaded(String recipientId) async {
if (_methodsByRecipient.containsKey(recipientId)) return;
await _loadRecipientMethods(recipientId);
}
Future<void> _loadRecipientMethods(String recipientId, {bool force = false}) async {
if (!force && _loadingByRecipient[recipientId] == true) return;
_setLoading(recipientId, true);
try {
final list = await PaymentMethodService.list(_organizations.current.id, recipientId);
_methodsByRecipient[recipientId] = _sortedMethods(list);
_errorByRecipient.remove(recipientId);
} catch (e) {
_errorByRecipient[recipientId] = toException(e);
} finally {
_setLoading(recipientId, false);
}
}
Future<PaymentMethod> _createMethod({
required String recipientId,
required PaymentMethodData data,
required String name,
}) async {
final organizationRef = _organizations.current.id;
final method = PaymentMethod(
storable: newStorable(),
permissionBound: newPermissionBound(
organizationBound: newOrganizationBound(organizationRef: organizationRef),
),
recipientRef: recipientId,
data: data,
describable: newDescribable(name: name),
);
final created = await PaymentMethodService.create(organizationRef, method);
return created.first;
}
void _setLoading(String recipientId, bool value) {
_loadingByRecipient[recipientId] = value;
notifyListeners();
}
List<PaymentMethod> _sortedMethods(List<PaymentMethod> methods) =>
methods.toList()..sort((a, b) => a.storable.createdAt.compareTo(b.storable.createdAt));
}