fleetmap-reports 2.0.386 → 2.0.388

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.386",
3
+ "version": "2.0.388",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/kms-report.js CHANGED
@@ -11,16 +11,18 @@ const { getDates, getTranslations, isClientSide, executeServerSide } = require('
11
11
  const driversUtil = require('./util/driver')
12
12
  const {
13
13
  isInsideTimetable, isPartialInsideTimetable, calculateTrip, getDataByDay, getKms,
14
- getTripTotalKms, calculateMultiDayTrips
14
+ getTripTotalKms, calculateMultiDayTrips, getUncompletedTrip
15
15
  } = require('./util/trips')
16
16
  const { devicesToProcess } = require('./util/device')
17
17
  const { getDriverData, reportByDriver } = require('./util/driver')
18
18
  const automaticReports = require('./automaticReports')
19
- const { getSummaryByDay } = require('./util/traccar')
20
19
  const { getStop } = require('./trip-report')
21
20
 
22
21
  const fileName = 'KmsReport'
23
22
 
23
+ // dates from getDates are at noon: 12h back/forward to the day boundary plus a 24h buffer
24
+ const CHUNK_BOUNDARY_BUFFER_MS = 36 * 60 * 60 * 1000
25
+
24
26
  async function createKmsReport (from, to, userData, traccarInstance) {
25
27
  console.log('Create KmsReport', from, to, userData.byDriver ? 'by driver' : (userData.byGroup ? 'by group' : 'by device'))
26
28
  const reportData = []
@@ -47,52 +49,31 @@ async function createKmsReportByDevice (from, to, userData, traccarInstance) {
47
49
  to
48
50
  }
49
51
 
50
- if (isClientSide() && (allDevices.length > 50 || ((new Date(to).getTime() - new Date(from).getTime()) > (1000 * 60 * 60 * 24 * 15))) && userData.groupByDay) {
52
+ if (isClientSide() && allDevices.length > 20) {
51
53
  const sliced = automaticReports.sliceArray(allDevices, 1)
52
54
  await executeServerSide('kms-report', sliced, from, to, userData, allDevices.length, traccarInstance).then(devices => {
53
55
  allData.devices = devices
54
56
  })
55
57
  } else {
56
- if (userData.allWeek && userData.groupByDay) {
57
- const dates = getDates(from, to, userData.user.attributes.timezone || getUserPartner(userData.user).timezone)
58
- const summary = await getSummaryByDay(dates, userData, traccarInstance, allDevices, false, from, to)
59
- for (const device of allDevices) {
60
- const days = []
61
- dates.forEach(date => {
62
- const summaryByDay = summary.find(a => a.date === date).summary
63
- const summaryByDevice = summaryByDay.find(a => a.deviceId === device.id)
64
- if (summaryByDevice) {
65
- days.push({
66
- date,
67
- kms: summaryByDevice.distance,
68
- startOdometer: summaryByDevice.startOdometer,
69
- endOdometer: summaryByDevice.endOdometer
70
- })
71
- }
72
- })
73
- const daysWithOdometer = days.filter(a => a.startOdometer > 0)
74
- allData.devices.push({
75
- device,
76
- startOdometer: daysWithOdometer.length > 0 ? daysWithOdometer[0].startOdometer : 0,
77
- endOdometer: daysWithOdometer.length > 0 ? daysWithOdometer[daysWithOdometer.length - 1].endOdometer : 0,
78
- days
79
- })
80
- }
58
+ if (userData.groupByDay) {
59
+ allData.devices = await createKmsReportByDeviceByDay(from, to, allDevices, userData, traccarInstance)
81
60
  } else {
82
61
  let deviceCount = 0
83
62
  const sliced = automaticReports.sliceArray(allDevices,
84
63
  20)
85
64
  for (const devices of sliced) {
86
- const needRoute = userData.groupByDay || !userData.allWeek
65
+ // without groupByDay the distance is the sum of getTripTotalKms, which needs route and stops
87
66
  const {
88
67
  trips,
89
68
  route,
69
+ stops,
90
70
  summary
91
- } = await traccar.getAllInOne(traccarInstance, from, to, devices, needRoute, true, false, !userData.groupByDay, deviceCount, allDevices.length, 10, 1)
71
+ } = await traccar.getAllInOne(traccarInstance, from, to, devices, true, true, !userData.groupByDay, !userData.groupByDay, deviceCount, allDevices.length, 10, 1)
92
72
 
93
73
  allData.devices = allData.devices.concat(await processDevices(from, to, devices, {
94
74
  trips,
95
75
  route,
76
+ stops,
96
77
  summary
97
78
  }, userData, traccarInstance))
98
79
 
@@ -104,6 +85,63 @@ async function createKmsReportByDevice (from, to, userData, traccarInstance) {
104
85
  return allData
105
86
  }
106
87
 
88
+ // Same distance as the trip report (sum of getTripTotalKms), split by day.
89
+ // The period is fetched in blocks of 7 days and 20 devices so long periods and large fleets
90
+ // aren't loaded all at once (same approach as the fuel consumption report).
91
+ async function createKmsReportByDeviceByDay (from, to, allDevices, userData, traccarInstance) {
92
+ const dates = getDates(from, to, userData.user.attributes.timezone || getUserPartner(userData.user).timezone)
93
+ const dateGroups = automaticReports.sliceArray(dates, 7)
94
+ const slicedDevices = automaticReports.sliceArray(allDevices, 20)
95
+ const totalDevices = allDevices.length * dateGroups.length
96
+ const results = new Map(allDevices.map(d => [d.id, { device: d, startOdometer: 0, endOdometer: 0, days: [], hasTrips: false }]))
97
+
98
+ let deviceCount = 0
99
+ for (const [index, chunkDates] of dateGroups.entries()) {
100
+ // internal boundaries are widened so a trip crossing them is fetched whole and then clipped by getDataByDay
101
+ const chunkFrom = index === 0 ? from : new Date(chunkDates[0].getTime() - CHUNK_BOUNDARY_BUFFER_MS)
102
+ const chunkTo = index === dateGroups.length - 1 ? to : new Date(chunkDates[chunkDates.length - 1].getTime() + CHUNK_BOUNDARY_BUFFER_MS)
103
+
104
+ for (const devices of slicedDevices) {
105
+ const data = await traccar.getAllInOne(traccarInstance, chunkFrom, chunkTo, devices, true, true, true, false, deviceCount, totalDevices, 10, 1)
106
+
107
+ for (const d of devices) {
108
+ const result = results.get(d.id)
109
+ const route = data.route.filter(p => p.deviceId === d.id)
110
+ const stops = data.stops.filter(s => s.deviceId === d.id)
111
+ const deviceTrips = data.trips.filter(t => t.deviceId === d.id)
112
+ const uncompletedTrip = getUncompletedTrip(d, route, deviceTrips)
113
+ if (uncompletedTrip) {
114
+ deviceTrips.push(uncompletedTrip)
115
+ }
116
+ const trips = deviceTrips.filter(t => userData.allWeek || !userData.weekDays || isInsideTimetable(t, userData) || isPartialInsideTimetable(d, t, userData, route))
117
+
118
+ for (const date of chunkDates) {
119
+ const { tripsByDay, stopsByDay } = await getDataByDay(d, date, { trips, stops, route }, userData, traccarInstance)
120
+ if (tripsByDay.length) {
121
+ if (!result.hasTrips) {
122
+ result.startOdometer = tripsByDay[0].startOdometer
123
+ }
124
+ result.endOdometer = tripsByDay[tripsByDay.length - 1].endOdometer
125
+ result.hasTrips = true
126
+ }
127
+ result.days.push({
128
+ date,
129
+ kms: tripsByDay.reduce((a, b) =>
130
+ a + getTripTotalKms(b, getStop(new Date(b.endTime), stopsByDay), route), 0
131
+ ) * 1000
132
+ })
133
+ }
134
+ }
135
+ deviceCount += devices.length
136
+ }
137
+ }
138
+
139
+ // with a timetable, devices without trips were already left out of the report
140
+ return [...results.values()]
141
+ .filter(r => userData.allWeek || r.hasTrips)
142
+ .map(({ hasTrips, ...r }) => r)
143
+ }
144
+
107
145
  async function createKmsReportByDriver (from, to, userData, traccarInstance) {
108
146
  const report = { drivers: [] }
109
147
 
@@ -257,7 +295,17 @@ async function processDevices (from, to, devices, data, userData, traccarInstanc
257
295
  })
258
296
  }
259
297
  } else {
260
- const summary = data.summary.find(s => s.deviceId === d.id)
298
+ let summary = data.summary.find(s => s.deviceId === d.id)
299
+ if (trips.length > 0) {
300
+ // same calculation as the trip report and the kms report by driver, so all of them give the same distance
301
+ const deviceStops = (data.stops || []).filter(s => s.deviceId === d.id)
302
+ summary = {
303
+ ...summary,
304
+ distance: trips.reduce((a, b) =>
305
+ a + getTripTotalKms(b, getStop(new Date(b.endTime), deviceStops), deviceRoute), 0
306
+ ) * 1000
307
+ }
308
+ }
261
309
  devicesResult.push({
262
310
  device: d,
263
311
  summary
@@ -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
  })