fleetmap-reports 1.0.410 → 1.0.411

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/.eslintrc.js ADDED
@@ -0,0 +1,15 @@
1
+ module.exports = {
2
+ env: {
3
+ browser: true,
4
+ commonjs: true,
5
+ es2021: true
6
+ },
7
+ extends: [
8
+ 'standard'
9
+ ],
10
+ parserOptions: {
11
+ ecmaVersion: 'latest'
12
+ },
13
+ rules: {
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetmap-reports",
3
- "version": "1.0.410",
3
+ "version": "1.0.411",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -32,7 +32,7 @@
32
32
  "eslint": "^8.15.0",
33
33
  "eslint-config-standard": "^17.0.0",
34
34
  "eslint-plugin-import": "^2.26.0",
35
- "eslint-plugin-n": "^15.2.0",
35
+ "eslint-plugin-node": "^11.1.0",
36
36
  "eslint-plugin-promise": "^6.0.0",
37
37
  "jest": "^27.5.0",
38
38
  "mocha": "^9.0.3",
@@ -1,280 +1,341 @@
1
- const traccar = require("./util/traccar");
2
- const jsPDF = require("jspdf");
3
- const {headerFromUser, AmiriRegular} = require("./util/pdfDocument");
4
- const {convertToLocaleString, convertMS, getTranslations} = require("./util/utils");
5
- const {getStyle} = require("./reportStyle");
6
- const {getUserPartner} = require("fleetmap-partners");
7
- const {devicesToProcess} = require("./util/device");
1
+ const jsPDF = require('jspdf')
2
+ const { headerFromUser, AmiriRegular } = require('./util/pdfDocument')
3
+ const { convertToLocaleString, convertMS, getTranslations } = require('./util/utils')
4
+ const { getStyle } = require('./reportStyle')
5
+ const { getUserPartner } = require('fleetmap-partners')
6
+ const { devicesToProcess } = require('./util/device')
7
+ const automaticReports = require('./automaticReports')
8
+ const traccarHelper = require('./util/traccar')
8
9
 
9
10
  const fileName = 'IdleReport'
10
11
 
11
- async function createIdleReport(from, to, userData, traccarInstance) {
12
- console.log('Create IdleReport')
13
- const reportData = []
14
-
15
- if (userData.byGroup) {
16
- console.log("ByGroup")
17
- const allData = await createTripReportByGroup(from, to, userData, traccarInstance)
18
- reportData.push(...allData)
19
- } else {
20
- console.log("ByDevice")
21
- const report = await createTripReportByDevice(from, to, userData, traccarInstance)
22
- reportData.push(report)
23
- }
24
-
25
- return reportData
12
+ async function createIdleReport (from, to, userData, traccarInstance) {
13
+ console.log('Create IdleReport')
14
+ const reportData = []
15
+
16
+ if (userData.byDriver) {
17
+ console.log('ByDriver')
18
+ const report = await createIdleReportByDriver(from, to, userData, traccarInstance)
19
+ reportData.push(report)
20
+ } else if (userData.byGroup) {
21
+ console.log('ByGroup')
22
+ const allData = await createIdleReportByGroup(from, to, userData, traccarInstance)
23
+ reportData.push(...allData)
24
+ } else {
25
+ console.log('ByDevice')
26
+ const report = await createIdleReportByDevice(from, to, userData, traccarInstance)
27
+ reportData.push(report)
28
+ }
29
+
30
+ return reportData
26
31
  }
27
32
 
28
- async function createTripReportByGroup(from, to, userData, traccarInstance) {
29
- const reportData = []
30
- for (const g of userData.groups) {
31
- const devices = userData.devices.filter(d => d.groupId === g.id)
32
- console.log(g.name + ' devices:' + devices.length)
33
- if(devices.length > 0) {
34
- const groupData = {
35
- devices: [],
36
- group: g,
37
- from: from,
38
- to: to
39
- }
40
-
41
- const response = await traccarInstance.reports.reportsRouteGet(from, to, null, [g.id])
42
- const routes = response.data
43
-
44
- devices.sort((a, b) => (a.name > b.name) ? 1 : -1)
45
-
46
- if (routes.length > 0) {
47
- console.log('Routes:' + routes.length)
48
- groupData.devices = processDevices(from, to, devices, routes, userData)
49
-
50
- reportData.push(groupData)
51
- }
52
- }
53
- }
33
+ async function createIdleReportByDriver (from, to, userData, traccarInstance) {
34
+ const devices = await traccarInstance.devices.devicesGet().then(d => d.data)
35
+ console.log(devices.length)
54
36
 
55
- const groupIds = userData.groups.map(g => g.id)
56
- const withoutGroupDevices = userData.devices.filter(d => !groupIds.includes(d.groupId))
37
+ if (!devices.length) {
38
+ // Empty report
39
+ return { drivers: [] }
40
+ }
57
41
 
58
- const allData = await createTripReportByDevice(from, to, withoutGroupDevices, userData, traccarInstance)
59
- reportData.push(allData)
42
+ const { route } = await traccarHelper.getAllInOne(traccarInstance, from, to, devices, true, false, false, false)
60
43
 
61
- return reportData
44
+ return { drivers: processDrivers(from, to, userData, route) }
62
45
  }
63
46
 
64
- async function createTripReportByDevice(from, to, userData, traccarInstance) {
65
- const allData = {
47
+ async function createIdleReportByGroup (from, to, userData, traccarInstance) {
48
+ const reportData = []
49
+ for (const g of userData.groups) {
50
+ const devices = userData.devices.filter(d => d.groupId === g.id)
51
+ console.log(g.name + ' devices:' + devices.length)
52
+ if (devices.length > 0) {
53
+ const groupData = {
66
54
  devices: [],
67
- from: from,
68
- to: to
55
+ group: g,
56
+ from,
57
+ to
58
+ }
59
+
60
+ const response = await traccarInstance.reports.reportsRouteGet(from, to, null, [g.id])
61
+ const routes = response.data
62
+
63
+ devices.sort((a, b) => (a.name > b.name) ? 1 : -1)
64
+
65
+ if (routes.length > 0) {
66
+ console.log('Routes:' + routes.length)
67
+ groupData.devices = processDevices(from, to, devices, routes, userData)
68
+
69
+ reportData.push(groupData)
70
+ }
69
71
  }
72
+ }
73
+
74
+ const groupIds = userData.groups.map(g => g.id)
75
+ const withoutGroupDevices = userData.devices.filter(d => !groupIds.includes(d.groupId))
70
76
 
71
- const devices = devicesToProcess(userData)
77
+ const allData = await createIdleReportByDevice(from, to, withoutGroupDevices, userData, traccarInstance)
78
+ reportData.push(allData)
72
79
 
73
- const route = await traccar.getRoute(traccarInstance, from, to, devices)
80
+ return reportData
81
+ }
82
+
83
+ async function createIdleReportByDevice (from, to, userData, traccarInstance) {
84
+ const allData = {
85
+ devices: [],
86
+ from,
87
+ to
88
+ }
89
+
90
+ const devices = devicesToProcess(userData)
91
+ const sliced = automaticReports.sliceArray(devices, 5)
74
92
 
75
- console.log('Route:' + route.length)
93
+ let deviceCount = 0
94
+ for (const slice of sliced) {
95
+ const { route } = await traccarHelper.getAllInOne(traccarInstance, from, to, slice, true, false, false, false, deviceCount, devices.length)
76
96
 
77
97
  if (route.length > 0) {
78
- allData.devices = processDevices(from, to, devices, route, userData)
98
+ allData.devices.push(...processDevices(from, to, slice, route, userData))
79
99
  }
80
100
 
81
- return allData
101
+ deviceCount = deviceCount + slice.length
102
+ }
103
+
104
+ return allData
82
105
  }
83
106
 
84
- function processDevices(from, to, devices, routes, userData) {
85
- const devicesResult = []
86
-
87
- devices.forEach(d => {
88
- const route = routes.filter(p => p.deviceId === d.id)
89
-
90
- const idleEvents = []
91
- let inIdle = false
92
- route.forEach(p => {
93
- if(p.attributes.ignition && p.speed === 0){
94
- if(!inIdle) {
95
- const idleEvent = {
96
- position: p,
97
- idleTime: 0
98
- }
99
- idleEvents.push(idleEvent)
100
- inIdle = true
101
- }
102
- } else if(inIdle) {
103
- const currentIdleEvent = idleEvents[idleEvents.length-1]
104
- currentIdleEvent.idleTime = new Date(p.fixTime) - new Date(currentIdleEvent.position.fixTime)
105
- inIdle = false
106
- }
107
- })
107
+ function processDrivers (from, to, userData, routes) {
108
+ const driversResult = []
109
+ userData.drivers.forEach(d => {
110
+ const route = routes.filter(p => p.attributes.driverUniqueId === d.uniqueId)
108
111
 
109
- const filteredEvents = idleEvents.filter(e => userData.minimumIdleMinutes ? e.idleTime > userData.minimumIdleMinutes*60*1000 : true)
112
+ const idleEvents = getIdleEvents(route)
110
113
 
111
- if(filteredEvents.length) {
112
- const deviceData = {
113
- device: d,
114
- idleEvents: filteredEvents
115
- }
116
- devicesResult.push(deviceData)
114
+ if (route.length > 0) {
115
+ const filteredEvents = idleEvents.filter(e => userData.minimumIdleMinutes ? e.idleTime > userData.minimumIdleMinutes * 60 * 1000 : true)
116
+
117
+ if (filteredEvents.length) {
118
+ const driverData = {
119
+ driver: d,
120
+ idleEvents: filteredEvents
117
121
  }
118
- })
122
+ driversResult.push(driverData)
123
+ }
124
+ }
125
+ })
119
126
 
120
- return devicesResult
127
+ return driversResult
121
128
  }
122
129
 
123
- async function exportIdleReportToPDF(userData, reportData) {
124
- console.log('Export to PDF')
130
+ function processDevices (from, to, devices, routes, userData) {
131
+ const devicesResult = []
125
132
 
126
- const timezone = userData.user.attributes.timezone
127
- const lang = userData.user.attributes.lang || (navigator && navigator.language)
128
- const translations = getTranslations(userData)
133
+ devices.forEach(d => {
134
+ const route = routes.filter(p => p.deviceId === d.id)
129
135
 
130
- const idleData = userData.byDriver ? reportData.drivers : reportData.devices
136
+ const idleEvents = getIdleEvents(route)
131
137
 
132
- const headers = [
133
- translations.report.date,
134
- translations.report.address,
135
- translations.report.duration
136
- ]
138
+ const filteredEvents = idleEvents.filter(e => userData.minimumIdleMinutes ? e.idleTime > userData.minimumIdleMinutes * 60 * 1000 : true)
137
139
 
138
- if (userData.byDriver) {
139
- headers.push(translations.report.vehicle)
140
- } else {
141
- headers.push(translations.report.driver)
140
+ if (filteredEvents.length) {
141
+ const deviceData = {
142
+ device: d,
143
+ idleEvents: filteredEvents
144
+ }
145
+ devicesResult.push(deviceData)
142
146
  }
147
+ })
143
148
 
144
- if (idleData) {
145
- let first = true
146
- const doc = new jsPDF.jsPDF('l');
147
- await headerFromUser(doc, translations.report.titleIdleReport, userData.user)
148
-
149
- idleData.forEach(d => {
150
- try {
151
- let data = []
152
-
153
- const name = userData.byDriver ? d.driver.name : deviceName(d.device)
154
- const group = userData.byDriver ? userData.groups.find(g => g.drivers.includes(d.id)) : userData.groups.find(g => d.device.groupId === g.id)
155
-
156
- let space = 0
157
- if(!first) {
158
- doc.addPage()
159
- } else {
160
- first = false
161
- space = 10
162
- }
163
- doc.setFontSize(13)
164
- doc.text(name, 20, space+20 )
165
- doc.setFontSize(11)
166
- doc.text(group ? translations.report.group + ': ' + group.name : '', 150, space+20 )
167
- doc.text(convertToLocaleString(reportData.from, lang, timezone) + ' - ' + convertToLocaleString(reportData.to, lang, timezone), 20, space+25 )
168
-
169
- d.idleEvents.map(a => {
170
- const temp = [
171
- getIdleEventDate(a, userData.user),
172
- a.position.address,
173
- convertMS(a.idleTime, true),
174
- userData.byDriver ? a.deviceName : a.driver
175
- ]
176
-
177
- data.push(temp)
178
- })
179
-
180
- const footValues = [
181
- 'Total:' + d.idleEvents.length,
182
- '',
183
- convertMS(d.idleEvents.reduce((a, b) => a + b.idleTime, 0), true)
184
- ]
185
-
186
- if(userData.roadSpeedLimits){
187
- footValues.splice(2, 0, '')
188
- }
189
-
190
- if(timezone === 'Asia/Qatar') {
191
- doc.addFileToVFS("Amiri-Regular.ttf", AmiriRegular());
192
- doc.addFont("Amiri-Regular.ttf", "Amiri", "normal");
193
- }
194
-
195
- const style = getStyle(getUserPartner(userData.user))
196
- doc.autoTable({
197
- head: [headers],
198
- body: data,
199
- foot: [footValues],
200
- showFoot: 'lastPage',
201
- headStyles: {
202
- fillColor: style.pdfHeaderColor,
203
- textColor: style.pdfHeaderTextColor,
204
- fontSize: 10
205
- },
206
- bodyStyles: {
207
- fillColor: style.pdfBodyColor,
208
- textColor: style.pdfBodyTextColor,
209
- fontSize: 8,
210
- font: timezone === 'Asia/Qatar' ? "Amiri" : ''
211
-
212
- },
213
- footStyles: {
214
- fillColor: style.pdfFooterColor,
215
- textColor: style.pdfFooterTextColor,
216
- fontSize: 9
217
- },
218
- startY: space+35 });
219
- } catch (e) {
220
- console.error(e, 'moving on...')
221
- }
222
- })
149
+ return devicesResult
150
+ }
223
151
 
224
- return doc
225
- }
152
+ function getIdleEvents (route) {
153
+ const idleEvents = []
154
+
155
+ const routeByDevice = route.reduce(function (a, x) {
156
+ (a[x.deviceId] = a[x.deviceId] || []).push(x)
157
+ return a
158
+ }, {})
159
+
160
+ Object.keys(routeByDevice).forEach(function (key) {
161
+ let inIdle = false
162
+ routeByDevice[key].forEach(p => {
163
+ if (p.attributes.ignition && p.speed === 0) {
164
+ if (!inIdle) {
165
+ const idleEvent = {
166
+ position: p,
167
+ idleTime: 0
168
+ }
169
+ idleEvents.push(idleEvent)
170
+ inIdle = true
171
+ }
172
+ } else if (inIdle) {
173
+ const currentIdleEvent = idleEvents[idleEvents.length - 1]
174
+ currentIdleEvent.idleTime = new Date(p.fixTime) - new Date(currentIdleEvent.position.fixTime)
175
+ inIdle = false
176
+ }
177
+ })
178
+ })
179
+
180
+ return idleEvents
226
181
  }
227
182
 
228
- function exportIdleReportToExcel(userData, reportData) {
229
- console.log('Export to Excel')
183
+ async function exportIdleReportToPDF (userData, reportData) {
184
+ console.log('Export to PDF')
185
+
186
+ const timezone = userData.user.attributes.timezone
187
+ const lang = userData.user.attributes.lang || (navigator && navigator.language)
188
+ const translations = getTranslations(userData)
189
+
190
+ const idleData = userData.byDriver ? reportData.drivers : reportData.devices
191
+
192
+ const headers = [
193
+ translations.report.date,
194
+ translations.report.address,
195
+ translations.report.duration
196
+ ]
197
+
198
+ if (userData.byDriver) {
199
+ headers.push(translations.report.vehicle)
200
+ } else {
201
+ headers.push(translations.report.driver)
202
+ }
203
+
204
+ if (idleData) {
205
+ let first = true
206
+ // eslint-disable-next-line new-cap
207
+ const doc = new jsPDF.jsPDF('l')
208
+ await headerFromUser(doc, translations.report.titleIdleReport, userData.user)
209
+
210
+ idleData.forEach(d => {
211
+ try {
212
+ const data = []
213
+
214
+ const name = userData.byDriver ? d.driver.name : deviceName(d.device)
215
+ const group = userData.byDriver ? userData.groups.find(g => g.drivers.includes(d.id)) : userData.groups.find(g => d.device.groupId === g.id)
216
+
217
+ let space = 0
218
+ if (!first) {
219
+ doc.addPage()
220
+ } else {
221
+ first = false
222
+ space = 10
223
+ }
224
+ doc.setFontSize(13)
225
+ doc.text(name, 20, space + 20)
226
+ doc.setFontSize(11)
227
+ doc.text(group ? translations.report.group + ': ' + group.name : '', 150, space + 20)
228
+ doc.text(convertToLocaleString(reportData.from, lang, timezone) + ' - ' + convertToLocaleString(reportData.to, lang, timezone), 20, space + 25)
229
+
230
+ d.idleEvents.forEach(a => {
231
+ const temp = [
232
+ getIdleEventDate(a, userData.user),
233
+ a.position.address,
234
+ convertMS(a.idleTime, true),
235
+ userData.byDriver ? a.deviceName : a.driver
236
+ ]
237
+ data.push(temp)
238
+ })
230
239
 
231
- const translations = getTranslations(userData)
240
+ const footValues = [
241
+ 'Total:' + d.idleEvents.length,
242
+ '',
243
+ convertMS(d.idleEvents.reduce((a, b) => a + b.idleTime, 0), true)
244
+ ]
232
245
 
233
- const idleData = userData.byDriver ? reportData.drivers : reportData.devices
246
+ if (userData.roadSpeedLimits) {
247
+ footValues.splice(2, 0, '')
248
+ }
234
249
 
235
- const settings = {
236
- sheetName: translations.report.titleIdleReport.substring(0, 31), // The name of the sheet
237
- fileName: fileName, // The name of the spreadsheet
238
- }
239
- const headers = [
240
- userData.byDriver ? {label: translations.report.driver, value:'driver'} : {label: translations.report.vehicle, value:'name'},
241
- {label: translations.report.date, value:'fixTime'},
242
- {label: translations.report.address, value:'address'},
243
- {label: translations.report.duration, value:'duration'},
244
- userData.byDriver ? {label: translations.report.vehicle, value:'name'} : {label: translations.report.driver, value:'driver'}
245
- ]
246
-
247
- let data = []
248
- if (idleData) {
249
- idleData.forEach(d => {
250
- data = data.concat([{}])
251
- data = data.concat(d.idleEvents.map(a => {
252
- return {
253
- driver: userData.byDriver ? d.driver.name : a.driver,
254
- duration: convertMS(a.idleTime, true),
255
- fixTime: getIdleEventDate(a, userData.user),
256
- name: userData.byDriver ? a.deviceName : d.device.name,
257
- address: a.position.address + (a.geofenceName ? ' - ' + a.geofenceName : '')
258
- }
259
- }))
250
+ if (timezone === 'Asia/Qatar') {
251
+ doc.addFileToVFS('Amiri-Regular.ttf', AmiriRegular())
252
+ doc.addFont('Amiri-Regular.ttf', 'Amiri', 'normal')
253
+ }
254
+
255
+ const style = getStyle(getUserPartner(userData.user))
256
+ doc.autoTable({
257
+ head: [headers],
258
+ body: data,
259
+ foot: [footValues],
260
+ showFoot: 'lastPage',
261
+ headStyles: {
262
+ fillColor: style.pdfHeaderColor,
263
+ textColor: style.pdfHeaderTextColor,
264
+ fontSize: 10
265
+ },
266
+ bodyStyles: {
267
+ fillColor: style.pdfBodyColor,
268
+ textColor: style.pdfBodyTextColor,
269
+ fontSize: 8,
270
+ font: timezone === 'Asia/Qatar' ? 'Amiri' : ''
271
+
272
+ },
273
+ footStyles: {
274
+ fillColor: style.pdfFooterColor,
275
+ textColor: style.pdfFooterTextColor,
276
+ fontSize: 9
277
+ },
278
+ startY: space + 35
260
279
  })
261
- console.log(data)
280
+ } catch (e) {
281
+ console.error(e, 'moving on...')
282
+ }
283
+ })
284
+
285
+ return doc
286
+ }
287
+ }
288
+
289
+ function exportIdleReportToExcel (userData, reportData) {
290
+ console.log('Export to Excel')
291
+
292
+ const translations = getTranslations(userData)
293
+
294
+ const idleData = userData.byDriver ? reportData.drivers : reportData.devices
295
+
296
+ const settings = {
297
+ sheetName: translations.report.titleIdleReport.substring(0, 31), // The name of the sheet
298
+ fileName // The name of the spreadsheet
299
+ }
300
+ const headers = [
301
+ userData.byDriver ? { label: translations.report.driver, value: 'driver' } : { label: translations.report.vehicle, value: 'name' },
302
+ { label: translations.report.date, value: 'fixTime' },
303
+ { label: translations.report.address, value: 'address' },
304
+ { label: translations.report.duration, value: 'duration' },
305
+ userData.byDriver ? { label: translations.report.vehicle, value: 'name' } : { label: translations.report.driver, value: 'driver' }
306
+ ]
307
+
308
+ let data = []
309
+ if (idleData) {
310
+ idleData.forEach(d => {
311
+ data = data.concat([{}])
312
+ data = data.concat(d.idleEvents.map(a => {
262
313
  return {
263
- headers,
264
- data,
265
- settings
314
+ driver: userData.byDriver ? d.driver.name : a.driver,
315
+ duration: convertMS(a.idleTime, true),
316
+ fixTime: getIdleEventDate(a, userData.user),
317
+ name: userData.byDriver ? a.deviceName : d.device.name,
318
+ address: a.position.address + (a.geofenceName ? ' - ' + a.geofenceName : '')
266
319
  }
320
+ }))
321
+ })
322
+ console.log(data)
323
+ return {
324
+ headers,
325
+ data,
326
+ settings
267
327
  }
328
+ }
268
329
  }
269
330
 
270
- function deviceName(device){
271
- return device.name + (device.attributes.license_plate ? ', ' +device.attributes.license_plate : '') + (device.model ? ', ' + device.model : '')
331
+ function deviceName (device) {
332
+ return device.name + (device.attributes.license_plate ? ', ' + device.attributes.license_plate : '') + (device.model ? ', ' + device.model : '')
272
333
  }
273
334
 
274
- function getIdleEventDate(row, user) {
275
- return convertToLocaleString(row.position.fixTime, user.attributes.lang, user.attributes.timezone)
335
+ function getIdleEventDate (row, user) {
336
+ return convertToLocaleString(row.position.fixTime, user.attributes.lang, user.attributes.timezone)
276
337
  }
277
338
 
278
- exports.createIdleReport = createIdleReport;
279
- exports.exportIdleReportToPDF = exportIdleReportToPDF;
280
- exports.exportIdleReportToExcel = exportIdleReportToExcel;
339
+ exports.createIdleReport = createIdleReport
340
+ exports.exportIdleReportToPDF = exportIdleReportToPDF
341
+ exports.exportIdleReportToExcel = exportIdleReportToExcel
package/src/index.test.js CHANGED
@@ -197,6 +197,21 @@ describe('Test_Reports', function () {
197
197
  assert.equal(totalIdleTime, 1294000) // Total Duration
198
198
  }, 20000)
199
199
  // eslint-disable-next-line no-undef
200
+ it('Idle by driver', async () => {
201
+ const report = await getReports()
202
+ const userData = await report.getUserData()
203
+ userData.minimumIdleMinutes = 2
204
+ userData.byDriver = true
205
+ const data = await report.idleReport(new Date(2022, 0, 3, 0, 0, 0, 0),
206
+ new Date(2022, 0, 7, 23, 59, 59, 0),
207
+ userData)
208
+ assert.equal(data.length, 1)
209
+ const driver = data[0].drivers.find(d => d.driver.id === 14020)
210
+ const totalIdleTime = driver.idleEvents.reduce((a, b) => a + b.idleTime, 0)
211
+ assert.equal(driver.idleEvents.length, 3) // Total Alerts
212
+ assert.equal(totalIdleTime, 184400000) // Total Duration
213
+ }, 20000)
214
+ // eslint-disable-next-line no-undef
200
215
  it('Activity by device', async () => {
201
216
  const report = await getReports()
202
217
  const userData = await report.getUserData()