cambios de desarrollo
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
const db = require('../config/db');
|
||||
const agheeraPushClient = require('../services/agheeraPushClient');
|
||||
|
||||
const AGHEERA_CLIENT_ID = 532;
|
||||
|
||||
const getDniFromLocation = (locationData) => {
|
||||
if (locationData?.extras?.alias) {
|
||||
@@ -70,6 +73,137 @@ const getTripIdFromLocation = (locationData) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const getRawTimestampFromLocation = (locationData) => {
|
||||
const candidates = [
|
||||
locationData?.timestamp,
|
||||
locationData?.location?.timestamp
|
||||
];
|
||||
|
||||
for (const value of candidates) {
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||
return String(value).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getPersistedTimestampFromLocation = (locationData) => {
|
||||
const rawTimestamp = getRawTimestampFromLocation(locationData);
|
||||
|
||||
if (rawTimestamp === null) {
|
||||
return {
|
||||
value: new Date(),
|
||||
usedFallback: true,
|
||||
reason: 'missing'
|
||||
};
|
||||
}
|
||||
|
||||
const parsedTimestamp = new Date(rawTimestamp);
|
||||
if (Number.isNaN(parsedTimestamp.getTime())) {
|
||||
return {
|
||||
value: new Date(),
|
||||
usedFallback: true,
|
||||
reason: 'invalid',
|
||||
rawTimestamp
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
value: parsedTimestamp,
|
||||
usedFallback: false,
|
||||
reason: null,
|
||||
rawTimestamp
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const pushLocationToAgheera = async ({ latitude, longitude, dni, tripId, measurementTime }) => {
|
||||
if (!tripId || !dni) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [tripRows] = await db.query(
|
||||
`SELECT id_cliente
|
||||
FROM c_viajes
|
||||
WHERE id_viaje = ?
|
||||
LIMIT 1`,
|
||||
[tripId]
|
||||
);
|
||||
|
||||
if (Number.parseInt(tripRows[0]?.id_cliente, 10) !== AGHEERA_CLIENT_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const baseResult = {
|
||||
trip_id: tripId,
|
||||
attempted: true,
|
||||
success: false,
|
||||
http_status: null,
|
||||
error: null
|
||||
};
|
||||
|
||||
const [authorizationRows] = await db.query(
|
||||
`SELECT id_tipovehiculo AS matricula
|
||||
FROM c_viajes_proveedor
|
||||
WHERE id_viaje = ?
|
||||
AND dni = ?
|
||||
ORDER BY n_proveedor ASC
|
||||
LIMIT 1`,
|
||||
[tripId, dni]
|
||||
);
|
||||
|
||||
const licensePlate = String(authorizationRows[0]?.matricula || '').trim();
|
||||
if (!licensePlate) {
|
||||
return {
|
||||
...baseResult,
|
||||
error: 'LICENSE_PLATE_NOT_FOUND'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await agheeraPushClient.pushPosition({
|
||||
latitude,
|
||||
longitude,
|
||||
vehicleId: licensePlate,
|
||||
licensePlate,
|
||||
measurementTime
|
||||
});
|
||||
|
||||
return {
|
||||
...baseResult,
|
||||
success: true,
|
||||
http_status: result?.status ?? null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Agheera push failed after location update:', {
|
||||
tripId,
|
||||
dni,
|
||||
message: error.message,
|
||||
status: error.status || null
|
||||
});
|
||||
|
||||
return {
|
||||
...baseResult,
|
||||
http_status: error.status || null,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const pushLocationsToAgheera = async (locationsToPush) => {
|
||||
const results = [];
|
||||
|
||||
for (const location of locationsToPush) {
|
||||
const result = await pushLocationToAgheera(location);
|
||||
if (result) {
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
const saveLocation = async (req, res) => {
|
||||
try {
|
||||
const data = req.body;
|
||||
@@ -100,8 +234,8 @@ const saveLocation = async (req, res) => {
|
||||
globalTripId = getTripIdFromLocation(data);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const rowsToInsert = [];
|
||||
const locationsToPush = [];
|
||||
|
||||
for (const loc of locations) {
|
||||
const coords = getCoordinatesFromLocation(loc);
|
||||
@@ -109,13 +243,32 @@ const saveLocation = async (req, res) => {
|
||||
const tripId = globalTripId !== null ? globalTripId : getTripIdFromLocation(loc);
|
||||
|
||||
if (coords && coords.lat !== undefined && coords.lat !== null && coords.lng !== undefined && coords.lng !== null) {
|
||||
const persistedTimestamp = getPersistedTimestampFromLocation(loc);
|
||||
|
||||
if (persistedTimestamp.usedFallback) {
|
||||
console.warn('Location timestamp fallback applied:', {
|
||||
reason: persistedTimestamp.reason,
|
||||
rawTimestamp: persistedTimestamp.rawTimestamp || null,
|
||||
uuid: loc?.uuid || loc?.location?.uuid || null,
|
||||
tripId,
|
||||
dni: dni || null
|
||||
});
|
||||
}
|
||||
|
||||
rowsToInsert.push([
|
||||
String(coords.lat),
|
||||
String(coords.lng),
|
||||
dni || null,
|
||||
now,
|
||||
persistedTimestamp.value,
|
||||
tripId
|
||||
]);
|
||||
locationsToPush.push({
|
||||
latitude: coords.lat,
|
||||
longitude: coords.lng,
|
||||
dni,
|
||||
tripId,
|
||||
measurementTime: persistedTimestamp.value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,17 +287,24 @@ const saveLocation = async (req, res) => {
|
||||
[rowsToInsert]
|
||||
);
|
||||
|
||||
return res.json({
|
||||
const agheeraResults = await pushLocationsToAgheera(locationsToPush);
|
||||
const responseBody = {
|
||||
success: true,
|
||||
count: rowsToInsert.length,
|
||||
message: 'Locations saved'
|
||||
});
|
||||
};
|
||||
|
||||
if (agheeraResults.length > 0) {
|
||||
responseBody.agheera_push = agheeraResults.length === 1 ? agheeraResults[0] : agheeraResults;
|
||||
}
|
||||
|
||||
return res.json(responseBody);
|
||||
} catch (error) {
|
||||
console.error('Error saving location:', error);
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
saveLocation
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
saveLocation
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ const db = require('../config/db');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const tripIncidenceMailer = require('../services/tripIncidenceMailer');
|
||||
const agheeraPushClient = require('../services/agheeraPushClient');
|
||||
const {
|
||||
collectUploadedTripStatusFiles,
|
||||
removeUploadedTripStatusFiles,
|
||||
@@ -23,6 +24,7 @@ const INTERMEDIATE_POINT_ALLOWED_STATES = [3, 4, 5];
|
||||
const INTERMEDIATE_POINT_ALLOWED_STATES_SET = new Set(INTERMEDIATE_POINT_ALLOWED_STATES);
|
||||
const SQL_DATETIME_REGEX = /^(\d{4})-(\d{2})-(\d{2}) ([0-2]\d):([0-5]\d):([0-5]\d)$/;
|
||||
const FAILED_TRIP_STATE = 9;
|
||||
const AGHEERA_CLIENT_ID = 532;
|
||||
const INTERMEDIATE_POINT_STATUS_IDS = new Set([3, 4, 5]);
|
||||
const INCIDENCE_TEXT_CONTROL_CHARACTERS_REGEX = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
|
||||
const GLOBAL_STATUS_KEYS_BY_STATE_ID = new Map([
|
||||
@@ -238,6 +240,85 @@ const normalizeCoordinateValue = (rawValue) => {
|
||||
return String(parsedNumericValue);
|
||||
};
|
||||
|
||||
const pushTripStatusPositionToAgheera = async ({
|
||||
tripRow,
|
||||
authorizationRow,
|
||||
latitud,
|
||||
longitud,
|
||||
measurementTime,
|
||||
tripId,
|
||||
requestId,
|
||||
flow
|
||||
}) => {
|
||||
const clientId = Number.parseInt(tripRow?.id_cliente, 10);
|
||||
if (clientId !== AGHEERA_CLIENT_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const baseResult = {
|
||||
trip_id: tripId,
|
||||
attempted: false,
|
||||
success: false,
|
||||
http_status: null,
|
||||
error: null
|
||||
};
|
||||
|
||||
if (latitud === null || longitud === null) {
|
||||
return {
|
||||
...baseResult,
|
||||
error: 'COORDINATES_MISSING'
|
||||
};
|
||||
}
|
||||
|
||||
const licensePlate = String(authorizationRow?.matricula || '').trim();
|
||||
if (!licensePlate) {
|
||||
return {
|
||||
...baseResult,
|
||||
error: 'LICENSE_PLATE_NOT_FOUND'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await agheeraPushClient.pushPosition({
|
||||
latitude: latitud,
|
||||
longitude: longitud,
|
||||
vehicleId: licensePlate,
|
||||
licensePlate,
|
||||
measurementTime
|
||||
});
|
||||
|
||||
appendTripStatusDebugLog({
|
||||
stage: 'update_trip_status:agheera_push_success',
|
||||
request_id: requestId,
|
||||
flow,
|
||||
trip_id: tripId,
|
||||
http_status: result?.status ?? null
|
||||
});
|
||||
|
||||
return {
|
||||
...baseResult,
|
||||
attempted: true,
|
||||
success: true,
|
||||
http_status: result?.status ?? null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Agheera push failed after trip status update:', {
|
||||
tripId,
|
||||
requestId,
|
||||
flow,
|
||||
message: error.message,
|
||||
status: error.status || null
|
||||
});
|
||||
|
||||
return {
|
||||
...baseResult,
|
||||
attempted: true,
|
||||
http_status: error.status || null,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeSqlDateTimeValue = (rawValue) => {
|
||||
if (typeof rawValue !== 'string') {
|
||||
return null;
|
||||
@@ -1420,7 +1501,7 @@ const updateTripStatus = async (req, res) => {
|
||||
await connection.beginTransaction();
|
||||
|
||||
const [tripRows] = await connection.query(
|
||||
`SELECT id_viaje, id_estado, id_viaje_padre
|
||||
`SELECT id_viaje, id_estado, id_viaje_padre, id_cliente
|
||||
FROM c_viajes
|
||||
WHERE id_viaje = ?
|
||||
LIMIT 1
|
||||
@@ -1453,7 +1534,7 @@ const updateTripStatus = async (req, res) => {
|
||||
}
|
||||
|
||||
const [authorizationRows] = await connection.query(
|
||||
`SELECT n_proveedor, id_proveedor
|
||||
`SELECT n_proveedor, id_proveedor, id_tipovehiculo AS matricula
|
||||
FROM c_viajes_proveedor
|
||||
WHERE id_viaje = ?
|
||||
AND dni = ?
|
||||
@@ -1716,8 +1797,18 @@ const updateTripStatus = async (req, res) => {
|
||||
idEstadoLogged: FAILED_TRIP_STATE,
|
||||
idPuntoLogged: idPunto
|
||||
});
|
||||
const agheeraPushResult = await pushTripStatusPositionToAgheera({
|
||||
tripRow: tripRows[0],
|
||||
authorizationRow: authorizationRows[0],
|
||||
latitud,
|
||||
longitud,
|
||||
measurementTime: now,
|
||||
tripId,
|
||||
requestId,
|
||||
flow: 'failed_branch_manual'
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
const responseBody = {
|
||||
success: true,
|
||||
trip_id: tripId,
|
||||
updated_status_id: idEstado,
|
||||
@@ -1729,7 +1820,13 @@ const updateTripStatus = async (req, res) => {
|
||||
failed_marked: true,
|
||||
fotos_concat: fotosConcat || '',
|
||||
updated_at: now.toISOString()
|
||||
});
|
||||
};
|
||||
|
||||
if (agheeraPushResult) {
|
||||
responseBody.agheera_push = agheeraPushResult;
|
||||
}
|
||||
|
||||
return res.status(200).json(responseBody);
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
try {
|
||||
@@ -1752,7 +1849,7 @@ const updateTripStatus = async (req, res) => {
|
||||
}
|
||||
|
||||
const [tripRows] = await db.query(
|
||||
`SELECT id_viaje, id_viaje_padre
|
||||
`SELECT id_viaje, id_viaje_padre, id_cliente
|
||||
FROM c_viajes
|
||||
WHERE id_viaje = ?
|
||||
LIMIT 1`,
|
||||
@@ -1783,7 +1880,7 @@ const updateTripStatus = async (req, res) => {
|
||||
}
|
||||
|
||||
const [authorizationRows] = await db.query(
|
||||
`SELECT n_proveedor, id_proveedor
|
||||
`SELECT n_proveedor, id_proveedor, id_tipovehiculo AS matricula
|
||||
FROM c_viajes_proveedor
|
||||
WHERE id_viaje = ?
|
||||
AND dni = ?
|
||||
@@ -1912,8 +2009,18 @@ const updateTripStatus = async (req, res) => {
|
||||
idEstadoLogged: idEstado,
|
||||
idPuntoLogged: idPunto
|
||||
});
|
||||
const agheeraPushResult = await pushTripStatusPositionToAgheera({
|
||||
tripRow: tripRows[0],
|
||||
authorizationRow: authorizationRows[0],
|
||||
latitud,
|
||||
longitud,
|
||||
measurementTime: now,
|
||||
tripId,
|
||||
requestId,
|
||||
flow: 'normal_manual'
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
const responseBody = {
|
||||
success: true,
|
||||
trip_id: tripId,
|
||||
updated_status_id: idEstado,
|
||||
@@ -1925,7 +2032,13 @@ const updateTripStatus = async (req, res) => {
|
||||
failed_marked: false,
|
||||
fotos_concat: fotosConcat || '',
|
||||
updated_at: now.toISOString()
|
||||
});
|
||||
};
|
||||
|
||||
if (agheeraPushResult) {
|
||||
responseBody.agheera_push = agheeraPushResult;
|
||||
}
|
||||
|
||||
return res.status(200).json(responseBody);
|
||||
} catch (error) {
|
||||
const parsedTripId = Number.parseInt(req.params?.id, 10);
|
||||
const parsedStatusId = Number.parseInt(req.body?.id_estado, 10);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
const DEFAULT_AGHEERA_PUSH_URL = 'https://push-test.agheera.com/Telematics/Positions';
|
||||
|
||||
let httpClientOverride = null;
|
||||
|
||||
const getPushUrl = () =>
|
||||
String(process.env.AGHEERA_PUSH_URL || DEFAULT_AGHEERA_PUSH_URL).trim();
|
||||
|
||||
const getApiKey = () =>
|
||||
String(process.env.AGHEERA_API_KEY || '').trim();
|
||||
|
||||
const formatMeasurementTime = (dateValue) => {
|
||||
const date = dateValue instanceof Date ? dateValue : new Date(dateValue);
|
||||
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
||||
};
|
||||
|
||||
const normalizeNumber = (rawValue) => {
|
||||
const numericValue = Number(rawValue);
|
||||
return Number.isFinite(numericValue) ? numericValue : null;
|
||||
};
|
||||
|
||||
const getHttpClient = () => {
|
||||
if (typeof httpClientOverride === 'function') {
|
||||
return httpClientOverride;
|
||||
}
|
||||
|
||||
if (typeof fetch === 'function') {
|
||||
return fetch;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const buildPositionPayload = ({
|
||||
latitude,
|
||||
longitude,
|
||||
vehicleId,
|
||||
licensePlate,
|
||||
measurementTime
|
||||
}) => ({
|
||||
Vehicles: [
|
||||
{
|
||||
latitude: normalizeNumber(latitude),
|
||||
longitude: normalizeNumber(longitude),
|
||||
vehicleId: String(vehicleId || '').trim(),
|
||||
licensePlate: String(licensePlate || '').trim(),
|
||||
measurementTime: formatMeasurementTime(measurementTime)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const pushPosition = async ({
|
||||
latitude,
|
||||
longitude,
|
||||
vehicleId,
|
||||
licensePlate,
|
||||
measurementTime
|
||||
}) => {
|
||||
const httpClient = getHttpClient();
|
||||
if (!httpClient) {
|
||||
throw new Error('Agheera HTTP client unavailable');
|
||||
}
|
||||
|
||||
const apiKey = getApiKey();
|
||||
if (!apiKey) {
|
||||
throw new Error('Agheera API key missing');
|
||||
}
|
||||
|
||||
const url = getPushUrl();
|
||||
const payload = buildPositionPayload({
|
||||
latitude,
|
||||
longitude,
|
||||
vehicleId,
|
||||
licensePlate,
|
||||
measurementTime
|
||||
});
|
||||
|
||||
const response = await httpClient(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
apiKey,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const responseBody = typeof response?.text === 'function' ? await response.text() : '';
|
||||
if (!response?.ok) {
|
||||
const error = new Error('Agheera push failed');
|
||||
error.status = response?.status || null;
|
||||
error.body = responseBody;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
body: responseBody
|
||||
};
|
||||
};
|
||||
|
||||
const __setHttpClientForTests = (httpClient) => {
|
||||
httpClientOverride = httpClient;
|
||||
};
|
||||
|
||||
const __resetHttpClientForTests = () => {
|
||||
httpClientOverride = null;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_AGHEERA_PUSH_URL,
|
||||
pushPosition,
|
||||
__setHttpClientForTests,
|
||||
__resetHttpClientForTests
|
||||
};
|
||||
Reference in New Issue
Block a user