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
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.356",
3
+ "version": "2.0.358",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -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: []
@@ -67,28 +100,37 @@ async function create (from, to, userData, traccar) {
67
100
  await processDevice(driversData, device, userData, { route, trips }, deviceAlarmEvents, deviceOverspeedEvents)
68
101
  deviceCount++
69
102
  }
103
+
104
+ console.log('LOADING_MESSAGE:' + slice[0].name)
105
+ console.log(`PROGRESS_PERC:${deviceCount / devices.length * 100}`)
70
106
  }
71
107
 
72
108
  const allDriversData = (Array.from(driversData.values())).filter(d => d.distance > 0)
73
109
  allDriversData.forEach(d => {
74
- const distanceScore = (Math.abs(d.distance - userData.distanceTarget) / userData.distanceTarget) * distanceCoefficient
75
- const rpmScore = ((d.highEngineRPM / 60000) / userData.rpmTarget) * rpmCoefficient
76
- const hardBrakingScore = (d.hardBraking / userData.hardBrakingTarget) * hardBrakingCoefficient
77
- const hardAccelerationScore = (d.hardAcceleration / userData.hardAccelerationTarget) * hardAccelerationCoefficient
78
- const hardCorneringScore = (d.hardCornering / userData.hardCorneringTarget) * hardCorneringCoefficient
79
- const overspeedScore = (d.overspeed / userData.overspeedTarget) * overspeedCoefficient
80
- const consumptionScore = (getCanAvgConsumption(d.distance, d.spentFuel).byKms / userData.fuelConsumptionTarget) * consumptionCoefficient
81
- const geofenceAlarmScore = (d.geofenceAlarm / userData.geofenceTarget) * geofenceCoefficient
82
- const continuesDrivingAlarmScore = (d.continuesDrivingAlarm / userData.continuesDrivingTarget) * continuesDrivingCoefficient
83
- const reverseAlarmScore = (d.reverseAlarm / userData.reverseTarget) * reverseCoefficient
84
- const otherAlarmScore = (d.otherAlarm / userData.otherTarget) * otherCoefficient
85
-
86
110
  d.avgFuelConsumption = getCanAvgConsumption(d.distance, d.spentFuel).byKms
87
- d.score = distanceScore + rpmScore + hardBrakingScore + hardAccelerationScore +
88
- hardCorneringScore + consumptionScore + overspeedScore + geofenceAlarmScore +
89
- continuesDrivingAlarmScore + reverseAlarmScore + otherAlarmScore
111
+
112
+ d.safetyScore = weightedAverage([
113
+ { value: goodnessScore(d.hardBraking, userData.hardBrakingTarget), weight: hardBrakingCoefficient },
114
+ { value: goodnessScore(d.hardAcceleration, userData.hardAccelerationTarget), weight: hardAccelerationCoefficient },
115
+ { value: goodnessScore(d.hardCornering, userData.hardCorneringTarget), weight: hardCorneringCoefficient },
116
+ { value: goodnessScore(d.overspeed, userData.overspeedTarget), weight: overspeedCoefficient },
117
+ { value: goodnessScore(d.reverseAlarm, userData.reverseTarget), weight: reverseCoefficient }
118
+ ])
119
+ d.efficiencyScore = weightedAverage([
120
+ { value: deviationScore(d.distance, userData.distanceTarget), weight: distanceCoefficient },
121
+ { value: goodnessScore(d.avgFuelConsumption, userData.fuelConsumptionTarget), weight: consumptionCoefficient },
122
+ { value: goodnessScore(d.highEngineRPM / 60000, userData.rpmTarget), weight: rpmCoefficient }
123
+ ])
124
+ d.complianceScore = weightedAverage([
125
+ { value: goodnessScore(d.geofenceAlarm, userData.geofenceTarget), weight: geofenceCoefficient },
126
+ { value: goodnessScore(d.continuesDrivingAlarm, userData.continuesDrivingTarget), weight: continuesDrivingCoefficient },
127
+ { value: goodnessScore(d.otherAlarm, userData.otherTarget), weight: otherCoefficient }
128
+ ])
129
+ d.overallScore = (d.safetyScore + d.efficiencyScore + d.complianceScore) / 3
130
+ d.grade = toGrade(d.overallScore)
90
131
  })
91
- allDriversData.sort((a, b) => (a.score > b.score) ? 1 : -1)
132
+ allDriversData.sort((a, b) => b.overallScore - a.overallScore)
133
+ allDriversData.forEach((d, index) => { d.rank = index + 1 })
92
134
 
93
135
  reportData.drivers.push(...allDriversData)
94
136
 
@@ -204,7 +246,13 @@ async function exportToExcel (userData, reportData) {
204
246
  }
205
247
 
