98 lines
3.7 KiB
Dart
98 lines
3.7 KiB
Dart
class FuelEntry {
|
|
final String id;
|
|
|
|
/// References [Vehicle.id] (the hidden, immutable identifier) — not the
|
|
/// VIN, which is user-editable and so unsuitable as a foreign key.
|
|
final String vehicleId;
|
|
|
|
final DateTime date;
|
|
final double gallons;
|
|
final double pricePerGallon;
|
|
final double totalCost;
|
|
final String? receiptImagePath;
|
|
final String? receiptDriveFileId;
|
|
final DateTime updatedAt;
|
|
|
|
/// Soft-delete tombstone: null means active. See [Vehicle.deletedAt] for
|
|
/// why this is a flag rather than an actual row deletion.
|
|
final DateTime? deletedAt;
|
|
|
|
FuelEntry({
|
|
required this.id,
|
|
required this.vehicleId,
|
|
required this.date,
|
|
required this.gallons,
|
|
required this.pricePerGallon,
|
|
required this.totalCost,
|
|
required this.updatedAt,
|
|
this.receiptImagePath,
|
|
this.receiptDriveFileId,
|
|
this.deletedAt,
|
|
});
|
|
|
|
/// True once the receipt photo has been uploaded to Drive, regardless of
|
|
/// whether a local copy is also being kept (see the "keep photos on this
|
|
/// phone" setting). Viewing it locally is preferred when a local copy
|
|
/// exists; otherwise it requires downloading from Drive on demand.
|
|
bool get isReceiptUploadedToDrive => receiptDriveFileId != null;
|
|
|
|
/// True while a receipt photo still needs to be uploaded to Drive: a
|
|
/// local file exists but hasn't made it there yet. Once
|
|
/// [receiptDriveFileId] is set this is false — even if a local copy is
|
|
/// also being kept — since that field alone is what marks "no longer
|
|
/// needs uploading", independent of local retention. A row in this state
|
|
/// is never pushed to Drive as data (a local file path is meaningless on
|
|
/// another device) — sync uploads the image first, which sets
|
|
/// [receiptDriveFileId] and clears this.
|
|
bool get needsReceiptUpload => receiptImagePath != null && receiptDriveFileId == null;
|
|
|
|
FuelEntry copyWith({
|
|
String? receiptImagePath,
|
|
String? receiptDriveFileId,
|
|
DateTime? updatedAt,
|
|
DateTime? deletedAt,
|
|
bool clearReceiptImagePath = false,
|
|
}) {
|
|
return FuelEntry(
|
|
id: id,
|
|
vehicleId: vehicleId,
|
|
date: date,
|
|
gallons: gallons,
|
|
pricePerGallon: pricePerGallon,
|
|
totalCost: totalCost,
|
|
updatedAt: updatedAt ?? this.updatedAt,
|
|
receiptImagePath:
|
|
clearReceiptImagePath ? null : (receiptImagePath ?? this.receiptImagePath),
|
|
receiptDriveFileId: receiptDriveFileId ?? this.receiptDriveFileId,
|
|
deletedAt: deletedAt ?? this.deletedAt,
|
|
);
|
|
}
|
|
|
|
Map<String, Object?> toMap() => {
|
|
'id': id,
|
|
'vehicle_id': vehicleId,
|
|
'date': date.millisecondsSinceEpoch,
|
|
'gallons': gallons,
|
|
'price_per_gallon': pricePerGallon,
|
|
'total_cost': totalCost,
|
|
'receipt_image_path': receiptImagePath,
|
|
'receipt_drive_file_id': receiptDriveFileId,
|
|
'updated_at': updatedAt.millisecondsSinceEpoch,
|
|
'deleted_at': deletedAt?.millisecondsSinceEpoch,
|
|
};
|
|
|
|
factory FuelEntry.fromMap(Map<String, Object?> map) => FuelEntry(
|
|
id: map['id'] as String,
|
|
vehicleId: map['vehicle_id'] as String,
|
|
date: DateTime.fromMillisecondsSinceEpoch(map['date'] as int),
|
|
gallons: (map['gallons'] as num).toDouble(),
|
|
pricePerGallon: (map['price_per_gallon'] as num).toDouble(),
|
|
totalCost: (map['total_cost'] as num).toDouble(),
|
|
receiptImagePath: map['receipt_image_path'] as String?,
|
|
receiptDriveFileId: map['receipt_drive_file_id'] as String?,
|
|
updatedAt: DateTime.fromMillisecondsSinceEpoch(map['updated_at'] as int, isUtc: true),
|
|
deletedAt: map['deleted_at'] != null
|
|
? DateTime.fromMillisecondsSinceEpoch(map['deleted_at'] as int, isUtc: true)
|
|
: null,
|
|
);
|
|
}
|