Source

services/events-service.js

import { splitOperation } from '@/utils/promise.js'
import moment from 'moment'
import { getNestedValue } from '../utils/object'
import { getQueryStringValue } from '@/utils/querystring'
import { getFormattedAddress } from '@/utils/address.js'
import api from '@/api'
import i18n from '@/i18n'
import { getAPIV3Pooling } from '@/api'
import { getEnvValue } from '@/services/env-service.js'
import APIUrls, {
  APIV3RequestDatetimeFormat,
} from '@/config/simpliciti-apis.js'

export async function getEventTypesCategories(onData = () => {}) {
  let types = {
    exploitation: 'operation_message',
    anomalie: 'anomaly',
    statut: 'status',
  }
  return await getAPIV3Pooling({
    uri: `${APIUrls.APIV3_EVENTS_ANOMALIES}?page=1&itemsPerPage=40`,
    callback: onData,
    transform(item) {
      return {
        id: item.id,
        code: item.code,
        label: item.description,
        type: types[item.type] || item.type,
      }
    },
  })
}

export async function fetchEventDetailsById(aggregateId) {
  return normalizeAPIV3Item(
    (await api.v3.get(APIUrls.APIV3_AGGREGATE_EVENTS + '/' + aggregateId)).data
  )
}

export async function fetchVehicleEvents(vehicleId, dates = [], filters = {}) {
  console.log('fetchVehicleEvents', filters)

  let filtersValue = []
  if (filters.anomaly) {
    filtersValue.push(0)
  }
  if (filters.eventExploitation) {
    filtersValue.push(1)
  }
  if (filters.status) {
    filtersValue.push(3)
  }

  const payload = {
    vehicleId: vehicleId,
    'multipleDate[between][datetime][start]': dates.map((date) =>
      moment(date)
        .hour(0)
        .minutes(0)
        .seconds(0)
        .format(APIV3RequestDatetimeFormat)
    ),
    'multipleDate[between][datetime][end]': dates.map((date) =>
      moment(date)
        .hour(23)
        .minutes(59)
        .seconds(59)
        .format(APIV3RequestDatetimeFormat)
    ),
    type: filtersValue,
    group: 'all',
  }

  const result =
    (await api.v3.get(APIUrls.APIV3_AGGREGATE_EVENTS, payload)).data || []

  console.log('fetchVehicleEvents', result)
  return result.map((item) => {
    return normalizeAPIV3Item(item)
  })
}

/**
 * Used by events module.
 * - Data used for: Map markers and table
 *
 * Split: 5 element, then 1 date (i.g: 1 request per vehicle/date)
 *
 * @TODO To be able to stop the search (i.g user stop an automatic search)
 *
 * @param {Array} options.elementIds Vehicle or circuit ids [1,2,3]
 * @param {Array} options.dates Dates ['2022-04-29'] or [Moment]
 * @param {Object} options.filters Custom querystring parameters
 * @param {String} options.elementType Helps build the uri (vehicle/circuit)
 * @param {Object} options.fetchOptions Give options to axios
 * @param {Function} options.handleResults (results)=>({}) Each time results are available
 */
export async function fetchEvents(options = {}) {
  /*  options = {
    ...options,
    handleResults: options.handleResults || (results)=>({})
  } */

  return await splitOperation({
    sequential: false,
    generateSubsets() {
      let subsets = []
      let single = {}
      options.elementIds.forEach((id) => {
        single.elementIds = single.elementIds || []
        single.elementIds.push(id)
        if (single.elementIds.length >= 5) {
          single.dates = options.dates
          subsets.push(single)
          single = {}
        }
      })
      if (single.elementIds) {
        single.dates = options.dates
        subsets.push(single)
      }
      return subsets
    },
    async handleSubset(subset) {
      console.log('fetchEvents:splitOperation:handleSubset')

      //Split by date (API supports 1 date at time)
      await splitOperation({
        sequential: true,
        generateSubsets() {
          return subset.dates.map((date) => {
            return {
              ...subset,
              dates: [date],
            }
          })
        },
        async handleSubset(subset) {
          console.log(
            'fetchEvents:splitOperation:handleSubset:splitOperation:handleSubset'
          )
          let dates = subset.dates
          let fetchOptions = options.fetchOptions || {}
          let elementParamName = {
            vehicle: 'vehicleId',
            circuit: 'roundId',
          }
          let res = await api.v3.get(
            APIUrls.APIV3_AGGREGATE_EVENTS,
            {
              [elementParamName[options.elementType || 'vehicle']]:
                subset.elementIds,

              'multipleDate[between][datetime][start]': dates.map((date) =>
                moment(date)
                  .hour(0)
                  .minute(0)
                  .second(0)
                  .format(APIV3RequestDatetimeFormat)
              ),
              'multipleDate[between][datetime][end]': dates.map((date) =>
                moment(date)
                  .hour(23)
                  .minute(59)
                  .second(59)
                  .format(APIV3RequestDatetimeFormat)
              ),

              //other filters goes here
              ...(options.filters || {}),
            },
            fetchOptions
          )
          res = (res.data || []).map(normalizeAPIV3Item)
          return res
        },
        withSubsetResult: (results) => {
          options.handleResults(results || [])
        },
      })
    },
  })
}

