import { linestringsToPolylines } from '@/mixins/map.js'
import moment from 'moment'
import {
formatDatetimeWithSeconds,
datetimeToTimestamp,
} from '@/utils/dates.js'
import {
getNestedValue,
getNestedValueFormattedDatetimeMilliseconds,
getNestedValueFormattedTime,
} from '@/utils/object.js'
import { generateShortId } from '@/utils/crypto.js'
import { APIV2ResponseDatetimeFormat } from '@/config/simpliciti-apis.js'
const R = require('ramda')
export function normalizeCategory(c) {
return Object.freeze({
id: getNestedValue(c, 'id'),
name: getNestedValue(c, 'label'),
parent_id: getNestedValue(c, 'parentId'),
})
}
/**
* Normalizes an API response item from real-time/history by vehicle/driver/circuit (APIV2)
*
* @param {*} item
* @param {*} options
* @returns
*/
export default function normalizeEntity(item, options = {}) {
const vehicleId = getNestedValue(item, ['vehicule.id', 'vehicule_id'])
const vehicleCategory = options.vehicleCategory || {}
const vehicleCategoryName =
getNestedValue(
item,
['vehicule.categorie_nom', 'vehicule_categorie_nom', 'categorie_nom'],
null
) ||
vehicleCategory.name ||
''
//options.getNormalizedItem = () => normalizedItem
// Take linestring raw data from linestring_troncons if available (#35102)
item.linestring = getNestedValue(
item,
[
'linestring_troncons' /*history by circuit*/,
'linestring' /*history by vehicle/driver*/,
],
'',
{
transform(value) {
if (value instanceof Array) {
//{couleur:'',linestring:''}
return value.map((valueItem) => {
if (typeof valueItem === 'string') {
//Use case: History by vehicle/driver
return {
linestring: valueItem,
color: null,
}
} else {
//Use case: History by circuit
return {
linestring: getNestedValue(valueItem, 'linestring', ''),
color: getNestedValue(valueItem, 'couleur', null),
}
}
})
} else {
return value
}
},
}
)
//A trip history colliding a zone interdiction might produce multiple linestrings/polylines.
let relatedPolylines = (options.relatedPolylines =
item.linestring === undefined
? []
: getPolylinesFromItemLinestring(item, {
...options,
color: getNestedValue(item, 'couleur'),
transform(obj) {
obj.itemId = vehicleId
return obj
},
}))
let normalizedItem = {}
return (normalizedItem = Object.freeze({
//Common (Mixed)f
searchType: options.isHistoryMode ? 'history' : 'realtime',
id: vehicleId,
type: options.selectionType,
label: (options.label = getLocationItemLabel(item, options.selectionType)),
//Temp reel
speed: (options.speed = getNestedValue(item, 'temps_reel.vitesse')),
statusColor: getNestedValue(item, ['temps_reel.status_couleur', 'couleur']),
address: {
streetNumber: getNestedValue(item, 'temps_reel.adresse.numero_rue'),
streetAddress: getNestedValue(item, [
'temps_reel.adresse.rue',
'adresse_deb',
]),
zipCode: getNestedValue(item, [
'temps_reel.adresse.code_postal',
'cp_deb',
]),
city: getNestedValue(item, ['temps_reel.adresse.ville', 'ville_deb']),
},
addressFormatted: (() => {
let address = {
streetNumber: getNestedValue(item, 'temps_reel.adresse.numero_rue'),
streetAddress: getNestedValue(item, [
'temps_reel.adresse.rue',
'adresse_deb',
]),
zipCode: getNestedValue(item, [
'temps_reel.adresse.code_postal',
'cp_deb',
]),
city: getNestedValue(item, ['temps_reel.adresse.ville', 'ville_deb']),
}
let r = `${address.streetNumber} ${address.streetAddress}`.trim()
r = (r ? r + `, ` : r + ``) + address.zipCode
r = (r ? r + `, ` : r + ``) + address.city
return r
})(),
lat: getNestedValue(item, 'temps_reel.latitude'),
lng: getNestedValue(item, 'temps_reel.longitude'),
cap: getNestedValue(item, 'temps_reel.cap'),
//Sensor infos (Temp reel)
...parseLocationItemSensors(item),
//Vehicle (Mixed)
vehicleId: parseInt(
getNestedValue(item, ['vehicule.id', 'vehicule_id'], null)
),
vehicleName: getNestedValue(item, ['vehicule.nom', 'vehicule_nom'], null),
vehicleCategoryClassName: getNestedValue(item, [
'vehicule.categorie_classe',
]),
vehicleCategoryId: getNestedValue(vehicleCategory, 'id', null),
vehicleCategoryName,
vehicleStatusColor: getNestedValue(item, 'temps_reel.status_couleur'),
vehicleContactOn: getNestedValue(item, 'temps_reel.contact', false),
vehicleStatusLabel: getNestedValue(item, 'temps_reel.status_libelle', ''),
vehicleMatriculation: getNestedValue(
item,
['vehicle.immatriculation', 'vehicule_immatriculation'],
''
),
//Circuit (Mixed)
circuitId: getNestedValue(
item,
['circuit.id', 'circuit_id', 'executions_circuits.0.circuit_id'],
null
),
circuitExecutions: getNestedValue(item, 'executions_circuits', [], {
transform(value) {
return (value || []).map((item) => {
return {
executionId: item.executioncircuit_id,
circuitId: item.circuit_id,
startDate: moment(item.date_debut),
endDate: item.date_fin
? moment(item.date_fin)
: moment(item.date_debut).hour(23).minute(59).second(59),
circuitName: item.nomcourt,
}
})
},
}),
circuitName: getNestedValue(
item,
['circuit.nom_court', 'circuit_nom_court'],
null
),
circuitCategoryName: getNestedValue(item, 'circuit_categorie', ''),
circuitExecutionId: getNestedValue(
item,
['circuit.execution_id', 'executioncircuit_id'],
null
),
circuitStartDate: getNestedValue(item, 'circuit.debut_execution', null),
circuitStartDateFormatted: getNestedValueFormattedDatetimeMilliseconds(
item,
'circuit.debut_execution'
),
circuitCompletionRate: getNestedValue(
item,
['circuit.taux_realisation', 'taux_realisation_circuit'],
0
),
circuitDriversCount: (() => {
let team = getNestedValue(item, 'fdr.equipage.membres', [])
return team.length
})(),
/**
* Use now as end date if null is supplied
*/
circuitServiceDuration: (() => {
let start = moment(getNestedValue(item, 'fdr.service_debut_reel', null))
let end = moment(getNestedValue(item, 'fdr.service_fin_reel', null))
if (!start.isValid() || !end.isValid()) {
return ''
}
let ms = end.diff(start)
let d = moment.duration(ms)
let hours = Math.floor(d.asHours())
let rightPart = moment.utc(ms)
if (isNaN(hours) || !rightPart.isValid()) {
return ''
}
return hours + rightPart.format('[ h ]mm[ m ]ss[ s ]')
})(),
missionId: getNestedValue(item, ['mission.id'], null),
//Driver (Temp reel)
driverId: getNestedValue(item, ['driverId', 'chauffeur.id'], null),
driverName: getNestedValue(
item,
['chauffeur.nom', 'driverName', 'nom_chauffeur'],
null
),
driverBadgeId: getNestedValue(item, 'chauffeur.badge_id', null),
driverCardNumber: getNestedValue(item, 'chauffeur.numero_carte', null),
driverPhone: getNestedValue(item, 'chauffeur.tel_portable', null),
driverDate: getNestedValue(item, 'chauffeur.dateheure', null),
driverDateFormatted: getNestedValueFormattedDatetimeMilliseconds(
item,
'chauffeur.dateheure'
),
driverChronoDate: getNestedValue(item, 'chauffeur.chrono_dateheure', null),
driverChronoEnabled: getNestedValue(item, 'chauffeur.chrono_etat', null),
driverChronoStatus: getDriverChronoStatus(
getNestedValue(item, 'chauffeur.chrono_imputation', null)
),
driverChornoStatusOriginal: getNestedValue(
item,
'chauffeur.chrono_imputation',
null
),
driverPlace: getNestedValue(item, 'chauffeur.site_nom', null),
//History
...parseHistoryItem(item, options),
//Mixed
relatedPolylines,
//@TODO: Refactor/Remove, use vehicleCategoryId instead
//Warning: Location - List sort results by this attribute, meaning drivers/circuits will be sorted by vehicle category id (BUG!?)
categoryId: getNestedValue(vehicleCategory, 'id', null),
...parseLocationItemDates(item, options),
//@todo: Refactor and remove raw attribute
raw: item,
}))
}
function stringDatetimeToDate(date, inputFormat = 'YYYY[-]MM[-]DD HH:mm:ss') {
return moment(date, inputFormat)._d
}
/**
*
* Parse dates fields from both real-time/history
*
* @param {*} item
* @param {*} options
* @returns
*/
function parseLocationItemDates(item, options) {
let date = getNestedValue(item, [
'temps_reel.dateheure',
'dh_vehicule_deb',
'dh_exec_circuit_debut',
'dh_activ_deb',
])
return {
date: stringDatetimeToDate(date),
timestamp: stringDatetimeToDate(date).getTime(),
dateFormatted: formatDatetimeWithSeconds(stringDatetimeToDate(date)),
dateElapsedFormatted: (() => {
let then = stringDatetimeToDate(date)
let ms = moment().diff(moment(then))
let d = moment.duration(ms)
return (
Math.floor(d.asHours()) + moment.utc(ms).format('[ h ]mm[ m ]ss[ s ]')
)
})(),
//History mode only
...(options.isHistoryMode && options.selectionType === 'circuit'
? {
circuitDatetimeFrom: (options.circuitDatetimeFrom =
stringDatetimeToDate(
getNestedValue(item, 'dh_exec_circuit_debut')
)),
circuitDatetimeTo: (options.circuitDatetimeTo = stringDatetimeToDate(
getNestedValue(item, 'dh_exec_circuit_fin')
)),
circuitTimeFrom: getNestedValueFormattedTime(
item,
'dh_exec_circuit_debut'
),
circuitTimeTo: getNestedValueFormattedTime(
item,
'dh_exec_circuit_fin'
),
}
: {}),
}
}
function getLocationItemLabel(item, selectionType = 'vehicle') {
let vehicleName = getNestedValue(item, ['vehicule.nom', 'vehicule_nom'], null)
let driverName = getNestedValue(item, ['chauffeur.nom', 'driverName'], null)
driverName = `${driverName} (${vehicleName})`
const labels = {
vehicle: vehicleName,
driver: driverName,
circuit: getNestedValue(
item,
['circuit.nom_court', 'circuit_nom_court'],
null
),
}
return labels[selectionType]
}
/**
* Parses item from API /v2/historique/
*
* - Circuit: by_circuit_dates
* - Vehicle/Driver: by_vehicule_dates/by_chauffeur_dates
*
* @param {Object} item
* @param {String} options.selectionType vehicle/driver/circuit
* @param {Array} options.relatedPolylines
* @returns
*/
function parseHistoryItem(item, options = {}) {
const isHistoryByCircuit =
options.isHistoryMode && options.selectionType === 'circuit'
const isHistoryByVehicleDriver =
options.isHistoryMode &&
['driver', 'vehicle'].includes(options.selectionType)
let historyByCircuitFields = !isHistoryByCircuit
? {}
: {
circuitDistance: getNestedValue(item, ['lg_realisee_circuit'], 0),
/*circuitLeafletPolyline:
options.relatedPolylines.length > 0
? options.relatedPolylines[0]
: null,*/
}
let historyByVehicleOrDriverFields = !isHistoryByVehicleDriver
? {}
: {
totalDistance: getNestedValue(item, 'distance_troncon', 0),
averageSpeed: getNestedValue(item, 'vitesse', 0),
driverIdentification: getNestedValue(item, 'hdeb_activite', 0),
driverDesidentification: getNestedValue(item, 'hfin_activite', 0),
historyAddressFrom: (() => {
let address = {
streetAddress: getNestedValue(item, 'adresse_deb'),
zipCode: getNestedValue(item, 'cp_deb'),
city: getNestedValue(item, 'ville_deb'),
}
let r = `${address.streetAddress}`.trim()
r = (r ? r + `, ` : r + ``) + address.zipCode
r = (r ? r + `, ` : r + ``) + address.city
return r
})(),
historyAddressTo: (() => {
let address = {
streetAddress: getNestedValue(item, 'adresse_fin'),
zipCode: getNestedValue(item, 'cp_fin'),
city: getNestedValue(item, 'ville_fin'),
}
let r = `${address.streetAddress}`.trim()
r = (r ? r + `, ` : r + ``) + address.zipCode
r = (r ? r + `, ` : r + ``) + address.city
return r
})(),
historyStartTime: getNestedValueFormattedTime(item, 'dh_activ_deb', 0),
historyEndTime: getNestedValueFormattedTime(item, 'dh_activ_fin', 0),
historyStartDatetimeStamp: datetimeToTimestamp(
getNestedValue(item, 'dh_activ_deb', 0),
APIV2ResponseDatetimeFormat
),
historyEndDatetimeStamp: datetimeToTimestamp(
getNestedValue(item, 'dh_activ_fin', 0),
APIV2ResponseDatetimeFormat
),
historyStartDatetime: getNestedValueFormattedDatetimeMilliseconds(
item,
'dh_activ_deb',
0
),
historyEndDatetime: getNestedValueFormattedDatetimeMilliseconds(
item,
'dh_activ_fin',
0
),
totalElapsedTime: getNestedValue(item, 'duree_troncon', 0),
totalContactOn: getNestedValue(item, 'duree_contact', 0),
totalBrakes: getNestedValue(item, 'duree_arret', 0),
brakesTotalAmount: getNestedValue(item, 'nb_arret', 0),
tripHistoryPolyline:
options.relatedPolylines.length > 0
? options.relatedPolylines[0]
: null,
}
return {
...(isHistoryByCircuit ? historyByCircuitFields : {}),
...(isHistoryByVehicleDriver ? historyByVehicleOrDriverFields : {}),
}
}
/**
* Parses sensor infos (Temp reel)
* @param {*} item
* @returns
*/
function parseLocationItemSensors(item) {
return {
//Sensors ------------
hasSensors:
Object.keys(getNestedValue(item, 'temps_reel.capteurs', {})).length > 0,
//Vitesse Rotation Moteur
sensorCanRPM: getNestedValue(item, 'temps_reel.capteurs.can.vrm', null),
//Fleet management system
sensorCanKMFMS: getNestedValue(
item,
'temps_reel.capteurs.can.km_fms',
null
),
sensorCanFuelPerc: getNestedValue(
item,
'temps_reel.capteurs.can.niveau_carburant',
null
),
sensorCanConsumptionLiters: getNestedValue(
item,
'temps_reel.capteurs.can.conso',
null
),
sensorCanThrottle: getNestedValue(
item,
'temps_reel.capteurs.can.accelerateur',
null
),
sensorCanBatteryPerc: getNestedValue(
item,
'temps_reel.capteurs.can.batterie',
null,
{
transform: (v) => (!isNaN(parseInt(v)) && parseInt(v) <= 4 ? null : v), //Only grab value if > 4
}
),
sensorCanBraking: getNestedValue(
item,
'temps_reel.capteurs.can.freinage',
null
),
sensorCanClutch: getNestedValue(
item,
'temps_reel.capteurs.can.embrayage',
null
),
sensorTor: getNestedValue(item, 'temps_reel.capteurs.tor', [])
.filter((tor) => tor.etat !== null)
.map((tor) => {
return {
name: tor.nom ? tor.nom : `TOR${tor.num || '?'}`,
enabled: tor.etat === 1,
}
}),
sensorAna: getNestedValue(item, 'temps_reel.capteurs.ana', [])
.filter((ana) => ana.val !== null)
.map((ana) => {
return {
name: ana.nom ? ana.nom : `ANA${ana.num || '?'}`,
value: ana.val,
}
}),
}
}
/**
*
* Special polyline entity used by Location - History, Location - Realtime - Circuit
* getNormalizedItem: To be able to retrieve item details from leaflet popup (Trip-history/Circuit-exec polyline popup)
*
* @param {*} linestring
* @param {*} options
* @returns
*/
function getPolylinesFromItemLinestring(item, options = {}) {
return linestringsToPolylines(
[
{
linestring: item.linestring,
number: generateShortId('history_'), //To be able to highlight history related polylines (Leaflet)
},
],
{
...options,
transform(obj) {
obj = {
...obj,
...((options && options.transform && options.transform(obj)) || {}),
}
return obj
},
}
)
}
/**
* @todo Move to chrono-service
* @param {*} value
* @returns
*/
function getDriverChronoStatus(value) {
return (
{
Travail: 'working',
Repos: 'resting',
Conduite: 'driving',
Dispo: 'available',
}[value] || value
)
}
Source