fleetmap-reports 2.0.356 → 2.0.358

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 (88) hide show
  1. package/.claude/worktrees/eager-stonebraker-c08ac7/.eslintrc.js +15 -0
  2. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/enGB.js +309 -0
  3. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/esCL.js +297 -0
  4. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/frFR.json +324 -0
  5. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/index.js +35 -0
  6. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptBR.js +301 -0
  7. package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptPT.js +310 -0
  8. package/.claude/worktrees/eager-stonebraker-c08ac7/package-lock.json +11944 -0
  9. package/.claude/worktrees/eager-stonebraker-c08ac7/package.json +45 -0
  10. package/.claude/worktrees/eager-stonebraker-c08ac7/src/activity-report.js +822 -0
  11. package/.claude/worktrees/eager-stonebraker-c08ac7/src/automaticReports.js +34 -0
  12. package/.claude/worktrees/eager-stonebraker-c08ac7/src/carbone/index.js +13 -0
  13. package/.claude/worktrees/eager-stonebraker-c08ac7/src/custom/vistawasteActivity-report.js +544 -0
  14. package/.claude/worktrees/eager-stonebraker-c08ac7/src/events-report.js +360 -0
  15. package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-consumption-report.js +296 -0
  16. package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-drop-report.js +128 -0
  17. package/.claude/worktrees/eager-stonebraker-c08ac7/src/here.js +172 -0
  18. package/.claude/worktrees/eager-stonebraker-c08ac7/src/idle-report.js +403 -0
  19. package/.claude/worktrees/eager-stonebraker-c08ac7/src/index.js +244 -0
  20. package/.claude/worktrees/eager-stonebraker-c08ac7/src/kms-report.js +549 -0
  21. package/.claude/worktrees/eager-stonebraker-c08ac7/src/location-report.js +496 -0
  22. package/.claude/worktrees/eager-stonebraker-c08ac7/src/machines-report.js +278 -0
  23. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/afriquia.js +129 -0
  24. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/dailyuse-report.js +80 -0
  25. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/delayedstart-report.js +223 -0
  26. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/driverranking-report.js +328 -0
  27. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/gpsjump-report.js +61 -0
  28. package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/performance-report.js +359 -0
  29. package/.claude/worktrees/eager-stonebraker-c08ac7/src/refueling-report.js +345 -0
  30. package/.claude/worktrees/eager-stonebraker-c08ac7/src/reportStyle.js +34 -0
  31. package/.claude/worktrees/eager-stonebraker-c08ac7/src/sensor-report.js +447 -0
  32. package/.claude/worktrees/eager-stonebraker-c08ac7/src/speeding-report.js +663 -0
  33. package/.claude/worktrees/eager-stonebraker-c08ac7/src/stop-report.js +242 -0
  34. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ActivityReportTemplate.mjml +51 -0
  35. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EmptyReportTemplate.mjml +25 -0
  36. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EventsReportTemplate.mjml +75 -0
  37. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/FuelDropReportTemplate.mjml +60 -0
  38. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/IdleReportTemplate.mjml +54 -0
  39. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportGroupByDayTemplate.mjml +43 -0
  40. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportTemplate.mjml +44 -0
  41. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/LocationReportTemplate.mjml +37 -0
  42. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/RefuelingReportTemplate.mjml +60 -0
  43. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SensorReportTemplate.mjml +23 -0
  44. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SpeedingReportTemplate.mjml +80 -0
  45. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/TripReportTemplate.mjml +97 -0
  46. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ZoneReportTemplate.mjml +52 -0
  47. package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/report.mjml +96 -0
  48. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/activity.test.js +99 -0
  49. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/carbone.test.js +20 -0
  50. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/events.test.js +30 -0
  51. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/fuel.test.js +35 -0
  52. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/idle.test.js +16 -0
  53. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.js +24 -0
  54. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.test.js +203 -0
  55. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/kms.test.js +74 -0
  56. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/performance.test.js +36 -0
  57. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/positions.test.js +67 -0
  58. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/speeding.test.js +120 -0
  59. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/translations.test.js +21 -0
  60. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/trips.test.js +140 -0
  61. package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/zones.test.js +162 -0
  62. package/.claude/worktrees/eager-stonebraker-c08ac7/src/trip-report.js +641 -0
  63. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/device.js +13 -0
  64. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/driver.js +90 -0
  65. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/fuel.js +102 -0
  66. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/geofence.js +52 -0
  67. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/odoo.js +18 -0
  68. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/parallel.js +64 -0
  69. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/pdfDocument.js +140 -0
  70. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/route.js +78 -0
  71. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/timetable.js +30 -0
  72. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.js +213 -0
  73. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.worker.js +49 -0
  74. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/trips.js +270 -0
  75. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/utils.js +358 -0
  76. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/worker.js +14 -0
  77. package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/xpert.js +58 -0
  78. package/.claude/worktrees/eager-stonebraker-c08ac7/src/word/index.js +95 -0
  79. package/.claude/worktrees/eager-stonebraker-c08ac7/src/zone-report.js +904 -0
  80. package/lang/enGB.js +18 -0
  81. package/lang/esCL.js +18 -0
  82. package/lang/frFR.json +7 -1
  83. package/lang/ptBR.js +18 -0
  84. package/lang/ptPT.js +18 -0
  85. package/package.json +1 -1
  86. package/src/partnerReports/driverranking-report.js +152 -49
  87. package/src/speeding-report.js +11 -1
  88. package/src/util/pdfDocument.js +2 -1
