bharat_erp/lib/modules/assets/presentation/providers/assets_provider.dart
2026-07-22 18:17:43 +05:30

757 lines
23 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/utils/column_search_paging.dart';
import '../../../../core/utils/table_search.dart';
import '../../../../shared/models/asset_model.dart';
import '../../../../shared/models/entity_attachment_model.dart';
import '../../../../shared/models/export_file_result.dart';
import '../../data/repositories/asset_repository_impl.dart';
import 'asset_categories_provider.dart';
class AssetsListState {
const AssetsListState({
this.assets = const [],
this.query = const AssetListQuery(limit: 20),
this.total = 0,
this.totalPages = 1,
this.isRefreshing = false,
this.isExporting = false,
this.actionError,
this.actionSuccess,
});
final List<AssetModel> assets;
final AssetListQuery query;
final int total;
final int totalPages;
final bool isRefreshing;
final bool isExporting;
final String? actionError;
final String? actionSuccess;
AssetsListState copyWith({
List<AssetModel>? assets,
AssetListQuery? query,
int? total,
int? totalPages,
bool? isRefreshing,
bool? isExporting,
String? actionError,
String? actionSuccess,
bool clearMessages = false,
}) {
return AssetsListState(
assets: assets ?? this.assets,
query: query ?? this.query,
total: total ?? this.total,
totalPages: totalPages ?? this.totalPages,
isRefreshing: isRefreshing ?? this.isRefreshing,
isExporting: isExporting ?? this.isExporting,
actionError: clearMessages ? null : actionError ?? this.actionError,
actionSuccess: clearMessages ? null : actionSuccess ?? this.actionSuccess,
);
}
}
final assetsListProvider =
AsyncNotifierProvider.autoDispose<AssetsListNotifier, AssetsListState>(
AssetsListNotifier.new,
);
class AssetsListNotifier extends AutoDisposeAsyncNotifier<AssetsListState> {
final _columnSearch = ColumnSearchPaging();
@override
Future<AssetsListState> build() async {
return _load(const AssetListQuery(limit: 20));
}
Future<AssetsListState> _load(AssetListQuery query) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.getAssets(query);
if (result.failure != null) throw result.failure!;
final page = result.data!;
var items = List<AssetModel>.from(page.items);
final search = TableSearch.normalize(query.search);
// API search often ignores category name — pull matching categories too.
if (search.isNotEmpty && query.itemCategoryId == null) {
final categories = await ref.read(itemCategoriesProvider.future);
var categoryMatches = 0;
for (final category in categories) {
if (!TableSearch.matches(search, [category.name])) continue;
if (++categoryMatches > 5) break;
final categoryId = int.tryParse(category.id);
if (categoryId == null) continue;
final byCategory = await repository.getAssets(
query.copyWith(search: null, itemCategoryId: categoryId),
);
if (byCategory.failure == null && byCategory.data != null) {
items = TableSearch.mergeById(
items,
byCategory.data!.items,
(asset) => asset.id,
);
}
}
// Keep API page results + enriched matches; pagination stays server-driven.
}
return AssetsListState(
assets: items,
query: query,
total: page.total,
totalPages: page.totalPages,
);
}
Future<void> refresh() async {
final current = state.valueOrNull ?? const AssetsListState();
state = AsyncData(current.copyWith(isRefreshing: true, clearMessages: true));
try {
state = AsyncData(await _load(current.query));
} catch (e, st) {
state = AsyncError(e, st);
}
}
Future<void> applyQuery(AssetListQuery query) async {
final previous = state.valueOrNull;
if (previous == null) {
state = const AsyncLoading();
}
try {
state = AsyncData(await _load(query));
} catch (e, st) {
state = AsyncError(e, st);
}
}
void setSearch(String search) {
final current = state.valueOrNull;
if (current == null) return;
applyQuery(
current.query.copyWith(search: TableSearch.normalize(search), page: 1),
);
}
Future<void> ensureColumnSearchDataset() async {
final current = state.valueOrNull;
if (current == null) return;
final limit = _columnSearch.beginFullDataset(
currentLimit: current.query.limit,
total: current.total,
);
if (limit == null) return;
await applyQuery(
current.query.copyWith(search: null, page: 1, limit: limit),
);
}
void clearColumnSearchDataset() {
final current = state.valueOrNull;
if (current == null) return;
final limit = _columnSearch.endFullDataset();
if (limit == null) return;
applyQuery(current.query.copyWith(search: null, page: 1, limit: limit));
}
void setStatusFilter(String? status) {
final current = state.valueOrNull;
if (current == null) return;
applyQuery(current.query.copyWith(status: status, page: 1));
}
void setCategoryFilter(int? itemCategoryId) {
final current = state.valueOrNull;
if (current == null) return;
applyQuery(current.query.copyWith(itemCategoryId: itemCategoryId, page: 1));
}
void setLocationFilter(int? locationId) {
final current = state.valueOrNull;
if (current == null) return;
applyQuery(current.query.copyWith(locationId: locationId, page: 1));
}
void setPage(int page) {
final current = state.valueOrNull;
if (current == null) return;
applyQuery(current.query.copyWith(page: page));
}
void setPageSize(int limit) {
final current = state.valueOrNull;
if (current == null) return;
applyQuery(current.query.copyWith(limit: limit, page: 1));
}
Future<ExportFileResult?> exportAssets() async {
final current = state.valueOrNull;
if (current == null) return null;
state = AsyncData(current.copyWith(isExporting: true, clearMessages: true));
final result =
await ref.read(assetRepositoryProvider).exportAssets(current.query);
final latest = state.valueOrNull ?? current;
if (result.failure != null) {
state = AsyncData(
latest.copyWith(
isExporting: false,
actionError: result.failure!.message,
),
);
return null;
}
state = AsyncData(latest.copyWith(isExporting: false));
return result.data;
}
Future<bool> deleteAsset(String id) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.deleteAsset(id);
if (result.failure != null) {
final current = state.valueOrNull;
if (current != null) {
state = AsyncData(current.copyWith(actionError: result.failure!.message));
}
return false;
}
await refresh();
ref.invalidate(myMaintenanceProvider);
final current = state.valueOrNull;
if (current != null) {
state = AsyncData(current.copyWith(actionSuccess: 'Asset deleted'));
}
return true;
}
}
final assetDetailProvider =
AsyncNotifierProvider.family<AssetDetailNotifier, AssetDetailState, String>(
AssetDetailNotifier.new,
);
class AssetDetailState {
const AssetDetailState({
required this.asset,
this.amcContracts = const [],
this.serviceVisits = const [],
this.insurancePolicies = const [],
this.attachments = const [],
});
final AssetModel asset;
final List<AmcContractModel> amcContracts;
final List<ServiceVisitModel> serviceVisits;
final List<InsurancePolicyModel> insurancePolicies;
final List<EntityAttachmentModel> attachments;
AssetDetailState copyWith({
AssetModel? asset,
List<AmcContractModel>? amcContracts,
List<ServiceVisitModel>? serviceVisits,
List<InsurancePolicyModel>? insurancePolicies,
List<EntityAttachmentModel>? attachments,
}) {
return AssetDetailState(
asset: asset ?? this.asset,
amcContracts: amcContracts ?? this.amcContracts,
serviceVisits: serviceVisits ?? this.serviceVisits,
insurancePolicies: insurancePolicies ?? this.insurancePolicies,
attachments: attachments ?? this.attachments,
);
}
}
class AssetDetailNotifier extends FamilyAsyncNotifier<AssetDetailState, String> {
@override
Future<AssetDetailState> build(String arg) async {
return _loadAll(arg);
}
Future<AssetDetailState> _loadAll(String assetId) async {
final repository = ref.read(assetRepositoryProvider);
final assetResult = await repository.getAssetById(assetId);
if (assetResult.failure != null) throw assetResult.failure!;
final amcResult = await repository.getAmcContracts(assetId);
final visitsResult = await repository.getServiceVisits(assetId);
final insuranceResult = await repository.getInsurancePolicies(assetId);
final attachmentsResult = await repository.listAttachments(assetId);
return AssetDetailState(
asset: assetResult.data!,
amcContracts: amcResult.data ?? [],
serviceVisits: visitsResult.data ?? [],
insurancePolicies: insuranceResult.data ?? [],
attachments: attachmentsResult.data ?? [],
);
}
Future<void> reload() async {
state = const AsyncLoading();
state = AsyncData(await _loadAll(arg));
}
Future<EntityAttachmentModel> uploadAttachment({
required List<int> bytes,
required String filename,
String attachmentType = 'DOCUMENT',
}) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.uploadAttachment(
arg,
bytes: bytes,
filename: filename,
attachmentType: attachmentType,
);
if (result.failure != null) throw result.failure!;
final current = state.valueOrNull;
if (current == null) {
await reload();
} else {
state = AsyncData(
current.copyWith(
attachments: [...current.attachments, result.data!],
),
);
}
return result.data!;
}
Future<List<int>> downloadAttachment(String attachmentId) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.downloadAttachment(arg, attachmentId);
if (result.failure != null) throw result.failure!;
return result.data ?? [];
}
Future<void> deleteAttachment(String attachmentId) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.deleteAttachment(arg, attachmentId);
if (result.failure != null) throw result.failure!;
final current = state.valueOrNull;
if (current == null) {
await reload();
return;
}
state = AsyncData(
current.copyWith(
attachments: current.attachments
.where((a) => a.id != attachmentId)
.toList(),
),
);
}
Future<AssetModel?> updateAsset(Map<String, dynamic> data) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.updateAsset(arg, data);
if (result.failure != null) throw result.failure!;
await reload();
ref.invalidate(assetsListProvider);
ref.invalidate(myMaintenanceProvider);
return result.data;
}
Future<bool> deleteAsset() async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.deleteAsset(arg);
if (result.failure != null) return false;
ref.invalidate(assetsListProvider);
ref.invalidate(myMaintenanceProvider);
return true;
}
Future<void> transferAsset(Map<String, dynamic> data) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.transferAsset(arg, data);
if (result.failure != null) throw result.failure!;
await reload();
ref.invalidate(assetsListProvider);
ref.invalidate(transferHistoryProvider(arg));
}
Future<AmcContractModel?> createAmc(Map<String, dynamic> data) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.createAmcContract(arg, data);
if (result.failure != null) throw result.failure!;
await reload();
return result.data;
}
Future<AmcContractModel?> updateAmc(
String contractId,
Map<String, dynamic> data,
) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.updateAmcContract(arg, contractId, data);
if (result.failure != null) throw result.failure!;
await reload();
return result.data;
}
Future<ServiceVisitModel?> logVisit(Map<String, dynamic> data) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.logServiceVisit(arg, data);
if (result.failure != null) throw result.failure!;
await reload();
return result.data;
}
Future<ServiceVisitModel?> updateVisit(
String visitId,
Map<String, dynamic> data,
) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.updateServiceVisit(arg, visitId, data);
if (result.failure != null) throw result.failure!;
await reload();
return result.data;
}
Future<InsurancePolicyModel?> createInsurance(Map<String, dynamic> data) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.createInsurancePolicy(arg, data);
if (result.failure != null) throw result.failure!;
await reload();
return result.data;
}
Future<InsurancePolicyModel?> updateInsurance(
String policyId,
Map<String, dynamic> data,
) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.updateInsurancePolicy(arg, policyId, data);
if (result.failure != null) throw result.failure!;
await reload();
return result.data;
}
}
typedef AmcContractFormParams = ({String assetId, String contractId});
final amcContractFormProvider = FutureProvider.autoDispose
.family<AmcContractModel, AmcContractFormParams>((ref, params) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.getAmcContractById(
params.assetId,
params.contractId,
);
if (result.failure != null) throw result.failure!;
return result.data!;
});
typedef ServiceVisitFormParams = ({String assetId, String visitId});
final serviceVisitFormProvider = FutureProvider.autoDispose
.family<ServiceVisitModel, ServiceVisitFormParams>((ref, params) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.getServiceVisitById(
params.assetId,
params.visitId,
);
if (result.failure != null) throw result.failure!;
return result.data!;
});
typedef InsurancePolicyFormParams = ({String assetId, String policyId});
final insurancePolicyFormProvider = FutureProvider.autoDispose
.family<InsurancePolicyModel, InsurancePolicyFormParams>((ref, params) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.getInsurancePolicyById(
params.assetId,
params.policyId,
);
if (result.failure != null) throw result.failure!;
return result.data!;
});
final transferHistoryProvider = FutureProvider.autoDispose
.family<List<AssetTransferHistoryModel>, String>((ref, assetId) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.getTransferHistory(assetId);
if (result.failure != null) throw result.failure!;
return result.data ?? const [];
});
final assetFormProvider =
AsyncNotifierProvider.family.autoDispose<AssetFormNotifier, AssetModel?, String?>(
AssetFormNotifier.new,
);
class AssetFormNotifier extends AutoDisposeFamilyAsyncNotifier<AssetModel?, String?> {
@override
Future<AssetModel?> build(String? arg) async {
if (arg == null) return null;
final repository = ref.read(assetRepositoryProvider);
final result = await repository.getAssetById(arg);
if (result.failure != null) throw result.failure!;
return result.data;
}
Future<AssetModel?> submitCreate(Map<String, dynamic> data) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.createAsset(data);
if (result.failure != null) throw result.failure!;
ref.invalidate(assetsListProvider);
ref.invalidate(myMaintenanceProvider);
return result.data;
}
Future<AssetModel?> submitUpdate(String id, Map<String, dynamic> data) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.updateAsset(id, data);
if (result.failure != null) throw result.failure!;
ref.invalidate(assetsListProvider);
ref.invalidate(assetDetailProvider(id));
ref.invalidate(myMaintenanceProvider);
final fresh = await repository.getAssetById(id);
final asset = fresh.data ?? result.data;
if (asset != null) {
state = AsyncData(asset);
}
return asset;
}
}
class AssetAlertsState {
const AssetAlertsState({
this.expiryAlerts = const [],
this.serviceAlerts = const [],
this.expiryDays = 30,
this.expiryType,
this.serviceStatus,
this.expiryPage = 1,
this.expiryTotalPages = 1,
this.isRefreshing = false,
});
final List<AssetAlertModel> expiryAlerts;
final List<AssetAlertModel> serviceAlerts;
final int expiryDays;
final String? expiryType;
final String? serviceStatus;
final int expiryPage;
final int expiryTotalPages;
final bool isRefreshing;
AssetAlertsState copyWith({
List<AssetAlertModel>? expiryAlerts,
List<AssetAlertModel>? serviceAlerts,
int? expiryDays,
String? expiryType,
String? serviceStatus,
int? expiryPage,
int? expiryTotalPages,
bool? isRefreshing,
bool clearExpiryType = false,
bool clearServiceStatus = false,
}) {
return AssetAlertsState(
expiryAlerts: expiryAlerts ?? this.expiryAlerts,
serviceAlerts: serviceAlerts ?? this.serviceAlerts,
expiryDays: expiryDays ?? this.expiryDays,
expiryType: clearExpiryType ? null : expiryType ?? this.expiryType,
serviceStatus: clearServiceStatus ? null : serviceStatus ?? this.serviceStatus,
expiryPage: expiryPage ?? this.expiryPage,
expiryTotalPages: expiryTotalPages ?? this.expiryTotalPages,
isRefreshing: isRefreshing ?? this.isRefreshing,
);
}
}
final assetAlertsProvider =
AsyncNotifierProvider<AssetAlertsNotifier, AssetAlertsState>(AssetAlertsNotifier.new);
class AssetAlertsNotifier extends AsyncNotifier<AssetAlertsState> {
@override
Future<AssetAlertsState> build() async {
return _load(const AssetAlertsState());
}
Future<AssetAlertsState> _load(AssetAlertsState filters) async {
final repository = ref.read(assetRepositoryProvider);
final expiryResult = await repository.getExpiryAlerts(
days: filters.expiryDays,
type: filters.expiryType,
page: filters.expiryPage,
);
final serviceResult = await repository.getServiceAlerts(status: filters.serviceStatus);
if (expiryResult.failure != null) throw expiryResult.failure!;
final expiryPage = expiryResult.data!;
return AssetAlertsState(
expiryAlerts: expiryPage.items,
serviceAlerts: serviceResult.data ?? [],
expiryDays: filters.expiryDays,
expiryType: filters.expiryType,
serviceStatus: filters.serviceStatus,
expiryPage: expiryPage.page,
expiryTotalPages: expiryPage.totalPages,
);
}
Future<void> refresh() async {
final current = state.valueOrNull ?? const AssetAlertsState();
state = AsyncData(current.copyWith(isRefreshing: true));
try {
state = AsyncData(await _load(current));
} catch (e, st) {
state = AsyncError(e, st);
}
}
Future<void> setExpiryDays(int days) async {
final current = state.valueOrNull ?? const AssetAlertsState();
state = const AsyncLoading();
state = AsyncData(await _load(current.copyWith(expiryDays: days, expiryPage: 1)));
}
Future<void> setExpiryType(String? type) async {
final current = state.valueOrNull ?? const AssetAlertsState();
state = const AsyncLoading();
state = AsyncData(
await _load(
current.copyWith(
expiryType: type,
expiryPage: 1,
clearExpiryType: type == null,
),
),
);
}
Future<void> setServiceStatus(String? status) async {
final current = state.valueOrNull ?? const AssetAlertsState();
state = const AsyncLoading();
state = AsyncData(
await _load(
current.copyWith(
serviceStatus: status,
clearServiceStatus: status == null,
),
),
);
}
Future<void> setExpiryPage(int page) async {
final current = state.valueOrNull ?? const AssetAlertsState();
state = const AsyncLoading();
state = AsyncData(await _load(current.copyWith(expiryPage: page)));
}
}
class MyMaintenanceState {
const MyMaintenanceState({
this.assets = const [],
this.dueOnly = true,
this.page = 1,
this.limit = 20,
this.total = 0,
this.totalPages = 1,
this.isRefreshing = false,
});
final List<AssetModel> assets;
final bool dueOnly;
final int page;
final int limit;
final int total;
final int totalPages;
final bool isRefreshing;
MyMaintenanceState copyWith({
List<AssetModel>? assets,
bool? dueOnly,
int? page,
int? limit,
int? total,
int? totalPages,
bool? isRefreshing,
}) {
return MyMaintenanceState(
assets: assets ?? this.assets,
dueOnly: dueOnly ?? this.dueOnly,
page: page ?? this.page,
limit: limit ?? this.limit,
total: total ?? this.total,
totalPages: totalPages ?? this.totalPages,
isRefreshing: isRefreshing ?? this.isRefreshing,
);
}
}
final myMaintenanceProvider =
AsyncNotifierProvider.autoDispose<MyMaintenanceNotifier, MyMaintenanceState>(
MyMaintenanceNotifier.new,
);
class MyMaintenanceNotifier extends AutoDisposeAsyncNotifier<MyMaintenanceState> {
@override
Future<MyMaintenanceState> build() async {
return _load(const MyMaintenanceState());
}
Future<MyMaintenanceState> _load(MyMaintenanceState filters) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.getMyMaintenanceAssets(
dueOnly: filters.dueOnly,
page: filters.page,
limit: filters.limit,
);
if (result.failure != null) throw result.failure!;
final page = result.data!;
return MyMaintenanceState(
assets: page.items,
dueOnly: filters.dueOnly,
page: page.page,
limit: filters.limit,
total: page.total,
totalPages: page.totalPages,
);
}
Future<void> refresh() async {
final current = state.valueOrNull ?? const MyMaintenanceState();
state = AsyncData(current.copyWith(isRefreshing: true));
try {
state = AsyncData(await _load(current));
} catch (e, st) {
state = AsyncError(e, st);
}
}
Future<void> setDueOnly(bool dueOnly) async {
final current = state.valueOrNull ?? const MyMaintenanceState();
state = const AsyncLoading();
state = AsyncData(
await _load(current.copyWith(dueOnly: dueOnly, page: 1)),
);
}
Future<void> setPage(int page) async {
final current = state.valueOrNull ?? const MyMaintenanceState();
state = const AsyncLoading();
state = AsyncData(await _load(current.copyWith(page: page)));
}
Future<void> setPageSize(int limit) async {
final current = state.valueOrNull ?? const MyMaintenanceState();
state = const AsyncLoading();
state = AsyncData(
await _load(current.copyWith(limit: limit, page: 1)),
);
}
}