feat: implement user-scoped receipt aliases with global fallback; enhance alias management in admin panel
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-04 19:43:13 +02:00
parent d73ea5ef7c
commit 64b06435cf
15 changed files with 751 additions and 36 deletions
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateReceiptAliasDto } from './dto/create-receipt-alias.dto';
@@ -6,23 +6,88 @@ import { CreateReceiptAliasDto } from './dto/create-receipt-alias.dto';
export class ReceiptAliasService {
constructor(private readonly prisma: PrismaService) {}
findAll() {
findAllForUser(userId: number, role: string) {
const where = role === 'admin'
? undefined
: {
OR: [
{ ownerId: userId, isGlobal: false },
{ isGlobal: true },
],
};
return this.prisma.receiptAlias.findMany({
where,
include: { product: { select: { id: true, name: true, canonicalName: true } } },
orderBy: { receiptName: 'asc' },
});
}
async upsert(dto: CreateReceiptAliasDto) {
async upsert(dto: CreateReceiptAliasDto, userId: number, role: string) {
const normalized = dto.receiptName.toLowerCase().trim();
return this.prisma.receiptAlias.upsert({
where: { receiptName: normalized },
create: { receiptName: normalized, productId: dto.productId },
update: { productId: dto.productId },
const wantsGlobal = dto.isGlobal === true;
if (wantsGlobal && role !== 'admin') {
throw new ForbiddenException('Endast admin kan skapa globala alias');
}
if (wantsGlobal) {
const existing = await this.prisma.receiptAlias.findFirst({
where: { receiptName: normalized, isGlobal: true },
});
if (existing) {
return this.prisma.receiptAlias.update({
where: { id: existing.id },
data: { productId: dto.productId },
});
}
return this.prisma.receiptAlias.create({
data: {
receiptName: normalized,
productId: dto.productId,
isGlobal: true,
ownerId: null,
},
});
}
const existing = await this.prisma.receiptAlias.findFirst({
where: { receiptName: normalized, ownerId: userId, isGlobal: false },
});
if (existing) {
return this.prisma.receiptAlias.update({
where: { id: existing.id },
data: { productId: dto.productId },
});
}
return this.prisma.receiptAlias.create({
data: {
receiptName: normalized,
productId: dto.productId,
ownerId: userId,
isGlobal: false,
},
});
}
remove(id: number) {
async remove(id: number, userId: number, role: string) {
const alias = await this.prisma.receiptAlias.findUnique({ where: { id } });
if (!alias) {
return this.prisma.receiptAlias.delete({ where: { id } });
}
const canDelete =
role === 'admin' ||
(alias.ownerId === userId && alias.isGlobal === false);
if (!canDelete) {
throw new ForbiddenException('Du har inte behörighet att ta bort aliaset');
}
return this.prisma.receiptAlias.delete({ where: { id } });
}
}