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,447 @@
1
+ const automaticReports = require('./automaticReports')
2
+ const traccarHelper = require('./util/traccar')
3
+ const { devicesToProcess, deviceName } = require('./util/device')
4
+ const {
5
+ getTranslations, isClientSide, convertToLocaleString, convertMS, getDates, convertToLocaleDateString,
6
+ convertToLocaleTimeString
7
+ } = require('./util/utils')
8
+ const jsPDF = require('jspdf')
9
+ const { headerFromUser, addTable } = require('./util/pdfDocument')
10
+ const { getStyle } = require('./reportStyle')
11
+ const { getUserPartner } = require('fleetmap-partners')
12
+
13
+ function processDevices (from, to, devices, data, userData) {
14
+ const result = []
15
+
16
+ for (const d of devices) {
17
+ const positions = data.route.filter(t => t.deviceId === d.id)
18
+
19
+ const statusPeriodBySensor = computeSensorPeriods(positions, userData.sensors, d, userData)
20
+
21
+ const rows = Object.values(statusPeriodBySensor).flat()
22
+ rows.sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime())
23
+
24
+ if (userData.groupByDay) {
25
+ const dates = getDates(from, to, userData.user.attributes.timezone)
26
+
27
+ const data = userData.groupBySensor ? groupByDayAndSensor(rows, dates) : groupByDay(rows, dates)
28
+
29
+ result.push({
30
+ device: d,
31
+ from,
32
+ to,
33
+ rows: data
34
+ })
35
+ } else {
36
+ result.push({
37
+ device: d,
38
+ from,
39
+ to,
40
+ rows
41
+ })
42
+ }
43
+ }
44
+
45
+ return result
46
+ }
47
+
48
+ function computeSensorPeriods (positions, sensors, d, userData) {
49
+ if (!positions || positions.length === 0) return {}
50
+
51
+ const result = {}
52
+ const minOn = (userData.minimumMinutesOn || 0) * 60 * 1000
53
+ const minOff = (userData.minimumMinutesOff || 0) * 60 * 1000
54
+ const maxDistanceBetweenPositions = 30 * 60 * 1000
55
+
56
+ const minThreshold = (value) => (value ? minOn : minOff)
57
+
58
+ function pushPeriod (sensorKey, s, value, startTime, endTime, endPos) {
59
+ const duration = endTime - startTime
60
+ const period = {
61
+ value,
62
+ startTime,
63
+ endTime,
64
+ duration,
65
+ name: s.name,
66
+ endAddress: endPos.address,
67
+ valueDescription: value
68
+ ? d.attributes[s.sensor + 'on']
69
+ : d.attributes[s.sensor + 'off']
70
+ }
71
+ console.debug(period)
72
+ result[sensorKey].push(period)
73
+ }
74
+
75
+ for (const s of sensors) result[s.sensor] = []
76
+
77
+ // Track current period per sensor
78
+ const current = {}
79
+ const firstPos = positions[0]
80
+ for (const s of sensors) {
81
+ current[s.sensor] = {
82
+ value: firstPos.attributes[s.id],
83
+ startTime: new Date(firstPos.fixTime)
84
+ }
85
+ }
86
+
87
+ for (let i = 1; i < positions.length; i++) {
88
+ const prevPos = positions[i - 1]
89
+ const currPos = positions[i]
90
+ const changeTime = new Date(currPos.fixTime)
91
+ const endTime = new Date(prevPos.fixTime)
92
+
93
+ for (const s of sensors) {
94
+ const key = s.sensor
95
+ const prevValue = current[key].value
96
+ const newValue = currPos.attributes[s.id]
97
+
98
+ const oldStartTime = current[key].startTime
99
+ const gap = changeTime - new Date(prevPos.fixTime)
100
+ if (gap > maxDistanceBetweenPositions) {
101
+ const prevEndTime = prevValue ? new Date(prevPos.fixTime) : changeTime
102
+ const duration = prevEndTime - oldStartTime
103
+ const threshold = minThreshold(prevValue)
104
+
105
+ if (duration >= threshold) {
106
+ pushPeriod(key, s, prevValue, oldStartTime, prevEndTime, prevPos)
107
+ }
108
+
109
+ current[key] = { value: newValue, startTime: changeTime }
110
+
111
+ continue
112
+ }
113
+
114
+ if (prevValue === newValue) continue
115
+
116
+ const prevEndTime = endTime
117
+ const duration = prevEndTime - oldStartTime
118
+ const threshold = minThreshold(prevValue)
119
+ let newPeriodStartTime = changeTime
120
+
121
+ // Check distance between positions
122
+ if (prevValue === false && newValue === true) {
123
+ const nextPos = positions[i + 1]
124
+ if (nextPos && nextPos.attributes && nextPos.attributes[s.id] === true) {
125
+ const nextTime = new Date(nextPos.fixTime)
126
+ if (nextTime - oldStartTime > maxDistanceBetweenPositions) {
127
+ newPeriodStartTime = nextTime
128
+ }
129
+ }
130
+ }
131
+
132
+ if (duration >= threshold) {
133
+ pushPeriod(key, s, prevValue, oldStartTime, prevEndTime, prevPos)
134
+ } else {
135
+ // merging logic for previous ignored period
136
+ const list = result[key]
137
+ if (list.length > 0) {
138
+ const last = list[list.length - 1]
139
+ if (last.value === newValue) {
140
+ list.pop()
141
+ newPeriodStartTime = new Date(last.startTime)
142
+ }
143
+ }
144
+ }
145
+
146
+ current[key] = {
147
+ value: newValue,
148
+ startTime: newPeriodStartTime
149
+ }
150
+ }
151
+ }
152
+
153
+ // Close final periods
154
+ const lastPos = positions[positions.length - 1]
155
+ const lastTime = new Date(lastPos.fixTime)
156
+
157
+ for (const s of sensors) {
158
+ const key = s.sensor
159
+ const st = current[key]
160
+ const startTime = st.startTime
161
+ const endTime = lastTime
162
+ const value = st.value
163
+ const duration = endTime - startTime
164
+ const threshold = minThreshold(value)
165
+
166
+ if (duration >= threshold) {
167
+ pushPeriod(key, s, value, startTime, endTime, lastPos)
168
+ } else {
169
+ // try to merge tiny final period into previous pushed period if same value
170
+ const list = result[key]
171
+ if (list.length > 0 && list[list.length - 1].value === value) {
172
+ const last = list[list.length - 1]
173
+ last.endTime = endTime
174
+ last.duration = last.endTime - last.startTime
175
+ last.endAddress = lastPos.address
176
+ last.valueDescription = value
177
+ ? d.attributes[s.sensor + 'on']
178
+ : d.attributes[s.sensor + 'off']
179
+ }
180
+ // otherwise drop final tiny period
181
+ }
182
+ }
183
+
184
+ return result
185
+ }
186
+
187
+ async function create (from, to, userData, traccar) {
188
+ const devices = devicesToProcess(userData)
189
+ const sliced = automaticReports.sliceArray(devices, 5)
190
+ const result = []
191
+
192
+ let deviceCount = 0
193
+ for (const slice of sliced) {
194
+ const allInOne = await traccarHelper.getAllInOne(traccar, from, to, slice, true, false, false, false, deviceCount, devices.length)
195
+ const routeData = allInOne.route
196
+
197
+ if (routeData.length > 0) {
198
+ result.push(...processDevices(from, to, slice, { route: routeData }, userData))
199
+ }
200
+ deviceCount = deviceCount + slice.length
201
+ }
202
+
203
+ return [{ devices: result }]
204
+ }
205
+
206
+ function groupByDay (rows, dates) {
207
+ const groupedByDay = {}
208
+
209
+ dates.forEach(dateStr => {
210
+ const dayStart = new Date(dateStr)
211
+ dayStart.setHours(0, 0, 0, 0)
212
+ const dayEnd = new Date(dayStart.getTime() + 86400000 - 1) // 23:59:59
213
+
214
+ rows.forEach(row => {
215
+ if (row.value) {
216
+ const rowStart = new Date(row.startTime)
217
+ const rowEnd = new Date(row.endTime)
218
+
219
+ if (rowStart <= dayEnd && rowEnd >= dayStart) {
220
+ if (!groupedByDay[dateStr]) {
221
+ groupedByDay[dateStr] = {}
222
+ }
223
+
224
+ if (!groupedByDay[dateStr][row.name]) {
225
+ groupedByDay[dateStr][row.name] = {
226
+ firstActivation: null,
227
+ lastActivation: null,
228
+ totalDuration: 0
229
+ }
230
+ }
231
+
232
+ const periodStart = Math.max(rowStart.getTime(), dayStart.getTime())
233
+ const periodEnd = Math.min(rowEnd.getTime(), dayEnd.getTime())
234
+
235
+ const sensorData = groupedByDay[dateStr][row.name]
236
+
237
+ if (!sensorData.firstActivation || periodStart < sensorData.firstActivation) {
238
+ sensorData.firstActivation = periodStart
239
+ }
240
+
241
+ if (!sensorData.lastActivation || periodEnd > sensorData.lastActivation) {
242
+ sensorData.lastActivation = periodEnd
243
+ }
244
+
245
+ sensorData.totalDuration += periodEnd - periodStart
246
+ }
247
+ }
248
+ })
249
+ })
250
+
251
+ // 🖨️ Exibir os dados processados
252
+ Object.entries(groupedByDay).forEach(([date, sensors]) => {
253
+ Object.entries(sensors).forEach(([sensor, data]) => {
254
+ console.log(`Data: ${date}, Sensor: ${sensor}`)
255
+ console.log(` Primeira ativação: ${data.firstActivation ? new Date(data.firstActivation).toLocaleTimeString() : '00:00'}`)
256
+ console.log(` Última ativação: ${data.lastActivation ? new Date(data.lastActivation).toLocaleTimeString() : '23:59'}`)
257
+ console.log(` Duração total: ${(data.totalDuration / 1000 / 60).toFixed(2)} minutos`)
258
+ })
259
+ })
260
+
261
+ return Object.entries(groupedByDay)
262
+ }
263
+
264
+ function groupByDayAndSensor (rows, dates) {
265
+ const groupedData = {}
266
+
267
+ dates.forEach(dateStr => {
268
+ const dayStart = new Date(dateStr)
269
+ dayStart.setHours(0, 0, 0, 0)
270
+ const dayEnd = new Date(dayStart.getTime() + 86400000 - 1) // 23:59:59
271
+
272
+ if (!groupedData[dateStr]) {
273
+ groupedData[dateStr] = {
274
+ firstActivation: null,
275
+ lastActivation: null,
276
+ totalDuration: 0
277
+ }
278
+ }
279
+
280
+ const activePeriods = []
281
+
282
+ rows.forEach(row => {
283
+ if (row.value) {
284
+ const rowStart = new Date(row.startTime).getTime()
285
+ const rowEnd = new Date(row.endTime).getTime()
286
+
287
+ if (rowStart <= dayEnd.getTime() && rowEnd >= dayStart.getTime()) {
288
+ const periodStart = Math.max(rowStart, dayStart.getTime())
289
+ const periodEnd = Math.min(rowEnd, dayEnd.getTime())
290
+
291
+ activePeriods.push([periodStart, periodEnd]) // Store activation period
292
+ }
293
+ }
294
+ })
295
+
296
+ // Merge overlapping activation periods
297
+ activePeriods.sort((a, b) => a[0] - b[0]) // Sort by start time
298
+ const mergedPeriods = []
299
+ let currentPeriod = null
300
+
301
+ activePeriods.forEach(([start, end]) => {
302
+ if (!currentPeriod) {
303
+ currentPeriod = [start, end]
304
+ } else if (start <= currentPeriod[1]) {
305
+ // Extend the current period if overlapping
306
+ currentPeriod[1] = Math.max(currentPeriod[1], end)
307
+ } else {
308
+ // Store the finished period and start a new one
309
+ mergedPeriods.push(currentPeriod)
310
+ currentPeriod = [start, end]
311
+ }
312
+ })
313
+ if (currentPeriod) {
314
+ mergedPeriods.push(currentPeriod)
315
+ }
316
+
317
+ const totalDuration = mergedPeriods.reduce((sum, [start, end]) => sum + (end - start), 0)
318
+
319
+ if (mergedPeriods.length > 0) {
320
+ groupedData[dateStr].firstActivation = mergedPeriods[0][0]
321
+ groupedData[dateStr].lastActivation = mergedPeriods[mergedPeriods.length - 1][1]
322
+ groupedData[dateStr].totalDuration = totalDuration
323
+ }
324
+ })
325
+
326
+ return Object.entries(groupedData)
327
+ }
328
+ async function exportToPDF (userData, reportData) {
329
+ console.log('exportSensorReportToPDF')
330
+
331
+ const timezone = userData.user.attributes.timezone
332
+ const translations = getTranslations(userData)
333
+ const lang = userData.user.attributes.lang || (isClientSide() && navigator.language)
334
+ const reportDataRows = reportData.devices
335
+
336
+ const headers = userData.groupByDay && userData.groupBySensor
337
+ ? [
338
+ translations.report.date,
339
+ translations.report.start,
340
+ translations.report.end,
341
+ translations.report.duration
342
+ ]
343
+ : (userData.groupByDay
344
+ ? [
345
+ translations.report.date,
346
+ 'Sensor',
347
+ translations.report.start,
348
+ translations.report.end,
349
+ translations.report.duration
350
+ ]
351
+ : [
352
+ 'Sensor',
353
+ translations.report.start,
354
+ translations.report.end,
355
+ 'Estado',
356
+ translations.report.duration,
357
+ translations.report.endAddress
358
+ ])
359
+
360
+ if (reportDataRows) {
361
+ // eslint-disable-next-line new-cap
362
+ const doc = new jsPDF.jsPDF('l')
363
+ await headerFromUser(doc, 'Relatório de Sensores', userData.user)
364
+
365
+ let index = 0
366
+ reportDataRows.forEach(function (d) {
367
+ if (d.rows.length) {
368
+ const data = []
369
+ const name = deviceName(d.device)
370
+ const group = userData.groups.find(g => d.device.groupId === g.id)
371
+
372
+ const space = index === 0 ? 8 : 0
373
+ if (index) {
374
+ doc.addPage()
375
+ }
376
+
377
+ doc.setFontSize(13)
378
+ doc.text(name, 20, space + 20)
379
+ doc.setFontSize(11)
380
+ doc.text(group ? translations.report.group + ': ' + group.name : '', 200, space + 20)
381
+ doc.text(convertToLocaleString(d.from, lang, timezone, userData.user) + ' - ' + convertToLocaleString(d.to, lang, timezone, userData.user), 20, space + 25)
382
+
383
+ if (userData.groupByDay && userData.groupBySensor) {
384
+ d.rows.forEach(day => {
385
+ const temp = [
386
+ convertToLocaleDateString(day[0], lang, timezone, userData.user),
387
+ day[1].firstActivation ? convertToLocaleTimeString(day[1].firstActivation, lang, timezone, userData.user) : '-',
388
+ day[1].lastActivation ? convertToLocaleTimeString(day[1].lastActivation, lang, timezone, userData.user) : '-',
389
+ convertMS(day[1].totalDuration)
390
+ ]
391
+ data.push(temp)
392
+ })
393
+ } else if (userData.groupByDay) {
394
+ d.rows.forEach(day => {
395
+ const sensors = Object.entries(day[1])
396
+ sensors.forEach(sensor => {
397
+ const temp = [
398
+ convertToLocaleDateString(day[0], lang, timezone, userData.user),
399
+ sensor[0],
400
+ sensor[1].firstActivation ? convertToLocaleTimeString(sensor[1].firstActivation, lang, timezone, userData.user) : '-',
401
+ sensor[1].lastActivation ? convertToLocaleTimeString(sensor[1].lastActivation, lang, timezone, userData.user) : '-',
402
+ convertMS(sensor[1].totalDuration)
403
+ ]
404
+ data.push(temp)
405
+ })
406
+ })
407
+ } else {
408
+ d.rows.forEach(row => {
409
+ const temp = [
410
+ row.name,
411
+ convertToLocaleString(row.startTime, lang, timezone, userData.user),
412
+ convertToLocaleString(row.endTime, lang, timezone, userData.user),
413
+ row.valueDescription,
414
+ convertMS(row.duration),
415
+ row.endAddress
416
+ ]
417
+ data.push(temp)
418
+ })
419
+ }
420
+
421
+ const totalDuration = (userData.groupByDay && userData.groupBySensor) ? getTotalDuration(d.rows) : 0
422
+
423
+ const footValues = (userData.groupByDay && userData.groupBySensor) ? ['', '', '', totalDuration] : []
424
+
425
+ const style = getStyle(getUserPartner(userData.user))
426
+
427
+ addTable(doc, headers, data, footValues, style, 40,
428
+ (userData.groupByDay && userData.groupBySensor ? { 3: { halign: 'right' } } : { 4: { halign: 'right' } }))
429
+
430
+ index++
431
+ }
432
+ })
433
+ return doc
434
+ }
435
+ }
436
+
437
+ function getTotalDuration (rows) {
438
+ return convertMS(rows.reduce((a, b) => a + b[1].totalDuration, 0), false)
439
+ }
440
+
441
+ function exportToExcel (userData, reportData) {
442
+
443
+ }
444
+
445
+ exports.exportToPDF = exportToPDF
446
+ exports.exportToExcel = exportToExcel
447
+ exports.create = create