Vizzuality/landgriffon

View on GitHub
api/src/modules/admin-regions/admin-regions.service.ts

Summary

Maintainability
C
1 day
Test Coverage
C
71%
import {
  forwardRef,
  Inject,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import {
  AppBaseService,
  JSONAPISerializerConfig,
} from 'utils/app-base.service';
import {
  AdminRegion,
  adminRegionResource,
} from 'modules/admin-regions/admin-region.entity';
import { AppInfoDTO } from 'dto/info.dto';
import { AdminRegionRepository } from 'modules/admin-regions/admin-region.repository';
import { CreateAdminRegionDto } from 'modules/admin-regions/dto/create.admin-region.dto';
import { UpdateAdminRegionDto } from 'modules/admin-regions/dto/update.admin-region.dto';
import { FindTreesWithOptionsArgs } from 'utils/tree.repository';
import { SourcingLocationsService } from 'modules/sourcing-locations/sourcing-locations.service';
import { GetAdminRegionTreeWithOptionsDto } from 'modules/admin-regions/dto/get-admin-region-tree-with-options.dto';
import { MaterialsService } from 'modules/materials/materials.service';
import { SuppliersService } from 'modules/suppliers/suppliers.service';
import { BusinessUnitsService } from 'modules/business-units/business-units.service';
import { SourcingLocation } from 'modules/sourcing-locations/sourcing-location.entity';
import { GeoRegion } from 'modules/geo-regions/geo-region.entity';
import { SelectQueryBuilder } from 'typeorm';
import { FindOneOptions } from 'typeorm/find-options/FindOneOptions';

@Injectable()
export class AdminRegionsService extends AppBaseService<
  AdminRegion,
  CreateAdminRegionDto,
  UpdateAdminRegionDto,
  AppInfoDTO
> {
  constructor(
    protected readonly adminRegionRepository: AdminRegionRepository,
    @Inject(forwardRef(() => MaterialsService))
    protected readonly materialService: MaterialsService,
    @Inject(forwardRef(() => SuppliersService))
    protected readonly supplierService: SuppliersService,
    @Inject(forwardRef(() => BusinessUnitsService))
    protected readonly businessUnitService: BusinessUnitsService,
    @Inject(forwardRef(() => SourcingLocationsService))
    protected readonly sourcingLocationsService: SourcingLocationsService,
  ) {
    super(
      adminRegionRepository,
      adminRegionResource.name.singular,
      adminRegionResource.name.plural,
    );
  }

  get serializerConfig(): JSONAPISerializerConfig<AdminRegion> {
    return {
      attributes: [
        'name',
        'description',
        'status',
        'geoRegionId',
        'geoRegion',
        'children',
      ],
      keyForAttribute: 'camelCase',
    };
  }

  /**
   * @description: Retrieves a Admin Region given its Id with optional parameters
   * @param id
   * @param {Object} [options] - Additional search options
   * @param {boolean} [options.isCountry] - Flag to find a country level admin-region given an ID. Defaults to false
   *
   */
  async getAdminRegionById(
    id: string,
    options?: { isCountry: boolean },
  ): Promise<AdminRegion> {
    const findOptions: FindOneOptions<AdminRegion> = { where: { id } };
    if (options?.isCountry) {
      // document
      findOptions.where = { ...findOptions.where, level: 0 };
    }
    const found: AdminRegion | null = await this.adminRegionRepository.findOne(
      findOptions,
    );

    if (!found) {
      throw new NotFoundException(`Admin region with ID "${id}" not found`);
    }

    return found;
  }

  async getAdminRegionsById(id: string[]): Promise<AdminRegion[]> {
    return await this.adminRegionRepository.findByIds(id);
  }

  async findAllUnpaginated(): Promise<AdminRegion[]> {
    return this.adminRegionRepository.find();
  }

  async findTreesWithOptions(
    args?: FindTreesWithOptionsArgs,
  ): Promise<AdminRegion[]> {
    return this.adminRegionRepository.findTrees(args);
  }

  async createTree(importData: CreateAdminRegionDto[]): Promise<AdminRegion[]> {
    return this.adminRegionRepository.saveListToTree(importData, 'mpath');
  }

  /**
   * @description: Get a AdminRegion Id and its GeoRegion Id by Admin Region name
   *               Optionally provide a Admin Region level to filter by it
   *               i.e: Level 0 is Country level, level 1 Region, level 2 Sub-Region...
   */

  async getAdminRegionAndGeoRegionIdsByAdminRegionName(
    adminRegionName: string,
    options?: { level: number },
  ): Promise<{ adminRegionId: string; geoRegionId: string }> {
    const queryBuilder: SelectQueryBuilder<AdminRegion> =
      this.adminRegionRepository
        .createQueryBuilder('adminRegion')
        .select('adminRegion.id', 'adminRegionId')
        .addSelect('geoRegion.id', 'geoRegionId')
        .innerJoin(
          GeoRegion,
          'geoRegion',
          'adminRegion.geoRegionId = geoRegion.id',
        )
        .where('adminRegion.name = :adminRegionName', { adminRegionName });
    if (options && !isNaN(options.level)) {
      queryBuilder.andWhere('adminRegion.level = :level', {
        level: options.level,
      });
    }
    const adminRegionAndGeoRegionIds: any = await queryBuilder.getRawOne();

    if (!adminRegionAndGeoRegionIds)
      throw new NotFoundException(
        `An Admin Region with name ${adminRegionName} ${
          options?.level ? `and level ${options.level} ` : ''
        }could not been found`,
      );
    return adminRegionAndGeoRegionIds;
  }

  async getAdminRegionIdByCoordinatesAndLevel(
    searchParams: {
      lng: number;
      lat: number;
      level: number;
    },
    sourcingLocation: SourcingLocation,
  ): Promise<{ adminRegionId: string; geoRegionId: string }> {
    return this.adminRegionRepository.getAdminRegionAndGeoRegionIdByCoordinatesAndLevel(
      searchParams,
      sourcingLocation,
    );
  }

  async getClosestAdminRegionByCoordinates(
    coordinates: {
      lng: number;
      lat: number;
    },
    sourcingLocation: SourcingLocation,
  ): Promise<any> {
    return this.adminRegionRepository.getClosestAdminRegionByCoordinates(
      coordinates,
      sourcingLocation,
    );
  }

  /**
   *
   * @description Get a tree of AdminRegions where there are sourcing-locations registered within
   */

  async getAdminRegionWithSourcingLocations(
    adminRegionTreeOptions: GetAdminRegionTreeWithOptionsDto,
    withAncestry: boolean = true,
  ): Promise<AdminRegion[]> {
    const adminRegionLineage: AdminRegion[] =
      await this.adminRegionRepository.getAdminRegionsFromSourcingLocations(
        adminRegionTreeOptions,
        withAncestry,
      );
    if (!withAncestry) {
      return adminRegionLineage;
    }
    return this.buildTree<AdminRegion>(adminRegionLineage, null);
  }

  async getTrees(
    adminRegionTreeOptions: GetAdminRegionTreeWithOptionsDto,
  ): Promise<AdminRegion[]> {
    if (adminRegionTreeOptions.withSourcingLocations) {
      if (adminRegionTreeOptions.businessUnitIds) {
        adminRegionTreeOptions.businessUnitIds =
          await this.businessUnitService.getBusinessUnitsDescendants(
            adminRegionTreeOptions.businessUnitIds,
          );
      }
      if (adminRegionTreeOptions.materialIds) {
        adminRegionTreeOptions.materialIds =
          await this.materialService.getMaterialsDescendants(
            adminRegionTreeOptions.materialIds,
          );
      }
      return this.getAdminRegionWithSourcingLocations(adminRegionTreeOptions);
    }

    return this.findTreesWithOptions({ depth: adminRegionTreeOptions.depth });
  }

  /**
   * @description: Returns an array of all children of given Admin Region's Ids with optional parameters
   * @param {string[]} adminRegionIds - The IDs of the admin regions.
   * @param {Object} [options] - Optional find options.
   * @param {boolean} [options.fullEntities] - A flag indicating whether to return the full entities or just the IDs. S
   * the [options.treeResponse] flag
   * @param {boolean} [options.treeResponse] - A flag indicating whether to return the response in tree format. This options overrides fullEntities option
   * @returns {Promise<any>} The promise that resolves to the descendants of the admin regions.
   **/
  // TODO: Add proper return typing to support retrieving entities or Ids
  async getAdminRegionDescendants(
    adminRegionIds: string[],
    options?: { fullEntities?: boolean; treeResponse?: boolean },
  ): Promise<any> {
    // using type casting not to search for and r
    //provide the full entity, since only id is used by the method (to improve performance)
    let adminRegions: AdminRegion[] = [];

    for (const id of adminRegionIds) {
      if (options?.treeResponse) {
        const result: AdminRegion =
          await this.adminRegionRepository.findDescendantsTree({
            id,
          } as AdminRegion);
        adminRegions.push(result);
      } else {
        const result: AdminRegion[] =
          await this.adminRegionRepository.findDescendants({
            id,
          } as AdminRegion);

        adminRegions = [...adminRegions, ...result];
      }
    }
    if (options?.fullEntities || options?.treeResponse) {
      return adminRegions;
    }

    return adminRegions.map((adminRegion: AdminRegion) => adminRegion.id);
  }
}