feat(stocks): implement new endpoints and methods for stock management

This commit enhances the stocks module by adding new import modules, implementing more stock related endpoints and methods, and creating a new dto for stock data validation. The stocks service now includes methods to get a product's stock, create, alter, get the current stock, rank products by stock, get all stocks, decrement and increment product stock. StocksController now holds endpoints to add a new stock, get products with more or less stock, edit a stock, and get a stock of a specific product.
This commit is contained in:
Mathis H (Avnyr) 2024-07-15 14:34:45 +02:00
parent 3451f17123
commit e2f7ae8b88
Signed by: Mathis
GPG Key ID: DD9E0666A747D126
4 changed files with 186 additions and 22 deletions

View File

@ -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]
//GET current stock of a product [user]
//GET product with more or less stock (10 each)
@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('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('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);
}
}
}

24
src/stocks/stocks.dto.ts Normal file
View File

@ -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;
}

View File

@ -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]
})

View File

@ -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));
}
}