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,360 +0,0 @@
1
- const automaticReports = require('./automaticReports')
2
- const jsPDF = require('jspdf')
3
- require('jspdf-autotable')
4
- const { getStyle } = require('./reportStyle')
5
- const { headerFromUser } = require('./util/pdfDocument')
6
- const { getUserPartner } = require('fleetmap-partners')
7
- const {
8
- convertToLocaleString, getTranslations, convertMS,
9
- getLanguage
10
- } = require('./util/utils')
11
- const { devicesToProcess } = require('./util/device')
12
- const { isInside } = require('./util/timetable')
13
- const { getDigitalPortValue } = require('./location-report')
14
- const { maxParallelRequests } = require('./automaticReports')
15
- const { getNearestPOI } = require('./util/geofence')
16
-
17
- const fileName = 'EventReport'
18
-
19
- async function createEventsReport (from, to, userData, traccar) {
20
- console.log('Create EventsReport')
21
- const reportData = []
22
-
23
- if (userData.byGroup) {
24
- console.log('ByGroup')
25
- for (const g of userData.groups) {
26
- const devices = userData.devices.filter(d => d.groupId === g.id)
27
- console.log(g.name + ' devices:' + devices.length)
28
- if (devices.length > 0) {
29
- const groupData = {
30
- devices: [],
31
- group: g,
32
- xpert: devices.filter(d => d.attributes.xpert).length > 0
33
- }
34
-
35
- const data = await getReportData(from, to, devices, userData.eventTypes, traccar)
36
-
37
- devices.sort((a, b) => (a.name > b.name) ? 1 : -1)
38
-
39
- if (data.length > 0) {
40
- groupData.devices = await processDevices(from, to, devices, data, traccar, userData)
41
- reportData.push(groupData)
42
- }
43
- }
44
- }
45
- }
46
-
47
- const devices = devicesToProcess(userData)
48
-
49
- const data = await getReportData(from, to, devices, userData.eventTypes || [], traccar)
50
-
51
- if (data.length > 0) {
52
- reportData.push({
53
- devices: await processDevices(from, to, devices, data, traccar, userData),
54
- xpert: devices.filter(d => d.attributes.xpert).length > 0
55
- })
56
- }
57
-
58
- return reportData
59
- }
60
-
61
- async function getReportData (from, to, devices, types, traccar) {
62
- let data = []
63
- const arrayOfArrays = automaticReports.sliceArray(devices)
64
- const traccarTypes = new Set(types.map(t => t.startsWith('alarm') ? 'alarm' : t))
65
- await Promise.all(arrayOfArrays.map(async a => {
66
- const response = await traccar.reports.reportsEventsGet(from, to, a.map(d => d.id), null, traccarTypes)
67
- data = data.concat(
68
- response.data.filter(
69
- e => types.find(t => t === e.type) || types.find(t => t.replace('alarm_', '') === e.attributes.alarm)
70
- ))
71
- }))
72
- console.log('Alerts:' + data.length)
73
- return data
74
- }
75
-
76
- async function processDevices (from, to, devices, data, traccar, userData) {
77
- const devicesResult = []
78
- let i = 0
79
- for (let j = 0; j < devices.length; j += maxParallelRequests) {
80
- await Promise.all(devices.slice(j, j + maxParallelRequests).map(async d => {
81
- const deviceAlerts = data.filter(t => t.deviceId === d.id)
82
- // events can arrive at bd very late, so we search with a 24 hours delay
83
- const dayBefore = new Date(new Date(from).setDate(from.getDate() - 1))
84
- const response = await traccar.reports.reportsRouteGet(dayBefore, to, [d.id])
85
- const positions = response.data
86
-
87
- for (const a of deviceAlerts) {
88
- a.position = positions.find(p => p.id === a.positionId)
89
-
90
- if (a.type === 'alarm' && a.position) {
91
- for (const p of positions.filter(p => p.fixTime >= a.position.fixTime)) {
92
- if (p.attributes.alarm !== a.position.attributes.alarm) { break }
93
- a.endPosition = p
94
- }
95
- }
96
-
97
- if (!a.geofenceId && a.position) {
98
- const poi = getNearestPOI(a.position, userData.geofences)
99
- a.geofenceId = poi && poi.p.id
100
- }
101
- if (a.geofenceId) {
102
- const geofence = userData.geofences.find(g => g.id === a.geofenceId)
103
- if (geofence) {
104
- if (a.type === 'deviceOverspeed') {
105
- a.geofenceName = geofence.name + ' (' + Math.round(a.attributes.speedLimit * 1.85200) + ' Km/h)'
106
- } else {
107
- a.geofenceName = geofence.name
108
- }
109
- }
110
- }
111
-
112
- if (a.position && a.position.attributes.driverUniqueId) {
113
- const driver = userData.drivers.find(d => d.uniqueId === a.position.attributes.driverUniqueId)
114
- a.driver = driver && driver.name
115
- }
116
- }
117
-
118
- const alerts = deviceAlerts.filter(a => userData.allWeek || !userData.weekDays || (!a.position || isInside(a.position.fixTime, a.position.fixTime, userData)))
119
- .filter(a => a.position).sort((a, b) => a.position.fixTime.localeCompare(b.position.fixTime))
120
- console.log('LOADING_MESSAGE:' + d.name)
121
- console.log(`PROGRESS_PERC:${++i / devices.length * 100}`)
122
-
123
- if (alerts.length > 0) {
124
- devicesResult.push({
125
- device: d,
126
- from,
127
- to,
128
- alerts
129
- })
130
- }
131
- }))
132
- }
133
-
134
- return devicesResult
135
- }
136
-
137
- async function exportSpeedingReportToPDF (userData, reportData) {
138
- console.log('Export to PDF')
139
- const lang = getLanguage(userData)
140
- const translations = getTranslations(userData)
141
- const timezone = userData.user.attributes.timezone
142
-
143
- const headers = [
144
- translations.report.eventType,
145
- translations.report.start,
146
- translations.report.end,
147
- translations.report.address,
148
- translations.report.duration,
149
- translations.report.info
150
- ]
151
- if (reportData.devices) {
152
- let first = true
153
- // eslint-disable-next-line new-cap
154
- const doc = new jsPDF.jsPDF('l')
155
- await headerFromUser(doc, translations.report.titleEventsReport, userData.user)
156
-
157
- reportData.devices.forEach(d => {
158
- const data = []
159
- const name = deviceName(d.device)
160
- const group = deviceGroupName(translations, userData.groups, d.device)
161
-
162
- let space = 0
163
- if (!first) {
164
- doc.addPage()
165
- } else {
166
- first = false
167
- space = 10
168
- }
169
- doc.setFontSize(13)
170
- doc.text(name, 20, space + 20)
171
- doc.setFontSize(11)
172
- doc.text(group, 200, space + 20)
173
- doc.text(convertToLocaleString(d.from, lang, timezone) + ' - ' + convertToLocaleString(d.to, lang, timezone), 20, space + 25)
174
-
175
- const headerEventTypes = []
176
- userData.eventTypes.forEach(e => {
177
- const data = e.startsWith('alarm') ? { type: 'alarm', attributes: { alarm: e.slice(6) } } : { type: e }
178
- const value = e.startsWith('alarm') ? d.alerts.filter(a => a.attributes.alarm === data.attributes.alarm).length : d.alerts.filter(a => a.type === e).length
179
- if (value > 0) {
180
- headerEventTypes.push([getEventType(data, translations, d.device), value])
181
- }
182
- })
183
-
184
- doc.autoTable({
185
- head: ['', '', '', '', ''],
186
- body: [
187
- headerEventTypes.slice(0, 6).map(a => a[0]),
188
- headerEventTypes.slice(0, 6).map(a => a[1]),
189
- headerEventTypes.slice(7).map(a => a[0]),
190
- headerEventTypes.slice(7).map(a => a[1])
191
- ],
192
- startY: space + 30,
193
- showHead: false,
194
- bodyStyles: { fillColor: [256, 256, 256], textColor: [30, 30, 30], halign: 'center', valign: 'middle' },
195
- alternateRowStyles: { fillColor: [256, 256, 256], valign: 'bottom' }
196
- })
197
- d.alerts.forEach(a => {
198
- const temp = [
199
- getEventType(a, translations, d.device, userData),
200
- getAlertDate(userData.user, a.position),
201
- getAlertDate(userData.user, a.endPosition),
202
- a.geofenceName || (a.position ? a.position.address : ''),
203
- a.position && a.endPosition ? convertMS(new Date(a.endPosition.fixTime).getTime() - new Date(a.position.fixTime).getTime()) : '',
204
- getAlertInfo(userData.drivers, a, d.device, translations)
205
- ]
206
- data.push(temp)
207
- })
208
-
209
- const footValues = [
210
- 'Total:' + d.alerts.length,
211
- '', '', ''
212
- ]
213
-
214
- const style = getStyle(getUserPartner(userData.user))
215
- doc.autoTable({
216
- head: [headers],
217
- body: data,
218
- foot: [footValues],
219
- showFoot: 'lastPage',
220
- headStyles: {
221
- fillColor: style.pdfHeaderColor,
222
- textColor: style.pdfHeaderTextColor,
223
- fontSize: 10
224
- },
225
- bodyStyles: {
226
- fillColor: style.pdfBodyColor,
227
- textColor: style.pdfBodyTextColor,
228
- fontSize: 8
229
- },
230
- footStyles: {
231
- fillColor: style.pdfFooterColor,
232
- textColor: style.pdfFooterTextColor,
233
- fontSize: 9
234
- },
235
- startY: space + 65
236
- })
237
- })
238
- return doc
239
- }
240
- }
241
-
242
- function getEventType (a, translations, device, userData) {
243
- if (a.type === 'alarm') {
244
- let sensor
245
- if (a.attributes.alarm === 'sensor') { sensor = device.attributes.sensor1 }
246
- return sensor || translations.report['event_' + a.attributes.alarm] || a.attributes.alarm
247
- } else {
248
- const result = translations.report['event_' + a.type] || a.type
249
- try {
250
- if (userData &&
251
- userData.maintenances &&
252
- a.type === 'maintenance') {
253
- const maintenance = userData.maintenances.find(m => m.id === a.maintenanceId)
254
- if (maintenance) { return `${result} ${maintenance.name}` }
255
- }
256
- } catch (e) {
257
- console.error(e)
258
- }
259
- return result
260
- }
261
- }
262
-
263
- function exportSpeedingReportToExcel (userData, reportData) {
264
- console.log('exportSpeedingReportToExcel')
265
-
266
- const translations = getTranslations(userData)
267
-
268
- const settings = {
269
- sheetName: translations.report.titleEventsReport, // The name of the sheet
270
- fileName // The name of the spreadsheet
271
- }
272
- const headers = [
273
- { label: translations.report.vehicle, value: 'name' },
274
- { label: translations.report.eventType, value: 'eventType' },
275
- { label: translations.report.start, value: 'fixTime' },
276
- { label: translations.report.end, value: 'endFixTime' },
277
- { label: translations.report.address, value: 'address' },
278
- { label: translations.report.duration, value: 'duration' },
279
- { label: translations.report.zone, value: 'geofenceName' },
280
- { label: translations.report.driver, value: 'driver' },
281
- { label: translations.report.info, value: 'info' },
282
- { label: '', value: 'alarm' },
283
- { label: 'Latitude', value: 'latitude' },
284
- { label: 'Longitude', value: 'longitude' }
285
- ]
286
- let data = []
287
- if (reportData.devices) {
288
- reportData.devices.forEach(d => {
289
- data = data.concat(d.alerts.map(a => {
290
- return {
291
- name: d.device.name,
292
- eventType: getEventType(a, translations, d.device),
293
- fixTime: getAlertDate(userData.user, a.position),
294
- endFixTime: getAlertDate(userData.user, a.endPosition),
295
- address: a.position && a.position.address,
296
- duration: a.position && a.endPosition ? convertMS(new Date(a.endPosition.fixTime).getTime() - new Date(a.position.fixTime).getTime()) : '',
297
- geofenceName: a.geofenceName,
298
- info: getAlertInfo(userData.drivers, a, d.device, translations),
299
- driver: a.driver,
300
- latitude: a.position && a.position.latitude,
301
- longitude: a.position && a.position.longitude,
302
- alarm: a.attributes.notifications ? translations.report.alarm : ''
303
- }
304
- }))
305
- })
306
- return {
307
- headers,
308
- data,
309
- settings
310
- }
311
- }
312
- }
313
-
314
- function deviceGroupName (translation, groups, device) {
315
- const group = groups.find(g => device.groupId === g.id)
316
- if (group) {
317
- return translation.report.group + ': ' + group.name
318
- }
319
- return ''
320
- }
321
-
322
- function deviceName (device) {
323
- return device.name + (device.attributes.license_plate ? ', ' + device.attributes.license_plate : '') + (device.model ? ', ' + device.model : '')
324
- }
325
-
326
- function getAlertInfo (drivers, alert, device, translations) {
327
- if (alert.type === 'alarm' && alert.attributes.alarm === 'temperature') {
328
- return alert.attributes.info
329
- }
330
- if (alert.type === 'alarm' && alert.attributes.alarm === 'sensor') {
331
- return device ? device.attributes.sensor1on : ''
332
- }
333
- if (alert.attributes.alarm && alert.attributes.alarm.startsWith('hard')) {
334
- return ''
335
- }
336
- if (alert.type === 'alarm' && alert.position) {
337
- return getDigitalPortValue(alert.position, 1, translations, device.attributes)
338
- }
339
- if (alert.type === 'deviceOverspeed') {
340
- return Math.round(alert.attributes.speed * 1.85200) + ' Km/h'
341
- }
342
- if (alert.type === 'driverChanged') {
343
- const driver = drivers.find(d => d.uniqueId === alert.attributes.driverUniqueId)
344
- return driver ? driver.name : alert.attributes.driverUniqueId
345
- }
346
- return ''
347
- }
348
-
349
- function getAlertDate (user, position) {
350
- if (position) {
351
- return convertToLocaleString(position.fixTime, user.attributes.lang, user.attributes.timezone)
352
- }
353
- }
354
-
355
- exports.createEventsReport = createEventsReport
356
- exports.exportSpeedingReportToPDF = exportSpeedingReportToPDF
357
- exports.exportSpeedingReportToExcel = exportSpeedingReportToExcel
358
- exports.create = createEventsReport
359
- exports.exportToPDF = exportSpeedingReportToPDF
360
- exports.exportToExcel = exportSpeedingReportToExcel
@@ -1,296 +0,0 @@
1
- const refuelingReport = require('./refueling-report')
2
- const traccarHelper = require('./util/traccar')
3
- const odoo = require('./util/odoo')
4
- const automaticReports = require('./automaticReports')
5
- const { devicesToProcess } = require('./util/device')
6
- const { getTranslations, convertToLocaleString, getLanguage, convertMS, formatNumber, getDates, isClientSide, executeServerSide } = require('./util/utils')
7
- const { getStyle } = require('./reportStyle')
8
- const { getUserPartner } = require('fleetmap-partners')
9
- const { addTable, headerFromUser } = require('./util/pdfDocument')
10
- const jsPDF = require('jspdf')
11
- const { calculateConsumption, calculateTotalFuelFromSensor} = require('./util/fuel')
12
- const { getSummaryByDay } = require('./util/traccar')
13
- const { getDataByDay } = require('./util/trips')
14
-
15
- async function createFuelConsumptionReport (from, to, userData, traccar) {
16
- console.log('Create FuelConsumption Report')
17
-
18
- const reportData = []
19
-
20
- const allData = {
21
- devices: [],
22
- from,
23
- to
24
- }
25
-
26
- const devices = devicesToProcess(userData)
27
-
28
- allData.totalDevices = 0
29
-
30
- if (isClientSide() && (devices.length > 50 || ((new Date(to).getTime() - new Date(from).getTime()) > (1000 * 60 * 60 * 24 * 15)))) {
31
- const sliced = automaticReports.sliceArray(devices, 1)
32
- allData.devices = await executeServerSide('fuel-consumption-report', sliced, from, to, userData, devices.length, traccar, 5)
33
- allData.totalDevices = allData.devices.length
34
- reportData.push(allData)
35
- return reportData
36
- }
37
-
38
- const allInOne = await traccarHelper.getAllInOne(traccar, from, to, devices, true, true, true, false)
39
- const dates = getDates(from, to, userData.user.attributes.timezone || getUserPartner(userData.user).timezone)
40
- const summary = await getSummaryByDay(dates, userData, traccar, devices, true, from, to)
41
-
42
- const fuelServicesData = []
43
- if (devices.some(d => d.attributes.odooId)) {
44
- fuelServicesData.push(...(await odoo.getOdooFuelServices(traccar, from, to)))
45
- }
46
-
47
- if (allInOne.trips.length === 0) {
48
- return reportData
49
- }
50
-
51
- for (const d of devices) {
52
- const stops = allInOne.stops.filter(t => t.deviceId === d.id)
53
- const trips = allInOne.trips.filter(t => t.deviceId === d.id)
54
- const route = allInOne.route.filter(r => r.deviceId === d.id)
55
- const deviceFuelServices = fuelServicesData.filter(f => Number.parseInt(d.attributes.odooId) === f.vehicle_id[0])
56
-
57
- if (trips.length > 0) {
58
- const refuelingPositions = await refuelingReport.calculateRefuelingPositions(userData, d, { route, trips, fuelServices: deviceFuelServices })
59
- const days = []
60
- for (const date of dates) {
61
- const { tripsByDay, stopsByDay, routeByDay, refuelingPositionsByDay } = await getDataByDay(d, date, { trips, stops, route, refuelingPositions }, userData, traccar)
62
- const summaryByDay = summary.find(a => a.date === date).summary
63
- const summaryByDevice = summaryByDay.find(a => a.deviceId === d.id)
64
-
65
- const firstStopIndex = !stopsByDay.length || !tripsByDay.length ? 0 : (new Date(tripsByDay[0].startTime) < new Date(stopsByDay[0].startTime) ? 0 : 1)
66
- const distance = summaryByDevice.distance
67
-
68
- const hasFuelSensor = 'fuel_low_threshold' in d.attributes &&
69
- 'fuel_high_threshold' in d.attributes &&
70
- d.attributes.fuel_tank_capacity
71
-
72
- const spentFuel = hasFuelSensor ?
73
- tripsByDay.length > 0 ? calculateTotalFuelFromSensor(tripsByDay, stopsByDay, route, d, firstStopIndex) : 0
74
- : calculateConsumption(d, { trips: tripsByDay, stops: stopsByDay, route: routeByDay, summary: summaryByDevice })
75
-
76
- const dataRow = {
77
- date,
78
- distance,
79
- spentFuel,
80
- avgConsumption: getCanAvgConsumption(distance, spentFuel),
81
- startOdometer: summaryByDevice.startOdometer,
82
- endOdometer: summaryByDevice.endOdometer,
83
- duration: tripsByDay.reduce((a, b) => a + b.duration, 0),
84
- refueling: (refuelingPositionsByDay && refuelingPositionsByDay.length > 0) ? refuelingPositionsByDay.reduce((a, b) => a + b.diff, 0) : 0
85
- }
86
-
87
- days.push(dataRow)
88
- }
89
-
90
- if (days.length > 0) {
91
- allData.devices.push({
92
- device: d,
93
- days
94
- })
95
- allData.totalDevices = allData.totalDevices + 1
96
- }
97
- }
98
- }
99
-
100
- reportData.push(allData)
101
-
102
- return reportData
103
- }
104
-
105
- function getOdooAvgConsumption (day, allRefuelingPositions, trips) {
106
- const refuelingData = allRefuelingPositions.filter(r => new Date(r.date).getTime() > new Date(day).getTime())
107
- let odooFuel
108
- let kms
109
- if (!refuelingData.length) {
110
- odooFuel = allRefuelingPositions.reduce((a, b) => a + b.diff, 0)
111
- kms = trips.reduce((a, b) => a + b.distance, 0)
112
- } else {
113
- odooFuel = allRefuelingPositions.filter(r => new Date(r.date).getTime() <= new Date(refuelingData[0].date).getTime()).reduce((a, b) => a + b.diff, 0)
114
- kms = trips.filter(t => new Date(t.startDate).getTime() < new Date(refuelingData[0].date).getTime()).reduce((a, b) => a + b.distance, 0)
115
- }
116
-
117
- if (kms === 0 || odooFuel === 0) { return { byKms: 0, byLiters: 0 } }
118
-
119
- return calculateAvgConsumption(odooFuel, kms)
120
- }
121
-
122
- function getCanAvgConsumption (distance, spentFuel) {
123
- if (distance === 0 || spentFuel === 0) { return { byKms: 0, byLiters: 0 } }
124
-
125
- return calculateAvgConsumption(spentFuel, distance)
126
- }
127
-
128
- function calculateAvgConsumption (fuel, distance) {
129
- return { byKms: fuel * 100 / (distance / 1000), byLiters: (distance / 1000) / fuel }
130
- }
131
-
132
- function exportFuelConsumptionReportToExcel (userData, reportData) {
133
- const translations = getTranslations(userData)
134
- const lang = getLanguage(userData)
135
-
136
- const settings = {
137
- sheetName: 'FuelConsumptionReport', // The name of the sheet
138
- fileName: 'FuelConsumptionReport' // The name of the spreadsheet
139
- }
140
- const headers = [
141
- { label: translations.report.vehicle, value: 'name' },
142
- { label: translations.report.date, value: 'date' },
143
- { label: translations.report.distance, value: 'kms' },
144
- { label: translations.report.consumption, value: 'consumption' },
145
- { label: (lang === 'pt-BR' ? translations.report.avg_consumption_by_liters : translations.report.avg_consumption), value: 'avg_consumption' },
146
- { label: translations.report.duration, value: 'duration' },
147
- { label: translations.report.odometer, value: 'accumulated_kms' },
148
- { label: translations.report.refueling, value: 'refueling' }
149
- ]
150
- let data = []
151
- if (reportData.devices) {
152
- reportData.devices.forEach((d) => {
153
- data = data.concat(d.days.map((r) => {
154
- return {
155
- name: d.device.name,
156
- date: new Date(r.date).toLocaleDateString(),
157
- kms: formatNumber(r.distance / 1000),
158
- consumption: r.spentFuel > 0 ? formatNumber(r.spentFuel) : 0,
159
- avg_consumption: formatNumber(lang === 'pt-BR' ? r.avgConsumption.byLiters : r.avgConsumption.byKms),
160
- duration: convertMS(r.duration),
161
- accumulated_kms: formatNumber((r.endOdometer / 1000)),
162
- refueling: formatNumber(r.refueling)
163
- }
164
- }))
165
- // Totals
166
- data = data.concat([{
167
- date: 'Total:' + d.days.length,
168
- kms: getTotalKms(userData.user, d.days),
169
- consumption: getTotalConsumption(userData.user, d.days),
170
- avg_consumption: getTotalAvgConsumption(userData.user, d.days),
171
- duration: getTotalDuration(userData.user, d.days),
172
- refueling: getTotalRefueling(userData.user, d.days)
173
- }])
174
- data = data.concat([{}])
175
- })
176
- return {
177
- headers,
178
- data,
179
- settings
180
- }
181
- }
182
- }
183
-
184
- async function exportFuelConsumptionReportToPDF (userData, reportData) {
185
- console.log('Export to PDF')
186
- const timezone = userData.user.attributes.timezone
187
- const translations = getTranslations(userData)
188
- const lang = getLanguage(userData)
189
-
190
- // eslint-disable-next-line new-cap
191
- const doc = new jsPDF.jsPDF('l')
192
- await headerFromUser(doc, translations.report.titleFuelConsumptionReport, userData.user)
193
-
194
- const headers = [
195
- translations.report.date,
196
- translations.report.distance,
197
- translations.report.consumption,
198
- (lang === 'pt-BR' ? translations.report.avg_consumption_by_liters : translations.report.avg_consumption),
199
- translations.report.duration,
200
- translations.report.odometer,
201
- translations.report.refueling
202
- ]
203
-
204
- if (reportData.devices) {
205
- reportData.devices.forEach((d, index) => {
206
- const data = []
207
- const name = deviceName(d.device)
208
- const group = userData.groups.find(g => d.device.groupId === g.id)
209
-
210
- const space = index === 0 ? 8 : 0
211
- if (index) {
212
- doc.addPage()
213
- }
214
-
215
- doc.setFontSize(13)
216
- doc.text(name, 20, space + 20)
217
- doc.setFontSize(11)
218
- doc.text(group ? translations.report.group + ': ' + group.name : '', 200, space + 20)
219
- doc.text(convertToLocaleString(reportData.from, lang, timezone) + ' - ' + convertToLocaleString(reportData.to, lang, timezone), 20, space + 25)
220
-
221
- d.days.forEach(r =>
222
- data.push([
223
- new Date(r.date).toLocaleDateString(),
224
- formatNumber(r.distance / 1000),
225
- r.spentFuel > 0 ? formatNumber(r.spentFuel) : 0,
226
- formatNumber(lang === 'pt-BR' ? r.avgConsumption.byLiters : r.avgConsumption.byKms),
227
- convertMS(r.duration),
228
- formatNumber((r.endOdometer / 1000)),
229
- formatNumber(r.refueling)
230
- ]))
231
-
232
- const footValues = [
233
- 'Total:' + d.days.length,
234
- getTotalKms(userData.user, d.days),
235
- getTotalConsumption(userData.user, d.days),
236
- getTotalAvgConsumption(userData.user, d.days),
237
- getTotalDuration(userData.user, d.days),
238
- '',
239
- getTotalRefueling(userData.user, d.days)
240
- ]
241
-
242
- const style = getStyle(getUserPartner(userData.user))
243
- addTable(doc, headers, data, footValues, style, space + (userData.allWeek ? 40 : 45),
244
- {
245
- 1: { halign: 'right' },
246
- 2: { halign: 'right' },
247
- 3: { halign: 'right' },
248
- 4: { halign: 'right' },
249
- 5: { halign: 'right' },
250
- 6: { halign: 'right' }
251
- })
252
- })
253
-
254
- return doc
255
- }
256
- }
257
-
258
- function deviceName (device) {
259
- return device.name + (device.attributes.license_plate ? ', ' + device.attributes.license_plate : '') + (device.model ? ', ' + device.model : '')
260
- }
261
-
262
- function getTotalKms (user, data) {
263
- const totalKms = data.reduce((a, b) => a + b.distance, 0)
264
- return formatNumber(totalKms / 1000, user.attributes.lang)
265
- }
266
-
267
- function getTotalConsumption (user, data) {
268
- const totalConsumption = data.reduce((a, b) => a + (b.spentFuel > 0 ? b.spentFuel : 0), 0)
269
- return formatNumber(totalConsumption, user.attributes.lang)
270
- }
271
-
272
- function getTotalDuration (user, data) {
273
- const totalDuration = data.reduce((a, b) => a + b.duration, 0)
274
- return convertMS(totalDuration)
275
- }
276
-
277
- function getTotalRefueling (user, data) {
278
- const totalRefueling = data.reduce((a, b) => a + b.refueling, 0)
279
- return formatNumber(totalRefueling, user.attributes.lang)
280
- }
281
-
282
- function getTotalAvgConsumption (user, data) {
283
- const values = data.map(a => a.distance * (user.attributes.lang === 'pt-BR' ? a.avgConsumption.byLiters : a.avgConsumption.byKms)).reduce((a, b) => a + b, 0)
284
- const totalKms = data.reduce((a, b) => a + b.distance, 0)
285
-
286
- const totalAvgConsumption = values / totalKms
287
- return formatNumber(totalAvgConsumption, user.attributes.lang)
288
- }
289
-
290
- exports.createFuelConsumptionReport = createFuelConsumptionReport
291
- exports.exportFuelConsumptionReportToPDF = exportFuelConsumptionReportToPDF
292
- exports.exportFuelConsumptionReportToExcel = exportFuelConsumptionReportToExcel
293
- exports.create = createFuelConsumptionReport
294
- exports.exportToPDF = exportFuelConsumptionReportToPDF
295
- exports.exportToExcel = exportFuelConsumptionReportToExcel
296
- exports.getCanAvgConsumption = getCanAvgConsumption