all
This commit is contained in:
1 parent
90125aede9
commit
2d6c29a1f3
404 files changed
+126983
-85
No files matched your search
+15
@@ -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>;
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user