diff --git a/src/stocks/stocks.controller.ts b/src/stocks/stocks.controller.ts index 86349bd..02ccb1f 100644 --- a/src/stocks/stocks.controller.ts +++ b/src/stocks/stocks.controller.ts @@ -1,12 +1,74 @@ -import { Controller } from '@nestjs/common'; +import { + BadRequestException, + Body, + Controller, + Get, + NotFoundException, + Param, + Patch, + Post, + UseGuards +} from "@nestjs/common"; +import { CreateStockDto, EditStockDto } from "src/stocks/stocks.dto"; +import { StocksService } from "src/stocks/stocks.service"; +import { error } from "winston"; +import { AdminGuard, UserGuard } from "src/auth/auth.guard"; @Controller('stocks') export class StocksController { - //POST new stock of a product (via id) [admin] + constructor( + private readonly stocks: StocksService + ) {} - //PATCH existing stock of a product (via id) [admin] + @UseGuards(AdminGuard) + @Post('add') + async addStock(@Body() stock: CreateStockDto) { + try { + await this.stocks.createStock(stock.productId, stock.quantity); + } catch (error) { + throw new BadRequestException(error.message); + } + } - //GET current stock of a product [user] + @Get('more/:limit') + async getProductsMoreStock(@Param('limit') limit: number) { + try { + const products = await this.stocks.getProductsRankedByStock(limit || 10, false); + return products + } catch (error) { + throw new BadRequestException(error.message); + } + } - //GET product with more or less stock (10 each) + @Get('less/:limit') + async getProductsLessStock(@Param('limit') limit: number) { + try { + const products = await this.stocks.getProductsRankedByStock(limit || 10, true); + return products + } catch (error) { + throw new BadRequestException(error.message); + } + + } + + @UseGuards(AdminGuard) + @Patch(':productId') + async editStock(@Param('productId') id: string, @Body() stock: EditStockDto) { + try { + await this.stocks.alterStock(id, stock.quantity); + } catch (error) { + throw new BadRequestException(error.message); + } + } + + @UseGuards(UserGuard) + @Get(':productId') + async getStock(@Param('productId') productId: string) { + try { + const stock = await this.stocks.getCurrentStock(productId); + return stock; + } catch (error) { + throw new NotFoundException(error.message); + } + } } diff --git a/src/stocks/stocks.dto.ts b/src/stocks/stocks.dto.ts new file mode 100644 index 0000000..75d7d1c --- /dev/null +++ b/src/stocks/stocks.dto.ts @@ -0,0 +1,24 @@ +import { + IsEmail, IsInt, + IsNotEmpty, + IsString, + IsStrongPassword, IsUUID, + MaxLength, Min, + MinLength +} from "class-validator"; +import { optional } from "zod"; + +export class CreateStockDto { + @IsUUID() + productId: string; + + @IsInt() + @Min(0) + quantity: number; +} + +export class EditStockDto { + @IsInt() + @Min(0) + quantity: number; +} \ No newline at end of file diff --git a/src/stocks/stocks.module.ts b/src/stocks/stocks.module.ts index 5fdc58d..f85a626 100644 --- a/src/stocks/stocks.module.ts +++ b/src/stocks/stocks.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; import { StocksController } from './stocks.controller'; import { StocksService } from './stocks.service'; +import { DrizzleModule } from "src/drizzle/drizzle.module"; +import { ProductsModule } from "src/products/products.module"; @Module({ + imports: [DrizzleModule, ProductsModule], controllers: [StocksController], providers: [StocksService] }) diff --git a/src/stocks/stocks.service.ts b/src/stocks/stocks.service.ts index dcb5aa2..7724ef0 100644 --- a/src/stocks/stocks.service.ts +++ b/src/stocks/stocks.service.ts @@ -1,39 +1,114 @@ -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; +import { DrizzleService } from "src/drizzle/drizzle.service"; +import { ProductsService } from "src/products/products.service"; +import { StocksTable } from "src/schema"; +import { asc, desc, eq } from "drizzle-orm"; + @Injectable() export class StocksService { - //create a new stock for a product + constructor( + private readonly db: DrizzleService, + private readonly products: ProductsService + ) {} + + private async getProductStock(productId: string) { + return this.db.use() + .select() + .from(StocksTable) + .where(eq(StocksTable.productId, productId)) + .execute(); + } + async createStock(productId: string, quantity: number) { - // implementation here + const product = await this.products.findById(productId) + if (!product) { + throw new NotFoundException('Product not found'); + } + const existingStock = await this.getProductStock(productId) + if (existingStock.length > 0) { + throw new BadRequestException(`Stock already exists, edit the existing one. (quantity: ${existingStock[0].quantity})`); + } + + return this.db.use() + .insert(StocksTable) + .values({ + quantity: quantity, + productId: productId, + }); } - //alter an existing stock of a product (via product id) async alterStock(productId: string, newQuantity: number) { - // implementation here + const product = await this.products.findById(productId) + if (!product) { + throw new NotFoundException('Product not found'); + } } - //get the current stock of a product (via product id) async getCurrentStock(productId: string) { - // implementation here + const product = await this.products.findById(productId) + if (!product) { + throw new NotFoundException('Product not found'); + } + return product; } - //get products with more or less stock (10 each) - async getProductsRankedByStock(quantity: number, asc: boolean) { - // implementation here + async getProductsRankedByStock(limit: number, _asc: boolean) { + const order = _asc ? asc(StocksTable.quantity) : desc(StocksTable.quantity); + return this.db.use() + .select() + .from(StocksTable) + .orderBy(order) + .limit(limit); } - //get all product's stocks with pagination - async getAllStocks(page: number, limit: number) { - // implementation here + async getAllStocks(page: number, limit: number, _asc: boolean) { + const order = _asc ? asc(StocksTable.quantity) : desc(StocksTable.quantity); + return this.db.use() + .select() + .from(StocksTable) + .orderBy(order) + .offset((page - 1) * limit) + .limit(limit); } - //decrement stock of a product (via product id and quantity to decrement) async decrementStock(productId: string, quantityToDrop: number) { - // implementation here + const product = await this.products.findById(productId) + if (!product) { + throw new NotFoundException('Product not found'); + } + + const existingStock = await this.getProductStock(productId) + if (existingStock.length === 0) { + throw new BadRequestException("Stock not exists, create an existing one."); + } + + if (existingStock[0].quantity - quantityToDrop < 0) { + throw new BadRequestException(`Operation not permitted, stock does not meet the quantity to do that operation. (current: ${existingStock[0].quantity}, after: ${existingStock[0].quantity - quantityToDrop})`); + } + + return this.db.use() + .update(StocksTable) + .set({ + quantity: existingStock[0].quantity - quantityToDrop, + }) + .where(eq(StocksTable.productId, productId)); } - //increment stock of a product (via product id and quantity to increment) async incrementStock(productId: string, quantity: number) { - // implementation here + const product = await this.products.findById(productId) + if (!product) { + throw new NotFoundException('Product not found'); + } + const existingStock = await this.getProductStock(productId) + if (existingStock.length === 0) { + throw new BadRequestException("Stock not exists, create an existing one."); + } + return this.db.use() + .update(StocksTable) + .set({ + quantity: existingStock[0].quantity + quantity, + }) + .where(eq(StocksTable.productId, productId)); } }