All files / apps/data-refreshments/src/refreshments-execute/sync teamSync.service.ts

23.83% Statements 46/193
25% Branches 1/4
25% Functions 1/4
23.83% Lines 46/193

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 1941x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                                   1x 1x 1x 1x 1x 1x 1x                                                                                     1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                 1x  
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { pick } from 'lodash-es';
import { In, IsNull, Not, Repository } from 'typeorm';
 
import { Team, TeamAssignment, User, type Company } from '@amalia/core/models';
import { DataConnectorTypes, type DataConnectorObjectRecord } from '@amalia/data-capture/connectors/types';
import { toError } from '@amalia/ext/typescript';
import { TeamRole } from '@amalia/tenants/assignments/teams/types';
 
@Injectable()
export class SyncTeamService {
  private readonly logger = new Logger(SyncTeamService.name);
 
  public constructor(
    @InjectRepository(Team)
    private readonly teamRepository: Repository<Team>,
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
    @InjectRepository(TeamAssignment)
    private readonly teamAssignmentRepository: Repository<TeamAssignment>,
  ) {}
 
  /**
   *
   * @param company
   * @param records
   * @param onlySyncExistingTeams
   */
  public async syncTeams(
    company: Company,
    records: DataConnectorObjectRecord[],
    onlySyncExistingTeams: boolean = false,
  ): Promise<void> {
    const teams = await this.teamRepository.findBy({ company: { id: company.id } });
    // Filter duplicates + check errors.
    try {
      // 1 -- Sync Team
      // Note: deletion of teams and matching fields are not implemented.
      await Promise.all(
        records.map(async ({ content = {} }) => {
          const importedId = content['id'] as string;
          const importedName = content['name'] as string;

          // if no id, we can't do anything
          if (!importedId) {
            return;
          }

          let teamToUpsert = teams.find(({ externalTeamId }) => externalTeamId === importedId);

          // If we have a match and the name is different, we update it
          if (teamToUpsert && teamToUpsert.name !== importedName) {
            teamToUpsert.name = importedName;
            await this.teamRepository.save(teamToUpsert);
          } else {
            if (onlySyncExistingTeams) {
              return;
            }
            teamToUpsert = new Team();
            // In case we already have a team with the same name, we merge them
            const teamWithSameName = teams.find(({ name }) => name === importedName);
            if (teamWithSameName) {
              teamToUpsert = { ...teamWithSameName, externalTeamId: importedId };
            } else {
              teamToUpsert.company = company;
              teamToUpsert.name = importedName;
              teamToUpsert.externalTeamId = importedId;
            }
            await this.teamRepository.save(teamToUpsert);
          }
        }),
      );
    } catch (e) {
      const error = toError(e);
      this.logger.error({ message: 'Error on sync Team', error, company: pick(company, ['id', 'name']) });
      throw new HttpException('Error during Team sync, please contact admin', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
 
  /**
   *
   * @param company
   * @param records
   */
  public async syncTeamHierarchy(company: Company, records: DataConnectorObjectRecord[]): Promise<void> {
    try {
      // get all teams with an externalTeamId
      const teamsWithExternalTeamId = await this.teamRepository.findBy({
        company: { id: company.id },
        externalTeamId: Not(IsNull()),
      });
      // 2 -- Sync Hierarchy
      await Promise.all(
        records.map(async ({ content = {} }) => {
          // if team exist check if parent is the same, otherwise update it
          // todo deletion of teams
          // todo use matching field

          // if no id, we can't do anything
          if (!content['id']) {
            return;
          }

          const teamToUpdate = teamsWithExternalTeamId.find(({ externalTeamId }) => externalTeamId === content['id']);

          if (!teamToUpdate) {
            return;
          }
          // Need to update parent?
          const parentRecordId = content['parentRoleId'];
          const teamHasNoParent = !teamToUpdate.parentTeamId;
          const parentDoesntMatch = teamToUpdate.parentTeam?.externalTeamId !== parentRecordId;
          const parentTeam = teamsWithExternalTeamId.find(({ externalTeamId }) => externalTeamId === parentRecordId);

          teamToUpdate.parentTeam = parentTeam && (teamHasNoParent || parentDoesntMatch) ? parentTeam : undefined;
          await this.teamRepository.save(teamToUpdate);
        }),
      );
    } catch (e) {
      const error = toError(e);
      this.logger.error({ message: 'Error on sync Team', error, company: pick(company, ['id', 'name']) });
      throw new HttpException(
        'Error during Team Hierarchy sync, please contact admin',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }
 
  /**
   *
   * @param company
   * @param records
   * @param connectorType
   */
  public async syncTeamAssignments(
    company: Company,
    records: DataConnectorObjectRecord[],
    connectorType: DataConnectorTypes,
  ): Promise<void> {
    const teamsWithExternalTeamId = await this.teamRepository.findBy({
      company: { id: company.id },
      externalTeamId: Not(IsNull()),
    });
    const users = await this.userRepository.findBy({ company: { id: company.id } });
    // For the sales hack we are just removing all assignment and rebuilding them
    // we should instead check for updates and manage start/end date
    // let's keep it simple for the hack and change that if we ever need to launch the epic :)
    try {
      // 1 -- Remove all Team Assignment for teams that have an externalTeamId
      await this.teamAssignmentRepository.delete({
        company: { id: company.id },
        teamId: In(teamsWithExternalTeamId.map(({ id }) => id)),
      });

      // 2 -- Create new team assignments
      await Promise.all(
        records.map(async ({ content = {} }) => {
          const userTeamId =
            connectorType === DataConnectorTypes.SALESFORCE ? content['userRoleId'] : content['primaryTeamId'];
          // if no id, we can't do anything
          if (!content['id'] || !userTeamId) {
            return;
          }
          // Find the team with the "team field" on the record
          // here we assume that we are on User SFDC data with the "Role" field #saleshack
          const currentTeam = userTeamId
            ? teamsWithExternalTeamId.find(({ externalTeamId }) => externalTeamId === userTeamId)
            : null;
          // Find current user
          const currentUser = users.find(({ externalId }) => externalId === content['id']);
          if (currentUser && currentTeam) {
            // create the team assignment
            await this.teamAssignmentRepository.save({
              user: currentUser,
              team: currentTeam,
              teamRole: TeamRole.TEAM_MANAGER,
              company,
            });
          }
        }),
      );
    } catch (e) {
      const error = toError(e);
      this.logger.error({ message: 'Error on sync Team Assignment', error, company: pick(company, ['id', 'name']) });

      throw new HttpException(
        'Error during Team Assignment sync, please contact admin',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }
}