fleetmap-reports 2.0.366 → 2.0.367

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.366",
3
+ "version": "2.0.367",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -35,65 +35,85 @@ async function createFuelConsumptionReport (from, to, userData, traccar) {
35
35
  return reportData
36
36
  }
37
37
 
38
- const allInOne = await traccarHelper.getAllInOne(traccar, from, to, devices, true, true, true, false)
39
- const dates = getDates(from, to, userData.user.attributes.timezone || getUserPartner(userData.user).timezone)
40
- const summary = await getSummaryByDay(dates, userData, traccar, devices, true, from, to)
38
+ const daysByDevice = new Map()
39
+
40
+ // Process the period in chunks of whole calendar days (computed once up front, then grouped) so
41
+ // we never hold more than ~a week of raw GPS positions for the whole device set in memory at
42
+ // once - a single busy device can have tens of thousands of positions over a month. Chunking the
43
+ // already-computed dates list, rather than re-deriving dates per raw millisecond sub-range, avoids
44
+ // any risk of a boundary day being counted twice (or dropped) due to getDates' timezone rounding.
45
+ const allDates = getDates(from, to, userData.user.attributes.timezone || getUserPartner(userData.user).timezone)
46
+ const dateGroups = automaticReports.sliceArray(allDates, 7)
47
+
48
+ for (let chunkIndex = 0; chunkIndex < dateGroups.length; chunkIndex++) {
49
+ const dates = dateGroups[chunkIndex]
50
+ const chunkFrom = chunkIndex === 0 ? from : dates[0]
51
+ const chunkTo = chunkIndex === dateGroups.length - 1 ? to : dateGroups[chunkIndex + 1][0]
52
+
53
+ const allInOne = await traccarHelper.getAllInOne(traccar, chunkFrom, chunkTo, devices, true, true, true, false)
54
+ const summary = await getSummaryByDay(dates, userData, traccar, devices, true, chunkFrom, chunkTo)
55
+
56
+ const fuelServicesData = []
57
+ if (devices.some(d => d.attributes.odooId)) {
58
+ fuelServicesData.push(...(await odoo.getOdooFuelServices(traccar, chunkFrom, chunkTo)))
59
+ }
41
60
 
42
- const fuelServicesData = []
43
- if (devices.some(d => d.attributes.odooId)) {
44
- fuelServicesData.push(...(await odoo.getOdooFuelServices(traccar, from, to)))
45
- }
61
+ if (allInOne.trips.length > 0) {
62
+ for (const d of devices) {
63
+ const stops = allInOne.stops.filter(t => t.deviceId === d.id)
64
+ const trips = allInOne.trips.filter(t => t.deviceId === d.id)
65
+ const route = allInOne.route.filter(r => r.deviceId === d.id)
66
+ const deviceFuelServices = fuelServicesData.filter(f => Number.parseInt(d.attributes.odooId) === f.vehicle_id[0])
67
+
68
+ if (trips.length > 0) {
69
+ const refuelingPositions = await refuelingReport.calculateRefuelingPositions(userData, d, { route, trips, fuelServices: deviceFuelServices })
70
+ const days = daysByDevice.get(d.id) || []
71
+ for (const date of dates) {
72
+ const { tripsByDay, stopsByDay, routeByDay, refuelingPositionsByDay } = await getDataByDay(d, date, { trips, stops, route, refuelingPositions }, userData, traccar)
73
+ const summaryByDay = summary.find(a => a.date === date).summary
74
+ const summaryByDevice = summaryByDay.find(a => a.deviceId === d.id)
75
+
76
+ const firstStopIndex = !stopsByDay.length || !tripsByDay.length ? 0 : (new Date(tripsByDay[0].startTime) < new Date(stopsByDay[0].startTime) ? 0 : 1)
77
+ const distance = summaryByDevice.distance
78
+
79
+ const hasFuelSensor = 'fuel_low_threshold' in d.attributes &&
80
+ 'fuel_high_threshold' in d.attributes &&
81
+ d.attributes.fuel_tank_capacity
82
+
83
+ const spentFuel = hasFuelSensor ?
84
+ tripsByDay.length > 0 ? calculateTotalFuelFromSensor(tripsByDay, stopsByDay, route, d, firstStopIndex) : 0
85
+ : calculateConsumption(d, { trips: tripsByDay, stops: stopsByDay, route: routeByDay, summary: summaryByDevice })
86
+
87
+ const dataRow = {
88
+ date,
89
+ distance,
90
+ spentFuel,
91
+ avgConsumption: getCanAvgConsumption(distance, spentFuel),
92
+ startOdometer: summaryByDevice.startOdometer,
93
+ endOdometer: summaryByDevice.endOdometer,
94
+ duration: tripsByDay.reduce((a, b) => a + b.duration, 0),
95
+ refueling: (refuelingPositionsByDay && refuelingPositionsByDay.length > 0) ? refuelingPositionsByDay.reduce((a, b) => a + b.diff, 0) : 0
96
+ }
97
+
98
+ days.push(dataRow)
99
+ }
100
+ daysByDevice.set(d.id, days)
101
+ }
102
+ }
103
+ }
46
104
 
47
- if (allInOne.trips.length === 0) {
48
- return reportData
105
+ console.log('LOADING_MESSAGE:' + (devices[0] && devices[0].name))
106
+ console.log(`PROGRESS_PERC:${(chunkIndex + 1) / dateGroups.length * 100}`)
49
107
  }
50
108
 
51
109
  for (const d of devices) {
52
- const stops = allInOne.stops.filter(t => t.deviceId === d.id)
53
- const trips = allInOne.trips.filter(t => t.deviceId === d.id)
54
- const route = allInOne.route.filter(r => r.deviceId === d.id)
55
- const deviceFuelServices = fuelServicesData.filter(f => Number.parseInt(d.attributes.odooId) === f.vehicle_id[0])
56
-
57
- if (trips.length > 0) {
58
- const refuelingPositions = await refuelingReport.calculateRefuelingPositions(userData, d, { route, trips, fuelServices: deviceFuelServices })
59
- const days = []
60
- for (const date of dates) {
61
- const { tripsByDay, stopsByDay, routeByDay, refuelingPositionsByDay } = await getDataByDay(d, date, { trips, stops, route, refuelingPositions }, userData, traccar)
62
- const summaryByDay = summary.find(a => a.date === date).summary
63
- const summaryByDevice = summaryByDay.find(a => a.deviceId === d.id)
64
-
65
- const firstStopIndex = !stopsByDay.length || !tripsByDay.length ? 0 : (new Date(tripsByDay[0].startTime) < new Date(stopsByDay[0].startTime) ? 0 : 1)
66
- const distance = summaryByDevice.distance
67
-
68
- const hasFuelSensor = 'fuel_low_threshold' in d.attributes &&
69
- 'fuel_high_threshold' in d.attributes &&
70
- d.attributes.fuel_tank_capacity
71
-
72
- const spentFuel = hasFuelSensor ?
73
- tripsByDay.length > 0 ? calculateTotalFuelFromSensor(tripsByDay, stopsByDay, route, d, firstStopIndex) : 0
74
- : calculateConsumption(d, { trips: tripsByDay, stops: stopsByDay, route: routeByDay, summary: summaryByDevice })
75
-
76
- const dataRow = {
77
- date,
78
- distance,
79
- spentFuel,
80
- avgConsumption: getCanAvgConsumption(distance, spentFuel),
81
- startOdometer: summaryByDevice.startOdometer,
82
- endOdometer: summaryByDevice.endOdometer,
83
- duration: tripsByDay.reduce((a, b) => a + b.duration, 0),
84
- refueling: (refuelingPositionsByDay && refuelingPositionsByDay.length > 0) ? refuelingPositionsByDay.reduce((a, b) => a + b.diff, 0) : 0
85
- }
86
-
87
- days.push(dataRow)
88
- }
89
-
90
- if (days.length > 0) {
91
- allData.devices.push({
92
- device: d,
93
- days
94
- })
95
- allData.totalDevices = allData.totalDevices + 1
96
- }
110
+ const days = daysByDevice.get(d.id)
111
+ if (days && days.length > 0) {
112
+ allData.devices.push({
113
+ device: d,
114
+ days
115
+ })
116
+ allData.totalDevices = allData.totalDevices + 1
97
117
  }
98
118
  }
99
119
 
@@ -3,7 +3,7 @@ const traccarHelper = require('../util/traccar')
3
3
  const { getDriverData, devicesByDriver } = require('../util/driver')
4
4
  const { getCanDriverStyleMessages, parseCanDriverStyleMessage } = require('../util/xpert')
5
5
  const { calculateRPMSections } = require('./performance-report')
6
- const { getTranslations, isClientSide, convertToLocaleString, convertMS, getServerHost } = require('../util/utils')
6
+ const { getTranslations, isClientSide, convertToLocaleString, convertMS, getServerHost, splitDateRange } = require('../util/utils')
7
7
  const jsPDF = require('jspdf')
8
8
  const { headerFromUser, addTable } = require('../util/pdfDocument')
9
9
  const { getStyle } = require('../reportStyle')
@@ -58,20 +58,6 @@ function toGrade (score) {
58
58
  return grades.find(g => score >= g.min).grade
59
59
  }
60
60
 
61
- // Splits [from, to] into consecutive sub-ranges of at most chunkDays each
62
- function splitDateRange (from, to, chunkDays) {
63
- const chunkMs = chunkDays * 24 * 60 * 60 * 1000
64
- const end = new Date(to).getTime()
65
- const chunks = []
66
- let chunkStart = new Date(from).getTime()
67
- while (chunkStart < end) {
68
- const chunkEnd = Math.min(chunkStart + chunkMs, end)
69
- chunks.push({ from: new Date(chunkStart), to: new Date(chunkEnd) })
70
- chunkStart = chunkEnd
71
- }
72
- return chunks.length ? chunks : [{ from: new Date(from), to: new Date(to) }]
73
- }
74
-
75
61
  async function create (from, to, userData, traccar) {
76
62
  const reportData = {
77
63
  drivers: []
package/src/util/utils.js CHANGED
@@ -110,6 +110,25 @@ function convertToFeature (geofence) {
110
110
  }
111
111
  }
112
112
 
113
+ // Splits [from, to] into consecutive sub-ranges of at most chunkDays each, so callers can process a
114
+ // long period in bounded-size pieces instead of holding the whole thing (e.g. a month of GPS
115
+ // positions) in memory at once.
116
+ function splitDateRange (from, to, chunkDays) {
117
+ const chunkMs = chunkDays * 24 * 60 * 60 * 1000
118
+ const end = new Date(to).getTime()
119
+ const chunks = []
120
+ let chunkStart = new Date(from).getTime()
121
+ while (chunkStart < end) {
122
+ const chunkEnd = Math.min(chunkStart + chunkMs, end)
123
+ const isLastChunk = chunkEnd >= end
124
+ // Pull the boundary back by 1ms on every chunk but the last, so callers that round to whole
125
+ // calendar days (e.g. getDates) don't count the boundary day in both adjacent chunks.
126
+ chunks.push({ from: new Date(chunkStart), to: new Date(isLastChunk ? chunkEnd : chunkEnd - 1) })
127
+ chunkStart = chunkEnd
128
+ }
129
+ return chunks.length ? chunks : [{ from: new Date(from), to: new Date(to) }]
130
+ }
131
+
113
132
  function getDates (startDate, endDate, timezone) {
114
133
  const dates = []
115
134
  let currentDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 12, 0, 0, 0)
@@ -307,6 +326,7 @@ exports.getImgFromUrl = getImgFromUrl
307
326
  exports.convertMS = convertMS
308
327
  exports.coordsDistance = coordsDistance
309
328
  exports.getDates = getDates
329
+ exports.splitDateRange = splitDateRange
310
330
  exports.convertToLocaleString = convertToLocaleString
311
331
  exports.convertToLocaleDateString = convertToLocaleDateString
312
332
  exports.convertToLocaleTimeString = convertToLocaleTimeString