fleetmap-reports 2.0.374 → 2.0.376
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/fuel-consumption-report.js +122 -17
package/package.json
CHANGED
|
@@ -3,14 +3,120 @@ const traccarHelper = require('./util/traccar')
|
|
|
3
3
|
const odoo = require('./util/odoo')
|
|
4
4
|
const automaticReports = require('./automaticReports')
|
|
5
5
|
const { devicesToProcess } = require('./util/device')
|
|
6
|
-
const { getTranslations, convertToLocaleString, getLanguage, convertMS, formatNumber,
|
|
6
|
+
const { getTranslations, convertToLocaleString, getLanguage, convertMS, formatNumber, isClientSide, executeServerSide } = require('./util/utils')
|
|
7
7
|
const { getStyle } = require('./reportStyle')
|
|
8
8
|
const { getUserPartner } = require('fleetmap-partners')
|
|
9
9
|
const { addTable, headerFromUser } = require('./util/pdfDocument')
|
|
10
10
|
const jsPDF = require('jspdf')
|
|
11
|
-
const { calculateConsumption, calculateTotalFuelFromSensor} = require('./util/fuel')
|
|
12
|
-
const {
|
|
13
|
-
const {
|
|
11
|
+
const { calculateConsumption, calculateTotalFuelFromSensor } = require('./util/fuel')
|
|
12
|
+
const { getSummary } = require('./util/traccar')
|
|
13
|
+
const { calculatePartialTrip } = require('./util/trips')
|
|
14
|
+
|
|
15
|
+
function getReportTimezone (userData) {
|
|
16
|
+
const tz = userData.user.attributes.timezone || getUserPartner(userData.user).timezone
|
|
17
|
+
try {
|
|
18
|
+
Intl.DateTimeFormat(undefined, { timeZone: tz })
|
|
19
|
+
return tz
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return undefined
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Wall-clock parts of `instant` as seen in `tz` (UTC parts when tz is unusable).
|
|
26
|
+
function zonedParts (instant, tz) {
|
|
27
|
+
const d = new Date(instant)
|
|
28
|
+
if (!tz) {
|
|
29
|
+
return { year: d.getUTCFullYear(), month: d.getUTCMonth(), day: d.getUTCDate(), hour: d.getUTCHours(), minute: d.getUTCMinutes(), second: d.getUTCSeconds() }
|
|
30
|
+
}
|
|
31
|
+
const p = {}
|
|
32
|
+
for (const part of new Intl.DateTimeFormat('en-US', {
|
|
33
|
+
timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
|
|
34
|
+
}).formatToParts(d)) {
|
|
35
|
+
if (part.type !== 'literal') p[part.type] = part.value
|
|
36
|
+
}
|
|
37
|
+
return { year: Number(p.year), month: Number(p.month) - 1, day: Number(p.day), hour: Number(p.hour) % 24, minute: Number(p.minute), second: Number(p.second) }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// UTC Date for a given wall-clock time in `tz`. Single correction step - exact
|
|
41
|
+
// except inside the ~1h DST fall-back overlap, and identical on client & server.
|
|
42
|
+
function zonedToUTC (year, month, day, hour, minute, second, tz) {
|
|
43
|
+
const guess = Date.UTC(year, month, day, hour || 0, minute || 0, second || 0)
|
|
44
|
+
if (!tz) return new Date(guess)
|
|
45
|
+
const seen = zonedParts(guess, tz)
|
|
46
|
+
const seenUTC = Date.UTC(seen.year, seen.month, seen.day, seen.hour, seen.minute, seen.second)
|
|
47
|
+
return new Date(guess - (seenUTC - guess))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// One noon-UTC marker per calendar day in [from, to], days counted in `tz`.
|
|
51
|
+
// Mirrors the server behaviour of getDates(from, to, tz).
|
|
52
|
+
function getReportDates (from, to, tz) {
|
|
53
|
+
const start = zonedParts(from, tz)
|
|
54
|
+
const end = zonedParts(to, tz)
|
|
55
|
+
const dates = []
|
|
56
|
+
let cursor = Date.UTC(start.year, start.month, start.day, 12, 0, 0, 0)
|
|
57
|
+
const last = Date.UTC(end.year, end.month, end.day, 12, 0, 0, 0)
|
|
58
|
+
while (cursor <= last) {
|
|
59
|
+
dates.push(new Date(cursor))
|
|
60
|
+
cursor += 24 * 60 * 60 * 1000
|
|
61
|
+
}
|
|
62
|
+
return dates
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseHMS (value) {
|
|
66
|
+
const [h, m, s] = String(value).split(':').map(Number)
|
|
67
|
+
return [h || 0, m || 0, s || 0]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Midnight-to-23:59:59 window for `date`'s calendar day, in `tz`. Mirrors the
|
|
71
|
+
// server branch of getDataByDay.
|
|
72
|
+
function getDayWindow (date, tz) {
|
|
73
|
+
const { year, month, day } = zonedParts(date, tz)
|
|
74
|
+
return [zonedToUTC(year, month, day, 0, 0, 0, tz), zonedToUTC(year, month, day, 23, 59, 59, tz)]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function getReportSummaryByDay (dates, userData, traccar, devices, tz, reportFrom, reportTo, firstDate, lastDate) {
|
|
78
|
+
const days = []
|
|
79
|
+
for (const date of dates) {
|
|
80
|
+
const { year, month, day } = zonedParts(date, tz)
|
|
81
|
+
|
|
82
|
+
let startDate = zonedToUTC(year, month, day, ...parseHMS(userData.dayHours.startTime), tz)
|
|
83
|
+
if (date.getTime() === firstDate.getTime()) {
|
|
84
|
+
const p = zonedParts(reportFrom, tz)
|
|
85
|
+
const clamp = zonedToUTC(year, month, day, p.hour, p.minute, p.second, tz)
|
|
86
|
+
if (clamp.getTime() > startDate.getTime()) startDate = clamp
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let endDate = zonedToUTC(year, month, day, ...parseHMS(userData.dayHours.endTime), tz)
|
|
90
|
+
if (date.getTime() === lastDate.getTime()) {
|
|
91
|
+
const p = zonedParts(reportTo, tz)
|
|
92
|
+
const clamp = zonedToUTC(year, month, day, p.hour, p.minute, p.second, tz)
|
|
93
|
+
if (clamp.getTime() < endDate.getTime()) endDate = clamp
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const summary = await getSummary(traccar, startDate, endDate, devices)
|
|
97
|
+
days.push({ date, summary })
|
|
98
|
+
}
|
|
99
|
+
return days
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Local, timezone-consistent equivalent of getDataByDay for this report.
|
|
103
|
+
async function getReportDataByDay (device, startDate, endDate, data) {
|
|
104
|
+
const tripsByDay = (data.trips || [])
|
|
105
|
+
.filter(t => new Date(t.endTime) > startDate && new Date(t.startTime) < endDate)
|
|
106
|
+
.map(t => ({ ...t }))
|
|
107
|
+
for (const t of tripsByDay) {
|
|
108
|
+
await calculatePartialTrip(device, startDate, endDate, data.route, t)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const stopsByDay = (data.stops || [])
|
|
112
|
+
.filter(t => new Date(t.endTime) > startDate && new Date(t.startTime) < endDate)
|
|
113
|
+
.map(t => ({ ...t }))
|
|
114
|
+
|
|
115
|
+
const routeByDay = data.route ? data.route.filter(p => new Date(p.fixTime) > startDate && new Date(p.fixTime) < endDate) : []
|
|
116
|
+
const refuelingPositionsByDay = data.refuelingPositions ? data.refuelingPositions.filter(p => new Date(p.date) > startDate && new Date(p.date) < endDate) : []
|
|
117
|
+
|
|
118
|
+
return { tripsByDay, stopsByDay, routeByDay, refuelingPositionsByDay }
|
|
119
|
+
}
|
|
14
120
|
|
|
15
121
|
async function createFuelConsumptionReport (from, to, userData, traccar) {
|
|
16
122
|
console.log('Create FuelConsumption Report')
|
|
@@ -37,21 +143,19 @@ async function createFuelConsumptionReport (from, to, userData, traccar) {
|
|
|
37
143
|
|
|
38
144
|
const daysByDevice = new Map()
|
|
39
145
|
|
|
40
|
-
|
|
41
|
-
|
|
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)
|
|
146
|
+
const timezone = getReportTimezone(userData)
|
|
147
|
+
const allDates = getReportDates(from, to, timezone)
|
|
46
148
|
const dateGroups = automaticReports.sliceArray(allDates, 7)
|
|
149
|
+
const firstDate = allDates[0]
|
|
150
|
+
const lastDate = allDates[allDates.length - 1]
|
|
47
151
|
|
|
48
152
|
for (let chunkIndex = 0; chunkIndex < dateGroups.length; chunkIndex++) {
|
|
49
153
|
const dates = dateGroups[chunkIndex]
|
|
50
|
-
const chunkFrom = chunkIndex === 0 ? from : dates[0]
|
|
51
|
-
const chunkTo = chunkIndex === dateGroups.length - 1 ? to :
|
|
154
|
+
const chunkFrom = chunkIndex === 0 ? from : getDayWindow(dates[0], timezone)[0]
|
|
155
|
+
const chunkTo = chunkIndex === dateGroups.length - 1 ? to : getDayWindow(dates[dates.length - 1], timezone)[1]
|
|
52
156
|
|
|
53
157
|
const allInOne = await traccarHelper.getAllInOne(traccar, chunkFrom, chunkTo, devices, true, true, true, false)
|
|
54
|
-
const summary = await
|
|
158
|
+
const summary = await getReportSummaryByDay(dates, userData, traccar, devices, timezone, from, to, firstDate, lastDate)
|
|
55
159
|
|
|
56
160
|
const fuelServicesData = []
|
|
57
161
|
if (devices.some(d => d.attributes.odooId)) {
|
|
@@ -69,7 +173,8 @@ async function createFuelConsumptionReport (from, to, userData, traccar) {
|
|
|
69
173
|
const refuelingPositions = await refuelingReport.calculateRefuelingPositions(userData, d, { route, trips, fuelServices: deviceFuelServices })
|
|
70
174
|
const days = daysByDevice.get(d.id) || []
|
|
71
175
|
for (const date of dates) {
|
|
72
|
-
const
|
|
176
|
+
const [dayStart, dayEnd] = getDayWindow(date, timezone)
|
|
177
|
+
const { tripsByDay, stopsByDay, routeByDay, refuelingPositionsByDay } = await getReportDataByDay(d, dayStart, dayEnd, { trips, stops, route, refuelingPositions })
|
|
73
178
|
const summaryByDay = summary.find(a => a.date === date).summary
|
|
74
179
|
const summaryByDevice = summaryByDay.find(a => a.deviceId === d.id)
|
|
75
180
|
|
|
@@ -80,9 +185,9 @@ async function createFuelConsumptionReport (from, to, userData, traccar) {
|
|
|
80
185
|
'fuel_high_threshold' in d.attributes &&
|
|
81
186
|
d.attributes.fuel_tank_capacity
|
|
82
187
|
|
|
83
|
-
const spentFuel = hasFuelSensor
|
|
84
|
-
|
|
85
|
-
|
|
188
|
+
const spentFuel = hasFuelSensor
|
|
189
|
+
? tripsByDay.length > 0 ? calculateTotalFuelFromSensor(tripsByDay, stopsByDay, route, d, firstStopIndex) : 0
|
|
190
|
+
: calculateConsumption(d, { trips: tripsByDay, stops: stopsByDay, route: routeByDay, summary: summaryByDevice })
|
|
86
191
|
|
|
87
192
|
const dataRow = {
|
|
88
193
|
date,
|