- Updated RichiesteTable to display decorrenza with tooltip based on status. - Enhanced status display with badges indicating various states (e.g., Attesa attivazione, Interrotto). - Added Popover for displaying Annunci details in RichiesteTable. - Refactored API to fetch user interests and related announcements. - Introduced Queue component to display email events for users. - Updated translations to include new "Requests" section in admin navigation. - Improved database queries for fetching user interests and announcements.
1547 lines
40 KiB
TypeScript
1547 lines
40 KiB
TypeScript
import { TRPCError } from "@trpc/server";
|
|
import { add, isBefore } from "date-fns";
|
|
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
|
|
import { z } from "zod/v4";
|
|
import type { PurchaseData } from "~/components/acquisto_receipt";
|
|
import { env } from "~/env";
|
|
import type { PaymentType } from "~/i18n/stripe";
|
|
import { parseDBComune, parseDBNazione } from "~/lib/catasto";
|
|
import { withParsedDates } from "~/lib/dateParserJsonKysely";
|
|
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
|
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
|
import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini";
|
|
import type { Prezziario } from "~/schemas/public/Prezziario";
|
|
import type {
|
|
Servizio,
|
|
ServizioServizioId,
|
|
ServizioUpdate,
|
|
} from "~/schemas/public/Servizio";
|
|
import type { ServizioAnnunci } from "~/schemas/public/ServizioAnnunci";
|
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
|
import type TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
|
import type { Users, UsersId } from "~/schemas/public/Users";
|
|
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
|
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
|
import {
|
|
addEventToQueue,
|
|
canSendNotification,
|
|
nextTimeForNotification,
|
|
} from "~/server/controllers/event_queue.controller";
|
|
import {
|
|
type MinimalSMSData,
|
|
SendMessage,
|
|
} from "~/server/controllers/skebby.controller";
|
|
import { db } from "~/server/db";
|
|
import { NewMail, type NewMailProps } from "~/server/services/mailer";
|
|
import { getUser } from "~/server/services/user.service";
|
|
import {
|
|
parseNullableDateString,
|
|
withImages,
|
|
withVideos,
|
|
} from "~/utils/kysely-helper";
|
|
import { GetUserInterests } from "../services/interests.service";
|
|
import { createOrdine } from "../services/ordini.service";
|
|
import { genPdfCondizioniBase64 } from "../services/puppeteer.service";
|
|
import { getServizioById } from "../services/servizio.service";
|
|
import type { AnnuncioRicerca } from "./annunci.controller";
|
|
import { getComuni, getNazioni } from "./catasto.controller";
|
|
import { SaldoSolver } from "./pagamenti.controller";
|
|
|
|
export const getServiziByUserId = async (userId: UsersId) => {
|
|
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}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const getDataPerAcquisto = async (servizioId: ServizioServizioId) => {
|
|
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(
|
|
() => new Error(`User not found - userId: ${servizio.user_id}`),
|
|
);
|
|
|
|
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 {
|
|
anagrafica,
|
|
servizio,
|
|
userData,
|
|
};
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: `Servizio not found: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const processServizioOnboard = async ({
|
|
servizioId,
|
|
userId,
|
|
user,
|
|
anagrafica,
|
|
servizio,
|
|
}: {
|
|
servizioId: ServizioServizioId;
|
|
userId: UsersId;
|
|
user: Pick<Users, "nome" | "cognome" | "email" | "telefono" | "username">;
|
|
anagrafica: UsersAnagraficaUpdate;
|
|
servizio: ServizioUpdate;
|
|
}) => {
|
|
try {
|
|
await db.transaction().execute(async (tx) => {
|
|
// Update user and anagrafica data
|
|
await tx
|
|
.updateTable("users")
|
|
.set(user)
|
|
.where("id", "=", userId)
|
|
.execute();
|
|
|
|
const { idanagrafica, ...rest } = anagrafica;
|
|
if (idanagrafica) {
|
|
await tx
|
|
.updateTable("users_anagrafica")
|
|
.set({
|
|
...rest,
|
|
userid: userId,
|
|
})
|
|
.where("idanagrafica", "=", idanagrafica)
|
|
.returningAll()
|
|
.executeTakeFirstOrThrow(
|
|
() =>
|
|
new Error(
|
|
`Failed to update users_anagrafica - idanagrafica: ${idanagrafica}`,
|
|
),
|
|
);
|
|
} else {
|
|
await tx
|
|
.insertInto("users_anagrafica")
|
|
.values({
|
|
...rest,
|
|
userid: userId,
|
|
})
|
|
.returningAll()
|
|
.executeTakeFirstOrThrow(
|
|
() => new Error("Failed to insert new users_anagrafica"),
|
|
);
|
|
}
|
|
// Update servizio data
|
|
return await tx
|
|
.updateTable("servizio")
|
|
.set({ ...servizio, onboardOk: true })
|
|
.where("servizio_id", "=", servizioId)
|
|
.returningAll()
|
|
.executeTakeFirstOrThrow(
|
|
() =>
|
|
new Error(`Failed to update servizio - servizioId: ${servizioId}`),
|
|
);
|
|
});
|
|
|
|
return true;
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error updating data for acquisto: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Attiva il servizio senza pagamento,\
|
|
* usato se un acconto già attivo è presente per il servizio,\
|
|
* invio delle condizioni contrattuali in allegato.
|
|
*/
|
|
export const attivaServizio_SaltaPagamentoAcconto = async (
|
|
ordineId: OrdiniOrdineId,
|
|
) => {
|
|
try {
|
|
return await db.transaction().execute(async (tx) => {
|
|
const ordine = await tx
|
|
.selectFrom("ordini")
|
|
.innerJoin("users", "users.id", "ordini.userid")
|
|
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
|
.select([
|
|
"ordini.ordine_id",
|
|
"ordini.servizio_id",
|
|
"ordini.created_at",
|
|
"prezziario.testo_condizioni",
|
|
"users.email",
|
|
"users.id as userId",
|
|
])
|
|
.where("ordine_id", "=", ordineId)
|
|
.executeTakeFirstOrThrow(
|
|
() => new Error(`Ordine not found - ordineId: ${ordineId}`),
|
|
);
|
|
|
|
const servizio = await tx
|
|
.updateTable("servizio")
|
|
.set({
|
|
decorrenza: new Date(),
|
|
isOkAcconto: true,
|
|
})
|
|
.where("servizio_id", "=", ordine.servizio_id)
|
|
.returning(["interruzioneDays"])
|
|
.executeTakeFirstOrThrow(
|
|
() =>
|
|
new Error(
|
|
`Failed to update servizio - servizioId: ${ordine.servizio_id}`,
|
|
),
|
|
);
|
|
|
|
await NewMail({
|
|
template: {
|
|
mailType: "servizioAttivato",
|
|
},
|
|
mail: {
|
|
subject: "Pagamento Confermato - Acconto Infoalloggi.it",
|
|
to: ordine.email,
|
|
attachments: ordine.testo_condizioni
|
|
? [
|
|
{
|
|
filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`,
|
|
|
|
content: await genPdfCondizioniBase64(
|
|
ordine.testo_condizioni as TestiEStringheStingaId,
|
|
),
|
|
|
|
encoding: "base64",
|
|
contentType: "application/pdf",
|
|
},
|
|
]
|
|
: [],
|
|
},
|
|
userId: ordine.userId,
|
|
});
|
|
await addEventToQueue({
|
|
data: {
|
|
tipo: "email",
|
|
data: {
|
|
template: {
|
|
mailType: "avvisoInterruzione",
|
|
},
|
|
mail: {
|
|
subject: "Avviso Interruzione",
|
|
to: ordine.email,
|
|
},
|
|
userId: ordine.userId,
|
|
},
|
|
},
|
|
lock_expires: add(new Date(), { days: servizio.interruzioneDays - 5 }),
|
|
});
|
|
return true;
|
|
});
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error activating servizio without payment: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const AdminAttivaServizio_Ufficio = async (
|
|
servizioId: ServizioServizioId,
|
|
) => {
|
|
try {
|
|
return await db.transaction().execute(async (tx) => {
|
|
const data = await tx
|
|
.selectFrom("servizio")
|
|
.select(["servizio.user_id", "servizio.interruzioneDays"])
|
|
.innerJoin("users", "users.id", "servizio.user_id")
|
|
.select("users.email")
|
|
.where("servizio_id", "=", servizioId)
|
|
.executeTakeFirstOrThrow(
|
|
() => new Error(`Servizio not found - servizioId: ${servizioId}`),
|
|
);
|
|
const today = new Date();
|
|
await tx
|
|
.updateTable("servizio")
|
|
.set({
|
|
decorrenza: today,
|
|
isOkAcconto: true,
|
|
onboardOk: true,
|
|
})
|
|
.where("servizio_id", "=", servizioId)
|
|
.execute();
|
|
|
|
await addEventToQueue({
|
|
data: {
|
|
tipo: "email",
|
|
data: {
|
|
template: {
|
|
mailType: "avvisoInterruzione",
|
|
},
|
|
mail: {
|
|
subject: "Avviso Interruzione",
|
|
to: data.email,
|
|
},
|
|
userId: data.user_id,
|
|
},
|
|
},
|
|
lock_expires: add(today, { days: data.interruzioneDays - 5 }), //avviso 5 giorni prima della scadenza del periodo di ripensamento
|
|
});
|
|
return true;
|
|
});
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error activating servizio from office: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
type DocRef = {
|
|
storageId: string;
|
|
ref: UsersStorageUserStorageId | null;
|
|
};
|
|
|
|
export type ServizioAnnuncioData = ServizioAnnunci &
|
|
Pick<
|
|
Annunci,
|
|
| "id"
|
|
| "codice"
|
|
| "comune"
|
|
| "provincia"
|
|
| "prezzo"
|
|
| "consegna"
|
|
| "numero_camere"
|
|
| "mq"
|
|
| "tipo"
|
|
| "titolo_it"
|
|
| "titolo_en"
|
|
| "desc_en"
|
|
| "desc_it"
|
|
| "modificato_il"
|
|
| "stato"
|
|
| "external_videos"
|
|
| "media_updated_at"
|
|
| "homepage"
|
|
| "web"
|
|
| "disponibile_da"
|
|
| "persone"
|
|
| "permanenza"
|
|
> & {
|
|
images: {
|
|
img: string;
|
|
thumb: string;
|
|
}[];
|
|
videos: {
|
|
thumb: string;
|
|
video: string;
|
|
}[];
|
|
};
|
|
export type ServizioData = Servizio & {
|
|
doc_personale_fronte: DocRef | null;
|
|
doc_personale_retro: DocRef | null;
|
|
doc_motivazione: DocRef | null;
|
|
annunci: ServizioAnnuncioData[];
|
|
ordini: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
|
};
|
|
|
|
export const getAllServizioAnnunci = async (
|
|
userId: UsersId,
|
|
): Promise<ServizioData[]> => {
|
|
try {
|
|
const data = await withParsedDates(
|
|
db
|
|
.selectFrom("servizio")
|
|
.where("user_id", "=", userId)
|
|
.leftJoin(
|
|
"users_anagrafica",
|
|
"servizio.user_id",
|
|
"users_anagrafica.userid",
|
|
)
|
|
.selectAll("servizio")
|
|
.select((eb) => [
|
|
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"),
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("servizio_annunci")
|
|
.selectAll("servizio_annunci")
|
|
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
|
.select((_eb) => [
|
|
"annunci.id",
|
|
"annunci.codice",
|
|
"annunci.comune",
|
|
"annunci.provincia",
|
|
"annunci.prezzo",
|
|
"annunci.consegna",
|
|
"annunci.numero_camere",
|
|
"annunci.mq",
|
|
"annunci.tipo",
|
|
"annunci.titolo_it",
|
|
"annunci.titolo_en",
|
|
"annunci.desc_en",
|
|
"annunci.desc_it",
|
|
"annunci.modificato_il",
|
|
"annunci.stato",
|
|
"annunci.external_videos",
|
|
"annunci.media_updated_at",
|
|
"annunci.homepage",
|
|
"annunci.web",
|
|
"annunci.disponibile_da",
|
|
"annunci.persone",
|
|
"annunci.permanenza",
|
|
withImages(),
|
|
withVideos(),
|
|
])
|
|
//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().nullsLast(),
|
|
)
|
|
.orderBy("servizio_annunci.created_at", "asc")
|
|
.whereRef(
|
|
"servizio_annunci.servizio_id",
|
|
"=",
|
|
"servizio.servizio_id",
|
|
),
|
|
).as("annunci"),
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("ordini")
|
|
.selectAll("ordini")
|
|
.innerJoin(
|
|
"prezziario",
|
|
"prezziario.idprezziario",
|
|
"ordini.packid",
|
|
)
|
|
.select("prezziario.testo_condizioni")
|
|
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
|
.orderBy("ordini.created_at", "desc"),
|
|
).as("ordini"),
|
|
])
|
|
.orderBy("servizio.created_at", "desc"),
|
|
[
|
|
"annunci.modificato_il",
|
|
"annunci.created_at",
|
|
"annunci.open_contatti_at",
|
|
"annunci.user_confirmed_at",
|
|
"annunci.accettato_conferma_at",
|
|
"annunci.contratto_decorrenza",
|
|
"annunci.contratto_scadenza",
|
|
"annunci.disponibile_da",
|
|
"annunci.media_updated_at",
|
|
"ordini.created_at",
|
|
"ordini.paid_at",
|
|
],
|
|
).execute();
|
|
|
|
return data;
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error fetching servizio annunci: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const getServizioDataById = async (
|
|
servizioId: ServizioServizioId,
|
|
): Promise<ServizioData> => {
|
|
try {
|
|
const data = await db
|
|
.selectFrom("servizio")
|
|
.leftJoin(
|
|
"users_anagrafica",
|
|
"servizio.user_id",
|
|
"users_anagrafica.userid",
|
|
)
|
|
.selectAll("servizio")
|
|
.select((eb) => [
|
|
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"),
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("servizio_annunci")
|
|
.selectAll("servizio_annunci")
|
|
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
|
.select((_eb) => [
|
|
"annunci.id",
|
|
"annunci.codice",
|
|
"annunci.comune",
|
|
"annunci.provincia",
|
|
"annunci.prezzo",
|
|
"annunci.consegna",
|
|
"annunci.numero_camere",
|
|
"annunci.mq",
|
|
"annunci.tipo",
|
|
"annunci.titolo_it",
|
|
"annunci.titolo_en",
|
|
"annunci.desc_en",
|
|
"annunci.desc_it",
|
|
"annunci.modificato_il",
|
|
"annunci.stato",
|
|
"annunci.external_videos",
|
|
"annunci.media_updated_at",
|
|
"annunci.homepage",
|
|
"annunci.web",
|
|
"annunci.disponibile_da",
|
|
"annunci.persone",
|
|
"annunci.permanenza",
|
|
withImages(),
|
|
withVideos(),
|
|
])
|
|
//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().nullsLast(),
|
|
)
|
|
.orderBy("servizio_annunci.created_at", "asc")
|
|
.whereRef(
|
|
"servizio_annunci.servizio_id",
|
|
"=",
|
|
"servizio.servizio_id",
|
|
),
|
|
).as("annunci"),
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("ordini")
|
|
.selectAll("ordini")
|
|
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
|
.select("prezziario.testo_condizioni")
|
|
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
|
.orderBy("ordini.created_at", "desc"),
|
|
).as("ordini"),
|
|
])
|
|
.orderBy("servizio.created_at", "desc")
|
|
.where("servizio.servizio_id", "=", servizioId)
|
|
.executeTakeFirst();
|
|
if (!data) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Servizio not found",
|
|
});
|
|
}
|
|
const formattedData = {
|
|
...data,
|
|
annunci: data.annunci.map((annuncio) => ({
|
|
...annuncio,
|
|
created_at: new Date(annuncio.created_at),
|
|
open_contatti_at: parseNullableDateString(annuncio.open_contatti_at),
|
|
user_confirmed_at: parseNullableDateString(annuncio.user_confirmed_at),
|
|
accettato_conferma_at: parseNullableDateString(
|
|
annuncio.accettato_conferma_at,
|
|
),
|
|
contratto_decorrenza: parseNullableDateString(
|
|
annuncio.contratto_decorrenza,
|
|
),
|
|
contratto_scadenza: parseNullableDateString(
|
|
annuncio.contratto_scadenza,
|
|
),
|
|
modificato_il: parseNullableDateString(annuncio.modificato_il),
|
|
disponibile_da: parseNullableDateString(annuncio.disponibile_da),
|
|
media_updated_at: parseNullableDateString(annuncio.media_updated_at),
|
|
})),
|
|
ordini: data.ordini.map((ordine) => ({
|
|
...ordine,
|
|
created_at: new Date(ordine.created_at),
|
|
paid_at: ordine.paid_at ? new Date(ordine.paid_at) : null,
|
|
})),
|
|
};
|
|
return formattedData;
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: `Servizio annunci not found: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const addServizioAnnunci = async ({
|
|
servizioId,
|
|
annunci,
|
|
}: {
|
|
servizioId: ServizioServizioId;
|
|
annunci: AnnunciId[];
|
|
}) => {
|
|
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,
|
|
}),
|
|
servizio_id: servizioId,
|
|
})),
|
|
)
|
|
.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}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const getAnnunciAvailableToAdd = async (
|
|
servizioId: ServizioServizioId,
|
|
) => {
|
|
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", "titolo_it"])
|
|
.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}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const SbloccaContatti = async ({
|
|
servizioId,
|
|
annuncioId,
|
|
}: {
|
|
servizioId: ServizioServizioId;
|
|
annuncioId: AnnunciId;
|
|
}) => {
|
|
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",
|
|
"tipo_locatore",
|
|
"civico",
|
|
"comune",
|
|
])
|
|
.executeTakeFirstOrThrow(() => new Error("Annuncio not found"));
|
|
|
|
return {
|
|
anagrafica,
|
|
annuncio,
|
|
annuncioId,
|
|
servizioId,
|
|
user,
|
|
};
|
|
});
|
|
|
|
const EmailProprietario: NewMailProps | null = data.annuncio.email
|
|
? {
|
|
template: {
|
|
mailType: "emailContattoInteressato",
|
|
props: {
|
|
indirizzo: data.annuncio.indirizzo,
|
|
nome_cognome: `${data.annuncio.locatore}${data.annuncio?.tipo_locatore ? ` (${data.annuncio.tipo_locatore})` : ""}`,
|
|
nome_cognome_utente: data.user.username,
|
|
sesso: data.anagrafica.sesso,
|
|
telefono: data.user.telefono,
|
|
},
|
|
},
|
|
mail: {
|
|
subject: "Un interessato ha ricevuto i Suoi contatti",
|
|
to: data.annuncio.email,
|
|
},
|
|
}
|
|
: null;
|
|
|
|
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}.`,
|
|
recipient: [numeroProprietario],
|
|
}
|
|
: null;
|
|
|
|
if (canSendNotification()) {
|
|
if (EmailProprietario) {
|
|
await NewMail(EmailProprietario);
|
|
}
|
|
if (SmsProprietario) {
|
|
await SendMessage(SmsProprietario);
|
|
}
|
|
} else {
|
|
const lock_expires = nextTimeForNotification();
|
|
if (EmailProprietario) {
|
|
await addEventToQueue({
|
|
data: {
|
|
data: EmailProprietario,
|
|
tipo: "email",
|
|
},
|
|
lock_expires,
|
|
});
|
|
}
|
|
if (SmsProprietario) {
|
|
await addEventToQueue({
|
|
data: {
|
|
data: SmsProprietario,
|
|
tipo: "sms",
|
|
},
|
|
lock_expires,
|
|
});
|
|
}
|
|
}
|
|
return true;
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error unlocking contatti: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const interruzioneServizio = async (servizioId: ServizioServizioId) => {
|
|
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 = isBefore(
|
|
new Date(),
|
|
add(servizio.decorrenza, {
|
|
days: servizio.interruzioneDays,
|
|
}),
|
|
);
|
|
|
|
if (!isWithinRipensamento) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Periodo di ripensamento scaduto",
|
|
});
|
|
}
|
|
|
|
await db
|
|
.updateTable("servizio")
|
|
.set({
|
|
isInterrotto: true,
|
|
})
|
|
.where("servizio_id", "=", servizioId)
|
|
.execute();
|
|
|
|
// NOTIFICHE EMAIL INTERRUZIONE SERVIZIO
|
|
const utente = await getUser({ db, userId: servizio.user_id });
|
|
await NewMail({
|
|
template: {
|
|
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.`,
|
|
title: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`,
|
|
},
|
|
},
|
|
mail: {
|
|
subject: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`,
|
|
to: utente.email,
|
|
},
|
|
userId: utente.id,
|
|
});
|
|
|
|
await NewMail({
|
|
template: {
|
|
mailType: "generic",
|
|
props: {
|
|
link: {
|
|
href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}`,
|
|
label: "Vai al servizio",
|
|
},
|
|
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`,
|
|
},
|
|
},
|
|
mail: {
|
|
subject: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`,
|
|
to: "web@infoalloggi.it",
|
|
},
|
|
});
|
|
|
|
return true;
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error interruzione servizio: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const InteractionLogicTipologia = async ({
|
|
tipologia,
|
|
userId,
|
|
annuncioId,
|
|
}: {
|
|
tipologia: string;
|
|
userId: UsersId;
|
|
annuncioId: AnnunciId;
|
|
}) => {
|
|
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,
|
|
status: "already_saved" as const,
|
|
};
|
|
}
|
|
return {
|
|
servizioId: servizi.servizio_id,
|
|
status: "found" as const,
|
|
tipologia: servizi.tipologia,
|
|
};
|
|
};
|
|
|
|
export const userSendConfermaIntent = async ({
|
|
servizioId,
|
|
annuncioId,
|
|
}: {
|
|
servizioId: ServizioServizioId;
|
|
annuncioId: AnnunciId;
|
|
}) => {
|
|
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)
|
|
.execute();
|
|
|
|
// 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(
|
|
() =>
|
|
new Error(
|
|
`Annuncio not found - servizioId: ${servizioId}, annunciId: ${annuncioId}`,
|
|
),
|
|
);
|
|
|
|
await NewMail({
|
|
template: {
|
|
mailType: "generic",
|
|
props: {
|
|
link: {
|
|
href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`,
|
|
label: "Visualizza Annuncio",
|
|
},
|
|
noreply: true,
|
|
testo: `L'utente ${utente.username} ha notificato interesse per l'immobile con codice ${annuncio_codice.codice}.`,
|
|
title: `Nuovo interesse conferma immobile ${annuncio_codice.codice}`,
|
|
},
|
|
},
|
|
mail: {
|
|
subject: `Nuovo interesse conferma immobile ${annuncio_codice.codice}`,
|
|
to: "web@infoalloggi.it",
|
|
},
|
|
});
|
|
|
|
await NewMail({
|
|
template: {
|
|
mailType: "generic",
|
|
props: {
|
|
noreply: true,
|
|
testo: `La tua notifica di interesse per l'immobile con codice ${annuncio_codice.codice} è stata inviata con successo. Contatteremo i proprietari per raccogliere le informazioni necessarie. Riceverai una notifica via email per procedere.`,
|
|
title: `Notifica interesse immobile ${annuncio_codice.codice} inviata con successo`,
|
|
},
|
|
},
|
|
mail: {
|
|
subject: `Notifica interesse immobile ${annuncio_codice.codice} inviata con successo`,
|
|
to: utente.email,
|
|
},
|
|
userId: utente.id,
|
|
});
|
|
return true;
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error confirming immobile: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
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 });
|
|
|
|
const saldo = await SaldoSolver({
|
|
tipologia: servizio.tipologia,
|
|
permanenza: servizio.permanenza,
|
|
budget: servizio.budget,
|
|
});
|
|
if (saldo.success) {
|
|
await createOrdine({
|
|
servizio_id: servizioId,
|
|
type: OrderTypeEnum.Saldo,
|
|
amount_cent: saldo.pack.prezzo_cent,
|
|
packid: saldo.pack.idprezziario,
|
|
userid: servizio.user_id,
|
|
});
|
|
} else {
|
|
console.error(
|
|
`Errore nel calcolo del saldo per il servizio ${servizioId}: ${saldo.message}, procedura invio conferma`,
|
|
);
|
|
}
|
|
|
|
//ADMIN CONFERMA LAVORATA EMAIL
|
|
if (status) {
|
|
await NewMail({
|
|
template: {
|
|
mailType: "generic",
|
|
props: {
|
|
link: {
|
|
href: `${env.BASE_URL}/area-riservata/servizio/${servizioId}`,
|
|
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",
|
|
},
|
|
},
|
|
mail: {
|
|
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({
|
|
template: {
|
|
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`,
|
|
},
|
|
},
|
|
mail: {
|
|
subject: `Utente ${utente.username} ha accettato le condizioni di conferma`,
|
|
to: "web@infoalloggi.it",
|
|
},
|
|
});
|
|
|
|
return { success: true, userId: utente.id };
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error accepting conferma from annuncio: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const UtenteInteressatoAdAnnuncioEmail = async ({
|
|
codice,
|
|
nome,
|
|
email,
|
|
telefono,
|
|
messaggio,
|
|
}: {
|
|
codice: string;
|
|
nome: string;
|
|
email: string;
|
|
telefono: string;
|
|
messaggio: string;
|
|
}) => {
|
|
try {
|
|
await NewMail({
|
|
template: {
|
|
mailType: "contattoAnnuncioEmail",
|
|
props: {
|
|
codice,
|
|
email,
|
|
messaggio,
|
|
nome,
|
|
telefono,
|
|
},
|
|
},
|
|
mail: {
|
|
subject: "Contatto per annuncio",
|
|
to: "web@infoalloggi.it",
|
|
},
|
|
});
|
|
return {
|
|
email,
|
|
nome,
|
|
telefono,
|
|
};
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error sending email: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const getCompatibileAnnunci = async ({
|
|
servizioId,
|
|
adminOverride = false,
|
|
}: {
|
|
servizioId: ServizioServizioId;
|
|
adminOverride?: boolean;
|
|
}): Promise<(AnnuncioRicerca & { alreadyRequested: boolean })[]> => {
|
|
try {
|
|
const servizio = await getServizioById(servizioId);
|
|
if (!servizio) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Servizio not found",
|
|
});
|
|
}
|
|
|
|
const interests = await GetUserInterests(servizio.user_id);
|
|
|
|
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",
|
|
"external_videos",
|
|
"modificato_il",
|
|
withImages(),
|
|
withVideos(),
|
|
eb
|
|
.case()
|
|
.when("id", "in", interests)
|
|
.then(true)
|
|
.else(false)
|
|
.end()
|
|
.as("alreadyRequested"),
|
|
])
|
|
.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
|
|
const { entromese } = servizio;
|
|
const month = new Date().getMonth() + 1; // Months are 0-indexed in JS
|
|
if (entromese !== null) {
|
|
if (entromese === month) {
|
|
qry = qry.where((eb) =>
|
|
eb.or([
|
|
eb("consegna", "=", 0),
|
|
eb("consegna", "=", entromese),
|
|
eb("consegna", "=", entromese + 1),
|
|
]),
|
|
);
|
|
} else if (entromese === 0) {
|
|
qry = qry.where((eb) =>
|
|
eb.or([
|
|
eb("consegna", "=", 0),
|
|
eb("consegna", "=", month),
|
|
eb("consegna", "=", month + 1),
|
|
]),
|
|
);
|
|
} else {
|
|
qry = qry.where((eb) =>
|
|
eb.or([
|
|
eb("consegna", "=", entromese),
|
|
eb("consegna", "=", entromese + 1),
|
|
]),
|
|
);
|
|
}
|
|
}
|
|
|
|
return await qry.execute();
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error fetching compatibile annunci: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const getOrdineRicevutaData = async ({
|
|
ordineId,
|
|
}: {
|
|
ordineId: OrdiniOrdineId;
|
|
}): Promise<PurchaseData> => {
|
|
try {
|
|
const data = await db
|
|
.selectFrom("ordini")
|
|
.select([
|
|
"ordini.ordine_id",
|
|
"ordini.userid",
|
|
"ordini.amount_cent",
|
|
"ordini.sconto",
|
|
"ordini.created_at",
|
|
"ordini.paymentmethod",
|
|
])
|
|
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
|
.select("prezziario.nome_it")
|
|
.where("ordini.ordine_id", "=", ordineId)
|
|
//.where("ordini.paymentstatus", "=", PaymentStatusEnum.success)
|
|
|
|
.executeTakeFirstOrThrow(
|
|
() => new Error(`Ordine not found - ordineId: ${ordineId}`),
|
|
);
|
|
|
|
const anagrafica = await db
|
|
.selectFrom("users")
|
|
.select("users.username")
|
|
.innerJoin("users_anagrafica", "users_anagrafica.userid", "users.id")
|
|
.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",
|
|
"users_anagrafica.codice_fiscale",
|
|
])
|
|
.where("users.id", "=", data.userid)
|
|
.executeTakeFirstOrThrow(
|
|
() => new Error(`Anagrafica not found - userid: ${data.userid}`),
|
|
);
|
|
const comuni = await getComuni();
|
|
const nazioni = await getNazioni();
|
|
|
|
const parsedComune = parseDBComune(anagrafica.comune_residenza, comuni);
|
|
const parsedNazione = parseDBNazione(anagrafica.nazione_residenza, nazioni);
|
|
|
|
return {
|
|
clienteIndirizzo: `${anagrafica.via_residenza} ${anagrafica.civico_residenza}\n${parsedComune?.nome} (${anagrafica.provincia_residenza}) ${anagrafica.cap_residenza}\n${parsedNazione?.nome}`,
|
|
clienteNome: anagrafica.username,
|
|
codiceFiscale: anagrafica.codice_fiscale,
|
|
date: data.created_at,
|
|
|
|
packName: data.nome_it,
|
|
sconto: data.sconto,
|
|
amount_cent: data.amount_cent,
|
|
|
|
ordineId: data.ordine_id,
|
|
paymentMethod: (data.paymentmethod || "manual") as PaymentType,
|
|
};
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error fetching ordine ricevuta data: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|
|
export type RichiesteData = Servizio &
|
|
Pick<Users, "username"> & {
|
|
annunci_servizio: ServizioAnnuncioData[];
|
|
ordini_servizio: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
|
|
};
|
|
|
|
export const getRichieste = async (): Promise<RichiesteData[]> => {
|
|
try {
|
|
const data = await withParsedDates(
|
|
db
|
|
.selectFrom("servizio")
|
|
.innerJoin("users", "users.id", "servizio.user_id")
|
|
.selectAll("servizio")
|
|
.select(["users.username"])
|
|
.select((eb) => [
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("servizio_annunci")
|
|
.selectAll("servizio_annunci")
|
|
.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
|
|
.select((_eb) => [
|
|
"annunci.id",
|
|
"annunci.codice",
|
|
"annunci.comune",
|
|
"annunci.provincia",
|
|
"annunci.prezzo",
|
|
"annunci.consegna",
|
|
"annunci.numero_camere",
|
|
"annunci.mq",
|
|
"annunci.tipo",
|
|
"annunci.titolo_it",
|
|
"annunci.titolo_en",
|
|
"annunci.desc_en",
|
|
"annunci.desc_it",
|
|
"annunci.modificato_il",
|
|
"annunci.stato",
|
|
"annunci.external_videos",
|
|
"annunci.media_updated_at",
|
|
"annunci.homepage",
|
|
"annunci.web",
|
|
"annunci.disponibile_da",
|
|
"annunci.persone",
|
|
"annunci.permanenza",
|
|
withImages(),
|
|
withVideos(),
|
|
])
|
|
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
|
ob.asc().nullsLast(),
|
|
)
|
|
.orderBy("servizio_annunci.open_contatti_at", (ob) =>
|
|
ob.asc().nullsLast(),
|
|
)
|
|
.orderBy("servizio_annunci.created_at", "asc")
|
|
|
|
.whereRef(
|
|
"servizio.servizio_id",
|
|
"=",
|
|
"servizio_annunci.servizio_id",
|
|
)
|
|
.orderBy("servizio_annunci.created_at", "desc"),
|
|
).as("annunci_servizio"),
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("ordini")
|
|
.selectAll("ordini")
|
|
.innerJoin(
|
|
"prezziario",
|
|
"prezziario.idprezziario",
|
|
"ordini.packid",
|
|
)
|
|
.select("prezziario.testo_condizioni")
|
|
.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
|
|
.orderBy("ordini.created_at", "desc"),
|
|
).as("ordini_servizio"),
|
|
])
|
|
.orderBy("servizio.created_at", "desc"),
|
|
[
|
|
"annunci_servizio.created_at",
|
|
"annunci_servizio.open_contatti_at",
|
|
"annunci_servizio.disponibile_da",
|
|
"annunci_servizio.user_confirmed_at",
|
|
"annunci_servizio.accettato_conferma_at",
|
|
"annunci_servizio.contratto_decorrenza",
|
|
"annunci_servizio.contratto_scadenza",
|
|
"annunci_servizio.modificato_il",
|
|
"annunci_servizio.media_updated_at",
|
|
"ordini_servizio.created_at",
|
|
"ordini_servizio.paid_at",
|
|
],
|
|
).execute();
|
|
return data;
|
|
} catch (e) {
|
|
throw new TRPCError({
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
message: `Error fetching richieste: ${(e as Error).message}`,
|
|
});
|
|
}
|
|
};
|