feat: enhance product model with subcategory, brand, tags, and nutrition; update related DTOs and services

This commit is contained in:
Nils-Johan Gynther
2026-04-17 18:11:06 +02:00
parent a05d907608
commit a4ea9be7a1
10 changed files with 517 additions and 33 deletions
+66 -4
View File
@@ -3,19 +3,26 @@ import { PrismaService } from '../prisma/prisma.service';
import { normalizeName } from '../common/utils/normalize-name';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { UpsertNutritionDto } from './dto/upsert-nutrition.dto';
@Injectable()
export class ProductsService {
constructor(private readonly prisma: PrismaService) {}
async findAll() {
async findAll(filters?: { tag?: string; subcategory?: string }) {
return this.prisma.product.findMany({
where: {
isActive: true,
...(filters?.subcategory ? { subcategory: filters.subcategory } : {}),
...(filters?.tag
? { tags: { some: { tag: { name: filters.tag } } } }
: {}),
},
orderBy: {
name: 'asc',
include: {
tags: { include: { tag: true } },
nutrition: true,
},
orderBy: { name: 'asc' },
});
}
@@ -105,6 +112,8 @@ export class ProductsService {
normalizedName?: string;
canonicalName?: string;
category?: string | null;
subcategory?: string | null;
brand?: string | null;
} = {};
if (typeof data.name === 'string') {
@@ -140,12 +149,21 @@ export class ProductsService {
}
if (typeof data.category === 'string') {
updateData.category = data.category.trim() || undefined;
updateData.category = data.category.trim() || null;
}
if (typeof data.subcategory === 'string') {
updateData.subcategory = data.subcategory.trim() || null;
}
if (typeof data.brand === 'string') {
updateData.brand = data.brand.trim() || null;
}
return this.prisma.product.update({
where: { id },
data: updateData,
include: { tags: { include: { tag: true } }, nutrition: true },
});
}
@@ -313,4 +331,48 @@ export class ProductsService {
products: results,
};
}
async setTags(productId: number, tagNames: string[]) {
await this.findOne(productId);
// Skapa taggar som inte finns och hämta ID för alla
const tags = await this.prisma.$transaction(
tagNames.map((name) =>
this.prisma.tag.upsert({
where: { name },
create: { name },
update: {},
}),
),
);
// Ersätt alla taggkopplingar för produkten
await this.prisma.productTag.deleteMany({ where: { productId } });
if (tags.length > 0) {
await this.prisma.productTag.createMany({
data: tags.map((tag) => ({ productId, tagId: tag.id })),
skipDuplicates: true,
});
}
return this.prisma.product.findUnique({
where: { id: productId },
include: { tags: { include: { tag: true } }, nutrition: true },
});
}
async upsertNutrition(productId: number, data: UpsertNutritionDto) {
await this.findOne(productId);
return this.prisma.nutrition.upsert({
where: { productId },
create: { productId, ...data },
update: { ...data },
});
}
async findAllTags() {
return this.prisma.tag.findMany({ orderBy: { name: 'asc' } });
}
}