From 18ff13a64db0209aa1c76aeca7f1a26c0a0720f9 Mon Sep 17 00:00:00 2001 From: Marco Pedone Date: Fri, 27 Mar 2026 20:23:21 +0100 Subject: [PATCH] feat: implement saldo preview functionality in payment process and update receipt generation --- apps/infoalloggi/TODO | 5 +- .../src/components/acquisto_receipt.tsx | 27 +++++-- .../src/pages/servizio/pagamento.tsx | 78 +++++++++++++++---- .../src/server/api/routers/pagamenti.ts | 10 +++ .../controllers/pagamenti.controller.ts | 25 +++++- .../server/controllers/servizio.controller.ts | 27 +++++++ 6 files changed, 145 insertions(+), 27 deletions(-) diff --git a/apps/infoalloggi/TODO b/apps/infoalloggi/TODO index 86da8de..a8051af 100644 --- a/apps/infoalloggi/TODO +++ b/apps/infoalloggi/TODO @@ -1,8 +1,5 @@ TODOS: - -NEW IDEA TODOS: - -Minimal Viable Product: + - Log email fallite e retry AFTER MVP: - TODO migrazione app router https://nextjs.org/docs/pages/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app diff --git a/apps/infoalloggi/src/components/acquisto_receipt.tsx b/apps/infoalloggi/src/components/acquisto_receipt.tsx index 6aca8f3..e7097cc 100644 --- a/apps/infoalloggi/src/components/acquisto_receipt.tsx +++ b/apps/infoalloggi/src/components/acquisto_receipt.tsx @@ -9,6 +9,7 @@ import { Card } from "~/components/ui/card"; import { PaymentMethodToString, type PaymentType } from "~/i18n/stripe"; import { formatCurrency } from "~/lib/utils"; import type { OrdiniOrdineId } from "~/schemas/public/Ordini"; +import type { Prezziario } from "~/schemas/public/Prezziario"; import { api } from "~/utils/api"; export function ReceiptGenerator({ @@ -83,6 +84,7 @@ export type PurchaseData = { sconto: number; amount_cent: number; + previewSaldo: Prezziario | null; }; interface ReceiptProps { @@ -149,17 +151,17 @@ function Receipt({ data }: ReceiptProps) {
- - - - +
Descrizione
+ + + - + + {data.previewSaldo && ( + + + + + + + )}
Oggetto Sconto applicato Importo
{data.packName} @@ -169,6 +171,21 @@ function Receipt({ data }: ReceiptProps) { {formatCurrency(data.amount_cent / 100)}
+ {data.previewSaldo.nome_it}{" "} + + pagamento differito, non è dovuto in questo momento + +   + {formatCurrency(data.previewSaldo.prezzo_cent / 100)} +
diff --git a/apps/infoalloggi/src/pages/servizio/pagamento.tsx b/apps/infoalloggi/src/pages/servizio/pagamento.tsx index b0a3115..5698716 100644 --- a/apps/infoalloggi/src/pages/servizio/pagamento.tsx +++ b/apps/infoalloggi/src/pages/servizio/pagamento.tsx @@ -5,10 +5,19 @@ import { useRouter } from "next/router"; import z from "zod"; import { AreaRiservataLayout } from "~/components/Layout"; import { Button } from "~/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "~/components/ui/card"; import { formatCurrency, redirectTo500 } from "~/lib/utils"; import type { NextPageWithLayout } from "~/pages/_app"; import { useTranslation } from "~/providers/I18nProvider"; import OrderTypeEnum from "~/schemas/public/OrderTypeEnum"; +import type { Prezziario } from "~/schemas/public/Prezziario"; import type { PreparedPaymentData } from "~/server/controllers/pagamenti.controller"; import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper"; import { zRinnovoId, zServizioId } from "~/server/utils/zod_types"; @@ -16,6 +25,7 @@ import { zRinnovoId, zServizioId } from "~/server/utils/zod_types"; type PreviewPurchaseProps = { stripeDisabled: boolean; data: PreparedPaymentData; + saldoPreview: Prezziario | null; }; /** * /servizio/pagamento?servizioId=|rinnovoId=&type= \ @@ -24,26 +34,26 @@ type PreviewPurchaseProps = { const PreviewPurchasePage: NextPageWithLayout = ({ stripeDisabled, data, + saldoPreview, }: PreviewPurchaseProps) => { const router = useRouter(); const { t } = useTranslation(); return ( -
-
-
-
-

{data.packName}

-

Cod: “{data.packid}”

-

- {formatCurrency(data.amount_cent / 100)}{" "} - {data.sconto > 0 && ( - (Sconto: {data.sconto}%) - )} -

-

{data.packDesc}

-
- +
+ + + {data.packName} + Cod: “{data.packid}” + + +

+ {formatCurrency(data.amount_cent / 100)}{" "} + {data.sconto > 0 && ( + (Sconto: {data.sconto}%) + )} +

+

{data.packDesc}

= ({ + {saldoPreview && ( +
+

{saldoPreview.nome_it}

+
+

+ {formatCurrency(saldoPreview.prezzo_cent / 100)} +

+

+ {formatCurrency(0)} +

+
+ + Il Saldo non è dovuto in questo momento, ma al termine della + ricerca. + +

+ Il saldo verrà aggiornato in base ai parametri del servizio. +

+
+ )} +
+ -
-
+ +
); }; @@ -125,6 +157,7 @@ export const getServerSideProps = (async (context) => { props: { stripeDisabled: flag || false, data: result.data, + saldoPreview: null, }, }; } @@ -147,10 +180,21 @@ export const getServerSideProps = (async (context) => { console.error("Error preparing payment", result.message); return redirectTo500; } + let saldoPreview: Prezziario | null = null; + if (type === OrderTypeEnum.Acconto) { + const saldoResult = await helper.trpc.pagamenti.previewSaldo.fetch({ + servizioId: parsedServizioId.data, + }); + if (saldoResult.success) { + saldoPreview = saldoResult.pack; + } + } + return { props: { stripeDisabled: flag || false, data: result.data, + saldoPreview, }, }; }) satisfies GetServerSideProps; diff --git a/apps/infoalloggi/src/server/api/routers/pagamenti.ts b/apps/infoalloggi/src/server/api/routers/pagamenti.ts index 2892f16..022a90e 100644 --- a/apps/infoalloggi/src/server/api/routers/pagamenti.ts +++ b/apps/infoalloggi/src/server/api/routers/pagamenti.ts @@ -6,6 +6,7 @@ import { getPagamentoDataForCheckout, type PreparedPaymentData, preparePayment, + previewSaldo, } from "~/server/controllers/pagamenti.controller"; import { db } from "~/server/db"; @@ -69,6 +70,15 @@ export const pagamentiRouter = createTRPCRouter({ servizioId: input.servizioId, }); }), + previewSaldo: protectedProcedure + .input( + z.object({ + servizioId: zServizioId, + }), + ) + .query(async ({ input }) => { + return await previewSaldo(input.servizioId); + }), preparePaymentRinnovi: protectedProcedure .input( z.object({ diff --git a/apps/infoalloggi/src/server/controllers/pagamenti.controller.ts b/apps/infoalloggi/src/server/controllers/pagamenti.controller.ts index 87b5c7a..ecda41d 100644 --- a/apps/infoalloggi/src/server/controllers/pagamenti.controller.ts +++ b/apps/infoalloggi/src/server/controllers/pagamenti.controller.ts @@ -130,7 +130,11 @@ export const preparePayment = async ( } if (!servizio.isOkSaldo) { //find saldo pack based on servizio tipologia and permanenza/budget, if tipologia posizione - const saldo = await SaldoSolver(servizio); + const saldo = await SaldoSolver({ + tipologia: servizio.tipologia, + permanenza: servizio.permanenza, + budget: servizio.budget, + }); if (!saldo.success) { return { success: false, @@ -592,3 +596,22 @@ export const getPacksPerServizio = async ( }), }; }; + +export const previewSaldo = async ( + servizioId: ServizioServizioId, +): Promise<{ success: false; message: string } | GetPackResult> => { + const servizio = await getServizioById(servizioId); + if (!servizio) { + return { + success: false, + message: "Servizio non trovato", + }; + } + const saldo = await SaldoSolver({ + tipologia: servizio.tipologia, + permanenza: servizio.permanenza, + budget: servizio.budget, + }); + + return saldo; +}; diff --git a/apps/infoalloggi/src/server/controllers/servizio.controller.ts b/apps/infoalloggi/src/server/controllers/servizio.controller.ts index bd3e91d..252a8e3 100644 --- a/apps/infoalloggi/src/server/controllers/servizio.controller.ts +++ b/apps/infoalloggi/src/server/controllers/servizio.controller.ts @@ -1394,6 +1394,8 @@ export const getOrdineRicevutaData = async ({ "ordini.sconto", "ordini.created_at", "ordini.paymentmethod", + "ordini.type", + "ordini.servizio_id", ]) .innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid") .select("prezziario.nome_it") @@ -1427,6 +1429,30 @@ export const getOrdineRicevutaData = async ({ const parsedComune = parseDBComune(anagrafica.comune_residenza, comuni); const parsedNazione = parseDBNazione(anagrafica.nazione_residenza, nazioni); + let previewSaldo: Prezziario | null = null; + + if (data.type === OrderTypeEnum.Acconto && data.servizio_id) { + const servizio = await getServizioById(data.servizio_id); + if (servizio) { + const saldo = await SaldoSolver({ + tipologia: servizio.tipologia, + permanenza: servizio.permanenza, + budget: servizio.budget, + }); + if (saldo.success) { + previewSaldo = saldo.pack; + } else { + console.error( + `Errore nel calcolo del saldo per il servizio ${data.servizio_id}: ${saldo.message}, procedura getOrdineRicevutaData`, + ); + } + } else { + console.error( + `Servizio non trovato per ordine ${ordineId} con servizio_id ${data.servizio_id}, procedura getOrdineRicevutaData`, + ); + } + } + return { clienteIndirizzo: `${anagrafica.via_residenza} ${anagrafica.civico_residenza}\n${parsedComune?.nome} (${anagrafica.provincia_residenza}) ${anagrafica.cap_residenza}\n${parsedNazione?.nome}`, clienteNome: anagrafica.username, @@ -1439,6 +1465,7 @@ export const getOrdineRicevutaData = async ({ ordineId: data.ordine_id, paymentMethod: (data.paymentmethod || "manual") as PaymentType, + previewSaldo, }; } catch (e) { throw new TRPCError({