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/route_constants.dart';
import '../../../../core/errors/failure.dart';
import '../../../../core/utils/formatters.dart';
import '../../../../shared/models/asset_model.dart';
import '../../../../shared/models/entity_attachment_model.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 '../providers/assets_provider.dart';
import '../providers/asset_form_lookups_provider.dart';
import '../utils/maintenance_due_display.dart';
import 'asset_form_screen.dart';
import '../widgets/asset_maintenance_panel.dart';
import '../widgets/asset_side_panels.dart';
@ -236,6 +238,13 @@ class _OverviewTab extends ConsumerWidget {
asset.maintenanceInchargeUserId,
users,
);
final daysUntilDueDisplay = MaintenanceDueDisplay.fromDaysUntilDue(
asset.maintenance?.daysUntilDue,
);
final retainedPct = _retainedPercentage(
currentValue: asset.resolvedCurrentValue,
purchaseCost: asset.purchaseCost,
);
return SingleChildScrollView(
child: Center(
@ -267,95 +276,176 @@ class _OverviewTab extends ConsumerWidget {
),
],
),
const SizedBox(height: 12),
_AssetInfoGrid(
items: [
_AssetInfo('Asset Name', asset.assetName),
_AssetInfo('Asset Code', asset.assetCode ?? ''),
_AssetInfo('Category', asset.assetCategoryName ?? ''),
_AssetInfo(
'Subcategory',
asset.assetSubcategoryName ?? '',
),
_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',
const SizedBox(height: 16),
// 1) Top Summary
_AssetOverviewSection(
title: 'Summary',
child: _AssetInfoGrid(
items: [
_AssetInfo.widget(
'Status',
AppStatusChip(status: asset.status ?? 'IN_USE'),
),
_AssetInfo(
'Next Due Date',
asset.maintenance!.nextDueDate != null
? dateFormat
.format(asset.maintenance!.nextDueDate!)
'Maintenance Due',
asset.maintenance == null
? ''
: (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(
'Days Until Due',
asset.maintenance!.daysUntilDue?.toString() ?? '',
'Purchase Cost',
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 ||
asset.maintenanceChecklistJson?.isNotEmpty == true ||
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 {
const _AssetInfoGrid({required this.items});

View File

@ -82,6 +82,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
final _usefulLifeController = TextEditingController();
final _depreciationRateController = TextEditingController();
final _salvageValueController = TextEditingController();
final _salvagePercentageController = TextEditingController();
final _remarksController = TextEditingController();
final _frequencyController = TextEditingController();
int? _categoryId;
@ -97,6 +98,8 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
String? _status;
String? _condition;
String? _depreciationMethod;
/// When true, salvage is entered as % of purchase cost; otherwise as amount.
bool _salvageAsPercentage = false;
DateTime? _warrantyExpiry;
DateTime? _purchaseDate;
DateTime? _commencementDate;
@ -123,6 +126,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
super.initState();
_costController.addListener(_onDepreciationFieldChanged);
_salvageValueController.addListener(_onDepreciationFieldChanged);
_salvagePercentageController.addListener(_onDepreciationFieldChanged);
_usefulLifeController.addListener(_onDepreciationFieldChanged);
_depreciationRateController.addListener(_onDepreciationFieldChanged);
}
@ -142,6 +146,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
_scrollController.dispose();
_costController.removeListener(_onDepreciationFieldChanged);
_salvageValueController.removeListener(_onDepreciationFieldChanged);
_salvagePercentageController.removeListener(_onDepreciationFieldChanged);
_usefulLifeController.removeListener(_onDepreciationFieldChanged);
_depreciationRateController.removeListener(_onDepreciationFieldChanged);
_nameController.dispose();
@ -157,6 +162,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
_usefulLifeController.dispose();
_depreciationRateController.dispose();
_salvageValueController.dispose();
_salvagePercentageController.dispose();
_remarksController.dispose();
_frequencyController.dispose();
super.dispose();
@ -166,6 +172,71 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
_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) =>
'${asset.id}:${asset.assetCategoryId}:${asset.assetSubcategoryId}:'
'${asset.locationId}:${asset.status}:${asset.assetName}';
@ -226,7 +297,10 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
_grnItemId = _nullablePositiveId(asset.grnItemId);
_status = asset.status;
_condition = asset.condition;
_depreciationMethod = asset.depreciationMethod;
_depreciationMethod = asset.depreciationMethod?.trim().toUpperCase() ==
'OTHER'
? 'CUSTOM'
: asset.depreciationMethod;
_warrantyExpiry = asset.warrantyExpiryDate;
_purchaseDate = asset.purchaseDate;
_commencementDate = asset.commencementDate;
@ -246,8 +320,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
CurrencyFormatter.formatEditable(asset.disposalValue);
_usefulLifeController.text = asset.usefulLifeYears?.toString() ?? '';
_depreciationRateController.text = asset.depreciationRate?.toString() ?? '';
_salvageValueController.text =
CurrencyFormatter.formatEditable(asset.salvageValue);
_applySalvageFromAsset(asset);
_remarksController.text = asset.remarks ?? '';
_frequencyController.text =
asset.maintenanceFrequencyInDays?.toString() ?? '';
@ -268,13 +341,13 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
}
Map<String, dynamic>? _buildDepreciationPreviewPayload() {
final method = _depreciationMethod?.trim().toUpperCase();
final method = _normalizedDepreciationMethod;
if (method == null || method.isEmpty) {
return null;
}
final rate = CurrencyFormatter.tryParse(_depreciationRateController.text);
if (method == 'OTHER' && rate == null) {
if (_requiresCustomRate && rate == null) {
return null;
}
@ -286,9 +359,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
final purchaseCost = CurrencyFormatter.tryParse(_costController.text);
if (purchaseCost != null) payload['purchase_cost'] = purchaseCost;
final salvageValue =
CurrencyFormatter.tryParse(_salvageValueController.text);
if (salvageValue != null) payload['salvage_value'] = salvageValue;
_putSalvageIntoPayload(payload);
final usefulLife = int.tryParse(_usefulLifeController.text.trim());
if (usefulLife != null) payload['useful_life_years'] = usefulLife;
@ -312,8 +383,8 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
setState(() {
_isDepreciationPreviewLoading = false;
_depreciationPreview = null;
_depreciationPreviewError = _depreciationMethod?.trim().toUpperCase() == 'OTHER'
? 'Depreciation rate is required for OTHER'
_depreciationPreviewError = _requiresCustomRate
? 'Depreciation rate is required for CUSTOM'
: null;
});
return;
@ -393,7 +464,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
}
_putOptionalDouble(payload, 'purchase_cost', _costController.text);
_putOptionalDouble(payload, 'salvage_value', _salvageValueController.text);
_putSalvageIntoPayload(payload);
_putOptionalDouble(payload, 'disposal_value', _disposalValueController.text);
final usefulLife = int.tryParse(_usefulLifeController.text.trim());
@ -417,8 +488,9 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
payload['maintenance_checklist_json'] = checklistPayload;
}
if (_depreciationMethod != null) {
payload['depreciation_method'] = _depreciationMethod;
final method = _normalizedDepreciationMethod;
if (method != null) {
payload['depreciation_method'] = method;
}
final depreciationRate =
@ -516,23 +588,30 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
}
final purchaseCost = CurrencyFormatter.tryParse(_costController.text);
final salvageValue =
CurrencyFormatter.tryParse(_salvageValueController.text);
if (purchaseCost != null &&
salvageValue != null &&
salvageValue > purchaseCost) {
return 'Salvage value cannot exceed purchase cost';
if (_salvageAsPercentage) {
final pct =
CurrencyFormatter.tryParse(_salvagePercentageController.text);
if (pct != null && (pct < 0 || pct > 100)) {
return 'Salvage percentage must be between 0 and 100';
}
} else {
final salvageValue =
CurrencyFormatter.tryParse(_salvageValueController.text);
if (purchaseCost != null &&
salvageValue != null &&
salvageValue > purchaseCost) {
return 'Salvage value cannot exceed purchase cost';
}
}
final hasDepreciationMethod =
_depreciationMethod != null && _depreciationMethod!.trim().isNotEmpty;
final hasDepreciationMethod = _normalizedDepreciationMethod != null;
if (hasDepreciationMethod) {
if (_usefulLifeController.text.trim().isEmpty) {
return 'Useful life is required when depreciation method is set';
}
final method = _depreciationMethod!.trim().toUpperCase();
if (method == 'OTHER' && _depreciationRateController.text.trim().isEmpty) {
return 'Depreciation rate is required when depreciation method is OTHER';
if (_requiresCustomRate &&
_depreciationRateController.text.trim().isEmpty) {
return 'Depreciation rate is required when depreciation method is CUSTOM';
}
}
@ -887,8 +966,10 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
AppDropdown<String>(
isDense: true,
label: 'Depreciation Method',
value: _depreciationMethod,
options: assetOptionDropdowns(depreciationMethods),
value: _normalizedDepreciationMethod,
options: _depreciationMethodOptions(
depreciationMethods,
),
enabled: depreciationMethods.isNotEmpty,
hint: depreciationMethods.isEmpty
? 'Loading depreciation methods...'
@ -910,20 +991,7 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
fieldName: 'Depreciation Rate',
),
),
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',
),
),
_buildSalvageField(),
],
),
_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({
required String label,
required int? value,
@ -1686,6 +1837,15 @@ class _DepreciationPreviewCard extends StatelessWidget {
label: 'Current Book Value',
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(
label: 'Years Elapsed',
value: preview!.yearsElapsed.toString(),

View File

@ -427,6 +427,28 @@ class _AssetDataTable extends StatelessWidget {
searchText: (asset) => 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(
label: 'Warranty Validity',
flex: 1,

View File

@ -18,6 +18,7 @@ import '../../../../shared/widgets/app_toast.dart';
import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart';
import '../providers/assets_provider.dart';
import '../utils/maintenance_due_display.dart';
import '../widgets/asset_maintenance_panel.dart';
class AssetMaintenanceScreen extends ConsumerStatefulWidget {
@ -351,16 +352,10 @@ class _DueBadge extends StatelessWidget {
@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');
final due = MaintenanceDueDisplay.fromDaysUntilDue(daysUntilDue);
final color = due?.color ??
(isDue ? const Color(0xFFDC2626) : const Color(0xFF16A34A));
final label = due?.label ?? (isDue ? 'Due' : 'On track');
return Container(
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/validators.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/app_button.dart';
import '../../../../shared/widgets/app_card.dart';
@ -543,7 +545,7 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
_serviceCostController.text =
CurrencyFormatter.formatEditable(visit.serviceCost);
}
_isUnderAmc = visit.isUnderAmc;
_isUnderAmc = visit.isUnderAmc || visit.amcContractId != null;
_assetConditionAfter = visit.assetConditionAfter;
_remarksController.text = visit.remarks ?? '';
}
@ -570,9 +572,11 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
final serviceCost =
CurrencyFormatter.tryParse(_serviceCostController.text);
return {
if (_visitType != null && _visitType!.trim().isNotEmpty) 'visit_type': _visitType,
if (_visitType != null && _visitType!.trim().isNotEmpty)
'visit_type': _visitType,
'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)
'complaint_no': _complaintNoController.text.trim(),
if (_complaintDate != null)
@ -594,7 +598,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
if (downtimeHours != null) 'downtime_hours': downtimeHours,
if (serviceCost != null) 'service_cost': serviceCost,
'is_under_amc': _isUnderAmc,
if (_assetConditionAfter != null && _assetConditionAfter!.trim().isNotEmpty)
if (_assetConditionAfter != null &&
_assetConditionAfter!.trim().isNotEmpty)
'asset_condition_after': _assetConditionAfter,
if (_remarksController.text.trim().isNotEmpty)
'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) {
final lookupsAsync = ref.watch(assetFormLookupsProvider);
final options = lookupsAsync.valueOrNull?.options ?? const AssetDropdownOptionsModel();
final options =
lookupsAsync.valueOrNull?.options ?? const AssetDropdownOptionsModel();
if (lookupsAsync.hasValue) {
final nextVisitType = resolveAssetOptionValue(
@ -666,6 +722,7 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1) Visit classification
SidePanelFormRow(
left: AppSearchableDropdown<String>(
label: 'Visit Type *',
@ -698,24 +755,35 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
onPicked: (date) => setState(() => _visitDate = date),
),
),
const SizedBox(height: 12),
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) => setState(() => _amcContractId = v),
const SizedBox(height: 8),
// 2) Contract & Vendor
AppFormToggleField(
label: 'Under AMC',
value: _isUnderAmc,
onChanged: _setUnderAmc,
),
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),
SidePanelFormRow(
left: lookupsAsync.when(
@ -728,13 +796,10 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
label: 'Vendor',
value: _vendorId,
searchHint: 'Search vendor...',
options: lookups.vendors
.map((vendor) => AppDropdownOption(
value: int.tryParse(vendor.id) ?? 0,
label: vendor.name,
))
.where((option) => option.value != 0)
.toList(),
options: _vendorOptions(
vendors: lookups.vendors,
amcContracts: amcContracts,
),
onChanged: (v) => setState(() => _vendorId = v),
),
),
@ -747,6 +812,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
onChanged: (v) => setState(() => _assetConditionAfter = v),
),
),
// 3) Complaint intake
SidePanelFormRow(
left: AppTextField(
controller: _complaintNoController,
@ -767,6 +834,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
maxLines: 3,
),
const SizedBox(height: 12),
// 4) Engineer & work performed
SidePanelFormRow(
left: AppTextField(
controller: _engineerNameController,
@ -792,6 +861,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
maxLines: 3,
),
const SizedBox(height: 12),
// 5) Outcome & cost
SidePanelFormRow(
left: _SidePanelDateField(
label: 'Next Service Date',
@ -804,7 +875,8 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
right: AppTextField(
controller: _downtimeHoursController,
label: 'Downtime Hours',
keyboardType: const TextInputType.numberWithOptions(decimal: true),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
validator: (v) => Validators.optionalPositiveDouble(
v,
fieldName: 'Downtime Hours',
@ -827,12 +899,6 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
label: 'Remarks',
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
Widget build(BuildContext context) {
final theme = Theme.of(context);
final labelStyle = theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
@ -103,22 +107,20 @@ class _DetailRow extends StatelessWidget {
children: [
SizedBox(
width: 140,
child: Text(
label,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
child: Text(label, style: labelStyle),
),
if (child != null)
// 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.yearsElapsed,
this.salvageValue,
this.salvagePercentage,
this.annualDepreciation,
this.accumulatedDepreciation,
this.bookValue,
@ -190,6 +191,7 @@ class DepreciationReportRow {
final int? usefulLifeYears;
final double? yearsElapsed;
final double? salvageValue;
final double? salvagePercentage;
final double? annualDepreciation;
final double? accumulatedDepreciation;
final double? bookValue;
@ -300,6 +302,10 @@ class DepreciationReportRow {
json['salvage_value'] ?? json['salvageValue'],
) ??
toDouble(depMap['salvage_value']),
salvagePercentage: toDouble(
json['salvage_percentage'] ?? json['salvagePercentage'],
) ??
toDouble(depMap['salvage_percentage']),
annualDepreciation: toDouble(
json['annual_depreciation'] ?? json['annualDepreciation'],
) ??

View File

@ -200,6 +200,16 @@ class AssetModel with _$AssetModel {
@JsonKey(name: 'depreciation_rate', fromJson: _doubleFromJsonNullable)
double? depreciationRate,
@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)
DateTime? warrantyExpiryDate,
String? condition,
@ -223,6 +233,84 @@ class AssetModel with _$AssetModel {
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 {
const AssetMaintenanceChecklistItem({
required this.label,
@ -708,6 +796,7 @@ class AssetDepreciationPreviewModel {
required this.yearsElapsed,
required this.purchaseCost,
required this.salvageValue,
this.salvagePercentage,
required this.usefulLifeYears,
});
@ -719,6 +808,7 @@ class AssetDepreciationPreviewModel {
final int yearsElapsed;
final double purchaseCost;
final double salvageValue;
final double? salvagePercentage;
final int usefulLifeYears;
factory AssetDepreciationPreviewModel.fromJson(Map<String, dynamic> json) {
@ -742,6 +832,7 @@ class AssetDepreciationPreviewModel {
yearsElapsed: toInt(json['years_elapsed']),
purchaseCost: toDouble(json['purchase_cost']),
salvageValue: toDouble(json['salvage_value']),
salvagePercentage: _doubleFromJsonNullable(json['salvage_percentage']),
usefulLifeYears: toInt(json['useful_life_years']),
);
}

View File

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

View File

@ -42,67 +42,69 @@ Map<String, dynamic> _$$AssetCategoryModelImplToJson(
'updatedAt': instance.updatedAt?.toIso8601String(),
};
_$AssetModelImpl _$$AssetModelImplFromJson(Map<String, dynamic> json) =>
_$AssetModelImpl(
id: _idFromJson(json['id']),
assetName: json['asset_name'] as String,
assetCode: json['asset_code'] as String?,
assetCategoryId: _intFromJsonNullable(
_readItemCategoryId(json, 'item_category_id'),
),
assetCategoryName:
_readItemCategoryName(json, 'item_category_name') as String?,
assetSubcategoryId: _intFromJsonNullable(
_readItemSubcategoryId(json, 'item_subcategory_id'),
),
assetSubcategoryName:
_readItemSubcategoryName(json, 'item_subcategory_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?,
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']),
grnId: _intFromJsonNullable(json['grn_id']),
grnItemId: _intFromJsonNullable(json['grn_item_id']),
purchaseDate: _dateFromJsonNullable(json['purchase_date']),
purchaseCost: _doubleFromJsonNullable(json['purchase_cost']),
usefulLifeYears: _intFromJsonNullable(json['useful_life_years']),
depreciationMethod: json['depreciation_method'] as String?,
depreciationRate: _doubleFromJsonNullable(json['depreciation_rate']),
salvageValue: _doubleFromJsonNullable(json['salvage_value']),
warrantyExpiryDate: _dateFromJsonNullable(json['warranty_expiry_date']),
condition: json['condition'] as String?,
status: json['status'] as String?,
qrCodeValue: json['qr_code_value'] as String?,
disposalDate: _dateFromJsonNullable(json['disposal_date']),
disposalReason: json['disposal_reason'] as String?,
disposalValue: _doubleFromJsonNullable(json['disposal_value']),
remarks: json['remarks'] as String?,
isActive: json['is_active'] as bool? ?? true,
createdAt: _dateFromJsonNullable(json['created_at']),
updatedAt: _dateFromJsonNullable(json['updated_at']),
maintenance: AssetMaintenanceSummary.fromJsonNullable(
json['maintenance'],
),
);
_$AssetModelImpl _$$AssetModelImplFromJson(
Map<String, dynamic> json,
) => _$AssetModelImpl(
id: _idFromJson(json['id']),
assetName: json['asset_name'] as String,
assetCode: json['asset_code'] as String?,
assetCategoryId: _intFromJsonNullable(
_readItemCategoryId(json, 'item_category_id'),
),
assetCategoryName:
_readItemCategoryName(json, 'item_category_name') as String?,
assetSubcategoryId: _intFromJsonNullable(
_readItemSubcategoryId(json, 'item_subcategory_id'),
),
assetSubcategoryName:
_readItemSubcategoryName(json, 'item_subcategory_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?,
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']),
grnId: _intFromJsonNullable(json['grn_id']),
grnItemId: _intFromJsonNullable(json['grn_item_id']),
purchaseDate: _dateFromJsonNullable(json['purchase_date']),
purchaseCost: _doubleFromJsonNullable(json['purchase_cost']),
usefulLifeYears: _intFromJsonNullable(json['useful_life_years']),
depreciationMethod: json['depreciation_method'] as String?,
depreciationRate: _doubleFromJsonNullable(json['depreciation_rate']),
salvageValue: _doubleFromJsonNullable(json['salvage_value']),
salvagePercentage: _doubleFromJsonNullable(json['salvage_percentage']),
currentValue: _doubleFromJsonNullable(json['current_value']),
depreciation: AssetDepreciationSummary.fromJsonNullable(json['depreciation']),
warrantyExpiryDate: _dateFromJsonNullable(json['warranty_expiry_date']),
condition: json['condition'] as String?,
status: json['status'] as String?,
qrCodeValue: json['qr_code_value'] as String?,
disposalDate: _dateFromJsonNullable(json['disposal_date']),
disposalReason: json['disposal_reason'] as String?,
disposalValue: _doubleFromJsonNullable(json['disposal_value']),
remarks: json['remarks'] as String?,
isActive: json['is_active'] as bool? ?? true,
createdAt: _dateFromJsonNullable(json['created_at']),
updatedAt: _dateFromJsonNullable(json['updated_at']),
maintenance: AssetMaintenanceSummary.fromJsonNullable(json['maintenance']),
);
Map<String, dynamic> _$$AssetModelImplToJson(
_$AssetModelImpl instance,
@ -141,6 +143,11 @@ Map<String, dynamic> _$$AssetModelImplToJson(
'depreciation_method': instance.depreciationMethod,
'depreciation_rate': instance.depreciationRate,
'salvage_value': instance.salvageValue,
'salvage_percentage': instance.salvagePercentage,
'current_value': instance.currentValue,
'depreciation': AssetDepreciationSummary.toJsonNullable(
instance.depreciation,
),
'warranty_expiry_date': instance.warrantyExpiryDate?.toIso8601String(),
'condition': instance.condition,
'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.
/// Kept hidden for now do not delete `_buildThemeToggle`.
const showSidebarThemeToggle = true;
const showSidebarThemeToggle = false;
class AppSidebar extends ConsumerStatefulWidget {
const AppSidebar({