feat: StorageService S3/MinIO, POST /users, invoice upload, portal dashboard §08 statuses + notifications

This commit is contained in:
Lizandro Guarnizo
2026-06-01 20:55:30 -05:00
parent 98ab5a309c
commit a047a8b032
19 changed files with 1166 additions and 134 deletions
@@ -15,6 +15,10 @@ export class CreatePreAlertDto {
@IsOptional()
@IsString()
vendorTracking?: string;
@IsOptional()
@IsString()
estimatedArrival?: string;
}
export class UpdatePreAlertStatusDto {
@@ -1,13 +1,22 @@
import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards } from "@nestjs/common";
import {
Controller, Get, Post, Patch, Delete, Body, Param, UseGuards,
UseInterceptors, UploadedFile,
} from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
import { memoryStorage } from "multer";
import { PreAlertsService } from "./pre-alerts.service";
import { CreatePreAlertDto, UpdatePreAlertStatusDto } from "./dto/pre-alert.dto";
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
import { CurrentUser } from "../auth/decorators/current-user.decorator";
import { StorageService } from "../storage/storage.service";
@Controller("pre-alerts")
@UseGuards(JwtAuthGuard, RolesGuard)
export class PreAlertsController {
constructor(private svc: PreAlertsService) {}
constructor(
private svc: PreAlertsService,
private storage: StorageService,
) {}
@Get()
findAll(@CurrentUser() user: any) {
@@ -20,6 +29,34 @@ export class PreAlertsController {
return this.svc.create(dto, user);
}
/** Upload invoice PDF/image for a pre-alert (doc §07 / §09) */
@Post(":id/invoice")
@Roles("CLIENTE")
@UseInterceptors(
FileInterceptor("invoice", {
storage: memoryStorage(),
fileFilter: (_req, file, cb) => {
const allowed = /pdf|jpg|jpeg|png|webp/;
const ext = file.originalname.split(".").pop()?.toLowerCase() ?? "";
cb(null, allowed.test(ext));
},
limits: { fileSize: 10 * 1024 * 1024 }, // 10 MB
}),
)
async uploadInvoice(
@Param("id") id: string,
@UploadedFile() file: Express.Multer.File,
@CurrentUser() user: any,
): Promise<any> {
const url = await this.storage.saveFile(
"invoices",
file.originalname,
file.buffer,
file.mimetype,
);
return this.svc.attachInvoice(id, url, user);
}
@Patch(":id/status")
@Roles("OPERADOR_BODEGA", "ADMIN_EMPRESA", "SUPER_ADMIN")
updateStatus(@Param("id") id: string, @Body() dto: UpdatePreAlertStatusDto): Promise<any> {
+7 -1
View File
@@ -1,6 +1,12 @@
import { Module } from "@nestjs/common";
import { PreAlertsController } from "./pre-alerts.controller";
import { PreAlertsService } from "./pre-alerts.service";
import { StorageModule } from "../storage/storage.module";
import { PrismaModule } from "../prisma/prisma.module";
@Module({ controllers: [PreAlertsController], providers: [PreAlertsService] })
@Module({
imports: [PrismaModule, StorageModule],
controllers: [PreAlertsController],
providers: [PreAlertsService],
})
export class PreAlertsModule {}
+19 -7
View File
@@ -19,17 +19,29 @@ export class PreAlertsService {
async create(dto: CreatePreAlertDto, user: any): Promise<any> {
return this.prisma.client.preAlert.create({
data: {
tenantId: user.tenantId,
userId: user.id,
store: dto.store,
description: dto.description,
declaredValue: dto.declaredValue ?? 0,
vendorTracking: dto.vendorTracking,
status: "PENDIENTE",
tenantId: user.tenantId,
userId: user.id,
store: dto.store,
description: dto.description,
declaredValue: dto.declaredValue ?? 0,
vendorTracking: dto.vendorTracking,
estimatedArrival: dto.estimatedArrival ? new Date(dto.estimatedArrival) : undefined,
status: "PENDIENTE",
},
});
}
/** Attach invoice URL to a pre-alert (doc §07/§09) */
async attachInvoice(id: string, invoiceKey: string, user: any): Promise<any> {
const alert = await this.prisma.client.preAlert.findUnique({ where: { id } });
if (!alert) throw new NotFoundException("Pre-alerta no encontrada.");
if (user.role === "CLIENTE" && alert.userId !== user.id) throw new ForbiddenException();
return this.prisma.client.preAlert.update({
where: { id },
data: { invoiceKey },
});
}
async updateStatus(id: string, dto: UpdatePreAlertStatusDto): Promise<any> {
const alert = await this.prisma.client.preAlert.findUnique({ where: { id } });
if (!alert) throw new NotFoundException("Pre-alerta no encontrada.");