This commit is contained in:
Surendiran 2026-07-17 09:54:15 +05:30
parent 36847aaf87
commit eb94390961
42 changed files with 3095 additions and 905 deletions

View File

@ -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';

View File

@ -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';

View File

@ -357,6 +357,55 @@ class AssetRemoteDataSource {
);
}
Future<PaginatedResponse<AssetModel>> 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<List<AssetMaintenanceLogModel>> getMaintenanceLogs(String assetId) async {
final response = await dio.get(ApiEndpoints.assetMaintenanceLogs(assetId));
return _parseList(response.data, AssetMaintenanceLogModel.fromJson);
}
Future<AssetMaintenanceLogModel> createMaintenanceLog(
String assetId,
Map<String, dynamic> data,
) async {
final response = await dio.post(
ApiEndpoints.assetMaintenanceLogs(assetId),
data: data,
);
return AssetMaintenanceLogModel.fromJson(
response.data['data'] as Map<String, dynamic>,
);
}
Future<AssetMaintenanceLogModel> getMaintenanceLogById(
String assetId,
String logId,
) async {
final response =
await dio.get(ApiEndpoints.assetMaintenanceLogById(assetId, logId));
return AssetMaintenanceLogModel.fromJson(
response.data['data'] as Map<String, dynamic>,
);
}
Future<void> deleteMaintenanceLog(String assetId, String logId) async {
await dio.delete(ApiEndpoints.assetMaintenanceLogById(assetId, logId));
}
Map<String, dynamic> _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,
};
}

View File

