express-api/src/services/agencies/agencyServices.ts
import { AppDataSource } from '@/appDataSource';
import { Agency } from '@/typeorm/Entities/Agency';
import { ErrorWithCode } from '@/utilities/customErrors/ErrorWithCode';
import { AgencyFilter } from './agencySchema';
import {
constructFindOptionFromQuery,
constructFindOptionFromQueryBoolean,
constructFindOptionFromQuerySingleSelect,
} from '@/utilities/helperFunctions';
import { Brackets, FindOptionsWhere } from 'typeorm';
import logger from '@/utilities/winstonLogger';
import { SortOrders } from '@/constants/types';
import { AgencyJoinView } from '@/typeorm/Entities/views/AgencyJoinView';
const agencyRepo = AppDataSource.getRepository(Agency);
/**
* Collects and constructs find options based on the provided AgencyFilter.
* @param filter - The filter object containing criteria for finding agencies.
* @returns An array of constructed find options based on the filter criteria.
*/
const collectFindOptions = (filter: AgencyFilter) => {
const options = [];
if (filter.name) options.push(constructFindOptionFromQuery('Name', filter.name));
if (filter.code) options.push(constructFindOptionFromQuery('Code', filter.code));
if (filter.isDisabled)
options.push(constructFindOptionFromQueryBoolean('IsDisabled', filter.isDisabled));
if (filter.parentName)
options.push(constructFindOptionFromQuerySingleSelect('ParentName', filter.parentName));
if (filter.sendEmail)
options.push(constructFindOptionFromQueryBoolean('SendEmail', filter.sendEmail));
if (filter.email) options.push(constructFindOptionFromQuery('Email', filter.email));
if (filter.createdOn) options.push(constructFindOptionFromQuery('CreatedOn', filter.createdOn));
if (filter.updatedOn) options.push(constructFindOptionFromQuery('UpdatedOn', filter.updatedOn));
return options;
};
/**
* Converts entity names to column names.
* Needed because the sort key in query builder uses the column name, not the entity name.
*/
const sortKeyTranslator: Record<string, string> = {
Name: 'name',
Code: 'code',
IsDisabled: 'is_disabled',
ParentName: 'parent_name',
SendEmail: 'send_email',
Email: 'email',
CreatedOn: 'created_on',
UpdatedOn: 'updated_on',
};
/**
* @description Gets and returns a list of agencies based on filter criteria.
* @param filter - The filter criteria to apply when retrieving agencies.
* @returns { Agency[] } A list of agencies in the database
*/
export const getAgencies = async (filter: AgencyFilter) => {
const options = collectFindOptions(filter);
const query = AppDataSource.getRepository(AgencyJoinView)
.createQueryBuilder()
.where(
new Brackets((qb) => {
options.forEach((option) => qb.andWhere(option));
}),
);
// Add quickfilter part
if (filter.quickFilter) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const quickFilterOptions: FindOptionsWhere<any>[] = [];
const quickfilterFields = ['Name', 'Code', 'ParentName', 'Email', 'UpdatedOn', 'CreatedOn'];
quickfilterFields.forEach((field) =>
quickFilterOptions.push(constructFindOptionFromQuery(field, filter.quickFilter)),
);
query.andWhere(
new Brackets((qb) => {
quickFilterOptions.forEach((option) => qb.orWhere(option));
}),
);
}
if (filter.quantity) query.take(filter.quantity);
if (filter.page && filter.quantity) query.skip((filter.page ?? 0) * (filter.quantity ?? 0));
if (filter.sortKey && filter.sortOrder) {
if (sortKeyTranslator[filter.sortKey]) {
query.orderBy(
sortKeyTranslator[filter.sortKey],
filter.sortOrder.toUpperCase() as SortOrders,
'NULLS LAST',
);
} else {
logger.error('getAgencies Service - Invalid Sort Key');
}
}
const [data, totalCount] = await query.getManyAndCount();
return { data, totalCount };
};
/**
* @description Creates a new agency in the database.
* @param {Agency} agency Information on agency to be created.
* @returns {Agency} The added agency
* @throws ErrorWithCode if agency already exists
*/
export const addAgency = async (agency: Agency) => {
const existingAgencies = await agencyRepo.find({
where: [{ Name: agency.Name }, { Code: agency.Code }], // OR check
});
if (existingAgencies.length > 0) {
throw new ErrorWithCode('Agency with that name or code already exists', 409);
}
const newAgency = AppDataSource.getRepository(Agency).save(agency);
return newAgency;
};
/**
* @description Finds and returns an agency with a given id.
* @param {number} agencyId Id of the agency to retrieve.
* @returns {Agency} The found agency or null.
*/
export const getAgencyById = async (agencyId: number) => {
const findAgency = await agencyRepo.findOne({
where: { Id: agencyId },
});
return findAgency;
};
/**
* @description Finds and updates an agency with the given id.
* @param {Agency} agencyIn An agency object used to update existing agency.
* @returns {Agency} Status and information on updated agency.
*/
export const updateAgencyById = async (agencyIn: Agency) => {
const { data: agencies } = await getAgencies({});
const findAgency = agencies.find((agency) => agency.Id === agencyIn.Id);
if (findAgency == null) {
throw new ErrorWithCode('Agency not found.', 404);
}
// Was a parent agency included?
if (agencyIn.ParentId != null) {
const findParentAgency = agencies.find((agency) => agency.Id === agencyIn.ParentId);
if (findParentAgency == null) {
throw new ErrorWithCode(`Requested Parent Agency Id ${agencyIn.ParentId} not found.`, 404);
}
// If updated agency is already a parent, it cannot be assigned a parent.
const isParent = agencies.some((agency) => agency.ParentId === agencyIn.Id);
if (isParent) {
throw new ErrorWithCode('Cannot assign Parent Agency to existing Parent Agency.', 400);
}
// If the requested parent has its own parent, it cannot be the parent for the updated agency
if (findParentAgency.ParentId != null) {
throw new ErrorWithCode('Cannot assign a child agency as a Parent Agency.', 400);
}
}
const updatedAgency = await agencyRepo.save(agencyIn);
return updatedAgency;
};
/**
* @description Finds and removes an agency with the given id.
* @param {number} agencyId ID of agency to delete.
* @returns {DeleteResult} The result of the delete action.
*/
export const deleteAgencyById = async (agencyId: number) => {
const findAgency = await getAgencyById(agencyId);
if (findAgency == null) {
throw new ErrorWithCode('Agency not found.', 404);
}
const deleted = await agencyRepo.delete(findAgency.Id);
return deleted;
};