feat: add email sending functionality for receipt and conditions in AdminOrdiniModal

This commit is contained in:
Marco Pedone 2026-03-10 13:16:32 +01:00
parent a09fa0597d
commit 0619ca0c34
4 changed files with 116 additions and 16 deletions

View file

@ -1,6 +1,7 @@
import { CircleCheck, CircleSlash, ReceiptText } from "lucide-react"; import { CircleCheck, CircleSlash, ReceiptText } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import toast from "react-hot-toast";
import { import {
Credenza, Credenza,
CredenzaBody, CredenzaBody,
@ -48,6 +49,16 @@ const AdminOrdiniModal = ({
ordineId, ordineId,
}); });
const { mutate: sendReceipt } =
api.comunicazioni.sendRicevutaECondizioniEmail.useMutation({
onSuccess: () => {
toast.success("Email inviata con successo!");
},
onError: (error) => {
toast.error(`Errore durante l'invio dell'email: ${error.message}`);
},
});
if (!data) return null; if (!data) return null;
return ( return (
@ -66,6 +77,7 @@ const AdminOrdiniModal = ({
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody className="max-h-[80vh] space-y-2 overflow-auto pb-5"> <CredenzaBody className="max-h-[80vh] space-y-2 overflow-auto pb-5">
<p>Id Ordine: {data.ordine_id}</p> <p>Id Ordine: {data.ordine_id}</p>
<div className="flex items-center gap-2">
{data.isActive && ( {data.isActive && (
<div> <div>
<Link <Link
@ -78,6 +90,14 @@ const AdminOrdiniModal = ({
</Link> </Link>
</div> </div>
)} )}
<Button
disabled={!data.isActive}
onClick={() => sendReceipt({ ordineId: data.ordine_id })}
variant="outline"
>
Invia Ricevuta e Condizioni
</Button>
</div>
<FormEditOrder <FormEditOrder
close={() => setSelected(null)} close={() => setSelected(null)}
data={data} data={data}

View file

@ -1,11 +1,13 @@
import { z } from "zod/v4"; import { z } from "zod/v4";
import type { EmailsIdEmail } from "~/schemas/public/Emails"; import type { EmailsIdEmail } from "~/schemas/public/Emails";
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,
protectedProcedure, protectedProcedure,
publicProcedure, publicProcedure,
} from "~/server/api/trpc"; } from "~/server/api/trpc";
import { addEventToQueue } from "~/server/controllers/event_queue.controller";
import { SendContattoAnnuncioEmail } from "~/server/controllers/servizio.controller"; import { SendContattoAnnuncioEmail } from "~/server/controllers/servizio.controller";
import { db } from "~/server/db"; import { db } from "~/server/db";
import { import {
@ -15,7 +17,7 @@ import {
} from "~/server/services/email.service"; } from "~/server/services/email.service";
import { NewMail } from "~/server/services/mailer"; import { NewMail } from "~/server/services/mailer";
import { genPdfSchedaAnnuncioBase64 } from "~/server/services/puppeteer.service"; import { genPdfSchedaAnnuncioBase64 } from "~/server/services/puppeteer.service";
import { zAnnuncioId, zUserId } from "~/server/utils/zod_types"; import { zAnnuncioId, zOrdineId, zUserId } from "~/server/utils/zod_types";
export const comunicazioniRouter = createTRPCRouter({ export const comunicazioniRouter = createTRPCRouter({
getEmails: protectedProcedure getEmails: protectedProcedure
@ -208,4 +210,68 @@ export const comunicazioniRouter = createTRPCRouter({
...input, ...input,
}); });
}), }),
sendRicevutaECondizioniEmail: adminProcedure
.input(
z.object({
ordineId: zOrdineId,
}),
)
.mutation(async ({ input }) => {
try {
const ordine = await db
.selectFrom("ordini")
.selectAll()
.innerJoin("users", "users.id", "ordini.userid")
.select(["users.email"])
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
.select(["prezziario.testo_condizioni"])
.where("ordine_id", "=", input.ordineId)
.executeTakeFirstOrThrow(() => {
throw new Error(`Ordine not found - ordineId: ${input.ordineId}`);
});
await addEventToQueue({
data: {
tipo: "email",
data: {
template: {
mailType: "servizioAttivato",
},
mail: {
subject: "Pagamento Confermato - Acconto Infoalloggi.it",
to: ordine.email,
attachments: [
{
filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`,
generate: {
type: "condizioni",
stringId: (ordine.testo_condizioni ||
"CONDIZIONI_CERCHI") as TestiEStringheStingaId,
},
encoding: "base64",
contentType: "application/pdf",
},
{
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
generate: {
type: "ricevuta_cortesia",
ordineId: ordine.ordine_id,
},
encoding: "base64",
contentType: "application/pdf",
},
],
},
userId: ordine.userid,
},
},
lock_expires: new Date(), // Lock expires in 1
});
} catch (e) {
throw new Error(
`Errore nell'invio dell'email: ${(e as Error).message}`,
);
}
}),
}); });

View file

@ -80,7 +80,14 @@ export const whIntentSucceededHandler = async ({
}) })
.where("intent_id", "=", pIntentId) .where("intent_id", "=", pIntentId)
.where("ordine_id", "=", ordineId) .where("ordine_id", "=", ordineId)
.returning(["servizio_id", "ordine_id", "userid", "type", "created_at"]) .returning([
"servizio_id",
"ordine_id",
"userid",
"type",
"created_at",
"packid",
])
.executeTakeFirstOrThrow(() => { .executeTakeFirstOrThrow(() => {
throw new Error( throw new Error(
`Payment not found - intent_id: ${pIntentId}, ordineId: ${ordineId}`, `Payment not found - intent_id: ${pIntentId}, ordineId: ${ordineId}`,
@ -97,6 +104,12 @@ export const whIntentSucceededHandler = async ({
); );
}); });
const condizioni = await trx
.selectFrom("prezziario")
.select("testo_condizioni")
.where("idprezziario", "=", ordine.packid)
.executeTakeFirst();
const utente = await getUser({ const utente = await getUser({
db: trx, db: trx,
userId: ordine.userid, userId: ordine.userid,
@ -131,7 +144,8 @@ export const whIntentSucceededHandler = async ({
generate: { generate: {
type: "condizioni", type: "condizioni",
stringId: "CONDIZIONI_CERCHI" as TestiEStringheStingaId, stringId: (condizioni?.testo_condizioni ||
"CONDIZIONI_CERCHI") as TestiEStringheStingaId,
}, },
encoding: "base64", encoding: "base64",
contentType: "application/pdf", contentType: "application/pdf",

View file

@ -171,7 +171,7 @@ const puppeteerGenPdf = async ({
try { try {
browser = await puppeteer.connect({ browser = await puppeteer.connect({
browserWSEndpoint: `${env.BROWSER_WS_URL}?--user-data-dir=/tmp/session-0`, browserWSEndpoint: `${env.BROWSER_WS_URL}`,
}); });
const page = await browser.newPage(); const page = await browser.newPage();