@ -262,6 +262,51 @@ class AssetRepositoryImpl implements AssetRepository {
return safeApiCall(() => dataSource.renewInsurancePolicy(assetId, policyId, data));
}
@override
Future<Result<PaginatedResponse<AssetModel>>> getMyMaintenanceAssets({
bool dueOnly = false,
int page = 1,
int limit = 20,
}) {
return safeApiCall(
() => dataSource.getMyMaintenanceAssets(
dueOnly: dueOnly,
page: page,
limit: limit,
),
);
}
@override
Future<Result<List<AssetMaintenanceLogModel>>> getMaintenanceLogs(
String assetId,
) {
return safeApiCall(() => dataSource.getMaintenanceLogs(assetId));
}
@override
Future<Result<AssetMaintenanceLogModel>> createMaintenanceLog(
String assetId,
Map<String, dynamic> data,
) {
return safeApiCall(() => dataSource.createMaintenanceLog(assetId, data));
}
@override
Future<Result<AssetMaintenanceLogModel>> getMaintenanceLogById(
String assetId,
String logId,
) {
return safeApiCall(
() => dataSource.getMaintenanceLogById(assetId, logId),
);
}
@override
Future<Result<void>> deleteMaintenanceLog(String assetId, String logId) {
return safeApiCall(() => dataSource.deleteMaintenanceLog(assetId, logId));
}
@override
Future<Result<List<EntityAttachmentModel>>> listAttachments(String assetId) {
return safeApiCall(() => dataSource.listAttachments(assetId));

View File

@ -89,6 +89,23 @@ abstract class AssetRepository {
String policyId,
Map<String, dynamic> data,
);
Future<Result<PaginatedResponse<AssetModel>>> getMyMaintenanceAssets({
bool dueOnly = false,
int page = 1,
int limit = 20,
});
Future<Result<List<AssetMaintenanceLogModel>>> getMaintenanceLogs(
String assetId,
);
Future<Result<AssetMaintenanceLogModel>> createMaintenanceLog(
String assetId,
Map<String, dynamic> data,
);
Future<Result<AssetMaintenanceLogModel>> getMaintenanceLogById(
String assetId,
String logId,
);
Future<Result<void>> deleteMaintenanceLog(String assetId, String logId);
Future<Result<List<EntityAttachmentModel>>> listAttachments(String assetId);
Future<Result<EntityAttachmentModel>> uploadAttachment(
String assetId, {

View File

@ -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<FilterOptionModel> plants;
final List<FilterOptionModel> locations;
final List<FilterOptionModel> departments;
final List<FilterOptionModel> warehouses;
final List<FilterOptionModel> vendors;
final List<FilterOptionModel> users;
final List<FilterOptionModel> purchaseOrders;
@ -50,9 +48,8 @@ final assetFormLookupsProvider =
FutureProvider.autoDispose<AssetFormLookups>((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,

View File

@ -118,10 +118,10 @@ class AssetsListNotifier extends AutoDisposeAsyncNotifier<AssetsListState> {
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<AssetAlertsState> {
state = AsyncData(await _load(current.copyWith(expiryPage: page)));
}
}
class MyMaintenanceState {
const MyMaintenanceState({
this.assets = const [],
this.dueOnly = true,
this.page = 1,
this.limit = 20,
this.total = 0,
this.totalPages = 1,
this.isRefreshing = false,
});
final List<AssetModel> assets;
final bool dueOnly;
final int page;
final int limit;
final int total;
final int totalPages;
final bool isRefreshing;
MyMaintenanceState copyWith({
List<AssetModel>? assets,
bool? dueOnly,
int? page,
int? limit,
int? total,
int? totalPages,
bool? isRefreshing,
}) {
return MyMaintenanceState(
assets: assets ?? this.assets,
dueOnly: dueOnly ?? this.dueOnly,
page: page ?? this.page,
limit: limit ?? this.limit,
total: total ?? this.total,
totalPages: totalPages ?? this.totalPages,
isRefreshing: isRefreshing ?? this.isRefreshing,
);
}
}
final myMaintenanceProvider =
AsyncNotifierProvider<MyMaintenanceNotifier, MyMaintenanceState>(
MyMaintenanceNotifier.new,
);
class MyMaintenanceNotifier extends AsyncNotifier<MyMaintenanceState> {
@override
Future<MyMaintenanceState> build() async {
return _load(const MyMaintenanceState());
}
Future<MyMaintenanceState> _load(MyMaintenanceState filters) async {
final repository = ref.read(assetRepositoryProvider);
final result = await repository.getMyMaintenanceAssets(
dueOnly: filters.dueOnly,
page: filters.page,
limit: filters.limit,
);
if (result.failure != null) throw result.failure!;
final page = result.data!;
return MyMaintenanceState(
assets: page.items,
dueOnly: filters.dueOnly,
page: page.page,
limit: filters.limit,
total: page.total,
totalPages: page.totalPages,
);
}
Future<void> refresh() async {
final current = state.valueOrNull ?? const MyMaintenanceState();
state = AsyncData(current.copyWith(isRefreshing: true));
try {
state = AsyncData(await _load(current));
} catch (e, st) {
state = AsyncError(e, st);
}
}
Future<void> setDueOnly(bool dueOnly) async {
final current = state.valueOrNull ?? const MyMaintenanceState();
state = const AsyncLoading();
state = AsyncData(
await _load(current.copyWith(dueOnly: dueOnly, page: 1)),
);
}
Future<void> setPage(int page) async {
final current = state.valueOrNull ?? const MyMaintenanceState();
state = const AsyncLoading();
state = AsyncData(await _load(current.copyWith(page: page)));
}
Future<void> setPageSize(int limit) async {
final current = state.valueOrNull ?? const MyMaintenanceState();
state = const AsyncLoading();
state = AsyncData(
await _load(current.copyWith(limit: limit, page: 1)),
);
}
}

View File

@ -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',
];

View File

@ -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<FilterOptionModel> 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});

View File

@ -69,7 +69,7 @@ class _AssetListScreenState extends ConsumerState<AssetListScreen> {
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<AssetListScreen> {
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<AssetCategoryModel> categories;
final List<FilterOptionModel> plants;
final List<FilterOptionModel> locations;
final List<AssetDropdownOption> statuses;
final ValueChanged<String> onSearch;
final ValueChanged<int?> onCategoryChanged;
final ValueChanged<int?> onPlantChanged;
final ValueChanged<int?> onLocationChanged;
final ValueChanged<String?> onStatusChanged;
@override
@ -315,13 +315,13 @@ class _AssetsFilterBar extends StatelessWidget {
),
];
final plantOptions = <AppDropdownOption<int?>>[
const AppDropdownOption(value: null, label: 'All Plants'),
for (final plant in plants)
if (int.tryParse(plant.id) != null)
final locationOptions = <AppDropdownOption<int?>>[
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<int?>(
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<String?>(
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,

View File

@ -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<void> _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<AssetModel> 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<AssetModel>(
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<AssetModel> 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,
),
),
);
}

View File

@ -78,12 +78,13 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
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<AssetFormPanel> {
String? _depreciationMethod;
DateTime? _warrantyExpiry;
DateTime? _purchaseDate;
DateTime? _commencementDate;
DateTime? _disposalDate;
List<AssetMaintenanceChecklistItem> _checklistItems = [];
bool _isActive = true;
bool _isSubmitting = false;
String? _populatedSignature;
@ -130,6 +133,7 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
_depreciationRateController.dispose();
_salvageValueController.dispose();
_remarksController.dispose();
_frequencyController.dispose();
super.dispose();
}
@ -139,7 +143,7 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
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<AssetFormPanel> {
_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<AssetFormPanel> {
_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<AssetFormPanel> {
_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<AssetFormPanel> {
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<AssetFormPanel> {
'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<AssetFormPanel> {
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<AssetFormPanel> {
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<AssetFormPanel> {
@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<AssetFormPanel> {
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<List<AssetCategoryModel>> categoriesAsync,
AsyncValue<List<FilterOptionModel>> plantsAsync,
) {
final subcategoriesAsync = ref.watch(itemSubcategoriesProvider(_categoryId));
final lookupsAsync = ref.watch(assetFormLookupsProvider);
@ -589,10 +637,10 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
),
),
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<AssetFormPanel> {
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<AssetFormPanel> {
),
],
),
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<AssetFormPanel> {
_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<AssetFormPanel> {
);
}
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<AssetFormPanel> {
options: dropdownOptions,
refreshLookups: () {
ref.invalidate(assetFormLookupsProvider);
ref.invalidate(assetPlantsProvider);
},
parseCreatedId: int.tryParse,
onChanged: onChanged,
@ -1128,33 +1276,125 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
);
}
Widget _plantDropdown(List<FilterOptionModel> plants) {
final plantIds = plants
.map((p) => int.tryParse(p.id))
Widget _locationDropdown(List<FilterOptionModel> locations) {
final locationIds = locations
.map((location) => int.tryParse(location.id))
.whereType<int>()
.toList();
return MasterQuickAddDropdown<int>(
masterId: 'plants',
label: 'Plant *',
value: _dropdownValue(_plantId, plantIds),
searchHint: 'Search plant...',
return AppSearchableDropdown<int>(
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<AssetMaintenanceChecklistItem> 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<List<FilterOptionModel>>((ref) async {
final dataSource = ref.watch(masterRemoteDataSourceProvider);
return dataSource.listPlants();
});
final itemSubcategoriesProvider =
FutureProvider.family<List<FilterOptionModel>, int?>((ref, categoryId) async {
if (categoryId == null) return [];

View File

@ -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<AssetMaintenanceChecklistItem> checklistForAsset(AssetModel asset) {
final fromSummary = asset.maintenance?.checklist;
if (fromSummary != null && fromSummary.isNotEmpty) {
return fromSummary;
}
return asset.maintenanceChecklistJson ?? const [];
}
Future<bool?> openSubmitMaintenancePanel(
BuildContext context,
WidgetRef ref, {
required AssetModel asset,
}) {
return showSidePanel<bool>(
context,
SubmitMaintenanceLogPanel(asset: asset),
width: 600,
);
}
class SubmitMaintenanceLogPanel extends ConsumerStatefulWidget {
const SubmitMaintenanceLogPanel({super.key, required this.asset});
final AssetModel asset;
@override
ConsumerState<SubmitMaintenanceLogPanel> 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<SubmitMaintenanceLogPanel> {
final _formKey = GlobalKey<FormState>();
final _remarksController = TextEditingController();
DateTime _performedDate = DateTime.now();
late final List<_ChecklistRowState> _rows;
bool _isSubmitting = false;
bool _showLogs = false;
List<AssetMaintenanceLogModel>? _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<void> _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<void> _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<void> _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 = <String, dynamic>{
'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<String?> 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<String>(
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(),
),
],
],
),
);
}
}

View File

@ -1242,10 +1242,9 @@ class _TransferAssetPanelState extends ConsumerState<TransferAssetPanel> {
final _formKey = GlobalKey<FormState>();
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<TransferAssetPanel> {
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<TransferAssetPanel> {
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<TransferAssetPanel> {
onPick: _pickDate,
),
const SizedBox(height: 12),
MasterQuickAddDropdown<int>(
masterId: 'plants',
label: 'To Plant',
value: _toPlantId,
searchHint: 'Search plant...',
options: lookups.plants
AppSearchableDropdown<int>(
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<TransferAssetPanel> {
})
.whereType<AppDropdownOption<int>>()
.toList(),
refreshLookups: () =>
ref.invalidate(assetFormLookupsProvider),
parseCreatedId: int.tryParse,
onChanged: (v) => setState(() => _toPlantId = v),
onChanged: (v) => setState(() => _toLocationId = v),
),
const SizedBox(height: 12),
MasterQuickAddDropdown<int>(
@ -1384,25 +1377,6 @@ class _TransferAssetPanelState extends ConsumerState<TransferAssetPanel> {
onChanged: (v) => setState(() => _toUserId = v),
),
const SizedBox(height: 12),
MasterQuickAddDropdown<int>(
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<AppDropdownOption<int>>()
.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,

View File

@ -23,7 +23,12 @@ final grnLookupsProvider = FutureProvider.autoDispose<GrnLookups>((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 = <PurchaseOrderModel>[];

View File

@ -112,8 +112,8 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
_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<GrnFormScreen> {
value: existing?.poNumber ?? '',
),
MasterQuickAddDropdown<int>(
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,

View File

@ -278,6 +278,12 @@ const masterDefinitions = <MasterDefinition>[
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>[
],
),
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<String, dynamic> 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';
}

View File

@ -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<MasterFormState, MasterForm
.getById(_definition, arg.recordId!);
if (result.failure != null) throw result.failure!;
values = Map<String, dynamic>.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<MasterFormState, MasterForm
continue;
}
if (key == 'location_states') {
try {
final states = await ref
.read(masterRemoteDataSourceProvider)
.listLocationStates();
options[lookupKey] = states
.map(
(option) => <String, dynamic>{
'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<MasterFormState, MasterForm
final value = current.values[field.key];
if (value == null || value == '') continue;
if (field.key == 'tags') {
final tags = _parseTags(value);
if (tags.isNotEmpty) payload['tags'] = tags;
continue;
}
payload[field.key] = switch (field.type) {
MasterFieldType.number => 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<MasterFormState, MasterForm
return payload;
}
List<String> _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<String?> submit() async {
final current = state.valueOrNull ?? const MasterFormState();

View File

@ -99,6 +99,9 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
labelText: _fieldLabel(field),
floatingLabelBehavior: FloatingLabelBehavior.always,
alignLabelWithHint: true,
hintText: field.key == 'tags'
? 'Comma-separated, e.g. critical, imported, rm'
: null,
);
}

View File

@ -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',

View File

@ -23,6 +23,77 @@ class MasterRemoteDataSource {
Future<List<FilterOptionModel>> listPlants() =>
_listOptions(ApiEndpoints.plants);
/// Plants and warehouses from the unified locations master.
/// Labels include type, e.g. "Chennai Plant (Plant)".
Future<List<FilterOptionModel>> listLocations() async {
final result = await listLocationsWithState();
return result.options;
}
/// Indian states/UTs for location.state (same values as vendor source_of_supply).
Future<List<FilterOptionModel>> 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<String, dynamic>.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<FilterOptionModel>()
.toList();
}
/// Locations plus each row's `state` (for PO GST intra/inter preview).
Future<({List<FilterOptionModel> options, Map<String, String?> stateById})>
listLocationsWithState() async {
final rows = await _listAllMaps(ApiEndpoints.locations);
final options = <FilterOptionModel>[];
final stateById = <String, String?>{};
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<List<FilterOptionModel>> listDesignations() =>
_listOptions(ApiEndpoints.designations);

View File

@ -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,
};

View File

@ -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<FilterOptionModel> vendors;
final List<FilterOptionModel> plants;
final List<FilterOptionModel> warehouses;
/// Billing / shipping options (plants + warehouses from locations master).
final List<FilterOptionModel> locations;
/// Vendor id `source_of_supply` (state code/name) for GST split preview.
final Map<String, String?> vendorSourceOfSupplyById;
/// Location id `state` for GST split preview.
final Map<String, String?> locationStateById;
final List<FilterOptionModel> paymentTerms;
final List<FilterOptionModel> deliveryTerms;
final List<FilterOptionModel> 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<List<FilterOptionModel>> _safeOptions(
}
}
Future<
({
List<FilterOptionModel> options,
Map<String, String?> sourceOfSupplyById,
})> _safeVendorsWithSos(VendorRepository vendorRepo) async {
try {
final result = await vendorRepo.listVendorOptions();
if (result.failure != null) {
throw result.failure!;
}
final options = <FilterOptionModel>[];
final sourceOfSupplyById = <String, String?>{};
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: <FilterOptionModel>[],
sourceOfSupplyById: <String, String?>{},
);
}
}
Future<
({
List<FilterOptionModel> options,
Map<String, String?> stateById,
})> _safeLocationsWithState(MasterRemoteDataSource master) async {
try {
return await master.listLocationsWithState();
} catch (_) {
return (options: <FilterOptionModel>[], stateById: <String, String?>{});
}
}
Future<
({
List<FilterOptionModel> options,
@ -146,24 +197,3 @@ Future<({List<FilterOptionModel> options, Map<String, int?> gstRateByHsnId})>
return (options: <FilterOptionModel>[], gstRateByHsnId: <String, int?>{});
}
}
Future<List<FilterOptionModel>> _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();
}

View File

@ -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<Widget> _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),

View File

@ -51,8 +51,8 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
DateTime? _poDate;
DateTime? _expectedDeliveryDate;
int? _vendorId;
int? _plantId;
int? _warehouseId;
int? _billingId;
int? _shippingId;
int? _paymentTermId;
int? _deliveryTermId;
final List<PoLineItemDraft> _lines = [];
@ -98,8 +98,8 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
_poDate = order.poDate ?? DateTime.now();
_expectedDeliveryDate = order.expectedDeliveryDate;
_vendorId = order.vendorId;
_plantId = order.plantId;
_warehouseId = order.warehouseId;
_billingId = order.billingId;
_shippingId = order.shippingId;
_paymentTermId = order.paymentTermId;
_deliveryTermId = order.deliveryTermId;
_discountController.text =
@ -183,12 +183,30 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
);
}
/// Mirrors BE: compare billing location state vs vendor source_of_supply.
/// Missing either side intra-state (CGST/SGST).
bool _isInterStateGst(PurchaseOrderLookups lookups) {
final billingState = lookups.locationStateById[_billingId?.toString()]
?.trim()
.toLowerCase();
final vendorSos = lookups.vendorSourceOfSupplyById[_vendorId?.toString()]
?.trim()
.toLowerCase();
if (billingState == null ||
billingState.isEmpty ||
vendorSos == null ||
vendorSos.isEmpty) {
return false;
}
return billingState != vendorSos;
}
Map<String, dynamic> _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<PurchaseOrderFormScree
return;
}
if (_vendorId == null || _plantId == null) {
if (_vendorId == null || _billingId == null || _shippingId == null) {
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please complete all required fields')),
);
@ -352,8 +370,8 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
final vendorIds =
lookups.vendors.map((e) => _parseId(e.id)).whereType<int>();
final plantIds =
lookups.plants.map((e) => _parseId(e.id)).whereType<int>();
final locationIds =
lookups.locations.map((e) => _parseId(e.id)).whereType<int>();
final totals = _computeTotals(lookups.gstRatePctById);
final theme = Theme.of(context);
@ -373,105 +391,94 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
const SizedBox(height: 16),
_SectionCard(
title: 'ORDER DETAILS',
child: Column(
children: [
FormRowFour(
children: [
_DateField(
label: 'PO Date *',
value: _poDate,
onTap: () => _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<int>(
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<int>(
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<int>(
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<int?>(
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<int?>(
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<int>(
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<int>(
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<int?>(
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<int?>(
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<int?>(
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<PurchaseOrderFormScree
otherChargesController: _otherChargesController,
discountController: _discountController,
isEditing: widget.isEditing,
isInterState: _isInterStateGst(lookups),
);
if (stack) {
@ -764,6 +772,7 @@ class _AmountSummaryCard extends StatelessWidget {
required this.otherChargesController,
required this.discountController,
required this.isEditing,
required this.isInterState,
});
final PoOrderTotals totals;
@ -771,6 +780,7 @@ class _AmountSummaryCard extends StatelessWidget {
final TextEditingController otherChargesController;
final TextEditingController discountController;
final bool isEditing;
final bool isInterState;
String? _validateDiscount(String? value) {
final text = value?.trim() ?? '';
@ -784,6 +794,34 @@ class _AmountSummaryCard extends StatelessWidget {
return null;
}
List<Widget> _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,

View File

@ -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<PoLineCalculation> 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<AppDropdownOption<int?>>().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 = <Widget>[
flex(
flex: 3,
child: MasterQuickAddDropdown<int>(
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<int>(
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<int?>(
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<int>(
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<int>(
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<int?>(
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 (23 columns)
return ResponsiveFormGrid(
spacing: spacing,
smallColumns: 1,
mediumColumns: 2,
largeColumns: 3,
mediumBreakpoint: 520,
largeBreakpoint: 800,
children: [
itemField,
qtyField,
uomField,
rateField,
discField,
gstField,
amountField,
],
);
},
),
),

View File

@ -66,7 +66,11 @@ class AddUserFormNotifier extends FamilyAsyncNotifier<AddUserFormState, String?>
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();

View File

@ -187,6 +187,7 @@ class _AddUserPanelState extends ConsumerState<AddUserPanel> {
String? hint,
bool required = false,
String? masterId,
Map<String, dynamic>? initialValues,
}) {
final fieldLabel = required ? '$label *' : label;
final fieldHint = hint ?? 'Select ${label.toLowerCase()}';
@ -215,6 +216,7 @@ class _AddUserPanelState extends ConsumerState<AddUserPanel> {
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<AddUserPanel> {
label: 'Plant / Unit',
value: _selectedPlantId,
options: formState.plants,
masterId: 'plants',
masterId: 'locations',
initialValues: const {'type': 'plant'},
onChanged: (v) => setState(() => _selectedPlantId = v),
),
right: _buildDropdown(

View File

@ -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,

View File

@ -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<ReportFilterOption> plants;
final List<ReportFilterOption> locations;
final List<ReportFilterOption> categories;
final List<ReportFilterOption> subcategories;
final List<ReportFilterOption> 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,

View File

@ -145,14 +145,14 @@ class DepreciationReportNotifier
);
}
Future<void> setPlantId(String? value) async {
Future<void> 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,
),
);
}

View File

@ -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<String> onSearch;
final ValueChanged<String?> onPlantChanged;
final ValueChanged<String?> onLocationChanged;
final ValueChanged<String?> onCategoryChanged;
final ValueChanged<String?> onSubcategoryChanged;
final ValueChanged<String?> 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',

View File

@ -45,10 +45,21 @@ Object? _readItemCategoryName(Map<dynamic, dynamic> json, String key) {
_readNestedName(json, 'asset_category');
}
Object? _readPlantName(Map<dynamic, dynamic> json, String key) {
final flat = json['plant_name'];
Object? _readLocationName(Map<dynamic, dynamic> 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<dynamic, dynamic> json, String key) {
@ -61,10 +72,10 @@ Object? _readItemCategoryId(Map<dynamic, dynamic> json, String key) {
return null;
}
Object? _readPlantId(Map<dynamic, dynamic> json, String key) {
final flat = json['plant_id'];
Object? _readLocationId(Map<dynamic, dynamic> 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<dynamic, dynamic> json, String key) {
return _readNestedName(json, 'department');
}
Object? _readWarehouseName(Map<dynamic, dynamic> json, String key) {
final flat = json['warehouse_name'];
if (flat is String && flat.isNotEmpty) return flat;
return _readNestedName(json, 'warehouse');
}
Object? _readVendorNameFromNested(Map<dynamic, dynamic> 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<AssetMaintenanceChecklistItem>? 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<String, dynamic> 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<String, dynamic> json) {
return AssetMaintenanceChecklistItem(
key: json['key']?.toString() ?? '',
label: json['label']?.toString() ?? '',
required: json['required'] == true,
);
}
Map<String, dynamic> 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<AssetMaintenanceChecklistItem> checklist;
static AssetMaintenanceSummary? fromJsonNullable(Object? value) {
if (value is! Map) return null;
return AssetMaintenanceSummary.fromJson(Map<String, dynamic>.from(value));
}
static Object? toJsonNullable(AssetMaintenanceSummary? value) {
if (value == null) return null;
return value.toJson();
}
Map<String, dynamic> 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<String, dynamic> 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 = <AssetMaintenanceChecklistItem>[];
if (rawChecklist is List) {
for (final item in rawChecklist) {
if (item is Map) {
checklist.add(
AssetMaintenanceChecklistItem.fromJson(
Map<String, dynamic>.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<Map<String, dynamic>> checklistJson;
final DateTime? createdAt;
final String? createdByName;
factory AssetMaintenanceLogModel.fromJson(Map<String, dynamic> 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 = <Map<String, dynamic>>[];
if (rawChecklist is List) {
for (final item in rawChecklist) {
if (item is Map) {
checklist.add(Map<String, dynamic>.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<AssetMaintenanceChecklistItem>? _checklistFromJson(Object? value) {
if (value is! List) return null;
return value
.whereType<Map>()
.map(
(item) => AssetMaintenanceChecklistItem.fromJson(
Map<String, dynamic>.from(item),
),
)
.where((item) => item.key.isNotEmpty)
.toList();
}
Object? _checklistToJson(List<AssetMaintenanceChecklistItem>? value) {
if (value == null) return null;
return value.map((item) => item.toJson()).toList();
}
Object? _readAlertLocationName(Map<dynamic, dynamic> 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<String, dynamic> 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?,
);

View File

@ -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<AssetMaintenanceChecklistItem>? 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<String, dynamic> 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<AssetMaintenanceChecklistItem>? 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<AssetMaintenanceChecklistItem>?,
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<AssetMaintenanceChecklistItem>? 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<AssetMaintenanceChecklistItem>?,
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<AssetMaintenanceChecklistItem>? 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<String, dynamic> 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<AssetMaintenanceChecklistItem>? _maintenanceChecklistJson;
@override
@JsonKey(
name: 'maintenance_checklist_json',
fromJson: _checklistFromJson,
toJson: _checklistToJson,
)
List<AssetMaintenanceChecklistItem>? 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<AssetMaintenanceChecklistItem>? 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<String, dynamic> 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<AssetMaintenanceChecklistItem>? 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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.

View File

@ -57,18 +57,26 @@ _$AssetModelImpl _$$AssetModelImplFromJson(Map<String, dynamic> 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<String, dynamic> json) =>
isActive: json['is_active'] as bool? ?? true,
createdAt: _dateFromJsonNullable(json['created_at']),
updatedAt: _dateFromJsonNullable(json['updated_at']),
maintenance: AssetMaintenanceSummary.fromJsonNullable(
json['maintenance'],
),
);
Map<String, dynamic> _$$AssetModelImplToJson(_$AssetModelImpl instance) =>
<String, dynamic>{
'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<String, dynamic> _$$AssetModelImplToJson(
_$AssetModelImpl instance,
) => <String, dynamic>{
'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<String, dynamic> 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<String, dynamic> _$$AssetAlertModelImplToJson(
@ -331,5 +348,5 @@ Map<String, dynamic> _$$AssetAlertModelImplToJson(
'due_date': instance.dueDate?.toIso8601String(),
'days_remaining': instance.daysRemaining,
'status': instance.status,
'plant_name': instance.plantName,
'location_name': instance.locationName,
};

View File

@ -49,11 +49,32 @@ Object? _readVendorType(Map<dynamic, dynamic> json, String key) {
return null;
}
Object? _readPlantName(Map<dynamic, dynamic> json, String key) =>
_readNestedName(json, 'plant_name', 'plant');
Object? _readLocationDisplayName(
Map<dynamic, dynamic> 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<dynamic, dynamic> json, String key) =>
_readNestedName(json, 'warehouse_name', 'warehouse');
Object? _readBillingName(Map<dynamic, dynamic> json, String key) =>
_readLocationDisplayName(json, 'billing_name', 'billing');
Object? _readShippingName(Map<dynamic, dynamic> json, String key) =>
_readLocationDisplayName(json, 'shipping_name', 'shipping');
Object? _readItemName(Map<dynamic, dynamic> 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;

View File

@ -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

View File

@ -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<String, dynamic> _$$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<String, dynamic> _$$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,

View File

@ -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<GoRouter>((ref) {
path: 'alerts',
builder: (context, state) => const AssetAlertsScreen(),
),
GoRoute(
path: 'maintenance',
builder: (context, state) => const AssetMaintenanceScreen(),
),
GoRoute(
path: ':id',
builder: (context, state) =>

View File

@ -60,6 +60,12 @@ const List<MenuItem> appMenuItems = [
route: RouteConstants.assetAlerts,
module: 'assets',
),
MenuItem(
label: 'My Maintenance',
icon: Icons.build_outlined,
route: RouteConstants.assetMaintenance,
module: 'assets',
),
],
),
MenuItem(

View File

@ -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!,
),
),

View File

@ -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,
);