ponshu-room-lite/lib/models/sake_item.dart

335 lines
8.6 KiB
Dart
Raw Normal View History

import 'package:hive/hive.dart';
import 'schema/display_data.dart';
import 'schema/hidden_specs.dart';
import 'schema/user_data.dart';
import 'schema/gamification.dart';
import 'schema/metadata.dart';
import 'schema/item_type.dart';
export 'schema/display_data.dart';
export 'schema/hidden_specs.dart';
export 'schema/user_data.dart';
export 'schema/gamification.dart';
export 'schema/metadata.dart';
export 'schema/item_type.dart';
part 'sake_item.g.dart';
@HiveType(typeId: 0)
class SakeItem extends HiveObject {
// --- Root Identity ---
@HiveField(0)
final String id;
// --- New Hierarchical Data (Fields 20+) ---
@HiveField(20)
DisplayData? _displayData;
@HiveField(21)
HiddenSpecs? _hiddenSpecs;
@HiveField(22)
UserData? _userData;
@HiveField(23)
Gamification? _gamification;
@HiveField(24)
Metadata? _metadata;
@HiveField(25)
ItemType? _itemType;
// --- Phase 1: Draft Mode Fields (Fields 26-27) ---
@HiveField(26)
bool? _isPendingAnalysis; // true = AI解析待ち, false/null = 通常登録済み
@HiveField(27)
String? _draftPhotoPath; // 解析待ち写真の一時パスDraft時のみ
// --- Legacy Fields (Deprecated: Kept for Migration) ---
@HiveField(1)
final String? legacyName;
@HiveField(2)
final String? legacyBrand;
@HiveField(3)
final String? legacyPrefecture;
@HiveField(4)
final String? legacyDescription;
@HiveField(5)
final String? legacyCatchCopy;
@HiveField(6)
final List<String>? legacyImagePaths;
@HiveField(7)
final double? legacySweetnessScore;
@HiveField(8)
final double? legacyBodyScore;
@HiveField(9)
final DateTime? legacyCreatedAt;
@HiveField(10)
final int? legacyConfidenceScore;
@HiveField(11)
final List<String>? legacyFlavorTags;
@HiveField(12)
final bool? legacyIsFavorite;
@HiveField(13)
final Map<String, int>? legacyTasteStats;
@HiveField(14)
final bool? legacyIsUserEdited;
@HiveField(15)
final int? legacyCostPrice;
@HiveField(16)
final int? legacyManualPrice;
@HiveField(17)
final double? legacyMarkup;
@HiveField(18)
final Map<String, int>? legacyPriceVariants;
SakeItem({
required this.id,
DisplayData? displayData,
HiddenSpecs? hiddenSpecs,
UserData? userData,
Gamification? gamification,
Metadata? metadata,
ItemType? itemType,
// Phase 1: Draft Mode params
bool? isPendingAnalysis,
String? draftPhotoPath,
// Legacy params for migration compatibility (optional)
this.legacyName,
this.legacyBrand,
this.legacyPrefecture,
this.legacyDescription,
this.legacyCatchCopy,
this.legacyImagePaths,
this.legacySweetnessScore,
this.legacyBodyScore,
this.legacyCreatedAt,
this.legacyConfidenceScore,
this.legacyFlavorTags,
this.legacyIsFavorite,
this.legacyTasteStats,
this.legacyIsUserEdited,
this.legacyCostPrice,
this.legacyManualPrice,
this.legacyMarkup,
this.legacyPriceVariants,
}) {
_displayData = displayData;
_hiddenSpecs = hiddenSpecs;
_userData = userData;
_gamification = gamification;
_metadata = metadata;
_itemType = itemType;
_isPendingAnalysis = isPendingAnalysis;
_draftPhotoPath = draftPhotoPath;
}
// --- Smart Getters (Auto-Migration / Fallback) ---
DisplayData get displayData {
if (_displayData != null) return _displayData!;
// Fallback: Construct from Legacy
return DisplayData(
name: legacyName ?? 'Unknown',
brewery: legacyBrand ?? 'Unknown',
prefecture: legacyPrefecture ?? 'Unknown',
catchCopy: legacyCatchCopy,
imagePaths: legacyImagePaths ?? [],
rating: null, // Legacy didn't have rating explicit in display
);
}
// Allow setting for UI updates
set displayData(DisplayData val) {
_displayData = val;
save(); // Auto-save on set? Or just update memory. HiveObject usually requires save().
// Better to just update memory here.
}
HiddenSpecs get hiddenSpecs {
if (_hiddenSpecs != null) return _hiddenSpecs!;
return HiddenSpecs(
description: legacyDescription,
tasteStats: legacyTasteStats ?? {},
flavorTags: legacyFlavorTags ?? [],
sweetnessScore: legacySweetnessScore,
bodyScore: legacyBodyScore,
);
}
// Allow setting for さけのわ auto-matching
set hiddenSpecs(HiddenSpecs val) {
_hiddenSpecs = val;
}
UserData get userData {
if (_userData != null) return _userData!;
return UserData(
isFavorite: legacyIsFavorite ?? false,
isUserEdited: legacyIsUserEdited ?? false,
price: legacyManualPrice,
costPrice: legacyCostPrice,
markup: legacyMarkup ?? 3.0,
priceVariants: legacyPriceVariants,
);
}
Gamification get gamification {
if (_gamification != null) return _gamification!;
return Gamification(
ponPoints: 0, // Legacy didn't track
);
}
Metadata get metadata {
if (_metadata != null) return _metadata!;
return Metadata(
createdAt: legacyCreatedAt ?? DateTime.now(),
aiConfidence: legacyConfidenceScore,
);
}
ItemType get itemType {
// Default to 'sake' for existing data
return _itemType ?? ItemType.sake;
}
set itemType(ItemType val) {
_itemType = val;
}
// Phase 1: Draft Mode Getters/Setters
bool get isPendingAnalysis => _isPendingAnalysis ?? false;
set isPendingAnalysis(bool val) {
_isPendingAnalysis = val;
}
// ignore: unnecessary_getters_setters
String? get draftPhotoPath => _draftPhotoPath;
set draftPhotoPath(String? val) {
_draftPhotoPath = val;
}
// --- Migration Method ---
// Returns true if migration was performed (i.e., was legacy)
bool ensureMigrated() {
if (_displayData != null) return false; // Already new structure
// Create New Objects from Legacy Fields
_displayData = displayData; // Uses getter logic
_hiddenSpecs = hiddenSpecs;
_userData = userData;
_gamification = gamification;
_metadata = metadata;
return true;
}
// --- CopyWith for Immutable Updates (Backend Compatible) ---
SakeItem copyWith({
String? name,
String? brand, // Maps to Brewery
String? prefecture,
String? description,
String? catchCopy,
List<String>? imagePaths,
double? sweetnessScore,
double? bodyScore,
int? confidenceScore, // Maps to aiConfidence
List<String>? flavorTags,
bool? isFavorite,
Map<String, int>? tasteStats,
bool? isUserEdited,
String? memo,
int? costPrice,
int? manualPrice, // Maps to price
double? markup,
Map<String, int>? priceVariants,
ItemType? itemType,
2026-01-15 15:53:44 +00:00
// New Specs
String? specificDesignation, // Maps to hiddenSpecs.type
double? alcoholContent,
int? polishingRatio,
double? sakeMeterValue,
String? riceVariety,
String? yeast,
String? manufacturingYearMonth,
// Phase 1: Draft Mode
bool? isPendingAnalysis,
String? draftPhotoPath,
}) {
// Ensure we have current data structures
final currentDisplay = displayData;
final currentHidden = hiddenSpecs;
final currentUser = userData;
final currentMeta = metadata;
final currentGame = gamification;
return SakeItem(
id: id,
displayData: currentDisplay.copyWith(
name: name,
brewery: brand,
prefecture: prefecture,
catchCopy: catchCopy,
imagePaths: imagePaths,
),
hiddenSpecs: currentHidden.copyWith(
description: description,
tasteStats: tasteStats,
flavorTags: flavorTags,
sweetnessScore: sweetnessScore,
bodyScore: bodyScore,
2026-01-15 15:53:44 +00:00
type: specificDesignation,
alcoholContent: alcoholContent,
polishingRatio: polishingRatio,
sakeMeterValue: sakeMeterValue,
riceVariety: riceVariety,
yeast: yeast,
manufacturingYearMonth: manufacturingYearMonth,
),
userData: currentUser.copyWith(
isFavorite: isFavorite,
isUserEdited: isUserEdited,
memo: memo,
price: manualPrice,
costPrice: costPrice,
markup: markup,
priceVariants: priceVariants,
),
gamification: currentGame,
metadata: currentMeta.copyWith(
aiConfidence: confidenceScore,
),
itemType: itemType ?? this.itemType,
// Phase 1: Draft Mode
isPendingAnalysis: isPendingAnalysis ?? this.isPendingAnalysis,
draftPhotoPath: draftPhotoPath ?? this.draftPhotoPath,
);
}
// Compact JSON for QR ecosystem
String toQrJson() {
return '{"id":"$id","n":"${displayData.displayName}","b":"${displayData.displayBrewery}","p":"${displayData.displayPrefecture}","s":${hiddenSpecs.sweetnessScore ?? 0},"y":${hiddenSpecs.bodyScore ?? 0},"a":${hiddenSpecs.alcoholContent ?? 0}}';
}
}