@@ -0,0 +1,641 @@
1
+ const automaticReports = require('./automaticReports')
2
+ const jsPDF = require('jspdf')
3
+ const {
4
+ convertMS, convertToLocaleString, convertToLocaleDateString, convertToLocaleTimeString, getTranslations,
5
+ isClientSide, formatNumber
6
+ } = require('./util/utils')
7
+ require('jspdf-autotable')
8
+ const { getUserPartner } = require('fleetmap-partners')
9
+ const { headerFromUser, addTable } = require('./util/pdfDocument')
10
+ const { getStyle } = require('./reportStyle')
11
+ const traccarHelper = require('./util/traccar')
12
+ const {
13
+ isInsideTimetable, isPartialInsideTimetable, getTripIdleTime, calculateStopDuration,
14
+ getUncompletedTrip, getTripTotalKms
15
+ } = require('./util/trips')
16
+ const { devicesToProcess, deviceName } = require('./util/device')
17
+ const { getIdleEvents } = require('./util/route')
18
+ const { reportByDriver, getDriverName, getDriverData } = require('./util/driver')
19
+ const { getNearestPOIs } = require('./util/geofence')
20
+ const { parallel } = require('./util/parallel')
21
+ const { calculateConsumption } = require('./util/fuel')
22
+
23
+ const fileName = 'TripReport'
24
+
25
+ async function createTripReport (from, to, userData, traccar) {
26
+ console.log('createTripReport', from, to, userData.byDriver ? 'by driver' : userData.byGroup ? 'by group' : 'by device')
27
+ const reportData = []
28
+
29
+ if (userData.byDriver) {
30
+ const report = await createTripReportByDriver(from, to, userData, traccar)
31
+ reportData.push(report)
32
+ } else if (userData.byGroup) {
33
+ const allData = await createTripReportByGroup(from, to, userData, traccar)
34
+ reportData.push(...allData)
35
+ } else {
36
+ const report = await createTripReportByDevice(from, to, userData, traccar)
37
+ reportData.push(report)
38
+ }
39
+
40
+ return reportData
41
+ }
42
+
43
+ async function createTripReportByDevice (from, to, userData, traccar) {
44
+ const devices = devicesToProcess(userData)
45
+
46
+ const allData = {
47
+ devices: [],
48
+ xpert: devices.filter(d => d.attributes.xpert).length > 0
49
+ }
50
+
51
+ const sliced = automaticReports.sliceArray(devices, 5)
52
+
53
+ let deviceCount = 0
54
+ for (const slice of sliced) {
55
+ const allInOne = await traccarHelper.getAllInOne(traccar, from, to, slice, true, true, true, false, deviceCount, devices.length)
56
+ const tripsData = allInOne.trips
57
+ const stopsData = allInOne.stops
58
+ const routeData = allInOne.route
59
+
60
+ console.log('Trips:' + tripsData.length)
61
+
62
+ if (tripsData.length > 0) {
63
+ allData.devices.push(...await processDevices(from, to, slice, {
64
+ trips: tripsData,
65
+ stops: stopsData,
66
+ route: routeData
67
+ }, userData, traccar))
68
+ }
69
+ deviceCount = deviceCount + slice.length
70
+ }
71
+
72
+ return allData
73
+ }
74
+
75
+ async function createTripReportByGroup (from, to, userData, traccar) {
76
+ const reportData = []
77
+ for (const g of userData.groups) {
78
+ const devices = userData.devices.filter(d => d.groupId === g.id)
79
+ console.log(g.name + ' devices:' + devices.length)
80
+ if (devices.length > 0) {
81
+ const groupData = {
82
+ devices: [],
83
+ group: g,
84
+ xpert: devices.filter(d => d.attributes.xpert).length > 0
85
+ }
86
+
87
+ const response = await traccar.reports.reportsTripsGet(from, to, null, [g.id])
88
+ const tripsData = response.data
89
+
90
+ const stopsResponse = await traccar.reports.reportsStopsGet(from, to, null, [g.id])
91
+ const stopsData = stopsResponse.data
92
+
93
+ const routeResponse = await traccar.reports.reportsRouteGet(from, to, null, [g.id])
94
+ const routeData = routeResponse.data
95
+
96
+ devices.sort((a, b) => (a.name > b.name) ? 1 : -1)
97
+
98
+ if (tripsData.length > 0) {
99
+ console.log('Trips:' + tripsData.length)
100
+ groupData.devices = await processDevices(from, to, devices, { trips: tripsData, stops: stopsData, route: routeData }, userData)
101
+
102
+ reportData.push(groupData)
103
+ }
104
+ }
105
+ }
106
+
107
+ const allData = await createTripReportByDevice(from, to, userData, traccar)
108
+ reportData.push(allData)
109
+
110
+ return reportData
111
+ }
112
+
113
+ async function createTripReportByDriver (from, to, userData, traccar) {
114
+ const devices = await traccar.devices.devicesGet().then(d => d.data)
115
+ console.log(devices.length, 'devices')
116
+ console.log('drivers', userData.drivers)
117
+
118
+ if (!devices.length) {
119
+ // Empty report
120
+ return { drivers: [] }
121
+ }
122
+
123
+ const results = await reportByDriver(devices, traccar, from, to, userData, processDrivers)
124
+
125
+ const report = { drivers: [] }
126
+
127
+ results.drivers.forEach(result => {
128
+ const driver = report.drivers.find(d => d.driver.id === result.driver.id)
129
+ if (driver) {
130
+ driver.trips.push(...result.trips)
131
+ } else {
132
+ report.drivers.push(result)
133
+ }
134
+ })
135
+
136
+ report.drivers.forEach(d => {
137
+ d.totalDuration = d.trips.reduce((a, b) => a + b.duration, 0)
138
+ d.totalDistance = d.trips.reduce((a, b) => a + b.distance, 0)
139
+ d.maxSpeed = d.trips.reduce((a, b) => { return a > b.maxSpeed ? a : b.maxSpeed }, 0)
140
+ d.trips = d.trips.sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime())
141
+
142
+ d.trips.forEach((trip, i) => {
143
+ trip.stopDuration = (i !== d.trips.length - 1 ? (new Date(d.trips[i + 1].startTime) - new Date(trip.endTime)) : (to - new Date(trip.endTime))) - trip.stopEngineHours
144
+ })
145
+ })
146
+
147
+ return report
148
+ }
149
+
150
+ async function processDevices (from, to, devices, data, userData, traccar) {
151
+ const devicesResult = []
152
+
153
+ for (const d of devices) {
154
+ const deviceRoute = data.route.filter(t => t.deviceId === d.id)
155
+ const deviceTrips = data.trips.filter(t => t.deviceId === d.id)
156
+
157
+ const uncompletedTrip = getUncompletedTrip(d, deviceRoute, deviceTrips)
158
+ if (uncompletedTrip) {
159
+ deviceTrips.push(uncompletedTrip)
160
+ }
161
+
162
+ const trips = []
163
+
164
+ for (const t of deviceTrips) {
165
+ if (userData.allWeek ||
166
+ !userData.weekDays ||
167
+ isInsideTimetable(t, userData) ||
168
+ await isPartialInsideTimetable(d, t, userData, deviceRoute)) {
169
+ trips.push(t)
170
+ }
171
+ }
172
+
173
+ const stops = data.stops.filter(s => s.deviceId === d.id)
174
+
175
+ trips.forEach(trip => {
176
+ trip.model = d.model
177
+ const stop = getStop(new Date(trip.startTime), stops)
178
+
179
+ trip.totalKms = getTripTotalKms(trip, stop, deviceRoute)
180
+
181
+ const nearestPOIs = getNearestPOIs(trip.endLon, trip.endLat, userData.geofences)
182
+ if (nearestPOIs.length > 0) {
183
+ trip.endPOIName = nearestPOIs[0].p.name
184
+ }
185
+
186
+ if (userData.includeTemperature) {
187
+ const tripRoute = deviceRoute.filter(p => trip.startTime <= p.fixTime && trip.endTime >= p.fixTime)
188
+
189
+ const temperatureValues = tripRoute.map(p => {
190
+ for (let i = 1; i <= 3; i++) {
191
+ const temp = 'temp' + i
192
+ if (p.attributes[temp] && parseFloat(p.attributes[temp]) !== 175) {
193
+ return parseFloat(p.attributes[temp])
194
+ }
195
+ }
196
+
197
+ return undefined
198
+ }).filter(a => a !== undefined)
199
+
200
+ trip.maxTemp = temperatureValues.length > 0
201
+ ? temperatureValues.reduce((a, b) => {
202
+ return a > b ? a : b
203
+ })
204
+ : 0
205
+ trip.minTemp = temperatureValues.length > 0
206
+ ? temperatureValues.reduce((a, b) => {
207
+ return a < b ? a : b
208
+ })
209
+ : 0
210
+ trip.avgTemp = temperatureValues.length > 0
211
+ ? (temperatureValues.reduce((a, b) => {
212
+ return a + b
213
+ }, 0)) / temperatureValues.length
214
+ : 0
215
+ }
216
+
217
+ if (deviceRoute.some(r => r.attributes.fuel) ||
218
+ automaticReports.deviceWithFuelInfo(d)) {
219
+ trip.fuelConsumption = calculateConsumption(d, { trips: [trip], stops: (stop ? [stop] : []), route: deviceRoute })
220
+ trip.avgFuelConsumption = trip.totalKms > 0 ? trip.fuelConsumption * 100 / trip.totalKms : 0
221
+ }
222
+
223
+ if (stop && !trip.endTimeIsOut) {
224
+ trip.stopDuration = calculateStopDuration(stop)
225
+ trip.stopEngineHours = trip.idleTime + stop.idleTime
226
+ } else {
227
+ trip.stopDuration = 0
228
+ trip.stopEngineHours = 0
229
+ }
230
+ if (trip.endAddress === null) {
231
+ traccar.axios.get(`/server/geocode?latitude=${trip.endLat}&longitude=${trip.endLon}`)
232
+ .then(d => { trip.endAddress = d.data }).catch(e => console.warn(e.message, trip.endLat, trip.endLon))
233
+ }
234
+ })
235
+
236
+ if (trips.length > 0) {
237
+ const deviceData = {
238
+ device: d,
239
+ from,
240
+ to,
241
+ totalDuration: trips.reduce((a, b) => a + b.duration, 0),
242
+ totalDistance: trips.reduce((a, b) => a + b.distance, 0),
243
+ maxSpeed: trips.reduce((a, b) => {
244
+ return a > b.maxSpeed ? a : b.maxSpeed
245
+ }, 0),
246
+ trips
247
+ }
248
+
249
+ if (trips[0].fuelConsumption !== undefined) {
250
+ deviceData.totalFuelConsumption = trips.reduce((a, b) => a + b.fuelConsumption, 0)
251
+ deviceData.avgFuelConsumption = deviceData.totalDistance > 0 ? (deviceData.totalFuelConsumption * 100) / (deviceData.totalDistance / 1000) : 0
252
+ }
253
+
254
+ devicesResult.push(deviceData)
255
+ }
256
+ }
257
+
258
+ return devicesResult
259
+ }
260
+
261
+ function calculateIdleEvents (drivers, route) {
262
+ const idleEvents = []
263
+ drivers.forEach(d => {
264
+ idleEvents.push(...getIdleEvents(route, d))
265
+ })
266
+ return idleEvents
267
+ }
268
+
269
+ async function processDrivers (from, to, userData, data) {
270
+ const driversResult = []
271
+
272
+ const idleEvents = []
273
+ if (isClientSide()) {
274
+ idleEvents.push(...(calculateIdleEvents(userData.drivers, data.route)))
275
+ } else {
276
+ if (data.route.length && userData.drivers.length) {
277
+ idleEvents.push(...(await parallel('trip-report', 'calculateIdleEvents', userData.drivers, data.route)))
278
+ }
279
+ }
280
+
281
+ for (const d of userData.drivers) {
282
+ const { trips, route } = await getDriverData(d, data, userData)
283
+
284
+ if (trips) {
285
+ trips.forEach(trip => {
286
+ const stop = data.stops.find(s => s.deviceId === trip.deviceId && (new Date(s.startTime).getTime() === new Date(trip.endTime).getTime()))
287
+
288
+ trip.totalKms = getTripTotalKms(trip, stop, route)
289
+
290
+ const nearestPOIs = getNearestPOIs(trip.endLon, trip.endLat, userData.geofences)
291
+ if (nearestPOIs.length > 0) {
292
+ trip.endPOIName = nearestPOIs[0].p.name
293
+ }
294
+
295
+ trip.stopEngineHours = getTripIdleTime(trip, idleEvents) + (stop ? stop.idleTime : 0)
296
+ })
297
+
298
+ if (trips.length > 0) {
299
+ const driverData = {
300
+ driver: d,
301
+ from,
302
+ to,
303
+ trips
304
+ }
305
+ driversResult.push(driverData)
306
+ }
307
+ }
308
+ }
309
+
310
+ return driversResult
311
+ }
312
+
313
+ async function exportTripReportToPDF (userData, reportData) {
314
+ console.log('exportTripReportToPDF')
315
+
316
+ const timezone = userData.user.attributes.timezone
317
+ const translations = getTranslations(userData)
318
+ const lang = userData.user.attributes.lang || (isClientSide() && navigator.language)
319
+ const tripsData = userData.byDriver ? reportData.drivers : reportData.devices
320
+
321
+ const headers = [
322
+ translations.report.date,
323
+ translations.report.start,
324
+ translations.report.end,
325
+ translations.report.endAddress,
326
+ translations.report.tripTime,
327
+ translations.report.idleTime,
328
+ translations.report.stopTime,
329
+ translations.report.distance,
330
+ translations.report.avgSpeed,
331
+ translations.report.maxSpeed
332
+ ]
333
+
334
+ if (userData.includeTemperature) {
335
+ headers.push(translations.report.maxTemp)
336
+ headers.push(translations.report.minTemp)
337
+ headers.push(translations.report.avgTemp)
338
+ }
339
+
340
+ if (tripsData) {
341
+ const withFuelData = tripsData.find(t => t.totalFuelConsumption !== undefined)
342
+
343
+ if (withFuelData) {
344
+ headers.push(translations.report.consumption,
345
+ translations.report.avg_consumption)
346
+ }
347
+
348
+ if (userData.byDriver) {
349
+ headers.push(translations.report.name)
350
+ } else {
351
+ headers.push(translations.report.driver)
352
+ }
353
+
354
+ // eslint-disable-next-line new-cap
355
+ const doc = new jsPDF.jsPDF('l')
356
+ await headerFromUser(doc, translations.report.titleTripReport, userData.user)
357
+
358
+ tripsData.forEach(function (d, index) {
359
+ const data = []
360
+ const name = userData.byDriver
361
+ ? d.driver.name + ' ' + ((d.driver.attributes && d.driver.attributes.notes) || '')
362
+ : deviceName(d.device)
363
+ const group = userData.byDriver ? userData.groups.find(g => g.drivers && g.drivers.includes(d.id)) : userData.groups.find(g => d.device.groupId === g.id)
364
+
365
+ const space = index === 0 ? 8 : 0
366
+ if (index) {
367
+ doc.addPage()
368
+ }
369
+
370
+ doc.setFontSize(13)
371
+ doc.text(name, 20, space + 20)
372
+ doc.setFontSize(11)
373
+ doc.text(group ? translations.report.group + ': ' + group.name : '', 200, space + 20)
374
+ doc.text(convertToLocaleString(d.from, lang, timezone, userData.user) + ' - ' + convertToLocaleString(d.to, lang, timezone, userData.user), 20, space + 25)
375
+ if (!userData.allWeek && userData.weekDays) {
376
+ doc.text((userData.weekDays.monday ? ' ' + translations.report.monday + ',' : '') +
377
+ (userData.weekDays.tuesday ? ' ' + translations.report.tuesday + ',' : '') +
378
+ (userData.weekDays.wednesday ? ' ' + translations.report.wednesday + ',' : '') +
379
+ (userData.weekDays.thursday ? ' ' + translations.report.thursday + ',' : '') +
380
+ (userData.weekDays.friday ? ' ' + translations.report.friday + ',' : '') +
381
+ (userData.weekDays.saturday ? ' ' + translations.report.saturday + ',' : '') +
382
+ (userData.weekDays.sunday ? ' ' + translations.report.sunday + ',' : '') +
383
+ ' das ' +
384
+ userData.dayHours.startTime + ' - ' + userData.dayHours.endTime, 20, space + 30)
385
+ doc.text(translations.report.headerStartAddress + ': ' + d.trips[0].startAddress, 20, space + 40)
386
+ } else {
387
+ doc.text(translations.report.headerStartAddress + ': ' + d.trips[0].startAddress, 20, space + 35)
388
+ }
389
+
390
+ d.trips.forEach(a => {
391
+ const temp = [
392
+ getTripDate(userData.user, a),
393
+ getTripStart(userData.user, a),
394
+ getTripEnd(userData.user, a),
395
+ a.endPOIName || a.endAddress,
396
+ convertMS(a.duration)
397
+ ]
398
+
399
+ temp.push(convertMS(a.stopEngineHours),
400
+ convertMS(a.stopDuration),
401
+ Intl.NumberFormat(userData.user.attributes.lang, { maximumFractionDigits: 1 }).format(a.totalKms),
402
+ Math.round(a.averageSpeed * 1.85200),
403
+ Math.round(a.maxSpeed * 1.85200))
404
+
405
+ if (userData.includeTemperature) {
406
+ temp.push(a.maxTemp)
407
+ temp.push(a.minTemp)
408
+ temp.push(a.avgTemp && a.avgTemp.toFixed(1))
409
+ }
410
+
411
+ if (withFuelData) {
412
+ temp.push(a.fuelConsumption && a.fuelConsumption.toFixed(1),
413
+ a.avgFuelConsumption && a.avgFuelConsumption.toFixed(1))
414
+ }
415
+
416
+ if (userData.byDriver) {
417
+ temp.push(a.deviceName)
418
+ } else {
419
+ temp.push(getDriverName(a.driverUniqueId, a.startTime, userData))
420
+ }
421
+
422
+ data.push(temp)
423
+ })
424
+ const footValues = [
425
+ 'Total:' + d.trips.length,
426
+ '', '', '',
427
+ convertMS(d.trips.reduce((a, b) => a + b.duration, 0))
428
+ ]
429
+
430
+ footValues.push(convertMS(d.trips.reduce((a, b) => a + b.stopEngineHours, 0)),
431
+ convertMS(d.trips.reduce((a, b) => a + b.stopDuration, 0)),
432
+ getSumTotalKms(userData.user, d.trips),
433
+ getSumAvgSpeed(d.trips),
434
+ getMaxSpeed(d.trips))
435
+
436
+ if (userData.includeTemperature) {
437
+ footValues.push(...['', '', ''])
438
+ }
439
+
440
+ if (withFuelData) {
441
+ footValues.push(
442
+ d.totalFuelConsumption && parseFloat(d.totalFuelConsumption).toFixed(1),
443
+ d.avgFuelConsumption && parseFloat(d.avgFuelConsumption).toFixed(1))
444
+ }
445
+
446
+ const style = getStyle(getUserPartner(userData.user))
447
+ if (userData.includeTemperature && withFuelData) {
448
+ style.headerFontSize = 8
449
+ style.bodyFontSize = 6
450
+ style.footerFontSize = 7
451
+ }
452
+
453
+ addTable(doc,
454
+ headers, data, footValues, style,
455
+ space + (userData.allWeek ? 40 : 45),
456
+ {
457
+ 4: { halign: 'right' },
458
+ 5: { halign: 'right' },
459
+ 6: { halign: 'right' },
460
+ 7: { halign: 'right' },
461
+ 8: { halign: 'right' },
462
+ 9: { halign: 'right' },
463
+ ...(userData.includeTemperature
464
+ ? {
465
+ 10: { halign: 'right' },
466
+ 11: { halign: 'right' },
467
+ 12: { halign: 'right' }
468
+ }
469
+ : {}),
470
+ ...(withFuelData && userData.includeTemperature
471
+ ? {
472
+ 13: { halign: 'right' },
473
+ 14: { halign: 'right' }
474
+ }
475
+ : withFuelData
476
+ ? {
477
+ 10: { halign: 'right' },
478
+ 11: { halign: 'right' }
479
+ }
480
+ : {})
481
+ })
482
+ })
483
+ return doc
484
+ }
485
+ }
486
+
487
+ function exportTripReportToExcel (userData, reportData) {
488
+ console.log('Export to Excel')
489
+
490
+ const translations = getTranslations(userData)
491
+
492
+ const tripsData = userData.byDriver ? reportData.drivers : reportData.devices
493
+
494
+ const settings = {
495
+ sheetName: translations.report.titleTripReport, // The name of the sheet
496
+ fileName // The name of the spreadsheet
497
+ }
498
+
499
+ const headers = [
500
+ { label: translations.report.date, value: 'date' },
501
+ { label: translations.report.start, value: 'start' },
502
+ { label: translations.report.end, value: 'end' },
503
+ { label: translations.report.endAddress, value: 'endAddress' },
504
+ { label: translations.report.tripTime, value: 'tripTime' },
505
+ { label: translations.report.idleTime, value: 'idleTime' },
506
+ { label: translations.report.stopTime, value: 'stopTime' },
507
+ { label: translations.report.distance, value: 'distance' },
508
+ { label: translations.report.avgSpeed, value: 'averageSpeed' },
509
+ { label: translations.report.maxSpeed, value: 'maxSpeed' }
510
+ ]
511
+
512
+ if (userData.includeTemperature) {
513
+ headers.push({ label: translations.report.maxTemp, value: 'maxTemp' })
514
+ headers.push({ label: translations.report.minTemp, value: 'minTemp' })
515
+ headers.push({ label: translations.report.avgTemp, value: 'avgTemp' })
516
+ }
517
+
518
+ let data = []
519
+ if (tripsData) {
520
+ const withFuelData = tripsData.find(b => b.totalFuelConsumption !== undefined)
521
+
522
+ if (withFuelData) {
523
+ headers.push({ label: translations.report.consumption, value: 'consumption' },
524
+ { label: translations.report.avg_consumption, value: 'avgConsumption' })
525
+ }
526
+
527
+ if (userData.byDriver) {
528
+ headers.unshift({ label: translations.report.driver, value: 'name' })
529
+ headers.push({ label: translations.report.vehicle, value: 'subname' })
530
+ } else {
531
+ headers.unshift({ label: translations.settings.vehicle_model, value: 'model' })
532
+ headers.unshift({ label: translations.report.group, value: 'group' })
533
+ headers.unshift({ label: translations.report.driver, value: 'subname' })
534
+ headers.unshift({ label: translations.report.vehicle, value: 'name' })
535
+ }
536
+
537
+ tripsData.forEach(d => {
538
+ const group = userData.byDriver
539
+ ? userData.groups.find(g => g.drivers && g.drivers.includes(d.id))
540
+ : userData.groups.find(g => g.id === d.device.groupId)
541
+ data = data.concat([{}])
542
+ data = data.concat(d.trips.map(a => {
543
+ return {
544
+ name: userData.byDriver ? d.driver.name : a.deviceName,
545
+ date: getTripDate(userData.user, a),
546
+ start: getTripStart(userData.user, a),
547
+ end: getTripEnd(userData.user, a),
548
+ endAddress: a.endPOIName || a.endAddress,
549
+ tripTime: convertMS(a.duration),
550
+ idleTime: convertMS(a.stopEngineHours),
551
+ stopTime: convertMS(a.stopDuration),
552
+ distance: Number(a.totalKms.toFixed(1)),
553
+ averageSpeed: Math.round(a.averageSpeed * 1.85200),
554
+ maxSpeed: Math.round(a.maxSpeed * 1.85200),
555
+ consumption: a.fuelConsumption && Number(a.fuelConsumption.toFixed(1)),
556
+ avgConsumption: a.avgFuelConsumption && Number(a.avgFuelConsumption.toFixed(1)),
557
+ subname: userData.byDriver ? a.deviceName : getDriverName(a.driverUniqueId, a.startTime, userData),
558
+ maxTemp: a.maxTemp,
559
+ minTemp: a.minTemp,
560
+ avgTemp: a.avgTemp && Number(a.avgTemp.toFixed(1)),
561
+ model: d.device && d.device.model,
562
+ group: group && group.name
563
+ }
564
+ }))
565
+ // Totals
566
+ data = data.concat([{
567
+ name: d.trips.length,
568
+ tripTime: convertMS(d.trips.reduce((a, b) => a + b.duration, 0)),
569
+ idleTime: convertMS(d.trips.reduce((a, b) => a + b.stopEngineHours, 0)),
570
+ stopTime: convertMS(d.trips.reduce((a, b) => a + b.stopDuration, 0)),
571
+ distance: getSumTotalKms(userData.user, d.trips),
572
+ averageSpeed: getSumAvgSpeed(d.trips),
573
+ maxSpeed: getMaxSpeed(d.trips),
574
+ consumption: d.totalFuelConsumption && Number(parseFloat(d.totalFuelConsumption).toFixed(1)),
575
+ avgConsumption: d.avgFuelConsumption && Number(parseFloat(d.avgFuelConsumption).toFixed(1))
576
+ }])
577
+ data = data.concat([{}])
578
+ })
579
+ console.log(data)
580
+ return {
581
+ headers,
582
+ data,
583
+ settings
584
+ }
585
+ }
586
+ }
587
+
588
+ function getTripDate (user, trip) {
589
+ return convertToLocaleDateString(trip.startTime, user.attributes.lang, user.attributes.timezone, user)
590
+ }
591
+
592
+ function getTripStart (user, trip) {
593
+ return convertToLocaleTimeString(trip.startTime, user.attributes.lang, user.attributes.timezone, user)
594
+ }
595
+
596
+ function getTripEnd (user, trip) {
597
+ return convertToLocaleTimeString(trip.endTime, user.attributes.lang, user.attributes.timezone, user)
598
+ }
599
+
600
+ function getSumTotalKms (user, data) {
601
+ const values = data.map(item => {
602
+ return item.totalKms ? parseFloat(item.totalKms) : 0
603
+ })
604
+ return formatNumber(values.reduce((a, b) => a + b, 0))
605
+ }
606
+
607
+ function getSumAvgSpeed (data) {
608
+ const values = data.map(item => {
609
+ return Math.round(item.averageSpeed * 1.85200) * item.totalKms
610
+ })
611
+ const totalKms = data.reduce((a, b) => a + b.totalKms, 0)
612
+ return totalKms > 0 ? Math.round((values.reduce((a, b) => a + b, 0)) / totalKms) : 0
613
+ }
614
+
615
+ function getMaxSpeed (data) {
616
+ const values = data.map(item => {
617
+ return item.maxSpeed
618
+ })
619
+ const max = values.reduce(function (a, b) {
620
+ return Math.max(a, b)
621
+ })
622
+ return Math.round(max * 1.85200)
623
+ }
624
+
625
+ function getStop (tripEndDate, stops) {
626
+ const stopList = stops.filter(s => new Date(s.startTime).getTime() >= tripEndDate.getTime())
627
+ if (stopList.length) {
628
+ return stopList[0]
629
+ }
630
+
631
+ return null
632
+ }
633
+
634
+ exports.getStop = getStop
635
+ exports.createTripReport = createTripReport
636
+ exports.exportTripReportToPDF = exportTripReportToPDF
637
+ exports.exportTripReportToExcel = exportTripReportToExcel
638
+ exports.create = createTripReport
639
+ exports.exportToPDF = exportTripReportToPDF
640
+ exports.exportToExcel = exportTripReportToExcel
641
+ exports.calculateIdleEvents = calculateIdleEvents
@@ -0,0 +1,13 @@
1
+ function devicesToProcess (userData) {
2
+ const groupIds = userData.groups.map(g => g.id)
3
+ const devices = userData.byGroup ? userData.devices.filter(d => !groupIds.includes(d.groupId)) : userData.devices
4
+ devices.sort((a, b) => (a.name > b.name) ? 1 : -1)
5
+ console.log('LOADING_MESSAGE:' + devices.map(d => d.name).join(', '))
6
+ return devices
7
+ }
8
+
9
+ exports.deviceName = function deviceName (device) {
10
+ return device.name + (device.attributes.license_plate ? ', ' + device.attributes.license_plate : '') + (device.model ? ', ' + device.model : '')
11
+ }
12
+
13
+ exports.devicesToProcess = devicesToProcess