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 +1 -1
- package/src/kms-report.js +79 -31
- package/src/partnerReports/performance-report.js +1 -1
- package/src/rpm-report.js +8 -6
- package/src/tests/rpm.test.js +18 -7
package/package.json
CHANGED
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() &&
|
|
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.
|
|
57
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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 ((
|
|
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 =
|
|
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
|
-
|
|
44
|
-
|
|
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++) {
|
package/src/tests/rpm.test.js
CHANGED
|
@@ -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
|
|
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('
|
|
28
|
-
const result = processDevice({ id: 1, attributes: { highRPM: 2650 } }, route, dates)
|
|
29
|
-
assert.equal(result.threshold,
|
|
30
|
-
assert.equal(result.days[0].time,
|
|
31
|
-
|
|
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
|
})
|