This commit is contained in:
Emmanuel Rojas committed 2026-09-30 14:45:10 -05:00
1 parent 90125aede9
commit 2d6c29a1f3
404 files changed
+126983 -85

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
import { AppService } from "./app.service.js";
@Controller()
export class AppController {
+40 -3
View File
@@ -1,9 +1,46 @@
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { AppController } from "./app.controller.js";
import { AppService } from "./app.service.js";
import { AuthModule } from "./modules/auth/auth.module.js";
import { DatabaseModule } from "./database/database.module.js";
import { CartProductModule } from "./modules/cart-product/cart-product.module.js";
import { CashMovementModule } from "./modules/cash-movement/cash-movement.module.js";
import { CashRegisterSessionModule } from "./modules/cash-register-session/cash-register-session.module.js";
import { CategoryModule } from "./modules/category/category.module.js";
import { CustomerModule } from "./modules/customer/customer.module.js";
import { InventoryModule } from "./modules/inventory/inventory.module.js";
import { InventoryMovementModule } from "./modules/inventory-movement/inventory-movement.module.js";
import { OperatingDayModule } from "./modules/operating-day/operating-day.module.js";
import { PaymentModule } from "./modules/payment/payment.module.js";
import { ProductModule } from "./modules/product/product.module.js";
import { ProductCategoryModule } from "./modules/product-category/product-category.module.js";
import { ProductHistoryModule } from "./modules/product-history/product-history.module.js";
import { ShopCartModule } from "./modules/shop-cart/shop-cart.module.js";
import { SupplierModule } from "./modules/supplier/supplier.module.js";
import { UserModule } from "./modules/user/user.module.js";
import { WarehouseModule } from "./modules/warehouse/warehouse.module.js";
@Module({
imports: [],
imports: [
DatabaseModule,
SupplierModule,
ProductModule,
CategoryModule,
ProductCategoryModule,
InventoryModule,
WarehouseModule,
InventoryMovementModule,
ProductHistoryModule,
ShopCartModule,
CartProductModule,
UserModule,
PaymentModule,
CustomerModule,
CashRegisterSessionModule,
CashMovementModule,
OperatingDayModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
+48
View File
@@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return -- `model` is the deliberate untyped boundary to Prisma's ORM; the public contract stays fully typed via T/CreateInput/UpdateInput/WhereKey above. */
import { Inject } from "@nestjs/common";
import { DB } from "../database/database.tokens.js";
import type { Database } from "../prisma/db.js";
import type { IRepository } from "./repository.interface.js";
/**
* Generic CRUD repository over a `db.orm.public.<Model>` collection.
* Subclasses only declare which columns to select and which model to target.
*/
export abstract class BaseRepository<
T,
CreateInput,
UpdateInput,
WhereKey extends object,
> implements IRepository<T, CreateInput, UpdateInput, WhereKey> {
constructor(@Inject(DB) protected readonly db: Database) {}
/** The `db.orm.public.<Model>` collection this repository targets. */
protected abstract readonly model: any;
/** Columns fetched by `findAll()`. */
protected abstract readonly columns: readonly string[];
findAll(): Promise<T[]> {
return this.model
.select(...this.columns)
.all()
.toArray();
}
findById(where: WhereKey): Promise<T | null> {
return this.model.where(where).first();
}
create(data: CreateInput): Promise<T> {
return this.model.create(data);
}
async update(where: WhereKey, data: UpdateInput): Promise<T> {
await this.model.where(where).update(data);
return this.findById(where) as Promise<T>;
}
async delete(where: WhereKey): Promise<void> {
await this.model.where(where).delete();
}
}
+54
View File
@@ -0,0 +1,54 @@
import { NotFoundException } from "@nestjs/common";
import type { IRepository } from "./repository.interface.js";
import type { IService } from "./service.interface.js";
/**
* Generic business layer: delegates to an `IRepository`, translating a
* missing row into a `NotFoundException`. Subclasses may override
* `notFoundMessage` for a more specific error message.
*/
export abstract class BaseService<
T,
CreateInput,
UpdateInput,
WhereKey,
> implements IService<T, CreateInput, UpdateInput, WhereKey> {
constructor(
protected readonly repository: IRepository<
T,
CreateInput,
UpdateInput,
WhereKey
>,
) {}
findAll(): Promise<T[]> {
return this.repository.findAll();
}
async findById(where: WhereKey): Promise<T> {
const entity = await this.repository.findById(where);
if (!entity) {
throw new NotFoundException(this.notFoundMessage(where));
}
return entity;
}
create(data: CreateInput): Promise<T> {
return this.repository.create(data);
}
async update(where: WhereKey, data: UpdateInput): Promise<T> {
await this.findById(where);
return this.repository.update(where, data);
}
async delete(where: WhereKey): Promise<void> {
await this.findById(where);
await this.repository.delete(where);
}
protected notFoundMessage(where: WhereKey): string {
return `Resource not found: ${JSON.stringify(where)}`;
}
}
+31
View File
@@ -0,0 +1,31 @@
import {
randomBytes,
scrypt as scryptCallback,
timingSafeEqual,
} from "node:crypto";
import { promisify } from "node:util";
const scrypt = promisify(scryptCallback);
const KEY_LENGTH = 64;
export async function hashPassword(password: string): Promise<string> {
const salt = randomBytes(16).toString("hex");
const derivedKey = (await scrypt(password, salt, KEY_LENGTH)) as Buffer;
return `${salt}:${derivedKey.toString("hex")}`;
}
export async function verifyPassword(
password: string,
storedHash: string,
): Promise<boolean> {
const [salt, key] = storedHash.split(":");
if (!salt || !key) {
return false;
}
const keyBuffer = Buffer.from(key, "hex");
const derivedKey = (await scrypt(password, salt, KEY_LENGTH)) as Buffer;
if (derivedKey.length !== keyBuffer.length) {
return false;
}
return timingSafeEqual(derivedKey, keyBuffer);
}
@@ -0,0 +1,7 @@
export interface IRepository<T, CreateInput, UpdateInput, WhereKey> {
findAll(): Promise<T[]>;
findById(where: WhereKey): Promise<T | null>;
create(data: CreateInput): Promise<T>;
update(where: WhereKey, data: UpdateInput): Promise<T>;
delete(where: WhereKey): Promise<void>;
}
+7
View File
@@ -0,0 +1,7 @@
export interface IService<T, CreateInput, UpdateInput, WhereKey> {
findAll(): Promise<T[]>;
findById(where: WhereKey): Promise<T>;
create(data: CreateInput): Promise<T>;
update(where: WhereKey, data: UpdateInput): Promise<T>;
delete(where: WhereKey): Promise<void>;
}
+10
View File
@@ -0,0 +1,10 @@
import { Global, Module } from "@nestjs/common";
import { db } from "../prisma/db.js";
import { DB } from "./database.tokens.js";
@Global()
@Module({
providers: [{ provide: DB, useValue: db }],
exports: [DB],
})
export class DatabaseModule {}
+1
View File
@@ -0,0 +1 @@
export const DB = Symbol("DB");
+4
View File
@@ -0,0 +1,4 @@
export interface LoginInput {
id: number;
password: string;
}
+9
View File
@@ -0,0 +1,9 @@
export interface CreateCartProductInput {
cartId: number;
historyId: number;
quantity: number;
}
export interface UpdateCartProductInput {
quantity: number;
}
+9
View File
@@ -0,0 +1,9 @@
export type CashMovementType = "in" | "out";
export interface CreateCashMovementInput {
sessionId: number;
type: CashMovementType;
amount: string;
reason?: string | null;
userId?: number | null;
}
@@ -0,0 +1,13 @@
export interface CreateCashRegisterSessionInput {
inventoryId: number;
openedById: number;
openingCash: string;
}
export interface UpdateCashRegisterSessionInput {
closedById?: number | null;
closingCash?: string | null;
expectedCash?: string | null;
status?: string;
closedAt?: string | null;
}
+7
View File
@@ -0,0 +1,7 @@
export interface CreateCategoryInput {
name: string;
}
export interface UpdateCategoryInput {
name?: string;
}
+13
View File
@@ -0,0 +1,13 @@
export interface CreateCustomerInput {
name: string;
phone?: string | null;
email?: string | null;
taxId?: string | null;
}
export interface UpdateCustomerInput {
name?: string;
phone?: string | null;
email?: string | null;
taxId?: string | null;
}
@@ -0,0 +1,10 @@
export type InventoryMovementType = "in" | "out";
export interface CreateInventoryMovementInput {
productId: number;
inventoryId: number;
type: InventoryMovementType;
quantity: number;
reason?: string | null;
userId?: number | null;
}
+7
View File
@@ -0,0 +1,7 @@
export interface CreateInventoryInput {
name: string;
}
export interface UpdateInventoryInput {
name?: string;
}
+9
View File
@@ -0,0 +1,9 @@
export interface CreateOperatingDayInput {
openedById: number;
}
export interface UpdateOperatingDayInput {
closedById?: number;
status?: string;
closedAt?: string | null;
}
+21
View File
@@ -0,0 +1,21 @@
export interface CreatePaymentInput {
cartId: number;
userId: number;
amount: string;
orderId?: number | null;
terminalId?: number | null;
status: string;
paid?: boolean;
paymentMethod: string;
paidAt?: string | null;
}
export interface UpdatePaymentInput {
amount?: string;
orderId?: number | null;
terminalId?: number | null;
status?: string;
paid?: boolean;
paymentMethod?: string;
paidAt?: string | null;
}
@@ -0,0 +1,7 @@
export interface CreateProductCategoryInput {
productId: number;
categoryId: number;
}
// No mutable fields on this junction table — nothing to PATCH.
export type UpdateProductCategoryInput = Record<string, never>;
+14
View File
@@ -0,0 +1,14 @@
export interface CreateProductHistoryInput {
productId: number;
unitPrice: string;
costUnit: string;
status: string;
soldAt?: string | null;
}
export interface UpdateProductHistoryInput {
unitPrice?: string;
costUnit?: string;
status?: string;
soldAt?: string | null;
}
+19
View File
@@ -0,0 +1,19 @@
export interface CreateProductInput {
name: string;
description?: string | null;
sku: string;
barcode?: string | null;
minStock?: number;
imageUrl?: string | null;
supplierId?: number | null;
}
export interface UpdateProductInput {
name?: string;
description?: string | null;
sku?: string;
barcode?: string | null;
minStock?: number;
imageUrl?: string | null;
supplierId?: number | null;
}
+15
View File
@@ -0,0 +1,15 @@
export interface CreateShopCartInput {
totalPrice: string;
discount?: string;
status?: string;
userId?: number | null;
customerId?: number | null;
}
export interface UpdateShopCartInput {
totalPrice?: string;
discount?: string;
status?: string;
userId?: number | null;
customerId?: number | null;
}
+9
View File
@@ -0,0 +1,9 @@
export interface CreateSupplierInput {
name: string;
lastSupply?: string | null;
}
export interface UpdateSupplierInput {
name?: string;
lastSupply?: string | null;
}
+13
View File
@@ -0,0 +1,13 @@
export interface CreateUserInput {
name: string;
email: string;
role: string;
password: string;
}
export interface UpdateUserInput {
name?: string;
email?: string;
role?: string;
password?: string;
}
+9
View File
@@ -0,0 +1,9 @@
export interface CreateWarehouseInput {
inventoryId: number;
productId: number;
stock?: number;
}
export interface UpdateWarehouseInput {
stock: number;
}
@@ -0,0 +1,10 @@
export interface CartProduct {
cartId: number;
historyId: number;
quantity: number;
}
export interface CartProductKey {
cartId: number;
historyId: number;
}
@@ -0,0 +1,13 @@
export interface CashMovement {
id: number;
sessionId: number;
type: string;
amount: string;
reason: string | null;
userId: number | null;
createdAt: string;
}
export interface CashMovementKey {
id: number;
}
@@ -0,0 +1,16 @@
export interface CashRegisterSession {
id: number;
inventoryId: number;
openedById: number;
closedById: number | null;
openingCash: string;
closingCash: string | null;
expectedCash: string | null;
status: string;
openedAt: string;
closedAt: string | null;
}
export interface CashRegisterSessionKey {
id: number;
}
+9
View File
@@ -0,0 +1,9 @@
export interface Category {
id: number;
name: string;
createdAt: string;
}
export interface CategoryKey {
id: number;
}
+12
View File
@@ -0,0 +1,12 @@
export interface Customer {
id: number;
name: string;
phone: string | null;
email: string | null;
taxId: string | null;
createdAt: string;
}
export interface CustomerKey {
id: number;
}
@@ -0,0 +1,14 @@
export interface InventoryMovement {
id: number;
productId: number;
inventoryId: number;
type: string;
quantity: number;
reason: string | null;
userId: number | null;
createdAt: string;
}
export interface InventoryMovementKey {
id: number;
}
@@ -0,0 +1,8 @@
export interface Inventory {
id: number;
name: string;
}
export interface InventoryKey {
id: number;
}
@@ -0,0 +1,13 @@
export interface OperatingDay {
id: number;
date: string;
status: string;
openedById: number;
closedById: number | null;
openedAt: string;
closedAt: string | null;
}
export interface OperatingDayKey {
id: number;
}
+17
View File
@@ -0,0 +1,17 @@
export interface Payment {
id: number;
cartId: number;
userId: number;
amount: string;
orderId: number | null;
terminalId: number | null;
status: string;
paid: boolean;
paymentMethod: string;
paidAt: string | null;
createdAt: string;
}
export interface PaymentKey {
id: number;
}
@@ -0,0 +1,9 @@
export interface ProductCategory {
productId: number;
categoryId: number;
}
export interface ProductCategoryKey {
productId: number;
categoryId: number;
}
@@ -0,0 +1,13 @@
export interface ProductHistory {
id: number;
productId: number;
unitPrice: string;
costUnit: string;
status: string;
createdAt: string;
soldAt: string | null;
}
export interface ProductHistoryKey {
id: number;
}
+15
View File
@@ -0,0 +1,15 @@
export interface Product {
id: number;
name: string;
description: string | null;
sku: string;
barcode: string | null;
minStock: number;
imageUrl: string | null;
createdAt: string;
supplierId: number | null;
}
export interface ProductKey {
id: number;
}
+13
View File
@@ -0,0 +1,13 @@
export interface ShopCart {
id: number;
totalPrice: string;
discount: string;
status: string;
createdAt: string;
userId: number | null;
customerId: number | null;
}
export interface ShopCartKey {
id: number;
}
+10
View File
@@ -0,0 +1,10 @@
export interface Supplier {
id: number;
name: string;
lastSupply: string | null;
createdAt: string;
}
export interface SupplierKey {
id: number;
}
+12
View File
@@ -0,0 +1,12 @@
export interface User {
id: number;
name: string;
email: string;
role: string;
createdAt: string;
updatedAt: string;
}
export interface UserKey {
id: number;
}
+10
View File
@@ -0,0 +1,10 @@
export interface Warehouse {
inventoryId: number;
productId: number;
stock: number;
}
export interface WarehouseKey {
inventoryId: number;
productId: number;
}
View File
Whitespace-only changes.
+14 -2
View File
@@ -1,9 +1,21 @@
import "dotenv/config";
import "reflect-metadata";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { NestFactory } from "@nestjs/core";
import type { NestExpressApplication } from "@nestjs/platform-express";
import next from "next";
import { AppModule } from "./app.module";
import nextFactory from "next";
import { AppModule } from "./app.module.js";
// El paquete `next` reexporta su función por defecto a través de varios
// niveles (`next/index.d.ts` -> `next/types.d.ts` -> `next/dist/server/next.d.ts`),
// lo que hace que TypeScript con `nodenext` infiera el namespace del módulo en
// lugar del `default` real. En runtime sí es una función invocable (ver
// `next/dist/server/next.js`), así que se re-tipa explícitamente aquí.
const next =
nextFactory as unknown as typeof import("next/dist/server/next.js").default;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
async function bootstrap() {
const dev = process.env.NODE_ENV !== "production";
@@ -0,0 +1,18 @@
import { Body, Controller, Inject, Post } from "@nestjs/common";
import type { LoginInput } from "../../dtos/auth.dto.js";
import {
AUTH_SERVICE,
type IAuthService,
} from "./interfaces/auth-service.interface.js";
@Controller("auth")
export class AuthController {
constructor(
@Inject(AUTH_SERVICE) private readonly authService: IAuthService,
) {}
@Post("login")
login(@Body() body: LoginInput) {
return this.authService.login(body);
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";
import { UserModule } from "../user/user.module.js";
import { AuthController } from "./auth.controller.js";
import { AuthService } from "./auth.service.js";
import { AUTH_SERVICE } from "./interfaces/auth-service.interface.js";
@Module({
imports: [UserModule],
controllers: [AuthController],
providers: [{ provide: AUTH_SERVICE, useClass: AuthService }],
})
export class AuthModule {}
+23
View File
@@ -0,0 +1,23 @@
import { Inject, Injectable, UnauthorizedException } from "@nestjs/common";
import type { LoginInput } from "../../dtos/auth.dto.js";
import type { User } from "../../entities/user.entity.js";
import {
USER_SERVICE,
type IUserService,
} from "../user/interfaces/user-service.interface.js";
import type { IAuthService } from "./interfaces/auth-service.interface.js";
@Injectable()
export class AuthService implements IAuthService {
constructor(
@Inject(USER_SERVICE) private readonly userService: IUserService,
) {}
async login({ id, password }: LoginInput): Promise<User> {
const user = await this.userService.verifyCredentials({ id, password });
if (!user) {
throw new UnauthorizedException("Credenciales inválidas");
}
return user;
}
}
@@ -0,0 +1,8 @@
import type { LoginInput } from "../../../dtos/auth.dto.js";
import type { User } from "../../../entities/user.entity.js";
export const AUTH_SERVICE = Symbol("AUTH_SERVICE");
export interface IAuthService {
login(input: LoginInput): Promise<User>;
}
@@ -0,0 +1,60 @@
import {
Body,
Controller,
Delete,
Get,
Inject,
Param,
ParseIntPipe,
Patch,
Post,
} from "@nestjs/common";
import type {
CreateCartProductInput,
UpdateCartProductInput,
} from "../../dtos/cart-product.dto.js";
import { CART_PRODUCT_SERVICE } from "./interfaces/cart-product-service.interface.js";
import type { ICartProductService } from "./interfaces/cart-product-service.interface.js";
@Controller("cart-products")
export class CartProductController {
constructor(
@Inject(CART_PRODUCT_SERVICE)
private readonly cartProductService: ICartProductService,
) {}
@Get()
findAll() {
return this.cartProductService.findAll();
}
@Get(":cartId/:historyId")
findOne(
@Param("cartId", ParseIntPipe) cartId: number,
@Param("historyId", ParseIntPipe) historyId: number,
) {
return this.cartProductService.findById({ cartId, historyId });
}
@Post()
create(@Body() body: CreateCartProductInput) {
return this.cartProductService.create(body);
}
@Patch(":cartId/:historyId")
update(
@Param("cartId", ParseIntPipe) cartId: number,
@Param("historyId", ParseIntPipe) historyId: number,
@Body() body: UpdateCartProductInput,
) {
return this.cartProductService.update({ cartId, historyId }, body);
}
@Delete(":cartId/:historyId")
remove(
@Param("cartId", ParseIntPipe) cartId: number,
@Param("historyId", ParseIntPipe) historyId: number,
) {
return this.cartProductService.delete({ cartId, historyId });
}
}
@@ -0,0 +1,16 @@
import { Module } from "@nestjs/common";
import { CART_PRODUCT_REPOSITORY } from "./interfaces/cart-product-repository.interface.js";
import { CART_PRODUCT_SERVICE } from "./interfaces/cart-product-service.interface.js";
import { CartProductController } from "./cart-product.controller.js";
import { CartProductRepository } from "./cart-product.repository.js";
import { CartProductService } from "./cart-product.service.js";
@Module({
controllers: [CartProductController],
providers: [
{ provide: CART_PRODUCT_REPOSITORY, useClass: CartProductRepository },
{ provide: CART_PRODUCT_SERVICE, useClass: CartProductService },
],
exports: [CART_PRODUCT_SERVICE],
})
export class CartProductModule {}
@@ -0,0 +1,28 @@
import { Injectable } from "@nestjs/common";
import { BaseRepository } from "../../common/base.repository.js";
import type {
CreateCartProductInput,
UpdateCartProductInput,
} from "../../dtos/cart-product.dto.js";
import type {
CartProduct,
CartProductKey,
} from "../../entities/cart-product.entity.js";
import type { ICartProductRepository } from "./interfaces/cart-product-repository.interface.js";
@Injectable()
export class CartProductRepository
extends BaseRepository<
CartProduct,
CreateCartProductInput,
UpdateCartProductInput,
CartProductKey
>
implements ICartProductRepository
{
protected readonly columns = ["cartId", "historyId", "quantity"] as const;
protected get model() {
return this.db.orm.public.CartProduct;
}
}
@@ -0,0 +1,36 @@
import { Inject, Injectable } from "@nestjs/common";
import { BaseService } from "../../common/base.service.js";
import type {
CreateCartProductInput,
UpdateCartProductInput,
} from "../../dtos/cart-product.dto.js";
import type {
CartProduct,
CartProductKey,
} from "../../entities/cart-product.entity.js";
import {
CART_PRODUCT_REPOSITORY,
type ICartProductRepository,
} from "./interfaces/cart-product-repository.interface.js";
import type { ICartProductService } from "./interfaces/cart-product-service.interface.js";
@Injectable()
export class CartProductService
extends BaseService<
CartProduct,
CreateCartProductInput,
UpdateCartProductInput,
CartProductKey
>
implements ICartProductService
{
constructor(
@Inject(CART_PRODUCT_REPOSITORY) repository: ICartProductRepository,
) {
super(repository);
}
protected override notFoundMessage(where: CartProductKey): string {
return `CartProduct (${where.cartId}, ${where.historyId}) not found`;
}
}
@@ -0,0 +1,18 @@
import type { IRepository } from "../../../common/repository.interface.js";
import type {
CartProduct,
CartProductKey,
} from "../../../entities/cart-product.entity.js";
import type {
CreateCartProductInput,
UpdateCartProductInput,
} from "../../../dtos/cart-product.dto.js";
export const CART_PRODUCT_REPOSITORY = Symbol("CART_PRODUCT_REPOSITORY");
export type ICartProductRepository = IRepository<
CartProduct,
CreateCartProductInput,
UpdateCartProductInput,
CartProductKey
>;
@@ -0,0 +1,18 @@
import type { IService } from "../../../common/service.interface.js";
import type {
CartProduct,
CartProductKey,
} from "../../../entities/cart-product.entity.js";
import type {
CreateCartProductInput,
UpdateCartProductInput,
} from "../../../dtos/cart-product.dto.js";
export const CART_PRODUCT_SERVICE = Symbol("CART_PRODUCT_SERVICE");
export type ICartProductService = IService<
CartProduct,
CreateCartProductInput,
UpdateCartProductInput,
CartProductKey
>;
@@ -0,0 +1,26 @@
import { Body, Controller, Get, Inject, Param, ParseIntPipe, Post } from "@nestjs/common";
import type { CreateCashMovementInput } from "../../dtos/cash-movement.dto.js";
import { CASH_MOVEMENT_SERVICE } from "./interfaces/cash-movement-service.interface.js";
import type { ICashMovementService } from "./interfaces/cash-movement-service.interface.js";
@Controller("cash-movements")
export class CashMovementController {
constructor(
@Inject(CASH_MOVEMENT_SERVICE) private readonly cashMovementService: ICashMovementService,
) {}
@Get()
findAll() {
return this.cashMovementService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.cashMovementService.findById({ id });
}
@Post()
create(@Body() body: CreateCashMovementInput) {
return this.cashMovementService.create(body);
}
}
@@ -0,0 +1,16 @@
import { Module } from "@nestjs/common";
import { CASH_MOVEMENT_REPOSITORY } from "./interfaces/cash-movement-repository.interface.js";
import { CASH_MOVEMENT_SERVICE } from "./interfaces/cash-movement-service.interface.js";
import { CashMovementController } from "./cash-movement.controller.js";
import { CashMovementRepository } from "./cash-movement.repository.js";
import { CashMovementService } from "./cash-movement.service.js";
@Module({
controllers: [CashMovementController],
providers: [
{ provide: CASH_MOVEMENT_REPOSITORY, useClass: CashMovementRepository },
{ provide: CASH_MOVEMENT_SERVICE, useClass: CashMovementService },
],
exports: [CASH_MOVEMENT_SERVICE],
})
export class CashMovementModule {}
@@ -0,0 +1,29 @@
import { Inject, Injectable } from "@nestjs/common";
import { DB } from "../../database/database.tokens.js";
import type { Database } from "../../prisma/db.js";
import type { CashMovement, CashMovementKey } from "../../entities/cash-movement.entity.js";
import type { CreateCashMovementInput } from "../../dtos/cash-movement.dto.js";
import type { ICashMovementRepository } from "./interfaces/cash-movement-repository.interface.js";
const COLUMNS = ["id", "sessionId", "type", "amount", "reason", "userId", "createdAt"] as const;
@Injectable()
export class CashMovementRepository implements ICashMovementRepository {
constructor(@Inject(DB) private readonly db: Database) {}
private get model() {
return this.db.orm.public.CashMovement;
}
findAll(): Promise<CashMovement[]> {
return this.model.select(...COLUMNS).all().toArray();
}
findById(where: CashMovementKey): Promise<CashMovement | null> {
return this.model.select(...COLUMNS).where(where).first();
}
create(data: CreateCashMovementInput): Promise<CashMovement> {
return this.model.select(...COLUMNS).create(data);
}
}
@@ -0,0 +1,31 @@
import { Inject, Injectable, NotFoundException } from "@nestjs/common";
import type { CashMovement, CashMovementKey } from "../../entities/cash-movement.entity.js";
import type { CreateCashMovementInput } from "../../dtos/cash-movement.dto.js";
import {
CASH_MOVEMENT_REPOSITORY,
type ICashMovementRepository,
} from "./interfaces/cash-movement-repository.interface.js";
import type { ICashMovementService } from "./interfaces/cash-movement-service.interface.js";
@Injectable()
export class CashMovementService implements ICashMovementService {
constructor(
@Inject(CASH_MOVEMENT_REPOSITORY) private readonly repository: ICashMovementRepository,
) {}
findAll(): Promise<CashMovement[]> {
return this.repository.findAll();
}
async findById(where: CashMovementKey): Promise<CashMovement> {
const movement = await this.repository.findById(where);
if (!movement) {
throw new NotFoundException(`CashMovement ${where.id} not found`);
}
return movement;
}
create(data: CreateCashMovementInput): Promise<CashMovement> {
return this.repository.create(data);
}
}
@@ -0,0 +1,10 @@
import type { CashMovement, CashMovementKey } from "../../../entities/cash-movement.entity.js";
import type { CreateCashMovementInput } from "../../../dtos/cash-movement.dto.js";
export const CASH_MOVEMENT_REPOSITORY = Symbol("CASH_MOVEMENT_REPOSITORY");
export interface ICashMovementRepository {
findAll(): Promise<CashMovement[]>;
findById(where: CashMovementKey): Promise<CashMovement | null>;
create(data: CreateCashMovementInput): Promise<CashMovement>;
}
@@ -0,0 +1,10 @@
import type { CashMovement, CashMovementKey } from "../../../entities/cash-movement.entity.js";
import type { CreateCashMovementInput } from "../../../dtos/cash-movement.dto.js";
export const CASH_MOVEMENT_SERVICE = Symbol("CASH_MOVEMENT_SERVICE");
export interface ICashMovementService {
findAll(): Promise<CashMovement[]>;
findById(where: CashMovementKey): Promise<CashMovement>;
create(data: CreateCashMovementInput): Promise<CashMovement>;
}
@@ -0,0 +1,40 @@
import { Body, Controller, Delete, Get, Inject, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
import type {
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
} from "../../dtos/cash-register-session.dto.js";
import { CASH_REGISTER_SESSION_SERVICE } from "./interfaces/cash-register-session-service.interface.js";
import type { ICashRegisterSessionService } from "./interfaces/cash-register-session-service.interface.js";
@Controller("cash-register-sessions")
export class CashRegisterSessionController {
constructor(
@Inject(CASH_REGISTER_SESSION_SERVICE)
private readonly sessionService: ICashRegisterSessionService,
) {}
@Get()
findAll() {
return this.sessionService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.sessionService.findById({ id });
}
@Post()
create(@Body() body: CreateCashRegisterSessionInput) {
return this.sessionService.create(body);
}
@Patch(":id")
update(@Param("id", ParseIntPipe) id: number, @Body() body: UpdateCashRegisterSessionInput) {
return this.sessionService.update({ id }, body);
}
@Delete(":id")
remove(@Param("id", ParseIntPipe) id: number) {
return this.sessionService.delete({ id });
}
}
@@ -0,0 +1,16 @@
import { Module } from "@nestjs/common";
import { CASH_REGISTER_SESSION_REPOSITORY } from "./interfaces/cash-register-session-repository.interface.js";
import { CASH_REGISTER_SESSION_SERVICE } from "./interfaces/cash-register-session-service.interface.js";
import { CashRegisterSessionController } from "./cash-register-session.controller.js";
import { CashRegisterSessionRepository } from "./cash-register-session.repository.js";
import { CashRegisterSessionService } from "./cash-register-session.service.js";
@Module({
controllers: [CashRegisterSessionController],
providers: [
{ provide: CASH_REGISTER_SESSION_REPOSITORY, useClass: CashRegisterSessionRepository },
{ provide: CASH_REGISTER_SESSION_SERVICE, useClass: CashRegisterSessionService },
],
exports: [CASH_REGISTER_SESSION_SERVICE],
})
export class CashRegisterSessionModule {}
@@ -0,0 +1,39 @@
import { Injectable } from "@nestjs/common";
import { BaseRepository } from "../../common/base.repository.js";
import type {
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
} from "../../dtos/cash-register-session.dto.js";
import type {
CashRegisterSession,
CashRegisterSessionKey,
} from "../../entities/cash-register-session.entity.js";
import type { ICashRegisterSessionRepository } from "./interfaces/cash-register-session-repository.interface.js";
@Injectable()
export class CashRegisterSessionRepository
extends BaseRepository<
CashRegisterSession,
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
CashRegisterSessionKey
>
implements ICashRegisterSessionRepository
{
protected readonly columns = [
"id",
"inventoryId",
"openedById",
"closedById",
"openingCash",
"closingCash",
"expectedCash",
"status",
"openedAt",
"closedAt",
] as const;
protected get model() {
return this.db.orm.public.CashRegisterSession;
}
}
@@ -0,0 +1,61 @@
import { BadRequestException, Inject, Injectable } from "@nestjs/common";
import { BaseService } from "../../common/base.service.js";
import type {
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
} from "../../dtos/cash-register-session.dto.js";
import type {
CashRegisterSession,
CashRegisterSessionKey,
} from "../../entities/cash-register-session.entity.js";
import {
CASH_REGISTER_SESSION_REPOSITORY,
type ICashRegisterSessionRepository,
} from "./interfaces/cash-register-session-repository.interface.js";
import type { ICashRegisterSessionService } from "./interfaces/cash-register-session-service.interface.js";
@Injectable()
export class CashRegisterSessionService
extends BaseService<
CashRegisterSession,
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
CashRegisterSessionKey
>
implements ICashRegisterSessionService
{
constructor(
@Inject(CASH_REGISTER_SESSION_REPOSITORY)
private readonly sessionRepository: ICashRegisterSessionRepository,
) {
super(sessionRepository);
}
override async create(data: CreateCashRegisterSessionInput): Promise<CashRegisterSession> {
const all = await this.sessionRepository.findAll();
const alreadyOpen = all.some((s) => s.inventoryId === data.inventoryId && s.status === "open");
if (alreadyOpen) {
throw new BadRequestException("Ya hay una caja abierta en este almacén");
}
return this.sessionRepository.create(data);
}
override async update(
where: CashRegisterSessionKey,
data: UpdateCashRegisterSessionInput,
): Promise<CashRegisterSession> {
const current = await this.findById(where);
if (data.status === "closed" && current.status === "closed") {
throw new BadRequestException("Esta caja ya está cerrada");
}
const payload =
data.status === "closed" && !data.closedAt
? { ...data, closedAt: new Date().toISOString() }
: data;
return this.sessionRepository.update(where, payload);
}
protected override notFoundMessage(where: CashRegisterSessionKey): string {
return `CashRegisterSession ${where.id} not found`;
}
}
@@ -0,0 +1,18 @@
import type { IRepository } from "../../../common/repository.interface.js";
import type {
CashRegisterSession,
CashRegisterSessionKey,
} from "../../../entities/cash-register-session.entity.js";
import type {
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
} from "../../../dtos/cash-register-session.dto.js";
export const CASH_REGISTER_SESSION_REPOSITORY = Symbol("CASH_REGISTER_SESSION_REPOSITORY");
export type ICashRegisterSessionRepository = IRepository<
CashRegisterSession,
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
CashRegisterSessionKey
>;
@@ -0,0 +1,18 @@
import type { IService } from "../../../common/service.interface.js";
import type {
CashRegisterSession,
CashRegisterSessionKey,
} from "../../../entities/cash-register-session.entity.js";
import type {
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
} from "../../../dtos/cash-register-session.dto.js";
export const CASH_REGISTER_SESSION_SERVICE = Symbol("CASH_REGISTER_SESSION_SERVICE");
export type ICashRegisterSessionService = IService<
CashRegisterSession,
CreateCashRegisterSessionInput,
UpdateCashRegisterSessionInput,
CashRegisterSessionKey
>;
@@ -0,0 +1,53 @@
import {
Body,
Controller,
Delete,
Get,
Inject,
Param,
ParseIntPipe,
Patch,
Post,
} from "@nestjs/common";
import type {
CreateCategoryInput,
UpdateCategoryInput,
} from "../../dtos/category.dto.js";
import { CATEGORY_SERVICE } from "./interfaces/category-service.interface.js";
import type { ICategoryService } from "./interfaces/category-service.interface.js";
@Controller("categories")
export class CategoryController {
constructor(
@Inject(CATEGORY_SERVICE)
private readonly categoryService: ICategoryService,
) {}
@Get()
findAll() {
return this.categoryService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.categoryService.findById({ id });
}
@Post()
create(@Body() body: CreateCategoryInput) {
return this.categoryService.create(body);
}
@Patch(":id")
update(
@Param("id", ParseIntPipe) id: number,
@Body() body: UpdateCategoryInput,
) {
return this.categoryService.update({ id }, body);
}
@Delete(":id")
remove(@Param("id", ParseIntPipe) id: number) {
return this.categoryService.delete({ id });
}
}
@@ -0,0 +1,16 @@
import { Module } from "@nestjs/common";
import { CATEGORY_REPOSITORY } from "./interfaces/category-repository.interface.js";
import { CATEGORY_SERVICE } from "./interfaces/category-service.interface.js";
import { CategoryController } from "./category.controller.js";
import { CategoryRepository } from "./category.repository.js";
import { CategoryService } from "./category.service.js";
@Module({
controllers: [CategoryController],
providers: [
{ provide: CATEGORY_REPOSITORY, useClass: CategoryRepository },
{ provide: CATEGORY_SERVICE, useClass: CategoryService },
],
exports: [CATEGORY_SERVICE],
})
export class CategoryModule {}
@@ -0,0 +1,25 @@
import { Injectable } from "@nestjs/common";
import { BaseRepository } from "../../common/base.repository.js";
import type {
CreateCategoryInput,
UpdateCategoryInput,
} from "../../dtos/category.dto.js";
import type { Category, CategoryKey } from "../../entities/category.entity.js";
import type { ICategoryRepository } from "./interfaces/category-repository.interface.js";
@Injectable()
export class CategoryRepository
extends BaseRepository<
Category,
CreateCategoryInput,
UpdateCategoryInput,
CategoryKey
>
implements ICategoryRepository
{
protected readonly columns = ["id", "name", "createdAt"] as const;
protected get model() {
return this.db.orm.public.Category;
}
}
@@ -0,0 +1,31 @@
import { Inject, Injectable } from "@nestjs/common";
import { BaseService } from "../../common/base.service.js";
import type {
CreateCategoryInput,
UpdateCategoryInput,
} from "../../dtos/category.dto.js";
import type { Category, CategoryKey } from "../../entities/category.entity.js";
import {
CATEGORY_REPOSITORY,
type ICategoryRepository,
} from "./interfaces/category-repository.interface.js";
import type { ICategoryService } from "./interfaces/category-service.interface.js";
@Injectable()
export class CategoryService
extends BaseService<
Category,
CreateCategoryInput,
UpdateCategoryInput,
CategoryKey
>
implements ICategoryService
{
constructor(@Inject(CATEGORY_REPOSITORY) repository: ICategoryRepository) {
super(repository);
}
protected override notFoundMessage(where: CategoryKey): string {
return `Category ${where.id} not found`;
}
}
@@ -0,0 +1,18 @@
import type { IRepository } from "../../../common/repository.interface.js";
import type {
Category,
CategoryKey,
} from "../../../entities/category.entity.js";
import type {
CreateCategoryInput,
UpdateCategoryInput,
} from "../../../dtos/category.dto.js";
export const CATEGORY_REPOSITORY = Symbol("CATEGORY_REPOSITORY");
export type ICategoryRepository = IRepository<
Category,
CreateCategoryInput,
UpdateCategoryInput,
CategoryKey
>;
@@ -0,0 +1,18 @@
import type { IService } from "../../../common/service.interface.js";
import type {
Category,
CategoryKey,
} from "../../../entities/category.entity.js";
import type {
CreateCategoryInput,
UpdateCategoryInput,
} from "../../../dtos/category.dto.js";
export const CATEGORY_SERVICE = Symbol("CATEGORY_SERVICE");
export type ICategoryService = IService<
Category,
CreateCategoryInput,
UpdateCategoryInput,
CategoryKey
>;
@@ -0,0 +1,34 @@
import { Body, Controller, Delete, Get, Inject, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
import type { CreateCustomerInput, UpdateCustomerInput } from "../../dtos/customer.dto.js";
import { CUSTOMER_SERVICE } from "./interfaces/customer-service.interface.js";
import type { ICustomerService } from "./interfaces/customer-service.interface.js";
@Controller("customers")
export class CustomerController {
constructor(@Inject(CUSTOMER_SERVICE) private readonly customerService: ICustomerService) {}
@Get()
findAll() {
return this.customerService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.customerService.findById({ id });
}
@Post()
create(@Body() body: CreateCustomerInput) {
return this.customerService.create(body);
}
@Patch(":id")
update(@Param("id", ParseIntPipe) id: number, @Body() body: UpdateCustomerInput) {
return this.customerService.update({ id }, body);
}
@Delete(":id")
remove(@Param("id", ParseIntPipe) id: number) {
return this.customerService.delete({ id });
}
}
@@ -0,0 +1,16 @@
import { Module } from "@nestjs/common";
import { CUSTOMER_REPOSITORY } from "./interfaces/customer-repository.interface.js";
import { CUSTOMER_SERVICE } from "./interfaces/customer-service.interface.js";
import { CustomerController } from "./customer.controller.js";
import { CustomerRepository } from "./customer.repository.js";
import { CustomerService } from "./customer.service.js";
@Module({
controllers: [CustomerController],
providers: [
{ provide: CUSTOMER_REPOSITORY, useClass: CustomerRepository },
{ provide: CUSTOMER_SERVICE, useClass: CustomerService },
],
exports: [CUSTOMER_SERVICE],
})
export class CustomerModule {}
@@ -0,0 +1,17 @@
import { Injectable } from "@nestjs/common";
import { BaseRepository } from "../../common/base.repository.js";
import type { CreateCustomerInput, UpdateCustomerInput } from "../../dtos/customer.dto.js";
import type { Customer, CustomerKey } from "../../entities/customer.entity.js";
import type { ICustomerRepository } from "./interfaces/customer-repository.interface.js";
@Injectable()
export class CustomerRepository
extends BaseRepository<Customer, CreateCustomerInput, UpdateCustomerInput, CustomerKey>
implements ICustomerRepository
{
protected readonly columns = ["id", "name", "phone", "email", "taxId", "createdAt"] as const;
protected get model() {
return this.db.orm.public.Customer;
}
}
@@ -0,0 +1,23 @@
import { Inject, Injectable } from "@nestjs/common";
import { BaseService } from "../../common/base.service.js";
import type { CreateCustomerInput, UpdateCustomerInput } from "../../dtos/customer.dto.js";
import type { Customer, CustomerKey } from "../../entities/customer.entity.js";
import {
CUSTOMER_REPOSITORY,
type ICustomerRepository,
} from "./interfaces/customer-repository.interface.js";
import type { ICustomerService } from "./interfaces/customer-service.interface.js";
@Injectable()
export class CustomerService
extends BaseService<Customer, CreateCustomerInput, UpdateCustomerInput, CustomerKey>
implements ICustomerService
{
constructor(@Inject(CUSTOMER_REPOSITORY) repository: ICustomerRepository) {
super(repository);
}
protected override notFoundMessage(where: CustomerKey): string {
return `Customer ${where.id} not found`;
}
}
@@ -0,0 +1,12 @@
import type { IRepository } from "../../../common/repository.interface.js";
import type { Customer, CustomerKey } from "../../../entities/customer.entity.js";
import type { CreateCustomerInput, UpdateCustomerInput } from "../../../dtos/customer.dto.js";
export const CUSTOMER_REPOSITORY = Symbol("CUSTOMER_REPOSITORY");
export type ICustomerRepository = IRepository<
Customer,
CreateCustomerInput,
UpdateCustomerInput,
CustomerKey
>;
@@ -0,0 +1,12 @@
import type { IService } from "../../../common/service.interface.js";
import type { Customer, CustomerKey } from "../../../entities/customer.entity.js";
import type { CreateCustomerInput, UpdateCustomerInput } from "../../../dtos/customer.dto.js";
export const CUSTOMER_SERVICE = Symbol("CUSTOMER_SERVICE");
export type ICustomerService = IService<
Customer,
CreateCustomerInput,
UpdateCustomerInput,
CustomerKey
>;
@@ -0,0 +1,15 @@
import type {
InventoryMovement,
InventoryMovementKey,
} from "../../../entities/inventory-movement.entity.js";
import type { CreateInventoryMovementInput } from "../../../dtos/inventory-movement.dto.js";
export const INVENTORY_MOVEMENT_REPOSITORY = Symbol(
"INVENTORY_MOVEMENT_REPOSITORY",
);
export interface IInventoryMovementRepository {
findAll(): Promise<InventoryMovement[]>;
findById(where: InventoryMovementKey): Promise<InventoryMovement | null>;
create(data: CreateInventoryMovementInput): Promise<InventoryMovement>;
}
@@ -0,0 +1,13 @@
import type {
InventoryMovement,
InventoryMovementKey,
} from "../../../entities/inventory-movement.entity.js";
import type { CreateInventoryMovementInput } from "../../../dtos/inventory-movement.dto.js";
export const INVENTORY_MOVEMENT_SERVICE = Symbol("INVENTORY_MOVEMENT_SERVICE");
export interface IInventoryMovementService {
findAll(): Promise<InventoryMovement[]>;
findById(where: InventoryMovementKey): Promise<InventoryMovement>;
create(data: CreateInventoryMovementInput): Promise<InventoryMovement>;
}
@@ -0,0 +1,29 @@
import { Body, Controller, Get, Inject, Param, ParseIntPipe, Post } from "@nestjs/common";
import type { CreateInventoryMovementInput } from "../../dtos/inventory-movement.dto.js";
import {
INVENTORY_MOVEMENT_SERVICE,
type IInventoryMovementService,
} from "./interfaces/inventory-movement-service.interface.js";
@Controller("inventory-movements")
export class InventoryMovementController {
constructor(
@Inject(INVENTORY_MOVEMENT_SERVICE)
private readonly inventoryMovementService: IInventoryMovementService,
) {}
@Get()
findAll() {
return this.inventoryMovementService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.inventoryMovementService.findById({ id });
}
@Post()
create(@Body() body: CreateInventoryMovementInput) {
return this.inventoryMovementService.create(body);
}
}
@@ -0,0 +1,20 @@
import { Module } from "@nestjs/common";
import { ProductModule } from "../product/product.module.js";
import { SupplierModule } from "../supplier/supplier.module.js";
import { WarehouseModule } from "../warehouse/warehouse.module.js";
import { INVENTORY_MOVEMENT_REPOSITORY } from "./interfaces/inventory-movement-repository.interface.js";
import { INVENTORY_MOVEMENT_SERVICE } from "./interfaces/inventory-movement-service.interface.js";
import { InventoryMovementController } from "./inventory-movement.controller.js";
import { InventoryMovementRepository } from "./inventory-movement.repository.js";
import { InventoryMovementService } from "./inventory-movement.service.js";
@Module({
imports: [WarehouseModule, ProductModule, SupplierModule],
controllers: [InventoryMovementController],
providers: [
{ provide: INVENTORY_MOVEMENT_REPOSITORY, useClass: InventoryMovementRepository },
{ provide: INVENTORY_MOVEMENT_SERVICE, useClass: InventoryMovementService },
],
exports: [INVENTORY_MOVEMENT_SERVICE],
})
export class InventoryMovementModule {}
@@ -0,0 +1,43 @@
import { Inject, Injectable } from "@nestjs/common";
import { DB } from "../../database/database.tokens.js";
import type { Database } from "../../prisma/db.js";
import type {
InventoryMovement,
InventoryMovementKey,
} from "../../entities/inventory-movement.entity.js";
import type { CreateInventoryMovementInput } from "../../dtos/inventory-movement.dto.js";
import type { IInventoryMovementRepository } from "./interfaces/inventory-movement-repository.interface.js";
const COLUMNS = [
"id",
"productId",
"inventoryId",
"type",
"quantity",
"reason",
"userId",
"createdAt",
] as const;
@Injectable()
export class InventoryMovementRepository
implements IInventoryMovementRepository
{
constructor(@Inject(DB) private readonly db: Database) {}
private get model() {
return this.db.orm.public.InventoryMovement;
}
findAll(): Promise<InventoryMovement[]> {
return this.model.select(...COLUMNS).all().toArray();
}
findById(where: InventoryMovementKey): Promise<InventoryMovement | null> {
return this.model.select(...COLUMNS).where(where).first();
}
create(data: CreateInventoryMovementInput): Promise<InventoryMovement> {
return this.model.select(...COLUMNS).create(data);
}
}
@@ -0,0 +1,90 @@
import { BadRequestException, Inject, Injectable, NotFoundException } from "@nestjs/common";
import {
PRODUCT_SERVICE,
type IProductService,
} from "../product/interfaces/product-service.interface.js";
import {
SUPPLIER_SERVICE,
type ISupplierService,
} from "../supplier/interfaces/supplier-service.interface.js";
import {
WAREHOUSE_SERVICE,
type IWarehouseService,
} from "../warehouse/interfaces/warehouse-service.interface.js";
import type {
InventoryMovement,
InventoryMovementKey,
} from "../../entities/inventory-movement.entity.js";
import type { CreateInventoryMovementInput } from "../../dtos/inventory-movement.dto.js";
import {
INVENTORY_MOVEMENT_REPOSITORY,
type IInventoryMovementRepository,
} from "./interfaces/inventory-movement-repository.interface.js";
import type { IInventoryMovementService } from "./interfaces/inventory-movement-service.interface.js";
/**
* El stock de un almacén nunca se edita directamente: cada cambio queda
* registrado como un movimiento (entrada/salida), que es quien ajusta
* `Warehouse.stock`. Una entrada además actualiza `Supplier.lastSupply`
* cuando el producto tiene proveedor asociado.
*/
@Injectable()
export class InventoryMovementService implements IInventoryMovementService {
constructor(
@Inject(INVENTORY_MOVEMENT_REPOSITORY)
private readonly repository: IInventoryMovementRepository,
@Inject(WAREHOUSE_SERVICE) private readonly warehouseService: IWarehouseService,
@Inject(PRODUCT_SERVICE) private readonly productService: IProductService,
@Inject(SUPPLIER_SERVICE) private readonly supplierService: ISupplierService,
) {}
findAll(): Promise<InventoryMovement[]> {
return this.repository.findAll();
}
async findById(where: InventoryMovementKey): Promise<InventoryMovement> {
const movement = await this.repository.findById(where);
if (!movement) {
throw new NotFoundException(`InventoryMovement ${where.id} not found`);
}
return movement;
}
async create(data: CreateInventoryMovementInput): Promise<InventoryMovement> {
if (data.quantity <= 0) {
throw new BadRequestException("La cantidad debe ser mayor a cero");
}
if (data.type !== "in" && data.type !== "out") {
throw new BadRequestException('El tipo debe ser "in" o "out"');
}
const key = { inventoryId: data.inventoryId, productId: data.productId };
const existing = await this.warehouseService
.findById(key)
.catch(() => null);
if (data.type === "out") {
const currentStock = existing?.stock ?? 0;
if (currentStock < data.quantity) {
throw new BadRequestException("Stock insuficiente para registrar la salida");
}
await this.warehouseService.update(key, { stock: currentStock - data.quantity });
} else {
if (existing) {
await this.warehouseService.update(key, { stock: existing.stock + data.quantity });
} else {
await this.warehouseService.create({ ...key, stock: data.quantity });
}
const product = await this.productService.findById({ id: data.productId }).catch(() => null);
if (product?.supplierId) {
await this.supplierService.update(
{ id: product.supplierId },
{ lastSupply: new Date().toISOString() },
);
}
}
return this.repository.create(data);
}
}
@@ -0,0 +1,18 @@
import type { IRepository } from "../../../common/repository.interface.js";
import type {
Inventory,
InventoryKey,
} from "../../../entities/inventory.entity.js";
import type {
CreateInventoryInput,
UpdateInventoryInput,
} from "../../../dtos/inventory.dto.js";
export const INVENTORY_REPOSITORY = Symbol("INVENTORY_REPOSITORY");
export type IInventoryRepository = IRepository<
Inventory,
CreateInventoryInput,
UpdateInventoryInput,
InventoryKey
>;
@@ -0,0 +1,18 @@
import type { IService } from "../../../common/service.interface.js";
import type {
Inventory,
InventoryKey,
} from "../../../entities/inventory.entity.js";
import type {
CreateInventoryInput,
UpdateInventoryInput,
} from "../../../dtos/inventory.dto.js";
export const INVENTORY_SERVICE = Symbol("INVENTORY_SERVICE");
export type IInventoryService = IService<
Inventory,
CreateInventoryInput,
UpdateInventoryInput,
InventoryKey
>;
@@ -0,0 +1,53 @@
import {
Body,
Controller,
Delete,
Get,
Inject,
Param,
ParseIntPipe,
Patch,
Post,
} from "@nestjs/common";
import type {
CreateInventoryInput,
UpdateInventoryInput,
} from "../../dtos/inventory.dto.js";
import { INVENTORY_SERVICE } from "./interfaces/inventory-service.interface.js";
import type { IInventoryService } from "./interfaces/inventory-service.interface.js";
@Controller("inventories")
export class InventoryController {
constructor(
@Inject(INVENTORY_SERVICE)
private readonly inventoryService: IInventoryService,
) {}
@Get()
findAll() {
return this.inventoryService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.inventoryService.findById({ id });
}
@Post()
create(@Body() body: CreateInventoryInput) {
return this.inventoryService.create(body);
}
@Patch(":id")
update(
@Param("id", ParseIntPipe) id: number,
@Body() body: UpdateInventoryInput,
) {
return this.inventoryService.update({ id }, body);
}
@Delete(":id")
remove(@Param("id", ParseIntPipe) id: number) {
return this.inventoryService.delete({ id });
}
}
@@ -0,0 +1,16 @@
import { Module } from "@nestjs/common";
import { INVENTORY_REPOSITORY } from "./interfaces/inventory-repository.interface.js";
import { INVENTORY_SERVICE } from "./interfaces/inventory-service.interface.js";
import { InventoryController } from "./inventory.controller.js";
import { InventoryRepository } from "./inventory.repository.js";
import { InventoryService } from "./inventory.service.js";
@Module({
controllers: [InventoryController],
providers: [
{ provide: INVENTORY_REPOSITORY, useClass: InventoryRepository },
{ provide: INVENTORY_SERVICE, useClass: InventoryService },
],
exports: [INVENTORY_SERVICE],
})
export class InventoryModule {}
@@ -0,0 +1,28 @@
import { Injectable } from "@nestjs/common";
import { BaseRepository } from "../../common/base.repository.js";
import type {
CreateInventoryInput,
UpdateInventoryInput,
} from "../../dtos/inventory.dto.js";
import type {
Inventory,
InventoryKey,
} from "../../entities/inventory.entity.js";
import type { IInventoryRepository } from "./interfaces/inventory-repository.interface.js";
@Injectable()
export class InventoryRepository
extends BaseRepository<
Inventory,
CreateInventoryInput,
UpdateInventoryInput,
InventoryKey
>
implements IInventoryRepository
{
protected readonly columns = ["id", "name"] as const;
protected get model() {
return this.db.orm.public.Inventory;
}
}
@@ -0,0 +1,34 @@
import { Inject, Injectable } from "@nestjs/common";
import { BaseService } from "../../common/base.service.js";
import type {
CreateInventoryInput,
UpdateInventoryInput,
} from "../../dtos/inventory.dto.js";
import type {
Inventory,
InventoryKey,
} from "../../entities/inventory.entity.js";
import {
INVENTORY_REPOSITORY,
type IInventoryRepository,
} from "./interfaces/inventory-repository.interface.js";
import type { IInventoryService } from "./interfaces/inventory-service.interface.js";
@Injectable()
export class InventoryService
extends BaseService<
Inventory,
CreateInventoryInput,
UpdateInventoryInput,
InventoryKey
>
implements IInventoryService
{
constructor(@Inject(INVENTORY_REPOSITORY) repository: IInventoryRepository) {
super(repository);
}
protected override notFoundMessage(where: InventoryKey): string {
return `Inventory ${where.id} not found`;
}
}
@@ -0,0 +1,12 @@
import type { IRepository } from "../../../common/repository.interface.js";
import type { OperatingDay, OperatingDayKey } from "../../../entities/operating-day.entity.js";
import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../../dtos/operating-day.dto.js";
export const OPERATING_DAY_REPOSITORY = Symbol("OPERATING_DAY_REPOSITORY");
export type IOperatingDayRepository = IRepository<
OperatingDay,
CreateOperatingDayInput,
UpdateOperatingDayInput,
OperatingDayKey
>;
@@ -0,0 +1,12 @@
import type { IService } from "../../../common/service.interface.js";
import type { OperatingDay, OperatingDayKey } from "../../../entities/operating-day.entity.js";
import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../../dtos/operating-day.dto.js";
export const OPERATING_DAY_SERVICE = Symbol("OPERATING_DAY_SERVICE");
export type IOperatingDayService = IService<
OperatingDay,
CreateOperatingDayInput,
UpdateOperatingDayInput,
OperatingDayKey
>;
@@ -0,0 +1,37 @@
import { Body, Controller, Delete, Get, Inject, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../dtos/operating-day.dto.js";
import { OPERATING_DAY_SERVICE } from "./interfaces/operating-day-service.interface.js";
import type { IOperatingDayService } from "./interfaces/operating-day-service.interface.js";
@Controller("operating-days")
export class OperatingDayController {
constructor(
@Inject(OPERATING_DAY_SERVICE)
private readonly operatingDayService: IOperatingDayService,
) {}
@Get()
findAll() {
return this.operatingDayService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.operatingDayService.findById({ id });
}
@Post()
create(@Body() body: CreateOperatingDayInput) {
return this.operatingDayService.create(body);
}
@Patch(":id")
update(@Param("id", ParseIntPipe) id: number, @Body() body: UpdateOperatingDayInput) {
return this.operatingDayService.update({ id }, body);
}
@Delete(":id")
remove(@Param("id", ParseIntPipe) id: number) {
return this.operatingDayService.delete({ id });
}
}
@@ -0,0 +1,18 @@
import { Module } from "@nestjs/common";
import { CashRegisterSessionModule } from "../cash-register-session/cash-register-session.module.js";
import { OPERATING_DAY_REPOSITORY } from "./interfaces/operating-day-repository.interface.js";
import { OPERATING_DAY_SERVICE } from "./interfaces/operating-day-service.interface.js";
import { OperatingDayController } from "./operating-day.controller.js";
import { OperatingDayRepository } from "./operating-day.repository.js";
import { OperatingDayService } from "./operating-day.service.js";
@Module({
imports: [CashRegisterSessionModule],
controllers: [OperatingDayController],
providers: [
{ provide: OPERATING_DAY_REPOSITORY, useClass: OperatingDayRepository },
{ provide: OPERATING_DAY_SERVICE, useClass: OperatingDayService },
],
exports: [OPERATING_DAY_SERVICE],
})
export class OperatingDayModule {}
@@ -0,0 +1,17 @@
import { Injectable } from "@nestjs/common";
import { BaseRepository } from "../../common/base.repository.js";
import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../dtos/operating-day.dto.js";
import type { OperatingDay, OperatingDayKey } from "../../entities/operating-day.entity.js";
import type { IOperatingDayRepository } from "./interfaces/operating-day-repository.interface.js";
@Injectable()
export class OperatingDayRepository
extends BaseRepository<OperatingDay, CreateOperatingDayInput, UpdateOperatingDayInput, OperatingDayKey>
implements IOperatingDayRepository
{
protected readonly columns = ["id", "date", "status", "openedById", "closedById", "openedAt", "closedAt"] as const;
protected get model() {
return this.db.orm.public.OperatingDay;
}
}
@@ -0,0 +1,65 @@
import { BadRequestException, Inject, Injectable } from "@nestjs/common";
import { BaseService } from "../../common/base.service.js";
import type { CreateOperatingDayInput, UpdateOperatingDayInput } from "../../dtos/operating-day.dto.js";
import type { OperatingDay, OperatingDayKey } from "../../entities/operating-day.entity.js";
import {
CASH_REGISTER_SESSION_SERVICE,
type ICashRegisterSessionService,
} from "../cash-register-session/interfaces/cash-register-session-service.interface.js";
import { OPERATING_DAY_REPOSITORY, type IOperatingDayRepository } from "./interfaces/operating-day-repository.interface.js";
import type { IOperatingDayService } from "./interfaces/operating-day-service.interface.js";
@Injectable()
export class OperatingDayService
extends BaseService<OperatingDay, CreateOperatingDayInput, UpdateOperatingDayInput, OperatingDayKey>
implements IOperatingDayService
{
constructor(
@Inject(OPERATING_DAY_REPOSITORY)
private readonly operatingDayRepository: IOperatingDayRepository,
@Inject(CASH_REGISTER_SESSION_SERVICE)
private readonly sessionService: ICashRegisterSessionService,
) {
super(operatingDayRepository);
}
override async create(data: CreateOperatingDayInput): Promise<OperatingDay> {
const all = await this.operatingDayRepository.findAll();
const alreadyOpen = all.some((d) => d.status === "open");
if (alreadyOpen) {
throw new BadRequestException("Ya hay una fecha de operación abierta");
}
return this.operatingDayRepository.create(data);
}
override async update(where: OperatingDayKey, data: UpdateOperatingDayInput): Promise<OperatingDay> {
const current = await this.findById(where);
if (data.status === "closed") {
if (current.status === "closed") {
throw new BadRequestException("Esta fecha de operación ya está cerrada");
}
const sessions = await this.sessionService.findAll();
const hasOpenSession = sessions.some((s) => s.status === "open");
if (hasOpenSession) {
throw new BadRequestException("No se puede cerrar el día de operación con cajas abiertas");
}
if (!data.closedById) {
throw new BadRequestException("Falta indicar quién cierra la fecha de operación");
}
const closed = await this.operatingDayRepository.update(where, {
...data,
closedAt: data.closedAt ?? new Date().toISOString(),
});
await this.operatingDayRepository.create({ openedById: data.closedById });
return closed;
}
return this.operatingDayRepository.update(where, data);
}
protected override notFoundMessage(where: OperatingDayKey): string {
return `OperatingDay ${where.id} not found`;
}
}
@@ -0,0 +1,15 @@
import type { IRepository } from "../../../common/repository.interface.js";
import type { Payment, PaymentKey } from "../../../entities/payment.entity.js";
import type {
CreatePaymentInput,
UpdatePaymentInput,
} from "../../../dtos/payment.dto.js";
export const PAYMENT_REPOSITORY = Symbol("PAYMENT_REPOSITORY");
export type IPaymentRepository = IRepository<
Payment,
CreatePaymentInput,
UpdatePaymentInput,
PaymentKey
>;
@@ -0,0 +1,15 @@
import type { IService } from "../../../common/service.interface.js";
import type { Payment, PaymentKey } from "../../../entities/payment.entity.js";
import type {
CreatePaymentInput,
UpdatePaymentInput,
} from "../../../dtos/payment.dto.js";
export const PAYMENT_SERVICE = Symbol("PAYMENT_SERVICE");
export type IPaymentService = IService<
Payment,
CreatePaymentInput,
UpdatePaymentInput,
PaymentKey
>;
@@ -0,0 +1,52 @@
import {
Body,
Controller,
Delete,
Get,
Inject,
Param,
ParseIntPipe,
Patch,
Post,
} from "@nestjs/common";
import type {
CreatePaymentInput,
UpdatePaymentInput,
} from "../../dtos/payment.dto.js";
import { PAYMENT_SERVICE } from "./interfaces/payment-service.interface.js";
import type { IPaymentService } from "./interfaces/payment-service.interface.js";
@Controller("payments")
export class PaymentController {
constructor(
@Inject(PAYMENT_SERVICE) private readonly paymentService: IPaymentService,
) {}
@Get()
findAll() {
return this.paymentService.findAll();
}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.paymentService.findById({ id });
}
@Post()
create(@Body() body: CreatePaymentInput) {
return this.paymentService.create(body);
}
@Patch(":id")
update(
@Param("id", ParseIntPipe) id: number,
@Body() body: UpdatePaymentInput,
) {
return this.paymentService.update({ id }, body);
}
@Delete(":id")
remove(@Param("id", ParseIntPipe) id: number) {
return this.paymentService.delete({ id });
}
}
@@ -0,0 +1,16 @@
import { Module } from "@nestjs/common";
import { PAYMENT_REPOSITORY } from "./interfaces/payment-repository.interface.js";
import { PAYMENT_SERVICE } from "./interfaces/payment-service.interface.js";
import { PaymentController } from "./payment.controller.js";
import { PaymentRepository } from "./payment.repository.js";
import { PaymentService } from "./payment.service.js";
@Module({
controllers: [PaymentController],
providers: [
{ provide: PAYMENT_REPOSITORY, useClass: PaymentRepository },
{ provide: PAYMENT_SERVICE, useClass: PaymentService },
],
exports: [PAYMENT_SERVICE],
})
export class PaymentModule {}
Loaded 100 of 404 files, more files were not shown because too many files have changed in this diff. Show more