2026-03-11 12:07:19 +01:00
import type { Override } from "TypeHelpers.type" ;
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 { jsonArrayFrom , jsonObjectFrom } from "kysely/helpers/postgres" ;
2025-08-07 14:50:40 +02:00
import { z } from "zod/v4" ;
2026-03-09 19:14:05 +01:00
import type { PurchaseData } from "~/components/acquisto_receipt" ;
2025-10-20 16:22:20 +02:00
import { env } from "~/env" ;
2026-03-09 18:14:22 +01:00
import type { PaymentType } from "~/i18n/stripe" ;
2026-03-10 15:17:25 +01:00
import { parseDBComune , parseDBNazione } from "~/lib/catasto" ;
2026-02-25 15:30:53 +01:00
import type { Annunci , AnnunciId } from "~/schemas/public/Annunci" ;
2025-08-28 18:27:07 +02:00
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum" ;
2026-03-08 01:02:57 +01:00
import type { Ordini , OrdiniOrdineId } from "~/schemas/public/Ordini" ;
import type { Prezziario } from "~/schemas/public/Prezziario" ;
2025-08-28 18:27:07 +02:00
import type {
2026-02-25 15:30:53 +01:00
Servizio ,
2025-08-28 18:27:07 +02:00
ServizioServizioId ,
ServizioUpdate ,
} from "~/schemas/public/Servizio" ;
2026-03-08 14:34:21 +01:00
import type { ServizioAnnunci } from "~/schemas/public/ServizioAnnunci" ;
2026-03-08 01:02:57 +01:00
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe" ;
2026-03-08 14:34:21 +01:00
import type TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum" ;
2025-08-28 18:27:07 +02:00
import type { Users , UsersId } from "~/schemas/public/Users" ;
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica" ;
2026-02-25 15:30:53 +01:00
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage" ;
2025-08-04 17:45:44 +02:00
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" ;
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" ;
2026-02-25 15:30:53 +01:00
import {
parseNullableDateString ,
withImages ,
withVideos ,
} from "~/utils/kysely-helper" ;
2025-12-04 17:16:49 +01:00
import { GetUserInterests } from "../services/interests.service" ;
2026-03-08 14:34:21 +01:00
import { createOrdine } from "../services/ordini.service" ;
2026-03-08 01:02:57 +01:00
import { genPdfCondizioniBase64 } from "../services/puppeteer.service" ;
2026-03-08 14:34:21 +01:00
import { getServizioById } from "../services/servizio.service" ;
2025-11-04 19:18:13 +01:00
import type { AnnuncioRicerca } from "./annunci.controller" ;
2026-03-10 15:17:25 +01:00
import { getComuni , getNazioni } from "./catasto.controller" ;
2026-03-08 14:34:21 +01:00
import { SaldoSolver } from "./pagamenti.controller" ;
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
} ;
2025-08-28 18:27:07 +02:00
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 )
2026-03-05 10:29:08 +01:00
. executeTakeFirstOrThrow (
( ) = > new Error ( ` User not found - userId: ${ servizio . user_id } ` ) ,
) ;
2025-08-28 18:27:07 +02:00
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
} ;
2026-03-08 01:02:57 +01:00
export const processServizioOnboard = 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 ;
2026-01-16 15:47:38 +01:00
user : Pick < Users , "nome" | "cognome" | "email" | "telefono" | "username" > ;
2025-08-28 18:27:07 +02:00
anagrafica : UsersAnagraficaUpdate ;
servizio : ServizioUpdate ;
2026-03-08 01:02:57 +01:00
} ) = > {
2025-08-28 18:27:07 +02:00
try {
2026-03-08 01:02:57 +01:00
await db . transaction ( ) . execute ( async ( tx ) = > {
2025-11-03 10:59:45 +01:00
// Update user and anagrafica data
2025-08-28 18:27:07 +02:00
await tx
. updateTable ( "users" )
. set ( user )
. where ( "id" , "=" , userId )
2026-03-05 10:29:08 +01:00
. execute ( ) ;
2025-08-28 18:27:07 +02:00
const { idanagrafica , . . . rest } = anagrafica ;
if ( idanagrafica ) {
await tx
. updateTable ( "users_anagrafica" )
. set ( {
. . . rest ,
userid : userId ,
} )
. where ( "idanagrafica" , "=" , idanagrafica )
. returningAll ( )
2026-03-05 10:29:08 +01:00
. executeTakeFirstOrThrow (
( ) = >
new Error (
` Failed to update users_anagrafica - idanagrafica: ${ idanagrafica } ` ,
) ,
) ;
2025-08-28 18:27:07 +02:00
} else {
await tx
. insertInto ( "users_anagrafica" )
. values ( {
. . . rest ,
userid : userId ,
} )
. returningAll ( )
2026-03-05 10:29:08 +01:00
. executeTakeFirstOrThrow (
( ) = > new Error ( "Failed to insert new users_anagrafica" ) ,
) ;
2025-08-28 18:27:07 +02:00
}
2025-11-03 10:59:45 +01:00
// Update servizio data
2025-08-28 18:27:07 +02:00
return await tx
. updateTable ( "servizio" )
2026-03-08 01:02:57 +01:00
. set ( { . . . servizio , onboardOk : true } )
2025-08-28 18:27:07 +02:00
. where ( "servizio_id" , "=" , servizioId )
. returningAll ( )
2026-03-05 10:29:08 +01:00
. executeTakeFirstOrThrow (
( ) = >
new Error ( ` Failed to update servizio - servizioId: ${ servizioId } ` ) ,
) ;
2025-08-28 18:27:07 +02:00
} ) ;
2026-03-08 01:02:57 +01:00
return true ;
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
} ;
2026-03-08 01:02:57 +01:00
/ * *
* 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 ,
) = > {
2025-08-28 18:27:07 +02:00
try {
return await db . transaction ( ) . execute ( async ( tx ) = > {
2026-03-08 01:02:57 +01:00
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 )
2026-03-05 10:29:08 +01:00
. executeTakeFirstOrThrow (
2026-03-08 01:02:57 +01:00
( ) = > new Error ( ` Ordine not found - ordineId: ${ ordineId } ` ) ,
2026-03-05 10:29:08 +01:00
) ;
2025-08-28 18:27:07 +02:00
await tx
2026-03-08 01:02:57 +01:00
. updateTable ( "servizio" )
. set ( {
decorrenza : new Date ( ) ,
isOkAcconto : true ,
2025-08-28 18:27:07 +02:00
} )
2026-03-08 01:02:57 +01:00
. where ( "servizio_id" , "=" , ordine . servizio_id )
2025-08-28 18:27:07 +02:00
. execute ( ) ;
2026-03-08 01:02:57 +01:00
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 ,
} ) ;
return true ;
2025-08-28 18:27:07 +02:00
} ) ;
} catch ( e ) {
throw new TRPCError ( {
code : "INTERNAL_SERVER_ERROR" ,
2026-03-08 01:02:57 +01:00
message : ` Error activating servizio without payment: ${ ( e as Error ) . message } ` ,
2025-08-28 18:27:07 +02:00
} ) ;
}
2025-08-04 17:45:44 +02:00
} ;
2025-11-17 19:01:21 +01:00
2026-02-25 15:30:53 +01:00
type DocRef = {
storageId : string ;
ref : UsersStorageUserStorageId | null ;
} ;
2026-03-06 18:23:35 +01:00
export type ServizioData = Servizio & {
2026-02-25 15:30:53 +01:00
doc_personale_fronte : DocRef | null ;
doc_personale_retro : DocRef | null ;
doc_motivazione : DocRef | null ;
annunci : ( 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 ;
} [ ] ;
} ) [ ] ;
2026-03-08 01:02:57 +01:00
ordini : ( Ordini & Pick < Prezziario , "testo_condizioni" > ) [ ] ;
2026-02-25 15:30:53 +01:00
} ;
export const getAllServizioAnnunci = async (
userId : UsersId ,
) : Promise < ServizioData [ ] > = > {
2025-08-28 18:27:07 +02:00
try {
2026-02-25 15:30:53 +01:00
const data = await db
2025-08-28 18:27:07 +02:00
. selectFrom ( "servizio" )
. where ( "user_id" , "=" , userId )
2025-11-10 16:27:15 +01:00
. leftJoin (
"users_anagrafica" ,
"servizio.user_id" ,
"users_anagrafica.userid" ,
)
2026-03-06 18:23:35 +01:00
. selectAll ( "servizio" )
2025-08-28 18:27:07 +02:00
. select ( ( eb ) = > [
2025-10-29 09:39:06 +01:00
jsonObjectFrom (
eb
. selectFrom ( "users_storage" )
. select ( [
"users_storage.storageId as storageId" ,
2025-11-10 16:27:15 +01:00
"users_anagrafica.doc_personale_fronte_ref as ref" ,
2025-10-29 09:39:06 +01:00
] )
. whereRef (
"users_storage.user_storage_id" ,
"=" ,
2025-11-10 16:27:15 +01:00
"users_anagrafica.doc_personale_fronte_ref" ,
2025-10-29 09:39:06 +01:00
) ,
) . as ( "doc_personale_fronte" ) ,
jsonObjectFrom (
eb
. selectFrom ( "users_storage" )
. select ( [
"users_storage.storageId as storageId" ,
2025-11-10 16:27:15 +01:00
"users_anagrafica.doc_personale_retro_ref as ref" ,
2025-10-29 09:39:06 +01:00
] )
. whereRef (
"users_storage.user_storage_id" ,
"=" ,
2025-11-10 16:27:15 +01:00
"users_anagrafica.doc_personale_retro_ref" ,
2025-10-29 09:39:06 +01:00
) ,
) . 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" )
2025-11-18 15:20:14 +01:00
. select ( ( _eb ) = > [
2025-08-28 18:27:07 +02:00
"annunci.id" ,
"annunci.codice" ,
2025-11-17 19:01:21 +01:00
"annunci.comune" ,
"annunci.provincia" ,
2025-08-28 18:27:07 +02:00
"annunci.prezzo" ,
2025-11-17 19:01:21 +01:00
"annunci.consegna" ,
"annunci.numero_camere" ,
"annunci.mq" ,
"annunci.tipo" ,
"annunci.titolo_it" ,
"annunci.titolo_en" ,
2025-08-28 18:27:07 +02:00
"annunci.desc_en" ,
"annunci.desc_it" ,
2025-11-17 19:01:21 +01:00
"annunci.modificato_il" ,
2025-08-28 18:27:07 +02:00
"annunci.stato" ,
2025-11-18 15:20:14 +01:00
"annunci.external_videos" ,
2025-11-06 13:17:20 +01:00
"annunci.media_updated_at" ,
2025-11-17 19:01:21 +01:00
"annunci.homepage" ,
"annunci.web" ,
2025-12-16 16:43:42 +01:00
"annunci.disponibile_da" ,
"annunci.persone" ,
"annunci.permanenza" ,
2025-11-18 15:20:14 +01:00
withImages ( ) ,
withVideos ( ) ,
2025-08-28 18:27:07 +02:00
] )
//ignora annunci che non sono disponibili
2026-03-04 19:12:01 +01:00
//.where("annunci.web", "=", true)
//.where("annunci.stato", "!=", "Sospeso")
2025-12-17 14:14:30 +01:00
. orderBy ( "servizio_annunci.user_confirmed_at" , ( ob ) = >
ob . asc ( ) . nullsLast ( ) ,
)
. orderBy ( "servizio_annunci.open_contatti_at" , ( ob ) = >
2026-01-16 15:47:38 +01:00
ob . asc ( ) . nullsLast ( ) ,
2025-12-17 14:14:30 +01:00
)
2025-08-28 18:27:07 +02:00
. orderBy ( "servizio_annunci.created_at" , "asc" )
. whereRef (
"servizio_annunci.servizio_id" ,
"=" ,
"servizio.servizio_id" ,
) ,
) . as ( "annunci" ) ,
2026-03-08 01:02:57 +01:00
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" ) ,
2025-08-28 18:27:07 +02:00
] )
. orderBy ( "servizio.created_at" , "desc" )
. execute ( ) ;
2026-02-25 15:30:53 +01:00
const formattedData = data . map ( ( item ) = > {
return {
. . . item ,
annunci : item.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 ) ,
} ) ) ,
2026-03-08 01:02:57 +01:00
ordini : item.ordini.map ( ( ordine ) = > ( {
. . . ordine ,
created_at : new Date ( ordine . created_at ) ,
paid_at : ordine.paid_at ? new Date ( ordine . paid_at ) : null ,
} ) ) ,
2026-02-25 15:30:53 +01:00
} ;
} ) ;
return formattedData ;
2025-08-28 18:27:07 +02:00
} 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
} ;
2026-02-25 15:30:53 +01:00
export const getServizioDataById = async (
servizioId : ServizioServizioId ,
) : Promise < ServizioData > = > {
try {
const data = await db
. selectFrom ( "servizio" )
. leftJoin (
"users_anagrafica" ,
"servizio.user_id" ,
"users_anagrafica.userid" ,
)
2026-03-06 18:23:35 +01:00
. selectAll ( "servizio" )
2026-02-25 15:30:53 +01:00
. 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
2026-03-04 19:12:01 +01:00
//.where("annunci.web", "=", true)
//.where("annunci.stato", "!=", "Sospeso")
2026-02-25 15:30:53 +01:00
. 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" ) ,
2026-03-08 01:02:57 +01:00
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" ) ,
2026-02-25 15:30:53 +01:00
] )
. 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 ) ,
} ) ) ,
2026-03-08 01:02:57 +01:00
ordini : data.ordini.map ( ( ordine ) = > ( {
. . . ordine ,
created_at : new Date ( ordine . created_at ) ,
paid_at : ordine.paid_at ? new Date ( ordine . paid_at ) : null ,
} ) ) ,
2026-02-25 15:30:53 +01:00
} ;
return formattedData ;
} catch ( e ) {
throw new TRPCError ( {
code : "NOT_FOUND" ,
message : ` Servizio annunci not found: ${ ( 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 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" )
2026-03-09 10:41:34 +01:00
. select ( [ "id" , "codice" , "titolo_it" ] )
2025-08-28 18:27:07 +02:00
. 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 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
? {
2025-11-19 17:18:28 +01:00
template : {
mailType : "emailContattoInteressato" ,
props : {
indirizzo : data.annuncio.indirizzo ,
nome_cognome : data.annuncio.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 ,
2025-08-28 18:27:07 +02:00
} ,
}
: 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 } . ` ,
2025-08-29 16:18:32 +02:00
recipient : [ numeroProprietario ] ,
2025-08-28 18:27:07 +02:00
}
: null ;
2026-03-08 01:02:57 +01:00
await addEventToQueue ( {
data : {
data : {
template : {
mailType : "emailContattiConScheda" ,
props : {
nominativo : data.annuncio.locatore || "" ,
telefono : data.annuncio.numero || "" ,
indirizzo : data.annuncio.indirizzo || "" ,
codice : data.annuncio.codice ,
} ,
} ,
mail : {
subject : ` Contatti Annuncio ${ data . annuncio . codice } - InfoAlloggi ` ,
to : data.user.email ,
attachments : [
{
filename : ` scheda annuncio ${ data . annuncio . codice } .pdf ` ,
generate : {
type : "scheda_annuncio" ,
annuncioId : data.annuncioId ,
} ,
encoding : "base64" ,
contentType : "application/pdf" ,
} ,
] ,
} ,
userId : data.user.id ,
} ,
tipo : "email" ,
} ,
2026-03-17 18:46:43 +01:00
lock_expires : new Date ( Date . now ( ) + 3000 ) ,
2026-03-08 01:02:57 +01:00
} ) ;
2025-08-28 18:27:07 +02:00
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 )
2026-03-05 10:29:08 +01:00
. execute ( ) ;
2025-08-28 18:27:07 +02:00
// NOTIFICHE EMAIL INTERRUZIONE SERVIZIO
const utente = await getUser ( { db , userId : servizio.user_id } ) ;
await NewMail ( {
2025-11-19 17:18:28 +01:00
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 ,
2025-08-28 18:27:07 +02:00
} ,
2025-08-29 16:18:32 +02:00
userId : utente.id ,
2025-08-28 18:27:07 +02:00
} ) ;
await NewMail ( {
2025-11-19 17:18:28 +01:00
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 ` ,
2025-08-28 18:27:07 +02:00
} ,
} ,
2025-11-19 17:18:28 +01:00
mail : {
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
} ;
2025-11-06 17:03:42 +01: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 )
2026-03-05 10:29:08 +01:00
. execute ( ) ;
2025-08-28 18:27:07 +02:00
// 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 )
2026-03-05 10:29:08 +01:00
. executeTakeFirstOrThrow (
( ) = >
new Error (
` Annuncio not found - servizioId: ${ servizioId } , annunciId: ${ annuncioId } ` ,
) ,
) ;
2025-08-28 18:27:07 +02:00
await NewMail ( {
2025-11-19 17:18:28 +01:00
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 confermato l'immobile con codice ${ annuncio_codice . codice } . ` ,
title : ` Nuova conferma immobile ${ annuncio_codice . codice } ` ,
2025-08-28 18:27:07 +02:00
} ,
} ,
2025-11-19 17:18:28 +01:00
mail : {
subject : ` Nuova conferma immobile ${ annuncio_codice . codice } ` ,
to : "web@infoalloggi.it" ,
} ,
2025-08-28 18:27:07 +02:00
} ) ;
await NewMail ( {
2025-11-19 17:18:28 +01:00
template : {
mailType : "generic" ,
props : {
noreply : true ,
2026-01-16 15:47:38 +01:00
testo : ` La tua conferma per l'immobile con codice ${ annuncio_codice . codice } è stata inviata con successo. Il nostro team la esaminerà a breve. Riceverai una notifica via email per procedere. ` ,
title : ` Conferma immobile ${ annuncio_codice . codice } inviata con successo ` ,
2025-11-19 17:18:28 +01:00
} ,
} ,
mail : {
2026-01-16 15:47:38 +01:00
subject : ` Conferma immobile ${ annuncio_codice . codice } inviata con successo ` ,
2025-11-19 17:18:28 +01:00
to : utente.email ,
2025-08-28 18:27:07 +02:00
} ,
2025-08-29 16:18:32 +02:00
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
} ;
2025-11-06 17:03:42 +01: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 } ) ;
2026-03-08 01:02:57 +01:00
2026-03-12 16:24:16 +01:00
const saldo = await SaldoSolver ( {
tipologia : servizio.tipologia ,
permanenza : servizio.permanenza ,
budget : servizio.budget ,
} ) ;
2026-03-08 01:02:57 +01:00
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 ` ,
) ;
}
2025-11-06 17:03:42 +01:00
//ADMIN CONFERMA LAVORATA EMAIL
if ( status ) {
await NewMail ( {
2025-11-19 17:18:28 +01:00
template : {
mailType : "generic" ,
props : {
link : {
2026-03-08 01:02:57 +01:00
href : ` ${ env . BASE_URL } /area-riservata/servizio/ ${ servizioId } ` ,
2025-11-19 17:18:28 +01:00
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" ,
2025-11-06 17:03:42 +01:00
} ,
} ,
2025-11-19 17:18:28 +01:00
mail : {
subject : "Conferma lavorata per il servizio di ricerca affitto" ,
to : utente.email ,
} ,
2025-11-06 17:03:42 +01:00
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 ( {
2025-11-19 17:18:28 +01:00
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 ` ,
2025-11-06 17:03:42 +01:00
} ,
} ,
2025-11-19 17:18:28 +01:00
mail : {
subject : ` Utente ${ utente . username } ha accettato le condizioni di conferma ` ,
to : "web@infoalloggi.it" ,
} ,
2025-11-06 17:03:42 +01:00
} ) ;
2025-11-21 12:24:55 +01:00
return { success : true , userId : utente.id } ;
2025-11-06 17:03:42 +01:00
} 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
2026-03-05 12:32:03 +01:00
export const SendContattoAnnuncioEmail = 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 ( {
2025-11-19 17:18:28 +01:00
template : {
mailType : "contattoAnnuncioEmail" ,
props : {
codice ,
email ,
messaggio ,
nome ,
telefono ,
} ,
} ,
mail : {
subject : "Contatto per annuncio" ,
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
} ;
2025-11-11 15:41:07 +01:00
export const getCompatibileAnnunci = async ( {
servizioId ,
adminOverride = false ,
} : {
servizioId : ServizioServizioId ;
adminOverride? : boolean ;
2025-12-04 17:16:49 +01:00
} ) : Promise < ( AnnuncioRicerca & { alreadyRequested : boolean } ) [ ] > = > {
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" ,
} ) ;
}
2025-12-04 17:16:49 +01:00
const interests = await GetUserInterests ( servizio . user_id ) ;
2025-08-28 18:27:07 +02:00
let qry = db
. selectFrom ( "annunci" )
2025-11-06 17:03:42 +01:00
. distinctOn ( "annunci.id" )
2025-12-04 17:16:49 +01:00
. select ( ( eb ) = > [
2025-11-04 19:18:13 +01:00
"id" ,
"codice" ,
"comune" ,
"provincia" ,
"mq" ,
"prezzo" ,
"desc_en" ,
"desc_it" ,
"titolo_en" ,
"titolo_it" ,
"tipo" ,
"consegna" ,
"stato" ,
"web" ,
2025-11-06 13:17:20 +01:00
"media_updated_at" ,
2025-11-04 19:18:13 +01:00
"homepage" ,
"numero_camere" ,
2025-11-18 15:20:14 +01:00
"external_videos" ,
2025-11-04 19:18:13 +01:00
"modificato_il" ,
2025-11-18 15:20:14 +01:00
withImages ( ) ,
withVideos ( ) ,
2025-12-04 17:16:49 +01:00
eb
. case ( )
. when ( "id" , "in" , interests )
. then ( true )
. else ( false )
. end ( )
. as ( "alreadyRequested" ) ,
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 ) ,
2025-11-11 15:41:07 +01:00
) ;
if ( adminOverride ) {
return await qry . execute ( ) ;
}
qry = qry . where ( "prezzo" , "<=" , servizio . budget * 1 e2 * 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 ) ;
}
2025-11-11 15:41:07 +01:00
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 ,
2025-08-04 17:45:44 +02:00
} : {
2025-08-28 18:27:07 +02:00
ordineId : OrdiniOrdineId ;
2026-03-09 19:14:05 +01:00
} ) : Promise < PurchaseData > = > {
2025-08-28 18:27:07 +02:00
try {
const data = await db
2026-03-08 01:02:57 +01:00
. selectFrom ( "ordini" )
2025-08-28 18:27:07 +02:00
. select ( [
2026-03-08 01:02:57 +01:00
"ordini.ordine_id" ,
"ordini.userid" ,
"ordini.amount_cent" ,
"ordini.sconto" ,
"ordini.created_at" ,
"ordini.paymentmethod" ,
2025-08-28 18:27:07 +02:00
] )
2026-03-08 01:02:57 +01:00
. innerJoin ( "prezziario" , "prezziario.idprezziario" , "ordini.packid" )
. select ( "prezziario.nome_it" )
. where ( "ordini.ordine_id" , "=" , ordineId )
2026-03-09 18:14:22 +01:00
//.where("ordini.paymentstatus", "=", PaymentStatusEnum.success)
2025-08-28 18:27:07 +02:00
2026-03-05 10:29:08 +01:00
. executeTakeFirstOrThrow (
( ) = > new Error ( ` Ordine not found - ordineId: ${ ordineId } ` ) ,
) ;
2025-08-28 18:27:07 +02:00
2026-03-08 01:02:57 +01:00
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" ,
2026-03-09 19:14:05 +01:00
"users_anagrafica.codice_fiscale" ,
2026-03-08 01:02:57 +01:00
] )
. where ( "users.id" , "=" , data . userid )
. executeTakeFirstOrThrow (
( ) = > new Error ( ` Anagrafica not found - userid: ${ data . userid } ` ) ,
) ;
2026-03-10 15:17:25 +01:00
const comuni = await getComuni ( ) ;
const nazioni = await getNazioni ( ) ;
const parsedComune = parseDBComune ( anagrafica . comune_residenza , comuni ) ;
const parsedNazione = parseDBNazione ( anagrafica . nazione_residenza , nazioni ) ;
2026-03-08 01:02:57 +01:00
2025-08-28 18:27:07 +02:00
return {
2026-03-10 15:17:25 +01:00
clienteIndirizzo : ` ${ anagrafica . via_residenza } ${ anagrafica . civico_residenza } \ n ${ parsedComune ? . nome } ( ${ anagrafica . provincia_residenza } ) ${ anagrafica . cap_residenza } \ n ${ parsedNazione ? . nome } ` ,
2026-03-08 01:02:57 +01:00
clienteNome : anagrafica.username ,
2026-03-09 19:14:05 +01:00
codiceFiscale : anagrafica.codice_fiscale ,
2025-08-29 16:18:32 +02:00
date : data.created_at ,
2026-03-08 01:22:03 +01:00
packName : data.nome_it ,
sconto : data.sconto ,
amount_cent : data.amount_cent ,
2026-03-08 01:02:57 +01:00
ordineId : data.ordine_id ,
2026-03-09 18:14:22 +01:00
paymentMethod : ( data . paymentmethod || "manual" ) as PaymentType ,
} ;
2025-08-28 18:27:07 +02:00
} 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
} ;
2026-03-11 12:07:19 +01:00
export type RichiesteData = Servizio &
Pick < Users , "username" > & {
annunci_servizio : ( Override <
ServizioAnnunci ,
{
created_at : string ;
open_contatti_at : string | null ;
user_confirmed_at : string | null ;
accettato_conferma_at : string | null ;
contratto_decorrenza : string | null ;
contratto_scadenza : string | null ;
}
> &
Pick < Annunci , "codice" | "titolo_it" > ) [ ] ;
ordini_servizio : ( Override <
Ordini ,
{
created_at : string ;
paid_at : string | null ;
}
> &
Pick < Prezziario , "testo_condizioni" > ) [ ] ;
} ;
export const getRichieste = async ( ) : Promise < RichiesteData [ ] > = > {
try {
return await 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 ( [ "annunci.codice" , "annunci.titolo_it" ] )
. 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" )
. execute ( ) ;
} catch ( e ) {
throw new TRPCError ( {
code : "INTERNAL_SERVER_ERROR" ,
message : ` Error fetching richieste: ${ ( e as Error ) . message } ` ,
} ) ;
}
} ;