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

344 lines
8.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
// --- 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,
// 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
);
}
/// displayData/hiddenSpecs をまとめて更新して即座にHiveへ保存する。
/// setterを直接使わずこのメソッドを使うこと。
Future<void> applyUpdates({
DisplayData? displayData,
HiddenSpecs? hiddenSpecs,
}) async {
if (displayData != null) _displayData = displayData;
if (hiddenSpecs != null) _hiddenSpecs = hiddenSpecs;
await save();
}
@Deprecated('Use applyUpdates() instead to ensure save() is always called.')
set displayData(DisplayData val) {
_displayData = val;
}
HiddenSpecs get hiddenSpecs {
if (_hiddenSpecs != null) return _hiddenSpecs!;
return HiddenSpecs(
description: legacyDescription,
tasteStats: legacyTasteStats ?? {},
flavorTags: legacyFlavorTags ?? [],
sweetnessScore: legacySweetnessScore,
bodyScore: legacyBodyScore,
);
}
@Deprecated('Use applyUpdates() instead to ensure save() is always called.')
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;
}
// 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,
// New Specs
String? specificDesignation, // Maps to hiddenSpecs.type
double? alcoholContent,
int? polishingRatio,
double? sakeMeterValue,
String? riceVariety,
String? yeast,
String? manufacturingYearMonth,
// 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,
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,
// 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}}';
}
}