| const { AppError } = require("../utils/appError"); |
|
|
| function mapLatestRowToApi(row) { |
| return { |
| key: `${row.truck_code}::${row.container_code}`, |
| tenantId: row.tenant_code, |
| truckId: row.truck_code, |
| containerId: row.container_code, |
| fleetId: row.fleet_code, |
| receivedAt: row.received_at, |
| telemetry: { |
| seq: row.seq, |
| ts: row.source_ts, |
| gps: { |
| lat: row.gps_lat, |
| lon: row.gps_lon, |
| speedKph: row.speed_kph, |
| }, |
| env: { |
| temperatureC: row.temperature_c, |
| humidityPct: row.humidity_pct, |
| pressureHpa: row.pressure_hpa, |
| }, |
| gas: { |
| mq2Raw: row.gas_raw, |
| alert: row.gas_alert, |
| }, |
| motion: { |
| tiltDeg: row.tilt_deg, |
| shock: row.shock, |
| }, |
| status: { |
| sdOk: row.sd_ok, |
| gpsFix: row.gps_fix, |
| uplink: row.uplink, |
| }, |
| raw: row.raw_payload, |
| }, |
| activeAlerts: row.active_alerts, |
| highestAlertSeverity: row.highest_alert_severity, |
| isOnline: row.is_online, |
| }; |
| } |
|
|
| function createFleetService(deps) { |
| const { config, telemetryRepository } = deps; |
|
|
| async function getLatestSnapshot(tenantCode = null, managerUserId = null) { |
| const rows = await telemetryRepository.getLatestSnapshot(deps.pool, { |
| tenantCode, |
| limit: config.query.historyMaxLimit, |
| managerUserId, |
| }); |
|
|
| const items = rows.map(mapLatestRowToApi); |
| const byKey = {}; |
| for (const item of items) { |
| byKey[item.key] = item; |
| } |
|
|
| return { |
| count: items.length, |
| byKey, |
| items, |
| }; |
| } |
|
|
| async function getFleetSummary(tenantCode = null, managerUserId = null) { |
| const summary = await telemetryRepository.getFleetSummary( |
| deps.pool, |
| config.alerts.offlineThresholdMs, |
| tenantCode, |
| managerUserId |
| ); |
|
|
| return { |
| totalTrucks: Number(summary.total_trucks || 0), |
| onlineTrucks: Number(summary.online_trucks || 0), |
| activeAlerts: Number(summary.active_alerts || 0), |
| warningContainers: Number(summary.containers_in_warning || 0), |
| lastUpdateTime: summary.last_update_time, |
| countsBySeverity: { |
| INFO: Number(summary.info_alerts || 0), |
| WARNING: Number(summary.warning_alerts || 0), |
| CRITICAL: Number(summary.critical_alerts || 0), |
| }, |
| }; |
| } |
|
|
| async function getFleetUnits(tenantCode = null, managerUserId = null) { |
| const rows = await telemetryRepository.listFleetUnits( |
| deps.pool, |
| config.alerts.offlineThresholdMs, |
| tenantCode, |
| config.query.historyMaxLimit, |
| managerUserId |
| ); |
|
|
| return rows.map(mapLatestRowToApi); |
| } |
|
|
| async function getLatestForUnit( |
| truckCode, |
| containerCode, |
| tenantCode = null, |
| managerUserId = null |
| ) { |
| const row = await telemetryRepository.getLatestByCodes(deps.pool, { |
| tenantCode, |
| truckCode, |
| containerCode, |
| managerUserId, |
| }); |
|
|
| if (!row) { |
| throw new AppError("Telemetry not found for truck/container", 404); |
| } |
|
|
| return mapLatestRowToApi(row); |
| } |
|
|
| async function getHistoryForUnit(truckCode, containerCode, options) { |
| const rows = await telemetryRepository.getHistoryByCodes(deps.pool, { |
| tenantCode: options.tenantCode || null, |
| truckCode, |
| containerCode, |
| from: options.from, |
| to: options.to, |
| limit: options.limit, |
| interval: options.interval, |
| managerUserId: options.managerUserId || null, |
| }); |
|
|
| const items = rows.map((row) => { |
| if (row.bucket_at) { |
| return { |
| ts: row.bucket_at, |
| occurredAt: row.bucket_at, |
| env: { |
| temperatureC: row.temperature_c, |
| humidityPct: row.humidity_pct, |
| pressureHpa: row.pressure_hpa, |
| }, |
| gas: { |
| mq2Raw: row.gas_raw, |
| alert: row.gas_alert, |
| }, |
| motion: { |
| shock: row.shock, |
| tiltDeg: row.tilt_deg, |
| }, |
| gps: { |
| lat: row.gps_lat, |
| lon: row.gps_lon, |
| speedKph: row.speed_kph, |
| gpsFix: row.gps_lost === true ? false : true, |
| }, |
| sampleCount: row.sample_count, |
| }; |
| } |
|
|
| return { |
| ts: row.occurred_at, |
| occurredAt: row.occurred_at, |
| env: { |
| temperatureC: row.temperature_c, |
| humidityPct: row.humidity_pct, |
| pressureHpa: row.pressure_hpa, |
| }, |
| gas: { |
| mq2Raw: row.gas_raw, |
| alert: row.gas_alert, |
| }, |
| motion: { |
| shock: row.shock, |
| tiltDeg: row.tilt_deg, |
| }, |
| gps: { |
| lat: row.gps_lat, |
| lon: row.gps_lon, |
| speedKph: row.speed_kph, |
| gpsFix: row.gps_fix, |
| }, |
| uplink: row.uplink, |
| receivedAt: row.received_at, |
| }; |
| }); |
|
|
| return { |
| truckId: truckCode, |
| containerId: containerCode, |
| count: items.length, |
| bucketMinutes: options.bucketMinutes || null, |
| interval: options.interval || null, |
| from: options.from, |
| to: options.to, |
| items, |
| }; |
| } |
|
|
| return { |
| getLatestSnapshot, |
| getFleetSummary, |
| getFleetUnits, |
| getLatestForUnit, |
| getHistoryForUnit, |
| }; |
| } |
|
|
| module.exports = { |
| createFleetService, |
| }; |
|
|