import '../../../common/domain/metal_type.dart'; class AssetImage { const AssetImage({ required this.localId, required this.localPath, required this.createdAt, this.remoteUrl, this.thumbnailUrl, }); final String localId; final String localPath; final String? remoteUrl; final String? thumbnailUrl; final DateTime createdAt; AssetImage copyWith({ String? localId, String? localPath, String? remoteUrl, String? thumbnailUrl, DateTime? createdAt, }) { return AssetImage( localId: localId ?? this.localId, localPath: localPath ?? this.localPath, remoteUrl: remoteUrl ?? this.remoteUrl, thumbnailUrl: thumbnailUrl ?? this.thumbnailUrl, createdAt: createdAt ?? this.createdAt, ); } Map toJson() { return { 'localId': localId, 'localPath': localPath, 'remoteUrl': remoteUrl, 'thumbnailUrl': thumbnailUrl, 'createdAt': createdAt.toIso8601String(), }; } factory AssetImage.fromJson(Map json) { return AssetImage( localId: json['localId'] as String? ?? '', localPath: json['localPath'] as String? ?? '', remoteUrl: json['remoteUrl'] as String?, thumbnailUrl: json['thumbnailUrl'] as String?, createdAt: DateTime.tryParse(json['createdAt'] as String? ?? '') ?? DateTime.fromMillisecondsSinceEpoch(0), ); } } enum AssetCategory { bar('金条'), necklace('项链'), ring('戒指'), bracelet('手镯'), bean('金豆'), earring('耳环'), silverware('银饰'), platinumPiece('铂金件'); const AssetCategory(this.label); final String label; } enum HoldingStatus { active, sold, gifted } class GoldAssetHolding { const GoldAssetHolding({ required this.id, required this.name, required this.metal, required this.category, required this.purity, required this.purityLabel, required this.weightGram, required this.createdAt, required this.updatedAt, this.images = const [], this.costAmount, this.purchaseDate, this.channel, this.note, this.status = HoldingStatus.active, }); final String id; final String name; final MetalType metal; final AssetCategory category; final double purity; final String purityLabel; final double weightGram; final List images; final double? costAmount; final DateTime? purchaseDate; final String? channel; final String? note; final DateTime createdAt; final DateTime updatedAt; final HoldingStatus status; GoldAssetHolding copyWith({ String? id, String? name, MetalType? metal, AssetCategory? category, double? purity, String? purityLabel, double? weightGram, List? images, double? costAmount, DateTime? purchaseDate, String? channel, String? note, DateTime? createdAt, DateTime? updatedAt, HoldingStatus? status, }) { return GoldAssetHolding( id: id ?? this.id, name: name ?? this.name, metal: metal ?? this.metal, category: category ?? this.category, purity: purity ?? this.purity, purityLabel: purityLabel ?? this.purityLabel, weightGram: weightGram ?? this.weightGram, images: images ?? this.images, costAmount: costAmount ?? this.costAmount, purchaseDate: purchaseDate ?? this.purchaseDate, channel: channel ?? this.channel, note: note ?? this.note, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, status: status ?? this.status, ); } double materialValue(double spotPrice) => weightGram * purity * spotPrice; Map toJson() { return { 'id': id, 'name': name, 'metalId': metal.id, 'categoryId': category.name, 'purity': purity, 'purityLabel': purityLabel, 'weightGram': weightGram, 'images': images.map((item) => item.toJson()).toList(growable: false), 'costAmount': costAmount, 'purchaseDate': purchaseDate?.toIso8601String(), 'channel': channel, 'note': note, 'createdAt': createdAt.toIso8601String(), 'updatedAt': updatedAt.toIso8601String(), 'status': status.name, }; } factory GoldAssetHolding.fromJson(Map json) { final rawImages = json['images']; return GoldAssetHolding( id: json['id'] as String? ?? '', name: json['name'] as String? ?? '', metal: _metalFromId(json['metalId'] as String?), category: _categoryFromId(json['categoryId'] as String?), purity: (json['purity'] as num?)?.toDouble() ?? 1, purityLabel: json['purityLabel'] as String? ?? '', weightGram: (json['weightGram'] as num?)?.toDouble() ?? 0, images: rawImages is List ? [ for (final raw in rawImages) if (raw is Map) AssetImage.fromJson(Map.from(raw)), ] : const [], costAmount: (json['costAmount'] as num?)?.toDouble(), purchaseDate: DateTime.tryParse(json['purchaseDate'] as String? ?? ''), channel: json['channel'] as String?, note: json['note'] as String?, createdAt: DateTime.tryParse(json['createdAt'] as String? ?? '') ?? DateTime.fromMillisecondsSinceEpoch(0), updatedAt: DateTime.tryParse(json['updatedAt'] as String? ?? '') ?? DateTime.fromMillisecondsSinceEpoch(0), status: _statusFromName(json['status'] as String?), ); } } class HoldingValuation { const HoldingValuation({ required this.holding, required this.materialValue, required this.todayChange, }); final GoldAssetHolding holding; final double materialValue; final double todayChange; } class MetalBreakdown { const MetalBreakdown({ required this.metal, required this.value, required this.weightGram, required this.count, }); final MetalType metal; final double value; final double weightGram; final int count; } class PortfolioSummary { const PortfolioSummary({ required this.totalValue, required this.todayChangeAmount, required this.todayChangePercent, required this.totalCost, required this.totalWeightGram, required this.recycleReferenceValue, required this.breakdowns, required this.holdings, }); final double totalValue; final double todayChangeAmount; final double todayChangePercent; final double totalCost; final double totalWeightGram; final double recycleReferenceValue; final List breakdowns; final List holdings; } MetalType _metalFromId(String? id) { return switch (id) { 'gold' => MetalType.gold, 'platinum' => MetalType.platinum, 'silver' => MetalType.silver, _ => MetalType.gold, }; } AssetCategory _categoryFromId(String? id) { return AssetCategory.values.firstWhere( (item) => item.name == id, orElse: () => AssetCategory.bar, ); } HoldingStatus _statusFromName(String? value) { return HoldingStatus.values.firstWhere( (item) => item.name == value, orElse: () => HoldingStatus.active, ); }