2025-08-07 14:50:40 +02:00
|
|
|
import type { z } from "zod/v4";
|
2025-08-28 18:27:07 +02:00
|
|
|
import type { ListOption } from "~/components/custom_ui/multiselect";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { getComuneFromIndex } from "~/i18n/comuni";
|
|
|
|
|
import { searchNazioneByIndex } from "~/i18n/nazioni";
|
|
|
|
|
|
|
|
|
|
export const getComuneFieldValue = (
|
2025-08-28 18:27:07 +02:00
|
|
|
fieldValue: string,
|
|
|
|
|
comuni_options: string[],
|
2025-08-04 17:45:44 +02:00
|
|
|
): ListOption | undefined => {
|
2025-08-28 18:27:07 +02:00
|
|
|
if (fieldValue !== "") {
|
|
|
|
|
const num = parseInt(fieldValue);
|
|
|
|
|
if (num <= comuni_options.length) {
|
|
|
|
|
return {
|
|
|
|
|
label: String(comuni_options[num as keyof typeof comuni_options]),
|
2025-08-29 16:18:32 +02:00
|
|
|
value: String(num),
|
2025-08-28 18:27:07 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getInizialiCF = (stringa: string) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const vocali = ["A", "E", "I", "O", "U"];
|
|
|
|
|
let iniziali = "";
|
|
|
|
|
for (const c of stringa) {
|
|
|
|
|
if (iniziali.length < 3) {
|
|
|
|
|
if (!vocali.includes(c)) {
|
|
|
|
|
iniziali += c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (iniziali.length < 3) {
|
|
|
|
|
for (const c of stringa) {
|
|
|
|
|
if (iniziali.length < 3) {
|
|
|
|
|
if (vocali.includes(c)) {
|
|
|
|
|
iniziali += c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (iniziali.length === 2) {
|
|
|
|
|
iniziali += "X";
|
|
|
|
|
}
|
|
|
|
|
return iniziali;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const lettere_mesi = "ABCDEHLMPRST";
|
|
|
|
|
const getMonthLetterCF = (month: number) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
return lettere_mesi[month];
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getDayCF = (day: number, sesso: string) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
if (sesso === "M") {
|
|
|
|
|
return day.toString().padStart(2, "0");
|
|
|
|
|
}
|
|
|
|
|
return (day + 40).toString().padStart(2, "0");
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AltMapping = "LMNPQRSTUV";
|
|
|
|
|
const getAlternativeCF = (input: string): string[] => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const substring = input.slice(0, Math.min(input.length, 15));
|
|
|
|
|
const result = [];
|
|
|
|
|
let wrk_string = substring;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
for (let i = substring.length - 1; i >= 0; i--) {
|
|
|
|
|
const char = substring[i];
|
|
|
|
|
if (char && !Number.isNaN(parseInt(char))) {
|
|
|
|
|
const str_arr = wrk_string.split("");
|
|
|
|
|
if (parseInt(char) > 9) {
|
|
|
|
|
throw new Error("Invalid CF");
|
|
|
|
|
}
|
|
|
|
|
// biome-ignore lint/style/noNonNullAssertion: <known length>
|
|
|
|
|
str_arr[i] = AltMapping[parseInt(char)]!;
|
|
|
|
|
wrk_string = str_arr.join("");
|
|
|
|
|
result.push(wrk_string);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const genCatasto = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
luogoNascita,
|
|
|
|
|
nazioneNascita,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
luogoNascita: string;
|
|
|
|
|
nazioneNascita: string;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
let catasto = "";
|
|
|
|
|
if (nazioneNascita === "0") {
|
|
|
|
|
if (luogoNascita !== "") {
|
|
|
|
|
const comune = getComuneFromIndex(parseInt(luogoNascita));
|
|
|
|
|
if (comune) {
|
|
|
|
|
catasto = comune.CATASTO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (nazioneNascita !== "") {
|
|
|
|
|
const nazione = searchNazioneByIndex(parseInt(nazioneNascita));
|
|
|
|
|
if (nazione) {
|
|
|
|
|
catasto = nazione.CATASTO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return catasto;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const gen15CF = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
cognome,
|
|
|
|
|
nome,
|
|
|
|
|
dataNascita,
|
|
|
|
|
sesso,
|
|
|
|
|
luogoNascita,
|
|
|
|
|
nazioneNascita,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
cognome: string;
|
|
|
|
|
nome: string;
|
|
|
|
|
dataNascita: Date;
|
|
|
|
|
sesso: string;
|
|
|
|
|
luogoNascita: string;
|
|
|
|
|
nazioneNascita: string;
|
2025-08-04 17:45:44 +02:00
|
|
|
}): string => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const data = new Date(dataNascita);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return `${getInizialiCF(cognome)}${getInizialiCF(nome)}${data.getFullYear().toString().slice(-2)}${getMonthLetterCF(data.getMonth())}${getDayCF(data.getDate(), sesso)}${genCatasto({ luogoNascita, nazioneNascita })}`;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const set1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
|
|
|
|
|
|
const set2 = "ABCDEFGHIJABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
|
|
|
|
|
|
const setpari = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
|
|
|
|
|
|
const setdisp = "BAKPLCQDREVOSFTGUHMINJWZYX";
|
|
|
|
|
|
|
|
|
|
const setcontrollo = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
|
|
|
|
|
|
const genCarattereControllo = (str: string) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
let s = 0;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
for (let i = 1; i <= 13; i += 2)
|
|
|
|
|
s += setpari.indexOf(set2.charAt(set1.indexOf(str.charAt(i))));
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
for (let i = 0; i <= 14; i += 2)
|
|
|
|
|
s += setdisp.indexOf(set2.charAt(set1.indexOf(str.charAt(i))));
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return setcontrollo[s % 26];
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const checkFiscalCodeValidity = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
path,
|
|
|
|
|
codiceFiscale,
|
|
|
|
|
cognome,
|
|
|
|
|
nome,
|
|
|
|
|
dataNascita,
|
|
|
|
|
sesso,
|
|
|
|
|
refinementContext,
|
|
|
|
|
luogoNascita,
|
|
|
|
|
nazioneNascita,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
path: string;
|
|
|
|
|
codiceFiscale: string;
|
|
|
|
|
cognome: string;
|
|
|
|
|
nome: string;
|
|
|
|
|
dataNascita: Date;
|
|
|
|
|
sesso: string;
|
|
|
|
|
refinementContext: z.RefinementCtx;
|
|
|
|
|
luogoNascita: string;
|
|
|
|
|
nazioneNascita: string;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const cognomeUpper = cognome.toUpperCase();
|
|
|
|
|
const nomeUpper = nome.toUpperCase();
|
|
|
|
|
const gen = gen15CF({
|
|
|
|
|
cognome: cognomeUpper,
|
|
|
|
|
dataNascita,
|
|
|
|
|
luogoNascita,
|
|
|
|
|
nazioneNascita,
|
2025-08-29 16:18:32 +02:00
|
|
|
nome: nomeUpper,
|
|
|
|
|
sesso,
|
2025-08-28 18:27:07 +02:00
|
|
|
});
|
|
|
|
|
const control = genCarattereControllo(gen);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (
|
|
|
|
|
gen !== codiceFiscale.slice(0, 15) &&
|
|
|
|
|
!(codiceFiscale in getAlternativeCF(gen))
|
|
|
|
|
) {
|
|
|
|
|
console.log("gen", gen);
|
|
|
|
|
console.log("codiceFiscale", codiceFiscale);
|
|
|
|
|
console.log("alternative", getAlternativeCF(gen));
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice fiscale non valido",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (control !== codiceFiscale.slice(-1)) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice di controllo non valido",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (codiceFiscale.length !== 16) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Inserisci un codice fiscale valido",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
!/^(?:[A-Z][AEIOU][AEIOUX]|[AEIOU]X{2}|[B-DF-HJ-NP-TV-Z]{2}[A-Z]){2}(?:[\dLMNP-V]{2}(?:[A-EHLMPR-T](?:[04LQ][1-9MNP-V]|[15MR][\dLMNP-V]|[26NS][0-8LMNP-U])|[DHPS][37PT][0L]|[ACELMRT][37PT][01LM]|[AC-EHLMPR-T][26NS][9V])|(?:[02468LNQSU][048LQU]|[13579MPRTV][26NS])B[26NS][9V])(?:[A-MZ][1-9MNP-V][\dLMNP-V]{2}|[A-M][0L](?:[1-9MNP-V][\dLMNP-V]|[0L][1-9MNP-V]))[A-Z]$/i.exec(
|
|
|
|
|
codiceFiscale,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Inserisci un codice fiscale valido",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (codiceFiscale.slice(0, 3) !== getInizialiCF(cognomeUpper)) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice fiscale incompatibile con cognome inserito",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (codiceFiscale.slice(3, 6) !== getInizialiCF(nomeUpper)) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice fiscale incompatibile con nome inserito",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (
|
|
|
|
|
codiceFiscale.slice(6, 8) !== dataNascita.getFullYear().toString().slice(-2)
|
|
|
|
|
) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice fiscale incompatibile con anno di nascita",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
if (codiceFiscale.slice(8, 9) !== getMonthLetterCF(dataNascita.getMonth())) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice fiscale incompatibile con mese di nascita",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (codiceFiscale.slice(9, 11) !== getDayCF(dataNascita.getDate(), sesso)) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice fiscale incompatibile con giorno di nascita",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (nazioneNascita === "0") {
|
|
|
|
|
if (luogoNascita !== "") {
|
|
|
|
|
const comune = getComuneFromIndex(parseInt(luogoNascita));
|
|
|
|
|
if (comune && codiceFiscale.slice(11, 15) !== comune.CATASTO) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice fiscale incompatibile con luogo di nascita",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (nazioneNascita !== "") {
|
|
|
|
|
const nazione = searchNazioneByIndex(parseInt(nazioneNascita));
|
|
|
|
|
if (nazione && codiceFiscale.slice(11, 15) !== nazione.CATASTO) {
|
|
|
|
|
refinementContext.issues.push({
|
|
|
|
|
code: "custom",
|
2025-08-29 16:18:32 +02:00
|
|
|
input: "",
|
2025-08-28 18:27:07 +02:00
|
|
|
message: "Codice fiscale incompatibile con luogo di nascita",
|
|
|
|
|
path: [path],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|