feat(inventory): editable vernacular (common) names

The "also known as" names were read-only; now they can be added and removed:
- Repository: addVernacularName + removeVernacularName (soft delete);
  VarietyDetail.vernacularNames carries ids (new VernacularName model).
- Cubit: addVernacularName / removeVernacularName.
- Detail: deletable name chips + an "Add name" chip opening a small dialog;
  the section is always visible so names can be added when empty.

Tests: repo add/remove + widget add/remove. 44 green.
This commit is contained in:
vjrj 2026-07-08 11:20:36 +02:00
parent f8d4d9a778
commit 9e5c82184b
10 changed files with 256 additions and 83 deletions

View file

@ -92,6 +92,24 @@ class VarietyLot extends Equatable {
];
}
/// One vernacular (common) name of a variety.
class VernacularName extends Equatable {
const VernacularName({
required this.id,
required this.name,
this.language,
this.region,
});
final String id;
final String name;
final String? language;
final String? region;
@override
List<Object?> get props => [id, name, language, region];
}
/// The full detail of one variety (identity + its lots, names and first photo).
class VarietyDetail extends Equatable {
const VarietyDetail({
@ -113,7 +131,7 @@ class VarietyDetail extends Equatable {
final String? speciesId;
final String? scientificName;
final List<VarietyLot> lots;
final List<String> vernacularNames;
final List<VernacularName> vernacularNames;
final Uint8List? photo;
@override
@ -373,7 +391,16 @@ class VarietyRepository {
speciesId: v.speciesId,
scientificName: scientificName,
lots: lots.map((l) => _toLot(l, testsByLot[l.id] ?? const [])).toList(),
vernacularNames: names.map((n) => n.name).toList(),
vernacularNames: names
.map(
(n) => VernacularName(
id: n.id,
name: n.name,
language: n.language,
region: n.region,
),
)
.toList(),
photo: photos.isEmpty ? null : photos.first.bytes,
);
}
@ -491,6 +518,46 @@ class VarietyRepository {
);
}
/// Adds a vernacular (common) name to a variety. Returns the new row id.
Future<String> addVernacularName(
String varietyId,
String name, {
String? language,
String? region,
}) async {
final (created, updated) = _stamp();
final id = idGen.newId();
await _db
.into(_db.varietyVernacularNames)
.insert(
VarietyVernacularNamesCompanion.insert(
id: id,
varietyId: varietyId,
name: name,
createdAt: created,
updatedAt: updated,
lastAuthor: nodeId,
language: Value(language),
region: Value(region),
),
);
return id;
}
/// Soft-deletes a vernacular name (tombstone).
Future<void> removeVernacularName(String nameId) async {
final (_, updated) = _stamp();
await (_db.update(
_db.varietyVernacularNames,
)..where((n) => n.id.equals(nameId))).write(
VarietyVernacularNamesCompanion(
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 {