fleetmap-reports 2.0.385 → 2.0.387

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetmap-reports",
3
- "version": "2.0.385",
3
+ "version": "2.0.387",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -207,7 +207,8 @@ async function processDevices (from, to, devices, data, userData, traccarInstanc
207
207
  endTime: tripsByDay.length && tripsByDay[tripsByDay.length - 1].endTime,
208
208
  endAddress: tripsByDay.length && tripsByDay[tripsByDay.length - 1].endAddress,
209
209
  startTimeIsOut: tripsByDay.some(t => t.startTimeIsOut),
210
- endTimeIsOut: tripsByDay.some(t => t.endTimeIsOut)
210
+ endTimeIsOut: tripsByDay.some(t => t.endTimeIsOut),
211
+ activity: getDayActivity(tripsByDay, fromDay, toDay)
211
212
  })
212
213
  }
213
214
  } else {
@@ -232,6 +233,7 @@ async function processDevices (from, to, devices, data, userData, traccarInstanc
232
233
  summaryCurrentDay.endOdometer = summaryByDevice.endOdometer
233
234
  summaryCurrentDay.startTimeIsOut = tripsByDay.some(t => t.startTimeIsOut)
234
235
  summaryCurrentDay.endTimeIsOut = tripsByDay.some(t => t.endTimeIsOut)
236
+ summaryCurrentDay.activity = getDayActivity(tripsByDay, fromDay, toDay)
235
237
  summary.push(summaryCurrentDay)
236
238
  }
237
239
  }
@@ -285,6 +287,17 @@ async function processDevices (from, to, devices, data, userData, traccarInstanc
285
287
  return devicesResult
286
288
  }
287
289
 
