feat(economy): implement smelting job creation and claiming logic
This commit is contained in:
73
src/game/smelting/service.ts
Normal file
73
src/game/smelting/service.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { prisma } from '../../core/database/prisma';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { findItemByKey, addItemByKey } from '../economy/service';
|
||||
|
||||
export type SmeltInput = { itemKey: string; qty: number };
|
||||
|
||||
export async function createSmeltJob(userId: string, guildId: string, inputs: SmeltInput[], outputItemKey: string, outputQty: number, smeltSeconds: number) {
|
||||
if (!inputs.length) throw new Error('No hay insumos');
|
||||
if (outputQty <= 0) throw new Error('Cantidad inválida');
|
||||
const readyAt = new Date(Date.now() + Math.max(1, smeltSeconds) * 1000);
|
||||
|
||||
// Validar output item id
|
||||
const outItem = await findItemByKey(guildId, outputItemKey);
|
||||
if (!outItem) throw new Error('Output item no encontrado');
|
||||
|
||||
// Validar y descontar inputs
|
||||
// Nota: para simplificar, chequeo y descuento debería ser transaccional; usamos una transacción
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Chequeo
|
||||
for (const i of inputs) {
|
||||
const it = await tx.economyItem.findFirst({ where: { key: i.itemKey, OR: [{ guildId }, { guildId: null }] }, orderBy: [{ guildId: 'desc' }] });
|
||||
if (!it) throw new Error(`Input no encontrado: ${i.itemKey}`);
|
||||
const inv = await tx.inventoryEntry.findUnique({ where: { userId_guildId_itemId: { userId, guildId, itemId: it.id } } });
|
||||
if ((inv?.quantity ?? 0) < i.qty) throw new Error(`Faltan insumos: ${i.itemKey}`);
|
||||
}
|
||||
// Descuento
|
||||
for (const i of inputs) {
|
||||
const it = await tx.economyItem.findFirst({ where: { key: i.itemKey, OR: [{ guildId }, { guildId: null }] }, orderBy: [{ guildId: 'desc' }] });
|
||||
if (!it) continue;
|
||||
await tx.inventoryEntry.update({ where: { userId_guildId_itemId: { userId, guildId, itemId: it.id } }, data: { quantity: { decrement: i.qty } } });
|
||||
}
|
||||
// Crear job
|
||||
await tx.smeltJob.create({
|
||||
data: {
|
||||
userId,
|
||||
guildId,
|
||||
inputs: { items: inputs } as unknown as Prisma.InputJsonValue,
|
||||
outputItemId: outItem.id,
|
||||
outputQty,
|
||||
readyAt,
|
||||
status: 'pending',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return { readyAt } as const;
|
||||
}
|
||||
|
||||
export async function claimSmeltJob(userId: string, guildId: string, jobId: string) {
|
||||
const job = await prisma.smeltJob.findUnique({ where: { id: jobId } });
|
||||
if (!job || job.userId !== userId || job.guildId !== guildId) throw new Error('Job inválido');
|
||||
if (job.status !== 'pending' && job.status !== 'ready') throw new Error('Estado inválido');
|
||||
if (job.readyAt > new Date()) throw new Error('Aún no está listo');
|
||||
|
||||
// Otorgar outputs y marcar claimed
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.smeltJob.update({ where: { id: job.id }, data: { status: 'claimed' } });
|
||||
const outItem = await tx.economyItem.findUnique({ where: { id: job.outputItemId } });
|
||||
if (outItem) {
|
||||
// usamos servicio economy por fuera de la transacción (para evitar nested client); hacemos simple aquí
|
||||
// añadir con tx: replicamos addItem
|
||||
const inv = await tx.inventoryEntry.findUnique({ where: { userId_guildId_itemId: { userId, guildId, itemId: outItem.id } } });
|
||||
if (inv) {
|
||||
await tx.inventoryEntry.update({ where: { userId_guildId_itemId: { userId, guildId, itemId: outItem.id } }, data: { quantity: { increment: job.outputQty } } });
|
||||
} else {
|
||||
await tx.inventoryEntry.create({ data: { userId, guildId, itemId: outItem.id, quantity: job.outputQty } });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { ok: true } as const;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user