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/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});
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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/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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -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'],
|
||||
) ??
|
||||
|
||||
@ -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']),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user