import { TRPCError } from "@trpc/server"; import { add, intervalToDuration } from "date-fns"; import OrderTypeEnum from "~/schemas/public/OrderTypeEnum"; import type { OrdiniOrdineId } from "~/schemas/public/Ordini"; import type { Prezziario, PrezziarioIdprezziario, } from "~/schemas/public/Prezziario"; import type { RinnoviId } from "~/schemas/public/Rinnovi"; import type { ServizioServizioId } from "~/schemas/public/Servizio"; import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum"; import { db } from "../db"; import { createOrdine } from "../services/ordini.service"; import { CONDIZIONI_DEFAULT, getPrezziarioByIdHandler, } from "../services/prezziario.service"; import { getServizioById } from "../services/servizio.service"; export type PreparedPaymentData = { ordine_id: OrdiniOrdineId; packid: PrezziarioIdprezziario; amount_cent: number; sconto: number; packName: string; packDesc: string; condizioni: string; }; export type preparePaymentInput = | { servizioId: ServizioServizioId; type: Exclude; } | { rinnovoId: RinnoviId; type: OrderTypeEnum.Rinnovo; }; export const preparePayment = async ( props: preparePaymentInput, ): Promise< | { success: true; data: PreparedPaymentData } | { success: false; message: string } > => { try { switch (props.type) { case OrderTypeEnum.Acconto: case OrderTypeEnum.Saldo: case OrderTypeEnum.Consulenza: case OrderTypeEnum.Altro: { const { servizioId, type } = props; const servizio = await getServizioById(servizioId); const inactiveOrders = await db .selectFrom("ordini") .selectAll() .where("servizio_id", "=", servizioId) .where("isActive", "=", false) .where("paymentstatus", "is", null) .execute(); switch (type) { case OrderTypeEnum.Acconto: { if (!servizio.isOkAcconto) { const acconto = await AccontoSolver(servizio.tipologia); if (!acconto.success) { return { success: false, message: `Acconto non trovato per il servizio: ${acconto.message}`, }; } // servizio sta aspettando attivazione acconto const preExistingAcconto = inactiveOrders.find( (ordine) => ordine.type === OrderTypeEnum.Acconto, ); if (preExistingAcconto) { // acconto già creato, procedere con questo return { success: true, data: { ordine_id: preExistingAcconto.ordine_id, packid: preExistingAcconto.packid, amount_cent: preExistingAcconto.amount_cent, sconto: preExistingAcconto.sconto, packName: acconto.pack.nome_it, packDesc: acconto.pack.desc_it, condizioni: acconto.pack.testo_condizioni || CONDIZIONI_DEFAULT, }, }; } // nessun acconto preesistente, crearne uno nuovo const ordine = await createOrdine({ servizio_id: servizioId, amount_cent: acconto.pack.prezzo_cent, packid: acconto.pack.idprezziario, type: OrderTypeEnum.Acconto, userid: servizio.user_id, }); return { success: true, data: { ordine_id: ordine.ordine_id, packid: acconto.pack.idprezziario, amount_cent: acconto.pack.prezzo_cent, sconto: 0, packName: acconto.pack.nome_it, packDesc: acconto.pack.desc_it, condizioni: acconto.pack.testo_condizioni || CONDIZIONI_DEFAULT, }, }; } return { success: false, message: "Il servizio ha già un acconto attivo, non è possibile preparare un nuovo pagamento", }; } case OrderTypeEnum.Saldo: { if (!servizio.isOkAcconto) { return { success: false, message: "Non è possibile procedere con il pagamento del saldo prima di aver attivato l'acconto", }; } if (!servizio.isOkSaldo) { //find saldo pack based on servizio tipologia and permanenza/budget, if tipologia posizione const saldo = await SaldoSolver(servizio); if (!saldo.success) { return { success: false, message: `Saldo non trovato per il servizio: ${saldo.message}`, }; } const preExistingSaldo = inactiveOrders.find( (ordine) => ordine.type === OrderTypeEnum.Saldo, ); if (preExistingSaldo) { // saldo già creato, procedere con questo return { success: true, data: { ordine_id: preExistingSaldo.ordine_id, packid: preExistingSaldo.packid, amount_cent: preExistingSaldo.amount_cent, sconto: preExistingSaldo.sconto, packName: saldo.pack.nome_it, packDesc: saldo.pack.desc_it, condizioni: saldo.pack.testo_condizioni || CONDIZIONI_DEFAULT, }, }; } // nessun saldo preesistente, crearne uno nuovo const ordine = await createOrdine({ servizio_id: servizioId, amount_cent: saldo.pack.prezzo_cent, packid: saldo.pack.idprezziario, type: OrderTypeEnum.Saldo, userid: servizio.user_id, }); return { success: true, data: { ordine_id: ordine.ordine_id, packid: saldo.pack.idprezziario, amount_cent: saldo.pack.prezzo_cent, sconto: 0, packName: saldo.pack.nome_it, packDesc: saldo.pack.desc_it, condizioni: saldo.pack.testo_condizioni || CONDIZIONI_DEFAULT, }, }; } return { success: false, message: "Il servizio ha già un saldo attivo, non è possibile preparare un nuovo pagamento", }; } case OrderTypeEnum.Consulenza: { if (!servizio.isOkAcconto || !servizio.isOkSaldo) { return { success: false, message: "Non è possibile procedere con il pagamento della consulenza prima di aver attivato acconto e saldo", }; } if (!servizio.isOkConsulenza) { const existingConsulenza = inactiveOrders.find( (ordine) => ordine.type === OrderTypeEnum.Consulenza, ); if (existingConsulenza) { // consulenza già creato, procedere con questo const consulenzaPack = await getPrezziarioByIdHandler( existingConsulenza.packid, ); if (!consulenzaPack) { return { success: false, message: `Pack della consulenza non trovato: ${existingConsulenza.packid}`, }; } return { success: true, data: { ordine_id: existingConsulenza.ordine_id, packid: existingConsulenza.packid, amount_cent: existingConsulenza.amount_cent, sconto: existingConsulenza.sconto, packName: consulenzaPack.nome_it, packDesc: consulenzaPack.desc_it, condizioni: consulenzaPack.testo_condizioni || CONDIZIONI_DEFAULT, }, }; } return { success: false, message: "Il servizio non ha un pacchetto di consulenza, è necessario inserirlo prima di procedere", }; } return { success: false, message: "Il servizio ha già una consulenza attiva, non è possibile preparare un nuovo pagamento", }; } case OrderTypeEnum.Altro: { const existingAltro = inactiveOrders.find( (ordine) => ordine.type === OrderTypeEnum.Altro, ); if (existingAltro) { // altro già creato, procedere con questo const altroPack = await getPrezziarioByIdHandler( existingAltro.packid, ); if (!altroPack) { return { success: false, message: `Pack dell'altro non trovato: ${existingAltro.packid}`, }; } return { success: true, data: { ordine_id: existingAltro.ordine_id, packid: existingAltro.packid, amount_cent: existingAltro.amount_cent, sconto: existingAltro.sconto, packName: altroPack.nome_it, packDesc: altroPack.desc_it, condizioni: altroPack.testo_condizioni || CONDIZIONI_DEFAULT, }, }; } return { success: false, message: "La preparazione del pagamento per questa tipologia non è supportata", }; } default: throw new Error( `Tipo di ordine non valido: ${type satisfies never}`, ); } } case OrderTypeEnum.Rinnovo: { const { rinnovoId } = props; const ordiniRinnovo = await db .selectFrom("ordini") .selectAll() .where("rinnovo_id", "=", rinnovoId) .where("type", "=", OrderTypeEnum.Rinnovo) .execute(); const alreadyPaid = ordiniRinnovo.some((ordine) => ordine.isActive); if (alreadyPaid) { return { success: false, message: "Il rinnovo ha già un ordine attivo, non è possibile preparare un nuovo pagamento", }; } const unusedOrdine = ordiniRinnovo.find((ordine) => !ordine.isActive); if (unusedOrdine) { // rinnovo già creato, procedere con questo const rinnovoPack = await getPrezziarioByIdHandler( unusedOrdine.packid, ); if (!rinnovoPack) { return { success: false, message: `Pack del rinnovo non trovato: ${unusedOrdine.packid}`, }; } return { success: true, data: { ordine_id: unusedOrdine.ordine_id, packid: unusedOrdine.packid, amount_cent: unusedOrdine.amount_cent, sconto: unusedOrdine.sconto, packName: rinnovoPack.nome_it, packDesc: rinnovoPack.desc_it, condizioni: rinnovoPack.testo_condizioni || CONDIZIONI_DEFAULT, }, }; } // nessun rinnovo preesistente, crearne uno nuovo const rinnovo = await db .selectFrom("rinnovi") .selectAll() .where("id", "=", rinnovoId) .executeTakeFirstOrThrow( () => new Error(`Rinnovo non trovato - id: ${rinnovoId}`), ); const pack = await RinnovoSolver(rinnovo.decorrenza, rinnovo.scadenza); if (!pack.success) { return { success: false, message: `Pack non trovato per il rinnovo: ${pack.message}`, }; } const ordine = await createOrdine({ rinnovo_id: rinnovoId, amount_cent: pack.pack.prezzo_cent, packid: pack.pack.idprezziario, type: OrderTypeEnum.Rinnovo, userid: rinnovo.userId, }); return { success: true, data: { ordine_id: ordine.ordine_id, packid: pack.pack.idprezziario, amount_cent: pack.pack.prezzo_cent, sconto: 0, packName: pack.pack.nome_it, packDesc: pack.pack.desc_it, condizioni: pack.pack.testo_condizioni || CONDIZIONI_DEFAULT, }, }; } } } catch (e) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Errore durante la preparazione del pagamento: ${(e as Error).message}`, }); } }; export const getPagamentoDataForCheckout = async (ordineId: OrdiniOrdineId) => { try { return await db .selectFrom("ordini") .select([ "ordini.ordine_id", "ordini.packid", "ordini.amount_cent", "ordini.servizio_id", "ordini.userid", "ordini.sconto", ]) .innerJoin("users", "users.id", "ordini.userid") .select(["users.username", "users.email"]) .innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid") .select(["prezziario.nome_it", "prezziario.desc_it"]) .where("ordini.ordine_id", "=", ordineId) .executeTakeFirstOrThrow( () => new Error(`Payment not found - ordineId: ${ordineId}`), ); } catch (e) { throw new TRPCError({ code: "NOT_FOUND", message: `Payment not found: ${(e as Error).message}`, }); } }; // const SaldoTransitorioMapping: Record = { // 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, // }; // const ConsulenzaPriceMapping = { // "4 + 4": "CONS_4+4" as PrezziarioIdprezziario, // "30 Giorni": "CONS_30GG" as PrezziarioIdprezziario, // Agevolato: "CONS_3+2" as PrezziarioIdprezziario, // Commerciale: "CONS_COMM" as PrezziarioIdprezziario, // Foresteria: "CONS_COMM" as PrezziarioIdprezziario, // "Transi.Age": "CONS_3+2" as PrezziarioIdprezziario, // Transitorio: "CONS_TRAN" as PrezziarioIdprezziario, // }; type GetPackError = { success: false; message: string }; type GetPackResult = { success: true; pack: Prezziario }; type ServizioPacks = { acconto: GetPackResult | GetPackError; saldo: GetPackResult | GetPackError; }; export const AccontoSolver = async ( tipologia: TipologiaPosizioneEnum, ): Promise => { switch (tipologia) { case TipologiaPosizioneEnum.Transitorio: { const pack = await db .selectFrom("prezziario") .selectAll() .where("isActive", "=", true) .where("order_type", "=", OrderTypeEnum.Acconto) .where("service_type", "=", TipologiaPosizioneEnum.Transitorio) .limit(1) .executeTakeFirst(); if (!pack) { return { success: false, message: "Pack non trovato" }; } return { success: true, pack, }; } case TipologiaPosizioneEnum.Stabile: { const pack = await db .selectFrom("prezziario") .selectAll() .where("isActive", "=", true) .where("order_type", "=", OrderTypeEnum.Acconto) .where("service_type", "=", TipologiaPosizioneEnum.Stabile) .limit(1) .executeTakeFirst(); if (!pack) { return { success: false, message: "Pack non trovato" }; } return { success: true, pack, }; } default: return { success: false, message: "Seleziona tipologia" }; } }; export const SaldoSolver = async ({ tipologia, permanenza, budget, }: { tipologia: TipologiaPosizioneEnum; permanenza: number | null; budget: number; }): Promise => { switch (tipologia) { case TipologiaPosizioneEnum.Transitorio: { if (permanenza === null) { return { success: false, message: "Permanenza non selezionata", }; } const pack = await db .selectFrom("prezziario") .selectAll() .where("isActive", "=", true) .where("order_type", "=", OrderTypeEnum.Saldo) .where("service_type", "=", TipologiaPosizioneEnum.Transitorio) .where("service_variant", "=", permanenza) .limit(1) .executeTakeFirst(); if (!pack) { return { success: false, message: "Pack non trovato" }; } return { success: true, pack }; } case TipologiaPosizioneEnum.Stabile: { const budget_step = budget > 700 ? 2 : budget > 500 ? 1 : 0; const pack = await db .selectFrom("prezziario") .selectAll() .where("isActive", "=", true) .where("order_type", "=", OrderTypeEnum.Saldo) .where("service_type", "=", TipologiaPosizioneEnum.Stabile) .where("service_variant", "=", budget_step) .limit(1) .executeTakeFirst(); if (!pack) { return { success: false, message: "Pack non trovato" }; } return { success: true, pack, }; } default: return { success: false, message: "Seleziona tipologia" }; } }; function rinnovoPermanenzaVariant(permanenza: number): number { if (permanenza <= 1) return 0; if (permanenza <= 3) return 1; if (permanenza <= 6) return 2; if (permanenza <= 9) return 3; return 4; } const rinnovoDurationToPermanenza = (decorrenza: Date, scadenza: Date) => { const dur = intervalToDuration({ start: new Date(decorrenza), end: add(new Date(scadenza), { days: 1 }), }); return (dur.years || 0) * 12 + (dur.months || 0) + (dur.days ? 1 : 0); }; export const RinnovoSolver = async ( decorrenza: Date, scadenza: Date, ): Promise => { try { const permanenza = rinnovoDurationToPermanenza(decorrenza, scadenza); const pack = await db .selectFrom("prezziario") .selectAll() .where("isActive", "=", true) .where("order_type", "=", OrderTypeEnum.Rinnovo) .where("service_type", "=", TipologiaPosizioneEnum.Transitorio) .where("service_variant", "=", rinnovoPermanenzaVariant(permanenza)) .limit(1) .executeTakeFirst(); if (!pack) { return { success: false, message: "Pack non trovato" }; } return { success: true, pack }; } catch (error) { return { success: false, message: `Errore nel calcolo del rinnovo: ${(error as Error).message}`, }; } }; export const getPacksPerServizio = async ( servizioId: ServizioServizioId, ): Promise => { const servizio = await getServizioById(servizioId); if (!servizio) { throw new TRPCError({ code: "NOT_FOUND", message: "Servizio non trovato", }); } return { acconto: await AccontoSolver(servizio.tipologia), saldo: await SaldoSolver({ tipologia: servizio.tipologia, permanenza: servizio.permanenza, budget: servizio.budget, }), }; };