diff --git a/lib/core/constants/api_endpoints.dart b/lib/core/constants/api_endpoints.dart index edec7ed..ee20da8 100644 --- a/lib/core/constants/api_endpoints.dart +++ b/lib/core/constants/api_endpoints.dart @@ -47,6 +47,9 @@ class ApiEndpoints { static String designationById(String id) => '/masters/designations/$id'; static const String plants = '/masters/plants'; static String plantById(String id) => '/masters/plants/$id'; + static const String locations = '/masters/locations'; + static String locationById(String id) => '/masters/locations/$id'; + static const String locationStates = '/masters/locations/states'; static const String uom = '/masters/uom'; static String uomById(String id) => '/masters/uom/$id'; static const String itemCategories = '/masters/item-categories'; @@ -148,6 +151,11 @@ class ApiEndpoints { static const String assetDepreciationCalculate = '/assets/depreciation/calculate'; static const String assetAlertsExpiry = '/assets/alerts/expiry'; static const String assetAlertsService = '/assets/alerts/service'; + static const String assetMaintenanceMy = '/assets/maintenance/my'; + static String assetMaintenanceLogs(String id) => + '/assets/$id/maintenance-logs'; + static String assetMaintenanceLogById(String id, String logId) => + '/assets/$id/maintenance-logs/$logId'; static String assetAmc(String assetId) => '/assets/$assetId/amc'; static String assetAmcById(String assetId, String contractId) => '/assets/$assetId/amc/$contractId'; diff --git a/lib/core/constants/route_constants.dart b/lib/core/constants/route_constants.dart index 982da9c..a43b1eb 100644 --- a/lib/core/constants/route_constants.dart +++ b/lib/core/constants/route_constants.dart @@ -63,6 +63,7 @@ class RouteConstants { static const String assetEdit = '/assets/:id/edit'; static const String assetDetail = '/assets/:id'; static const String assetAlerts = '/assets/alerts'; + static const String assetMaintenance = '/assets/maintenance'; // Master Data static const String masterData = '/master-data'; 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 f85e051..f866fad 100644 --- a/lib/modules/assets/data/datasources/asset_remote_data_source.dart +++ b/lib/modules/assets/data/datasources/asset_remote_data_source.dart @@ -357,6 +357,55 @@ class AssetRemoteDataSource { ); } + Future> getMyMaintenanceAssets({ + bool dueOnly = false, + int page = 1, + int limit = 20, + }) async { + final response = await dio.get( + ApiEndpoints.assetMaintenanceMy, + queryParameters: { + 'page': page, + 'limit': limit, + if (dueOnly) 'due_only': true, + }, + ); + return _parsePaginated(response.data, AssetModel.fromJson); + } + + Future> getMaintenanceLogs(String assetId) async { + final response = await dio.get(ApiEndpoints.assetMaintenanceLogs(assetId)); + return _parseList(response.data, AssetMaintenanceLogModel.fromJson); + } + + Future createMaintenanceLog( + String assetId, + Map data, + ) async { + final response = await dio.post( + ApiEndpoints.assetMaintenanceLogs(assetId), + data: data, + ); + return AssetMaintenanceLogModel.fromJson( + response.data['data'] as Map, + ); + } + + Future getMaintenanceLogById( + String assetId, + String logId, + ) async { + final response = + await dio.get(ApiEndpoints.assetMaintenanceLogById(assetId, logId)); + return AssetMaintenanceLogModel.fromJson( + response.data['data'] as Map, + ); + } + + Future deleteMaintenanceLog(String assetId, String logId) async { + await dio.delete(ApiEndpoints.assetMaintenanceLogById(assetId, logId)); + } + Map _queryToMap(AssetListQuery query) { return { 'page': query.page, @@ -374,8 +423,11 @@ class AssetRemoteDataSource { if (query.itemCategoryId != null) 'item_category_id': query.itemCategoryId, if (query.itemSubcategoryId != null) 'item_subcategory_id': query.itemSubcategoryId, - if (query.plantId != null) 'plant_id': query.plantId, + if (query.locationId != null) 'location_id': query.locationId, if (query.departmentId != null) 'department_id': query.departmentId, + if (query.maintenanceInchargeUserId != null) + 'maintenance_incharge_user_id': query.maintenanceInchargeUserId, + if (query.dueOnly == true) 'due_only': true, if (query.isActive != null) 'is_active': query.isActive, }; } diff --git a/lib/modules/assets/data/repositories/asset_repository_impl.dart b/lib/modules/assets/data/repositories/asset_repository_impl.dart index 826674b..8bf576c 100644 --- a/lib/modules/assets/data/repositories/asset_repository_impl.dart +++ b/lib/modules/assets/data/repositories/asset_repository_impl.dart @@ -262,6 +262,51 @@ class AssetRepositoryImpl implements AssetRepository { return safeApiCall(() => dataSource.renewInsurancePolicy(assetId, policyId, data)); } + @override + Future>> getMyMaintenanceAssets({ + bool dueOnly = false, + int page = 1, + int limit = 20, + }) { + return safeApiCall( + () => dataSource.getMyMaintenanceAssets( + dueOnly: dueOnly, + page: page, + limit: limit, + ), + ); + } + + @override + Future>> getMaintenanceLogs( + String assetId, + ) { + return safeApiCall(() => dataSource.getMaintenanceLogs(assetId)); + } + + @override + Future> createMaintenanceLog( + String assetId, + Map data, + ) { + return safeApiCall(() => dataSource.createMaintenanceLog(assetId, data)); + } + + @override + Future> getMaintenanceLogById( + String assetId, + String logId, + ) { + return safeApiCall( + () => dataSource.getMaintenanceLogById(assetId, logId), + ); + } + + @override + Future> deleteMaintenanceLog(String assetId, String logId) { + return safeApiCall(() => dataSource.deleteMaintenanceLog(assetId, logId)); + } + @override Future>> listAttachments(String assetId) { return safeApiCall(() => dataSource.listAttachments(assetId)); diff --git a/lib/modules/assets/domain/repositories/asset_repository.dart b/lib/modules/assets/domain/repositories/asset_repository.dart index 84d6bf3..b50124f 100644 --- a/lib/modules/assets/domain/repositories/asset_repository.dart +++ b/lib/modules/assets/domain/repositories/asset_repository.dart @@ -89,6 +89,23 @@ abstract class AssetRepository { String policyId, Map data, ); + Future>> getMyMaintenanceAssets({ + bool dueOnly = false, + int page = 1, + int limit = 20, + }); + Future>> getMaintenanceLogs( + String assetId, + ); + Future> createMaintenanceLog( + String assetId, + Map data, + ); + Future> getMaintenanceLogById( + String assetId, + String logId, + ); + Future> deleteMaintenanceLog(String assetId, String logId); Future>> listAttachments(String assetId); Future> uploadAttachment( String assetId, { diff --git a/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart b/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart index 8216cce..f6f1034 100644 --- a/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart +++ b/lib/modules/assets/presentation/providers/asset_form_lookups_provider.dart @@ -14,9 +14,8 @@ import '../../../vendors/data/repositories/vendor_repository_impl.dart'; class AssetFormLookups { const AssetFormLookups({ - this.plants = const [], + this.locations = const [], this.departments = const [], - this.warehouses = const [], this.vendors = const [], this.users = const [], this.purchaseOrders = const [], @@ -24,9 +23,8 @@ class AssetFormLookups { this.options = const AssetDropdownOptionsModel(), }); - final List plants; + final List locations; final List departments; - final List warehouses; final List vendors; final List users; final List purchaseOrders; @@ -50,9 +48,8 @@ final assetFormLookupsProvider = FutureProvider.autoDispose((ref) async { final master = ref.watch(masterRemoteDataSourceProvider); - final plants = await _safeOptions(master.listPlants); + final locations = await _safeOptions(master.listLocations); 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); @@ -60,9 +57,8 @@ final assetFormLookupsProvider = final options = await _safeAssetOptions(ref); return AssetFormLookups( - plants: plants, + locations: locations, departments: departments, - warehouses: warehouses, vendors: vendors, users: users, purchaseOrders: purchaseOrders, diff --git a/lib/modules/assets/presentation/providers/assets_provider.dart b/lib/modules/assets/presentation/providers/assets_provider.dart index 4e0c2c9..420fe15 100644 --- a/lib/modules/assets/presentation/providers/assets_provider.dart +++ b/lib/modules/assets/presentation/providers/assets_provider.dart @@ -118,10 +118,10 @@ class AssetsListNotifier extends AutoDisposeAsyncNotifier { applyQuery(current.query.copyWith(itemCategoryId: itemCategoryId, page: 1)); } - void setPlantFilter(int? plantId) { + void setLocationFilter(int? locationId) { final current = state.valueOrNull; if (current == null) return; - applyQuery(current.query.copyWith(plantId: plantId, page: 1)); + applyQuery(current.query.copyWith(locationId: locationId, page: 1)); } void setPage(int page) { @@ -594,3 +594,106 @@ class AssetAlertsNotifier extends AsyncNotifier { state = AsyncData(await _load(current.copyWith(expiryPage: page))); } } + +class MyMaintenanceState { + const MyMaintenanceState({ + this.assets = const [], + this.dueOnly = true, + this.page = 1, + this.limit = 20, + this.total = 0, + this.totalPages = 1, + this.isRefreshing = false, + }); + + final List assets; + final bool dueOnly; + final int page; + final int limit; + final int total; + final int totalPages; + final bool isRefreshing; + + MyMaintenanceState copyWith({ + List? assets, + bool? dueOnly, + int? page, + int? limit, + int? total, + int? totalPages, + bool? isRefreshing, + }) { + return MyMaintenanceState( + assets: assets ?? this.assets, + dueOnly: dueOnly ?? this.dueOnly, + page: page ?? this.page, + limit: limit ?? this.limit, + total: total ?? this.total, + totalPages: totalPages ?? this.totalPages, + isRefreshing: isRefreshing ?? this.isRefreshing, + ); + } +} + +final myMaintenanceProvider = + AsyncNotifierProvider( + MyMaintenanceNotifier.new, +); + +class MyMaintenanceNotifier extends AsyncNotifier { + @override + Future build() async { + return _load(const MyMaintenanceState()); + } + + Future _load(MyMaintenanceState filters) async { + final repository = ref.read(assetRepositoryProvider); + final result = await repository.getMyMaintenanceAssets( + dueOnly: filters.dueOnly, + page: filters.page, + limit: filters.limit, + ); + if (result.failure != null) throw result.failure!; + final page = result.data!; + return MyMaintenanceState( + assets: page.items, + dueOnly: filters.dueOnly, + page: page.page, + limit: filters.limit, + total: page.total, + totalPages: page.totalPages, + ); + } + + Future refresh() async { + final current = state.valueOrNull ?? const MyMaintenanceState(); + state = AsyncData(current.copyWith(isRefreshing: true)); + try { + state = AsyncData(await _load(current)); + } catch (e, st) { + state = AsyncError(e, st); + } + } + + Future setDueOnly(bool dueOnly) async { + final current = state.valueOrNull ?? const MyMaintenanceState(); + state = const AsyncLoading(); + state = AsyncData( + await _load(current.copyWith(dueOnly: dueOnly, page: 1)), + ); + } + + Future setPage(int page) async { + final current = state.valueOrNull ?? const MyMaintenanceState(); + state = const AsyncLoading(); + state = AsyncData(await _load(current.copyWith(page: page))); + } + + Future setPageSize(int limit) async { + final current = state.valueOrNull ?? const MyMaintenanceState(); + state = const AsyncLoading(); + state = AsyncData( + await _load(current.copyWith(limit: limit, page: 1)), + ); + } +} diff --git a/lib/modules/assets/presentation/screens/asset_alerts_screen.dart b/lib/modules/assets/presentation/screens/asset_alerts_screen.dart index 5263864..e5ca13c 100644 --- a/lib/modules/assets/presentation/screens/asset_alerts_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_alerts_screen.dart @@ -285,7 +285,8 @@ class _AlertCard extends StatelessWidget { final visual = _alertVisualStyle(alert); final subtitleParts = [ if (alert.assetCode?.trim().isNotEmpty == true) alert.assetCode!.trim(), - if (alert.plantName?.trim().isNotEmpty == true) alert.plantName!.trim(), + if (alert.locationName?.trim().isNotEmpty == true) + alert.locationName!.trim(), if (dateLabel != null) 'Due: $dateLabel', ]; diff --git a/lib/modules/assets/presentation/screens/asset_detail_screen.dart b/lib/modules/assets/presentation/screens/asset_detail_screen.dart index d5308b9..6462d3e 100644 --- a/lib/modules/assets/presentation/screens/asset_detail_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_detail_screen.dart @@ -23,6 +23,7 @@ import '../../../../shared/widgets/app_side_panel.dart'; import '../providers/assets_provider.dart'; import '../providers/asset_form_lookups_provider.dart'; import '../widgets/asset_form_panel.dart'; +import '../widgets/asset_maintenance_panel.dart'; import '../widgets/asset_side_panels.dart'; import '../../../../shared/widgets/app_toast.dart'; @@ -198,6 +199,12 @@ class _OverviewTab extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final dateFormat = DateFormat('dd MMM yyyy'); + final users = + ref.watch(assetFormLookupsProvider).valueOrNull?.users ?? const []; + final maintenanceInchargeLabel = _userLabel( + asset.maintenanceInchargeUserId, + users, + ); return SingleChildScrollView( child: Center( @@ -239,7 +246,40 @@ class _OverviewTab extends ConsumerWidget { 'Subcategory', asset.assetSubcategoryName ?? '—', ), - _AssetInfo('Plant', asset.plantName ?? '—'), + _AssetInfo('Location', asset.locationName ?? '—'), + _AssetInfo( + 'Commencement Date', + asset.commencementDate != null + ? dateFormat.format(asset.commencementDate!) + : '—', + ), + _AssetInfo( + 'Maintenance Incharge', + maintenanceInchargeLabel, + ), + _AssetInfo( + 'Maintenance Frequency', + asset.maintenanceFrequencyInDays != null + ? '${asset.maintenanceFrequencyInDays} days' + : '—', + ), + if (asset.maintenance != null) ...[ + _AssetInfo( + 'Maintenance Due', + asset.maintenance!.isDue ? 'Yes' : 'No', + ), + _AssetInfo( + 'Next Due Date', + asset.maintenance!.nextDueDate != null + ? dateFormat + .format(asset.maintenance!.nextDueDate!) + : '—', + ), + _AssetInfo( + 'Days Until Due', + asset.maintenance!.daysUntilDue?.toString() ?? '—', + ), + ], _AssetInfo('Serial Number', asset.serialNumber ?? '—'), _AssetInfo('Brand / Model', asset.brandModel ?? '—'), _AssetInfo('Manufacturer', asset.manufacturer ?? '—'), @@ -285,6 +325,36 @@ class _OverviewTab extends ConsumerWidget { _AssetInfo('Active', asset.isActive ? 'Yes' : 'No'), ], ), + if (asset.maintenanceFrequencyInDays != null || + asset.maintenanceChecklistJson?.isNotEmpty == true || + asset.maintenance != null) ...[ + const Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Divider(height: 1), + ), + Align( + alignment: Alignment.centerLeft, + child: OutlinedButton.icon( + onPressed: () async { + final saved = await openSubmitMaintenancePanel( + context, + ref, + asset: asset, + ); + if (saved == true && context.mounted) { + showAppToastFromSnackBar( + context, + const SnackBar( + content: Text('Maintenance log submitted'), + ), + ); + } + }, + icon: const Icon(Icons.checklist_outlined), + label: const Text('Log Maintenance'), + ), + ), + ], if (asset.remarks?.trim().isNotEmpty == true) ...[ const Padding( padding: EdgeInsets.symmetric(vertical: 20), @@ -410,6 +480,17 @@ class _AssetInfo { final Widget? valueWidget; } +String _userLabel(int? userId, List users) { + if (userId == null || userId <= 0) return '—'; + final id = userId.toString(); + for (final user in users) { + if (user.id == id && user.name.trim().isNotEmpty) { + return user.name.trim(); + } + } + return id; +} + class _AmcTab extends ConsumerWidget { const _AmcTab({required this.assetId, required this.contracts}); diff --git a/lib/modules/assets/presentation/screens/asset_list_screen.dart b/lib/modules/assets/presentation/screens/asset_list_screen.dart index dddf8eb..5c406a1 100644 --- a/lib/modules/assets/presentation/screens/asset_list_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_list_screen.dart @@ -69,7 +69,7 @@ class _AssetListScreenState extends ConsumerState { final allCategories = ref.watch(itemCategoriesProvider).valueOrNull ?? []; final lookups = ref.watch(assetFormLookupsProvider).valueOrNull; - final allPlants = lookups?.plants ?? const []; + final allLocations = lookups?.locations ?? const []; final notifier = ref.read(assetsListProvider.notifier); return Column( @@ -139,11 +139,11 @@ class _AssetListScreenState extends ConsumerState { child: _AssetsFilterBar( query: state.query, categories: allCategories, - plants: allPlants, + locations: allLocations, statuses: lookups?.statuses ?? const [], onSearch: notifier.setSearch, onCategoryChanged: notifier.setCategoryFilter, - onPlantChanged: notifier.setPlantFilter, + onLocationChanged: notifier.setLocationFilter, onStatusChanged: notifier.setStatusFilter, ), ), @@ -272,21 +272,21 @@ class _AssetsFilterBar extends StatelessWidget { const _AssetsFilterBar({ required this.query, required this.categories, - required this.plants, + required this.locations, required this.statuses, required this.onSearch, required this.onCategoryChanged, - required this.onPlantChanged, + required this.onLocationChanged, required this.onStatusChanged, }); final AssetListQuery query; final List categories; - final List plants; + final List locations; final List statuses; final ValueChanged onSearch; final ValueChanged onCategoryChanged; - final ValueChanged onPlantChanged; + final ValueChanged onLocationChanged; final ValueChanged onStatusChanged; @override @@ -315,13 +315,13 @@ class _AssetsFilterBar extends StatelessWidget { ), ]; - final plantOptions = >[ - const AppDropdownOption(value: null, label: 'All Plants'), - for (final plant in plants) - if (int.tryParse(plant.id) != null) + final locationOptions = >[ + const AppDropdownOption(value: null, label: 'All Locations'), + for (final location in locations) + if (int.tryParse(location.id) != null) AppDropdownOption( - value: int.parse(plant.id), - label: plant.name, + value: int.parse(location.id), + label: location.name, ), ]; @@ -347,12 +347,12 @@ class _AssetsFilterBar extends StatelessWidget { onChanged: onCategoryChanged, ), AppSearchableDropdown( - label: 'Plant', - value: query.plantId, - searchHint: 'Search plant...', + label: 'Location', + value: query.locationId, + searchHint: 'Search location...', isDense: true, - options: plantOptions, - onChanged: onPlantChanged, + options: locationOptions, + onChanged: onLocationChanged, ), AppSearchableDropdown( label: 'Status', @@ -416,10 +416,10 @@ class _AssetDataTable extends StatelessWidget { cellBuilder: (_, asset) => Text(asset.assetCategoryName ?? '—'), ), AppDataColumn( - label: 'Plant', + label: 'Location', flex: 1, - searchText: (asset) => asset.plantName ?? '', - cellBuilder: (_, asset) => Text(asset.plantName ?? '—'), + searchText: (asset) => asset.locationName ?? '', + cellBuilder: (_, asset) => Text(asset.locationName ?? '—'), ), AppDataColumn( label: 'Warranty', @@ -533,7 +533,7 @@ class _AssetMobileList extends StatelessWidget { _AssetCodeBadge(code: asset.assetCode!) else const Text('—'), - Text('${asset.assetCategoryName ?? '—'} · ${asset.plantName ?? '—'}'), + Text('${asset.assetCategoryName ?? '—'} · ${asset.locationName ?? '—'}'), const SizedBox(height: 8), Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/modules/assets/presentation/screens/asset_maintenance_screen.dart b/lib/modules/assets/presentation/screens/asset_maintenance_screen.dart index 744ca48..73c10d1 100644 --- a/lib/modules/assets/presentation/screens/asset_maintenance_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_maintenance_screen.dart @@ -1,30 +1,355 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import '../../../../core/constants/route_constants.dart'; +import '../../../../core/errors/failure.dart'; +import '../../../../core/utils/formatters.dart'; +import '../../../../core/utils/responsive_utils.dart'; +import '../../../../shared/models/asset_model.dart'; +import '../../../../shared/widgets/app_data_table.dart'; +import '../../../../shared/widgets/app_empty_state.dart'; +import '../../../../shared/widgets/app_form_toggle_field.dart'; +import '../../../../shared/widgets/app_loading_view.dart'; +import '../../../../shared/widgets/app_pagination.dart'; +import '../../../../shared/widgets/app_table_action_icon.dart'; +import '../../../../shared/widgets/app_toast.dart'; import '../../../../shared/widgets/error_view.dart'; import '../../../../shared/widgets/page_header.dart'; +import '../providers/assets_provider.dart'; +import '../widgets/asset_maintenance_panel.dart'; -class AssetMaintenanceScreen extends StatelessWidget { +class AssetMaintenanceScreen extends ConsumerWidget { const AssetMaintenanceScreen({super.key}); @override - Widget build(BuildContext context) { - return const Padding( - padding: EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - PageHeader( - title: 'Asset Maintenance', - subtitle: 'Track repairs, service vendors, and costs', - ), - Expanded( - child: EmptyStateView( - title: 'No maintenance requests', - description: 'Create maintenance requests and view service history.', - icon: Icons.build_outlined, - ), - ), - ], + Widget build(BuildContext context, WidgetRef ref) { + final maintenanceAsync = ref.watch(myMaintenanceProvider); + + return Padding( + padding: const EdgeInsets.all(24), + child: maintenanceAsync.when( + loading: () => + const AppLoadingView(message: 'Loading maintenance assets...'), + error: (e, _) => ErrorView.fromFailure( + e is Failure ? e : Failure.unknown(message: e.toString()), + onRetry: () => ref.invalidate(myMaintenanceProvider), + ), + data: (state) => _MyMaintenanceBody(state: state), + ), + ); + } +} + +class _MyMaintenanceBody extends ConsumerWidget { + const _MyMaintenanceBody({required this.state}); + + final MyMaintenanceState state; + + Future _openSubmit( + BuildContext context, + WidgetRef ref, + AssetModel asset, + ) async { + final saved = await openSubmitMaintenancePanel(context, ref, asset: asset); + if (saved == true && context.mounted) { + showAppToastFromSnackBar( + context, + const SnackBar(content: Text('Maintenance log submitted')), + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final notifier = ref.read(myMaintenanceProvider.notifier); + final isWide = context.isDesktop; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PageHeader( + title: 'My Maintenance', + subtitle: 'Assets assigned to you for checklist-based maintenance', + actions: [ + OutlinedButton.icon( + onPressed: () => context.go(RouteConstants.assets), + icon: const Icon(Icons.inventory_2_outlined), + label: const Text('Asset Master'), + ), + ], + ), + const SizedBox(height: 8), + Wrap( + spacing: 16, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + AppFormToggleField( + label: 'Due only', + subtitle: 'Show assets with maintenance due', + value: state.dueOnly, + onChanged: notifier.setDueOnly, + ), + IconButton( + onPressed: state.isRefreshing ? null : () => notifier.refresh(), + icon: state.isRefreshing + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.refresh), + tooltip: 'Refresh', + ), + Text( + '${state.total} asset${state.total == 1 ? '' : 's'}', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + const SizedBox(height: 16), + Expanded( + child: state.assets.isEmpty + ? AppEmptyState( + title: state.dueOnly + ? 'No due maintenance' + : 'No maintenance assets', + description: state.dueOnly + ? 'Nothing is due right now. Turn off “Due only” to see all assigned assets.' + : 'Assets where you are the maintenance in-charge will appear here.', + icon: Icons.build_circle_outlined, + ) + : isWide + ? _MaintenanceTable( + assets: state.assets, + onSubmit: (asset) => _openSubmit(context, ref, asset), + onView: (asset) => + context.push('${RouteConstants.assets}/${asset.id}'), + ) + : _MaintenanceMobileList( + assets: state.assets, + onSubmit: (asset) => _openSubmit(context, ref, asset), + onView: (asset) => + context.push('${RouteConstants.assets}/${asset.id}'), + ), + ), + AppPagination( + currentPage: state.page, + totalPages: state.totalPages, + totalItems: state.total, + pageSize: state.limit, + onPageChanged: notifier.setPage, + onPageSizeChanged: notifier.setPageSize, + itemLabel: 'assets', + ), + ], + ); + } +} + +class _MaintenanceTable extends StatelessWidget { + const _MaintenanceTable({ + required this.assets, + required this.onSubmit, + required this.onView, + }); + + final List assets; + final void Function(AssetModel asset) onSubmit; + final void Function(AssetModel asset) onView; + + @override + Widget build(BuildContext context) { + final dateFormat = DateFormat('dd MMM yyyy'); + + return AppDataTable( + wrapInCard: false, + columns: [ + AppDataColumn( + label: 'Asset Code', + flex: 1, + searchText: (asset) => asset.assetCode ?? '', + cellBuilder: (_, asset) => Text(asset.assetCode ?? '—'), + ), + AppDataColumn( + label: 'Asset Name', + flex: 2, + searchText: (asset) => asset.assetName, + cellBuilder: (_, asset) => Text(asset.assetName), + ), + AppDataColumn( + label: 'Next Due', + flex: 1, + searchText: (asset) => DateFormatter.displayDate( + asset.maintenance?.nextDueDate, + ), + cellBuilder: (_, asset) { + final nextDue = asset.maintenance?.nextDueDate; + return Text( + nextDue != null ? dateFormat.format(nextDue) : '—', + ); + }, + ), + AppDataColumn( + label: 'Status', + flex: 1, + searchText: (asset) => + asset.maintenance?.isDue == true ? 'due' : 'ok', + cellBuilder: (_, asset) => _DueBadge( + isDue: asset.maintenance?.isDue == true, + daysUntilDue: asset.maintenance?.daysUntilDue, + ), + ), + AppDataColumn( + label: 'Actions', + flex: 1, + alignment: Alignment.centerRight, + enableSearch: false, + cellBuilder: (_, asset) => AppTableActions( + children: [ + AppTableActionIcon( + tooltip: 'Submit checklist', + icon: Icons.checklist_outlined, + onPressed: () => onSubmit(asset), + ), + AppTableActionIcon( + tooltip: 'View asset', + icon: Icons.visibility_outlined, + onPressed: () => onView(asset), + ), + ], + ), + ), + ], + rows: assets, + ); + } +} + +class _MaintenanceMobileList extends StatelessWidget { + const _MaintenanceMobileList({ + required this.assets, + required this.onSubmit, + required this.onView, + }); + + final List assets; + final void Function(AssetModel asset) onSubmit; + final void Function(AssetModel asset) onView; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final dateFormat = DateFormat('dd MMM yyyy'); + + return ListView.separated( + itemCount: assets.length, + separatorBuilder: (_, __) => const SizedBox(height: 8), + itemBuilder: (context, index) { + final asset = assets[index]; + final nextDue = asset.maintenance?.nextDueDate; + final subtitleParts = [ + if (asset.assetCode?.trim().isNotEmpty == true) + asset.assetCode!.trim(), + if (nextDue != null) 'Next due: ${dateFormat.format(nextDue)}', + ]; + + return Material( + color: theme.colorScheme.surfaceContainerHighest.withValues( + alpha: 0.35, + ), + borderRadius: BorderRadius.circular(12), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () => onSubmit(asset), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + asset.assetName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + if (subtitleParts.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + subtitleParts.join(' · '), + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + const SizedBox(height: 8), + _DueBadge( + isDue: asset.maintenance?.isDue == true, + daysUntilDue: asset.maintenance?.daysUntilDue, + ), + ], + ), + ), + IconButton( + tooltip: 'View asset', + onPressed: () => onView(asset), + icon: const Icon(Icons.visibility_outlined), + ), + ], + ), + ), + ), + ); + }, + ); + } +} + +class _DueBadge extends StatelessWidget { + const _DueBadge({ + required this.isDue, + this.daysUntilDue, + }); + + final bool isDue; + final int? daysUntilDue; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final color = isDue + ? const Color(0xFFDC2626) + : const Color(0xFF16A34A); + final label = isDue + ? (daysUntilDue != null && daysUntilDue! < 0 + ? 'Overdue' + : 'Due') + : (daysUntilDue != null + ? 'In $daysUntilDue day${daysUntilDue == 1 ? '' : 's'}' + : 'On track'); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(999), + ), + child: Text( + label, + style: theme.textTheme.labelSmall?.copyWith( + color: color, + fontWeight: FontWeight.w700, + letterSpacing: 0.2, + ), ), ); } diff --git a/lib/modules/assets/presentation/widgets/asset_form_panel.dart b/lib/modules/assets/presentation/widgets/asset_form_panel.dart index bbfc59c..29d88e0 100644 --- a/lib/modules/assets/presentation/widgets/asset_form_panel.dart +++ b/lib/modules/assets/presentation/widgets/asset_form_panel.dart @@ -78,12 +78,13 @@ class _AssetFormPanelState extends ConsumerState { final _depreciationRateController = TextEditingController(); final _salvageValueController = TextEditingController(); final _remarksController = TextEditingController(); + final _frequencyController = TextEditingController(); int? _categoryId; int? _subcategoryId; - int? _plantId; + int? _locationId; int? _departmentId; - int? _warehouseId; int? _assignedToUserId; + int? _maintenanceInchargeUserId; int? _vendorId; int? _poId; int? _grnId; @@ -93,7 +94,9 @@ class _AssetFormPanelState extends ConsumerState { String? _depreciationMethod; DateTime? _warrantyExpiry; DateTime? _purchaseDate; + DateTime? _commencementDate; DateTime? _disposalDate; + List _checklistItems = []; bool _isActive = true; bool _isSubmitting = false; String? _populatedSignature; @@ -130,6 +133,7 @@ class _AssetFormPanelState extends ConsumerState { _depreciationRateController.dispose(); _salvageValueController.dispose(); _remarksController.dispose(); + _frequencyController.dispose(); super.dispose(); } @@ -139,7 +143,7 @@ class _AssetFormPanelState extends ConsumerState { String _assetSignature(AssetModel asset) => '${asset.id}:${asset.assetCategoryId}:${asset.assetSubcategoryId}:' - '${asset.plantId}:${asset.status}:${asset.assetName}'; + '${asset.locationId}:${asset.status}:${asset.assetName}'; int? _nullablePositiveId(int? id) { if (id == null || id <= 0) return null; @@ -188,10 +192,11 @@ class _AssetFormPanelState extends ConsumerState { _nameController.text = asset.assetName; _categoryId = asset.assetCategoryId; _subcategoryId = asset.assetSubcategoryId; - _plantId = asset.plantId; + _locationId = asset.locationId; _departmentId = _nullablePositiveId(asset.departmentId); - _warehouseId = _nullablePositiveId(asset.warehouseId); _assignedToUserId = _nullablePositiveId(asset.assignedToUserId); + _maintenanceInchargeUserId = + _nullablePositiveId(asset.maintenanceInchargeUserId); _vendorId = _nullablePositiveId(asset.vendorId); _poId = _nullablePositiveId(asset.poId); _grnId = _nullablePositiveId(asset.grnId); @@ -201,7 +206,11 @@ class _AssetFormPanelState extends ConsumerState { _depreciationMethod = asset.depreciationMethod; _warrantyExpiry = asset.warrantyExpiryDate; _purchaseDate = asset.purchaseDate; + _commencementDate = asset.commencementDate; _disposalDate = asset.disposalDate; + _checklistItems = [ + ...(asset.maintenanceChecklistJson ?? const []), + ]; _isActive = asset.isActive; _serialController.text = asset.serialNumber ?? ''; _partNumberController.text = asset.partNumber ?? ''; @@ -215,6 +224,8 @@ class _AssetFormPanelState extends ConsumerState { _depreciationRateController.text = asset.depreciationRate?.toString() ?? ''; _salvageValueController.text = asset.salvageValue?.toString() ?? ''; _remarksController.text = asset.remarks ?? ''; + _frequencyController.text = + asset.maintenanceFrequencyInDays?.toString() ?? ''; if (asset.purchaseCost != null) { _costController.text = asset.purchaseCost!.toStringAsFixed( asset.purchaseCost! % 1 == 0 ? 0 : 2, @@ -261,6 +272,10 @@ class _AssetFormPanelState extends ConsumerState { final usefulLife = int.tryParse(_usefulLifeController.text.trim()); if (usefulLife != null) payload['useful_life_years'] = usefulLife; + if (_commencementDate != null) { + payload['commencement_date'] = + DateFormat('yyyy-MM-dd').format(_commencementDate!); + } if (_purchaseDate != null) { payload['purchase_date'] = DateFormat('yyyy-MM-dd').format(_purchaseDate!); } @@ -312,15 +327,21 @@ class _AssetFormPanelState extends ConsumerState { 'asset_name': _nameController.text.trim(), 'item_category_id': _categoryId, 'item_subcategory_id': _subcategoryId, - 'plant_id': _plantId, + 'location_id': _locationId, 'is_active': _isActive, }; _putOptionalId(payload, 'department_id', _departmentId); - _putOptionalId(payload, 'warehouse_id', _warehouseId); if (_isAssignableUser(_assignedToUserId)) { _putOptionalId(payload, 'assigned_to_user_id', _assignedToUserId); } + if (_isAssignableUser(_maintenanceInchargeUserId)) { + _putOptionalId( + payload, + 'maintenance_incharge_user_id', + _maintenanceInchargeUserId, + ); + } _putOptionalId(payload, 'vendor_id', _vendorId); _putOptionalId(payload, 'po_id', _poId); _putOptionalId(payload, 'grn_id', _grnId); @@ -338,6 +359,10 @@ class _AssetFormPanelState extends ConsumerState { if (_purchaseDate != null) { payload['purchase_date'] = DateFormat('yyyy-MM-dd').format(_purchaseDate!); } + if (_commencementDate != null) { + payload['commencement_date'] = + DateFormat('yyyy-MM-dd').format(_commencementDate!); + } if (_warrantyExpiry != null) { payload['warranty_expiry_date'] = DateFormat('yyyy-MM-dd').format(_warrantyExpiry!); @@ -353,6 +378,31 @@ class _AssetFormPanelState extends ConsumerState { final usefulLife = int.tryParse(_usefulLifeController.text.trim()); if (usefulLife != null) payload['useful_life_years'] = usefulLife; + final frequency = int.tryParse(_frequencyController.text.trim()); + if (frequency != null) payload['maintenance_frequency_in_days'] = frequency; + + final checklistPayload = _checklistItems + .map((item) => item.toJson()) + .where((item) { + final key = (item['key'] as String?)?.trim() ?? ''; + final label = (item['label'] as String?)?.trim() ?? ''; + return key.isNotEmpty || label.isNotEmpty; + }) + .map((item) { + final key = (item['key'] as String?)?.trim() ?? ''; + final label = (item['label'] as String?)?.trim() ?? ''; + return { + 'key': key.isNotEmpty ? key : label, + 'label': label.isNotEmpty ? label : key, + 'required': item['required'] == true, + }; + }) + .where((item) => (item['key'] as String).isNotEmpty) + .toList(); + if (checklistPayload.isNotEmpty) { + payload['maintenance_checklist_json'] = checklistPayload; + } + if (_depreciationMethod != null) { payload['depreciation_method'] = _depreciationMethod; } @@ -467,7 +517,6 @@ class _AssetFormPanelState extends ConsumerState { @override Widget build(BuildContext context) { final categoriesAsync = ref.watch(itemCategoriesFormProvider); - final plantsAsync = ref.watch(assetPlantsProvider); if (widget.isEditing) { ref.listen(assetFormProvider(widget.assetId), (prev, next) { @@ -513,15 +562,14 @@ class _AssetFormPanelState extends ConsumerState { e is Failure ? e : Failure.unknown(message: e.toString()), onRetry: () => ref.invalidate(assetFormProvider(widget.assetId)), ), - data: (_) => _buildForm(categoriesAsync, plantsAsync), + data: (_) => _buildForm(categoriesAsync), ) - : _buildForm(categoriesAsync, plantsAsync), + : _buildForm(categoriesAsync), ); } Widget _buildForm( AsyncValue> categoriesAsync, - AsyncValue> plantsAsync, ) { final subcategoriesAsync = ref.watch(itemSubcategoriesProvider(_categoryId)); final lookupsAsync = ref.watch(assetFormLookupsProvider); @@ -589,10 +637,10 @@ class _AssetFormPanelState extends ConsumerState { ), ), const SizedBox(height: 12), - plantsAsync.when( + lookupsAsync.when( loading: () => const LinearProgressIndicator(), - error: (_, __) => const Text('Failed to load plants'), - data: (plants) => _plantDropdown(plants), + error: (_, __) => const Text('Failed to load locations'), + data: (lookups) => _locationDropdown(lookups.locations), ), ], ), @@ -681,29 +729,21 @@ class _AssetFormPanelState extends ConsumerState { masterId: 'departments', onChanged: (v) => setState(() => _departmentId = v), ), - right: _optionalLookupDropdown( - label: 'Warehouse', - value: _warehouseId, - options: lookups.warehouses, - masterId: 'warehouses', - onChanged: (v) => setState(() => _warehouseId = v), + right: 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), - 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, @@ -713,6 +753,33 @@ class _AssetFormPanelState extends ConsumerState { ), ], ), + SidePanelSection( + title: 'MAINTENANCE', + children: [ + SidePanelFormRow( + left: _optionalLookupDropdown( + label: 'Maintenance Incharge', + value: _maintenanceInchargeUserId, + options: lookups.users, + onChanged: (v) => + setState(() => _maintenanceInchargeUserId = v), + emptyHint: 'Unassigned', + ), + right: AppTextField( + isDense: true, + controller: _frequencyController, + label: 'Frequency (Days)', + keyboardType: TextInputType.number, + validator: (v) => Validators.optionalPositiveInt( + v, + fieldName: 'Frequency', + ), + ), + ), + const SizedBox(height: 12), + _buildChecklistEditor(), + ], + ), SidePanelSection( title: 'PROCUREMENT', children: [ @@ -779,11 +846,23 @@ class _AssetFormPanelState extends ConsumerState { _pickDate((d) => _purchaseDate = d, _purchaseDate), ), right: _AssetFormDateField( + label: 'Commencement Date', + value: _commencementDate, + onPick: () => _pickDate( + (d) => _commencementDate = d, + _commencementDate, + ), + ), + ), + const SizedBox(height: 12), + SidePanelFormRow( + left: _AssetFormDateField( label: 'Warranty Expiry Date', value: _warrantyExpiry, onPick: () => _pickDate((d) => _warrantyExpiry = d, _warrantyExpiry), ), + right: const SizedBox.shrink(), ), const SizedBox(height: 12), SidePanelFormRow( @@ -979,6 +1058,76 @@ class _AssetFormPanelState extends ConsumerState { ); } + Widget _buildChecklistEditor() { + final theme = Theme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Expanded( + child: Text( + 'Checklist Template', + style: theme.textTheme.labelLarge?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w600, + ), + ), + ), + TextButton.icon( + onPressed: () { + setState(() { + _checklistItems = [ + ..._checklistItems, + const AssetMaintenanceChecklistItem( + key: '', + label: '', + ), + ]; + }); + }, + icon: const Icon(Icons.add, size: 18), + label: const Text('Add Item'), + ), + ], + ), + if (_checklistItems.isEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + 'No checklist items. Add items for maintenance checks.', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + for (var i = 0; i < _checklistItems.length; i++) ...[ + if (i > 0) const SizedBox(height: 8), + _ChecklistItemRow( + key: ValueKey('checklist-row-$i'), + item: _checklistItems[i], + onChanged: (updated) { + setState(() { + _checklistItems = [ + for (var j = 0; j < _checklistItems.length; j++) + if (j == i) updated else _checklistItems[j], + ]; + }); + }, + onRemove: () { + setState(() { + _checklistItems = [ + for (var j = 0; j < _checklistItems.length; j++) + if (j != i) _checklistItems[j], + ]; + }); + }, + ), + ], + ], + ); + } + Widget _optionalLookupDropdown({ required String label, required int? value, @@ -1034,7 +1183,6 @@ class _AssetFormPanelState extends ConsumerState { options: dropdownOptions, refreshLookups: () { ref.invalidate(assetFormLookupsProvider); - ref.invalidate(assetPlantsProvider); }, parseCreatedId: int.tryParse, onChanged: onChanged, @@ -1128,33 +1276,125 @@ class _AssetFormPanelState extends ConsumerState { ); } - Widget _plantDropdown(List plants) { - final plantIds = plants - .map((p) => int.tryParse(p.id)) + Widget _locationDropdown(List locations) { + final locationIds = locations + .map((location) => int.tryParse(location.id)) .whereType() .toList(); - return MasterQuickAddDropdown( - masterId: 'plants', - label: 'Plant *', - value: _dropdownValue(_plantId, plantIds), - searchHint: 'Search plant...', + return AppSearchableDropdown( + label: 'Location *', + value: _dropdownValue(_locationId, locationIds), + searchHint: 'Search plant or warehouse...', isDense: true, - options: plants + options: locations .map( - (p) => AppDropdownOption( - value: int.tryParse(p.id) ?? 0, - label: p.name, + (location) => AppDropdownOption( + value: int.tryParse(location.id) ?? 0, + label: location.name, ), ) .where((option) => option.value != 0) .toList(), - refreshLookups: () { - ref.invalidate(assetPlantsProvider); - ref.invalidate(assetFormLookupsProvider); - }, - parseCreatedId: int.tryParse, - onChanged: (v) => setState(() => _plantId = v), - validator: (v) => v == null ? 'Plant is required' : null, + onChanged: (v) => setState(() => _locationId = v), + validator: (v) => v == null ? 'Location is required' : null, + ); + } +} + +class _ChecklistItemRow extends StatefulWidget { + const _ChecklistItemRow({ + super.key, + required this.item, + required this.onChanged, + required this.onRemove, + }); + + final AssetMaintenanceChecklistItem item; + final ValueChanged onChanged; + final VoidCallback onRemove; + + @override + State<_ChecklistItemRow> createState() => _ChecklistItemRowState(); +} + +class _ChecklistItemRowState extends State<_ChecklistItemRow> { + late final TextEditingController _keyController; + late final TextEditingController _labelController; + + @override + void initState() { + super.initState(); + _keyController = TextEditingController(text: widget.item.key); + _labelController = TextEditingController(text: widget.item.label); + } + + @override + void didUpdateWidget(covariant _ChecklistItemRow oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.item.key != widget.item.key && + _keyController.text != widget.item.key) { + _keyController.text = widget.item.key; + } + if (oldWidget.item.label != widget.item.label && + _labelController.text != widget.item.label) { + _labelController.text = widget.item.label; + } + } + + @override + void dispose() { + _keyController.dispose(); + _labelController.dispose(); + super.dispose(); + } + + void _emit({String? key, String? label, bool? required}) { + widget.onChanged( + AssetMaintenanceChecklistItem( + key: key ?? _keyController.text, + label: label ?? _labelController.text, + required: required ?? widget.item.required, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SidePanelFormRow( + left: AppTextField( + isDense: true, + controller: _keyController, + label: 'Key', + onChanged: (value) => _emit(key: value), + ), + right: AppTextField( + isDense: true, + controller: _labelController, + label: 'Label', + onChanged: (value) => _emit(label: value), + ), + ), + const SizedBox(height: 4), + Row( + children: [ + Expanded( + child: AppFormToggleField( + label: 'Required', + value: widget.item.required, + onChanged: (value) => _emit(required: value), + ), + ), + IconButton( + tooltip: 'Remove item', + onPressed: widget.onRemove, + icon: const Icon(Icons.delete_outline, size: 20), + ), + ], + ), + ], ); } } @@ -1340,11 +1580,6 @@ class _AssetFormDateFieldState extends State<_AssetFormDateField> { } } -final assetPlantsProvider = FutureProvider>((ref) async { - final dataSource = ref.watch(masterRemoteDataSourceProvider); - return dataSource.listPlants(); -}); - final itemSubcategoriesProvider = FutureProvider.family, int?>((ref, categoryId) async { if (categoryId == null) return []; diff --git a/lib/modules/assets/presentation/widgets/asset_maintenance_panel.dart b/lib/modules/assets/presentation/widgets/asset_maintenance_panel.dart new file mode 100644 index 0000000..fc7a6a7 --- /dev/null +++ b/lib/modules/assets/presentation/widgets/asset_maintenance_panel.dart @@ -0,0 +1,475 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; + +import '../../../../core/utils/formatters.dart'; +import '../../../../shared/models/asset_model.dart'; +import '../../../../shared/widgets/app_button.dart'; +import '../../../../shared/widgets/app_date_popup.dart'; +import '../../../../shared/widgets/app_dropdown.dart'; +import '../../../../shared/widgets/app_empty_state.dart'; +import '../../../../shared/widgets/app_side_panel.dart'; +import '../../../../shared/widgets/app_text_field.dart'; +import '../../data/repositories/asset_repository_impl.dart'; +import '../providers/assets_provider.dart'; + +List checklistForAsset(AssetModel asset) { + final fromSummary = asset.maintenance?.checklist; + if (fromSummary != null && fromSummary.isNotEmpty) { + return fromSummary; + } + return asset.maintenanceChecklistJson ?? const []; +} + +Future openSubmitMaintenancePanel( + BuildContext context, + WidgetRef ref, { + required AssetModel asset, +}) { + return showSidePanel( + context, + SubmitMaintenanceLogPanel(asset: asset), + width: 600, + ); +} + +class SubmitMaintenanceLogPanel extends ConsumerStatefulWidget { + const SubmitMaintenanceLogPanel({super.key, required this.asset}); + + final AssetModel asset; + + @override + ConsumerState createState() => + _SubmitMaintenanceLogPanelState(); +} + +class _ChecklistRowState { + _ChecklistRowState({ + required this.keyName, + required this.label, + }); + + final String keyName; + final String label; + String status = 'OK'; + final TextEditingController remarksController = TextEditingController(); + + void dispose() => remarksController.dispose(); +} + +class _SubmitMaintenanceLogPanelState + extends ConsumerState { + final _formKey = GlobalKey(); + final _remarksController = TextEditingController(); + DateTime _performedDate = DateTime.now(); + late final List<_ChecklistRowState> _rows; + bool _isSubmitting = false; + bool _showLogs = false; + List? _logs; + bool _logsLoading = false; + String? _logsError; + + @override + void initState() { + super.initState(); + final checklist = checklistForAsset(widget.asset); + _rows = checklist + .map( + (item) => _ChecklistRowState( + keyName: item.key.isNotEmpty ? item.key : item.label, + label: item.label.isNotEmpty ? item.label : item.key, + ), + ) + .toList(); + } + + @override + void dispose() { + _remarksController.dispose(); + for (final row in _rows) { + row.dispose(); + } + super.dispose(); + } + + Future _pickPerformedDate() async { + final picked = await showAppDatePopup( + context: context, + initialDate: _performedDate, + firstDate: DateTime(2000), + lastDate: DateTime(2100), + helpText: 'Performed date', + ); + if (picked != null) { + setState(() => _performedDate = picked); + } + } + + Future _loadLogs() async { + setState(() { + _showLogs = true; + _logsLoading = true; + _logsError = null; + }); + final result = await ref + .read(assetRepositoryProvider) + .getMaintenanceLogs(widget.asset.id); + if (!mounted) return; + setState(() { + _logsLoading = false; + if (result.failure != null) { + _logsError = result.failure!.message; + _logs = null; + } else { + _logs = result.data ?? []; + } + }); + } + + Future _save() async { + if (!_formKey.currentState!.validate()) return; + if (_rows.isEmpty) { + showSidePanelSnackBar(context, 'No checklist items configured for this asset'); + return; + } + + setState(() => _isSubmitting = true); + try { + final payload = { + 'performed_date': DateFormatter.toApiDate(_performedDate), + 'checklist_json': _rows + .map( + (row) => { + 'key': row.keyName, + 'status': row.status, + if (row.remarksController.text.trim().isNotEmpty) + 'remarks': row.remarksController.text.trim(), + }, + ) + .toList(), + if (_remarksController.text.trim().isNotEmpty) + 'remarks': _remarksController.text.trim(), + }; + + final result = await ref + .read(assetRepositoryProvider) + .createMaintenanceLog(widget.asset.id, payload); + if (result.failure != null) throw result.failure!; + + ref.invalidate(myMaintenanceProvider); + ref.invalidate(assetDetailProvider(widget.asset.id)); + + 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 theme = Theme.of(context); + final assetLabel = widget.asset.assetCode?.trim().isNotEmpty == true + ? '${widget.asset.assetName} (${widget.asset.assetCode})' + : widget.asset.assetName; + + return SidePanelScaffold( + title: 'Submit Maintenance', + footer: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton( + onPressed: _isSubmitting + ? null + : () => Navigator.of(context, rootNavigator: true).pop(), + child: const Text('Cancel'), + ), + const SizedBox(width: 12), + AppButton( + label: 'Submit Log', + expand: false, + icon: Icons.check, + isLoading: _isSubmitting, + onPressed: _isSubmitting ? null : _save, + ), + ], + ), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + assetLabel, + style: theme.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + Text( + 'Record checklist results for this maintenance visit.', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 16), + SidePanelSection( + title: 'Visit', + children: [ + _DateField( + label: 'Performed Date *', + value: _performedDate, + onPick: _pickPerformedDate, + ), + const SizedBox(height: 12), + AppTextField( + controller: _remarksController, + label: 'Remarks', + maxLines: 3, + ), + ], + ), + const SizedBox(height: 16), + SidePanelSection( + title: 'Checklist', + children: [ + if (_rows.isEmpty) + const AppEmptyState( + title: 'No checklist items', + description: + 'Configure a maintenance checklist on the asset first.', + icon: Icons.checklist_outlined, + ) + else + ..._rows.asMap().entries.map((entry) { + final index = entry.key; + final row = entry.value; + return Padding( + padding: EdgeInsets.only( + bottom: index == _rows.length - 1 ? 0 : 12, + ), + child: _ChecklistItemCard( + row: row, + onStatusChanged: (status) { + if (status == null) return; + setState(() => row.status = status); + }, + ), + ); + }), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Text( + 'Recent Logs', + style: theme.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const Spacer(), + TextButton.icon( + onPressed: _logsLoading + ? null + : () { + if (_showLogs && _logs != null) { + setState(() => _showLogs = !_showLogs); + } else { + _loadLogs(); + } + }, + icon: Icon( + _showLogs ? Icons.expand_less : Icons.history, + size: 18, + ), + label: Text(_showLogs ? 'Hide' : 'View'), + ), + ], + ), + if (_showLogs) ...[ + const SizedBox(height: 8), + if (_logsLoading) + const Padding( + padding: EdgeInsets.symmetric(vertical: 24), + child: Center(child: CircularProgressIndicator()), + ) + else if (_logsError != null) + Text( + _logsError!, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.error, + ), + ) + else if (_logs == null || _logs!.isEmpty) + const AppEmptyState( + title: 'No logs yet', + description: 'Submitted maintenance visits will appear here.', + icon: Icons.history_toggle_off_outlined, + ) + else + ..._logs!.take(5).map( + (log) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: _MaintenanceLogTile(log: log), + ), + ), + ], + ], + ), + ), + ); + } +} + +class _DateField extends StatelessWidget { + const _DateField({ + required this.label, + required this.value, + required this.onPick, + }); + + final String label; + final DateTime? value; + final VoidCallback onPick; + + @override + Widget build(BuildContext context) { + return TextFormField( + readOnly: true, + onTap: onPick, + decoration: InputDecoration( + labelText: label, + hintText: 'Select date', + suffixIcon: const Icon(Icons.calendar_today_outlined, size: 20), + ), + controller: TextEditingController( + text: value != null ? DateFormatter.displayDate(value) : '', + ), + validator: (_) => value == null ? 'Required' : null, + ); + } +} + +class _ChecklistItemCard extends StatelessWidget { + const _ChecklistItemCard({ + required this.row, + required this.onStatusChanged, + }); + + final _ChecklistRowState row; + final ValueChanged onStatusChanged; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: theme.dividerColor), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + row.label, + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 10), + AppDropdown( + label: 'Status', + isDense: true, + value: row.status, + options: const [ + AppDropdownOption(value: 'OK', label: 'OK'), + AppDropdownOption(value: 'NOT_OK', label: 'Not OK'), + AppDropdownOption(value: 'NA', label: 'N/A'), + ], + onChanged: onStatusChanged, + ), + const SizedBox(height: 10), + AppTextField( + controller: row.remarksController, + label: 'Item remarks', + ), + ], + ), + ); + } +} + +class _MaintenanceLogTile extends StatelessWidget { + const _MaintenanceLogTile({required this.log}); + + final AssetMaintenanceLogModel log; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final dateFormat = DateFormat('dd MMM yyyy'); + final performed = log.performedDate != null + ? dateFormat.format(log.performedDate!) + : '—'; + final nextDue = + log.nextDueDate != null ? dateFormat.format(log.nextDueDate!) : null; + final subtitleParts = [ + 'Performed: $performed', + if (nextDue != null) 'Next due: $nextDue', + if (log.createdByName?.trim().isNotEmpty == true) log.createdByName!.trim(), + ]; + + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.35), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + subtitleParts.join(' · '), + style: theme.textTheme.bodySmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + if (log.remarks?.trim().isNotEmpty == true) ...[ + const SizedBox(height: 4), + Text( + log.remarks!, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + if (log.checklistJson.isNotEmpty) ...[ + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 4, + children: log.checklistJson.map((item) { + final key = item['key']?.toString() ?? 'Item'; + final status = item['status']?.toString() ?? '—'; + return Chip( + visualDensity: VisualDensity.compact, + label: Text( + '$key: $status', + style: theme.textTheme.labelSmall, + ), + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ); + }).toList(), + ), + ], + ], + ), + ); + } +} diff --git a/lib/modules/assets/presentation/widgets/asset_side_panels.dart b/lib/modules/assets/presentation/widgets/asset_side_panels.dart index 2bac004..ebc2a79 100644 --- a/lib/modules/assets/presentation/widgets/asset_side_panels.dart +++ b/lib/modules/assets/presentation/widgets/asset_side_panels.dart @@ -1242,10 +1242,9 @@ class _TransferAssetPanelState extends ConsumerState { final _formKey = GlobalKey(); final _reasonController = TextEditingController(); DateTime _transferDate = DateTime.now(); - int? _toPlantId; + int? _toLocationId; int? _toDepartmentId; int? _toUserId; - int? _toWarehouseId; bool _isSubmitting = false; @override @@ -1271,10 +1270,9 @@ class _TransferAssetPanelState extends ConsumerState { if (!_formKey.currentState!.validate()) return; final hasDestination = - _toPlantId != null || + _toLocationId != null || _toDepartmentId != null || - _toUserId != null || - _toWarehouseId != null; + _toUserId != null; if (!hasDestination) { showSidePanelSnackBar( context, @@ -1287,10 +1285,9 @@ class _TransferAssetPanelState extends ConsumerState { try { await ref.read(assetDetailProvider(widget.assetId).notifier).transferAsset({ 'transfer_date': DateFormatter.toApiDate(_transferDate), - if (_toPlantId != null) 'to_plant_id': _toPlantId, + if (_toLocationId != null) 'to_location_id': _toLocationId, 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) { @@ -1331,12 +1328,11 @@ class _TransferAssetPanelState extends ConsumerState { onPick: _pickDate, ), const SizedBox(height: 12), - MasterQuickAddDropdown( - masterId: 'plants', - label: 'To Plant', - value: _toPlantId, - searchHint: 'Search plant...', - options: lookups.plants + AppSearchableDropdown( + label: 'To Location', + value: _toLocationId, + searchHint: 'Search plant or warehouse...', + options: lookups.locations .map((option) { final id = int.tryParse(option.id); if (id == null) return null; @@ -1344,10 +1340,7 @@ class _TransferAssetPanelState extends ConsumerState { }) .whereType>() .toList(), - refreshLookups: () => - ref.invalidate(assetFormLookupsProvider), - parseCreatedId: int.tryParse, - onChanged: (v) => setState(() => _toPlantId = v), + onChanged: (v) => setState(() => _toLocationId = v), ), const SizedBox(height: 12), MasterQuickAddDropdown( @@ -1384,25 +1377,6 @@ class _TransferAssetPanelState extends ConsumerState { onChanged: (v) => setState(() => _toUserId = v), ), const SizedBox(height: 12), - MasterQuickAddDropdown( - masterId: 'warehouses', - 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(), - refreshLookups: () => - ref.invalidate(assetFormLookupsProvider), - parseCreatedId: int.tryParse, - onChanged: (v) => setState(() => _toWarehouseId = v), - ), - const SizedBox(height: 12), AppTextField( controller: _reasonController, label: 'Reason *', @@ -1476,9 +1450,8 @@ class _TransferHistoryEntry extends StatelessWidget { Expanded( child: _TransferLocationBlock( label: 'From', - plant: item.fromPlantName, + location: item.fromLocationName, department: item.fromDepartmentName, - warehouse: item.fromWarehouseName, user: item.fromUserName, ), ), @@ -1493,9 +1466,8 @@ class _TransferHistoryEntry extends StatelessWidget { Expanded( child: _TransferLocationBlock( label: 'To', - plant: item.toPlantName, + location: item.toLocationName, department: item.toDepartmentName, - warehouse: item.toWarehouseName, user: item.toUserName, ), ), @@ -1535,16 +1507,14 @@ class _TransferHistoryEntry extends StatelessWidget { class _TransferLocationBlock extends StatelessWidget { const _TransferLocationBlock({ required this.label, - this.plant, + this.location, this.department, - this.warehouse, this.user, }); final String label; - final String? plant; + final String? location; final String? department; - final String? warehouse; final String? user; @override @@ -1564,17 +1534,13 @@ class _TransferLocationBlock extends StatelessWidget { ), const SizedBox(height: 8), _TransferLocationRow( - icon: Icons.factory_outlined, - value: plant, + icon: Icons.place_outlined, + value: location, ), _TransferLocationRow( icon: Icons.apartment_outlined, value: department, ), - _TransferLocationRow( - icon: Icons.warehouse_outlined, - value: warehouse, - ), _TransferLocationRow( icon: Icons.person_outline, value: user, diff --git a/lib/modules/grn/presentation/providers/grn_lookups_provider.dart b/lib/modules/grn/presentation/providers/grn_lookups_provider.dart index bb5b047..e3d71f6 100644 --- a/lib/modules/grn/presentation/providers/grn_lookups_provider.dart +++ b/lib/modules/grn/presentation/providers/grn_lookups_provider.dart @@ -23,7 +23,12 @@ final grnLookupsProvider = FutureProvider.autoDispose((ref) async { final master = ref.watch(masterRemoteDataSourceProvider); final poRepo = ref.watch(purchaseOrderRepositoryProvider); - final warehouses = await _safeOptions(master.listWarehouses); + final warehouses = await _safeOptions(() async { + final locations = await master.listLocations(); + return locations + .where((location) => location.slug?.toLowerCase() == 'warehouse') + .toList(); + }); final users = await _safeUserOptions(ref); final receivablePos = []; diff --git a/lib/modules/grn/presentation/screens/grn_form_screen.dart b/lib/modules/grn/presentation/screens/grn_form_screen.dart index d38ec6d..fba0519 100644 --- a/lib/modules/grn/presentation/screens/grn_form_screen.dart +++ b/lib/modules/grn/presentation/screens/grn_form_screen.dart @@ -112,8 +112,8 @@ class _GrnFormScreenState extends ConsumerState { _lines ..clear() ..addAll(draftsFromPurchaseOrder(po)); - if (_warehouseId == null && po.warehouseId != null) { - _warehouseId = po.warehouseId; + if (_warehouseId == null && po.shippingId != null) { + _warehouseId = po.shippingId; } }); } @@ -453,12 +453,13 @@ class _GrnFormScreenState extends ConsumerState { value: existing?.poNumber ?? '—', ), MasterQuickAddDropdown( - masterId: 'warehouses', + masterId: 'locations', label: 'Warehouse *', value: _dropdownValue(_warehouseId, warehouseIds), hint: 'Select warehouse', searchHint: 'Search warehouse...', options: _intOptions(lookups.warehouses), + initialValues: const {'type': 'warehouse'}, refreshLookups: () => ref.invalidate(grnLookupsProvider), parseCreatedId: int.tryParse, diff --git a/lib/modules/master_data/domain/entities/master_definition.dart b/lib/modules/master_data/domain/entities/master_definition.dart index 40ddc07..3a8730b 100644 --- a/lib/modules/master_data/domain/entities/master_definition.dart +++ b/lib/modules/master_data/domain/entities/master_definition.dart @@ -278,6 +278,12 @@ const masterDefinitions = [ type: MasterFieldType.number, required: true, ), + MasterFieldDef( + key: 'tags', + label: 'Tags', + multiline: true, + showInList: true, + ), MasterFieldDef(key: 'description', label: 'Description', multiline: true), MasterFieldDef(key: 'specification', label: 'Specification', multiline: true), _activeField, @@ -341,47 +347,54 @@ const masterDefinitions = [ ], ), MasterDefinition( - id: 'plants', - title: 'Plants', - subtitle: 'Manufacturing plants and units', + id: 'locations', + title: 'Locations', + subtitle: 'Plants and warehouses', category: 'Organization', - routeKey: 'plants', - apiPath: '/masters/plants', - module: 'plants', - icon: Icons.factory_outlined, + routeKey: 'locations', + apiPath: '/masters/locations', + module: 'locations', + icon: Icons.place_outlined, fields: [ - MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true), - MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true), - MasterFieldDef(key: 'gstin', label: 'GSTIN', showInList: true), - MasterFieldDef(key: 'city', label: 'City', showInList: true), - MasterFieldDef(key: 'state', label: 'State'), - MasterFieldDef(key: 'address', label: 'Address', multiline: true), - MasterFieldDef(key: 'pincode', label: 'Pincode'), - MasterFieldDef(key: 'phone', label: 'Phone'), - _activeField, - ], - ), - MasterDefinition( - id: 'warehouses', - title: 'Warehouse', - subtitle: 'Storage locations by plant', - category: 'Organization', - routeKey: 'warehouses', - apiPath: '/masters/warehouses', - module: 'warehouses', - icon: Icons.warehouse_outlined, - fields: [ - MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true), - MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true), MasterFieldDef( - key: 'plant_id', - label: 'Plant', + key: 'type', + label: 'Type', type: MasterFieldType.dropdown, required: true, showInList: true, - optionsMasterKey: 'plants', + staticOptions: const ['plant', 'warehouse'], + ), + MasterFieldDef(key: 'code', label: 'Code', required: true, showInList: true), + MasterFieldDef(key: 'name', label: 'Name', required: true, showInList: true), + MasterFieldDef( + key: 'parent_id', + label: 'Parent Plant', + type: MasterFieldType.dropdown, + showInList: true, + optionsMasterKey: 'locations', + optionsQueryParams: const {'type': 'plant'}, + visibleWhenFieldKey: 'type', + visibleWhenValue: 'warehouse', + required: true, + ), + MasterFieldDef(key: 'gstin', label: 'GSTIN', showInList: true), + MasterFieldDef(key: 'city', label: 'City', showInList: true), + MasterFieldDef( + key: 'state', + label: 'State', + type: MasterFieldType.dropdown, + optionsMasterKey: 'location_states', + showInList: true, + ), + MasterFieldDef(key: 'address', label: 'Address', multiline: true), + MasterFieldDef(key: 'pincode', label: 'Pincode'), + MasterFieldDef(key: 'phone', label: 'Phone'), + MasterFieldDef( + key: 'location', + label: 'Location Detail', + visibleWhenFieldKey: 'type', + visibleWhenValue: 'warehouse', ), - MasterFieldDef(key: 'location', label: 'Location'), _activeField, ], ), @@ -521,6 +534,18 @@ String masterCellValue(Map row, MasterFieldDef field) { final value = row[field.key]; if (value == null || value == '') return '—'; + if (field.key == 'tags') { + if (value is List) { + final tags = value + .map((e) => e.toString().trim()) + .where((e) => e.isNotEmpty) + .toList(); + return tags.isEmpty ? '—' : tags.join(', '); + } + final text = value.toString().trim(); + return text.isEmpty ? '—' : text; + } + if (field.type == MasterFieldType.boolean) { return value == true ? 'Yes' : 'No'; } diff --git a/lib/modules/master_data/presentation/providers/master_provider.dart b/lib/modules/master_data/presentation/providers/master_provider.dart index c553b94..96e81b8 100644 --- a/lib/modules/master_data/presentation/providers/master_provider.dart +++ b/lib/modules/master_data/presentation/providers/master_provider.dart @@ -4,6 +4,7 @@ import '../../../../core/constants/app_constants.dart'; import '../../../../core/utils/table_search.dart'; import '../../../../shared/models/export_file_result.dart'; import '../../../assets/data/repositories/asset_repository_impl.dart'; +import '../../../masters/data/datasources/master_remote_data_source.dart'; import '../../data/repositories/master_repository_impl.dart'; import '../../domain/entities/master_definition.dart'; @@ -259,6 +260,13 @@ class MasterFormNotifier extends FamilyAsyncNotifier.from(result.data ?? const {}); + final tags = values['tags']; + if (tags is List) { + values['tags'] = tags + .map((e) => e.toString().trim()) + .where((e) => e.isNotEmpty) + .join(', '); + } } else { for (final field in _definition.formFields) { if (field.type == MasterFieldType.boolean) { @@ -342,6 +350,23 @@ class MasterFormNotifier extends FamilyAsyncNotifier { + 'id': option.id, + 'name': option.name, + }, + ) + .toList(); + } catch (_) {} + continue; + } + final def = masterDefinitionById(key); if (def == null) continue; final queryParameters = masterFieldOptionsQuery( @@ -481,10 +506,17 @@ class MasterFormNotifier extends FamilyAsyncNotifier num.tryParse(value.toString()) ?? value, MasterFieldType.dropdown => field.staticOptions != null || - field.optionsMasterKey == 'asset_depreciation_methods' + field.optionsMasterKey == 'asset_depreciation_methods' || + field.optionsMasterKey == 'location_states' ? value.toString() : int.tryParse(value.toString()) ?? value, MasterFieldType.boolean => value == true, @@ -494,6 +526,26 @@ class MasterFormNotifier extends FamilyAsyncNotifier _parseTags(dynamic value) { + if (value is List) { + return value + .map((e) => e.toString().trim()) + .where((e) => e.isNotEmpty) + .take(50) + .map((e) => e.length > 50 ? e.substring(0, 50) : e) + .toSet() + .toList(); + } + final parts = value + .toString() + .split(RegExp(r'[,;\n]+')) + .map((e) => e.trim()) + .where((e) => e.isNotEmpty) + .take(50) + .map((e) => e.length > 50 ? e.substring(0, 50) : e); + return parts.toSet().toList(); + } + /// Returns the created/updated record id on success, otherwise null. Future submit() async { final current = state.valueOrNull ?? const MasterFormState(); diff --git a/lib/modules/master_data/presentation/widgets/master_form_panel.dart b/lib/modules/master_data/presentation/widgets/master_form_panel.dart index beaffa4..5a1ce35 100644 --- a/lib/modules/master_data/presentation/widgets/master_form_panel.dart +++ b/lib/modules/master_data/presentation/widgets/master_form_panel.dart @@ -99,6 +99,9 @@ class _MasterFormPanelState extends ConsumerState { labelText: _fieldLabel(field), floatingLabelBehavior: FloatingLabelBehavior.always, alignLabelWithHint: true, + hintText: field.key == 'tags' + ? 'Comma-separated, e.g. critical, imported, rm' + : null, ); } diff --git a/lib/modules/master_data/presentation/widgets/master_quick_add.dart b/lib/modules/master_data/presentation/widgets/master_quick_add.dart index 1999bc8..461b7a7 100644 --- a/lib/modules/master_data/presentation/widgets/master_quick_add.dart +++ b/lib/modules/master_data/presentation/widgets/master_quick_add.dart @@ -224,6 +224,7 @@ String masterQuickAddNoun(String masterId) { 'item_subcategories' => 'subcategory', 'items' => 'item', 'hsn_codes' => 'HSN code', + 'locations' => 'location', 'plants' => 'plant', 'warehouses' => 'warehouse', 'designations' => 'designation', 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 fa174db..bb83541 100644 --- a/lib/modules/masters/data/datasources/master_remote_data_source.dart +++ b/lib/modules/masters/data/datasources/master_remote_data_source.dart @@ -23,6 +23,77 @@ class MasterRemoteDataSource { Future> listPlants() => _listOptions(ApiEndpoints.plants); + /// Plants and warehouses from the unified locations master. + /// Labels include type, e.g. "Chennai Plant (Plant)". + Future> listLocations() async { + final result = await listLocationsWithState(); + return result.options; + } + + /// Indian states/UTs for location.state (same values as vendor source_of_supply). + Future> listLocationStates() async { + final response = await dio.get(ApiEndpoints.locationStates); + final data = response.data; + final list = data is Map ? data['data'] : null; + if (list is! List) return const []; + + return list + .map((item) { + if (item is String) { + final value = item.trim(); + if (value.isEmpty) return null; + return FilterOptionModel(id: value, name: value); + } + if (item is Map) { + final map = Map.from(item); + final value = + (map['value'] ?? map['id'] ?? map['code'] ?? '').toString().trim(); + final label = + (map['label'] ?? map['name'] ?? value).toString().trim(); + if (value.isEmpty) return null; + return FilterOptionModel( + id: value, + name: label.isEmpty ? value : label, + ); + } + return null; + }) + .whereType() + .toList(); + } + + /// Locations plus each row's `state` (for PO GST intra/inter preview). + Future<({List options, Map stateById})> + listLocationsWithState() async { + final rows = await _listAllMaps(ApiEndpoints.locations); + final options = []; + final stateById = {}; + + for (final item in rows) { + if (!isActiveOptionRow(item)) continue; + final id = item['id']?.toString() ?? ''; + final name = _optionLabel(item); + if (id.isEmpty || name.isEmpty) continue; + + final type = item['type']?.toString().trim(); + final typeLabel = (type == null || type.isEmpty) + ? null + : '${type[0].toUpperCase()}${type.substring(1)}'; + final state = item['state']?.toString().trim(); + stateById[id] = (state == null || state.isEmpty) ? null : state; + + options.add( + FilterOptionModel( + id: id, + name: typeLabel == null ? name : '$name ($typeLabel)', + slug: type, + ), + ); + } + + return (options: options, stateById: stateById); + } + Future> listDesignations() => _listOptions(ApiEndpoints.designations); diff --git a/lib/modules/purchase_orders/data/datasources/purchase_order_remote_data_source.dart b/lib/modules/purchase_orders/data/datasources/purchase_order_remote_data_source.dart index a0c8f82..d609489 100644 --- a/lib/modules/purchase_orders/data/datasources/purchase_order_remote_data_source.dart +++ b/lib/modules/purchase_orders/data/datasources/purchase_order_remote_data_source.dart @@ -232,7 +232,8 @@ class PurchaseOrderRemoteDataSource { if (query.search != null && query.search!.isNotEmpty) 'search': query.search, if (query.status != null) 'status': query.status, if (query.vendorId != null) 'vendor_id': query.vendorId, - if (query.plantId != null) 'plant_id': query.plantId, + if (query.billingId != null) 'billing_id': query.billingId, + if (query.shippingId != null) 'shipping_id': query.shippingId, if (query.dateFrom != null) 'date_from': query.dateFrom, if (query.dateTo != null) 'date_to': query.dateTo, }; diff --git a/lib/modules/purchase_orders/presentation/providers/purchase_order_lookups_provider.dart b/lib/modules/purchase_orders/presentation/providers/purchase_order_lookups_provider.dart index 9eb0e05..b371f78 100644 --- a/lib/modules/purchase_orders/presentation/providers/purchase_order_lookups_provider.dart +++ b/lib/modules/purchase_orders/presentation/providers/purchase_order_lookups_provider.dart @@ -9,8 +9,9 @@ import '../../../vendors/domain/repositories/vendor_repository.dart'; class PurchaseOrderLookups { const PurchaseOrderLookups({ this.vendors = const [], - this.plants = const [], - this.warehouses = const [], + this.locations = const [], + this.vendorSourceOfSupplyById = const {}, + this.locationStateById = const {}, this.paymentTerms = const [], this.deliveryTerms = const [], this.items = const [], @@ -25,8 +26,12 @@ class PurchaseOrderLookups { }); final List vendors; - final List plants; - final List warehouses; + /// Billing / shipping options (plants + warehouses from locations master). + final List locations; + /// Vendor id → `source_of_supply` (state code/name) for GST split preview. + final Map vendorSourceOfSupplyById; + /// Location id → `state` for GST split preview. + final Map locationStateById; final List paymentTerms; final List deliveryTerms; final List items; @@ -50,31 +55,31 @@ final purchaseOrderLookupsProvider = final master = ref.watch(masterRemoteDataSourceProvider); final vendorRepo = ref.watch(vendorRepositoryProvider); - final vendors = await _safeOptions(() => _fetchActiveVendors(vendorRepo)); + final vendorsWithSos = await _safeVendorsWithSos(vendorRepo); + final locationsWithState = await _safeLocationsWithState(master); final itemsWithDefaults = await _safeItemsWithDefaults(master.listItemsWithHsn); final gstWithPct = await _safeGstRatesWithPct(master.listGstRatesWithPct); final hsnWithGst = await _safeHsnWithGst(master.listHsnCodesWithGstRate); final results = await Future.wait([ - _safeOptions(master.listPlants), - _safeOptions(master.listWarehouses), _safeOptions(master.listPaymentTerms), _safeOptions(master.listDeliveryTerms), _safeOptions(master.listUom), ]); return PurchaseOrderLookups( - vendors: vendors, - plants: results[0], - warehouses: results[1], - paymentTerms: results[2], - deliveryTerms: results[3], + vendors: vendorsWithSos.options, + locations: locationsWithState.options, + vendorSourceOfSupplyById: vendorsWithSos.sourceOfSupplyById, + locationStateById: locationsWithState.stateById, + paymentTerms: results[0], + deliveryTerms: results[1], items: itemsWithDefaults.options, itemHsnById: itemsWithDefaults.hsnByItemId, itemUomById: itemsWithDefaults.uomByItemId, itemGstRateById: itemsWithDefaults.gstRateByItemId, - uom: results[4], + uom: results[2], gstRates: gstWithPct.options, gstRatePctById: gstWithPct.pctById, hsnCodes: hsnWithGst.options, @@ -92,6 +97,52 @@ Future> _safeOptions( } } +Future< + ({ + List options, + Map sourceOfSupplyById, + })> _safeVendorsWithSos(VendorRepository vendorRepo) async { + try { + final result = await vendorRepo.listVendorOptions(); + if (result.failure != null) { + throw result.failure!; + } + + final options = []; + final sourceOfSupplyById = {}; + for (final vendor in result.data ?? const []) { + if (!isActiveVendorOption( + isActive: vendor.isActive, + status: vendor.status, + )) { + continue; + } + options.add(FilterOptionModel(id: vendor.id, name: vendor.vendorName)); + final sos = vendor.sourceOfSupply?.trim(); + sourceOfSupplyById[vendor.id] = + (sos == null || sos.isEmpty) ? null : sos; + } + return (options: options, sourceOfSupplyById: sourceOfSupplyById); + } catch (_) { + return ( + options: [], + sourceOfSupplyById: {}, + ); + } +} + +Future< + ({ + List options, + Map stateById, + })> _safeLocationsWithState(MasterRemoteDataSource master) async { + try { + return await master.listLocationsWithState(); + } catch (_) { + return (options: [], stateById: {}); + } +} + Future< ({ List options, @@ -146,24 +197,3 @@ Future<({List options, Map gstRateByHsnId})> return (options: [], gstRateByHsnId: {}); } } - -Future> _fetchActiveVendors( - VendorRepository vendorRepo, -) async { - final result = await vendorRepo.listVendorOptions(); - if (result.failure != null) { - throw result.failure!; - } - - return (result.data ?? const []) - .where( - (vendor) => isActiveVendorOption( - isActive: vendor.isActive, - status: vendor.status, - ), - ) - .map( - (vendor) => FilterOptionModel(id: vendor.id, name: vendor.vendorName), - ) - .toList(); -} diff --git a/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart b/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart index b2dab74..63eaa96 100644 --- a/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart +++ b/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart @@ -421,7 +421,8 @@ class _DetailHeader extends StatelessWidget { final subtitleParts = [ vendorTypeLabel(order.vendorType), if (order.vendorName?.trim().isNotEmpty == true) order.vendorName!.trim(), - if (order.plantName?.trim().isNotEmpty == true) order.plantName!.trim(), + if (order.billingName?.trim().isNotEmpty == true) + order.billingName!.trim(), ]; final actions = Wrap( @@ -737,12 +738,12 @@ class _OrderDetailsCard extends StatelessWidget { value: vendorTypeLabel(order.vendorType), ), _DetailField( - label: 'Plant', - value: _displayOrDash(order.plantName), + label: 'Billing', + value: _displayOrDash(order.billingName), ), _DetailField( - label: 'Warehouse', - value: _displayOrDash(order.warehouseName), + label: 'Shipping', + value: _displayOrDash(order.shippingName), ), _DetailField(label: 'Payment Term', value: paymentTerm), _DetailField(label: 'Delivery Term', value: deliveryTerm), @@ -1150,10 +1151,47 @@ class _AmountSummaryCard extends StatelessWidget { final PurchaseOrderModel order; + List _gstSummaryRows() { + final igst = order.igst ?? 0; + final cgst = order.cgst ?? 0; + final sgst = order.sgst ?? 0; + final hasSplit = + order.cgst != null || order.sgst != null || order.igst != null; + if (!hasSplit) return const []; + + if (igst > 0) { + return [ + _SummaryRow( + label: 'IGST', + value: CurrencyFormatter.format(igst), + ), + const SizedBox(height: 12), + ]; + } + + return [ + _SummaryRow( + label: 'CGST', + value: CurrencyFormatter.format(cgst), + ), + const SizedBox(height: 12), + _SummaryRow( + label: 'SGST', + value: CurrencyFormatter.format(sgst), + ), + const SizedBox(height: 12), + ]; + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); final discount = order.discountAmount ?? 0; + final freight = order.freightCharges ?? 0; + final other = order.otherCharges ?? 0; + final subTotal = order.taxableAmount ?? 0; + final taxableAmount = (subTotal + freight + other - discount) + .clamp(0.0, double.infinity); final primaryTint = theme.colorScheme.primary.withValues(alpha: 0.1); return _SectionCard( @@ -1162,32 +1200,38 @@ class _AmountSummaryCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _SummaryRow( - label: 'Taxable Amount', - value: CurrencyFormatter.format(order.taxableAmount), - ), - const SizedBox(height: 12), - _SummaryRow( - label: 'Tax (GST)', - value: CurrencyFormatter.format(order.taxAmount), + label: 'Sub Total', + value: CurrencyFormatter.format(subTotal), ), const SizedBox(height: 12), _SummaryRow( label: 'Freight Charges', - value: CurrencyFormatter.format(order.freightCharges ?? 0), + value: CurrencyFormatter.format(freight), ), const SizedBox(height: 12), _SummaryRow( label: 'Other Charges', - value: CurrencyFormatter.format(order.otherCharges ?? 0), + value: CurrencyFormatter.format(other), ), const SizedBox(height: 12), _SummaryRow( - label: 'Discount', + label: 'Discount Amount', value: discount > 0 ? '-${CurrencyFormatter.format(discount)}' : CurrencyFormatter.format(0), valueColor: discount > 0 ? AppColors.error : null, ), + const SizedBox(height: 12), + _SummaryRow( + label: 'Taxable Amount', + value: CurrencyFormatter.format(taxableAmount), + ), + const SizedBox(height: 12), + ..._gstSummaryRows(), + _SummaryRow( + label: 'Tax Total', + value: CurrencyFormatter.format(order.taxAmount), + ), const SizedBox(height: 16), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), diff --git a/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart b/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart index 6a5fc3a..6b94807 100644 --- a/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart +++ b/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart @@ -51,8 +51,8 @@ class _PurchaseOrderFormScreenState extends ConsumerState _lines = []; @@ -98,8 +98,8 @@ class _PurchaseOrderFormScreenState extends ConsumerState _buildPayload() { return { 'po_date': DateFormatter.toApiDate(_poDate ?? DateTime.now()), 'vendor_id': _vendorId, - 'plant_id': _plantId, - if (_warehouseId != null) 'warehouse_id': _warehouseId, + 'billing_id': _billingId, + 'shipping_id': _shippingId, if (_paymentTermId != null) 'payment_term_id': _paymentTermId, if (_deliveryTermId != null) 'delivery_term_id': _deliveryTermId, if (_expectedDeliveryDate != null) @@ -238,7 +256,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState _parseId(e.id)).whereType(); - final plantIds = - lookups.plants.map((e) => _parseId(e.id)).whereType(); + final locationIds = + lookups.locations.map((e) => _parseId(e.id)).whereType(); final totals = _computeTotals(lookups.gstRatePctById); final theme = Theme.of(context); @@ -373,105 +391,94 @@ class _PurchaseOrderFormScreenState extends ConsumerState _pickDate( - current: _poDate, - onPicked: (d) => setState(() => _poDate = d), + child: QuickAddInlineHost( + child: ResponsiveFormGrid( + smallColumns: 1, + mediumColumns: 2, + largeColumns: 4, + mediumBreakpoint: 640, + largeBreakpoint: 1100, + children: [ + _DateField( + label: 'PO Date *', + value: _poDate, + onTap: () => _pickDate( + current: _poDate, + onPicked: (d) => setState(() => _poDate = d), + ), + ), + AppSearchableDropdown( + label: 'Vendor *', + value: _dropdownValue(_vendorId, vendorIds), + hint: 'Select vendor', + searchHint: 'Search vendor...', + options: _intOptions(lookups.vendors), + onChanged: (v) => setState(() => _vendorId = v), + validator: (v) => + v == null ? 'Vendor is required' : null, + ), + AppSearchableDropdown( + label: 'Billing *', + value: _dropdownValue(_billingId, locationIds), + hint: 'Select billing location', + searchHint: 'Search plant or warehouse...', + options: _intOptions(lookups.locations), + onChanged: (v) => + setState(() => _billingId = v), + validator: (v) => + v == null ? 'Billing is required' : null, + ), + AppSearchableDropdown( + label: 'Shipping *', + value: _dropdownValue(_shippingId, locationIds), + hint: 'Select shipping location', + searchHint: 'Search plant or warehouse...', + options: _intOptions(lookups.locations), + onChanged: (v) => + setState(() => _shippingId = v), + validator: (v) => + v == null ? 'Shipping is required' : null, + ), + MasterQuickAddDropdown( + masterId: 'payment_terms', + label: 'Payment Term', + value: _paymentTermId, + hint: 'Select payment term', + searchHint: 'Search payment term...', + options: + _nullableIntOptions(lookups.paymentTerms), + refreshLookups: () => + ref.invalidate(purchaseOrderLookupsProvider), + parseCreatedId: int.tryParse, + onChanged: (v) => + setState(() => _paymentTermId = v), + ), + MasterQuickAddDropdown( + masterId: 'delivery_terms', + label: 'Delivery Term', + value: _deliveryTermId, + hint: 'Select delivery term', + searchHint: 'Search delivery term...', + options: + _nullableIntOptions(lookups.deliveryTerms), + refreshLookups: () => + ref.invalidate(purchaseOrderLookupsProvider), + parseCreatedId: int.tryParse, + onChanged: (v) => + setState(() => _deliveryTermId = v), + ), + _DateField( + label: 'Expected Delivery', + value: _expectedDeliveryDate, + onTap: () => _pickDate( + current: _expectedDeliveryDate, + onPicked: (d) => setState( + () => _expectedDeliveryDate = d, ), ), - AppSearchableDropdown( - label: 'Vendor *', - value: _dropdownValue(_vendorId, vendorIds), - hint: 'Select vendor', - searchHint: 'Search vendor...', - options: _intOptions(lookups.vendors), - onChanged: (v) => setState(() => _vendorId = v), - validator: (v) => - v == null ? 'Vendor is required' : null, - ), - MasterQuickAddDropdown( - masterId: 'plants', - label: 'Plant *', - value: _dropdownValue(_plantId, plantIds), - hint: 'Select plant', - searchHint: 'Search plant...', - options: _intOptions(lookups.plants), - refreshLookups: () => - ref.invalidate(purchaseOrderLookupsProvider), - parseCreatedId: int.tryParse, - onChanged: (v) => setState(() => _plantId = v), - validator: (v) => - v == null ? 'Plant is required' : null, - ), - MasterQuickAddDropdown( - masterId: 'warehouses', - label: 'Warehouse', - value: _warehouseId, - hint: 'Select warehouse', - searchHint: 'Search warehouse...', - options: _nullableIntOptions(lookups.warehouses), - refreshLookups: () => - ref.invalidate(purchaseOrderLookupsProvider), - parseCreatedId: int.tryParse, - onChanged: (v) => - setState(() => _warehouseId = v), - ), - ], - ), - FormRowFour( - children: [ - MasterQuickAddDropdown( - masterId: 'payment_terms', - label: 'Payment Term', - value: _paymentTermId, - hint: 'Select payment term', - searchHint: 'Search payment term...', - options: - _nullableIntOptions(lookups.paymentTerms), - refreshLookups: () => - ref.invalidate(purchaseOrderLookupsProvider), - parseCreatedId: int.tryParse, - onChanged: (v) => - setState(() => _paymentTermId = v), - ), - MasterQuickAddDropdown( - masterId: 'delivery_terms', - label: 'Delivery Term', - value: _deliveryTermId, - hint: 'Select delivery term', - searchHint: 'Search delivery term...', - options: - _nullableIntOptions(lookups.deliveryTerms), - refreshLookups: () => - ref.invalidate(purchaseOrderLookupsProvider), - parseCreatedId: int.tryParse, - onChanged: (v) => - setState(() => _deliveryTermId = v), - ), - ], - ), - FormRow( - columnCount: 4, - children: [ - _DateField( - label: 'Expected Delivery', - value: _expectedDeliveryDate, - onTap: () => _pickDate( - current: _expectedDeliveryDate, - onPicked: (d) => setState( - () => _expectedDeliveryDate = d, - ), - ), - ), - ], - ), - ], + ), + ], + ), ), ), const SizedBox(height: 16), @@ -518,6 +525,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState _gstSplitRows() { + final tax = totals.taxAmount; + if (isInterState) { + return [ + _SummaryReadOnlyRow( + label: 'IGST', + value: CurrencyFormatter.format(tax), + ), + const SizedBox(height: 10), + ]; + } + + final cgst = double.parse((tax / 2).toStringAsFixed(4)); + final sgst = double.parse((tax - cgst).toStringAsFixed(4)); + return [ + _SummaryReadOnlyRow( + label: 'CGST', + value: CurrencyFormatter.format(cgst), + ), + const SizedBox(height: 10), + _SummaryReadOnlyRow( + label: 'SGST', + value: CurrencyFormatter.format(sgst), + ), + const SizedBox(height: 10), + ]; + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -800,13 +838,8 @@ class _AmountSummaryCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _SummaryReadOnlyRow( - label: 'Taxable Amount', - value: CurrencyFormatter.format(totals.taxableAmount), - ), - const SizedBox(height: 10), - _SummaryReadOnlyRow( - label: 'Tax (GST)', - value: CurrencyFormatter.format(totals.taxAmount), + label: 'Sub Total', + value: CurrencyFormatter.format(totals.subTotal), ), const SizedBox(height: 4), _SummaryInputRow( @@ -826,6 +859,17 @@ class _AmountSummaryCard extends StatelessWidget { validator: _validateDiscount, autovalidateMode: AutovalidateMode.always, ), + const SizedBox(height: 6), + _SummaryReadOnlyRow( + label: 'Taxable Amount', + value: CurrencyFormatter.format(totals.taxableAmount), + ), + const SizedBox(height: 10), + ..._gstSplitRows(), + _SummaryReadOnlyRow( + label: 'Tax Total', + value: CurrencyFormatter.format(totals.taxAmount), + ), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), @@ -856,7 +900,9 @@ class _AmountSummaryCard extends StatelessWidget { Text( isEditing ? 'Editing charges or discount recalculates the grand total immediately — matches what will print on the PDF.' - : 'Taxable amount and tax are calculated automatically from line items. Grand total updates as you edit freight, other charges or discount.', + : isInterState + ? 'IGST is previewed (vendor state differs from billing location); final values are computed on save.' + : 'CGST/SGST are previewed (vendor state matches billing location); final values are computed on save.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, height: 1.4, diff --git a/lib/modules/purchase_orders/presentation/widgets/purchase_order_line_items_editor.dart b/lib/modules/purchase_orders/presentation/widgets/purchase_order_line_items_editor.dart index b6ec31d..f5a49b0 100644 --- a/lib/modules/purchase_orders/presentation/widgets/purchase_order_line_items_editor.dart +++ b/lib/modules/purchase_orders/presentation/widgets/purchase_order_line_items_editor.dart @@ -57,49 +57,60 @@ class PoLineCalculation { /// Order-level totals derived from line calculations + header charges. class PoOrderTotals { const PoOrderTotals({ + required this.subTotal, required this.taxableAmount, required this.taxAmount, required this.grandTotal, required this.maxDiscountAmount, }); + /// Sum of line amounts (before freight / other / discount). + final double subTotal; + + /// Sub Total + Freight + Other − Discount. final double taxableAmount; final double taxAmount; final double grandTotal; - /// Taxable + Tax + Freight + Other (discount cannot exceed this). + /// Sub Total + Tax + Freight + Other (discount cannot exceed this). final double maxDiscountAmount; static const zero = PoOrderTotals( + subTotal: 0, taxableAmount: 0, taxAmount: 0, grandTotal: 0, maxDiscountAmount: 0, ); - /// 3.5 Taxable = sum of Line Amounts - /// 3.6 Tax = sum of GST Amounts - /// 3.7 Grand Total = Taxable + Tax + Freight + Other − Discount + /// Sub Total = sum of line amounts + /// Taxable = Sub Total + Freight + Other − Discount + /// Tax = sum of line GST amounts + /// Grand Total = Taxable + Tax factory PoOrderTotals.compute({ required Iterable lines, required double freight, required double otherCharges, required double discountAmount, }) { - var taxable = 0.0; + var subTotal = 0.0; var tax = 0.0; for (final line in lines) { - taxable += line.lineAmount; + subTotal += line.lineAmount; tax += line.gstAmount; } - final maxDiscount = taxable + tax + freight + otherCharges; - final clampedDiscount = - discountAmount < 0 ? 0.0 : discountAmount; - final grandTotalRaw = maxDiscount - clampedDiscount; + final freightSafe = freight < 0 ? 0.0 : freight; + final otherSafe = otherCharges < 0 ? 0.0 : otherCharges; + final clampedDiscount = discountAmount < 0 ? 0.0 : discountAmount; + final taxableRaw = subTotal + freightSafe + otherSafe - clampedDiscount; + final taxable = taxableRaw < 0 ? 0.0 : taxableRaw; + final maxDiscount = subTotal + tax + freightSafe + otherSafe; + final grandTotal = taxable + tax; return PoOrderTotals( + subTotal: subTotal, taxableAmount: taxable, taxAmount: tax, - grandTotal: grandTotalRaw < 0 ? 0 : grandTotalRaw, + grandTotal: grandTotal < 0 ? 0 : grandTotal, maxDiscountAmount: maxDiscount < 0 ? 0 : maxDiscount, ); } @@ -521,120 +532,92 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> { ].whereType>().toList(); const spacing = 8.0; - const minRowWidth = 1040.0; - Widget flex({required int flex, required Widget child}) { - return Expanded(flex: flex, child: child); - } - - final fields = [ - flex( - flex: 3, - child: MasterQuickAddDropdown( - key: ValueKey('$lineKey-item'), - masterId: 'items', - label: 'Item *', - value: line.itemId, - hint: 'Select item', - searchHint: 'Search item name or code...', - options: itemOptions, - refreshLookups: () async { - ref.invalidate(purchaseOrderLookupsProvider); - await ref.read(purchaseOrderLookupsProvider.future); - }, - parseCreatedId: int.tryParse, - onChanged: _onItemChanged, - validator: (v) => v == null ? 'Item is required' : null, - ), - ), - flex( - flex: 1, - child: AppTextField( - key: ValueKey('$lineKey-qty'), - controller: line.qtyController, - label: 'Qty *', - hint: '0', - keyboardType: const TextInputType.numberWithOptions(decimal: true), - validator: (v) { - if (v == null || v.trim().isEmpty) return 'Required'; - final qty = double.tryParse(v); - if (qty == null || qty <= 0) return 'Invalid'; - return null; - }, - ), - ), - flex( - flex: 2, - child: MasterQuickAddDropdown( - key: ValueKey('$lineKey-uom'), - masterId: 'uom', - label: 'UOM *', - value: line.uomId, - hint: 'Select UOM', - searchHint: 'Search UOM...', - options: uomOptions, - refreshLookups: () async { - ref.invalidate(purchaseOrderLookupsProvider); - await ref.read(purchaseOrderLookupsProvider.future); - }, - parseCreatedId: int.tryParse, - onChanged: (v) => _updateLine(() => line.uomId = v), - validator: (v) => v == null ? 'Required' : null, - ), - ), - flex( - flex: 1, - child: AppTextField( - key: ValueKey('$lineKey-rate'), - controller: line.rateController, - label: 'Rate *', - hint: '0.00', - keyboardType: const TextInputType.numberWithOptions(decimal: true), - validator: (v) { - if (v == null || v.trim().isEmpty) return 'Required'; - final rate = double.tryParse(v); - if (rate == null || rate < 0) return 'Invalid'; - return null; - }, - ), - ), - flex( - flex: 1, - child: AppTextField( - key: ValueKey('$lineKey-discount'), - controller: line.discountController, - label: 'Disc %', - hint: '0', - keyboardType: const TextInputType.numberWithOptions(decimal: true), - ), - ), - flex( - flex: 2, - child: MasterQuickAddDropdown( - key: ValueKey('$lineKey-gst'), - masterId: 'gst_rates', - label: 'GST %', - value: line.gstRateId, - hint: 'Select', - searchHint: 'Search GST %...', - options: gstOptions, - refreshLookups: () async { - ref.invalidate(purchaseOrderLookupsProvider); - await ref.read(purchaseOrderLookupsProvider.future); - }, - parseCreatedId: int.tryParse, - onChanged: (v) => _updateLine(() => line.gstRateId = v), - ), - ), - flex( - flex: 2, - child: _AmountWithRemove( - amount: CurrencyFormatter.format(calc.lineAmount), - backgroundColor: amountBg, - onRemove: widget.onRemove, - ), - ), - ]; + final itemField = MasterQuickAddDropdown( + key: ValueKey('$lineKey-item'), + masterId: 'items', + label: 'Item *', + value: line.itemId, + hint: 'Select item', + searchHint: 'Search item name or code...', + options: itemOptions, + refreshLookups: () async { + ref.invalidate(purchaseOrderLookupsProvider); + await ref.read(purchaseOrderLookupsProvider.future); + }, + parseCreatedId: int.tryParse, + onChanged: _onItemChanged, + validator: (v) => v == null ? 'Item is required' : null, + ); + final qtyField = AppTextField( + key: ValueKey('$lineKey-qty'), + controller: line.qtyController, + label: 'Qty *', + hint: '0', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) { + if (v == null || v.trim().isEmpty) return 'Required'; + final qty = double.tryParse(v); + if (qty == null || qty <= 0) return 'Invalid'; + return null; + }, + ); + final uomField = MasterQuickAddDropdown( + key: ValueKey('$lineKey-uom'), + masterId: 'uom', + label: 'UOM *', + value: line.uomId, + hint: 'Select UOM', + searchHint: 'Search UOM...', + options: uomOptions, + refreshLookups: () async { + ref.invalidate(purchaseOrderLookupsProvider); + await ref.read(purchaseOrderLookupsProvider.future); + }, + parseCreatedId: int.tryParse, + onChanged: (v) => _updateLine(() => line.uomId = v), + validator: (v) => v == null ? 'Required' : null, + ); + final rateField = AppTextField( + key: ValueKey('$lineKey-rate'), + controller: line.rateController, + label: 'Rate *', + hint: '0.00', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (v) { + if (v == null || v.trim().isEmpty) return 'Required'; + final rate = double.tryParse(v); + if (rate == null || rate < 0) return 'Invalid'; + return null; + }, + ); + final discField = AppTextField( + key: ValueKey('$lineKey-discount'), + controller: line.discountController, + label: 'Disc %', + hint: '0', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + ); + final gstField = MasterQuickAddDropdown( + key: ValueKey('$lineKey-gst'), + masterId: 'gst_rates', + label: 'GST %', + value: line.gstRateId, + hint: 'Select', + searchHint: 'Search GST %...', + options: gstOptions, + refreshLookups: () async { + ref.invalidate(purchaseOrderLookupsProvider); + await ref.read(purchaseOrderLookupsProvider.future); + }, + parseCreatedId: int.tryParse, + onChanged: (v) => _updateLine(() => line.gstRateId = v), + ); + final amountField = _AmountWithRemove( + amount: CurrencyFormatter.format(calc.lineAmount), + backgroundColor: amountBg, + onRemove: widget.onRemove, + ); return Container( padding: const EdgeInsets.all(16), @@ -645,29 +628,48 @@ class _LineItemCardState extends ConsumerState<_LineItemCard> { child: QuickAddInlineHost( child: LayoutBuilder( builder: (context, constraints) { - final rowWidth = constraints.maxWidth < minRowWidth - ? minRowWidth - : constraints.maxWidth; - final row = SizedBox( - width: rowWidth, - child: Row( + final width = constraints.maxWidth; + + // Wide: single flex row + if (width >= 1100) { + return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - for (var i = 0; i < fields.length; i++) ...[ - if (i > 0) const SizedBox(width: spacing), - fields[i], - ], + Expanded(flex: 3, child: itemField), + const SizedBox(width: spacing), + Expanded(flex: 1, child: qtyField), + const SizedBox(width: spacing), + Expanded(flex: 2, child: uomField), + const SizedBox(width: spacing), + Expanded(flex: 1, child: rateField), + const SizedBox(width: spacing), + Expanded(flex: 1, child: discField), + const SizedBox(width: spacing), + Expanded(flex: 2, child: gstField), + const SizedBox(width: spacing), + Expanded(flex: 2, child: amountField), ], - ), - ); - - if (constraints.maxWidth < minRowWidth) { - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: row, ); } - return row; + + // Medium / narrow: wrapping grid (2–3 columns) + return ResponsiveFormGrid( + spacing: spacing, + smallColumns: 1, + mediumColumns: 2, + largeColumns: 3, + mediumBreakpoint: 520, + largeBreakpoint: 800, + children: [ + itemField, + qtyField, + uomField, + rateField, + discField, + gstField, + amountField, + ], + ); }, ), ), diff --git a/lib/modules/rbac/presentation/providers/add_user_form_provider.dart b/lib/modules/rbac/presentation/providers/add_user_form_provider.dart index 3ac9ab1..4bfd8ce 100644 --- a/lib/modules/rbac/presentation/providers/add_user_form_provider.dart +++ b/lib/modules/rbac/presentation/providers/add_user_form_provider.dart @@ -66,7 +66,11 @@ class AddUserFormNotifier extends FamilyAsyncNotifier if (rolesResult.failure != null) throw rolesResult.failure!; final departments = await masterRemote.listDepartments(); - final plants = await masterRemote.listPlants(); + final plants = await masterRemote.listLocations().then( + (locations) => locations + .where((location) => location.slug?.toLowerCase() == 'plant') + .toList(), + ); final designations = await masterRemote.listDesignations(); final usersResult = await userRepository.listUserOptions(); diff --git a/lib/modules/rbac/presentation/widgets/add_user_panel.dart b/lib/modules/rbac/presentation/widgets/add_user_panel.dart index 3adebf1..e2c6eb2 100644 --- a/lib/modules/rbac/presentation/widgets/add_user_panel.dart +++ b/lib/modules/rbac/presentation/widgets/add_user_panel.dart @@ -187,6 +187,7 @@ class _AddUserPanelState extends ConsumerState { String? hint, bool required = false, String? masterId, + Map? initialValues, }) { final fieldLabel = required ? '$label *' : label; final fieldHint = hint ?? 'Select ${label.toLowerCase()}'; @@ -215,6 +216,7 @@ class _AddUserPanelState extends ConsumerState { searchHint: 'Search $label...', enabled: fieldEnabled, options: _toOptions(options), + initialValues: initialValues, refreshLookups: () => ref.invalidate(addUserFormProvider(widget.userId)), parseCreatedId: (id) => id, @@ -357,7 +359,8 @@ class _AddUserPanelState extends ConsumerState { label: 'Plant / Unit', value: _selectedPlantId, options: formState.plants, - masterId: 'plants', + masterId: 'locations', + initialValues: const {'type': 'plant'}, onChanged: (v) => setState(() => _selectedPlantId = v), ), right: _buildDropdown( diff --git a/lib/modules/reports/data/datasources/reports_remote_data_source.dart b/lib/modules/reports/data/datasources/reports_remote_data_source.dart index 189e9fb..1f05c5d 100644 --- a/lib/modules/reports/data/datasources/reports_remote_data_source.dart +++ b/lib/modules/reports/data/datasources/reports_remote_data_source.dart @@ -106,8 +106,8 @@ class ReportsRemoteDataSource { if (query.itemSubcategoryId != null && query.itemSubcategoryId!.isNotEmpty) 'item_subcategory_id': query.itemSubcategoryId, - if (query.plantId != null && query.plantId!.isNotEmpty) - 'plant_id': query.plantId, + if (query.locationId != null && query.locationId!.isNotEmpty) + 'location_id': query.locationId, if (query.departmentId != null && query.departmentId!.isNotEmpty) 'department_id': query.departmentId, if (query.isActive != null) 'is_active': query.isActive, diff --git a/lib/modules/reports/domain/entities/depreciation_report.dart b/lib/modules/reports/domain/entities/depreciation_report.dart index 3e140b8..caf9aa3 100644 --- a/lib/modules/reports/domain/entities/depreciation_report.dart +++ b/lib/modules/reports/domain/entities/depreciation_report.dart @@ -22,12 +22,17 @@ class ReportFilterOption { .toString() .trim(); final code = map['code']?.toString().trim(); - final display = (code != null && + final type = map['type']?.toString().trim(); + var display = (code != null && code.isNotEmpty && label.isNotEmpty && code != label) ? '$label ($code)' : (label.isEmpty ? value : label); + if (type != null && type.isNotEmpty) { + final typeLabel = '${type[0].toUpperCase()}${type.substring(1)}'; + display = '$display ($typeLabel)'; + } final parentId = (map['item_category_id'] ?? map['parent_id']) ?.toString() .trim(); @@ -45,7 +50,7 @@ class ReportFilterOption { class DepreciationReportFilters { const DepreciationReportFilters({ - this.plants = const [], + this.locations = const [], this.categories = const [], this.subcategories = const [], this.departments = const [], @@ -53,7 +58,7 @@ class DepreciationReportFilters { this.depreciationMethods = const [], }); - final List plants; + final List locations; final List categories; final List subcategories; final List departments; @@ -79,7 +84,7 @@ class DepreciationReportFilters { } return DepreciationReportFilters( - plants: parse(['plants', 'plant_options', 'plant']), + locations: parse(['locations', 'location_options', 'location']), categories: parse([ 'item_categories', 'categories', @@ -155,7 +160,7 @@ class DepreciationReportRow { this.assetName, this.categoryName, this.subcategoryName, - this.plantName, + this.locationName, this.departmentName, this.status, this.purchaseDate, @@ -175,7 +180,7 @@ class DepreciationReportRow { final String? assetName; final String? categoryName; final String? subcategoryName; - final String? plantName; + final String? locationName; final String? departmentName; final String? status; final DateTime? purchaseDate; @@ -262,8 +267,9 @@ class DepreciationReportRow { ]) ?? readNestedName('item_subcategory') ?? readNestedName('subcategory'), - plantName: - readString(['plant_name', 'plantName']) ?? readNestedName('plant'), + locationName: + readString(['location_name', 'locationName']) ?? + readNestedName('location'), departmentName: readString(['department_name', 'departmentName']) ?? readNestedName('department'), status: readString(['status']), @@ -315,7 +321,7 @@ class DepreciationReportQuery { this.page = 1, this.limit = 20, this.search, - this.plantId, + this.locationId, this.itemCategoryId, this.itemSubcategoryId, this.departmentId, @@ -330,7 +336,7 @@ class DepreciationReportQuery { final int page; final int limit; final String? search; - final String? plantId; + final String? locationId; final String? itemCategoryId; final String? itemSubcategoryId; final String? departmentId; @@ -343,7 +349,7 @@ class DepreciationReportQuery { bool get hasActiveFilter => (search?.isNotEmpty ?? false) || - (plantId?.isNotEmpty ?? false) || + (locationId?.isNotEmpty ?? false) || (itemCategoryId?.isNotEmpty ?? false) || (itemSubcategoryId?.isNotEmpty ?? false) || (departmentId?.isNotEmpty ?? false) || @@ -358,7 +364,7 @@ class DepreciationReportQuery { int? page, int? limit, String? search, - String? plantId, + String? locationId, String? itemCategoryId, String? itemSubcategoryId, String? departmentId, @@ -369,7 +375,7 @@ class DepreciationReportQuery { DateTime? purchaseDateFrom, DateTime? purchaseDateTo, bool clearSearch = false, - bool clearPlantId = false, + bool clearLocationId = false, bool clearItemCategoryId = false, bool clearItemSubcategoryId = false, bool clearDepartmentId = false, @@ -384,7 +390,7 @@ class DepreciationReportQuery { page: page ?? this.page, limit: limit ?? this.limit, search: clearSearch ? null : search ?? this.search, - plantId: clearPlantId ? null : plantId ?? this.plantId, + locationId: clearLocationId ? null : locationId ?? this.locationId, itemCategoryId: clearItemCategoryId ? null : itemCategoryId ?? this.itemCategoryId, diff --git a/lib/modules/reports/presentation/providers/depreciation_report_provider.dart b/lib/modules/reports/presentation/providers/depreciation_report_provider.dart index 4b36879..0060874 100644 --- a/lib/modules/reports/presentation/providers/depreciation_report_provider.dart +++ b/lib/modules/reports/presentation/providers/depreciation_report_provider.dart @@ -145,14 +145,14 @@ class DepreciationReportNotifier ); } - Future setPlantId(String? value) async { + Future setLocationId(String? value) async { final current = state.valueOrNull?.query ?? const DepreciationReportQuery(limit: AppConstants.defaultPageSize); await applyQuery( current.copyWith( page: 1, - plantId: value, - clearPlantId: value == null || value.isEmpty, + locationId: value, + clearLocationId: value == null || value.isEmpty, ), ); } diff --git a/lib/modules/reports/presentation/screens/depreciation_report_screen.dart b/lib/modules/reports/presentation/screens/depreciation_report_screen.dart index 55bb361..b1db0d7 100644 --- a/lib/modules/reports/presentation/screens/depreciation_report_screen.dart +++ b/lib/modules/reports/presentation/screens/depreciation_report_screen.dart @@ -174,7 +174,7 @@ class _DepreciationReportScreenState filters: state.filters, query: state.query, onSearch: notifier.setSearch, - onPlantChanged: notifier.setPlantId, + onLocationChanged: notifier.setLocationId, onCategoryChanged: notifier.setItemCategoryId, onSubcategoryChanged: notifier.setItemSubcategoryId, onDepartmentChanged: notifier.setDepartmentId, @@ -324,7 +324,7 @@ class _FiltersBar extends StatefulWidget { required this.filters, required this.query, required this.onSearch, - required this.onPlantChanged, + required this.onLocationChanged, required this.onCategoryChanged, required this.onSubcategoryChanged, required this.onDepartmentChanged, @@ -342,7 +342,7 @@ class _FiltersBar extends StatefulWidget { final DepreciationReportFilters filters; final DepreciationReportQuery query; final ValueChanged onSearch; - final ValueChanged onPlantChanged; + final ValueChanged onLocationChanged; final ValueChanged onCategoryChanged; final ValueChanged onSubcategoryChanged; final ValueChanged onDepartmentChanged; @@ -425,11 +425,11 @@ class _FiltersBarState extends State<_FiltersBar> { ); } - final plant = dropdown( - label: 'Plant', - value: query.plantId, - options: filters.plants, - onChanged: widget.onPlantChanged, + final location = dropdown( + label: 'Location', + value: query.locationId, + options: filters.locations, + onChanged: widget.onLocationChanged, ); final category = dropdown( label: 'Category', @@ -549,7 +549,7 @@ class _FiltersBarState extends State<_FiltersBar> { : null; return AppResponsiveFilterGrid( - fields: [searchField, plant, category, asOfField], + fields: [searchField, location, category, asOfField], footer: Align( alignment: Alignment.centerRight, child: actions, @@ -589,10 +589,10 @@ class _ReportTable extends StatelessWidget { cellBuilder: (_, row) => AppTableCell.text(row.categoryName), ), AppDataColumn( - label: 'Plant', + label: 'Location', flex: 2, - searchText: (row) => row.plantName ?? '', - cellBuilder: (_, row) => AppTableCell.text(row.plantName), + searchText: (row) => row.locationName ?? '', + cellBuilder: (_, row) => AppTableCell.text(row.locationName), ), AppDataColumn( label: 'Purchase Date', diff --git a/lib/shared/models/asset_model.dart b/lib/shared/models/asset_model.dart index 1d4aa14..7f650ee 100644 --- a/lib/shared/models/asset_model.dart +++ b/lib/shared/models/asset_model.dart @@ -45,10 +45,21 @@ Object? _readItemCategoryName(Map json, String key) { _readNestedName(json, 'asset_category'); } -Object? _readPlantName(Map json, String key) { - final flat = json['plant_name']; +Object? _readLocationName(Map json, String key) { + final flat = json['location_name']; if (flat is String && flat.isNotEmpty) return flat; - return _readNestedName(json, 'plant'); + final nested = json['location']; + if (nested is Map) { + final name = nested['name']?.toString().trim(); + if (name == null || name.isEmpty) return null; + final type = nested['type']?.toString().trim(); + if (type != null && type.isNotEmpty) { + final typeLabel = type[0].toUpperCase() + type.substring(1); + return '$name ($typeLabel)'; + } + return name; + } + return null; } Object? _readItemCategoryId(Map json, String key) { @@ -61,10 +72,10 @@ Object? _readItemCategoryId(Map json, String key) { return null; } -Object? _readPlantId(Map json, String key) { - final flat = json['plant_id']; +Object? _readLocationId(Map json, String key) { + final flat = json['location_id']; if (flat != null) return flat; - final nested = json['plant']; + final nested = json['location']; if (nested is Map) return nested['id']; return null; } @@ -94,12 +105,6 @@ Object? _readDepartmentName(Map json, String key) { 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; @@ -153,9 +158,14 @@ class AssetModel with _$AssetModel { int? assetSubcategoryId, @JsonKey(name: 'item_subcategory_name', readValue: _readItemSubcategoryName) String? assetSubcategoryName, - @JsonKey(name: 'plant_id', readValue: _readPlantId, fromJson: _intFromJsonNullable) - int? plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, + @JsonKey( + name: 'location_id', + readValue: _readLocationId, + fromJson: _intFromJsonNullable, + ) + int? locationId, + @JsonKey(name: 'location_name', readValue: _readLocationName) + String? locationName, @JsonKey(name: 'brand_model') String? brandModel, String? manufacturer, @JsonKey(name: 'serial_number') String? serialNumber, @@ -163,11 +173,21 @@ class AssetModel with _$AssetModel { @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: 'maintenance_incharge_user_id', fromJson: _intFromJsonNullable) + int? maintenanceInchargeUserId, + @JsonKey(name: 'maintenance_frequency_in_days', fromJson: _intFromJsonNullable) + int? maintenanceFrequencyInDays, + @JsonKey( + name: 'maintenance_checklist_json', + fromJson: _checklistFromJson, + toJson: _checklistToJson, + ) + List? maintenanceChecklistJson, + @JsonKey(name: 'commencement_date', fromJson: _dateFromJsonNullable) + DateTime? commencementDate, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) String? vendorName, @JsonKey(name: 'po_id', fromJson: _intFromJsonNullable) int? poId, @@ -192,11 +212,161 @@ class AssetModel with _$AssetModel { @JsonKey(name: 'is_active') @Default(true) bool isActive, @JsonKey(name: 'created_at', fromJson: _dateFromJsonNullable) DateTime? createdAt, @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) DateTime? updatedAt, + @JsonKey( + name: 'maintenance', + fromJson: AssetMaintenanceSummary.fromJsonNullable, + toJson: AssetMaintenanceSummary.toJsonNullable, + ) + AssetMaintenanceSummary? maintenance, }) = _AssetModel; factory AssetModel.fromJson(Map json) => _$AssetModelFromJson(json); } +class AssetMaintenanceChecklistItem { + const AssetMaintenanceChecklistItem({ + required this.key, + required this.label, + this.required = false, + }); + + final String key; + final String label; + final bool required; + + factory AssetMaintenanceChecklistItem.fromJson(Map json) { + return AssetMaintenanceChecklistItem( + key: json['key']?.toString() ?? '', + label: json['label']?.toString() ?? '', + required: json['required'] == true, + ); + } + + Map toJson() => { + 'key': key, + 'label': label, + 'required': required, + }; +} + +class AssetMaintenanceSummary { + const AssetMaintenanceSummary({ + this.isDue = false, + this.nextDueDate, + this.lastMaintenanceDate, + this.daysUntilDue, + this.checklist = const [], + }); + + final bool isDue; + final DateTime? nextDueDate; + final DateTime? lastMaintenanceDate; + final int? daysUntilDue; + final List checklist; + + static AssetMaintenanceSummary? fromJsonNullable(Object? value) { + if (value is! Map) return null; + return AssetMaintenanceSummary.fromJson(Map.from(value)); + } + + static Object? toJsonNullable(AssetMaintenanceSummary? value) { + if (value == null) return null; + return value.toJson(); + } + + Map toJson() => { + 'is_due': isDue, + if (nextDueDate != null) + 'next_due_date': nextDueDate!.toIso8601String(), + if (lastMaintenanceDate != null) + 'last_maintenance_date': lastMaintenanceDate!.toIso8601String(), + if (daysUntilDue != null) 'days_until_due': daysUntilDue, + 'checklist': checklist.map((item) => item.toJson()).toList(), + }; + + factory AssetMaintenanceSummary.fromJson(Map json) { + DateTime? parseDate(dynamic value) { + if (value == null) return null; + if (value is DateTime) return value; + return DateTime.tryParse(value.toString()); + } + + final rawChecklist = json['checklist'] ?? json['checklist_json']; + final checklist = []; + if (rawChecklist is List) { + for (final item in rawChecklist) { + if (item is Map) { + checklist.add( + AssetMaintenanceChecklistItem.fromJson( + Map.from(item), + ), + ); + } + } + } + + return AssetMaintenanceSummary( + isDue: json['is_due'] == true, + nextDueDate: parseDate(json['next_due_date']), + lastMaintenanceDate: parseDate(json['last_maintenance_date']), + daysUntilDue: _intFromJsonNullable(json['days_until_due']), + checklist: checklist, + ); + } +} + +class AssetMaintenanceLogModel { + const AssetMaintenanceLogModel({ + required this.id, + this.performedDate, + this.nextDueDate, + this.remarks, + this.checklistJson = const [], + this.createdAt, + this.createdByName, + }); + + final String id; + final DateTime? performedDate; + final DateTime? nextDueDate; + final String? remarks; + final List> checklistJson; + final DateTime? createdAt; + final String? createdByName; + + factory AssetMaintenanceLogModel.fromJson(Map json) { + DateTime? parseDate(dynamic value) { + if (value == null) return null; + if (value is DateTime) return value; + return DateTime.tryParse(value.toString()); + } + + final rawChecklist = json['checklist_json'] ?? json['checklist']; + final checklist = >[]; + if (rawChecklist is List) { + for (final item in rawChecklist) { + if (item is Map) { + checklist.add(Map.from(item)); + } + } + } + + return AssetMaintenanceLogModel( + id: _idFromJson(json['id']), + performedDate: parseDate(json['performed_date']), + nextDueDate: parseDate(json['next_due_date']), + remarks: json['remarks']?.toString(), + checklistJson: checklist, + createdAt: parseDate(json['created_at']), + createdByName: json['created_by_name']?.toString() ?? + (json['created_by'] is Map + ? (json['created_by']['name'] ?? json['created_by']['full_name']) + ?.toString() + : null), + ); + } +} + String assetConditionLabel(String? value) { if (value == null || value.trim().isEmpty) return '—'; return value.replaceAll('_', ' '); @@ -402,6 +572,35 @@ class InsurancePolicyModel with _$InsurancePolicyModel { _$InsurancePolicyModelFromJson(json); } +List? _checklistFromJson(Object? value) { + if (value is! List) return null; + return value + .whereType() + .map( + (item) => AssetMaintenanceChecklistItem.fromJson( + Map.from(item), + ), + ) + .where((item) => item.key.isNotEmpty) + .toList(); +} + +Object? _checklistToJson(List? value) { + if (value == null) return null; + return value.map((item) => item.toJson()).toList(); +} + +Object? _readAlertLocationName(Map json, String key) { + final flat = json['location_name']; + if (flat is String && flat.isNotEmpty) return flat; + final nested = json['location']; + if (nested is Map) { + final name = nested['name']; + if (name is String && name.isNotEmpty) return name; + } + return null; +} + @freezed class AssetAlertModel with _$AssetAlertModel { const factory AssetAlertModel({ @@ -416,7 +615,8 @@ class AssetAlertModel with _$AssetAlertModel { @JsonKey(name: 'due_date', fromJson: _dateFromJsonNullable) DateTime? dueDate, @JsonKey(name: 'days_remaining', fromJson: _intFromJsonNullable) int? daysRemaining, String? status, - @JsonKey(name: 'plant_name') String? plantName, + @JsonKey(name: 'location_name', readValue: _readAlertLocationName) + String? locationName, }) = _AssetAlertModel; factory AssetAlertModel.fromJson(Map json) => @@ -428,12 +628,10 @@ class AssetTransferHistoryModel { required this.id, this.transferDate, this.reason, - this.fromPlantName, - this.toPlantName, + this.fromLocationName, + this.toLocationName, this.fromDepartmentName, this.toDepartmentName, - this.fromWarehouseName, - this.toWarehouseName, this.fromUserName, this.toUserName, this.createdAt, @@ -443,12 +641,10 @@ class AssetTransferHistoryModel { final String id; final DateTime? transferDate; final String? reason; - final String? fromPlantName; - final String? toPlantName; + final String? fromLocationName; + final String? toLocationName; final String? fromDepartmentName; final String? toDepartmentName; - final String? fromWarehouseName; - final String? toWarehouseName; final String? fromUserName; final String? toUserName; final DateTime? createdAt; @@ -466,7 +662,15 @@ class AssetTransferHistoryModel { if (nested is Map) { for (final field in ['name', 'full_name']) { final value = nested[field]; - if (value is String && value.trim().isNotEmpty) return value.trim(); + if (value is String && value.trim().isNotEmpty) { + final name = value.trim(); + final type = nested['type']?.toString().trim(); + if (type != null && type.isNotEmpty) { + final typeLabel = type[0].toUpperCase() + type.substring(1); + return '$name ($typeLabel)'; + } + return name; + } } } return null; @@ -482,14 +686,13 @@ class 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'), + fromLocationName: + readName('from_location_name') ?? readNestedName('from_location'), + toLocationName: + readName('to_location_name') ?? readNestedName('to_location'), 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']), @@ -557,8 +760,10 @@ class AssetListQuery { this.condition, this.itemCategoryId, this.itemSubcategoryId, - this.plantId, + this.locationId, this.departmentId, + this.maintenanceInchargeUserId, + this.dueOnly, this.isActive, }); @@ -569,8 +774,10 @@ class AssetListQuery { final String? condition; final int? itemCategoryId; final int? itemSubcategoryId; - final int? plantId; + final int? locationId; final int? departmentId; + final int? maintenanceInchargeUserId; + final bool? dueOnly; final bool? isActive; AssetListQuery copyWith({ @@ -581,8 +788,10 @@ class AssetListQuery { Object? condition = _unset, Object? itemCategoryId = _unset, Object? itemSubcategoryId = _unset, - Object? plantId = _unset, + Object? locationId = _unset, Object? departmentId = _unset, + Object? maintenanceInchargeUserId = _unset, + Object? dueOnly = _unset, Object? isActive = _unset, }) { return AssetListQuery( @@ -598,10 +807,15 @@ class AssetListQuery { itemSubcategoryId: identical(itemSubcategoryId, _unset) ? this.itemSubcategoryId : itemSubcategoryId as int?, - plantId: identical(plantId, _unset) ? this.plantId : plantId as int?, + locationId: + identical(locationId, _unset) ? this.locationId : locationId as int?, departmentId: identical(departmentId, _unset) ? this.departmentId : departmentId as int?, + maintenanceInchargeUserId: identical(maintenanceInchargeUserId, _unset) + ? this.maintenanceInchargeUserId + : maintenanceInchargeUserId as int?, + dueOnly: identical(dueOnly, _unset) ? this.dueOnly : dueOnly as bool?, isActive: identical(isActive, _unset) ? this.isActive : isActive as bool?, ); diff --git a/lib/shared/models/asset_model.freezed.dart b/lib/shared/models/asset_model.freezed.dart index 100c2f1..ab8d9e8 100644 --- a/lib/shared/models/asset_model.freezed.dart +++ b/lib/shared/models/asset_model.freezed.dart @@ -438,13 +438,13 @@ mixin _$AssetModel { @JsonKey(name: 'item_subcategory_name', readValue: _readItemSubcategoryName) String? get assetSubcategoryName => throw _privateConstructorUsedError; @JsonKey( - name: 'plant_id', - readValue: _readPlantId, + name: 'location_id', + readValue: _readLocationId, fromJson: _intFromJsonNullable, ) - int? get plantId => throw _privateConstructorUsedError; - @JsonKey(name: 'plant_name', readValue: _readPlantName) - String? get plantName => throw _privateConstructorUsedError; + int? get locationId => throw _privateConstructorUsedError; + @JsonKey(name: 'location_name', readValue: _readLocationName) + String? get locationName => throw _privateConstructorUsedError; @JsonKey(name: 'brand_model') String? get brandModel => throw _privateConstructorUsedError; String? get manufacturer => throw _privateConstructorUsedError; @@ -456,14 +456,26 @@ mixin _$AssetModel { 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: 'maintenance_incharge_user_id', fromJson: _intFromJsonNullable) + int? get maintenanceInchargeUserId => throw _privateConstructorUsedError; + @JsonKey( + name: 'maintenance_frequency_in_days', + fromJson: _intFromJsonNullable, + ) + int? get maintenanceFrequencyInDays => throw _privateConstructorUsedError; + @JsonKey( + name: 'maintenance_checklist_json', + fromJson: _checklistFromJson, + toJson: _checklistToJson, + ) + List? get maintenanceChecklistJson => + throw _privateConstructorUsedError; + @JsonKey(name: 'commencement_date', fromJson: _dateFromJsonNullable) + DateTime? get commencementDate => throw _privateConstructorUsedError; @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? get vendorId => throw _privateConstructorUsedError; @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) @@ -505,6 +517,13 @@ mixin _$AssetModel { DateTime? get createdAt => throw _privateConstructorUsedError; @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) DateTime? get updatedAt => throw _privateConstructorUsedError; + @JsonKey( + name: 'maintenance', + fromJson: AssetMaintenanceSummary.fromJsonNullable, + toJson: AssetMaintenanceSummary.toJsonNullable, + ) + AssetMaintenanceSummary? get maintenance => + throw _privateConstructorUsedError; /// Serializes this AssetModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -544,12 +563,13 @@ abstract class $AssetModelCopyWith<$Res> { @JsonKey(name: 'item_subcategory_name', readValue: _readItemSubcategoryName) String? assetSubcategoryName, @JsonKey( - name: 'plant_id', - readValue: _readPlantId, + name: 'location_id', + readValue: _readLocationId, fromJson: _intFromJsonNullable, ) - int? plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, + int? locationId, + @JsonKey(name: 'location_name', readValue: _readLocationName) + String? locationName, @JsonKey(name: 'brand_model') String? brandModel, String? manufacturer, @JsonKey(name: 'serial_number') String? serialNumber, @@ -558,13 +578,27 @@ abstract class $AssetModelCopyWith<$Res> { 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: 'maintenance_incharge_user_id', + fromJson: _intFromJsonNullable, + ) + int? maintenanceInchargeUserId, + @JsonKey( + name: 'maintenance_frequency_in_days', + fromJson: _intFromJsonNullable, + ) + int? maintenanceFrequencyInDays, + @JsonKey( + name: 'maintenance_checklist_json', + fromJson: _checklistFromJson, + toJson: _checklistToJson, + ) + List? maintenanceChecklistJson, + @JsonKey(name: 'commencement_date', fromJson: _dateFromJsonNullable) + DateTime? commencementDate, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) String? vendorName, @@ -599,6 +633,12 @@ abstract class $AssetModelCopyWith<$Res> { DateTime? createdAt, @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) DateTime? updatedAt, + @JsonKey( + name: 'maintenance', + fromJson: AssetMaintenanceSummary.fromJsonNullable, + toJson: AssetMaintenanceSummary.toJsonNullable, + ) + AssetMaintenanceSummary? maintenance, }); } @@ -624,18 +664,20 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> Object? assetCategoryName = freezed, Object? assetSubcategoryId = freezed, Object? assetSubcategoryName = freezed, - Object? plantId = freezed, - Object? plantName = freezed, + Object? locationId = freezed, + Object? locationName = 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? maintenanceInchargeUserId = freezed, + Object? maintenanceFrequencyInDays = freezed, + Object? maintenanceChecklistJson = freezed, + Object? commencementDate = freezed, Object? vendorId = freezed, Object? vendorName = freezed, Object? poId = freezed, @@ -658,6 +700,7 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> Object? isActive = null, Object? createdAt = freezed, Object? updatedAt = freezed, + Object? maintenance = freezed, }) { return _then( _value.copyWith( @@ -689,13 +732,13 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> ? _value.assetSubcategoryName : assetSubcategoryName // ignore: cast_nullable_to_non_nullable as String?, - plantId: freezed == plantId - ? _value.plantId - : plantId // ignore: cast_nullable_to_non_nullable + locationId: freezed == locationId + ? _value.locationId + : locationId // ignore: cast_nullable_to_non_nullable as int?, - plantName: freezed == plantName - ? _value.plantName - : plantName // ignore: cast_nullable_to_non_nullable + locationName: freezed == locationName + ? _value.locationName + : locationName // ignore: cast_nullable_to_non_nullable as String?, brandModel: freezed == brandModel ? _value.brandModel @@ -721,14 +764,6 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> ? _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 @@ -737,6 +772,22 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> ? _value.assignedToUserId : assignedToUserId // ignore: cast_nullable_to_non_nullable as int?, + maintenanceInchargeUserId: freezed == maintenanceInchargeUserId + ? _value.maintenanceInchargeUserId + : maintenanceInchargeUserId // ignore: cast_nullable_to_non_nullable + as int?, + maintenanceFrequencyInDays: freezed == maintenanceFrequencyInDays + ? _value.maintenanceFrequencyInDays + : maintenanceFrequencyInDays // ignore: cast_nullable_to_non_nullable + as int?, + maintenanceChecklistJson: freezed == maintenanceChecklistJson + ? _value.maintenanceChecklistJson + : maintenanceChecklistJson // ignore: cast_nullable_to_non_nullable + as List?, + commencementDate: freezed == commencementDate + ? _value.commencementDate + : commencementDate // ignore: cast_nullable_to_non_nullable + as DateTime?, vendorId: freezed == vendorId ? _value.vendorId : vendorId // ignore: cast_nullable_to_non_nullable @@ -825,6 +876,10 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel> ? _value.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime?, + maintenance: freezed == maintenance + ? _value.maintenance + : maintenance // ignore: cast_nullable_to_non_nullable + as AssetMaintenanceSummary?, ) as $Val, ); @@ -861,12 +916,13 @@ abstract class _$$AssetModelImplCopyWith<$Res> @JsonKey(name: 'item_subcategory_name', readValue: _readItemSubcategoryName) String? assetSubcategoryName, @JsonKey( - name: 'plant_id', - readValue: _readPlantId, + name: 'location_id', + readValue: _readLocationId, fromJson: _intFromJsonNullable, ) - int? plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, + int? locationId, + @JsonKey(name: 'location_name', readValue: _readLocationName) + String? locationName, @JsonKey(name: 'brand_model') String? brandModel, String? manufacturer, @JsonKey(name: 'serial_number') String? serialNumber, @@ -875,13 +931,27 @@ abstract class _$$AssetModelImplCopyWith<$Res> 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: 'maintenance_incharge_user_id', + fromJson: _intFromJsonNullable, + ) + int? maintenanceInchargeUserId, + @JsonKey( + name: 'maintenance_frequency_in_days', + fromJson: _intFromJsonNullable, + ) + int? maintenanceFrequencyInDays, + @JsonKey( + name: 'maintenance_checklist_json', + fromJson: _checklistFromJson, + toJson: _checklistToJson, + ) + List? maintenanceChecklistJson, + @JsonKey(name: 'commencement_date', fromJson: _dateFromJsonNullable) + DateTime? commencementDate, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) String? vendorName, @@ -916,6 +986,12 @@ abstract class _$$AssetModelImplCopyWith<$Res> DateTime? createdAt, @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) DateTime? updatedAt, + @JsonKey( + name: 'maintenance', + fromJson: AssetMaintenanceSummary.fromJsonNullable, + toJson: AssetMaintenanceSummary.toJsonNullable, + ) + AssetMaintenanceSummary? maintenance, }); } @@ -940,18 +1016,20 @@ class __$$AssetModelImplCopyWithImpl<$Res> Object? assetCategoryName = freezed, Object? assetSubcategoryId = freezed, Object? assetSubcategoryName = freezed, - Object? plantId = freezed, - Object? plantName = freezed, + Object? locationId = freezed, + Object? locationName = 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? maintenanceInchargeUserId = freezed, + Object? maintenanceFrequencyInDays = freezed, + Object? maintenanceChecklistJson = freezed, + Object? commencementDate = freezed, Object? vendorId = freezed, Object? vendorName = freezed, Object? poId = freezed, @@ -974,6 +1052,7 @@ class __$$AssetModelImplCopyWithImpl<$Res> Object? isActive = null, Object? createdAt = freezed, Object? updatedAt = freezed, + Object? maintenance = freezed, }) { return _then( _$AssetModelImpl( @@ -1005,13 +1084,13 @@ class __$$AssetModelImplCopyWithImpl<$Res> ? _value.assetSubcategoryName : assetSubcategoryName // ignore: cast_nullable_to_non_nullable as String?, - plantId: freezed == plantId - ? _value.plantId - : plantId // ignore: cast_nullable_to_non_nullable + locationId: freezed == locationId + ? _value.locationId + : locationId // ignore: cast_nullable_to_non_nullable as int?, - plantName: freezed == plantName - ? _value.plantName - : plantName // ignore: cast_nullable_to_non_nullable + locationName: freezed == locationName + ? _value.locationName + : locationName // ignore: cast_nullable_to_non_nullable as String?, brandModel: freezed == brandModel ? _value.brandModel @@ -1037,14 +1116,6 @@ class __$$AssetModelImplCopyWithImpl<$Res> ? _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 @@ -1053,6 +1124,22 @@ class __$$AssetModelImplCopyWithImpl<$Res> ? _value.assignedToUserId : assignedToUserId // ignore: cast_nullable_to_non_nullable as int?, + maintenanceInchargeUserId: freezed == maintenanceInchargeUserId + ? _value.maintenanceInchargeUserId + : maintenanceInchargeUserId // ignore: cast_nullable_to_non_nullable + as int?, + maintenanceFrequencyInDays: freezed == maintenanceFrequencyInDays + ? _value.maintenanceFrequencyInDays + : maintenanceFrequencyInDays // ignore: cast_nullable_to_non_nullable + as int?, + maintenanceChecklistJson: freezed == maintenanceChecklistJson + ? _value._maintenanceChecklistJson + : maintenanceChecklistJson // ignore: cast_nullable_to_non_nullable + as List?, + commencementDate: freezed == commencementDate + ? _value.commencementDate + : commencementDate // ignore: cast_nullable_to_non_nullable + as DateTime?, vendorId: freezed == vendorId ? _value.vendorId : vendorId // ignore: cast_nullable_to_non_nullable @@ -1141,6 +1228,10 @@ class __$$AssetModelImplCopyWithImpl<$Res> ? _value.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime?, + maintenance: freezed == maintenance + ? _value.maintenance + : maintenance // ignore: cast_nullable_to_non_nullable + as AssetMaintenanceSummary?, ), ); } @@ -1170,12 +1261,13 @@ class _$AssetModelImpl implements _AssetModel { @JsonKey(name: 'item_subcategory_name', readValue: _readItemSubcategoryName) this.assetSubcategoryName, @JsonKey( - name: 'plant_id', - readValue: _readPlantId, + name: 'location_id', + readValue: _readLocationId, fromJson: _intFromJsonNullable, ) - this.plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) this.plantName, + this.locationId, + @JsonKey(name: 'location_name', readValue: _readLocationName) + this.locationName, @JsonKey(name: 'brand_model') this.brandModel, this.manufacturer, @JsonKey(name: 'serial_number') this.serialNumber, @@ -1184,13 +1276,27 @@ class _$AssetModelImpl implements _AssetModel { 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: 'maintenance_incharge_user_id', + fromJson: _intFromJsonNullable, + ) + this.maintenanceInchargeUserId, + @JsonKey( + name: 'maintenance_frequency_in_days', + fromJson: _intFromJsonNullable, + ) + this.maintenanceFrequencyInDays, + @JsonKey( + name: 'maintenance_checklist_json', + fromJson: _checklistFromJson, + toJson: _checklistToJson, + ) + final List? maintenanceChecklistJson, + @JsonKey(name: 'commencement_date', fromJson: _dateFromJsonNullable) + this.commencementDate, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) this.vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) this.vendorName, @@ -1225,7 +1331,13 @@ class _$AssetModelImpl implements _AssetModel { this.createdAt, @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) this.updatedAt, - }); + @JsonKey( + name: 'maintenance', + fromJson: AssetMaintenanceSummary.fromJsonNullable, + toJson: AssetMaintenanceSummary.toJsonNullable, + ) + this.maintenance, + }) : _maintenanceChecklistJson = maintenanceChecklistJson; factory _$AssetModelImpl.fromJson(Map json) => _$$AssetModelImplFromJson(json); @@ -1261,14 +1373,14 @@ class _$AssetModelImpl implements _AssetModel { final String? assetSubcategoryName; @override @JsonKey( - name: 'plant_id', - readValue: _readPlantId, + name: 'location_id', + readValue: _readLocationId, fromJson: _intFromJsonNullable, ) - final int? plantId; + final int? locationId; @override - @JsonKey(name: 'plant_name', readValue: _readPlantName) - final String? plantName; + @JsonKey(name: 'location_name', readValue: _readLocationName) + final String? locationName; @override @JsonKey(name: 'brand_model') final String? brandModel; @@ -1287,18 +1399,40 @@ class _$AssetModelImpl implements _AssetModel { @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: 'maintenance_incharge_user_id', fromJson: _intFromJsonNullable) + final int? maintenanceInchargeUserId; + @override + @JsonKey( + name: 'maintenance_frequency_in_days', + fromJson: _intFromJsonNullable, + ) + final int? maintenanceFrequencyInDays; + final List? _maintenanceChecklistJson; + @override + @JsonKey( + name: 'maintenance_checklist_json', + fromJson: _checklistFromJson, + toJson: _checklistToJson, + ) + List? get maintenanceChecklistJson { + final value = _maintenanceChecklistJson; + if (value == null) return null; + if (_maintenanceChecklistJson is EqualUnmodifiableListView) + return _maintenanceChecklistJson; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + @JsonKey(name: 'commencement_date', fromJson: _dateFromJsonNullable) + final DateTime? commencementDate; + @override @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) final int? vendorId; @override @@ -1361,10 +1495,17 @@ class _$AssetModelImpl implements _AssetModel { @override @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) final DateTime? updatedAt; + @override + @JsonKey( + name: 'maintenance', + fromJson: AssetMaintenanceSummary.fromJsonNullable, + toJson: AssetMaintenanceSummary.toJsonNullable, + ) + final AssetMaintenanceSummary? maintenance; @override String toString() { - 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)'; + return 'AssetModel(id: $id, assetName: $assetName, assetCode: $assetCode, assetCategoryId: $assetCategoryId, assetCategoryName: $assetCategoryName, assetSubcategoryId: $assetSubcategoryId, assetSubcategoryName: $assetSubcategoryName, locationId: $locationId, locationName: $locationName, brandModel: $brandModel, manufacturer: $manufacturer, serialNumber: $serialNumber, partNumber: $partNumber, departmentId: $departmentId, departmentName: $departmentName, locationDetail: $locationDetail, assignedToUserId: $assignedToUserId, maintenanceInchargeUserId: $maintenanceInchargeUserId, maintenanceFrequencyInDays: $maintenanceFrequencyInDays, maintenanceChecklistJson: $maintenanceChecklistJson, commencementDate: $commencementDate, 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, maintenance: $maintenance)'; } @override @@ -1385,9 +1526,10 @@ class _$AssetModelImpl implements _AssetModel { 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.locationId, locationId) || + other.locationId == locationId) && + (identical(other.locationName, locationName) || + other.locationName == locationName) && (identical(other.brandModel, brandModel) || other.brandModel == brandModel) && (identical(other.manufacturer, manufacturer) || @@ -1400,14 +1542,27 @@ class _$AssetModelImpl implements _AssetModel { 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.maintenanceInchargeUserId, + maintenanceInchargeUserId, + ) || + other.maintenanceInchargeUserId == maintenanceInchargeUserId) && + (identical( + other.maintenanceFrequencyInDays, + maintenanceFrequencyInDays, + ) || + other.maintenanceFrequencyInDays == + maintenanceFrequencyInDays) && + const DeepCollectionEquality().equals( + other._maintenanceChecklistJson, + _maintenanceChecklistJson, + ) && + (identical(other.commencementDate, commencementDate) || + other.commencementDate == commencementDate) && (identical(other.vendorId, vendorId) || other.vendorId == vendorId) && (identical(other.vendorName, vendorName) || @@ -1447,7 +1602,9 @@ class _$AssetModelImpl implements _AssetModel { (identical(other.createdAt, createdAt) || other.createdAt == createdAt) && (identical(other.updatedAt, updatedAt) || - other.updatedAt == updatedAt)); + other.updatedAt == updatedAt) && + (identical(other.maintenance, maintenance) || + other.maintenance == maintenance)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -1461,18 +1618,20 @@ class _$AssetModelImpl implements _AssetModel { assetCategoryName, assetSubcategoryId, assetSubcategoryName, - plantId, - plantName, + locationId, + locationName, brandModel, manufacturer, serialNumber, partNumber, departmentId, departmentName, - warehouseId, - warehouseName, locationDetail, assignedToUserId, + maintenanceInchargeUserId, + maintenanceFrequencyInDays, + const DeepCollectionEquality().hash(_maintenanceChecklistJson), + commencementDate, vendorId, vendorName, poId, @@ -1495,6 +1654,7 @@ class _$AssetModelImpl implements _AssetModel { isActive, createdAt, updatedAt, + maintenance, ]); /// Create a copy of AssetModel @@ -1533,13 +1693,13 @@ abstract class _AssetModel implements AssetModel { @JsonKey(name: 'item_subcategory_name', readValue: _readItemSubcategoryName) final String? assetSubcategoryName, @JsonKey( - name: 'plant_id', - readValue: _readPlantId, + name: 'location_id', + readValue: _readLocationId, fromJson: _intFromJsonNullable, ) - final int? plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) - final String? plantName, + final int? locationId, + @JsonKey(name: 'location_name', readValue: _readLocationName) + final String? locationName, @JsonKey(name: 'brand_model') final String? brandModel, final String? manufacturer, @JsonKey(name: 'serial_number') final String? serialNumber, @@ -1548,13 +1708,27 @@ abstract class _AssetModel implements AssetModel { 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: 'maintenance_incharge_user_id', + fromJson: _intFromJsonNullable, + ) + final int? maintenanceInchargeUserId, + @JsonKey( + name: 'maintenance_frequency_in_days', + fromJson: _intFromJsonNullable, + ) + final int? maintenanceFrequencyInDays, + @JsonKey( + name: 'maintenance_checklist_json', + fromJson: _checklistFromJson, + toJson: _checklistToJson, + ) + final List? maintenanceChecklistJson, + @JsonKey(name: 'commencement_date', fromJson: _dateFromJsonNullable) + final DateTime? commencementDate, @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) final int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorNameFromNested) @@ -1590,6 +1764,12 @@ abstract class _AssetModel implements AssetModel { final DateTime? createdAt, @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) final DateTime? updatedAt, + @JsonKey( + name: 'maintenance', + fromJson: AssetMaintenanceSummary.fromJsonNullable, + toJson: AssetMaintenanceSummary.toJsonNullable, + ) + final AssetMaintenanceSummary? maintenance, }) = _$AssetModelImpl; factory _AssetModel.fromJson(Map json) = @@ -1626,14 +1806,14 @@ abstract class _AssetModel implements AssetModel { String? get assetSubcategoryName; @override @JsonKey( - name: 'plant_id', - readValue: _readPlantId, + name: 'location_id', + readValue: _readLocationId, fromJson: _intFromJsonNullable, ) - int? get plantId; + int? get locationId; @override - @JsonKey(name: 'plant_name', readValue: _readPlantName) - String? get plantName; + @JsonKey(name: 'location_name', readValue: _readLocationName) + String? get locationName; @override @JsonKey(name: 'brand_model') String? get brandModel; @@ -1652,18 +1832,31 @@ abstract class _AssetModel implements AssetModel { @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: 'maintenance_incharge_user_id', fromJson: _intFromJsonNullable) + int? get maintenanceInchargeUserId; + @override + @JsonKey( + name: 'maintenance_frequency_in_days', + fromJson: _intFromJsonNullable, + ) + int? get maintenanceFrequencyInDays; + @override + @JsonKey( + name: 'maintenance_checklist_json', + fromJson: _checklistFromJson, + toJson: _checklistToJson, + ) + List? get maintenanceChecklistJson; + @override + @JsonKey(name: 'commencement_date', fromJson: _dateFromJsonNullable) + DateTime? get commencementDate; + @override @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? get vendorId; @override @@ -1726,6 +1919,13 @@ abstract class _AssetModel implements AssetModel { @override @JsonKey(name: 'updated_at', fromJson: _dateFromJsonNullable) DateTime? get updatedAt; + @override + @JsonKey( + name: 'maintenance', + fromJson: AssetMaintenanceSummary.fromJsonNullable, + toJson: AssetMaintenanceSummary.toJsonNullable, + ) + AssetMaintenanceSummary? get maintenance; /// Create a copy of AssetModel /// with the given fields replaced by the non-null parameter values. @@ -3913,8 +4113,8 @@ mixin _$AssetAlertModel { @JsonKey(name: 'days_remaining', fromJson: _intFromJsonNullable) int? get daysRemaining => throw _privateConstructorUsedError; String? get status => throw _privateConstructorUsedError; - @JsonKey(name: 'plant_name') - String? get plantName => throw _privateConstructorUsedError; + @JsonKey(name: 'location_name', readValue: _readAlertLocationName) + String? get locationName => throw _privateConstructorUsedError; /// Serializes this AssetAlertModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -3948,7 +4148,8 @@ abstract class $AssetAlertModelCopyWith<$Res> { @JsonKey(name: 'days_remaining', fromJson: _intFromJsonNullable) int? daysRemaining, String? status, - @JsonKey(name: 'plant_name') String? plantName, + @JsonKey(name: 'location_name', readValue: _readAlertLocationName) + String? locationName, }); } @@ -3978,7 +4179,7 @@ class _$AssetAlertModelCopyWithImpl<$Res, $Val extends AssetAlertModel> Object? dueDate = freezed, Object? daysRemaining = freezed, Object? status = freezed, - Object? plantName = freezed, + Object? locationName = freezed, }) { return _then( _value.copyWith( @@ -4026,9 +4227,9 @@ class _$AssetAlertModelCopyWithImpl<$Res, $Val extends AssetAlertModel> ? _value.status : status // ignore: cast_nullable_to_non_nullable as String?, - plantName: freezed == plantName - ? _value.plantName - : plantName // ignore: cast_nullable_to_non_nullable + locationName: freezed == locationName + ? _value.locationName + : locationName // ignore: cast_nullable_to_non_nullable as String?, ) as $Val, @@ -4060,7 +4261,8 @@ abstract class _$$AssetAlertModelImplCopyWith<$Res> @JsonKey(name: 'days_remaining', fromJson: _intFromJsonNullable) int? daysRemaining, String? status, - @JsonKey(name: 'plant_name') String? plantName, + @JsonKey(name: 'location_name', readValue: _readAlertLocationName) + String? locationName, }); } @@ -4089,7 +4291,7 @@ class __$$AssetAlertModelImplCopyWithImpl<$Res> Object? dueDate = freezed, Object? daysRemaining = freezed, Object? status = freezed, - Object? plantName = freezed, + Object? locationName = freezed, }) { return _then( _$AssetAlertModelImpl( @@ -4137,9 +4339,9 @@ class __$$AssetAlertModelImplCopyWithImpl<$Res> ? _value.status : status // ignore: cast_nullable_to_non_nullable as String?, - plantName: freezed == plantName - ? _value.plantName - : plantName // ignore: cast_nullable_to_non_nullable + locationName: freezed == locationName + ? _value.locationName + : locationName // ignore: cast_nullable_to_non_nullable as String?, ), ); @@ -4163,7 +4365,8 @@ class _$AssetAlertModelImpl implements _AssetAlertModel { @JsonKey(name: 'days_remaining', fromJson: _intFromJsonNullable) this.daysRemaining, this.status, - @JsonKey(name: 'plant_name') this.plantName, + @JsonKey(name: 'location_name', readValue: _readAlertLocationName) + this.locationName, }); factory _$AssetAlertModelImpl.fromJson(Map json) => @@ -4199,12 +4402,12 @@ class _$AssetAlertModelImpl implements _AssetAlertModel { @override final String? status; @override - @JsonKey(name: 'plant_name') - final String? plantName; + @JsonKey(name: 'location_name', readValue: _readAlertLocationName) + final String? locationName; @override String toString() { - return 'AssetAlertModel(id: $id, assetId: $assetId, assetName: $assetName, assetCode: $assetCode, type: $type, title: $title, message: $message, expiryDate: $expiryDate, dueDate: $dueDate, daysRemaining: $daysRemaining, status: $status, plantName: $plantName)'; + return 'AssetAlertModel(id: $id, assetId: $assetId, assetName: $assetName, assetCode: $assetCode, type: $type, title: $title, message: $message, expiryDate: $expiryDate, dueDate: $dueDate, daysRemaining: $daysRemaining, status: $status, locationName: $locationName)'; } @override @@ -4227,8 +4430,8 @@ class _$AssetAlertModelImpl implements _AssetAlertModel { (identical(other.daysRemaining, daysRemaining) || other.daysRemaining == daysRemaining) && (identical(other.status, status) || other.status == status) && - (identical(other.plantName, plantName) || - other.plantName == plantName)); + (identical(other.locationName, locationName) || + other.locationName == locationName)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -4246,7 +4449,7 @@ class _$AssetAlertModelImpl implements _AssetAlertModel { dueDate, daysRemaining, status, - plantName, + locationName, ); /// Create a copy of AssetAlertModel @@ -4282,7 +4485,8 @@ abstract class _AssetAlertModel implements AssetAlertModel { @JsonKey(name: 'days_remaining', fromJson: _intFromJsonNullable) final int? daysRemaining, final String? status, - @JsonKey(name: 'plant_name') final String? plantName, + @JsonKey(name: 'location_name', readValue: _readAlertLocationName) + final String? locationName, }) = _$AssetAlertModelImpl; factory _AssetAlertModel.fromJson(Map json) = @@ -4318,8 +4522,8 @@ abstract class _AssetAlertModel implements AssetAlertModel { @override String? get status; @override - @JsonKey(name: 'plant_name') - String? get plantName; + @JsonKey(name: 'location_name', readValue: _readAlertLocationName) + String? get locationName; /// Create a copy of AssetAlertModel /// with the given fields replaced by the non-null parameter values. diff --git a/lib/shared/models/asset_model.g.dart b/lib/shared/models/asset_model.g.dart index afd35b4..b818a36 100644 --- a/lib/shared/models/asset_model.g.dart +++ b/lib/shared/models/asset_model.g.dart @@ -57,18 +57,26 @@ _$AssetModelImpl _$$AssetModelImplFromJson(Map json) => ), assetSubcategoryName: _readItemSubcategoryName(json, 'item_subcategory_name') as String?, - plantId: _intFromJsonNullable(_readPlantId(json, 'plant_id')), - plantName: _readPlantName(json, 'plant_name') as String?, + locationId: _intFromJsonNullable(_readLocationId(json, 'location_id')), + locationName: _readLocationName(json, 'location_name') as String?, 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']), + maintenanceInchargeUserId: _intFromJsonNullable( + json['maintenance_incharge_user_id'], + ), + maintenanceFrequencyInDays: _intFromJsonNullable( + json['maintenance_frequency_in_days'], + ), + maintenanceChecklistJson: _checklistFromJson( + json['maintenance_checklist_json'], + ), + commencementDate: _dateFromJsonNullable(json['commencement_date']), vendorId: _intFromJsonNullable(json['vendor_id']), vendorName: _readVendorNameFromNested(json, 'vendor_name') as String?, poId: _intFromJsonNullable(json['po_id']), @@ -91,52 +99,61 @@ _$AssetModelImpl _$$AssetModelImplFromJson(Map json) => isActive: json['is_active'] as bool? ?? true, createdAt: _dateFromJsonNullable(json['created_at']), updatedAt: _dateFromJsonNullable(json['updated_at']), + maintenance: AssetMaintenanceSummary.fromJsonNullable( + json['maintenance'], + ), ); -Map _$$AssetModelImplToJson(_$AssetModelImpl instance) => - { - 'id': instance.id, - 'asset_name': instance.assetName, - 'asset_code': instance.assetCode, - 'item_category_id': instance.assetCategoryId, - 'item_category_name': instance.assetCategoryName, - 'item_subcategory_id': instance.assetSubcategoryId, - 'item_subcategory_name': instance.assetSubcategoryName, - 'plant_id': instance.plantId, - 'plant_name': instance.plantName, - '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(), - }; +Map _$$AssetModelImplToJson( + _$AssetModelImpl instance, +) => { + 'id': instance.id, + 'asset_name': instance.assetName, + 'asset_code': instance.assetCode, + 'item_category_id': instance.assetCategoryId, + 'item_category_name': instance.assetCategoryName, + 'item_subcategory_id': instance.assetSubcategoryId, + 'item_subcategory_name': instance.assetSubcategoryName, + 'location_id': instance.locationId, + 'location_name': instance.locationName, + 'brand_model': instance.brandModel, + 'manufacturer': instance.manufacturer, + 'serial_number': instance.serialNumber, + 'part_number': instance.partNumber, + 'department_id': instance.departmentId, + 'department_name': instance.departmentName, + 'location_detail': instance.locationDetail, + 'assigned_to_user_id': instance.assignedToUserId, + 'maintenance_incharge_user_id': instance.maintenanceInchargeUserId, + 'maintenance_frequency_in_days': instance.maintenanceFrequencyInDays, + 'maintenance_checklist_json': _checklistToJson( + instance.maintenanceChecklistJson, + ), + 'commencement_date': instance.commencementDate?.toIso8601String(), + '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(), + 'maintenance': AssetMaintenanceSummary.toJsonNullable(instance.maintenance), +}; _$AmcContractModelImpl _$$AmcContractModelImplFromJson( Map json, @@ -314,7 +331,7 @@ _$AssetAlertModelImpl _$$AssetAlertModelImplFromJson( dueDate: _dateFromJsonNullable(json['due_date']), daysRemaining: _intFromJsonNullable(json['days_remaining']), status: json['status'] as String?, - plantName: json['plant_name'] as String?, + locationName: _readAlertLocationName(json, 'location_name') as String?, ); Map _$$AssetAlertModelImplToJson( @@ -331,5 +348,5 @@ Map _$$AssetAlertModelImplToJson( 'due_date': instance.dueDate?.toIso8601String(), 'days_remaining': instance.daysRemaining, 'status': instance.status, - 'plant_name': instance.plantName, + 'location_name': instance.locationName, }; diff --git a/lib/shared/models/purchase_order_model.dart b/lib/shared/models/purchase_order_model.dart index 7e2dd13..c66357f 100644 --- a/lib/shared/models/purchase_order_model.dart +++ b/lib/shared/models/purchase_order_model.dart @@ -49,11 +49,32 @@ Object? _readVendorType(Map json, String key) { return null; } -Object? _readPlantName(Map json, String key) => - _readNestedName(json, 'plant_name', 'plant'); +Object? _readLocationDisplayName( + Map json, + String flatKey, + String nestedKey, +) { + final flat = json[flatKey]; + if (flat is String && flat.isNotEmpty) return flat; + final nested = json[nestedKey]; + if (nested is Map) { + final name = nested['name']?.toString().trim(); + if (name == null || name.isEmpty) return null; + final type = nested['type']?.toString().trim(); + if (type != null && type.isNotEmpty) { + final typeLabel = type[0].toUpperCase() + type.substring(1); + return '$name ($typeLabel)'; + } + return name; + } + return null; +} -Object? _readWarehouseName(Map json, String key) => - _readNestedName(json, 'warehouse_name', 'warehouse'); +Object? _readBillingName(Map json, String key) => + _readLocationDisplayName(json, 'billing_name', 'billing'); + +Object? _readShippingName(Map json, String key) => + _readLocationDisplayName(json, 'shipping_name', 'shipping'); Object? _readItemName(Map json, String key) { final flat = json['item_name']; @@ -154,10 +175,11 @@ class PurchaseOrderModel with _$PurchaseOrderModel { @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) int? vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorName) String? vendorName, @JsonKey(name: 'vendor_type', readValue: _readVendorType) String? vendorType, - @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, - @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) int? warehouseId, - @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) String? warehouseName, + @JsonKey(name: 'billing_id', fromJson: _intFromJsonNullable) int? billingId, + @JsonKey(name: 'billing_name', readValue: _readBillingName) String? billingName, + @JsonKey(name: 'shipping_id', fromJson: _intFromJsonNullable) int? shippingId, + @JsonKey(name: 'shipping_name', readValue: _readShippingName) + String? shippingName, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) int? deliveryTermId, @JsonKey(name: 'expected_delivery_date', fromJson: _dateFromJsonNullable) @@ -172,6 +194,9 @@ class PurchaseOrderModel with _$PurchaseOrderModel { double? taxableAmount, @JsonKey(name: 'tax_total', readValue: _readTaxTotal) double? taxAmount, + @JsonKey(name: 'cgst', fromJson: _doubleFromJsonNullable) double? cgst, + @JsonKey(name: 'sgst', fromJson: _doubleFromJsonNullable) double? sgst, + @JsonKey(name: 'igst', fromJson: _doubleFromJsonNullable) double? igst, @JsonKey(name: 'grand_total', readValue: _readGrandTotal) double? totalAmount, @JsonKey(name: 'terms_and_conditions') String? termsAndConditions, @@ -265,7 +290,8 @@ class PurchaseOrderListQuery with _$PurchaseOrderListQuery { String? search, String? status, int? vendorId, - int? plantId, + int? billingId, + int? shippingId, String? dateFrom, String? dateTo, }) = _PurchaseOrderListQuery; diff --git a/lib/shared/models/purchase_order_model.freezed.dart b/lib/shared/models/purchase_order_model.freezed.dart index 80feaae..720d23a 100644 --- a/lib/shared/models/purchase_order_model.freezed.dart +++ b/lib/shared/models/purchase_order_model.freezed.dart @@ -34,14 +34,14 @@ mixin _$PurchaseOrderModel { String? get vendorName => throw _privateConstructorUsedError; @JsonKey(name: 'vendor_type', readValue: _readVendorType) String? get vendorType => throw _privateConstructorUsedError; - @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) - int? get plantId => throw _privateConstructorUsedError; - @JsonKey(name: 'plant_name', readValue: _readPlantName) - String? get plantName => 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: 'billing_id', fromJson: _intFromJsonNullable) + int? get billingId => throw _privateConstructorUsedError; + @JsonKey(name: 'billing_name', readValue: _readBillingName) + String? get billingName => throw _privateConstructorUsedError; + @JsonKey(name: 'shipping_id', fromJson: _intFromJsonNullable) + int? get shippingId => throw _privateConstructorUsedError; + @JsonKey(name: 'shipping_name', readValue: _readShippingName) + String? get shippingName => throw _privateConstructorUsedError; @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? get paymentTermId => throw _privateConstructorUsedError; @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -58,6 +58,12 @@ mixin _$PurchaseOrderModel { double? get taxableAmount => throw _privateConstructorUsedError; @JsonKey(name: 'tax_total', readValue: _readTaxTotal) double? get taxAmount => throw _privateConstructorUsedError; + @JsonKey(name: 'cgst', fromJson: _doubleFromJsonNullable) + double? get cgst => throw _privateConstructorUsedError; + @JsonKey(name: 'sgst', fromJson: _doubleFromJsonNullable) + double? get sgst => throw _privateConstructorUsedError; + @JsonKey(name: 'igst', fromJson: _doubleFromJsonNullable) + double? get igst => throw _privateConstructorUsedError; @JsonKey(name: 'grand_total', readValue: _readGrandTotal) double? get totalAmount => throw _privateConstructorUsedError; @JsonKey(name: 'terms_and_conditions') @@ -98,12 +104,13 @@ abstract class $PurchaseOrderModelCopyWith<$Res> { String? vendorName, @JsonKey(name: 'vendor_type', readValue: _readVendorType) String? vendorType, - @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, - @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) - int? warehouseId, - @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) - String? warehouseName, + @JsonKey(name: 'billing_id', fromJson: _intFromJsonNullable) int? billingId, + @JsonKey(name: 'billing_name', readValue: _readBillingName) + String? billingName, + @JsonKey(name: 'shipping_id', fromJson: _intFromJsonNullable) + int? shippingId, + @JsonKey(name: 'shipping_name', readValue: _readShippingName) + String? shippingName, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -118,6 +125,9 @@ abstract class $PurchaseOrderModelCopyWith<$Res> { double? otherCharges, @JsonKey(name: 'sub_total', readValue: _readSubTotal) double? taxableAmount, @JsonKey(name: 'tax_total', readValue: _readTaxTotal) double? taxAmount, + @JsonKey(name: 'cgst', fromJson: _doubleFromJsonNullable) double? cgst, + @JsonKey(name: 'sgst', fromJson: _doubleFromJsonNullable) double? sgst, + @JsonKey(name: 'igst', fromJson: _doubleFromJsonNullable) double? igst, @JsonKey(name: 'grand_total', readValue: _readGrandTotal) double? totalAmount, @JsonKey(name: 'terms_and_conditions') String? termsAndConditions, @@ -154,10 +164,10 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel> Object? vendorId = freezed, Object? vendorName = freezed, Object? vendorType = freezed, - Object? plantId = freezed, - Object? plantName = freezed, - Object? warehouseId = freezed, - Object? warehouseName = freezed, + Object? billingId = freezed, + Object? billingName = freezed, + Object? shippingId = freezed, + Object? shippingName = freezed, Object? paymentTermId = freezed, Object? deliveryTermId = freezed, Object? expectedDeliveryDate = freezed, @@ -166,6 +176,9 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel> Object? otherCharges = freezed, Object? taxableAmount = freezed, Object? taxAmount = freezed, + Object? cgst = freezed, + Object? sgst = freezed, + Object? igst = freezed, Object? totalAmount = freezed, Object? termsAndConditions = freezed, Object? remarks = freezed, @@ -204,21 +217,21 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel> ? _value.vendorType : vendorType // ignore: cast_nullable_to_non_nullable as String?, - plantId: freezed == plantId - ? _value.plantId - : plantId // ignore: cast_nullable_to_non_nullable + billingId: freezed == billingId + ? _value.billingId + : billingId // ignore: cast_nullable_to_non_nullable as int?, - plantName: freezed == plantName - ? _value.plantName - : plantName // ignore: cast_nullable_to_non_nullable + billingName: freezed == billingName + ? _value.billingName + : billingName // ignore: cast_nullable_to_non_nullable as String?, - warehouseId: freezed == warehouseId - ? _value.warehouseId - : warehouseId // ignore: cast_nullable_to_non_nullable + shippingId: freezed == shippingId + ? _value.shippingId + : shippingId // ignore: cast_nullable_to_non_nullable as int?, - warehouseName: freezed == warehouseName - ? _value.warehouseName - : warehouseName // ignore: cast_nullable_to_non_nullable + shippingName: freezed == shippingName + ? _value.shippingName + : shippingName // ignore: cast_nullable_to_non_nullable as String?, paymentTermId: freezed == paymentTermId ? _value.paymentTermId @@ -252,6 +265,18 @@ class _$PurchaseOrderModelCopyWithImpl<$Res, $Val extends PurchaseOrderModel> ? _value.taxAmount : taxAmount // ignore: cast_nullable_to_non_nullable as double?, + cgst: freezed == cgst + ? _value.cgst + : cgst // ignore: cast_nullable_to_non_nullable + as double?, + sgst: freezed == sgst + ? _value.sgst + : sgst // ignore: cast_nullable_to_non_nullable + as double?, + igst: freezed == igst + ? _value.igst + : igst // ignore: cast_nullable_to_non_nullable + as double?, totalAmount: freezed == totalAmount ? _value.totalAmount : totalAmount // ignore: cast_nullable_to_non_nullable @@ -305,12 +330,13 @@ abstract class _$$PurchaseOrderModelImplCopyWith<$Res> String? vendorName, @JsonKey(name: 'vendor_type', readValue: _readVendorType) String? vendorType, - @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) int? plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) String? plantName, - @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) - int? warehouseId, - @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) - String? warehouseName, + @JsonKey(name: 'billing_id', fromJson: _intFromJsonNullable) int? billingId, + @JsonKey(name: 'billing_name', readValue: _readBillingName) + String? billingName, + @JsonKey(name: 'shipping_id', fromJson: _intFromJsonNullable) + int? shippingId, + @JsonKey(name: 'shipping_name', readValue: _readShippingName) + String? shippingName, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -325,6 +351,9 @@ abstract class _$$PurchaseOrderModelImplCopyWith<$Res> double? otherCharges, @JsonKey(name: 'sub_total', readValue: _readSubTotal) double? taxableAmount, @JsonKey(name: 'tax_total', readValue: _readTaxTotal) double? taxAmount, + @JsonKey(name: 'cgst', fromJson: _doubleFromJsonNullable) double? cgst, + @JsonKey(name: 'sgst', fromJson: _doubleFromJsonNullable) double? sgst, + @JsonKey(name: 'igst', fromJson: _doubleFromJsonNullable) double? igst, @JsonKey(name: 'grand_total', readValue: _readGrandTotal) double? totalAmount, @JsonKey(name: 'terms_and_conditions') String? termsAndConditions, @@ -360,10 +389,10 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res> Object? vendorId = freezed, Object? vendorName = freezed, Object? vendorType = freezed, - Object? plantId = freezed, - Object? plantName = freezed, - Object? warehouseId = freezed, - Object? warehouseName = freezed, + Object? billingId = freezed, + Object? billingName = freezed, + Object? shippingId = freezed, + Object? shippingName = freezed, Object? paymentTermId = freezed, Object? deliveryTermId = freezed, Object? expectedDeliveryDate = freezed, @@ -372,6 +401,9 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res> Object? otherCharges = freezed, Object? taxableAmount = freezed, Object? taxAmount = freezed, + Object? cgst = freezed, + Object? sgst = freezed, + Object? igst = freezed, Object? totalAmount = freezed, Object? termsAndConditions = freezed, Object? remarks = freezed, @@ -410,21 +442,21 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res> ? _value.vendorType : vendorType // ignore: cast_nullable_to_non_nullable as String?, - plantId: freezed == plantId - ? _value.plantId - : plantId // ignore: cast_nullable_to_non_nullable + billingId: freezed == billingId + ? _value.billingId + : billingId // ignore: cast_nullable_to_non_nullable as int?, - plantName: freezed == plantName - ? _value.plantName - : plantName // ignore: cast_nullable_to_non_nullable + billingName: freezed == billingName + ? _value.billingName + : billingName // ignore: cast_nullable_to_non_nullable as String?, - warehouseId: freezed == warehouseId - ? _value.warehouseId - : warehouseId // ignore: cast_nullable_to_non_nullable + shippingId: freezed == shippingId + ? _value.shippingId + : shippingId // ignore: cast_nullable_to_non_nullable as int?, - warehouseName: freezed == warehouseName - ? _value.warehouseName - : warehouseName // ignore: cast_nullable_to_non_nullable + shippingName: freezed == shippingName + ? _value.shippingName + : shippingName // ignore: cast_nullable_to_non_nullable as String?, paymentTermId: freezed == paymentTermId ? _value.paymentTermId @@ -458,6 +490,18 @@ class __$$PurchaseOrderModelImplCopyWithImpl<$Res> ? _value.taxAmount : taxAmount // ignore: cast_nullable_to_non_nullable as double?, + cgst: freezed == cgst + ? _value.cgst + : cgst // ignore: cast_nullable_to_non_nullable + as double?, + sgst: freezed == sgst + ? _value.sgst + : sgst // ignore: cast_nullable_to_non_nullable + as double?, + igst: freezed == igst + ? _value.igst + : igst // ignore: cast_nullable_to_non_nullable + as double?, totalAmount: freezed == totalAmount ? _value.totalAmount : totalAmount // ignore: cast_nullable_to_non_nullable @@ -502,12 +546,13 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @JsonKey(name: 'vendor_id', fromJson: _intFromJsonNullable) this.vendorId, @JsonKey(name: 'vendor_name', readValue: _readVendorName) this.vendorName, @JsonKey(name: 'vendor_type', readValue: _readVendorType) this.vendorType, - @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) this.plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) this.plantName, - @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) - this.warehouseId, - @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) - this.warehouseName, + @JsonKey(name: 'billing_id', fromJson: _intFromJsonNullable) this.billingId, + @JsonKey(name: 'billing_name', readValue: _readBillingName) + this.billingName, + @JsonKey(name: 'shipping_id', fromJson: _intFromJsonNullable) + this.shippingId, + @JsonKey(name: 'shipping_name', readValue: _readShippingName) + this.shippingName, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) this.paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -522,6 +567,9 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { this.otherCharges, @JsonKey(name: 'sub_total', readValue: _readSubTotal) this.taxableAmount, @JsonKey(name: 'tax_total', readValue: _readTaxTotal) this.taxAmount, + @JsonKey(name: 'cgst', fromJson: _doubleFromJsonNullable) this.cgst, + @JsonKey(name: 'sgst', fromJson: _doubleFromJsonNullable) this.sgst, + @JsonKey(name: 'igst', fromJson: _doubleFromJsonNullable) this.igst, @JsonKey(name: 'grand_total', readValue: _readGrandTotal) this.totalAmount, @JsonKey(name: 'terms_and_conditions') this.termsAndConditions, this.remarks, @@ -560,17 +608,17 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @JsonKey(name: 'vendor_type', readValue: _readVendorType) final String? vendorType; @override - @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) - final int? plantId; + @JsonKey(name: 'billing_id', fromJson: _intFromJsonNullable) + final int? billingId; @override - @JsonKey(name: 'plant_name', readValue: _readPlantName) - final String? plantName; + @JsonKey(name: 'billing_name', readValue: _readBillingName) + final String? billingName; @override - @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) - final int? warehouseId; + @JsonKey(name: 'shipping_id', fromJson: _intFromJsonNullable) + final int? shippingId; @override - @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) - final String? warehouseName; + @JsonKey(name: 'shipping_name', readValue: _readShippingName) + final String? shippingName; @override @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) final int? paymentTermId; @@ -596,6 +644,15 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @JsonKey(name: 'tax_total', readValue: _readTaxTotal) final double? taxAmount; @override + @JsonKey(name: 'cgst', fromJson: _doubleFromJsonNullable) + final double? cgst; + @override + @JsonKey(name: 'sgst', fromJson: _doubleFromJsonNullable) + final double? sgst; + @override + @JsonKey(name: 'igst', fromJson: _doubleFromJsonNullable) + final double? igst; + @override @JsonKey(name: 'grand_total', readValue: _readGrandTotal) final double? totalAmount; @override @@ -623,7 +680,7 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { @override String toString() { - return 'PurchaseOrderModel(id: $id, poNo: $poNo, poDate: $poDate, status: $status, vendorId: $vendorId, vendorName: $vendorName, vendorType: $vendorType, plantId: $plantId, plantName: $plantName, warehouseId: $warehouseId, warehouseName: $warehouseName, paymentTermId: $paymentTermId, deliveryTermId: $deliveryTermId, expectedDeliveryDate: $expectedDeliveryDate, discountAmount: $discountAmount, freightCharges: $freightCharges, otherCharges: $otherCharges, taxableAmount: $taxableAmount, taxAmount: $taxAmount, totalAmount: $totalAmount, termsAndConditions: $termsAndConditions, remarks: $remarks, revisionNo: $revisionNo, createdAt: $createdAt, updatedAt: $updatedAt, items: $items)'; + return 'PurchaseOrderModel(id: $id, poNo: $poNo, poDate: $poDate, status: $status, vendorId: $vendorId, vendorName: $vendorName, vendorType: $vendorType, billingId: $billingId, billingName: $billingName, shippingId: $shippingId, shippingName: $shippingName, paymentTermId: $paymentTermId, deliveryTermId: $deliveryTermId, expectedDeliveryDate: $expectedDeliveryDate, discountAmount: $discountAmount, freightCharges: $freightCharges, otherCharges: $otherCharges, taxableAmount: $taxableAmount, taxAmount: $taxAmount, cgst: $cgst, sgst: $sgst, igst: $igst, totalAmount: $totalAmount, termsAndConditions: $termsAndConditions, remarks: $remarks, revisionNo: $revisionNo, createdAt: $createdAt, updatedAt: $updatedAt, items: $items)'; } @override @@ -641,13 +698,14 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { other.vendorName == vendorName) && (identical(other.vendorType, vendorType) || other.vendorType == vendorType) && - (identical(other.plantId, plantId) || other.plantId == plantId) && - (identical(other.plantName, plantName) || - other.plantName == plantName) && - (identical(other.warehouseId, warehouseId) || - other.warehouseId == warehouseId) && - (identical(other.warehouseName, warehouseName) || - other.warehouseName == warehouseName) && + (identical(other.billingId, billingId) || + other.billingId == billingId) && + (identical(other.billingName, billingName) || + other.billingName == billingName) && + (identical(other.shippingId, shippingId) || + other.shippingId == shippingId) && + (identical(other.shippingName, shippingName) || + other.shippingName == shippingName) && (identical(other.paymentTermId, paymentTermId) || other.paymentTermId == paymentTermId) && (identical(other.deliveryTermId, deliveryTermId) || @@ -664,6 +722,9 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { other.taxableAmount == taxableAmount) && (identical(other.taxAmount, taxAmount) || other.taxAmount == taxAmount) && + (identical(other.cgst, cgst) || other.cgst == cgst) && + (identical(other.sgst, sgst) || other.sgst == sgst) && + (identical(other.igst, igst) || other.igst == igst) && (identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount) && (identical(other.termsAndConditions, termsAndConditions) || @@ -689,10 +750,10 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { vendorId, vendorName, vendorType, - plantId, - plantName, - warehouseId, - warehouseName, + billingId, + billingName, + shippingId, + shippingName, paymentTermId, deliveryTermId, expectedDeliveryDate, @@ -701,6 +762,9 @@ class _$PurchaseOrderModelImpl extends _PurchaseOrderModel { otherCharges, taxableAmount, taxAmount, + cgst, + sgst, + igst, totalAmount, termsAndConditions, remarks, @@ -740,14 +804,14 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { final String? vendorName, @JsonKey(name: 'vendor_type', readValue: _readVendorType) final String? vendorType, - @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) - final int? plantId, - @JsonKey(name: 'plant_name', readValue: _readPlantName) - final String? plantName, - @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) - final int? warehouseId, - @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) - final String? warehouseName, + @JsonKey(name: 'billing_id', fromJson: _intFromJsonNullable) + final int? billingId, + @JsonKey(name: 'billing_name', readValue: _readBillingName) + final String? billingName, + @JsonKey(name: 'shipping_id', fromJson: _intFromJsonNullable) + final int? shippingId, + @JsonKey(name: 'shipping_name', readValue: _readShippingName) + final String? shippingName, @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) final int? paymentTermId, @JsonKey(name: 'delivery_term_id', fromJson: _intFromJsonNullable) @@ -764,6 +828,12 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { final double? taxableAmount, @JsonKey(name: 'tax_total', readValue: _readTaxTotal) final double? taxAmount, + @JsonKey(name: 'cgst', fromJson: _doubleFromJsonNullable) + final double? cgst, + @JsonKey(name: 'sgst', fromJson: _doubleFromJsonNullable) + final double? sgst, + @JsonKey(name: 'igst', fromJson: _doubleFromJsonNullable) + final double? igst, @JsonKey(name: 'grand_total', readValue: _readGrandTotal) final double? totalAmount, @JsonKey(name: 'terms_and_conditions') final String? termsAndConditions, @@ -802,17 +872,17 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { @JsonKey(name: 'vendor_type', readValue: _readVendorType) String? get vendorType; @override - @JsonKey(name: 'plant_id', fromJson: _intFromJsonNullable) - int? get plantId; + @JsonKey(name: 'billing_id', fromJson: _intFromJsonNullable) + int? get billingId; @override - @JsonKey(name: 'plant_name', readValue: _readPlantName) - String? get plantName; + @JsonKey(name: 'billing_name', readValue: _readBillingName) + String? get billingName; @override - @JsonKey(name: 'warehouse_id', fromJson: _intFromJsonNullable) - int? get warehouseId; + @JsonKey(name: 'shipping_id', fromJson: _intFromJsonNullable) + int? get shippingId; @override - @JsonKey(name: 'warehouse_name', readValue: _readWarehouseName) - String? get warehouseName; + @JsonKey(name: 'shipping_name', readValue: _readShippingName) + String? get shippingName; @override @JsonKey(name: 'payment_term_id', fromJson: _intFromJsonNullable) int? get paymentTermId; @@ -838,6 +908,15 @@ abstract class _PurchaseOrderModel extends PurchaseOrderModel { @JsonKey(name: 'tax_total', readValue: _readTaxTotal) double? get taxAmount; @override + @JsonKey(name: 'cgst', fromJson: _doubleFromJsonNullable) + double? get cgst; + @override + @JsonKey(name: 'sgst', fromJson: _doubleFromJsonNullable) + double? get sgst; + @override + @JsonKey(name: 'igst', fromJson: _doubleFromJsonNullable) + double? get igst; + @override @JsonKey(name: 'grand_total', readValue: _readGrandTotal) double? get totalAmount; @override @@ -1642,7 +1721,8 @@ mixin _$PurchaseOrderListQuery { String? get search => throw _privateConstructorUsedError; String? get status => throw _privateConstructorUsedError; int? get vendorId => throw _privateConstructorUsedError; - int? get plantId => throw _privateConstructorUsedError; + int? get billingId => throw _privateConstructorUsedError; + int? get shippingId => throw _privateConstructorUsedError; String? get dateFrom => throw _privateConstructorUsedError; String? get dateTo => throw _privateConstructorUsedError; @@ -1666,7 +1746,8 @@ abstract class $PurchaseOrderListQueryCopyWith<$Res> { String? search, String? status, int? vendorId, - int? plantId, + int? billingId, + int? shippingId, String? dateFrom, String? dateTo, }); @@ -1695,7 +1776,8 @@ class _$PurchaseOrderListQueryCopyWithImpl< Object? search = freezed, Object? status = freezed, Object? vendorId = freezed, - Object? plantId = freezed, + Object? billingId = freezed, + Object? shippingId = freezed, Object? dateFrom = freezed, Object? dateTo = freezed, }) { @@ -1721,9 +1803,13 @@ class _$PurchaseOrderListQueryCopyWithImpl< ? _value.vendorId : vendorId // ignore: cast_nullable_to_non_nullable as int?, - plantId: freezed == plantId - ? _value.plantId - : plantId // ignore: cast_nullable_to_non_nullable + billingId: freezed == billingId + ? _value.billingId + : billingId // ignore: cast_nullable_to_non_nullable + as int?, + shippingId: freezed == shippingId + ? _value.shippingId + : shippingId // ignore: cast_nullable_to_non_nullable as int?, dateFrom: freezed == dateFrom ? _value.dateFrom @@ -1754,7 +1840,8 @@ abstract class _$$PurchaseOrderListQueryImplCopyWith<$Res> String? search, String? status, int? vendorId, - int? plantId, + int? billingId, + int? shippingId, String? dateFrom, String? dateTo, }); @@ -1780,7 +1867,8 @@ class __$$PurchaseOrderListQueryImplCopyWithImpl<$Res> Object? search = freezed, Object? status = freezed, Object? vendorId = freezed, - Object? plantId = freezed, + Object? billingId = freezed, + Object? shippingId = freezed, Object? dateFrom = freezed, Object? dateTo = freezed, }) { @@ -1806,9 +1894,13 @@ class __$$PurchaseOrderListQueryImplCopyWithImpl<$Res> ? _value.vendorId : vendorId // ignore: cast_nullable_to_non_nullable as int?, - plantId: freezed == plantId - ? _value.plantId - : plantId // ignore: cast_nullable_to_non_nullable + billingId: freezed == billingId + ? _value.billingId + : billingId // ignore: cast_nullable_to_non_nullable + as int?, + shippingId: freezed == shippingId + ? _value.shippingId + : shippingId // ignore: cast_nullable_to_non_nullable as int?, dateFrom: freezed == dateFrom ? _value.dateFrom @@ -1832,7 +1924,8 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { this.search, this.status, this.vendorId, - this.plantId, + this.billingId, + this.shippingId, this.dateFrom, this.dateTo, }); @@ -1850,7 +1943,9 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { @override final int? vendorId; @override - final int? plantId; + final int? billingId; + @override + final int? shippingId; @override final String? dateFrom; @override @@ -1858,7 +1953,7 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { @override String toString() { - return 'PurchaseOrderListQuery(page: $page, limit: $limit, search: $search, status: $status, vendorId: $vendorId, plantId: $plantId, dateFrom: $dateFrom, dateTo: $dateTo)'; + return 'PurchaseOrderListQuery(page: $page, limit: $limit, search: $search, status: $status, vendorId: $vendorId, billingId: $billingId, shippingId: $shippingId, dateFrom: $dateFrom, dateTo: $dateTo)'; } @override @@ -1872,7 +1967,10 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { (identical(other.status, status) || other.status == status) && (identical(other.vendorId, vendorId) || other.vendorId == vendorId) && - (identical(other.plantId, plantId) || other.plantId == plantId) && + (identical(other.billingId, billingId) || + other.billingId == billingId) && + (identical(other.shippingId, shippingId) || + other.shippingId == shippingId) && (identical(other.dateFrom, dateFrom) || other.dateFrom == dateFrom) && (identical(other.dateTo, dateTo) || other.dateTo == dateTo)); @@ -1886,7 +1984,8 @@ class _$PurchaseOrderListQueryImpl implements _PurchaseOrderListQuery { search, status, vendorId, - plantId, + billingId, + shippingId, dateFrom, dateTo, ); @@ -1911,7 +2010,8 @@ abstract class _PurchaseOrderListQuery implements PurchaseOrderListQuery { final String? search, final String? status, final int? vendorId, - final int? plantId, + final int? billingId, + final int? shippingId, final String? dateFrom, final String? dateTo, }) = _$PurchaseOrderListQueryImpl; @@ -1927,7 +2027,9 @@ abstract class _PurchaseOrderListQuery implements PurchaseOrderListQuery { @override int? get vendorId; @override - int? get plantId; + int? get billingId; + @override + int? get shippingId; @override String? get dateFrom; @override diff --git a/lib/shared/models/purchase_order_model.g.dart b/lib/shared/models/purchase_order_model.g.dart index 286e7dc..8f282b6 100644 --- a/lib/shared/models/purchase_order_model.g.dart +++ b/lib/shared/models/purchase_order_model.g.dart @@ -16,10 +16,10 @@ _$PurchaseOrderModelImpl _$$PurchaseOrderModelImplFromJson( vendorId: _intFromJsonNullable(json['vendor_id']), vendorName: _readVendorName(json, 'vendor_name') as String?, vendorType: _readVendorType(json, 'vendor_type') as String?, - plantId: _intFromJsonNullable(json['plant_id']), - plantName: _readPlantName(json, 'plant_name') as String?, - warehouseId: _intFromJsonNullable(json['warehouse_id']), - warehouseName: _readWarehouseName(json, 'warehouse_name') as String?, + billingId: _intFromJsonNullable(json['billing_id']), + billingName: _readBillingName(json, 'billing_name') as String?, + shippingId: _intFromJsonNullable(json['shipping_id']), + shippingName: _readShippingName(json, 'shipping_name') as String?, paymentTermId: _intFromJsonNullable(json['payment_term_id']), deliveryTermId: _intFromJsonNullable(json['delivery_term_id']), expectedDeliveryDate: _dateFromJsonNullable(json['expected_delivery_date']), @@ -28,6 +28,9 @@ _$PurchaseOrderModelImpl _$$PurchaseOrderModelImplFromJson( otherCharges: _doubleFromJsonNullable(json['other_charges']), taxableAmount: (_readSubTotal(json, 'sub_total') as num?)?.toDouble(), taxAmount: (_readTaxTotal(json, 'tax_total') as num?)?.toDouble(), + cgst: _doubleFromJsonNullable(json['cgst']), + sgst: _doubleFromJsonNullable(json['sgst']), + igst: _doubleFromJsonNullable(json['igst']), totalAmount: (_readGrandTotal(json, 'grand_total') as num?)?.toDouble(), termsAndConditions: json['terms_and_conditions'] as String?, remarks: json['remarks'] as String?, @@ -53,10 +56,10 @@ Map _$$PurchaseOrderModelImplToJson( 'vendor_id': instance.vendorId, 'vendor_name': instance.vendorName, 'vendor_type': instance.vendorType, - 'plant_id': instance.plantId, - 'plant_name': instance.plantName, - 'warehouse_id': instance.warehouseId, - 'warehouse_name': instance.warehouseName, + 'billing_id': instance.billingId, + 'billing_name': instance.billingName, + 'shipping_id': instance.shippingId, + 'shipping_name': instance.shippingName, 'payment_term_id': instance.paymentTermId, 'delivery_term_id': instance.deliveryTermId, 'expected_delivery_date': instance.expectedDeliveryDate?.toIso8601String(), @@ -65,6 +68,9 @@ Map _$$PurchaseOrderModelImplToJson( 'other_charges': instance.otherCharges, 'sub_total': instance.taxableAmount, 'tax_total': instance.taxAmount, + 'cgst': instance.cgst, + 'sgst': instance.sgst, + 'igst': instance.igst, 'grand_total': instance.totalAmount, 'terms_and_conditions': instance.termsAndConditions, 'remarks': instance.remarks, diff --git a/lib/shared/routes/app_router.dart b/lib/shared/routes/app_router.dart index 984a32c..058367a 100644 --- a/lib/shared/routes/app_router.dart +++ b/lib/shared/routes/app_router.dart @@ -8,6 +8,7 @@ import '../../modules/dashboard/presentation/screens/dashboard_screen.dart'; import '../../modules/assets/presentation/screens/asset_alerts_screen.dart'; import '../../modules/assets/presentation/screens/asset_detail_screen.dart'; import '../../modules/assets/presentation/screens/asset_list_screen.dart'; +import '../../modules/assets/presentation/screens/asset_maintenance_screen.dart'; import '../../modules/auth/presentation/screens/change_password_screen.dart'; import '../../modules/auth/presentation/screens/forgot_password_screen.dart'; import '../../modules/auth/presentation/screens/login_screen.dart'; @@ -298,6 +299,10 @@ final routerProvider = Provider((ref) { path: 'alerts', builder: (context, state) => const AssetAlertsScreen(), ), + GoRoute( + path: 'maintenance', + builder: (context, state) => const AssetMaintenanceScreen(), + ), GoRoute( path: ':id', builder: (context, state) => diff --git a/lib/shared/routes/menu_config.dart b/lib/shared/routes/menu_config.dart index 5a87925..c473112 100644 --- a/lib/shared/routes/menu_config.dart +++ b/lib/shared/routes/menu_config.dart @@ -60,6 +60,12 @@ const List appMenuItems = [ route: RouteConstants.assetAlerts, module: 'assets', ), + MenuItem( + label: 'My Maintenance', + icon: Icons.build_outlined, + route: RouteConstants.assetMaintenance, + module: 'assets', + ), ], ), MenuItem( diff --git a/lib/shared/widgets/app_responsive_filter_bar.dart b/lib/shared/widgets/app_responsive_filter_bar.dart index 835fc39..afcdaf7 100644 --- a/lib/shared/widgets/app_responsive_filter_bar.dart +++ b/lib/shared/widgets/app_responsive_filter_bar.dart @@ -41,8 +41,11 @@ class AppResponsiveFilterBar extends StatelessWidget { final hasTrailing = trailing != null; if (maxWidth >= _rowBreakpoint) { + final alignWithLabeledFilters = filters.isNotEmpty; return Row( - crossAxisAlignment: crossAxisAlignment, + crossAxisAlignment: alignWithLabeledFilters + ? crossAxisAlignment + : CrossAxisAlignment.center, children: [ Expanded(flex: searchFlex, child: search), for (final filter in filters) ...[ @@ -52,7 +55,7 @@ class AppResponsiveFilterBar extends StatelessWidget { if (hasTrailing) ...[ SizedBox(width: spacing), Padding( - padding: const EdgeInsets.only(top: 8), + padding: EdgeInsets.only(top: alignWithLabeledFilters ? 8 : 0), child: trailing!, ), ], @@ -61,6 +64,7 @@ class AppResponsiveFilterBar extends StatelessWidget { } if (maxWidth >= _stackBreakpoint) { + final alignWithLabeledFilters = filters.isNotEmpty; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -69,6 +73,7 @@ class AppResponsiveFilterBar extends StatelessWidget { Wrap( spacing: spacing, runSpacing: runSpacing, + crossAxisAlignment: WrapCrossAlignment.center, children: [ for (final filter in filters) SizedBox( @@ -87,7 +92,9 @@ class AppResponsiveFilterBar extends StatelessWidget { child: Align( alignment: Alignment.centerRight, child: Padding( - padding: const EdgeInsets.only(top: 8), + padding: EdgeInsets.only( + top: alignWithLabeledFilters ? 8 : 0, + ), child: trailing!, ), ), diff --git a/lib/shared/widgets/app_search_export_bar.dart b/lib/shared/widgets/app_search_export_bar.dart index 69caa12..83d2893 100644 --- a/lib/shared/widgets/app_search_export_bar.dart +++ b/lib/shared/widgets/app_search_export_bar.dart @@ -34,6 +34,7 @@ class AppSearchExportBar extends StatelessWidget { hintText: searchHint, prefixIcon: const Icon(Icons.search, size: 20), isDense: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), onChanged: onSearch, ); @@ -41,16 +42,19 @@ class AppSearchExportBar extends StatelessWidget { return AppResponsiveFilterBar( search: searchField, trailing: showExport - ? OutlinedButton.icon( - onPressed: isExporting ? null : onExport, - icon: isExporting - ? const SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Icon(Icons.download_outlined, size: 18), - label: Text(isExporting ? 'Exporting...' : 'Export'), + ? SizedBox( + height: 40, + child: OutlinedButton.icon( + onPressed: isExporting ? null : onExport, + icon: isExporting + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.download_outlined, size: 18), + label: Text(isExporting ? 'Exporting...' : 'Export'), + ), ) : null, );