fix: improve GPS position sync reliability and add route trails (#21)
Backend: - Increase sync overlap buffer from 5s to 30s to catch late-arriving positions - Add position history endpoint GET /gps/locations/:driverId/history - Add logging for position sync counts (returned vs inserted) Frontend: - Add useDriverLocationHistory hook for fetching position trails - Draw Polyline route trails on GPS map for each tracked driver - Historical positions shown as semi-transparent paths behind live markers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -471,6 +471,49 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driver location history (for route trail display)
|
||||
*/
|
||||
async getDriverLocationHistory(
|
||||
driverId: string,
|
||||
fromDate?: Date,
|
||||
toDate?: Date,
|
||||
): Promise<LocationDataDto[]> {
|
||||
const device = await this.prisma.gpsDevice.findUnique({
|
||||
where: { driverId },
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
throw new NotFoundException('Driver is not enrolled for GPS tracking');
|
||||
}
|
||||
|
||||
// Default to last 4 hours if no date range specified
|
||||
const to = toDate || new Date();
|
||||
const from = fromDate || new Date(to.getTime() - 4 * 60 * 60 * 1000);
|
||||
|
||||
const locations = await this.prisma.gpsLocationHistory.findMany({
|
||||
where: {
|
||||
deviceId: device.id,
|
||||
timestamp: {
|
||||
gte: from,
|
||||
lte: to,
|
||||
},
|
||||
},
|
||||
orderBy: { timestamp: 'asc' },
|
||||
});
|
||||
|
||||
return locations.map((loc) => ({
|
||||
latitude: loc.latitude,
|
||||
longitude: loc.longitude,
|
||||
altitude: loc.altitude,
|
||||
speed: loc.speed,
|
||||
course: loc.course,
|
||||
accuracy: loc.accuracy,
|
||||
battery: loc.battery,
|
||||
timestamp: loc.timestamp,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driver's own stats (for driver self-view)
|
||||
*/
|
||||
@@ -605,28 +648,37 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
|
||||
},
|
||||
});
|
||||
|
||||
if (devices.length === 0) return;
|
||||
if (devices.length === 0) {
|
||||
this.logger.debug('[GPS Sync] No active devices to sync');
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
this.logger.log(`[GPS Sync] Starting sync for ${devices.length} active devices`);
|
||||
|
||||
for (const device of devices) {
|
||||
try {
|
||||
// Calculate "since" from device's last active time with 5s overlap buffer
|
||||
// Calculate "since" from device's last active time with 30s overlap buffer
|
||||
// (increased from 5s to catch late-arriving positions)
|
||||
// Falls back to 2 minutes ago if no lastActive
|
||||
const since = device.lastActive
|
||||
? new Date(device.lastActive.getTime() - 5000)
|
||||
? new Date(device.lastActive.getTime() - 30000)
|
||||
: new Date(now.getTime() - 120000);
|
||||
|
||||
this.logger.debug(`[GPS Sync] Fetching positions for device ${device.traccarDeviceId} since ${since.toISOString()}`);
|
||||
|
||||
const positions = await this.traccarClient.getPositionHistory(
|
||||
device.traccarDeviceId,
|
||||
since,
|
||||
now,
|
||||
);
|
||||
|
||||
this.logger.log(`[GPS Sync] Device ${device.traccarDeviceId}: Retrieved ${positions.length} positions from Traccar`);
|
||||
|
||||
if (positions.length === 0) continue;
|
||||
|
||||
// Batch insert with skipDuplicates (unique constraint on deviceId+timestamp)
|
||||
await this.prisma.gpsLocationHistory.createMany({
|
||||
const insertResult = await this.prisma.gpsLocationHistory.createMany({
|
||||
data: positions.map((p) => ({
|
||||
deviceId: device.id,
|
||||
latitude: p.latitude,
|
||||
@@ -641,6 +693,13 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
|
||||
skipDuplicates: true,
|
||||
});
|
||||
|
||||
const inserted = insertResult.count;
|
||||
const skipped = positions.length - inserted;
|
||||
this.logger.log(
|
||||
`[GPS Sync] Device ${device.traccarDeviceId}: ` +
|
||||
`Inserted ${inserted} new positions, skipped ${skipped} duplicates`
|
||||
);
|
||||
|
||||
// Update lastActive to the latest position timestamp
|
||||
const latestPosition = positions.reduce((latest, p) =>
|
||||
new Date(p.deviceTime) > new Date(latest.deviceTime) ? p : latest
|
||||
@@ -650,9 +709,11 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
|
||||
data: { lastActive: new Date(latestPosition.deviceTime) },
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to sync positions for device ${device.traccarDeviceId}: ${error}`);
|
||||
this.logger.error(`[GPS Sync] Failed to sync positions for device ${device.traccarDeviceId}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log('[GPS Sync] Sync completed');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user