feat(inventory): add origin field to InventoryItem and update related DTOs and services

This commit is contained in:
Nils-Johan Gynther
2026-04-19 15:11:35 +02:00
parent 3b0208b5b4
commit 976a72612e
14 changed files with 210 additions and 23 deletions
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `InventoryItem` ADD COLUMN `origin` VARCHAR(191) NULL;
+1
View File
@@ -89,6 +89,7 @@ model InventoryItem {
quantity Decimal @db.Decimal(10, 2)
unit String
brand String?
origin String?
receiptName String?
location String?
purchaseDate DateTime?
@@ -32,6 +32,10 @@ export class CreateInventoryDto {
@IsString()
brand?: string;
@IsOptional()
@IsString()
origin?: string;
@IsOptional()
@IsString()
receiptName?: string;
@@ -143,6 +143,7 @@ export class InventoryService {
quantity: new Prisma.Decimal(data.quantity),
location: data.location?.trim() || undefined,
brand: data.brand?.trim() || undefined,
origin: data.origin?.trim() || undefined,
receiptName: data.receiptName?.trim() || undefined,
suitableFor: data.suitableFor?.trim() || undefined,
comment: data.comment?.trim() || undefined,
@@ -125,11 +125,20 @@ export class ProductsController {
return this.aiService.suggestCategory(product.canonicalName ?? product.name, categories);
}
@Roles('admin')
@Post()
create(@Body() body: CreateProductDto) {
return this.productsService.create(body);
}
@Post('pending')
createPending(
@Body() body: CreateProductDto,
@Request() req: { user: { id: number } },
) {
return this.productsService.createPending(body, req.user.id);
}
@Roles('admin')
@Post('merge')
merge(@Body() body: MergeProductsDto) {
+31
View File
@@ -427,6 +427,37 @@ export class ProductsService {
});
}
async createPending(data: CreateProductDto, userId: number) {
const name = data.name.trim();
const normalizedName = normalizeName(name);
const existing = await this.prisma.product.findUnique({
where: { normalizedName },
});
if (existing) {
// Om produkten redan finns (aktiv), returnera den direkt
if (existing.isActive && existing.status === 'active') {
return existing;
}
// Om det redan finns ett pending-förslag, returnera det
if (existing.status === 'pending') {
return existing;
}
}
return this.prisma.product.create({
data: {
name,
normalizedName,
canonicalName: name,
isActive: false,
status: 'pending',
ownerId: userId,
},
});
}
setStatus(id: number, status: string) {
return this.prisma.product.update({ where: { id }, data: { status } });
}
@@ -5,6 +5,8 @@ export interface ParsedReceiptItem {
quantity: number;
unit: string;
price?: number | null;
brand?: string | null;
origin?: string | null;
// alias-match: säker, användaren slipper bekräfta
matchedProductId?: number;
matchedProductName?: string;
@@ -19,6 +19,8 @@ Varje vara ska ha följande fält:
- "quantity": antal eller mängd som ett tal (t.ex. 1, 2, 0.5)
- "unit": enhet — välj ett av: "st", "kg", "g", "l", "dl", "cl", "ml", "förp", "pak", "burk", "flaska"
- "price": pris i SEK som ett tal, eller null
- "brand": märke eller leverantör om det tydligt framgår av varunamnet (t.ex. "Arla", "ICA", "Oatly"), annars null
- "origin": ursprungsland om det framgår av varunamnet (t.ex. "Brasilien", "Sverige", "Italien"), annars null
Returnera BARA JSON-arrayen utan markdown-formatering.`;
@@ -29,6 +31,8 @@ Varje vara ska ha följande fält:
- "quantity": antal eller mängd som ett tal (t.ex. 1, 2, 0.5)
- "unit": enhet — välj ett av: "st", "kg", "g", "l", "dl", "cl", "ml", "förp", "pak", "burk", "flaska"
- "price": pris i SEK som ett tal, eller null
- "brand": märke eller leverantör om det tydligt framgår av varunamnet (t.ex. "Arla", "ICA", "Oatly"), annars null
- "origin": ursprungsland om det framgår av varunamnet (t.ex. "Brasilien", "Sverige", "Italien"), annars null
Returnera BARA JSON-arrayen utan markdown-formatering.