fleetmap-reports 2.0.358 → 2.0.360

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/package.json +1 -1
  2. package/src/partnerReports/driverranking-report.js +39 -7
  3. package/src/util/utils.js +3 -0
  4. package/.claude/worktrees/eager-stonebraker-c08ac7/.eslintrc.js +0 -15
  5. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/enGB.js +0 -309
  6. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/esCL.js +0 -297
  7. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/frFR.json +0 -324
  8. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/index.js +0 -35
  9. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptBR.js +0 -301
  10. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptPT.js +0 -310
  11. package/.claude/worktrees/eager-stonebraker-c08ac7/package-lock.json +0 -11944
  12. package/.claude/worktrees/eager-stonebraker-c08ac7/package.json +0 -45
  13. package/.claude/worktrees/eager-stonebraker-c08ac7/src/activity-report.js +0 -822
  14. package/.claude/worktrees/eager-stonebraker-c08ac7/src/automaticReports.js +0 -34
  15. package/.claude/worktrees/eager-stonebraker-c08ac7/src/carbone/index.js +0 -13
  16. package/.claude/worktrees/eager-stonebraker-c08ac7/src/custom/vistawasteActivity-report.js +0 -544
  17. package/.claude/worktrees/eager-stonebraker-c08ac7/src/events-report.js +0 -360
  18. package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-consumption-report.js +0 -296
  19. package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-drop-report.js +0 -128
  20. package/.claude/worktrees/eager-stonebraker-c08ac7/src/here.js +0 -172
  21. package/.claude/worktrees/eager-stonebraker-c08ac7/src/idle-report.js +0 -403
  22. package/.claude/worktrees/eager-stonebraker-c08ac7/src/index.js +0 -244
  23. package/.claude/worktrees/eager-stonebraker-c08ac7/src/kms-report.js +0 -549
  24. package/.claude/worktrees/eager-stonebraker-c08ac7/src/location-report.js +0 -496
  25. package/.claude/worktrees/eager-stonebraker-c08ac7/src/machines-report.js +0 -278
  26. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/afriquia.js +0 -129
  27. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/dailyuse-report.js +0 -80
  28. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/delayedstart-report.js +0 -223
  29. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/driverranking-report.js +0 -328
  30. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/gpsjump-report.js +0 -61
  31. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/performance-report.js +0 -359
  32. package/.claude/worktrees/eager-stonebraker-c08ac7/src/refueling-report.js +0 -345
  33. package/.claude/worktrees/eager-stonebraker-c08ac7/src/reportStyle.js +0 -34
  34. package/.claude/worktrees/eager-stonebraker-c08ac7/src/sensor-report.js +0 -447
  35. package/.claude/worktrees/eager-stonebraker-c08ac7/src/speeding-report.js +0 -663
  36. package/.claude/worktrees/eager-stonebraker-c08ac7/src/stop-report.js +0 -242
  37. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ActivityReportTemplate.mjml +0 -51
  38. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EmptyReportTemplate.mjml +0 -25
  39. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EventsReportTemplate.mjml +0 -75
  40. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/FuelDropReportTemplate.mjml +0 -60
  41. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/IdleReportTemplate.mjml +0 -54
  42. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportGroupByDayTemplate.mjml +0 -43
  43. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportTemplate.mjml +0 -44
  44. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/LocationReportTemplate.mjml +0 -37
  45. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/RefuelingReportTemplate.mjml +0 -60
  46. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SensorReportTemplate.mjml +0 -23
  47. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SpeedingReportTemplate.mjml +0 -80
  48. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/TripReportTemplate.mjml +0 -97
  49. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ZoneReportTemplate.mjml +0 -52
  50. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/report.mjml +0 -96
  51. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/activity.test.js +0 -99
  52. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/carbone.test.js +0 -20
  53. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/events.test.js +0 -30
  54. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/fuel.test.js +0 -35
  55. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/idle.test.js +0 -16
  56. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.js +0 -24
  57. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.test.js +0 -203
  58. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/kms.test.js +0 -74
  59. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/performance.test.js +0 -36
  60. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/positions.test.js +0 -67
  61. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/speeding.test.js +0 -120
  62. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/translations.test.js +0 -21
  63. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/trips.test.js +0 -140
  64. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/zones.test.js +0 -162
  65. package/.claude/worktrees/eager-stonebraker-c08ac7/src/trip-report.js +0 -641
  66. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/device.js +0 -13
  67. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/driver.js +0 -90
  68. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/fuel.js +0 -102
  69. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/geofence.js +0 -52
  70. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/odoo.js +0 -18
  71. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/parallel.js +0 -64
  72. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/pdfDocument.js +0 -140
  73. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/route.js +0 -78
  74. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/timetable.js +0 -30
  75. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.js +0 -213
  76. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.worker.js +0 -49
  77. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/trips.js +0 -270
  78. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/utils.js +0 -358
  79. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/worker.js +0 -14
  80. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/xpert.js +0 -58
  81. package/.claude/worktrees/eager-stonebraker-c08ac7/src/word/index.js +0 -95
  82. package/.claude/worktrees/eager-stonebraker-c08ac7/src/zone-report.js +0 -904
