diff --git a/lib/core/constants/api_endpoints.dart b/lib/core/constants/api_endpoints.dart index 509ca90..7de2499 100644 --- a/lib/core/constants/api_endpoints.dart +++ b/lib/core/constants/api_endpoints.dart @@ -40,6 +40,8 @@ class ApiEndpoints { // Asset Categories static const String assetCategories = '/masters/asset-categories'; static String assetCategoryById(String id) => '/masters/asset-categories/$id'; + static const String assetSubcategories = '/masters/asset-subcategories'; + static String assetSubcategoryById(String id) => '/masters/asset-subcategories/$id'; // Masters static const String departments = '/masters/departments'; @@ -72,6 +74,8 @@ class ApiEndpoints { // Vendors static const String vendors = '/vendors'; + static const String vendorGstTreatments = '/vendors/gst-treatments'; + static const String vendorSourceOfSupply = '/vendors/source-of-supply'; static String vendorById(String id) => '/vendors/$id'; static String vendorStatus(String id) => '/vendors/$id/status'; static String vendorAddresses(String vendorId) => '/vendors/$vendorId/addresses'; @@ -84,6 +88,9 @@ class ApiEndpoints { '/vendors/$vendorId/bank-details'; static String vendorBankDetailById(String vendorId, String bankDetailId) => '/vendors/$vendorId/bank-details/$bankDetailId'; + static String vendorItems(String vendorId) => '/vendors/$vendorId/items'; + static String vendorItemById(String vendorId, String mappingId) => + '/vendors/$vendorId/items/$mappingId'; // Purchase Orders static const String purchaseOrders = '/purchase-orders'; @@ -105,13 +112,22 @@ class ApiEndpoints { static const String assets = '/assets'; static String assetById(String id) => '/assets/$id'; static String assetTransfer(String id) => '/assets/$id/transfer'; + static String assetTransferHistory(String id) => '/assets/$id/transfer-history'; + static const String assetDepreciationMethods = '/assets/depreciation-methods'; + static const String assetDepreciationCalculate = '/assets/depreciation/calculate'; static const String assetAlertsExpiry = '/assets/alerts/expiry'; static const String assetAlertsService = '/assets/alerts/service'; static String assetAmc(String assetId) => '/assets/$assetId/amc'; + static String assetAmcById(String assetId, String contractId) => + '/assets/$assetId/amc/$contractId'; static String assetAmcRenew(String assetId, String contractId) => '/assets/$assetId/amc/$contractId/renew'; static String assetServiceVisits(String assetId) => '/assets/$assetId/service-visits'; + static String assetServiceVisitById(String assetId, String visitId) => + '/assets/$assetId/service-visits/$visitId'; static String assetInsurance(String assetId) => '/assets/$assetId/insurance'; + static String assetInsuranceById(String assetId, String policyId) => + '/assets/$assetId/insurance/$policyId'; static String assetInsuranceRenew(String assetId, String policyId) => '/assets/$assetId/insurance/$policyId/renew'; @@ -130,7 +146,8 @@ class ApiEndpoints { static const String settings = '/settings'; static const String settingsBranding = '/settings/branding'; static const String settingsGeneral = '/settings/general'; - static const String settingsCompany = '/settings/company-profile'; + static const String settingsCompany = '/settings/company'; + static String settingsCompanyLogo = '/settings/company/logo'; static const String settingsAsset = '/settings/asset'; static const String settingsNotifications = '/settings/notifications'; static const String settingsEmail = '/settings/email'; diff --git a/lib/core/utils/validators.dart b/lib/core/utils/validators.dart index d0a9f64..2ed0f7c 100644 --- a/lib/core/utils/validators.dart +++ b/lib/core/utils/validators.dart @@ -206,6 +206,56 @@ class Validators { return null; } + /// Validates a positive decimal when a value is entered. + static String? optionalPositiveDouble( + String? value, { + String fieldName = 'Value', + }) { + if (value == null || value.trim().isEmpty) return null; + final parsed = double.tryParse(value.trim()); + if (parsed == null) return 'Enter a valid number'; + if (parsed <= 0) return '$fieldName must be greater than 0'; + return null; + } + + /// Validates a non-negative decimal when a value is entered. + static String? optionalNonNegativeDouble( + String? value, { + String fieldName = 'Value', + }) { + if (value == null || value.trim().isEmpty) return null; + final parsed = double.tryParse(value.trim()); + if (parsed == null) return 'Enter a valid number'; + if (parsed < 0) return '$fieldName cannot be negative'; + return null; + } + + /// Validates a positive whole number when a value is entered. + static String? optionalPositiveInt( + String? value, { + String fieldName = 'Value', + }) { + if (value == null || value.trim().isEmpty) return null; + final parsed = int.tryParse(value.trim()); + if (parsed == null) return 'Enter a valid whole number'; + if (parsed <= 0) return '$fieldName must be greater than 0'; + return null; + } + + /// Validates a percentage between 0 and 100 when a value is entered. + static String? optionalPercentage( + String? value, { + String fieldName = 'Percentage', + }) { + if (value == null || value.trim().isEmpty) return null; + final parsed = double.tryParse(value.trim()); + if (parsed == null) return 'Enter a valid percentage'; + if (parsed < 0 || parsed > 100) { + return '$fieldName must be between 0 and 100'; + } + return null; + } + static final RegExp _roleNamePattern = RegExp(r'^[a-zA-Z0-9 ]+$'); static final RegExp _masterNamePattern = RegExp(r'^[A-Za-z0-9 _/&.()\-]+$'); diff --git a/lib/modules/assets/data/datasources/asset_remote_data_source.dart b/lib/modules/assets/data/datasources/asset_remote_data_source.dart index 36ffeea..d0bf615 100644 --- a/lib/modules/assets/data/datasources/asset_remote_data_source.dart +++ b/lib/modules/assets/data/datasources/asset_remote_data_source.dart @@ -45,6 +45,11 @@ class AssetRemoteDataSource { return AssetModel.fromJson(response.data['data'] as Map); } + Future> getTransferHistory(String assetId) async { + final response = await dio.get(ApiEndpoints.assetTransferHistory(assetId)); + return _parseList(response.data, AssetTransferHistoryModel.fromJson); + } + Future> getCategories() async { final response = await dio.get( ApiEndpoints.assetCategories, @@ -53,6 +58,11 @@ class AssetRemoteDataSource { return _parseList(response.data, AssetCategoryModel.fromJson); } + Future> getDepreciationMethods() async { + final response = await dio.get(ApiEndpoints.assetDepreciationMethods); + return _parseStringOptions(response.data); + } + Future> getExpiryAlerts({ int? days, String? type, @@ -92,6 +102,26 @@ class AssetRemoteDataSource { return AmcContractModel.fromJson(response.data['data'] as Map); } + Future getAmcContractById( + String assetId, + String contractId, + ) async { + final response = await dio.get(ApiEndpoints.assetAmcById(assetId, contractId)); + return AmcContractModel.fromJson(response.data['data'] as Map); + } + + Future updateAmcContract( + String assetId, + String contractId, + Map data, + ) async { + final response = await dio.put( + ApiEndpoints.assetAmcById(assetId, contractId), + data: data, + ); + return AmcContractModel.fromJson(response.data['data'] as Map); + } + Future renewAmcContract( String assetId, String contractId, @@ -117,6 +147,26 @@ class AssetRemoteDataSource { return ServiceVisitModel.fromJson(response.data['data'] as Map); } + Future getServiceVisitById( + String assetId, + String visitId, + ) async { + final response = await dio.get(ApiEndpoints.assetServiceVisitById(assetId, visitId)); + return ServiceVisitModel.fromJson(response.data['data'] as Map); + } + + Future updateServiceVisit( + String assetId, + String visitId, + Map data, + ) async { + final response = await dio.put( + ApiEndpoints.assetServiceVisitById(assetId, visitId), + data: data, + ); + return ServiceVisitModel.fromJson(response.data['data'] as Map); + } + Future> getInsurancePolicies(String assetId) async { final response = await dio.get(ApiEndpoints.assetInsurance(assetId)); return _parseList(response.data, InsurancePolicyModel.fromJson); @@ -132,6 +182,30 @@ class AssetRemoteDataSource { ); } + Future getInsurancePolicyById( + String assetId, + String policyId, + ) async { + final response = await dio.get(ApiEndpoints.assetInsuranceById(assetId, policyId)); + return InsurancePolicyModel.fromJson( + response.data['data'] as Map, + ); + } + + Future updateInsurancePolicy( + String assetId, + String policyId, + Map data, + ) async { + final response = await dio.put( + ApiEndpoints.assetInsuranceById(assetId, policyId), + data: data, + ); + return InsurancePolicyModel.fromJson( + response.data['data'] as Map, + ); + } + Future renewInsurancePolicy( String assetId, String policyId, @@ -210,4 +284,29 @@ class AssetRemoteDataSource { totalPages: 1, ); } + + List _parseStringOptions(dynamic body) { + if (body is! Map) return const []; + final raw = body['data']; + final list = raw is List + ? raw + : raw is Map + ? raw['items'] as List? ?? const [] + : const []; + + return list + .map((item) { + if (item is String) return item.trim(); + if (item is Map) { + final value = + item['value']?.toString() ?? + item['code']?.toString() ?? + item['id']?.toString(); + return value?.trim() ?? ''; + } + return ''; + }) + .where((value) => value.isNotEmpty) + .toList(); + } } diff --git a/lib/modules/assets/data/repositories/asset_repository_impl.dart b/lib/modules/assets/data/repositories/asset_repository_impl.dart index 3e3d290..3e478e8 100644 --- a/lib/modules/assets/data/repositories/asset_repository_impl.dart +++ b/lib/modules/assets/data/repositories/asset_repository_impl.dart @@ -50,11 +50,21 @@ class AssetRepositoryImpl implements AssetRepository { return safeApiCall(() => dataSource.transferAsset(id, data)); } + @override + Future>> getTransferHistory(String assetId) { + return safeApiCall(() => dataSource.getTransferHistory(assetId)); + } + @override Future>> getCategories() { return safeApiCall(() => dataSource.getCategories()); } + @override + Future>> getDepreciationMethods() { + return safeApiCall(() => dataSource.getDepreciationMethods()); + } + @override Future>> getExpiryAlerts({ int? days, @@ -85,6 +95,25 @@ class AssetRepositoryImpl implements AssetRepository { return safeApiCall(() => dataSource.createAmcContract(assetId, data)); } + @override + Future> getAmcContractById( + String assetId, + String contractId, + ) { + return safeApiCall(() => dataSource.getAmcContractById(assetId, contractId)); + } + + @override + Future> updateAmcContract( + String assetId, + String contractId, + Map data, + ) { + return safeApiCall( + () => dataSource.updateAmcContract(assetId, contractId, data), + ); + } + @override Future> renewAmcContract( String assetId, @@ -107,6 +136,25 @@ class AssetRepositoryImpl implements AssetRepository { return safeApiCall(() => dataSource.logServiceVisit(assetId, data)); } + @override + Future> getServiceVisitById( + String assetId, + String visitId, + ) { + return safeApiCall(() => dataSource.getServiceVisitById(assetId, visitId)); + } + + @override + Future> updateServiceVisit( + String assetId, + String visitId, + Map data, + ) { + return safeApiCall( + () => dataSource.updateServiceVisit(assetId, visitId, data), + ); + } + @override Future>> getInsurancePolicies(String assetId) { return safeApiCall(() => dataSource.getInsurancePolicies(assetId)); @@ -120,6 +168,25 @@ class AssetRepositoryImpl implements AssetRepository { return safeApiCall(() => dataSource.createInsurancePolicy(assetId, data)); } + @override + Future> getInsurancePolicyById( + String assetId, + String policyId, + ) { + return safeApiCall(() => dataSource.getInsurancePolicyById(assetId, policyId)); + } + + @override + Future> updateInsurancePolicy( + String assetId, + String policyId, + Map data, + ) { + return safeApiCall( + () => dataSource.updateInsurancePolicy(assetId, policyId, data), + ); + } + @override Future> renewInsurancePolicy( String assetId, diff --git a/lib/modules/assets/domain/repositories/asset_repository.dart b/lib/modules/assets/domain/repositories/asset_repository.dart index d3b2cfc..9890719 100644 --- a/lib/modules/assets/domain/repositories/asset_repository.dart +++ b/lib/modules/assets/domain/repositories/asset_repository.dart @@ -9,7 +9,9 @@ abstract class AssetRepository { Future> updateAsset(String id, Map data); Future> deleteAsset(String id); Future> transferAsset(String id, Map data); + Future>> getTransferHistory(String assetId); Future>> getCategories(); + Future>> getDepreciationMethods(); Future>> getExpiryAlerts({ int? days, String? type, @@ -22,6 +24,15 @@ abstract class AssetRepository { String assetId, Map data, ); + Future> getAmcContractById( + String assetId, + String contractId, + ); + Future> updateAmcContract( + String assetId, + String contractId, + Map data, + ); Future> renewAmcContract( String assetId, String contractId, @@ -32,11 +43,29 @@ abstract class AssetRepository { String assetId, Map data, ); + Future> getServiceVisitById( + String assetId, + String visitId, + ); + Future> updateServiceVisit( + String assetId, + String visitId, + Map data, + ); Future>> getInsurancePolicies(String assetId); Future> createInsurancePolicy( String assetId, Map data, ); + Future> getInsurancePolicyById( + String assetId, + String policyId, + ); + Future> updateInsurancePolicy( + String assetId, + String policyId, + Map data, + ); Future> renewInsurancePolicy( String assetId, String policyId, diff --git a/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart b/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart new file mode 100644 index 0000000..3c419c3 --- /dev/null +++ b/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart @@ -0,0 +1,202 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../core/constants/app_constants.dart'; +import '../../../../shared/models/grn_model.dart'; +import '../../../../shared/models/purchase_order_model.dart'; +import '../../../../shared/models/user_management_models.dart'; +import '../../../assets/data/repositories/asset_repository_impl.dart'; +import '../../../grn/data/repositories/grn_repository_impl.dart'; +import '../../../masters/data/datasources/master_remote_data_source.dart'; +import '../../../purchase_orders/data/repositories/purchase_order_repository_impl.dart'; +import '../../../users/presentation/providers/users_provider.dart'; +import '../../../vendors/data/repositories/vendor_repository_impl.dart'; +import '../../../../shared/models/vendor_model.dart'; + +class AssetFormLookups { + const AssetFormLookups({ + this.plants = const [], + this.departments = const [], + this.warehouses = const [], + this.vendors = const [], + this.users = const [], + this.purchaseOrders = const [], + this.grns = const [], + this.depreciationMethods = const [], + }); + + final List plants; + final List departments; + final List warehouses; + final List vendors; + final List users; + final List purchaseOrders; + final List grns; + final List depreciationMethods; +} + +final assetFormLookupsProvider = + FutureProvider.autoDispose((ref) async { + final master = ref.watch(masterRemoteDataSourceProvider); + + final plants = await _safeOptions(master.listPlants); + final departments = await _safeOptions(master.listDepartments); + final warehouses = await _safeOptions(master.listWarehouses); + final vendors = await _safeVendorOptions(ref); + final users = await _safeUserOptions(ref); + final purchaseOrders = await _safePurchaseOrderOptions(ref); + final grns = await _safeGrnOptions(ref); + final depreciationMethods = await _safeDepreciationMethods(ref); + + return AssetFormLookups( + plants: plants, + departments: departments, + warehouses: warehouses, + vendors: vendors, + users: users, + purchaseOrders: purchaseOrders, + grns: grns, + depreciationMethods: depreciationMethods, + ); +}); + +final assetGrnItemsProvider = + FutureProvider.autoDispose.family, int?>( + (ref, grnId) async { + if (grnId == null || grnId <= 0) return const []; + final result = + await ref.read(grnRepositoryProvider).getGrnById(grnId.toString()); + if (result.failure != null || result.data == null) return const []; + + return result.data!.items.map((item) { + final code = item.itemCode; + final name = item.itemName; + final line = item.lineNo; + final label = [ + if (code != null && code.isNotEmpty) code, + if (name != null && name.isNotEmpty) name, + if (line != null) 'Line $line', + ].join(' · '); + return FilterOptionModel( + id: item.id, + name: label.isEmpty ? 'Item #${item.id}' : label, + ); + }).toList(); + }, +); + +Future> _safeOptions( + Future> Function() load, +) async { + try { + return await load(); + } catch (_) { + return const []; + } +} + +Future> _safeVendorOptions(Ref ref) async { + try { + final result = await ref.read(vendorRepositoryProvider).getVendors( + const VendorListQuery( + page: 1, + limit: AppConstants.maxPageSize, + status: 'active', + ), + ); + if (result.failure != null || result.data == null) return const []; + return result.data!.items + .map( + (vendor) => FilterOptionModel( + id: vendor.id, + name: vendor.vendorName, + ), + ) + .toList(); + } catch (_) { + return const []; + } +} + +Future> _safeUserOptions(Ref ref) async { + try { + final result = await ref.read(getUsersUseCaseProvider)( + const UserListQuery( + page: 1, + limit: AppConstants.maxPageSize, + status: 'active', + isActive: true, + ), + ); + if (result.failure != null || result.data == null) return const []; + return result.data!.items + .where((user) { + final status = user.status.trim().toLowerCase(); + return status == 'active' && user.isActive; + }) + .map( + (user) => FilterOptionModel( + id: user.id, + name: user.fullName, + ), + ) + .toList(); + } catch (_) { + return const []; + } +} + +Future> _safePurchaseOrderOptions(Ref ref) async { + try { + final result = + await ref.read(purchaseOrderRepositoryProvider).getPurchaseOrders( + const PurchaseOrderListQuery( + page: 1, + limit: AppConstants.maxPageSize, + ), + ); + if (result.failure != null || result.data == null) return const []; + return result.data!.items + .map( + (po) => FilterOptionModel( + id: po.id, + name: po.poNo ?? 'PO #${po.id}', + ), + ) + .toList(); + } catch (_) { + return const []; + } +} + +Future> _safeGrnOptions(Ref ref) async { + try { + final result = await ref.read(grnRepositoryProvider).getGrns( + const GrnListQuery(page: 1, limit: AppConstants.maxPageSize), + ); + if (result.failure != null || result.data == null) return const []; + return result.data!.items + .map( + (grn) => FilterOptionModel( + id: grn.id, + name: grn.grnNumber ?? 'GRN #${grn.id}', + ), + ) + .toList(); + } catch (_) { + return const []; + } +} + +Future> _safeDepreciationMethods(Ref ref) async { + try { + final result = await ref.read(assetRepositoryProvider).getDepreciationMethods(); + if (result.failure != null || result.data == null) return const []; + return result.data! + .map((method) => method.trim().toUpperCase()) + .where((method) => method.isNotEmpty) + .toSet() + .toList(); + } catch (_) { + return const []; + } +} diff --git a/lib/modules/assets/presentation/providers/assets_provider.dart b/lib/modules/assets/presentation/providers/assets_provider.dart index 984d422..29b576d 100644 --- a/lib/modules/assets/presentation/providers/assets_provider.dart +++ b/lib/modules/assets/presentation/providers/assets_provider.dart @@ -223,6 +223,17 @@ class AssetDetailNotifier extends FamilyAsyncNotifier return result.data; } + Future updateAmc( + String contractId, + Map 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 logVisit(Map data) async { final repository = ref.read(assetRepositoryProvider); final result = await repository.logServiceVisit(arg, data); @@ -231,6 +242,17 @@ class AssetDetailNotifier extends FamilyAsyncNotifier return result.data; } + Future updateVisit( + String visitId, + Map 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 createInsurance(Map data) async { final repository = ref.read(assetRepositoryProvider); final result = await repository.createInsurancePolicy(arg, data); @@ -238,8 +260,66 @@ class AssetDetailNotifier extends FamilyAsyncNotifier await reload(); return result.data; } + + Future updateInsurance( + String policyId, + Map 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((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((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((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, 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.new, diff --git a/lib/modules/assets/presentation/screens/asset_alerts_screen.dart b/lib/modules/assets/presentation/screens/asset_alerts_screen.dart index 39023cf..773e501 100644 --- a/lib/modules/assets/presentation/screens/asset_alerts_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_alerts_screen.dart @@ -15,11 +15,31 @@ import '../../../../shared/widgets/error_view.dart'; import '../../../../shared/widgets/page_header.dart'; import '../providers/assets_provider.dart'; -class AssetAlertsScreen extends ConsumerWidget { +class AssetAlertsScreen extends ConsumerStatefulWidget { const AssetAlertsScreen({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => _AssetAlertsScreenState(); +} + +class _AssetAlertsScreenState extends ConsumerState + with SingleTickerProviderStateMixin { + late final TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { final alertsAsync = ref.watch(assetAlertsProvider); return Padding( @@ -30,41 +50,40 @@ class AssetAlertsScreen extends ConsumerWidget { e is Failure ? e : Failure.unknown(message: e.toString()), onRetry: () => ref.invalidate(assetAlertsProvider), ), - data: (state) => DefaultTabController( - length: 2, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - PageHeader( - title: 'Asset Alerts', - subtitle: 'Expiry and service due notifications', - actions: [ - OutlinedButton.icon( - onPressed: () => context.go(RouteConstants.assets), - icon: const Icon(Icons.inventory_2_outlined), - label: const Text('Asset Master'), - ), - ], - ), - const SizedBox(height: 16), - const TabBar( - tabs: [ - Tab(text: 'Expiry Alerts'), - Tab(text: 'Service Alerts'), - ], - ), - const SizedBox(height: 16), - Expanded( - child: TabBarView( - clipBehavior: Clip.none, - children: [ - _ExpiryAlertsTab(state: state), - _ServiceAlertsTab(state: state), - ], + data: (state) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PageHeader( + title: 'Asset Alerts', + subtitle: 'Expiry and service due notifications', + actions: [ + OutlinedButton.icon( + onPressed: () => context.go(RouteConstants.assets), + icon: const Icon(Icons.inventory_2_outlined), + label: const Text('Asset Master'), ), + ], + ), + const SizedBox(height: 16), + TabBar( + controller: _tabController, + tabs: const [ + Tab(text: 'Expiry Alerts'), + Tab(text: 'Service Alerts'), + ], + ), + const SizedBox(height: 16), + Expanded( + child: TabBarView( + controller: _tabController, + clipBehavior: Clip.none, + children: [ + _ExpiryAlertsTab(state: state), + _ServiceAlertsTab(state: state), + ], ), - ], - ), + ), + ], ), ), ); @@ -87,7 +106,7 @@ class _ExpiryAlertsTab extends ConsumerWidget { Wrap( spacing: 12, runSpacing: 8, - crossAxisAlignment: WrapCrossAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, children: [ SizedBox( width: 160, @@ -118,10 +137,13 @@ class _ExpiryAlertsTab extends ConsumerWidget { onChanged: notifier.setExpiryType, ), ), - IconButton( - onPressed: () => notifier.refresh(), - icon: const Icon(Icons.refresh), - tooltip: 'Refresh', + Padding( + padding: const EdgeInsets.only(top: 8), + child: IconButton( + onPressed: () => notifier.refresh(), + icon: const Icon(Icons.refresh), + tooltip: 'Refresh', + ), ), ], ), @@ -179,6 +201,7 @@ class _ServiceAlertsTab extends ConsumerWidget { Wrap( spacing: 12, runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, children: [ SizedBox( width: 200, @@ -196,10 +219,13 @@ class _ServiceAlertsTab extends ConsumerWidget { onChanged: notifier.setServiceStatus, ), ), - IconButton( - onPressed: () => notifier.refresh(), - icon: const Icon(Icons.refresh), - tooltip: 'Refresh', + Padding( + padding: const EdgeInsets.only(top: 8), + child: IconButton( + onPressed: () => notifier.refresh(), + icon: const Icon(Icons.refresh), + tooltip: 'Refresh', + ), ), ], ), diff --git a/lib/modules/assets/presentation/screens/asset_detail_screen.dart b/lib/modules/assets/presentation/screens/asset_detail_screen.dart index b9821e3..6c8d1a5 100644 --- a/lib/modules/assets/presentation/screens/asset_detail_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_detail_screen.dart @@ -77,7 +77,7 @@ class _AssetDetailScreenState extends ConsumerState if (canEdit) ...[ const SizedBox(width: 8), OutlinedButton.icon( - onPressed: () => _showTransferDialog(state.asset), + onPressed: _showTransferDialog, icon: const Icon(Icons.swap_horiz), label: const Text('Transfer'), ), @@ -107,9 +107,16 @@ class _AssetDetailScreenState extends ConsumerState child: TabBarView( controller: _tabController, children: [ - _OverviewTab(asset: state.asset), + _OverviewTab( + asset: state.asset, + onOpenTransferHistory: _openTransferHistoryPanel, + ), _AmcTab(assetId: widget.assetId, contracts: state.amcContracts), - _ServiceVisitsTab(assetId: widget.assetId, visits: state.serviceVisits), + _ServiceVisitsTab( + assetId: widget.assetId, + visits: state.serviceVisits, + amcContracts: state.amcContracts, + ), _InsuranceTab(assetId: widget.assetId, policies: state.insurancePolicies), ], ), @@ -134,52 +141,36 @@ class _AssetDetailScreenState extends ConsumerState if (deleted && mounted) context.go(RouteConstants.assets); } - Future _showTransferDialog(AssetModel asset) async { - final plantIdController = TextEditingController(text: asset.plantId?.toString() ?? ''); - final confirmed = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Transfer Asset'), - content: TextField( - controller: plantIdController, - decoration: const InputDecoration( - labelText: 'Target Plant ID', - helperText: 'Enter the plant ID to transfer this asset to', - ), - keyboardType: TextInputType.number, - ), - actions: [ - TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')), - ElevatedButton(onPressed: () => Navigator.pop(context, true), child: const Text('Transfer')), - ], - ), + Future _showTransferDialog() async { + final transferred = await showSidePanel( + context, + TransferAssetPanel(assetId: widget.assetId), + width: 520, ); - if (confirmed != true || !mounted) return; - - final plantId = int.tryParse(plantIdController.text.trim()); - if (plantId == null) return; - - try { - await ref.read(assetDetailProvider(widget.assetId).notifier).transferAsset({ - 'plant_id': plantId, - }); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Asset transferred')), - ); - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString()))); - } + if (transferred == true && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Asset transferred')), + ); } } + + Future _openTransferHistoryPanel() async { + await showSidePanel( + context, + TransferHistoryPanel(assetId: widget.assetId), + width: 520, + ); + } } class _OverviewTab extends StatelessWidget { - const _OverviewTab({required this.asset}); + const _OverviewTab({ + required this.asset, + required this.onOpenTransferHistory, + }); final AssetModel asset; + final VoidCallback onOpenTransferHistory; @override Widget build(BuildContext context) { @@ -196,12 +187,22 @@ class _OverviewTab extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - 'Asset Details', - style: theme.textTheme.labelLarge?.copyWith( - color: theme.colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w600, - ), + Row( + children: [ + Text( + 'Asset Details', + style: theme.textTheme.labelLarge?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + TextButton.icon( + onPressed: onOpenTransferHistory, + icon: const Icon(Icons.history, size: 18), + label: const Text('Transfer History'), + ), + ], ), const SizedBox(height: 12), _AssetInfoGrid( @@ -209,7 +210,17 @@ class _OverviewTab extends StatelessWidget { _AssetInfo('Asset Name', asset.assetName), _AssetInfo('Asset Code', asset.assetCode ?? '—'), _AssetInfo('Category', asset.assetCategoryName ?? '—'), + _AssetInfo('Subcategory', asset.assetSubcategoryName ?? '—'), _AssetInfo('Plant', asset.plantName ?? '—'), + _AssetInfo('Serial Number', asset.serialNumber ?? '—'), + _AssetInfo('Brand / Model', asset.brandModel ?? '—'), + _AssetInfo('Manufacturer', asset.manufacturer ?? '—'), + _AssetInfo( + 'Purchase Date', + asset.purchaseDate != null + ? dateFormat.format(asset.purchaseDate!) + : '—', + ), _AssetInfo( 'Warranty Expiry', asset.warrantyExpiryDate != null @@ -220,16 +231,36 @@ class _OverviewTab extends StatelessWidget { 'Purchase Cost', asset.purchaseCost != null ? '₹${asset.purchaseCost}' : '—', ), - _AssetInfo.status( - 'Status', - AppStatusChip(status: asset.status ?? 'active'), + _AssetInfo( + 'Useful Life', + asset.usefulLifeYears != null + ? '${asset.usefulLifeYears} years' + : '—', ), _AssetInfo( - 'Active', - asset.isActive ? 'Yes' : 'No', + 'Depreciation', + asset.depreciationMethod != null + ? '${asset.depreciationMethod}' + '${asset.depreciationRate != null ? ' (${asset.depreciationRate}%)' : ''}' + : '—', ), + _AssetInfo('Condition', assetConditionLabel(asset.condition)), + _AssetInfo.widget( + 'Status', + AppStatusChip(status: asset.status ?? 'IN_USE'), + ), + _AssetInfo('Active', asset.isActive ? 'Yes' : 'No'), ], ), + if (asset.remarks?.trim().isNotEmpty == true) ...[ + const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Divider(height: 1), + ), + _AssetInfoGrid( + items: [_AssetInfo('Remarks', asset.remarks!)], + ), + ], ], ), ), @@ -315,7 +346,7 @@ class _AssetDetailTile extends StatelessWidget { class _AssetInfo { const _AssetInfo(this.label, this.value) : valueWidget = null; - const _AssetInfo.status(this.label, this.valueWidget) : value = null; + const _AssetInfo.widget(this.label, this.valueWidget) : value = null; final String label; final String? value; @@ -363,7 +394,14 @@ class _AmcTab extends ConsumerWidget { ), itemCount: contracts.length, itemBuilder: (context, index) { - return _AssetAmcCard(contract: contracts[index]); + return _AssetAmcCard( + contract: contracts[index], + onEdit: () => _openEditAmcPanel( + context, + ref, + contracts[index].id, + ), + ); }, ), ), @@ -383,13 +421,35 @@ class _AmcTab extends ConsumerWidget { ); } } + + Future _openEditAmcPanel( + BuildContext context, + WidgetRef ref, + String contractId, + ) async { + final saved = await showSidePanel( + context, + AddAmcPanel(assetId: assetId, contractId: contractId), + width: 520, + ); + if (saved == true && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('AMC contract updated')), + ); + } + } } class _ServiceVisitsTab extends ConsumerWidget { - const _ServiceVisitsTab({required this.assetId, required this.visits}); + const _ServiceVisitsTab({ + required this.assetId, + required this.visits, + required this.amcContracts, + }); final String assetId; final List visits; + final List amcContracts; @override Widget build(BuildContext context, WidgetRef ref) { @@ -426,7 +486,14 @@ class _ServiceVisitsTab extends ConsumerWidget { ), itemCount: visits.length, itemBuilder: (context, index) { - return _AssetServiceVisitCard(visit: visits[index]); + return _AssetServiceVisitCard( + visit: visits[index], + onEdit: () => _openEditVisitPanel( + context, + ref, + visits[index].id, + ), + ); }, ), ), @@ -437,8 +504,11 @@ class _ServiceVisitsTab extends ConsumerWidget { Future _openLogVisitPanel(BuildContext context, WidgetRef ref) async { final saved = await showSidePanel( context, - LogServiceVisitPanel(assetId: assetId), - width: 520, + LogServiceVisitPanel( + assetId: assetId, + amcContracts: amcContracts, + ), + width: 560, ); if (saved == true && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -446,6 +516,27 @@ class _ServiceVisitsTab extends ConsumerWidget { ); } } + + Future _openEditVisitPanel( + BuildContext context, + WidgetRef ref, + String visitId, + ) async { + final saved = await showSidePanel( + context, + LogServiceVisitPanel( + assetId: assetId, + visitId: visitId, + amcContracts: amcContracts, + ), + width: 560, + ); + if (saved == true && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Service visit updated')), + ); + } + } } class _InsuranceTab extends ConsumerWidget { @@ -489,7 +580,14 @@ class _InsuranceTab extends ConsumerWidget { ), itemCount: policies.length, itemBuilder: (context, index) { - return _AssetInsuranceCard(policy: policies[index]); + return _AssetInsuranceCard( + policy: policies[index], + onEdit: () => _openEditInsurancePanel( + context, + ref, + policies[index].id, + ), + ); }, ), ), @@ -509,12 +607,33 @@ class _InsuranceTab extends ConsumerWidget { ); } } + + Future _openEditInsurancePanel( + BuildContext context, + WidgetRef ref, + String policyId, + ) async { + final saved = await showSidePanel( + context, + AddInsurancePanel(assetId: assetId, policyId: policyId), + width: 520, + ); + if (saved == true && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Insurance policy updated')), + ); + } + } } class _AssetAmcCard extends StatelessWidget { - const _AssetAmcCard({required this.contract}); + const _AssetAmcCard({ + required this.contract, + this.onEdit, + }); final AmcContractModel contract; + final VoidCallback? onEdit; @override Widget build(BuildContext context) { @@ -535,18 +654,40 @@ class _AssetAmcCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: const Color(0xFFCA8A04).withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.handyman_outlined, - color: Color(0xFFCA8A04), - size: 20, - ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: const Color(0xFFCA8A04).withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.handyman_outlined, + color: Color(0xFFCA8A04), + size: 20, + ), + ), + const Spacer(), + if (onEdit != null) + CanPermission( + module: 'assets', + action: PermissionAction.update, + child: IconButton( + icon: const Icon(Icons.edit_outlined, size: 18), + tooltip: 'Edit', + visualDensity: VisualDensity.compact, + padding: EdgeInsets.zero, + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + onPressed: onEdit, + ), + ), + ], ), const SizedBox(height: 8), Text( @@ -628,9 +769,13 @@ class _AssetAmcCard extends StatelessWidget { } class _AssetServiceVisitCard extends StatelessWidget { - const _AssetServiceVisitCard({required this.visit}); + const _AssetServiceVisitCard({ + required this.visit, + this.onEdit, + }); final ServiceVisitModel visit; + final VoidCallback? onEdit; @override Widget build(BuildContext context) { @@ -646,18 +791,40 @@ class _AssetServiceVisitCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: const Color(0xFF2563EB).withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.build_outlined, - color: Color(0xFF2563EB), - size: 20, - ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: const Color(0xFF2563EB).withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.build_outlined, + color: Color(0xFF2563EB), + size: 20, + ), + ), + const Spacer(), + if (onEdit != null) + CanPermission( + module: 'assets', + action: PermissionAction.update, + child: IconButton( + icon: const Icon(Icons.edit_outlined, size: 18), + tooltip: 'Edit', + visualDensity: VisualDensity.compact, + padding: EdgeInsets.zero, + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + onPressed: onEdit, + ), + ), + ], ), const SizedBox(height: 8), Text( @@ -748,9 +915,13 @@ class _AssetServiceVisitCard extends StatelessWidget { } class _AssetInsuranceCard extends StatelessWidget { - const _AssetInsuranceCard({required this.policy}); + const _AssetInsuranceCard({ + required this.policy, + this.onEdit, + }); final InsurancePolicyModel policy; + final VoidCallback? onEdit; @override Widget build(BuildContext context) { @@ -766,18 +937,40 @@ class _AssetInsuranceCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: const Color(0xFF16A34A).withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.shield_outlined, - color: Color(0xFF16A34A), - size: 20, - ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: const Color(0xFF16A34A).withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.shield_outlined, + color: Color(0xFF16A34A), + size: 20, + ), + ), + const Spacer(), + if (onEdit != null) + CanPermission( + module: 'assets', + action: PermissionAction.update, + child: IconButton( + icon: const Icon(Icons.edit_outlined, size: 18), + tooltip: 'Edit', + visualDensity: VisualDensity.compact, + padding: EdgeInsets.zero, + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + onPressed: onEdit, + ), + ), + ], ), const SizedBox(height: 8), Text( diff --git a/lib/modules/assets/presentation/screens/asset_list_screen.dart b/lib/modules/assets/presentation/screens/asset_list_screen.dart index 282bee4..df9a36c 100644 --- a/lib/modules/assets/presentation/screens/asset_list_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_list_screen.dart @@ -16,6 +16,7 @@ import '../../../../shared/widgets/app_dropdown.dart'; import '../../../../shared/widgets/app_empty_state.dart'; import '../../../../shared/widgets/app_loading_view.dart'; import '../../../../shared/widgets/app_searchable_dropdown.dart'; +import '../../../../shared/widgets/app_pagination.dart'; import '../../../../shared/widgets/app_status_chip.dart'; import '../../../../shared/widgets/app_table_action_icon.dart'; import '../../../../shared/widgets/can_permission.dart'; @@ -74,8 +75,6 @@ class _AssetListScreenState extends ConsumerState { final page = state.query.page; final pageSize = state.query.limit; final total = state.total; - final start = total == 0 ? 0 : ((page - 1) * pageSize) + 1; - final end = (page * pageSize).clamp(0, total); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -194,67 +193,18 @@ class _AssetListScreenState extends ConsumerState { const Divider(height: 1), Padding( padding: const EdgeInsets.all(16), - child: Row( - children: [ - Text( - 'Showing $start–$end of $total assets', - style: Theme.of(context).textTheme.bodySmall, - ), - const Spacer(), - TextButton( - onPressed: page > 1 - ? () => ref - .read(assetsListProvider.notifier) - .setPage(page - 1) - : null, - child: const Text('Previous'), - ), - ...List.generate(state.totalPages.clamp(0, 4), (i) { - final pageIndex = i + 1; - final selected = page == pageIndex; - return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 2), - child: Material( - color: selected - ? Theme.of(context).colorScheme.primary - : Colors.transparent, - shape: const CircleBorder(), - child: InkWell( - customBorder: const CircleBorder(), - onTap: () => ref - .read(assetsListProvider.notifier) - .setPage(pageIndex), - child: SizedBox( - width: 36, - height: 36, - child: Center( - child: Text( - '$pageIndex', - style: TextStyle( - color: selected - ? Theme.of(context) - .colorScheme - .onPrimary - : null, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ), - ), - ); - }), - TextButton( - onPressed: page < state.totalPages - ? () => ref - .read(assetsListProvider.notifier) - .setPage(page + 1) - : null, - child: const Text('Next'), - ), - ], + child: AppPagination( + currentPage: page, + totalPages: state.totalPages, + totalItems: total, + pageSize: pageSize, + itemLabel: 'assets', + onPageChanged: ref + .read(assetsListProvider.notifier) + .setPage, + onPageSizeChanged: ref + .read(assetsListProvider.notifier) + .setPageSize, ), ), ], @@ -393,13 +343,18 @@ class _AssetsFilterBar extends StatelessWidget { @override Widget build(BuildContext context) { - final searchField = TextField( - decoration: const InputDecoration( - hintText: 'Search by name, code, category...', - prefixIcon: Icon(Icons.search, size: 20), - isDense: true, + final searchField = Padding( + padding: const EdgeInsets.only(top: 8), + child: TextField( + decoration: const InputDecoration( + labelText: 'Search', + hintText: 'Search by name, code, category...', + prefixIcon: Icon(Icons.search, size: 20), + floatingLabelBehavior: FloatingLabelBehavior.always, + isDense: true, + ), + onChanged: onSearch, ), - onChanged: onSearch, ); final filters = [ @@ -429,12 +384,14 @@ class _AssetsFilterBar extends StatelessWidget { children: [ searchField, const SizedBox(height: 12), - Wrap(spacing: 12, runSpacing: 12, children: filters), + ...filters.expand((f) => [f, const SizedBox(height: 12)]).toList() + ..removeLast(), ], ); } return Row( + crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded(flex: 3, child: searchField), const SizedBox(width: 12), @@ -503,7 +460,11 @@ class _AssetDataTable extends StatelessWidget { AppDataColumn( label: 'Asset Code', flex: 1, - cellBuilder: (_, asset) => Text(asset.assetCode ?? '—'), + cellBuilder: (_, asset) { + final code = asset.assetCode; + if (code == null || code.isEmpty) return const Text('—'); + return _AssetCodeBadge(code: code); + }, ), AppDataColumn( label: 'Asset Name', @@ -621,7 +582,10 @@ class _AssetMobileList extends StatelessWidget { ], ), const SizedBox(height: 4), - Text(asset.assetCode ?? '—'), + if (asset.assetCode != null && asset.assetCode!.isNotEmpty) + _AssetCodeBadge(code: asset.assetCode!) + else + const Text('—'), Text('${asset.assetCategoryName ?? '—'} · ${asset.plantName ?? '—'}'), const SizedBox(height: 8), Row( @@ -654,3 +618,28 @@ class _AssetMobileList extends StatelessWidget { ); } } + +class _AssetCodeBadge extends StatelessWidget { + const _AssetCodeBadge({required this.code}); + + final String code; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.7), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + code, + style: theme.textTheme.labelSmall?.copyWith( + fontWeight: FontWeight.w600, + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ); + } +} diff --git a/lib/modules/assets/presentation/widgets/asset_form_panel.dart b/lib/modules/assets/presentation/widgets/asset_form_panel.dart index f088060..19163da 100644 --- a/lib/modules/assets/presentation/widgets/asset_form_panel.dart +++ b/lib/modules/assets/presentation/widgets/asset_form_panel.dart @@ -16,6 +16,7 @@ import '../../../../shared/widgets/app_text_field.dart'; import '../../../../shared/widgets/error_view.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../providers/asset_categories_provider.dart'; +import '../providers/asset_form_lookups_provider.dart'; import '../providers/assets_provider.dart'; Future openAssetFormPanel( @@ -24,10 +25,11 @@ Future openAssetFormPanel( String? assetId, }) async { ref.invalidate(assetFormProvider(assetId)); + final panelWidth = MediaQuery.sizeOf(context).width * 0.4; final saved = await showSidePanel( context, AssetFormPanel(assetId: assetId), - width: 520, + width: panelWidth, ); if (saved == true && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -57,35 +59,143 @@ class _AssetFormPanelState extends ConsumerState { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _costController = TextEditingController(); + final _serialController = TextEditingController(); + final _partNumberController = TextEditingController(); + final _brandModelController = TextEditingController(); + final _manufacturerController = TextEditingController(); + final _locationDetailController = TextEditingController(); + final _qrCodeController = TextEditingController(); + final _disposalReasonController = TextEditingController(); + final _disposalValueController = TextEditingController(); + final _usefulLifeController = TextEditingController(); + final _depreciationRateController = TextEditingController(); + final _salvageValueController = TextEditingController(); + final _remarksController = TextEditingController(); int? _categoryId; + int? _subcategoryId; int? _plantId; + int? _departmentId; + int? _warehouseId; + int? _assignedToUserId; + int? _vendorId; + int? _poId; + int? _grnId; + int? _grnItemId; + String? _status; + String? _condition; + String? _depreciationMethod; DateTime? _warrantyExpiry; + DateTime? _purchaseDate; + DateTime? _disposalDate; + bool _isActive = true; bool _isSubmitting = false; String? _populatedSignature; + @override + void initState() { + super.initState(); + if (!widget.isEditing) { + _condition = 'NEW'; + _status = 'IN_USE'; + } + } + @override void dispose() { _nameController.dispose(); _costController.dispose(); + _serialController.dispose(); + _partNumberController.dispose(); + _brandModelController.dispose(); + _manufacturerController.dispose(); + _locationDetailController.dispose(); + _qrCodeController.dispose(); + _disposalReasonController.dispose(); + _disposalValueController.dispose(); + _usefulLifeController.dispose(); + _depreciationRateController.dispose(); + _salvageValueController.dispose(); + _remarksController.dispose(); super.dispose(); } String _assetSignature(AssetModel asset) => - '${asset.id}:${asset.assetCategoryId}:${asset.plantId}:' - '${asset.warrantyExpiryDate?.toIso8601String()}:' - '${asset.purchaseCost}:${asset.assetName}'; + '${asset.id}:${asset.assetCategoryId}:${asset.assetSubcategoryId}:' + '${asset.plantId}:${asset.status}:${asset.assetName}'; + + int? _nullablePositiveId(int? id) { + if (id == null || id <= 0) return null; + return id; + } + + void _putOptionalId(Map payload, String key, int? value) { + final normalized = _nullablePositiveId(value); + if (normalized != null) payload[key] = normalized; + } + + void _putOptionalText( + Map payload, + String key, + String value, + ) { + final trimmed = value.trim(); + if (trimmed.isNotEmpty) payload[key] = trimmed; + } + + void _putOptionalDouble( + Map payload, + String key, + String value, + ) { + final trimmed = value.trim(); + if (trimmed.isEmpty) return; + final parsed = double.tryParse(trimmed); + if (parsed != null) payload[key] = parsed; + } int? _dropdownValue(int? selected, Iterable validIds) { if (selected == null) return null; return validIds.contains(selected) ? selected : null; } + bool _isAssignableUser(int? userId) { + if (userId == null || userId <= 0) return false; + final users = ref.read(assetFormLookupsProvider).valueOrNull?.users ?? const []; + return users.any((user) => int.tryParse(user.id) == userId); + } + void _populateFromAsset(AssetModel asset) { setState(() { _nameController.text = asset.assetName; _categoryId = asset.assetCategoryId; + _subcategoryId = asset.assetSubcategoryId; _plantId = asset.plantId; + _departmentId = _nullablePositiveId(asset.departmentId); + _warehouseId = _nullablePositiveId(asset.warehouseId); + _assignedToUserId = _nullablePositiveId(asset.assignedToUserId); + _vendorId = _nullablePositiveId(asset.vendorId); + _poId = _nullablePositiveId(asset.poId); + _grnId = _nullablePositiveId(asset.grnId); + _grnItemId = _nullablePositiveId(asset.grnItemId); + _status = asset.status; + _condition = asset.condition; + _depreciationMethod = asset.depreciationMethod; _warrantyExpiry = asset.warrantyExpiryDate; + _purchaseDate = asset.purchaseDate; + _disposalDate = asset.disposalDate; + _isActive = asset.isActive; + _serialController.text = asset.serialNumber ?? ''; + _partNumberController.text = asset.partNumber ?? ''; + _brandModelController.text = asset.brandModel ?? ''; + _manufacturerController.text = asset.manufacturer ?? ''; + _locationDetailController.text = asset.locationDetail ?? ''; + _qrCodeController.text = asset.qrCodeValue ?? ''; + _disposalReasonController.text = asset.disposalReason ?? ''; + _disposalValueController.text = asset.disposalValue?.toString() ?? ''; + _usefulLifeController.text = asset.usefulLifeYears?.toString() ?? ''; + _depreciationRateController.text = asset.depreciationRate?.toString() ?? ''; + _salvageValueController.text = asset.salvageValue?.toString() ?? ''; + _remarksController.text = asset.remarks ?? ''; if (asset.purchaseCost != null) { _costController.text = asset.purchaseCost!.toStringAsFixed( asset.purchaseCost! % 1 == 0 ? 0 : 2, @@ -97,33 +207,81 @@ class _AssetFormPanelState extends ConsumerState { } Map _buildPayload() { - return { + final payload = { 'asset_name': _nameController.text.trim(), 'asset_category_id': _categoryId, + 'asset_subcategory_id': _subcategoryId, 'plant_id': _plantId, - if (_warrantyExpiry != null) - 'warranty_expiry_date': DateFormat('yyyy-MM-dd').format(_warrantyExpiry!), - if (_costController.text.trim().isNotEmpty) - 'purchase_cost': double.tryParse(_costController.text.trim()), + 'is_active': _isActive, }; + + _putOptionalId(payload, 'department_id', _departmentId); + _putOptionalId(payload, 'warehouse_id', _warehouseId); + if (_isAssignableUser(_assignedToUserId)) { + _putOptionalId(payload, 'assigned_to_user_id', _assignedToUserId); + } + _putOptionalId(payload, 'vendor_id', _vendorId); + _putOptionalId(payload, 'po_id', _poId); + _putOptionalId(payload, 'grn_id', _grnId); + _putOptionalId(payload, 'grn_item_id', _grnItemId); + + _putOptionalText(payload, 'serial_number', _serialController.text); + _putOptionalText(payload, 'part_number', _partNumberController.text); + _putOptionalText(payload, 'brand_model', _brandModelController.text); + _putOptionalText(payload, 'manufacturer', _manufacturerController.text); + _putOptionalText(payload, 'location_detail', _locationDetailController.text); + _putOptionalText(payload, 'qr_code_value', _qrCodeController.text); + _putOptionalText(payload, 'remarks', _remarksController.text); + _putOptionalText(payload, 'disposal_reason', _disposalReasonController.text); + + if (_purchaseDate != null) { + payload['purchase_date'] = DateFormat('yyyy-MM-dd').format(_purchaseDate!); + } + if (_warrantyExpiry != null) { + payload['warranty_expiry_date'] = + DateFormat('yyyy-MM-dd').format(_warrantyExpiry!); + } + if (_disposalDate != null) { + payload['disposal_date'] = DateFormat('yyyy-MM-dd').format(_disposalDate!); + } + + _putOptionalDouble(payload, 'purchase_cost', _costController.text); + _putOptionalDouble(payload, 'salvage_value', _salvageValueController.text); + _putOptionalDouble(payload, 'disposal_value', _disposalValueController.text); + + final usefulLife = int.tryParse(_usefulLifeController.text.trim()); + if (usefulLife != null) payload['useful_life_years'] = usefulLife; + + if (_depreciationMethod != null) { + payload['depreciation_method'] = _depreciationMethod; + } + + final depreciationRate = + double.tryParse(_depreciationRateController.text.trim()); + if (depreciationRate != null) payload['depreciation_rate'] = depreciationRate; + + if (_condition != null) payload['condition'] = _condition; + if (_status != null) payload['status'] = _status; + + return payload; } - Future _pickWarrantyDate() async { + Future _pickDate(void Function(DateTime) onPicked, DateTime? current) async { final picked = await showDatePicker( context: context, - initialDate: _warrantyExpiry ?? DateTime.now(), + initialDate: current ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); - if (picked != null) setState(() => _warrantyExpiry = picked); + if (picked != null) setState(() => onPicked(picked)); } Future _submit() async { if (!_formKey.currentState!.validate()) return; - if (_categoryId == null || _plantId == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please select category and plant')), - ); + + final businessError = _validateBusinessRules(); + if (businessError != null) { + showSidePanelSnackBar(context, businessError); return; } @@ -139,15 +297,67 @@ class _AssetFormPanelState extends ConsumerState { if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString())), - ); + showSidePanelSnackBar(context, e.toString()); } } finally { if (mounted) setState(() => _isSubmitting = false); } } + String? _validateBusinessRules() { + if (_purchaseDate != null && + _warrantyExpiry != null && + _warrantyExpiry!.isBefore(_purchaseDate!)) { + return 'Warranty expiry must be on or after purchase date'; + } + + final purchaseCost = double.tryParse(_costController.text.trim()); + final salvageValue = double.tryParse(_salvageValueController.text.trim()); + if (purchaseCost != null && + salvageValue != null && + salvageValue > purchaseCost) { + return 'Salvage value cannot exceed purchase cost'; + } + + final hasDepreciationMethod = + _depreciationMethod != null && _depreciationMethod!.trim().isNotEmpty; + if (hasDepreciationMethod) { + if (_usefulLifeController.text.trim().isEmpty) { + return 'Useful life is required when depreciation method is set'; + } + if (_depreciationRateController.text.trim().isEmpty) { + return 'Depreciation rate is required when depreciation method is set'; + } + } + + final isDisposed = _status == 'DISPOSED' || _status == 'SCRAPPED'; + if (isDisposed) { + if (_disposalDate == null) { + return 'Disposal date is required for disposed or scrapped assets'; + } + if (_disposalReasonController.text.trim().isEmpty) { + return 'Disposal reason is required for disposed or scrapped assets'; + } + } + + final hasDisposalInput = _disposalDate != null || + _disposalReasonController.text.trim().isNotEmpty || + _disposalValueController.text.trim().isNotEmpty; + if (hasDisposalInput && !isDisposed) { + return 'Set status to Disposed or Scrapped when entering disposal details'; + } + + if (_grnId != null) { + final grnItems = + ref.read(assetGrnItemsProvider(_grnId)).valueOrNull ?? const []; + if (grnItems.isNotEmpty && _grnItemId == null) { + return 'Please select a GRN item when a GRN is linked'; + } + } + + return null; + } + @override Widget build(BuildContext context) { final categoriesAsync = ref.watch(assetCategoriesProvider); @@ -207,46 +417,489 @@ class _AssetFormPanelState extends ConsumerState { AsyncValue> categoriesAsync, AsyncValue> plantsAsync, ) { + final subcategoriesAsync = ref.watch(assetSubcategoriesProvider(_categoryId)); + final lookupsAsync = ref.watch(assetFormLookupsProvider); + final grnItemsAsync = ref.watch(assetGrnItemsProvider(_grnId)); + final depreciationMethods = + lookupsAsync.valueOrNull?.depreciationMethods ?? const []; + return Form( key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - AppTextField( - controller: _nameController, - label: 'Asset Name *', - validator: (v) => Validators.required(v, fieldName: 'Asset name'), - ), - const SizedBox(height: 12), - SidePanelFormRow( - left: categoriesAsync.when( - loading: () => const LinearProgressIndicator(), - error: (_, __) => const Text('Failed to load categories'), - data: (categories) => _categoryDropdown(categories), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SidePanelSection( + title: 'BASIC DETAILS', + children: [ + AppTextField( + isDense: true, + controller: _nameController, + label: 'Asset Name *', + validator: (v) { + final requiredError = + Validators.required(v, fieldName: 'Asset name'); + if (requiredError != null) return requiredError; + return Validators.minLength(v!.trim(), 2, + fieldName: 'Asset name'); + }, + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: categoriesAsync.when( + loading: () => const LinearProgressIndicator(), + error: (_, __) => const Text('Failed to load categories'), + data: (categories) => _categoryDropdown(categories), + ), + right: subcategoriesAsync.when( + loading: () => const LinearProgressIndicator(), + error: (_, __) => const Text('Failed to load subcategories'), + data: (subcategories) => _subcategoryDropdown(subcategories), + ), + ), + const SizedBox(height: 12), + plantsAsync.when( + loading: () => const LinearProgressIndicator(), + error: (_, __) => const Text('Failed to load plants'), + data: (plants) => _plantDropdown(plants), + ), + ], ), - right: plantsAsync.when( - loading: () => const LinearProgressIndicator(), - error: (_, __) => const Text('Failed to load plants'), - data: (plants) => _plantDropdown(plants), + SidePanelSection( + title: 'IDENTIFICATION', + children: [ + SidePanelFormRow( + left: AppTextField( + isDense: true, + controller: _serialController, + label: 'Serial Number', + validator: (v) { + if (v == null || v.trim().isEmpty) return null; + return Validators.minLength( + v.trim(), + 2, + fieldName: 'Serial number', + ); + }, + ), + right: AppTextField( + isDense: true, + controller: _partNumberController, + label: 'Part Number', + validator: (v) { + if (v == null || v.trim().isEmpty) return null; + return Validators.minLength( + v.trim(), + 2, + fieldName: 'Part number', + ); + }, + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppTextField( + isDense: true, + controller: _brandModelController, + label: 'Brand / Model', + validator: (v) { + if (v == null || v.trim().isEmpty) return null; + return Validators.minLength( + v.trim(), + 2, + fieldName: 'Brand / model', + ); + }, + ), + right: AppTextField( + isDense: true, + controller: _manufacturerController, + label: 'Manufacturer', + validator: (v) { + if (v == null || v.trim().isEmpty) return null; + return Validators.minLength( + v.trim(), + 2, + fieldName: 'Manufacturer', + ); + }, + ), + ), + ], ), - ), - SidePanelFormRow( - left: _AssetFormDateField( - label: 'Warranty Expiry Date', - value: _warrantyExpiry, - onPick: _pickWarrantyDate, + lookupsAsync.when( + loading: () => const Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: LinearProgressIndicator(), + ), + error: (_, __) => const Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: Text('Failed to load lookup options'), + ), + data: (lookups) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SidePanelSection( + title: 'LOCATION & ASSIGNMENT', + children: [ + SidePanelFormRow( + left: _optionalLookupDropdown( + label: 'Department', + value: _departmentId, + options: lookups.departments, + onChanged: (v) => setState(() => _departmentId = v), + ), + right: _optionalLookupDropdown( + label: 'Warehouse', + value: _warehouseId, + options: lookups.warehouses, + onChanged: (v) => setState(() => _warehouseId = v), + ), + ), + const SizedBox(height: 12), + AppTextField( + isDense: true, + controller: _locationDetailController, + label: 'Location Detail', + validator: (v) { + if (v == null || v.trim().isEmpty) return null; + return Validators.minLength( + v.trim(), + 2, + fieldName: 'Location detail', + ); + }, + ), + const SizedBox(height: 12), + _optionalLookupDropdown( + label: 'Assigned To', + value: _assignedToUserId, + options: lookups.users, + onChanged: (v) => setState(() => _assignedToUserId = v), + emptyHint: 'Unassigned', + ), + ], + ), + SidePanelSection( + title: 'PROCUREMENT', + children: [ + SidePanelFormRow( + left: _optionalLookupDropdown( + label: 'Vendor', + value: _vendorId, + options: lookups.vendors, + onChanged: (v) => setState(() => _vendorId = v), + ), + right: _optionalLookupDropdown( + label: 'Purchase Order', + value: _poId, + options: lookups.purchaseOrders, + onChanged: (v) => setState(() => _poId = v), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: _optionalLookupDropdown( + label: 'GRN', + value: _grnId, + options: lookups.grns, + onChanged: (v) => setState(() { + _grnId = v; + _grnItemId = null; + }), + ), + right: grnItemsAsync.when( + loading: () => const LinearProgressIndicator(), + error: (_, __) => _optionalLookupDropdown( + label: 'GRN Item', + value: _grnItemId, + options: const [], + onChanged: (v) => setState(() => _grnItemId = v), + enabled: false, + ), + data: (items) => _optionalLookupDropdown( + label: 'GRN Item', + value: _grnItemId, + options: items, + onChanged: (v) => setState(() => _grnItemId = v), + enabled: _grnId != null && items.isNotEmpty, + emptyHint: _grnId == null + ? 'Select GRN first' + : 'None', + required: _grnId != null && items.isNotEmpty, + ), + ), + ), + ], + ), + ], + ), ), - right: AppTextField( - controller: _costController, - label: 'Purchase Cost', - keyboardType: TextInputType.number, + SidePanelSection( + title: 'PURCHASE & DEPRECIATION', + children: [ + SidePanelFormRow( + left: _AssetFormDateField( + label: 'Purchase Date', + value: _purchaseDate, + onPick: () => + _pickDate((d) => _purchaseDate = d, _purchaseDate), + ), + right: _AssetFormDateField( + label: 'Warranty Expiry Date', + value: _warrantyExpiry, + onPick: () => + _pickDate((d) => _warrantyExpiry = d, _warrantyExpiry), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppTextField( + isDense: true, + controller: _costController, + label: 'Purchase Cost', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) => Validators.optionalPositiveDouble( + v, + fieldName: 'Purchase cost', + ), + ), + right: AppTextField( + isDense: true, + controller: _usefulLifeController, + label: 'Useful Life (Years)', + keyboardType: TextInputType.number, + validator: (v) => Validators.optionalPositiveInt( + v, + fieldName: 'Useful life', + ), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppDropdown( + isDense: true, + label: 'Depreciation Method', + value: _depreciationMethod, + options: depreciationMethods + .map((method) => AppDropdownOption( + value: method, + label: method, + )) + .toList(), + enabled: depreciationMethods.isNotEmpty, + onChanged: (v) => setState(() => _depreciationMethod = v), + ), + right: AppTextField( + isDense: true, + controller: _depreciationRateController, + label: 'Depreciation Rate (%)', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) => Validators.optionalPercentage( + v, + fieldName: 'Depreciation rate', + ), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppTextField( + isDense: true, + controller: _salvageValueController, + label: 'Salvage Value', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) => Validators.optionalNonNegativeDouble( + v, + fieldName: 'Salvage value', + ), + ), + right: const SizedBox.shrink(), + ), + ], ), - ), - ], + SidePanelSection( + title: 'STATUS', + children: [ + SidePanelFormRow( + left: AppDropdown( + isDense: true, + label: 'Condition *', + value: _condition, + options: assetConditionOptions + .map((e) => AppDropdownOption(value: e.$1, label: e.$2)) + .toList(), + onChanged: (v) => setState(() => _condition = v), + validator: (v) => v == null ? 'Condition is required' : null, + ), + right: AppDropdown( + isDense: true, + label: 'Status *', + value: _status, + options: assetStatusOptions + .map((e) => AppDropdownOption(value: e.$1, label: e.$2)) + .toList(), + onChanged: (v) => setState(() => _status = v), + validator: (v) => v == null ? 'Status is required' : null, + ), + ), + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Active'), + subtitle: const Text('Inactive assets are hidden from active lists'), + value: _isActive, + onChanged: (value) => setState(() => _isActive = value), + ), + ], + ), + SidePanelSection( + title: 'DISPOSAL', + children: [ + SidePanelFormRow( + left: _AssetFormDateField( + label: 'Disposal Date', + value: _disposalDate, + onPick: () => + _pickDate((d) => _disposalDate = d, _disposalDate), + validator: () { + final isDisposed = + _status == 'DISPOSED' || _status == 'SCRAPPED'; + if (isDisposed && _disposalDate == null) { + return 'Disposal date is required'; + } + return null; + }, + ), + right: AppTextField( + isDense: true, + controller: _disposalValueController, + label: 'Disposal Value', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) => Validators.optionalNonNegativeDouble( + v, + fieldName: 'Disposal value', + ), + ), + ), + const SizedBox(height: 12), + AppTextField( + isDense: true, + controller: _disposalReasonController, + label: 'Disposal Reason', + maxLines: 2, + validator: (v) { + final isDisposed = + _status == 'DISPOSED' || _status == 'SCRAPPED'; + if (isDisposed) { + return Validators.required(v, fieldName: 'Disposal reason'); + } + if (v == null || v.trim().isEmpty) return null; + return Validators.minLength( + v.trim(), + 3, + fieldName: 'Disposal reason', + ); + }, + ), + ], + ), + SidePanelSection( + title: 'OTHER', + children: [ + AppTextField( + isDense: true, + controller: _qrCodeController, + label: 'QR Code Value', + validator: (v) { + if (v == null || v.trim().isEmpty) return null; + return Validators.minLength( + v.trim(), + 2, + fieldName: 'QR code value', + ); + }, + ), + const SizedBox(height: 12), + AppTextField( + isDense: true, + controller: _remarksController, + label: 'Remarks', + maxLines: 2, + validator: (v) { + if (v == null || v.trim().isEmpty) return null; + return Validators.minLength( + v.trim(), + 2, + fieldName: 'Remarks', + ); + }, + ), + ], + ), + ], + ), ), ); } + Widget _optionalLookupDropdown({ + required String label, + required int? value, + required List options, + required ValueChanged onChanged, + String? emptyHint, + bool enabled = true, + bool required = false, + }) { + final dropdownOptions = >[ + AppDropdownOption(value: null, label: emptyHint ?? 'None'), + ...options + .map((option) { + final id = int.tryParse(option.id); + if (id == null || id <= 0) return null; + return AppDropdownOption(value: id, label: option.name); + }) + .whereType>(), + ]; + final validIds = dropdownOptions + .map((option) => option.value) + .whereType() + .toList(); + + return AppSearchableDropdown( + label: required ? '$label *' : label, + value: value != null && validIds.contains(value) ? value : null, + hint: emptyHint ?? 'None', + searchHint: 'Search ${label.toLowerCase()}...', + isDense: true, + enabled: enabled && options.isNotEmpty, + options: dropdownOptions, + onChanged: onChanged, + validator: required ? (v) => v == null ? '$label is required' : null : null, + ); + } + + Widget _subcategoryDropdown(List subcategories) { + final ids = subcategories.map((c) => int.tryParse(c.id)).whereType().toList(); + return AppSearchableDropdown( + label: 'Subcategory *', + value: _dropdownValue(_subcategoryId, ids), + searchHint: 'Search subcategory...', + isDense: true, + options: subcategories + .map( + (c) => AppDropdownOption( + value: int.tryParse(c.id) ?? 0, + label: c.name, + ), + ) + .where((option) => option.value != 0) + .toList(), + onChanged: (v) => setState(() => _subcategoryId = v), + validator: (v) => v == null ? 'Subcategory is required' : null, + ); + } + Widget _categoryDropdown(List categories) { final categoryIds = categories .map((c) => int.tryParse(c.id)) @@ -256,6 +909,7 @@ class _AssetFormPanelState extends ConsumerState { label: 'Category *', value: _dropdownValue(_categoryId, categoryIds), searchHint: 'Search category...', + isDense: true, options: categories .map( (c) => AppDropdownOption( @@ -265,7 +919,10 @@ class _AssetFormPanelState extends ConsumerState { ) .where((option) => option.value != 0) .toList(), - onChanged: (v) => setState(() => _categoryId = v), + onChanged: (v) => setState(() { + _categoryId = v; + _subcategoryId = null; + }), validator: (v) => v == null ? 'Category is required' : null, ); } @@ -279,6 +936,7 @@ class _AssetFormPanelState extends ConsumerState { label: 'Plant *', value: _dropdownValue(_plantId, plantIds), searchHint: 'Search plant...', + isDense: true, options: plants .map( (p) => AppDropdownOption( @@ -294,30 +952,67 @@ class _AssetFormPanelState extends ConsumerState { } } -class _AssetFormDateField extends StatelessWidget { +class _AssetFormDateField extends StatefulWidget { const _AssetFormDateField({ required this.label, required this.value, required this.onPick, + this.validator, }); final String label; final DateTime? value; final VoidCallback onPick; + final String? Function()? validator; + + @override + State<_AssetFormDateField> createState() => _AssetFormDateFieldState(); +} + +class _AssetFormDateFieldState extends State<_AssetFormDateField> { + late final TextEditingController _controller; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(text: _displayText); + } + + @override + void didUpdateWidget(covariant _AssetFormDateField oldWidget) { + super.didUpdateWidget(oldWidget); + final text = _displayText; + if (_controller.text != text) { + _controller.text = text; + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + String get _displayText => + widget.value != null ? DateFormatter.displayDate(widget.value) : ''; @override Widget build(BuildContext context) { - final displayText = value != null ? DateFormatter.displayDate(value) : ''; - - return TextFormField( - readOnly: true, - onTap: onPick, - decoration: InputDecoration( - labelText: label, - hintText: 'Select date', - suffixIcon: const Icon(Icons.calendar_today_outlined, size: 20), + return Padding( + padding: const EdgeInsets.only(top: 8), + child: TextFormField( + readOnly: true, + onTap: widget.onPick, + controller: _controller, + validator: (_) => widget.validator?.call(), + decoration: InputDecoration( + labelText: widget.label, + hintText: 'Select date', + floatingLabelBehavior: FloatingLabelBehavior.always, + isDense: true, + suffixIcon: const Icon(Icons.calendar_today_outlined, size: 20), + ), ), - controller: TextEditingController(text: displayText), ); } } @@ -326,3 +1021,10 @@ final assetPlantsProvider = FutureProvider>((ref) async final dataSource = ref.watch(masterRemoteDataSourceProvider); return dataSource.listPlants(); }); + +final assetSubcategoriesProvider = + FutureProvider.family, int?>((ref, categoryId) async { + if (categoryId == null) return []; + final dataSource = ref.watch(masterRemoteDataSourceProvider); + return dataSource.listAssetSubcategories(assetCategoryId: categoryId); +}); diff --git a/lib/modules/assets/presentation/widgets/asset_side_panels.dart b/lib/modules/assets/presentation/widgets/asset_side_panels.dart index c010675..51f1686 100644 --- a/lib/modules/assets/presentation/widgets/asset_side_panels.dart +++ b/lib/modules/assets/presentation/widgets/asset_side_panels.dart @@ -3,17 +3,26 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/utils/formatters.dart'; import '../../../../core/utils/validators.dart'; +import '../../../../shared/models/asset_model.dart'; import '../../../../shared/widgets/app_button.dart'; import '../../../../shared/widgets/app_dropdown.dart'; import '../../../../shared/widgets/app_searchable_dropdown.dart'; import '../../../../shared/widgets/app_side_panel.dart'; import '../../../../shared/widgets/app_text_field.dart'; +import '../providers/asset_form_lookups_provider.dart'; import '../providers/assets_provider.dart'; class AddAmcPanel extends ConsumerStatefulWidget { - const AddAmcPanel({super.key, required this.assetId}); + const AddAmcPanel({ + super.key, + required this.assetId, + this.contractId, + }); final String assetId; + final String? contractId; + + bool get isEdit => contractId != null; @override ConsumerState createState() => _AddAmcPanelState(); @@ -21,81 +30,360 @@ class AddAmcPanel extends ConsumerStatefulWidget { class _AddAmcPanelState extends ConsumerState { final _formKey = GlobalKey(); - final _vendorIdController = TextEditingController(); final _contractNoController = TextEditingController(); final _annualCostController = TextEditingController(); + final _visitsPerYearController = TextEditingController(); + final _contactPersonController = TextEditingController(); + final _contactPhoneController = TextEditingController(); + final _contactEmailController = TextEditingController(); + final _scopeOfWorkController = TextEditingController(); + final _exclusionsController = TextEditingController(); + final _remarksController = TextEditingController(); DateTime? _startDate; DateTime? _endDate; + DateTime? _renewalDate; + int? _vendorId; String _contractType = 'COMPREHENSIVE'; + String? _paymentFrequency = 'MONTHLY'; + String? _serviceFrequency = 'MONTHLY'; + bool _isActive = true; bool _isSubmitting = false; + String? _populatedContractId; @override void dispose() { - _vendorIdController.dispose(); _contractNoController.dispose(); _annualCostController.dispose(); + _visitsPerYearController.dispose(); + _contactPersonController.dispose(); + _contactPhoneController.dispose(); + _contactEmailController.dispose(); + _scopeOfWorkController.dispose(); + _exclusionsController.dispose(); + _remarksController.dispose(); super.dispose(); } - Future _pickDate({required bool isStart}) async { + void _populateFromContract(AmcContractModel contract) { + if (_populatedContractId == contract.id) return; + _populatedContractId = contract.id; + + _vendorId = contract.vendorId; + _contractNoController.text = contract.contractNo ?? ''; + _contractType = contract.contractType ?? 'COMPREHENSIVE'; + _startDate = contract.startDate; + _endDate = contract.endDate; + _renewalDate = contract.renewalDate; + if (contract.annualCost != null) { + _annualCostController.text = contract.annualCost.toString(); + } + _paymentFrequency = contract.paymentFrequency ?? 'MONTHLY'; + _serviceFrequency = contract.serviceFrequency ?? 'MONTHLY'; + if (contract.visitsPerYear != null) { + _visitsPerYearController.text = contract.visitsPerYear.toString(); + } + _contactPersonController.text = contract.contactPerson ?? ''; + _contactPhoneController.text = contract.contactPhone ?? ''; + _contactEmailController.text = contract.contactEmail ?? ''; + _scopeOfWorkController.text = contract.scopeOfWork ?? ''; + _exclusionsController.text = contract.exclusions ?? ''; + _remarksController.text = contract.remarks ?? ''; + _isActive = contract.isActive; + } + + Map _buildPayload() { + final annualCost = double.tryParse(_annualCostController.text.trim()); + final visitsPerYear = int.tryParse(_visitsPerYearController.text.trim()); + return { + 'vendor_id': _vendorId, + if (_contractNoController.text.isNotEmpty) + 'contract_no': _contractNoController.text.trim(), + 'contract_type': _contractType, + 'start_date': DateFormatter.toApiDate(_startDate!), + 'end_date': DateFormatter.toApiDate(_endDate!), + if (_renewalDate != null) 'renewal_date': DateFormatter.toApiDate(_renewalDate!), + if (annualCost != null) 'annual_cost': annualCost, + if (_paymentFrequency != null && _paymentFrequency!.trim().isNotEmpty) + 'payment_frequency': _paymentFrequency, + if (_serviceFrequency != null && _serviceFrequency!.trim().isNotEmpty) + 'service_frequency': _serviceFrequency, + if (visitsPerYear != null) 'visits_per_year': visitsPerYear, + if (_contactPersonController.text.trim().isNotEmpty) + 'contact_person': _contactPersonController.text.trim(), + if (_contactPhoneController.text.trim().isNotEmpty) + 'contact_phone': _contactPhoneController.text.trim(), + if (_contactEmailController.text.trim().isNotEmpty) + 'contact_email': _contactEmailController.text.trim(), + if (_scopeOfWorkController.text.trim().isNotEmpty) + 'scope_of_work': _scopeOfWorkController.text.trim(), + if (_exclusionsController.text.trim().isNotEmpty) + 'exclusions': _exclusionsController.text.trim(), + if (_remarksController.text.trim().isNotEmpty) + 'remarks': _remarksController.text.trim(), + 'is_active': _isActive, + }; + } + + Future _pickDate({ + required DateTime? current, + required void Function(DateTime date) onPicked, + }) async { final picked = await showDatePicker( context: context, - initialDate: (isStart ? _startDate : _endDate) ?? DateTime.now(), + initialDate: current ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); if (picked != null) { - setState(() { - if (isStart) { - _startDate = picked; - } else { - _endDate = picked; - } - }); + setState(() => onPicked(picked)); } } Future _save() async { if (!_formKey.currentState!.validate()) return; if (_startDate == null || _endDate == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please select start and end dates')), - ); + showSidePanelSnackBar(context, 'Please select start and end dates'); return; } - final vendorId = int.tryParse(_vendorIdController.text.trim()); - if (vendorId == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please enter a valid vendor ID')), - ); + if (_vendorId == null) { + showSidePanelSnackBar(context, 'Please select a vendor'); return; } setState(() => _isSubmitting = true); try { - await ref.read(assetDetailProvider(widget.assetId).notifier).createAmc({ - 'vendor_id': vendorId, - if (_contractNoController.text.isNotEmpty) - 'contract_no': _contractNoController.text.trim(), - 'contract_type': _contractType, - 'start_date': DateFormatter.toApiDate(_startDate!), - 'end_date': DateFormatter.toApiDate(_endDate!), - if (_annualCostController.text.isNotEmpty) - 'annual_cost': double.tryParse(_annualCostController.text), - }); + final payload = _buildPayload(); + final notifier = ref.read(assetDetailProvider(widget.assetId).notifier); + if (widget.isEdit) { + await notifier.updateAmc(widget.contractId!, payload); + } else { + await notifier.createAmc(payload); + } if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString()))); + showSidePanelSnackBar(context, e.toString()); } } finally { if (mounted) setState(() => _isSubmitting = false); } } + Widget _buildForm() { + final vendorOptionsAsync = ref.watch(assetFormLookupsProvider); + return Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SidePanelFormRow( + left: vendorOptionsAsync.when( + loading: () => const LinearProgressIndicator(), + error: (_, __) => const Text('Failed to load vendors'), + data: (lookups) => AppSearchableDropdown( + label: 'Vendor *', + value: _vendorId, + searchHint: 'Search vendor...', + options: lookups.vendors + .map((vendor) => AppDropdownOption( + value: int.tryParse(vendor.id) ?? 0, + label: vendor.name, + )) + .where((option) => option.value != 0) + .toList(), + onChanged: (v) => setState(() => _vendorId = v), + validator: (v) => v == null ? 'Vendor is required' : null, + ), + ), + right: AppTextField( + controller: _contractNoController, + label: 'Contract No', + ), + ), + AppSearchableDropdown( + label: 'Contract Type', + value: _contractType, + searchHint: 'Search contract type...', + options: stringDropdownOptions(const [ + 'COMPREHENSIVE', + 'LABOUR_ONLY', + 'PARTS_ONLY', + 'PREVENTIVE_ONLY', + ]), + onChanged: (v) { + if (v != null) setState(() => _contractType = v); + }, + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: _SidePanelDateField( + label: 'Start Date', + isRequired: true, + value: _startDate, + onPick: () => _pickDate( + current: _startDate, + onPicked: (date) => setState(() => _startDate = date), + ), + ), + right: _SidePanelDateField( + label: 'End Date', + isRequired: true, + value: _endDate, + onPick: () => _pickDate( + current: _endDate, + onPicked: (date) => setState(() => _endDate = date), + ), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: _SidePanelDateField( + label: 'Renewal Date', + value: _renewalDate, + onPick: () => _pickDate( + current: _renewalDate, + onPicked: (date) => setState(() => _renewalDate = date), + ), + ), + right: AppTextField( + controller: _annualCostController, + label: 'Annual Cost', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppSearchableDropdown( + label: 'Payment Frequency', + value: _paymentFrequency, + searchHint: 'Search payment frequency...', + options: stringDropdownOptions(const [ + 'DAILY', + 'WEEKLY', + 'MONTHLY', + 'QUARTERLY', + 'HALF_YEARLY', + 'YEARLY', + ]), + onChanged: (v) => setState(() => _paymentFrequency = v), + ), + right: AppSearchableDropdown( + label: 'Service Frequency', + value: _serviceFrequency, + searchHint: 'Search service frequency...', + options: stringDropdownOptions(const [ + 'DAILY', + 'WEEKLY', + 'MONTHLY', + 'QUARTERLY', + 'HALF_YEARLY', + 'YEARLY', + ]), + onChanged: (v) => setState(() => _serviceFrequency = v), + ), + ), + const SizedBox(height: 12), + AppTextField( + controller: _visitsPerYearController, + label: 'Visits Per Year', + keyboardType: TextInputType.number, + validator: (v) => Validators.optionalPositiveInt( + v, + fieldName: 'Visits per year', + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppTextField( + controller: _contactPersonController, + label: 'Contact Person', + ), + right: AppTextField( + controller: _contactPhoneController, + label: 'Contact Phone', + keyboardType: TextInputType.phone, + validator: Validators.optionalMobile, + inputFormatters: Validators.mobileInput, + ), + ), + const SizedBox(height: 12), + AppTextField( + controller: _contactEmailController, + label: 'Contact Email', + keyboardType: TextInputType.emailAddress, + validator: Validators.optionalEmail, + ), + const SizedBox(height: 12), + AppTextField( + controller: _scopeOfWorkController, + label: 'Scope Of Work', + maxLines: 3, + ), + const SizedBox(height: 12), + AppTextField( + controller: _exclusionsController, + label: 'Exclusions', + maxLines: 3, + ), + const SizedBox(height: 12), + AppTextField( + controller: _remarksController, + label: 'Remarks', + maxLines: 3, + ), + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Active'), + value: _isActive, + onChanged: (value) => setState(() => _isActive = value), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { + if (widget.isEdit) { + final contractAsync = ref.watch( + amcContractFormProvider(( + assetId: widget.assetId, + contractId: widget.contractId!, + )), + ); + return contractAsync.when( + loading: () => SidePanelScaffold( + title: 'Edit AMC Contract', + footer: _panelFooter( + context, + isSubmitting: true, + saveLabel: 'Update contract', + onSave: () {}, + ), + child: const Center(child: CircularProgressIndicator()), + ), + error: (error, _) => SidePanelScaffold( + title: 'Edit AMC Contract', + child: Center(child: Text(error.toString())), + ), + data: (contract) { + _populateFromContract(contract); + return SidePanelScaffold( + title: 'Edit AMC Contract', + footer: _panelFooter( + context, + isSubmitting: _isSubmitting, + saveLabel: 'Update contract', + onSave: _save, + ), + child: _buildForm(), + ); + }, + ); + } + return SidePanelScaffold( title: 'Add AMC Contract', footer: _panelFooter( @@ -104,68 +392,24 @@ class _AddAmcPanelState extends ConsumerState { saveLabel: 'Save contract', onSave: _save, ), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SidePanelFormRow( - left: AppTextField( - controller: _vendorIdController, - label: 'Vendor ID *', - keyboardType: TextInputType.number, - validator: (v) => Validators.required(v, fieldName: 'Vendor ID'), - ), - right: AppTextField( - controller: _contractNoController, - label: 'Contract No', - ), - ), - AppSearchableDropdown( - label: 'Contract Type', - value: _contractType, - searchHint: 'Search contract type...', - options: stringDropdownOptions(const [ - 'COMPREHENSIVE', - 'LABOUR_ONLY', - 'PARTS_ONLY', - 'PREVENTIVE_ONLY', - ]), - onChanged: (v) { - if (v != null) setState(() => _contractType = v); - }, - ), - const SizedBox(height: 12), - SidePanelFormRow( - left: _SidePanelDateField( - label: 'Start Date', - isRequired: true, - value: _startDate, - onPick: () => _pickDate(isStart: true), - ), - right: _SidePanelDateField( - label: 'End Date', - isRequired: true, - value: _endDate, - onPick: () => _pickDate(isStart: false), - ), - ), - AppTextField( - controller: _annualCostController, - label: 'Annual Cost', - keyboardType: TextInputType.number, - ), - ], - ), - ), + child: _buildForm(), ); } } class LogServiceVisitPanel extends ConsumerStatefulWidget { - const LogServiceVisitPanel({super.key, required this.assetId}); + const LogServiceVisitPanel({ + super.key, + required this.assetId, + this.visitId, + this.amcContracts = const [], + }); final String assetId; + final String? visitId; + final List amcContracts; + + bool get isEdit => visitId != null; @override ConsumerState createState() => _LogServiceVisitPanelState(); @@ -173,57 +417,399 @@ class LogServiceVisitPanel extends ConsumerStatefulWidget { class _LogServiceVisitPanelState extends ConsumerState { final _formKey = GlobalKey(); + final _visitNumberController = TextEditingController(); + final _complaintNoController = TextEditingController(); + final _complaintDescController = TextEditingController(); + final _engineerNameController = TextEditingController(); + final _engineerPhoneController = TextEditingController(); final _workDoneController = TextEditingController(); + final _partsReplacedController = TextEditingController(); + final _downtimeHoursController = TextEditingController(); + final _serviceCostController = TextEditingController(); + final _remarksController = TextEditingController(); DateTime? _visitDate = DateTime.now(); + DateTime? _complaintDate; + DateTime? _nextServiceDate; + int? _amcContractId; + int? _vendorId; String _visitType = 'PREVENTIVE'; String _status = 'COMPLETED'; + String? _assetConditionAfter = 'GOOD'; + bool _isUnderAmc = false; bool _isSubmitting = false; + String? _populatedVisitId; @override void dispose() { + _visitNumberController.dispose(); + _complaintNoController.dispose(); + _complaintDescController.dispose(); + _engineerNameController.dispose(); + _engineerPhoneController.dispose(); _workDoneController.dispose(); + _partsReplacedController.dispose(); + _downtimeHoursController.dispose(); + _serviceCostController.dispose(); + _remarksController.dispose(); super.dispose(); } - Future _pickVisitDate() async { + void _populateFromVisit(ServiceVisitModel visit) { + if (_populatedVisitId == visit.id) return; + _populatedVisitId = visit.id; + + _visitType = visit.visitType; + _visitDate = visit.visitDate; + _amcContractId = visit.amcContractId; + if (visit.visitNumber != null) { + _visitNumberController.text = visit.visitNumber.toString(); + } + _complaintNoController.text = visit.complaintNo ?? ''; + _complaintDate = visit.complaintDate; + _complaintDescController.text = visit.complaintDesc ?? ''; + _engineerNameController.text = visit.engineerName ?? ''; + _engineerPhoneController.text = visit.engineerPhone ?? ''; + _vendorId = visit.vendorId; + _workDoneController.text = visit.workDone ?? ''; + _partsReplacedController.text = visit.partsReplaced ?? ''; + _nextServiceDate = visit.nextServiceDate; + _status = visit.status; + if (visit.downtimeHours != null) { + _downtimeHoursController.text = visit.downtimeHours.toString(); + } + if (visit.serviceCost != null) { + _serviceCostController.text = visit.serviceCost.toString(); + } + _isUnderAmc = visit.isUnderAmc; + _assetConditionAfter = visit.assetConditionAfter ?? 'GOOD'; + _remarksController.text = visit.remarks ?? ''; + } + + Future _pickDate({ + required DateTime? current, + required void Function(DateTime date) onPicked, + }) async { final picked = await showDatePicker( context: context, - initialDate: _visitDate ?? DateTime.now(), + initialDate: current ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); - if (picked != null) setState(() => _visitDate = picked); + if (picked != null) { + setState(() => onPicked(picked)); + } + } + + Map _buildPayload() { + final visitNumber = int.tryParse(_visitNumberController.text.trim()); + final downtimeHours = double.tryParse(_downtimeHoursController.text.trim()); + final serviceCost = double.tryParse(_serviceCostController.text.trim()); + return { + 'visit_type': _visitType, + 'visit_date': DateFormatter.toApiDate(_visitDate!), + if (_amcContractId != null) 'amc_contract_id': _amcContractId, + if (visitNumber != null) 'visit_number': visitNumber, + if (_complaintNoController.text.trim().isNotEmpty) + 'complaint_no': _complaintNoController.text.trim(), + if (_complaintDate != null) + 'complaint_date': DateFormatter.toApiDate(_complaintDate!), + if (_complaintDescController.text.trim().isNotEmpty) + 'complaint_desc': _complaintDescController.text.trim(), + if (_engineerNameController.text.trim().isNotEmpty) + 'engineer_name': _engineerNameController.text.trim(), + if (_engineerPhoneController.text.trim().isNotEmpty) + 'engineer_phone': _engineerPhoneController.text.trim(), + if (_vendorId != null) 'vendor_id': _vendorId, + if (_workDoneController.text.trim().isNotEmpty) + 'work_done': _workDoneController.text.trim(), + if (_partsReplacedController.text.trim().isNotEmpty) + 'parts_replaced': _partsReplacedController.text.trim(), + if (_nextServiceDate != null) + 'next_service_date': DateFormatter.toApiDate(_nextServiceDate!), + 'status': _status, + if (downtimeHours != null) 'downtime_hours': downtimeHours, + if (serviceCost != null) 'service_cost': serviceCost, + 'is_under_amc': _isUnderAmc, + if (_assetConditionAfter != null && _assetConditionAfter!.trim().isNotEmpty) + 'asset_condition_after': _assetConditionAfter, + if (_remarksController.text.trim().isNotEmpty) + 'remarks': _remarksController.text.trim(), + }; } Future _save() async { + if (!_formKey.currentState!.validate()) return; if (_visitDate == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please select visit date')), - ); + showSidePanelSnackBar(context, 'Please select visit date'); return; } setState(() => _isSubmitting = true); try { - await ref.read(assetDetailProvider(widget.assetId).notifier).logVisit({ - 'visit_type': _visitType, - 'visit_date': DateFormatter.toApiDate(_visitDate!), - if (_workDoneController.text.isNotEmpty) - 'work_done': _workDoneController.text.trim(), - 'status': _status, - }); + final payload = _buildPayload(); + final notifier = ref.read(assetDetailProvider(widget.assetId).notifier); + if (widget.isEdit) { + await notifier.updateVisit(widget.visitId!, payload); + } else { + await notifier.logVisit(payload); + } if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString()))); + showSidePanelSnackBar(context, e.toString()); } } finally { if (mounted) setState(() => _isSubmitting = false); } } + Widget _buildForm(List amcContracts) { + final vendorOptionsAsync = ref.watch(assetFormLookupsProvider); + return Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SidePanelFormRow( + left: AppSearchableDropdown( + label: 'Visit Type *', + value: _visitType, + searchHint: 'Search visit type...', + options: stringDropdownOptions(const [ + 'PREVENTIVE', + 'BREAKDOWN', + 'INSPECTION', + 'INSTALLATION', + 'CALIBRATION', + 'OTHER', + ]), + onChanged: (v) { + if (v != null) setState(() => _visitType = v); + }, + ), + right: AppSearchableDropdown( + label: 'Status', + value: _status, + searchHint: 'Search status...', + options: stringDropdownOptions(const [ + 'SCHEDULED', + 'IN_PROGRESS', + 'COMPLETED', + 'CANCELLED', + 'PENDING_PARTS', + ]), + onChanged: (v) { + if (v != null) setState(() => _status = v); + }, + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: _SidePanelDateField( + label: 'Visit Date', + isRequired: true, + value: _visitDate, + onPick: () => _pickDate( + current: _visitDate, + onPicked: (date) => setState(() => _visitDate = date), + ), + ), + right: AppTextField( + controller: _visitNumberController, + label: 'Visit Number', + keyboardType: TextInputType.number, + validator: (v) => Validators.optionalPositiveInt( + v, + fieldName: 'Visit number', + ), + ), + ), + const SizedBox(height: 12), + AppSearchableDropdown( + label: 'AMC Contract', + value: _amcContractId, + searchHint: 'Search AMC contract...', + options: amcContracts + .map((contract) { + final id = int.tryParse(contract.id); + if (id == null) return null; + final label = contract.contractNo?.trim().isNotEmpty == true + ? contract.contractNo! + : 'AMC #${contract.id}'; + return AppDropdownOption(value: id, label: label); + }) + .whereType>() + .toList(), + onChanged: (v) => setState(() => _amcContractId = v), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: vendorOptionsAsync.when( + loading: () => const LinearProgressIndicator(), + error: (_, __) => const Text('Failed to load vendors'), + data: (lookups) => AppSearchableDropdown( + label: 'Vendor', + value: _vendorId, + searchHint: 'Search vendor...', + options: lookups.vendors + .map((vendor) => AppDropdownOption( + value: int.tryParse(vendor.id) ?? 0, + label: vendor.name, + )) + .where((option) => option.value != 0) + .toList(), + onChanged: (v) => setState(() => _vendorId = v), + ), + ), + right: AppSearchableDropdown( + label: 'Asset Condition After', + value: _assetConditionAfter, + searchHint: 'Search condition...', + options: stringDropdownOptions(const [ + 'GOOD', + 'FAIR', + 'POOR', + 'DAMAGED', + 'NEEDS_REPAIR', + ]), + onChanged: (v) => setState(() => _assetConditionAfter = v), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppTextField( + controller: _complaintNoController, + label: 'Complaint No', + ), + right: _SidePanelDateField( + label: 'Complaint Date', + value: _complaintDate, + onPick: () => _pickDate( + current: _complaintDate, + onPicked: (date) => setState(() => _complaintDate = date), + ), + ), + ), + const SizedBox(height: 12), + AppTextField( + controller: _complaintDescController, + label: 'Complaint Description', + maxLines: 3, + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppTextField( + controller: _engineerNameController, + label: 'Engineer Name', + ), + right: AppTextField( + controller: _engineerPhoneController, + label: 'Engineer Phone', + keyboardType: TextInputType.phone, + validator: Validators.optionalMobile, + inputFormatters: Validators.mobileInput, + ), + ), + const SizedBox(height: 12), + AppTextField( + controller: _workDoneController, + label: 'Work Done', + maxLines: 3, + ), + const SizedBox(height: 12), + AppTextField( + controller: _partsReplacedController, + label: 'Parts Replaced', + maxLines: 3, + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: _SidePanelDateField( + label: 'Next Service Date', + value: _nextServiceDate, + onPick: () => _pickDate( + current: _nextServiceDate, + onPicked: (date) => setState(() => _nextServiceDate = date), + ), + ), + right: AppTextField( + controller: _downtimeHoursController, + label: 'Downtime Hours', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) => Validators.optionalPositiveDouble( + v, + fieldName: 'Downtime hours', + ), + ), + ), + const SizedBox(height: 12), + AppTextField( + controller: _serviceCostController, + label: 'Service Cost', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) => Validators.optionalPositiveDouble( + v, + fieldName: 'Service cost', + ), + ), + const SizedBox(height: 12), + AppTextField( + controller: _remarksController, + label: 'Remarks', + maxLines: 3, + ), + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Under AMC'), + value: _isUnderAmc, + onChanged: (value) => setState(() => _isUnderAmc = value), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { + if (widget.isEdit) { + final visitAsync = ref.watch( + serviceVisitFormProvider(( + assetId: widget.assetId, + visitId: widget.visitId!, + )), + ); + return visitAsync.when( + loading: () => SidePanelScaffold( + title: 'Edit Service Visit', + footer: _panelFooter( + context, + isSubmitting: true, + saveLabel: 'Update visit', + onSave: () {}, + ), + child: const Center(child: CircularProgressIndicator()), + ), + error: (error, _) => SidePanelScaffold( + title: 'Edit Service Visit', + child: Center(child: Text(error.toString())), + ), + data: (visit) { + _populateFromVisit(visit); + return SidePanelScaffold( + title: 'Edit Service Visit', + footer: _panelFooter( + context, + isSubmitting: _isSubmitting, + saveLabel: 'Update visit', + onSave: _save, + ), + child: _buildForm(widget.amcContracts), + ); + }, + ); + } + return SidePanelScaffold( title: 'Log Service Visit', footer: _panelFooter( @@ -232,67 +818,22 @@ class _LogServiceVisitPanelState extends ConsumerState { saveLabel: 'Save visit', onSave: _save, ), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SidePanelFormRow( - left: AppSearchableDropdown( - label: 'Visit Type *', - value: _visitType, - searchHint: 'Search visit type...', - options: stringDropdownOptions(const [ - 'PREVENTIVE', - 'BREAKDOWN', - 'INSPECTION', - 'INSTALLATION', - 'CALIBRATION', - 'OTHER', - ]), - onChanged: (v) { - if (v != null) setState(() => _visitType = v); - }, - ), - right: AppSearchableDropdown( - label: 'Status', - value: _status, - searchHint: 'Search status...', - options: stringDropdownOptions(const [ - 'SCHEDULED', - 'IN_PROGRESS', - 'COMPLETED', - 'CANCELLED', - 'PENDING_PARTS', - ]), - onChanged: (v) { - if (v != null) setState(() => _status = v); - }, - ), - ), - _SidePanelDateField( - label: 'Visit Date', - isRequired: true, - value: _visitDate, - onPick: _pickVisitDate, - ), - const SizedBox(height: 12), - AppTextField( - controller: _workDoneController, - label: 'Work Done', - maxLines: 4, - ), - ], - ), - ), + child: _buildForm(widget.amcContracts), ); } } class AddInsurancePanel extends ConsumerStatefulWidget { - const AddInsurancePanel({super.key, required this.assetId}); + const AddInsurancePanel({ + super.key, + required this.assetId, + this.policyId, + }); final String assetId; + final String? policyId; + + bool get isEdit => policyId != null; @override ConsumerState createState() => _AddInsurancePanelState(); @@ -302,70 +843,325 @@ class _AddInsurancePanelState extends ConsumerState { final _formKey = GlobalKey(); final _policyNoController = TextEditingController(); final _insurerNameController = TextEditingController(); + final _insurerBranchController = TextEditingController(); + final _insurerContactController = TextEditingController(); + final _insurerPhoneController = TextEditingController(); + final _insurerEmailController = TextEditingController(); final _sumInsuredController = TextEditingController(); + final _annualPremiumController = TextEditingController(); + final _remarksController = TextEditingController(); DateTime? _startDate; DateTime? _endDate; + DateTime? _renewalDate; + DateTime? _premiumPaidDate; String _policyType = 'COMPREHENSIVE'; + bool _isAutoRenewal = false; + bool _premiumPaid = false; + bool _isActive = true; bool _isSubmitting = false; + String? _populatedPolicyId; @override void dispose() { _policyNoController.dispose(); _insurerNameController.dispose(); + _insurerBranchController.dispose(); + _insurerContactController.dispose(); + _insurerPhoneController.dispose(); + _insurerEmailController.dispose(); _sumInsuredController.dispose(); + _annualPremiumController.dispose(); + _remarksController.dispose(); super.dispose(); } - Future _pickDate({required bool isStart}) async { + void _populateFromPolicy(InsurancePolicyModel policy) { + if (_populatedPolicyId == policy.id) return; + _populatedPolicyId = policy.id; + + _policyNoController.text = policy.policyNo; + _insurerNameController.text = policy.insurerName; + _insurerBranchController.text = policy.insurerBranch ?? ''; + _insurerContactController.text = policy.insurerContact ?? ''; + _insurerPhoneController.text = policy.insurerPhone ?? ''; + _insurerEmailController.text = policy.insurerEmail ?? ''; + _policyType = policy.policyType ?? 'COMPREHENSIVE'; + if (policy.sumInsured != null) { + _sumInsuredController.text = policy.sumInsured.toString(); + } + if (policy.annualPremium != null) { + _annualPremiumController.text = policy.annualPremium.toString(); + } + _startDate = policy.policyStartDate; + _endDate = policy.policyEndDate; + _renewalDate = policy.renewalDate; + _isAutoRenewal = policy.isAutoRenewal; + _premiumPaid = policy.premiumPaid; + _premiumPaidDate = policy.premiumPaidDate; + _remarksController.text = policy.remarks ?? ''; + _isActive = policy.isActive; + } + + Future _pickDate({ + required DateTime? current, + required void Function(DateTime date) onPicked, + }) async { final picked = await showDatePicker( context: context, - initialDate: (isStart ? _startDate : _endDate) ?? DateTime.now(), + initialDate: current ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); if (picked != null) { - setState(() { - if (isStart) { - _startDate = picked; - } else { - _endDate = picked; - } - }); + setState(() => onPicked(picked)); } } + Map _buildPayload() { + final sumInsured = double.tryParse(_sumInsuredController.text.trim()); + final annualPremium = double.tryParse(_annualPremiumController.text.trim()); + return { + 'policy_no': _policyNoController.text.trim(), + 'insurer_name': _insurerNameController.text.trim(), + if (_insurerBranchController.text.trim().isNotEmpty) + 'insurer_branch': _insurerBranchController.text.trim(), + if (_insurerContactController.text.trim().isNotEmpty) + 'insurer_contact': _insurerContactController.text.trim(), + if (_insurerPhoneController.text.trim().isNotEmpty) + 'insurer_phone': _insurerPhoneController.text.trim(), + if (_insurerEmailController.text.trim().isNotEmpty) + 'insurer_email': _insurerEmailController.text.trim(), + 'policy_type': _policyType, + if (sumInsured != null) 'sum_insured': sumInsured, + if (annualPremium != null) 'annual_premium': annualPremium, + 'policy_start_date': DateFormatter.toApiDate(_startDate!), + 'policy_end_date': DateFormatter.toApiDate(_endDate!), + if (_renewalDate != null) 'renewal_date': DateFormatter.toApiDate(_renewalDate!), + 'is_auto_renewal': _isAutoRenewal, + 'premium_paid': _premiumPaid, + if (_premiumPaidDate != null) + 'premium_paid_date': DateFormatter.toApiDate(_premiumPaidDate!), + if (_remarksController.text.trim().isNotEmpty) 'remarks': _remarksController.text.trim(), + 'is_active': _isActive, + }; + } + Future _save() async { if (!_formKey.currentState!.validate()) return; if (_startDate == null || _endDate == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please select start and end dates')), - ); + showSidePanelSnackBar(context, 'Please select start and end dates'); return; } setState(() => _isSubmitting = true); try { - await ref.read(assetDetailProvider(widget.assetId).notifier).createInsurance({ - 'policy_no': _policyNoController.text.trim(), - 'insurer_name': _insurerNameController.text.trim(), - 'policy_type': _policyType, - if (_sumInsuredController.text.isNotEmpty) - 'sum_insured': double.tryParse(_sumInsuredController.text), - 'policy_start_date': DateFormatter.toApiDate(_startDate!), - 'policy_end_date': DateFormatter.toApiDate(_endDate!), - }); + final payload = _buildPayload(); + final notifier = ref.read(assetDetailProvider(widget.assetId).notifier); + if (widget.isEdit) { + await notifier.updateInsurance(widget.policyId!, payload); + } else { + await notifier.createInsurance(payload); + } if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString()))); + showSidePanelSnackBar(context, e.toString()); } } finally { if (mounted) setState(() => _isSubmitting = false); } } + Widget _buildForm() { + return Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SidePanelFormRow( + left: AppTextField( + controller: _policyNoController, + label: 'Policy No *', + validator: (v) => Validators.required(v, fieldName: 'Policy no'), + ), + right: AppTextField( + controller: _insurerNameController, + label: 'Insurer Name *', + validator: (v) => Validators.required(v, fieldName: 'Insurer name'), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppTextField( + controller: _insurerBranchController, + label: 'Insurer Branch', + ), + right: AppTextField( + controller: _insurerContactController, + label: 'Insurer Contact', + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppTextField( + controller: _insurerPhoneController, + label: 'Insurer Phone', + ), + right: AppTextField( + controller: _insurerEmailController, + label: 'Insurer Email', + keyboardType: TextInputType.emailAddress, + validator: Validators.optionalEmail, + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: AppSearchableDropdown( + label: 'Policy Type', + value: _policyType, + searchHint: 'Search policy type...', + options: stringDropdownOptions(const [ + 'FIRE_AND_ALLIED', + 'MACHINERY_BREAKDOWN', + 'COMPREHENSIVE', + 'THIRD_PARTY', + 'VEHICLE', + 'OTHER', + ]), + onChanged: (v) { + if (v != null) setState(() => _policyType = v); + }, + ), + right: AppTextField( + controller: _sumInsuredController, + label: 'Sum Insured', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) => Validators.optionalNonNegativeDouble( + v, + fieldName: 'Sum insured', + ), + ), + ), + const SizedBox(height: 12), + AppTextField( + controller: _annualPremiumController, + label: 'Annual Premium', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) => Validators.optionalNonNegativeDouble( + v, + fieldName: 'Annual premium', + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: _SidePanelDateField( + label: 'Start Date', + isRequired: true, + value: _startDate, + onPick: () => _pickDate( + current: _startDate, + onPicked: (date) => setState(() => _startDate = date), + ), + ), + right: _SidePanelDateField( + label: 'End Date', + isRequired: true, + value: _endDate, + onPick: () => _pickDate( + current: _endDate, + onPicked: (date) => setState(() => _endDate = date), + ), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: _SidePanelDateField( + label: 'Renewal Date', + value: _renewalDate, + onPick: () => _pickDate( + current: _renewalDate, + onPicked: (date) => setState(() => _renewalDate = date), + ), + ), + right: _SidePanelDateField( + label: 'Premium Paid Date', + value: _premiumPaidDate, + onPick: () => _pickDate( + current: _premiumPaidDate, + onPicked: (date) => setState(() => _premiumPaidDate = date), + ), + ), + ), + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Auto Renewal'), + value: _isAutoRenewal, + onChanged: (value) => setState(() => _isAutoRenewal = value), + ), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Premium Paid'), + value: _premiumPaid, + onChanged: (value) => setState(() => _premiumPaid = value), + ), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Active'), + value: _isActive, + onChanged: (value) => setState(() => _isActive = value), + ), + const SizedBox(height: 12), + AppTextField( + controller: _remarksController, + label: 'Remarks', + maxLines: 3, + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { + if (widget.isEdit) { + final policyAsync = ref.watch( + insurancePolicyFormProvider(( + assetId: widget.assetId, + policyId: widget.policyId!, + )), + ); + return policyAsync.when( + loading: () => SidePanelScaffold( + title: 'Edit Insurance Policy', + footer: _panelFooter( + context, + isSubmitting: true, + saveLabel: 'Update policy', + onSave: () {}, + ), + child: const Center(child: CircularProgressIndicator()), + ), + error: (error, _) => SidePanelScaffold( + title: 'Edit Insurance Policy', + child: Center(child: Text(error.toString())), + ), + data: (policy) { + _populateFromPolicy(policy); + return SidePanelScaffold( + title: 'Edit Insurance Policy', + footer: _panelFooter( + context, + isSubmitting: _isSubmitting, + saveLabel: 'Update policy', + onSave: _save, + ), + child: _buildForm(), + ); + }, + ); + } + return SidePanelScaffold( title: 'Add Insurance Policy', footer: _panelFooter( @@ -374,67 +1170,256 @@ class _AddInsurancePanelState extends ConsumerState { saveLabel: 'Save policy', onSave: _save, ), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SidePanelFormRow( - left: AppTextField( - controller: _policyNoController, - label: 'Policy No *', - validator: (v) => Validators.required(v, fieldName: 'Policy no'), - ), - right: AppTextField( - controller: _insurerNameController, - label: 'Insurer Name *', - validator: (v) => Validators.required(v, fieldName: 'Insurer name'), - ), - ), - SidePanelFormRow( - left: AppSearchableDropdown( - label: 'Policy Type', - value: _policyType, - searchHint: 'Search policy type...', - options: stringDropdownOptions(const [ - 'FIRE_AND_ALLIED', - 'MACHINERY_BREAKDOWN', - 'COMPREHENSIVE', - 'THIRD_PARTY', - 'VEHICLE', - 'OTHER', - ]), - onChanged: (v) { - if (v != null) setState(() => _policyType = v); - }, - ), - right: AppTextField( - controller: _sumInsuredController, - label: 'Sum Insured', - keyboardType: TextInputType.number, - ), - ), - SidePanelFormRow( - left: _SidePanelDateField( - label: 'Start Date', + child: _buildForm(), + ); + } +} + +class TransferAssetPanel extends ConsumerStatefulWidget { + const TransferAssetPanel({super.key, required this.assetId}); + + final String assetId; + + @override + ConsumerState createState() => _TransferAssetPanelState(); +} + +class _TransferAssetPanelState extends ConsumerState { + final _formKey = GlobalKey(); + final _reasonController = TextEditingController(); + DateTime _transferDate = DateTime.now(); + int? _toPlantId; + int? _toDepartmentId; + int? _toUserId; + int? _toWarehouseId; + bool _isSubmitting = false; + + @override + void dispose() { + _reasonController.dispose(); + super.dispose(); + } + + Future _pickDate() async { + final picked = await showDatePicker( + context: context, + initialDate: _transferDate, + firstDate: DateTime(2000), + lastDate: DateTime(2100), + ); + if (picked != null) { + setState(() => _transferDate = picked); + } + } + + Future _save() async { + if (!_formKey.currentState!.validate()) return; + + final hasDestination = + _toPlantId != null || + _toDepartmentId != null || + _toUserId != null || + _toWarehouseId != null; + if (!hasDestination) { + showSidePanelSnackBar( + context, + 'Choose at least one destination field', + ); + return; + } + + setState(() => _isSubmitting = true); + try { + await ref.read(assetDetailProvider(widget.assetId).notifier).transferAsset({ + 'transfer_date': DateFormatter.toApiDate(_transferDate), + if (_toPlantId != null) 'to_plant_id': _toPlantId, + if (_toDepartmentId != null) 'to_department_id': _toDepartmentId, + if (_toUserId != null) 'to_user_id': _toUserId, + if (_toWarehouseId != null) 'to_warehouse_id': _toWarehouseId, + 'reason': _reasonController.text.trim(), + }); + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(true); + } + } catch (e) { + if (mounted) { + showSidePanelSnackBar(context, e.toString()); + } + } finally { + if (mounted) setState(() => _isSubmitting = false); + } + } + + @override + Widget build(BuildContext context) { + final lookupsAsync = ref.watch(assetFormLookupsProvider); + return SidePanelScaffold( + title: 'Transfer Asset', + footer: _panelFooter( + context, + isSubmitting: _isSubmitting, + saveLabel: 'Transfer', + onSave: _save, + ), + child: lookupsAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, _) => Center(child: Text(error.toString())), + data: (lookups) => Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _SidePanelDateField( + label: 'Transfer Date', isRequired: true, - value: _startDate, - onPick: () => _pickDate(isStart: true), + value: _transferDate, + onPick: _pickDate, ), - right: _SidePanelDateField( - label: 'End Date', - isRequired: true, - value: _endDate, - onPick: () => _pickDate(isStart: false), + const SizedBox(height: 12), + AppSearchableDropdown( + label: 'To Plant', + value: _toPlantId, + searchHint: 'Search plant...', + options: lookups.plants + .map((option) { + final id = int.tryParse(option.id); + if (id == null) return null; + return AppDropdownOption(value: id, label: option.name); + }) + .whereType>() + .toList(), + onChanged: (v) => setState(() => _toPlantId = v), ), - ), - ], + const SizedBox(height: 12), + AppSearchableDropdown( + label: 'To Department', + value: _toDepartmentId, + searchHint: 'Search department...', + options: lookups.departments + .map((option) { + final id = int.tryParse(option.id); + if (id == null) return null; + return AppDropdownOption(value: id, label: option.name); + }) + .whereType>() + .toList(), + onChanged: (v) => setState(() => _toDepartmentId = v), + ), + const SizedBox(height: 12), + AppSearchableDropdown( + label: 'To User', + value: _toUserId, + searchHint: 'Search user...', + options: lookups.users + .map((option) { + final id = int.tryParse(option.id); + if (id == null) return null; + return AppDropdownOption(value: id, label: option.name); + }) + .whereType>() + .toList(), + onChanged: (v) => setState(() => _toUserId = v), + ), + const SizedBox(height: 12), + AppSearchableDropdown( + label: 'To Warehouse', + value: _toWarehouseId, + searchHint: 'Search warehouse...', + options: lookups.warehouses + .map((option) { + final id = int.tryParse(option.id); + if (id == null) return null; + return AppDropdownOption(value: id, label: option.name); + }) + .whereType>() + .toList(), + onChanged: (v) => setState(() => _toWarehouseId = v), + ), + const SizedBox(height: 12), + AppTextField( + controller: _reasonController, + label: 'Reason *', + maxLines: 3, + validator: (v) => Validators.required(v, fieldName: 'Reason'), + ), + ], + ), ), ), ); } } +class TransferHistoryPanel extends ConsumerWidget { + const TransferHistoryPanel({super.key, required this.assetId}); + + final String assetId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final historyAsync = ref.watch(transferHistoryProvider(assetId)); + return SidePanelScaffold( + title: 'Transfer History', + child: historyAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, _) => Center(child: Text(error.toString())), + data: (history) { + if (history.isEmpty) { + return const Center( + child: Text('No transfer history found'), + ); + } + return ListView.separated( + itemCount: history.length, + separatorBuilder: (_, _) => const Divider(height: 24), + itemBuilder: (context, index) { + final item = history[index]; + final transferDate = item.transferDate != null + ? DateFormatter.displayDate(item.transferDate) + : '—'; + final fromParts = [ + item.fromPlantName, + item.fromDepartmentName, + item.fromWarehouseName, + item.fromUserName, + ].whereType().where((e) => e.trim().isNotEmpty).toList(); + final toParts = [ + item.toPlantName, + item.toDepartmentName, + item.toWarehouseName, + item.toUserName, + ].whereType().where((e) => e.trim().isNotEmpty).toList(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + transferDate, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 8), + Text('From: ${fromParts.isEmpty ? '—' : fromParts.join(' · ')}'), + const SizedBox(height: 4), + Text('To: ${toParts.isEmpty ? '—' : toParts.join(' · ')}'), + if (item.reason?.trim().isNotEmpty == true) ...[ + const SizedBox(height: 8), + Text( + 'Reason: ${item.reason!}', + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ], + ); + }, + ); + }, + ), + ); + } +} + Widget _panelFooter( BuildContext context, { required bool isSubmitting, diff --git a/lib/modules/company/presentation/screens/company_list_screen.dart b/lib/modules/company/presentation/screens/company_list_screen.dart index 4218751..5c0e78c 100644 --- a/lib/modules/company/presentation/screens/company_list_screen.dart +++ b/lib/modules/company/presentation/screens/company_list_screen.dart @@ -1,40 +1,123 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/constants/route_constants.dart'; -import '../../../../shared/widgets/error_view.dart'; +import '../../../../shared/widgets/app_button.dart'; +import '../../../../shared/widgets/app_card.dart'; +import '../../../../shared/widgets/app_empty_state.dart'; +import '../../../../shared/widgets/app_loading_view.dart'; import '../../../../shared/widgets/page_header.dart'; +import '../../../settings/presentation/providers/settings_provider.dart'; -class CompanyListScreen extends StatelessWidget { +class CompanyListScreen extends ConsumerStatefulWidget { const CompanyListScreen({super.key}); + @override + ConsumerState createState() => _CompanyListScreenState(); +} + +class _CompanyListScreenState extends ConsumerState { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(appSettingsProvider.notifier).refreshCompanyProfile(); + }); + } + @override Widget build(BuildContext context) { + final profile = ref.watch(appSettingsProvider).companyProfile; + final theme = Theme.of(context); + return Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ PageHeader( - title: 'Companies', - subtitle: 'Manage organizations', + title: 'Company', + subtitle: 'Organisation profile from server settings', actions: [ - ElevatedButton.icon( - onPressed: () => context.push('${RouteConstants.companies}/add'), - icon: const Icon(Icons.add), - label: const Text('Add Company'), + AppButton( + label: 'Edit Company', + expand: false, + icon: Icons.edit_outlined, + onPressed: () => context.push(RouteConstants.settingsCompanyProfile), ), ], ), - const Expanded( - child: EmptyStateView( - title: 'No companies yet', - description: 'Connect to the API to load companies, or add your first company.', - icon: Icons.business_outlined, - ), + const SizedBox(height: 16), + Expanded( + child: profile.companyName.isEmpty + ? const AppEmptyState( + title: 'No company profile', + description: 'Configure your organisation details in settings.', + icon: Icons.business_outlined, + ) + : AppCard( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + profile.companyName, + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 16), + _InfoRow(label: 'Mobile', value: profile.phone), + _InfoRow(label: 'Email', value: profile.email), + _InfoRow(label: 'Website', value: profile.website), + _InfoRow(label: 'Address', value: profile.address), + _InfoRow( + label: 'City / State / Pincode', + value: [ + profile.city, + profile.state, + profile.pincode, + ].where((e) => e.isNotEmpty).join(', '), + ), + ], + ), + ), + ), ), ], ), ); } } + +class _InfoRow extends StatelessWidget { + const _InfoRow({required this.label, required this.value}); + + final String label; + final String value; + + @override + Widget build(BuildContext context) { + if (value.isEmpty) return const SizedBox.shrink(); + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 160, + child: Text( + label, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + Expanded(child: Text(value)), + ], + ), + ); + } +} diff --git a/lib/modules/master_data/domain/entities/master_definition.dart b/lib/modules/master_data/domain/entities/master_definition.dart index 304fe57..57f1b1b 100644 --- a/lib/modules/master_data/domain/entities/master_definition.dart +++ b/lib/modules/master_data/domain/entities/master_definition.dart @@ -352,12 +352,37 @@ const masterDefinitions = [ MasterFieldDef( key: 'default_depreciation_method', label: 'Depreciation Method', + type: MasterFieldType.dropdown, required: true, showInList: true, + optionsMasterKey: 'asset_depreciation_methods', ), _activeField, ], ), + MasterDefinition( + id: 'asset_subcategories', + title: 'Asset Subcategories', + subtitle: 'Sub-classification under asset categories', + category: 'Assets', + routeKey: 'asset-subcategories', + apiPath: '/masters/asset-subcategories', + module: 'asset_subcategories', + icon: Icons.category_outlined, + fields: [ + MasterFieldDef( + key: 'asset_category_id', + label: 'Asset Category', + type: MasterFieldType.dropdown, + required: true, + showInList: true, + optionsMasterKey: 'asset_categories', + ), + MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true), + MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true), + _activeField, + ], + ), MasterDefinition( id: 'delivery_terms', title: 'Delivery Terms', diff --git a/lib/modules/master_data/presentation/providers/master_provider.dart b/lib/modules/master_data/presentation/providers/master_provider.dart index d4158d0..a7d2680 100644 --- a/lib/modules/master_data/presentation/providers/master_provider.dart +++ b/lib/modules/master_data/presentation/providers/master_provider.dart @@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/constants/app_constants.dart'; import '../../../../shared/models/export_file_result.dart'; +import '../../../assets/data/repositories/asset_repository_impl.dart'; import '../../data/repositories/master_repository_impl.dart'; import '../../domain/entities/master_definition.dart'; @@ -280,6 +281,20 @@ class MasterFormNotifier extends FamilyAsyncNotifier[]) + .map((method) => method.trim()) + .where((method) => method.isNotEmpty) + .toSet() + .toList(); + options[key] = methods + .map((method) => {'id': method, 'name': method}) + .toList(); + continue; + } + final def = masterDefinitionById(key); if (def == null) continue; final result = await ref.read(masterRepositoryProvider).listOptions(def); diff --git a/lib/modules/masters/data/datasources/master_remote_data_source.dart b/lib/modules/masters/data/datasources/master_remote_data_source.dart index 624044c..da478ba 100644 --- a/lib/modules/masters/data/datasources/master_remote_data_source.dart +++ b/lib/modules/masters/data/datasources/master_remote_data_source.dart @@ -47,6 +47,37 @@ class MasterRemoteDataSource { Future> listAssetCategories() => _listOptions(ApiEndpoints.assetCategories); + Future> listAssetSubcategories({int? assetCategoryId}) async { + final response = await dio.get( + ApiEndpoints.assetSubcategories, + queryParameters: { + 'limit': AppConstants.maxPageSize, + 'is_active': true, + if (assetCategoryId != null) 'asset_category_id': assetCategoryId, + }, + ); + final body = response.data as Map; + final raw = body['data']; + + final list = raw is List + ? raw + : raw is Map + ? raw['items'] as List? ?? const [] + : const []; + + return list + .whereType>() + .where((item) => item['is_active'] != false) + .map( + (item) => FilterOptionModel( + id: item['id']?.toString() ?? '', + name: _optionLabel(item), + ), + ) + .where((item) => item.id.isNotEmpty && item.name.isNotEmpty) + .toList(); + } + Future> _listOptions(String endpoint) async { final response = await dio.get( endpoint, diff --git a/lib/modules/rbac/presentation/screens/users_role_management_screen.dart b/lib/modules/rbac/presentation/screens/users_role_management_screen.dart index c0d566b..2eb44a1 100644 --- a/lib/modules/rbac/presentation/screens/users_role_management_screen.dart +++ b/lib/modules/rbac/presentation/screens/users_role_management_screen.dart @@ -792,6 +792,8 @@ class _UsersTabState extends ConsumerState<_UsersTab> { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), onPageChanged: ref.read(usersListProvider.notifier).setPage, + onPageSizeChanged: + ref.read(usersListProvider.notifier).setPageSize, ), ], ), @@ -838,14 +840,19 @@ class _UsersFilterBar extends StatelessWidget { @override Widget build(BuildContext context) { - final searchField = TextField( - controller: searchController, - decoration: const InputDecoration( - hintText: 'Search by name, email, employee code...', - prefixIcon: Icon(Icons.search, size: 20), - isDense: true, + final searchField = Padding( + padding: const EdgeInsets.only(top: 8), + child: TextField( + controller: searchController, + decoration: const InputDecoration( + labelText: 'Search', + hintText: 'Search by name, email, employee code...', + prefixIcon: Icon(Icons.search, size: 20), + floatingLabelBehavior: FloatingLabelBehavior.always, + isDense: true, + ), + onChanged: onSearch, ), - onChanged: onSearch, ); final filters = [ @@ -867,8 +874,16 @@ class _UsersFilterBar extends StatelessWidget { items: statuses, onChanged: onStatusChanged, ), - OutlinedButton.icon( + ]; + + final exportButton = Padding( + padding: const EdgeInsets.only(top: 8), + child: OutlinedButton.icon( onPressed: isExporting ? null : onExport, + style: OutlinedButton.styleFrom( + minimumSize: const Size(0, 48), + padding: const EdgeInsets.symmetric(horizontal: 16), + ), icon: isExporting ? const SizedBox( width: 18, @@ -878,12 +893,7 @@ class _UsersFilterBar extends StatelessWidget { : const Icon(Icons.download_outlined, size: 18), label: Text(isExporting ? 'Exporting...' : 'Export'), ), - ]; - - final visibleFilters = [ - ...filters.take(3), - if (showExport) filters[3], - ]; + ); if (wrapped) { return Column( @@ -891,12 +901,18 @@ class _UsersFilterBar extends StatelessWidget { children: [ searchField, const SizedBox(height: 12), - Wrap(spacing: 12, runSpacing: 12, children: visibleFilters), + ...filters.expand((f) => [f, const SizedBox(height: 12)]).toList() + ..removeLast(), + if (showExport) ...[ + const SizedBox(height: 12), + exportButton, + ], ], ); } return Row( + crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded(flex: 3, child: searchField), const SizedBox(width: 12), @@ -907,7 +923,7 @@ class _UsersFilterBar extends StatelessWidget { Expanded(child: filters[2]), if (showExport) ...[ const SizedBox(width: 12), - filters[3], + exportButton, ], ], ); diff --git a/lib/modules/rbac/presentation/widgets/add_user_panel.dart b/lib/modules/rbac/presentation/widgets/add_user_panel.dart index 3733b1f..79d5ad0 100644 --- a/lib/modules/rbac/presentation/widgets/add_user_panel.dart +++ b/lib/modules/rbac/presentation/widgets/add_user_panel.dart @@ -82,7 +82,16 @@ class _AddUserPanelState extends ConsumerState { _selectedDesignationId = user.designationId; _selectedPlantId = user.plantId; _selectedReportingToId = user.reportingTo; - _selectedStatus = _statusFromApi(user.status); + _selectedStatus = _statusLabelFromUser(user); + } + + String _statusLabelFromUser(ManagedUserModel user) { + final normalized = user.status.trim().toLowerCase(); + if (normalized.isNotEmpty && normalized != 'active') { + return _statusFromApi(user.status); + } + if (!user.isActive) return 'Inactive'; + return _statusFromApi(user.status); } List> _toOptions(List items) { @@ -224,12 +233,7 @@ class _AddUserPanelState extends ConsumerState { ), data: (formState) { if (formState.editingUser != null) { - if (!_prefilled) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted || _prefilled) return; - setState(() => _prefillFromUser(formState.editingUser!)); - }); - } + _prefillFromUser(formState.editingUser!); } return Form( diff --git a/lib/modules/rbac/presentation/widgets/rbac_widgets.dart b/lib/modules/rbac/presentation/widgets/rbac_widgets.dart index 4d68e47..364f4fc 100644 --- a/lib/modules/rbac/presentation/widgets/rbac_widgets.dart +++ b/lib/modules/rbac/presentation/widgets/rbac_widgets.dart @@ -167,10 +167,42 @@ class UserRolesCell extends StatelessWidget { return const Text('—'); } - return Wrap( - spacing: 6, - runSpacing: 6, - children: roles.map((role) => RoleBadge(label: role)).toList(), + final theme = Theme.of(context); + final extraCount = roles.length - 1; + final tooltipMessage = roles.join('\n'); + + return Tooltip( + message: tooltipMessage, + preferBelow: true, + waitDuration: const Duration(milliseconds: 250), + child: SizedBox( + height: 28, + child: Row( + children: [ + Flexible( + child: RoleBadge(label: roles.first), + ), + if (extraCount > 0) ...[ + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest + .withValues(alpha: 0.8), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '+$extraCount', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ], + ), + ), ); } } diff --git a/lib/modules/settings/data/datasources/settings_remote_data_source.dart b/lib/modules/settings/data/datasources/settings_remote_data_source.dart index 12db4a7..28c1d89 100644 --- a/lib/modules/settings/data/datasources/settings_remote_data_source.dart +++ b/lib/modules/settings/data/datasources/settings_remote_data_source.dart @@ -23,4 +23,69 @@ class SettingsRemoteDataSource { } return settings; } + + Future fetchCompany() async { + final response = await _dio.get(ApiEndpoints.settingsCompany); + final data = response.data['data']; + if (data is! Map) return null; + return CompanyProfileSettings.fromApiJson(data); + } + + Future saveCompany(CompanyProfileSettings profile) async { + final response = await _dio.put( + ApiEndpoints.settingsCompany, + data: profile.toApiJson(), + ); + final data = response.data['data']; + if (data is Map) { + return CompanyProfileSettings.fromApiJson(data).copyWith( + companyCode: profile.companyCode, + registrationNumber: profile.registrationNumber, + gstNumber: profile.gstNumber, + faviconUrl: profile.faviconUrl, + ); + } + return profile; + } + + Future uploadCompanyLogo(List bytes, String filename) async { + final formData = FormData.fromMap({ + 'logo': MultipartFile.fromBytes(bytes, filename: filename), + }); + final response = await _dio.post( + ApiEndpoints.settingsCompanyLogo, + data: formData, + ); + final data = response.data['data']; + if (data is Map) { + return data['logo_url'] as String? ?? data['logo'] as String?; + } + return null; + } + + Future fetchEmail() async { + final response = await _dio.get(ApiEndpoints.settingsEmail); + final data = response.data['data']; + if (data is! Map) return null; + return EmailConfigurationSettings.fromApiJson(data); + } + + Future saveEmail( + EmailConfigurationSettings email, + ) async { + final response = await _dio.put( + ApiEndpoints.settingsEmail, + data: email.toApiJson(), + ); + final data = response.data['data']; + if (data is Map) { + return EmailConfigurationSettings.fromApiJson(data).copyWith( + allocationTemplate: email.allocationTemplate, + returnTemplate: email.returnTemplate, + maintenanceTemplate: email.maintenanceTemplate, + warrantyTemplate: email.warrantyTemplate, + ); + } + return email; + } } diff --git a/lib/modules/settings/data/repositories/settings_repository_impl.dart b/lib/modules/settings/data/repositories/settings_repository_impl.dart index c59c1b8..decd991 100644 --- a/lib/modules/settings/data/repositories/settings_repository_impl.dart +++ b/lib/modules/settings/data/repositories/settings_repository_impl.dart @@ -16,17 +16,147 @@ class SettingsRepositoryImpl implements SettingsRepository { @override Future> getSettings() async { return safeApiCall(() async { - // Local-first until settings API is available. - return await local.read() ?? const AppSettings(); + final localSettings = await local.read() ?? const AppSettings(); + try { + final company = await remote.fetchCompany(); + final email = await remote.fetchEmail(); + return localSettings.copyWith( + companyProfile: company ?? localSettings.companyProfile, + email: email != null + ? localSettings.email.copyWith( + smtpHost: email.smtpHost, + smtpPort: email.smtpPort, + smtpUsername: email.smtpUsername, + smtpPassword: email.smtpPassword.isNotEmpty + ? email.smtpPassword + : localSettings.email.smtpPassword, + senderEmail: email.senderEmail, + senderName: email.senderName, + ) + : localSettings.email, + ); + } catch (_) { + return localSettings; + } }); } @override Future> saveSettings(AppSettings settings) async { return safeApiCall(() async { - // Always persist locally; remote sync can be wired when API is ready. await local.write(settings); return settings; }); } + + @override + Future> fetchCompanyProfile() async { + return safeApiCall(() async { + final remoteProfile = await remote.fetchCompany(); + if (remoteProfile != null) { + final current = await local.read(); + final merged = (current?.companyProfile ?? const CompanyProfileSettings()) + .copyWith( + companyName: remoteProfile.companyName, + address: remoteProfile.address, + city: remoteProfile.city, + state: remoteProfile.state, + pincode: remoteProfile.pincode, + email: remoteProfile.email, + phone: remoteProfile.phone, + website: remoteProfile.website, + logoUrl: remoteProfile.logoUrl.isNotEmpty + ? remoteProfile.logoUrl + : current?.companyProfile.logoUrl ?? '', + ); + await local.write((current ?? const AppSettings()).copyWith( + companyProfile: merged, + )); + return merged; + } + return (await local.read())?.companyProfile ?? + const CompanyProfileSettings(); + }); + } + + @override + Future> saveCompanyProfile( + CompanyProfileSettings profile, + ) async { + return safeApiCall(() async { + final saved = await remote.saveCompany(profile); + final current = await local.read() ?? const AppSettings(); + final merged = profile.copyWith( + companyName: saved.companyName.isNotEmpty + ? saved.companyName + : profile.companyName, + address: saved.address.isNotEmpty ? saved.address : profile.address, + city: saved.city.isNotEmpty ? saved.city : profile.city, + state: saved.state.isNotEmpty ? saved.state : profile.state, + pincode: saved.pincode.isNotEmpty ? saved.pincode : profile.pincode, + email: saved.email.isNotEmpty ? saved.email : profile.email, + phone: saved.phone.isNotEmpty ? saved.phone : profile.phone, + website: saved.website.isNotEmpty ? saved.website : profile.website, + logoUrl: saved.logoUrl.isNotEmpty ? saved.logoUrl : profile.logoUrl, + ); + await local.write(current.copyWith(companyProfile: merged)); + return merged; + }); + } + + @override + Future> uploadCompanyLogo( + List bytes, + String filename, + ) async { + return safeApiCall(() => remote.uploadCompanyLogo(bytes, filename)); + } + + @override + Future> fetchEmailSettings() async { + return safeApiCall(() async { + final remoteEmail = await remote.fetchEmail(); + if (remoteEmail != null) { + final current = await local.read() ?? const AppSettings(); + final merged = current.email.copyWith( + smtpHost: remoteEmail.smtpHost, + smtpPort: remoteEmail.smtpPort, + smtpUsername: remoteEmail.smtpUsername, + smtpPassword: remoteEmail.smtpPassword.isNotEmpty + ? remoteEmail.smtpPassword + : current.email.smtpPassword, + senderEmail: remoteEmail.senderEmail, + senderName: remoteEmail.senderName, + ); + await local.write(current.copyWith(email: merged)); + return merged; + } + return (await local.read())?.email ?? const EmailConfigurationSettings(); + }); + } + + @override + Future> saveEmailSettings( + EmailConfigurationSettings email, + ) async { + return safeApiCall(() async { + final saved = await remote.saveEmail(email); + final current = await local.read() ?? const AppSettings(); + final merged = email.copyWith( + smtpHost: saved.smtpHost.isNotEmpty ? saved.smtpHost : email.smtpHost, + smtpPort: saved.smtpPort, + smtpUsername: + saved.smtpUsername.isNotEmpty ? saved.smtpUsername : email.smtpUsername, + smtpPassword: saved.smtpPassword.isNotEmpty + ? saved.smtpPassword + : email.smtpPassword, + senderEmail: + saved.senderEmail.isNotEmpty ? saved.senderEmail : email.senderEmail, + senderName: + saved.senderName.isNotEmpty ? saved.senderName : email.senderName, + ); + await local.write(current.copyWith(email: merged)); + return merged; + }); + } } diff --git a/lib/modules/settings/domain/entities/app_settings.dart b/lib/modules/settings/domain/entities/app_settings.dart index fb0861e..91faf9b 100644 --- a/lib/modules/settings/domain/entities/app_settings.dart +++ b/lib/modules/settings/domain/entities/app_settings.dart @@ -73,6 +73,9 @@ class CompanyProfileSettings { this.registrationNumber = '', this.gstNumber = '', this.address = '', + this.city = '', + this.state = '', + this.pincode = '', this.email = '', this.phone = '', this.website = '', @@ -85,6 +88,9 @@ class CompanyProfileSettings { final String registrationNumber; final String gstNumber; final String address; + final String city; + final String state; + final String pincode; final String email; final String phone; final String website; @@ -97,6 +103,9 @@ class CompanyProfileSettings { String? registrationNumber, String? gstNumber, String? address, + String? city, + String? state, + String? pincode, String? email, String? phone, String? website, @@ -109,6 +118,9 @@ class CompanyProfileSettings { registrationNumber: registrationNumber ?? this.registrationNumber, gstNumber: gstNumber ?? this.gstNumber, address: address ?? this.address, + city: city ?? this.city, + state: state ?? this.state, + pincode: pincode ?? this.pincode, email: email ?? this.email, phone: phone ?? this.phone, website: website ?? this.website, @@ -123,6 +135,9 @@ class CompanyProfileSettings { 'registrationNumber': registrationNumber, 'gstNumber': gstNumber, 'address': address, + 'city': city, + 'state': state, + 'pincode': pincode, 'email': email, 'phone': phone, 'website': website, @@ -130,17 +145,41 @@ class CompanyProfileSettings { 'faviconUrl': faviconUrl, }; + Map toApiJson() { + final payload = {}; + if (companyName.isNotEmpty) payload['org_name'] = companyName; + if (phone.isNotEmpty) payload['mobile'] = phone; + if (email.isNotEmpty) payload['email'] = email; + if (website.isNotEmpty) payload['website'] = website; + if (address.isNotEmpty) payload['address'] = address; + if (city.isNotEmpty) payload['city'] = city; + if (state.isNotEmpty) payload['state'] = state; + if (pincode.isNotEmpty) payload['pincode'] = pincode; + return payload; + } + factory CompanyProfileSettings.fromJson(Map json) => + CompanyProfileSettings.fromApiJson(json); + + factory CompanyProfileSettings.fromApiJson(Map json) => CompanyProfileSettings( - companyName: json['companyName'] as String? ?? '', + companyName: json['org_name'] as String? ?? + json['companyName'] as String? ?? + '', companyCode: json['companyCode'] as String? ?? '', registrationNumber: json['registrationNumber'] as String? ?? '', gstNumber: json['gstNumber'] as String? ?? '', address: json['address'] as String? ?? '', + city: json['city'] as String? ?? '', + state: json['state'] as String? ?? '', + pincode: json['pincode'] as String? ?? '', email: json['email'] as String? ?? '', - phone: json['phone'] as String? ?? '', + phone: json['mobile'] as String? ?? json['phone'] as String? ?? '', website: json['website'] as String? ?? '', - logoUrl: json['logoUrl'] as String? ?? '', + logoUrl: json['logo_url'] as String? ?? + json['logoUrl'] as String? ?? + json['logo'] as String? ?? + '', faviconUrl: json['faviconUrl'] as String? ?? '', ); } @@ -411,14 +450,34 @@ class EmailConfigurationSettings { 'warrantyTemplate': warrantyTemplate, }; + Map toApiJson() { + final payload = {}; + if (smtpHost.isNotEmpty) payload['smtp_host'] = smtpHost; + if (smtpPort > 0) payload['smtp_port'] = smtpPort; + if (smtpUsername.isNotEmpty) payload['smtp_username'] = smtpUsername; + if (smtpPassword.isNotEmpty) payload['smtp_password'] = smtpPassword; + if (senderEmail.isNotEmpty) payload['sender_email'] = senderEmail; + if (senderName.isNotEmpty) payload['sender_name'] = senderName; + return payload; + } + factory EmailConfigurationSettings.fromJson(Map json) => + EmailConfigurationSettings.fromApiJson(json); + + factory EmailConfigurationSettings.fromApiJson(Map json) => EmailConfigurationSettings( - smtpHost: json['smtpHost'] as String? ?? '', - smtpPort: json['smtpPort'] as int? ?? 587, - smtpUsername: json['smtpUsername'] as String? ?? '', - smtpPassword: json['smtpPassword'] as String? ?? '', - senderEmail: json['senderEmail'] as String? ?? '', - senderName: json['senderName'] as String? ?? '', + smtpHost: json['smtp_host'] as String? ?? json['smtpHost'] as String? ?? '', + smtpPort: (json['smtp_port'] as num?)?.toInt() ?? + (json['smtpPort'] as num?)?.toInt() ?? + 587, + smtpUsername: + json['smtp_username'] as String? ?? json['smtpUsername'] as String? ?? '', + smtpPassword: + json['smtp_password'] as String? ?? json['smtpPassword'] as String? ?? '', + senderEmail: + json['sender_email'] as String? ?? json['senderEmail'] as String? ?? '', + senderName: + json['sender_name'] as String? ?? json['senderName'] as String? ?? '', allocationTemplate: json['allocationTemplate'] as String? ?? 'Your asset {{asset_name}} has been allocated.', returnTemplate: json['returnTemplate'] as String? ?? diff --git a/lib/modules/settings/domain/repositories/settings_repository.dart b/lib/modules/settings/domain/repositories/settings_repository.dart index 3980145..582e6ef 100644 --- a/lib/modules/settings/domain/repositories/settings_repository.dart +++ b/lib/modules/settings/domain/repositories/settings_repository.dart @@ -4,4 +4,16 @@ import '../entities/app_settings.dart'; abstract class SettingsRepository { Future> getSettings(); Future> saveSettings(AppSettings settings); + Future> fetchCompanyProfile(); + Future> saveCompanyProfile( + CompanyProfileSettings profile, + ); + Future> uploadCompanyLogo( + List bytes, + String filename, + ); + Future> fetchEmailSettings(); + Future> saveEmailSettings( + EmailConfigurationSettings email, + ); } diff --git a/lib/modules/settings/presentation/providers/settings_provider.dart b/lib/modules/settings/presentation/providers/settings_provider.dart index d0a937e..20cb235 100644 --- a/lib/modules/settings/presentation/providers/settings_provider.dart +++ b/lib/modules/settings/presentation/providers/settings_provider.dart @@ -40,6 +40,7 @@ final appSettingsProvider = return AppSettingsNotifier( getSettings: ref.watch(getSettingsUseCaseProvider), saveSettings: ref.watch(saveSettingsUseCaseProvider), + repository: ref.watch(settingsRepositoryProvider), faviconStore: FaviconStore(ref.watch(sharedPreferencesProvider)), ); }); @@ -48,9 +49,11 @@ class AppSettingsNotifier extends StateNotifier { AppSettingsNotifier({ required GetSettingsUseCase getSettings, required SaveSettingsUseCase saveSettings, + required SettingsRepository repository, required FaviconStore faviconStore, }) : _getSettings = getSettings, _saveSettings = saveSettings, + _repository = repository, _faviconStore = faviconStore, super(const AppSettings()) { _load(); @@ -58,6 +61,7 @@ class AppSettingsNotifier extends StateNotifier { final GetSettingsUseCase _getSettings; final SaveSettingsUseCase _saveSettings; + final SettingsRepository _repository; final FaviconStore _faviconStore; Future _load() async { @@ -66,6 +70,20 @@ class AppSettingsNotifier extends StateNotifier { _faviconStore.apply(); } + Future refreshCompanyProfile() async { + final result = await _repository.fetchCompanyProfile(); + if (result.failure == null && result.data != null) { + state = state.copyWith(companyProfile: result.data!); + } + } + + Future refreshEmailSettings() async { + final result = await _repository.fetchEmailSettings(); + if (result.failure == null && result.data != null) { + state = state.copyWith(email: result.data!); + } + } + Future _persist(AppSettings settings) async { state = settings; final result = await _saveSettings(settings); @@ -77,9 +95,28 @@ class AppSettingsNotifier extends StateNotifier { } Future updateCompanyProfile(CompanyProfileSettings profile) async { + final result = await _repository.saveCompanyProfile(profile); + if (result.failure == null && result.data != null) { + state = state.copyWith(companyProfile: result.data!); + return; + } await _persist(state.copyWith(companyProfile: profile)); } + Future uploadCompanyLogo(List bytes, String filename) async { + final result = await _repository.uploadCompanyLogo(bytes, filename); + if (result.failure == null && result.data != null) { + final logoUrl = result.data!; + await _persist( + state.copyWith( + companyProfile: state.companyProfile.copyWith(logoUrl: logoUrl), + ), + ); + return logoUrl; + } + return null; + } + Future updateUiPreferences(UiPreferencesSettings prefs) async { await _persist(state.copyWith(uiPreferences: prefs)); } @@ -93,6 +130,12 @@ class AppSettingsNotifier extends StateNotifier { } Future updateEmail(EmailConfigurationSettings email) async { + final result = await _repository.saveEmailSettings(email); + if (result.failure == null && result.data != null) { + state = state.copyWith(email: result.data!); + await _saveSettings(state); + return; + } await _persist(state.copyWith(email: email)); } diff --git a/lib/modules/settings/presentation/screens/company_profile_settings_screen.dart b/lib/modules/settings/presentation/screens/company_profile_settings_screen.dart index c189959..d213fcc 100644 --- a/lib/modules/settings/presentation/screens/company_profile_settings_screen.dart +++ b/lib/modules/settings/presentation/screens/company_profile_settings_screen.dart @@ -9,6 +9,7 @@ import '../../../../core/utils/favicon_store.dart'; import '../../../../core/utils/favicon_updater.dart'; import '../../../../core/utils/validators.dart'; import '../../../../shared/widgets/app_button.dart'; +import '../../../../shared/widgets/app_side_panel.dart'; import '../../../../shared/widgets/app_text_field.dart'; import '../../../../shared/widgets/sidebar_logo.dart'; import '../../domain/entities/app_settings.dart'; @@ -31,6 +32,9 @@ class _CompanyProfileSettingsScreenState late final TextEditingController _registrationController; late final TextEditingController _gstController; late final TextEditingController _addressController; + late final TextEditingController _cityController; + late final TextEditingController _stateController; + late final TextEditingController _pincodeController; late final TextEditingController _emailController; late final TextEditingController _phoneController; late final TextEditingController _websiteController; @@ -43,12 +47,18 @@ class _CompanyProfileSettingsScreenState final profile = ref.read(appSettingsProvider).companyProfile; final faviconFromPrefs = FaviconStore(ref.read(sharedPreferencesProvider)).read(); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(appSettingsProvider.notifier).refreshCompanyProfile(); + }); _nameController = TextEditingController(text: profile.companyName); _codeController = TextEditingController(text: profile.companyCode); _registrationController = TextEditingController(text: profile.registrationNumber); _gstController = TextEditingController(text: profile.gstNumber); _addressController = TextEditingController(text: profile.address); + _cityController = TextEditingController(text: profile.city); + _stateController = TextEditingController(text: profile.state); + _pincodeController = TextEditingController(text: profile.pincode); _emailController = TextEditingController(text: profile.email); _phoneController = TextEditingController(text: profile.phone); _websiteController = TextEditingController(text: profile.website); @@ -67,6 +77,9 @@ class _CompanyProfileSettingsScreenState _registrationController.dispose(); _gstController.dispose(); _addressController.dispose(); + _cityController.dispose(); + _stateController.dispose(); + _pincodeController.dispose(); _emailController.dispose(); _phoneController.dispose(); _websiteController.dispose(); @@ -89,6 +102,9 @@ class _CompanyProfileSettingsScreenState registrationNumber: _registrationController.text.trim(), gstNumber: _gstController.text.trim(), address: _addressController.text.trim(), + city: _cityController.text.trim(), + state: _stateController.text.trim(), + pincode: _pincodeController.text.trim(), email: _emailController.text.trim(), phone: _phoneController.text.trim(), website: _websiteController.text.trim(), @@ -137,9 +153,38 @@ class _CompanyProfileSettingsScreenState }); } - Future _pickLogo() => _pickImage((dataUri) { - _logoUrlController.text = dataUri; - }); + Future _pickLogo() async { + final result = await FilePicker.pickFiles( + type: FileType.image, + withData: true, + ); + + if (result == null || result.files.isEmpty) return; + + final file = result.files.single; + final bytes = file.bytes; + if (bytes == null) return; + + final uploadedUrl = await ref.read(appSettingsProvider.notifier).uploadCompanyLogo( + bytes, + file.name, + ); + + if (!mounted) return; + + if (uploadedUrl != null && uploadedUrl.isNotEmpty) { + setState(() => _logoUrlController.text = uploadedUrl); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Logo uploaded')), + ); + return; + } + + final ext = (file.extension ?? 'png').toLowerCase(); + final mime = ext == 'jpg' ? 'jpeg' : ext; + final dataUri = 'data:image/$mime;base64,${base64Encode(bytes)}'; + setState(() => _logoUrlController.text = dataUri); + } Future _pickFavicon() => _pickImage((dataUri) { _faviconUrlController.text = dataUri; @@ -184,7 +229,24 @@ class _CompanyProfileSettingsScreenState AppTextField( controller: _addressController, label: 'Address', - maxLines: 3, + maxLines: 2, + ), + const SizedBox(height: 16), + SidePanelFormRow( + left: AppTextField( + controller: _cityController, + label: 'City', + ), + right: AppTextField( + controller: _stateController, + label: 'State', + ), + ), + const SizedBox(height: 16), + AppTextField( + controller: _pincodeController, + label: 'Pincode', + keyboardType: TextInputType.number, ), const SizedBox(height: 16), AppTextField( diff --git a/lib/modules/settings/presentation/screens/email_configuration_screen.dart b/lib/modules/settings/presentation/screens/email_configuration_screen.dart index 7af9cd7..968f514 100644 --- a/lib/modules/settings/presentation/screens/email_configuration_screen.dart +++ b/lib/modules/settings/presentation/screens/email_configuration_screen.dart @@ -34,6 +34,9 @@ class _EmailConfigurationScreenState void initState() { super.initState(); final email = ref.read(appSettingsProvider).email; + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(appSettingsProvider.notifier).refreshEmailSettings(); + }); _hostController = TextEditingController(text: email.smtpHost); _portController = TextEditingController(text: '${email.smtpPort}'); _usernameController = TextEditingController(text: email.smtpUsername); diff --git a/lib/modules/vendors/data/datasources/vendor_remote_data_source.dart b/lib/modules/vendors/data/datasources/vendor_remote_data_source.dart index 5910dd4..3fa3af0 100644 --- a/lib/modules/vendors/data/datasources/vendor_remote_data_source.dart +++ b/lib/modules/vendors/data/datasources/vendor_remote_data_source.dart @@ -2,6 +2,7 @@ import 'package:dio/dio.dart'; import '../../../../core/constants/api_endpoints.dart'; import '../../../../shared/models/api_response.dart'; +import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/models/vendor_model.dart'; class VendorRemoteDataSource { @@ -152,6 +153,39 @@ class VendorRemoteDataSource { await dio.delete(ApiEndpoints.vendorBankDetailById(vendorId, bankDetailId)); } + Future> getGstTreatments() async { + final response = await dio.get(ApiEndpoints.vendorGstTreatments); + return _parseDropdownOptions(response.data); + } + + Future> getSourceOfSupply() async { + final response = await dio.get(ApiEndpoints.vendorSourceOfSupply); + return _parseDropdownOptions(response.data); + } + + List _parseDropdownOptions(dynamic body) { + if (body is! Map) return []; + final raw = body['data']; + final list = raw is List + ? raw + : raw is Map + ? raw['items'] as List? ?? const [] + : const []; + return list.map((item) { + if (item is String) { + return FilterOptionModel(id: item, name: item); + } + if (item is Map) { + final value = item['value']?.toString() ?? item['id']?.toString() ?? ''; + final label = item['label']?.toString() ?? + item['name']?.toString() ?? + value; + return FilterOptionModel(id: value, name: label); + } + return const FilterOptionModel(id: '', name: ''); + }).where((item) => item.id.isNotEmpty).toList(); + } + Map _queryToMap(VendorListQuery query) { return { 'page': query.page, diff --git a/lib/modules/vendors/presentation/screens/vendor_detail_screen.dart b/lib/modules/vendors/presentation/screens/vendor_detail_screen.dart index 03be52b..1eba3dc 100644 --- a/lib/modules/vendors/presentation/screens/vendor_detail_screen.dart +++ b/lib/modules/vendors/presentation/screens/vendor_detail_screen.dart @@ -231,6 +231,14 @@ class _OverviewTab extends StatelessWidget { _VendorInfo('Vendor Code', vendor.vendorCode ?? '—'), _VendorInfo('Vendor Name', vendor.vendorName), _VendorInfo('Type', vendorTypeLabel(vendor.vendorType)), + _VendorInfo( + 'GST Treatment', + gstTreatmentLabel(vendor.gstTreatment), + ), + _VendorInfo( + 'Source of Supply', + vendor.sourceOfSupply ?? '—', + ), _VendorInfo('GSTIN', vendor.gstin ?? '—'), _VendorInfo('PAN', vendor.pan ?? '—'), _VendorInfo('Payment Term', vendor.paymentTermName ?? '—'), diff --git a/lib/modules/vendors/presentation/widgets/vendor_form_panel.dart b/lib/modules/vendors/presentation/widgets/vendor_form_panel.dart index 1b6e5fe..cfc9fd6 100644 --- a/lib/modules/vendors/presentation/widgets/vendor_form_panel.dart +++ b/lib/modules/vendors/presentation/widgets/vendor_form_panel.dart @@ -13,6 +13,7 @@ import '../../../../shared/widgets/app_side_panel.dart'; import '../../../../shared/widgets/app_text_field.dart'; import '../../../../shared/widgets/error_view.dart'; import '../../../masters/data/datasources/master_remote_data_source.dart'; +import '../../data/repositories/vendor_repository_impl.dart'; import '../providers/vendors_provider.dart'; Future openVendorFormPanel( @@ -58,6 +59,8 @@ class _VendorFormPanelState extends ConsumerState { final _creditDaysController = TextEditingController(); final _remarksController = TextEditingController(); String? _vendorType; + String? _gstTreatment; + String? _sourceOfSupply; int? _paymentTermId; bool _isActive = true; bool _isSubmitting = false; @@ -74,13 +77,16 @@ class _VendorFormPanelState extends ConsumerState { } String _vendorSignature(VendorModel vendor) => - '${vendor.id}:${vendor.vendorType}:${vendor.paymentTermId}:' + '${vendor.id}:${vendor.vendorType}:${vendor.gstTreatment}:' + '${vendor.sourceOfSupply}:${vendor.paymentTermId}:' '${vendor.creditPeriodDays}:${vendor.isActive}:${vendor.vendorName}'; void _populateFromVendor(VendorModel vendor) { setState(() { _nameController.text = vendor.vendorName; _vendorType = vendor.vendorType; + _gstTreatment = vendor.gstTreatment; + _sourceOfSupply = vendor.sourceOfSupply; _gstinController.text = vendor.gstin ?? ''; _panController.text = vendor.pan ?? ''; _paymentTermId = vendor.paymentTermId; @@ -95,6 +101,9 @@ class _VendorFormPanelState extends ConsumerState { return { 'vendor_name': _nameController.text.trim(), 'vendor_type': _vendorType, + if (_gstTreatment != null) 'gst_treatment': _gstTreatment, + if (_sourceOfSupply != null && _sourceOfSupply!.isNotEmpty) + 'source_of_supply': _sourceOfSupply, if (_gstinController.text.trim().isNotEmpty) 'gstin': _gstinController.text.trim(), if (_panController.text.trim().isNotEmpty) 'pan': _panController.text.trim(), @@ -140,6 +149,8 @@ class _VendorFormPanelState extends ConsumerState { @override Widget build(BuildContext context) { final paymentTermsAsync = ref.watch(vendorPaymentTermsProvider); + final gstTreatmentsAsync = ref.watch(vendorGstTreatmentsProvider); + final sourceOfSupplyAsync = ref.watch(vendorSourceOfSupplyProvider); if (widget.isEditing) { ref.listen(vendorFormProvider(widget.vendorId), (prev, next) { @@ -185,13 +196,25 @@ class _VendorFormPanelState extends ConsumerState { e is Failure ? e : Failure.unknown(message: e.toString()), onRetry: () => ref.invalidate(vendorFormProvider(widget.vendorId)), ), - data: (_) => _buildForm(paymentTermsAsync), + data: (_) => _buildForm( + paymentTermsAsync, + gstTreatmentsAsync, + sourceOfSupplyAsync, + ), ) - : _buildForm(paymentTermsAsync), + : _buildForm( + paymentTermsAsync, + gstTreatmentsAsync, + sourceOfSupplyAsync, + ), ); } - Widget _buildForm(AsyncValue> paymentTermsAsync) { + Widget _buildForm( + AsyncValue> paymentTermsAsync, + AsyncValue> gstTreatmentsAsync, + AsyncValue> sourceOfSupplyAsync, + ) { return Form( key: _formKey, child: Column( @@ -213,6 +236,46 @@ class _VendorFormPanelState extends ConsumerState { validator: (v) => v == null ? 'Vendor type is required' : null, ), const SizedBox(height: 12), + SidePanelFormRow( + left: gstTreatmentsAsync.when( + loading: () => const LinearProgressIndicator(), + error: (_, __) => AppDropdown( + label: 'GST Treatment', + value: _gstTreatment, + options: gstTreatmentOptions + .map((e) => AppDropdownOption(value: e.$1, label: e.$2)) + .toList(), + onChanged: (v) => setState(() => _gstTreatment = v), + ), + data: (options) => AppSearchableDropdown( + label: 'GST Treatment', + value: _gstTreatment, + searchHint: 'Search GST treatment...', + options: options + .map((o) => AppDropdownOption(value: o.id, label: o.name)) + .toList(), + onChanged: (v) => setState(() => _gstTreatment = v), + ), + ), + right: sourceOfSupplyAsync.when( + loading: () => const LinearProgressIndicator(), + error: (_, __) => AppTextField( + controller: TextEditingController(text: _sourceOfSupply ?? ''), + label: 'Source of Supply', + onChanged: (v) => _sourceOfSupply = v, + ), + data: (options) => AppSearchableDropdown( + label: 'Source of Supply', + value: _sourceOfSupply, + searchHint: 'Search state...', + options: options + .map((o) => AppDropdownOption(value: o.id, label: o.name)) + .toList(), + onChanged: (v) => setState(() => _sourceOfSupply = v), + ), + ), + ), + const SizedBox(height: 12), SidePanelFormRow( left: AppTextField( controller: _gstinController, @@ -286,3 +349,15 @@ final vendorPaymentTermsProvider = final dataSource = ref.watch(masterRemoteDataSourceProvider); return dataSource.listPaymentTerms(); }); + +final vendorGstTreatmentsProvider = + FutureProvider>((ref) async { + final dataSource = ref.watch(vendorRemoteDataSourceProvider); + return dataSource.getGstTreatments(); +}); + +final vendorSourceOfSupplyProvider = + FutureProvider>((ref) async { + final dataSource = ref.watch(vendorRemoteDataSourceProvider); + return dataSource.getSourceOfSupply(); +}); diff --git a/lib/shared/models/asset_model.dart b/lib/shared/models/asset_model.dart index 56e4d73..e4e58ce 100644 --- a/lib/shared/models/asset_model.dart +++ b/lib/shared/models/asset_model.dart @@ -64,6 +64,38 @@ Object? _readPlantId(Map json, String key) { return null; } +Object? _readAssetSubcategoryName(Map json, String key) { + final flat = json['asset_subcategory_name']; + if (flat is String && flat.isNotEmpty) return flat; + return _readNestedName(json, 'asset_subcategory'); +} + +Object? _readAssetSubcategoryId(Map json, String key) { + final flat = json['asset_subcategory_id']; + if (flat != null) return flat; + final nested = json['asset_subcategory']; + if (nested is Map) return nested['id']; + return null; +} + +Object? _readDepartmentName(Map json, String key) { + final flat = json['department_name']; + if (flat is String && flat.isNotEmpty) return flat; + return _readNestedName(json, 'department'); +} + +Object? _readWarehouseName(Map json, String key) { + final flat = json['warehouse_name']; + if (flat is String && flat.isNotEmpty) return flat; + return _readNestedName(json, 'warehouse'); +} + +Object? _readVendorNameFromNested(Map json, String key) { + final flat = json['vendor_name']; + if (flat is String && flat.isNotEmpty) return flat; + return _readNestedName(json, 'vendor'); +} + @freezed class AssetCategoryModel with _$AssetCategoryModel { const factory AssetCategoryModel({ @@ -97,13 +129,50 @@ class AssetModel with _$AssetModel { int? assetCategoryId, @JsonKey(name: 'asset_category_name', readValue: _readAssetCategoryName) String? assetCategoryName, + @JsonKey( + name: 'asset_subcategory_id', + readValue: _readAssetSubcategoryId, + fromJson: _intFromJsonNullable, + ) + int? assetSubcategoryId, + @JsonKey(name: 'asset_subcategory_name', readValue: _readAssetSubcategoryName) + String? assetSubcategoryName, @JsonKey(name: 'plant_id', readValue: _readPlantId, fromJson: _intFromJsonNullable) int? plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, + @JsonKey(name: 'brand_model') String? brandModel, + String? manufacturer, + @JsonKey(name: 'serial_number') String? serialNumber, + @JsonKey(name: 'part_number') String? partNumber, + @JsonKey(name: 'department_id', fromJson: _intFromJsonNullable) int? departmentId, + @JsonKey(name: 'department_name', readValue: _readDepartmentName) + String? departmentName, + @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) int? warehouseId, + @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) String? warehouseName, + @JsonKey(name: 'location_detail') String? locationDetail, + @JsonKey(name: 'assigned_to_user_id', fromJson: _intFromJsonNullable) + int? assignedToUserId, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, + @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) String? vendorName, + @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) int? poId, + @JsonKey(name: 'grn_id', fromJson: _intFromJsonNullable) int? grnId, + @JsonKey(name: 'grn_item_id', fromJson: _intFromJsonNullable) int? grnItemId, + @JsonKey(name: 'purchase_date', fromJson: _dateFromJsonNullable) DateTime? purchaseDate, + @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) double? purchaseCost, + @JsonKey(name: 'useful_life_years', fromJson: _intFromJsonNullable) int? usefulLifeYears, + @JsonKey(name: 'depreciation_method') String? depreciationMethod, + @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) + double? depreciationRate, + @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) double? salvageValue, @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) DateTime? warrantyExpiryDate, - @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) double? purchaseCost, + String? condition, String? status, + @JsonKey(name: 'qr_code_value') String? qrCodeValue, + @JsonKey(name: 'disposal_date', fromJson: _dateFromJsonNullable) DateTime? disposalDate, + @JsonKey(name: 'disposal_reason') String? disposalReason, + @JsonKey(name: 'disposal_value', fromJson: _doubleFromJsonNullable) double? disposalValue, + String? remarks, @JsonKey(name: 'is_active') @Default(true) bool isActive, @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) DateTime? updatedAt, @@ -112,6 +181,45 @@ class AssetModel with _$AssetModel { factory AssetModel.fromJson(Map json) => _$AssetModelFromJson(json); } +const assetConditionOptions = [ + ('NEW', 'New'), + ('GOOD', 'Good'), + ('FAIR', 'Fair'), + ('POOR', 'Poor'), +]; + +const assetStatusOptions = [ + ('IN_USE', 'In Use'), + ('IDLE', 'Idle'), + ('UNDER_MAINTENANCE', 'Under Maintenance'), + ('DISPOSED', 'Disposed'), + ('SCRAPPED', 'Scrapped'), +]; + +const depreciationMethodOptions = [ + ('SLM', 'SLM'), + ('WDV', 'WDV'), + ('OTHER', 'Other'), +]; + +String assetConditionLabel(String? value) { + if (value == null) return '—'; + return assetConditionOptions + .where((e) => e.$1 == value) + .map((e) => e.$2) + .firstOrNull ?? + value; +} + +String assetStatusLabel(String? value) { + if (value == null) return '—'; + return assetStatusOptions + .where((e) => e.$1 == value) + .map((e) => e.$2) + .firstOrNull ?? + value.replaceAll('_', ' '); +} + @freezed class AmcContractModel with _$AmcContractModel { const factory AmcContractModel({ @@ -123,9 +231,21 @@ class AmcContractModel with _$AmcContractModel { @JsonKey(name: 'contract_type') String? contractType, @JsonKey(name: 'start_date', fromJson: _dateFromJson) required DateTime startDate, @JsonKey(name: 'end_date', fromJson: _dateFromJson) required DateTime endDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? renewalDate, @JsonKey(name: 'annual_cost', fromJson: _doubleFromJsonNullable) double? annualCost, + @JsonKey(name: 'payment_frequency') String? paymentFrequency, + @JsonKey(name: 'service_frequency') String? serviceFrequency, + @JsonKey(name: 'visits_per_year', fromJson: _intFromJsonNullable) int? visitsPerYear, + @JsonKey(name: 'contact_person') String? contactPerson, + @JsonKey(name: 'contact_phone') String? contactPhone, + @JsonKey(name: 'contact_email') String? contactEmail, + @JsonKey(name: 'scope_of_work') String? scopeOfWork, + String? exclusions, + String? remarks, + @JsonKey(name: 'is_active') @Default(true) bool isActive, @Default('active') String status, - DateTime? createdAt, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }) = _AmcContractModel; factory AmcContractModel.fromJson(Map json) => @@ -139,12 +259,26 @@ class ServiceVisitModel with _$ServiceVisitModel { @JsonKey(name: 'asset_id', fromJson: _idFromJson) String? assetId, @JsonKey(name: 'visit_type') required String visitType, @JsonKey(name: 'visit_date', fromJson: _dateFromJson) required DateTime visitDate, + @JsonKey(name: 'amc_contract_id', fromJson: _intFromJsonNullable) int? amcContractId, + @JsonKey(name: 'visit_number', fromJson: _intFromJsonNullable) int? visitNumber, @JsonKey(name: 'complaint_no') String? complaintNo, + @JsonKey(name: 'complaint_date', fromJson: _dateFromJsonNullable) DateTime? complaintDate, + @JsonKey(name: 'complaint_desc') String? complaintDesc, + @JsonKey(name: 'engineer_name') String? engineerName, + @JsonKey(name: 'engineer_phone') String? engineerPhone, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, + @JsonKey(name: 'vendor_name') String? vendorName, @JsonKey(name: 'work_done') String? workDone, + @JsonKey(name: 'parts_replaced') String? partsReplaced, @JsonKey(name: 'next_service_date', fromJson: _dateFromJsonNullable) DateTime? nextServiceDate, @Default('COMPLETED') String status, - DateTime? createdAt, + @JsonKey(name: 'downtime_hours', fromJson: _doubleFromJsonNullable) double? downtimeHours, + @JsonKey(name: 'service_cost', fromJson: _doubleFromJsonNullable) double? serviceCost, + @JsonKey(name: 'is_under_amc') @Default(false) bool isUnderAmc, + @JsonKey(name: 'asset_condition_after') String? assetConditionAfter, + String? remarks, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }) = _ServiceVisitModel; factory ServiceVisitModel.fromJson(Map json) => @@ -158,13 +292,25 @@ class InsurancePolicyModel with _$InsurancePolicyModel { @JsonKey(name: 'asset_id', fromJson: _idFromJson) String? assetId, @JsonKey(name: 'policy_no') required String policyNo, @JsonKey(name: 'insurer_name') required String insurerName, + @JsonKey(name: 'insurer_branch') String? insurerBranch, + @JsonKey(name: 'insurer_contact') String? insurerContact, + @JsonKey(name: 'insurer_phone') String? insurerPhone, + @JsonKey(name: 'insurer_email') String? insurerEmail, @JsonKey(name: 'policy_type') String? policyType, @JsonKey(name: 'sum_insured', fromJson: _doubleFromJsonNullable) double? sumInsured, + @JsonKey(name: 'annual_premium', fromJson: _doubleFromJsonNullable) double? annualPremium, @JsonKey(name: 'policy_start_date', fromJson: _dateFromJson) required DateTime policyStartDate, @JsonKey(name: 'policy_end_date', fromJson: _dateFromJson) required DateTime policyEndDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) DateTime? renewalDate, + @JsonKey(name: 'is_auto_renewal') @Default(false) bool isAutoRenewal, + @JsonKey(name: 'premium_paid') @Default(false) bool premiumPaid, + @JsonKey(name: 'premium_paid_date', fromJson: _dateFromJsonNullable) + DateTime? premiumPaidDate, + String? remarks, + @JsonKey(name: 'is_active') @Default(true) bool isActive, @Default('active') String status, - DateTime? createdAt, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }) = _InsurancePolicyModel; factory InsurancePolicyModel.fromJson(Map json) => @@ -191,3 +337,76 @@ class AssetAlertModel with _$AssetAlertModel { factory AssetAlertModel.fromJson(Map json) => _$AssetAlertModelFromJson(json); } + +class AssetTransferHistoryModel { + const AssetTransferHistoryModel({ + required this.id, + this.transferDate, + this.reason, + this.fromPlantName, + this.toPlantName, + this.fromDepartmentName, + this.toDepartmentName, + this.fromWarehouseName, + this.toWarehouseName, + this.fromUserName, + this.toUserName, + this.createdAt, + this.createdByName, + }); + + final String id; + final DateTime? transferDate; + final String? reason; + final String? fromPlantName; + final String? toPlantName; + final String? fromDepartmentName; + final String? toDepartmentName; + final String? fromWarehouseName; + final String? toWarehouseName; + final String? fromUserName; + final String? toUserName; + final DateTime? createdAt; + final String? createdByName; + + factory AssetTransferHistoryModel.fromJson(Map json) { + String? readName(String key) { + final value = json[key]; + if (value is String && value.trim().isNotEmpty) return value.trim(); + return null; + } + + String? readNestedName(String key) { + final nested = json[key]; + if (nested is Map) { + final name = nested['name']; + if (name is String && name.trim().isNotEmpty) return name.trim(); + } + return null; + } + + DateTime? parseDate(dynamic value) { + if (value == null) return null; + if (value is DateTime) return value; + return DateTime.tryParse(value.toString()); + } + + return AssetTransferHistoryModel( + id: _idFromJson(json['id']), + transferDate: parseDate(json['transfer_date']), + reason: readName('reason'), + fromPlantName: readName('from_plant_name') ?? readNestedName('from_plant'), + toPlantName: readName('to_plant_name') ?? readNestedName('to_plant'), + fromDepartmentName: + readName('from_department_name') ?? readNestedName('from_department'), + toDepartmentName: readName('to_department_name') ?? readNestedName('to_department'), + fromWarehouseName: + readName('from_warehouse_name') ?? readNestedName('from_warehouse'), + toWarehouseName: readName('to_warehouse_name') ?? readNestedName('to_warehouse'), + fromUserName: readName('from_user_name') ?? readNestedName('from_user'), + toUserName: readName('to_user_name') ?? readNestedName('to_user'), + createdAt: parseDate(json['created_at']), + createdByName: readName('created_by_name') ?? readNestedName('created_by'), + ); + } +} diff --git a/lib/shared/models/asset_model.freezed.dart b/lib/shared/models/asset_model.freezed.dart index 00ba61d..dad0362 100644 --- a/lib/shared/models/asset_model.freezed.dart +++ b/lib/shared/models/asset_model.freezed.dart @@ -404,6 +404,14 @@ mixin _$AssetModel { int? get assetCategoryId => throw _privateConstructorUsedError; @JsonKey(name: 'asset_category_name', readValue: _readAssetCategoryName) String? get assetCategoryName => throw _privateConstructorUsedError; + @JsonKey( + name: 'asset_subcategory_id', + readValue: _readAssetSubcategoryId, + fromJson: _intFromJsonNullable, + ) + int? get assetSubcategoryId => throw _privateConstructorUsedError; + @JsonKey(name: 'asset_subcategory_name', readValue: _readAssetSubcategoryName) + String? get assetSubcategoryName => throw _privateConstructorUsedError; @JsonKey( name: 'plant_id', readValue: _readPlantId, @@ -412,11 +420,60 @@ mixin _$AssetModel { int? get plantId => throw _privateConstructorUsedError; @JsonKey(name: 'plant_name', readValue: _readPlantName) String? get plantName => throw _privateConstructorUsedError; - @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) - DateTime? get warrantyExpiryDate => throw _privateConstructorUsedError; + @JsonKey(name: 'brand_model') + String? get brandModel => throw _privateConstructorUsedError; + String? get manufacturer => throw _privateConstructorUsedError; + @JsonKey(name: 'serial_number') + String? get serialNumber => throw _privateConstructorUsedError; + @JsonKey(name: 'part_number') + String? get partNumber => throw _privateConstructorUsedError; + @JsonKey(name: 'department_id', fromJson: _intFromJsonNullable) + int? get departmentId => throw _privateConstructorUsedError; + @JsonKey(name: 'department_name', readValue: _readDepartmentName) + String? get departmentName => throw _privateConstructorUsedError; + @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) + int? get warehouseId => throw _privateConstructorUsedError; + @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) + String? get warehouseName => throw _privateConstructorUsedError; + @JsonKey(name: 'location_detail') + String? get locationDetail => throw _privateConstructorUsedError; + @JsonKey(name: 'assigned_to_user_id', fromJson: _intFromJsonNullable) + int? get assignedToUserId => throw _privateConstructorUsedError; + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) + int? get vendorId => throw _privateConstructorUsedError; + @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) + String? get vendorName => throw _privateConstructorUsedError; + @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) + int? get poId => throw _privateConstructorUsedError; + @JsonKey(name: 'grn_id', fromJson: _intFromJsonNullable) + int? get grnId => throw _privateConstructorUsedError; + @JsonKey(name: 'grn_item_id', fromJson: _intFromJsonNullable) + int? get grnItemId => throw _privateConstructorUsedError; + @JsonKey(name: 'purchase_date', fromJson: _dateFromJsonNullable) + DateTime? get purchaseDate => throw _privateConstructorUsedError; @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) double? get purchaseCost => throw _privateConstructorUsedError; + @JsonKey(name: 'useful_life_years', fromJson: _intFromJsonNullable) + int? get usefulLifeYears => throw _privateConstructorUsedError; + @JsonKey(name: 'depreciation_method') + String? get depreciationMethod => throw _privateConstructorUsedError; + @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) + double? get depreciationRate => throw _privateConstructorUsedError; + @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) + double? get salvageValue => throw _privateConstructorUsedError; + @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) + DateTime? get warrantyExpiryDate => throw _privateConstructorUsedError; + String? get condition => throw _privateConstructorUsedError; String? get status => throw _privateConstructorUsedError; + @JsonKey(name: 'qr_code_value') + String? get qrCodeValue => throw _privateConstructorUsedError; + @JsonKey(name: 'disposal_date', fromJson: _dateFromJsonNullable) + DateTime? get disposalDate => throw _privateConstructorUsedError; + @JsonKey(name: 'disposal_reason') + String? get disposalReason => throw _privateConstructorUsedError; + @JsonKey(name: 'disposal_value', fromJson: _doubleFromJsonNullable) + double? get disposalValue => throw _privateConstructorUsedError; + String? get remarks => throw _privateConstructorUsedError; @JsonKey(name: 'is_active') bool get isActive => throw _privateConstructorUsedError; @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) @@ -453,6 +510,17 @@ abstract class $AssetModelCopyWith<$Res> { int? assetCategoryId, @JsonKey(name: 'asset_category_name', readValue: _readAssetCategoryName) String? assetCategoryName, + @JsonKey( + name: 'asset_subcategory_id', + readValue: _readAssetSubcategoryId, + fromJson: _intFromJsonNullable, + ) + int? assetSubcategoryId, + @JsonKey( + name: 'asset_subcategory_name', + readValue: _readAssetSubcategoryName, + ) + String? assetSubcategoryName, @JsonKey( name: 'plant_id', readValue: _readPlantId, @@ -460,11 +528,50 @@ abstract class $AssetModelCopyWith<$Res> { ) int? plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, - @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) - DateTime? warrantyExpiryDate, + @JsonKey(name: 'brand_model') String? brandModel, + String? manufacturer, + @JsonKey(name: 'serial_number') String? serialNumber, + @JsonKey(name: 'part_number') String? partNumber, + @JsonKey(name: 'department_id', fromJson: _intFromJsonNullable) + int? departmentId, + @JsonKey(name: 'department_name', readValue: _readDepartmentName) + String? departmentName, + @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) + int? warehouseId, + @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) + String? warehouseName, + @JsonKey(name: 'location_detail') String? locationDetail, + @JsonKey(name: 'assigned_to_user_id', fromJson: _intFromJsonNullable) + int? assignedToUserId, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, + @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) + String? vendorName, + @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) int? poId, + @JsonKey(name: 'grn_id', fromJson: _intFromJsonNullable) int? grnId, + @JsonKey(name: 'grn_item_id', fromJson: _intFromJsonNullable) + int? grnItemId, + @JsonKey(name: 'purchase_date', fromJson: _dateFromJsonNullable) + DateTime? purchaseDate, @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) double? purchaseCost, + @JsonKey(name: 'useful_life_years', fromJson: _intFromJsonNullable) + int? usefulLifeYears, + @JsonKey(name: 'depreciation_method') String? depreciationMethod, + @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) + double? depreciationRate, + @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) + double? salvageValue, + @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) + DateTime? warrantyExpiryDate, + String? condition, String? status, + @JsonKey(name: 'qr_code_value') String? qrCodeValue, + @JsonKey(name: 'disposal_date', fromJson: _dateFromJsonNullable) + DateTime? disposalDate, + @JsonKey(name: 'disposal_reason') String? disposalReason, + @JsonKey(name: 'disposal_value', fromJson: _doubleFromJsonNullable) + double? disposalValue, + String? remarks, @JsonKey(name: 'is_active') bool isActive, @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, @@ -493,11 +600,39 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> Object? assetCode = freezed, Object? assetCategoryId = freezed, Object? assetCategoryName = freezed, + Object? assetSubcategoryId = freezed, + Object? assetSubcategoryName = freezed, Object? plantId = freezed, Object? plantName = freezed, - Object? warrantyExpiryDate = freezed, + Object? brandModel = freezed, + Object? manufacturer = freezed, + Object? serialNumber = freezed, + Object? partNumber = freezed, + Object? departmentId = freezed, + Object? departmentName = freezed, + Object? warehouseId = freezed, + Object? warehouseName = freezed, + Object? locationDetail = freezed, + Object? assignedToUserId = freezed, + Object? vendorId = freezed, + Object? vendorName = freezed, + Object? poId = freezed, + Object? grnId = freezed, + Object? grnItemId = freezed, + Object? purchaseDate = freezed, Object? purchaseCost = freezed, + Object? usefulLifeYears = freezed, + Object? depreciationMethod = freezed, + Object? depreciationRate = freezed, + Object? salvageValue = freezed, + Object? warrantyExpiryDate = freezed, + Object? condition = freezed, Object? status = freezed, + Object? qrCodeValue = freezed, + Object? disposalDate = freezed, + Object? disposalReason = freezed, + Object? disposalValue = freezed, + Object? remarks = freezed, Object? isActive = null, Object? createdAt = freezed, Object? updatedAt = freezed, @@ -524,6 +659,14 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> ? _value.assetCategoryName : assetCategoryName // ignore: cast_nullable_to_non_nullable as String?, + assetSubcategoryId: freezed == assetSubcategoryId + ? _value.assetSubcategoryId + : assetSubcategoryId // ignore: cast_nullable_to_non_nullable + as int?, + assetSubcategoryName: freezed == assetSubcategoryName + ? _value.assetSubcategoryName + : assetSubcategoryName // ignore: cast_nullable_to_non_nullable + as String?, plantId: freezed == plantId ? _value.plantId : plantId // ignore: cast_nullable_to_non_nullable @@ -532,18 +675,122 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> ? _value.plantName : plantName // ignore: cast_nullable_to_non_nullable as String?, - warrantyExpiryDate: freezed == warrantyExpiryDate - ? _value.warrantyExpiryDate - : warrantyExpiryDate // ignore: cast_nullable_to_non_nullable + brandModel: freezed == brandModel + ? _value.brandModel + : brandModel // ignore: cast_nullable_to_non_nullable + as String?, + manufacturer: freezed == manufacturer + ? _value.manufacturer + : manufacturer // ignore: cast_nullable_to_non_nullable + as String?, + serialNumber: freezed == serialNumber + ? _value.serialNumber + : serialNumber // ignore: cast_nullable_to_non_nullable + as String?, + partNumber: freezed == partNumber + ? _value.partNumber + : partNumber // ignore: cast_nullable_to_non_nullable + as String?, + departmentId: freezed == departmentId + ? _value.departmentId + : departmentId // ignore: cast_nullable_to_non_nullable + as int?, + departmentName: freezed == departmentName + ? _value.departmentName + : departmentName // ignore: cast_nullable_to_non_nullable + as String?, + warehouseId: freezed == warehouseId + ? _value.warehouseId + : warehouseId // ignore: cast_nullable_to_non_nullable + as int?, + warehouseName: freezed == warehouseName + ? _value.warehouseName + : warehouseName // ignore: cast_nullable_to_non_nullable + as String?, + locationDetail: freezed == locationDetail + ? _value.locationDetail + : locationDetail // ignore: cast_nullable_to_non_nullable + as String?, + assignedToUserId: freezed == assignedToUserId + ? _value.assignedToUserId + : assignedToUserId // ignore: cast_nullable_to_non_nullable + as int?, + vendorId: freezed == vendorId + ? _value.vendorId + : vendorId // ignore: cast_nullable_to_non_nullable + as int?, + vendorName: freezed == vendorName + ? _value.vendorName + : vendorName // ignore: cast_nullable_to_non_nullable + as String?, + poId: freezed == poId + ? _value.poId + : poId // ignore: cast_nullable_to_non_nullable + as int?, + grnId: freezed == grnId + ? _value.grnId + : grnId // ignore: cast_nullable_to_non_nullable + as int?, + grnItemId: freezed == grnItemId + ? _value.grnItemId + : grnItemId // ignore: cast_nullable_to_non_nullable + as int?, + purchaseDate: freezed == purchaseDate + ? _value.purchaseDate + : purchaseDate // ignore: cast_nullable_to_non_nullable as DateTime?, purchaseCost: freezed == purchaseCost ? _value.purchaseCost : purchaseCost // ignore: cast_nullable_to_non_nullable as double?, + usefulLifeYears: freezed == usefulLifeYears + ? _value.usefulLifeYears + : usefulLifeYears // ignore: cast_nullable_to_non_nullable + as int?, + depreciationMethod: freezed == depreciationMethod + ? _value.depreciationMethod + : depreciationMethod // ignore: cast_nullable_to_non_nullable + as String?, + depreciationRate: freezed == depreciationRate + ? _value.depreciationRate + : depreciationRate // ignore: cast_nullable_to_non_nullable + as double?, + salvageValue: freezed == salvageValue + ? _value.salvageValue + : salvageValue // ignore: cast_nullable_to_non_nullable + as double?, + warrantyExpiryDate: freezed == warrantyExpiryDate + ? _value.warrantyExpiryDate + : warrantyExpiryDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + condition: freezed == condition + ? _value.condition + : condition // ignore: cast_nullable_to_non_nullable + as String?, status: freezed == status ? _value.status : status // ignore: cast_nullable_to_non_nullable as String?, + qrCodeValue: freezed == qrCodeValue + ? _value.qrCodeValue + : qrCodeValue // ignore: cast_nullable_to_non_nullable + as String?, + disposalDate: freezed == disposalDate + ? _value.disposalDate + : disposalDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + disposalReason: freezed == disposalReason + ? _value.disposalReason + : disposalReason // ignore: cast_nullable_to_non_nullable + as String?, + disposalValue: freezed == disposalValue + ? _value.disposalValue + : disposalValue // ignore: cast_nullable_to_non_nullable + as double?, + remarks: freezed == remarks + ? _value.remarks + : remarks // ignore: cast_nullable_to_non_nullable + as String?, isActive: null == isActive ? _value.isActive : isActive // ignore: cast_nullable_to_non_nullable @@ -583,6 +830,17 @@ abstract class _$$AssetModelImplCopyWith<$Res> int? assetCategoryId, @JsonKey(name: 'asset_category_name', readValue: _readAssetCategoryName) String? assetCategoryName, + @JsonKey( + name: 'asset_subcategory_id', + readValue: _readAssetSubcategoryId, + fromJson: _intFromJsonNullable, + ) + int? assetSubcategoryId, + @JsonKey( + name: 'asset_subcategory_name', + readValue: _readAssetSubcategoryName, + ) + String? assetSubcategoryName, @JsonKey( name: 'plant_id', readValue: _readPlantId, @@ -590,11 +848,50 @@ abstract class _$$AssetModelImplCopyWith<$Res> ) int? plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, - @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) - DateTime? warrantyExpiryDate, + @JsonKey(name: 'brand_model') String? brandModel, + String? manufacturer, + @JsonKey(name: 'serial_number') String? serialNumber, + @JsonKey(name: 'part_number') String? partNumber, + @JsonKey(name: 'department_id', fromJson: _intFromJsonNullable) + int? departmentId, + @JsonKey(name: 'department_name', readValue: _readDepartmentName) + String? departmentName, + @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) + int? warehouseId, + @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) + String? warehouseName, + @JsonKey(name: 'location_detail') String? locationDetail, + @JsonKey(name: 'assigned_to_user_id', fromJson: _intFromJsonNullable) + int? assignedToUserId, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, + @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) + String? vendorName, + @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) int? poId, + @JsonKey(name: 'grn_id', fromJson: _intFromJsonNullable) int? grnId, + @JsonKey(name: 'grn_item_id', fromJson: _intFromJsonNullable) + int? grnItemId, + @JsonKey(name: 'purchase_date', fromJson: _dateFromJsonNullable) + DateTime? purchaseDate, @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) double? purchaseCost, + @JsonKey(name: 'useful_life_years', fromJson: _intFromJsonNullable) + int? usefulLifeYears, + @JsonKey(name: 'depreciation_method') String? depreciationMethod, + @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) + double? depreciationRate, + @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) + double? salvageValue, + @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) + DateTime? warrantyExpiryDate, + String? condition, String? status, + @JsonKey(name: 'qr_code_value') String? qrCodeValue, + @JsonKey(name: 'disposal_date', fromJson: _dateFromJsonNullable) + DateTime? disposalDate, + @JsonKey(name: 'disposal_reason') String? disposalReason, + @JsonKey(name: 'disposal_value', fromJson: _doubleFromJsonNullable) + double? disposalValue, + String? remarks, @JsonKey(name: 'is_active') bool isActive, @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, @@ -622,11 +919,39 @@ class __$$AssetModelImplCopyWithImpl<$Res> Object? assetCode = freezed, Object? assetCategoryId = freezed, Object? assetCategoryName = freezed, + Object? assetSubcategoryId = freezed, + Object? assetSubcategoryName = freezed, Object? plantId = freezed, Object? plantName = freezed, - Object? warrantyExpiryDate = freezed, + Object? brandModel = freezed, + Object? manufacturer = freezed, + Object? serialNumber = freezed, + Object? partNumber = freezed, + Object? departmentId = freezed, + Object? departmentName = freezed, + Object? warehouseId = freezed, + Object? warehouseName = freezed, + Object? locationDetail = freezed, + Object? assignedToUserId = freezed, + Object? vendorId = freezed, + Object? vendorName = freezed, + Object? poId = freezed, + Object? grnId = freezed, + Object? grnItemId = freezed, + Object? purchaseDate = freezed, Object? purchaseCost = freezed, + Object? usefulLifeYears = freezed, + Object? depreciationMethod = freezed, + Object? depreciationRate = freezed, + Object? salvageValue = freezed, + Object? warrantyExpiryDate = freezed, + Object? condition = freezed, Object? status = freezed, + Object? qrCodeValue = freezed, + Object? disposalDate = freezed, + Object? disposalReason = freezed, + Object? disposalValue = freezed, + Object? remarks = freezed, Object? isActive = null, Object? createdAt = freezed, Object? updatedAt = freezed, @@ -653,6 +978,14 @@ class __$$AssetModelImplCopyWithImpl<$Res> ? _value.assetCategoryName : assetCategoryName // ignore: cast_nullable_to_non_nullable as String?, + assetSubcategoryId: freezed == assetSubcategoryId + ? _value.assetSubcategoryId + : assetSubcategoryId // ignore: cast_nullable_to_non_nullable + as int?, + assetSubcategoryName: freezed == assetSubcategoryName + ? _value.assetSubcategoryName + : assetSubcategoryName // ignore: cast_nullable_to_non_nullable + as String?, plantId: freezed == plantId ? _value.plantId : plantId // ignore: cast_nullable_to_non_nullable @@ -661,18 +994,122 @@ class __$$AssetModelImplCopyWithImpl<$Res> ? _value.plantName : plantName // ignore: cast_nullable_to_non_nullable as String?, - warrantyExpiryDate: freezed == warrantyExpiryDate - ? _value.warrantyExpiryDate - : warrantyExpiryDate // ignore: cast_nullable_to_non_nullable + brandModel: freezed == brandModel + ? _value.brandModel + : brandModel // ignore: cast_nullable_to_non_nullable + as String?, + manufacturer: freezed == manufacturer + ? _value.manufacturer + : manufacturer // ignore: cast_nullable_to_non_nullable + as String?, + serialNumber: freezed == serialNumber + ? _value.serialNumber + : serialNumber // ignore: cast_nullable_to_non_nullable + as String?, + partNumber: freezed == partNumber + ? _value.partNumber + : partNumber // ignore: cast_nullable_to_non_nullable + as String?, + departmentId: freezed == departmentId + ? _value.departmentId + : departmentId // ignore: cast_nullable_to_non_nullable + as int?, + departmentName: freezed == departmentName + ? _value.departmentName + : departmentName // ignore: cast_nullable_to_non_nullable + as String?, + warehouseId: freezed == warehouseId + ? _value.warehouseId + : warehouseId // ignore: cast_nullable_to_non_nullable + as int?, + warehouseName: freezed == warehouseName + ? _value.warehouseName + : warehouseName // ignore: cast_nullable_to_non_nullable + as String?, + locationDetail: freezed == locationDetail + ? _value.locationDetail + : locationDetail // ignore: cast_nullable_to_non_nullable + as String?, + assignedToUserId: freezed == assignedToUserId + ? _value.assignedToUserId + : assignedToUserId // ignore: cast_nullable_to_non_nullable + as int?, + vendorId: freezed == vendorId + ? _value.vendorId + : vendorId // ignore: cast_nullable_to_non_nullable + as int?, + vendorName: freezed == vendorName + ? _value.vendorName + : vendorName // ignore: cast_nullable_to_non_nullable + as String?, + poId: freezed == poId + ? _value.poId + : poId // ignore: cast_nullable_to_non_nullable + as int?, + grnId: freezed == grnId + ? _value.grnId + : grnId // ignore: cast_nullable_to_non_nullable + as int?, + grnItemId: freezed == grnItemId + ? _value.grnItemId + : grnItemId // ignore: cast_nullable_to_non_nullable + as int?, + purchaseDate: freezed == purchaseDate + ? _value.purchaseDate + : purchaseDate // ignore: cast_nullable_to_non_nullable as DateTime?, purchaseCost: freezed == purchaseCost ? _value.purchaseCost : purchaseCost // ignore: cast_nullable_to_non_nullable as double?, + usefulLifeYears: freezed == usefulLifeYears + ? _value.usefulLifeYears + : usefulLifeYears // ignore: cast_nullable_to_non_nullable + as int?, + depreciationMethod: freezed == depreciationMethod + ? _value.depreciationMethod + : depreciationMethod // ignore: cast_nullable_to_non_nullable + as String?, + depreciationRate: freezed == depreciationRate + ? _value.depreciationRate + : depreciationRate // ignore: cast_nullable_to_non_nullable + as double?, + salvageValue: freezed == salvageValue + ? _value.salvageValue + : salvageValue // ignore: cast_nullable_to_non_nullable + as double?, + warrantyExpiryDate: freezed == warrantyExpiryDate + ? _value.warrantyExpiryDate + : warrantyExpiryDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + condition: freezed == condition + ? _value.condition + : condition // ignore: cast_nullable_to_non_nullable + as String?, status: freezed == status ? _value.status : status // ignore: cast_nullable_to_non_nullable as String?, + qrCodeValue: freezed == qrCodeValue + ? _value.qrCodeValue + : qrCodeValue // ignore: cast_nullable_to_non_nullable + as String?, + disposalDate: freezed == disposalDate + ? _value.disposalDate + : disposalDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + disposalReason: freezed == disposalReason + ? _value.disposalReason + : disposalReason // ignore: cast_nullable_to_non_nullable + as String?, + disposalValue: freezed == disposalValue + ? _value.disposalValue + : disposalValue // ignore: cast_nullable_to_non_nullable + as double?, + remarks: freezed == remarks + ? _value.remarks + : remarks // ignore: cast_nullable_to_non_nullable + as String?, isActive: null == isActive ? _value.isActive : isActive // ignore: cast_nullable_to_non_nullable @@ -705,6 +1142,17 @@ class _$AssetModelImpl implements _AssetModel { this.assetCategoryId, @JsonKey(name: 'asset_category_name', readValue: _readAssetCategoryName) this.assetCategoryName, + @JsonKey( + name: 'asset_subcategory_id', + readValue: _readAssetSubcategoryId, + fromJson: _intFromJsonNullable, + ) + this.assetSubcategoryId, + @JsonKey( + name: 'asset_subcategory_name', + readValue: _readAssetSubcategoryName, + ) + this.assetSubcategoryName, @JsonKey( name: 'plant_id', readValue: _readPlantId, @@ -712,11 +1160,50 @@ class _$AssetModelImpl implements _AssetModel { ) this.plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) this.plantName, - @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) - this.warrantyExpiryDate, + @JsonKey(name: 'brand_model') this.brandModel, + this.manufacturer, + @JsonKey(name: 'serial_number') this.serialNumber, + @JsonKey(name: 'part_number') this.partNumber, + @JsonKey(name: 'department_id', fromJson: _intFromJsonNullable) + this.departmentId, + @JsonKey(name: 'department_name', readValue: _readDepartmentName) + this.departmentName, + @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) + this.warehouseId, + @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) + this.warehouseName, + @JsonKey(name: 'location_detail') this.locationDetail, + @JsonKey(name: 'assigned_to_user_id', fromJson: _intFromJsonNullable) + this.assignedToUserId, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) this.vendorId, + @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) + this.vendorName, + @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) this.poId, + @JsonKey(name: 'grn_id', fromJson: _intFromJsonNullable) this.grnId, + @JsonKey(name: 'grn_item_id', fromJson: _intFromJsonNullable) + this.grnItemId, + @JsonKey(name: 'purchase_date', fromJson: _dateFromJsonNullable) + this.purchaseDate, @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) this.purchaseCost, + @JsonKey(name: 'useful_life_years', fromJson: _intFromJsonNullable) + this.usefulLifeYears, + @JsonKey(name: 'depreciation_method') this.depreciationMethod, + @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) + this.depreciationRate, + @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) + this.salvageValue, + @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) + this.warrantyExpiryDate, + this.condition, this.status, + @JsonKey(name: 'qr_code_value') this.qrCodeValue, + @JsonKey(name: 'disposal_date', fromJson: _dateFromJsonNullable) + this.disposalDate, + @JsonKey(name: 'disposal_reason') this.disposalReason, + @JsonKey(name: 'disposal_value', fromJson: _doubleFromJsonNullable) + this.disposalValue, + this.remarks, @JsonKey(name: 'is_active') this.isActive = true, @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) this.createdAt, @@ -747,6 +1234,16 @@ class _$AssetModelImpl implements _AssetModel { @JsonKey(name: 'asset_category_name', readValue: _readAssetCategoryName) final String? assetCategoryName; @override + @JsonKey( + name: 'asset_subcategory_id', + readValue: _readAssetSubcategoryId, + fromJson: _intFromJsonNullable, + ) + final int? assetSubcategoryId; + @override + @JsonKey(name: 'asset_subcategory_name', readValue: _readAssetSubcategoryName) + final String? assetSubcategoryName; + @override @JsonKey( name: 'plant_id', readValue: _readPlantId, @@ -757,14 +1254,89 @@ class _$AssetModelImpl implements _AssetModel { @JsonKey(name: 'plant_name', readValue: _readPlantName) final String? plantName; @override - @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) - final DateTime? warrantyExpiryDate; + @JsonKey(name: 'brand_model') + final String? brandModel; + @override + final String? manufacturer; + @override + @JsonKey(name: 'serial_number') + final String? serialNumber; + @override + @JsonKey(name: 'part_number') + final String? partNumber; + @override + @JsonKey(name: 'department_id', fromJson: _intFromJsonNullable) + final int? departmentId; + @override + @JsonKey(name: 'department_name', readValue: _readDepartmentName) + final String? departmentName; + @override + @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) + final int? warehouseId; + @override + @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) + final String? warehouseName; + @override + @JsonKey(name: 'location_detail') + final String? locationDetail; + @override + @JsonKey(name: 'assigned_to_user_id', fromJson: _intFromJsonNullable) + final int? assignedToUserId; + @override + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) + final int? vendorId; + @override + @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) + final String? vendorName; + @override + @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) + final int? poId; + @override + @JsonKey(name: 'grn_id', fromJson: _intFromJsonNullable) + final int? grnId; + @override + @JsonKey(name: 'grn_item_id', fromJson: _intFromJsonNullable) + final int? grnItemId; + @override + @JsonKey(name: 'purchase_date', fromJson: _dateFromJsonNullable) + final DateTime? purchaseDate; @override @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) final double? purchaseCost; @override + @JsonKey(name: 'useful_life_years', fromJson: _intFromJsonNullable) + final int? usefulLifeYears; + @override + @JsonKey(name: 'depreciation_method') + final String? depreciationMethod; + @override + @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) + final double? depreciationRate; + @override + @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) + final double? salvageValue; + @override + @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) + final DateTime? warrantyExpiryDate; + @override + final String? condition; + @override final String? status; @override + @JsonKey(name: 'qr_code_value') + final String? qrCodeValue; + @override + @JsonKey(name: 'disposal_date', fromJson: _dateFromJsonNullable) + final DateTime? disposalDate; + @override + @JsonKey(name: 'disposal_reason') + final String? disposalReason; + @override + @JsonKey(name: 'disposal_value', fromJson: _doubleFromJsonNullable) + final double? disposalValue; + @override + final String? remarks; + @override @JsonKey(name: 'is_active') final bool isActive; @override @@ -776,7 +1348,7 @@ class _$AssetModelImpl implements _AssetModel { @override String toString() { - return 'AssetModel(id: $id, assetName: $assetName, assetCode: $assetCode, assetCategoryId: $assetCategoryId, assetCategoryName: $assetCategoryName, plantId: $plantId, plantName: $plantName, warrantyExpiryDate: $warrantyExpiryDate, purchaseCost: $purchaseCost, status: $status, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)'; + return 'AssetModel(id: $id, assetName: $assetName, assetCode: $assetCode, assetCategoryId: $assetCategoryId, assetCategoryName: $assetCategoryName, assetSubcategoryId: $assetSubcategoryId, assetSubcategoryName: $assetSubcategoryName, plantId: $plantId, plantName: $plantName, brandModel: $brandModel, manufacturer: $manufacturer, serialNumber: $serialNumber, partNumber: $partNumber, departmentId: $departmentId, departmentName: $departmentName, warehouseId: $warehouseId, warehouseName: $warehouseName, locationDetail: $locationDetail, assignedToUserId: $assignedToUserId, vendorId: $vendorId, vendorName: $vendorName, poId: $poId, grnId: $grnId, grnItemId: $grnItemId, purchaseDate: $purchaseDate, purchaseCost: $purchaseCost, usefulLifeYears: $usefulLifeYears, depreciationMethod: $depreciationMethod, depreciationRate: $depreciationRate, salvageValue: $salvageValue, warrantyExpiryDate: $warrantyExpiryDate, condition: $condition, status: $status, qrCodeValue: $qrCodeValue, disposalDate: $disposalDate, disposalReason: $disposalReason, disposalValue: $disposalValue, remarks: $remarks, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)'; } @override @@ -793,14 +1365,67 @@ class _$AssetModelImpl implements _AssetModel { other.assetCategoryId == assetCategoryId) && (identical(other.assetCategoryName, assetCategoryName) || other.assetCategoryName == assetCategoryName) && + (identical(other.assetSubcategoryId, assetSubcategoryId) || + other.assetSubcategoryId == assetSubcategoryId) && + (identical(other.assetSubcategoryName, assetSubcategoryName) || + other.assetSubcategoryName == assetSubcategoryName) && (identical(other.plantId, plantId) || other.plantId == plantId) && (identical(other.plantName, plantName) || other.plantName == plantName) && - (identical(other.warrantyExpiryDate, warrantyExpiryDate) || - other.warrantyExpiryDate == warrantyExpiryDate) && + (identical(other.brandModel, brandModel) || + other.brandModel == brandModel) && + (identical(other.manufacturer, manufacturer) || + other.manufacturer == manufacturer) && + (identical(other.serialNumber, serialNumber) || + other.serialNumber == serialNumber) && + (identical(other.partNumber, partNumber) || + other.partNumber == partNumber) && + (identical(other.departmentId, departmentId) || + other.departmentId == departmentId) && + (identical(other.departmentName, departmentName) || + other.departmentName == departmentName) && + (identical(other.warehouseId, warehouseId) || + other.warehouseId == warehouseId) && + (identical(other.warehouseName, warehouseName) || + other.warehouseName == warehouseName) && + (identical(other.locationDetail, locationDetail) || + other.locationDetail == locationDetail) && + (identical(other.assignedToUserId, assignedToUserId) || + other.assignedToUserId == assignedToUserId) && + (identical(other.vendorId, vendorId) || + other.vendorId == vendorId) && + (identical(other.vendorName, vendorName) || + other.vendorName == vendorName) && + (identical(other.poId, poId) || other.poId == poId) && + (identical(other.grnId, grnId) || other.grnId == grnId) && + (identical(other.grnItemId, grnItemId) || + other.grnItemId == grnItemId) && + (identical(other.purchaseDate, purchaseDate) || + other.purchaseDate == purchaseDate) && (identical(other.purchaseCost, purchaseCost) || other.purchaseCost == purchaseCost) && + (identical(other.usefulLifeYears, usefulLifeYears) || + other.usefulLifeYears == usefulLifeYears) && + (identical(other.depreciationMethod, depreciationMethod) || + other.depreciationMethod == depreciationMethod) && + (identical(other.depreciationRate, depreciationRate) || + other.depreciationRate == depreciationRate) && + (identical(other.salvageValue, salvageValue) || + other.salvageValue == salvageValue) && + (identical(other.warrantyExpiryDate, warrantyExpiryDate) || + other.warrantyExpiryDate == warrantyExpiryDate) && + (identical(other.condition, condition) || + other.condition == condition) && (identical(other.status, status) || other.status == status) && + (identical(other.qrCodeValue, qrCodeValue) || + other.qrCodeValue == qrCodeValue) && + (identical(other.disposalDate, disposalDate) || + other.disposalDate == disposalDate) && + (identical(other.disposalReason, disposalReason) || + other.disposalReason == disposalReason) && + (identical(other.disposalValue, disposalValue) || + other.disposalValue == disposalValue) && + (identical(other.remarks, remarks) || other.remarks == remarks) && (identical(other.isActive, isActive) || other.isActive == isActive) && (identical(other.createdAt, createdAt) || @@ -811,22 +1436,50 @@ class _$AssetModelImpl implements _AssetModel { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( + int get hashCode => Object.hashAll([ runtimeType, id, assetName, assetCode, assetCategoryId, assetCategoryName, + assetSubcategoryId, + assetSubcategoryName, plantId, plantName, - warrantyExpiryDate, + brandModel, + manufacturer, + serialNumber, + partNumber, + departmentId, + departmentName, + warehouseId, + warehouseName, + locationDetail, + assignedToUserId, + vendorId, + vendorName, + poId, + grnId, + grnItemId, + purchaseDate, purchaseCost, + usefulLifeYears, + depreciationMethod, + depreciationRate, + salvageValue, + warrantyExpiryDate, + condition, status, + qrCodeValue, + disposalDate, + disposalReason, + disposalValue, + remarks, isActive, createdAt, updatedAt, - ); + ]); /// Create a copy of AssetModel /// with the given fields replaced by the non-null parameter values. @@ -855,6 +1508,17 @@ abstract class _AssetModel implements AssetModel { final int? assetCategoryId, @JsonKey(name: 'asset_category_name', readValue: _readAssetCategoryName) final String? assetCategoryName, + @JsonKey( + name: 'asset_subcategory_id', + readValue: _readAssetSubcategoryId, + fromJson: _intFromJsonNullable, + ) + final int? assetSubcategoryId, + @JsonKey( + name: 'asset_subcategory_name', + readValue: _readAssetSubcategoryName, + ) + final String? assetSubcategoryName, @JsonKey( name: 'plant_id', readValue: _readPlantId, @@ -863,11 +1527,51 @@ abstract class _AssetModel implements AssetModel { final int? plantId, @JsonKey(name: 'plant_name', readValue: _readPlantName) final String? plantName, - @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) - final DateTime? warrantyExpiryDate, + @JsonKey(name: 'brand_model') final String? brandModel, + final String? manufacturer, + @JsonKey(name: 'serial_number') final String? serialNumber, + @JsonKey(name: 'part_number') final String? partNumber, + @JsonKey(name: 'department_id', fromJson: _intFromJsonNullable) + final int? departmentId, + @JsonKey(name: 'department_name', readValue: _readDepartmentName) + final String? departmentName, + @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) + final int? warehouseId, + @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) + final String? warehouseName, + @JsonKey(name: 'location_detail') final String? locationDetail, + @JsonKey(name: 'assigned_to_user_id', fromJson: _intFromJsonNullable) + final int? assignedToUserId, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) + final int? vendorId, + @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) + final String? vendorName, + @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) final int? poId, + @JsonKey(name: 'grn_id', fromJson: _intFromJsonNullable) final int? grnId, + @JsonKey(name: 'grn_item_id', fromJson: _intFromJsonNullable) + final int? grnItemId, + @JsonKey(name: 'purchase_date', fromJson: _dateFromJsonNullable) + final DateTime? purchaseDate, @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) final double? purchaseCost, + @JsonKey(name: 'useful_life_years', fromJson: _intFromJsonNullable) + final int? usefulLifeYears, + @JsonKey(name: 'depreciation_method') final String? depreciationMethod, + @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) + final double? depreciationRate, + @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) + final double? salvageValue, + @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) + final DateTime? warrantyExpiryDate, + final String? condition, final String? status, + @JsonKey(name: 'qr_code_value') final String? qrCodeValue, + @JsonKey(name: 'disposal_date', fromJson: _dateFromJsonNullable) + final DateTime? disposalDate, + @JsonKey(name: 'disposal_reason') final String? disposalReason, + @JsonKey(name: 'disposal_value', fromJson: _doubleFromJsonNullable) + final double? disposalValue, + final String? remarks, @JsonKey(name: 'is_active') final bool isActive, @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) final DateTime? createdAt, @@ -898,6 +1602,16 @@ abstract class _AssetModel implements AssetModel { @JsonKey(name: 'asset_category_name', readValue: _readAssetCategoryName) String? get assetCategoryName; @override + @JsonKey( + name: 'asset_subcategory_id', + readValue: _readAssetSubcategoryId, + fromJson: _intFromJsonNullable, + ) + int? get assetSubcategoryId; + @override + @JsonKey(name: 'asset_subcategory_name', readValue: _readAssetSubcategoryName) + String? get assetSubcategoryName; + @override @JsonKey( name: 'plant_id', readValue: _readPlantId, @@ -908,14 +1622,89 @@ abstract class _AssetModel implements AssetModel { @JsonKey(name: 'plant_name', readValue: _readPlantName) String? get plantName; @override - @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) - DateTime? get warrantyExpiryDate; + @JsonKey(name: 'brand_model') + String? get brandModel; + @override + String? get manufacturer; + @override + @JsonKey(name: 'serial_number') + String? get serialNumber; + @override + @JsonKey(name: 'part_number') + String? get partNumber; + @override + @JsonKey(name: 'department_id', fromJson: _intFromJsonNullable) + int? get departmentId; + @override + @JsonKey(name: 'department_name', readValue: _readDepartmentName) + String? get departmentName; + @override + @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) + int? get warehouseId; + @override + @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) + String? get warehouseName; + @override + @JsonKey(name: 'location_detail') + String? get locationDetail; + @override + @JsonKey(name: 'assigned_to_user_id', fromJson: _intFromJsonNullable) + int? get assignedToUserId; + @override + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) + int? get vendorId; + @override + @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) + String? get vendorName; + @override + @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) + int? get poId; + @override + @JsonKey(name: 'grn_id', fromJson: _intFromJsonNullable) + int? get grnId; + @override + @JsonKey(name: 'grn_item_id', fromJson: _intFromJsonNullable) + int? get grnItemId; + @override + @JsonKey(name: 'purchase_date', fromJson: _dateFromJsonNullable) + DateTime? get purchaseDate; @override @JsonKey(name: 'purchase_cost', fromJson: _doubleFromJsonNullable) double? get purchaseCost; @override + @JsonKey(name: 'useful_life_years', fromJson: _intFromJsonNullable) + int? get usefulLifeYears; + @override + @JsonKey(name: 'depreciation_method') + String? get depreciationMethod; + @override + @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) + double? get depreciationRate; + @override + @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) + double? get salvageValue; + @override + @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) + DateTime? get warrantyExpiryDate; + @override + String? get condition; + @override String? get status; @override + @JsonKey(name: 'qr_code_value') + String? get qrCodeValue; + @override + @JsonKey(name: 'disposal_date', fromJson: _dateFromJsonNullable) + DateTime? get disposalDate; + @override + @JsonKey(name: 'disposal_reason') + String? get disposalReason; + @override + @JsonKey(name: 'disposal_value', fromJson: _doubleFromJsonNullable) + double? get disposalValue; + @override + String? get remarks; + @override @JsonKey(name: 'is_active') bool get isActive; @override @@ -955,9 +1744,30 @@ mixin _$AmcContractModel { DateTime get startDate => throw _privateConstructorUsedError; @JsonKey(name: 'end_date', fromJson: _dateFromJson) DateTime get endDate => throw _privateConstructorUsedError; + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? get renewalDate => throw _privateConstructorUsedError; @JsonKey(name: 'annual_cost', fromJson: _doubleFromJsonNullable) double? get annualCost => throw _privateConstructorUsedError; + @JsonKey(name: 'payment_frequency') + String? get paymentFrequency => throw _privateConstructorUsedError; + @JsonKey(name: 'service_frequency') + String? get serviceFrequency => throw _privateConstructorUsedError; + @JsonKey(name: 'visits_per_year', fromJson: _intFromJsonNullable) + int? get visitsPerYear => throw _privateConstructorUsedError; + @JsonKey(name: 'contact_person') + String? get contactPerson => throw _privateConstructorUsedError; + @JsonKey(name: 'contact_phone') + String? get contactPhone => throw _privateConstructorUsedError; + @JsonKey(name: 'contact_email') + String? get contactEmail => throw _privateConstructorUsedError; + @JsonKey(name: 'scope_of_work') + String? get scopeOfWork => throw _privateConstructorUsedError; + String? get exclusions => throw _privateConstructorUsedError; + String? get remarks => throw _privateConstructorUsedError; + @JsonKey(name: 'is_active') + bool get isActive => throw _privateConstructorUsedError; String get status => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? get createdAt => throw _privateConstructorUsedError; /// Serializes this AmcContractModel to a JSON map. @@ -986,9 +1796,23 @@ abstract class $AmcContractModelCopyWith<$Res> { @JsonKey(name: 'contract_type') String? contractType, @JsonKey(name: 'start_date', fromJson: _dateFromJson) DateTime startDate, @JsonKey(name: 'end_date', fromJson: _dateFromJson) DateTime endDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? renewalDate, @JsonKey(name: 'annual_cost', fromJson: _doubleFromJsonNullable) double? annualCost, + @JsonKey(name: 'payment_frequency') String? paymentFrequency, + @JsonKey(name: 'service_frequency') String? serviceFrequency, + @JsonKey(name: 'visits_per_year', fromJson: _intFromJsonNullable) + int? visitsPerYear, + @JsonKey(name: 'contact_person') String? contactPerson, + @JsonKey(name: 'contact_phone') String? contactPhone, + @JsonKey(name: 'contact_email') String? contactEmail, + @JsonKey(name: 'scope_of_work') String? scopeOfWork, + String? exclusions, + String? remarks, + @JsonKey(name: 'is_active') bool isActive, String status, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }); } @@ -1016,7 +1840,18 @@ class _$AmcContractModelCopyWithImpl<$Res, $Val extends AmcContractModel> Object? contractType = freezed, Object? startDate = null, Object? endDate = null, + Object? renewalDate = freezed, Object? annualCost = freezed, + Object? paymentFrequency = freezed, + Object? serviceFrequency = freezed, + Object? visitsPerYear = freezed, + Object? contactPerson = freezed, + Object? contactPhone = freezed, + Object? contactEmail = freezed, + Object? scopeOfWork = freezed, + Object? exclusions = freezed, + Object? remarks = freezed, + Object? isActive = null, Object? status = null, Object? createdAt = freezed, }) { @@ -1054,10 +1889,54 @@ class _$AmcContractModelCopyWithImpl<$Res, $Val extends AmcContractModel> ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable as DateTime, + renewalDate: freezed == renewalDate + ? _value.renewalDate + : renewalDate // ignore: cast_nullable_to_non_nullable + as DateTime?, annualCost: freezed == annualCost ? _value.annualCost : annualCost // ignore: cast_nullable_to_non_nullable as double?, + paymentFrequency: freezed == paymentFrequency + ? _value.paymentFrequency + : paymentFrequency // ignore: cast_nullable_to_non_nullable + as String?, + serviceFrequency: freezed == serviceFrequency + ? _value.serviceFrequency + : serviceFrequency // ignore: cast_nullable_to_non_nullable + as String?, + visitsPerYear: freezed == visitsPerYear + ? _value.visitsPerYear + : visitsPerYear // ignore: cast_nullable_to_non_nullable + as int?, + contactPerson: freezed == contactPerson + ? _value.contactPerson + : contactPerson // ignore: cast_nullable_to_non_nullable + as String?, + contactPhone: freezed == contactPhone + ? _value.contactPhone + : contactPhone // ignore: cast_nullable_to_non_nullable + as String?, + contactEmail: freezed == contactEmail + ? _value.contactEmail + : contactEmail // ignore: cast_nullable_to_non_nullable + as String?, + scopeOfWork: freezed == scopeOfWork + ? _value.scopeOfWork + : scopeOfWork // ignore: cast_nullable_to_non_nullable + as String?, + exclusions: freezed == exclusions + ? _value.exclusions + : exclusions // ignore: cast_nullable_to_non_nullable + as String?, + remarks: freezed == remarks + ? _value.remarks + : remarks // ignore: cast_nullable_to_non_nullable + as String?, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable @@ -1090,9 +1969,23 @@ abstract class _$$AmcContractModelImplCopyWith<$Res> @JsonKey(name: 'contract_type') String? contractType, @JsonKey(name: 'start_date', fromJson: _dateFromJson) DateTime startDate, @JsonKey(name: 'end_date', fromJson: _dateFromJson) DateTime endDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? renewalDate, @JsonKey(name: 'annual_cost', fromJson: _doubleFromJsonNullable) double? annualCost, + @JsonKey(name: 'payment_frequency') String? paymentFrequency, + @JsonKey(name: 'service_frequency') String? serviceFrequency, + @JsonKey(name: 'visits_per_year', fromJson: _intFromJsonNullable) + int? visitsPerYear, + @JsonKey(name: 'contact_person') String? contactPerson, + @JsonKey(name: 'contact_phone') String? contactPhone, + @JsonKey(name: 'contact_email') String? contactEmail, + @JsonKey(name: 'scope_of_work') String? scopeOfWork, + String? exclusions, + String? remarks, + @JsonKey(name: 'is_active') bool isActive, String status, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }); } @@ -1119,7 +2012,18 @@ class __$$AmcContractModelImplCopyWithImpl<$Res> Object? contractType = freezed, Object? startDate = null, Object? endDate = null, + Object? renewalDate = freezed, Object? annualCost = freezed, + Object? paymentFrequency = freezed, + Object? serviceFrequency = freezed, + Object? visitsPerYear = freezed, + Object? contactPerson = freezed, + Object? contactPhone = freezed, + Object? contactEmail = freezed, + Object? scopeOfWork = freezed, + Object? exclusions = freezed, + Object? remarks = freezed, + Object? isActive = null, Object? status = null, Object? createdAt = freezed, }) { @@ -1157,10 +2061,54 @@ class __$$AmcContractModelImplCopyWithImpl<$Res> ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable as DateTime, + renewalDate: freezed == renewalDate + ? _value.renewalDate + : renewalDate // ignore: cast_nullable_to_non_nullable + as DateTime?, annualCost: freezed == annualCost ? _value.annualCost : annualCost // ignore: cast_nullable_to_non_nullable as double?, + paymentFrequency: freezed == paymentFrequency + ? _value.paymentFrequency + : paymentFrequency // ignore: cast_nullable_to_non_nullable + as String?, + serviceFrequency: freezed == serviceFrequency + ? _value.serviceFrequency + : serviceFrequency // ignore: cast_nullable_to_non_nullable + as String?, + visitsPerYear: freezed == visitsPerYear + ? _value.visitsPerYear + : visitsPerYear // ignore: cast_nullable_to_non_nullable + as int?, + contactPerson: freezed == contactPerson + ? _value.contactPerson + : contactPerson // ignore: cast_nullable_to_non_nullable + as String?, + contactPhone: freezed == contactPhone + ? _value.contactPhone + : contactPhone // ignore: cast_nullable_to_non_nullable + as String?, + contactEmail: freezed == contactEmail + ? _value.contactEmail + : contactEmail // ignore: cast_nullable_to_non_nullable + as String?, + scopeOfWork: freezed == scopeOfWork + ? _value.scopeOfWork + : scopeOfWork // ignore: cast_nullable_to_non_nullable + as String?, + exclusions: freezed == exclusions + ? _value.exclusions + : exclusions // ignore: cast_nullable_to_non_nullable + as String?, + remarks: freezed == remarks + ? _value.remarks + : remarks // ignore: cast_nullable_to_non_nullable + as String?, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable @@ -1187,9 +2135,23 @@ class _$AmcContractModelImpl implements _AmcContractModel { @JsonKey(name: 'start_date', fromJson: _dateFromJson) required this.startDate, @JsonKey(name: 'end_date', fromJson: _dateFromJson) required this.endDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + this.renewalDate, @JsonKey(name: 'annual_cost', fromJson: _doubleFromJsonNullable) this.annualCost, + @JsonKey(name: 'payment_frequency') this.paymentFrequency, + @JsonKey(name: 'service_frequency') this.serviceFrequency, + @JsonKey(name: 'visits_per_year', fromJson: _intFromJsonNullable) + this.visitsPerYear, + @JsonKey(name: 'contact_person') this.contactPerson, + @JsonKey(name: 'contact_phone') this.contactPhone, + @JsonKey(name: 'contact_email') this.contactEmail, + @JsonKey(name: 'scope_of_work') this.scopeOfWork, + this.exclusions, + this.remarks, + @JsonKey(name: 'is_active') this.isActive = true, this.status = 'active', + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) this.createdAt, }); @@ -1221,17 +2183,49 @@ class _$AmcContractModelImpl implements _AmcContractModel { @JsonKey(name: 'end_date', fromJson: _dateFromJson) final DateTime endDate; @override + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + final DateTime? renewalDate; + @override @JsonKey(name: 'annual_cost', fromJson: _doubleFromJsonNullable) final double? annualCost; @override + @JsonKey(name: 'payment_frequency') + final String? paymentFrequency; + @override + @JsonKey(name: 'service_frequency') + final String? serviceFrequency; + @override + @JsonKey(name: 'visits_per_year', fromJson: _intFromJsonNullable) + final int? visitsPerYear; + @override + @JsonKey(name: 'contact_person') + final String? contactPerson; + @override + @JsonKey(name: 'contact_phone') + final String? contactPhone; + @override + @JsonKey(name: 'contact_email') + final String? contactEmail; + @override + @JsonKey(name: 'scope_of_work') + final String? scopeOfWork; + @override + final String? exclusions; + @override + final String? remarks; + @override + @JsonKey(name: 'is_active') + final bool isActive; + @override @JsonKey() final String status; @override + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) final DateTime? createdAt; @override String toString() { - return 'AmcContractModel(id: $id, assetId: $assetId, vendorId: $vendorId, vendorName: $vendorName, contractNo: $contractNo, contractType: $contractType, startDate: $startDate, endDate: $endDate, annualCost: $annualCost, status: $status, createdAt: $createdAt)'; + return 'AmcContractModel(id: $id, assetId: $assetId, vendorId: $vendorId, vendorName: $vendorName, contractNo: $contractNo, contractType: $contractType, startDate: $startDate, endDate: $endDate, renewalDate: $renewalDate, annualCost: $annualCost, paymentFrequency: $paymentFrequency, serviceFrequency: $serviceFrequency, visitsPerYear: $visitsPerYear, contactPerson: $contactPerson, contactPhone: $contactPhone, contactEmail: $contactEmail, scopeOfWork: $scopeOfWork, exclusions: $exclusions, remarks: $remarks, isActive: $isActive, status: $status, createdAt: $createdAt)'; } @override @@ -1252,8 +2246,29 @@ class _$AmcContractModelImpl implements _AmcContractModel { (identical(other.startDate, startDate) || other.startDate == startDate) && (identical(other.endDate, endDate) || other.endDate == endDate) && + (identical(other.renewalDate, renewalDate) || + other.renewalDate == renewalDate) && (identical(other.annualCost, annualCost) || other.annualCost == annualCost) && + (identical(other.paymentFrequency, paymentFrequency) || + other.paymentFrequency == paymentFrequency) && + (identical(other.serviceFrequency, serviceFrequency) || + other.serviceFrequency == serviceFrequency) && + (identical(other.visitsPerYear, visitsPerYear) || + other.visitsPerYear == visitsPerYear) && + (identical(other.contactPerson, contactPerson) || + other.contactPerson == contactPerson) && + (identical(other.contactPhone, contactPhone) || + other.contactPhone == contactPhone) && + (identical(other.contactEmail, contactEmail) || + other.contactEmail == contactEmail) && + (identical(other.scopeOfWork, scopeOfWork) || + other.scopeOfWork == scopeOfWork) && + (identical(other.exclusions, exclusions) || + other.exclusions == exclusions) && + (identical(other.remarks, remarks) || other.remarks == remarks) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && (identical(other.status, status) || other.status == status) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt)); @@ -1261,7 +2276,7 @@ class _$AmcContractModelImpl implements _AmcContractModel { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( + int get hashCode => Object.hashAll([ runtimeType, id, assetId, @@ -1271,10 +2286,21 @@ class _$AmcContractModelImpl implements _AmcContractModel { contractType, startDate, endDate, + renewalDate, annualCost, + paymentFrequency, + serviceFrequency, + visitsPerYear, + contactPerson, + contactPhone, + contactEmail, + scopeOfWork, + exclusions, + remarks, + isActive, status, createdAt, - ); + ]); /// Create a copy of AmcContractModel /// with the given fields replaced by the non-null parameter values. @@ -1306,9 +2332,23 @@ abstract class _AmcContractModel implements AmcContractModel { required final DateTime startDate, @JsonKey(name: 'end_date', fromJson: _dateFromJson) required final DateTime endDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + final DateTime? renewalDate, @JsonKey(name: 'annual_cost', fromJson: _doubleFromJsonNullable) final double? annualCost, + @JsonKey(name: 'payment_frequency') final String? paymentFrequency, + @JsonKey(name: 'service_frequency') final String? serviceFrequency, + @JsonKey(name: 'visits_per_year', fromJson: _intFromJsonNullable) + final int? visitsPerYear, + @JsonKey(name: 'contact_person') final String? contactPerson, + @JsonKey(name: 'contact_phone') final String? contactPhone, + @JsonKey(name: 'contact_email') final String? contactEmail, + @JsonKey(name: 'scope_of_work') final String? scopeOfWork, + final String? exclusions, + final String? remarks, + @JsonKey(name: 'is_active') final bool isActive, final String status, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) final DateTime? createdAt, }) = _$AmcContractModelImpl; @@ -1340,11 +2380,43 @@ abstract class _AmcContractModel implements AmcContractModel { @JsonKey(name: 'end_date', fromJson: _dateFromJson) DateTime get endDate; @override + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? get renewalDate; + @override @JsonKey(name: 'annual_cost', fromJson: _doubleFromJsonNullable) double? get annualCost; @override + @JsonKey(name: 'payment_frequency') + String? get paymentFrequency; + @override + @JsonKey(name: 'service_frequency') + String? get serviceFrequency; + @override + @JsonKey(name: 'visits_per_year', fromJson: _intFromJsonNullable) + int? get visitsPerYear; + @override + @JsonKey(name: 'contact_person') + String? get contactPerson; + @override + @JsonKey(name: 'contact_phone') + String? get contactPhone; + @override + @JsonKey(name: 'contact_email') + String? get contactEmail; + @override + @JsonKey(name: 'scope_of_work') + String? get scopeOfWork; + @override + String? get exclusions; + @override + String? get remarks; + @override + @JsonKey(name: 'is_active') + bool get isActive; + @override String get status; @override + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? get createdAt; /// Create a copy of AmcContractModel @@ -1369,13 +2441,41 @@ mixin _$ServiceVisitModel { String get visitType => throw _privateConstructorUsedError; @JsonKey(name: 'visit_date', fromJson: _dateFromJson) DateTime get visitDate => throw _privateConstructorUsedError; + @JsonKey(name: 'amc_contract_id', fromJson: _intFromJsonNullable) + int? get amcContractId => throw _privateConstructorUsedError; + @JsonKey(name: 'visit_number', fromJson: _intFromJsonNullable) + int? get visitNumber => throw _privateConstructorUsedError; @JsonKey(name: 'complaint_no') String? get complaintNo => throw _privateConstructorUsedError; + @JsonKey(name: 'complaint_date', fromJson: _dateFromJsonNullable) + DateTime? get complaintDate => throw _privateConstructorUsedError; + @JsonKey(name: 'complaint_desc') + String? get complaintDesc => throw _privateConstructorUsedError; + @JsonKey(name: 'engineer_name') + String? get engineerName => throw _privateConstructorUsedError; + @JsonKey(name: 'engineer_phone') + String? get engineerPhone => throw _privateConstructorUsedError; + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) + int? get vendorId => throw _privateConstructorUsedError; + @JsonKey(name: 'vendor_name') + String? get vendorName => throw _privateConstructorUsedError; @JsonKey(name: 'work_done') String? get workDone => throw _privateConstructorUsedError; + @JsonKey(name: 'parts_replaced') + String? get partsReplaced => throw _privateConstructorUsedError; @JsonKey(name: 'next_service_date', fromJson: _dateFromJsonNullable) DateTime? get nextServiceDate => throw _privateConstructorUsedError; String get status => throw _privateConstructorUsedError; + @JsonKey(name: 'downtime_hours', fromJson: _doubleFromJsonNullable) + double? get downtimeHours => throw _privateConstructorUsedError; + @JsonKey(name: 'service_cost', fromJson: _doubleFromJsonNullable) + double? get serviceCost => throw _privateConstructorUsedError; + @JsonKey(name: 'is_under_amc') + bool get isUnderAmc => throw _privateConstructorUsedError; + @JsonKey(name: 'asset_condition_after') + String? get assetConditionAfter => throw _privateConstructorUsedError; + String? get remarks => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? get createdAt => throw _privateConstructorUsedError; /// Serializes this ServiceVisitModel to a JSON map. @@ -1400,11 +2500,31 @@ abstract class $ServiceVisitModelCopyWith<$Res> { @JsonKey(name: 'asset_id', fromJson: _idFromJson) String? assetId, @JsonKey(name: 'visit_type') String visitType, @JsonKey(name: 'visit_date', fromJson: _dateFromJson) DateTime visitDate, + @JsonKey(name: 'amc_contract_id', fromJson: _intFromJsonNullable) + int? amcContractId, + @JsonKey(name: 'visit_number', fromJson: _intFromJsonNullable) + int? visitNumber, @JsonKey(name: 'complaint_no') String? complaintNo, + @JsonKey(name: 'complaint_date', fromJson: _dateFromJsonNullable) + DateTime? complaintDate, + @JsonKey(name: 'complaint_desc') String? complaintDesc, + @JsonKey(name: 'engineer_name') String? engineerName, + @JsonKey(name: 'engineer_phone') String? engineerPhone, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, + @JsonKey(name: 'vendor_name') String? vendorName, @JsonKey(name: 'work_done') String? workDone, + @JsonKey(name: 'parts_replaced') String? partsReplaced, @JsonKey(name: 'next_service_date', fromJson: _dateFromJsonNullable) DateTime? nextServiceDate, String status, + @JsonKey(name: 'downtime_hours', fromJson: _doubleFromJsonNullable) + double? downtimeHours, + @JsonKey(name: 'service_cost', fromJson: _doubleFromJsonNullable) + double? serviceCost, + @JsonKey(name: 'is_under_amc') bool isUnderAmc, + @JsonKey(name: 'asset_condition_after') String? assetConditionAfter, + String? remarks, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }); } @@ -1428,10 +2548,24 @@ class _$ServiceVisitModelCopyWithImpl<$Res, $Val extends ServiceVisitModel> Object? assetId = freezed, Object? visitType = null, Object? visitDate = null, + Object? amcContractId = freezed, + Object? visitNumber = freezed, Object? complaintNo = freezed, + Object? complaintDate = freezed, + Object? complaintDesc = freezed, + Object? engineerName = freezed, + Object? engineerPhone = freezed, + Object? vendorId = freezed, + Object? vendorName = freezed, Object? workDone = freezed, + Object? partsReplaced = freezed, Object? nextServiceDate = freezed, Object? status = null, + Object? downtimeHours = freezed, + Object? serviceCost = freezed, + Object? isUnderAmc = null, + Object? assetConditionAfter = freezed, + Object? remarks = freezed, Object? createdAt = freezed, }) { return _then( @@ -1452,14 +2586,50 @@ class _$ServiceVisitModelCopyWithImpl<$Res, $Val extends ServiceVisitModel> ? _value.visitDate : visitDate // ignore: cast_nullable_to_non_nullable as DateTime, + amcContractId: freezed == amcContractId + ? _value.amcContractId + : amcContractId // ignore: cast_nullable_to_non_nullable + as int?, + visitNumber: freezed == visitNumber + ? _value.visitNumber + : visitNumber // ignore: cast_nullable_to_non_nullable + as int?, complaintNo: freezed == complaintNo ? _value.complaintNo : complaintNo // ignore: cast_nullable_to_non_nullable as String?, + complaintDate: freezed == complaintDate + ? _value.complaintDate + : complaintDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + complaintDesc: freezed == complaintDesc + ? _value.complaintDesc + : complaintDesc // ignore: cast_nullable_to_non_nullable + as String?, + engineerName: freezed == engineerName + ? _value.engineerName + : engineerName // ignore: cast_nullable_to_non_nullable + as String?, + engineerPhone: freezed == engineerPhone + ? _value.engineerPhone + : engineerPhone // ignore: cast_nullable_to_non_nullable + as String?, + vendorId: freezed == vendorId + ? _value.vendorId + : vendorId // ignore: cast_nullable_to_non_nullable + as int?, + vendorName: freezed == vendorName + ? _value.vendorName + : vendorName // ignore: cast_nullable_to_non_nullable + as String?, workDone: freezed == workDone ? _value.workDone : workDone // ignore: cast_nullable_to_non_nullable as String?, + partsReplaced: freezed == partsReplaced + ? _value.partsReplaced + : partsReplaced // ignore: cast_nullable_to_non_nullable + as String?, nextServiceDate: freezed == nextServiceDate ? _value.nextServiceDate : nextServiceDate // ignore: cast_nullable_to_non_nullable @@ -1468,6 +2638,26 @@ class _$ServiceVisitModelCopyWithImpl<$Res, $Val extends ServiceVisitModel> ? _value.status : status // ignore: cast_nullable_to_non_nullable as String, + downtimeHours: freezed == downtimeHours + ? _value.downtimeHours + : downtimeHours // ignore: cast_nullable_to_non_nullable + as double?, + serviceCost: freezed == serviceCost + ? _value.serviceCost + : serviceCost // ignore: cast_nullable_to_non_nullable + as double?, + isUnderAmc: null == isUnderAmc + ? _value.isUnderAmc + : isUnderAmc // ignore: cast_nullable_to_non_nullable + as bool, + assetConditionAfter: freezed == assetConditionAfter + ? _value.assetConditionAfter + : assetConditionAfter // ignore: cast_nullable_to_non_nullable + as String?, + remarks: freezed == remarks + ? _value.remarks + : remarks // ignore: cast_nullable_to_non_nullable + as String?, createdAt: freezed == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -1492,11 +2682,31 @@ abstract class _$$ServiceVisitModelImplCopyWith<$Res> @JsonKey(name: 'asset_id', fromJson: _idFromJson) String? assetId, @JsonKey(name: 'visit_type') String visitType, @JsonKey(name: 'visit_date', fromJson: _dateFromJson) DateTime visitDate, + @JsonKey(name: 'amc_contract_id', fromJson: _intFromJsonNullable) + int? amcContractId, + @JsonKey(name: 'visit_number', fromJson: _intFromJsonNullable) + int? visitNumber, @JsonKey(name: 'complaint_no') String? complaintNo, + @JsonKey(name: 'complaint_date', fromJson: _dateFromJsonNullable) + DateTime? complaintDate, + @JsonKey(name: 'complaint_desc') String? complaintDesc, + @JsonKey(name: 'engineer_name') String? engineerName, + @JsonKey(name: 'engineer_phone') String? engineerPhone, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, + @JsonKey(name: 'vendor_name') String? vendorName, @JsonKey(name: 'work_done') String? workDone, + @JsonKey(name: 'parts_replaced') String? partsReplaced, @JsonKey(name: 'next_service_date', fromJson: _dateFromJsonNullable) DateTime? nextServiceDate, String status, + @JsonKey(name: 'downtime_hours', fromJson: _doubleFromJsonNullable) + double? downtimeHours, + @JsonKey(name: 'service_cost', fromJson: _doubleFromJsonNullable) + double? serviceCost, + @JsonKey(name: 'is_under_amc') bool isUnderAmc, + @JsonKey(name: 'asset_condition_after') String? assetConditionAfter, + String? remarks, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }); } @@ -1519,10 +2729,24 @@ class __$$ServiceVisitModelImplCopyWithImpl<$Res> Object? assetId = freezed, Object? visitType = null, Object? visitDate = null, + Object? amcContractId = freezed, + Object? visitNumber = freezed, Object? complaintNo = freezed, + Object? complaintDate = freezed, + Object? complaintDesc = freezed, + Object? engineerName = freezed, + Object? engineerPhone = freezed, + Object? vendorId = freezed, + Object? vendorName = freezed, Object? workDone = freezed, + Object? partsReplaced = freezed, Object? nextServiceDate = freezed, Object? status = null, + Object? downtimeHours = freezed, + Object? serviceCost = freezed, + Object? isUnderAmc = null, + Object? assetConditionAfter = freezed, + Object? remarks = freezed, Object? createdAt = freezed, }) { return _then( @@ -1543,14 +2767,50 @@ class __$$ServiceVisitModelImplCopyWithImpl<$Res> ? _value.visitDate : visitDate // ignore: cast_nullable_to_non_nullable as DateTime, + amcContractId: freezed == amcContractId + ? _value.amcContractId + : amcContractId // ignore: cast_nullable_to_non_nullable + as int?, + visitNumber: freezed == visitNumber + ? _value.visitNumber + : visitNumber // ignore: cast_nullable_to_non_nullable + as int?, complaintNo: freezed == complaintNo ? _value.complaintNo : complaintNo // ignore: cast_nullable_to_non_nullable as String?, + complaintDate: freezed == complaintDate + ? _value.complaintDate + : complaintDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + complaintDesc: freezed == complaintDesc + ? _value.complaintDesc + : complaintDesc // ignore: cast_nullable_to_non_nullable + as String?, + engineerName: freezed == engineerName + ? _value.engineerName + : engineerName // ignore: cast_nullable_to_non_nullable + as String?, + engineerPhone: freezed == engineerPhone + ? _value.engineerPhone + : engineerPhone // ignore: cast_nullable_to_non_nullable + as String?, + vendorId: freezed == vendorId + ? _value.vendorId + : vendorId // ignore: cast_nullable_to_non_nullable + as int?, + vendorName: freezed == vendorName + ? _value.vendorName + : vendorName // ignore: cast_nullable_to_non_nullable + as String?, workDone: freezed == workDone ? _value.workDone : workDone // ignore: cast_nullable_to_non_nullable as String?, + partsReplaced: freezed == partsReplaced + ? _value.partsReplaced + : partsReplaced // ignore: cast_nullable_to_non_nullable + as String?, nextServiceDate: freezed == nextServiceDate ? _value.nextServiceDate : nextServiceDate // ignore: cast_nullable_to_non_nullable @@ -1559,6 +2819,26 @@ class __$$ServiceVisitModelImplCopyWithImpl<$Res> ? _value.status : status // ignore: cast_nullable_to_non_nullable as String, + downtimeHours: freezed == downtimeHours + ? _value.downtimeHours + : downtimeHours // ignore: cast_nullable_to_non_nullable + as double?, + serviceCost: freezed == serviceCost + ? _value.serviceCost + : serviceCost // ignore: cast_nullable_to_non_nullable + as double?, + isUnderAmc: null == isUnderAmc + ? _value.isUnderAmc + : isUnderAmc // ignore: cast_nullable_to_non_nullable + as bool, + assetConditionAfter: freezed == assetConditionAfter + ? _value.assetConditionAfter + : assetConditionAfter // ignore: cast_nullable_to_non_nullable + as String?, + remarks: freezed == remarks + ? _value.remarks + : remarks // ignore: cast_nullable_to_non_nullable + as String?, createdAt: freezed == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -1577,11 +2857,31 @@ class _$ServiceVisitModelImpl implements _ServiceVisitModel { @JsonKey(name: 'visit_type') required this.visitType, @JsonKey(name: 'visit_date', fromJson: _dateFromJson) required this.visitDate, + @JsonKey(name: 'amc_contract_id', fromJson: _intFromJsonNullable) + this.amcContractId, + @JsonKey(name: 'visit_number', fromJson: _intFromJsonNullable) + this.visitNumber, @JsonKey(name: 'complaint_no') this.complaintNo, + @JsonKey(name: 'complaint_date', fromJson: _dateFromJsonNullable) + this.complaintDate, + @JsonKey(name: 'complaint_desc') this.complaintDesc, + @JsonKey(name: 'engineer_name') this.engineerName, + @JsonKey(name: 'engineer_phone') this.engineerPhone, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) this.vendorId, + @JsonKey(name: 'vendor_name') this.vendorName, @JsonKey(name: 'work_done') this.workDone, + @JsonKey(name: 'parts_replaced') this.partsReplaced, @JsonKey(name: 'next_service_date', fromJson: _dateFromJsonNullable) this.nextServiceDate, this.status = 'COMPLETED', + @JsonKey(name: 'downtime_hours', fromJson: _doubleFromJsonNullable) + this.downtimeHours, + @JsonKey(name: 'service_cost', fromJson: _doubleFromJsonNullable) + this.serviceCost, + @JsonKey(name: 'is_under_amc') this.isUnderAmc = false, + @JsonKey(name: 'asset_condition_after') this.assetConditionAfter, + this.remarks, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) this.createdAt, }); @@ -1601,23 +2901,65 @@ class _$ServiceVisitModelImpl implements _ServiceVisitModel { @JsonKey(name: 'visit_date', fromJson: _dateFromJson) final DateTime visitDate; @override + @JsonKey(name: 'amc_contract_id', fromJson: _intFromJsonNullable) + final int? amcContractId; + @override + @JsonKey(name: 'visit_number', fromJson: _intFromJsonNullable) + final int? visitNumber; + @override @JsonKey(name: 'complaint_no') final String? complaintNo; @override + @JsonKey(name: 'complaint_date', fromJson: _dateFromJsonNullable) + final DateTime? complaintDate; + @override + @JsonKey(name: 'complaint_desc') + final String? complaintDesc; + @override + @JsonKey(name: 'engineer_name') + final String? engineerName; + @override + @JsonKey(name: 'engineer_phone') + final String? engineerPhone; + @override + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) + final int? vendorId; + @override + @JsonKey(name: 'vendor_name') + final String? vendorName; + @override @JsonKey(name: 'work_done') final String? workDone; @override + @JsonKey(name: 'parts_replaced') + final String? partsReplaced; + @override @JsonKey(name: 'next_service_date', fromJson: _dateFromJsonNullable) final DateTime? nextServiceDate; @override @JsonKey() final String status; @override + @JsonKey(name: 'downtime_hours', fromJson: _doubleFromJsonNullable) + final double? downtimeHours; + @override + @JsonKey(name: 'service_cost', fromJson: _doubleFromJsonNullable) + final double? serviceCost; + @override + @JsonKey(name: 'is_under_amc') + final bool isUnderAmc; + @override + @JsonKey(name: 'asset_condition_after') + final String? assetConditionAfter; + @override + final String? remarks; + @override + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) final DateTime? createdAt; @override String toString() { - return 'ServiceVisitModel(id: $id, assetId: $assetId, visitType: $visitType, visitDate: $visitDate, complaintNo: $complaintNo, workDone: $workDone, nextServiceDate: $nextServiceDate, status: $status, createdAt: $createdAt)'; + return 'ServiceVisitModel(id: $id, assetId: $assetId, visitType: $visitType, visitDate: $visitDate, amcContractId: $amcContractId, visitNumber: $visitNumber, complaintNo: $complaintNo, complaintDate: $complaintDate, complaintDesc: $complaintDesc, engineerName: $engineerName, engineerPhone: $engineerPhone, vendorId: $vendorId, vendorName: $vendorName, workDone: $workDone, partsReplaced: $partsReplaced, nextServiceDate: $nextServiceDate, status: $status, downtimeHours: $downtimeHours, serviceCost: $serviceCost, isUnderAmc: $isUnderAmc, assetConditionAfter: $assetConditionAfter, remarks: $remarks, createdAt: $createdAt)'; } @override @@ -1631,31 +2973,72 @@ class _$ServiceVisitModelImpl implements _ServiceVisitModel { other.visitType == visitType) && (identical(other.visitDate, visitDate) || other.visitDate == visitDate) && + (identical(other.amcContractId, amcContractId) || + other.amcContractId == amcContractId) && + (identical(other.visitNumber, visitNumber) || + other.visitNumber == visitNumber) && (identical(other.complaintNo, complaintNo) || other.complaintNo == complaintNo) && + (identical(other.complaintDate, complaintDate) || + other.complaintDate == complaintDate) && + (identical(other.complaintDesc, complaintDesc) || + other.complaintDesc == complaintDesc) && + (identical(other.engineerName, engineerName) || + other.engineerName == engineerName) && + (identical(other.engineerPhone, engineerPhone) || + other.engineerPhone == engineerPhone) && + (identical(other.vendorId, vendorId) || + other.vendorId == vendorId) && + (identical(other.vendorName, vendorName) || + other.vendorName == vendorName) && (identical(other.workDone, workDone) || other.workDone == workDone) && + (identical(other.partsReplaced, partsReplaced) || + other.partsReplaced == partsReplaced) && (identical(other.nextServiceDate, nextServiceDate) || other.nextServiceDate == nextServiceDate) && (identical(other.status, status) || other.status == status) && + (identical(other.downtimeHours, downtimeHours) || + other.downtimeHours == downtimeHours) && + (identical(other.serviceCost, serviceCost) || + other.serviceCost == serviceCost) && + (identical(other.isUnderAmc, isUnderAmc) || + other.isUnderAmc == isUnderAmc) && + (identical(other.assetConditionAfter, assetConditionAfter) || + other.assetConditionAfter == assetConditionAfter) && + (identical(other.remarks, remarks) || other.remarks == remarks) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( + int get hashCode => Object.hashAll([ runtimeType, id, assetId, visitType, visitDate, + amcContractId, + visitNumber, complaintNo, + complaintDate, + complaintDesc, + engineerName, + engineerPhone, + vendorId, + vendorName, workDone, + partsReplaced, nextServiceDate, status, + downtimeHours, + serviceCost, + isUnderAmc, + assetConditionAfter, + remarks, createdAt, - ); + ]); /// Create a copy of ServiceVisitModel /// with the given fields replaced by the non-null parameter values. @@ -1681,11 +3064,32 @@ abstract class _ServiceVisitModel implements ServiceVisitModel { @JsonKey(name: 'visit_type') required final String visitType, @JsonKey(name: 'visit_date', fromJson: _dateFromJson) required final DateTime visitDate, + @JsonKey(name: 'amc_contract_id', fromJson: _intFromJsonNullable) + final int? amcContractId, + @JsonKey(name: 'visit_number', fromJson: _intFromJsonNullable) + final int? visitNumber, @JsonKey(name: 'complaint_no') final String? complaintNo, + @JsonKey(name: 'complaint_date', fromJson: _dateFromJsonNullable) + final DateTime? complaintDate, + @JsonKey(name: 'complaint_desc') final String? complaintDesc, + @JsonKey(name: 'engineer_name') final String? engineerName, + @JsonKey(name: 'engineer_phone') final String? engineerPhone, + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) + final int? vendorId, + @JsonKey(name: 'vendor_name') final String? vendorName, @JsonKey(name: 'work_done') final String? workDone, + @JsonKey(name: 'parts_replaced') final String? partsReplaced, @JsonKey(name: 'next_service_date', fromJson: _dateFromJsonNullable) final DateTime? nextServiceDate, final String status, + @JsonKey(name: 'downtime_hours', fromJson: _doubleFromJsonNullable) + final double? downtimeHours, + @JsonKey(name: 'service_cost', fromJson: _doubleFromJsonNullable) + final double? serviceCost, + @JsonKey(name: 'is_under_amc') final bool isUnderAmc, + @JsonKey(name: 'asset_condition_after') final String? assetConditionAfter, + final String? remarks, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) final DateTime? createdAt, }) = _$ServiceVisitModelImpl; @@ -1705,17 +3109,59 @@ abstract class _ServiceVisitModel implements ServiceVisitModel { @JsonKey(name: 'visit_date', fromJson: _dateFromJson) DateTime get visitDate; @override + @JsonKey(name: 'amc_contract_id', fromJson: _intFromJsonNullable) + int? get amcContractId; + @override + @JsonKey(name: 'visit_number', fromJson: _intFromJsonNullable) + int? get visitNumber; + @override @JsonKey(name: 'complaint_no') String? get complaintNo; @override + @JsonKey(name: 'complaint_date', fromJson: _dateFromJsonNullable) + DateTime? get complaintDate; + @override + @JsonKey(name: 'complaint_desc') + String? get complaintDesc; + @override + @JsonKey(name: 'engineer_name') + String? get engineerName; + @override + @JsonKey(name: 'engineer_phone') + String? get engineerPhone; + @override + @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) + int? get vendorId; + @override + @JsonKey(name: 'vendor_name') + String? get vendorName; + @override @JsonKey(name: 'work_done') String? get workDone; @override + @JsonKey(name: 'parts_replaced') + String? get partsReplaced; + @override @JsonKey(name: 'next_service_date', fromJson: _dateFromJsonNullable) DateTime? get nextServiceDate; @override String get status; @override + @JsonKey(name: 'downtime_hours', fromJson: _doubleFromJsonNullable) + double? get downtimeHours; + @override + @JsonKey(name: 'service_cost', fromJson: _doubleFromJsonNullable) + double? get serviceCost; + @override + @JsonKey(name: 'is_under_amc') + bool get isUnderAmc; + @override + @JsonKey(name: 'asset_condition_after') + String? get assetConditionAfter; + @override + String? get remarks; + @override + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? get createdAt; /// Create a copy of ServiceVisitModel @@ -1740,15 +3186,37 @@ mixin _$InsurancePolicyModel { String get policyNo => throw _privateConstructorUsedError; @JsonKey(name: 'insurer_name') String get insurerName => throw _privateConstructorUsedError; + @JsonKey(name: 'insurer_branch') + String? get insurerBranch => throw _privateConstructorUsedError; + @JsonKey(name: 'insurer_contact') + String? get insurerContact => throw _privateConstructorUsedError; + @JsonKey(name: 'insurer_phone') + String? get insurerPhone => throw _privateConstructorUsedError; + @JsonKey(name: 'insurer_email') + String? get insurerEmail => throw _privateConstructorUsedError; @JsonKey(name: 'policy_type') String? get policyType => throw _privateConstructorUsedError; @JsonKey(name: 'sum_insured', fromJson: _doubleFromJsonNullable) double? get sumInsured => throw _privateConstructorUsedError; + @JsonKey(name: 'annual_premium', fromJson: _doubleFromJsonNullable) + double? get annualPremium => throw _privateConstructorUsedError; @JsonKey(name: 'policy_start_date', fromJson: _dateFromJson) DateTime get policyStartDate => throw _privateConstructorUsedError; @JsonKey(name: 'policy_end_date', fromJson: _dateFromJson) DateTime get policyEndDate => throw _privateConstructorUsedError; + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? get renewalDate => throw _privateConstructorUsedError; + @JsonKey(name: 'is_auto_renewal') + bool get isAutoRenewal => throw _privateConstructorUsedError; + @JsonKey(name: 'premium_paid') + bool get premiumPaid => throw _privateConstructorUsedError; + @JsonKey(name: 'premium_paid_date', fromJson: _dateFromJsonNullable) + DateTime? get premiumPaidDate => throw _privateConstructorUsedError; + String? get remarks => throw _privateConstructorUsedError; + @JsonKey(name: 'is_active') + bool get isActive => throw _privateConstructorUsedError; String get status => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? get createdAt => throw _privateConstructorUsedError; /// Serializes this InsurancePolicyModel to a JSON map. @@ -1773,14 +3241,29 @@ abstract class $InsurancePolicyModelCopyWith<$Res> { @JsonKey(name: 'asset_id', fromJson: _idFromJson) String? assetId, @JsonKey(name: 'policy_no') String policyNo, @JsonKey(name: 'insurer_name') String insurerName, + @JsonKey(name: 'insurer_branch') String? insurerBranch, + @JsonKey(name: 'insurer_contact') String? insurerContact, + @JsonKey(name: 'insurer_phone') String? insurerPhone, + @JsonKey(name: 'insurer_email') String? insurerEmail, @JsonKey(name: 'policy_type') String? policyType, @JsonKey(name: 'sum_insured', fromJson: _doubleFromJsonNullable) double? sumInsured, + @JsonKey(name: 'annual_premium', fromJson: _doubleFromJsonNullable) + double? annualPremium, @JsonKey(name: 'policy_start_date', fromJson: _dateFromJson) DateTime policyStartDate, @JsonKey(name: 'policy_end_date', fromJson: _dateFromJson) DateTime policyEndDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? renewalDate, + @JsonKey(name: 'is_auto_renewal') bool isAutoRenewal, + @JsonKey(name: 'premium_paid') bool premiumPaid, + @JsonKey(name: 'premium_paid_date', fromJson: _dateFromJsonNullable) + DateTime? premiumPaidDate, + String? remarks, + @JsonKey(name: 'is_active') bool isActive, String status, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }); } @@ -1807,10 +3290,21 @@ class _$InsurancePolicyModelCopyWithImpl< Object? assetId = freezed, Object? policyNo = null, Object? insurerName = null, + Object? insurerBranch = freezed, + Object? insurerContact = freezed, + Object? insurerPhone = freezed, + Object? insurerEmail = freezed, Object? policyType = freezed, Object? sumInsured = freezed, + Object? annualPremium = freezed, Object? policyStartDate = null, Object? policyEndDate = null, + Object? renewalDate = freezed, + Object? isAutoRenewal = null, + Object? premiumPaid = null, + Object? premiumPaidDate = freezed, + Object? remarks = freezed, + Object? isActive = null, Object? status = null, Object? createdAt = freezed, }) { @@ -1832,6 +3326,22 @@ class _$InsurancePolicyModelCopyWithImpl< ? _value.insurerName : insurerName // ignore: cast_nullable_to_non_nullable as String, + insurerBranch: freezed == insurerBranch + ? _value.insurerBranch + : insurerBranch // ignore: cast_nullable_to_non_nullable + as String?, + insurerContact: freezed == insurerContact + ? _value.insurerContact + : insurerContact // ignore: cast_nullable_to_non_nullable + as String?, + insurerPhone: freezed == insurerPhone + ? _value.insurerPhone + : insurerPhone // ignore: cast_nullable_to_non_nullable + as String?, + insurerEmail: freezed == insurerEmail + ? _value.insurerEmail + : insurerEmail // ignore: cast_nullable_to_non_nullable + as String?, policyType: freezed == policyType ? _value.policyType : policyType // ignore: cast_nullable_to_non_nullable @@ -1840,6 +3350,10 @@ class _$InsurancePolicyModelCopyWithImpl< ? _value.sumInsured : sumInsured // ignore: cast_nullable_to_non_nullable as double?, + annualPremium: freezed == annualPremium + ? _value.annualPremium + : annualPremium // ignore: cast_nullable_to_non_nullable + as double?, policyStartDate: null == policyStartDate ? _value.policyStartDate : policyStartDate // ignore: cast_nullable_to_non_nullable @@ -1848,6 +3362,30 @@ class _$InsurancePolicyModelCopyWithImpl< ? _value.policyEndDate : policyEndDate // ignore: cast_nullable_to_non_nullable as DateTime, + renewalDate: freezed == renewalDate + ? _value.renewalDate + : renewalDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + isAutoRenewal: null == isAutoRenewal + ? _value.isAutoRenewal + : isAutoRenewal // ignore: cast_nullable_to_non_nullable + as bool, + premiumPaid: null == premiumPaid + ? _value.premiumPaid + : premiumPaid // ignore: cast_nullable_to_non_nullable + as bool, + premiumPaidDate: freezed == premiumPaidDate + ? _value.premiumPaidDate + : premiumPaidDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + remarks: freezed == remarks + ? _value.remarks + : remarks // ignore: cast_nullable_to_non_nullable + as String?, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable @@ -1876,14 +3414,29 @@ abstract class _$$InsurancePolicyModelImplCopyWith<$Res> @JsonKey(name: 'asset_id', fromJson: _idFromJson) String? assetId, @JsonKey(name: 'policy_no') String policyNo, @JsonKey(name: 'insurer_name') String insurerName, + @JsonKey(name: 'insurer_branch') String? insurerBranch, + @JsonKey(name: 'insurer_contact') String? insurerContact, + @JsonKey(name: 'insurer_phone') String? insurerPhone, + @JsonKey(name: 'insurer_email') String? insurerEmail, @JsonKey(name: 'policy_type') String? policyType, @JsonKey(name: 'sum_insured', fromJson: _doubleFromJsonNullable) double? sumInsured, + @JsonKey(name: 'annual_premium', fromJson: _doubleFromJsonNullable) + double? annualPremium, @JsonKey(name: 'policy_start_date', fromJson: _dateFromJson) DateTime policyStartDate, @JsonKey(name: 'policy_end_date', fromJson: _dateFromJson) DateTime policyEndDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? renewalDate, + @JsonKey(name: 'is_auto_renewal') bool isAutoRenewal, + @JsonKey(name: 'premium_paid') bool premiumPaid, + @JsonKey(name: 'premium_paid_date', fromJson: _dateFromJsonNullable) + DateTime? premiumPaidDate, + String? remarks, + @JsonKey(name: 'is_active') bool isActive, String status, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, }); } @@ -1906,10 +3459,21 @@ class __$$InsurancePolicyModelImplCopyWithImpl<$Res> Object? assetId = freezed, Object? policyNo = null, Object? insurerName = null, + Object? insurerBranch = freezed, + Object? insurerContact = freezed, + Object? insurerPhone = freezed, + Object? insurerEmail = freezed, Object? policyType = freezed, Object? sumInsured = freezed, + Object? annualPremium = freezed, Object? policyStartDate = null, Object? policyEndDate = null, + Object? renewalDate = freezed, + Object? isAutoRenewal = null, + Object? premiumPaid = null, + Object? premiumPaidDate = freezed, + Object? remarks = freezed, + Object? isActive = null, Object? status = null, Object? createdAt = freezed, }) { @@ -1931,6 +3495,22 @@ class __$$InsurancePolicyModelImplCopyWithImpl<$Res> ? _value.insurerName : insurerName // ignore: cast_nullable_to_non_nullable as String, + insurerBranch: freezed == insurerBranch + ? _value.insurerBranch + : insurerBranch // ignore: cast_nullable_to_non_nullable + as String?, + insurerContact: freezed == insurerContact + ? _value.insurerContact + : insurerContact // ignore: cast_nullable_to_non_nullable + as String?, + insurerPhone: freezed == insurerPhone + ? _value.insurerPhone + : insurerPhone // ignore: cast_nullable_to_non_nullable + as String?, + insurerEmail: freezed == insurerEmail + ? _value.insurerEmail + : insurerEmail // ignore: cast_nullable_to_non_nullable + as String?, policyType: freezed == policyType ? _value.policyType : policyType // ignore: cast_nullable_to_non_nullable @@ -1939,6 +3519,10 @@ class __$$InsurancePolicyModelImplCopyWithImpl<$Res> ? _value.sumInsured : sumInsured // ignore: cast_nullable_to_non_nullable as double?, + annualPremium: freezed == annualPremium + ? _value.annualPremium + : annualPremium // ignore: cast_nullable_to_non_nullable + as double?, policyStartDate: null == policyStartDate ? _value.policyStartDate : policyStartDate // ignore: cast_nullable_to_non_nullable @@ -1947,6 +3531,30 @@ class __$$InsurancePolicyModelImplCopyWithImpl<$Res> ? _value.policyEndDate : policyEndDate // ignore: cast_nullable_to_non_nullable as DateTime, + renewalDate: freezed == renewalDate + ? _value.renewalDate + : renewalDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + isAutoRenewal: null == isAutoRenewal + ? _value.isAutoRenewal + : isAutoRenewal // ignore: cast_nullable_to_non_nullable + as bool, + premiumPaid: null == premiumPaid + ? _value.premiumPaid + : premiumPaid // ignore: cast_nullable_to_non_nullable + as bool, + premiumPaidDate: freezed == premiumPaidDate + ? _value.premiumPaidDate + : premiumPaidDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + remarks: freezed == remarks + ? _value.remarks + : remarks // ignore: cast_nullable_to_non_nullable + as String?, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable @@ -1968,14 +3576,29 @@ class _$InsurancePolicyModelImpl implements _InsurancePolicyModel { @JsonKey(name: 'asset_id', fromJson: _idFromJson) this.assetId, @JsonKey(name: 'policy_no') required this.policyNo, @JsonKey(name: 'insurer_name') required this.insurerName, + @JsonKey(name: 'insurer_branch') this.insurerBranch, + @JsonKey(name: 'insurer_contact') this.insurerContact, + @JsonKey(name: 'insurer_phone') this.insurerPhone, + @JsonKey(name: 'insurer_email') this.insurerEmail, @JsonKey(name: 'policy_type') this.policyType, @JsonKey(name: 'sum_insured', fromJson: _doubleFromJsonNullable) this.sumInsured, + @JsonKey(name: 'annual_premium', fromJson: _doubleFromJsonNullable) + this.annualPremium, @JsonKey(name: 'policy_start_date', fromJson: _dateFromJson) required this.policyStartDate, @JsonKey(name: 'policy_end_date', fromJson: _dateFromJson) required this.policyEndDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + this.renewalDate, + @JsonKey(name: 'is_auto_renewal') this.isAutoRenewal = false, + @JsonKey(name: 'premium_paid') this.premiumPaid = false, + @JsonKey(name: 'premium_paid_date', fromJson: _dateFromJsonNullable) + this.premiumPaidDate, + this.remarks, + @JsonKey(name: 'is_active') this.isActive = true, this.status = 'active', + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) this.createdAt, }); @@ -1995,26 +3618,59 @@ class _$InsurancePolicyModelImpl implements _InsurancePolicyModel { @JsonKey(name: 'insurer_name') final String insurerName; @override + @JsonKey(name: 'insurer_branch') + final String? insurerBranch; + @override + @JsonKey(name: 'insurer_contact') + final String? insurerContact; + @override + @JsonKey(name: 'insurer_phone') + final String? insurerPhone; + @override + @JsonKey(name: 'insurer_email') + final String? insurerEmail; + @override @JsonKey(name: 'policy_type') final String? policyType; @override @JsonKey(name: 'sum_insured', fromJson: _doubleFromJsonNullable) final double? sumInsured; @override + @JsonKey(name: 'annual_premium', fromJson: _doubleFromJsonNullable) + final double? annualPremium; + @override @JsonKey(name: 'policy_start_date', fromJson: _dateFromJson) final DateTime policyStartDate; @override @JsonKey(name: 'policy_end_date', fromJson: _dateFromJson) final DateTime policyEndDate; @override + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + final DateTime? renewalDate; + @override + @JsonKey(name: 'is_auto_renewal') + final bool isAutoRenewal; + @override + @JsonKey(name: 'premium_paid') + final bool premiumPaid; + @override + @JsonKey(name: 'premium_paid_date', fromJson: _dateFromJsonNullable) + final DateTime? premiumPaidDate; + @override + final String? remarks; + @override + @JsonKey(name: 'is_active') + final bool isActive; + @override @JsonKey() final String status; @override + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) final DateTime? createdAt; @override String toString() { - return 'InsurancePolicyModel(id: $id, assetId: $assetId, policyNo: $policyNo, insurerName: $insurerName, policyType: $policyType, sumInsured: $sumInsured, policyStartDate: $policyStartDate, policyEndDate: $policyEndDate, status: $status, createdAt: $createdAt)'; + return 'InsurancePolicyModel(id: $id, assetId: $assetId, policyNo: $policyNo, insurerName: $insurerName, insurerBranch: $insurerBranch, insurerContact: $insurerContact, insurerPhone: $insurerPhone, insurerEmail: $insurerEmail, policyType: $policyType, sumInsured: $sumInsured, annualPremium: $annualPremium, policyStartDate: $policyStartDate, policyEndDate: $policyEndDate, renewalDate: $renewalDate, isAutoRenewal: $isAutoRenewal, premiumPaid: $premiumPaid, premiumPaidDate: $premiumPaidDate, remarks: $remarks, isActive: $isActive, status: $status, createdAt: $createdAt)'; } @override @@ -2028,14 +3684,35 @@ class _$InsurancePolicyModelImpl implements _InsurancePolicyModel { other.policyNo == policyNo) && (identical(other.insurerName, insurerName) || other.insurerName == insurerName) && + (identical(other.insurerBranch, insurerBranch) || + other.insurerBranch == insurerBranch) && + (identical(other.insurerContact, insurerContact) || + other.insurerContact == insurerContact) && + (identical(other.insurerPhone, insurerPhone) || + other.insurerPhone == insurerPhone) && + (identical(other.insurerEmail, insurerEmail) || + other.insurerEmail == insurerEmail) && (identical(other.policyType, policyType) || other.policyType == policyType) && (identical(other.sumInsured, sumInsured) || other.sumInsured == sumInsured) && + (identical(other.annualPremium, annualPremium) || + other.annualPremium == annualPremium) && (identical(other.policyStartDate, policyStartDate) || other.policyStartDate == policyStartDate) && (identical(other.policyEndDate, policyEndDate) || other.policyEndDate == policyEndDate) && + (identical(other.renewalDate, renewalDate) || + other.renewalDate == renewalDate) && + (identical(other.isAutoRenewal, isAutoRenewal) || + other.isAutoRenewal == isAutoRenewal) && + (identical(other.premiumPaid, premiumPaid) || + other.premiumPaid == premiumPaid) && + (identical(other.premiumPaidDate, premiumPaidDate) || + other.premiumPaidDate == premiumPaidDate) && + (identical(other.remarks, remarks) || other.remarks == remarks) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && (identical(other.status, status) || other.status == status) && (identical(other.createdAt, createdAt) || other.createdAt == createdAt)); @@ -2043,19 +3720,30 @@ class _$InsurancePolicyModelImpl implements _InsurancePolicyModel { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( + int get hashCode => Object.hashAll([ runtimeType, id, assetId, policyNo, insurerName, + insurerBranch, + insurerContact, + insurerPhone, + insurerEmail, policyType, sumInsured, + annualPremium, policyStartDate, policyEndDate, + renewalDate, + isAutoRenewal, + premiumPaid, + premiumPaidDate, + remarks, + isActive, status, createdAt, - ); + ]); /// Create a copy of InsurancePolicyModel /// with the given fields replaced by the non-null parameter values. @@ -2081,14 +3769,29 @@ abstract class _InsurancePolicyModel implements InsurancePolicyModel { @JsonKey(name: 'asset_id', fromJson: _idFromJson) final String? assetId, @JsonKey(name: 'policy_no') required final String policyNo, @JsonKey(name: 'insurer_name') required final String insurerName, + @JsonKey(name: 'insurer_branch') final String? insurerBranch, + @JsonKey(name: 'insurer_contact') final String? insurerContact, + @JsonKey(name: 'insurer_phone') final String? insurerPhone, + @JsonKey(name: 'insurer_email') final String? insurerEmail, @JsonKey(name: 'policy_type') final String? policyType, @JsonKey(name: 'sum_insured', fromJson: _doubleFromJsonNullable) final double? sumInsured, + @JsonKey(name: 'annual_premium', fromJson: _doubleFromJsonNullable) + final double? annualPremium, @JsonKey(name: 'policy_start_date', fromJson: _dateFromJson) required final DateTime policyStartDate, @JsonKey(name: 'policy_end_date', fromJson: _dateFromJson) required final DateTime policyEndDate, + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + final DateTime? renewalDate, + @JsonKey(name: 'is_auto_renewal') final bool isAutoRenewal, + @JsonKey(name: 'premium_paid') final bool premiumPaid, + @JsonKey(name: 'premium_paid_date', fromJson: _dateFromJsonNullable) + final DateTime? premiumPaidDate, + final String? remarks, + @JsonKey(name: 'is_active') final bool isActive, final String status, + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) final DateTime? createdAt, }) = _$InsurancePolicyModelImpl; @@ -2108,20 +3811,53 @@ abstract class _InsurancePolicyModel implements InsurancePolicyModel { @JsonKey(name: 'insurer_name') String get insurerName; @override + @JsonKey(name: 'insurer_branch') + String? get insurerBranch; + @override + @JsonKey(name: 'insurer_contact') + String? get insurerContact; + @override + @JsonKey(name: 'insurer_phone') + String? get insurerPhone; + @override + @JsonKey(name: 'insurer_email') + String? get insurerEmail; + @override @JsonKey(name: 'policy_type') String? get policyType; @override @JsonKey(name: 'sum_insured', fromJson: _doubleFromJsonNullable) double? get sumInsured; @override + @JsonKey(name: 'annual_premium', fromJson: _doubleFromJsonNullable) + double? get annualPremium; + @override @JsonKey(name: 'policy_start_date', fromJson: _dateFromJson) DateTime get policyStartDate; @override @JsonKey(name: 'policy_end_date', fromJson: _dateFromJson) DateTime get policyEndDate; @override + @JsonKey(name: 'renewal_date', fromJson: _dateFromJsonNullable) + DateTime? get renewalDate; + @override + @JsonKey(name: 'is_auto_renewal') + bool get isAutoRenewal; + @override + @JsonKey(name: 'premium_paid') + bool get premiumPaid; + @override + @JsonKey(name: 'premium_paid_date', fromJson: _dateFromJsonNullable) + DateTime? get premiumPaidDate; + @override + String? get remarks; + @override + @JsonKey(name: 'is_active') + bool get isActive; + @override String get status; @override + @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? get createdAt; /// Create a copy of InsurancePolicyModel diff --git a/lib/shared/models/asset_model.g.dart b/lib/shared/models/asset_model.g.dart index ac55a5f..4991cab 100644 --- a/lib/shared/models/asset_model.g.dart +++ b/lib/shared/models/asset_model.g.dart @@ -50,11 +50,42 @@ _$AssetModelImpl _$$AssetModelImplFromJson(Map json) => ), assetCategoryName: _readAssetCategoryName(json, 'asset_category_name') as String?, + assetSubcategoryId: _intFromJsonNullable( + _readAssetSubcategoryId(json, 'asset_subcategory_id'), + ), + assetSubcategoryName: + _readAssetSubcategoryName(json, 'asset_subcategory_name') as String?, plantId: _intFromJsonNullable(_readPlantId(json, 'plant_id')), plantName: _readPlantName(json, 'plant_name') as String?, - warrantyExpiryDate: _dateFromJsonNullable(json['warranty_expiry_date']), + brandModel: json['brand_model'] as String?, + manufacturer: json['manufacturer'] as String?, + serialNumber: json['serial_number'] as String?, + partNumber: json['part_number'] as String?, + departmentId: _intFromJsonNullable(json['department_id']), + departmentName: _readDepartmentName(json, 'department_name') as String?, + warehouseId: _intFromJsonNullable(json['warehouse_id']), + warehouseName: _readWarehouseName(json, 'warehouse_name') as String?, + locationDetail: json['location_detail'] as String?, + assignedToUserId: _intFromJsonNullable(json['assigned_to_user_id']), + vendorId: _intFromJsonNullable(json['vendor_id']), + vendorName: _readVendorNameFromNested(json, 'vendor_name') as String?, + poId: _intFromJsonNullable(json['po_id']), + grnId: _intFromJsonNullable(json['grn_id']), + grnItemId: _intFromJsonNullable(json['grn_item_id']), + purchaseDate: _dateFromJsonNullable(json['purchase_date']), purchaseCost: _doubleFromJsonNullable(json['purchase_cost']), + usefulLifeYears: _intFromJsonNullable(json['useful_life_years']), + depreciationMethod: json['depreciation_method'] as String?, + depreciationRate: _doubleFromJsonNullable(json['depreciation_rate']), + salvageValue: _doubleFromJsonNullable(json['salvage_value']), + warrantyExpiryDate: _dateFromJsonNullable(json['warranty_expiry_date']), + condition: json['condition'] as String?, status: json['status'] as String?, + qrCodeValue: json['qr_code_value'] as String?, + disposalDate: _dateFromJsonNullable(json['disposal_date']), + disposalReason: json['disposal_reason'] as String?, + disposalValue: _doubleFromJsonNullable(json['disposal_value']), + remarks: json['remarks'] as String?, isActive: json['is_active'] as bool? ?? true, createdAt: _dateFromJsonNullable(json['created_at']), updatedAt: _dateFromJsonNullable(json['updated_at']), @@ -67,11 +98,39 @@ Map _$$AssetModelImplToJson(_$AssetModelImpl instance) => 'asset_code': instance.assetCode, 'asset_category_id': instance.assetCategoryId, 'asset_category_name': instance.assetCategoryName, + 'asset_subcategory_id': instance.assetSubcategoryId, + 'asset_subcategory_name': instance.assetSubcategoryName, 'plant_id': instance.plantId, 'plant_name': instance.plantName, - 'warranty_expiry_date': instance.warrantyExpiryDate?.toIso8601String(), + 'brand_model': instance.brandModel, + 'manufacturer': instance.manufacturer, + 'serial_number': instance.serialNumber, + 'part_number': instance.partNumber, + 'department_id': instance.departmentId, + 'department_name': instance.departmentName, + 'warehouse_id': instance.warehouseId, + 'warehouse_name': instance.warehouseName, + 'location_detail': instance.locationDetail, + 'assigned_to_user_id': instance.assignedToUserId, + 'vendor_id': instance.vendorId, + 'vendor_name': instance.vendorName, + 'po_id': instance.poId, + 'grn_id': instance.grnId, + 'grn_item_id': instance.grnItemId, + 'purchase_date': instance.purchaseDate?.toIso8601String(), 'purchase_cost': instance.purchaseCost, + 'useful_life_years': instance.usefulLifeYears, + 'depreciation_method': instance.depreciationMethod, + 'depreciation_rate': instance.depreciationRate, + 'salvage_value': instance.salvageValue, + 'warranty_expiry_date': instance.warrantyExpiryDate?.toIso8601String(), + 'condition': instance.condition, 'status': instance.status, + 'qr_code_value': instance.qrCodeValue, + 'disposal_date': instance.disposalDate?.toIso8601String(), + 'disposal_reason': instance.disposalReason, + 'disposal_value': instance.disposalValue, + 'remarks': instance.remarks, 'is_active': instance.isActive, 'created_at': instance.createdAt?.toIso8601String(), 'updated_at': instance.updatedAt?.toIso8601String(), @@ -88,11 +147,20 @@ _$AmcContractModelImpl _$$AmcContractModelImplFromJson( contractType: json['contract_type'] as String?, startDate: _dateFromJson(json['start_date']), endDate: _dateFromJson(json['end_date']), + renewalDate: _dateFromJsonNullable(json['renewal_date']), annualCost: _doubleFromJsonNullable(json['annual_cost']), + paymentFrequency: json['payment_frequency'] as String?, + serviceFrequency: json['service_frequency'] as String?, + visitsPerYear: _intFromJsonNullable(json['visits_per_year']), + contactPerson: json['contact_person'] as String?, + contactPhone: json['contact_phone'] as String?, + contactEmail: json['contact_email'] as String?, + scopeOfWork: json['scope_of_work'] as String?, + exclusions: json['exclusions'] as String?, + remarks: json['remarks'] as String?, + isActive: json['is_active'] as bool? ?? true, status: json['status'] as String? ?? 'active', - createdAt: json['createdAt'] == null - ? null - : DateTime.parse(json['createdAt'] as String), + createdAt: _dateFromJsonNullable(json['created_at']), ); Map _$$AmcContractModelImplToJson( @@ -106,9 +174,20 @@ Map _$$AmcContractModelImplToJson( 'contract_type': instance.contractType, 'start_date': instance.startDate.toIso8601String(), 'end_date': instance.endDate.toIso8601String(), + 'renewal_date': instance.renewalDate?.toIso8601String(), 'annual_cost': instance.annualCost, + 'payment_frequency': instance.paymentFrequency, + 'service_frequency': instance.serviceFrequency, + 'visits_per_year': instance.visitsPerYear, + 'contact_person': instance.contactPerson, + 'contact_phone': instance.contactPhone, + 'contact_email': instance.contactEmail, + 'scope_of_work': instance.scopeOfWork, + 'exclusions': instance.exclusions, + 'remarks': instance.remarks, + 'is_active': instance.isActive, 'status': instance.status, - 'createdAt': instance.createdAt?.toIso8601String(), + 'created_at': instance.createdAt?.toIso8601String(), }; _$ServiceVisitModelImpl _$$ServiceVisitModelImplFromJson( @@ -118,13 +197,25 @@ _$ServiceVisitModelImpl _$$ServiceVisitModelImplFromJson( assetId: _idFromJson(json['asset_id']), visitType: json['visit_type'] as String, visitDate: _dateFromJson(json['visit_date']), + amcContractId: _intFromJsonNullable(json['amc_contract_id']), + visitNumber: _intFromJsonNullable(json['visit_number']), complaintNo: json['complaint_no'] as String?, + complaintDate: _dateFromJsonNullable(json['complaint_date']), + complaintDesc: json['complaint_desc'] as String?, + engineerName: json['engineer_name'] as String?, + engineerPhone: json['engineer_phone'] as String?, + vendorId: _intFromJsonNullable(json['vendor_id']), + vendorName: json['vendor_name'] as String?, workDone: json['work_done'] as String?, + partsReplaced: json['parts_replaced'] as String?, nextServiceDate: _dateFromJsonNullable(json['next_service_date']), status: json['status'] as String? ?? 'COMPLETED', - createdAt: json['createdAt'] == null - ? null - : DateTime.parse(json['createdAt'] as String), + downtimeHours: _doubleFromJsonNullable(json['downtime_hours']), + serviceCost: _doubleFromJsonNullable(json['service_cost']), + isUnderAmc: json['is_under_amc'] as bool? ?? false, + assetConditionAfter: json['asset_condition_after'] as String?, + remarks: json['remarks'] as String?, + createdAt: _dateFromJsonNullable(json['created_at']), ); Map _$$ServiceVisitModelImplToJson( @@ -134,11 +225,25 @@ Map _$$ServiceVisitModelImplToJson( 'asset_id': instance.assetId, 'visit_type': instance.visitType, 'visit_date': instance.visitDate.toIso8601String(), + 'amc_contract_id': instance.amcContractId, + 'visit_number': instance.visitNumber, 'complaint_no': instance.complaintNo, + 'complaint_date': instance.complaintDate?.toIso8601String(), + 'complaint_desc': instance.complaintDesc, + 'engineer_name': instance.engineerName, + 'engineer_phone': instance.engineerPhone, + 'vendor_id': instance.vendorId, + 'vendor_name': instance.vendorName, 'work_done': instance.workDone, + 'parts_replaced': instance.partsReplaced, 'next_service_date': instance.nextServiceDate?.toIso8601String(), 'status': instance.status, - 'createdAt': instance.createdAt?.toIso8601String(), + 'downtime_hours': instance.downtimeHours, + 'service_cost': instance.serviceCost, + 'is_under_amc': instance.isUnderAmc, + 'asset_condition_after': instance.assetConditionAfter, + 'remarks': instance.remarks, + 'created_at': instance.createdAt?.toIso8601String(), }; _$InsurancePolicyModelImpl _$$InsurancePolicyModelImplFromJson( @@ -148,14 +253,23 @@ _$InsurancePolicyModelImpl _$$InsurancePolicyModelImplFromJson( assetId: _idFromJson(json['asset_id']), policyNo: json['policy_no'] as String, insurerName: json['insurer_name'] as String, + insurerBranch: json['insurer_branch'] as String?, + insurerContact: json['insurer_contact'] as String?, + insurerPhone: json['insurer_phone'] as String?, + insurerEmail: json['insurer_email'] as String?, policyType: json['policy_type'] as String?, sumInsured: _doubleFromJsonNullable(json['sum_insured']), + annualPremium: _doubleFromJsonNullable(json['annual_premium']), policyStartDate: _dateFromJson(json['policy_start_date']), policyEndDate: _dateFromJson(json['policy_end_date']), + renewalDate: _dateFromJsonNullable(json['renewal_date']), + isAutoRenewal: json['is_auto_renewal'] as bool? ?? false, + premiumPaid: json['premium_paid'] as bool? ?? false, + premiumPaidDate: _dateFromJsonNullable(json['premium_paid_date']), + remarks: json['remarks'] as String?, + isActive: json['is_active'] as bool? ?? true, status: json['status'] as String? ?? 'active', - createdAt: json['createdAt'] == null - ? null - : DateTime.parse(json['createdAt'] as String), + createdAt: _dateFromJsonNullable(json['created_at']), ); Map _$$InsurancePolicyModelImplToJson( @@ -165,12 +279,23 @@ Map _$$InsurancePolicyModelImplToJson( 'asset_id': instance.assetId, 'policy_no': instance.policyNo, 'insurer_name': instance.insurerName, + 'insurer_branch': instance.insurerBranch, + 'insurer_contact': instance.insurerContact, + 'insurer_phone': instance.insurerPhone, + 'insurer_email': instance.insurerEmail, 'policy_type': instance.policyType, 'sum_insured': instance.sumInsured, + 'annual_premium': instance.annualPremium, 'policy_start_date': instance.policyStartDate.toIso8601String(), 'policy_end_date': instance.policyEndDate.toIso8601String(), + 'renewal_date': instance.renewalDate?.toIso8601String(), + 'is_auto_renewal': instance.isAutoRenewal, + 'premium_paid': instance.premiumPaid, + 'premium_paid_date': instance.premiumPaidDate?.toIso8601String(), + 'remarks': instance.remarks, + 'is_active': instance.isActive, 'status': instance.status, - 'createdAt': instance.createdAt?.toIso8601String(), + 'created_at': instance.createdAt?.toIso8601String(), }; _$AssetAlertModelImpl _$$AssetAlertModelImplFromJson( diff --git a/lib/shared/models/vendor_model.dart b/lib/shared/models/vendor_model.dart index 0b0c811..1473503 100644 --- a/lib/shared/models/vendor_model.dart +++ b/lib/shared/models/vendor_model.dart @@ -36,6 +36,8 @@ class VendorModel with _$VendorModel { @JsonKey(name: 'vendor_code') String? vendorCode, @JsonKey(name: 'vendor_name') required String vendorName, @JsonKey(name: 'vendor_type') String? vendorType, + @JsonKey(name: 'gst_treatment') String? gstTreatment, + @JsonKey(name: 'source_of_supply') String? sourceOfSupply, String? gstin, String? pan, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @@ -136,6 +138,17 @@ const vendorTypeOptions = [ ('GENERAL', 'General'), ]; +const gstTreatmentOptions = [ + ('REGULAR', 'Regular'), + ('COMPOSITION', 'Composition'), + ('UNREGISTERED', 'Unregistered'), + ('CONSUMER', 'Consumer'), + ('SEZ', 'SEZ'), + ('OVERSEAS', 'Overseas'), + ('DEEMED_EXPORT', 'Deemed Export'), + ('OTHER', 'Other'), +]; + const vendorStatusOptions = [ ('active', 'Active'), ('inactive', 'Inactive'), @@ -163,6 +176,15 @@ String vendorTypeLabel(String? value) { value; } +String gstTreatmentLabel(String? value) { + if (value == null) return '—'; + return gstTreatmentOptions + .where((e) => e.$1 == value) + .map((e) => e.$2) + .firstOrNull ?? + value.replaceAll('_', ' '); +} + String vendorStatusLabel(String? value) { if (value == null) return '—'; return vendorStatusOptions diff --git a/lib/shared/models/vendor_model.freezed.dart b/lib/shared/models/vendor_model.freezed.dart index 2425585..8ae14be 100644 --- a/lib/shared/models/vendor_model.freezed.dart +++ b/lib/shared/models/vendor_model.freezed.dart @@ -29,6 +29,10 @@ mixin _$VendorModel { String get vendorName => throw _privateConstructorUsedError; @JsonKey(name: 'vendor_type') String? get vendorType => throw _privateConstructorUsedError; + @JsonKey(name: 'gst_treatment') + String? get gstTreatment => throw _privateConstructorUsedError; + @JsonKey(name: 'source_of_supply') + String? get sourceOfSupply => throw _privateConstructorUsedError; String? get gstin => throw _privateConstructorUsedError; String? get pan => throw _privateConstructorUsedError; @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @@ -73,6 +77,8 @@ abstract class $VendorModelCopyWith<$Res> { @JsonKey(name: 'vendor_code') String? vendorCode, @JsonKey(name: 'vendor_name') String vendorName, @JsonKey(name: 'vendor_type') String? vendorType, + @JsonKey(name: 'gst_treatment') String? gstTreatment, + @JsonKey(name: 'source_of_supply') String? sourceOfSupply, String? gstin, String? pan, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @@ -113,6 +119,8 @@ class _$VendorModelCopyWithImpl<$Res, $Val extends VendorModel> Object? vendorCode = freezed, Object? vendorName = null, Object? vendorType = freezed, + Object? gstTreatment = freezed, + Object? sourceOfSupply = freezed, Object? gstin = freezed, Object? pan = freezed, Object? paymentTermId = freezed, @@ -145,6 +153,14 @@ class _$VendorModelCopyWithImpl<$Res, $Val extends VendorModel> ? _value.vendorType : vendorType // ignore: cast_nullable_to_non_nullable as String?, + gstTreatment: freezed == gstTreatment + ? _value.gstTreatment + : gstTreatment // ignore: cast_nullable_to_non_nullable + as String?, + sourceOfSupply: freezed == sourceOfSupply + ? _value.sourceOfSupply + : sourceOfSupply // ignore: cast_nullable_to_non_nullable + as String?, gstin: freezed == gstin ? _value.gstin : gstin // ignore: cast_nullable_to_non_nullable @@ -217,6 +233,8 @@ abstract class _$$VendorModelImplCopyWith<$Res> @JsonKey(name: 'vendor_code') String? vendorCode, @JsonKey(name: 'vendor_name') String vendorName, @JsonKey(name: 'vendor_type') String? vendorType, + @JsonKey(name: 'gst_treatment') String? gstTreatment, + @JsonKey(name: 'source_of_supply') String? sourceOfSupply, String? gstin, String? pan, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @@ -256,6 +274,8 @@ class __$$VendorModelImplCopyWithImpl<$Res> Object? vendorCode = freezed, Object? vendorName = null, Object? vendorType = freezed, + Object? gstTreatment = freezed, + Object? sourceOfSupply = freezed, Object? gstin = freezed, Object? pan = freezed, Object? paymentTermId = freezed, @@ -288,6 +308,14 @@ class __$$VendorModelImplCopyWithImpl<$Res> ? _value.vendorType : vendorType // ignore: cast_nullable_to_non_nullable as String?, + gstTreatment: freezed == gstTreatment + ? _value.gstTreatment + : gstTreatment // ignore: cast_nullable_to_non_nullable + as String?, + sourceOfSupply: freezed == sourceOfSupply + ? _value.sourceOfSupply + : sourceOfSupply // ignore: cast_nullable_to_non_nullable + as String?, gstin: freezed == gstin ? _value.gstin : gstin // ignore: cast_nullable_to_non_nullable @@ -353,6 +381,8 @@ class _$VendorModelImpl implements _VendorModel { @JsonKey(name: 'vendor_code') this.vendorCode, @JsonKey(name: 'vendor_name') required this.vendorName, @JsonKey(name: 'vendor_type') this.vendorType, + @JsonKey(name: 'gst_treatment') this.gstTreatment, + @JsonKey(name: 'source_of_supply') this.sourceOfSupply, this.gstin, this.pan, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @@ -392,6 +422,12 @@ class _$VendorModelImpl implements _VendorModel { @JsonKey(name: 'vendor_type') final String? vendorType; @override + @JsonKey(name: 'gst_treatment') + final String? gstTreatment; + @override + @JsonKey(name: 'source_of_supply') + final String? sourceOfSupply; + @override final String? gstin; @override final String? pan; @@ -446,7 +482,7 @@ class _$VendorModelImpl implements _VendorModel { @override String toString() { - return 'VendorModel(id: $id, vendorCode: $vendorCode, vendorName: $vendorName, vendorType: $vendorType, gstin: $gstin, pan: $pan, paymentTermId: $paymentTermId, paymentTermName: $paymentTermName, creditPeriodDays: $creditPeriodDays, remarks: $remarks, status: $status, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, addresses: $addresses, contacts: $contacts, bankDetails: $bankDetails)'; + return 'VendorModel(id: $id, vendorCode: $vendorCode, vendorName: $vendorName, vendorType: $vendorType, gstTreatment: $gstTreatment, sourceOfSupply: $sourceOfSupply, gstin: $gstin, pan: $pan, paymentTermId: $paymentTermId, paymentTermName: $paymentTermName, creditPeriodDays: $creditPeriodDays, remarks: $remarks, status: $status, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, addresses: $addresses, contacts: $contacts, bankDetails: $bankDetails)'; } @override @@ -461,6 +497,10 @@ class _$VendorModelImpl implements _VendorModel { other.vendorName == vendorName) && (identical(other.vendorType, vendorType) || other.vendorType == vendorType) && + (identical(other.gstTreatment, gstTreatment) || + other.gstTreatment == gstTreatment) && + (identical(other.sourceOfSupply, sourceOfSupply) || + other.sourceOfSupply == sourceOfSupply) && (identical(other.gstin, gstin) || other.gstin == gstin) && (identical(other.pan, pan) || other.pan == pan) && (identical(other.paymentTermId, paymentTermId) || @@ -490,12 +530,14 @@ class _$VendorModelImpl implements _VendorModel { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( + int get hashCode => Object.hashAll([ runtimeType, id, vendorCode, vendorName, vendorType, + gstTreatment, + sourceOfSupply, gstin, pan, paymentTermId, @@ -509,7 +551,7 @@ class _$VendorModelImpl implements _VendorModel { const DeepCollectionEquality().hash(_addresses), const DeepCollectionEquality().hash(_contacts), const DeepCollectionEquality().hash(_bankDetails), - ); + ]); /// Create a copy of VendorModel /// with the given fields replaced by the non-null parameter values. @@ -531,6 +573,8 @@ abstract class _VendorModel implements VendorModel { @JsonKey(name: 'vendor_code') final String? vendorCode, @JsonKey(name: 'vendor_name') required final String vendorName, @JsonKey(name: 'vendor_type') final String? vendorType, + @JsonKey(name: 'gst_treatment') final String? gstTreatment, + @JsonKey(name: 'source_of_supply') final String? sourceOfSupply, final String? gstin, final String? pan, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) @@ -568,6 +612,12 @@ abstract class _VendorModel implements VendorModel { @JsonKey(name: 'vendor_type') String? get vendorType; @override + @JsonKey(name: 'gst_treatment') + String? get gstTreatment; + @override + @JsonKey(name: 'source_of_supply') + String? get sourceOfSupply; + @override String? get gstin; @override String? get pan; diff --git a/lib/shared/models/vendor_model.g.dart b/lib/shared/models/vendor_model.g.dart index 704ec55..4ba247a 100644 --- a/lib/shared/models/vendor_model.g.dart +++ b/lib/shared/models/vendor_model.g.dart @@ -13,6 +13,8 @@ _$VendorModelImpl _$$VendorModelImplFromJson( vendorCode: json['vendor_code'] as String?, vendorName: json['vendor_name'] as String, vendorType: json['vendor_type'] as String?, + gstTreatment: json['gst_treatment'] as String?, + sourceOfSupply: json['source_of_supply'] as String?, gstin: json['gstin'] as String?, pan: json['pan'] as String?, paymentTermId: _intFromJsonNullable(json['payment_term_id']), @@ -48,6 +50,8 @@ Map _$$VendorModelImplToJson(_$VendorModelImpl instance) => 'vendor_code': instance.vendorCode, 'vendor_name': instance.vendorName, 'vendor_type': instance.vendorType, + 'gst_treatment': instance.gstTreatment, + 'source_of_supply': instance.sourceOfSupply, 'gstin': instance.gstin, 'pan': instance.pan, 'payment_term_id': instance.paymentTermId, diff --git a/lib/shared/routes/menu_config.dart b/lib/shared/routes/menu_config.dart index e42e197..be9abe4 100644 --- a/lib/shared/routes/menu_config.dart +++ b/lib/shared/routes/menu_config.dart @@ -60,12 +60,6 @@ const List appMenuItems = [ route: RouteConstants.assets, module: 'assets', ), - MenuItem( - label: 'Categories', - icon: Icons.category_outlined, - route: RouteConstants.assetCategories, - module: 'asset_categories', - ), MenuItem( label: 'Alerts', icon: Icons.notifications_outlined, diff --git a/lib/shared/widgets/app_searchable_dropdown.dart b/lib/shared/widgets/app_searchable_dropdown.dart index 84ea81b..29ad613 100644 --- a/lib/shared/widgets/app_searchable_dropdown.dart +++ b/lib/shared/widgets/app_searchable_dropdown.dart @@ -166,6 +166,7 @@ class _AppSearchableDropdownState extends State> { final theme = Theme.of(context); return FormField( + key: ValueKey(widget.value), initialValue: widget.value, validator: widget.validator, builder: (field) { diff --git a/lib/shared/widgets/app_searchable_multi_select_dropdown.dart b/lib/shared/widgets/app_searchable_multi_select_dropdown.dart index 825f0b9..0adb8de 100644 --- a/lib/shared/widgets/app_searchable_multi_select_dropdown.dart +++ b/lib/shared/widgets/app_searchable_multi_select_dropdown.dart @@ -44,8 +44,7 @@ class _AppSearchableMultiSelectDropdownState super.dispose(); } - String _displayText() { - if (widget.values.isEmpty) return ''; + List _selectedLabels() { final labels = []; for (final value in widget.values) { for (final option in widget.options) { @@ -55,7 +54,7 @@ class _AppSearchableMultiSelectDropdownState } } } - return labels.join(', '); + return labels; } void _removeOverlay() { @@ -161,9 +160,9 @@ class _AppSearchableMultiSelectDropdownState @override Widget build(BuildContext context) { final theme = Theme.of(context); - final displayText = _displayText(); return FormField>( + key: ValueKey(widget.values.join('\u0000')), initialValue: widget.values, validator: widget.validator, builder: (field) { @@ -171,6 +170,10 @@ class _AppSearchableMultiSelectDropdownState widget.hint ?? 'Select ${widget.label.toLowerCase()}'; final canOpen = widget.enabled && widget.options.isNotEmpty; final colors = theme.colorScheme; + final labels = _selectedLabels(); + final displayText = labels.join(', '); + final tooltipMessage = + labels.length > 1 ? labels.join('\n') : displayText; return Padding( padding: const EdgeInsets.only(top: 8), @@ -202,12 +205,17 @@ class _AppSearchableMultiSelectDropdownState ), child: displayText.isEmpty ? const SizedBox.shrink() - : Text( - displayText, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyLarge?.copyWith( - color: colors.onSurface, + : Tooltip( + message: tooltipMessage, + preferBelow: true, + waitDuration: const Duration(milliseconds: 250), + child: Text( + displayText, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodyLarge?.copyWith( + color: colors.onSurface, + ), ), ), ), diff --git a/lib/shared/widgets/app_side_panel.dart b/lib/shared/widgets/app_side_panel.dart index 04a1a17..8815c05 100644 --- a/lib/shared/widgets/app_side_panel.dart +++ b/lib/shared/widgets/app_side_panel.dart @@ -30,7 +30,12 @@ Future showSidePanel( child: SizedBox( width: panelWidth, height: MediaQuery.sizeOf(context).height, - child: panel, + child: ScaffoldMessenger( + child: Scaffold( + backgroundColor: theme.colorScheme.surface, + body: panel, + ), + ), ), ), ); @@ -47,6 +52,21 @@ Future showSidePanel( ); } +/// Shows a snackbar above the side panel overlay (not behind it). +void showSidePanelSnackBar( + BuildContext context, + String message, { + Duration duration = const Duration(seconds: 4), +}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + behavior: SnackBarBehavior.floating, + duration: duration, + ), + ); +} + class SidePanelScaffold extends StatelessWidget { const SidePanelScaffold({ super.key, @@ -180,7 +200,7 @@ class SidePanelFormRow extends StatelessWidget { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded(child: left), SizedBox(width: spacing), diff --git a/lib/shared/widgets/app_text_field.dart b/lib/shared/widgets/app_text_field.dart index cd7f35f..1c40c16 100644 --- a/lib/shared/widgets/app_text_field.dart +++ b/lib/shared/widgets/app_text_field.dart @@ -18,6 +18,7 @@ class AppTextField extends StatelessWidget { this.inputFormatters, this.enabled = true, this.autofillHints, + this.isDense = false, }); final TextEditingController controller; @@ -34,6 +35,7 @@ class AppTextField extends StatelessWidget { final List? inputFormatters; final bool enabled; final Iterable? autofillHints; + final bool isDense; @override Widget build(BuildContext context) { @@ -55,6 +57,7 @@ class AppTextField extends StatelessWidget { hintText: hint, prefixIcon: prefixIcon, suffixIcon: suffixIcon, + isDense: isDense, floatingLabelBehavior: label != null ? FloatingLabelBehavior.always : null, ), diff --git a/pubspec.lock b/pubspec.lock index 3acee31..daaf828 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -596,26 +596,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.18.0" mime: dependency: transitive description: @@ -1009,10 +1009,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.11" timing: dependency: transitive description: @@ -1142,5 +1142,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.9.2 <4.0.0" + dart: ">=3.10.0-0 <4.0.0" flutter: ">=3.35.6"