feat: add unit mapping functionality and confirmation dialog for unit changes in import process
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-07 08:10:56 +02:00
parent a19bc1279a
commit 7d63b615b6
3 changed files with 63 additions and 1 deletions
+15
View File
@@ -242,3 +242,18 @@ model Nutrition {
fiber Float? fiber Float?
product Product @relation(fields: [productId], references: [id], onDelete: Cascade) 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])
}
@@ -215,7 +215,7 @@ export class ReceiptImportService {
], ],
} }
: { isGlobal: true }; : { isGlobal: true };
const [aliases, products] = await Promise.all([ const [aliases, products, unitMappings] = await Promise.all([
this.prisma.receiptAlias.findMany({ this.prisma.receiptAlias.findMany({
where: aliasFilter, where: aliasFilter,
orderBy: [ orderBy: [
@@ -228,6 +228,10 @@ export class ReceiptImportService {
where: productFilter, where: productFilter,
select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true } } }, 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) => { return items.map((item) => {
@@ -251,11 +255,17 @@ export class ReceiptImportService {
if (!suggestion) { if (!suggestion) {
return { ...item }; 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; const cat = suggestion.categoryRef;
return { return {
...item, ...item,
suggestedProductId: suggestion.id, suggestedProductId: suggestion.id,
suggestedProductName: suggestion.canonicalName ?? suggestion.name, suggestedProductName: suggestion.canonicalName ?? suggestion.name,
unit: preferredUnit,
...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'medium' as const, usedFallback: false } } : {}), ...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'medium' as const, usedFallback: false } } : {}),
}; };
}); });
@@ -206,6 +206,11 @@ class _EditDialogState extends State<EditDialog> {
} }
Future<void> _confirm() async { Future<void> _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) { if (_entryMode == ImportProductEntryMode.create) {
final trimmedName = _newProductNameCtrl.text.trim(); final trimmedName = _newProductNameCtrl.text.trim();
if (trimmedName.isEmpty) { if (trimmedName.isEmpty) {
@@ -281,6 +286,38 @@ class _EditDialogState extends State<EditDialog> {
); );
} }
Future<void> _confirmUnitChange(String originalUnit, String newUnit) async {
if (originalUnit == newUnit) return;
return showDialog<void>(
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: <Widget>[
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 ────────────────────────────────────────────────────────────────── // ── Build ──────────────────────────────────────────────────────────────────
@override @override