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" ;
2025-08-07 14:50:40 +02:00
import { z } from "zod/v4" ;
2025-08-28 18:27:07 +02:00
import type { PurchaseData } from "~/components/acquisto_receipt" ;
2025-10-20 16:22:20 +02:00
import { env } from "~/env" ;
2025-10-27 17:58:42 +01:00
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" ;
2025-11-04 19:18:13 +01:00
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 ,
skipOrdine = false ,
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 ;
skipOrdine? : boolean ;
2025-08-04 17:45:44 +02:00
} ) = > {
2025-08-28 18:27:07 +02:00
try {
const servizioData = 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 )
. 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 ( ) ;
}
2025-11-03 10:59:45 +01:00
// 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 ( skipOrdine ) {
2025-11-03 10:59:45 +01:00
// 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 ,
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 null ;
}
//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 existingOrder . ordine_id ;
}
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 order . ordine_id ;
} 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 )
. select ( ( eb ) = > [
"servizio.servizio_id" ,
"servizio.created_at" ,
"servizio.decorrenza" ,
"servizio.tipologia" ,
"servizio.budget" ,
"servizio.permanenza" ,
"servizio.isInterrotto" ,
"servizio.isOkAcconto" ,
"servizio.isOkSaldo" ,
"servizio.isOkConsulenza" ,
2025-11-03 10:59:45 +01:00
"servizio.skipPayment" ,
"servizio.skipControlloDoc" ,
"servizio.skipDocMotivazione" ,
2025-10-29 09:39:06 +01:00
jsonObjectFrom (
eb
. selectFrom ( "users_storage" )
. select ( [
"users_storage.storageId as storageId" ,
"servizio.doc_personale_fronte_ref as ref" ,
] )
. whereRef (
"users_storage.user_storage_id" ,
"=" ,
"servizio.doc_personale_fronte_ref" ,
) ,
) . as ( "doc_personale_fronte" ) ,
jsonObjectFrom (
eb
. selectFrom ( "users_storage" )
. select ( [
"users_storage.storageId as storageId" ,
"servizio.doc_personale_retro_ref as ref" ,
] )
. whereRef (
"users_storage.user_storage_id" ,
"=" ,
"servizio.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" )
2025-10-27 17:58:42 +01:00
. 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" ,
2025-11-06 13:17:20 +01:00
"annunci.media_updated_at" ,
2025-10-27 17:58:42 +01:00
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" )
2025-10-21 14:33:47 +02:00
// .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 ( ) ;
const servizio = await getServizioById ( servizioId ) ;
const utente = await getUser ( { db , userId : servizio.user_id } ) ;
//ADMIN CONFERMA LAVORATA EMAIL
if ( data . hasConfermaAdmin ) {
await NewMail ( {
mailType : "generic" ,
props : {
link : {
2025-10-20 16:22:20 +02:00
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 : ` La conferma è stata lavorata con successo. Puoi procedere con le prossime azioni. ` ,
title : "Conferma lavorata per il servizio di ricerca affitto" ,
2025-08-28 18:27:07 +02:00
} ,
2025-08-29 16:18:32 +02:00
subject : "Conferma lavorata per il servizio di ricerca affitto" ,
to : utente.email ,
userId : utente.id ,
2025-08-28 18:27:07 +02:00
} ) ;
}
// USER ACCETTA CONFERMA EMAIL
if ( data . accettato_conferma_at ) {
await NewMail ( {
mailType : "generic" ,
props : {
link : {
2025-10-20 16:22:20 +02:00
href : ` ${ env . BASE_URL } /area-riservata/admin/user-view/ricerca/ ${ servizio . user_id } # ${ 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 : ` 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-08-28 18:27:07 +02:00
} ,
2025-08-29 16:18:32 +02:00
subject : ` Utente ${ utente . username } ha accettato le condizioni di conferma ` ,
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 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 : {
2025-10-20 16:22:20 +02:00
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 : {
2025-10-20 16:22:20 +02:00
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" )
2025-10-27 17:58:42 +01:00
. select ( ( eb ) = > [
2025-08-28 18:27:07 +02:00
"annunci.titolo_it" ,
"annunci.codice" ,
"annunci.prezzo" ,
2025-10-27 17:58:42 +01:00
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 ,
2025-10-27 17:58:42 +01:00
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 userConfirmImmobile = 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 : {
2025-10-20 16:22:20 +02:00
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 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
} ;
2025-11-04 19:18:13 +01:00
export const getCompatibileAnnunci = async (
servizioId : ServizioServizioId ,
) : 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" )
2025-10-27 17:58:42 +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" ,
"url_video" ,
"modificato_il" ,
2025-10-27 17:58:42 +01:00
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 ) ,
)
. where ( "prezzo" , "<=" , servizio . budget * 1 e2 * 1.2 ) ; // Allow 20% margin
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 ) ;
}
const annunci = await qry . execute ( ) ;
return annunci ;
} 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
} ;