594 lines
17 KiB
TypeScript
594 lines
17 KiB
TypeScript
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;
|
|
};
|
|
type preparePaymentInput =
|
|
| {
|
|
servizioId: ServizioServizioId;
|
|
type: Exclude<OrderTypeEnum, OrderTypeEnum.Rinnovo>;
|
|
}
|
|
| {
|
|
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<number, PrezziarioIdprezziario> = {
|
|
// 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<GetPackResult | GetPackError> => {
|
|
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<GetPackResult | GetPackError> => {
|
|
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<GetPackResult | GetPackError> => {
|
|
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<ServizioPacks> => {
|
|
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,
|
|
}),
|
|
};
|
|
};
|