UNPKG

@marketto/codice-fiscale-utils

Version:

TS & JS utilities to handle Italian Codice Fiscale

262 lines (244 loc) 7.64 kB
import dayjs from "dayjs"; import { IBelfioreConnector, BelfiorePlace, } from "@marketto/belfiore-connector"; import { CRC_OFFSET, CRC_SIZE, DATE_OFFSET, DATE_SIZE, FIRSTNAME_OFFSET, FIRSTNAME_SIZE, GENDER_OFFSET, GENDER_SIZE, LASTNAME_OFFSET, LASTNAME_SIZE, PLACE_OFFSET, PLACE_SIZE, YEAR_OFFSET, YEAR_SIZE, } from "../const/cf-offsets.const"; import { DateUtils, MultiFormatDate } from "../date-utils/"; import type IPersonalInfo from "../interfaces/personal-info.interface"; import type Genders from "../types/genders.type"; import CheckDigitizer from "./check-digitizer.class"; import Parser from "./parser.class"; import Pattern from "./pattern.class"; import type IMismatchVerboseErrors from "../interfaces/mismatch-verbose-errors.interface"; export default class CFMismatchValidator { private pattern: Pattern; private parser: Parser; constructor( belfioreConnector: IBelfioreConnector, private readonly codiceFiscale: string ) { this.pattern = new Pattern(belfioreConnector); this.parser = new Parser(belfioreConnector); } private get hasLastName() { return this.codiceFiscale?.length >= LASTNAME_OFFSET + LASTNAME_SIZE; } private get hasFirstName() { return this.codiceFiscale?.length >= FIRSTNAME_OFFSET + FIRSTNAME_SIZE; } private get hasBirthYear() { return this.codiceFiscale?.length >= YEAR_OFFSET + YEAR_SIZE; } private get hasBirthDate() { return this.codiceFiscale?.length >= DATE_OFFSET + DATE_SIZE; } private get hasGender() { return this.codiceFiscale?.length >= GENDER_OFFSET + GENDER_SIZE; } private get hasBirthPlace() { return this.codiceFiscale?.length >= PLACE_OFFSET + PLACE_SIZE; } private get hasCRC() { return this.codiceFiscale?.length >= CRC_OFFSET + CRC_SIZE; } public async matchPersonalInfo( personalInfo: Omit<IPersonalInfo, "place"> & { place?: string | BelfiorePlace; } ): Promise<boolean> { return (await this.pattern.codiceFiscale(personalInfo)).test( this.codiceFiscale ); } public async mismatchPersonalInfo( personalInfo: Omit<IPersonalInfo, "place"> & { place?: string | BelfiorePlace; } ): Promise<boolean> { return !!( this.codiceFiscale && personalInfo && personalInfo.lastName && personalInfo.firstName && (personalInfo.date || (personalInfo.day && typeof personalInfo.month === "number" && personalInfo.year)) && personalInfo.gender && personalInfo.place && !(await this.matchPersonalInfo(personalInfo)) ); } public matchLastName(lastName?: string): boolean { return ( this.hasLastName && this.pattern.lastName(this.codiceFiscale).test(lastName || "") ); } public mismatchLastName(lastName?: string): boolean { return this.hasLastName && !!lastName && !this.matchLastName(lastName); } public matchFirstName(firstName: string): boolean { return ( this.hasFirstName && this.pattern.firstName(this.codiceFiscale).test(firstName || "") ); } public mismatchFirstName(firstName: string): boolean { return this.hasFirstName && !!firstName && !this.matchFirstName(firstName); } public matchBirthDate(birthDate: MultiFormatDate): boolean { if (this.hasBirthDate) { const parsedDate = DateUtils.parseDate(birthDate); const birthDateMatcher = this.pattern.date(this.codiceFiscale); return !!parsedDate && birthDateMatcher.test(parsedDate.toJSON()); } return false; } public mismatchBirthDate(birthDate: MultiFormatDate): boolean { return ( this.hasBirthYear && !!DateUtils.parseDate(birthDate) && !this.matchBirthDate(birthDate) ); } public matchGender(gender: Genders | string): boolean { return ( this.hasGender && this.pattern.gender(this.codiceFiscale).test(gender || "") ); } public mismatchGender(gender: Genders | string): boolean { return this.hasGender && !!gender && !this.matchGender(gender); } /** * @param birthPlace BirthPlace, place name or BelfioreCode */ public async matchBirthPlace( birthPlace: BelfiorePlace | string ): Promise<boolean> { if (this.hasBirthPlace && birthPlace) { const matcher = await this.pattern.place(this.codiceFiscale); const parsedBirthPlace = await this.parser.parsePlace(birthPlace); return !!parsedBirthPlace && matcher.test(parsedBirthPlace?.belfioreCode); } return false; } /** * @param birthPlace BirthPlace, place name or BelfioreCode */ public async mismatchBirthPlace( birthPlace: BelfiorePlace | string ): Promise<boolean> { return ( this.hasBirthPlace && !!birthPlace && !(await this.matchBirthPlace(birthPlace)) ); } /** * Check the given cf validity by form, birth date/place and digit code * @param codiceFiscale Complete CF to parse * @return Verbose errors */ public get errors(): Promise<IMismatchVerboseErrors | null> { return Promise.all([ this.parser.cfToBirthPlace(this.codiceFiscale, false), this.parser.cfToBirthPlace(this.codiceFiscale, true), ]) .then(([placeCheck, placeCreationExpirationCheck]) => ({ // Checking lastName validity ...(this.parser.cfToLastName(this.codiceFiscale) ? {} : { lastName: "MISSING_OR_INVALID_LAST_NAME" }), // Checking firstName validity ...(this.parser.cfToFirstName(this.codiceFiscale) ? {} : { firstName: "MISSING_OR_INVALID_FIRST_NAME" }), // Checking Date validity ...(this.parser.cfToBirthDate(this.codiceFiscale) ? {} : { date: "MISSING_OR_INVALID_DATE" }), // Checking Day validity ...(this.parser.cfToBirthDay(this.codiceFiscale) ? {} : { date: "MISSING_OR_INVALID_DAY" }), // Checking Month validity ...(typeof this.parser.cfToBirthMonth(this.codiceFiscale) === "number" ? {} : { date: "MISSING_OR_INVALID_MONTH" }), // Checking Year validity ...(this.parser.cfToBirthYear(this.codiceFiscale) ? {} : { date: "MISSING_OR_INVALID_YEAR" }), // Checking Gender validity ...(this.parser.cfToGender(this.codiceFiscale) ? {} : { gender: "MISSING_DAY" }), // Checking Place validity ...(placeCheck ? {} : { place: "MISSING_OR_INVALID_PLACE" }), // Checking Place Creation/Expiration vs Birthdate validity ...(placeCreationExpirationCheck ? {} : { place: "PLACE_EXPIRED_OR_NOT_YET_CREATED_ON_BIRTDATE", date: "BIRTHDATE_OUT_OF_BIRTH_PLACE_LIFE_RANGE", }), // Checking 16th char check digit validity ...(this.codiceFiscale ?.substring(CRC_OFFSET, CRC_OFFSET + CRC_SIZE) ?.toUpperCase() === CheckDigitizer.checkDigit(this.codiceFiscale) ? {} : { crc: "INVALID_CRC_CODE" }), // Checking length ...(this.hasCRC ? {} : { crc: "MISSING_CRC_CODE" }), })) .then((errors) => (Object.keys(errors).length ? errors : null)); } /** * Check the given cf validity by form, birth date/place and digit code * @return Generic or specific regular expression */ public get valid(): Promise<boolean> { return Promise.all([ this.pattern.codiceFiscale(), this.parser.cfToBirthPlace(this.codiceFiscale), ]).then( ([cfPattern, cfToBirthPlace]) => !( // Checking length ( !this.hasCRC || // Checking form validity !cfPattern.test(this.codiceFiscale) || // Checking 16th char check digit validity this.codiceFiscale ?.substring(CRC_OFFSET, CRC_OFFSET + CRC_SIZE) ?.toUpperCase() !== CheckDigitizer.checkDigit(this.codiceFiscale) || // Checking Birth date/place validity !cfToBirthPlace ) ) ); } public get invalid(): Promise<boolean> { return this.valid.then((isValid) => !!this.codiceFiscale && !isValid); } }