feat(products): include ownerId in product creation and enforce its requirement
This commit is contained in:
@@ -128,8 +128,8 @@ export class ProductsController {
|
|||||||
|
|
||||||
@Roles('admin')
|
@Roles('admin')
|
||||||
@Post()
|
@Post()
|
||||||
create(@Body() body: CreateProductDto) {
|
create(@Body() body: CreateProductDto, @Request() req: { user: { id: number } }) {
|
||||||
return this.productsService.create(body);
|
return this.productsService.create(body, req.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tillgänglig för alla inloggade användare — req.user.id injiceras av JWT-guard
|
// Tillgänglig för alla inloggade användare — req.user.id injiceras av JWT-guard
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export class ProductsService {
|
|||||||
return product;
|
return product;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateProductDto) {
|
async create(data: CreateProductDto, ownerId?: number) {
|
||||||
const name = data.name.trim();
|
const name = data.name.trim();
|
||||||
const normalizedName = normalizeName(name);
|
const normalizedName = normalizeName(name);
|
||||||
|
|
||||||
@@ -140,8 +140,13 @@ export class ProductsService {
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ownerId) {
|
||||||
|
throw new Error('ownerId är obligatorisk för att skapa en produkt');
|
||||||
|
}
|
||||||
|
|
||||||
return this.prisma.product.create({
|
return this.prisma.product.create({
|
||||||
data: {
|
data: {
|
||||||
|
ownerId,
|
||||||
name,
|
name,
|
||||||
normalizedName,
|
normalizedName,
|
||||||
canonicalName: name,
|
canonicalName: name,
|
||||||
|
|||||||
@@ -120,11 +120,11 @@ export class ReceiptImportService {
|
|||||||
const [aliases, products] = await Promise.all([
|
const [aliases, products] = await Promise.all([
|
||||||
this.prisma.receiptAlias.findMany({
|
this.prisma.receiptAlias.findMany({
|
||||||
where: aliasFilter,
|
where: aliasFilter,
|
||||||
select: { receiptName: true, productId: true, product: { select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true, path: true } } } } },
|
select: { receiptName: true, productId: true, product: { select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true } } } } },
|
||||||
}),
|
}),
|
||||||
this.prisma.product.findMany({
|
this.prisma.product.findMany({
|
||||||
where: productFilter,
|
where: productFilter,
|
||||||
select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true, path: true } } },
|
select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true } } },
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ export class ReceiptImportService {
|
|||||||
...item,
|
...item,
|
||||||
matchedProductId: alias.product.id,
|
matchedProductId: alias.product.id,
|
||||||
matchedProductName: alias.product.canonicalName ?? alias.product.name,
|
matchedProductName: alias.product.canonicalName ?? alias.product.name,
|
||||||
...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.path, confidence: 'high' as const, usedFallback: false } } : {}),
|
...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'high' as const, usedFallback: false } } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,15 +154,15 @@ export class ReceiptImportService {
|
|||||||
...item,
|
...item,
|
||||||
suggestedProductId: suggestion.id,
|
suggestedProductId: suggestion.id,
|
||||||
suggestedProductName: suggestion.canonicalName ?? suggestion.name,
|
suggestedProductName: suggestion.canonicalName ?? suggestion.name,
|
||||||
...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.path, confidence: 'medium' as const, usedFallback: false } } : {}),
|
...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'medium' as const, usedFallback: false } } : {}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private findWordMatch(
|
private findWordMatch(
|
||||||
raw: string,
|
raw: string,
|
||||||
products: { id: number; name: string; canonicalName: string | null; categoryId: number | null; categoryRef: { id: number; name: string; path: string } | null }[],
|
products: { id: number; name: string; canonicalName: string | null; categoryId: number | null; categoryRef: { id: number; name: string } | null }[],
|
||||||
): { id: number; name: string; canonicalName: string | null; categoryId: number | null; categoryRef: { id: number; name: string; path: string } | null } | undefined {
|
): { id: number; name: string; canonicalName: string | null; categoryId: number | null; categoryRef: { id: number; name: string } | null } | undefined {
|
||||||
// Dela upp kvittonamnet i ord (min 3 tecken)
|
// Dela upp kvittonamnet i ord (min 3 tecken)
|
||||||
const rawWords = tokenize(raw);
|
const rawWords = tokenize(raw);
|
||||||
if (rawWords.length === 0) return undefined;
|
if (rawWords.length === 0) return undefined;
|
||||||
@@ -173,7 +173,7 @@ export class ReceiptImportService {
|
|||||||
const rawWordSetNorm = new Set(rawWordsNorm);
|
const rawWordSetNorm = new Set(rawWordsNorm);
|
||||||
|
|
||||||
let best:
|
let best:
|
||||||
| { product: { id: number; name: string; canonicalName: string | null }; score: number }
|
| { product: { id: number; name: string; canonicalName: string | null; categoryId: number | null; categoryRef: { id: number; name: string } | null }; score: number }
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
for (const product of products) {
|
for (const product of products) {
|
||||||
|
|||||||
Reference in New Issue
Block a user