fleetmap-reports 2.0.357 → 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.
package/lang/enGB.js CHANGED
@@ -193,7 +193,25 @@ module.exports = {
193
193
  titleIdleReport: 'Idle Report',
194
194
  titleMachinesReport: 'Machines Report',
195
195
  titleStopReport: 'Stop Report',
196
+ titleDriverRankingReport: 'Driver Ranking Report',
196
197
  titleSensorReport: 'Sensor Report',
198
+ rank: 'Rank',
199
+ grade: 'Grade',
200
+ safetyScore: 'Safety',
201
+ efficiencyScore: 'Efficiency',
202
+ complianceScore: 'Compliance',
203
+ score: 'Overall Score',
204
+ avgFuelConsumption: 'Avg. L/100km',
205
+ highEngineRPM: 'High RPM',
206
+ hardBraking: 'Hard Braking',
207
+ hardAcceleration: 'Hard Acceleration',
208
+ hardCornering: 'Hard Cornering',
209
+ overspeedAlarm: 'Speeding',
210
+ geofenceAlarm: 'Seatbelt',
211
+ continuesDrivingAlarm: 'Continuous Driving',
212
+ reverseAlarm: 'Reverse Gear',
213
+ otherAlarm: 'Other',
214
+ detailedMetrics: 'Detailed metrics',
197
215
  from: 'From',
198
216
  to: 'to',
199
217
  headerStartAddress: 'Start address',
package/lang/esCL.js CHANGED
@@ -183,7 +183,25 @@ module.exports = {
183
183
  titleIdleReport: 'Informe de Ralenti',
184
184
  titleMachinesReport: 'Informe de Maquinas',
185
185
  titleStopReport: 'Informe de Paradas',
186
+ titleDriverRankingReport: 'Informe de Ranking de Conductores',
186
187
  titleSensorReport: 'Informe de Sensores',
188
+ rank: 'Posición',
189
+ grade: 'Calificación',
190
+ safetyScore: 'Seguridad',
191
+ efficiencyScore: 'Eficiencia',
192
+ complianceScore: 'Cumplimiento',
193
+ score: 'Puntuación General',
194
+ avgFuelConsumption: 'Consumo Prom. L/100km',
195
+ highEngineRPM: 'RPM Alto',
196
+ hardBraking: 'Frenado Brusco',
197
+ hardAcceleration: 'Aceleración Brusca',
198
+ hardCornering: 'Giro Brusco',
199
+ overspeedAlarm: 'Exceso de Velocidad',
200
+ geofenceAlarm: 'Cinturón de Seguridad',
201
+ continuesDrivingAlarm: 'Conducción Continua',
202
+ reverseAlarm: 'Marcha Atrás',
203
+ otherAlarm: 'Otros',
204
+ detailedMetrics: 'Métricas detalladas',
187
205
  from: 'De',
188
206
  to: 'a',
189
207
  headerStartAddress: 'Lugar de início',
package/lang/frFR.json CHANGED
@@ -297,7 +297,13 @@
297
297
  "geofenceAlarm": "Ceinture",
298
298
  "continuesDrivingAlarm": "Cond. Continue",
299
299
  "reverseAlarm": "Marche Arriere",
300
- "otherAlarm": "Other"
300
+ "otherAlarm": "Other",
301
+ "rank": "Rang",
302
+ "grade": "Note",
303
+ "safetyScore": "Sécurité",
304
+ "efficiencyScore": "Efficacité",
305
+ "complianceScore": "Conformité",
306
+ "detailedMetrics": "Métriques détaillées"
301
307
  },
302
308
  "layout": {
303
309
  "deviceOnline": "Appareil en ligne",
package/lang/ptBR.js CHANGED
@@ -185,7 +185,25 @@ module.exports = {
185
185
  titleIdleReport: 'Relatórios de Ralenti',
186
186
  titleMachinesReport: 'Relatório de Máquinas',
187
187
  titleStopReport: 'Relatório de Paragens',
188
+ titleDriverRankingReport: 'Relatório de Ranking de Motoristas',
188
189
  titleSensorReport: 'Relatório de Sensores',
190
+ rank: 'Posição',
191
+ grade: 'Nota',
192
+ safetyScore: 'Segurança',
193
+ efficiencyScore: 'Eficiência',
194
+ complianceScore: 'Conformidade',
195
+ score: 'Pontuação Geral',
196
+ avgFuelConsumption: 'Consumo Médio L/100km',
197
+ highEngineRPM: 'RPM Alta',
198
+ hardBraking: 'Frenagem Brusca',
199
+ hardAcceleration: 'Aceleração Brusca',
200
+ hardCornering: 'Curva Brusca',
201
+ overspeedAlarm: 'Excesso de Velocidade',
202
+ geofenceAlarm: 'Cinto de Segurança',
203
+ continuesDrivingAlarm: 'Condução Contínua',
204
+ reverseAlarm: 'Marcha à Ré',
205
+ otherAlarm: 'Outros',
206
+ detailedMetrics: 'Métricas detalhadas',
189
207
  from: 'De',
190
208
  to: 'a',
191
209
  headerStartAddress: 'Local de início',
package/lang/ptPT.js CHANGED
@@ -191,7 +191,25 @@ module.exports = {
191
191
  titleIdleReport: 'Relatórios de Ralenti',
192
192
  titleMachinesReport: 'Relatório de Máquinas',
193
193
  titleStopReport: 'Relatório de Paragens',
194
+ titleDriverRankingReport: 'Relatório de Ranking de Condutores',
194
195
  titleSensorReport: 'Relatório de Sensores',
196
+ rank: 'Posição',
197
+ grade: 'Nota',
198
+ safetyScore: 'Segurança',
199
+ efficiencyScore: 'Eficiência',
200
+ complianceScore: 'Conformidade',
201
+ score: 'Pontuação Geral',
202
+ avgFuelConsumption: 'Consumo Médio L/100km',
203
+ highEngineRPM: 'RPM Alto',
204
+ hardBraking: 'Travagem Brusca',
205
+ hardAcceleration: 'Aceleração Brusca',
206
+ hardCornering: 'Curva Brusca',
207
+ overspeedAlarm: 'Excesso de Velocidade',
208
+ geofenceAlarm: 'Cinto de Segurança',
209
+ continuesDrivingAlarm: 'Condução Contínua',
210
+ reverseAlarm: 'Marcha-atrás',
211
+ otherAlarm: 'Outros',
212
+ detailedMetrics: 'Métricas detalhadas',
195
213
  from: 'De',
196
214
  to: 'a',
197
215
  headerStartAddress: 'Local de início',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetmap-reports",
3
- "version": "2.0.357",
3
+ "version": "2.0.359",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -2,7 +2,7 @@ const traccarHelper = require('../util/traccar')
2
2
  const { getDriverData, devicesByDriver } = require('../util/driver')
3
3
  const { getCanDriverStyleMessages, parseCanDriverStyleMessage } = require('../util/xpert')
4
4
  const { calculateRPMSections } = require('./performance-report')
5
- const { getTranslations, isClientSide, convertToLocaleString, convertMS } = require('../util/utils')
5
+ const { getTranslations, isClientSide, convertToLocaleString, convertMS, executeServerSide } = require('../util/utils')
6
6
  const jsPDF = require('jspdf')
7
7
  const { headerFromUser, addTable } = require('../util/pdfDocument')
8
8
  const { getStyle } = require('../reportStyle')
@@ -24,6 +24,39 @@ const continuesDrivingCoefficient = 0.1
24
24
  const reverseCoefficient = 0.1
25
25
  const otherCoefficient = 0.1
26
26
 
27
+ const grades = [
28
+ { min: 90, grade: 'A' },
29
+ { min: 75, grade: 'B' },
30
+ { min: 60, grade: 'C' },
31
+ { min: 40, grade: 'D' },
32
+ { min: -Infinity, grade: 'F' }
33
+ ]
34
+
35
+ // Converts a "the lower the better" metric into a 0-100 score (100 = met target, 0 = twice the target or worse)
36
+ function goodnessScore (actual, target) {
37
+ if (!target || target <= 0) {
38
+ return 100
39
+ }
40
+ return Math.max(0, Math.min(100, 100 * (1 - (actual / target))))
41
+ }
42
+
43
+ // Same as goodnessScore, but for metrics where deviating from the target in either direction is bad (e.g. distance)
44
+ function deviationScore (actual, target) {
45
+ if (!target || target <= 0) {
46
+ return 100
47
+ }
48
+ return Math.max(0, Math.min(100, 100 * (1 - (Math.abs(actual - target) / target))))
49
+ }
50
+
51
+ function weightedAverage (metrics) {
52
+ const totalWeight = metrics.reduce((sum, m) => sum + m.weight, 0)
53
+ return metrics.reduce((sum, m) => sum + (m.value * m.weight), 0) / totalWeight
54
+ }
55
+
56
+ function toGrade (score) {
57
+ return grades.find(g => score >= g.min).grade
58
+ }
59
+
27
60
  async function create (from, to, userData, traccar) {
28
61
  const reportData = {
29
62
  drivers: []
@@ -32,9 +65,28 @@ async function create (from, to, userData, traccar) {
32
65
  userData.geofences = []
33
66
  userData.roadSpeedLimits = true
34
67
 
35
- const { devices, drivers } = await devicesByDriver(traccar, from, to, userData.drivers, userData.devices)
36
- userData.drivers = drivers
68
+ if (!userData.requestToRunServerSide) {
69
+ const { devices, drivers } = await devicesByDriver(traccar, from, to, userData.drivers, userData.devices)
70
+ userData.devices = devices
71
+ userData.drivers = drivers
72
+ }
37
73
 
74
+ if (isClientSide() && (userData.drivers.length > 50 || ((new Date(to).getTime() - new Date(from).getTime()) > (1000 * 60 * 60 * 24 * 15)))) {
75
+ const sliced = automaticReports.sliceArray(userData.drivers, 5)
76
+ userData.requestToRunServerSide = true
77
+ userData.byDriver = true
78
+ const allDriversData = await executeServerSide('driverranking-report', sliced, from, to, userData, userData.drivers.length, traccar, 5)
79
+
80
+ allDriversData.sort((a, b) => b.overallScore - a.overallScore)
81
+ allDriversData.forEach((d, index) => { d.rank = index + 1 })
82
+
83
+ reportData.drivers = allDriversData
84
+ reportData.target = buildTargets(userData)
85
+ reportData.coefficient = buildCoefficients()
86
+ return reportData
87
+ }
88
+
89
+ const devices = userData.devices
38
90
  let deviceCount = 0
39
91
  const driversData = new Map()
40
92
  const overspeedEvents = await getOverspeedEvents(from, to, userData, devices, userData, traccar)
@@ -74,28 +126,40 @@ async function create (from, to, userData, traccar) {
74
126
 
75
127
  const allDriversData = (Array.from(driversData.values())).filter(d => d.distance > 0)
76
128
  allDriversData.forEach(d => {
77
- const distanceScore = (Math.abs(d.distance - userData.distanceTarget) / userData.distanceTarget) * distanceCoefficient
78
- const rpmScore = ((d.highEngineRPM / 60000) / userData.rpmTarget) * rpmCoefficient
79
- const hardBrakingScore = (d.hardBraking / userData.hardBrakingTarget) * hardBrakingCoefficient
80
- const hardAccelerationScore = (d.hardAcceleration / userData.hardAccelerationTarget) * hardAccelerationCoefficient
81
- const hardCorneringScore = (d.hardCornering / userData.hardCorneringTarget) * hardCorneringCoefficient
82
- const overspeedScore = (d.overspeed / userData.overspeedTarget) * overspeedCoefficient
83
- const consumptionScore = (getCanAvgConsumption(d.distance, d.spentFuel).byKms / userData.fuelConsumptionTarget) * consumptionCoefficient
84
- const geofenceAlarmScore = (d.geofenceAlarm / userData.geofenceTarget) * geofenceCoefficient
85
- const continuesDrivingAlarmScore = (d.continuesDrivingAlarm / userData.continuesDrivingTarget) * continuesDrivingCoefficient
86
- const reverseAlarmScore = (d.reverseAlarm / userData.reverseTarget) * reverseCoefficient
87
- const otherAlarmScore = (d.otherAlarm / userData.otherTarget) * otherCoefficient
88
-
89
129
  d.avgFuelConsumption = getCanAvgConsumption(d.distance, d.spentFuel).byKms
90
- d.score = distanceScore + rpmScore + hardBrakingScore + hardAccelerationScore +
91
- hardCorneringScore + consumptionScore + overspeedScore + geofenceAlarmScore +
92
- continuesDrivingAlarmScore + reverseAlarmScore + otherAlarmScore
130
+
131
+ d.safetyScore = weightedAverage([
132
+ { value: goodnessScore(d.hardBraking, userData.hardBrakingTarget), weight: hardBrakingCoefficient },
133
+ { value: goodnessScore(d.hardAcceleration, userData.hardAccelerationTarget), weight: hardAccelerationCoefficient },
134
+ { value: goodnessScore(d.hardCornering, userData.hardCorneringTarget), weight: hardCorneringCoefficient },
135
+ { value: goodnessScore(d.overspeed, userData.overspeedTarget), weight: overspeedCoefficient },
136
+ { value: goodnessScore(d.reverseAlarm, userData.reverseTarget), weight: reverseCoefficient }
137
+ ])
138
+ d.efficiencyScore = weightedAverage([
139
+ { value: deviationScore(d.distance, userData.distanceTarget), weight: distanceCoefficient },
140
+ { value: goodnessScore(d.avgFuelConsumption, userData.fuelConsumptionTarget), weight: consumptionCoefficient },
141
+ { value: goodnessScore(d.highEngineRPM / 60000, userData.rpmTarget), weight: rpmCoefficient }
142
+ ])
143
+ d.complianceScore = weightedAverage([
144
+ { value: goodnessScore(d.geofenceAlarm, userData.geofenceTarget), weight: geofenceCoefficient },
145
+ { value: goodnessScore(d.continuesDrivingAlarm, userData.continuesDrivingTarget), weight: continuesDrivingCoefficient },
146
+ { value: goodnessScore(d.otherAlarm, userData.otherTarget), weight: otherCoefficient }
147
+ ])
148
+ d.overallScore = (d.safetyScore + d.efficiencyScore + d.complianceScore) / 3
149
+ d.grade = toGrade(d.overallScore)
93
150
  })
94
- allDriversData.sort((a, b) => (a.score > b.score) ? 1 : -1)
151
+ allDriversData.sort((a, b) => b.overallScore - a.overallScore)
152
+ allDriversData.forEach((d, index) => { d.rank = index + 1 })
95
153
 
96
154
  reportData.drivers.push(...allDriversData)
155
+ reportData.target = buildTargets(userData)
156
+ reportData.coefficient = buildCoefficients()
97
157
 
98
- reportData.target = {
158
+ return reportData
159
+ }
160
+
161
+ function buildTargets (userData) {
162
+ return {
99
163
  distanceTarget: userData.distanceTarget,
100
164
  rpmTarget: userData.rpmTarget,
101
165
  hardBrakingTarget: userData.hardBrakingTarget,
@@ -108,8 +172,10 @@ async function create (from, to, userData, traccar) {
108
172
  reverseTarget: userData.reverseTarget,
109
173
  otherTarget: userData.otherTarget
110
174
  }
175
+ }
111
176
 
112
- reportData.coefficient = {
177
+ function buildCoefficients () {
178
+ return {
113
179
  distanceCoefficient,
114
180
  rpmCoefficient,
115
181
  hardBrakingCoefficient,
@@ -122,8 +188,6 @@ async function create (from, to, userData, traccar) {
122
188
  reverseCoefficient,
123
189
  otherCoefficient
124
190
  }
125
-
126
- return reportData
127
191
  }
128
192
 
129
193
  async function processDevice (driversData, device, userData, allInOne, alarmEvents, overspeedEvents) {
@@ -207,7 +271,13 @@ async function exportToExcel (userData, reportData) {
207
271
  }
208
272
 
209
273
  const headers = [
274
+ { label: translations.report.rank, value: 'rank' },
210
275
  { label: translations.report.driver, value: 'name' },
276
+ { label: translations.report.grade, value: 'grade' },
277
+ { label: translations.report.score, value: 'overallScore' },
278
+ { label: translations.report.safetyScore, value: 'safetyScore' },
279
+ { label: translations.report.efficiencyScore, value: 'efficiencyScore' },
280
+ { label: translations.report.complianceScore, value: 'complianceScore' },
211
281
  { label: translations.report.distance, value: 'distance' },
212
282
  { label: translations.report.avgFuelConsumption, value: 'avgFuelConsumption' },
213
283
  { label: translations.report.highEngineRPM, value: 'highEngineRPM' },
@@ -218,16 +288,21 @@ async function exportToExcel (userData, reportData) {
218
288
  { label: translations.report.geofenceAlarm, value: 'geofenceAlarm' },
219
289
  { label: translations.report.continuesDrivingAlarm, value: 'continuesDrivingAlarm' },
220
290
  { label: translations.report.reverseAlarm, value: 'reverseAlarm' },
221
- { label: translations.report.otherAlarm, value: 'otherAlarm' },
222
- { label: translations.report.score, value: 'score' }
291
+ { label: translations.report.otherAlarm, value: 'otherAlarm' }
223
292
  ]
224
293
 
225
294
  let data = []
226
295
  if (reportDataRows) {
227
296
  reportDataRows.forEach(row => {
228
297
  data = data.concat({
298
+ rank: row.rank,
229
299
  name: row.name,
230
- distance: Number(row.distance.toFixed(1)),
300
+ grade: row.grade,
301
+ overallScore: Math.round(row.overallScore),
302
+ safetyScore: Math.round(row.safetyScore),
303
+ efficiencyScore: Math.round(row.efficiencyScore),
304
+ complianceScore: Math.round(row.complianceScore),
305
+ distance: Number((row.distance / 1000).toFixed(1)),
231
306
  avgFuelConsumption: Number(row.avgFuelConsumption.toFixed(2)),
232
307
  highEngineRPM: convertMS(row.highEngineRPM, false),
233
308
  hardBraking: row.hardBraking,
@@ -237,11 +312,9 @@ async function exportToExcel (userData, reportData) {
237
312
  geofenceAlarm: row.geofenceAlarm,
238
313
  continuesDrivingAlarm: row.continuesDrivingAlarm,
239
314
  reverseAlarm: row.reverseAlarm,
240
- otherAlarm: row.otherAlarm,
241
- score: Number(row.score.toFixed(2))
315
+ otherAlarm: row.otherAlarm
242
316
  })
243
317
  })
244
- console.log(data)
245
318
  return {
246
319
  headers,
247
320
  data,
@@ -250,15 +323,33 @@ async function exportToExcel (userData, reportData) {
250
323
  }
251
324
  }
252
325
 
326
+ const gradeColors = {
327
+ A: { fill: [200, 236, 204], text: [30, 110, 40] },
328
+ B: { fill: [223, 240, 196], text: [66, 110, 30] },
329
+ C: { fill: [255, 236, 179], text: [153, 101, 0] },
330
+ D: { fill: [255, 205, 178], text: [166, 74, 0] },
331
+ F: { fill: [255, 205, 210], text: [153, 0, 20] }
332
+ }
333
+
253
334
  async function exportToPDF (userData, reportData) {
254
- console.log('exportDelayedStartReportToPDF')
335
+ console.log('exportDriverRankingReportToPDF')
255
336
 
256
337
  const timezone = userData.user.attributes.timezone
257
338
  const translations = getTranslations(userData)
258
339
  const lang = userData.user.attributes.lang || (isClientSide() && navigator.language)
259
340
  const reportDataRows = reportData.drivers
260
341
 
261
- const headers = [
342
+ const scoreboardHeaders = [
343
+ translations.report.rank,
344
+ translations.report.driver,
345
+ translations.report.grade,
346
+ translations.report.score,
347
+ translations.report.safetyScore,
348
+ translations.report.efficiencyScore,
349
+ translations.report.complianceScore
350
+ ]
351
+
352
+ const detailHeaders = [
262
353
  translations.report.driver,
263
354
  translations.report.distance,
264
355
  translations.report.avgFuelConsumption,
@@ -270,8 +361,7 @@ async function exportToPDF (userData, reportData) {
270
361
  translations.report.geofenceAlarm,
271
362
  translations.report.continuesDrivingAlarm,
272
363
  translations.report.reverseAlarm,
273
- translations.report.otherAlarm,
274
- translations.report.score
364
+ translations.report.otherAlarm
275
365
  ]
276
366
 
277
367
  if (reportDataRows) {
@@ -282,30 +372,66 @@ async function exportToPDF (userData, reportData) {
282
372
  doc.setFontSize(13)
283
373
  doc.text(convertToLocaleString(reportData.from, lang, timezone, userData.user) + ' - ' + convertToLocaleString(reportData.to, lang, timezone, userData.user), 20, 25)
284
374
 
285
- const data = []
286
- reportDataRows.forEach(function (row) {
287
- const temp = [
288
- row.name,
289
- row.distance.toFixed(1),
290
- row.avgFuelConsumption.toFixed(2),
291
- convertMS(row.highEngineRPM, false),
292
- row.hardBraking,
293
- row.hardAcceleration,
294
- row.hardCornering,
295
- row.overspeed,
296
- row.geofenceAlarm,
297
- row.continuesDrivingAlarm,
298
- row.reverseAlarm,
299
- row.otherAlarm,
300
- row.score.toFixed(2)
301
- ]
302
- data.push(temp)
303
- })
304
- const footValues = []
375
+ const scoreboardData = reportDataRows.map(row => [
376
+ row.rank,
377
+ row.name,
378
+ row.grade,
379
+ Math.round(row.overallScore),
380
+ Math.round(row.safetyScore),
381
+ Math.round(row.efficiencyScore),
382
+ Math.round(row.complianceScore)
383
+ ])
305
384
 
306
385
  const style = getStyle(getUserPartner(userData.user))
307
386
 
308
- addTable(doc, headers, data, footValues, style, 40, {
387
+ addTable(doc, scoreboardHeaders, scoreboardData, [], style, 40, {
388
+ 0: { halign: 'center' },
389
+ 2: { halign: 'center' },
390
+ 3: { halign: 'right' },
391
+ 4: { halign: 'right' },
392
+ 5: { halign: 'right' },
393
+ 6: { halign: 'right' }
394
+ }, (cellData) => {
395
+ if (cellData.section !== 'body') {
396
+ return
397
+ }
398
+ const row = reportDataRows[cellData.row.index]
399
+ if (!row) {
400
+ return
401
+ }
402
+ if (cellData.column.index === 0) {
403
+ cellData.cell.styles.fontStyle = row.rank <= 3 ? 'bold' : 'normal'
404
+ }
405
+ if (cellData.column.index === 2 || cellData.column.index === 3) {
406
+ const colors = gradeColors[row.grade]
407
+ cellData.cell.styles.fillColor = colors.fill
408
+ cellData.cell.styles.textColor = colors.text
409
+ cellData.cell.styles.fontStyle = 'bold'
410
+ }
411
+ })
412
+
413
+ const detailData = reportDataRows.map(row => [
414
+ row.name,
415
+ (row.distance / 1000).toFixed(1),
416
+ row.avgFuelConsumption.toFixed(2),
417
+ convertMS(row.highEngineRPM, false),
418
+ row.hardBraking,
419
+ row.hardAcceleration,
420
+ row.hardCornering,
421
+ row.overspeed,
422
+ row.geofenceAlarm,
423
+ row.continuesDrivingAlarm,
424
+ row.reverseAlarm,
425
+ row.otherAlarm
426
+ ])
427
+
428
+ const detailStartY = doc.lastAutoTable.finalY + 12
429
+ doc.setFontSize(10)
430
+ doc.setFont('helvetica', 'bold')
431
+ doc.text(translations.report.detailedMetrics, 15, detailStartY - 4)
432
+ doc.setFont('helvetica', 'normal')
433
+
434
+ addTable(doc, detailHeaders, detailData, [], { ...style, bodyFontSize: 7, headerFontSize: 8 }, detailStartY, {
309
435
  1: { halign: 'right' },
310
436
  2: { halign: 'right' },
311
437
  3: { halign: 'right' },
@@ -316,13 +442,19 @@ async function exportToPDF (userData, reportData) {
316
442
  8: { halign: 'right' },
317
443
  9: { halign: 'right' },
318
444
  10: { halign: 'right' },
319
- 11: { halign: 'right' },
320
- 12: { halign: 'right' }
445
+ 11: { halign: 'right' }
321
446
  })
322
447
  return doc
323
448
  }
324
449
  }
325
450
 
451
+ // Entry point for the generic server-side report dispatcher (see executeServerSide / pinme-backend-v2's getReport),
452
+ // which requires a "create" + PascalCase(report name) export returning an array.
453
+ async function createDriverrankingReport (from, to, userData, traccar) {
454
+ return [await create(from, to, userData, traccar)]
455
+ }
456
+
326
457
  exports.create = create
458
+ exports.createDriverrankingReport = createDriverrankingReport
327
459
  exports.exportToPDF = exportToPDF
328
460
  exports.exportToExcel = exportToExcel
@@ -61,7 +61,7 @@ async function headerFromUser (doc, title, user, orientation = 'l') {
61
61
  return header(doc, title, partner && partner.host, getStyle(partner), orientation)
62
62
  }
63
63
 
64
- function addTable (doc, headers, data, footValues, style, startY, columnStyles = {}) {
64
+ function addTable (doc, headers, data, footValues, style, startY, columnStyles = {}, didParseCell) {
65
65
  doc.autoTable({
66
66
  head: [headers],
67
67
  body: data,
@@ -97,6 +97,7 @@ function addTable (doc, headers, data, footValues, style, startY, columnStyles =
97
97
  })
98
98
  })
99
99
  },
100
+ ...(didParseCell ? { didParseCell } : {}),
100
101
  columnStyles,
101
102
  startY
102
103
  })