/**
 * @todo Immatriculation
 * @param {Object} item Object to transform
 * @returns
 */
export function normalizeAPIV3Item(item = {}) {
  const colors = {
    0: 'red', //anomaly
    1: 'orange', //Message d’exploitation
    3: 'green', //status
  }
  let photos = (item.files || []).map((file) => file.url)
  return {
    id: item.id,
    lat: item.latitude,
    lng: item.longitude,
    ...(item.datetime
      ? {
          date: item.datetime,
          timestamp: moment(item.datetime)._d.getTime(),
          formattedDate: moment(item.datetime).format('DD-MM-YYYY'),
          formattedTime: moment(item.datetime).format('HH:mm:ss'),
        }
      : {}),
    immat: '',
    address: getFormattedAddress(item.mapMatch || {}, {
      city: 'nocity',
    }),
    city: getNestedValue(item, 'mapMatch.city'),
    fullAddress: getFormattedAddress(item.mapMatch || {}, {}),
    comment: item.comment,
    label: item.anomalyLabel,
    photos,
    hasPhotos:
      getNestedValue(item, ['totalFiles', 'filesNumber'], photos.length) > 0,
    type: item.type,
    color: colors[item.type] || 'red',
    vehicleName: item.vehicleName,
    vehicleId: item.vehicleId,
    isAPIV3Item: true,
    circuitName: item.roundName || '',
    puceNumber: getNestedValue(item, 'binCollection.tagNumber'),
    binCollectionSide: getNestedValue(item, 'binCollection.side'),
    binCollectionSideLabel: getNestedValue(item, 'binCollection.side', '', {
      allowZero: true,
      transform(sideNumber, originalValue, rootValue) {
        return (
          {
            0: i18n.t('events.table_column_side_left'), //gauche
            1: i18n.t('events.table_column_side_right'), //droite
            2: i18n.t('events.table_column_side_combine'), //combiné
            3: i18n.t('events.table_column_side_bicomp'), //bicomp
            5: i18n.t('events.table_column_side_arm'), //bras
            9: i18n.t('events.table_column_side_pedestrian'), //pieton
          }[sideNumber.toString()] || ''
        )
      },
    }),
  }
}

export function normalizeAPIV2Array(data) {
  let normalizedData = []

  const colors = {
    0: 'red', //anomaly
    1: 'orange', //Message d’exploitation
    3: 'green', //status
  }

  data.forEach((item) => {
    let streetNum =
      item.evenement_num_rue !== '' && item.evenement_num_rue !== '0'
        ? item.evenement_num_rue + ' '
        : ''
    let streetName =
      item.evenement_adresse !== '' ? item.evenement_adresse + ' ' : ''
    let formatAddress = streetNum + streetName

    normalizedData.push({
      id: item.evenement_id,
      lat: item.evenement_latitude_wgs84,
      lng: item.evenement_longitude_wgs84,
      date: item.evenement_dateheure,
      timestamp: moment(item.evenement_dateheure)._d.getTime(),
      formattedDate: moment(item.evenement_dateheure).format('DD-MM-YYYY'),
      formattedTime: moment(item.evenement_dateheure).format('HH:mm:ss'),
      immat: item.nom_vehicule,
      address: formatAddress,
      city: item.evenement_commune,
      fullAddress: formatAddress + item.evenement_commune,
      comment: item.evenement_tud150,
      label: item.anomalie_libelle,
      photos: item.photos ? (item.photos.photo || []).map((i) => i.url) : [],
      type: item.evenement_type,
      color: colors[item.evenement_type] || 'red',
      vehicleName: item.nom_vehicule,
      vehicleId: item.vehicule_id,
    })
  })
  return normalizedData
}

function getLabelByType(type) {
  return (
    {
      0: i18n.te('events.types.anomaly')
        ? i18n.t('events.types.anomaly')
        : 'anomaly', //anomaly
      1: i18n.te('events.types.message_exploitation')
        ? i18n.t('events.types.message_exploitation')
        : 'Message d’exploitation', //Message d’exploitation
      3: i18n.te('events.types.status')
        ? i18n.t('events.types.status')
        : 'status', //status
    }[type] ||
    (i18n.te('events.types.unknown')
      ? i18n.t('events.types.unknown')
      : '(No name)')
  )
}

/**
 * Replace JSON mock with hardcoded/auto-generated mock inside the code source (Optimize)
 * @param {*} elementIds
 * @param {*} dates
 * @param {*} options
 * @returns
 */
export async function fetchEventsCumulation(elementIds, dates, options = {}) {
  console.log('event-service::fetchEventsCumulation', {
    elementIds,
    dates,
    options,
  })
  let res =
    (getQueryStringValue('mockapi') === '1' &&
      require('@/api/mocks/v3/geored/events/service/cumulation/events_service_cumulation_berto_prolians.json')) ||
    (
      await api.v3.get(APIUrls.APIV3_EVENTS_CUMULATION, {
        format: {
          vehicle: 'vehicle',
          circuit: 'round',
        }[options.elementType || 'vehicle'],
        page: 1,
        itemsPerPage: 30,

        //vehicule_id/circuit_id
        [{
          vehicle: 'vehicleId',
          circuit: 'roundId',
        }[options.elementType || 'vehicle']]: elementIds,

        'multipleDate[between][datetime][start]': dates.map((date) =>
          moment(date)
            .hour(0)
            .minutes(0)
            .seconds(0)
            .format(APIV3RequestDatetimeFormat)
        ),
        'multipleDate[between][datetime][end]': dates.map((date) =>
          moment(date)
            .hour(23)
            .minutes(59)
            .seconds(59)
            .format(APIV3RequestDatetimeFormat)
        ),

        ...(options.filters || {}),
      })
    ).data

  function mapResponseItems(root, onSingleMapHandler = null) {
    let items = []
    Object.keys(root).forEach((label) => {
      let singleItemRaw = root[label]
      let codesObject = singleItemRaw.types || singleItemRaw.codes || []
      let singleItem = {
        id: singleItemRaw.id || label,
        label: label,
        count: singleItemRaw.total,
        items: Object.keys(codesObject).map((eventGroupKey) => {
          let eventGroup = codesObject[eventGroupKey]
          return {
            id: eventGroup.code,
            code: eventGroup.code,
            label: eventGroup.label,
            count: eventGroup.count,
            type: eventGroup.type,
            typeLabel: getLabelByType(eventGroup.type),
          }
        }),
      }
      onSingleMapHandler && onSingleMapHandler(singleItem, singleItemRaw)
      items.push(singleItem)
    })
    return items
  }

  return mapResponseItems(res, (dateItem, rawItem) => {
    //@todo: format using i18n date wrapper
    dateItem.label = moment(dateItem.label).format(`DD/MM/YYYY`)
    dateItem.subItems = mapResponseItems(
      rawItem.vehicles || rawItem.rounds || []
    )
    dateItem.timestamp = moment(dateItem.label)._d.getTime()
    return dateItem
  })
}

export function getEventsSearchSelectionLimit() {
  const getEnvValueFloat = (n) =>
    getEnvValue(n, '', {
      transform: (value) => parseFloat(value),
    })
  return (
    getEnvValueFloat('VUE_APP_EVENTS_MODULE_SEARCH_SELECTION_LIMIT') ||
    getEnvValueFloat('VUE_APP_SEARCH_SELECTION_LIMIT') ||
    100
  )
}

export default {
  fetchEvents,
  fetchEventDetailsById,
  fetchEventsCumulation,
  fetchVehicleEvents,
  getEventsSearchSelectionLimit,
  getEventTypesCategories,
  normalizeAPIV2Array,
  normalizeAPIV3Item,
}