From 7d63b615b6b38aa57a4e02f3caf819d6ed3739f9 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Thu, 7 May 2026 08:10:56 +0200 Subject: [PATCH] feat: add unit mapping functionality and confirmation dialog for unit changes in import process --- backend/prisma/schema.prisma | 15 ++++++++ .../receipt-import/receipt-import.service.ts | 12 +++++- .../import/presentation/edit_dialog.dart | 37 +++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 72b16cd0..7e758290 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -242,3 +242,18 @@ model Nutrition { fiber Float? product Product @relation(fields: [productId], references: [id], onDelete: Cascade) } + +model UnitMapping { + id Int @id @default(autoincrement()) + productId Int + originalUnit String + preferredUnit String + userId Int + + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([productId, originalUnit, userId]) + @@index([productId]) + @@index([userId]) +} diff --git a/backend/src/receipt-import/receipt-import.service.ts b/backend/src/receipt-import/receipt-import.service.ts index e18528a6..1803e230 100644 --- a/backend/src/receipt-import/receipt-import.service.ts +++ b/backend/src/receipt-import/receipt-import.service.ts @@ -215,7 +215,7 @@ export class ReceiptImportService { ], } : { isGlobal: true }; - const [aliases, products] = await Promise.all([ + const [aliases, products, unitMappings] = await Promise.all([ this.prisma.receiptAlias.findMany({ where: aliasFilter, orderBy: [ @@ -228,6 +228,10 @@ export class ReceiptImportService { where: productFilter, select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true } } }, }), + this.prisma.unitMapping.findMany({ + where: { userId: userId }, + select: { productId: true, originalUnit: true, preferredUnit: true }, + }), ]); return items.map((item) => { @@ -251,11 +255,17 @@ export class ReceiptImportService { if (!suggestion) { return { ...item }; } + + // Kontrollera om det finns en enhetsmappning för produkten och användaren + const unitMapping = unitMappings.find((um) => um.productId === suggestion.id && um.originalUnit === item.unit); + const preferredUnit = unitMapping ? unitMapping.preferredUnit : item.unit; + const cat = suggestion.categoryRef; return { ...item, suggestedProductId: suggestion.id, suggestedProductName: suggestion.canonicalName ?? suggestion.name, + unit: preferredUnit, ...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'medium' as const, usedFallback: false } } : {}), }; }); diff --git a/flutter/lib/features/import/presentation/edit_dialog.dart b/flutter/lib/features/import/presentation/edit_dialog.dart index ecf7dcb1..3cdd5b5e 100644 --- a/flutter/lib/features/import/presentation/edit_dialog.dart +++ b/flutter/lib/features/import/presentation/edit_dialog.dart @@ -206,6 +206,11 @@ class _EditDialogState extends State { } Future _confirm() async { + final originalUnit = widget.current.unit ?? widget.item.unit; + final newUnit = _unitCtrl.text.trim().isEmpty ? originalUnit : _unitCtrl.text.trim(); + + await _confirmUnitChange(originalUnit!, newUnit); + if (_entryMode == ImportProductEntryMode.create) { final trimmedName = _newProductNameCtrl.text.trim(); if (trimmedName.isEmpty) { @@ -281,6 +286,38 @@ class _EditDialogState extends State { ); } + Future _confirmUnitChange(String originalUnit, String newUnit) async { + if (originalUnit == newUnit) return; + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Bekräfta enhetsändring'), + content: Text( + 'Du försöker ändra enheten från "$originalUnit" till "$newUnit". Vill du fortsätta med denna ändring?', + ), + actions: [ + TextButton( + child: const Text('Avbryt'), + onPressed: () { + _unitCtrl.text = originalUnit; + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Bekräfta'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + // ── Build ────────────────────────────────────────────────────────────────── @override