feat: implement matchedVia tracking for receipt items and enhance user alias management
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import type { CategorySuggestion } from '../../ai/ai.service';
|
||||
|
||||
export type MatchedVia = 'alias' | 'wordmatch' | 'ai' | 'none';
|
||||
|
||||
export interface ParsedReceiptItem {
|
||||
rawName: string;
|
||||
quantity: number;
|
||||
@@ -15,4 +17,6 @@ export interface ParsedReceiptItem {
|
||||
suggestedProductName?: string;
|
||||
// AI-kategorisuggestion för ej matchade varor (premium)
|
||||
categorySuggestion?: CategorySuggestion;
|
||||
// matchkälla för UI-visning
|
||||
matchedVia?: MatchedVia;
|
||||
}
|
||||
|
||||
@@ -257,4 +257,41 @@ describe('ReceiptImportService test matrix', () => {
|
||||
expect(result[0].unit).toBe('st');
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchedVia', () => {
|
||||
it('sätter matchedVia: alias vid aliasträff', async () => {
|
||||
prismaMock.receiptAlias.findMany.mockResolvedValue([
|
||||
{
|
||||
receiptName: 'snickers',
|
||||
productId: 222,
|
||||
product: { id: 222, name: 'Snickers', canonicalName: 'Snickers', categoryId: null, categoryRef: null },
|
||||
},
|
||||
]);
|
||||
prismaMock.product.findMany.mockResolvedValue([]);
|
||||
|
||||
const result = await (service as any).matchProducts([{ rawName: 'SNICKERS' }], 10);
|
||||
|
||||
expect(result[0].matchedVia).toBe('alias');
|
||||
});
|
||||
|
||||
it('sätter matchedVia: wordmatch vid ordbaserad matchning', async () => {
|
||||
prismaMock.receiptAlias.findMany.mockResolvedValue([]);
|
||||
prismaMock.product.findMany.mockResolvedValue([
|
||||
{ id: 300, name: 'Mjolk', canonicalName: 'Mjolk', categoryId: null, categoryRef: null },
|
||||
]);
|
||||
|
||||
const result = await (service as any).matchProducts([{ rawName: 'MJOLK 1L' }], 10);
|
||||
|
||||
expect(result[0].matchedVia).toBe('wordmatch');
|
||||
});
|
||||
|
||||
it('sätter matchedVia: none när ingen matchning finns', async () => {
|
||||
prismaMock.receiptAlias.findMany.mockResolvedValue([]);
|
||||
prismaMock.product.findMany.mockResolvedValue([]);
|
||||
|
||||
const result = await (service as any).matchProducts([{ rawName: 'XYZXYZ' }], 10);
|
||||
|
||||
expect(result[0].matchedVia).toBe('none');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -309,6 +309,7 @@ export class ReceiptImportService {
|
||||
matchedProductId: alias.product.id,
|
||||
matchedProductName: alias.product.canonicalName ?? alias.product.name,
|
||||
unit: mappedUnit ?? item.unit,
|
||||
matchedVia: 'alias' as const,
|
||||
...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'high' as const, usedFallback: false } } : {}),
|
||||
};
|
||||
}
|
||||
@@ -316,7 +317,7 @@ export class ReceiptImportService {
|
||||
// 2. Ordbaserad matchning (förslag, kräver bekräftelse)
|
||||
const suggestion = this.findWordMatch(raw, products);
|
||||
if (!suggestion) {
|
||||
return { ...item };
|
||||
return { ...item, matchedVia: 'none' as const };
|
||||
}
|
||||
|
||||
// Kontrollera om det finns en enhetsmappning för produkten och användaren
|
||||
@@ -333,6 +334,7 @@ export class ReceiptImportService {
|
||||
suggestedProductId: suggestion.id,
|
||||
suggestedProductName: suggestion.canonicalName ?? suggestion.name,
|
||||
unit: preferredUnit,
|
||||
matchedVia: 'wordmatch' as const,
|
||||
...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'medium' as const, usedFallback: false } } : {}),
|
||||
};
|
||||
});
|
||||
@@ -547,7 +549,7 @@ export class ReceiptImportService {
|
||||
|
||||
enriched.push(
|
||||
finalSuggestion
|
||||
? { ...item, categorySuggestion: finalSuggestion }
|
||||
? { ...item, categorySuggestion: finalSuggestion, matchedVia: item.matchedVia ?? (finalSuggestion ? 'ai' as const : 'none' as const) }
|
||||
: item,
|
||||
);
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user