cityssm/attendance-tracking

View on GitHub
tasks/avantiEmployeeSync.ts

Summary

Maintainability
A
0 mins
Test Coverage
import {
  AvantiApi,
  type types as AvantiApiTypes,
  lookups as avantiLookups
} from '@cityssm/avanti-api'
import Debug from 'debug'
import exitHook from 'exit-hook'
import { clearIntervalAsync, setIntervalAsync } from 'set-interval-async'

import { createEmployee } from '../database/createEmployee.js'
import { deleteEmployeeProperties } from '../database/deleteEmployeeProperties.js'
import { deleteMissingSyncedEmployees } from '../database/deleteMissingSyncedEmployees.js'
import { getEmployee } from '../database/getEmployee.js'
import { setEmployeeProperty } from '../database/setEmployeeProperty.js'
import { updateEmployee } from '../database/updateEmployee.js'
import { getConfigProperty } from '../helpers/functions.config.js'
import type { Employee } from '../types/recordTypes.js'

const debug = Debug('attendance-tracking:tasks:avantiEmployeeSync')

let terminateTask = false

const sessionUser: AttendUser = {
  userName: 'sys.employeeSync',
  canLogin: true,
  isAdmin: false
}

const avantiConfig = getConfigProperty('settings.avantiSync.config')

const avanti =
  avantiConfig === undefined ? undefined : new AvantiApi(avantiConfig)

const getEmployeeOptions: AvantiApiTypes.GetEmployeesRequest = {
  skip: 0,
  take: 10_000
}

if (getConfigProperty('settings.avantiSync.locationCodes').length > 0) {
  getEmployeeOptions.locations = getConfigProperty(
    'settings.avantiSync.locationCodes'
  )
}

// eslint-disable-next-line sonarjs/cognitive-complexity
async function doSync(): Promise<void> {
  if (avanti === undefined) {
    return
  }

  debug('Requesting employees from API...')

  const employees = await avanti.getEmployees(getEmployeeOptions)

  if (!employees.success) {
    debug(employees.error)
    return
  }

  const syncDateTime = new Date()

  debug(
    `Processing ${(
      employees.response.employees?.length ?? 0
    ).toString()} employee(s)...`
  )

  for (const avantiEmployee of employees.response.employees ?? []) {
    if (terminateTask) {
      break
    }

    if (
      (avantiEmployee.empNo ?? '') === '' ||
      !(avantiEmployee.active ?? false)
    ) {
      // Avanti employee record has no employee number
      // Skip the record
      continue
    }

    try {
      const currentEmployee = await getEmployee(avantiEmployee.empNo ?? '')

      if (
        currentEmployee !== undefined &&
        !(currentEmployee.isSynced ?? false)
      ) {
        continue
      }

      debug(`Processing ${avantiEmployee.empNo ?? ''}...`)

      const newEmployee: Employee = {
        employeeNumber: avantiEmployee.empNo ?? '',
        employeeSurname: avantiEmployee.surname ?? '',
        employeeGivenName: avantiEmployee.givenName ?? '',
        userName: '',
        workContact1: '',
        workContact2: '',
        homeContact1: '',
        homeContact2: '',
        jobTitle: avantiEmployee.positionName ?? '',
        department: '',
        isSynced: true,
        syncContacts: true,
        syncDateTime,
        isActive: true
      }

      const avantiEmployeePersonalResponse =
        await avanti.getEmployeePersonalInfo(avantiEmployee.empNo ?? '')

      if (avantiEmployeePersonalResponse.success) {
        const avantiEmployeePersonal = avantiEmployeePersonalResponse.response

        newEmployee.seniorityDateTime = new Date(
          avantiEmployeePersonal.seniorityDate
        )

        newEmployee.userName = (
          avantiEmployeePersonal.userName ?? ''
        ).toLowerCase()

        const workContacts: string[] = []
        const homeContacts: string[] = []

        for (const phoneTypeIndex of [1, 2, 3, 4]) {
          if (
            avantiLookups.phoneTypes[
              avantiEmployeePersonal[`phoneType${phoneTypeIndex}`]
            ].isPhone &&
            (avantiEmployeePersonal[`phoneNumber${phoneTypeIndex}`] ?? '') !==
              ''
          ) {
            if (
              avantiLookups.phoneTypes[
                avantiEmployeePersonal[`phoneType${phoneTypeIndex}`]
              ].isWork
            ) {
              workContacts.push(
                avantiEmployeePersonal[`phoneNumber${phoneTypeIndex}`] as string
              )
            } else {
              homeContacts.push(
                avantiEmployeePersonal[`phoneNumber${phoneTypeIndex}`] as string
              )
            }
          }
        }

        if (workContacts[0] !== undefined) {
          newEmployee.workContact1 = workContacts[0]
        }

        if (workContacts[1] !== undefined) {
          newEmployee.workContact2 = workContacts[1]
        }

        if (homeContacts[0] !== undefined) {
          newEmployee.homeContact1 = homeContacts[0]
        }

        if (homeContacts[1] !== undefined) {
          newEmployee.homeContact2 = homeContacts[1]
        }
      }

      currentEmployee === undefined
        ? await createEmployee(newEmployee, sessionUser)
        : await updateEmployee(newEmployee, true, sessionUser)

      await deleteEmployeeProperties(
        newEmployee.employeeNumber,
        true,
        sessionUser
      )

      if (avantiEmployeePersonalResponse.success) {
        const avantiEmployeePersonal = avantiEmployeePersonalResponse.response

        await setEmployeeProperty(
          {
            employeeNumber: newEmployee.employeeNumber,
            propertyName: 'position',
            propertyValue: avantiEmployeePersonal.position ?? '',
            isSynced: true
          },
          true,
          sessionUser
        )

        await setEmployeeProperty(
          {
            employeeNumber: newEmployee.employeeNumber,
            propertyName: 'payGroup',
            propertyValue: avantiEmployeePersonal.payGroup ?? '',
            isSynced: true
          },
          true,
          sessionUser
        )

        await setEmployeeProperty(
          {
            employeeNumber: newEmployee.employeeNumber,
            propertyName: 'location',
            propertyValue: avantiEmployeePersonal.location ?? '',
            isSynced: true
          },
          true,
          sessionUser
        )

        await setEmployeeProperty(
          {
            employeeNumber: newEmployee.employeeNumber,
            propertyName: 'workGroup',
            propertyValue: avantiEmployeePersonal.workGroup ?? '',
            isSynced: true
          },
          true,
          sessionUser
        )

        for (
          let otherTextIndex = 1;
          otherTextIndex <= 20;
          otherTextIndex += 1
        ) {
          if (avantiEmployeePersonal[`otherText${otherTextIndex}`] !== '') {
            await setEmployeeProperty(
              {
                employeeNumber: newEmployee.employeeNumber,
                propertyName: `otherText${otherTextIndex}`,
                propertyValue: avantiEmployeePersonal[
                  `otherText${otherTextIndex}`
                ] as string,
                isSynced: true
              },
              true,
              sessionUser
            )
          }
        }
      }
    } catch (error) {
      debug(error)
    }
  }

  const employeesDeleted = await deleteMissingSyncedEmployees(
    syncDateTime,
    sessionUser
  )

  debug(`${employeesDeleted} employee(s) deleted`)
}

await doSync().catch(() => {
  debug('Error running task.')
})

const intervalID = setIntervalAsync(doSync, 6 * 3600 * 1000)

exitHook(() => {
  terminateTask = true
  try {
    void clearIntervalAsync(intervalID)
  } catch {
    debug('Error exiting task.')
  }
})