feat(inventory): month/year harvest date picker (schema v3)
Replace the free-text harvest year with a themed month/year picker: a tappable field opens a green-styled dialog with month and year grids (navigable, paginated years), month optional. Lots now read e.g. "September 2021" or just "Year 2021". - schema v3: add nullable Lot.harvest_month (1..12) + migration + tests - repo/cubit propagate harvestMonth; localized month names (en/es) via slang
This commit is contained in:
parent
9e5c82184b
commit
5d4053157f
17 changed files with 3799 additions and 57 deletions
|
|
@ -65,6 +65,7 @@ class VarietyLot extends Equatable {
|
|||
required this.id,
|
||||
this.type = LotType.seed,
|
||||
this.harvestYear,
|
||||
this.harvestMonth,
|
||||
this.quantity,
|
||||
this.storageLocation,
|
||||
this.germinationTests = const [],
|
||||
|
|
@ -73,6 +74,9 @@ class VarietyLot extends Equatable {
|
|||
final String id;
|
||||
final LotType type;
|
||||
final int? harvestYear;
|
||||
|
||||
/// Optional harvest month (1..12). Only meaningful when [harvestYear] is set.
|
||||
final int? harvestMonth;
|
||||
final Quantity? quantity;
|
||||
final String? storageLocation;
|
||||
final List<GerminationEntry> germinationTests;
|
||||
|
|
@ -86,6 +90,7 @@ class VarietyLot extends Equatable {
|
|||
id,
|
||||
type,
|
||||
harvestYear,
|
||||
harvestMonth,
|
||||
quantity,
|
||||
storageLocation,
|
||||
germinationTests,
|
||||
|
|
@ -110,7 +115,18 @@ class VernacularName extends Equatable {
|
|||
List<Object?> get props => [id, name, language, region];
|
||||
}
|
||||
|
||||
/// The full detail of one variety (identity + its lots, names and first photo).
|
||||
/// One stored photo of a variety (encrypted BLOB in the DB).
|
||||
class VarietyPhoto extends Equatable {
|
||||
const VarietyPhoto({required this.id, required this.bytes});
|
||||
|
||||
final String id;
|
||||
final Uint8List bytes;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id, bytes];
|
||||
}
|
||||
|
||||
/// The full detail of one variety (identity + its lots, names and photos).
|
||||
class VarietyDetail extends Equatable {
|
||||
const VarietyDetail({
|
||||
required this.id,
|
||||
|
|
@ -121,7 +137,7 @@ class VarietyDetail extends Equatable {
|
|||
this.scientificName,
|
||||
this.lots = const [],
|
||||
this.vernacularNames = const [],
|
||||
this.photo,
|
||||
this.photos = const [],
|
||||
});
|
||||
|
||||
final String id;
|
||||
|
|
@ -132,7 +148,7 @@ class VarietyDetail extends Equatable {
|
|||
final String? scientificName;
|
||||
final List<VarietyLot> lots;
|
||||
final List<VernacularName> vernacularNames;
|
||||
final Uint8List? photo;
|
||||
final List<VarietyPhoto> photos;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
|
|
@ -144,7 +160,7 @@ class VarietyDetail extends Equatable {
|
|||
scientificName,
|
||||
lots,
|
||||
vernacularNames,
|
||||
photo,
|
||||
photos,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -380,7 +396,7 @@ class VarietyRepository {
|
|||
a.kind.equalsValue(AttachmentKind.photo) &
|
||||
a.isDeleted.equals(false),
|
||||
)
|
||||
..limit(1))
|
||||
..orderBy([(a) => OrderingTerm(expression: a.createdAt)]))
|
||||
.get();
|
||||
|
||||
return VarietyDetail(
|
||||
|
|
@ -401,7 +417,10 @@ class VarietyRepository {
|
|||
),
|
||||
)
|
||||
.toList(),
|
||||
photo: photos.isEmpty ? null : photos.first.bytes,
|
||||
photos: [
|
||||
for (final p in photos)
|
||||
if (p.bytes != null) VarietyPhoto(id: p.id, bytes: p.bytes!),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -457,6 +476,7 @@ class VarietyRepository {
|
|||
required String varietyId,
|
||||
LotType type = LotType.seed,
|
||||
int? harvestYear,
|
||||
int? harvestMonth,
|
||||
Quantity? quantity,
|
||||
String? storageLocation,
|
||||
}) async {
|
||||
|
|
@ -473,6 +493,7 @@ class VarietyRepository {
|
|||
lastAuthor: nodeId,
|
||||
type: Value(type),
|
||||
harvestYear: Value(harvestYear),
|
||||
harvestMonth: Value(harvestMonth),
|
||||
quantityKind: Value(quantity?.kind.name),
|
||||
quantityPrecise: Value(quantity?.count?.toDouble()),
|
||||
quantityLabel: Value(quantity?.label),
|
||||
|
|
@ -482,12 +503,13 @@ class VarietyRepository {
|
|||
return id;
|
||||
}
|
||||
|
||||
/// Updates an existing lot's fields (LWW). The [quantity] and [harvestYear]
|
||||
/// are set as given (null clears them).
|
||||
/// Updates an existing lot's fields (LWW). The [quantity], [harvestYear] and
|
||||
/// [harvestMonth] are set as given (null clears them).
|
||||
Future<void> updateLot({
|
||||
required String lotId,
|
||||
required LotType type,
|
||||
int? harvestYear,
|
||||
int? harvestMonth,
|
||||
Quantity? quantity,
|
||||
String? storageLocation,
|
||||
}) async {
|
||||
|
|
@ -496,6 +518,7 @@ class VarietyRepository {
|
|||
LotsCompanion(
|
||||
type: Value(type),
|
||||
harvestYear: Value(harvestYear),
|
||||
harvestMonth: Value(harvestMonth),
|
||||
quantityKind: Value(quantity?.kind.name),
|
||||
quantityPrecise: Value(quantity?.count?.toDouble()),
|
||||
quantityLabel: Value(quantity?.label),
|
||||
|
|
@ -558,6 +581,42 @@ class VarietyRepository {
|
|||
);
|
||||
}
|
||||
|
||||
/// Adds a photo (encrypted BLOB) to a variety. Returns the new attachment id.
|
||||
Future<String> addPhoto(String varietyId, Uint8List bytes) async {
|
||||
final (created, updated) = _stamp();
|
||||
final id = idGen.newId();
|
||||
await _db
|
||||
.into(_db.attachments)
|
||||
.insert(
|
||||
AttachmentsCompanion.insert(
|
||||
id: id,
|
||||
createdAt: created,
|
||||
updatedAt: updated,
|
||||
lastAuthor: nodeId,
|
||||
parentType: ParentType.variety,
|
||||
parentId: varietyId,
|
||||
kind: AttachmentKind.photo,
|
||||
bytes: Value(bytes),
|
||||
mimeType: const Value('image/jpeg'),
|
||||
),
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Soft-deletes a photo (tombstone).
|
||||
Future<void> removePhoto(String attachmentId) async {
|
||||
final (_, updated) = _stamp();
|
||||
await (_db.update(
|
||||
_db.attachments,
|
||||
)..where((a) => a.id.equals(attachmentId))).write(
|
||||
AttachmentsCompanion(
|
||||
isDeleted: const Value(true),
|
||||
updatedAt: Value(updated),
|
||||
lastAuthor: Value(nodeId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Soft-deletes a variety (tombstone); it disappears from the inventory but
|
||||
/// the row survives for correct CRDT merges later.
|
||||
Future<void> softDeleteVariety(String id) async {
|
||||
|
|
@ -608,6 +667,7 @@ class VarietyRepository {
|
|||
id: l.id,
|
||||
type: l.type,
|
||||
harvestYear: l.harvestYear,
|
||||
harvestMonth: l.harvestMonth,
|
||||
storageLocation: l.storageLocation,
|
||||
germinationTests: germinationTests,
|
||||
quantity: hasQuantity
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue