/**
* @namespace api
* @category api
* @module history-api
* @todo Refactor/Move all the logic into history-service
* */
import moment from 'moment'
import api from '@/api'
import Vue from 'vue'
import { getFormattedAddress } from '@/utils/address'
import APIUrls, {
APIV2RequestDatetimeFormat,
APIV2RequestDateFormat,
APIV3RequestDatetimeFormat,
} from '@/config/simpliciti-apis.js'
import i18n from '@/i18n'
import normalizeTripHistoryItem from '@/services/entities/trip-history-item.js'
import { getNestedValue } from '@/utils/object'
import { isUnitTest } from '@/utils/unit.js'
const hash = require('object-hash')
/**
* @todo Those functions should be consumed/proxied-by by history-service only
*/
const scope = {
getVehiclePositionsV2,
getVehiclePositionsV3,
getPositionsFromVehicle,
getVehicleHistoryInfosFromAPIV2,
getChartTripHistoryFromVehicleId,
getTripHistoryFromVehicleId,
}
export default scope
async function getVehiclePositionsV3(vehicleId, date, options = {}) {
const t = (d, h = 0, m = 0, s = 0) =>
moment(date).hour(h).minute(m).second(s).format(APIV3RequestDatetimeFormat)
const startDatetime = options.startDate
? moment(options.startDate).format(APIV3RequestDatetimeFormat)
: t(date)
const endDatetime = options.endDate
? moment(options.endDate).format(APIV3RequestDatetimeFormat)
: t(date, 23, 59, 59)
console.log('getVehiclePositionsV3', {
vehicleId,
date,
startDatetime,
endDatetime,
})
return (
(isUnitTest() &&
require('@/api/mocks/v3/positions/geored_get_sensors.json')[0]) ||
((
await api.v3.get(APIUrls.APIV3_GET_SENSORS, {
vehicleId: vehicleId,
startDatetime,
endDatetime,
positions: true,
/* groups: [
'complementary_informations',
'sensors',
'sensor_details',
'can',
], */
...(options.queryParams || {}),
})
).data || [null])[0]
)
}
/**
* deprecated
async function getVehiclePositionsSensorsV2(vehicleId, date, options = {}) {
let startDate = moment(date)
.hour(0)
.minute(0)
.second(0)
.format(APIV2RequestDatetimeFormat)
let endDate = moment(date)
.hour(23)
.minute(59)
.second(59)
.format(APIV2RequestDatetimeFormat)
return ((
await api.v2.post(
`${APIUrls.APIV2_POSITIONS_GPS}?dateheure_debut=${startDate}&dateheure_fin=${endDate}&vehicule_id=${vehicleId}&groups=infos_complementaires,capteurs,capteurs_details,can`
)
).data || [null])[0]
}
*/
/**
*
* Used by Location module (Trip history details - table) to load positions and sensors configuration
*
* @param {*} vehicleId
* @param {*} date
* @param {Object} options.startDate
* @todo Remove fixtures feature without braking unit test
* @returns {Array}
*/
async function getVehiclePositionsV2(vehicleId, date, options = {}) {
const t = (d, h = 0, m = 0, s = 0) =>
moment(date).hour(h).minute(m).second(s).format(APIV2RequestDatetimeFormat)
let startDate = t(date)
let endDate = t(date, 23, 59, 59)
if (options.startDate) {
startDate = moment(options.startDate).format(APIV2RequestDatetimeFormat)
}
if (options.endDate) {
endDate = moment(options.endDate).format(APIV2RequestDatetimeFormat)
}
console.log('getVehiclePositionsV2', {
vehicleId,
date,
startDate,
endDate,
})
/* return ((await sensorsService.getSensors(
vehicleId,
options.startDate,
options.endDate
)) || [null])[0] */
return (
(await Vue.$api.loadFixtures(
`${APIUrls.APIV2_POSITIONS_CAPTEURS.substring(1)}/${vehicleId}_${moment(
date
).format(APIV2RequestDateFormat)}`,
(items) => items && items.length > 0 && items[0]
)) ||
((
await api.v2.post(
`${APIUrls.APIV2_POSITIONS_CAPTEURS}?dateheure_debut=${startDate}&positions=true&capteurs=false&dateheure_fin=${endDate}&vehicule_id=${vehicleId}`
)
).data || [null])[0]
)
}
/**
* Retrieves vehicle positions from a date (00:00:00 to 23:59:59)
* @param {*} vehicleId
* @param {*} date
* @returns
*/
async function getPositionsFromVehicle(
vehicleId,
date = new Date(),
fromTime = '',
toTime = ''
) {
Vue.$log.debug('getPositionsFromVehicle', vehicleId, date, fromTime, toTime)
date = moment(date).format(APIV2RequestDateFormat)
fromTime =
(fromTime && encodeURIComponent(` ${fromTime}`)) || '%2000%3A00%3A00'
toTime = (toTime && encodeURIComponent(` ${toTime}`)) || '%2023%3A59%3A59'
let res
res = (
await api.v2.get(
`${APIUrls.APIV2_POSITIONS}/${vehicleId}/${date}${fromTime}/${date}${toTime}`
)
).data
let data = (res && res.length > 0 && res[0]) || {} //response comes in first item
if (res.error && res.error[0]) {
Vue.$log.error(res.error[0])
throw new Error(res.error[0].message || 'unknown error')
}
return data.positions || []
}
/**
* Used by Diagnostics module to display analysis overview (After search validation)
*
* Retrieves history infos (such as averange speed, distance) and positions from a vehicle, date
* @returns {Object} Vehicle infos or an empty object
*/
async function getVehicleHistoryInfosFromAPIV2(
vehicleId,
dateOrDates,
options = {}
) {
let datesArr = dateOrDates instanceof Array ? dateOrDates : [dateOrDates]
if (!vehicleId) {
throw new Error('At least one vehicle must be specified')
}
if (datesArr.length === 0) {
throw new Error('At least one date is required')
}
let formattedDates = datesArr.map((date) =>
moment(date).format(APIV2RequestDateFormat)
)
let formattedDatesAsString = formattedDates.join(',')
let startTimeAsString = moment(datesArr[0]).format('HH:mm')
let endTimeAsString = moment(datesArr[datesArr.length - 1]).format('HH:mm')
if (datesArr.length === 1) {
endTimeAsString = '23:59' //If one date is selected, take the whole day
}
let shouldApplyTheTimeToFirstAndLastDateOnly =
options.shouldApplyTheTimeToFirstAndLastDateOnly !== undefined
? options.shouldApplyTheTimeToFirstAndLastDateOnly
: true
let data = (
await api.v2.post(
`${APIUrls.APIV2_HISTORIQUE_BY_VEHICLE_DATES}?vehicule_id=${vehicleId}&dates=${formattedDatesAsString}&linestring=true&heure_debut=${startTimeAsString}&heure_fin=${endTimeAsString}&premier_dernier_jour=${shouldApplyTheTimeToFirstAndLastDateOnly}`
)
).data
//Temporal fix: If multiple dates, cumulate results
let cumulatedDataResult = null
const cumulateData = (rawItem) => {
console.log('cumulateData', {
cumulatedDataResult: { ...cumulatedDataResult },
rawItem,
})
if (!cumulatedDataResult) {
cumulatedDataResult = rawItem
} else {
;['duree_troncon', 'distance_troncon'].forEach((fieldToCumulate) => {
cumulatedDataResult[fieldToCumulate] += rawItem[fieldToCumulate]
})
;['dh_activ_fin', 'adresse_fin', 'cp_fin', 'ville_fin'].forEach(
(fieldToAssign) => {
cumulatedDataResult[fieldToAssign] = rawItem[fieldToAssign]
}
)
}
}
let normalizedData = {}
Object.keys(data || {}).forEach((d) => {
//Temporal fix: If multiple dates, cumulate results
if (datesArr.length > 1) {
data[d].forEach((rawItem) => {
cumulateData(rawItem)
})
}
normalizedData[d] = (data[d] || []).map(normalizeTripHistoryItem)
})
/*console.log('getVehicleHistoryInfosFromAPIV2::normalizedData', {
normalizedData,
})*/
let response = {}
//Temporal fix: If multiple dates, cumulate results
if (datesArr.length > 1) {
response = normalizeTripHistoryItem(cumulatedDataResult || {})
response.isCumulated = true
} else {
response =
normalizedData[formattedDates[0]] &&
normalizedData[formattedDates[0]].length > 0
? normalizedData[formattedDates[0]][0]
: null
}
/*
console.log('getVehicleHistoryInfosFromAPIV2::response', {
response,
cumulated: datesArr.length > 1,
cumulatedDataResult,
})*/
return response
}
/**
* Feth from APIV2
* Support fixtures
* @param {*} vehicleId
* @param {*} date
* @todo Remove fixtures feature without braking unit test
* @returns
*/
async function getTripHistoryFromVehicleId(vehicleId, date = new Date()) {
return (
(await Vue.$api.loadFixtures(
`${APIUrls.APIV2_HISTORIQUE_TRONCONS_DETAILS.substring(
1
)}/${vehicleId}_${moment(date).format(APIV2RequestDateFormat)}`
)) ||
(
await api.v2.post(
`${
APIUrls.APIV2_HISTORIQUE_TRONCONS_DETAILS
}?vehicule_id=${vehicleId}&dateheure_debut=${encodeURI(
moment(date)
.hour(0)
.minute(0)
.second(0)
.format(APIV2RequestDatetimeFormat)
)}&dateheure_fin=${encodeURI(
moment(date)
.hour(23)
.minute(59)
.second(59)
.format(APIV2RequestDatetimeFormat)
)}&linestring=true&linestring_couleur=true&linestring_mouvement=true`
)
).data
)
}
/**
* Retrieves one vehicle trip history (normalized for a vertical chart)
* @param {*} vehicleId
* @param {*} date
* @returns {Array} Normalized chart dataset
* @todo Refactor: Instead of historyApi, Chart component (TripHistoryDetails) should be responsable of transofrming API response into chart normalized dataset
*/
export async function getChartTripHistoryFromVehicleId(
vehicleId,
date = new Date(),
data = null
) {
// Helper functions:
function pushItem(item) {
item.id = hash(item)
nodesGroupedByDate.push(item)
}
function createNode(tripStepNumber, trip, type = 'contact_on') {
let label = ``,
nodeDate = ``
let prefix = ['contact_on', 'flag'].includes(type) ? '_deb' : '_fin'
let address = getFormattedAddress(trip, {
streetAddress: `adresse${prefix}`,
zipCode: `cp${prefix}`,
city: `ville${prefix}`,
useZone: trip[`in_zoneinterne${prefix}`],
zone: `zone${prefix}`,
})
if (type === 'contact_on') {
label = `${i18n.t('location.history_tab.item.contact_on')}
<br/><br/>
${address}
`
nodeDate = Vue.$date.formatTimeWithSeconds(
moment(trip.dh_contact_on, APIV2RequestDatetimeFormat)
)
}
if (type === 'play') {
label = `${i18n.t('common.Départ')} ${address}
<br/><br/>
Durée du trajet : ${getTimeDuration(
trip.dh_trajet_deb,
trip.dh_arret_deb
)} ${i18n.t(
'location.history_tab.item.traveled_distance'
)} : ${trip.distance} Km
`
nodeDate = Vue.$date.formatTimeWithSeconds(
moment(trip.dh_trajet_deb, APIV2RequestDatetimeFormat)
)
}
if (type === 'flag') {
nodeDate = Vue.$date.formatTimeWithSeconds(
moment(trip.dh_trajet_fin, APIV2RequestDatetimeFormat)
)
label = `${i18n.t('common.Arrivée')} ${address}`
}
if (type === 'contact_off') {
label = `${i18n.t('location.history_tab.item.contact_off')}
<br/><br/>
${address}
`
}
let object = {
step: tripStepNumber,
date: nodeDate,
label: label,
type,
lng: trip[`longitude${prefix}`],
lat: trip[`latitude${prefix}`],
linestring: trip.linestring,
}
delete object.id
object.id = hash(object)
return object
}
async function parseItem(item) {
let newItem = {}
let trip
for (let index in item.troncons) {
trip = item.troncons[index]
let title = Vue.$date.formatDate(
moment(trip.date, APIV2RequestDateFormat)
)
if (newItem.title && newItem.title != title) {
pushItem(newItem)
newItem = {}
}
if (!newItem.title) {
newItem.title = title
newItem.nodes = newItem.nodes || []
//Trip history head infos (averages)
newItem.fromDatetime = moment(
getNestedValue(item, 'historique.dh_contact_on')
)
newItem.toDatetime = moment(
getNestedValue(item, 'historique.dh_contact_off')
)
newItem.tronconDistance = getNestedValue(item, [
'historique.distance_troncons', //invalid?
'historique.distance_troncon',
])
newItem.averageSpeed = item.historique.vitesse || ''
}
let stepNumber = parseInt(index) + 1
if (trip.dh_contact_on) {
newItem.nodes.push(createNode(stepNumber, trip, 'contact_on'))
}
newItem.nodes.push(createNode(stepNumber, trip, 'play'))
newItem.nodes.push(createNode(stepNumber, trip, 'flag'))
if (trip.dh_contact_off) {
newItem.nodes.push(createNode(stepNumber, trip, 'contact_off'))
}
}
pushItem(newItem)
}
//Get data
data = data || (await getTripHistoryFromVehicleId(vehicleId, date))
let nodesGroupedByDate = []
//Group data by date
await Promise.all(data.map(parseItem))
//Group data by steps
// nodesGroupedByDate = {title,nodes:{step}} ==> {title (date) ,steps:[{stepNumber,nodes:[]}]}
nodesGroupedByDate.forEach((grouped) => {
grouped.steps = []
let stepGroup = {
stepNumber: 1,
nodes: [],
}
grouped.nodes.forEach((node) => {
if (stepGroup.stepNumber != node.step) {
grouped.steps.push(stepGroup)
stepGroup = {
stepNumber: node.step,
nodes: [],
}
}
stepGroup.nodes.push(node)
})
grouped.steps.push(stepGroup)
delete grouped.nodes
})
return nodesGroupedByDate
}
function getTimeDuration(from, to) {
let then = moment(from, APIV2RequestDatetimeFormat)._d
let ms = moment(to, APIV2RequestDatetimeFormat).diff(moment(then))
let d = moment.duration(ms)
return Math.floor(d.asHours()) + moment.utc(ms).format('[h] mm[m] ss[s]')
}
Source