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,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