infoalloggi-monorepo/apps/infoalloggi/src/server/controllers/servizio.controller.ts

1754 lines
43 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import { TRPCError } from "@trpc/server";
import { add } from "date-fns";
2025-08-28 18:27:07 +02:00
import { sql } from "kysely";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
import { z } from "zod/v4";
2025-08-28 18:27:07 +02:00
import type { PurchaseData } from "~/components/acquisto_receipt";
import { env } from "~/env";
import type { AnnunciId } from "~/schemas/public/Annunci";
2025-08-28 18:27:07 +02:00
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
2025-08-04 17:45:44 +02:00
import type {
2025-08-28 18:27:07 +02:00
Payments,
PaymentsId,
PaymentsUpdate,
2025-08-04 17:45:44 +02:00
} from "~/schemas/public/Payments";
import type {
2025-08-28 18:27:07 +02:00
Prezziario,
PrezziarioIdprezziario,
2025-08-04 17:45:44 +02:00
} from "~/schemas/public/Prezziario";
2025-08-28 18:27:07 +02:00
import type {
NewServizio,
ServizioServizioId,
ServizioUpdate,
} from "~/schemas/public/Servizio";
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import type { Users, UsersId } from "~/schemas/public/Users";
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
2025-08-04 17:45:44 +02:00
import type { Context } from "~/server/api/trpc";
import {
2025-08-28 18:27:07 +02:00
addEventToQueue,
canSendNotification,
nextTimeForNotification,
2025-08-04 17:45:44 +02:00
} from "~/server/controllers/event_queue.controller";
2025-08-28 18:27:07 +02:00
import { cleanupUnusedOrders } from "~/server/controllers/ordini.controller";
2025-08-04 17:45:44 +02:00
import {
2025-08-28 18:27:07 +02:00
type MinimalSMSData,
SendMessage,
2025-08-04 17:45:44 +02:00
} from "~/server/controllers/skebby.controller";
2025-08-28 18:27:07 +02:00
import { db } from "~/server/db";
import { NewMail, type NewMailProps } from "~/server/services/mailer";
import { getUser } from "~/server/services/user.service";
import type { AnnuncioRicerca } from "./annunci.controller";
2025-08-04 17:45:44 +02:00
export const addServizio = async (data: NewServizio) => {
2025-08-28 18:27:07 +02:00
try {
return await db
.insertInto("servizio")
.values(data)
.returningAll()
.executeTakeFirstOrThrow();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error adding new servizio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getServizioById = async (servizioId: ServizioServizioId) => {
2025-08-28 18:27:07 +02:00
try {
return await db
.selectFrom("servizio")
.selectAll()
.where("servizio_id", "=", servizioId)
.executeTakeFirstOrThrow();
} catch (e) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Servizio not found: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const updateServizio = async ({
2025-08-28 18:27:07 +02:00
servizioId,
data,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
data: ServizioUpdate;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
return await db
.updateTable("servizio")
.set(data)
.where("servizio_id", "=", servizioId)
.returningAll()
.executeTakeFirstOrThrow();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error updating servizio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const deleteServizio = async (servizioId: ServizioServizioId) => {
2025-08-28 18:27:07 +02:00
try {
await db
.deleteFrom("servizio")
.where("servizio_id", "=", servizioId)
.executeTakeFirstOrThrow();
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error deleting servizio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getServiziByUserId = async (userId: UsersId) => {
2025-08-28 18:27:07 +02:00
try {
return await db
.selectFrom("servizio")
.selectAll("servizio")
.select((eb) => [
jsonArrayFrom(
eb
.selectFrom("servizio_annunci")
.select(["servizio_annunci.annunci_id"])
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
.select("annunci.codice")
.whereRef(
"servizio_annunci.servizio_id",
"=",
"servizio.servizio_id",
),
).as("annunci"),
])
.where("user_id", "=", userId)
.orderBy("created_at", "asc")
.execute();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error fetching servizi by user ID: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getPreOnboardData = async (servizioId: ServizioServizioId) => {
2025-08-28 18:27:07 +02:00
try {
const servizio = await getServizioById(servizioId);
if (!servizio) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Servizio not found",
});
}
const userData = await db
.selectFrom("users")
.select([
"users.id",
"users.nome",
"users.cognome",
"users.email",
"users.telefono",
])
.where("id", "=", servizio.user_id)
.executeTakeFirstOrThrow();
if (!userData) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
const hasPrevious = await db
.selectFrom("servizio")
.select("servizio_id")
.where("user_id", "=", servizio.user_id)
.where("servizio_id", "!=", servizioId)
.where("isOkAcconto", "=", true)
.executeTakeFirst();
2025-08-29 16:18:32 +02:00
return { hasPrevious: !!hasPrevious, servizio, userData };
2025-08-28 18:27:07 +02:00
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error fetching pre-onboard data: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getDataPerAcquisto = async (servizioId: ServizioServizioId) => {
2025-08-28 18:27:07 +02:00
try {
const servizio = await getServizioById(servizioId);
if (!servizio) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Servizio not found",
});
}
const userData = await db
.selectFrom("users")
.select([
"users.id",
"users.nome",
"users.cognome",
"users.email",
"users.telefono",
])
.where("id", "=", servizio.user_id)
.executeTakeFirstOrThrow();
if (!userData) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
const anagrafica = await db
.selectFrom("users_anagrafica")
.selectAll("users_anagrafica")
.where("users_anagrafica.userid", "=", servizio.user_id)
.executeTakeFirst();
return {
2025-08-29 16:18:32 +02:00
anagrafica,
2025-08-28 18:27:07 +02:00
servizio,
userData,
};
} catch (e) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Servizio not found: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const postAcquistoDataHandler = async ({
2025-08-28 18:27:07 +02:00
servizioId,
userId,
user,
anagrafica,
servizio,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
userId: UsersId;
user: Pick<Users, "nome" | "cognome" | "email" | "telefono">;
anagrafica: UsersAnagraficaUpdate;
servizio: ServizioUpdate;
}): Promise<{ ordineIdForPayment: OrdiniOrdineId | null }> => {
2025-08-28 18:27:07 +02:00
try {
const servizioData = await db.transaction().execute(async (tx) => {
// Update user and anagrafica data
2025-08-28 18:27:07 +02:00
await tx
.updateTable("users")
.set(user)
.where("id", "=", userId)
.executeTakeFirstOrThrow();
const { idanagrafica, ...rest } = anagrafica;
if (idanagrafica) {
await tx
.updateTable("users_anagrafica")
.set({
...rest,
userid: userId,
})
.where("idanagrafica", "=", idanagrafica)
.returningAll()
.executeTakeFirstOrThrow();
} else {
await tx
.insertInto("users_anagrafica")
.values({
...rest,
userid: userId,
})
.returningAll()
.executeTakeFirstOrThrow();
}
// Update servizio data
2025-08-28 18:27:07 +02:00
return await tx
.updateTable("servizio")
.set(servizio)
.where("servizio_id", "=", servizioId)
.returningAll()
.executeTakeFirstOrThrow();
});
if (servizioData.skipPayment) {
// If skipping order, set servizio as active with decorrenza today
2025-08-28 18:27:07 +02:00
await updateServizio({
data: {
decorrenza: new Date(),
2025-08-29 16:18:32 +02:00
isOkAcconto: true,
isOkSaldo: true,
isOkConsulenza: true,
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
servizioId,
2025-08-28 18:27:07 +02:00
});
return { ordineIdForPayment: null };
2025-08-28 18:27:07 +02:00
}
//cleanup unused payments
await db
.deleteFrom("payments")
.where("servizio_id", "=", servizioId)
.where("paymentstatus", "is", null)
.execute();
if (
!servizioData ||
servizioData.isInterrotto ||
servizioData.isOkAcconto ||
servizioData.decorrenza !== null
) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Cannot create payment for this servizioData",
});
}
const packId =
servizioData.tipologia === TipologiaPosizioneEnum.Transitorio
? ("ACCONTO_BD" as PrezziarioIdprezziario)
: ("ACCONTO_CA" as PrezziarioIdprezziario);
const acconto = await db
.selectFrom("prezziario")
.select("idprezziario")
.where("isActive", "=", true)
.where("idprezziario", "=", packId)
.executeTakeFirst();
if (!acconto) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Acconto not found",
});
}
const existingOrder = await db
.selectFrom("ordini")
.select("ordini.ordine_id")
.where("ordini.servizio_id", "=", servizioData.servizio_id)
.where("ordini.packid", "=", acconto.idprezziario)
.where("ordini.type", "=", OrderTypeEnum.Acconto)
.where("ordini.isActive", "=", false)
.orderBy("ordini.created_at", "desc")
.executeTakeFirst();
if (existingOrder) {
await db
.deleteFrom("ordini")
.where("ordini.isActive", "=", false)
.where("ordine_id", "!=", existingOrder.ordine_id)
.execute();
return { ordineIdForPayment: existingOrder.ordine_id };
2025-08-28 18:27:07 +02:00
}
const order = await db
.insertInto("ordini")
.values({
packid: acconto.idprezziario,
2025-08-29 16:18:32 +02:00
servizio_id: servizioData.servizio_id,
2025-08-28 18:27:07 +02:00
type: OrderTypeEnum.Acconto,
userid: userId,
})
.returning("ordine_id")
.executeTakeFirstOrThrow();
return { ordineIdForPayment: order.ordine_id };
2025-08-28 18:27:07 +02:00
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error updating data for acquisto: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
const SaldoTransitorioMapping: Record<number, PrezziarioIdprezziario> = {
2025-08-28 18:27:07 +02:00
0: "SALDOBD_1MESE" as PrezziarioIdprezziario,
1: "SALDOBD_3MESI" as PrezziarioIdprezziario,
2: "SALDOBD_6MESI" as PrezziarioIdprezziario,
3: "SALDOBD_9MESI" as PrezziarioIdprezziario,
4: "SALDOBD_12MESI" as PrezziarioIdprezziario,
2025-08-04 17:45:44 +02:00
};
export const setupSaldoServizio = async ({
2025-08-28 18:27:07 +02:00
servizioId,
annuncioId,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
annuncioId: AnnunciId;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
return await db.transaction().execute(async (tx) => {
// Cleanup previous payments and orders
await cleanupUnusedOrders({ db: tx });
const servizio = await tx
.selectFrom("servizio")
.selectAll()
.where("servizio_id", "=", servizioId)
.executeTakeFirstOrThrow();
if (
!servizio.isOkAcconto ||
servizio.isInterrotto ||
servizio.decorrenza == null ||
servizio.isOkSaldo
) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Servizio not found",
});
}
const annuncio = await tx
.selectFrom("servizio_annunci")
.selectAll()
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.executeTakeFirstOrThrow();
if (
annuncio.user_confirmed_at == null ||
annuncio.accettato_conferma_at == null
) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Annuncio not found",
});
}
let saldo:
| Pick<
Prezziario,
"idprezziario" | "prezzo_cent" | "nome_it" | "nome_en" | "sconto"
>
| undefined;
const qry = tx
.selectFrom("prezziario")
.select(["idprezziario", "prezzo_cent", "nome_it", "nome_en", "sconto"])
.where("isActive", "=", true);
if (servizio.tipologia === TipologiaPosizioneEnum.Transitorio) {
const mpp =
servizio.permanenza && SaldoTransitorioMapping[servizio.permanenza];
if (!servizio.permanenza || !mpp) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Permanenza not set for transitorio servizio",
});
}
saldo = await qry.where("idprezziario", "=", mpp).executeTakeFirst();
} else if (servizio.tipologia === TipologiaPosizioneEnum.Stabile) {
if (servizio.budget >= 500) {
saldo = await qry
.where("idprezziario", "=", "SALDOCA500+" as PrezziarioIdprezziario)
.executeTakeFirst();
} else {
saldo = await qry
.where("idprezziario", "=", "SALDOCA500" as PrezziarioIdprezziario)
.executeTakeFirst();
}
}
if (!saldo) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Acconto not found",
});
}
await tx
.deleteFrom("payments")
.where("servizio_id", "=", servizioId)
.where("paymentstatus", "is", null)
.execute();
const order = await db
.insertInto("ordini")
.values({
packid: saldo.idprezziario,
2025-08-29 16:18:32 +02:00
servizio_id: servizio.servizio_id,
2025-08-28 18:27:07 +02:00
type: OrderTypeEnum.Saldo,
userid: servizio.user_id,
})
.returning("ordine_id")
.executeTakeFirstOrThrow();
return order.ordine_id;
});
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error setting up saldo servizio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
const ConsulenzaPriceMapping = {
2025-08-28 18:27:07 +02:00
"4 + 4": "CONS_4+4" as PrezziarioIdprezziario,
"30 Giorni": "CONS_30GG" as PrezziarioIdprezziario,
2025-08-29 16:18:32 +02:00
Agevolato: "CONS_3+2" as PrezziarioIdprezziario,
2025-08-28 18:27:07 +02:00
Commerciale: "CONS_COMM" as PrezziarioIdprezziario,
2025-08-29 16:18:32 +02:00
Foresteria: "CONS_COMM" as PrezziarioIdprezziario,
"Transi.Age": "CONS_3+2" as PrezziarioIdprezziario,
Transitorio: "CONS_TRAN" as PrezziarioIdprezziario,
2025-08-04 17:45:44 +02:00
};
type TipoEnum = keyof typeof ConsulenzaPriceMapping;
export const setupSaldoConsulenzaServizio = async ({
2025-08-28 18:27:07 +02:00
servizioId,
annuncioId,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
annuncioId: AnnunciId;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
return await db.transaction().execute(async (tx) => {
// Cleanup previous payments and orders
await cleanupUnusedOrders({ db: tx });
const servizio = await tx
.selectFrom("servizio")
.selectAll()
.where("servizio_id", "=", servizioId)
.executeTakeFirstOrThrow();
if (
!servizio.isOkAcconto ||
servizio.isInterrotto ||
servizio.decorrenza == null ||
!servizio.isOkSaldo ||
servizio.isOkConsulenza
) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Non serve saldo per questo servizio",
});
}
const annuncio = await tx
.selectFrom("servizio_annunci")
.selectAll()
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.executeTakeFirstOrThrow();
if (
annuncio.user_confirmed_at == null ||
annuncio.accettato_conferma_at == null ||
!annuncio.contratto_tipo
) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Annuncio not found or contratto not set",
});
}
if (
!Object.keys(ConsulenzaPriceMapping).includes(annuncio.contratto_tipo)
) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Consulenza type not found",
});
}
const pack = await tx
.selectFrom("prezziario")
.select(["idprezziario", "prezzo_cent", "nome_it", "nome_en", "sconto"])
.where("isActive", "=", true)
.where(
"idprezziario",
"=",
ConsulenzaPriceMapping[annuncio.contratto_tipo as TipoEnum],
)
.executeTakeFirst();
if (!pack) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Pack not found",
});
}
await tx
.deleteFrom("payments")
.where("servizio_id", "=", servizioId)
.where("paymentstatus", "is", null)
.execute();
const order = await db
.insertInto("ordini")
.values({
packid: pack.idprezziario,
2025-08-29 16:18:32 +02:00
servizio_id: servizio.servizio_id,
2025-08-28 18:27:07 +02:00
type: OrderTypeEnum.Consulenza,
userid: servizio.user_id,
})
.returning("ordine_id")
.executeTakeFirstOrThrow();
return order.ordine_id;
});
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error setting up consulenza servizio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export type ServizioData = Awaited<
2025-08-28 18:27:07 +02:00
ReturnType<typeof getAllServizioAnnunci>
2025-08-04 17:45:44 +02:00
>[number];
export const getAllServizioAnnunci = async (userId: UsersId) => {
2025-08-28 18:27:07 +02:00
try {
return await db
.selectFrom("servizio")
.where("user_id", "=", userId)
.leftJoin(
"users_anagrafica",
"servizio.user_id",
"users_anagrafica.userid",
)
2025-08-28 18:27:07 +02:00
.select((eb) => [
"servizio.servizio_id",
"servizio.created_at",
"servizio.decorrenza",
"servizio.tipologia",
"servizio.budget",
"servizio.permanenza",
"servizio.isInterrotto",
"servizio.isOkAcconto",
"servizio.isOkSaldo",
"servizio.isOkConsulenza",
"servizio.skipPayment",
"servizio.skipControlloDoc",
"servizio.skipDocMotivazione",
jsonObjectFrom(
eb
.selectFrom("users_storage")
.select([
"users_storage.storageId as storageId",
"users_anagrafica.doc_personale_fronte_ref as ref",
])
.whereRef(
"users_storage.user_storage_id",
"=",
"users_anagrafica.doc_personale_fronte_ref",
),
).as("doc_personale_fronte"),
jsonObjectFrom(
eb
.selectFrom("users_storage")
.select([
"users_storage.storageId as storageId",
"users_anagrafica.doc_personale_retro_ref as ref",
])
.whereRef(
"users_storage.user_storage_id",
"=",
"users_anagrafica.doc_personale_retro_ref",
),
).as("doc_personale_retro"),
jsonObjectFrom(
eb
.selectFrom("users_storage")
.select([
"users_storage.storageId as storageId",
"servizio.doc_motivazione_ref as ref",
])
.whereRef(
"users_storage.user_storage_id",
"=",
"servizio.doc_motivazione_ref",
),
).as("doc_motivazione"),
2025-08-28 18:27:07 +02:00
jsonArrayFrom(
eb
.selectFrom("servizio_annunci")
.selectAll("servizio_annunci")
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
.select((eb) => [
2025-08-28 18:27:07 +02:00
"annunci.id",
"annunci.codice",
"annunci.prezzo",
"annunci.desc_en",
"annunci.desc_it",
"annunci.titolo_en",
"annunci.titolo_it",
"annunci.tipo",
"annunci.consegna",
"annunci.stato",
"annunci.web",
"annunci.media_updated_at",
jsonArrayFrom(
eb
.selectFrom("images_refs")
.select(["images_refs.img", "images_refs.thumb"])
.whereRef("images_refs.codice", "=", "annunci.codice")
.orderBy("images_refs.ordine", "asc"),
).as("images"),
2025-08-28 18:27:07 +02:00
])
//ignora annunci che non sono disponibili
.where("annunci.web", "=", true)
.where("annunci.stato", "!=", "Sospeso")
// .orderBy("servizio_annunci.user_confirmed_at", (ob) =>
// ob.asc().nullsLast(),
// )
// .orderBy("servizio_annunci.open_contatti_at", (ob) =>
// ob.asc().nullsFirst(),
// )
2025-08-28 18:27:07 +02:00
.orderBy("servizio_annunci.created_at", "asc")
.whereRef(
"servizio_annunci.servizio_id",
"=",
"servizio.servizio_id",
),
).as("annunci"),
])
.orderBy("servizio.created_at", "desc")
.execute();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error fetching servizio annunci: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const addServizioAnnunci = async ({
2025-08-28 18:27:07 +02:00
servizioId,
annunci,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
annunci: AnnunciId[];
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
await db
.insertInto("servizio_annunci")
.values(
annunci.map((annuncio, idx) => ({
annunci_id: annuncio,
created_at: add(new Date(), {
//trick per assicurare che la data sia sempre diversa
seconds: idx,
}),
2025-08-29 16:18:32 +02:00
servizio_id: servizioId,
2025-08-28 18:27:07 +02:00
})),
)
.onConflict((oc) => oc.columns(["servizio_id", "annunci_id"]).doNothing())
.execute();
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error adding servizio to annuncio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getServizioAnnuncio = async ({
2025-08-28 18:27:07 +02:00
servizioId,
annuncioId,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
annuncioId: AnnunciId;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
return await db
.selectFrom("servizio_annunci")
.selectAll()
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.executeTakeFirstOrThrow();
} catch (e) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Servizio not found: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const updateServizioAnnuncio = async ({
2025-08-28 18:27:07 +02:00
servizioId,
annuncioId,
data,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
annuncioId: AnnunciId;
data: ServizioAnnunciUpdate;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
await db
.updateTable("servizio_annunci")
.set(data)
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.execute();
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error updating servizio from annuncio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getAnnunciAvailableToAdd = async (
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId,
2025-08-04 17:45:44 +02:00
) => {
2025-08-28 18:27:07 +02:00
try {
return await db.transaction().execute(async (tx) => {
const servizioAnnunci = await tx
.selectFrom("servizio_annunci")
.select("annunci_id")
.where("servizio_id", "=", servizioId)
.execute();
const ids = servizioAnnunci.map((item) => item.annunci_id);
let qry = tx
.selectFrom("annunci")
.select(["id", "codice"])
.where("annunci.web", "=", true)
.where("stato", "!=", "Sospeso");
if (ids.length > 0) {
qry = qry.where("id", "not in", ids);
}
return await qry.execute();
});
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error fetching available annunci: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const deleteServizioAnnuncio = async (
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId,
annuncioId: AnnunciId,
2025-08-04 17:45:44 +02:00
) => {
2025-08-28 18:27:07 +02:00
try {
await db
.deleteFrom("servizio_annunci")
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.execute();
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error deleting servizio from annuncio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const SbloccaContatti = async ({
2025-08-28 18:27:07 +02:00
servizioId,
annuncioId,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
annuncioId: AnnunciId;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
const alreadyUnlocked = await db
.selectFrom("servizio_annunci")
.where("servizio_id", "=", servizioId)
.where("open_contatti_at", "is not", null)
.select((eb) => [eb.fn.count<number>("annunci_id").as("count")])
.executeTakeFirst();
if (alreadyUnlocked && alreadyUnlocked.count >= 10) {
return false;
}
const data = await db.transaction().execute(async (tx) => {
await tx
.updateTable("servizio_annunci")
.set({
open_contatti_at: new Date(),
})
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.execute();
const servizio = await tx
.selectFrom("servizio")
.select("user_id")
.where("servizio_id", "=", servizioId)
.executeTakeFirstOrThrow(() => new Error("Servizio not found"));
const user = await tx
.selectFrom("users")
.select(["id", "username", "email", "telefono"])
.where("id", "=", servizio.user_id)
.executeTakeFirstOrThrow(() => new Error("User not found"));
const anagrafica = await tx
.selectFrom("users_anagrafica")
.select("sesso")
.where("userid", "=", servizio.user_id)
.executeTakeFirstOrThrow(() => new Error("Anagrafica not found"));
const annuncio = await tx
.selectFrom("annunci")
.where("id", "=", annuncioId)
.select([
"codice",
"indirizzo",
"email",
"numero",
"locatore",
"civico",
"comune",
])
.executeTakeFirstOrThrow(() => new Error("Annuncio not found"));
return {
anagrafica,
annuncio,
annuncioId,
2025-08-29 16:18:32 +02:00
servizioId,
user,
2025-08-28 18:27:07 +02:00
};
});
const EmailProprietario: NewMailProps | null = data.annuncio.email
? {
mailType: "emailContattoInteressato",
props: {
indirizzo: data.annuncio.indirizzo,
nome_cognome: data.annuncio.locatore || "",
nome_cognome_utente: data.user.username,
sesso: data.anagrafica.sesso,
2025-08-29 16:18:32 +02:00
telefono: data.user.telefono,
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
subject: "Un interessato ha ricevuto i Suoi contatti",
to: data.annuncio.email,
2025-08-28 18:27:07 +02:00
}
: null;
const EmailInteressato: NewMailProps = {
mailType: "generic",
props: {
link: {
href: `${env.BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`,
2025-08-28 18:27:07 +02:00
label: "Vai al servizio",
},
2025-08-29 16:18:32 +02:00
noreply: true,
testo: `I contatti per l'annuncio ${data.annuncio.codice} sono stati sbloccati con successo.`,
title: "Contatti sbloccati per il servizio di ricerca affitto",
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
subject: "Contatti sbloccati per il servizio di ricerca affitto",
to: data.user.email,
2025-08-28 18:27:07 +02:00
};
const numeroProprietario = data.annuncio.numero
? data.annuncio.numero.replace(/\s+/g, "").split(",")[0] || null
: null;
const SmsProprietario: MinimalSMSData | null = numeroProprietario
? {
message: `${data.anagrafica.sesso ? (data.anagrafica.sesso === "M" ? "Il Sig." : "La Sig.ra") : "Il/La Sig./Sig.ra"} ${data.user.username}, con numero ${data.user.telefono} ha sbloccato i contatti per l'annuncio in ${data.annuncio.indirizzo} ${data.annuncio.civico} a ${data.annuncio.comune}.`,
2025-08-29 16:18:32 +02:00
recipient: [numeroProprietario],
2025-08-28 18:27:07 +02:00
}
: null;
await NewMail(EmailInteressato);
if (canSendNotification()) {
if (EmailProprietario) {
await NewMail(EmailProprietario);
}
if (SmsProprietario) {
await SendMessage(SmsProprietario);
}
} else {
const lock_expires = nextTimeForNotification();
if (EmailProprietario) {
await addEventToQueue({
data: {
data: EmailProprietario,
2025-08-29 16:18:32 +02:00
tipo: "email",
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
lock_expires,
2025-08-28 18:27:07 +02:00
});
}
if (SmsProprietario) {
await addEventToQueue({
data: {
data: SmsProprietario,
2025-08-29 16:18:32 +02:00
tipo: "sms",
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
lock_expires,
2025-08-28 18:27:07 +02:00
});
}
}
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error unlocking contatti: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const interruzioneServizio = async (servizioId: ServizioServizioId) => {
2025-08-28 18:27:07 +02:00
try {
const servizio = await getServizioById(servizioId);
if (!servizio) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Servizio not found",
});
}
if (servizio.isInterrotto) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Servizio already interrupted",
});
}
if (servizio.decorrenza == null) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Servizio decorrenza not set",
});
}
const isWithinRipensamento =
new Date() <=
add(servizio.decorrenza, {
days: 14,
});
if (!isWithinRipensamento) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Periodo di ripensamento scaduto",
});
}
await db
.updateTable("servizio")
.set({
isInterrotto: true,
})
.where("servizio_id", "=", servizioId)
.executeTakeFirstOrThrow();
// NOTIFICHE EMAIL INTERRUZIONE SERVIZIO
const utente = await getUser({ db, userId: servizio.user_id });
await NewMail({
mailType: "generic",
props: {
noreply: true,
testo: `Abbiamo ricevuto la tua richiesta di interruzione del servizio di ricerca affitto per la tipologia ${servizio.tipologia}. Il servizio è stato interrotto con successo.`,
2025-08-29 16:18:32 +02:00
title: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`,
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
subject: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`,
to: utente.email,
userId: utente.id,
2025-08-28 18:27:07 +02:00
});
await NewMail({
mailType: "generic",
props: {
link: {
href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}`,
2025-08-28 18:27:07 +02:00
label: "Vai al servizio",
},
2025-08-29 16:18:32 +02:00
noreply: true,
testo: `L'utente ha richiesto l'interruzione del servizio di ricerca affitto per la tipologia ${servizio.tipologia}. Puoi procedere con le prossime azioni.`,
title: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`,
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
subject: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`,
to: "web@infoalloggi.it",
2025-08-28 18:27:07 +02:00
});
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error interruzione servizio: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const InteractionLogicTipologia = async ({
2025-08-28 18:27:07 +02:00
tipologia,
userId,
annuncioId,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
tipologia: string;
userId: UsersId;
annuncioId: AnnunciId;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
const parse = z.custom<TipologiaPosizioneEnum>();
const parsedTipologia = parse.safeParse(tipologia);
if (!parsedTipologia.success) {
return { status: "invalid" as const };
}
const servizi = await db
.selectFrom("servizio")
.select(["tipologia", "servizio_id"])
.where("tipologia", "=", parsedTipologia.data)
.where("isOkAcconto", "=", true)
.where("isInterrotto", "=", false)
.where("decorrenza", "is not", null)
.where("user_id", "=", userId)
.select((eb) => [
jsonObjectFrom(
eb
.selectFrom("servizio_annunci")
.select("annunci_id")
.where("servizio_annunci.annunci_id", "=", annuncioId)
.whereRef("servizio.servizio_id", "=", "servizio_annunci.servizio_id")
.limit(1),
).as("alreadySaved"),
])
.executeTakeFirst();
if (!servizi) {
return { status: "not_found" as const };
}
if (servizi.alreadySaved) {
return {
servizioId: servizi.servizio_id,
2025-08-29 16:18:32 +02:00
status: "already_saved" as const,
2025-08-28 18:27:07 +02:00
};
}
return {
servizioId: servizi.servizio_id,
2025-08-29 16:18:32 +02:00
status: "found" as const,
2025-08-28 18:27:07 +02:00
tipologia: servizi.tipologia,
};
2025-08-04 17:45:44 +02:00
};
export const sendServizioEmail = async ({
2025-08-28 18:27:07 +02:00
servizioId,
userId,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
userId: UsersId;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
const user = await db
.selectFrom("users")
.select(["email", "nome"])
.where("id", "=", userId)
.executeTakeFirstOrThrow();
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
const annunci = await db
.selectFrom("servizio_annunci")
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
.select((eb) => [
2025-08-28 18:27:07 +02:00
"annunci.titolo_it",
"annunci.codice",
"annunci.prezzo",
jsonArrayFrom(
eb
.selectFrom("images_refs")
.select(["images_refs.img", "images_refs.thumb"])
.whereRef("images_refs.codice", "=", "annunci.codice")
.orderBy("images_refs.ordine", "asc"),
).as("images"),
2025-08-28 18:27:07 +02:00
])
.where("servizio_annunci.servizio_id", "=", servizioId)
.execute();
const { email, nome } = user;
await NewMail({
mailType: "onboardingServizio",
props: {
annunci: annunci.map((annuncio) => ({
2025-08-29 16:18:32 +02:00
codice: annuncio.codice,
immagine: annuncio?.images
? annuncio.images[0]?.img || "fallback"
: "fallback",
2025-08-28 18:27:07 +02:00
prezzo: annuncio.prezzo,
2025-08-29 16:18:32 +02:00
titolo: annuncio.titolo_it,
2025-08-28 18:27:07 +02:00
})),
2025-08-29 16:18:32 +02:00
nome,
token: servizioId,
2025-08-28 18:27:07 +02:00
},
subject: "Procedi ora su Infoalloggi.it",
2025-08-29 16:18:32 +02:00
to: email,
userId,
2025-08-28 18:27:07 +02:00
});
await db
.updateTable("servizio")
.set({
isSent: true,
})
.where("servizio_id", "=", servizioId)
.executeTakeFirstOrThrow();
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error sending email: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const userSendConfermaIntent = async ({
2025-08-28 18:27:07 +02:00
servizioId,
annuncioId,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
servizioId: ServizioServizioId;
annuncioId: AnnunciId;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
const servizio = await getServizioById(servizioId);
if (
!servizio ||
servizio.isInterrotto ||
!servizio.isOkAcconto ||
servizio.decorrenza == null
) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Servizio not found",
});
}
await db
.updateTable("servizio_annunci")
.set({
user_confirmed_at: new Date(),
})
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.executeTakeFirstOrThrow();
// NOTIFICHE EMAIL CONFERMA IMMOBILE
const utente = await getUser({ db, userId: servizio.user_id });
const annuncio_codice = await db
.selectFrom("annunci")
.select("codice")
.where("id", "=", annuncioId)
.executeTakeFirstOrThrow();
await NewMail({
mailType: "generic",
props: {
link: {
href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`,
2025-08-28 18:27:07 +02:00
label: "Visualizza Annuncio",
},
2025-08-29 16:18:32 +02:00
noreply: true,
testo: `L'utente ${utente.username} ha confermato l'immobile con codice ${annuncio_codice.codice}.`,
title: `Nuova conferma immobile ${annuncio_codice.codice}`,
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
subject: `Nuova conferma immobile ${annuncio_codice.codice}`,
to: "web@infoalloggi.it",
2025-08-28 18:27:07 +02:00
});
await NewMail({
mailType: "generic",
props: {
noreply: true,
testo: `La tua conferma per l'immobile con codice ${annuncio_codice.codice} è stata ricevuta. Il nostro team la esaminerà a breve. Riceverai una notifica via email per procedere.`,
2025-08-29 16:18:32 +02:00
title: `Conferma immobile ${annuncio_codice.codice} ricevuta`,
2025-08-28 18:27:07 +02:00
},
2025-08-29 16:18:32 +02:00
subject: `Conferma immobile ${annuncio_codice.codice} ricevuta`,
to: utente.email,
userId: utente.id,
2025-08-28 18:27:07 +02:00
});
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error confirming immobile: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const adminUpdateConfermaStatus = async ({
servizioId,
annuncioId,
status,
}: {
servizioId: ServizioServizioId;
annuncioId: AnnunciId;
status: boolean;
}) => {
try {
await db
.updateTable("servizio_annunci")
.set({
hasConfermaAdmin: status,
})
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.execute();
const servizio = await getServizioById(servizioId);
const utente = await getUser({ db, userId: servizio.user_id });
//ADMIN CONFERMA LAVORATA EMAIL
if (status) {
await NewMail({
mailType: "generic",
props: {
link: {
href: `${env.BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`,
label: "Vai al servizio",
},
noreply: true,
testo: `La conferma è stata lavorata con successo. Puoi procedere con le prossime azioni.`,
title: "Conferma lavorata per il servizio di ricerca affitto",
},
subject: "Conferma lavorata per il servizio di ricerca affitto",
to: utente.email,
userId: utente.id,
});
}
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error updating conferma status from annuncio: ${(e as Error).message}`,
});
}
};
export const userAccettaConferma = async ({
servizioId,
annuncioId,
}: {
servizioId: ServizioServizioId;
annuncioId: AnnunciId;
}) => {
try {
await db
.updateTable("servizio_annunci")
.set({
accettato_conferma_at: new Date(),
})
.where("servizio_id", "=", servizioId)
.where("annunci_id", "=", annuncioId)
.execute();
const servizio = await getServizioById(servizioId);
const utente = await getUser({ db, userId: servizio.user_id });
// USER ACCETTA CONFERMA EMAIL
await NewMail({
mailType: "generic",
props: {
link: {
href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`,
label: "Vai al servizio",
},
noreply: true,
testo: `L'utente ha accettato le condizioni di conferma per la locazione. Puoi procedere con le prossime azioni.`,
title: `Utente ${utente.username} ha accettato le condizioni di conferma`,
},
subject: `Utente ${utente.username} ha accettato le condizioni di conferma`,
to: "web@infoalloggi.it",
});
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error accepting conferma from annuncio: ${(e as Error).message}`,
});
}
};
2025-08-04 17:45:44 +02:00
export const isOrdineAwaitingPayment = async (ordineId: OrdiniOrdineId) => {
2025-08-28 18:27:07 +02:00
try {
const ordine = await db
.selectFrom("ordini")
.select("ordine_id")
.where("ordine_id", "=", ordineId)
.where("isActive", "=", false)
.executeTakeFirst();
if (!ordine) {
console.error("Ordine not found:", ordineId);
return false;
}
const payment = await db
.selectFrom("payments")
.select("id")
.where("ordine_id", "=", ordineId)
.where("paymentstatus", "=", PaymentStatusEnum.success)
.executeTakeFirst();
if (!payment) {
return true;
}
console.error("Payment already exists for ordine:", ordineId);
return false;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error checking ordine payment status: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getServizioPagamentoData = async (ordineId: OrdiniOrdineId) => {
2025-08-28 18:27:07 +02:00
try {
await db
.deleteFrom("payments")
.where("ordine_id", "=", ordineId)
.where("paymentstatus", "is", null)
.execute();
const ordine = await db
.selectFrom("ordini")
.selectAll()
.where("ordine_id", "=", ordineId)
.executeTakeFirstOrThrow();
const pack = await db
.selectFrom("prezziario")
.selectAll()
.where("idprezziario", "=", ordine.packid)
.executeTakeFirstOrThrow();
const payment = await db
.insertInto("payments")
.values({
amount_cent: pack.prezzo_cent,
2025-08-29 16:18:32 +02:00
ordine_id: ordineId,
2025-08-28 18:27:07 +02:00
paymentname: pack.nome_it,
2025-08-29 16:18:32 +02:00
servizio_id: ordine.servizio_id,
2025-08-28 18:27:07 +02:00
userid: ordine.userid,
})
.returningAll()
.executeTakeFirstOrThrow();
const servizio = await db
.selectFrom("servizio")
.selectAll()
.where("servizio.servizio_id", "=", payment.servizio_id)
.executeTakeFirstOrThrow();
if (!servizio) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Servizio not found",
});
}
return {
ordine,
payment,
2025-08-29 16:18:32 +02:00
servizio,
2025-08-28 18:27:07 +02:00
};
} catch (e) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Servizio not found: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
type Pagamento = Payments & Pick<Users, "username">;
export const getAllPagamenti = async (): Promise<Pagamento[]> => {
2025-08-28 18:27:07 +02:00
try {
return await db
.selectFrom("payments")
.selectAll()
.innerJoin("users", "users.id", "payments.userid")
.select("users.username")
.orderBy("payments.created_at", "desc")
.execute();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error fetching user pagamenti: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const updatePagamento = async ({
2025-08-28 18:27:07 +02:00
paymentId,
data,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
paymentId: PaymentsId;
data: PaymentsUpdate;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
return await db
.updateTable("payments")
.set(data)
.where("id", "=", paymentId)
.returningAll()
.executeTakeFirstOrThrow();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error updating pagamento: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const removePagamento = async (paymentId: PaymentsId) => {
2025-08-28 18:27:07 +02:00
try {
await db.deleteFrom("payments").where("id", "=", paymentId).execute();
return true;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error removing pagamento: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getUserPagamenti = async (
2025-08-28 18:27:07 +02:00
userId: UsersId,
2025-08-04 17:45:44 +02:00
): Promise<Pagamento[]> => {
2025-08-28 18:27:07 +02:00
try {
return await db
.selectFrom("payments")
.selectAll()
.select([sql.lit("").as("username")])
.where("payments.userid", "=", userId)
.orderBy("payments.created_at", "desc")
.execute();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error fetching user pagamenti: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const SendContactEmail = async ({
2025-08-28 18:27:07 +02:00
codice,
nome,
email,
telefono,
messaggio,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
codice: string;
nome: string;
email: string;
telefono: string;
messaggio: string;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
await NewMail({
mailType: "contattoAnnuncioEmail",
props: {
codice,
2025-08-29 16:18:32 +02:00
email,
2025-08-28 18:27:07 +02:00
messaggio,
2025-08-29 16:18:32 +02:00
nome,
telefono,
2025-08-28 18:27:07 +02:00
},
subject: "Contatto per annuncio",
2025-08-29 16:18:32 +02:00
to: "web@infoalloggi.it",
2025-08-28 18:27:07 +02:00
});
return {
email,
2025-08-29 16:18:32 +02:00
nome,
2025-08-28 18:27:07 +02:00
telefono,
};
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error sending email: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getCompatibileAnnunci = async ({
servizioId,
adminOverride = false,
}: {
servizioId: ServizioServizioId;
adminOverride?: boolean;
}): Promise<AnnuncioRicerca[]> => {
2025-08-28 18:27:07 +02:00
try {
const servizio = await getServizioById(servizioId);
if (!servizio) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Servizio not found",
});
}
let qry = db
.selectFrom("annunci")
.distinctOn("annunci.id")
.select((eb) => [
"id",
"codice",
"comune",
"provincia",
"mq",
"prezzo",
"desc_en",
"desc_it",
"titolo_en",
"titolo_it",
"tipo",
"consegna",
"stato",
"web",
"media_updated_at",
"homepage",
"numero_camere",
"url_video",
"modificato_il",
jsonArrayFrom(
eb
.selectFrom("images_refs")
.select(["images_refs.img", "images_refs.thumb"])
.whereRef("images_refs.codice", "=", "annunci.codice")
.orderBy("images_refs.ordine", "asc"),
).as("images"),
2025-08-28 18:27:07 +02:00
])
.where("web", "=", true)
.where("stato", "!=", "Sospeso")
.where("tipo", "=", servizio.tipologia)
.where("id", "not in", (eb) =>
eb
.selectFrom("servizio_annunci")
.select("annunci_id")
.where("servizio_id", "=", servizioId),
);
if (adminOverride) {
return await qry.execute();
}
qry = qry.where("prezzo", "<=", servizio.budget * 1e2 * 1.2); // Allow 20% margin
2025-08-28 18:27:07 +02:00
const month = new Date().getMonth() + 1; // Months are 0-indexed in JS
if (servizio.entromese === month) {
qry = qry.where((eb) =>
eb.or([
eb("consegna", "=", 0),
eb("consegna", "=", servizio.entromese),
]),
);
} else if (servizio.entromese === 0) {
qry = qry.where((eb) =>
eb.or([eb("consegna", "=", 0), eb("consegna", "=", month)]),
);
} else {
qry = qry.where("consegna", "=", servizio.entromese);
}
return await qry.execute();
2025-08-28 18:27:07 +02:00
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error fetching compatibile annunci: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};
export const getOrdineRicevutaData = async ({
2025-08-28 18:27:07 +02:00
ordineId,
ctx,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
ordineId: OrdiniOrdineId;
ctx: Context;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
try {
const data = await db
.selectFrom("payments")
.select([
"payments.id",
"payments.userid",
"payments.amount_cent",
"payments.paymentname",
"payments.created_at",
"payments.paymentmethod",
])
.select((eb) => [
jsonObjectFrom(
eb
.selectFrom("users")
.whereRef("users.id", "=", "payments.userid")
.select(["users.nome", "users.cognome"])
.limit(1),
).as("user"),
jsonObjectFrom(
eb
.selectFrom("users_anagrafica")
.whereRef("users_anagrafica.userid", "=", "payments.userid")
.select([
"users_anagrafica.via_residenza",
"users_anagrafica.civico_residenza",
"users_anagrafica.comune_residenza",
"users_anagrafica.provincia_residenza",
"users_anagrafica.cap_residenza",
"users_anagrafica.nazione_residenza",
])
.limit(1),
).as("anagrafica"),
])
.where("payments.ordine_id", "=", ordineId)
.where("payments.paymentstatus", "=", PaymentStatusEnum.success)
.executeTakeFirstOrThrow();
if (!ctx.session) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "User not authenticated",
});
}
if (!ctx.session.isAdmin && data.userid !== ctx.session.id) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You do not have permission to access this ordine",
});
}
if (!data || !data.user || !data.anagrafica) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Ordine not found",
});
}
return {
clienteIndirizzo: `${data.anagrafica.via_residenza} ${data.anagrafica.civico_residenza}\n${data.anagrafica.comune_residenza} (${data.anagrafica.provincia_residenza}) ${data.anagrafica.cap_residenza}\n${data.anagrafica.nazione_residenza}`,
2025-08-29 16:18:32 +02:00
clienteNome: `${data.user.nome} ${data.user.cognome}`,
date: data.created_at,
2025-08-28 18:27:07 +02:00
items: [
{
description: data.paymentname,
discount: 0, // Assuming no discount for simplicity
2025-08-29 16:18:32 +02:00
price: data.amount_cent / 100, // Convert cents to euros
2025-08-28 18:27:07 +02:00
},
],
2025-08-29 16:18:32 +02:00
paymentId: data.id,
paymentMethod: data.paymentmethod,
2025-08-28 18:27:07 +02:00
} as PurchaseData;
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error fetching ordine ricevuta data: ${(e as Error).message}`,
});
}
2025-08-04 17:45:44 +02:00
};