feat(inventory): additive CSV import for bulk digitization

Import a spreadsheet list (the same 21-column schema the CSV export
already produces) as new varieties/lots. Tolerant reader: columns
matched by header name in any order, only variety_label required,
unknown enums fall back to a safe default, rows sharing variety_id
collapse into one variety with several lots. Species re-linked by
scientific name. Additive by design (fresh ids + HLC), so it never
overwrites existing rows; JSON stays the canonical id-reconciling import.

Adds a hand-rolled RFC 4180 parser (no new dependency), an importCsv
path in VarietyRepository/ExportImportService, an 'Import from CSV'
tile in the backup section, and en/es strings.

Note: i18n .g.dart also carries pre-existing About/intro strings from
the working tree (regenerated by slang).
This commit is contained in:
vjrj 2026-07-09 14:41:28 +02:00
parent 21072633b5
commit 89b61bc9e4
11 changed files with 1070 additions and 2 deletions

View file

@ -56,6 +56,16 @@ class ExportImportService {
return _repository.importInventory(snapshot);
}
/// Imports a spreadsheet CSV additively (every row becomes a new variety/lot;
/// see [VarietyRepository.importCsv]). Returns the summary (`inserted` =
/// varieties added), or null when the user cancelled the file dialog.
Future<ImportSummary?> importCsv() async {
final bytes = await _files.pickFileBytes(allowedExtensions: ['csv']);
if (bytes == null) return null;
final csv = _csvCodec.decode(utf8.decode(bytes));
return _repository.importCsv(csv);
}
String _fileName(String extension) {
final date = _now().toIso8601String().substring(0, 10);
return 'tanemaki-inventory-$date.$extension';