fleetmap-reports 2.0.358 → 2.0.359

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 (81) hide show
  1. package/package.json +1 -1
  2. package/src/partnerReports/driverranking-report.js +39 -7
  3. package/.claude/worktrees/eager-stonebraker-c08ac7/.eslintrc.js +0 -15
  4. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/enGB.js +0 -309
  5. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/esCL.js +0 -297
  6. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/frFR.json +0 -324
  7. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/index.js +0 -35
  8. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptBR.js +0 -301
  9. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptPT.js +0 -310
  10. package/.claude/worktrees/eager-stonebraker-c08ac7/package-lock.json +0 -11944
  11. package/.claude/worktrees/eager-stonebraker-c08ac7/package.json +0 -45
  12. package/.claude/worktrees/eager-stonebraker-c08ac7/src/activity-report.js +0 -822
  13. package/.claude/worktrees/eager-stonebraker-c08ac7/src/automaticReports.js +0 -34
  14. package/.claude/worktrees/eager-stonebraker-c08ac7/src/carbone/index.js +0 -13
  15. package/.claude/worktrees/eager-stonebraker-c08ac7/src/custom/vistawasteActivity-report.js +0 -544
  16. package/.claude/worktrees/eager-stonebraker-c08ac7/src/events-report.js +0 -360
  17. package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-consumption-report.js +0 -296
  18. package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-drop-report.js +0 -128
  19. package/.claude/worktrees/eager-stonebraker-c08ac7/src/here.js +0 -172
  20. package/.claude/worktrees/eager-stonebraker-c08ac7/src/idle-report.js +0 -403
  21. package/.claude/worktrees/eager-stonebraker-c08ac7/src/index.js +0 -244
  22. package/.claude/worktrees/eager-stonebraker-c08ac7/src/kms-report.js +0 -549
  23. package/.claude/worktrees/eager-stonebraker-c08ac7/src/location-report.js +0 -496
  24. package/.claude/worktrees/eager-stonebraker-c08ac7/src/machines-report.js +0 -278
  25. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/afriquia.js +0 -129
  26. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/dailyuse-report.js +0 -80
  27. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/delayedstart-report.js +0 -223
  28. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/driverranking-report.js +0 -328
  29. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/gpsjump-report.js +0 -61
  30. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/performance-report.js +0 -359
  31. package/.claude/worktrees/eager-stonebraker-c08ac7/src/refueling-report.js +0 -345
  32. package/.claude/worktrees/eager-stonebraker-c08ac7/src/reportStyle.js +0 -34
  33. package/.claude/worktrees/eager-stonebraker-c08ac7/src/sensor-report.js +0 -447
  34. package/.claude/worktrees/eager-stonebraker-c08ac7/src/speeding-report.js +0 -663
  35. package/.claude/worktrees/eager-stonebraker-c08ac7/src/stop-report.js +0 -242
  36. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ActivityReportTemplate.mjml +0 -51
  37. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EmptyReportTemplate.mjml +0 -25
  38. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EventsReportTemplate.mjml +0 -75
  39. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/FuelDropReportTemplate.mjml +0 -60
  40. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/IdleReportTemplate.mjml +0 -54
  41. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportGroupByDayTemplate.mjml +0 -43
  42. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportTemplate.mjml +0 -44
  43. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/LocationReportTemplate.mjml +0 -37
  44. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/RefuelingReportTemplate.mjml +0 -60
  45. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SensorReportTemplate.mjml +0 -23
  46. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SpeedingReportTemplate.mjml +0 -80
  47. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/TripReportTemplate.mjml +0 -97
  48. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ZoneReportTemplate.mjml +0 -52
  49. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/report.mjml +0 -96
  50. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/activity.test.js +0 -99
  51. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/carbone.test.js +0 -20
  52. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/events.test.js +0 -30
  53. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/fuel.test.js +0 -35
  54. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/idle.test.js +0 -16
  55. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.js +0 -24
  56. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.test.js +0 -203
  57. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/kms.test.js +0 -74
  58. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/performance.test.js +0 -36
  59. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/positions.test.js +0 -67
  60. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/speeding.test.js +0 -120
  61. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/translations.test.js +0 -21
  62. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/trips.test.js +0 -140
  63. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/zones.test.js +0 -162
  64. package/.claude/worktrees/eager-stonebraker-c08ac7/src/trip-report.js +0 -641
  65. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/device.js +0 -13
  66. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/driver.js +0 -90
  67. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/fuel.js +0 -102
  68. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/geofence.js +0 -52
  69. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/odoo.js +0 -18
  70. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/parallel.js +0 -64
  71. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/pdfDocument.js +0 -140
  72. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/route.js +0 -78
  73. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/timetable.js +0 -30
  74. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.js +0 -213
  75. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.worker.js +0 -49
  76. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/trips.js +0 -270
  77. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/utils.js +0 -358
  78. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/worker.js +0 -14
  79. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/xpert.js +0 -58
  80. package/.claude/worktrees/eager-stonebraker-c08ac7/src/word/index.js +0 -95
  81. 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