michangarrito/apps/backend/dist/modules/inventory/inventory.service.js
rckrdmrd 48dea7a5d0 feat: Initial commit - michangarrito
Marketplace móvil para negocios locales mexicanos.

Estructura inicial:
- apps/backend (NestJS API)
- apps/frontend (React Web)
- apps/mobile (Expo/React Native)
- apps/mcp-server (Claude MCP Server)
- apps/whatsapp-service (WhatsApp Business API)
- database/ (PostgreSQL DDL)
- docs/ (Documentación)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:41:02 -06:00

196 lines
8.1 KiB
JavaScript

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InventoryService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const inventory_movement_entity_1 = require("./entities/inventory-movement.entity");
const stock_alert_entity_1 = require("./entities/stock-alert.entity");
const product_entity_1 = require("../products/entities/product.entity");
let InventoryService = class InventoryService {
constructor(movementRepo, alertRepo, productRepo) {
this.movementRepo = movementRepo;
this.alertRepo = alertRepo;
this.productRepo = productRepo;
}
async createMovement(tenantId, dto, userId) {
const product = await this.productRepo.findOne({
where: { id: dto.productId, tenantId },
});
if (!product) {
throw new common_1.NotFoundException('Producto no encontrado');
}
const quantityBefore = Number(product.stockQuantity);
const quantityAfter = quantityBefore + dto.quantity;
if (quantityAfter < 0) {
throw new common_1.BadRequestException(`Stock insuficiente. Disponible: ${quantityBefore}, Solicitado: ${Math.abs(dto.quantity)}`);
}
const movement = this.movementRepo.create({
tenantId,
productId: dto.productId,
movementType: dto.movementType,
quantity: dto.quantity,
quantityBefore,
quantityAfter,
unitCost: dto.unitCost,
referenceType: dto.referenceType,
referenceId: dto.referenceId,
notes: dto.notes,
createdBy: userId,
});
await this.movementRepo.save(movement);
product.stockQuantity = quantityAfter;
await this.productRepo.save(product);
await this.checkStockAlerts(tenantId, product);
return movement;
}
async adjustStock(tenantId, dto, userId) {
const product = await this.productRepo.findOne({
where: { id: dto.productId, tenantId },
});
if (!product) {
throw new common_1.NotFoundException('Producto no encontrado');
}
const quantityBefore = Number(product.stockQuantity);
const difference = dto.newQuantity - quantityBefore;
return this.createMovement(tenantId, {
productId: dto.productId,
movementType: inventory_movement_entity_1.MovementType.ADJUSTMENT,
quantity: difference,
notes: dto.reason || `Ajuste de inventario: ${quantityBefore} -> ${dto.newQuantity}`,
}, userId);
}
async getMovements(tenantId, productId, limit = 50) {
const where = { tenantId };
if (productId) {
where.productId = productId;
}
return this.movementRepo.find({
where,
relations: ['product'],
order: { createdAt: 'DESC' },
take: limit,
});
}
async getProductHistory(tenantId, productId) {
return this.movementRepo.find({
where: { tenantId, productId },
order: { createdAt: 'DESC' },
});
}
async checkStockAlerts(tenantId, product) {
const currentStock = Number(product.stockQuantity);
const threshold = Number(product.lowStockThreshold);
if (currentStock > threshold) {
await this.alertRepo.update({ productId: product.id, status: stock_alert_entity_1.AlertStatus.ACTIVE }, { status: stock_alert_entity_1.AlertStatus.RESOLVED, resolvedAt: new Date() });
return;
}
const existingAlert = await this.alertRepo.findOne({
where: { productId: product.id, status: stock_alert_entity_1.AlertStatus.ACTIVE },
});
if (existingAlert) {
existingAlert.currentStock = currentStock;
await this.alertRepo.save(existingAlert);
return;
}
const alertType = currentStock <= 0 ? stock_alert_entity_1.AlertType.OUT_OF_STOCK : stock_alert_entity_1.AlertType.LOW_STOCK;
const alert = this.alertRepo.create({
tenantId,
productId: product.id,
alertType,
currentStock,
threshold,
});
await this.alertRepo.save(alert);
}
async getActiveAlerts(tenantId) {
return this.alertRepo.find({
where: { tenantId, status: stock_alert_entity_1.AlertStatus.ACTIVE },
relations: ['product'],
order: { createdAt: 'DESC' },
});
}
async dismissAlert(tenantId, alertId) {
const alert = await this.alertRepo.findOne({
where: { id: alertId, tenantId },
});
if (!alert) {
throw new common_1.NotFoundException('Alerta no encontrada');
}
alert.status = stock_alert_entity_1.AlertStatus.DISMISSED;
return this.alertRepo.save(alert);
}
async getLowStockProducts(tenantId) {
return this.productRepo
.createQueryBuilder('product')
.where('product.tenant_id = :tenantId', { tenantId })
.andWhere('product.track_inventory = true')
.andWhere('product.stock_quantity <= product.low_stock_threshold')
.andWhere("product.status = 'active'")
.orderBy('product.stock_quantity', 'ASC')
.getMany();
}
async getOutOfStockProducts(tenantId) {
return this.productRepo.find({
where: {
tenantId,
trackInventory: true,
stockQuantity: (0, typeorm_2.LessThanOrEqual)(0),
status: 'active',
},
order: { name: 'ASC' },
});
}
async getInventoryStats(tenantId) {
const products = await this.productRepo.find({
where: { tenantId, trackInventory: true, status: 'active' },
});
let totalValue = 0;
let lowStockCount = 0;
let outOfStockCount = 0;
for (const product of products) {
const stock = Number(product.stockQuantity);
const cost = Number(product.costPrice) || 0;
totalValue += stock * cost;
if (stock <= 0) {
outOfStockCount++;
}
else if (stock <= Number(product.lowStockThreshold)) {
lowStockCount++;
}
}
const activeAlerts = await this.alertRepo.count({
where: { tenantId, status: stock_alert_entity_1.AlertStatus.ACTIVE },
});
return {
totalProducts: products.length,
totalValue,
lowStockCount,
outOfStockCount,
activeAlerts,
};
}
};
exports.InventoryService = InventoryService;
exports.InventoryService = InventoryService = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(inventory_movement_entity_1.InventoryMovement)),
__param(1, (0, typeorm_1.InjectRepository)(stock_alert_entity_1.StockAlert)),
__param(2, (0, typeorm_1.InjectRepository)(product_entity_1.Product)),
__metadata("design:paramtypes", [typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository])
], InventoryService);
//# sourceMappingURL=inventory.service.js.map