491 lines
11 KiB
TypeScript
491 lines
11 KiB
TypeScript
/**
|
|
* Queries de Purchase Intentions usando Prisma
|
|
*/
|
|
|
|
import { Prisma } from "@prisma/client";
|
|
import { prisma } from "../app";
|
|
|
|
export type PurchaseIntentionStatus =
|
|
| "pending"
|
|
| "accepted"
|
|
| "rejected"
|
|
| "expired"
|
|
| "completed";
|
|
export type PaymentMethod = "pix" | "tibia_coins" | "gold_coins" | "trade";
|
|
|
|
export interface CreatePurchaseIntentionData {
|
|
listing_id: number;
|
|
buyer_id: number;
|
|
payment_method: PaymentMethod;
|
|
buyer_message?: string;
|
|
expires_at?: Date;
|
|
custom_offer_amount?: number | null;
|
|
custom_offer_currency?: PaymentMethod | null;
|
|
}
|
|
|
|
export interface PurchaseIntention {
|
|
intention_id: number;
|
|
listing_id: number;
|
|
buyer_id: number;
|
|
payment_method: string;
|
|
payment_method_change_count: number;
|
|
custom_offer_amount: number | null;
|
|
custom_offer_currency: string | null;
|
|
custom_offer_created_at: Date | null;
|
|
status: PurchaseIntentionStatus;
|
|
buyer_message: string | null;
|
|
seller_response: string | null;
|
|
created_at: Date;
|
|
updated_at: Date;
|
|
expires_at: Date | null;
|
|
accepted_at: Date | null;
|
|
rejected_at: Date | null;
|
|
// Relacionamentos
|
|
listing?: {
|
|
listing_id: number;
|
|
item_name: string;
|
|
seller_id: number;
|
|
};
|
|
buyer?: {
|
|
user_id: number;
|
|
username: string;
|
|
email: string;
|
|
};
|
|
}
|
|
|
|
function serializePurchaseIntention(intention: any): PurchaseIntention {
|
|
if (!intention) return intention;
|
|
|
|
return {
|
|
...intention,
|
|
custom_offer_amount:
|
|
intention.custom_offer_amount !== null &&
|
|
intention.custom_offer_amount !== undefined
|
|
? Number(intention.custom_offer_amount)
|
|
: null,
|
|
custom_offer_currency: intention.custom_offer_currency ?? null,
|
|
custom_offer_created_at: intention.custom_offer_created_at ?? null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Cria uma nova intenção de compra
|
|
*/
|
|
export async function createPurchaseIntention(
|
|
data: CreatePurchaseIntentionData,
|
|
): Promise<PurchaseIntention> {
|
|
const result = await prisma.purchaseIntention.create({
|
|
data: {
|
|
listing_id: data.listing_id,
|
|
buyer_id: data.buyer_id,
|
|
payment_method: data.payment_method,
|
|
buyer_message: data.buyer_message || null,
|
|
expires_at: data.expires_at || null,
|
|
status: "pending",
|
|
custom_offer_amount:
|
|
data.custom_offer_amount !== undefined &&
|
|
data.custom_offer_amount !== null
|
|
? new Prisma.Decimal(data.custom_offer_amount)
|
|
: null,
|
|
custom_offer_currency: data.custom_offer_currency || null,
|
|
custom_offer_created_at:
|
|
data.custom_offer_amount !== undefined &&
|
|
data.custom_offer_amount !== null
|
|
? new Date()
|
|
: null,
|
|
},
|
|
include: {
|
|
listing: {
|
|
select: {
|
|
listing_id: true,
|
|
item_name: true,
|
|
seller_id: true,
|
|
},
|
|
},
|
|
buyer: {
|
|
select: {
|
|
user_id: true,
|
|
username: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return serializePurchaseIntention(result);
|
|
}
|
|
|
|
/**
|
|
* Busca intenções de um anúncio específico
|
|
*/
|
|
export async function getPurchaseIntentionsByListing(
|
|
listingId: number,
|
|
filters: { status?: PurchaseIntentionStatus; limit?: number } = {},
|
|
): Promise<PurchaseIntention[]> {
|
|
const { status, limit = 50 } = filters;
|
|
|
|
const where: any = { listing_id: listingId };
|
|
if (status) {
|
|
where.status = status;
|
|
}
|
|
|
|
const intentions = await prisma.purchaseIntention.findMany({
|
|
where,
|
|
include: {
|
|
listing: {
|
|
select: {
|
|
listing_id: true,
|
|
item_name: true,
|
|
seller_id: true,
|
|
},
|
|
},
|
|
buyer: {
|
|
select: {
|
|
user_id: true,
|
|
username: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { created_at: "desc" },
|
|
take: limit,
|
|
});
|
|
|
|
return intentions.map(serializePurchaseIntention);
|
|
}
|
|
|
|
/**
|
|
* Busca intenções de um comprador
|
|
*/
|
|
export async function getPurchaseIntentionsByBuyer(
|
|
buyerId: number,
|
|
filters: { status?: PurchaseIntentionStatus; limit?: number } = {},
|
|
): Promise<PurchaseIntention[]> {
|
|
const { status, limit = 50 } = filters;
|
|
|
|
const where: any = { buyer_id: buyerId };
|
|
if (status) {
|
|
where.status = status;
|
|
}
|
|
|
|
const intentions = await prisma.purchaseIntention.findMany({
|
|
where,
|
|
include: {
|
|
listing: {
|
|
select: {
|
|
listing_id: true,
|
|
item_name: true,
|
|
seller_id: true,
|
|
},
|
|
},
|
|
buyer: {
|
|
select: {
|
|
user_id: true,
|
|
username: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { created_at: "desc" },
|
|
take: limit,
|
|
});
|
|
|
|
return intentions.map(serializePurchaseIntention);
|
|
}
|
|
|
|
/**
|
|
* Busca uma intenção específica por ID
|
|
*/
|
|
export async function getPurchaseIntentionById(
|
|
intentionId: number,
|
|
): Promise<PurchaseIntention | null> {
|
|
const intention = await prisma.purchaseIntention.findUnique({
|
|
where: { intention_id: intentionId },
|
|
include: {
|
|
listing: {
|
|
select: {
|
|
listing_id: true,
|
|
item_name: true,
|
|
seller_id: true,
|
|
},
|
|
},
|
|
buyer: {
|
|
select: {
|
|
user_id: true,
|
|
username: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return serializePurchaseIntention(intention);
|
|
}
|
|
|
|
/**
|
|
* Atualiza o status de uma intenção
|
|
*/
|
|
export async function updatePurchaseIntentionStatus(
|
|
intentionId: number,
|
|
status: PurchaseIntentionStatus,
|
|
sellerResponse?: string,
|
|
): Promise<PurchaseIntention> {
|
|
const updateData: any = {
|
|
status,
|
|
updated_at: new Date(),
|
|
};
|
|
|
|
if (status === "accepted") {
|
|
updateData.accepted_at = new Date();
|
|
updateData.seller_response = sellerResponse || null;
|
|
} else if (status === "rejected") {
|
|
updateData.rejected_at = new Date();
|
|
updateData.seller_response = sellerResponse || null;
|
|
} else if (sellerResponse) {
|
|
updateData.seller_response = sellerResponse;
|
|
}
|
|
|
|
const updated = await prisma.purchaseIntention.update({
|
|
where: { intention_id: intentionId },
|
|
data: updateData,
|
|
include: {
|
|
listing: {
|
|
select: {
|
|
listing_id: true,
|
|
item_name: true,
|
|
seller_id: true,
|
|
},
|
|
},
|
|
buyer: {
|
|
select: {
|
|
user_id: true,
|
|
username: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return serializePurchaseIntention(updated);
|
|
}
|
|
|
|
/**
|
|
* Conta intenções pendentes de um anúncio
|
|
*/
|
|
export async function getPendingIntentionsCount(
|
|
listingId: number,
|
|
): Promise<number> {
|
|
return await prisma.purchaseIntention.count({
|
|
where: {
|
|
listing_id: listingId,
|
|
status: "pending",
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Verifica se já existe uma intenção pendente para um listing+buyer
|
|
*/
|
|
export async function hasPendingIntention(
|
|
listingId: number,
|
|
buyerId: number,
|
|
): Promise<boolean> {
|
|
const count = await prisma.purchaseIntention.count({
|
|
where: {
|
|
listing_id: listingId,
|
|
buyer_id: buyerId,
|
|
status: "pending",
|
|
},
|
|
});
|
|
|
|
return count > 0;
|
|
}
|
|
|
|
/**
|
|
* Busca intenção pendente de um comprador para um listing específico
|
|
*/
|
|
export async function getPendingIntentionByBuyerAndListing(
|
|
listingId: number,
|
|
buyerId: number,
|
|
): Promise<PurchaseIntention | null> {
|
|
const intention = await prisma.purchaseIntention.findFirst({
|
|
where: {
|
|
listing_id: listingId,
|
|
buyer_id: buyerId,
|
|
status: "pending",
|
|
},
|
|
include: {
|
|
listing: {
|
|
select: {
|
|
listing_id: true,
|
|
item_name: true,
|
|
seller_id: true,
|
|
},
|
|
},
|
|
buyer: {
|
|
select: {
|
|
user_id: true,
|
|
username: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: {
|
|
created_at: "desc",
|
|
},
|
|
});
|
|
|
|
return serializePurchaseIntention(intention);
|
|
}
|
|
|
|
/**
|
|
* Atualiza o método de pagamento de uma intenção
|
|
*/
|
|
export async function updatePurchaseIntentionPaymentMethod(
|
|
intentionId: number,
|
|
newPaymentMethod: PaymentMethod,
|
|
): Promise<PurchaseIntention> {
|
|
// Buscar intenção atual
|
|
const intention = await prisma.purchaseIntention.findUnique({
|
|
where: { intention_id: intentionId },
|
|
});
|
|
|
|
if (!intention) {
|
|
throw new Error("Intenção não encontrada");
|
|
}
|
|
|
|
// Verificar se pode trocar (máximo 1 troca)
|
|
if (intention.payment_method_change_count >= 1) {
|
|
throw new Error(
|
|
"Você já atingiu o limite de trocas de método de pagamento",
|
|
);
|
|
}
|
|
|
|
// Verificar se o novo método é diferente
|
|
if (intention.payment_method === newPaymentMethod) {
|
|
throw new Error("O novo método de pagamento deve ser diferente do atual");
|
|
}
|
|
|
|
// Atualizar
|
|
const updated = await prisma.purchaseIntention.update({
|
|
where: { intention_id: intentionId },
|
|
data: {
|
|
payment_method: newPaymentMethod,
|
|
payment_method_change_count: intention.payment_method_change_count + 1,
|
|
updated_at: new Date(),
|
|
},
|
|
include: {
|
|
listing: {
|
|
select: {
|
|
listing_id: true,
|
|
item_name: true,
|
|
seller_id: true,
|
|
},
|
|
},
|
|
buyer: {
|
|
select: {
|
|
user_id: true,
|
|
username: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return serializePurchaseIntention(updated);
|
|
}
|
|
|
|
/**
|
|
* Retorna os IDs de anúncios com intenções pendentes para um comprador
|
|
*/
|
|
export async function getPendingIntentionListingIdsForBuyer(
|
|
buyerId: number,
|
|
listingIds: number[],
|
|
): Promise<number[]> {
|
|
if (listingIds.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const intentions = await prisma.purchaseIntention.findMany({
|
|
where: {
|
|
buyer_id: buyerId,
|
|
status: "pending",
|
|
listing_id: {
|
|
in: listingIds,
|
|
},
|
|
},
|
|
select: {
|
|
listing_id: true,
|
|
},
|
|
});
|
|
|
|
const uniqueIds = new Set<number>();
|
|
intentions.forEach((intention) => {
|
|
uniqueIds.add(intention.listing_id);
|
|
});
|
|
|
|
return Array.from(uniqueIds);
|
|
}
|
|
|
|
export async function getHighestCustomOfferForListing(
|
|
listingId: number,
|
|
): Promise<{ amount: number; currency: string } | null> {
|
|
const offer = await prisma.purchaseIntention.findFirst({
|
|
where: {
|
|
listing_id: listingId,
|
|
status: { in: ["pending", "accepted"] },
|
|
custom_offer_amount: { not: null },
|
|
custom_offer_currency: { not: null },
|
|
},
|
|
orderBy: { custom_offer_amount: "desc" },
|
|
select: {
|
|
custom_offer_amount: true,
|
|
custom_offer_currency: true,
|
|
},
|
|
});
|
|
|
|
if (!offer) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
amount: Number(offer.custom_offer_amount),
|
|
currency: offer.custom_offer_currency!,
|
|
};
|
|
}
|
|
|
|
export async function getHighestCustomOffersForListings(
|
|
listingIds: number[],
|
|
): Promise<Map<number, { amount: number; currency: string }>> {
|
|
if (listingIds.length === 0) {
|
|
return new Map();
|
|
}
|
|
|
|
const offers = await prisma.purchaseIntention.findMany({
|
|
where: {
|
|
listing_id: { in: listingIds },
|
|
status: { in: ["pending", "accepted"] },
|
|
custom_offer_amount: { not: null },
|
|
custom_offer_currency: { not: null },
|
|
},
|
|
select: {
|
|
listing_id: true,
|
|
custom_offer_amount: true,
|
|
custom_offer_currency: true,
|
|
},
|
|
orderBy: [{ listing_id: "asc" }, { custom_offer_amount: "desc" }],
|
|
});
|
|
|
|
const result = new Map<number, { amount: number; currency: string }>();
|
|
for (const offer of offers) {
|
|
if (!result.has(offer.listing_id)) {
|
|
result.set(offer.listing_id, {
|
|
amount: Number(offer.custom_offer_amount),
|
|
currency: offer.custom_offer_currency!,
|
|
});
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|