fleetmap-reports 2.0.358 → 2.0.359
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/partnerReports/driverranking-report.js +39 -7
- package/.claude/worktrees/eager-stonebraker-c08ac7/.eslintrc.js +0 -15
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/enGB.js +0 -309
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/esCL.js +0 -297
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/frFR.json +0 -324
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/index.js +0 -35
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptBR.js +0 -301
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptPT.js +0 -310
- package/.claude/worktrees/eager-stonebraker-c08ac7/package-lock.json +0 -11944
- package/.claude/worktrees/eager-stonebraker-c08ac7/package.json +0 -45
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/activity-report.js +0 -822
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/automaticReports.js +0 -34
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/carbone/index.js +0 -13
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/custom/vistawasteActivity-report.js +0 -544
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/events-report.js +0 -360
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-consumption-report.js +0 -296
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-drop-report.js +0 -128
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/here.js +0 -172
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/idle-report.js +0 -403
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/index.js +0 -244
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/kms-report.js +0 -549
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/location-report.js +0 -496
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/machines-report.js +0 -278
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/afriquia.js +0 -129
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/dailyuse-report.js +0 -80
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/delayedstart-report.js +0 -223
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/driverranking-report.js +0 -328
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/gpsjump-report.js +0 -61
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/performance-report.js +0 -359
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/refueling-report.js +0 -345
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/reportStyle.js +0 -34
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/sensor-report.js +0 -447
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/speeding-report.js +0 -663
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/stop-report.js +0 -242
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ActivityReportTemplate.mjml +0 -51
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EmptyReportTemplate.mjml +0 -25
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EventsReportTemplate.mjml +0 -75
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/FuelDropReportTemplate.mjml +0 -60
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/IdleReportTemplate.mjml +0 -54
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportGroupByDayTemplate.mjml +0 -43
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportTemplate.mjml +0 -44
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/LocationReportTemplate.mjml +0 -37
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/RefuelingReportTemplate.mjml +0 -60
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SensorReportTemplate.mjml +0 -23
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SpeedingReportTemplate.mjml +0 -80
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/TripReportTemplate.mjml +0 -97
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ZoneReportTemplate.mjml +0 -52
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/report.mjml +0 -96
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/activity.test.js +0 -99
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/carbone.test.js +0 -20
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/events.test.js +0 -30
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/fuel.test.js +0 -35
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/idle.test.js +0 -16
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.js +0 -24
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.test.js +0 -203
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/kms.test.js +0 -74
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/performance.test.js +0 -36
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/positions.test.js +0 -67
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/speeding.test.js +0 -120
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/translations.test.js +0 -21
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/trips.test.js +0 -140
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/zones.test.js +0 -162
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/trip-report.js +0 -641
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/device.js +0 -13
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/driver.js +0 -90
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/fuel.js +0 -102
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/geofence.js +0 -52
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/odoo.js +0 -18
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/parallel.js +0 -64
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/pdfDocument.js +0 -140
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/route.js +0 -78
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/timetable.js +0 -30
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.js +0 -213
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.worker.js +0 -49
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/trips.js +0 -270
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/utils.js +0 -358
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/worker.js +0 -14
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/xpert.js +0 -58
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/word/index.js +0 -95
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/zone-report.js +0 -904
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
const automaticReports = require('./automaticReports')
|
|
2
|
-
const { devicesToProcess } = require('./util/device')
|
|
3
|
-
const { getTranslations } = require('./util/utils')
|
|
4
|
-
const traccarHelper = require('./util/traccar')
|
|
5
|
-
|
|
6
|
-
function calculateFuelDrop (position, positions) {
|
|
7
|
-
const index = positions.indexOf(position)
|
|
8
|
-
if (index > 0) {
|
|
9
|
-
let backIndex = 1
|
|
10
|
-
while ((backIndex < 10) && index - backIndex >= 0) {
|
|
11
|
-
const positionBefore = positions[index - backIndex]
|
|
12
|
-
const diff = positionBefore.attributes.fuel - position.attributes.fuel
|
|
13
|
-
if (diff > 0) {
|
|
14
|
-
return diff
|
|
15
|
-
}
|
|
16
|
-
backIndex++
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return 0
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function createFuelDropReport (from, to, userData, traccar) {
|
|
23
|
-
console.log('Create FuelDropReport')
|
|
24
|
-
|
|
25
|
-
const allData = {
|
|
26
|
-
devices: [],
|
|
27
|
-
from,
|
|
28
|
-
to
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const reportData = []
|
|
32
|
-
reportData.push(allData)
|
|
33
|
-
|
|
34
|
-
const devices = devicesToProcess(userData)
|
|
35
|
-
|
|
36
|
-
const arrayOfArrays = automaticReports.sliceArray(devices, 50)
|
|
37
|
-
let data = []
|
|
38
|
-
|
|
39
|
-
const types = ['deviceFuelDrop']
|
|
40
|
-
for (const a of arrayOfArrays) {
|
|
41
|
-
const response = await traccar.reports.reportsEventsGet(from, to, a.map(d => d.id), null, types)
|
|
42
|
-
data = data.concat(response.data)
|
|
43
|
-
}
|
|
44
|
-
console.log('FuelDrop Alerts:' + data.length)
|
|
45
|
-
allData.totalDevices = 0
|
|
46
|
-
allData.totalFuelDrops = 0
|
|
47
|
-
allData.totalFuelDropLiters = 0
|
|
48
|
-
|
|
49
|
-
for (const d of devices) {
|
|
50
|
-
const alerts = data.filter(t => t.deviceId === d.id)
|
|
51
|
-
if (alerts.length > 0) {
|
|
52
|
-
const allInOne = await traccarHelper.getAllInOne(traccar, from, to, [d], true, false, false, false, 1, devices.length)
|
|
53
|
-
const route = allInOne.route.filter(t => t.deviceId === d.id)
|
|
54
|
-
|
|
55
|
-
for (const a of alerts) {
|
|
56
|
-
a.fuelDropLiters = 0
|
|
57
|
-
if (a.positionId > 0) {
|
|
58
|
-
const position = route.find(p => p.id === a.positionId)
|
|
59
|
-
if (position) {
|
|
60
|
-
a.position = position
|
|
61
|
-
const diff = calculateFuelDrop(position, route)
|
|
62
|
-
a.fuelDropLiters = Math.round(diff * d.attributes.fuel_tank_capacity / 100)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Filter wrong fuel drop values
|
|
68
|
-
const filteredAlerts = alerts.filter(a => a.fuelDropLiters < d.attributes.fuel_tank_capacity && a.fuelDropLiters > 0)
|
|
69
|
-
|
|
70
|
-
if (filteredAlerts.length > 0) {
|
|
71
|
-
allData.devices.push({
|
|
72
|
-
device: d,
|
|
73
|
-
alerts: filteredAlerts
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return reportData
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function exportFuelDropReportToExcel (userData, reportData) {
|
|
83
|
-
console.log('Export to Excel')
|
|
84
|
-
const translations = getTranslations(userData)
|
|
85
|
-
|
|
86
|
-
const settings = {
|
|
87
|
-
sheetName: 'FuelDropReport', // The name of the sheet
|
|
88
|
-
fileName: 'FuelDropReport' // The name of the spreadsheet
|
|
89
|
-
}
|
|
90
|
-
const headers = [
|
|
91
|
-
{ label: translations.report.name, value: 'name' },
|
|
92
|
-
{ label: translations.report.driver, value: 'driver' },
|
|
93
|
-
{ label: translations.report.date, value: 'fixTime' },
|
|
94
|
-
{ label: translations.report.address, value: 'address' },
|
|
95
|
-
{ label: translations.report.fueldrop, value: 'fuelDropLiters' }
|
|
96
|
-
]
|
|
97
|
-
let data = []
|
|
98
|
-
if (reportData.devices) {
|
|
99
|
-
reportData.devices.forEach(d => {
|
|
100
|
-
data = data.concat(d.alerts.map(r => {
|
|
101
|
-
return {
|
|
102
|
-
driver: r.driverName,
|
|
103
|
-
fuelDropLiters: r.fuelDropLiters,
|
|
104
|
-
fixTime: new Date(r.position.fixTime).toLocaleString(),
|
|
105
|
-
name: d.device.name,
|
|
106
|
-
address: r.geofenceName || r.position.address
|
|
107
|
-
}
|
|
108
|
-
}))
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
headers,
|
|
114
|
-
data,
|
|
115
|
-
settings
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function exportFuelDropReportToPDF (userData, reportData) {
|
|
120
|
-
console.log('Export to PDF')
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
exports.createFuelDropReport = createFuelDropReport
|
|
124
|
-
exports.exportFuelDropReportToPDF = exportFuelDropReportToPDF
|
|
125
|
-
exports.exportFuelDropReportToExcel = exportFuelDropReportToExcel
|
|
126
|
-
exports.create = createFuelDropReport
|
|
127
|
-
exports.exportToPDF = exportFuelDropReportToPDF
|
|
128
|
-
exports.exportToExcel = exportFuelDropReportToExcel
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
const axios = require('axios')
|
|
2
|
-
|
|
3
|
-
const appId = 'apiKey=' + process.env.HERE_API_KEY
|
|
4
|
-
|
|
5
|
-
const calcRoute = 'https://fleet.ls.hereapi.com/2/calculateroute.json?&detailedTollCosts=1&' +
|
|
6
|
-
appId +
|
|
7
|
-
'&routeMatch=1&filetype=CSV&mode=car&tollVehicleType=1&rollups=none,country;tollsys&' +
|
|
8
|
-
'attributes=SPEED_LIMITS_FCn(*),ROAD_NAME_FCn(*),ROAD_ADMIN_FCn(*),DISTANCE_MARKERS_FCn(*)' +
|
|
9
|
-
',ROAD_GEOM_FCn(*),TOLL_LINK_FCn(*),TOLL_BOOTH_FCn(*),TRUCK_SPEED_LIMITS_FCn(*),TRUCK_RESTR_FCn(*)'
|
|
10
|
-
|
|
11
|
-
const util = require('util')
|
|
12
|
-
|
|
13
|
-
function mpsToKmh (mps) {
|
|
14
|
-
return mps * 3.6
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getSpeedLimit (li) {
|
|
18
|
-
if (li && li.attributes && li.attributes.SPEED_LIMITS_FCN) {
|
|
19
|
-
const from = li.attributes.SPEED_LIMITS_FCN[0].FROM_REF_SPEED_LIMIT
|
|
20
|
-
const to = li.attributes.SPEED_LIMITS_FCN[0].TO_REF_SPEED_LIMIT
|
|
21
|
-
if (from > to) {
|
|
22
|
-
return from
|
|
23
|
-
}
|
|
24
|
-
return to
|
|
25
|
-
}
|
|
26
|
-
return 999
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getRoadName (li) {
|
|
30
|
-
if (li.attributes.ROAD_GEOM_FCN) {
|
|
31
|
-
const result = li.attributes.ROAD_GEOM_FCN[0].NAME
|
|
32
|
-
if (result && result.length > 0) { return result }
|
|
33
|
-
}
|
|
34
|
-
return ''
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getTruckSpeedLimits (li) {
|
|
38
|
-
if (li.attributes.TRUCK_SPEED_LIMITS_FCN) {
|
|
39
|
-
return li.attributes.TRUCK_SPEED_LIMITS_FCN
|
|
40
|
-
}
|
|
41
|
-
return null
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getDistanceMarkers (li) {
|
|
45
|
-
if (li.attributes.DISTANCE_MARKERS_FCN) {
|
|
46
|
-
const dMarkers = li.attributes.DISTANCE_MARKERS_FCN
|
|
47
|
-
return util.format('km %d', dMarkers[0].DISTANCE_VALUE)
|
|
48
|
-
}
|
|
49
|
-
return ''
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function generateCSV (rows) {
|
|
53
|
-
const route = []
|
|
54
|
-
route.push('latitude, longitude, speed_kmh, heading, timestamp')
|
|
55
|
-
for (let i = 0; i < rows.length; i++) {
|
|
56
|
-
const r = rows[i]
|
|
57
|
-
route.push(r.latitude + ',' + r.longitude + ',' + r.speed * 1.852 + ',' + r.course + ',' + r.fixTime)
|
|
58
|
-
}
|
|
59
|
-
return route.join('\n')
|
|
60
|
-
}
|
|
61
|
-
/*
|
|
62
|
-
function generateCoordsCSV(rows) {
|
|
63
|
-
const route = []
|
|
64
|
-
for (let i = 0; i < rows.length; i++) {
|
|
65
|
-
const r = rows[i]
|
|
66
|
-
route.push('id=' + i + '&prox=' + r.latMatched + ',' + r.lonMatched + ',20')
|
|
67
|
-
}
|
|
68
|
-
return route.join('\n')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function fillGeocoding(rows) {
|
|
72
|
-
return new Promise((resolve) => {
|
|
73
|
-
const csv = generateCoordsCSV(rows.slice(0, 300))
|
|
74
|
-
const body = {
|
|
75
|
-
report: 'reverseGeocode',
|
|
76
|
-
rows: csv
|
|
77
|
-
}
|
|
78
|
-
axios.post(rServerUrl,
|
|
79
|
-
body,
|
|
80
|
-
{
|
|
81
|
-
headers: { 'Content-Type': 'application/json' },
|
|
82
|
-
timeout: 60000 // Maximum timeour for the Lambda API Gateway
|
|
83
|
-
}
|
|
84
|
-
).then((response) => {
|
|
85
|
-
if (response.data) {
|
|
86
|
-
const hereData = response.data
|
|
87
|
-
for (let i = 0; i < hereData.Response.Item.length; i++) {
|
|
88
|
-
rows[i].geocoding = hereData.Response.Item[i].Result[0].Location.Address.Label
|
|
89
|
-
}
|
|
90
|
-
resolve(rows)
|
|
91
|
-
}
|
|
92
|
-
}).catch((e) => {
|
|
93
|
-
console.error(e)
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
} */
|
|
97
|
-
|
|
98
|
-
exports.routeMatch = async (rows) => {
|
|
99
|
-
const csv = generateCSV(rows)
|
|
100
|
-
const response = await axios.post(calcRoute, csv, { headers: { 'Content-Type': 'application/binary' } })
|
|
101
|
-
const hereData = response.data
|
|
102
|
-
const route = hereData.response.route[0]
|
|
103
|
-
const results = []
|
|
104
|
-
const wayPoints = route.waypoint
|
|
105
|
-
const links = route.leg[0].link
|
|
106
|
-
if (wayPoints && wayPoints.length > 0) {
|
|
107
|
-
for (let i = 0; i < wayPoints.length; i++) {
|
|
108
|
-
const tp = wayPoints[i]
|
|
109
|
-
const li = links[tp.routeLinkSeqNrMatched]
|
|
110
|
-
results.push({
|
|
111
|
-
linkId: tp.routeLinkSeqNrMatched,
|
|
112
|
-
timestamp: tp.timestamp,
|
|
113
|
-
currentSpeedKmh: mpsToKmh(tp.speedMps),
|
|
114
|
-
latitude: tp.originalPosition.latitude,
|
|
115
|
-
longitude: tp.originalPosition.longitude,
|
|
116
|
-
latMatched: tp.mappedPosition.latitude,
|
|
117
|
-
lonMatched: tp.mappedPosition.longitude,
|
|
118
|
-
heading: tp.headingDegreeNorthClockwise,
|
|
119
|
-
headingMatched: tp.headingMatched,
|
|
120
|
-
speedLimit: getSpeedLimit(li),
|
|
121
|
-
roadName: getRoadName(li),
|
|
122
|
-
truckSpeedLimits: getTruckSpeedLimits(li),
|
|
123
|
-
distanceMarkers: getDistanceMarkers(li),
|
|
124
|
-
geocoding: ''
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return results
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
exports.tollsMatch = async (rows, result) => {
|
|
132
|
-
const csv = generateCSV(rows)
|
|
133
|
-
axios.post(calcRoute, csv, { headers: { 'Content-Type': 'application/binary' } }).then((response) => {
|
|
134
|
-
const hereData = response.data
|
|
135
|
-
const results = []
|
|
136
|
-
const tollData = hereData.response.route[0].tollCost.routeTollItems
|
|
137
|
-
if (tollData) {
|
|
138
|
-
let it = 0
|
|
139
|
-
const wayPoints = hereData.response.route[0].waypoint
|
|
140
|
-
const links = hereData.response.route[0].leg[0].link
|
|
141
|
-
for (let ti = 0; ti < tollData.length; ti++) {
|
|
142
|
-
const i = tollData[ti]
|
|
143
|
-
if (i.tollCostAlternatives) {
|
|
144
|
-
for (let il = 0; il < links.length; il++) {
|
|
145
|
-
if (parseInt(links[il].linkId) === parseInt(i.tollStructures[0].linkIds[0])) {
|
|
146
|
-
for (; it < wayPoints.length; it++) {
|
|
147
|
-
if (wayPoints[it].routeLinkSeqNrMatched >= il) {
|
|
148
|
-
const li = wayPoints[it]
|
|
149
|
-
results.push({
|
|
150
|
-
country: i.country,
|
|
151
|
-
road: i.tollSystem[0].name,
|
|
152
|
-
amount: i.tollCostAlternatives[0].amount,
|
|
153
|
-
currency: i.tollCostAlternatives[0].currency,
|
|
154
|
-
toll: i.tollStructures[0].name,
|
|
155
|
-
timestamp: li ? li.timestamp : '',
|
|
156
|
-
speed: li ? mpsToKmh(li.speedMps) : ''
|
|
157
|
-
})
|
|
158
|
-
break
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
break
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
result(results)
|
|
167
|
-
} else { result([]) }
|
|
168
|
-
}).catch(e => {
|
|
169
|
-
console.error(e)
|
|
170
|
-
result([])
|
|
171
|
-
})
|
|
172
|
-
}
|
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
const jsPDF = require('jspdf')
|
|
2
|
-
const { headerFromUser, AmiriRegular } = require('./util/pdfDocument')
|
|
3
|
-
const {
|
|
4
|
-
convertToLocaleString, convertMS, getTranslations, getLanguage, convertToLocaleDateString, convertToLocaleTimeString,
|
|
5
|
-
convertPositionToFeature,
|
|
6
|
-
convertToFeature
|
|
7
|
-
} = require('./util/utils')
|
|
8
|
-
const { getStyle } = require('./reportStyle')
|
|
9
|
-
const { getUserPartner } = require('fleetmap-partners')
|
|
10
|
-
const { devicesToProcess } = require('./util/device')
|
|
11
|
-
const automaticReports = require('./automaticReports')
|
|
12
|
-
const traccarHelper = require('./util/traccar')
|
|
13
|
-
const { getIdleEvents } = require('./util/route')
|
|
14
|
-
const { reportByDriver, getDriverData } = require('./util/driver')
|
|
15
|
-
const { getNearestPOIs, checkGeofenceIn, loadGroupsData } = require('./util/geofence')
|
|
16
|
-
|
|
17
|
-
const fileName = 'IdleReport'
|
|
18
|
-
|
|
19
|
-
function getDriver (idleEvent, drivers) {
|
|
20
|
-
if (idleEvent && idleEvent.position && idleEvent.position.attributes.driverUniqueId) {
|
|
21
|
-
const driver = drivers.find(d => d.uniqueId === idleEvent.position.attributes.driverUniqueId)
|
|
22
|
-
return driver ? driver.name : idleEvent.position.attributes.driverUniqueId
|
|
23
|
-
}
|
|
24
|
-
return ''
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function createIdleReport (from, to, userData, traccarInstance) {
|
|
28
|
-
console.log('Create IdleReport')
|
|
29
|
-
const reportData = []
|
|
30
|
-
|
|
31
|
-
if (userData.byDriver) {
|
|
32
|
-
console.log('ByDriver')
|
|
33
|
-
const report = await createIdleReportByDriver(from, to, userData, traccarInstance)
|
|
34
|
-
reportData.push(report)
|
|
35
|
-
} else if (userData.byGroup) {
|
|
36
|
-
console.log('ByGroup')
|
|
37
|
-
const allData = await createIdleReportByGroup(from, to, userData, traccarInstance)
|
|
38
|
-
reportData.push(...allData)
|
|
39
|
-
} else {
|
|
40
|
-
console.log('ByDevice')
|
|
41
|
-
const report = await createIdleReportByDevice(from, to, userData, traccarInstance)
|
|
42
|
-
reportData.push(report)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return reportData
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function createIdleReportByDriver (from, to, userData, traccar) {
|
|
49
|
-
const devices = await traccar.devices.devicesGet().then(d => d.data)
|
|
50
|
-
|
|
51
|
-
if (!devices.length) {
|
|
52
|
-
// Empty report
|
|
53
|
-
return { drivers: [] }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
await loadGroupsData(userData, traccar)
|
|
57
|
-
|
|
58
|
-
const results = await reportByDriver(devices, traccar, from, to, userData, processDrivers)
|
|
59
|
-
|
|
60
|
-
const report = {
|
|
61
|
-
drivers: [],
|
|
62
|
-
from,
|
|
63
|
-
to
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
results.drivers.forEach(result => {
|
|
67
|
-
const driver = report.drivers.find(d => d.driver.id === result.driver.id)
|
|
68
|
-
if (driver) {
|
|
69
|
-
driver.idleEvents.push(...result.idleEvents)
|
|
70
|
-
} else {
|
|
71
|
-
report.drivers.push(result)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
report.drivers.forEach(d => {
|
|
76
|
-
d.idleEvents = d.idleEvents.sort((a, b) => (new Date(a.position.fixTime) > new Date(b.position.fixTime)) ? 1 : -1)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
return report
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function createIdleReportByGroup (from, to, userData, traccarInstance) {
|
|
83
|
-
const reportData = []
|
|
84
|
-
for (const g of userData.groups) {
|
|
85
|
-
const devices = userData.devices.filter(d => d.groupId === g.id)
|
|
86
|
-
console.log(g.name + ' devices:' + devices.length)
|
|
87
|
-
if (devices.length > 0) {
|
|
88
|
-
const groupData = {
|
|
89
|
-
devices: [],
|
|
90
|
-
group: g,
|
|
91
|
-
from,
|
|
92
|
-
to
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const response = await traccarInstance.reports.reportsRouteGet(from, to, null, [g.id])
|
|
96
|
-
const routes = response.data
|
|
97
|
-
|
|
98
|
-
devices.sort((a, b) => (a.name > b.name) ? 1 : -1)
|
|
99
|
-
|
|
100
|
-
if (routes.length > 0) {
|
|
101
|
-
console.log('Routes:' + routes.length)
|
|
102
|
-
groupData.devices = processDevices(from, to, devices, routes, userData)
|
|
103
|
-
|
|
104
|
-
reportData.push(groupData)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const groupIds = userData.groups.map(g => g.id)
|
|
110
|
-
const withoutGroupDevices = userData.devices.filter(d => !groupIds.includes(d.groupId))
|
|
111
|
-
|
|
112
|
-
const allData = await createIdleReportByDevice(from, to, withoutGroupDevices, userData, traccarInstance)
|
|
113
|
-
reportData.push(allData)
|
|
114
|
-
|
|
115
|
-
return reportData
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function createIdleReportByDevice (from, to, userData, traccarInstance) {
|
|
119
|
-
const allData = {
|
|
120
|
-
devices: [],
|
|
121
|
-
from,
|
|
122
|
-
to
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
await loadGroupsData(userData, traccarInstance)
|
|
126
|
-
|
|
127
|
-
const devices = devicesToProcess(userData)
|
|
128
|
-
const sliced = automaticReports.sliceArray(devices, 5)
|
|
129
|
-
|
|
130
|
-
let deviceCount = 0
|
|
131
|
-
for (const slice of sliced) {
|
|
132
|
-
const { route } = await traccarHelper.getAllInOne(traccarInstance, from, to, slice, true, false, false, false, deviceCount, devices.length)
|
|
133
|
-
|
|
134
|
-
if (route.length > 0) {
|
|
135
|
-
allData.devices.push(...await processDevices(from, to, slice, route, userData))
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
deviceCount = deviceCount + slice.length
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return allData
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function processDrivers (from, to, userData, data) {
|
|
145
|
-
const driversResult = []
|
|
146
|
-
for (const d of userData.drivers) {
|
|
147
|
-
const { route } = await getDriverData(d, data, userData)
|
|
148
|
-
|
|
149
|
-
if (route.length > 0) {
|
|
150
|
-
const idleEvents = getIdleEvents(data.route, d, userData.idleSpeedThreshold / 1.85200)
|
|
151
|
-
const filteredEvents = idleEvents.filter(e => userData.minimumIdleMinutes ? e.idleTime > userData.minimumIdleMinutes * 60 * 1000 : true)
|
|
152
|
-
|
|
153
|
-
if (filteredEvents.length) {
|
|
154
|
-
filteredEvents.forEach(e => {
|
|
155
|
-
findPOIData(e, userData)
|
|
156
|
-
findGeofenceData(e, userData)
|
|
157
|
-
const device = userData.devices.find(d => d.id === e.position.deviceId)
|
|
158
|
-
e.deviceName = device ? device.name : ''
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
const driverData = {
|
|
162
|
-
driver: d,
|
|
163
|
-
idleEvents: filteredEvents
|
|
164
|
-
}
|
|
165
|
-
driversResult.push(driverData)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return driversResult
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function processDevices (from, to, devices, routes, userData) {
|
|
174
|
-
const devicesResult = []
|
|
175
|
-
for (const d of devices) {
|
|
176
|
-
const route = routes.filter(p => p.deviceId === d.id)
|
|
177
|
-
|
|
178
|
-
const idleEvents = getIdleEvents(route, undefined, userData.idleSpeedThreshold / 1.85200)
|
|
179
|
-
const filteredEvents = idleEvents.filter(e => e.idleTime > (userData.minimumIdleMinutes ? userData.minimumIdleMinutes * 60 * 1000 : 0))
|
|
180
|
-
|
|
181
|
-
if (filteredEvents.length) {
|
|
182
|
-
for (const e of filteredEvents) {
|
|
183
|
-
findPOIData(e, userData)
|
|
184
|
-
findGeofenceData(e, userData)
|
|
185
|
-
|
|
186
|
-
const driver = userData.drivers.find(d => d.id === e.position.attributes.driverUniqueId)
|
|
187
|
-
e.driver = driver ? driver.name : ''
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const deviceData = {
|
|
191
|
-
device: d,
|
|
192
|
-
idleEvents: filteredEvents
|
|
193
|
-
}
|
|
194
|
-
devicesResult.push(deviceData)
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return devicesResult
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function findPOIData (event, userData) {
|
|
202
|
-
const nearestPOIs = getNearestPOIs(event.position.longitude, event.position.latitude, userData.geofences)
|
|
203
|
-
if (nearestPOIs.length > 0) {
|
|
204
|
-
event.endPOIName = nearestPOIs[0].p.name
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function findGeofenceData (event, userData) {
|
|
209
|
-
const geofence = userData.geofences.filter(g => !g.area.startsWith('CIRCLE')).find(g =>
|
|
210
|
-
checkGeofenceIn(convertPositionToFeature(event.position), convertToFeature(g)))
|
|
211
|
-
if (geofence) {
|
|
212
|
-
const group = userData.groups.find(g => g.geofenceIds && g.geofenceIds.includes(geofence.id))
|
|
213
|
-
event.geofenceName = geofence.name
|
|
214
|
-
event.geofenceGroup = (group && group.name) || ''
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function exportIdleReportToPDF (userData, reportData) {
|
|
219
|
-
console.log('Export to PDF')
|
|
220
|
-
|
|
221
|
-
const timezone = userData.user.attributes.timezone
|
|
222
|
-
const lang = getLanguage(userData)
|
|
223
|
-
const translations = getTranslations(userData)
|
|
224
|
-
|
|
225
|
-
const idleData = userData.byDriver ? reportData.drivers : reportData.devices
|
|
226
|
-
|
|
227
|
-
const headers = [
|
|
228
|
-
translations.report.date,
|
|
229
|
-
translations.report.start,
|
|
230
|
-
translations.report.end,
|
|
231
|
-
translations.report.address,
|
|
232
|
-
translations.report.duration,
|
|
233
|
-
translations.report.zone
|
|
234
|
-
]
|
|
235
|
-
|
|
236
|
-
if (userData.byDriver) {
|
|
237
|
-
headers.push(translations.report.vehicle)
|
|
238
|
-
} else {
|
|
239
|
-
headers.push(translations.report.driver)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (idleData) {
|
|
243
|
-
let first = true
|
|
244
|
-
// eslint-disable-next-line new-cap
|
|
245
|
-
const doc = new jsPDF.jsPDF('l')
|
|
246
|
-
await headerFromUser(doc, translations.report.titleIdleReport, userData.user)
|
|
247
|
-
|
|
248
|
-
idleData.forEach(d => {
|
|
249
|
-
try {
|
|
250
|
-
const data = []
|
|
251
|
-
|
|
252
|
-
const name = userData.byDriver ? d.driver.name : deviceName(d.device)
|
|
253
|
-
const group = userData.byDriver ? userData.groups.find(g => g.drivers.includes(d.id)) : userData.groups.find(g => d.device.groupId === g.id)
|
|
254
|
-
|
|
255
|
-
let space = 0
|
|
256
|
-
if (!first) {
|
|
257
|
-
doc.addPage()
|
|
258
|
-
} else {
|
|
259
|
-
first = false
|
|
260
|
-
space = 10
|
|
261
|
-
}
|
|
262
|
-
doc.setFontSize(13)
|
|
263
|
-
doc.text(name, 20, space + 20)
|
|
264
|
-
doc.setFontSize(11)
|
|
265
|
-
doc.text(group ? translations.report.group + ': ' + group.name : '', 150, space + 20)
|
|
266
|
-
doc.text(convertToLocaleString(reportData.from, lang, timezone) + ' - ' + convertToLocaleString(reportData.to, lang, timezone), 20, space + 25)
|
|
267
|
-
doc.text(translations.report.minimumMinutes + ': ' + userData.minimumIdleMinutes + ' ' + translations.report.minutes, 20, space + 35)
|
|
268
|
-
doc.text(translations.report.maximumSpeed + ': ' + userData.idleSpeedThreshold + ' Km/h', 100, space + 35)
|
|
269
|
-
|
|
270
|
-
d.idleEvents.forEach(a => {
|
|
271
|
-
const temp = [
|
|
272
|
-
getIdleEventDate(a.position, userData.user),
|
|
273
|
-
getIdleEventTime(a.position, userData.user),
|
|
274
|
-
getIdleEventTime(a.lastPosition, userData.user),
|
|
275
|
-
a.endPOIName || a.position.address,
|
|
276
|
-
convertMS(a.idleTime, true),
|
|
277
|
-
(a.geofenceName || '') + (a.geofenceGroup ? ' (' + a.geofenceGroup + ')' : ''),
|
|
278
|
-
userData.byDriver ? a.deviceName : getDriver(a, userData.drivers)
|
|
279
|
-
]
|
|
280
|
-
data.push(temp)
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
const footValues = [
|
|
284
|
-
'Total:' + d.idleEvents.length,
|
|
285
|
-
'', '', '',
|
|
286
|
-
convertMS(d.idleEvents.reduce((a, b) => a + b.idleTime, 0), true)
|
|
287
|
-
]
|
|
288
|
-
|
|
289
|
-
if (userData.roadSpeedLimits) {
|
|
290
|
-
footValues.splice(2, 0, '')
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (timezone === 'Asia/Qatar') {
|
|
294
|
-
doc.addFileToVFS('Amiri-Regular.ttf', AmiriRegular())
|
|
295
|
-
doc.addFont('Amiri-Regular.ttf', 'Amiri', 'normal')
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const style = getStyle(getUserPartner(userData.user))
|
|
299
|
-
doc.autoTable({
|
|
300
|
-
head: [headers],
|
|
301
|
-
body: data,
|
|
302
|
-
foot: [footValues],
|
|
303
|
-
showFoot: 'lastPage',
|
|
304
|
-
headStyles: {
|
|
305
|
-
fillColor: style.pdfHeaderColor,
|
|
306
|
-
textColor: style.pdfHeaderTextColor,
|
|
307
|
-
fontSize: 10
|
|
308
|
-
},
|
|
309
|
-
bodyStyles: {
|
|
310
|
-
fillColor: style.pdfBodyColor,
|
|
311
|
-
textColor: style.pdfBodyTextColor,
|
|
312
|
-
fontSize: 8,
|
|
313
|
-
font: timezone === 'Asia/Qatar' ? 'Amiri' : ''
|
|
314
|
-
|
|
315
|
-
},
|
|
316
|
-
footStyles: {
|
|
317
|
-
fillColor: style.pdfFooterColor,
|
|
318
|
-
textColor: style.pdfFooterTextColor,
|
|
319
|
-
fontSize: 9
|
|
320
|
-
},
|
|
321
|
-
startY: space + 45
|
|
322
|
-
})
|
|
323
|
-
} catch (e) {
|
|
324
|
-
console.error(e, 'moving on...')
|
|
325
|
-
}
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
return doc
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function exportIdleReportToExcel (userData, reportData) {
|
|
333
|
-
console.log('Export to Excel')
|
|
334
|
-
|
|
335
|
-
const translations = getTranslations(userData)
|
|
336
|
-
|
|
337
|
-
const idleData = userData.byDriver ? reportData.drivers : reportData.devices
|
|
338
|
-
|
|
339
|
-
const settings = {
|
|
340
|
-
sheetName: translations.report.titleIdleReport.substring(0, 31), // The name of the sheet
|
|
341
|
-
fileName // The name of the spreadsheet
|
|
342
|
-
}
|
|
343
|
-
const headers = [
|
|
344
|
-
{ label: userData.byDriver ? translations.report.driver : translations.report.vehicle, value: 'name' },
|
|
345
|
-
{ label: translations.report.date, value: 'date' },
|
|
346
|
-
{ label: translations.report.start, value: 'start' },
|
|
347
|
-
{ label: translations.report.end, value: 'end' },
|
|
348
|
-
{ label: translations.report.address, value: 'address' },
|
|
349
|
-
{ label: translations.report.duration, value: 'duration' },
|
|
350
|
-
{ label: translations.report.zone, value: 'zone' },
|
|
351
|
-
{ label: userData.byDriver ? translations.report.vehicle : translations.report.driver, value: 'subname' }
|
|
352
|
-
]
|
|
353
|
-
|
|
354
|
-
let data = []
|
|
355
|
-
if (idleData) {
|
|
356
|
-
idleData.forEach(d => {
|
|
357
|
-
data = data.concat([{}])
|
|
358
|
-
data = data.concat(d.idleEvents.map(a => {
|
|
359
|
-
return {
|
|
360
|
-
name: userData.byDriver ? d.driver.name : d.device.name,
|
|
361
|
-
duration: convertMS(a.idleTime, true),
|
|
362
|
-
date: getIdleEventDate(a.position, userData.user),
|
|
363
|
-
start: getIdleEventTime(a.position, userData.user),
|
|
364
|
-
end: getIdleEventTime(a.lastPosition, userData.user),
|
|
365
|
-
subname: userData.byDriver ? a.deviceName : getDriver(a, userData.drivers),
|
|
366
|
-
address: a.endPOIName || a.position.address,
|
|
367
|
-
zone: (a.geofenceName || '') + (a.geofenceGroup ? ' (' + a.geofenceGroup + ')' : '')
|
|
368
|
-
}
|
|
369
|
-
}))
|
|
370
|
-
// Totals
|
|
371
|
-
data = data.concat([{
|
|
372
|
-
name: d.idleEvents.length,
|
|
373
|
-
duration: convertMS(d.idleEvents.reduce((a, b) => a + b.idleTime, 0), true)
|
|
374
|
-
}])
|
|
375
|
-
data = data.concat([{}])
|
|
376
|
-
})
|
|
377
|
-
console.log(data)
|
|
378
|
-
return {
|
|
379
|
-
headers,
|
|
380
|
-
data,
|
|
381
|
-
settings
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function deviceName (device) {
|
|
387
|
-
return device.name + (device.attributes.license_plate ? ', ' + device.attributes.license_plate : '') + (device.model ? ', ' + device.model : '')
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function getIdleEventDate (position, user) {
|
|
391
|
-
return convertToLocaleDateString(position.fixTime, user.attributes.lang, user.attributes.timezone)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function getIdleEventTime (position, user) {
|
|
395
|
-
return convertToLocaleTimeString(position.fixTime, user.attributes.lang, user.attributes.timezone)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
exports.createIdleReport = createIdleReport
|
|
399
|
-
exports.exportIdleReportToPDF = exportIdleReportToPDF
|
|
400
|
-
exports.exportIdleReportToExcel = exportIdleReportToExcel
|
|
401
|
-
exports.create = createIdleReport
|
|
402
|
-
exports.exportToPDF = exportIdleReportToPDF
|
|
403
|
-
exports.exportToExcel = exportIdleReportToExcel
|