review changes done
This commit is contained in:
parent
167bd99e0a
commit
048ecab961
@ -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,23 +276,79 @@ class _OverviewTab extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 16),
|
||||||
_AssetInfoGrid(
|
|
||||||
|
// 1) Top Summary
|
||||||
|
_AssetOverviewSection(
|
||||||
|
title: 'Summary',
|
||||||
|
child: _AssetInfoGrid(
|
||||||
|
items: [
|
||||||
|
_AssetInfo.widget(
|
||||||
|
'Status',
|
||||||
|
AppStatusChip(status: asset.status ?? 'IN_USE'),
|
||||||
|
),
|
||||||
|
_AssetInfo(
|
||||||
|
'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: [
|
items: [
|
||||||
_AssetInfo('Asset Name', asset.assetName),
|
_AssetInfo('Asset Name', asset.assetName),
|
||||||
_AssetInfo('Asset Code', asset.assetCode ?? '—'),
|
_AssetInfo('Asset Code', asset.assetCode ?? '—'),
|
||||||
_AssetInfo('Category', asset.assetCategoryName ?? '—'),
|
_AssetInfo('Location', asset.locationName ?? '—'),
|
||||||
|
_AssetInfo(
|
||||||
|
'Asset Category',
|
||||||
|
asset.assetCategoryName ?? '—',
|
||||||
|
),
|
||||||
_AssetInfo(
|
_AssetInfo(
|
||||||
'Subcategory',
|
'Subcategory',
|
||||||
asset.assetSubcategoryName ?? '—',
|
asset.assetSubcategoryName ?? '—',
|
||||||
),
|
),
|
||||||
_AssetInfo('Location', asset.locationName ?? '—'),
|
|
||||||
_AssetInfo(
|
_AssetInfo(
|
||||||
'Commencement Date',
|
'Manufacturer',
|
||||||
asset.commencementDate != null
|
asset.manufacturer ?? '—',
|
||||||
? dateFormat.format(asset.commencementDate!)
|
|
||||||
: '—',
|
|
||||||
),
|
),
|
||||||
|
_AssetInfo(
|
||||||
|
'Brand / Model',
|
||||||
|
asset.brandModel ?? '—',
|
||||||
|
),
|
||||||
|
_AssetInfo(
|
||||||
|
'Serial Number',
|
||||||
|
asset.serialNumber ?? '—',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 3) Maintenance
|
||||||
|
_AssetOverviewSection(
|
||||||
|
title: 'Maintenance',
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_AssetInfoGrid(
|
||||||
|
items: [
|
||||||
_AssetInfo(
|
_AssetInfo(
|
||||||
'Maintenance Incharge',
|
'Maintenance Incharge',
|
||||||
maintenanceInchargeLabel,
|
maintenanceInchargeLabel,
|
||||||
@ -294,68 +359,93 @@ class _OverviewTab extends ConsumerWidget {
|
|||||||
? '${asset.maintenanceFrequencyInDays} days'
|
? '${asset.maintenanceFrequencyInDays} days'
|
||||||
: '—',
|
: '—',
|
||||||
),
|
),
|
||||||
if (asset.maintenance != null) ...[
|
|
||||||
_AssetInfo(
|
|
||||||
'Maintenance Due',
|
|
||||||
asset.maintenance!.isDue ? 'Yes' : 'No',
|
|
||||||
),
|
|
||||||
_AssetInfo(
|
_AssetInfo(
|
||||||
'Next Due Date',
|
'Next Due Date',
|
||||||
asset.maintenance!.nextDueDate != null
|
asset.maintenance?.nextDueDate != null
|
||||||
? dateFormat
|
? dateFormat.format(
|
||||||
.format(asset.maintenance!.nextDueDate!)
|
asset.maintenance!.nextDueDate!,
|
||||||
|
)
|
||||||
: '—',
|
: '—',
|
||||||
),
|
),
|
||||||
_AssetInfo(
|
if (daysUntilDueDisplay == null)
|
||||||
|
const _AssetInfo('Days Until Due', '—')
|
||||||
|
else
|
||||||
|
_AssetInfo.widget(
|
||||||
'Days Until Due',
|
'Days Until Due',
|
||||||
asset.maintenance!.daysUntilDue?.toString() ?? '—',
|
Text(
|
||||||
|
daysUntilDueDisplay.label,
|
||||||
|
style: TextStyle(
|
||||||
|
color: daysUntilDueDisplay.color,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_AssetInfo(
|
||||||
|
'Condition',
|
||||||
|
assetConditionLabel(asset.condition),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
_AssetInfo('Serial Number', asset.serialNumber ?? '—'),
|
),
|
||||||
_AssetInfo('Brand / Model', asset.brandModel ?? '—'),
|
if (asset.maintenance != null) ...[
|
||||||
_AssetInfo('Manufacturer', asset.manufacturer ?? '—'),
|
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(
|
_AssetInfo(
|
||||||
'Purchase Date',
|
'Purchase Date',
|
||||||
asset.purchaseDate != null
|
asset.purchaseDate != null
|
||||||
? dateFormat.format(asset.purchaseDate!)
|
? dateFormat.format(asset.purchaseDate!)
|
||||||
: '—',
|
: '—',
|
||||||
),
|
),
|
||||||
|
_AssetInfo(
|
||||||
|
'Purchase Cost',
|
||||||
|
asset.purchaseCost != null
|
||||||
|
? CurrencyFormatter.format(asset.purchaseCost)
|
||||||
|
: '—',
|
||||||
|
),
|
||||||
_AssetInfo(
|
_AssetInfo(
|
||||||
'Warranty Expiry',
|
'Warranty Expiry',
|
||||||
asset.warrantyExpiryDate != null
|
asset.warrantyExpiryDate != null
|
||||||
? dateFormat.format(asset.warrantyExpiryDate!)
|
? 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});
|
||||||
|
|
||||||
|
|||||||
@ -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,6 +588,13 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final purchaseCost = CurrencyFormatter.tryParse(_costController.text);
|
final purchaseCost = CurrencyFormatter.tryParse(_costController.text);
|
||||||
|
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 =
|
final salvageValue =
|
||||||
CurrencyFormatter.tryParse(_salvageValueController.text);
|
CurrencyFormatter.tryParse(_salvageValueController.text);
|
||||||
if (purchaseCost != null &&
|
if (purchaseCost != null &&
|
||||||
@ -523,16 +602,16 @@ class _AssetFormScreenState extends ConsumerState<AssetFormScreen> {
|
|||||||
salvageValue > purchaseCost) {
|
salvageValue > purchaseCost) {
|
||||||
return 'Salvage value cannot exceed purchase cost';
|
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(),
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,7 +755,16 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
|
|||||||
onPicked: (date) => setState(() => _visitDate = date),
|
onPicked: (date) => setState(() => _visitDate = date),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// 2) Contract & Vendor
|
||||||
|
AppFormToggleField(
|
||||||
|
label: 'Under AMC',
|
||||||
|
value: _isUnderAmc,
|
||||||
|
onChanged: _setUnderAmc,
|
||||||
|
),
|
||||||
|
if (_isUnderAmc) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
AppSearchableDropdown<int>(
|
AppSearchableDropdown<int>(
|
||||||
label: 'AMC Contract',
|
label: 'AMC Contract',
|
||||||
value: _amcContractId,
|
value: _amcContractId,
|
||||||
@ -707,15 +773,17 @@ class _LogServiceVisitPanelState extends ConsumerState<LogServiceVisitPanel> {
|
|||||||
.map((contract) {
|
.map((contract) {
|
||||||
final id = int.tryParse(contract.id);
|
final id = int.tryParse(contract.id);
|
||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
final label = contract.contractNo?.trim().isNotEmpty == true
|
final label =
|
||||||
|
contract.contractNo?.trim().isNotEmpty == true
|
||||||
? contract.contractNo!
|
? contract.contractNo!
|
||||||
: 'AMC #${contract.id}';
|
: 'AMC #${contract.id}';
|
||||||
return AppDropdownOption(value: id, label: label);
|
return AppDropdownOption(value: id, label: label);
|
||||||
})
|
})
|
||||||
.whereType<AppDropdownOption<int>>()
|
.whereType<AppDropdownOption<int>>()
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (v) => setState(() => _amcContractId = v),
|
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),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,16 +107,14 @@ 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(
|
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
if (child != null)
|
||||||
|
// Keep badge left-aligned with text values (do not expand Chip).
|
||||||
|
child!
|
||||||
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: child ??
|
child: SelectableText(
|
||||||
SelectableText(
|
|
||||||
value ?? '—',
|
value ?? '—',
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|||||||
@ -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'],
|
||||||
) ??
|
) ??
|
||||||
|
|||||||
@ -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']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -42,8 +42,9 @@ 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,
|
||||||
|
) => _$AssetModelImpl(
|
||||||
id: _idFromJson(json['id']),
|
id: _idFromJson(json['id']),
|
||||||
assetName: json['asset_name'] as String,
|
assetName: json['asset_name'] as String,
|
||||||
assetCode: json['asset_code'] as String?,
|
assetCode: json['asset_code'] as String?,
|
||||||
@ -88,6 +89,9 @@ _$AssetModelImpl _$$AssetModelImplFromJson(Map<String, dynamic> json) =>
|
|||||||
depreciationMethod: json['depreciation_method'] as String?,
|
depreciationMethod: json['depreciation_method'] as String?,
|
||||||
depreciationRate: _doubleFromJsonNullable(json['depreciation_rate']),
|
depreciationRate: _doubleFromJsonNullable(json['depreciation_rate']),
|
||||||
salvageValue: _doubleFromJsonNullable(json['salvage_value']),
|
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']),
|
warrantyExpiryDate: _dateFromJsonNullable(json['warranty_expiry_date']),
|
||||||
condition: json['condition'] as String?,
|
condition: json['condition'] as String?,
|
||||||
status: json['status'] as String?,
|
status: json['status'] as String?,
|
||||||
@ -99,10 +103,8 @@ _$AssetModelImpl _$$AssetModelImplFromJson(Map<String, dynamic> json) =>
|
|||||||
isActive: json['is_active'] as bool? ?? true,
|
isActive: json['is_active'] as bool? ?? true,
|
||||||
createdAt: _dateFromJsonNullable(json['created_at']),
|
createdAt: _dateFromJsonNullable(json['created_at']),
|
||||||
updatedAt: _dateFromJsonNullable(json['updated_at']),
|
updatedAt: _dateFromJsonNullable(json['updated_at']),
|
||||||
maintenance: AssetMaintenanceSummary.fromJsonNullable(
|
maintenance: AssetMaintenanceSummary.fromJsonNullable(json['maintenance']),
|
||||||
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,
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user