import { randomUUID } from "crypto"; import { validate } from "@olagg/validate"; import { inject, injectable } from "tsyringe"; import Item from "../domain/entities/item"; import Stock from "../domain/entities/stock"; import User from "../domain/entities/user"; import ItemType from "../domain/entities/vo/itemType.vo"; import ItemRepository from "../domain/repositories/item.repository"; import StockRepository from "../domain/repositories/stock.repository"; import UserRepository from "../domain/repositories/user.repository"; import AssignStockToUserService from "../domain/services/assignStockToUser"; import assignStockToUserSchema from "../domain/services/schemas/assignStockToUser.schema"; import { AssignStockAttr } from "../domain/types/stock.type"; import Result from "../utils/result"; import analytics from "../utils/segment"; @injectable() export default class AssignStockToUserServiceImpl implements AssignStockToUserService { constructor( @inject("Items:ItemRepository") private repository: ItemRepository, @inject("Stock:StockRepository") private stockRepo: StockRepository, @inject("Items:UserRepository") private userRepo: UserRepository, ) { } @validate(assignStockToUserSchema) async run(payload: AssignStockAttr, validate = true): Promise> { const item = await this.repository.find({ id: payload.itemId }); try { await this.validateUniqueness(payload); const user = await this.userRepo.find(payload.userId); if (validate) { this.validatePrice(item!, user!); this.validateDueDate(item!); } let stock: Stock | undefined; switch (item!.type) { case ItemType.LIMITED: stock = await this.assignLimited(payload); break; case ItemType.UNLIMITED: stock = await this.assignUnlimited(payload); break; case ItemType.LOTTERY: stock = await this.assignLottery(payload); break; default: return Result.failure("Tipo de item incorrecto"); } if (stock) { this.sendSuccessEvent(item!, payload.userId, false); return Result.success(stock); } else { this.sendErrorEvent(item!, payload.userId, "Error al asignar stock. No identificado.", false); return Result.failure("No hay stock disponible"); } } catch (error) { console.error(error); this.sendErrorEvent(item!, payload.userId, (error as Error).message, false); return Result.failure("No se pudo asignar el stock:", (error as Error).message); } } async fromCode(itemId: string, userId: number, encodedCode: string): Promise> { const stock = await this.stockRepo.find({ itemId, encodedCode, noAssigned: true }); if (!stock) return Result.failure("Invalid code"); const result = await this.stockRepo.update({ id: stock.id!, data: { assignedToId: userId, itemId: stock.item.id, code: stock.code, } }); const item = await this.repository.find({ id: itemId }); if (result?.assignedToId === userId) { this.sendSuccessEvent(item!, userId, true) return Result.success(result); } else { this.sendErrorEvent(item!, userId, "Error al asignar el código", true); return Result.failure("Error al asignar el código"); } } private validateDueDate(item: Item): void { if (item.dueDate && item.dueDate < new Date()) throw Error("Item vencido"); } private validatePrice(item: Item, user: User): void { if (item.price < 1) { throw Error("Este item es canjeable solo por cupones"); } if (item.price > user.level) throw Error("No tienes el nivel suficiente"); } private async validateUniqueness(payload: AssignStockAttr): Promise { const alreadyAssigned = await this.stockRepo.find({ itemId: payload.itemId, userId: payload.userId }); if (alreadyAssigned) throw Error("Ya te asignaron este item"); } private async assignLimited(payload: AssignStockAttr): Promise { return await this.stockRepo.assign({ itemId: payload.itemId, userId: payload.userId }); } private async assignUnlimited(payload: AssignStockAttr): Promise { const lastAvailableStock = await this.stockRepo.find({ itemId: payload.itemId, noAssigned: true }); if (!lastAvailableStock) { const lastStock = await this.stockRepo.find({ itemId: payload.itemId }); const result = await this.stockRepo.create({ itemId: payload.itemId, stock: [{ code: lastStock!.code, assignedToId: payload.userId }] }); return result[0]; } else { const result = await this.stockRepo.update({ id: lastAvailableStock.id!, data: { itemId: payload.itemId, code: lastAvailableStock.code, assignedToId: payload.userId } }); return result; } } private async assignLottery(payload: AssignStockAttr): Promise { const result = await this.stockRepo.create({ itemId: payload.itemId, stock: [{ code: randomUUID(), assignedToId: payload.userId }] }); return result[0]; } private sendSuccessEvent(item: Item, userId: number, coupon: boolean): void { analytics.track({ event: "Reward Redeem Completed", userId, properties: { "reward_id": item.id, "reward_name": item.name, "reward_type": item.type, "reward_price": item.price, "is_coupon": coupon } }); } private sendErrorEvent(item: Item, userId: number, error: string, coupon: boolean): void { analytics.track({ event: "Reward Redeem Failed", userId, properties: { "reward_id": item.id, "reward_name": item.name, "reward_type": item.type, "reward_price": item.price, "is_coupon": coupon, "reason": error } }); } }