756 lines
22 KiB
Dart
756 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:qpay/http/http.dart';
|
|
import 'package:qpay/models/api_response.dart';
|
|
import 'package:qpay/screens/groups/models/group_batch_model.dart';
|
|
import 'package:qpay/models/pageable_model.dart';
|
|
import 'package:qpay/screens/home/home_controller.dart';
|
|
import 'package:qpay/screens/pay/pay_controller.dart';
|
|
import 'package:qpay/screens/transactions/transaction_model.dart';
|
|
|
|
import '../models/batch_item_update_model.dart';
|
|
|
|
class BatchScreenModel {
|
|
List<GroupBatch> batches = [];
|
|
List<GroupBatchItem> batchItems = [];
|
|
GroupBatch? currentBatch;
|
|
List<BillProvider> providers = [];
|
|
List<PaymentProcessor> processors = [];
|
|
BillProvider? selectedProvider;
|
|
PaymentProcessor? selectedProcessor;
|
|
bool isLoading = false;
|
|
bool isLoadingMore = false;
|
|
bool isActing = false;
|
|
String? errorMessage;
|
|
int currentPage = 0;
|
|
int totalPages = 0;
|
|
int totalElements = 0;
|
|
int pageSize = 10;
|
|
String searchQuery = '';
|
|
String? statusFilter;
|
|
String? currencyFilter;
|
|
bool selectMode = false;
|
|
Set<String> selectedBatchIds = {};
|
|
|
|
// Batch items pagination state
|
|
int batchItemsCurrentPage = 0;
|
|
int batchItemsTotalPages = 0;
|
|
int batchItemsTotalElements = 0;
|
|
int batchItemsPageSize = 10;
|
|
String batchItemsSearchQuery = '';
|
|
String? batchItemsStatusFilter;
|
|
String? batchItemsRecipientAccount;
|
|
bool batchItemsIsLoadingMore = false;
|
|
|
|
// Provider products cache: providerId -> list of products
|
|
Map<String, List<BillProduct>> providerProducts = {};
|
|
Set<String> loadingProviderProducts = {};
|
|
}
|
|
|
|
class BatchController extends ChangeNotifier {
|
|
final BatchScreenModel model = BatchScreenModel();
|
|
final Http http = Http();
|
|
BuildContext context;
|
|
bool _disposed = false;
|
|
|
|
BatchController(this.context);
|
|
|
|
@override
|
|
void dispose() {
|
|
_disposed = true;
|
|
super.dispose();
|
|
}
|
|
|
|
dynamic _unwrap(dynamic response) {
|
|
if (response is Map && response.containsKey('body')) {
|
|
return response['body'];
|
|
}
|
|
return response;
|
|
}
|
|
|
|
String buildQueryParameters(Map<String, String> params) {
|
|
if (params.isEmpty) {
|
|
return '';
|
|
}
|
|
|
|
String query = '';
|
|
params.forEach((key, value) {
|
|
query += '$key=$value&';
|
|
});
|
|
return query.substring(0, query.length - 1);
|
|
}
|
|
|
|
Future<ApiResponse<List<BillProvider>>> loadProviders(String currency) async {
|
|
try {
|
|
final raw = await http.get(
|
|
'/public/providers?currency=$currency&sort=priority,asc',
|
|
);
|
|
final response = _unwrap(raw);
|
|
final List<dynamic> content;
|
|
if (response is Map && response.containsKey('content')) {
|
|
content = response['content'] as List<dynamic>;
|
|
} else if (response is List) {
|
|
content = response;
|
|
} else {
|
|
content = [];
|
|
}
|
|
model.providers = content
|
|
.map((e) => BillProvider.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
if (model.providers.isNotEmpty) {
|
|
model.selectedProvider = model.providers.first;
|
|
}
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(model.providers);
|
|
} catch (e) {
|
|
logger.e(e);
|
|
return ApiResponse.failure('Failed to load providers');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<List<PaymentProcessor>>> loadProcessors(currency) async {
|
|
try {
|
|
final raw = await http.get(
|
|
'/public/payment-processors?currency=$currency',
|
|
);
|
|
final response = _unwrap(raw);
|
|
final List<dynamic> content = response is List
|
|
? response
|
|
: (response['content'] ?? []);
|
|
model.processors = content
|
|
.map((e) => PaymentProcessor.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
if (model.processors.isNotEmpty) {
|
|
model.selectedProcessor = model.processors.first;
|
|
}
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(model.processors);
|
|
} catch (e) {
|
|
logger.e(e);
|
|
return ApiResponse.failure('Failed to load processors');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<List<BillProduct>>> loadProviderProducts(
|
|
String providerId,
|
|
) async {
|
|
if (model.loadingProviderProducts.contains(providerId)) {
|
|
// Already loading, return cached data if available
|
|
final cached = model.providerProducts[providerId];
|
|
if (cached != null) {
|
|
return ApiResponse.success(cached);
|
|
}
|
|
return ApiResponse.failure('Loading in progress');
|
|
}
|
|
|
|
model.loadingProviderProducts.add(providerId);
|
|
if (!_disposed) notifyListeners();
|
|
|
|
try {
|
|
final raw = await http.get('/public/providers/$providerId/products');
|
|
final response = _unwrap(raw);
|
|
final List<dynamic> list = response is List
|
|
? response
|
|
: (response['content'] ?? []);
|
|
final products = list
|
|
.map((e) => BillProduct.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
model.providerProducts[providerId] = products;
|
|
model.loadingProviderProducts.remove(providerId);
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(products);
|
|
} catch (e) {
|
|
logger.e(e);
|
|
model.loadingProviderProducts.remove(providerId);
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to load products');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<List<GroupBatch>>> listBatches(
|
|
String groupId, {
|
|
int page = 0,
|
|
int size = 10,
|
|
String? search,
|
|
String? status,
|
|
String? currency,
|
|
}) async {
|
|
if (page == 0) {
|
|
model.isLoading = true;
|
|
model.batches = [];
|
|
model.currentPage = 0;
|
|
} else {
|
|
model.isLoadingMore = true;
|
|
}
|
|
if (!_disposed) notifyListeners();
|
|
|
|
try {
|
|
final params = <String, String>{
|
|
'recipientGroupId': groupId,
|
|
'page': page.toString(),
|
|
'size': size.toString(),
|
|
'sort': 'createdAt,desc',
|
|
};
|
|
if (search != null && search.isNotEmpty) {
|
|
params['description'] = search;
|
|
}
|
|
if (status != null && status.isNotEmpty) {
|
|
params['status'] = status;
|
|
}
|
|
if (currency != null && currency.isNotEmpty) {
|
|
params['currency'] = currency;
|
|
}
|
|
|
|
final raw = await http.get(
|
|
'/group-batches?${buildQueryParameters(params)}',
|
|
);
|
|
final response = _unwrap(raw);
|
|
final pageableModel = PageableModel.fromJson(
|
|
response as Map<String, dynamic>,
|
|
);
|
|
|
|
final newBatches = pageableModel.content
|
|
.map((e) => GroupBatch.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
|
|
if (page == 0) {
|
|
model.batches = newBatches;
|
|
} else {
|
|
model.batches.addAll(newBatches);
|
|
}
|
|
model.currentPage = pageableModel.number;
|
|
model.totalPages = pageableModel.totalPages;
|
|
model.totalElements = pageableModel.totalElements;
|
|
model.isLoading = false;
|
|
model.isLoadingMore = false;
|
|
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(model.batches);
|
|
} catch (e) {
|
|
logger.e(e);
|
|
model.errorMessage = e.toString();
|
|
if (page == 0) {
|
|
model.batches = [];
|
|
}
|
|
model.isLoading = false;
|
|
model.isLoadingMore = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to load batches');
|
|
}
|
|
}
|
|
|
|
Future<void> searchBatches(
|
|
String groupId, {
|
|
String? search,
|
|
String? status,
|
|
String? currency,
|
|
}) async {
|
|
model.searchQuery = search ?? '';
|
|
model.statusFilter = status;
|
|
model.currencyFilter = currency;
|
|
await listBatches(
|
|
groupId,
|
|
page: 0,
|
|
size: model.pageSize,
|
|
search: search,
|
|
status: status,
|
|
currency: currency,
|
|
);
|
|
}
|
|
|
|
Future<void> loadNextPage(String groupId) async {
|
|
if (model.isLoadingMore || model.currentPage + 1 >= model.totalPages) {
|
|
return;
|
|
}
|
|
await listBatches(
|
|
groupId,
|
|
page: model.currentPage + 1,
|
|
size: model.pageSize,
|
|
search: model.searchQuery.isNotEmpty ? model.searchQuery : null,
|
|
status: model.statusFilter,
|
|
currency: model.currencyFilter,
|
|
);
|
|
}
|
|
|
|
/// Navigates to a specific page of batches (0-based index).
|
|
/// Clears the current list and loads the requested page.
|
|
Future<void> goToPage(String groupId, int page) async {
|
|
if (model.isLoadingMore) return;
|
|
if (page < 0 || page >= model.totalPages) return;
|
|
if (page == model.currentPage) return;
|
|
|
|
// Clear existing batches so listBatches replaces instead of appending
|
|
model.batches = [];
|
|
model.currentPage = 0;
|
|
if (!_disposed) notifyListeners();
|
|
|
|
await listBatches(
|
|
groupId,
|
|
page: page,
|
|
size: model.pageSize,
|
|
search: model.searchQuery.isNotEmpty ? model.searchQuery : null,
|
|
status: model.statusFilter,
|
|
currency: model.currencyFilter,
|
|
);
|
|
}
|
|
|
|
/// Changes the page size for batches and reloads from page 0.
|
|
Future<void> setPageSize(String groupId, int size) async {
|
|
if (size == model.pageSize) return;
|
|
model.pageSize = size;
|
|
if (!_disposed) notifyListeners();
|
|
// Reload from page 0 with the new size
|
|
await searchBatches(
|
|
groupId,
|
|
search: model.searchQuery.isNotEmpty ? model.searchQuery : null,
|
|
status: model.statusFilter,
|
|
currency: model.currencyFilter,
|
|
);
|
|
}
|
|
|
|
Future<ApiResponse<List<GroupBatchItem>>> listBatchItems(
|
|
String batchId, {
|
|
int page = 0,
|
|
int size = 10,
|
|
Map<String, String>? filters,
|
|
}) async {
|
|
if (page == 0) {
|
|
model.isLoading = true;
|
|
model.batchItems = [];
|
|
model.batchItemsCurrentPage = 0;
|
|
} else {
|
|
model.batchItemsIsLoadingMore = true;
|
|
}
|
|
if (!_disposed) notifyListeners();
|
|
|
|
try {
|
|
final params = <String, String>{
|
|
'groupBatchId': batchId,
|
|
'page': page.toString(),
|
|
'size': size.toString(),
|
|
'sort': 'createdAt,desc',
|
|
};
|
|
if (filters != null) {
|
|
params.addAll(filters);
|
|
}
|
|
|
|
final raw = await http.get(
|
|
'/group-batches/items?${buildQueryParameters(params)}',
|
|
);
|
|
final response = _unwrap(raw);
|
|
final pageableModel = PageableModel.fromJson(
|
|
response as Map<String, dynamic>,
|
|
);
|
|
|
|
final newItems = pageableModel.content
|
|
.map((e) => GroupBatchItem.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
|
|
if (page == 0) {
|
|
model.batchItems = newItems;
|
|
} else {
|
|
model.batchItems.addAll(newItems);
|
|
}
|
|
model.batchItemsCurrentPage = pageableModel.number;
|
|
model.batchItemsTotalPages = pageableModel.totalPages;
|
|
model.batchItemsTotalElements = pageableModel.totalElements;
|
|
model.isLoading = false;
|
|
model.batchItemsIsLoadingMore = false;
|
|
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(model.batchItems);
|
|
} catch (e) {
|
|
logger.e(e);
|
|
model.errorMessage = e.toString();
|
|
if (page == 0) {
|
|
model.batchItems = [];
|
|
}
|
|
model.isLoading = false;
|
|
model.batchItemsIsLoadingMore = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to load batch items');
|
|
}
|
|
}
|
|
|
|
Future<void> searchBatchItems(
|
|
String batchId, {
|
|
String? search,
|
|
String? status,
|
|
String? account,
|
|
}) async {
|
|
model.batchItemsSearchQuery = search ?? '';
|
|
model.batchItemsStatusFilter = status;
|
|
model.batchItemsRecipientAccount = account;
|
|
await listBatchItems(
|
|
batchId,
|
|
page: 0,
|
|
size: model.batchItemsPageSize,
|
|
filters: _buildBatchItemFilters(
|
|
search: search,
|
|
status: status,
|
|
account: account,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> loadNextBatchItemsPage(String batchId) async {
|
|
if (model.batchItemsIsLoadingMore ||
|
|
model.batchItemsCurrentPage + 1 >= model.batchItemsTotalPages) {
|
|
return;
|
|
}
|
|
await listBatchItems(
|
|
batchId,
|
|
page: model.batchItemsCurrentPage + 1,
|
|
size: model.batchItemsPageSize,
|
|
filters: _buildBatchItemFilters(
|
|
search: model.batchItemsSearchQuery.isNotEmpty
|
|
? model.batchItemsSearchQuery
|
|
: null,
|
|
status: model.batchItemsStatusFilter,
|
|
account: model.batchItemsRecipientAccount,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Navigates to a specific page of batch items (0-based index).
|
|
/// Clears the current list and loads the requested page.
|
|
Future<void> goToBatchItemsPage(String batchId, int page) async {
|
|
if (model.batchItemsIsLoadingMore) return;
|
|
if (page < 0 || page >= model.batchItemsTotalPages) return;
|
|
if (page == model.batchItemsCurrentPage) return;
|
|
|
|
// Clear existing items so listBatchItems replaces instead of appending
|
|
model.batchItems = [];
|
|
model.batchItemsCurrentPage = 0;
|
|
if (!_disposed) notifyListeners();
|
|
|
|
await listBatchItems(
|
|
batchId,
|
|
page: page,
|
|
size: model.batchItemsPageSize,
|
|
filters: _buildBatchItemFilters(
|
|
search: model.batchItemsSearchQuery.isNotEmpty
|
|
? model.batchItemsSearchQuery
|
|
: null,
|
|
status: model.batchItemsStatusFilter,
|
|
account: model.batchItemsRecipientAccount,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Changes the page size for batch items and reloads from page 0.
|
|
void setBatchItemsPageSize(int size) {
|
|
if (size == model.batchItemsPageSize) return;
|
|
model.batchItemsPageSize = size;
|
|
if (!_disposed) notifyListeners();
|
|
}
|
|
|
|
/// Builds the filters map for batch items queries from individual params.
|
|
Map<String, String>? _buildBatchItemFilters({
|
|
String? search,
|
|
String? status,
|
|
String? account,
|
|
}) {
|
|
final filters = <String, String>{};
|
|
if (search != null && search.isNotEmpty) {
|
|
filters['recipientName'] = search;
|
|
}
|
|
if (status != null && status.isNotEmpty) {
|
|
filters['status'] = status;
|
|
}
|
|
if (account != null && account.isNotEmpty) {
|
|
filters['recipientAccount'] = account;
|
|
}
|
|
return filters.isEmpty ? null : filters;
|
|
}
|
|
|
|
Future<ApiResponse<GroupBatch>> getBatch(
|
|
String batchId,
|
|
String workspaceId,
|
|
) async {
|
|
model.isActing = true;
|
|
if (!_disposed) notifyListeners();
|
|
try {
|
|
final raw = await http.get(
|
|
'/group-batches/$batchId?workspaceId=$workspaceId',
|
|
);
|
|
final response = _unwrap(raw);
|
|
final batch = GroupBatch.fromJson(response as Map<String, dynamic>);
|
|
model.currentBatch = batch;
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(batch);
|
|
} catch (e) {
|
|
logger.e(e);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to load batch');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<GroupBatch>> createBatch(CreateBatchRequest req) async {
|
|
model.isActing = true;
|
|
if (!_disposed) notifyListeners();
|
|
try {
|
|
final raw = await http.post('/group-batches', req.toJson());
|
|
final response = _unwrap(raw);
|
|
final batch = GroupBatch.fromJson(response as Map<String, dynamic>);
|
|
model.currentBatch = batch;
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(batch);
|
|
} catch (e, s) {
|
|
logger.e(e);
|
|
logger.e(s);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to create batch');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<GroupBatch>> updateBatch(
|
|
GroupBatchUpdateRequest request,
|
|
) async {
|
|
model.isActing = true;
|
|
if (!_disposed) notifyListeners();
|
|
try {
|
|
final raw = await http.put(
|
|
'/group-batches/${request.id}',
|
|
request.toJson(),
|
|
);
|
|
final response = _unwrap(raw);
|
|
final batch = GroupBatch.fromJson(response as Map<String, dynamic>);
|
|
// Update the batch in the local list if present
|
|
final index = model.batches.indexWhere((b) => b.id == batch.id);
|
|
if (index != -1) {
|
|
model.batches[index] = batch;
|
|
}
|
|
model.currentBatch = batch;
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(batch);
|
|
} catch (e, s) {
|
|
logger.e(e);
|
|
logger.e(s);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to update batch');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<List<GroupBatchItem>>> updateBatchItems(
|
|
String batchId,
|
|
GroupBatchItemUpdateList updateList,
|
|
) async {
|
|
model.isActing = true;
|
|
if (!_disposed) notifyListeners();
|
|
try {
|
|
final raw = await http.put(
|
|
'/group-batches/$batchId/items',
|
|
updateList.toJson(),
|
|
);
|
|
final response = _unwrap(raw);
|
|
final items = (response as List<dynamic>)
|
|
.map((e) => GroupBatchItem.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
// Update items in the local batch items list if the current batch matches
|
|
model.batchItems = items;
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(items);
|
|
} catch (e, s) {
|
|
logger.e(e);
|
|
logger.e(s);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to update batch items');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<GroupBatch>> confirmBatch(
|
|
String batchId,
|
|
String workspaceId,
|
|
String authType,
|
|
) async {
|
|
model.isActing = true;
|
|
if (!_disposed) notifyListeners();
|
|
try {
|
|
final key =
|
|
'batch-$batchId-confirm-${DateTime.now().millisecondsSinceEpoch}';
|
|
final body = BatchActionRequest(
|
|
workspaceId: workspaceId,
|
|
authType: authType,
|
|
idempotencyKey: key,
|
|
);
|
|
final raw = await http.post(
|
|
'/group-batches/$batchId/confirm',
|
|
body.toJson(),
|
|
);
|
|
final response = _unwrap(raw);
|
|
final batch = GroupBatch.fromJson(response as Map<String, dynamic>);
|
|
model.currentBatch = batch;
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(batch);
|
|
} catch (e) {
|
|
logger.e(e);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to confirm batch');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<TransactionModel>> requestBatch(
|
|
String batchId,
|
|
String workspaceId,
|
|
) async {
|
|
model.isActing = true;
|
|
if (!_disposed) notifyListeners();
|
|
try {
|
|
final key =
|
|
'batch-$batchId-request-${DateTime.now().millisecondsSinceEpoch}';
|
|
final body = BatchActionRequest(
|
|
workspaceId: workspaceId,
|
|
idempotencyKey: key,
|
|
);
|
|
final raw = await http.post(
|
|
'/group-batches/$batchId/request',
|
|
body.toJson(),
|
|
);
|
|
final response = _unwrap(raw);
|
|
final txn = TransactionModel.fromJson(response as Map<String, dynamic>);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(txn);
|
|
} catch (e, s) {
|
|
logger.e(e);
|
|
logger.e(s);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to request batch payment');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<Map<String, dynamic>>> downloadBatchReport(
|
|
String batchId,
|
|
String workspaceId,
|
|
) async {
|
|
model.isActing = true;
|
|
if (!_disposed) notifyListeners();
|
|
try {
|
|
final raw = await http.get(
|
|
'/group-batches/$batchId/report?workspaceId=$workspaceId',
|
|
);
|
|
final response = _unwrap(raw);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.success(response as Map<String, dynamic>);
|
|
} catch (e, s) {
|
|
logger.e(e);
|
|
logger.e(s);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to download batch report');
|
|
}
|
|
}
|
|
|
|
Future<ApiResponse<TransactionModel>> pollBatch(
|
|
String batchId,
|
|
String workspaceId,
|
|
) async {
|
|
model.isActing = true;
|
|
if (!_disposed) notifyListeners();
|
|
try {
|
|
final key =
|
|
'batch-$batchId-poll-${DateTime.now().millisecondsSinceEpoch}';
|
|
final body = BatchActionRequest(
|
|
workspaceId: workspaceId,
|
|
idempotencyKey: key,
|
|
);
|
|
final raw = await http.post(
|
|
'/group-batches/$batchId/poll',
|
|
body.toJson(),
|
|
);
|
|
final response = _unwrap(raw);
|
|
final batchTransaction = TransactionModel.fromJson(
|
|
response as Map<String, dynamic>,
|
|
);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
if (batchTransaction.pollingStatus == 'SUCCESS') {
|
|
return ApiResponse.success(batchTransaction);
|
|
} else {
|
|
return ApiResponse.failure(
|
|
response['errorMessage'] ?? "Transaction failed",
|
|
);
|
|
}
|
|
} catch (e, s) {
|
|
logger.e(e);
|
|
logger.e(s);
|
|
model.isActing = false;
|
|
if (!_disposed) notifyListeners();
|
|
return ApiResponse.failure('Failed to poll batch');
|
|
}
|
|
}
|
|
|
|
Future<void> deleteBatch({
|
|
required String batchId,
|
|
required String workspaceId,
|
|
}) async {
|
|
try {
|
|
await http.delete('/group-batches/$batchId?workspaceId=$workspaceId');
|
|
model.batches.removeWhere((b) => b.id == batchId);
|
|
model.selectedBatchIds.remove(batchId);
|
|
model.currentBatch = null;
|
|
if (!_disposed) notifyListeners();
|
|
} catch (e) {
|
|
logger.e(e);
|
|
model.errorMessage = e.toString();
|
|
if (!_disposed) notifyListeners();
|
|
}
|
|
}
|
|
|
|
Future<void> deleteSelectedBatches({
|
|
required String groupId,
|
|
required String workspaceId,
|
|
}) async {
|
|
final idsToDelete = List<String>.from(model.selectedBatchIds);
|
|
model.selectMode = false;
|
|
model.selectedBatchIds.clear();
|
|
if (!_disposed) notifyListeners();
|
|
|
|
for (final id in idsToDelete) {
|
|
await deleteBatch(batchId: id, workspaceId: workspaceId);
|
|
}
|
|
|
|
// Refresh after deletion
|
|
await listBatches(groupId, page: 0);
|
|
}
|
|
|
|
void selectProvider(BillProvider provider) {
|
|
model.selectedProvider = provider;
|
|
if (!_disposed) notifyListeners();
|
|
}
|
|
|
|
void selectProcessor(PaymentProcessor processor) {
|
|
model.selectedProcessor = processor;
|
|
if (!_disposed) notifyListeners();
|
|
}
|
|
|
|
void toggleSelectMode() {
|
|
model.selectMode = !model.selectMode;
|
|
if (!model.selectMode) {
|
|
model.selectedBatchIds.clear();
|
|
}
|
|
if (!_disposed) notifyListeners();
|
|
}
|
|
|
|
void toggleBatchSelection(String batchId) {
|
|
if (model.selectedBatchIds.contains(batchId)) {
|
|
model.selectedBatchIds.remove(batchId);
|
|
} else {
|
|
model.selectedBatchIds.add(batchId);
|
|
}
|
|
if (!_disposed) notifyListeners();
|
|
}
|
|
}
|