290
+ // Trips of one day as [{ start, end }] in hours since the start of that day (0-24),
291
+ // used to draw the daily activity timeline on screen and in the PDF.
292
+ function getDayActivity (tripsByDay, fromDay, toDay) {
293
+ const dayStart = new Date(fromDay).getTime()
294
+ const dayEnd = new Date(toDay).getTime()
295
+ const toHours = time => (Math.min(Math.max(new Date(time).getTime(), dayStart), dayEnd) - dayStart) / 3600000
296
+ return tripsByDay
297
+ .map(t => ({ start: toHours(t.startTime), end: toHours(t.endTime) }))
298
+ .filter(a => a.end > a.start)
299
+ }
300
+
288
301
  function processDrivers (from, to, drivers, driversData, userData) {
289
302
  const driversResult = []
290
303
  drivers.forEach(d => {
@@ -508,6 +521,63 @@ function exportActivityReportToPDFVehicleGroupByDay (doc, translations, reportDa
508
521
 
509
522
  const style = getStyle(getUserPartner(userData.user))
510
523
  addTable(doc, headers, data, footValues, style, 40)
524
+ addActivityTimeline(doc, translations, d.summary, weekDays)
525
+ })
526
+ }
527
+
528
+ // Draws one bar per day (00h-24h) below the table: grey when stopped, green while driving.
529
+ function addActivityTimeline (doc, translations, summary, weekDays) {
530
+ if (!summary.some(s => s.activity)) {
531
+ return
532
+ }
533
+ const pageWidth = doc.internal.pageSize.getWidth()
534
+ const pageHeight = doc.internal.pageSize.getHeight()
535
+ const labelX = 14
536
+ const left = 50
537
+ const width = pageWidth - 14 - left
538
+ const rowHeight = 4
539
+ const rowGap = 2
540
+ const bottom = pageHeight - 15
541
+
542
+ const drawAxis = (y) => {
543
+ doc.setFontSize(7)
544
+ doc.setTextColor(80, 80, 80)
545
+ for (let h = 0; h <= 24; h += 2) {
546
+ doc.text(String(h).padStart(2, '0') + 'h', left + h / 24 * width, y, { align: 'center' })
547
+ }
548
+ return y + 2
549
+ }
550
+
551
+ let y = doc.lastAutoTable.finalY + 10
552
+ if (y + 20 > bottom) {
553
+ doc.addPage()
554
+ y = 20
555
+ }
556
+ doc.setFillColor(103, 194, 58)
557
+ doc.rect(labelX, y - 2.5, 3, 3, 'F')
558
+ doc.setFontSize(8)
559
+ doc.setTextColor(0, 0, 0)
560
+ doc.text(translations.vehicleTable.moving_vehicles, labelX + 4, y)
561
+ doc.setFillColor(220, 220, 220)
562
+ doc.rect(labelX + 30, y - 2.5, 3, 3, 'F')
563
+ doc.text(translations.vehicleTable.stopped_vehicles, labelX + 34, y)
564
+ y = drawAxis(y + 6)
565
+
566
+ summary.forEach(s => {
567
+ if (y + rowHeight > bottom) {
568
+ doc.addPage()
569
+ y = drawAxis(20)
570
+ }
571
+ doc.setFontSize(7)
572
+ doc.setTextColor(0, 0, 0)
573
+ doc.text(new Date(s.date).toLocaleDateString() + ' - ' + weekDays[new Date(s.date).getDay()], labelX, y + rowHeight - 0.8)
574
+ doc.setFillColor(220, 220, 220)
575
+ doc.rect(left, y, width, rowHeight, 'F')
576
+ doc.setFillColor(103, 194, 58)
577
+ for (const a of (s.activity || [])) {
578
+ doc.rect(left + a.start / 24 * width, y, Math.max((a.end - a.start) / 24 * width, 0.3), rowHeight, 'F')
579
+ }
580
+ y += rowHeight + rowGap
511
581
  })
512
582
  }
513
583
 
@@ -209,7 +209,7 @@ function calculateRPMSections (route, min, max) {
209
209
  let newSection = true
210
210
  route.forEach(p => {
211
211
  const rpm = p.attributes.rpm
212
- if ((!min || rpm > min) && (!max || rpm < max)) {
212
+ if ((min === undefined || rpm > min) && (!max || rpm < max)) {
213
213
  if (newSection) {
214
214
  sections.push([p])
215
215
  } else {
package/src/rpm-report.js CHANGED
@@ -10,7 +10,7 @@ const automaticReports = require('./automaticReports')
10
10
  const { calculateRPMSections } = require('./partnerReports/performance-report')
11
11
 
12
12
  const fileName = 'RPMReport'
13
- const defaultThreshold = 2500
13
+ const defaultThreshold = 0
14
14
 
15
15
  async function createRPMReport (from, to, userData, traccarInstance) {
16
16
  console.log('Create RPMReport', from, to)
@@ -23,6 +23,7 @@ async function createRPMReport (from, to, userData, traccarInstance) {
23
23
  }
24
24
 
25
25
  const dates = getDates(from, to, userData.user.attributes.timezone || getUserPartner(userData.user).timezone)
26
+ const threshold = getThreshold(userData)
26
27
 
27
28
  let deviceCount = 0
28
29
  const sliced = automaticReports.sliceArray(allDevices, 20)
@@ -31,7 +32,7 @@ async function createRPMReport (from, to, userData, traccarInstance) {
31
32
  for (const d of devices) {
32
33
  const deviceRoute = route.filter(p => p.deviceId === d.id && p.attributes.rpm !== undefined)
33
34
  if (deviceRoute.length) {
34
- allData.devices.push(processDevice(d, deviceRoute, dates))
35
+ allData.devices.push(processDevice(d, deviceRoute, dates, threshold))
35
36
  }
36
37
  }
37
38
  deviceCount += devices.length
@@ -40,8 +41,10 @@ async function createRPMReport (from, to, userData, traccarInstance) {
40
41
  return [allData]
41
42
  }
42
43
 
43
- function getThreshold (device) {
44
- return Number(device.attributes.highRPM) || defaultThreshold
44
+ // the report's own threshold (userData.rpmThreshold), independent of the device highRPM attribute used by other reports
45
+ function getThreshold (userData) {
46
+ const threshold = Number(userData.rpmThreshold)
47
+ return userData.rpmThreshold !== undefined && userData.rpmThreshold !== '' && Number.isFinite(threshold) ? threshold : defaultThreshold
45
48
  }
46
49
 
47
50
  function dayKey (date) {
@@ -49,8 +52,7 @@ function dayKey (date) {
49
52
  }
50
53
 
51
54
  // time between consecutive positions that are both above the threshold, attributed to the day of the first one
52
- function processDevice (device, deviceRoute, dates) {
53
- const threshold = getThreshold(device)
55
+ function processDevice (device, deviceRoute, dates, threshold = defaultThreshold) {
54
56
  const days = dates.map(date => ({ date, time: 0 }))
55
57
  calculateRPMSections(deviceRoute, threshold).forEach(section => {
56
58
  for (let i = 1; i < section.length; i++) {
@@ -17,17 +17,28 @@ describe('RPM_Report', function () {
17
17
  position(new Date(2026, 9, 2, 9, 0), 2600),
18
18
  position(new Date(2026, 9, 2, 9, 0, 30), 2600)
19
19
  ]
20
- it('sums time above the default threshold per day', () => {
21
- const result = processDevice({ id: 1, attributes: {} }, route, dates)
20
+ it('sums time above the threshold per day', () => {
21
+ const result = processDevice({ id: 1, attributes: {} }, route, dates, 2500)
22
22
  assert.equal(result.threshold, 2500)
23
23
  assert.equal(result.days[0].time, 3 * 60 * 1000)
24
24
  assert.equal(result.days[1].time, 30 * 1000)
25
25
  assert.equal(result.time, 3 * 60 * 1000 + 30 * 1000)
26
26
  })
27
- it('uses the device highRPM attribute as threshold', () => {
28
- const result = processDevice({ id: 1, attributes: { highRPM: 2650 } }, route, dates)
29
- assert.equal(result.threshold, 2650)
30
- assert.equal(result.days[0].time, 2 * 60 * 1000)
31
- assert.equal(result.days[1].time, 0)
27
+ it('ignores the device highRPM attribute', () => {
28
+ const result = processDevice({ id: 1, attributes: { highRPM: 2650 } }, route, dates, 2500)
29
+ assert.equal(result.threshold, 2500)
30
+ assert.equal(result.days[0].time, 3 * 60 * 1000)
31
+ })
32
+ it('defaults to rpm > 0 (engine running time)', () => {
33
+ const idleRoute = [
34
+ position(new Date(2026, 9, 1, 8, 0), 0),
35
+ position(new Date(2026, 9, 1, 8, 10), 800),
36
+ position(new Date(2026, 9, 1, 8, 20), 2000),
37
+ position(new Date(2026, 9, 1, 8, 30), 0),
38
+ position(new Date(2026, 9, 1, 8, 40), 0)
39
+ ]
40
+ const result = processDevice({ id: 1, attributes: {} }, idleRoute, dates)
41
+ assert.equal(result.threshold, 0)
42
+ assert.equal(result.days[0].time, 10 * 60 * 1000)
32
43
  })
33
44
  })