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

335 lines
8.6 KiB
Dart
Raw Permalink 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;
// --- 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,
// 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,
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}}';
}
}