review changes done

This commit is contained in:
Surendiran 2026-07-27 09:15:35 +05:30
parent 167bd99e0a
commit 048ecab961
12 changed files with 1219 additions and 244 deletions

View File

@ -6,6 +6,7 @@ import 'package:intl/intl.dart';
import '../../../../core/constants/enums.dart'; import '../../../../core/constants/enums.dart';
import '../../../../core/constants/route_constants.dart'; import '../../../../core/constants/route_constants.dart';
import '../../../../core/errors/failure.dart'; import '../../../../core/errors/failure.dart';
import '../../../../core/utils/formatters.dart';
import '../../../../shared/models/asset_model.dart'; import '../../../../shared/models/asset_model.dart';
import '../../../../shared/models/entity_attachment_model.dart'; import '../../../../shared/models/entity_attachment_model.dart';
import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/models/user_management_models.dart';
@ -23,6 +24,7 @@ import '../../../../shared/widgets/app_side_panel.dart';
import '../../../../shared/widgets/app_segmented_tab_bar.dart'; import '../../../../shared/widgets/app_segmented_tab_bar.dart';
import '../providers/assets_provider.dart'; import '../providers/assets_provider.dart';
import '../providers/asset_form_lookups_provider.dart'; import '../providers/asset_form_lookups_provider.dart';
import '../utils/maintenance_due_display.dart';
import 'asset_form_screen.dart'; import 'asset_form_screen.dart';
import '../widgets/asset_maintenance_panel.dart'; import '../widgets/asset_maintenance_panel.dart';
import '../widgets/asset_side_panels.dart'; import '../widgets/asset_side_panels.dart';
@ -236,6 +238,13 @@ class _OverviewTab extends ConsumerWidget {
asset.maintenanceInchargeUserId, asset.maintenanceInchargeUserId,
users, users,
); );
final daysUntilDueDisplay = MaintenanceDueDisplay.fromDaysUntilDue(
asset.maintenance?.daysUntilDue,
);
final retainedPct = _retainedPercentage(
currentValue: asset.resolvedCurrentValue,
purchaseCost: asset.purchaseCost,
);
return SingleChildScrollView( return SingleChildScrollView(
child: Center( child: Center(
@ -267,95 +276,176 @@ class _OverviewTab extends ConsumerWidget {
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 16),
_AssetInfoGrid(
items: [ // 1) Top Summary
_AssetInfo('Asset Name', asset.assetName), _AssetOverviewSection(
_AssetInfo('Asset Code', asset.assetCode ?? ''), title: 'Summary',
_AssetInfo('Category', asset.assetCategoryName ?? ''), child: _AssetInfoGrid(
_AssetInfo( items: [
'Subcategory', _AssetInfo.widget(
asset.assetSubcategoryName ?? '', 'Status',
), AppStatusChip(status: asset.status ?? 'IN_USE'),
_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( _AssetInfo(
'Next Due Date', 'Maintenance Due',
asset.maintenance!.nextDueDate != null asset.maintenance == null
? dateFormat ? ''
.format(asset.maintenance!.nextDueDate!) : (asset.maintenance!.isDue ? 'Yes' : 'No'),
),
_AssetInfo(
'Current Value',
asset.resolvedCurrentValue != null
? CurrencyFormatter.format(
asset.resolvedCurrentValue,
)
: '',
),
_AssetInfo.widget(
'Active',
_ActiveDotIndicator(isActive: asset.isActive),
),
],
),
),
// 2) Identity
_AssetOverviewSection(
title: 'Identity',
child: _AssetInfoGrid(
items: [
_AssetInfo('Asset Name', asset.assetName),
_AssetInfo('Asset Code', asset.assetCode ?? ''),
_AssetInfo('Location', asset.locationName ?? ''),
_AssetInfo(
'Asset Category',
asset.assetCategoryName ?? '',
),
_AssetInfo(
'Subcategory',
asset.assetSubcategoryName ?? '',
),
_AssetInfo(
'Manufacturer',
asset.manufacturer ?? '',
),
_AssetInfo(
'Brand / Model',
asset.brandModel ?? '',
),
_AssetInfo(
'Serial Number',
asset.serialNumber ?? '',
),
],
),
),
// 3) Maintenance
_AssetOverviewSection(
title: 'Maintenance',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_AssetInfoGrid(
items: [
_AssetInfo(
'Maintenance Incharge',
maintenanceInchargeLabel,
),
_AssetInfo(
'Maintenance Frequency',
asset.maintenanceFrequencyInDays != null
? '${asset.maintenanceFrequencyInDays} days'
: '',
),
_AssetInfo(
'Next Due Date',
asset.maintenance?.nextDueDate != null
? dateFormat.format(
asset.maintenance!.nextDueDate!,
)
: '',
),
if (daysUntilDueDisplay == null)
const _AssetInfo('Days Until Due', '')
else
_AssetInfo.widget(
'Days Until Due',
Text(
daysUntilDueDisplay.label,
style: TextStyle(
color: daysUntilDueDisplay.color,
fontWeight: FontWeight.w600,
),
),
),
_AssetInfo(
'Condition',
assetConditionLabel(asset.condition),
),
],
),
if (asset.maintenance != null) ...[
const SizedBox(height: 12),
_MaintenanceDueCallout(
isDue: asset.maintenance!.isDue,
daysUntilDue: asset.maintenance!.daysUntilDue,
display: daysUntilDueDisplay,
),
],
],
),
),
// 4) Purchase & Warranty
_AssetOverviewSection(
title: 'Purchase & Warranty',
child: _AssetInfoGrid(
items: [
_AssetInfo(
'Purchase Date',
asset.purchaseDate != null
? dateFormat.format(asset.purchaseDate!)
: '', : '',
), ),
_AssetInfo( _AssetInfo(
'Days Until Due', 'Purchase Cost',
asset.maintenance!.daysUntilDue?.toString() ?? '', asset.purchaseCost != null
? CurrencyFormatter.format(asset.purchaseCost)
: '',
),
_AssetInfo(
'Warranty Expiry',
asset.warrantyExpiryDate != null
? dateFormat.format(asset.warrantyExpiryDate!)
: '',
), ),
], ],
_AssetInfo('Serial Number', asset.serialNumber ?? ''), ),
_AssetInfo('Brand / Model', asset.brandModel ?? ''),
_AssetInfo('Manufacturer', asset.manufacturer ?? ''),
_AssetInfo(
'Purchase Date',
asset.purchaseDate != null
? dateFormat.format(asset.purchaseDate!)
: '',
),
_AssetInfo(
'Warranty Expiry',
asset.warrantyExpiryDate != null
? dateFormat.format(asset.warrantyExpiryDate!)
: '',
),
_AssetInfo(
'Purchase Cost',
asset.purchaseCost != null
? '${asset.purchaseCost}'
: '',
),
_AssetInfo(
'Useful Life',
asset.usefulLifeYears != null
? '${asset.usefulLifeYears} years'
: '',
),
_AssetInfo(
'Depreciation',
asset.depreciationMethod != null
? '${asset.depreciationMethod}'
'${asset.depreciationRate != null ? ' (${asset.depreciationRate}%)' : ''}'
: '',
),
_AssetInfo(
'Condition',
assetConditionLabel(asset.condition),
),
_AssetInfo.widget(
'Status',
AppStatusChip(status: asset.status ?? 'IN_USE'),
),
_AssetInfo('Active', asset.isActive ? 'Yes' : 'No'),
],
), ),
// 5) Valuation & Depreciation
_AssetOverviewSection(
title: 'Valuation & Depreciation',
showDivider: false,
child: _ValuationDepreciationCard(
currentValue: asset.resolvedCurrentValue,
purchaseCost: asset.purchaseCost,
retainedPct: retainedPct,
commencementDate: asset.commencementDate,
usefulLifeYears: asset.usefulLifeYears,
depreciationMethod: asset.depreciationMethod,
depreciationRate: asset.depreciationRate,
annualDepreciation:
asset.depreciation?.annualDepreciation,
depreciatedAmount: asset.depreciatedAmount,
salvageValue: asset.salvageValue,
salvagePercentage: asset.salvagePercentage,
dateFormat: dateFormat,
),
),
if (asset.maintenanceFrequencyInDays != null || if (asset.maintenanceFrequencyInDays != null ||
asset.maintenanceChecklistJson?.isNotEmpty == true || asset.maintenanceChecklistJson?.isNotEmpty == true ||
asset.maintenance != null) ...[ asset.maintenance != null) ...[
@ -434,6 +524,388 @@ class _OverviewTab extends ConsumerWidget {
} }
} }
double? _retainedPercentage({
required double? currentValue,
required double? purchaseCost,
}) {
if (currentValue == null || purchaseCost == null || purchaseCost <= 0) {
return null;
}
return (currentValue / purchaseCost * 100).clamp(0, 100);
}
class _AssetOverviewSection extends StatelessWidget {
const _AssetOverviewSection({
required this.title,
required this.child,
this.showDivider = true,
});
final String title;
final Widget child;
final bool showDivider;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
title.toUpperCase(),
style: theme.textTheme.labelMedium?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w700,
letterSpacing: 0.4,
),
),
const SizedBox(height: 12),
child,
if (showDivider)
const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Divider(height: 1),
)
else
const SizedBox(height: 8),
],
);
}
}
class _ActiveDotIndicator extends StatelessWidget {
const _ActiveDotIndicator({required this.isActive});
final bool isActive;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final color =
isActive ? const Color(0xFF16A34A) : theme.colorScheme.onSurfaceVariant;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
Text(
isActive ? 'Active' : 'Inactive',
style: theme.textTheme.bodyLarge?.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
),
],
);
}
}
class _MaintenanceDueCallout extends StatelessWidget {
const _MaintenanceDueCallout({
required this.isDue,
required this.daysUntilDue,
required this.display,
});
final bool isDue;
final int? daysUntilDue;
final MaintenanceDueDisplay? display;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final color = display?.color ?? theme.colorScheme.onSurfaceVariant;
final suffix = () {
if (daysUntilDue == null) return null;
if (daysUntilDue! > 0) {
return 'next service in $daysUntilDue day${daysUntilDue == 1 ? '' : 's'}';
}
if (daysUntilDue == 0) return 'due today';
final overdueBy = daysUntilDue!.abs();
return 'overdue by $overdueBy day${overdueBy == 1 ? '' : 's'}';
}();
final text = suffix == null
? 'Maintenance Due: ${isDue ? 'Yes' : 'No'}'
: 'Maintenance Due: ${isDue ? 'Yes' : 'No'}$suffix';
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withValues(alpha: 0.28)),
),
child: Text(
text,
style: theme.textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
),
);
}
}
class _ValuationDepreciationCard extends StatelessWidget {
const _ValuationDepreciationCard({
required this.currentValue,
required this.purchaseCost,
required this.retainedPct,
required this.commencementDate,
required this.usefulLifeYears,
required this.depreciationMethod,
required this.depreciationRate,
required this.annualDepreciation,
required this.depreciatedAmount,
required this.salvageValue,
required this.salvagePercentage,
required this.dateFormat,
});
final double? currentValue;
final double? purchaseCost;
final double? retainedPct;
final DateTime? commencementDate;
final int? usefulLifeYears;
final String? depreciationMethod;
final double? depreciationRate;
final double? annualDepreciation;
final double? depreciatedAmount;
final double? salvageValue;
final double? salvagePercentage;
final DateFormat dateFormat;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final onCard = scheme.onPrimary;
final muted = onCard.withValues(alpha: 0.72);
final progress = retainedPct == null ? 0.0 : retainedPct! / 100;
final subtitleParts = <String>[];
if (purchaseCost != null) {
subtitleParts.add(
'of ${CurrencyFormatter.format(purchaseCost)} purchase cost',
);
}
if (commencementDate != null) {
subtitleParts.add('commenced ${dateFormat.format(commencementDate!)}');
}
final methodLabel = depreciationMethod == null
? ''
: '$depreciationMethod'
'${depreciationRate != null ? ' ($depreciationRate%)' : ''}';
final detailItems = <(String, String)>[
(
'Commencement Date',
commencementDate != null ? dateFormat.format(commencementDate!) : '',
),
(
'Useful Life',
usefulLifeYears != null ? '$usefulLifeYears years' : '',
),
('Depreciation Method', methodLabel),
(
'Annual Depreciation',
annualDepreciation != null
? CurrencyFormatter.format(annualDepreciation)
: '',
),
(
'Depreciated Amount',
depreciatedAmount != null
? CurrencyFormatter.format(depreciatedAmount)
: '',
),
(
'Salvage Value',
salvageValue != null ? CurrencyFormatter.format(salvageValue) : '',
),
(
'Salvage Percentage',
salvagePercentage != null ? '$salvagePercentage%' : '',
),
];
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
scheme.primary,
Color.lerp(scheme.primary, scheme.secondary, 0.55)!,
scheme.secondary,
],
stops: const [0, 0.55, 1],
),
boxShadow: [
BoxShadow(
color: scheme.primary.withValues(alpha: 0.28),
blurRadius: 18,
offset: const Offset(0, 8),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 88,
height: 88,
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 88,
height: 88,
child: CircularProgressIndicator(
value: progress,
strokeWidth: 7,
backgroundColor: onCard.withValues(alpha: 0.18),
color: Color.lerp(scheme.secondary, Colors.white, 0.45)!,
strokeCap: StrokeCap.round,
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
retainedPct == null
? ''
: '${retainedPct!.toStringAsFixed(2)}%',
style: theme.textTheme.titleMedium?.copyWith(
color: onCard,
fontWeight: FontWeight.w800,
),
),
Text(
'RETAINED',
style: theme.textTheme.labelSmall?.copyWith(
color: muted,
fontWeight: FontWeight.w600,
letterSpacing: 0.6,
fontSize: 9,
),
),
],
),
],
),
),
const SizedBox(width: 18),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'CURRENT VALUE',
style: theme.textTheme.labelSmall?.copyWith(
color: muted,
fontWeight: FontWeight.w600,
letterSpacing: 0.8,
),
),
const SizedBox(height: 4),
Text(
currentValue != null
? CurrencyFormatter.format(currentValue)
: '',
style: theme.textTheme.headlineSmall?.copyWith(
color: onCard,
fontWeight: FontWeight.w800,
),
),
if (subtitleParts.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
subtitleParts.join(' · '),
style: theme.textTheme.bodySmall?.copyWith(
color: muted,
),
),
],
],
),
),
],
),
const SizedBox(height: 18),
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: onCard.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: onCard.withValues(alpha: 0.12)),
),
child: LayoutBuilder(
builder: (context, constraints) {
final cols = constraints.maxWidth < 560
? 2
: constraints.maxWidth < 820
? 3
: 4;
const spacing = 16.0;
final colWidth = (constraints.maxWidth - spacing * (cols - 1)) /
cols;
return Wrap(
spacing: spacing,
runSpacing: 14,
children: [
for (final item in detailItems)
SizedBox(
width: colWidth,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.$1,
style: theme.textTheme.labelSmall?.copyWith(
color: muted,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
item.$2,
style: theme.textTheme.bodyMedium?.copyWith(
color: onCard,
fontWeight: FontWeight.w700,
),
),
],
),
),
],
);
},
),
),
],
),
);
}
}
class _AssetInfoGrid extends StatelessWidget { class _AssetInfoGrid extends StatelessWidget {
const _AssetInfoGrid({required this.items}); const _AssetInfoGrid({required this.items});

View File

@ -82,6 +82,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
final _usefulLifeController = TextEditingController(); final _usefulLifeController = TextEditingController();
final _depreciationRateController = TextEditingController(); final _depreciationRateController = TextEditingController();
final _salvageValueController = TextEditingController(); final _salvageValueController = TextEditingController();
final _salvagePercentageController = TextEditingController();
final _remarksController = TextEditingController(); final _remarksController = TextEditingController();
final _frequencyController = TextEditingController(); final _frequencyController = TextEditingController();
int? _categoryId; int? _categoryId;
@ -97,6 +98,8 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
String? _status; String? _status;
String? _condition; String? _condition;
String? _depreciationMethod; String? _depreciationMethod;
/// When true, salvage is entered as % of purchase cost; otherwise as amount.
bool _salvageAsPercentage = false;
DateTime? _warrantyExpiry; DateTime? _warrantyExpiry;
DateTime? _purchaseDate; DateTime? _purchaseDate;
DateTime? _commencementDate; DateTime? _commencementDate;
@ -123,6 +126,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
super.initState(); super.initState();
_costController.addListener(_onDepreciationFieldChanged); _costController.addListener(_onDepreciationFieldChanged);
_salvageValueController.addListener(_onDepreciationFieldChanged); _salvageValueController.addListener(_onDepreciationFieldChanged);
_salvagePercentageController.addListener(_onDepreciationFieldChanged);
_usefulLifeController.addListener(_onDepreciationFieldChanged); _usefulLifeController.addListener(_onDepreciationFieldChanged);
_depreciationRateController.addListener(_onDepreciationFieldChanged); _depreciationRateController.addListener(_onDepreciationFieldChanged);
} }
@ -142,6 +146,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
_scrollController.dispose(); _scrollController.dispose();
_costController.removeListener(_onDepreciationFieldChanged); _costController.removeListener(_onDepreciationFieldChanged);
_salvageValueController.removeListener(_onDepreciationFieldChanged); _salvageValueController.removeListener(_onDepreciationFieldChanged);
_salvagePercentageController.removeListener(_onDepreciationFieldChanged);
_usefulLifeController.removeListener(_onDepreciationFieldChanged); _usefulLifeController.removeListener(_onDepreciationFieldChanged);
_depreciationRateController.removeListener(_onDepreciationFieldChanged); _depreciationRateController.removeListener(_onDepreciationFieldChanged);
_nameController.dispose(); _nameController.dispose();
@ -157,6 +162,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
_usefulLifeController.dispose(); _usefulLifeController.dispose();
_depreciationRateController.dispose(); _depreciationRateController.dispose();
_salvageValueController.dispose(); _salvageValueController.dispose();
_salvagePercentageController.dispose();
_remarksController.dispose(); _remarksController.dispose();
_frequencyController.dispose(); _frequencyController.dispose();
super.dispose(); super.dispose();
@ -166,6 +172,71 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
_triggerPreviewRecalculation(); _triggerPreviewRecalculation();
} }
/// Backend renamed OTHER CUSTOM; accept both while options refresh.
String? get _normalizedDepreciationMethod {
final method = _depreciationMethod?.trim().toUpperCase();
if (method == null || method.isEmpty) return null;
if (method == 'OTHER') return 'CUSTOM';
return method;
}
bool get _requiresCustomRate {
final method = _normalizedDepreciationMethod;
return method == 'CUSTOM';
}
void _applySalvageFromAsset(AssetModel asset) {
_salvageValueController.text =
CurrencyFormatter.formatEditable(asset.salvageValue);
_salvagePercentageController.text =
asset.salvagePercentage != null
? CurrencyFormatter.formatEditable(asset.salvagePercentage)
: '';
// Prefer % mode when percentage is present (API syncs both).
_salvageAsPercentage = asset.salvagePercentage != null;
}
void _setSalvageInputMode(bool asPercentage) {
if (_salvageAsPercentage == asPercentage) return;
final purchaseCost = CurrencyFormatter.tryParse(_costController.text);
setState(() {
if (asPercentage) {
final amount = CurrencyFormatter.tryParse(_salvageValueController.text);
if (purchaseCost != null &&
purchaseCost > 0 &&
amount != null &&
_salvagePercentageController.text.trim().isEmpty) {
final pct = (amount / purchaseCost) * 100;
_salvagePercentageController.text =
CurrencyFormatter.formatEditable(pct);
}
} else {
final pct =
CurrencyFormatter.tryParse(_salvagePercentageController.text);
if (purchaseCost != null &&
pct != null &&
_salvageValueController.text.trim().isEmpty) {
final amount = purchaseCost * pct / 100;
_salvageValueController.text =
CurrencyFormatter.formatEditable(amount);
}
}
_salvageAsPercentage = asPercentage;
});
_triggerPreviewRecalculation();
}
void _putSalvageIntoPayload(Map<String, dynamic> payload) {
// Prefer sending only one salvage field; % wins when both modes apply.
if (_salvageAsPercentage) {
final pct =
CurrencyFormatter.tryParse(_salvagePercentageController.text);
if (pct != null) payload['salvage_percentage'] = pct;
return;
}
_putOptionalDouble(payload, 'salvage_value', _salvageValueController.text);
}
String _assetSignature(AssetModel asset) => String _assetSignature(AssetModel asset) =>
'${asset.id}:${asset.assetCategoryId}:${asset.assetSubcategoryId}:' '${asset.id}:${asset.assetCategoryId}:${asset.assetSubcategoryId}:'
'${asset.locationId}:${asset.status}:${asset.assetName}'; '${asset.locationId}:${asset.status}:${asset.assetName}';
@ -226,7 +297,10 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
_grnItemId = _nullablePositiveId(asset.grnItemId); _grnItemId = _nullablePositiveId(asset.grnItemId);
_status = asset.status; _status = asset.status;
_condition = asset.condition; _condition = asset.condition;
_depreciationMethod = asset.depreciationMethod; _depreciationMethod = asset.depreciationMethod?.trim().toUpperCase() ==
'OTHER'
? 'CUSTOM'
: asset.depreciationMethod;
_warrantyExpiry = asset.warrantyExpiryDate; _warrantyExpiry = asset.warrantyExpiryDate;
_purchaseDate = asset.purchaseDate; _purchaseDate = asset.purchaseDate;
_commencementDate = asset.commencementDate; _commencementDate = asset.commencementDate;
@ -246,8 +320,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
CurrencyFormatter.formatEditable(asset.disposalValue); CurrencyFormatter.formatEditable(asset.disposalValue);
_usefulLifeController.text = asset.usefulLifeYears?.toString() ?? ''; _usefulLifeController.text = asset.usefulLifeYears?.toString() ?? '';
_depreciationRateController.text = asset.depreciationRate?.toString() ?? ''; _depreciationRateController.text = asset.depreciationRate?.toString() ?? '';
_salvageValueController.text = _applySalvageFromAsset(asset);
CurrencyFormatter.formatEditable(asset.salvageValue);
_remarksController.text = asset.remarks ?? ''; _remarksController.text = asset.remarks ?? '';
_frequencyController.text = _frequencyController.text =
asset.maintenanceFrequencyInDays?.toString() ?? ''; asset.maintenanceFrequencyInDays?.toString() ?? '';
@ -268,13 +341,13 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
} }
Map<String, dynamic>? _buildDepreciationPreviewPayload() { Map<String, dynamic>? _buildDepreciationPreviewPayload() {
final method = _depreciationMethod?.trim().toUpperCase(); final method = _normalizedDepreciationMethod;
if (method == null || method.isEmpty) { if (method == null || method.isEmpty) {
return null; return null;
} }
final rate = CurrencyFormatter.tryParse(_depreciationRateController.text); final rate = CurrencyFormatter.tryParse(_depreciationRateController.text);
if (method == 'OTHER' && rate == null) { if (_requiresCustomRate && rate == null) {
return null; return null;
} }
@ -286,9 +359,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
final purchaseCost = CurrencyFormatter.tryParse(_costController.text); final purchaseCost = CurrencyFormatter.tryParse(_costController.text);
if (purchaseCost != null) payload['purchase_cost'] = purchaseCost; if (purchaseCost != null) payload['purchase_cost'] = purchaseCost;
final salvageValue = _putSalvageIntoPayload(payload);
CurrencyFormatter.tryParse(_salvageValueController.text);
if (salvageValue != null) payload['salvage_value'] = salvageValue;
final usefulLife = int.tryParse(_usefulLifeController.text.trim()); final usefulLife = int.tryParse(_usefulLifeController.text.trim());
if (usefulLife != null) payload['useful_life_years'] = usefulLife; if (usefulLife != null) payload['useful_life_years'] = usefulLife;
@ -312,8 +383,8 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
setState(() { setState(() {
_isDepreciationPreviewLoading = false; _isDepreciationPreviewLoading = false;
_depreciationPreview = null; _depreciationPreview = null;
_depreciationPreviewError = _depreciationMethod?.trim().toUpperCase() == 'OTHER' _depreciationPreviewError = _requiresCustomRate
? 'Depreciation rate is required for OTHER' ? 'Depreciation rate is required for CUSTOM'
: null; : null;
}); });
return; return;
@ -393,7 +464,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
} }
_putOptionalDouble(payload, 'purchase_cost', _costController.text); _putOptionalDouble(payload, 'purchase_cost', _costController.text);
_putOptionalDouble(payload, 'salvage_value', _salvageValueController.text); _putSalvageIntoPayload(payload);
_putOptionalDouble(payload, 'disposal_value', _disposalValueController.text); _putOptionalDouble(payload, 'disposal_value', _disposalValueController.text);
final usefulLife = int.tryParse(_usefulLifeController.text.trim()); final usefulLife = int.tryParse(_usefulLifeController.text.trim());
@ -417,8 +488,9 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
payload['maintenance_checklist_json'] = checklistPayload; payload['maintenance_checklist_json'] = checklistPayload;
} }
if (_depreciationMethod != null) { final method = _normalizedDepreciationMethod;
payload['depreciation_method'] = _depreciationMethod; if (method != null) {
payload['depreciation_method'] = method;
} }
final depreciationRate = final depreciationRate =
@ -516,23 +588,30 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
} }
final purchaseCost = CurrencyFormatter.tryParse(_costController.text); final purchaseCost = CurrencyFormatter.tryParse(_costController.text);
final salvageValue = if (_salvageAsPercentage) {
CurrencyFormatter.tryParse(_salvageValueController.text); final pct =
if (purchaseCost != null && CurrencyFormatter.tryParse(_salvagePercentageController.text);
salvageValue != null && if (pct != null && (pct < 0 || pct > 100)) {
salvageValue > purchaseCost) { return 'Salvage percentage must be between 0 and 100';
return 'Salvage value cannot exceed purchase cost'; }
} else {
final salvageValue =
CurrencyFormatter.tryParse(_salvageValueController.text);
if (purchaseCost != null &&
salvageValue != null &&
salvageValue > purchaseCost) {
return 'Salvage value cannot exceed purchase cost';
}
} }
final hasDepreciationMethod = final hasDepreciationMethod = _normalizedDepreciationMethod != null;
_depreciationMethod != null && _depreciationMethod!.trim().isNotEmpty;
if (hasDepreciationMethod) { if (hasDepreciationMethod) {
if (_usefulLifeController.text.trim().isEmpty) { if (_usefulLifeController.text.trim().isEmpty) {
return 'Useful life is required when depreciation method is set'; return 'Useful life is required when depreciation method is set';
} }
final method = _depreciationMethod!.trim().toUpperCase(); if (_requiresCustomRate &&
if (method == 'OTHER' && _depreciationRateController.text.trim().isEmpty) { _depreciationRateController.text.trim().isEmpty) {
return 'Depreciation rate is required when depreciation method is OTHER'; return 'Depreciation rate is required when depreciation method is CUSTOM';
} }
} }
@ -887,8 +966,10 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
AppDropdown<String>( AppDropdown<String>(
isDense: true, isDense: true,
label: 'Depreciation Method', label: 'Depreciation Method',
value: _depreciationMethod, value: _normalizedDepreciationMethod,
options: assetOptionDropdowns(depreciationMethods), options: _depreciationMethodOptions(
depreciationMethods,
),
enabled: depreciationMethods.isNotEmpty, enabled: depreciationMethods.isNotEmpty,
hint: depreciationMethods.isEmpty hint: depreciationMethods.isEmpty
? 'Loading depreciation methods...' ? 'Loading depreciation methods...'
@ -910,20 +991,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
fieldName: 'Depreciation Rate', fieldName: 'Depreciation Rate',
), ),
), ),
AppTextField( _buildSalvageField(),
isDense: true,
controller: _salvageValueController,
label: 'Salvage Value',
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
inputFormatters: CurrencyFormatter.amountInput,
validator: (v) =>
Validators.optionalNonNegativeDouble(
v,
fieldName: 'Salvage Value',
),
),
], ],
), ),
_DepreciationPreviewCard( _DepreciationPreviewCard(
@ -1283,6 +1351,89 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
); );
} }
List<AppDropdownOption<String>> _depreciationMethodOptions(
List<AssetDropdownOption> methods,
) {
final mapped = <AppDropdownOption<String>>[];
final seen = <String>{};
for (final option in assetOptionDropdowns(methods)) {
final raw = option.value.trim().toUpperCase();
final value = raw == 'OTHER' ? 'CUSTOM' : option.value;
final key = value.trim().toUpperCase();
if (key.isEmpty || seen.contains(key)) continue;
seen.add(key);
mapped.add(
AppDropdownOption(
value: value,
label: key == 'CUSTOM' ? 'CUSTOM' : option.label,
),
);
}
return mapped;
}
Widget _buildSalvageField() {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Salvage',
style: theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 6),
SegmentedButton<bool>(
segments: const [
ButtonSegment<bool>(
value: false,
label: Text('Amount'),
icon: Icon(Icons.currency_rupee, size: 16),
),
ButtonSegment<bool>(
value: true,
label: Text('%'),
icon: Icon(Icons.percent, size: 16),
),
],
selected: {_salvageAsPercentage},
onSelectionChanged: (selected) {
_setSalvageInputMode(selected.first);
},
style: ButtonStyle(
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
const SizedBox(height: 8),
if (_salvageAsPercentage)
AppTextField(
isDense: true,
controller: _salvagePercentageController,
label: 'Salvage Percentage (%)',
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: (v) => Validators.optionalPercentage(
v,
fieldName: 'Salvage Percentage',
),
)
else
AppTextField(
isDense: true,
controller: _salvageValueController,
label: 'Salvage Value (₹)',
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: CurrencyFormatter.amountInput,
validator: (v) => Validators.optionalNonNegativeDouble(
v,
fieldName: 'Salvage Value',
),
),
],
);
}
Widget _optionalLookupDropdown({ Widget _optionalLookupDropdown({
required String label, required String label,
required int? value, required int? value,
@ -1686,6 +1837,15 @@ class _DepreciationPreviewCard extends StatelessWidget {
label: 'Current Book Value', label: 'Current Book Value',
value: money(preview!.bookValue), value: money(preview!.bookValue),
), ),
_PreviewItem(
label: 'Salvage Value',
value: money(preview!.salvageValue),
),
if (preview!.salvagePercentage != null)
_PreviewItem(
label: 'Salvage %',
value: '${preview!.salvagePercentage!.toStringAsFixed(2)}%',
),
_PreviewItem( _PreviewItem(
label: 'Years Elapsed', label: 'Years Elapsed',
value: preview!.yearsElapsed.toString(), value: preview!.yearsElapsed.toString(),

View File

@ -427,6 +427,28 @@ class _AssetDataTable extends StatelessWidget {
searchText: (asset) => asset.locationName ?? '', searchText: (asset) => asset.locationName ?? '',
cellBuilder: (_, asset) => Text(asset.locationName ?? ''), cellBuilder: (_, asset) => Text(asset.locationName ?? ''),
), ),
AppDataColumn(
label: 'Current Value',
flex: 1,
searchText: (asset) =>
CurrencyFormatter.searchable(asset.resolvedCurrentValue),
cellBuilder: (_, asset) => Text(
asset.resolvedCurrentValue != null
? CurrencyFormatter.format(asset.resolvedCurrentValue)
: '',
),
),
AppDataColumn(
label: 'Depreciated Amount',
flex: 1,
searchText: (asset) =>
CurrencyFormatter.searchable(asset.depreciatedAmount),
cellBuilder: (_, asset) => Text(
asset.depreciatedAmount != null
? CurrencyFormatter.format(asset.depreciatedAmount)
: '',
),
),
AppDataColumn( AppDataColumn(
label: 'Warranty Validity', label: 'Warranty Validity',
flex: 1, flex: 1,

View File

@ -18,6 +18,7 @@ import '../../../../shared/widgets/app_toast.dart';
import '../../../../shared/widgets/error_view.dart'; import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart'; import '../../../../shared/widgets/page_header.dart';
import '../providers/assets_provider.dart'; import '../providers/assets_provider.dart';
import '../utils/maintenance_due_display.dart';
import '../widgets/asset_maintenance_panel.dart'; import '../widgets/asset_maintenance_panel.dart';
class AssetMaintenanceScreen extends ConsumerStatefulWidget { class AssetMaintenanceScreen extends ConsumerStatefulWidget {
@ -351,16 +352,10 @@ class _DueBadge extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final color = isDue final due = MaintenanceDueDisplay.fromDaysUntilDue(daysUntilDue);
? const Color(0xFFDC2626) final color = due?.color ??
: const Color(0xFF16A34A); (isDue ? const Color(0xFFDC2626) : const Color(0xFF16A34A));
final label = isDue final label = due?.label ?? (isDue ? 'Due' : 'On track');
? (daysUntilDue != null && daysUntilDue! < 0
? 'Overdue'
: 'Due')
: (daysUntilDue != null
? 'In $daysUntilDue day${daysUntilDue == 1 ? '' : 's'}'
: 'On track');
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
/// Display copy + color for maintenance `days_until_due` from the API.
class MaintenanceDueDisplay {
const MaintenanceDueDisplay({
required this.label,
required this.color,
});
final String label;
final Color color;
/// Green/neutral upcoming, amber due today, red overdue.
static MaintenanceDueDisplay? fromDaysUntilDue(int? daysUntilDue) {
if (daysUntilDue == null) return null;
if (daysUntilDue > 0) {
return MaintenanceDueDisplay(
label:
'Due in $daysUntilDue day${daysUntilDue == 1 ? '' : 's'}',
color: const Color(0xFF16A34A),
);
}
if (daysUntilDue == 0) {
return const MaintenanceDueDisplay(
label: 'Due today',
color: Color(0xFFD97706),
);
}
final overdueBy = daysUntilDue.abs();
return MaintenanceDueDisplay(
label: 'Overdue by $overdueBy day${overdueBy == 1 ? '' : 's'}',
color: const Color(0xFFDC2626),
);
}
}

View File

@ -5,6 +5,8 @@ import '../../../../core/errors/failure.dart';
import '../../../../core/utils/formatters.dart'; import '../../../../core/utils/formatters.dart';
import '../../../../core/utils/validators.dart'; import '../../../../core/utils/validators.dart';
import '../../../../shared/models/asset_model.dart'; import '../../../../shared/models/asset_model.dart';
import '../../../../shared/models/user_management_models.dart'
show FilterOptionModel;
import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/api_feedback.dart';
import '../../../../shared/widgets/app_button.dart'; import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/app_card.dart'; import '../../../../shared/widgets/app_card.dart';
@ -543,7 +545,7 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
_serviceCostController.text = _serviceCostController.text =
CurrencyFormatter.formatEditable(visit.serviceCost); CurrencyFormatter.formatEditable(visit.serviceCost);
} }
_isUnderAmc = visit.isUnderAmc; _isUnderAmc = visit.isUnderAmc || visit.amcContractId != null;
_assetConditionAfter = visit.assetConditionAfter; _assetConditionAfter = visit.assetConditionAfter;
_remarksController.text = visit.remarks ?? ''; _remarksController.text = visit.remarks ?? '';
} }
@ -570,9 +572,11 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
final serviceCost = final serviceCost =
CurrencyFormatter.tryParse(_serviceCostController.text); CurrencyFormatter.tryParse(_serviceCostController.text);
return { return {
if (_visitType != null && _visitType!.trim().isNotEmpty) 'visit_type': _visitType, if (_visitType != null && _visitType!.trim().isNotEmpty)
'visit_type': _visitType,
'visit_date': DateFormatter.toApiDate(_visitDate!), 'visit_date': DateFormatter.toApiDate(_visitDate!),
if (_amcContractId != null) 'amc_contract_id': _amcContractId, if (_isUnderAmc && _amcContractId != null)
'amc_contract_id': _amcContractId,
if (_complaintNoController.text.trim().isNotEmpty) if (_complaintNoController.text.trim().isNotEmpty)
'complaint_no': _complaintNoController.text.trim(), 'complaint_no': _complaintNoController.text.trim(),
if (_complaintDate != null) if (_complaintDate != null)
@ -594,7 +598,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
if (downtimeHours != null) 'downtime_hours': downtimeHours, if (downtimeHours != null) 'downtime_hours': downtimeHours,
if (serviceCost != null) 'service_cost': serviceCost, if (serviceCost != null) 'service_cost': serviceCost,
'is_under_amc': _isUnderAmc, 'is_under_amc': _isUnderAmc,
if (_assetConditionAfter != null && _assetConditionAfter!.trim().isNotEmpty) if (_assetConditionAfter != null &&
_assetConditionAfter!.trim().isNotEmpty)
'asset_condition_after': _assetConditionAfter, 'asset_condition_after': _assetConditionAfter,
if (_remarksController.text.trim().isNotEmpty) if (_remarksController.text.trim().isNotEmpty)
'remarks': _remarksController.text.trim(), 'remarks': _remarksController.text.trim(),
@ -627,9 +632,60 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
} }
} }
void _setUnderAmc(bool value) {
setState(() {
_isUnderAmc = value;
if (!value) {
_amcContractId = null;
}
});
}
void _onAmcContractChanged(
int? contractId,
List<AmcContractModel> amcContracts,
) {
setState(() {
_amcContractId = contractId;
if (contractId == null) return;
final contract = amcContracts
.where((c) => int.tryParse(c.id) == contractId)
.firstOrNull;
if (contract?.vendorId != null) {
_vendorId = contract!.vendorId;
}
});
}
List<AppDropdownOption<int>> _vendorOptions({
required List<FilterOptionModel> vendors,
required List<AmcContractModel> amcContracts,
}) {
final all = vendors
.map(
(vendor) => AppDropdownOption(
value: int.tryParse(vendor.id) ?? 0,
label: vendor.name,
),
)
.where((option) => option.value != 0)
.toList();
if (!_isUnderAmc || _amcContractId == null) return all;
final contract = amcContracts
.where((c) => int.tryParse(c.id) == _amcContractId)
.firstOrNull;
final contractVendorId = contract?.vendorId;
if (contractVendorId == null) return all;
return all.where((option) => option.value == contractVendorId).toList();
}
Widget _buildForm(List<AmcContractModel> amcContracts) { Widget _buildForm(List<AmcContractModel> amcContracts) {
final lookupsAsync = ref.watch(assetFormLookupsProvider); final lookupsAsync = ref.watch(assetFormLookupsProvider);
final options = lookupsAsync.valueOrNull?.options ?? const AssetDropdownOptionsModel(); final options =
lookupsAsync.valueOrNull?.options ?? const AssetDropdownOptionsModel();
if (lookupsAsync.hasValue) { if (lookupsAsync.hasValue) {
final nextVisitType = resolveAssetOptionValue( final nextVisitType = resolveAssetOptionValue(
@ -666,6 +722,7 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// 1) Visit classification
SidePanelFormRow( SidePanelFormRow(
left: AppSearchableDropdown<String>( left: AppSearchableDropdown<String>(
label: 'Visit Type *', label: 'Visit Type *',
@ -698,24 +755,35 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
onPicked: (date) => setState(() => _visitDate = date), onPicked: (date) => setState(() => _visitDate = date),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 8),
AppSearchableDropdown<int>(
label: 'AMC Contract', // 2) Contract & Vendor
value: _amcContractId, AppFormToggleField(
searchHint: 'Search AMC contract...', label: 'Under AMC',
options: amcContracts value: _isUnderAmc,
.map((contract) { onChanged: _setUnderAmc,
final id = int.tryParse(contract.id);
if (id == null) return null;
final label = contract.contractNo?.trim().isNotEmpty == true
? contract.contractNo!
: 'AMC #${contract.id}';
return AppDropdownOption(value: id, label: label);
})
.whereType<AppDropdownOption<int>>()
.toList(),
onChanged: (v) => setState(() => _amcContractId = v),
), ),
if (_isUnderAmc) ...[
const SizedBox(height: 8),
AppSearchableDropdown<int>(
label: 'AMC Contract',
value: _amcContractId,
searchHint: 'Search AMC contract...',
options: amcContracts
.map((contract) {
final id = int.tryParse(contract.id);
if (id == null) return null;
final label =
contract.contractNo?.trim().isNotEmpty == true
? contract.contractNo!
: 'AMC #${contract.id}';
return AppDropdownOption(value: id, label: label);
})
.whereType<AppDropdownOption<int>>()
.toList(),
onChanged: (v) => _onAmcContractChanged(v, amcContracts),
),
],
const SizedBox(height: 12), const SizedBox(height: 12),
SidePanelFormRow( SidePanelFormRow(
left: lookupsAsync.when( left: lookupsAsync.when(
@ -728,13 +796,10 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
label: 'Vendor', label: 'Vendor',
value: _vendorId, value: _vendorId,
searchHint: 'Search vendor...', searchHint: 'Search vendor...',
options: lookups.vendors options: _vendorOptions(
.map((vendor) => AppDropdownOption( vendors: lookups.vendors,
value: int.tryParse(vendor.id) ?? 0, amcContracts: amcContracts,
label: vendor.name, ),
))
.where((option) => option.value != 0)
.toList(),
onChanged: (v) => setState(() => _vendorId = v), onChanged: (v) => setState(() => _vendorId = v),
), ),
), ),
@ -747,6 +812,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
onChanged: (v) => setState(() => _assetConditionAfter = v), onChanged: (v) => setState(() => _assetConditionAfter = v),
), ),
), ),
// 3) Complaint intake
SidePanelFormRow( SidePanelFormRow(
left: AppTextField( left: AppTextField(
controller: _complaintNoController, controller: _complaintNoController,
@ -767,6 +834,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
maxLines: 3, maxLines: 3,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// 4) Engineer & work performed
SidePanelFormRow( SidePanelFormRow(
left: AppTextField( left: AppTextField(
controller: _engineerNameController, controller: _engineerNameController,
@ -792,6 +861,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
maxLines: 3, maxLines: 3,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// 5) Outcome & cost
SidePanelFormRow( SidePanelFormRow(
left: _SidePanelDateField( left: _SidePanelDateField(
label: 'Next Service Date', label: 'Next Service Date',
@ -804,7 +875,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
right: AppTextField( right: AppTextField(
controller: _downtimeHoursController, controller: _downtimeHoursController,
label: 'Downtime Hours', label: 'Downtime Hours',
keyboardType: const TextInputType.numberWithOptions(decimal: true), keyboardType:
const TextInputType.numberWithOptions(decimal: true),
validator: (v) => Validators.optionalPositiveDouble( validator: (v) => Validators.optionalPositiveDouble(
v, v,
fieldName: 'Downtime Hours', fieldName: 'Downtime Hours',
@ -827,12 +899,6 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
label: 'Remarks', label: 'Remarks',
maxLines: 3, maxLines: 3,
), ),
const SizedBox(height: 8),
AppFormToggleField(
label: 'Under AMC',
value: _isUnderAmc,
onChanged: (value) => setState(() => _isUnderAmc = value),
),
], ],
), ),
); );

View File

@ -96,6 +96,10 @@ class _DetailRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final labelStyle = theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
);
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: Row( child: Row(
@ -103,22 +107,20 @@ class _DetailRow extends StatelessWidget {
children: [ children: [
SizedBox( SizedBox(
width: 140, width: 140,
child: Text( child: Text(label, style: labelStyle),
label, ),
style: theme.textTheme.bodyMedium?.copyWith( if (child != null)
color: theme.colorScheme.onSurfaceVariant, // Keep badge left-aligned with text values (do not expand Chip).
child!
else
Expanded(
child: SelectableText(
value ?? '',
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
), ),
), ),
),
Expanded(
child: child ??
SelectableText(
value ?? '',
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
),
], ],
), ),
); );

View File

@ -170,6 +170,7 @@ class DepreciationReportRow {
this.usefulLifeYears, this.usefulLifeYears,
this.yearsElapsed, this.yearsElapsed,
this.salvageValue, this.salvageValue,
this.salvagePercentage,
this.annualDepreciation, this.annualDepreciation,
this.accumulatedDepreciation, this.accumulatedDepreciation,
this.bookValue, this.bookValue,
@ -190,6 +191,7 @@ class DepreciationReportRow {
final int? usefulLifeYears; final int? usefulLifeYears;
final double? yearsElapsed; final double? yearsElapsed;
final double? salvageValue; final double? salvageValue;
final double? salvagePercentage;
final double? annualDepreciation; final double? annualDepreciation;
final double? accumulatedDepreciation; final double? accumulatedDepreciation;
final double? bookValue; final double? bookValue;
@ -300,6 +302,10 @@ class DepreciationReportRow {
json['salvage_value'] ?? json['salvageValue'], json['salvage_value'] ?? json['salvageValue'],
) ?? ) ??
toDouble(depMap['salvage_value']), toDouble(depMap['salvage_value']),
salvagePercentage: toDouble(
json['salvage_percentage'] ?? json['salvagePercentage'],
) ??
toDouble(depMap['salvage_percentage']),
annualDepreciation: toDouble( annualDepreciation: toDouble(
json['annual_depreciation'] ?? json['annualDepreciation'], json['annual_depreciation'] ?? json['annualDepreciation'],
) ?? ) ??

View File

@ -200,6 +200,16 @@ class AssetModel with _$AssetModel {
@JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable) @JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable)
double? depreciationRate, double? depreciationRate,
@JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) double? salvageValue, @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) double? salvageValue,
@JsonKey(name: 'salvage_percentage', fromJson: _doubleFromJsonNullable)
double? salvagePercentage,
@JsonKey(name: 'current_value', fromJson: _doubleFromJsonNullable)
double? currentValue,
@JsonKey(
name: 'depreciation',
fromJson: AssetDepreciationSummary.fromJsonNullable,
toJson: AssetDepreciationSummary.toJsonNullable,
)
AssetDepreciationSummary? depreciation,
@JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable)
DateTime? warrantyExpiryDate, DateTime? warrantyExpiryDate,
String? condition, String? condition,
@ -223,6 +233,84 @@ class AssetModel with _$AssetModel {
factory AssetModel.fromJson(Map<String, dynamic> json) => _$AssetModelFromJson(json); factory AssetModel.fromJson(Map<String, dynamic> json) => _$AssetModelFromJson(json);
} }
extension AssetValueX on AssetModel {
/// Book / WDV prefer top-level `current_value`.
double? get resolvedCurrentValue =>
currentValue ?? depreciation?.currentValue ?? depreciation?.bookValue;
/// Total depreciated so far `depreciation.accumulated_depreciation`.
double? get depreciatedAmount => depreciation?.accumulatedDepreciation;
}
class AssetDepreciationSummary {
const AssetDepreciationSummary({
this.depreciationMethod,
this.depreciationRate,
this.annualDepreciation,
this.accumulatedDepreciation,
this.bookValue,
this.currentValue,
this.purchaseCost,
this.salvageValue,
this.salvagePercentage,
this.usefulLifeYears,
});
final String? depreciationMethod;
final double? depreciationRate;
final double? annualDepreciation;
final double? accumulatedDepreciation;
final double? bookValue;
final double? currentValue;
final double? purchaseCost;
final double? salvageValue;
final double? salvagePercentage;
final int? usefulLifeYears;
static AssetDepreciationSummary? fromJsonNullable(Object? value) {
if (value is! Map) return null;
return AssetDepreciationSummary.fromJson(Map<String, dynamic>.from(value));
}
static Object? toJsonNullable(AssetDepreciationSummary? value) {
if (value == null) return null;
return value.toJson();
}
factory AssetDepreciationSummary.fromJson(Map<String, dynamic> json) {
return AssetDepreciationSummary(
depreciationMethod: json['depreciation_method']?.toString(),
depreciationRate: _doubleFromJsonNullable(json['depreciation_rate']),
annualDepreciation: _doubleFromJsonNullable(json['annual_depreciation']),
accumulatedDepreciation:
_doubleFromJsonNullable(json['accumulated_depreciation']),
bookValue: _doubleFromJsonNullable(json['book_value']),
currentValue: _doubleFromJsonNullable(json['current_value']),
purchaseCost: _doubleFromJsonNullable(json['purchase_cost']),
salvageValue: _doubleFromJsonNullable(json['salvage_value']),
salvagePercentage: _doubleFromJsonNullable(json['salvage_percentage']),
usefulLifeYears: _intFromJsonNullable(json['useful_life_years']),
);
}
Map<String, dynamic> toJson() => {
if (depreciationMethod != null)
'depreciation_method': depreciationMethod,
if (depreciationRate != null) 'depreciation_rate': depreciationRate,
if (annualDepreciation != null)
'annual_depreciation': annualDepreciation,
if (accumulatedDepreciation != null)
'accumulated_depreciation': accumulatedDepreciation,
if (bookValue != null) 'book_value': bookValue,
if (currentValue != null) 'current_value': currentValue,
if (purchaseCost != null) 'purchase_cost': purchaseCost,
if (salvageValue != null) 'salvage_value': salvageValue,
if (salvagePercentage != null)
'salvage_percentage': salvagePercentage,
if (usefulLifeYears != null) 'useful_life_years': usefulLifeYears,
};
}
class AssetMaintenanceChecklistItem { class AssetMaintenanceChecklistItem {
const AssetMaintenanceChecklistItem({ const AssetMaintenanceChecklistItem({
required this.label, required this.label,
@ -708,6 +796,7 @@ class AssetDepreciationPreviewModel {
required this.yearsElapsed, required this.yearsElapsed,
required this.purchaseCost, required this.purchaseCost,
required this.salvageValue, required this.salvageValue,
this.salvagePercentage,
required this.usefulLifeYears, required this.usefulLifeYears,
}); });
@ -719,6 +808,7 @@ class AssetDepreciationPreviewModel {
final int yearsElapsed; final int yearsElapsed;
final double purchaseCost; final double purchaseCost;
final double salvageValue; final double salvageValue;
final double? salvagePercentage;
final int usefulLifeYears; final int usefulLifeYears;
factory AssetDepreciationPreviewModel.fromJson(Map<String, dynamic> json) { factory AssetDepreciationPreviewModel.fromJson(Map<String, dynamic> json) {
@ -742,6 +832,7 @@ class AssetDepreciationPreviewModel {
yearsElapsed: toInt(json['years_elapsed']), yearsElapsed: toInt(json['years_elapsed']),
purchaseCost: toDouble(json['purchase_cost']), purchaseCost: toDouble(json['purchase_cost']),
salvageValue: toDouble(json['salvage_value']), salvageValue: toDouble(json['salvage_value']),
salvagePercentage: _doubleFromJsonNullable(json['salvage_percentage']),
usefulLifeYears: toInt(json['useful_life_years']), usefulLifeYears: toInt(json['useful_life_years']),
); );
} }

View File

@ -498,6 +498,17 @@ mixin _$AssetModel {
double? get depreciationRate => throw _privateConstructorUsedError; double? get depreciationRate => throw _privateConstructorUsedError;
@JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable)
double? get salvageValue => throw _privateConstructorUsedError; double? get salvageValue => throw _privateConstructorUsedError;
@JsonKey(name: 'salvage_percentage', fromJson: _doubleFromJsonNullable)
double? get salvagePercentage => throw _privateConstructorUsedError;
@JsonKey(name: 'current_value', fromJson: _doubleFromJsonNullable)
double? get currentValue => throw _privateConstructorUsedError;
@JsonKey(
name: 'depreciation',
fromJson: AssetDepreciationSummary.fromJsonNullable,
toJson: AssetDepreciationSummary.toJsonNullable,
)
AssetDepreciationSummary? get depreciation =>
throw _privateConstructorUsedError;
@JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable)
DateTime? get warrantyExpiryDate => throw _privateConstructorUsedError; DateTime? get warrantyExpiryDate => throw _privateConstructorUsedError;
String? get condition => throw _privateConstructorUsedError; String? get condition => throw _privateConstructorUsedError;
@ -617,6 +628,16 @@ abstract class $AssetModelCopyWith<$Res> {
double? depreciationRate, double? depreciationRate,
@JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable)
double? salvageValue, double? salvageValue,
@JsonKey(name: 'salvage_percentage', fromJson: _doubleFromJsonNullable)
double? salvagePercentage,
@JsonKey(name: 'current_value', fromJson: _doubleFromJsonNullable)
double? currentValue,
@JsonKey(
name: 'depreciation',
fromJson: AssetDepreciationSummary.fromJsonNullable,
toJson: AssetDepreciationSummary.toJsonNullable,
)
AssetDepreciationSummary? depreciation,
@JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable)
DateTime? warrantyExpiryDate, DateTime? warrantyExpiryDate,
String? condition, String? condition,
@ -689,6 +710,9 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel>
Object? depreciationMethod = freezed, Object? depreciationMethod = freezed,
Object? depreciationRate = freezed, Object? depreciationRate = freezed,
Object? salvageValue = freezed, Object? salvageValue = freezed,
Object? salvagePercentage = freezed,
Object? currentValue = freezed,
Object? depreciation = freezed,
Object? warrantyExpiryDate = freezed, Object? warrantyExpiryDate = freezed,
Object? condition = freezed, Object? condition = freezed,
Object? status = freezed, Object? status = freezed,
@ -832,6 +856,18 @@ class _$AssetModelCopyWithImpl<$Res, $Val extends AssetModel>
? _value.salvageValue ? _value.salvageValue
: salvageValue // ignore: cast_nullable_to_non_nullable : salvageValue // ignore: cast_nullable_to_non_nullable
as double?, as double?,
salvagePercentage: freezed == salvagePercentage
? _value.salvagePercentage
: salvagePercentage // ignore: cast_nullable_to_non_nullable
as double?,
currentValue: freezed == currentValue
? _value.currentValue
: currentValue // ignore: cast_nullable_to_non_nullable
as double?,
depreciation: freezed == depreciation
? _value.depreciation
: depreciation // ignore: cast_nullable_to_non_nullable
as AssetDepreciationSummary?,
warrantyExpiryDate: freezed == warrantyExpiryDate warrantyExpiryDate: freezed == warrantyExpiryDate
? _value.warrantyExpiryDate ? _value.warrantyExpiryDate
: warrantyExpiryDate // ignore: cast_nullable_to_non_nullable : warrantyExpiryDate // ignore: cast_nullable_to_non_nullable
@ -970,6 +1006,16 @@ abstract class _$$AssetModelImplCopyWith<$Res>
double? depreciationRate, double? depreciationRate,
@JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable)
double? salvageValue, double? salvageValue,
@JsonKey(name: 'salvage_percentage', fromJson: _doubleFromJsonNullable)
double? salvagePercentage,
@JsonKey(name: 'current_value', fromJson: _doubleFromJsonNullable)
double? currentValue,
@JsonKey(
name: 'depreciation',
fromJson: AssetDepreciationSummary.fromJsonNullable,
toJson: AssetDepreciationSummary.toJsonNullable,
)
AssetDepreciationSummary? depreciation,
@JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable)
DateTime? warrantyExpiryDate, DateTime? warrantyExpiryDate,
String? condition, String? condition,
@ -1041,6 +1087,9 @@ class __$$AssetModelImplCopyWithImpl<$Res>
Object? depreciationMethod = freezed, Object? depreciationMethod = freezed,
Object? depreciationRate = freezed, Object? depreciationRate = freezed,
Object? salvageValue = freezed, Object? salvageValue = freezed,
Object? salvagePercentage = freezed,
Object? currentValue = freezed,
Object? depreciation = freezed,
Object? warrantyExpiryDate = freezed, Object? warrantyExpiryDate = freezed,
Object? condition = freezed, Object? condition = freezed,
Object? status = freezed, Object? status = freezed,
@ -1184,6 +1233,18 @@ class __$$AssetModelImplCopyWithImpl<$Res>
? _value.salvageValue ? _value.salvageValue
: salvageValue // ignore: cast_nullable_to_non_nullable : salvageValue // ignore: cast_nullable_to_non_nullable
as double?, as double?,
salvagePercentage: freezed == salvagePercentage
? _value.salvagePercentage
: salvagePercentage // ignore: cast_nullable_to_non_nullable
as double?,
currentValue: freezed == currentValue
? _value.currentValue
: currentValue // ignore: cast_nullable_to_non_nullable
as double?,
depreciation: freezed == depreciation
? _value.depreciation
: depreciation // ignore: cast_nullable_to_non_nullable
as AssetDepreciationSummary?,
warrantyExpiryDate: freezed == warrantyExpiryDate warrantyExpiryDate: freezed == warrantyExpiryDate
? _value.warrantyExpiryDate ? _value.warrantyExpiryDate
: warrantyExpiryDate // ignore: cast_nullable_to_non_nullable : warrantyExpiryDate // ignore: cast_nullable_to_non_nullable
@ -1315,6 +1376,16 @@ class _$AssetModelImpl implements _AssetModel {
this.depreciationRate, this.depreciationRate,
@JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable)
this.salvageValue, this.salvageValue,
@JsonKey(name: 'salvage_percentage', fromJson: _doubleFromJsonNullable)
this.salvagePercentage,
@JsonKey(name: 'current_value', fromJson: _doubleFromJsonNullable)
this.currentValue,
@JsonKey(
name: 'depreciation',
fromJson: AssetDepreciationSummary.fromJsonNullable,
toJson: AssetDepreciationSummary.toJsonNullable,
)
this.depreciation,
@JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable)
this.warrantyExpiryDate, this.warrantyExpiryDate,
this.condition, this.condition,
@ -1466,6 +1537,19 @@ class _$AssetModelImpl implements _AssetModel {
@JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable)
final double? salvageValue; final double? salvageValue;
@override @override
@JsonKey(name: 'salvage_percentage', fromJson: _doubleFromJsonNullable)
final double? salvagePercentage;
@override
@JsonKey(name: 'current_value', fromJson: _doubleFromJsonNullable)
final double? currentValue;
@override
@JsonKey(
name: 'depreciation',
fromJson: AssetDepreciationSummary.fromJsonNullable,
toJson: AssetDepreciationSummary.toJsonNullable,
)
final AssetDepreciationSummary? depreciation;
@override
@JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable)
final DateTime? warrantyExpiryDate; final DateTime? warrantyExpiryDate;
@override @override
@ -1505,7 +1589,7 @@ class _$AssetModelImpl implements _AssetModel {
@override @override
String toString() { String toString() {
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)'; 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, salvagePercentage: $salvagePercentage, currentValue: $currentValue, depreciation: $depreciation, 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 @override
@ -1583,6 +1667,12 @@ class _$AssetModelImpl implements _AssetModel {
other.depreciationRate == depreciationRate) && other.depreciationRate == depreciationRate) &&
(identical(other.salvageValue, salvageValue) || (identical(other.salvageValue, salvageValue) ||
other.salvageValue == salvageValue) && other.salvageValue == salvageValue) &&
(identical(other.salvagePercentage, salvagePercentage) ||
other.salvagePercentage == salvagePercentage) &&
(identical(other.currentValue, currentValue) ||
other.currentValue == currentValue) &&
(identical(other.depreciation, depreciation) ||
other.depreciation == depreciation) &&
(identical(other.warrantyExpiryDate, warrantyExpiryDate) || (identical(other.warrantyExpiryDate, warrantyExpiryDate) ||
other.warrantyExpiryDate == warrantyExpiryDate) && other.warrantyExpiryDate == warrantyExpiryDate) &&
(identical(other.condition, condition) || (identical(other.condition, condition) ||
@ -1643,6 +1733,9 @@ class _$AssetModelImpl implements _AssetModel {
depreciationMethod, depreciationMethod,
depreciationRate, depreciationRate,
salvageValue, salvageValue,
salvagePercentage,
currentValue,
depreciation,
warrantyExpiryDate, warrantyExpiryDate,
condition, condition,
status, status,
@ -1748,6 +1841,16 @@ abstract class _AssetModel implements AssetModel {
final double? depreciationRate, final double? depreciationRate,
@JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable)
final double? salvageValue, final double? salvageValue,
@JsonKey(name: 'salvage_percentage', fromJson: _doubleFromJsonNullable)
final double? salvagePercentage,
@JsonKey(name: 'current_value', fromJson: _doubleFromJsonNullable)
final double? currentValue,
@JsonKey(
name: 'depreciation',
fromJson: AssetDepreciationSummary.fromJsonNullable,
toJson: AssetDepreciationSummary.toJsonNullable,
)
final AssetDepreciationSummary? depreciation,
@JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable)
final DateTime? warrantyExpiryDate, final DateTime? warrantyExpiryDate,
final String? condition, final String? condition,
@ -1890,6 +1993,19 @@ abstract class _AssetModel implements AssetModel {
@JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable) @JsonKey(name: 'salvage_value', fromJson: _doubleFromJsonNullable)
double? get salvageValue; double? get salvageValue;
@override @override
@JsonKey(name: 'salvage_percentage', fromJson: _doubleFromJsonNullable)
double? get salvagePercentage;
@override
@JsonKey(name: 'current_value', fromJson: _doubleFromJsonNullable)
double? get currentValue;
@override
@JsonKey(
name: 'depreciation',
fromJson: AssetDepreciationSummary.fromJsonNullable,
toJson: AssetDepreciationSummary.toJsonNullable,
)
AssetDepreciationSummary? get depreciation;
@override
@JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable) @JsonKey(name: 'warranty_expiry_date', fromJson: _dateFromJsonNullable)
DateTime? get warrantyExpiryDate; DateTime? get warrantyExpiryDate;
@override @override

View File

@ -42,67 +42,69 @@ Map<String, dynamic> _$$AssetCategoryModelImplToJson(
'updatedAt': instance.updatedAt?.toIso8601String(), 'updatedAt': instance.updatedAt?.toIso8601String(),
}; };
_$AssetModelImpl _$$AssetModelImplFromJson(Map<String, dynamic> json) => _$AssetModelImpl _$$AssetModelImplFromJson(
_$AssetModelImpl( Map<String, dynamic> json,
id: _idFromJson(json['id']), ) => _$AssetModelImpl(
assetName: json['asset_name'] as String, id: _idFromJson(json['id']),
assetCode: json['asset_code'] as String?, assetName: json['asset_name'] as String,
assetCategoryId: _intFromJsonNullable( assetCode: json['asset_code'] as String?,
_readItemCategoryId(json, 'item_category_id'), assetCategoryId: _intFromJsonNullable(
), _readItemCategoryId(json, 'item_category_id'),
assetCategoryName: ),
_readItemCategoryName(json, 'item_category_name') as String?, assetCategoryName:
assetSubcategoryId: _intFromJsonNullable( _readItemCategoryName(json, 'item_category_name') as String?,
_readItemSubcategoryId(json, 'item_subcategory_id'), assetSubcategoryId: _intFromJsonNullable(
), _readItemSubcategoryId(json, 'item_subcategory_id'),
assetSubcategoryName: ),
_readItemSubcategoryName(json, 'item_subcategory_name') as String?, assetSubcategoryName:
locationId: _intFromJsonNullable(_readLocationId(json, 'location_id')), _readItemSubcategoryName(json, 'item_subcategory_name') as String?,
locationName: _readLocationName(json, 'location_name') as String?, locationId: _intFromJsonNullable(_readLocationId(json, 'location_id')),
brandModel: json['brand_model'] as String?, locationName: _readLocationName(json, 'location_name') as String?,
manufacturer: json['manufacturer'] as String?, brandModel: json['brand_model'] as String?,
serialNumber: json['serial_number'] as String?, manufacturer: json['manufacturer'] as String?,
partNumber: json['part_number'] as String?, serialNumber: json['serial_number'] as String?,
departmentId: _intFromJsonNullable(json['department_id']), partNumber: json['part_number'] as String?,
departmentName: _readDepartmentName(json, 'department_name') as String?, departmentId: _intFromJsonNullable(json['department_id']),
locationDetail: json['location_detail'] as String?, departmentName: _readDepartmentName(json, 'department_name') as String?,
assignedToUserId: _intFromJsonNullable(json['assigned_to_user_id']), locationDetail: json['location_detail'] as String?,
maintenanceInchargeUserId: _intFromJsonNullable( assignedToUserId: _intFromJsonNullable(json['assigned_to_user_id']),
json['maintenance_incharge_user_id'], maintenanceInchargeUserId: _intFromJsonNullable(
), json['maintenance_incharge_user_id'],
maintenanceFrequencyInDays: _intFromJsonNullable( ),
json['maintenance_frequency_in_days'], maintenanceFrequencyInDays: _intFromJsonNullable(
), json['maintenance_frequency_in_days'],
maintenanceChecklistJson: _checklistFromJson( ),
json['maintenance_checklist_json'], maintenanceChecklistJson: _checklistFromJson(
), json['maintenance_checklist_json'],
commencementDate: _dateFromJsonNullable(json['commencement_date']), ),
vendorId: _intFromJsonNullable(json['vendor_id']), commencementDate: _dateFromJsonNullable(json['commencement_date']),
vendorName: _readVendorNameFromNested(json, 'vendor_name') as String?, vendorId: _intFromJsonNullable(json['vendor_id']),
poId: _intFromJsonNullable(json['po_id']), vendorName: _readVendorNameFromNested(json, 'vendor_name') as String?,
grnId: _intFromJsonNullable(json['grn_id']), poId: _intFromJsonNullable(json['po_id']),
grnItemId: _intFromJsonNullable(json['grn_item_id']), grnId: _intFromJsonNullable(json['grn_id']),
purchaseDate: _dateFromJsonNullable(json['purchase_date']), grnItemId: _intFromJsonNullable(json['grn_item_id']),
purchaseCost: _doubleFromJsonNullable(json['purchase_cost']), purchaseDate: _dateFromJsonNullable(json['purchase_date']),
usefulLifeYears: _intFromJsonNullable(json['useful_life_years']), purchaseCost: _doubleFromJsonNullable(json['purchase_cost']),
depreciationMethod: json['depreciation_method'] as String?, usefulLifeYears: _intFromJsonNullable(json['useful_life_years']),
depreciationRate: _doubleFromJsonNullable(json['depreciation_rate']), depreciationMethod: json['depreciation_method'] as String?,
salvageValue: _doubleFromJsonNullable(json['salvage_value']), depreciationRate: _doubleFromJsonNullable(json['depreciation_rate']),
warrantyExpiryDate: _dateFromJsonNullable(json['warranty_expiry_date']), salvageValue: _doubleFromJsonNullable(json['salvage_value']),
condition: json['condition'] as String?, salvagePercentage: _doubleFromJsonNullable(json['salvage_percentage']),
status: json['status'] as String?, currentValue: _doubleFromJsonNullable(json['current_value']),
qrCodeValue: json['qr_code_value'] as String?, depreciation: AssetDepreciationSummary.fromJsonNullable(json['depreciation']),
disposalDate: _dateFromJsonNullable(json['disposal_date']), warrantyExpiryDate: _dateFromJsonNullable(json['warranty_expiry_date']),
disposalReason: json['disposal_reason'] as String?, condition: json['condition'] as String?,
disposalValue: _doubleFromJsonNullable(json['disposal_value']), status: json['status'] as String?,
remarks: json['remarks'] as String?, qrCodeValue: json['qr_code_value'] as String?,
isActive: json['is_active'] as bool? ?? true, disposalDate: _dateFromJsonNullable(json['disposal_date']),
createdAt: _dateFromJsonNullable(json['created_at']), disposalReason: json['disposal_reason'] as String?,
updatedAt: _dateFromJsonNullable(json['updated_at']), disposalValue: _doubleFromJsonNullable(json['disposal_value']),
maintenance: AssetMaintenanceSummary.fromJsonNullable( remarks: json['remarks'] as String?,
json['maintenance'], 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( Map<String, dynamic> _$$AssetModelImplToJson(
_$AssetModelImpl instance, _$AssetModelImpl instance,
@ -141,6 +143,11 @@ Map<String, dynamic> _$$AssetModelImplToJson(
'depreciation_method': instance.depreciationMethod, 'depreciation_method': instance.depreciationMethod,
'depreciation_rate': instance.depreciationRate, 'depreciation_rate': instance.depreciationRate,
'salvage_value': instance.salvageValue, 'salvage_value': instance.salvageValue,
'salvage_percentage': instance.salvagePercentage,
'current_value': instance.currentValue,
'depreciation': AssetDepreciationSummary.toJsonNullable(
instance.depreciation,
),
'warranty_expiry_date': instance.warrantyExpiryDate?.toIso8601String(), 'warranty_expiry_date': instance.warrantyExpiryDate?.toIso8601String(),
'condition': instance.condition, 'condition': instance.condition,
'status': instance.status, 'status': instance.status,

View File

@ -27,7 +27,7 @@ const _sidebarChildIndent = 28.0;
/// Set to `true` to show the Light/Dark toggle in the sidebar again. /// Set to `true` to show the Light/Dark toggle in the sidebar again.
/// Kept hidden for now do not delete `_buildThemeToggle`. /// Kept hidden for now do not delete `_buildThemeToggle`.
const showSidebarThemeToggle = true; const showSidebarThemeToggle = false;
class AppSidebar extends ConsumerStatefulWidget { class AppSidebar extends ConsumerStatefulWidget {
const AppSidebar({ const AppSidebar({