@@ -1,663 +0,0 @@
1
- const jsPDF = require('jspdf')
2
- const {
3
- convertMS, convertToLocaleString, convertToFeature, convertPositionToFeature, getTranslations, getLanguage,
4
- getCountry,
5
- getServerHost
6
- } = require('./util/utils')
7
- const { headerFromUser, AmiriRegular } = require('./util/pdfDocument')
8
- require('jspdf-autotable')
9
- const { getStyle } = require('./reportStyle')
10
- const drivers = require('./util/driver')
11
- const {
12
- getUserPartner
13
- } = require('fleetmap-partners')
14
- const traccarHelper = require('./util/traccar')
15
- const { devicesToProcess } = require('./util/device')
16
- const { point } = require('@turf/helpers')
17
- const distance = require('@turf/distance')
18
- const { insideGeofence } = require('./util/geofence')
19
- const booleanPointInPolygon = require('@turf/boolean-point-in-polygon')
20
- const automaticReports = require('./automaticReports')
21
- const { default: axios } = require('axios')
22
- const { getDriverName, getDriverData } = require('./util/driver')
23
-
24
- const fileName = 'SpeedingReport'
25
- const eventTypes = ['deviceOverspeed']
26
-
27
- async function createSpeedingReport (from, to, userData, traccarInstance) {
28
- const reportData = []
29
-
30
- if (userData.byDriver) {
31
- console.log('ByDriver')
32
- const allData = await createSpeedingReportByDriver(from, to, userData, traccarInstance)
33
- reportData.push(allData)
34
- } else if (userData.byGroup) {
35
- console.log('ByGroup')
36
- const allData = await createSpeedingReportByGroup(from, to, userData, traccarInstance)
37
- reportData.push(...allData)
38
- } else {
39
- console.log('createSpeedingReportByDevice')
40
- const allData = await createSpeedingReportByDevice(from, to, userData, traccarInstance)
41
- reportData.push(allData)
42
- }
43
-
44
- return reportData
45
- }
46
-
47
- async function createSpeedingReportByGroup (from, to, userData, traccarInstance) {
48
- const reportData = []
49
- for (const g of userData.groups) {
50
- const devices = userData.devices.filter(d => d.groupId === g.id)
51
- console.log(g.name + ' devices:' + devices.length)
52
- if (devices.length > 0) {
53
- const groupData = {
54
- devices: [],
55
- group: g,
56
- xpert: devices.filter(d => d.attributes.xpert).length > 0
57
- }
58
-
59
- const events = await traccarHelper.getEvents(traccarInstance, from, to, devices, eventTypes)
60
- const routes = await traccarHelper.getRoute(traccarInstance, from, to, devices)
61
-
62
- devices.sort((a, b) => (a.name > b.name) ? 1 : -1)
63
-
64
- if (events.length > 0) {
65
- groupData.devices = processDevices(from, to, devices, events, routes, userData)
66
- reportData.push(groupData)
67
- }
68
- }
69
- }
70
-
71
- const withoutGroupData = await createSpeedingReportByDevice(from, to, userData, traccarInstance)
72
- reportData.push(withoutGroupData)
73
-
74
- return reportData
75
- }
76
-
77
- function processServerSide () {
78
- return (process.env.PROCESS_HERE_EVENTS && process.env.PROCESS_HERE_EVENTS === 'server')
79
- }
80
-
81
- async function getOverspeedEvents (from, to, userData, devices, allData, traccarInstance) {
82
- const _sliced = automaticReports.sliceArray(automaticReports.sliceArray(devices, 5), 5)
83
- let deviceCount = 0
84
- const overspeedEvents = []
85
- for (const sliced of _sliced) {
86
- await Promise.all(sliced.map(async slice => {
87
- overspeedEvents.push(...await getEvents(traccarInstance, from, to, slice, userData, deviceCount, devices.length, slice.length))
88
- deviceCount = deviceCount + slice.length
89
- if (processServerSide()) {
90
- console.log('LOADING_MESSAGE:' + slice[0].name)
91
- console.log(`PROGRESS_PERC:${deviceCount / devices.length * 100}`)
92
- }
93
- }))
94
- }
95
- return overspeedEvents
96
- }
97
-
98
- async function createSpeedingReportByDevice (from, to, userData, traccarInstance) {
99
- const devices = devicesToProcess(userData)
100
-
101
- const allData = {
102
- devices: [],
103
- xpert: devices.filter(d => d && d.attributes && d.attributes.xpert).length > 0
104
- }
105
-
106
- allData.devices = await getOverspeedEvents(from, to, userData, devices, allData, traccarInstance)
107
-
108
- return allData
109
- }
110
-
111
- async function createSpeedingReportByDriver (from, to, userData, traccarInstance) {
112
- const { devices } = await drivers.devicesByDriver(traccarInstance, from, to, userData.drivers, userData.devices)
113
- console.log('Devices with driver', devices.length)
114
- const _drivers = []
115
- const sliced = automaticReports.sliceArray(devices, 5)
116
- let deviceCount = 0
117
- for (const slice of sliced) {
118
- const eventsByDevice = await getEvents(traccarInstance, from, to, slice, userData, deviceCount, devices.length, slice.length)
119
- if (processServerSide()) {
120
- console.log('LOADING_MESSAGE:' + slice[0].name)
121
- console.log(`PROGRESS_PERC:${deviceCount / devices.length * 100}`)
122
- }
123
- if (eventsByDevice && eventsByDevice.length > 0) {
124
- _drivers.push(...(await processDrivers(from, to, eventsByDevice, userData)))
125
- }
126
- deviceCount = deviceCount + slice.length
127
- }
128
- return { drivers: _drivers }
129
- }
130
-
131
- async function getEvents (traccarInstance, from, to, devices, userData, deviceCount, totalDevices, sliceSize) {
132
- if (processServerSide() && !userData.useVehicleSpeedLimit) {
133
- const cookie = await axios.get('/pinmeapi/cookie/get', { withCredentials: true }).then(d => d.data)
134
- const url = `https://${getServerHost()}/pinmeapi/reports/speeding-report/getEvents`
135
- const maxAttempts = 3
136
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
137
- try {
138
- return await axios.post(url, { from, to, devices, userData, deviceCount, totalDevices, sliceSize, cookie }, { withCredentials: true, timeout: 60000 }).then(d => d.data)
139
- } catch (e) {
140
- console.log(`getEvents failed for devices [${devices.map(d => d.name).join(', ')}] (attempt ${attempt}/${maxAttempts}): ${e.message}`)
141
- if (attempt === maxAttempts) {
142
- return []
143
- }
144
- }
145
- }
146
- } else {
147
- const geofencesFeatures = userData.geofences.filter(g => g.area && g.area.startsWith('POLYGON') &&
148
- g.attributes.speedLimit).map(g => convertToFeature(g))
149
-
150
- const allInOne = await traccarHelper.getAllInOne(traccarInstance, from, to, devices, true, false,
151
- false, false, deviceCount, totalDevices, sliceSize)
152
- const routes = allInOne.route
153
-
154
- const events = []
155
- if (userData.roadSpeedLimits) {
156
- const hereResults = await getRoadSpeedLimits(devices, routes, userData.maxSpeedThreshold, userData.minimumIdleMinutes)
157
- console.log('got', hereResults.length, 'from here')
158
- events.push(...hereResults)
159
- } else if (!userData.useVehicleSpeedLimit && userData.customSpeed) {
160
- events.push(...await getCustomSpeedLimitEvents(devices, routes, userData.customSpeed))
161
- } else {
162
- const traccarEvents = await traccarHelper.getEvents(traccarInstance, from, to, devices, eventTypes)
163
- if (geofencesFeatures.length) {
164
- events.push(...getGeofenceSpeedLimitEvents(geofencesFeatures, routes, devices))
165
- events.push(...traccarEvents.filter(e => !(e.geofenceId)))
166
- } else {
167
- events.push(...traccarEvents)
168
- }
169
- }
170
- if (events.length > 0) {
171
- return processDevices(from, to, devices, events, routes, userData)
172
- }
173
- return []
174
- }
175
- }
176
-
177
- function getGeofenceSpeedLimitEvents (geofencesFeatures, routes, devices) {
178
- const events = []
179
-
180
- devices.forEach(d => {
181
- const deviceRoute = routes.filter(p => p.deviceId === d.id)
182
- const routePoints = deviceRoute.map(p => convertPositionToFeature(p))
183
- geofencesFeatures.forEach(g => {
184
- let eventIsActive = false
185
- routePoints.forEach(p => {
186
- if (booleanPointInPolygon.default(p, g) && p.properties.position.speed > g.properties.geofence.attributes.speedLimit) {
187
- if (!eventIsActive) {
188
- const event = {
189
- positionId: p.properties.position.id,
190
- deviceId: d.id,
191
- geofenceId: g.properties.geofence.id,
192
- attributes: {
193
- speedLimit: g.properties.geofence.attributes.speedLimit,
194
- speed: p.properties.position.speed
195
- }
196
- }
197
- events.push(event)
198
- }
199
- eventIsActive = true
200
- } else {
201
- eventIsActive = false
202
- }
203
- })
204
- })
205
- })
206
-
207
- return events
208
- }
209
-
210
- async function processDrivers (from, to, eventsByDevice, userData) {
211
- const driversResult = []
212
-
213
- for (const d of userData.drivers) {
214
- const driverEvents = (await getDriverData(d, { events: eventsByDevice.map(d => d.alerts).flat() }, userData)).events
215
- if (driverEvents.length > 0) {
216
- driverEvents.sort((a, b) => a.positionId - b.positionId)
217
- driversResult.push({
218
- driver: d,
219
- from,
220
- to,
221
- alerts: driverEvents,
222
- maxSpeed: driverEvents.reduce((a, b) => {
223
- return a > b.attributes.maxSpeed ? a : b.attributes.maxSpeed
224
- }, 0)
225
- })
226
- }
227
- }
228
-
229
- return driversResult
230
- }
231
- const MAX_DEVICE_SPEED = 160
232
-
233
- function processDevices (from, to, devices, events, routes, userData) {
234
- const devicesResult = []
235
- findEventsPosition(from, to, devices, events, routes, userData)
236
-
237
- for (const d of devices) {
238
- const deviceEvents = events.filter(e => e.deviceId === d.id && e.position).filter(e => e.position.speed < MAX_DEVICE_SPEED)
239
- if (deviceEvents.length > 0) {
240
- deviceEvents.sort((a, b) => a.positionId - b.positionId)
241
- devicesResult.push({
242
- device: d,
243
- from,
244
- to,
245
- alerts: deviceEvents,
246
- maxSpeed: deviceEvents.reduce((a, b) => {
247
- return a > (b && b.attributes && b.attributes.maxSpeed) ? a : (b && b.attributes && b.attributes.maxSpeed)
248
- }, 0)
249
- })
250
- }
251
- }
252
-
253
- return devicesResult
254
- }
255
-
256
- function findEventsPosition (from, to, devices, events, routes, userData) {
257
- for (const d of devices) {
258
- const deviceEvents = events.filter(e => e.deviceId === d.id && e.positionId)
259
- if (deviceEvents.length > 0) {
260
- const positions = routes.filter(p => p.deviceId === d.id)
261
- for (const a of deviceEvents) {
262
- const pIndex = positions.findIndex(p => p.id === a.positionId)
263
- if (pIndex >= 0) {
264
- let geofence = null
265
- if (a.geofenceId) {
266
- geofence = userData.geofences.find(g => g.id === a.geofenceId)
267
- if (geofence) {
268
- a.geofenceName = geofence.name + ' (' + Math.round(a.attributes.speedLimit * 1.85200) + ' Km/h)'
269
- }
270
- }
271
-
272
- if (!userData.roadSpeedLimits) {
273
- a.position = positions[pIndex]
274
- calculateEventData(positions, pIndex, a, d, geofence, userData)
275
- }
276
- if (a.position.attributes.driverUniqueId) {
277
- a.driver = getDriverName(a.position.attributes.driverUniqueId, a.position.fixTime, userData)
278
- }
279
- a.deviceName = d.name
280
- } else {
281
- console.warn('cant find position for event', a)
282
- }
283
- }
284
- }
285
- }
286
- }
287
-
288
- async function getCustomSpeedLimitEvents (devices, routes, customSpeed) {
289
- console.log('custom speed limit events')
290
- const events = []
291
- const maxSpeed = customSpeed / 1.85200
292
-
293
- for (const d of devices) {
294
- const positions = routes.filter(p => p.deviceId === d.id)
295
- let eventIsActive = false
296
-
297
- for (const position of positions) {
298
- if (position.speed > maxSpeed && !eventIsActive) {
299
- const event = {
300
- positionId: position && position.id,
301
- position,
302
- deviceId: d.id,
303
- attributes: {
304
- speedLimit: maxSpeed,
305
- speed: position.speed
306
- }
307
- }
308
- events.push(event)
309
- eventIsActive = true
310
- } else if (position.speed <= maxSpeed) {
311
- eventIsActive = false
312
- }
313
- }
314
- }
315
-
316
- return events
317
- }
318
-
319
- async function getRoadSpeedLimits (devices, routes, threshold, minimumMinutes = 0) {
320
- const position = routes.find(p => getCountry(p))
321
- const country = position && getCountry(position)
322
- return getOSMSpeedingEvents(devices, routes, threshold, minimumMinutes, country)
323
- }
324
-
325
- exports.getRoadSpeedLimits = getRoadSpeedLimits
326
-
327
- let countSuccess = 0
328
- let countError = 0
329
- let lastSuccessDuration
330
- async function invokeValhalla (route, i, chunk, country, threshold, results, retry = 3) {
331
- const slice = route.slice(i, i + chunk)
332
- const url = `http://valhalla-${country}.pinme.io:8002/trace_attributes`
333
- try {
334
- const now = new Date()
335
- const {
336
- // eslint-disable-next-line camelcase
337
- matched_points,
338
- edges
339
- } = await axios.post(url,
340
- {
341
- costing: 'auto',
342
- shape_match: 'map_snap',
343
- filters: {
344
- attributes: [
345
- 'admin.country_code',
346
- 'admin.country_text',
347
- 'admin.state_code',
348
- 'admin.state_text',
349
- 'edge.names',
350
- 'edge.way_id',
351
- 'edge.speed_limit',
352
- 'matched.point',
353
- 'matched.type',
354
- 'matched.edge_index'
355
- ],
356
- action: 'include'
357
- },
358
- shape: slice.map(p => ({
359
- lon: p.longitude,
360
- lat: p.latitude
361
- }))
362
- }, { timeout: 50000 })
363
- .then(r => r.data)
364
- countSuccess++
365
- lastSuccessDuration = new Date() - now
366
- // eslint-disable-next-line camelcase
367
- matched_points.forEach((mp, mIndex) => {
368
- const edge = edges[mp.edge_index]
369
- const position = route[mIndex + i]
370
- if (edge && (edge.speed_limit + (threshold || 0)) < position.speed * 1.852) {
371
- results.push({ ...mp, ...edge, ...position, position })
372
- }
373
- })
374
- } catch (e) {
375
- countError++
376
- if (e.response && e.response.data && e.response.data.error_code === 444) {
377
- console.warn(e.response.data.error)
378
- return
379
- }
380
- if (--retry) {
381
- console.warn('retry', retry, e.message, (e.response && e.response.data) || url, 'deviceId',
382
- slice[0] && slice[0].deviceId, slice[0] && slice[0].address, slice[0] && slice[0].fixTime, country, 'chunk', chunk,
383
- 'success', countSuccess, 'error', countError, lastSuccessDuration)
384
- return invokeValhalla(route, i, chunk, country, threshold, results, retry)
385
- } else {
386
- console.error(e.message, (e.response && e.response.data) || url, 'deviceId',
387
- slice[0] && slice[0].deviceId, slice[0] && slice[0].address, slice[0] && slice[0].fixTime, country, 'chunk', chunk, 'success', countSuccess, 'error', countError, lastSuccessDuration)
388
- }
389
- }
390
- }
391
-
392
- async function getOSMSpeedingEvents (devices, routes, threshold, minimumMinutes = 0, country) {
393
- const chunk = 100
394
- const events = []
395
- for (const d of devices) {
396
- const route = routes.filter(r => r.deviceId === d.id)
397
- const results = []
398
- const promises = []
399
- for (let i = 0; i < route.length; i += chunk) {
400
- promises.push(invokeValhalla(route, i, chunk, country, threshold, results))
401
- }
402
- await Promise.all(promises)
403
- const reduced = results.reduce((acc, cur, idx, src) => {
404
- const last = acc.length && acc.slice(-1)[0]
405
- if (last && new Date(cur.fixTime) - new Date(src[idx - 1].fixTime) < 1000 * 60 * 2) {
406
- last.eventTime = new Date(cur.fixTime) - new Date(last.fixTime)
407
- last.attributes.maxSpeed = Math.max(last.attributes.maxSpeed, cur.speed)
408
- last.distance += distance.default(point([cur.longitude, cur.latitude]), point([src[idx - 1].longitude, src[idx - 1].latitude]))
409
- } else {
410
- cur.positionId = cur && cur.id
411
- cur.roadSpeedLimit = cur.speed_limit
412
- cur.deviceId = d.id
413
- cur.attributes = { speedLimit: cur.speed_limit, speed: cur.speed, maxSpeed: cur.speed }
414
- cur.eventTime = 0
415
- cur.distance = 0
416
- acc.push(cur)
417
- }
418
- return acc
419
- }, [])
420
- events.push(...reduced)
421
- }
422
- return events.filter(e => e.eventTime >= minimumMinutes * 60 * 1000)
423
- }
424
-
425
- function calculateEventData (positions, pIndex, alert, device, geofence) {
426
- let endEventPosition = alert.position
427
- let maxSpeed = alert.position.speed
428
- let dist = 0
429
- while (pIndex + 1 < positions.length) {
430
- pIndex++
431
- dist += distance.default(point([endEventPosition.longitude, endEventPosition.latitude]),
432
- point([positions[pIndex].longitude, positions[pIndex].latitude]))
433
- endEventPosition = positions[pIndex]
434
- if (endEventPosition.speed <= alert.attributes.speedLimit || (geofence && !insideGeofence(endEventPosition, geofence))) {
435
- alert.eventTime = new Date(endEventPosition.fixTime) - new Date(alert.position.fixTime) + (device.attributes.overspeedMinimalDuration || 0) * 1000
436
- alert.attributes.maxSpeed = maxSpeed
437
- alert.distance = dist
438
- break
439
- } else {
440
- if (maxSpeed < endEventPosition.speed) {
441
- maxSpeed = endEventPosition.speed
442
- }
443
- }
444
- }
445
- }
446
-
447
- async function exportSpeedingReportToPDF (userData, reportData) {
448
- console.log('Export to PDF')
449
- const lang = getLanguage(userData)
450
-
451
- const timezone = userData.user.attributes.timezone
452
- const translations = getTranslations(userData)
453
-
454
- const overspeedData = userData.byDriver ? reportData.drivers : reportData.devices
455
-
456
- const headers = [
457
- translations.report.date,
458
- translations.report.address,
459
- translations.report.overspeed,
460
- translations.report.distance,
461
- translations.report.maxSpeed,
462
- translations.report.duration
463
- ]
464
-
465
- if (userData.roadSpeedLimits) {
466
- headers.splice(2, 0, translations.report.roadSpeedLimit)
467
- }
468
-
469
- if (userData.byDriver) {
470
- headers.push(translations.report.vehicle)
471
- } else {
472
- headers.push(translations.report.driver)
473
- }
474
-
475
- if (overspeedData) {
476
- let first = true
477
- // eslint-disable-next-line new-cap
478
- const doc = new jsPDF.jsPDF('l', undefined, undefined, true)
479
- await headerFromUser(doc, translations.report.titleSpeedingReport, userData.user)
480
-
481
- overspeedData.forEach(d => {
482
- try {
483
- const data = []
484
-
485
- const name = userData.byDriver ? d.driver.name : deviceName(d.device)
486
- const group = userData.byDriver ? userData.groups.find(g => g.drivers && g.drivers.includes(d.id)) : userData.groups.find(g => d.device.groupId === g.id)
487
-
488
- let space = 0
489
- if (!first) {
490
- doc.addPage()
491
- } else {
492
- first = false
493
- space = 10
494
- }
495
- doc.setFontSize(13)
496
- doc.text(name, 20, space + 20)
497
- doc.setFontSize(11)
498
- doc.text(group ? translations.report.group + ': ' + group.name : '', 150, space + 20)
499
- doc.text(convertToLocaleString(d.from, lang, timezone, userData.user) + ' - ' + convertToLocaleString(d.to, lang, timezone, userData.user), 20, space + 25)
500
- if (d.alerts[0] && !userData.byDriver) {
501
- doc.text(translations.report.speedLimit + ': ' + ((userData.useVehicleSpeedLimit || !userData.customSpeed) ? Math.round(d.alerts[0].attributes.speedLimit * 1.85200) + ' Km/h' : (userData.roadSpeedLimits ? translations.report.roadSpeedLimit : userData.customSpeed + ' Km/h')), 20, space + 30)
502
- }
503
- d.alerts.forEach(a => {
504
- const temp = [
505
- getAlertDate(a, userData.user),
506
- a.position.address + (a.geofenceName ? ' - ' + a.geofenceName : ''),
507
- Math.round(a.attributes.speed * 1.85200),
508
- a.distance && new Intl.NumberFormat(userData.user.attributes.lang, { maximumSignificantDigits: 1 }).format(a.distance),
509
- a.attributes.maxSpeed ? Math.round(a.attributes.maxSpeed * 1.85200) : Math.round(a.attributes.speed * 1.85200),
510
- convertMS(a.eventTime, true),
511
- userData.byDriver ? a.deviceName : a.driver
512
- ]
513
-
514
- if (userData.roadSpeedLimits) {
515
- temp.splice(2, 0, a.roadSpeedLimit)
516
- }
517
-
518
- data.push(temp)
519
- })
520
-
521
- const footValues = [
522
- 'Total:' + d.alerts.length,
523
- '',
524
- '',
525
- '',
526
- getMaxSpeed(d.alerts),
527
- convertMS(d.alerts.reduce((a, b) => a + b.eventTime, 0), true)
528
- ]
529
-
530
- if (userData.roadSpeedLimits) {
531
- footValues.splice(2, 0, '')
532
- }
533
-
534
- if (timezone === 'Asia/Qatar') {
535
- doc.addFileToVFS('Amiri-Regular.ttf', AmiriRegular())
536
- doc.addFont('Amiri-Regular.ttf', 'Amiri', 'normal')
537
- }
538
-
539
- const style = getStyle(getUserPartner(userData.user))
540
- doc.autoTable({
541
- head: [headers],
542
- body: data,
543
- foot: [footValues],
544
- showFoot: 'lastPage',
545
- headStyles: {
546
- fillColor: style.pdfHeaderColor,
547
- textColor: style.pdfHeaderTextColor,
548
- fontSize: 10
549
- },
550
- bodyStyles: {
551
- fillColor: style.pdfBodyColor,
552
- textColor: style.pdfBodyTextColor,
553
- fontSize: 8,
554
- font: timezone === 'Asia/Qatar' ? 'Amiri' : ''
555
- },
556
- footStyles: {
557
- fillColor: style.pdfFooterColor,
558
- textColor: style.pdfFooterTextColor,
559
- fontSize: 9
560
- },
561
- startY: space + 35
562
- })
563
- } catch (e) {
564
- console.error(e, 'moving on...')
565
- }
566
- })
567
-
568
- return doc
569
- }
570
- }
571
-
572
- function exportSpeedingReportToExcel (userData, reportData) {
573
- console.log('Export to Excel')
574
-
575
- const lang = getLanguage(userData)
576
- const translations = getTranslations(userData)
577
- const overspeedData = userData.byDriver ? reportData.drivers : reportData.devices
578
-
579
-
580
- const settings = {
581
- sheetName: translations.report.titleSpeedingReport.substring(0, 31), // The name of the sheet
582
- fileName // The name of the spreadsheet
583
- }
584
- const headers = [
585
- userData.byDriver ? { label: translations.report.driver, value: 'driver' } : { label: translations.report.vehicle, value: 'name' },
586
- userData.byDriver ? { label: translations.report.group, value: 'groupName' } : { label: translations.report.group, value: 'groupName' },
587
- { label: translations.report.date, value: 'fixTime' },
588
- { label: translations.report.address, value: 'address' },
589
- {
590
- label: userData.roadSpeedLimits ? translations.report.roadSpeedLimit : translations.report.speedLimit,
591
- value: 'speedLimit'
592
- },
593
- { label: translations.report.overspeed, value: 'speed' },
594
- { label: translations.report.distance, value: 'distance' },
595
- { label: translations.report.maxSpeed, value: 'maxSpeed' },
596
- { label: translations.report.duration, value: 'duration' },
597
- userData.byDriver ? { label: translations.report.vehicle, value: 'name' } : { label: translations.report.driver, value: 'driver' },
598
- { label: 'link', value: 'link', format: 'hyperlink' }
599
- ]
600
-
601
- if (userData.roadSpeedLimits) {
602
- headers.push({ label: translations.report.info, value: 'info' })
603
- }
604
-
605
- let data = []
606
- if (overspeedData) {
607
- overspeedData.forEach(d => {
608
- data = data.concat([{}])
609
- data = data.concat(d.alerts.map(a => {
610
- const group = userData.groups.find(g => g.id === d.device.groupId)
611
- return {
612
- groupName: group ? group.name : '',
613
- driver: userData.byDriver ? d.driver.name : a.driver,
614
- speed: Math.round(a.attributes.speed * 1.85200),
615
- distance: a.distance && new Intl.NumberFormat(lang, { maximumSignificantDigits: 1 }).format(a.distance),
616
- maxSpeed: a.attributes.maxSpeed ? Math.round(a.attributes.maxSpeed * 1.85200) : Math.round(a.attributes.speed * 1.85200),
617
- duration: convertMS(a.eventTime, true),
618
- fixTime: getAlertDate(a, userData.user),
619
- name: userData.byDriver ? a.deviceName : d.device.name,
620
- address: a.position.address + (a.geofenceName ? ' - ' + a.geofenceName : ''),
621
- speedLimit: userData.roadSpeedLimits
622
- ? a.roadSpeedLimit
623
- : userData.useVehicleSpeedLimit
624
- ? Math.round(a.attributes.speedLimit * 1.85200)
625
- : userData.customSpeed,
626
- ...(userData.roadSpeedLimits ? { info: `https://openstreetmap.org/way/${a.way_id}` } : {}),
627
- link: `https://${getUserPartner(userData.user).host}/#/map?vehicleName=${userData.byDriver ? a.deviceName : d.device.name}&date=${new Date(a.position.fixTime).toISOString()}`
628
- }
629
- }))
630
- })
631
- return {
632
- headers,
633
- data,
634
- settings
635
- }
636
- }
637
- }
638
-
639
- function deviceName (device) {
640
- return device.name + (device.attributes.license_plate ? ', ' + device.attributes.license_plate : '') + (device.model ? ', ' + device.model : '')
641
- }
642
-
643
- function getAlertDate (row, user) {
644
- return convertToLocaleString(row.position.fixTime, user.attributes.lang, user.attributes.timezone, user)
645
- }
646
-
647
- function getMaxSpeed (data) {
648
- const values = data.map(item => {
649
- return item.attributes.maxSpeed
650
- })
651
- const max = values.reduce(function (a, b) {
652
- return Math.max(a, b)
653
- })
654
- return Math.round(max * 1.85200)
655
- }
656
-
657
- exports.createSpeedingReport = createSpeedingReport
658
- exports.exportSpeedingReportToPDF = exportSpeedingReportToPDF
659
- exports.exportSpeedingReportToExcel = exportSpeedingReportToExcel
660
- exports.create = createSpeedingReport
661
- exports.exportToPDF = exportSpeedingReportToPDF
662
- exports.exportToExcel = exportSpeedingReportToExcel
663
- exports.getOverspeedEvents = getOverspeedEvents