feat: add comms_enabled flag to users and update email notification logic

This commit is contained in:
Marco Pedone 2026-03-30 18:44:51 +02:00
parent 73b1997c15
commit a11fc1241c
7 changed files with 266 additions and 194 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS public.users
ADD COLUMN IF NOT EXISTS comms_enabled boolean NOT NULL DEFAULT true;

View file

@ -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

View file

@ -210,6 +210,19 @@ const ServizioPrezzo = () => {
</div>
)}
</div>
{packs.acconto.success && (
<Link
aria-label="Leggi le condizioni"
className="block w-fit"
href={`/servizio/condizioni/${packs.acconto.pack.testo_condizioni}`}
target="_blank"
>
<Button className="w-full text-sm">
<span>{t.acquisto.leggi_condizioni}</span>
<ExternalLink />
</Button>
</Link>
)}
</div>
</DialogContent>
</Dialog>

View file

@ -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<ComunicazioniUserProps> = ({
</PopoverContent>
</Popover>
</div>
<Queue userId={userId} />
<CommsSwitch userId={userId} />
<EmailAccordion isAdmin={user.isAdmin} userId={userId} />
</main>
);
@ -120,12 +122,35 @@ ComunicazioniUser.getLayout = function getLayout(page) {
return <AreaRiservataLayoutUserView>{page}</AreaRiservataLayoutUserView>;
};
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 <div>Loading...</div>;
}
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 (
<div>
<h2>Eventi email in coda per user {userId}</h2>
<div className="flex items-center gap-2">
<Label htmlFor="comms">Abilita comunicazioni</Label>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={data.comms_enabled}
id="comms"
onCheckedChange={(checked) => {
mutate({ id: userId, data: { comms_enabled: checked } });
}}
/>
</div>
);
};

View file

@ -39,6 +39,8 @@ export default interface UsersTable {
mustChangePassword: ColumnType<boolean, boolean | undefined, boolean>;
note: ColumnType<string | null, string | null, string | null>;
comms_enabled: ColumnType<boolean, boolean | undefined, boolean>;
}
export type Users = Selectable<UsersTable>;

View file

@ -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,6 +241,7 @@ export const attivaServizio_SaltaPagamentoAcconto = async (
),
);
if (user.comms_enabled) {
await NewMail({
template: {
mailType: "servizioAttivato",
@ -277,8 +280,11 @@ export const attivaServizio_SaltaPagamentoAcconto = async (
userId: ordine.userId,
},
},
lock_expires: add(new Date(), { days: servizio.interruzioneDays - 5 }),
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,6 +320,7 @@ export const AdminAttivaServizio_Ufficio = async (
.where("servizio_id", "=", servizioId)
.execute();
if (data.comms_enabled) {
await addEventToQueue({
data: {
tipo: "email",
@ -330,6 +337,7 @@ export const AdminAttivaServizio_Ufficio = async (
},
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,6 +937,7 @@ export const interruzioneServizio = async (servizioId: ServizioServizioId) => {
// NOTIFICHE EMAIL INTERRUZIONE SERVIZIO
const utente = await getUser({ db, userId: servizio.user_id });
if (utente.comms_enabled) {
await NewMail({
template: {
mailType: "interruzione",
@ -938,7 +948,7 @@ export const interruzioneServizio = async (servizioId: ServizioServizioId) => {
},
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: {

View file

@ -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,7 +124,7 @@ export const whIntentSucceededHandler = async ({
`Failed to update servizio - servizioId: ${ordine.servizio_id}`,
),
);
if (utente.comms_enabled) {
await addEventToQueue({
data: {
tipo: "email",
@ -136,7 +137,7 @@ export const whIntentSucceededHandler = async ({
to: utente.email,
attachments: [
{
filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`,
filename: condizioniFilename,
generate: {
type: "condizioni",
stringId: (condizioni?.testo_condizioni ||
@ -146,7 +147,7 @@ export const whIntentSucceededHandler = async ({
contentType: "application/pdf",
},
{
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
filename: ricevutaFilename,
generate: {
type: "ricevuta_cortesia",
ordineId: ordine.ordine_id,
@ -180,6 +181,7 @@ export const whIntentSucceededHandler = async ({
days: servizio.interruzioneDays - 5,
}),
});
}
//SERVIZIO ATTIVATO
break;
@ -210,6 +212,7 @@ export const whIntentSucceededHandler = async ({
isOkSaldo: true,
})
.execute();
if (utente.comms_enabled) {
await addEventToQueue({
data: {
tipo: "email",
@ -222,7 +225,7 @@ export const whIntentSucceededHandler = async ({
to: utente.email,
attachments: [
{
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
filename: ricevutaFilename,
generate: {
type: "ricevuta_cortesia",
ordineId: ordine.ordine_id,
@ -237,10 +240,12 @@ export const whIntentSucceededHandler = async ({
},
lock_expires,
});
}
break;
}
case OrderTypeEnum.Rinnovo: {
// nessuna azione aggiuntiva richiesta
if (utente.comms_enabled) {
await addEventToQueue({
data: {
tipo: "email",
@ -253,7 +258,7 @@ export const whIntentSucceededHandler = async ({
to: utente.email,
attachments: [
{
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
filename: ricevutaFilename,
generate: {
type: "ricevuta_cortesia",
ordineId: ordine.ordine_id,
@ -268,6 +273,7 @@ export const whIntentSucceededHandler = async ({
},
lock_expires,
});
}
break;
}
case OrderTypeEnum.Consulenza: {
@ -284,6 +290,7 @@ export const whIntentSucceededHandler = async ({
isOkConsulenza: true,
})
.execute();
if (utente.comms_enabled) {
await addEventToQueue({
data: {
tipo: "email",
@ -296,7 +303,7 @@ export const whIntentSucceededHandler = async ({
to: utente.email,
attachments: [
{
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
filename: ricevutaFilename,
generate: {
type: "ricevuta_cortesia",
ordineId: ordine.ordine_id,
@ -311,6 +318,7 @@ export const whIntentSucceededHandler = async ({
},
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",
},
],
},
},