- Prefijo v2: MCH - TRACEABILITY-MASTER.yml creado - Listo para integracion como submodulo Workspace: v2.0.0 | SIMCO: v4.0.0
318 lines
15 KiB
JavaScript
318 lines
15 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.MarketplaceService = void 0;
|
|
const common_1 = require("@nestjs/common");
|
|
const typeorm_1 = require("@nestjs/typeorm");
|
|
const typeorm_2 = require("typeorm");
|
|
const supplier_entity_1 = require("./entities/supplier.entity");
|
|
const supplier_product_entity_1 = require("./entities/supplier-product.entity");
|
|
const supplier_order_entity_1 = require("./entities/supplier-order.entity");
|
|
const supplier_order_item_entity_1 = require("./entities/supplier-order-item.entity");
|
|
const supplier_review_entity_1 = require("./entities/supplier-review.entity");
|
|
let MarketplaceService = class MarketplaceService {
|
|
constructor(supplierRepo, productRepo, orderRepo, orderItemRepo, reviewRepo, dataSource) {
|
|
this.supplierRepo = supplierRepo;
|
|
this.productRepo = productRepo;
|
|
this.orderRepo = orderRepo;
|
|
this.orderItemRepo = orderItemRepo;
|
|
this.reviewRepo = reviewRepo;
|
|
this.dataSource = dataSource;
|
|
}
|
|
async findSuppliers(options) {
|
|
const query = this.supplierRepo.createQueryBuilder('supplier')
|
|
.where('supplier.status = :status', { status: supplier_entity_1.SupplierStatus.ACTIVE })
|
|
.orderBy('supplier.rating', 'DESC')
|
|
.addOrderBy('supplier.total_orders', 'DESC');
|
|
if (options?.category) {
|
|
query.andWhere(':category = ANY(supplier.categories)', {
|
|
category: options.category,
|
|
});
|
|
}
|
|
if (options?.zipCode) {
|
|
query.andWhere('(supplier.coverage_zones = \'{}\' OR :zipCode = ANY(supplier.coverage_zones))', { zipCode: options.zipCode });
|
|
}
|
|
if (options?.search) {
|
|
query.andWhere('(supplier.name ILIKE :search OR supplier.description ILIKE :search)', { search: `%${options.search}%` });
|
|
}
|
|
if (options?.limit) {
|
|
query.limit(options.limit);
|
|
}
|
|
return query.getMany();
|
|
}
|
|
async getSupplier(id) {
|
|
const supplier = await this.supplierRepo.findOne({
|
|
where: { id },
|
|
relations: ['products', 'reviews'],
|
|
});
|
|
if (!supplier) {
|
|
throw new common_1.NotFoundException('Proveedor no encontrado');
|
|
}
|
|
return supplier;
|
|
}
|
|
async getSupplierProducts(supplierId, options) {
|
|
const query = this.productRepo.createQueryBuilder('product')
|
|
.where('product.supplier_id = :supplierId', { supplierId })
|
|
.andWhere('product.active = true')
|
|
.orderBy('product.category', 'ASC')
|
|
.addOrderBy('product.name', 'ASC');
|
|
if (options?.category) {
|
|
query.andWhere('product.category = :category', { category: options.category });
|
|
}
|
|
if (options?.search) {
|
|
query.andWhere('(product.name ILIKE :search OR product.description ILIKE :search)', { search: `%${options.search}%` });
|
|
}
|
|
if (options?.inStock !== undefined) {
|
|
query.andWhere('product.in_stock = :inStock', { inStock: options.inStock });
|
|
}
|
|
return query.getMany();
|
|
}
|
|
async createOrder(tenantId, dto) {
|
|
const supplier = await this.supplierRepo.findOne({
|
|
where: { id: dto.supplierId, status: supplier_entity_1.SupplierStatus.ACTIVE },
|
|
});
|
|
if (!supplier) {
|
|
throw new common_1.NotFoundException('Proveedor no encontrado o no activo');
|
|
}
|
|
const productIds = dto.items.map((item) => item.productId);
|
|
const products = await this.productRepo.findByIds(productIds);
|
|
if (products.length !== productIds.length) {
|
|
throw new common_1.BadRequestException('Algunos productos no fueron encontrados');
|
|
}
|
|
const productMap = new Map(products.map((p) => [p.id, p]));
|
|
let subtotal = 0;
|
|
for (const item of dto.items) {
|
|
const product = productMap.get(item.productId);
|
|
if (!product) {
|
|
throw new common_1.BadRequestException(`Producto ${item.productId} no encontrado`);
|
|
}
|
|
if (item.quantity < product.minQuantity) {
|
|
throw new common_1.BadRequestException(`Cantidad minima para ${product.name} es ${product.minQuantity}`);
|
|
}
|
|
if (!product.inStock) {
|
|
throw new common_1.BadRequestException(`${product.name} no esta disponible`);
|
|
}
|
|
let unitPrice = Number(product.unitPrice);
|
|
if (product.tieredPricing && product.tieredPricing.length > 0) {
|
|
for (const tier of product.tieredPricing.sort((a, b) => b.min - a.min)) {
|
|
if (item.quantity >= tier.min) {
|
|
unitPrice = tier.price;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
subtotal += unitPrice * item.quantity;
|
|
}
|
|
let deliveryFee = Number(supplier.deliveryFee);
|
|
if (supplier.freeDeliveryMin && subtotal >= Number(supplier.freeDeliveryMin)) {
|
|
deliveryFee = 0;
|
|
}
|
|
if (subtotal < Number(supplier.minOrderAmount)) {
|
|
throw new common_1.BadRequestException(`Pedido minimo es $${supplier.minOrderAmount}`);
|
|
}
|
|
const total = subtotal + deliveryFee;
|
|
const order = this.orderRepo.create({
|
|
tenantId,
|
|
supplierId: dto.supplierId,
|
|
status: supplier_order_entity_1.SupplierOrderStatus.PENDING,
|
|
subtotal,
|
|
deliveryFee,
|
|
total,
|
|
deliveryAddress: dto.deliveryAddress,
|
|
deliveryCity: dto.deliveryCity,
|
|
deliveryZip: dto.deliveryZip,
|
|
deliveryPhone: dto.deliveryPhone,
|
|
deliveryContact: dto.deliveryContact,
|
|
requestedDate: dto.requestedDate ? new Date(dto.requestedDate) : null,
|
|
notes: dto.notes,
|
|
});
|
|
await this.orderRepo.save(order);
|
|
for (const item of dto.items) {
|
|
const product = productMap.get(item.productId);
|
|
let unitPrice = Number(product.unitPrice);
|
|
if (product.tieredPricing && product.tieredPricing.length > 0) {
|
|
for (const tier of product.tieredPricing.sort((a, b) => b.min - a.min)) {
|
|
if (item.quantity >= tier.min) {
|
|
unitPrice = tier.price;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const orderItem = this.orderItemRepo.create({
|
|
orderId: order.id,
|
|
productId: item.productId,
|
|
productName: product.name,
|
|
productSku: product.sku,
|
|
quantity: item.quantity,
|
|
unitPrice,
|
|
total: unitPrice * item.quantity,
|
|
notes: item.notes,
|
|
});
|
|
await this.orderItemRepo.save(orderItem);
|
|
}
|
|
return this.getOrder(order.id);
|
|
}
|
|
async getOrder(id) {
|
|
const order = await this.orderRepo.findOne({
|
|
where: { id },
|
|
relations: ['items', 'supplier'],
|
|
});
|
|
if (!order) {
|
|
throw new common_1.NotFoundException('Pedido no encontrado');
|
|
}
|
|
return order;
|
|
}
|
|
async getOrders(tenantId, options) {
|
|
const query = this.orderRepo.createQueryBuilder('order')
|
|
.where('order.tenant_id = :tenantId', { tenantId })
|
|
.leftJoinAndSelect('order.supplier', 'supplier')
|
|
.leftJoinAndSelect('order.items', 'items')
|
|
.orderBy('order.created_at', 'DESC');
|
|
if (options?.status) {
|
|
query.andWhere('order.status = :status', { status: options.status });
|
|
}
|
|
if (options?.supplierId) {
|
|
query.andWhere('order.supplier_id = :supplierId', { supplierId: options.supplierId });
|
|
}
|
|
if (options?.limit) {
|
|
query.limit(options.limit);
|
|
}
|
|
return query.getMany();
|
|
}
|
|
async updateOrderStatus(id, status, notes) {
|
|
const order = await this.getOrder(id);
|
|
const validTransitions = {
|
|
[supplier_order_entity_1.SupplierOrderStatus.PENDING]: [supplier_order_entity_1.SupplierOrderStatus.CONFIRMED, supplier_order_entity_1.SupplierOrderStatus.CANCELLED, supplier_order_entity_1.SupplierOrderStatus.REJECTED],
|
|
[supplier_order_entity_1.SupplierOrderStatus.CONFIRMED]: [supplier_order_entity_1.SupplierOrderStatus.PREPARING, supplier_order_entity_1.SupplierOrderStatus.CANCELLED],
|
|
[supplier_order_entity_1.SupplierOrderStatus.PREPARING]: [supplier_order_entity_1.SupplierOrderStatus.SHIPPED, supplier_order_entity_1.SupplierOrderStatus.CANCELLED],
|
|
[supplier_order_entity_1.SupplierOrderStatus.SHIPPED]: [supplier_order_entity_1.SupplierOrderStatus.DELIVERED, supplier_order_entity_1.SupplierOrderStatus.CANCELLED],
|
|
[supplier_order_entity_1.SupplierOrderStatus.DELIVERED]: [],
|
|
[supplier_order_entity_1.SupplierOrderStatus.CANCELLED]: [],
|
|
[supplier_order_entity_1.SupplierOrderStatus.REJECTED]: [],
|
|
};
|
|
if (!validTransitions[order.status].includes(status)) {
|
|
throw new common_1.BadRequestException(`No se puede cambiar estado de ${order.status} a ${status}`);
|
|
}
|
|
order.status = status;
|
|
if (status === supplier_order_entity_1.SupplierOrderStatus.CONFIRMED) {
|
|
order.confirmedDate = new Date();
|
|
}
|
|
if (status === supplier_order_entity_1.SupplierOrderStatus.DELIVERED) {
|
|
order.deliveredAt = new Date();
|
|
}
|
|
if (status === supplier_order_entity_1.SupplierOrderStatus.CANCELLED || status === supplier_order_entity_1.SupplierOrderStatus.REJECTED) {
|
|
order.cancelledAt = new Date();
|
|
order.cancelReason = notes;
|
|
}
|
|
if (notes) {
|
|
order.supplierNotes = notes;
|
|
}
|
|
return this.orderRepo.save(order);
|
|
}
|
|
async cancelOrder(id, tenantId, reason) {
|
|
const order = await this.getOrder(id);
|
|
if (order.tenantId !== tenantId) {
|
|
throw new common_1.BadRequestException('No autorizado');
|
|
}
|
|
if (![supplier_order_entity_1.SupplierOrderStatus.PENDING, supplier_order_entity_1.SupplierOrderStatus.CONFIRMED].includes(order.status)) {
|
|
throw new common_1.BadRequestException('No se puede cancelar el pedido en este estado');
|
|
}
|
|
order.status = supplier_order_entity_1.SupplierOrderStatus.CANCELLED;
|
|
order.cancelledAt = new Date();
|
|
order.cancelReason = reason;
|
|
order.cancelledBy = 'tenant';
|
|
return this.orderRepo.save(order);
|
|
}
|
|
async createReview(tenantId, dto) {
|
|
const supplier = await this.supplierRepo.findOne({
|
|
where: { id: dto.supplierId },
|
|
});
|
|
if (!supplier) {
|
|
throw new common_1.NotFoundException('Proveedor no encontrado');
|
|
}
|
|
let verified = false;
|
|
if (dto.orderId) {
|
|
const order = await this.orderRepo.findOne({
|
|
where: { id: dto.orderId, tenantId, supplierId: dto.supplierId },
|
|
});
|
|
if (!order) {
|
|
throw new common_1.BadRequestException('Orden no encontrada');
|
|
}
|
|
if (order.status === supplier_order_entity_1.SupplierOrderStatus.DELIVERED) {
|
|
verified = true;
|
|
}
|
|
}
|
|
const review = this.reviewRepo.create({
|
|
tenantId,
|
|
supplierId: dto.supplierId,
|
|
orderId: dto.orderId,
|
|
rating: dto.rating,
|
|
title: dto.title,
|
|
comment: dto.comment,
|
|
ratingQuality: dto.ratingQuality,
|
|
ratingDelivery: dto.ratingDelivery,
|
|
ratingPrice: dto.ratingPrice,
|
|
verified,
|
|
});
|
|
return this.reviewRepo.save(review);
|
|
}
|
|
async getReviews(supplierId, options) {
|
|
return this.reviewRepo.find({
|
|
where: { supplierId, status: 'active' },
|
|
order: { createdAt: 'DESC' },
|
|
take: options?.limit || 20,
|
|
skip: options?.offset || 0,
|
|
});
|
|
}
|
|
async addFavorite(tenantId, supplierId) {
|
|
await this.dataSource.query(`INSERT INTO marketplace.supplier_favorites (tenant_id, supplier_id)
|
|
VALUES ($1, $2) ON CONFLICT DO NOTHING`, [tenantId, supplierId]);
|
|
}
|
|
async removeFavorite(tenantId, supplierId) {
|
|
await this.dataSource.query(`DELETE FROM marketplace.supplier_favorites WHERE tenant_id = $1 AND supplier_id = $2`, [tenantId, supplierId]);
|
|
}
|
|
async getFavorites(tenantId) {
|
|
const result = await this.dataSource.query(`SELECT s.* FROM marketplace.suppliers s
|
|
JOIN marketplace.supplier_favorites f ON s.id = f.supplier_id
|
|
WHERE f.tenant_id = $1`, [tenantId]);
|
|
return result;
|
|
}
|
|
async getMarketplaceStats() {
|
|
const result = await this.dataSource.query(`SELECT * FROM marketplace.get_marketplace_stats()`);
|
|
return result[0] || {
|
|
total_suppliers: 0,
|
|
active_suppliers: 0,
|
|
total_products: 0,
|
|
total_orders: 0,
|
|
total_gmv: 0,
|
|
avg_rating: 0,
|
|
};
|
|
}
|
|
};
|
|
exports.MarketplaceService = MarketplaceService;
|
|
exports.MarketplaceService = MarketplaceService = __decorate([
|
|
(0, common_1.Injectable)(),
|
|
__param(0, (0, typeorm_1.InjectRepository)(supplier_entity_1.Supplier)),
|
|
__param(1, (0, typeorm_1.InjectRepository)(supplier_product_entity_1.SupplierProduct)),
|
|
__param(2, (0, typeorm_1.InjectRepository)(supplier_order_entity_1.SupplierOrder)),
|
|
__param(3, (0, typeorm_1.InjectRepository)(supplier_order_item_entity_1.SupplierOrderItem)),
|
|
__param(4, (0, typeorm_1.InjectRepository)(supplier_review_entity_1.SupplierReview)),
|
|
__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
typeorm_2.Repository,
|
|
typeorm_2.Repository,
|
|
typeorm_2.Repository,
|
|
typeorm_2.Repository,
|
|
typeorm_2.DataSource])
|
|
], MarketplaceService);
|
|
//# sourceMappingURL=marketplace.service.js.map
|