diff --git a/frontend/app/.server/domain/person-case/models.ts b/frontend/app/.server/domain/person-case/models.ts new file mode 100644 index 00000000..84d1be3e --- /dev/null +++ b/frontend/app/.server/domain/person-case/models.ts @@ -0,0 +1,76 @@ +export type ApplicantGender = Readonly<{ + id: string; + nameEn: string | null; + nameFr: string | null; +}>; + +export type LocalizedApplicantGender = Readonly<{ + id: string; + name: string | null; +}>; + +export type ApplicantPrimaryDocumentChoice = Readonly<{ + id: string; + nameEn: string | null; + nameFr: string | null; +}>; + +export type LocalizedApplicantPrimaryDocumentChoice = Readonly<{ + id: string; + name: string | null; +}>; + +export type ApplicantSecondaryDocumentChoice = Readonly<{ + id: string; + nameEn: string | null; + nameFr: string | null; +}>; + +export type LocalizedApplicantSecondaryDocumentChoice = Readonly<{ + id: string; + name: string | null; +}>; + +export type ApplicantHadSinOption = Readonly<{ + id: string; + nameEn: string | null; + nameFr: string | null; +}>; + +export type LocalizedApplicantHadSinOption = Readonly<{ + id: string; + name: string | null; +}>; + +export type ApplicationSubmissionScenario = Readonly<{ + id: string; + nameEn: string | null; + nameFr: string | null; +}>; + +export type LocalizedApplicationSubmissionScenario = Readonly<{ + id: string; + name: string | null; +}>; + +export type TypeOfApplicationToSubmit = Readonly<{ + id: string; + nameEn: string | null; + nameFr: string | null; +}>; + +export type LocalizedTypeOfApplicationToSubmit = Readonly<{ + id: string; + name: string | null; +}>; + +export type LanguageOfCorrespondence = Readonly<{ + id: string; + nameEn: string | null; + nameFr: string | null; +}>; + +export type LocalizedPreferredLanguage = Readonly<{ + id: string; + name: string | null; +}>; diff --git a/frontend/app/.server/domain/person-case/services/applicant-gender-service.ts b/frontend/app/.server/domain/person-case/services/applicant-gender-service.ts new file mode 100644 index 00000000..553a7d7b --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/applicant-gender-service.ts @@ -0,0 +1,62 @@ +import type { ApplicantGender, LocalizedApplicantGender } from '~/.server/domain/person-case/models'; +import { getOptionSet } from '~/.server/domain/person-case/services/data-service'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of applicant genders. + * + * @returns An array of applicant gender objects. + */ +export function getApplicantGenders(): readonly ApplicantGender[] { + const optionSet = getOptionSet('esdc_applicantgender'); + return optionSet.options.map((option) => ({ + id: option.value.toString(), + nameEn: option.labelEn, + nameFr: option.labelFr, + })); +} + +/** + * Retrieves a single applicant gender by its ID. + * + * @param id The ID of the applicant gender to retrieve. + * @returns The applicant gender object if found. + * @throws {AppError} If the applicant gender is not found. + */ +export function getApplicantGenderById(id: string): ApplicantGender { + const gender = getApplicantGenders().find((g) => g.id === id); + if (!gender) { + throw new AppError(`Applicant gender with ID '${id}' not found.`, ErrorCodes.NO_GENDER_FOUND); + } + return gender; +} + +/** + * Retrieves a list of applicant genders localized to the specified language. + * + * @param language The language to localize the gender names to. + * @returns An array of localized applicant gender objects. + */ +export function getLocalizedApplicantGenders(language: Language): LocalizedApplicantGender[] { + return getApplicantGenders().map((option) => ({ + id: option.id, + name: language === 'fr' ? option.nameFr : option.nameEn, + })); +} + +/** + * Retrieves a single localized applicant gender by its ID. + * + * @param id The ID of the applicant gender to retrieve. + * @param language The language to localize the gender name to. + * @returns The localized applicant gender object if found. + * @throws {AppError} If the applicant gender is not found. + */ +export function getLocalizedApplicantGenderById(id: string, language: Language): LocalizedApplicantGender { + const gender = getLocalizedApplicantGenders(language).find((g) => g.id === id); + if (!gender) { + throw new AppError(`Localized applicant gender with ID '${id}' not found.`, ErrorCodes.NO_GENDER_FOUND); + } + return gender; +} diff --git a/frontend/app/.server/domain/person-case/services/applicant-primary-document-service.ts b/frontend/app/.server/domain/person-case/services/applicant-primary-document-service.ts new file mode 100644 index 00000000..867bbf0f --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/applicant-primary-document-service.ts @@ -0,0 +1,74 @@ +import type { + ApplicantPrimaryDocumentChoice, + LocalizedApplicantPrimaryDocumentChoice, +} from '~/.server/domain/person-case/models'; +import { getOptionSet } from '~/.server/domain/person-case/services/data-service'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of applicant primary document choices. + * + * @returns An array of applicant primary document choice objects. + */ +export function getApplicantPrimaryDocumentChoices(): readonly ApplicantPrimaryDocumentChoice[] { + const optionSet = getOptionSet('esdc_applicantprimarydocumentchoices'); + return optionSet.options.map((option) => ({ + id: option.value.toString(), + nameEn: option.labelEn, + nameFr: option.labelFr, + })); +} + +/** + * Retrieves a single applicant primary document choice by its ID. + * + * @param id The ID of the applicant primary document choice to retrieve. + * @returns The applicant primary document choice object if found. + * @throws {AppError} If the choice is not found. + */ +export function getApplicantPrimaryDocumentChoiceById(id: string): ApplicantPrimaryDocumentChoice { + const choice = getApplicantPrimaryDocumentChoices().find((c) => c.id === id); + if (!choice) { + throw new AppError( + `Applicant primary document choice with ID '${id}' not found.`, + ErrorCodes.NO_APPLICANT_PRIMARY_DOCUMENT_CHOICE_FOUND, + ); + } + return choice; +} + +/** + * Retrieves a list of applicant primary document choices localized to the specified language. + * + * @param language The language to localize the choice names to. + * @returns An array of localized applicant primary document choice objects. + */ +export function getLocalizedApplicantPrimaryDocumentChoices(language: Language): LocalizedApplicantPrimaryDocumentChoice[] { + return getApplicantPrimaryDocumentChoices().map((option) => ({ + id: option.id, + name: language === 'fr' ? option.nameFr : option.nameEn, + })); +} + +/** + * Retrieves a single localized applicant primary document choice by its ID. + * + * @param id The ID of the applicant primary document choice to retrieve. + * @param language The language to localize the choice name to. + * @returns The localized applicant primary document choice object if found. + * @throws {AppError} If the choice is not found. + */ +export function getLocalizedApplicantPrimaryDocumentChoiceById( + id: string, + language: Language, +): LocalizedApplicantPrimaryDocumentChoice { + const choice = getLocalizedApplicantPrimaryDocumentChoices(language).find((c) => c.id === id); + if (!choice) { + throw new AppError( + `Localized applicant primary document choice with ID '${id}' not found.`, + ErrorCodes.NO_APPLICANT_PRIMARY_DOCUMENT_CHOICE_FOUND, + ); + } + return choice; +} diff --git a/frontend/app/.server/domain/person-case/services/applicant-secondary-document-service.ts b/frontend/app/.server/domain/person-case/services/applicant-secondary-document-service.ts new file mode 100644 index 00000000..db8f5946 --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/applicant-secondary-document-service.ts @@ -0,0 +1,74 @@ +import type { + ApplicantSecondaryDocumentChoice, + LocalizedApplicantSecondaryDocumentChoice, +} from '~/.server/domain/person-case/models'; +import { getOptionSet } from '~/.server/domain/person-case/services/data-service'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of applicant secondary document choices. + * + * @returns An array of applicant secondary document choice objects. + */ +export function getApplicantSecondaryDocumentChoices(): readonly ApplicantSecondaryDocumentChoice[] { + const optionSet = getOptionSet('esdc_applicantsecondarydocumentchoices'); + return optionSet.options.map((option) => ({ + id: option.value.toString(), + nameEn: option.labelEn, + nameFr: option.labelFr, + })); +} + +/** + * Retrieves a single applicant secondary document choice by its ID. + * + * @param id The ID of the applicant secondary document choice to retrieve. + * @returns The applicant secondary document choice object if found. + * @throws {AppError} If the choice is not found. + */ +export function getApplicantSecondaryDocumentChoiceById(id: string): ApplicantSecondaryDocumentChoice { + const choice = getApplicantSecondaryDocumentChoices().find((c) => c.id === id); + if (!choice) { + throw new AppError( + `Applicant secondary document choice with ID '${id}' not found.`, + ErrorCodes.NO_APPLICANT_SECONDARY_DOCUMENT_CHOICE_FOUND, + ); + } + return choice; +} + +/** + * Retrieves a list of applicant secondary document choices localized to the specified language. + * + * @param language The language to localize the choice names to. + * @returns An array of localized applicant secondary document choice objects. + */ +export function getLocalizedApplicantSecondaryDocumentChoices(language: Language): LocalizedApplicantSecondaryDocumentChoice[] { + return getApplicantSecondaryDocumentChoices().map((option) => ({ + id: option.id, + name: language === 'fr' ? option.nameFr : option.nameEn, + })); +} + +/** + * Retrieves a single localized applicant secondary document choice by its ID. + * + * @param id The ID of the applicant secondary document choice to retrieve. + * @param language The language to localize the choice name to. + * @returns The localized applicant secondary document choice object if found. + * @throws {AppError} If the choice is not found. + */ +export function getLocalizedApplicantSecondaryDocumentChoiceById( + id: string, + language: Language, +): LocalizedApplicantSecondaryDocumentChoice { + const choice = getLocalizedApplicantSecondaryDocumentChoices(language).find((c) => c.id === id); + if (!choice) { + throw new AppError( + `Localized applicant secondary document choice with ID '${id}' not found.`, + ErrorCodes.NO_APPLICANT_SECONDARY_DOCUMENT_CHOICE_FOUND, + ); + } + return choice; +} diff --git a/frontend/app/.server/domain/person-case/services/applicant-sin-service.ts b/frontend/app/.server/domain/person-case/services/applicant-sin-service.ts new file mode 100644 index 00000000..42e606a0 --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/applicant-sin-service.ts @@ -0,0 +1,65 @@ +import type { ApplicantHadSinOption, LocalizedApplicantHadSinOption } from '~/.server/domain/person-case/models'; +import { getOptionSet } from '~/.server/domain/person-case/services/data-service'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of applicant had SIN options. + * + * @returns An array of applicant had SIN option objects. + */ +export function getApplicantHadSinOptions(): readonly ApplicantHadSinOption[] { + const optionSet = getOptionSet('esdc_didtheapplicanteverhadasinnumber'); + return optionSet.options.map((option) => ({ + id: option.value.toString(), + nameEn: option.labelEn, + nameFr: option.labelFr, + })); +} + +/** + * Retrieves a single applicant had SIN option by its ID. + * + * @param id The ID of the applicant had SIN option to retrieve. + * @returns The applicant had SIN option object if found. + * @throws {AppError} If the option is not found. + */ +export function getApplicantHadSinOptionById(id: string): ApplicantHadSinOption { + const option = getApplicantHadSinOptions().find((o) => o.id === id); + if (!option) { + throw new AppError(`Applicant had SIN option with ID '${id}' not found.`, ErrorCodes.NO_APPLICANT_HAD_SIN_OPTION_FOUND); + } + return option; +} + +/** + * Retrieves a list of applicant had SIN options localized to the specified language. + * + * @param language The language to localize the option names to. + * @returns An array of localized applicant had SIN option objects. + */ +export function getLocalizedApplicantHadSinOptions(language: Language): LocalizedApplicantHadSinOption[] { + return getApplicantHadSinOptions().map((option) => ({ + id: option.id, + name: language === 'fr' ? option.nameFr : option.nameEn, + })); +} + +/** + * Retrieves a single localized applicant had SIN option by its ID. + * + * @param id The ID of the applicant had SIN option to retrieve. + * @param language The language to localize the option name to. + * @returns The localized applicant had SIN option object if found. + * @throws {AppError} If the option is not found. + */ +export function getLocalizedApplicantHadSinOptionById(id: string, language: Language): LocalizedApplicantHadSinOption { + const option = getLocalizedApplicantHadSinOptions(language).find((o) => o.id === id); + if (!option) { + throw new AppError( + `Localized applicant had SIN option with ID '${id}' not found.`, + ErrorCodes.NO_APPLICANT_HAD_SIN_OPTION_FOUND, + ); + } + return option; +} diff --git a/frontend/app/.server/domain/person-case/services/application-submission-scenario.ts b/frontend/app/.server/domain/person-case/services/application-submission-scenario.ts new file mode 100644 index 00000000..0c36e7b8 --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/application-submission-scenario.ts @@ -0,0 +1,74 @@ +import type { + ApplicationSubmissionScenario, + LocalizedApplicationSubmissionScenario, +} from '~/.server/domain/person-case/models'; +import { getOptionSet } from '~/.server/domain/person-case/services/data-service'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of application submission scenarios. + * + * @returns An array of application submission scenario objects. + */ +export function getApplicationSubmissionScenarios(): readonly ApplicationSubmissionScenario[] { + const optionSet = getOptionSet('esdc_applicationsubmissionscenarios'); + return optionSet.options.map((option) => ({ + id: option.value.toString(), + nameEn: option.labelEn, + nameFr: option.labelFr, + })); +} + +/** + * Retrieves a single application submission scenario by its ID. + * + * @param id The ID of the application submission scenario to retrieve. + * @returns The application submission scenario object if found. + * @throws {AppError} If the scenario is not found. + */ +export function getApplicationSubmissionScenarioById(id: string): ApplicationSubmissionScenario { + const scenario = getApplicationSubmissionScenarios().find((s) => s.id === id); + if (!scenario) { + throw new AppError( + `Application submission scenario with ID '${id}' not found.`, + ErrorCodes.NO_APPLICATION_SUBMISSION_SCENARIO_FOUND, + ); + } + return scenario; +} + +/** + * Retrieves a list of application submission scenarios localized to the specified language. + * + * @param language The language to localize the scenario names to. + * @returns An array of localized application submission scenario objects. + */ +export function getLocalizedApplicationSubmissionScenarios(language: Language): LocalizedApplicationSubmissionScenario[] { + return getApplicationSubmissionScenarios().map((option) => ({ + id: option.id, + name: language === 'fr' ? option.nameFr : option.nameEn, + })); +} + +/** + * Retrieves a single localized application submission scenario by its ID. + * + * @param id The ID of the application submission scenario to retrieve. + * @param language The language to localize the scenario name to. + * @returns The localized application submission scenario object if found. + * @throws {AppError} If the scenario is not found. + */ +export function getLocalizedApplicationSubmissionScenarioById( + id: string, + language: Language, +): LocalizedApplicationSubmissionScenario { + const scenario = getLocalizedApplicationSubmissionScenarios(language).find((s) => s.id === id); + if (!scenario) { + throw new AppError( + `Localized application submission scenario with ID '${id}' not found.`, + ErrorCodes.NO_APPLICATION_SUBMISSION_SCENARIO_FOUND, + ); + } + return scenario; +} diff --git a/frontend/app/.server/domain/person-case/services/application-type-service.ts b/frontend/app/.server/domain/person-case/services/application-type-service.ts new file mode 100644 index 00000000..0cd1c7d6 --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/application-type-service.ts @@ -0,0 +1,68 @@ +import type { LocalizedTypeOfApplicationToSubmit, TypeOfApplicationToSubmit } from '~/.server/domain/person-case/models'; +import { getOptionSet } from '~/.server/domain/person-case/services/data-service'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of types of applications to submit. + * + * @returns An array of type of application to submit objects. + */ +export function getTypesOfApplicationToSubmit(): readonly TypeOfApplicationToSubmit[] { + const optionSet = getOptionSet('esdc_typeofapplicationtosubmit'); + return optionSet.options.map((option) => ({ + id: option.value.toString(), + nameEn: option.labelEn, + nameFr: option.labelFr, + })); +} + +/** + * Retrieves a single type of application to submit by its ID. + * + * @param id The ID of the type of application to submit to retrieve. + * @returns The type of application to submit object if found. + * @throws {AppError} If the type is not found. + */ +export function getTypeOfApplicationToSubmitById(id: string): TypeOfApplicationToSubmit { + const type = getTypesOfApplicationToSubmit().find((t) => t.id === id); + if (!type) { + throw new AppError( + `Type of application to submit with ID '${id}' not found.`, + ErrorCodes.NO_TYPE_OF_APPLICATION_TO_SUBMIT_FOUND, + ); + } + return type; +} + +/** + * Retrieves a list of types of applications to submit localized to the specified language. + * + * @param language The language to localize the type names to. + * @returns An array of localized type of application to submit objects. + */ +export function getLocalizedTypesOfApplicationToSubmit(language: Language): LocalizedTypeOfApplicationToSubmit[] { + return getTypesOfApplicationToSubmit().map((option) => ({ + id: option.id, + name: language === 'fr' ? option.nameFr : option.nameEn, + })); +} + +/** + * Retrieves a single localized type of application to submit by its ID. + * + * @param id The ID of the type of application to submit to retrieve. + * @param language The language to localize the type name to. + * @returns The localized type of application to submit object if found. + * @throws {AppError} If the type is not found. + */ +export function getLocalizedTypeOfApplicationToSubmitById(id: string, language: Language): LocalizedTypeOfApplicationToSubmit { + const type = getLocalizedTypesOfApplicationToSubmit(language).find((t) => t.id === id); + if (!type) { + throw new AppError( + `Localized type of application to submit with ID '${id}' not found.`, + ErrorCodes.NO_TYPE_OF_APPLICATION_TO_SUBMIT_FOUND, + ); + } + return type; +} diff --git a/frontend/app/.server/domain/person-case/services/data-service.ts b/frontend/app/.server/domain/person-case/services/data-service.ts new file mode 100644 index 00000000..f724990a --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/data-service.ts @@ -0,0 +1,24 @@ +import type { ReadonlyDeep } from 'type-fest'; + +import globalOptionSetsData from '~/.server/resources/esdc-global-option-sets.json'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +type OptionSet = ReadonlyDeep<(typeof globalOptionSetsData)[number]>; + +/** + * Retrieves an option set by its name. + * + * @param optionSetName The name of the option set to retrieve. + * @returns The option set if found. + * @throws {AppError} If the option set is not found. + */ +export function getOptionSet(optionSetName: string): OptionSet { + const optionSet = globalOptionSetsData.find((os) => os.name === optionSetName); + + if (!optionSet) { + throw new AppError(`Option set '${optionSetName}' not found.`, ErrorCodes.NO_OPTION_SET_FOUND); + } + + return optionSet; +} diff --git a/frontend/app/.server/domain/person-case/services/index.ts b/frontend/app/.server/domain/person-case/services/index.ts new file mode 100644 index 00000000..a4c28809 --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/index.ts @@ -0,0 +1,7 @@ +export * as applicantGenderService from './applicant-gender-service'; +export * as applicantPrimaryDocumentService from './applicant-primary-document-service'; +export * as applicantSecondaryDocumentService from './applicant-secondary-document-service'; +export * as applicantSinService from './applicant-sin-service'; +export * as applicationSubmissionScenario from './application-submission-scenario'; +export * as applicationTypeService from './application-type-service'; +export * as languageCorrespondenceService from './language-correspondence-service'; diff --git a/frontend/app/.server/domain/person-case/services/language-correspondence-service.ts b/frontend/app/.server/domain/person-case/services/language-correspondence-service.ts new file mode 100644 index 00000000..7e742c43 --- /dev/null +++ b/frontend/app/.server/domain/person-case/services/language-correspondence-service.ts @@ -0,0 +1,62 @@ +import type { LanguageOfCorrespondence, LocalizedPreferredLanguage } from '~/.server/domain/person-case/models'; +import { getOptionSet } from '~/.server/domain/person-case/services/data-service'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of languages of correspondence. + * + * @returns An array of language of correspondence objects. + */ +export function getLanguagesOfCorrespondence(): readonly LanguageOfCorrespondence[] { + const optionSet = getOptionSet('esdc_languageofcorrespondence'); + return optionSet.options.map((option) => ({ + id: option.value.toString(), + nameEn: option.labelEn, + nameFr: option.labelFr, + })); +} + +/** + * Retrieves a single language of correspondence by its ID. + * + * @param id The ID of the language of correspondence to retrieve. + * @returns The language of correspondence object if found. + * @throws {AppError} If the language of correspondence is not found. + */ +export function getLanguageOfCorrespondenceById(id: string): LanguageOfCorrespondence { + const languagesOfCorrespondence = getLanguagesOfCorrespondence().find((l) => l.id === id); + if (!languagesOfCorrespondence) { + throw new AppError(`Language of correspondence with ID '${id}' not found.`, ErrorCodes.NO_LANGUAGE_FOUND); + } + return languagesOfCorrespondence; +} + +/** + * Retrieves a list of languages of correspondence localized to the specified language. + * + * @param language The language to localize the language names to. + * @returns An array of localized language of correspondence objects. + */ +export function getLocalizedLanguageOfCorrespondence(language: Language): LocalizedPreferredLanguage[] { + return getLanguagesOfCorrespondence().map((option) => ({ + id: option.id, + name: language === 'fr' ? option.nameFr : option.nameEn, + })); +} + +/** + * Retrieves a single localized language of correspondence by its ID. + * + * @param id The ID of the language of correspondence to retrieve. + * @param language The language to localize the language name to. + * @returns The localized language of correspondence object if found. + * @throws {AppError} If the language of correspondence is not found. + */ +export function getLocalizedLanguageOfCorrespondenceById(id: string, language: Language): LocalizedPreferredLanguage { + const languagesOfCorrespondence = getLocalizedLanguageOfCorrespondence(language).find((l) => l.id === id); + if (!languagesOfCorrespondence) { + throw new AppError(`Localized language of correspondence with ID '${id}' not found.`, ErrorCodes.NO_LANGUAGE_FOUND); + } + return languagesOfCorrespondence; +} diff --git a/frontend/app/.server/services/locale-data-service.ts b/frontend/app/.server/services/locale-data-service.ts deleted file mode 100644 index a948e207..00000000 --- a/frontend/app/.server/services/locale-data-service.ts +++ /dev/null @@ -1,709 +0,0 @@ -import type { ReadonlyDeep } from 'type-fest'; - -import { serverEnvironment } from '~/.server/environment'; -import countriesData from '~/.server/resources/esdc-countries.json'; -import globalOptionSetsData from '~/.server/resources/esdc-global-option-sets.json'; -import provincesData from '~/.server/resources/esdc-provinces.json'; -import { AppError } from '~/errors/app-error'; -import { ErrorCodes } from '~/errors/error-codes'; - -type OptionSet = ReadonlyDeep<(typeof globalOptionSetsData)[number]>; - -/** - * Retrieves an option set by its name. - * - * @param optionSetName The name of the option set to retrieve. - * @returns The option set if found. - * @throws {AppError} If the option set is not found. - */ -function getOptionSet(optionSetName: string): OptionSet { - const optionSet = globalOptionSetsData.find((os) => os.name === optionSetName); - - if (!optionSet) { - throw new AppError(`Option set '${optionSetName}' not found.`, ErrorCodes.NO_OPTION_SET_FOUND); - } - - return optionSet; -} - -type Country = Readonly<{ - id: string; - alphaCode: string; - nameEn: string; - nameFr: string; -}>; - -/** - * Retrieves a list of all countries. - * - * @returns An array of country objects. - */ -export function getCountries(): readonly Country[] { - return countriesData.map((country) => ({ - id: country.id, - alphaCode: country.alphaCode, - nameEn: country.nameEn, - nameFr: country.nameFr, - })); -} - -/** - * Retrieves a single country by its ID. - * - * @param id The ID of the country to retrieve. - * @returns The country object if found. - * @throws {AppError} If the country is not found. - */ -export function getCountryById(id: string): Country { - const country = getCountries().find((c) => c.id === id); - if (!country) { - throw new AppError(`Country with ID '${id}' not found.`, ErrorCodes.NO_COUNTRY_FOUND); - } - return country; -} - -type LocalizedCountry = Readonly<{ - id: string; - alphaCode: string; - name: string; -}>; - -/** - * Retrieves a list of countries localized to the specified language. - * - * @param locale The language to localize the country names to (default: 'en'). - * @returns An array of localized country objects. - */ -export function getLocalizedCountries(locale: Language = 'en'): readonly LocalizedCountry[] { - const { PP_CANADA_COUNTRY_CODE } = serverEnvironment; - - const countries = getCountries().map((country) => ({ - id: country.id, - alphaCode: country.alphaCode, - name: locale === 'fr' ? country.nameFr : country.nameEn, - })); - - return countries - .filter((country) => country.id === PP_CANADA_COUNTRY_CODE) - .concat( - countries - .filter((country) => country.id !== PP_CANADA_COUNTRY_CODE) - .sort((a, b) => a.name.localeCompare(b.name, locale, { sensitivity: 'base' })), - ); -} - -/** - * Retrieves a single localized country by its ID. - * - * @param id The ID of the country to retrieve. - * @param locale The language to localize the country name to (default: 'en'). - * @returns The localized country object if found. - * @throws {AppError} If the country is not found. - */ -export function getLocalizedCountryById(id: string, locale: Language = 'en'): LocalizedCountry { - const country = getLocalizedCountries(locale).find((c) => c.id === id); - if (!country) { - throw new AppError(`Localized country with ID '${id}' not found.`, ErrorCodes.NO_COUNTRY_FOUND); - } - return country; -} - -type Province = Readonly<{ - id: string; - alphaCode: string; - nameEn: string; - nameFr: string; -}>; - -/** - * Retrieves a list of all provinces. - * - * @returns An array of province objects. - */ -export function getProvinces(): readonly Province[] { - return provincesData.map((province) => ({ - id: province.id, - alphaCode: province.alphaCode, - nameEn: province.nameEn, - nameFr: province.nameFr, - })); -} - -/** - * Retrieves a single province by its ID. - * - * @param id The ID of the province to retrieve. - * @returns The province object if found. - * @throws {AppError} If the province is not found. - */ -export function getProvinceById(id: string): Province { - const province = getProvinces().find((p) => p.id === id); - if (!province) { - throw new AppError(`Province with ID '${id}' not found.`, ErrorCodes.NO_PROVINCE_FOUND); - } - return province; -} - -type LocalizedProvince = Readonly<{ - id: string; - alphaCode: string; - name: string; -}>; - -/** - * Retrieves a list of provinces localized to the specified language. - * - * @param locale The language to localize the province names to (default: 'en'). - * @returns An array of localized province objects. - */ -export function getLocalizedProvinces(locale: Language = 'en'): readonly LocalizedProvince[] { - return getProvinces() - .map((province) => ({ - id: province.id, - alphaCode: province.alphaCode, - name: locale === 'fr' ? province.nameFr : province.nameEn, - })) - .sort((a, b) => a.name.localeCompare(b.name, locale, { sensitivity: 'base' })); -} - -/** - * Retrieves a single localized province by its ID. - * - * @param id The ID of the province to retrieve. - * @param locale The language to localize the province name to (default: 'en'). - * @returns The localized province object if found. - * @throws {AppError} If the province is not found. - */ -export function getLocalizedProvinceById(id: string, locale: Language = 'en'): LocalizedProvince { - const province = getLocalizedProvinces(locale).find((p) => p.id === id); - if (!province) { - throw new AppError(`Localized province with ID '${id}' not found.`, ErrorCodes.NO_PROVINCE_FOUND); - } - return province; -} - -type LanguageOfCorrespondence = Readonly<{ - id: string; - nameEn: string | null; - nameFr: string | null; -}>; - -/** - * Retrieves a list of languages of correspondence. - * - * @returns An array of language of correspondence objects. - */ -export function getLanguagesOfCorrespondence(): readonly LanguageOfCorrespondence[] { - const optionSet = getOptionSet('esdc_languageofcorrespondence'); - return optionSet.options.map((option) => ({ - id: option.value.toString(), - nameEn: option.labelEn, - nameFr: option.labelFr, - })); -} - -/** - * Retrieves a single language of correspondence by its ID. - * - * @param id The ID of the language of correspondence to retrieve. - * @returns The language of correspondence object if found. - * @throws {AppError} If the language of correspondence is not found. - */ -export function getLanguageOfCorrespondenceById(id: string): LanguageOfCorrespondence { - const language = getLanguagesOfCorrespondence().find((l) => l.id === id); - if (!language) { - throw new AppError(`Language of correspondence with ID '${id}' not found.`, ErrorCodes.NO_LANGUAGE_FOUND); - } - return language; -} - -type LocalizedPreferredLanguage = Readonly<{ - id: string; - name: string | null; -}>; - -/** - * Retrieves a list of languages of correspondence localized to the specified language. - * - * @param locale The language to localize the language names to (default: 'en'). - * @returns An array of localized language of correspondence objects. - */ -export function getLocalizedLanguageOfCorrespondence(locale: Language = 'en'): LocalizedPreferredLanguage[] { - return getLanguagesOfCorrespondence().map((option) => ({ - id: option.id, - name: locale === 'fr' ? option.nameFr : option.nameEn, - })); -} - -/** - * Retrieves a single localized language of correspondence by its ID. - * - * @param id The ID of the language of correspondence to retrieve. - * @param locale The language to localize the language name to (default: 'en'). - * @returns The localized language of correspondence object if found. - * @throws {AppError} If the language of correspondence is not found. - */ -export function getLocalizedLanguageOfCorrespondenceById(id: string, locale: Language = 'en'): LocalizedPreferredLanguage { - const language = getLocalizedLanguageOfCorrespondence(locale).find((l) => l.id === id); - if (!language) { - throw new AppError(`Localized language of correspondence with ID '${id}' not found.`, ErrorCodes.NO_LANGUAGE_FOUND); - } - return language; -} - -type ApplicantGender = Readonly<{ - id: string; - nameEn: string | null; - nameFr: string | null; -}>; - -/** - * Retrieves a list of applicant genders. - * - * @returns An array of applicant gender objects. - */ -export function getApplicantGenders(): readonly ApplicantGender[] { - const optionSet = getOptionSet('esdc_applicantgender'); - return optionSet.options.map((option) => ({ - id: option.value.toString(), - nameEn: option.labelEn, - nameFr: option.labelFr, - })); -} - -/** - * Retrieves a single applicant gender by its ID. - * - * @param id The ID of the applicant gender to retrieve. - * @returns The applicant gender object if found. - * @throws {AppError} If the applicant gender is not found. - */ -export function getApplicantGenderById(id: string): ApplicantGender { - const gender = getApplicantGenders().find((g) => g.id === id); - if (!gender) { - throw new AppError(`Applicant gender with ID '${id}' not found.`, ErrorCodes.NO_GENDER_FOUND); - } - return gender; -} - -export type LocalizedApplicantGender = Readonly<{ - id: string; - name: string | null; -}>; - -/** - * Retrieves a list of applicant genders localized to the specified language. - * - * @param locale The language to localize the gender names to (default: 'en'). - * @returns An array of localized applicant gender objects. - */ -export function getLocalizedApplicantGenders(locale: Language = 'en'): LocalizedApplicantGender[] { - return getApplicantGenders().map((option) => ({ - id: option.id, - name: locale === 'fr' ? option.nameFr : option.nameEn, - })); -} - -/** - * Retrieves a single localized applicant gender by its ID. - * - * @param id The ID of the applicant gender to retrieve. - * @param locale The language to localize the gender name to (default: 'en'). - * @returns The localized applicant gender object if found. - * @throws {AppError} If the applicant gender is not found. - */ -export function getLocalizedApplicantGenderById(id: string, locale: Language = 'en'): LocalizedApplicantGender { - const gender = getLocalizedApplicantGenders(locale).find((g) => g.id === id); - if (!gender) { - throw new AppError(`Localized applicant gender with ID '${id}' not found.`, ErrorCodes.NO_GENDER_FOUND); - } - return gender; -} - -type ApplicationSubmissionScenario = Readonly<{ - id: string; - nameEn: string | null; - nameFr: string | null; -}>; - -/** - * Retrieves a list of application submission scenarios. - * - * @returns An array of application submission scenario objects. - */ -export function getApplicationSubmissionScenarios(): readonly ApplicationSubmissionScenario[] { - const optionSet = getOptionSet('esdc_applicationsubmissionscenarios'); - return optionSet.options.map((option) => ({ - id: option.value.toString(), - nameEn: option.labelEn, - nameFr: option.labelFr, - })); -} - -/** - * Retrieves a single application submission scenario by its ID. - * - * @param id The ID of the application submission scenario to retrieve. - * @returns The application submission scenario object if found. - * @throws {AppError} If the scenario is not found. - */ -export function getApplicationSubmissionScenarioById(id: string): ApplicationSubmissionScenario { - const scenario = getApplicationSubmissionScenarios().find((s) => s.id === id); - if (!scenario) { - throw new AppError( - `Application submission scenario with ID '${id}' not found.`, - ErrorCodes.NO_APPLICATION_SUBMISSION_SCENARIO_FOUND, - ); - } - return scenario; -} - -export type LocalizedApplicationSubmissionScenario = Readonly<{ - id: string; - name: string | null; -}>; - -/** - * Retrieves a list of application submission scenarios localized to the specified language. - * - * @param locale The language to localize the scenario names to (default: 'en'). - * @returns An array of localized application submission scenario objects. - */ -export function getLocalizedApplicationSubmissionScenarios(locale: Language = 'en'): LocalizedApplicationSubmissionScenario[] { - return getApplicationSubmissionScenarios().map((option) => ({ - id: option.id, - name: locale === 'fr' ? option.nameFr : option.nameEn, - })); -} - -/** - * Retrieves a single localized application submission scenario by its ID. - * - * @param id The ID of the application submission scenario to retrieve. - * @param locale The language to localize the scenario name to (default: 'en'). - * @returns The localized application submission scenario object if found. - * @throws {AppError} If the scenario is not found. - */ -export function getLocalizedApplicationSubmissionScenarioById( - id: string, - locale: Language = 'en', -): LocalizedApplicationSubmissionScenario { - const scenario = getLocalizedApplicationSubmissionScenarios(locale).find((s) => s.id === id); - if (!scenario) { - throw new AppError( - `Localized application submission scenario with ID '${id}' not found.`, - ErrorCodes.NO_APPLICATION_SUBMISSION_SCENARIO_FOUND, - ); - } - return scenario; -} - -type TypeOfApplicationToSubmit = Readonly<{ - id: string; - nameEn: string | null; - nameFr: string | null; -}>; - -/** - * Retrieves a list of types of applications to submit. - * - * @returns An array of type of application to submit objects. - */ -export function getTypesOfApplicationToSubmit(): readonly TypeOfApplicationToSubmit[] { - const optionSet = getOptionSet('esdc_typeofapplicationtosubmit'); - return optionSet.options.map((option) => ({ - id: option.value.toString(), - nameEn: option.labelEn, - nameFr: option.labelFr, - })); -} - -/** - * Retrieves a single type of application to submit by its ID. - * - * @param id The ID of the type of application to submit to retrieve. - * @returns The type of application to submit object if found. - * @throws {AppError} If the type is not found. - */ -export function getTypeOfApplicationToSubmitById(id: string): TypeOfApplicationToSubmit { - const type = getTypesOfApplicationToSubmit().find((t) => t.id === id); - if (!type) { - throw new AppError( - `Type of application to submit with ID '${id}' not found.`, - ErrorCodes.NO_TYPE_OF_APPLICATION_TO_SUBMIT_FOUND, - ); - } - return type; -} - -export type LocalizedTypeOfApplicationToSubmit = Readonly<{ - id: string; - name: string | null; -}>; - -/** - * Retrieves a list of types of applications to submit localized to the specified language. - * - * @param locale The language to localize the type names to (default: 'en'). - * @returns An array of localized type of application to submit objects. - */ -export function getLocalizedTypesOfApplicationToSubmit(locale: Language = 'en'): LocalizedTypeOfApplicationToSubmit[] { - return getTypesOfApplicationToSubmit().map((option) => ({ - id: option.id, - name: locale === 'fr' ? option.nameFr : option.nameEn, - })); -} - -/** - * Retrieves a single localized type of application to submit by its ID. - * - * @param id The ID of the type of application to submit to retrieve. - * @param locale The language to localize the type name to (default: 'en'). - * @returns The localized type of application to submit object if found. - * @throws {AppError} If the type is not found. - */ -export function getLocalizedTypeOfApplicationToSubmitById( - id: string, - locale: Language = 'en', -): LocalizedTypeOfApplicationToSubmit { - const type = getLocalizedTypesOfApplicationToSubmit(locale).find((t) => t.id === id); - if (!type) { - throw new AppError( - `Localized type of application to submit with ID '${id}' not found.`, - ErrorCodes.NO_TYPE_OF_APPLICATION_TO_SUBMIT_FOUND, - ); - } - return type; -} - -type ApplicantPrimaryDocumentChoice = Readonly<{ - id: string; - nameEn: string | null; - nameFr: string | null; -}>; - -/** - * Retrieves a list of applicant primary document choices. - * - * @returns An array of applicant primary document choice objects. - */ -export function getApplicantPrimaryDocumentChoices(): readonly ApplicantPrimaryDocumentChoice[] { - const optionSet = getOptionSet('esdc_applicantprimarydocumentchoices'); - return optionSet.options.map((option) => ({ - id: option.value.toString(), - nameEn: option.labelEn, - nameFr: option.labelFr, - })); -} - -/** - * Retrieves a single applicant primary document choice by its ID. - * - * @param id The ID of the applicant primary document choice to retrieve. - * @returns The applicant primary document choice object if found. - * @throws {AppError} If the choice is not found. - */ -export function getApplicantPrimaryDocumentChoiceById(id: string): ApplicantPrimaryDocumentChoice { - const choice = getApplicantPrimaryDocumentChoices().find((c) => c.id === id); - if (!choice) { - throw new AppError( - `Applicant primary document choice with ID '${id}' not found.`, - ErrorCodes.NO_APPLICANT_PRIMARY_DOCUMENT_CHOICE_FOUND, - ); - } - return choice; -} - -export type LocalizedApplicantPrimaryDocumentChoice = Readonly<{ - id: string; - name: string | null; -}>; - -/** - * Retrieves a list of applicant primary document choices localized to the specified language. - * - * @param locale The language to localize the choice names to (default: 'en'). - * @returns An array of localized applicant primary document choice objects. - */ -export function getLocalizedApplicantPrimaryDocumentChoices( - locale: Language = 'en', -): LocalizedApplicantPrimaryDocumentChoice[] { - return getApplicantPrimaryDocumentChoices().map((option) => ({ - id: option.id, - name: locale === 'fr' ? option.nameFr : option.nameEn, - })); -} - -/** - * Retrieves a single localized applicant primary document choice by its ID. - * - * @param id The ID of the applicant primary document choice to retrieve. - * @param locale The language to localize the choice name to (default: 'en'). - * @returns The localized applicant primary document choice object if found. - * @throws {AppError} If the choice is not found. - */ -export function getLocalizedApplicantPrimaryDocumentChoiceById( - id: string, - locale: Language = 'en', -): LocalizedApplicantPrimaryDocumentChoice { - const choice = getLocalizedApplicantPrimaryDocumentChoices(locale).find((c) => c.id === id); - if (!choice) { - throw new AppError( - `Localized applicant primary document choice with ID '${id}' not found.`, - ErrorCodes.NO_APPLICANT_PRIMARY_DOCUMENT_CHOICE_FOUND, - ); - } - return choice; -} - -type ApplicantSecondaryDocumentChoice = Readonly<{ - id: string; - nameEn: string | null; - nameFr: string | null; -}>; - -/** - * Retrieves a list of applicant secondary document choices. - * - * @returns An array of applicant secondary document choice objects. - */ -export function getApplicantSecondaryDocumentChoices(): readonly ApplicantSecondaryDocumentChoice[] { - const optionSet = getOptionSet('esdc_applicantsecondarydocumentchoices'); - return optionSet.options.map((option) => ({ - id: option.value.toString(), - nameEn: option.labelEn, - nameFr: option.labelFr, - })); -} - -/** - * Retrieves a single applicant secondary document choice by its ID. - * - * @param id The ID of the applicant secondary document choice to retrieve. - * @returns The applicant secondary document choice object if found. - * @throws {AppError} If the choice is not found. - */ -export function getApplicantSecondaryDocumentChoiceById(id: string): ApplicantSecondaryDocumentChoice { - const choice = getApplicantSecondaryDocumentChoices().find((c) => c.id === id); - if (!choice) { - throw new AppError( - `Applicant secondary document choice with ID '${id}' not found.`, - ErrorCodes.NO_APPLICANT_SECONDARY_DOCUMENT_CHOICE_FOUND, - ); - } - return choice; -} - -export type LocalizedApplicantSecondaryDocumentChoice = Readonly<{ - id: string; - name: string | null; -}>; - -/** - * Retrieves a list of applicant secondary document choices localized to the specified language. - * - * @param locale The language to localize the choice names to (default: 'en'). - * @returns An array of localized applicant secondary document choice objects. - */ -export function getLocalizedApplicantSecondaryDocumentChoices( - locale: Language = 'en', -): LocalizedApplicantSecondaryDocumentChoice[] { - return getApplicantSecondaryDocumentChoices().map((option) => ({ - id: option.id, - name: locale === 'fr' ? option.nameFr : option.nameEn, - })); -} - -/** - * Retrieves a single localized applicant secondary document choice by its ID. - * - * @param id The ID of the applicant secondary document choice to retrieve. - * @param locale The language to localize the choice name to (default: 'en'). - * @returns The localized applicant secondary document choice object if found. - * @throws {AppError} If the choice is not found. - */ -export function getLocalizedApplicantSecondaryDocumentChoiceById( - id: string, - locale: Language = 'en', -): LocalizedApplicantSecondaryDocumentChoice { - const choice = getLocalizedApplicantSecondaryDocumentChoices(locale).find((c) => c.id === id); - if (!choice) { - throw new AppError( - `Localized applicant secondary document choice with ID '${id}' not found.`, - ErrorCodes.NO_APPLICANT_SECONDARY_DOCUMENT_CHOICE_FOUND, - ); - } - return choice; -} - -type ApplicantHadSinOption = Readonly<{ - id: string; - nameEn: string | null; - nameFr: string | null; -}>; - -/** - * Retrieves a list of applicant had SIN options. - * - * @returns An array of applicant had SIN option objects. - */ -export function getApplicantHadSinOptions(): readonly ApplicantHadSinOption[] { - const optionSet = getOptionSet('esdc_didtheapplicanteverhadasinnumber'); - return optionSet.options.map((option) => ({ - id: option.value.toString(), - nameEn: option.labelEn, - nameFr: option.labelFr, - })); -} - -/** - * Retrieves a single applicant had SIN option by its ID. - * - * @param id The ID of the applicant had SIN option to retrieve. - * @returns The applicant had SIN option object if found. - * @throws {AppError} If the option is not found. - */ -export function getApplicantHadSinOptionById(id: string): ApplicantHadSinOption { - const option = getApplicantHadSinOptions().find((o) => o.id === id); - if (!option) { - throw new AppError(`Applicant had SIN option with ID '${id}' not found.`, ErrorCodes.NO_APPLICANT_HAD_SIN_OPTION_FOUND); - } - return option; -} - -export type LocalizedApplicantHadSinOption = Readonly<{ - id: string; - name: string | null; -}>; - -/** - * Retrieves a list of applicant had SIN options localized to the specified language. - * - * @param locale The language to localize the option names to (default: 'en'). - * @returns An array of localized applicant had SIN option objects. - */ -export function getLocalizedApplicantHadSinOptions(locale: Language = 'en'): LocalizedApplicantHadSinOption[] { - return getApplicantHadSinOptions().map((option) => ({ - id: option.id, - name: locale === 'fr' ? option.nameFr : option.nameEn, - })); -} - -/** - * Retrieves a single localized applicant had SIN option by its ID. - * - * @param id The ID of the applicant had SIN option to retrieve. - * @param locale The language to localize the option name to (default: 'en'). - * @returns The localized applicant had SIN option object if found. - * @throws {AppError} If the option is not found. - */ -export function getLocalizedApplicantHadSinOptionById(id: string, locale: Language = 'en'): LocalizedApplicantHadSinOption { - const option = getLocalizedApplicantHadSinOptions(locale).find((o) => o.id === id); - if (!option) { - throw new AppError( - `Localized applicant had SIN option with ID '${id}' not found.`, - ErrorCodes.NO_APPLICANT_HAD_SIN_OPTION_FOUND, - ); - } - return option; -} diff --git a/frontend/app/.server/shared/models.ts b/frontend/app/.server/shared/models.ts new file mode 100644 index 00000000..50a55da1 --- /dev/null +++ b/frontend/app/.server/shared/models.ts @@ -0,0 +1,25 @@ +export type Country = Readonly<{ + id: string; + alphaCode: string; + nameEn: string; + nameFr: string; +}>; + +export type LocalizedCountry = Readonly<{ + id: string; + alphaCode: string; + name: string; +}>; + +export type Province = Readonly<{ + id: string; + alphaCode: string; + nameEn: string; + nameFr: string; +}>; + +export type LocalizedProvince = Readonly<{ + id: string; + alphaCode: string; + name: string; +}>; diff --git a/frontend/app/.server/shared/services/country-service.ts b/frontend/app/.server/shared/services/country-service.ts new file mode 100644 index 00000000..22bb044f --- /dev/null +++ b/frontend/app/.server/shared/services/country-service.ts @@ -0,0 +1,74 @@ +import { serverEnvironment } from '~/.server/environment'; +import countriesData from '~/.server/resources/esdc-countries.json'; +import type { Country, LocalizedCountry } from '~/.server/shared/models'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of all countries. + * + * @returns An array of country objects. + */ +export function getCountries(): readonly Country[] { + return countriesData.map((country) => ({ + id: country.id, + alphaCode: country.alphaCode, + nameEn: country.nameEn, + nameFr: country.nameFr, + })); +} + +/** + * Retrieves a single country by its ID. + * + * @param id The ID of the country to retrieve. + * @returns The country object if found. + * @throws {AppError} If the country is not found. + */ +export function getCountryById(id: string): Country { + const country = getCountries().find((c) => c.id === id); + if (!country) { + throw new AppError(`Country with ID '${id}' not found.`, ErrorCodes.NO_COUNTRY_FOUND); + } + return country; +} + +/** + * Retrieves a list of countries localized to the specified language. + * + * @param language The language to localize the country names to. + * @returns An array of localized country objects. + */ +export function getLocalizedCountries(language: Language): readonly LocalizedCountry[] { + const { PP_CANADA_COUNTRY_CODE } = serverEnvironment; + + const countries = getCountries().map((country) => ({ + id: country.id, + alphaCode: country.alphaCode, + name: language === 'fr' ? country.nameFr : country.nameEn, + })); + + return countries + .filter((country) => country.id === PP_CANADA_COUNTRY_CODE) + .concat( + countries + .filter((country) => country.id !== PP_CANADA_COUNTRY_CODE) + .sort((a, b) => a.name.localeCompare(b.name, language, { sensitivity: 'base' })), + ); +} + +/** + * Retrieves a single localized country by its ID. + * + * @param id The ID of the country to retrieve. + * @param language The language to localize the country name to. + * @returns The localized country object if found. + * @throws {AppError} If the country is not found. + */ +export function getLocalizedCountryById(id: string, language: Language): LocalizedCountry { + const country = getLocalizedCountries(language).find((c) => c.id === id); + if (!country) { + throw new AppError(`Localized country with ID '${id}' not found.`, ErrorCodes.NO_COUNTRY_FOUND); + } + return country; +} diff --git a/frontend/app/.server/shared/services/index.ts b/frontend/app/.server/shared/services/index.ts new file mode 100644 index 00000000..fc69abb4 --- /dev/null +++ b/frontend/app/.server/shared/services/index.ts @@ -0,0 +1,2 @@ +export * as countryService from './country-service'; +export * as provinceService from './province-service'; diff --git a/frontend/app/.server/shared/services/province-service.ts b/frontend/app/.server/shared/services/province-service.ts new file mode 100644 index 00000000..6986ad95 --- /dev/null +++ b/frontend/app/.server/shared/services/province-service.ts @@ -0,0 +1,65 @@ +import provincesData from '~/.server/resources/esdc-provinces.json'; +import type { LocalizedProvince, Province } from '~/.server/shared/models'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; + +/** + * Retrieves a list of all provinces. + * + * @returns An array of province objects. + */ +export function getProvinces(): readonly Province[] { + return provincesData.map((province) => ({ + id: province.id, + alphaCode: province.alphaCode, + nameEn: province.nameEn, + nameFr: province.nameFr, + })); +} + +/** + * Retrieves a single province by its ID. + * + * @param id The ID of the province to retrieve. + * @returns The province object if found. + * @throws {AppError} If the province is not found. + */ +export function getProvinceById(id: string): Province { + const province = getProvinces().find((p) => p.id === id); + if (!province) { + throw new AppError(`Province with ID '${id}' not found.`, ErrorCodes.NO_PROVINCE_FOUND); + } + return province; +} + +/** + * Retrieves a list of provinces localized to the specified language. + * + * @param language The language to localize the province names to. + * @returns An array of localized province objects. + */ +export function getLocalizedProvinces(language: Language): readonly LocalizedProvince[] { + return getProvinces() + .map((province) => ({ + id: province.id, + alphaCode: province.alphaCode, + name: language === 'fr' ? province.nameFr : province.nameEn, + })) + .sort((a, b) => a.name.localeCompare(b.name, language, { sensitivity: 'base' })); +} + +/** + * Retrieves a single localized province by its ID. + * + * @param id The ID of the province to retrieve. + * @param language The language to localize the province name to. + * @returns The localized province object if found. + * @throws {AppError} If the province is not found. + */ +export function getLocalizedProvinceById(id: string, language: Language): LocalizedProvince { + const province = getLocalizedProvinces(language).find((p) => p.id === id); + if (!province) { + throw new AppError(`Localized province with ID '${id}' not found.`, ErrorCodes.NO_PROVINCE_FOUND); + } + return province; +} diff --git a/frontend/app/routes/protected/person-case/birth-details.tsx b/frontend/app/routes/protected/person-case/birth-details.tsx index 72b44964..1032b448 100644 --- a/frontend/app/routes/protected/person-case/birth-details.tsx +++ b/frontend/app/routes/protected/person-case/birth-details.tsx @@ -11,12 +11,7 @@ import * as v from 'valibot'; import type { Info, Route } from './+types/birth-details'; import { serverEnvironment } from '~/.server/environment'; -import { - getCountries, - getLocalizedCountries, - getLocalizedProvinces, - getProvinces, -} from '~/.server/services/locale-data-service'; +import { countryService, provinceService } from '~/.server/shared/services'; import { requireAuth } from '~/.server/utils/auth-utils'; import { i18nRedirect } from '~/.server/utils/route-utils'; import { Button } from '~/components/button'; @@ -51,8 +46,8 @@ export async function loader({ context, request }: Route.LoaderArgs) { return { documentTitle: t('protected:primary-identity-document.page-title'), - localizedCountries: getLocalizedCountries(lang), - localizedProvincesTerritoriesStates: getLocalizedProvinces(lang), + localizedCountries: countryService.getLocalizedCountries(lang), + localizedProvincesTerritoriesStates: provinceService.getLocalizedProvinces(lang), PP_CANADA_COUNTRY_CODE, defaultFormValues: { country: birthDetails?.country, @@ -93,7 +88,7 @@ export async function action({ context, request }: Route.ActionArgs) { v.object({ country: v.literal(PP_CANADA_COUNTRY_CODE, t('protected:birth-details.country.invalid-country')), province: v.picklist( - getProvinces().map(({ id }) => id), + provinceService.getProvinces().map(({ id }) => id), t('protected:birth-details.province.required-province'), ), city: v.pipe( @@ -111,7 +106,7 @@ export async function action({ context, request }: Route.ActionArgs) { v.nonEmpty(t('protected:birth-details.country.required-country')), v.excludes(PP_CANADA_COUNTRY_CODE, t('protected:birth-details.country.invalid-country')), v.picklist( - getCountries().map(({ id }) => id), + countryService.getCountries().map(({ id }) => id), t('protected:birth-details.country.invalid-country'), ), ), diff --git a/frontend/app/routes/protected/person-case/contact-information.tsx b/frontend/app/routes/protected/person-case/contact-information.tsx index 463ce39b..7bfb7a45 100644 --- a/frontend/app/routes/protected/person-case/contact-information.tsx +++ b/frontend/app/routes/protected/person-case/contact-information.tsx @@ -9,15 +9,9 @@ import * as v from 'valibot'; import type { Info, Route } from './+types/contact-information'; +import { languageCorrespondenceService } from '~/.server/domain/person-case/services'; import { serverEnvironment } from '~/.server/environment'; -import { - getCountries, - getLocalizedCountries, - getLocalizedLanguageOfCorrespondence, - getLocalizedProvinces, - getLanguagesOfCorrespondence, - getProvinces, -} from '~/.server/services/locale-data-service'; +import { countryService, provinceService } from '~/.server/shared/services'; import { requireAuth } from '~/.server/utils/auth-utils'; import { i18nRedirect } from '~/.server/utils/route-utils'; import { Button } from '~/components/button'; @@ -46,9 +40,9 @@ export async function loader({ context, request }: Route.LoaderArgs) { return { documentTitle: t('protected:contact-information.page-title'), defaultFormValues: context.session.inPersonSINCase?.contactInformation, - localizedpreferredLanguages: getLocalizedLanguageOfCorrespondence(lang), - localizedCountries: getLocalizedCountries(lang), - localizedProvincesTerritoriesStates: getLocalizedProvinces(lang), + localizedpreferredLanguages: languageCorrespondenceService.getLocalizedLanguageOfCorrespondence(lang), + localizedCountries: countryService.getLocalizedCountries(lang), + localizedProvincesTerritoriesStates: provinceService.getLocalizedProvinces(lang), PP_CANADA_COUNTRY_CODE, }; } @@ -80,7 +74,7 @@ export async function action({ context, request }: Route.ActionArgs) { const schema = v.intersect([ v.object({ preferredLanguage: v.picklist( - getLanguagesOfCorrespondence().map(({ id }) => id), + languageCorrespondenceService.getLanguagesOfCorrespondence().map(({ id }) => id), t('protected:contact-information.error-messages.preferred-language-required'), ), primaryPhoneNumber: v.pipe( @@ -97,7 +91,7 @@ export async function action({ context, request }: Route.ActionArgs) { ), ), country: v.picklist( - getCountries().map(({ id }) => id), + countryService.getCountries().map(({ id }) => id), t('protected:contact-information.error-messages.country-required'), ), address: v.pipe(v.string(), v.trim(), v.nonEmpty(t('protected:contact-information.error-messages.address-required'))), @@ -117,7 +111,7 @@ export async function action({ context, request }: Route.ActionArgs) { v.object({ country: v.literal(PP_CANADA_COUNTRY_CODE), province: v.picklist( - getProvinces().map(({ id }) => id), + provinceService.getProvinces().map(({ id }) => id), t('protected:contact-information.error-messages.province-required'), ), }), diff --git a/frontend/app/routes/protected/person-case/parent-details.tsx b/frontend/app/routes/protected/person-case/parent-details.tsx index b8de17ed..7cec8b5d 100644 --- a/frontend/app/routes/protected/person-case/parent-details.tsx +++ b/frontend/app/routes/protected/person-case/parent-details.tsx @@ -11,12 +11,7 @@ import * as v from 'valibot'; import type { Info, Route } from './+types/parent-details'; import { serverEnvironment } from '~/.server/environment'; -import { - getCountries, - getLocalizedCountries, - getLocalizedProvinces, - getProvinces, -} from '~/.server/services/locale-data-service'; +import { countryService, provinceService } from '~/.server/shared/services'; import { requireAuth } from '~/.server/utils/auth-utils'; import { i18nRedirect } from '~/.server/utils/route-utils'; import { Button } from '~/components/button'; @@ -49,8 +44,8 @@ export async function loader({ context, request }: Route.LoaderArgs) { return { documentTitle: t('protected:parent-details.page-title'), - localizedCountries: getLocalizedCountries(lang), - localizedProvincesTerritoriesStates: getLocalizedProvinces(lang), + localizedCountries: countryService.getLocalizedCountries(lang), + localizedProvincesTerritoriesStates: provinceService.getLocalizedProvinces(lang), PP_CANADA_COUNTRY_CODE, defaultFormValues: sessionData.map((details) => details.unavailable @@ -122,7 +117,7 @@ export async function action({ context, request }: Route.ActionArgs) { v.object({ country: v.literal(PP_CANADA_COUNTRY_CODE, t('protected:parent-details.country-error.invalid-country')), province: v.picklist( - getProvinces().map(({ id }) => id), + provinceService.getProvinces().map(({ id }) => id), t('protected:parent-details.province-error.required-province'), ), city: v.pipe( @@ -139,7 +134,7 @@ export async function action({ context, request }: Route.ActionArgs) { v.nonEmpty(t('protected:parent-details.country-error.required-country')), v.excludes(PP_CANADA_COUNTRY_CODE, t('protected:parent-details.country-error.invalid-country')), v.picklist( - getCountries().map(({ id }) => id), + countryService.getCountries().map(({ id }) => id), t('protected:parent-details.country-error.invalid-country'), ), ), diff --git a/frontend/app/routes/protected/person-case/personal-info.tsx b/frontend/app/routes/protected/person-case/personal-info.tsx index 2c92dc3a..6613879a 100644 --- a/frontend/app/routes/protected/person-case/personal-info.tsx +++ b/frontend/app/routes/protected/person-case/personal-info.tsx @@ -11,7 +11,7 @@ import * as v from 'valibot'; import type { Info, Route } from './+types/personal-info'; -import { getApplicantGenders, getLocalizedApplicantGenders } from '~/.server/services/locale-data-service'; +import { applicantGenderService } from '~/.server/domain/person-case/services'; import { requireAuth } from '~/.server/utils/auth-utils'; import { i18nRedirect } from '~/.server/utils/route-utils'; import { Button } from '~/components/button'; @@ -44,7 +44,7 @@ export async function loader({ context, request }: Route.LoaderArgs) { lastNamePreviouslyUsed: context.session.inPersonSINCase?.personalInformation?.lastNamePreviouslyUsed ?? [], gender: context.session.inPersonSINCase?.personalInformation?.gender, }, - localizedGenders: getLocalizedApplicantGenders(lang), + localizedGenders: applicantGenderService.getLocalizedApplicantGenders(lang), }; } @@ -86,7 +86,7 @@ export async function action({ context, request }: Route.ActionArgs) { ), lastNamePreviouslyUsed: v.optional(v.array(v.string())), gender: v.picklist( - getApplicantGenders().map(({ id }) => id), + applicantGenderService.getApplicantGenders().map(({ id }) => id), t('protected:personal-information.gender.required'), ), }) satisfies v.GenericSchema; diff --git a/frontend/app/routes/protected/person-case/primary-docs.tsx b/frontend/app/routes/protected/person-case/primary-docs.tsx index d0ff89e8..30fd55ed 100644 --- a/frontend/app/routes/protected/person-case/primary-docs.tsx +++ b/frontend/app/routes/protected/person-case/primary-docs.tsx @@ -11,9 +11,9 @@ import * as v from 'valibot'; import type { Info, Route } from './+types/primary-docs'; +import type { LocalizedApplicantGender } from '~/.server/domain/person-case/models'; +import { applicantGenderService } from '~/.server/domain/person-case/services'; import { serverEnvironment } from '~/.server/environment'; -import type { LocalizedApplicantGender } from '~/.server/services/locale-data-service'; -import { getApplicantGenders, getLocalizedApplicantGenders } from '~/.server/services/locale-data-service'; import { requireAuth } from '~/.server/utils/auth-utils'; import { i18nRedirect } from '~/.server/utils/route-utils'; import { Button } from '~/components/button'; @@ -53,7 +53,7 @@ export async function loader({ context, request }: Route.LoaderArgs) { return { documentTitle: t('protected:primary-identity-document.page-title'), defaultFormValues: context.session.inPersonSINCase?.primaryDocuments, - localizedGenders: getLocalizedApplicantGenders(lang), + localizedGenders: applicantGenderService.getLocalizedApplicantGenders(lang), }; } @@ -188,7 +188,7 @@ export async function action({ context, request }: Route.ActionArgs) { ), ), gender: v.picklist( - getApplicantGenders().map(({ id }) => id), + applicantGenderService.getApplicantGenders().map(({ id }) => id), t('protected:primary-identity-document.gender.required'), ), citizenshipDateYear: v.pipe(