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.
- package/.claude/worktrees/eager-stonebraker-c08ac7/.eslintrc.js +15 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/enGB.js +309 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/esCL.js +297 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/frFR.json +324 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/index.js +35 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptBR.js +301 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptPT.js +310 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/package-lock.json +11944 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/package.json +45 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/activity-report.js +822 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/automaticReports.js +34 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/carbone/index.js +13 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/custom/vistawasteActivity-report.js +544 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/events-report.js +360 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-consumption-report.js +296 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-drop-report.js +128 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/here.js +172 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/idle-report.js +403 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/index.js +244 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/kms-report.js +549 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/location-report.js +496 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/machines-report.js +278 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/afriquia.js +129 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/dailyuse-report.js +80 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/delayedstart-report.js +223 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/driverranking-report.js +328 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/gpsjump-report.js +61 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/performance-report.js +359 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/refueling-report.js +345 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/reportStyle.js +34 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/sensor-report.js +447 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/speeding-report.js +663 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/stop-report.js +242 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ActivityReportTemplate.mjml +51 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EmptyReportTemplate.mjml +25 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EventsReportTemplate.mjml +75 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/FuelDropReportTemplate.mjml +60 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/IdleReportTemplate.mjml +54 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportGroupByDayTemplate.mjml +43 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportTemplate.mjml +44 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/LocationReportTemplate.mjml +37 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/RefuelingReportTemplate.mjml +60 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SensorReportTemplate.mjml +23 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SpeedingReportTemplate.mjml +80 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/TripReportTemplate.mjml +97 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ZoneReportTemplate.mjml +52 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/report.mjml +96 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/activity.test.js +99 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/carbone.test.js +20 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/events.test.js +30 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/fuel.test.js +35 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/idle.test.js +16 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.js +24 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.test.js +203 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/kms.test.js +74 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/performance.test.js +36 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/positions.test.js +67 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/speeding.test.js +120 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/translations.test.js +21 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/trips.test.js +140 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/zones.test.js +162 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/trip-report.js +641 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/device.js +13 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/driver.js +90 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/fuel.js +102 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/geofence.js +52 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/odoo.js +18 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/parallel.js +64 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/pdfDocument.js +140 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/route.js +78 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/timetable.js +30 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.js +213 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.worker.js +49 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/trips.js +270 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/utils.js +358 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/worker.js +14 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/xpert.js +58 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/word/index.js +95 -0
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/zone-report.js +904 -0
- package/lang/enGB.js +18 -0
- package/lang/esCL.js +18 -0
- package/lang/frFR.json +7 -1
- package/lang/ptBR.js +18 -0
- package/lang/ptPT.js +18 -0
- package/package.json +1 -1
- package/src/partnerReports/driverranking-report.js +152 -49
- package/src/speeding-report.js +11 -1
- package/src/util/pdfDocument.js +2 -1
|
@@ -0,0 +1,360 @@
|
|
|
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
|
|
@@ -0,0 +1,296 @@
|
|
|
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
|