d5f903db98
- Replace BadRequestException with UnauthorizedException for authentication failures in flyer-import and flyer-selection controllers - Add bulk selection endpoint in FlyerSelectionController for creating multiple selections in one request - Update FlyerSelectionModule to include new FlyerSelectionMatcherService and FlyerSelectionSyncController - Extend FlyerSelectionService with createMany method for bulk operations - Add new DTOs for bulk selection and receipt matching functionality - Update ReceiptImportService to accept FlyerSelectionService dependency and track successful rows - Extend SaveReceiptResponse with flyerAutoSync field for receipt-to-flyer matching results - Add new API paths for flyer import and selection endpoints - Update Flutter UI to include Flyer import tab and adjust tab controller length - Add new domain models and repository methods for flyer import functionality - Update test files to include new FlyerSelectionService dependency - Modify .kilo plan documentation to reflect current system architecture
114 lines
3.4 KiB
TypeScript
114 lines
3.4 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
HttpCode,
|
|
Post,
|
|
Request,
|
|
UploadedFile,
|
|
UseGuards,
|
|
UseInterceptors,
|
|
BadRequestException,
|
|
UnauthorizedException,
|
|
} from '@nestjs/common';
|
|
import { Throttle } from '@nestjs/throttler';
|
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
import { memoryStorage } from 'multer';
|
|
import { ReceiptImportService } from './receipt-import.service';
|
|
import { ParsedReceiptItem } from './dto/parsed-receipt-item.dto';
|
|
import { SaveReceiptDto } from './dto/save-receipt.dto';
|
|
import { SaveReceiptResponse } from './dto/save-receipt.response';
|
|
import { CreateUnitMappingDto } from './dto/create-unit-mapping.dto';
|
|
import { AuthGuard } from '@nestjs/passport';
|
|
|
|
const ALLOWED_MIMES = [
|
|
'image/jpeg',
|
|
'image/png',
|
|
'image/webp',
|
|
'image/heic',
|
|
'image/heif',
|
|
'application/pdf',
|
|
'application/octet-stream', // Flutter Web skickar detta för PDF-filer
|
|
];
|
|
|
|
@Controller('receipt-import')
|
|
export class ReceiptImportController {
|
|
constructor(private readonly receiptImportService: ReceiptImportService) {}
|
|
|
|
@HttpCode(200)
|
|
@Post()
|
|
@Throttle({ default: { ttl: 60_000, limit: 20 } })
|
|
@UseInterceptors(
|
|
FileInterceptor('file', {
|
|
storage: memoryStorage(),
|
|
limits: { fileSize: 15 * 1024 * 1024 }, // 15 MB
|
|
}),
|
|
)
|
|
async parseReceipt(
|
|
@UploadedFile() file?: Express.Multer.File,
|
|
@Request() req?: any,
|
|
): Promise<ParsedReceiptItem[]> {
|
|
if (!file?.buffer) {
|
|
throw new BadRequestException('Ingen fil skickades med.');
|
|
}
|
|
if (!ALLOWED_MIMES.includes(file.mimetype)) {
|
|
throw new BadRequestException(
|
|
'Otillåten filtyp. Använd JPEG, PNG, WebP eller PDF.',
|
|
);
|
|
}
|
|
const isPremium = req?.user?.isPremium === true || req?.user?.role === 'admin';
|
|
const userId = typeof req?.user?.id === 'number' ? req.user.id : undefined;
|
|
return this.receiptImportService.parseReceipt(file, isPremium, userId);
|
|
}
|
|
|
|
@Post('unit-mappings')
|
|
@UseGuards(AuthGuard('jwt'))
|
|
async upsertUnitMapping(
|
|
@Body() dto: CreateUnitMappingDto,
|
|
@Request() req?: any,
|
|
) {
|
|
const userId =
|
|
typeof req?.user?.id === 'number'
|
|
? req.user.id
|
|
: typeof req?.user?.userId === 'number'
|
|
? req.user.userId
|
|
: undefined;
|
|
if (!userId) {
|
|
throw new UnauthorizedException('Kunde inte identifiera användaren.');
|
|
}
|
|
|
|
return this.receiptImportService.upsertUnitMapping(
|
|
userId,
|
|
dto.productId,
|
|
dto.originalUnit,
|
|
dto.preferredUnit,
|
|
);
|
|
}
|
|
|
|
@HttpCode(200)
|
|
@Post('save')
|
|
@UseGuards(AuthGuard('jwt'))
|
|
@Throttle({ default: { ttl: 60_000, limit: 10 } })
|
|
async saveReceipt(
|
|
@Body() dto: SaveReceiptDto,
|
|
@Request() req?: any,
|
|
): Promise<SaveReceiptResponse> {
|
|
const userId =
|
|
typeof req?.user?.id === 'number'
|
|
? req.user.id
|
|
: typeof req?.user?.userId === 'number'
|
|
? req.user.userId
|
|
: undefined;
|
|
if (!userId) {
|
|
throw new UnauthorizedException('Kunde inte identifiera användaren.');
|
|
}
|
|
|
|
const isAdmin = req?.user?.role === 'admin';
|
|
if (dto.items.some((item) => item.learnAliasGlobally === true) && !isAdmin) {
|
|
throw new BadRequestException('Endast administratörer kan spara globala aliaser.');
|
|
}
|
|
|
|
return this.receiptImportService.saveReceipt(userId, dto);
|
|
}
|
|
}
|
|
|