inventario

This commit is contained in:
2025-06-20 12:56:47 -04:00
parent f87f235967
commit 0a65946a8a
11 changed files with 403 additions and 3 deletions

View File

@@ -3,3 +3,4 @@ export * from './schema/activity_logs';
export * from './schema/auth';
export * from './schema/general';
export * from './schema/surveys'
export * from './schema/inventory'

View File

@@ -0,0 +1,17 @@
import * as t from 'drizzle-orm/pg-core';
import { timestamps } from '../timestamps';
import { users } from './auth';
export const products = t.pgTable(
'products',
{
id: t.serial('id').primaryKey(),
title: t.text('title').notNull(),
description: t.text('description').notNull(),
price: t.numeric('price').notNull(),
stock: t.integer('stock').notNull(),
urlImg: t.text('url_img').notNull(),
userId: t.integer('user_id').references(() => users.id, { onDelete: 'cascade' }),
...timestamps,
}
);

View File

@@ -8,6 +8,7 @@ import { seedMunicipalites } from './municipalities';
import { seedParishes } from './parishes';
import { seedStates } from './states';
import { seedUserAdmin } from './user-admin.seed';
import {seedProducts} from './inventory.seed'
async function main() {
const pool = new Pool({
@@ -25,6 +26,7 @@ async function main() {
await seedLocalities(db);
await seedAdminRole(db);
await seedUserAdmin(db);
await seedProducts(db);
console.log('All seeds completed successfully');
} catch (error) {

View File

@@ -0,0 +1,28 @@
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import * as schema from '../index';
import { products } from '../schema/inventory';
export async function seedProducts(db: NodePgDatabase<typeof schema>) {
console.log('Seeding example product...');
// Insert inventory
const array = [{title:'manzana',description:'fruta roja',price:'100',urlImg:'apple.avif',userId:1,stock:0}];
for (const item of array) {
try {
await db.insert(products).values({
title: item.title,
description: item.description,
price: item.price,
stock: item.stock,
urlImg: item.urlImg,
userId: item.userId
}).onConflictDoNothing();
} catch (error) {
console.error(`Error creating products '${item.title}':`, error);
}
}
console.log('All products seeded successfully');
}

View File

@@ -0,0 +1,32 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsInt, IsOptional, IsString } from 'class-validator';
export class CreateProductDto {
@ApiProperty()
@IsString()
title: string;
@ApiProperty()
@IsString()
description: string;
@ApiProperty()
@IsString({
message: 'price must be a string',
})
@IsOptional()
price: string;
@ApiProperty()
@IsString({
message: 'stock must be a number',
})
@IsOptional()
stock: number;
@ApiProperty()
@IsString({
message: 'urlImg must be a string',
})
urlImg: string;
}

View File

@@ -0,0 +1,22 @@
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { CreateProductDto } from './create-product.dto';
// import { ApiProperty } from '@nestjs/swagger';
import { IsOptional, IsString } from 'class-validator';
export class UpdateProductDto extends PartialType(CreateProductDto) {
@IsOptional()
title: string;
@IsOptional()
description: string;
@IsOptional()
price: string;
@IsOptional()
stock: number;
@IsOptional()
urlImg: string;
}

View File

@@ -0,0 +1,8 @@
export class Product {
id?: number;
title: string;
description: string;
price: string;
stock: number;
urlImg: string;
}

View File

@@ -0,0 +1,70 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { InventoryService } from './inventory.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Roles } from '../../common/decorators/roles.decorator';
import { PaginationDto } from '../../common/dto/pagination.dto';
@ApiTags('inventory')
@Controller('inventory')
export class UsersController {
constructor(private readonly inventoryService: InventoryService) {}
@Get()
// @Roles('admin')
@ApiOperation({ summary: 'Get all products with pagination and filters' })
@ApiResponse({ status: 200, description: 'Return paginated products.' })
async findAll(@Query() paginationDto: PaginationDto) {
const result = await this.inventoryService.findAll(paginationDto);
return {
message: 'products fetched successfully',
data: result.data,
meta: result.meta
};
}
@Get(':id')
// @Roles('admin')
@ApiOperation({ summary: 'Get a product by ID' })
@ApiResponse({ status: 200, description: 'Return the product.' })
@ApiResponse({ status: 404, description: 'product not found.' })
async findOne(@Param('id') id: string) {
const data = await this.inventoryService.findOne(id);
return { message: 'product fetched successfully', data };
}
@Post()
// @Roles('admin')
@ApiOperation({ summary: 'Create a new product' })
@ApiResponse({ status: 201, description: 'Product created successfully.' })
async create(
@Body() createUserDto: CreateProductDto,
@Query('roleId') roleId?: string,
) {
const data = await this.inventoryService.create(
createUserDto,
roleId ? parseInt(roleId) : undefined,
);
return { message: 'User created successfully', data };
}
// @Patch(':id')
// @Roles('admin')
// @ApiOperation({ summary: 'Update a user' })
// @ApiResponse({ status: 200, description: 'User updated successfully.' })
// @ApiResponse({ status: 404, description: 'User not found.' })
// async update(@Param('id') id: string, @Body() UpdateProductDto: UpdateProductDto) {
// const data = await this.inventoryService.update(id, UpdateProductDto);
// return { message: 'User updated successfully', data };
// }
// @Delete(':id')
// @Roles('admin')
// @ApiOperation({ summary: 'Delete a user' })
// @ApiResponse({ status: 200, description: 'User deleted successfully.' })
// @ApiResponse({ status: 404, description: 'User not found.' })
// async remove(@Param('id') id: string) {
// return await this.inventoryService.remove(id);
// }
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { UsersController } from './inventory.controller';
import { InventoryService } from './inventory.service';
import { DrizzleModule } from '@/database/drizzle.module';
@Module({
imports: [DrizzleModule],
controllers: [UsersController],
providers: [InventoryService],
})
export class InventoryModule {}

View File

@@ -0,0 +1,209 @@
import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider';
import { Env, validateString } from '@/common/utils';
import { Inject, Injectable, HttpException, HttpStatus, NotFoundException, UnauthorizedException } from '@nestjs/common';
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import * as schema from 'src/database/index';
import { products } from 'src/database/index';
import { eq, like, or, SQL, sql, and, not } from 'drizzle-orm';
import * as bcrypt from 'bcryptjs';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateUserDto } from './dto/update-product.dto';
import { Product } from './entities/inventory.entity';
import { PaginationDto } from '../../common/dto/pagination.dto';
@Injectable()
export class InventoryService {
constructor(
@Inject(DRIZZLE_PROVIDER) private drizzle: NodePgDatabase<typeof schema>,
) { }
async findAll(paginationDto?: PaginationDto): Promise<{ data: Product[], meta: any }> {
const { page = 1, limit = 10, search = '', sortBy = 'id', sortOrder = 'asc' } = paginationDto || {};
// Calculate offset
const offset = (page - 1) * limit;
// Build search condition
let searchCondition: SQL<unknown> | undefined;
if (search) {
searchCondition = or(
like(products.title, `%${search}%`),
like(products.description, `%${search}%`),
// like(users.fullname, `%${search}%`)
);
}
// Build sort condition
const orderBy = sortOrder === 'asc'
? sql`${products[sortBy as keyof typeof products]} asc`
: sql`${products[sortBy as keyof typeof products]} desc`;
// Get total count for pagination
const totalCountResult = await this.drizzle
.select({ count: sql<number>`count(*)` })
.from(products)
.where(searchCondition);
const totalCount = Number(totalCountResult[0].count);
const totalPages = Math.ceil(totalCount / limit);
// Get paginated data
const data = await this.drizzle
.select({
id: products.id,
title: products.title,
description: products.description,
valuePerUnit: products.valuePerUnit,
urlImg: products.urlImg,
// price: products.price,
// quantity: products.quantity,
// isActive: products.isActive
})
.from(products)
// .leftJoin(usersRole, eq(usersRole.userId, users.id))
// .leftJoin(roles, eq(roles.id, usersRole.roleId))
.where(searchCondition)
.orderBy(orderBy)
.limit(limit)
.offset(offset);
// Build pagination metadata
const meta = {
page,
limit,
totalCount,
totalPages,
hasNextPage: page < totalPages,
hasPreviousPage: page > 1,
nextPage: page < totalPages ? page + 1 : null,
previousPage: page > 1 ? page - 1 : null,
};
// console.log(data);
return { data, meta };
}
async findOne(id: string): Promise<Product> {
const find = await this.drizzle
.select({
id: products.id,
title: products.title,
description: products.description,
valuePerUnit: products.valuePerUnit,
urlImg: products.urlImg,
})
.from(products)
// .leftJoin(usersRole, eq(usersRole.userId, users.id))
// .leftJoin(roles, eq(roles.id, usersRole.roleId))
// .leftJoin(schema.states, eq(schema.states.id, users.state))
// .leftJoin(schema.municipalities, eq(schema.municipalities.id, users.municipality))
// .leftJoin(schema.parishes, eq(schema.parishes.id, users.parish))
.where(eq(products.id, parseInt(id)));
if (find.length === 0) {
throw new HttpException('User does not exist', HttpStatus.BAD_REQUEST);
}
return find[0];
}
// Rest of the service remains the same
async create(
createProductDto: CreateProductDto,
roleId: number = 2,
): Promise<Product> {
// Start a transaction
return await this.drizzle.transaction(async (tx) => {
// Create the user
const [newProduct] = await tx
.insert(products)
.values({
title: createProductDto.title,
description: createProductDto.description,
valuePerUnit: createProductDto.valuePerUnit,
urlImg: createProductDto.urlImg,
})
.returning();
// Assign role to user
// await tx.insert(usersRole).values({
// userId: newProduct.id,
// roleId: roleId,
// });
// Return the created user with role
// const [userWithRole] = await tx
// .select({
// id: users.id,
// username: users.username,
// email: users.email,
// fullname: users.fullname,
// phone: users.phone,
// isActive: users.isActive,
// role: roles.name,
// })
// .from(users)
// .leftJoin(usersRole, eq(usersRole.userId, users.id))
// .leftJoin(roles, eq(roles.id, usersRole.roleId))
// .where(eq(users.id, newProduct.id));
return this.findOne(String(newProduct.id));
});
}
// async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
// const userId = parseInt(id);
// // Check if user exists
// await this.findOne(id);
// // Prepare update data
// const updateData: any = {};
// if (updateUserDto.username) updateData.username = updateUserDto.username;
// if (updateUserDto.email) updateData.email = updateUserDto.email;
// if (updateUserDto.fullname) updateData.fullname = updateUserDto.fullname;
// if (updateUserDto.password) {
// updateData.password = await bcrypt.hash(updateUserDto.password, 10);
// }
// if (updateUserDto.phone) updateData.phone = updateUserDto.phone;
// if (updateUserDto.isActive) updateData.isActive = updateUserDto.isActive;
// const updateDataRole: any = {};
// if (updateUserDto.role) updateDataRole.roleId = updateUserDto.role;
// // Update user
// await this.drizzle
// .update(users)
// .set(updateData)
// .where(eq(users.id, userId));
// await this.drizzle
// .update(usersRole)
// .set(updateDataRole)
// .where(eq(usersRole.userId, userId));
// // Return updated user
// return this.findOne(id);
// }
// async remove(id: string): Promise<{ message: string, data: User }> {
// const userId = parseInt(id);
// // Check if user exists
// const user = await this.findOne(id);
// // Delete user (this will cascade delete related records due to foreign key constraints)
// // await this.drizzle.delete(users).where(eq(users.id, userId));
// await this.drizzle.update(users).set({ isActive: false }).where(eq(users.id, userId));
// return { message: 'User deleted successfully', data: user };
// }
}