feat: add rematch functionality for recipe ingredients and enhance inventory management
Test Suite / test (24.15.0) (push) Has been cancelled

- Added a new API path for rematching recipe ingredients in `api_paths.dart`.
- Implemented a manual product creation dialog in `inventory_screen.dart` to allow users to create new products directly.
- Integrated the rematch functionality in `recipe_repository.dart` to handle rematching of recipe ingredients.
- Updated the recipe detail screen to include a button for triggering the rematch process.
- Introduced a new `RecipeMatchingService` in the backend to handle ingredient matching logic.
- Added database migration to include `aiEngineEnabled` column in the User table.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Nils-Johan Gynther
2026-05-06 09:20:31 +02:00
parent 9fe85a719c
commit 04b1fc3024
53 changed files with 1420 additions and 652 deletions
@@ -159,7 +159,7 @@ export class ReceiptImportService {
const matched = await this.matchProducts(rawItems, userId);
// Steg 3: Regel + AI-kategorisering för alla användare
return this.enrichWithAiCategories(matched);
return this.enrichWithAiCategories(matched, userId);
}
private async parseReceiptViaImporter(file: Express.Multer.File): Promise<ParsedReceiptItem[]> {
@@ -341,7 +341,7 @@ export class ReceiptImportService {
return best?.product;
}
private async enrichWithAiCategories(items: ParsedReceiptItem[]): Promise<ParsedReceiptItem[]> {
private async enrichWithAiCategories(items: ParsedReceiptItem[], userId?: number): Promise<ParsedReceiptItem[]> {
let categories: Awaited<ReturnType<CategoriesService['findFlattened']>>;
try {
categories = await this.categoriesService.findFlattened();
@@ -349,6 +349,13 @@ export class ReceiptImportService {
return items; // Om kategoritjänsten är otillgänglig, returnera utan AI-förslag
}
const user = userId
? await this.prisma.user.findUnique({
where: { id: userId },
select: { aiEngineEnabled: true },
})
: null;
const enriched: ParsedReceiptItem[] = [];
for (const item of items) {
if (!item.rawName) {
@@ -424,9 +431,13 @@ export class ReceiptImportService {
// AI används som fallback när varken matchning eller regler satte kategori
if (!nextSuggestion) {
pushTrace('ai invoked');
nextSuggestion = await this.aiService.suggestCategory(item.rawName, categories);
pushTrace(`ai result -> "${nextSuggestion.path}" (${nextSuggestion.confidence})`);
if (user?.aiEngineEnabled) {
pushTrace('ai invoked');
nextSuggestion = await this.aiService.suggestCategory(item.rawName, categories);
pushTrace(`ai result -> "${nextSuggestion.path}" (${nextSuggestion.confidence})`);
} else {
pushTrace('ai skipped, feature disabled');
}
} else {
pushTrace(`ai skipped, current -> "${nextSuggestion.path}"`);
}