206
248
  const headers = [
249
+ { label: translations.report.rank, value: 'rank' },
207
250
  { label: translations.report.driver, value: 'name' },
251
+ { label: translations.report.grade, value: 'grade' },
252
+ { label: translations.report.score, value: 'overallScore' },
253
+ { label: translations.report.safetyScore, value: 'safetyScore' },
254
+ { label: translations.report.efficiencyScore, value: 'efficiencyScore' },
255
+ { label: translations.report.complianceScore, value: 'complianceScore' },
208
256
  { label: translations.report.distance, value: 'distance' },
209
257
  { label: translations.report.avgFuelConsumption, value: 'avgFuelConsumption' },
210
258
  { label: translations.report.highEngineRPM, value: 'highEngineRPM' },
@@ -215,16 +263,21 @@ async function exportToExcel (userData, reportData) {
215
263
  { label: translations.report.geofenceAlarm, value: 'geofenceAlarm' },
216
264
  { label: translations.report.continuesDrivingAlarm, value: 'continuesDrivingAlarm' },
217
265
  { label: translations.report.reverseAlarm, value: 'reverseAlarm' },
218
- { label: translations.report.otherAlarm, value: 'otherAlarm' },
219
- { label: translations.report.score, value: 'score' }
266
+ { label: translations.report.otherAlarm, value: 'otherAlarm' }
220
267
  ]
221
268
 
222
269
  let data = []
223
270
  if (reportDataRows) {
224
271
  reportDataRows.forEach(row => {
225
272
  data = data.concat({
273
+ rank: row.rank,
226
274
  name: row.name,
227
- distance: Number(row.distance.toFixed(1)),
275
+ grade: row.grade,
276
+ overallScore: Math.round(row.overallScore),
277
+ safetyScore: Math.round(row.safetyScore),
278
+ efficiencyScore: Math.round(row.efficiencyScore),
279
+ complianceScore: Math.round(row.complianceScore),
280
+ distance: Number((row.distance / 1000).toFixed(1)),
228
281
  avgFuelConsumption: Number(row.avgFuelConsumption.toFixed(2)),
229
282
  highEngineRPM: convertMS(row.highEngineRPM, false),
230
283
  hardBraking: row.hardBraking,
@@ -234,11 +287,9 @@ async function exportToExcel (userData, reportData) {
234
287
  geofenceAlarm: row.geofenceAlarm,
235
288
  continuesDrivingAlarm: row.continuesDrivingAlarm,
236
289
  reverseAlarm: row.reverseAlarm,
237
- otherAlarm: row.otherAlarm,
238
- score: Number(row.score.toFixed(2))
290
+ otherAlarm: row.otherAlarm
239
291
  })
240
292
  })
241
- console.log(data)
242
293
  return {
243
294
  headers,
244
295
  data,
@@ -247,15 +298,33 @@ async function exportToExcel (userData, reportData) {
247
298
  }
248
299
  }
249
300
 
301
+ const gradeColors = {
302
+ A: { fill: [200, 236, 204], text: [30, 110, 40] },
303
+ B: { fill: [223, 240, 196], text: [66, 110, 30] },
304
+ C: { fill: [255, 236, 179], text: [153, 101, 0] },
305
+ D: { fill: [255, 205, 178], text: [166, 74, 0] },
306
+ F: { fill: [255, 205, 210], text: [153, 0, 20] }
307
+ }
308
+
250
309
  async function exportToPDF (userData, reportData) {
251
- console.log('exportDelayedStartReportToPDF')
310
+ console.log('exportDriverRankingReportToPDF')
252
311
 
253
312
  const timezone = userData.user.attributes.timezone
254
313
  const translations = getTranslations(userData)
255
314
  const lang = userData.user.attributes.lang || (isClientSide() && navigator.language)
256
315
  const reportDataRows = reportData.drivers
257
316
 
258
- const headers = [
317
+ const scoreboardHeaders = [
318
+ translations.report.rank,
319
+ translations.report.driver,
320
+ translations.report.grade,
321
+ translations.report.score,
322
+ translations.report.safetyScore,
323
+ translations.report.efficiencyScore,
324
+ translations.report.complianceScore
325
+ ]
326
+
327
+ const detailHeaders = [
259
328
  translations.report.driver,
260
329
  translations.report.distance,
261
330
  translations.report.avgFuelConsumption,
@@ -267,8 +336,7 @@ async function exportToPDF (userData, reportData) {
267
336
  translations.report.geofenceAlarm,
268
337
  translations.report.continuesDrivingAlarm,
269
338
  translations.report.reverseAlarm,
270
- translations.report.otherAlarm,
271
- translations.report.score
339
+ translations.report.otherAlarm
272
340
  ]
273
341
 
274
342
  if (reportDataRows) {
@@ -279,30 +347,66 @@ async function exportToPDF (userData, reportData) {
279
347
  doc.setFontSize(13)
280
348
  doc.text(convertToLocaleString(reportData.from, lang, timezone, userData.user) + ' - ' + convertToLocaleString(reportData.to, lang, timezone, userData.user), 20, 25)
281
349
 
282
- const data = []
283
- reportDataRows.forEach(function (row) {
284
- const temp = [
285
- row.name,
286
- row.distance.toFixed(1),
287
- row.avgFuelConsumption.toFixed(2),
288
- convertMS(row.highEngineRPM, false),
289
- row.hardBraking,
290
- row.hardAcceleration,
291
- row.hardCornering,
292
- row.overspeed,
293
- row.geofenceAlarm,
294
- row.continuesDrivingAlarm,
295
- row.reverseAlarm,
296
- row.otherAlarm,
297
- row.score.toFixed(2)
298
- ]
299
- data.push(temp)
300
- })
301
- const footValues = []
350
+ const scoreboardData = reportDataRows.map(row => [
351
+ row.rank,
352
+ row.name,
353
+ row.grade,
354
+ Math.round(row.overallScore),
355
+ Math.round(row.safetyScore),
356
+ Math.round(row.efficiencyScore),
357
+ Math.round(row.complianceScore)
358
+ ])
302
359
 
303
360
  const style = getStyle(getUserPartner(userData.user))
304
361
 
305
- addTable(doc, headers, data, footValues, style, 40, {
362
+ addTable(doc, scoreboardHeaders, scoreboardData, [], style, 40, {
363
+ 0: { halign: 'center' },
364
+ 2: { halign: 'center' },
365
+ 3: { halign: 'right' },
366
+ 4: { halign: 'right' },
367
+ 5: { halign: 'right' },
368
+ 6: { halign: 'right' }
369
+ }, (cellData) => {
370
+ if (cellData.section !== 'body') {
371
+ return
372
+ }
373
+ const row = reportDataRows[cellData.row.index]
374
+ if (!row) {
375
+ return
376
+ }
377
+ if (cellData.column.index === 0) {
378
+ cellData.cell.styles.fontStyle = row.rank <= 3 ? 'bold' : 'normal'
379
+ }
380
+ if (cellData.column.index === 2 || cellData.column.index === 3) {
381
+ const colors = gradeColors[row.grade]
382
+ cellData.cell.styles.fillColor = colors.fill
383
+ cellData.cell.styles.textColor = colors.text
384
+ cellData.cell.styles.fontStyle = 'bold'
385
+ }
386
+ })
387
+
388
+ const detailData = reportDataRows.map(row => [
389
+ row.name,
390
+ (row.distance / 1000).toFixed(1),
391
+ row.avgFuelConsumption.toFixed(2),
392
+ convertMS(row.highEngineRPM, false),
393
+ row.hardBraking,
394
+ row.hardAcceleration,
395
+ row.hardCornering,
396
+ row.overspeed,
397
+ row.geofenceAlarm,
398
+ row.continuesDrivingAlarm,
399
+ row.reverseAlarm,
400
+ row.otherAlarm
401
+ ])
402
+
403
+ const detailStartY = doc.lastAutoTable.finalY + 12
404
+ doc.setFontSize(10)
405
+ doc.setFont('helvetica', 'bold')
406
+ doc.text(translations.report.detailedMetrics, 15, detailStartY - 4)
407
+ doc.setFont('helvetica', 'normal')
408
+
409
+ addTable(doc, detailHeaders, detailData, [], { ...style, bodyFontSize: 7, headerFontSize: 8 }, detailStartY, {
306
410
  1: { halign: 'right' },
307
411
  2: { halign: 'right' },
308
412
  3: { halign: 'right' },
@@ -313,8 +417,7 @@ async function exportToPDF (userData, reportData) {
313
417
  8: { halign: 'right' },
314
418
  9: { halign: 'right' },
315
419
  10: { halign: 'right' },
316
- 11: { halign: 'right' },
317
- 12: { halign: 'right' }
420
+ 11: { halign: 'right' }
318
421
  })
319
422
  return doc
320
423
  }
@@ -132,7 +132,17 @@ async function getEvents (traccarInstance, from, to, devices, userData, deviceCo
132
132
  if (processServerSide() && !userData.useVehicleSpeedLimit) {
133
133
  const cookie = await axios.get('/pinmeapi/cookie/get', { withCredentials: true }).then(d => d.data)
134
134
  const url = `https://${getServerHost()}/pinmeapi/reports/speeding-report/getEvents`
135
- return axios.post(url, { from, to, devices, userData, deviceCount, totalDevices, sliceSize, cookie }, { withCredentials: true }).then(d => d.data)
135
+ const maxAttempts = 3
136
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
137
+ try {
138
+ return await axios.post(url, { from, to, devices, userData, deviceCount, totalDevices, sliceSize, cookie }, { withCredentials: true, timeout: 60000 }).then(d => d.data)
139
+ } catch (e) {
140
+ console.log(`getEvents failed for devices [${devices.map(d => d.name).join(', ')}] (attempt ${attempt}/${maxAttempts}): ${e.message}`)
141
+ if (attempt === maxAttempts) {
142
+ return []
143
+ }
144
+ }
145
+ }
136
146
  } else {
137
147
  const geofencesFeatures = userData.geofences.filter(g => g.area && g.area.startsWith('POLYGON') &&
138
148
  g.attributes.speedLimit).map(g => convertToFeature(g))
@@ -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
  })