From a11fc1241c0da499c68429d1dc850415f592821e Mon Sep 17 00:00:00 2001 From: Marco Pedone Date: Mon, 30 Mar 2026 18:44:51 +0200 Subject: [PATCH] feat: add comms_enabled flag to users and update email notification logic --- apps/db/migrations/42_comms_bool.up.sql | 2 + apps/infoalloggi/TODO | 6 +- .../components/servizio/servizio_actions.tsx | 13 + .../user-view/comunicazioni/[userId].tsx | 37 ++- apps/infoalloggi/src/schemas/public/Users.ts | 2 + .../server/controllers/servizio.controller.ts | 146 +++++----- .../server/controllers/stripe.controller.ts | 254 ++++++++++-------- 7 files changed, 266 insertions(+), 194 deletions(-) create mode 100644 apps/db/migrations/42_comms_bool.up.sql diff --git a/apps/db/migrations/42_comms_bool.up.sql b/apps/db/migrations/42_comms_bool.up.sql new file mode 100644 index 0000000..4b19984 --- /dev/null +++ b/apps/db/migrations/42_comms_bool.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE IF EXISTS public.users + ADD COLUMN IF NOT EXISTS comms_enabled boolean NOT NULL DEFAULT true; \ No newline at end of file diff --git a/apps/infoalloggi/TODO b/apps/infoalloggi/TODO index 73db714..c052631 100644 --- a/apps/infoalloggi/TODO +++ b/apps/infoalloggi/TODO @@ -1,10 +1,6 @@ TODOS: - Log email fallite e retry - - conferma presa visione deve rimandare a servizio giusto non /ricerrca - - AFTER MVP: - TODO migrazione app router https://nextjs.org/docs/pages/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app - Sezione "La mia locazione" con i dati della locazione attuale dell'utente @@ -16,4 +12,4 @@ AFTER MVP: - Third party apps: Etra, add to calendario, scarica calendario pdf - Report andamento props: Generate a Report with selected user review, analytics and other data for the apartment owner - Infoalloggi.AI: Pagina area riservata "Infoalloggi.it AI" o "la mia ricerca Infoalloggi" robe cosí in cui il sistema fa una ricerca personalizzata, elemento marketizzabile - - Document scan: Document scan for upload + - Document scan: Document scan for upload \ No newline at end of file diff --git a/apps/infoalloggi/src/components/servizio/servizio_actions.tsx b/apps/infoalloggi/src/components/servizio/servizio_actions.tsx index 626280d..37f670a 100644 --- a/apps/infoalloggi/src/components/servizio/servizio_actions.tsx +++ b/apps/infoalloggi/src/components/servizio/servizio_actions.tsx @@ -210,6 +210,19 @@ const ServizioPrezzo = () => { )} + {packs.acconto.success && ( + + + + )} diff --git a/apps/infoalloggi/src/pages/area-riservata/admin/user-view/comunicazioni/[userId].tsx b/apps/infoalloggi/src/pages/area-riservata/admin/user-view/comunicazioni/[userId].tsx index 38c8fa2..6743eab 100644 --- a/apps/infoalloggi/src/pages/area-riservata/admin/user-view/comunicazioni/[userId].tsx +++ b/apps/infoalloggi/src/pages/area-riservata/admin/user-view/comunicazioni/[userId].tsx @@ -5,11 +5,13 @@ import { EmailAccordion } from "~/components/area-riservata/comunicazioni"; import { Confirm } from "~/components/confirm"; import { AreaRiservataLayoutUserView } from "~/components/Layout"; import { Button } from "~/components/ui/button"; +import { Label } from "~/components/ui/label"; import { Popover, PopoverContent, PopoverTrigger, } from "~/components/ui/popover"; +import { Switch } from "~/components/ui/switch"; import type { NextPageWithLayout } from "~/pages/_app"; import { useEnforcedSession } from "~/providers/SessionProvider"; import type { UsersId } from "~/schemas/public/Users"; @@ -74,7 +76,7 @@ const ComunicazioniUser: NextPageWithLayout = ({ - + ); @@ -120,12 +122,35 @@ ComunicazioniUser.getLayout = function getLayout(page) { return {page}; }; -const Queue = ({ userId }: { userId: UsersId }) => { - const { data } = api.comunicazioni.getUserQueuedEmails.useQuery({ userId }); - console.log("Queue data", data); +const CommsSwitch = ({ userId }: { userId: UsersId }) => { + const { data, isLoading } = api.users.getUser.useQuery({ id: userId }); + if (isLoading || !data) { + return
Loading...
; + } + + const utils = api.useUtils(); + const { mutate } = api.users.editUser.useMutation({ + onSuccess: async () => { + toast.success("Impostazione modificata"); + await utils.users.getUser.invalidate({ id: userId }); + }, + onError: (error) => { + console.error(error); + toast.error("Errore durante la modifica dell'impostazione"); + }, + }); + return ( -
-

Eventi email in coda per user {userId}

+
+ + { + mutate({ id: userId, data: { comms_enabled: checked } }); + }} + />
); }; diff --git a/apps/infoalloggi/src/schemas/public/Users.ts b/apps/infoalloggi/src/schemas/public/Users.ts index a91d6f6..f0671e4 100644 --- a/apps/infoalloggi/src/schemas/public/Users.ts +++ b/apps/infoalloggi/src/schemas/public/Users.ts @@ -39,6 +39,8 @@ export default interface UsersTable { mustChangePassword: ColumnType; note: ColumnType; + + comms_enabled: ColumnType; } export type Users = Selectable; diff --git a/apps/infoalloggi/src/server/controllers/servizio.controller.ts b/apps/infoalloggi/src/server/controllers/servizio.controller.ts index 56baaaf..34646a1 100644 --- a/apps/infoalloggi/src/server/controllers/servizio.controller.ts +++ b/apps/infoalloggi/src/server/controllers/servizio.controller.ts @@ -224,6 +224,8 @@ export const attivaServizio_SaltaPagamentoAcconto = async ( () => new Error(`Ordine not found - ordineId: ${ordineId}`), ); + const user = await getUser({ db: tx, userId: ordine.userId }); + const servizio = await tx .updateTable("servizio") .set({ @@ -239,46 +241,50 @@ export const attivaServizio_SaltaPagamentoAcconto = async ( ), ); - await NewMail({ - template: { - mailType: "servizioAttivato", - }, - mail: { - subject: "Pagamento Confermato - Acconto Infoalloggi.it", - to: ordine.email, - attachments: ordine.testo_condizioni - ? [ - { - filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`, - - content: await genPdfCondizioniBase64( - ordine.testo_condizioni as TestiEStringheStingaId, - ), - - encoding: "base64", - contentType: "application/pdf", - }, - ] - : [], - }, - userId: ordine.userId, - }); - await addEventToQueue({ - data: { - tipo: "email", - data: { - template: { - mailType: "avvisoInterruzione", - }, - mail: { - subject: "Avviso Interruzione", - to: ordine.email, - }, - userId: ordine.userId, + if (user.comms_enabled) { + await NewMail({ + template: { + mailType: "servizioAttivato", }, - }, - lock_expires: add(new Date(), { days: servizio.interruzioneDays - 5 }), - }); + mail: { + subject: "Pagamento Confermato - Acconto Infoalloggi.it", + to: ordine.email, + attachments: ordine.testo_condizioni + ? [ + { + filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`, + + content: await genPdfCondizioniBase64( + ordine.testo_condizioni as TestiEStringheStingaId, + ), + + encoding: "base64", + contentType: "application/pdf", + }, + ] + : [], + }, + userId: ordine.userId, + }); + await addEventToQueue({ + data: { + tipo: "email", + data: { + template: { + mailType: "avvisoInterruzione", + }, + mail: { + subject: "Avviso Interruzione", + to: ordine.email, + }, + userId: ordine.userId, + }, + }, + lock_expires: add(new Date(), { + days: servizio.interruzioneDays - 5, + }), + }); + } return true; }); } catch (e) { @@ -298,7 +304,7 @@ export const AdminAttivaServizio_Ufficio = async ( .selectFrom("servizio") .select(["servizio.user_id", "servizio.interruzioneDays"]) .innerJoin("users", "users.id", "servizio.user_id") - .select("users.email") + .select(["users.email", "users.comms_enabled"]) .where("servizio_id", "=", servizioId) .executeTakeFirstOrThrow( () => new Error(`Servizio not found - servizioId: ${servizioId}`), @@ -314,22 +320,24 @@ export const AdminAttivaServizio_Ufficio = async ( .where("servizio_id", "=", servizioId) .execute(); - await addEventToQueue({ - data: { - tipo: "email", + if (data.comms_enabled) { + await addEventToQueue({ data: { - template: { - mailType: "avvisoInterruzione", + tipo: "email", + data: { + template: { + mailType: "avvisoInterruzione", + }, + mail: { + subject: "Avviso Interruzione", + to: data.email, + }, + userId: data.user_id, }, - mail: { - subject: "Avviso Interruzione", - to: data.email, - }, - userId: data.user_id, }, - }, - lock_expires: add(today, { days: data.interruzioneDays - 5 }), //avviso 5 giorni prima della scadenza del periodo di ripensamento - }); + lock_expires: add(today, { days: data.interruzioneDays - 5 }), //avviso 5 giorni prima della scadenza del periodo di ripensamento + }); + } return true; }); } catch (e) { @@ -779,7 +787,7 @@ export const SbloccaContatti = async ({ const user = await tx .selectFrom("users") - .select(["id", "username", "email", "telefono"]) + .select(["id", "username", "email", "telefono", "comms_enabled"]) .where("id", "=", servizio.user_id) .executeTakeFirstOrThrow(() => new Error("User not found")); @@ -812,6 +820,7 @@ export const SbloccaContatti = async ({ user, }; }); + if (!data.user.comms_enabled) return true; const EmailProprietario: NewMailProps | null = data.annuncio.email ? { @@ -928,17 +937,18 @@ export const interruzioneServizio = async (servizioId: ServizioServizioId) => { // NOTIFICHE EMAIL INTERRUZIONE SERVIZIO const utente = await getUser({ db, userId: servizio.user_id }); - await NewMail({ - template: { - mailType: "interruzione", - }, - mail: { - subject: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto.`, - to: utente.email, - }, - userId: utente.id, - }); - + if (utente.comms_enabled) { + await NewMail({ + template: { + mailType: "interruzione", + }, + mail: { + subject: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto.`, + to: utente.email, + }, + userId: utente.id, + }); + } await NewMail({ template: { mailType: "generic", @@ -1051,6 +1061,9 @@ export const userSendConfermaIntent = async ({ // NOTIFICHE EMAIL CONFERMA IMMOBILE const utente = await getUser({ db, userId: servizio.user_id }); + if (!utente.comms_enabled) { + return true; + } const annuncio_codice = await db .selectFrom("annunci") @@ -1148,6 +1161,9 @@ export const adminUpdateConfermaStatus = async ({ } //ADMIN CONFERMA LAVORATA EMAIL + if (!utente.comms_enabled) { + return true; + } if (status) { await NewMail({ template: { diff --git a/apps/infoalloggi/src/server/controllers/stripe.controller.ts b/apps/infoalloggi/src/server/controllers/stripe.controller.ts index d7140ae..66bc18e 100644 --- a/apps/infoalloggi/src/server/controllers/stripe.controller.ts +++ b/apps/infoalloggi/src/server/controllers/stripe.controller.ts @@ -87,7 +87,8 @@ export const whIntentSucceededHandler = async ({ `Payment not found - intent_id: ${pIntentId}, ordineId: ${ordineId}`, ); }); - + const condizioniFilename = `condizioni_contrattuali_${(ordine.paid_at || ordine.created_at).toISOString()}.pdf`; + const ricevutaFilename = `ricevuta_cortesia_${(ordine.paid_at || ordine.created_at).toISOString()}.pdf`; const condizioni = await trx .selectFrom("prezziario") .select("testo_condizioni") @@ -123,63 +124,64 @@ export const whIntentSucceededHandler = async ({ `Failed to update servizio - servizioId: ${ordine.servizio_id}`, ), ); - - await addEventToQueue({ - data: { - tipo: "email", + if (utente.comms_enabled) { + await addEventToQueue({ data: { - template: { - mailType: "servizioAttivato", - }, - mail: { - subject: "Pagamento Confermato - Acconto Infoalloggi.it", - to: utente.email, - attachments: [ - { - filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`, - generate: { - type: "condizioni", - stringId: (condizioni?.testo_condizioni || - CONDIZIONI_DEFAULT) as TestiEStringheStingaId, + tipo: "email", + data: { + template: { + mailType: "servizioAttivato", + }, + mail: { + subject: "Pagamento Confermato - Acconto Infoalloggi.it", + to: utente.email, + attachments: [ + { + filename: condizioniFilename, + generate: { + type: "condizioni", + stringId: (condizioni?.testo_condizioni || + CONDIZIONI_DEFAULT) as TestiEStringheStingaId, + }, + encoding: "base64", + contentType: "application/pdf", }, - encoding: "base64", - contentType: "application/pdf", - }, - { - filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`, - generate: { - type: "ricevuta_cortesia", - ordineId: ordine.ordine_id, + { + filename: ricevutaFilename, + generate: { + type: "ricevuta_cortesia", + ordineId: ordine.ordine_id, + }, + encoding: "base64", + contentType: "application/pdf", }, - encoding: "base64", - contentType: "application/pdf", - }, - ], + ], + }, + userId: utente.id, }, - userId: utente.id, }, - }, - lock_expires, - }); + lock_expires, + }); - await addEventToQueue({ - data: { - tipo: "email", + await addEventToQueue({ data: { - template: { - mailType: "avvisoInterruzione", + tipo: "email", + data: { + template: { + mailType: "avvisoInterruzione", + }, + mail: { + subject: "Promemoria durata servizio - Infoalloggi.it", + to: utente.email, + }, + userId: utente.id, }, - mail: { - subject: "Promemoria durata servizio - Infoalloggi.it", - to: utente.email, - }, - userId: utente.id, }, - }, - lock_expires: add(new Date(), { - days: servizio.interruzioneDays - 5, - }), - }); + lock_expires: add(new Date(), { + days: servizio.interruzioneDays - 5, + }), + }); + } //SERVIZIO ATTIVATO break; @@ -210,64 +212,68 @@ export const whIntentSucceededHandler = async ({ isOkSaldo: true, }) .execute(); - await addEventToQueue({ - data: { - tipo: "email", + if (utente.comms_enabled) { + await addEventToQueue({ data: { - template: { - mailType: "pagamentoConferma", - }, - mail: { - subject: "Pagamento Confermato - Saldo Infoalloggi.it", - to: utente.email, - attachments: [ - { - filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`, - generate: { - type: "ricevuta_cortesia", - ordineId: ordine.ordine_id, + tipo: "email", + data: { + template: { + mailType: "pagamentoConferma", + }, + mail: { + subject: "Pagamento Confermato - Saldo Infoalloggi.it", + to: utente.email, + attachments: [ + { + filename: ricevutaFilename, + generate: { + type: "ricevuta_cortesia", + ordineId: ordine.ordine_id, + }, + encoding: "base64", + contentType: "application/pdf", }, - encoding: "base64", - contentType: "application/pdf", - }, - ], + ], + }, + userId: utente.id, }, - userId: utente.id, }, - }, - lock_expires, - }); + lock_expires, + }); + } break; } case OrderTypeEnum.Rinnovo: { // nessuna azione aggiuntiva richiesta - await addEventToQueue({ - data: { - tipo: "email", + if (utente.comms_enabled) { + await addEventToQueue({ data: { - template: { - mailType: "pagamentoConferma", - }, - mail: { - subject: "Pagamento Confermato - Rinnovo Infoalloggi.it", - to: utente.email, - attachments: [ - { - filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`, - generate: { - type: "ricevuta_cortesia", - ordineId: ordine.ordine_id, + tipo: "email", + data: { + template: { + mailType: "pagamentoConferma", + }, + mail: { + subject: "Pagamento Confermato - Rinnovo Infoalloggi.it", + to: utente.email, + attachments: [ + { + filename: ricevutaFilename, + generate: { + type: "ricevuta_cortesia", + ordineId: ordine.ordine_id, + }, + encoding: "base64", + contentType: "application/pdf", }, - encoding: "base64", - contentType: "application/pdf", - }, - ], + ], + }, + userId: utente.id, }, - userId: utente.id, }, - }, - lock_expires, - }); + lock_expires, + }); + } break; } case OrderTypeEnum.Consulenza: { @@ -284,33 +290,35 @@ export const whIntentSucceededHandler = async ({ isOkConsulenza: true, }) .execute(); - await addEventToQueue({ - data: { - tipo: "email", + if (utente.comms_enabled) { + await addEventToQueue({ data: { - template: { - mailType: "pagamentoConferma", - }, - mail: { - subject: "Pagamento Confermato - Consulenza Infoalloggi.it", - to: utente.email, - attachments: [ - { - filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`, - generate: { - type: "ricevuta_cortesia", - ordineId: ordine.ordine_id, + tipo: "email", + data: { + template: { + mailType: "pagamentoConferma", + }, + mail: { + subject: "Pagamento Confermato - Consulenza Infoalloggi.it", + to: utente.email, + attachments: [ + { + filename: ricevutaFilename, + generate: { + type: "ricevuta_cortesia", + ordineId: ordine.ordine_id, + }, + encoding: "base64", + contentType: "application/pdf", }, - encoding: "base64", - contentType: "application/pdf", - }, - ], + ], + }, + userId: utente.id, }, - userId: utente.id, }, - }, - lock_expires, - }); + lock_expires, + }); + } break; } case OrderTypeEnum.Altro: @@ -341,6 +349,16 @@ export const whIntentSucceededHandler = async ({ encoding: "base64", contentType: "application/pdf", }, + { + filename: condizioniFilename, + generate: { + type: "condizioni", + stringId: (condizioni?.testo_condizioni || + CONDIZIONI_DEFAULT) as TestiEStringheStingaId, + }, + encoding: "base64", + contentType: "application/pdf", + }, ], }, },