Compare commits
No commits in common. "12364bcb44963504fc123e82af50a667ca6bc4f7" and "5828395907af28b49171410088c4df166a4c0f97" have entirely different histories.
12364bcb44
...
5828395907
@ -1,11 +1,11 @@
|
|||||||
PORT=3001
|
PORT=3001
|
||||||
DB_HOST=localhost
|
DB_HOST=194.164.175.51
|
||||||
DB_USER=roganet
|
DB_USER=roganet
|
||||||
DB_PASSWORD=bdIRGLnet2905*/
|
DB_PASSWORD=bdIRGLnet2905*/
|
||||||
DB_NAME=abian_app_produccion
|
DB_NAME=abian_app_produccion
|
||||||
JWT_SECRET=9c64f2727d53bfefaaa17a5fda5009ffe93cae904860c659bd18d2d14ad6b467
|
JWT_SECRET=9c64f2727d53bfefaaa17a5fda5009ffe93cae904860c659bd18d2d14ad6b467
|
||||||
|
|
||||||
DB_HOST_P = localhost
|
DB_HOST_P = 194.164.175.51
|
||||||
DB_USER_P = roganet
|
DB_USER_P = roganet
|
||||||
DB_PASSWORD_P = bdIRGLnet2905*/
|
DB_PASSWORD_P = bdIRGLnet2905*/
|
||||||
DB_NAME_P = abian_app_produccion
|
DB_NAME_P = abian_app_produccion
|
||||||
@ -26,7 +26,7 @@ DRIVER_LICENSE_RETENTION_DAYS=365
|
|||||||
|
|
||||||
#Carga de fotos dual
|
#Carga de fotos dual
|
||||||
TRIP_STATUS_PHOTO_STORAGE_MODE=local
|
TRIP_STATUS_PHOTO_STORAGE_MODE=local
|
||||||
TRIP_STATUS_SFTP_HOST=localhost
|
TRIP_STATUS_SFTP_HOST=194.164.175.51
|
||||||
TRIP_STATUS_SFTP_PORT=22
|
TRIP_STATUS_SFTP_PORT=22
|
||||||
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
||||||
TRIP_STATUS_SFTP_PASSWORD=IZYj%c0FiIlCc@rI%W0Z
|
TRIP_STATUS_SFTP_PASSWORD=IZYj%c0FiIlCc@rI%W0Z
|
||||||
|
|||||||
@ -96,7 +96,7 @@ Ejemplo de modo temporal dual:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
TRIP_STATUS_PHOTO_STORAGE_MODE=dual
|
TRIP_STATUS_PHOTO_STORAGE_MODE=dual
|
||||||
TRIP_STATUS_SFTP_HOST=localhost
|
TRIP_STATUS_SFTP_HOST=194.164.175.51
|
||||||
TRIP_STATUS_SFTP_PORT=22
|
TRIP_STATUS_SFTP_PORT=22
|
||||||
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
||||||
TRIP_STATUS_SFTP_PASSWORD=********
|
TRIP_STATUS_SFTP_PASSWORD=********
|
||||||
|
|||||||
@ -96,7 +96,7 @@ Ejemplo de modo temporal dual:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
TRIP_STATUS_PHOTO_STORAGE_MODE=dual
|
TRIP_STATUS_PHOTO_STORAGE_MODE=dual
|
||||||
TRIP_STATUS_SFTP_HOST=localhost
|
TRIP_STATUS_SFTP_HOST=194.164.175.51
|
||||||
TRIP_STATUS_SFTP_PORT=22
|
TRIP_STATUS_SFTP_PORT=22
|
||||||
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado
|
||||||
TRIP_STATUS_SFTP_PASSWORD=********
|
TRIP_STATUS_SFTP_PASSWORD=********
|
||||||
|
|||||||
12
app.js
12
app.js
@ -13,7 +13,6 @@ dotenv.config({ path: path.resolve(__dirname, '.env'), override: true });
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3001;
|
const PORT = process.env.PORT || 3001;
|
||||||
const HOST = process.env.HOST || "127.0.0.1";
|
|
||||||
const uploadsDir = path.resolve(__dirname, 'uploads');
|
const uploadsDir = path.resolve(__dirname, 'uploads');
|
||||||
|
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', 1);
|
||||||
@ -89,17 +88,6 @@ const limiter = rateLimit({
|
|||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
message: "Too many requests from this IP, please try again after 15 minutes"
|
message: "Too many requests from this IP, please try again after 15 minutes"
|
||||||
});
|
});
|
||||||
app.get(["/health", "/healthcheck"], (req, res) => {
|
|
||||||
res.set("Cache-Control", "no-store");
|
|
||||||
res.status(200).json({
|
|
||||||
status: "ok",
|
|
||||||
service: "node-gestion-api",
|
|
||||||
pid: process.pid,
|
|
||||||
uptime_seconds: Math.floor(process.uptime()),
|
|
||||||
timestamp_utc: new Date().toISOString()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(limiter);
|
app.use(limiter);
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
|
|||||||
Binary file not shown.
@ -1,191 +0,0 @@
|
|||||||
<?php
|
|
||||||
header('Access-Control-Allow-Origin: *');
|
|
||||||
include("mysql.php");
|
|
||||||
|
|
||||||
$id_viaje = $_POST['id_viaje'];
|
|
||||||
$n_proveedor = $_POST['n_proveedor'];
|
|
||||||
$usuario = $_POST['usuario'];
|
|
||||||
$id_estado = $_POST['id_estado'];
|
|
||||||
$incidencia = $_POST['incidencia'];
|
|
||||||
$latitud = $_POST['latitud'];
|
|
||||||
$longitud = $_POST['longitud'];
|
|
||||||
$nombre = $_POST['nombre'] . '.jpg';
|
|
||||||
|
|
||||||
if ($nombre == "vacio.jpg") {
|
|
||||||
$nombre = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
$consulta = "SELECT count(*) from c_cambios_estado where id_viaje='" . $id_viaje . "' and id_estado='" . $id_estado . "' and foto='" . $nombre . "'";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $consulta) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
|
||||||
$fila = mysqli_fetch_row($rResult2);
|
|
||||||
$duplicado = $fila[0];
|
|
||||||
|
|
||||||
if ($duplicado > 0) {
|
|
||||||
echo json_encode("0");
|
|
||||||
die();
|
|
||||||
}
|
|
||||||
|
|
||||||
$sQuery2 = "UPDATE c_viajes SET id_estado = $id_estado, ind_edi_app = 1 WHERE id_viaje = $id_viaje";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $sQuery2) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
|
||||||
|
|
||||||
$sQuery2 = "INSERT INTO c_cambios_estado (id_viaje, n_proveedor, id_transportista, id_estado, incidencia, latitud, longitud, fecha_y_hora, foto) VALUES ('" . $id_viaje . "','" . $n_proveedor . "','" . $usuario . "','" . $id_estado . "','" . $incidencia . "','" . $latitud . "','" . $longitud . "',NOW(),'" . $nombre . "')";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $sQuery2) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
|
||||||
|
|
||||||
$consulta = "SELECT id_viaje_padre, id_cliente, cod_viaje from c_viajes where id_viaje='" . $id_viaje . "'";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $consulta) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
|
||||||
$fila = mysqli_fetch_row($rResult2);
|
|
||||||
$id_viaje_padre = $fila[0];
|
|
||||||
$id_cliente = $fila[1];
|
|
||||||
$cod_viaje = $fila[2];
|
|
||||||
|
|
||||||
while ($id_viaje_padre > 0) {
|
|
||||||
$consulta = "UPDATE `c_viajes` SET id_estado='" . $id_estado . "', ind_edi_app = 1 where id_viaje='" . $id_viaje_padre . "'";
|
|
||||||
$query_res = mysqli_query($gaSql['link'], $consulta);
|
|
||||||
|
|
||||||
$consulta = "INSERT INTO c_cambios_estado (id_viaje, n_proveedor, id_transportista, id_estado, incidencia, latitud, longitud, fecha_y_hora, foto) VALUES ('" . $id_viaje . "','" . $n_proveedor . "','" . $usuario . "','" . $id_estado . "','" . $incidencia . "','" . $latitud . "','" . $longitud . "',NOW(),'" . $nombre . "')";
|
|
||||||
$query_res = mysqli_query($gaSql['link'], $consulta);
|
|
||||||
|
|
||||||
//vover consultar para ver si se sale del bucle
|
|
||||||
$consulta = "SELECT id_viaje_padre, id_cr from c_viajes where id_viaje='" . $id_viaje_padre . "'";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $consulta) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
|
||||||
$fila = mysqli_fetch_row($rResult2);
|
|
||||||
$id_viaje_padre = $fila[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
$resultado = "0";
|
|
||||||
|
|
||||||
$consulta = "SELECT html FROM html_correo WHERE id_mensaje = 34";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $consulta);
|
|
||||||
$fila = mysqli_fetch_row($rResult2);
|
|
||||||
$mensaje = $fila[0];
|
|
||||||
|
|
||||||
$consulta = "SELECT user_smtp_admin, user_smtp_admin, pass_smtp_admin, host_smtp, puerto_smtp, date_format(NOW(), '%Y-%m-%d_%H_%i_%s') as ahora, email_operaciones FROM m_cr WHERE id_cr = $cr";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $consulta);
|
|
||||||
$fila = mysqli_fetch_row($rResult2);
|
|
||||||
$email_operaciones = $fila[0];
|
|
||||||
$user_smtp = $fila[1];
|
|
||||||
$pass_smtp = $fila[2];
|
|
||||||
$host_smtp = $fila[3];
|
|
||||||
$puerto_smtp = $fila[4];
|
|
||||||
$ahora = $fila[5];
|
|
||||||
$email_operaciones = $fila[6];
|
|
||||||
|
|
||||||
$consulta = "SELECT estado FROM t_viaje_estados WHERE id_estado = $id_estado";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $consulta);
|
|
||||||
$fila = mysqli_fetch_row($rResult2);
|
|
||||||
$estado = $fila[0];
|
|
||||||
|
|
||||||
if (strlen($user_smtp) > 4 && strlen($pass_smtp) > 4 && strlen($host_smtp) > 4) {
|
|
||||||
|
|
||||||
$mail = new PHPMailer();
|
|
||||||
$mail->IsSMTP();
|
|
||||||
$mail->From = $user_smtp;
|
|
||||||
$mail->FromName = "Abian Service";
|
|
||||||
|
|
||||||
$html = '<br>Se ha cambiado el estado del viaje ' . $cod_viaje . ' a ' . $estado . ' mediante la APP';
|
|
||||||
|
|
||||||
$mail->AddAddress($email_operaciones);
|
|
||||||
$mail->SMTPAuth = true;
|
|
||||||
$mail->SMTPSecure = 'tls';
|
|
||||||
$mail->Host = $host_smtp;
|
|
||||||
$mail->Port = $puerto_smtp;
|
|
||||||
$mail->Username = $user_smtp;
|
|
||||||
$mail->Password = $pass_smtp;
|
|
||||||
$mail->CharSet = 'UTF-8';
|
|
||||||
$mail->Subject = "Facturas pendientes Abian Service";
|
|
||||||
$mail->Body = $mensaje . "<br>" . $html;
|
|
||||||
$mail->IsHTML(true);
|
|
||||||
$mail->SMTPDebug = 0;
|
|
||||||
|
|
||||||
if ($mail->Send()) {
|
|
||||||
echo json_encode("0");
|
|
||||||
} else {
|
|
||||||
echo json_encode("Error al enviar el correo");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
echo json_encode("Error al enviar el correo: fallo con usuario/contraseña");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($id_cliente == 532) {
|
|
||||||
$consulta = "SELECT id_tipovehiculo AS matricula FROM c_viajes_proveedor WHERE id_viaje = $id_viaje AND n_proveedor = $n_proveedor";
|
|
||||||
$rResult2 = mysqli_query($gaSql['link'], $consulta) or fatal_error('MySQL Error: ' . mysqli_errno($gaSql['link']));
|
|
||||||
$fila = mysqli_fetch_row($rResult2);
|
|
||||||
$matricula = $fila[0];
|
|
||||||
|
|
||||||
$xml = '
|
|
||||||
<S:Envelope
|
|
||||||
xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
|
|
||||||
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
|
|
||||||
<SOAP-ENV:Header/>
|
|
||||||
<S:Body>
|
|
||||||
<ns2:SubmitPosition xmlns="http://schemas.datacontract.org/2004/07/QESE.QFV.Classes.QAP"
|
|
||||||
xmlns:ns2="QESE.QFV.QAP" xmlns:ns3="http://schemas.datacontract.org/2004/07/System"
|
|
||||||
xmlns:ns4="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
|
|
||||||
xmlns:ns5="http://schemas.datacontract.org/2004/07/QESE.QFV.Classes"
|
|
||||||
xmlns:ns6="http://schemas.datacontract.org/2004/07/FV.Enums"
|
|
||||||
xmlns:ns7="http://schemas.datacontract.org/2004/07/FV.Enums.DriverHours"
|
|
||||||
xmlns:ns8="http://schemas.microsoft.com/2003/10/Serialization/">
|
|
||||||
<ns2:credentials>
|
|
||||||
<Client>abian</Client>
|
|
||||||
<Password>TVwuW57u0^</Password>
|
|
||||||
<UserName>abian-5567</UserName>
|
|
||||||
<Version>1</Version>
|
|
||||||
</ns2:credentials>
|
|
||||||
<ns2:positions>
|
|
||||||
<Position>
|
|
||||||
<AssetCategory>1</AssetCategory>
|
|
||||||
<CID>5567</CID>
|
|
||||||
<CustomerName/>
|
|
||||||
<DT>
|
|
||||||
<ns3:DateTime>?fecha_hora?</ns3:DateTime>
|
|
||||||
<ns3:OffsetMinutes>0</ns3:OffsetMinutes>
|
|
||||||
</DT>
|
|
||||||
<DeviceType>14</DeviceType>
|
|
||||||
<From>' . $matricula . '</From>
|
|
||||||
<GatewayMsgId/>
|
|
||||||
<IdentifierType>12</IdentifierType>
|
|
||||||
<MSISDN>' . $matricula . '</MSISDN>
|
|
||||||
<RequestId>1</RequestId>
|
|
||||||
<Version>1</Version>
|
|
||||||
<Latitude>?latitud?</Latitude>
|
|
||||||
<Longitude>?longitud?</Longitude>
|
|
||||||
</Position>
|
|
||||||
</ns2:positions>
|
|
||||||
</ns2:SubmitPosition>
|
|
||||||
</S:Body>
|
|
||||||
</S:Envelope>';
|
|
||||||
|
|
||||||
$url = 'https://export.fleetvisor.eu/wsQAP/Positions.svc/ssl';
|
|
||||||
$str = date("Y-m-d");
|
|
||||||
$strb = date("H:i:s");
|
|
||||||
|
|
||||||
$fecha_hora = $str . "T" . $strb . "Z";
|
|
||||||
|
|
||||||
$xml = str_replace("?fecha_hora?", $fecha_hora, $xml);
|
|
||||||
$xml = str_replace("?latitud?", $latitud, $xml);
|
|
||||||
$xml = str_replace("?longitud?", $longitud, $xml);
|
|
||||||
|
|
||||||
//echo $xml;
|
|
||||||
|
|
||||||
$curl = curl_init();
|
|
||||||
curl_setopt($curl, CURLOPT_URL, $url);
|
|
||||||
curl_setopt($curl, CURLOPT_POST, true);
|
|
||||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
|
|
||||||
$headers = array(
|
|
||||||
"Content-Type: text/xml",
|
|
||||||
"SOAPAction: QESE.QFV.QAP/PositionsService/SubmitPosition"
|
|
||||||
);
|
|
||||||
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
|
||||||
|
|
||||||
$data = $xml;
|
|
||||||
|
|
||||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
|
||||||
|
|
||||||
$resp = curl_exec($curl);
|
|
||||||
curl_close($curl);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($resultado);
|
|
||||||
?>
|
|
||||||
109
docu/correo.txt
109
docu/correo.txt
@ -1,109 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
Dear Gorka,
|
|
||||||
thanks for your reply!
|
|
||||||
|
|
||||||
We have a standard API that we provide openly - please see the attached documentation for more details.
|
|
||||||
The attached document also includes the URL and apikey for our TEST instance.
|
|
||||||
|
|
||||||
- endpoint URL for test and production
|
|
||||||
|
|
||||||
PROD URL: https://push-dhl.agheera.com/Telematics/positions
|
|
||||||
apiKey for Abian: 2NL7G-0QTMP-T0N54-7ERGY-S7U6U-SXKWF
|
|
||||||
TEST URL: https://push-test.agheera.com/Telematics/Positions
|
|
||||||
apiKey for testing purposes: 0eec610a-aad5-4f60-91c7-7f9a4089d5b5
|
|
||||||
|
|
||||||
- authentication method and credentials process
|
|
||||||
Authentication is done by sending an apikey in a HTTP header "apiKey" with each request.
|
|
||||||
There is no further handshake (oauth flow or similar). If the apikey is correct and accepted, data will be assigned to the customer (in this case Abian)
|
|
||||||
Apikey is provided by Agheera on request.
|
|
||||||
1 Apikey is bound to 1 customer and 1 environment (prod/test).
|
|
||||||
|
|
||||||
- required payload format
|
|
||||||
Please see attached documentation for more details.
|
|
||||||
We encourage to send data batches whenever possible.
|
|
||||||
|
|
||||||
- required vehicle identifier: license plate, device ID, MSISDN, etc.
|
|
||||||
Device ID and License Plate shall both be provided
|
|
||||||
|
|
||||||
- timestamp format/timezone
|
|
||||||
Must be in UTC timezone
|
|
||||||
Format: yyyy-MM-ddTHH:mm:ssZ
|
|
||||||
|
|
||||||
- required fields for position updates
|
|
||||||
Minimum required are: latitude, longitude, vehicleid(deviceid), licensePlate, measurementTime
|
|
||||||
We appreciate the following fields to have better Trip ETA calculation: speed, direction, assetType
|
|
||||||
|
|
||||||
- expected response codes
|
|
||||||
200 - OK
|
|
||||||
If data was accepted and was syntactically correct
|
|
||||||
400 - Bad request
|
|
||||||
If data was syntactically wrong/malformed
|
|
||||||
401 - Unauthorized
|
|
||||||
Wrong apikey or apikey header missing
|
|
||||||
405 - Method not available
|
|
||||||
If request method was not POST
|
|
||||||
|
|
||||||
- retry/error handling recommendations
|
|
||||||
We are not validating the semantics of position data at the point of response generation, so we return 200 - OK on almost all requests unless the request is badly malformed or unauthorized.
|
|
||||||
This also means a retry is not very likely needed, unless the Agheera server is down.
|
|
||||||
For sending position data to us, it is fine to "fire and forget" and not schedule a retry.
|
|
||||||
In case you notice any errors, please contact ops@agheera.com and we will investigate.
|
|
||||||
|
|
||||||
- whether positions should be sent only for active DHL trips/customer id 532 or for all authorized vehicles
|
|
||||||
Agheera does already filter the data so that DHL can only see data that is relevant for DHL trips.
|
|
||||||
So as far as we are concerned, you can send for all vehicles, and we will filter it anyway.
|
|
||||||
But I think this should be decided by ABIAN if they want this.
|
|
||||||
|
|
||||||
- any IP allowlist requirements
|
|
||||||
No IP whitelist in place. We do not need to know your IPs upfront.
|
|
||||||
|
|
||||||
|
|
||||||
Let me know if you have any open questions!
|
|
||||||
|
|
||||||
Kind Regards / Freundliche Grüße
|
|
||||||
Stephan Wahlen
|
|
||||||
Head of IoT Hardware
|
|
||||||
Agheera GmbH – a DHL Group company
|
|
||||||
|
|
||||||
stephan.wahlen@agheera.com
|
|
||||||
+49 2203 29757-23
|
|
||||||
Office: August-Horch-Straße 5, 51149 Köln
|
|
||||||
Warehouse: Kasinostraße 24, 53840 Troisdorf, Deutschland
|
|
||||||
Registered office Cologne; Register court Bonn; HRB 18111
|
|
||||||
VAT ID no. DE 273 231 181
|
|
||||||
Managing Directors: Pierre Lynch, Sven Kefferpütz
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-----Ursprüngliche Nachricht-----
|
|
||||||
Von: Gorka Leceta <gleceta@roganet.es>
|
|
||||||
Gesendet: Donnerstag, 28. Mai 2026 11:52
|
|
||||||
An: Operations <ops@agheera.com>; Stephan Wahlen <stephan.wahlen@agheera.com>
|
|
||||||
Betreff: ABIAN SERVICE - Agheera Push API documentation request for GPS position integration
|
|
||||||
|
|
||||||
Hello Stephan / Agheera team,
|
|
||||||
|
|
||||||
We are Roganet, GPS provider for ABIAN SERVICE.
|
|
||||||
|
|
||||||
ABIAN has asked us to integrate active position pushing from their mobile app backend to Agheera for DHL transports.
|
|
||||||
|
|
||||||
Could you please send us the technical documentation for Agheera's push API?
|
|
||||||
|
|
||||||
We need:
|
|
||||||
- endpoint URL for test and production
|
|
||||||
- authentication method and credentials process
|
|
||||||
- required payload format
|
|
||||||
- required vehicle identifier: license plate, device ID, MSISDN, etc.
|
|
||||||
- timestamp format/timezone
|
|
||||||
- required fields for position updates
|
|
||||||
- expected response codes
|
|
||||||
- retry/error handling recommendations
|
|
||||||
- whether positions should be sent only for active DHL trips/customer id
|
|
||||||
532 or for all authorized vehicles
|
|
||||||
- any IP allowlist requirements
|
|
||||||
|
|
||||||
ABIAN registration was submitted on 2024-01-23 and the authorization document is AFTemplateV2-ABIAN SERVICE_2024-01-23T11_55_17Z.PDF.
|
|
||||||
|
|
||||||
Kind regards
|
|
||||||
|
|
||||||
@ -1,7 +1,4 @@
|
|||||||
const db = require('../config/db');
|
const db = require('../config/db');
|
||||||
const agheeraPushClient = require('../services/agheeraPushClient');
|
|
||||||
|
|
||||||
const AGHEERA_CLIENT_ID = 532;
|
|
||||||
|
|
||||||
const getDniFromLocation = (locationData) => {
|
const getDniFromLocation = (locationData) => {
|
||||||
if (locationData?.extras?.alias) {
|
if (locationData?.extras?.alias) {
|
||||||
@ -73,137 +70,6 @@ const getTripIdFromLocation = (locationData) => {
|
|||||||
return null;
|
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) => {
|
const saveLocation = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
@ -234,8 +100,8 @@ const saveLocation = async (req, res) => {
|
|||||||
globalTripId = getTripIdFromLocation(data);
|
globalTripId = getTripIdFromLocation(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
const rowsToInsert = [];
|
const rowsToInsert = [];
|
||||||
const locationsToPush = [];
|
|
||||||
|
|
||||||
for (const loc of locations) {
|
for (const loc of locations) {
|
||||||
const coords = getCoordinatesFromLocation(loc);
|
const coords = getCoordinatesFromLocation(loc);
|
||||||
@ -243,32 +109,13 @@ const saveLocation = async (req, res) => {
|
|||||||
const tripId = globalTripId !== null ? globalTripId : getTripIdFromLocation(loc);
|
const tripId = globalTripId !== null ? globalTripId : getTripIdFromLocation(loc);
|
||||||
|
|
||||||
if (coords && coords.lat !== undefined && coords.lat !== null && coords.lng !== undefined && coords.lng !== null) {
|
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([
|
rowsToInsert.push([
|
||||||
String(coords.lat),
|
String(coords.lat),
|
||||||
String(coords.lng),
|
String(coords.lng),
|
||||||
dni || null,
|
dni || null,
|
||||||
persistedTimestamp.value,
|
now,
|
||||||
tripId
|
tripId
|
||||||
]);
|
]);
|
||||||
locationsToPush.push({
|
|
||||||
latitude: coords.lat,
|
|
||||||
longitude: coords.lng,
|
|
||||||
dni,
|
|
||||||
tripId,
|
|
||||||
measurementTime: persistedTimestamp.value
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,24 +134,17 @@ const saveLocation = async (req, res) => {
|
|||||||
[rowsToInsert]
|
[rowsToInsert]
|
||||||
);
|
);
|
||||||
|
|
||||||
const agheeraResults = await pushLocationsToAgheera(locationsToPush);
|
return res.json({
|
||||||
const responseBody = {
|
|
||||||
success: true,
|
success: true,
|
||||||
count: rowsToInsert.length,
|
count: rowsToInsert.length,
|
||||||
message: 'Locations saved'
|
message: 'Locations saved'
|
||||||
};
|
});
|
||||||
|
|
||||||
if (agheeraResults.length > 0) {
|
|
||||||
responseBody.agheera_push = agheeraResults.length === 1 ? agheeraResults[0] : agheeraResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json(responseBody);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving location:', error);
|
console.error('Error saving location:', error);
|
||||||
return res.status(500).json({ success: false, error: error.message });
|
return res.status(500).json({ success: false, error: error.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
saveLocation
|
saveLocation
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,6 @@ const db = require('../config/db');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const tripIncidenceMailer = require('../services/tripIncidenceMailer');
|
const tripIncidenceMailer = require('../services/tripIncidenceMailer');
|
||||||
const agheeraPushClient = require('../services/agheeraPushClient');
|
|
||||||
const {
|
const {
|
||||||
collectUploadedTripStatusFiles,
|
collectUploadedTripStatusFiles,
|
||||||
removeUploadedTripStatusFiles,
|
removeUploadedTripStatusFiles,
|
||||||
@ -24,7 +23,6 @@ const INTERMEDIATE_POINT_ALLOWED_STATES = [3, 4, 5];
|
|||||||
const INTERMEDIATE_POINT_ALLOWED_STATES_SET = new Set(INTERMEDIATE_POINT_ALLOWED_STATES);
|
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 SQL_DATETIME_REGEX = /^(\d{4})-(\d{2})-(\d{2}) ([0-2]\d):([0-5]\d):([0-5]\d)$/;
|
||||||
const FAILED_TRIP_STATE = 9;
|
const FAILED_TRIP_STATE = 9;
|
||||||
const AGHEERA_CLIENT_ID = 532;
|
|
||||||
const INTERMEDIATE_POINT_STATUS_IDS = new Set([3, 4, 5]);
|
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 INCIDENCE_TEXT_CONTROL_CHARACTERS_REGEX = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
|
||||||
const GLOBAL_STATUS_KEYS_BY_STATE_ID = new Map([
|
const GLOBAL_STATUS_KEYS_BY_STATE_ID = new Map([
|
||||||
@ -240,85 +238,6 @@ const normalizeCoordinateValue = (rawValue) => {
|
|||||||
return String(parsedNumericValue);
|
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) => {
|
const normalizeSqlDateTimeValue = (rawValue) => {
|
||||||
if (typeof rawValue !== 'string') {
|
if (typeof rawValue !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
@ -1501,7 +1420,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
await connection.beginTransaction();
|
await connection.beginTransaction();
|
||||||
|
|
||||||
const [tripRows] = await connection.query(
|
const [tripRows] = await connection.query(
|
||||||
`SELECT id_viaje, id_estado, id_viaje_padre, id_cliente
|
`SELECT id_viaje, id_estado, id_viaje_padre
|
||||||
FROM c_viajes
|
FROM c_viajes
|
||||||
WHERE id_viaje = ?
|
WHERE id_viaje = ?
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@ -1534,7 +1453,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [authorizationRows] = await connection.query(
|
const [authorizationRows] = await connection.query(
|
||||||
`SELECT n_proveedor, id_proveedor, id_tipovehiculo AS matricula
|
`SELECT n_proveedor, id_proveedor
|
||||||
FROM c_viajes_proveedor
|
FROM c_viajes_proveedor
|
||||||
WHERE id_viaje = ?
|
WHERE id_viaje = ?
|
||||||
AND dni = ?
|
AND dni = ?
|
||||||
@ -1797,18 +1716,8 @@ const updateTripStatus = async (req, res) => {
|
|||||||
idEstadoLogged: FAILED_TRIP_STATE,
|
idEstadoLogged: FAILED_TRIP_STATE,
|
||||||
idPuntoLogged: idPunto
|
idPuntoLogged: idPunto
|
||||||
});
|
});
|
||||||
const agheeraPushResult = await pushTripStatusPositionToAgheera({
|
|
||||||
tripRow: tripRows[0],
|
|
||||||
authorizationRow: authorizationRows[0],
|
|
||||||
latitud,
|
|
||||||
longitud,
|
|
||||||
measurementTime: now,
|
|
||||||
tripId,
|
|
||||||
requestId,
|
|
||||||
flow: 'failed_branch_manual'
|
|
||||||
});
|
|
||||||
|
|
||||||
const responseBody = {
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
trip_id: tripId,
|
trip_id: tripId,
|
||||||
updated_status_id: idEstado,
|
updated_status_id: idEstado,
|
||||||
@ -1820,13 +1729,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
failed_marked: true,
|
failed_marked: true,
|
||||||
fotos_concat: fotosConcat || '',
|
fotos_concat: fotosConcat || '',
|
||||||
updated_at: now.toISOString()
|
updated_at: now.toISOString()
|
||||||
};
|
});
|
||||||
|
|
||||||
if (agheeraPushResult) {
|
|
||||||
responseBody.agheera_push = agheeraPushResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json(responseBody);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
@ -1849,7 +1752,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [tripRows] = await db.query(
|
const [tripRows] = await db.query(
|
||||||
`SELECT id_viaje, id_viaje_padre, id_cliente
|
`SELECT id_viaje, id_viaje_padre
|
||||||
FROM c_viajes
|
FROM c_viajes
|
||||||
WHERE id_viaje = ?
|
WHERE id_viaje = ?
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
@ -1880,7 +1783,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [authorizationRows] = await db.query(
|
const [authorizationRows] = await db.query(
|
||||||
`SELECT n_proveedor, id_proveedor, id_tipovehiculo AS matricula
|
`SELECT n_proveedor, id_proveedor
|
||||||
FROM c_viajes_proveedor
|
FROM c_viajes_proveedor
|
||||||
WHERE id_viaje = ?
|
WHERE id_viaje = ?
|
||||||
AND dni = ?
|
AND dni = ?
|
||||||
@ -2009,18 +1912,8 @@ const updateTripStatus = async (req, res) => {
|
|||||||
idEstadoLogged: idEstado,
|
idEstadoLogged: idEstado,
|
||||||
idPuntoLogged: idPunto
|
idPuntoLogged: idPunto
|
||||||
});
|
});
|
||||||
const agheeraPushResult = await pushTripStatusPositionToAgheera({
|
|
||||||
tripRow: tripRows[0],
|
|
||||||
authorizationRow: authorizationRows[0],
|
|
||||||
latitud,
|
|
||||||
longitud,
|
|
||||||
measurementTime: now,
|
|
||||||
tripId,
|
|
||||||
requestId,
|
|
||||||
flow: 'normal_manual'
|
|
||||||
});
|
|
||||||
|
|
||||||
const responseBody = {
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
trip_id: tripId,
|
trip_id: tripId,
|
||||||
updated_status_id: idEstado,
|
updated_status_id: idEstado,
|
||||||
@ -2032,13 +1925,7 @@ const updateTripStatus = async (req, res) => {
|
|||||||
failed_marked: false,
|
failed_marked: false,
|
||||||
fotos_concat: fotosConcat || '',
|
fotos_concat: fotosConcat || '',
|
||||||
updated_at: now.toISOString()
|
updated_at: now.toISOString()
|
||||||
};
|
});
|
||||||
|
|
||||||
if (agheeraPushResult) {
|
|
||||||
responseBody.agheera_push = agheeraPushResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json(responseBody);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const parsedTripId = Number.parseInt(req.params?.id, 10);
|
const parsedTripId = Number.parseInt(req.params?.id, 10);
|
||||||
const parsedStatusId = Number.parseInt(req.body?.id_estado, 10);
|
const parsedStatusId = Number.parseInt(req.body?.id_estado, 10);
|
||||||
|
|||||||
@ -1,113 +0,0 @@
|
|||||||
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
|
|
||||||
};
|
|
||||||
@ -1,289 +0,0 @@
|
|||||||
const assert = require('node:assert/strict');
|
|
||||||
const http = require('node:http');
|
|
||||||
const test = require('node:test');
|
|
||||||
const jwt = require('jsonwebtoken');
|
|
||||||
|
|
||||||
const app = require('../app');
|
|
||||||
const db = require('../src/config/db');
|
|
||||||
const agheeraPushClient = require('../src/services/agheeraPushClient');
|
|
||||||
|
|
||||||
const TEST_JWT_SECRET = 'test-jwt-secret';
|
|
||||||
|
|
||||||
let originalQuery;
|
|
||||||
let originalJwtSecret;
|
|
||||||
let originalAgheeraApiKey;
|
|
||||||
|
|
||||||
const createToken = (payload = {}) =>
|
|
||||||
jwt.sign(
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
dni: '58045340X',
|
|
||||||
id_proveedor: 675,
|
|
||||||
...payload
|
|
||||||
},
|
|
||||||
TEST_JWT_SECRET,
|
|
||||||
{ expiresIn: '1h' }
|
|
||||||
);
|
|
||||||
|
|
||||||
const withServer = async (callback) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const server = app.listen(0, '127.0.0.1');
|
|
||||||
|
|
||||||
server.on('error', reject);
|
|
||||||
server.on('listening', async () => {
|
|
||||||
try {
|
|
||||||
const result = await callback(server);
|
|
||||||
server.close((closeError) => {
|
|
||||||
if (closeError) {
|
|
||||||
reject(closeError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
server.close(() => reject(error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const postJson = async ({ port, path, authorization, body }) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const rawBody = JSON.stringify(body);
|
|
||||||
const req = http.request(
|
|
||||||
{
|
|
||||||
hostname: '127.0.0.1',
|
|
||||||
port,
|
|
||||||
method: 'POST',
|
|
||||||
path,
|
|
||||||
headers: {
|
|
||||||
authorization,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Content-Length': Buffer.byteLength(rawBody)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(res) => {
|
|
||||||
let responseBody = '';
|
|
||||||
|
|
||||||
res.on('data', (chunk) => {
|
|
||||||
responseBody += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.on('end', () => {
|
|
||||||
resolve({
|
|
||||||
statusCode: res.statusCode,
|
|
||||||
body: responseBody ? JSON.parse(responseBody) : null
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
req.on('error', reject);
|
|
||||||
req.write(rawBody);
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test.before(() => {
|
|
||||||
originalQuery = db.query;
|
|
||||||
originalJwtSecret = process.env.JWT_SECRET;
|
|
||||||
originalAgheeraApiKey = process.env.AGHEERA_API_KEY;
|
|
||||||
});
|
|
||||||
|
|
||||||
test.after(() => {
|
|
||||||
db.query = originalQuery;
|
|
||||||
process.env.JWT_SECRET = originalJwtSecret;
|
|
||||||
process.env.AGHEERA_API_KEY = originalAgheeraApiKey;
|
|
||||||
agheeraPushClient.__resetHttpClientForTests();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.afterEach(() => {
|
|
||||||
db.query = originalQuery;
|
|
||||||
agheeraPushClient.__resetHttpClientForTests();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /api/locations envia posicion a Agheera para cliente 532', async () => {
|
|
||||||
process.env.JWT_SECRET = TEST_JWT_SECRET;
|
|
||||||
process.env.AGHEERA_API_KEY = 'test-api-key';
|
|
||||||
|
|
||||||
const agheeraCalls = [];
|
|
||||||
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
|
||||||
agheeraCalls.push({ url, options });
|
|
||||||
return { ok: true, status: 200, text: async () => 'Messages received.' };
|
|
||||||
});
|
|
||||||
|
|
||||||
let step = 0;
|
|
||||||
db.query = async (sql, params) => {
|
|
||||||
step += 1;
|
|
||||||
|
|
||||||
if (step === 1) {
|
|
||||||
assert.match(sql, /INSERT INTO c_trazabilidad_transportista/);
|
|
||||||
assert.equal(params[0].length, 1);
|
|
||||||
assert.deepEqual(params[0][0].slice(0, 3), ['40.416775', '-3.70379', '58045340X']);
|
|
||||||
assert.equal(params[0][0][4], 248230);
|
|
||||||
return [{ affectedRows: 1 }];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 2) {
|
|
||||||
assert.match(sql, /FROM c_viajes/);
|
|
||||||
assert.deepEqual(params, [248230]);
|
|
||||||
return [[{ id_cliente: 532 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(sql, /FROM c_viajes_proveedor/);
|
|
||||||
assert.deepEqual(params, [248230, '58045340X']);
|
|
||||||
return [[{ matricula: '6599LCN' }]];
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await withServer(async (server) =>
|
|
||||||
postJson({
|
|
||||||
port: server.address().port,
|
|
||||||
path: '/api/locations',
|
|
||||||
authorization: `Bearer ${createToken()}`,
|
|
||||||
body: {
|
|
||||||
latitude: 40.416775,
|
|
||||||
longitude: -3.70379,
|
|
||||||
id_viaje: 248230,
|
|
||||||
timestamp: '2026-06-01T13:20:00Z'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
assert.deepEqual(response.body, {
|
|
||||||
success: true,
|
|
||||||
count: 1,
|
|
||||||
message: 'Locations saved',
|
|
||||||
agheera_push: {
|
|
||||||
trip_id: 248230,
|
|
||||||
attempted: true,
|
|
||||||
success: true,
|
|
||||||
http_status: 200,
|
|
||||||
error: null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(agheeraCalls.length, 1);
|
|
||||||
|
|
||||||
const call = agheeraCalls[0];
|
|
||||||
assert.equal(call.url, 'https://push-test.agheera.com/Telematics/Positions');
|
|
||||||
assert.equal(call.options.headers.apiKey, 'test-api-key');
|
|
||||||
|
|
||||||
const payload = JSON.parse(call.options.body);
|
|
||||||
assert.deepEqual(payload, {
|
|
||||||
Vehicles: [
|
|
||||||
{
|
|
||||||
latitude: 40.416775,
|
|
||||||
longitude: -3.70379,
|
|
||||||
vehicleId: '6599LCN',
|
|
||||||
licensePlate: '6599LCN',
|
|
||||||
measurementTime: '2026-06-01T13:20:00Z'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /api/locations no envia a Agheera para clientes distintos de 532', async () => {
|
|
||||||
process.env.JWT_SECRET = TEST_JWT_SECRET;
|
|
||||||
process.env.AGHEERA_API_KEY = 'test-api-key';
|
|
||||||
|
|
||||||
const agheeraCalls = [];
|
|
||||||
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
|
||||||
agheeraCalls.push({ url, options });
|
|
||||||
return { ok: true, status: 200, text: async () => 'Messages received.' };
|
|
||||||
});
|
|
||||||
|
|
||||||
let step = 0;
|
|
||||||
db.query = async (sql, params) => {
|
|
||||||
step += 1;
|
|
||||||
|
|
||||||
if (step === 1) {
|
|
||||||
assert.match(sql, /INSERT INTO c_trazabilidad_transportista/);
|
|
||||||
return [{ affectedRows: 1 }];
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(sql, /FROM c_viajes/);
|
|
||||||
assert.deepEqual(params, [248230]);
|
|
||||||
return [[{ id_cliente: 700 }]];
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await withServer(async (server) =>
|
|
||||||
postJson({
|
|
||||||
port: server.address().port,
|
|
||||||
path: '/api/locations',
|
|
||||||
authorization: `Bearer ${createToken()}`,
|
|
||||||
body: {
|
|
||||||
latitude: 40.416775,
|
|
||||||
longitude: -3.70379,
|
|
||||||
id_viaje: 248230,
|
|
||||||
timestamp: '2026-06-01T13:20:00Z'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
assert.deepEqual(response.body, {
|
|
||||||
success: true,
|
|
||||||
count: 1,
|
|
||||||
message: 'Locations saved'
|
|
||||||
});
|
|
||||||
assert.equal(agheeraCalls.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /api/locations devuelve error de Agheera sin romper guardado local', async () => {
|
|
||||||
process.env.JWT_SECRET = TEST_JWT_SECRET;
|
|
||||||
process.env.AGHEERA_API_KEY = 'test-api-key';
|
|
||||||
|
|
||||||
agheeraPushClient.__setHttpClientForTests(async () => ({
|
|
||||||
ok: false,
|
|
||||||
status: 401,
|
|
||||||
text: async () => 'Unauthorized'
|
|
||||||
}));
|
|
||||||
|
|
||||||
let step = 0;
|
|
||||||
db.query = async (sql, params) => {
|
|
||||||
step += 1;
|
|
||||||
|
|
||||||
if (step === 1) {
|
|
||||||
assert.match(sql, /INSERT INTO c_trazabilidad_transportista/);
|
|
||||||
return [{ affectedRows: 1 }];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 2) {
|
|
||||||
assert.match(sql, /FROM c_viajes/);
|
|
||||||
assert.deepEqual(params, [248230]);
|
|
||||||
return [[{ id_cliente: 532 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(sql, /FROM c_viajes_proveedor/);
|
|
||||||
assert.deepEqual(params, [248230, '58045340X']);
|
|
||||||
return [[{ matricula: '6599LCN' }]];
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await withServer(async (server) =>
|
|
||||||
postJson({
|
|
||||||
port: server.address().port,
|
|
||||||
path: '/api/locations',
|
|
||||||
authorization: `Bearer ${createToken()}`,
|
|
||||||
body: {
|
|
||||||
latitude: 40.416775,
|
|
||||||
longitude: -3.70379,
|
|
||||||
id_viaje: 248230,
|
|
||||||
timestamp: '2026-06-01T13:20:00Z'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
assert.deepEqual(response.body, {
|
|
||||||
success: true,
|
|
||||||
count: 1,
|
|
||||||
message: 'Locations saved',
|
|
||||||
agheera_push: {
|
|
||||||
trip_id: 248230,
|
|
||||||
attempted: true,
|
|
||||||
success: false,
|
|
||||||
http_status: 401,
|
|
||||||
error: 'Agheera push failed'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -12,11 +12,8 @@ process.env.TRIP_STATUS_UPLOAD_DIR = TEST_UPLOAD_DIR;
|
|||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual'
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual'
|
||||||
|
|
||||||
const app = require('../app');
|
const app = require('../app');
|
||||||
process.env.TRIP_STATUS_UPLOAD_DIR = TEST_UPLOAD_DIR;
|
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
|
||||||
const db = require('../src/config/db');
|
const db = require('../src/config/db');
|
||||||
const tripStatusPhotoStorage = require('../src/services/tripStatusPhotoStorage');
|
const tripStatusPhotoStorage = require('../src/services/tripStatusPhotoStorage');
|
||||||
const agheeraPushClient = require('../src/services/agheeraPushClient');
|
|
||||||
|
|
||||||
const JWT_SECRET = process.env.JWT_SECRET || 'test-jwt-secret';
|
const JWT_SECRET = process.env.JWT_SECRET || 'test-jwt-secret';
|
||||||
process.env.JWT_SECRET = JWT_SECRET;
|
process.env.JWT_SECRET = JWT_SECRET;
|
||||||
@ -236,7 +233,6 @@ test.after(() => {
|
|||||||
db.query = originalQuery;
|
db.query = originalQuery;
|
||||||
db.getConnection = originalGetConnection;
|
db.getConnection = originalGetConnection;
|
||||||
tripStatusPhotoStorage.__resetSftpClientFactoryForTests();
|
tripStatusPhotoStorage.__resetSftpClientFactoryForTests();
|
||||||
agheeraPushClient.__resetHttpClientForTests();
|
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
delete process.env.TRIP_STATUS_SFTP_HOST;
|
delete process.env.TRIP_STATUS_SFTP_HOST;
|
||||||
delete process.env.TRIP_STATUS_SFTP_PORT;
|
delete process.env.TRIP_STATUS_SFTP_PORT;
|
||||||
@ -245,8 +241,6 @@ test.after(() => {
|
|||||||
delete process.env.TRIP_STATUS_SFTP_REMOTE_BASE_DIR;
|
delete process.env.TRIP_STATUS_SFTP_REMOTE_BASE_DIR;
|
||||||
delete process.env.POSTS_LOG_PATH;
|
delete process.env.POSTS_LOG_PATH;
|
||||||
delete process.env.TRIP_STATUS_UPDATES_LOG_PATH;
|
delete process.env.TRIP_STATUS_UPDATES_LOG_PATH;
|
||||||
delete process.env.AGHEERA_PUSH_URL;
|
|
||||||
delete process.env.AGHEERA_API_KEY;
|
|
||||||
fs.rmSync(TEST_UPLOAD_DIR, { recursive: true, force: true });
|
fs.rmSync(TEST_UPLOAD_DIR, { recursive: true, force: true });
|
||||||
fs.rmSync(TEST_POSTS_LOG_PATH, { force: true });
|
fs.rmSync(TEST_POSTS_LOG_PATH, { force: true });
|
||||||
fs.rmSync(TEST_STATUS_LOG_PATH, { force: true });
|
fs.rmSync(TEST_STATUS_LOG_PATH, { force: true });
|
||||||
@ -256,7 +250,6 @@ test.afterEach(() => {
|
|||||||
db.query = originalQuery;
|
db.query = originalQuery;
|
||||||
db.getConnection = originalGetConnection;
|
db.getConnection = originalGetConnection;
|
||||||
tripStatusPhotoStorage.__resetSftpClientFactoryForTests();
|
tripStatusPhotoStorage.__resetSftpClientFactoryForTests();
|
||||||
agheeraPushClient.__resetHttpClientForTests();
|
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
delete process.env.TRIP_STATUS_SFTP_HOST;
|
delete process.env.TRIP_STATUS_SFTP_HOST;
|
||||||
delete process.env.TRIP_STATUS_SFTP_PORT;
|
delete process.env.TRIP_STATUS_SFTP_PORT;
|
||||||
@ -265,8 +258,6 @@ test.afterEach(() => {
|
|||||||
delete process.env.TRIP_STATUS_SFTP_REMOTE_BASE_DIR;
|
delete process.env.TRIP_STATUS_SFTP_REMOTE_BASE_DIR;
|
||||||
delete process.env.POSTS_LOG_PATH;
|
delete process.env.POSTS_LOG_PATH;
|
||||||
delete process.env.TRIP_STATUS_UPDATES_LOG_PATH;
|
delete process.env.TRIP_STATUS_UPDATES_LOG_PATH;
|
||||||
delete process.env.AGHEERA_PUSH_URL;
|
|
||||||
delete process.env.AGHEERA_API_KEY;
|
|
||||||
fs.rmSync(TEST_POSTS_LOG_PATH, { force: true });
|
fs.rmSync(TEST_POSTS_LOG_PATH, { force: true });
|
||||||
fs.rmSync(TEST_STATUS_LOG_PATH, { force: true });
|
fs.rmSync(TEST_STATUS_LOG_PATH, { force: true });
|
||||||
});
|
});
|
||||||
@ -719,7 +710,7 @@ test('POST /api/trips/:id/status modo dual replica foto a SFTP y mantiene local'
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -796,7 +787,7 @@ test('POST /api/trips/:id/status modo dual con fallo SFTP mantiene fallback loca
|
|||||||
createFakeSftpClientFactory(recorder, { failPut: true })
|
createFakeSftpClientFactory(recorder, { failPut: true })
|
||||||
);
|
);
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -862,7 +853,7 @@ test('POST /api/trips/:id/status payload inválido tras upload limpia remoto y l
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
@ -1130,269 +1121,6 @@ test('POST /api/trips/:id/status propaga estado global al viaje padre', async ()
|
|||||||
assert.equal(step, 7);
|
assert.equal(step, 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /api/trips/:id/status cliente 532 envia posicion a Agheera en estado global', async () => {
|
|
||||||
process.env.AGHEERA_API_KEY = 'test-api-key';
|
|
||||||
const agheeraCalls = [];
|
|
||||||
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
|
||||||
agheeraCalls.push({ url, options });
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
status: 200,
|
|
||||||
text: async () => 'Messages received.'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let step = 0;
|
|
||||||
db.query = async (sql, params) => {
|
|
||||||
step += 1;
|
|
||||||
|
|
||||||
if (step === 1) {
|
|
||||||
assert.match(sql, /FROM t_viaje_estados/);
|
|
||||||
assert.deepEqual(params, [6]);
|
|
||||||
return [[{ id_estado: 6 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 2) {
|
|
||||||
assert.match(sql, /FROM c_viajes/);
|
|
||||||
assert.match(sql, /id_cliente/);
|
|
||||||
assert.deepEqual(params, [248230]);
|
|
||||||
return [[{ id_viaje: 248230, id_viaje_padre: 0, id_cliente: 532 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 3) {
|
|
||||||
assert.match(sql, /FROM c_viajes_proveedor/);
|
|
||||||
assert.match(sql, /id_tipovehiculo AS matricula/);
|
|
||||||
assert.deepEqual(params, [248230, '58045340X']);
|
|
||||||
return [[{ n_proveedor: 1, id_proveedor: 675, matricula: '1234ABC' }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 4) {
|
|
||||||
assert.match(sql, /UPDATE c_viajes/);
|
|
||||||
assert.deepEqual(params, [6, 1, 248230]);
|
|
||||||
return [{ affectedRows: 1 }];
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(sql, /INSERT INTO c_cambios_estado/);
|
|
||||||
assert.equal(params[6], '40.416775');
|
|
||||||
assert.equal(params[7], '-3.70379');
|
|
||||||
return [{ insertId: 6, affectedRows: 1 }];
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await withServer(async (server) =>
|
|
||||||
requestJson({
|
|
||||||
port: server.address().port,
|
|
||||||
method: 'POST',
|
|
||||||
path: '/api/trips/248230/status',
|
|
||||||
authorization: `Bearer ${createToken()}`,
|
|
||||||
body: {
|
|
||||||
id_estado: 6,
|
|
||||||
latitud: '40,416775',
|
|
||||||
longitud: '-3.703790'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
assert.equal(response.body.success, true);
|
|
||||||
assert.deepEqual(response.body.agheera_push, {
|
|
||||||
trip_id: 248230,
|
|
||||||
attempted: true,
|
|
||||||
success: true,
|
|
||||||
http_status: 200,
|
|
||||||
error: null
|
|
||||||
});
|
|
||||||
assert.equal(agheeraCalls.length, 1);
|
|
||||||
|
|
||||||
const call = agheeraCalls[0];
|
|
||||||
assert.equal(call.url, 'https://push-test.agheera.com/Telematics/Positions');
|
|
||||||
assert.equal(call.options.method, 'POST');
|
|
||||||
assert.equal(call.options.headers.apiKey, 'test-api-key');
|
|
||||||
assert.equal(call.options.headers['Content-Type'], 'application/json');
|
|
||||||
|
|
||||||
const payload = JSON.parse(call.options.body);
|
|
||||||
assert.deepEqual(Object.keys(payload), ['Vehicles']);
|
|
||||||
assert.equal(payload.Vehicles.length, 1);
|
|
||||||
assert.equal(payload.Vehicles[0].latitude, 40.416775);
|
|
||||||
assert.equal(payload.Vehicles[0].longitude, -3.70379);
|
|
||||||
assert.equal(payload.Vehicles[0].vehicleId, '1234ABC');
|
|
||||||
assert.equal(payload.Vehicles[0].licensePlate, '1234ABC');
|
|
||||||
assert.match(payload.Vehicles[0].measurementTime, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /api/trips/:id/status cliente distinto de 532 no envia a Agheera', async () => {
|
|
||||||
process.env.AGHEERA_API_KEY = 'test-api-key';
|
|
||||||
const agheeraCalls = [];
|
|
||||||
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
|
||||||
agheeraCalls.push({ url, options });
|
|
||||||
return { ok: true, status: 200, text: async () => 'Messages received.' };
|
|
||||||
});
|
|
||||||
|
|
||||||
let step = 0;
|
|
||||||
db.query = async (sql, params) => {
|
|
||||||
step += 1;
|
|
||||||
|
|
||||||
if (step === 1) {
|
|
||||||
return [[{ id_estado: 6 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 2) {
|
|
||||||
return [[{ id_viaje: 248230, id_viaje_padre: 0, id_cliente: 700 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 3) {
|
|
||||||
return [[{ n_proveedor: 1, id_proveedor: 675, matricula: '1234ABC' }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 4) {
|
|
||||||
assert.match(sql, /UPDATE c_viajes/);
|
|
||||||
return [{ affectedRows: 1 }];
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(sql, /INSERT INTO c_cambios_estado/);
|
|
||||||
return [{ insertId: 6, affectedRows: 1 }];
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await withServer(async (server) =>
|
|
||||||
requestJson({
|
|
||||||
port: server.address().port,
|
|
||||||
method: 'POST',
|
|
||||||
path: '/api/trips/248230/status',
|
|
||||||
authorization: `Bearer ${createToken()}`,
|
|
||||||
body: {
|
|
||||||
id_estado: 6,
|
|
||||||
latitud: '40.416775',
|
|
||||||
longitud: '-3.703790'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
assert.equal(response.body.success, true);
|
|
||||||
assert.equal(response.body.agheera_push, undefined);
|
|
||||||
assert.equal(agheeraCalls.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /api/trips/:id/status estado intermedio con id_punto no envia a Agheera', async () => {
|
|
||||||
process.env.AGHEERA_API_KEY = 'test-api-key';
|
|
||||||
const agheeraCalls = [];
|
|
||||||
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
|
||||||
agheeraCalls.push({ url, options });
|
|
||||||
return { ok: true, status: 200, text: async () => 'Messages received.' };
|
|
||||||
});
|
|
||||||
|
|
||||||
let step = 0;
|
|
||||||
db.query = async (sql, params) => {
|
|
||||||
step += 1;
|
|
||||||
|
|
||||||
if (step === 1) {
|
|
||||||
assert.match(sql, /FROM t_viaje_estados/);
|
|
||||||
return [[{ id_estado: 5 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 2) {
|
|
||||||
assert.match(sql, /FROM c_viajes/);
|
|
||||||
return [[{ id_viaje: 248230 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 3) {
|
|
||||||
assert.match(sql, /FROM c_viajes_proveedor/);
|
|
||||||
return [[{ n_proveedor: 1 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 4) {
|
|
||||||
assert.match(sql, /FROM c_viajes_puntos/);
|
|
||||||
assert.deepEqual(params, [8123, 248230]);
|
|
||||||
return [[{ id_punto: 8123, id_estado_intermedio: 4, valor: null, foto: null }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(sql, /UPDATE c_viajes_puntos/);
|
|
||||||
return [{ affectedRows: 1 }];
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await withServer(async (server) =>
|
|
||||||
requestJson({
|
|
||||||
port: server.address().port,
|
|
||||||
method: 'POST',
|
|
||||||
path: '/api/trips/248230/status',
|
|
||||||
authorization: `Bearer ${createToken()}`,
|
|
||||||
body: {
|
|
||||||
id_estado: 5,
|
|
||||||
id_punto: 8123,
|
|
||||||
latitud: '40.416775',
|
|
||||||
longitud: '-3.703790'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
assert.equal(response.body.success, true);
|
|
||||||
assert.equal(response.body.agheera_push, undefined);
|
|
||||||
assert.equal(agheeraCalls.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /api/trips/:id/status fallo de Agheera mantiene respuesta 200', async () => {
|
|
||||||
process.env.AGHEERA_API_KEY = 'test-api-key';
|
|
||||||
const agheeraCalls = [];
|
|
||||||
agheeraPushClient.__setHttpClientForTests(async (url, options) => {
|
|
||||||
agheeraCalls.push({ url, options });
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
status: 500,
|
|
||||||
text: async () => 'temporary error'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let step = 0;
|
|
||||||
db.query = async (sql) => {
|
|
||||||
step += 1;
|
|
||||||
|
|
||||||
if (step === 1) {
|
|
||||||
return [[{ id_estado: 6 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 2) {
|
|
||||||
return [[{ id_viaje: 248230, id_viaje_padre: 0, id_cliente: 532 }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 3) {
|
|
||||||
return [[{ n_proveedor: 1, id_proveedor: 675, matricula: '1234ABC' }]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step === 4) {
|
|
||||||
assert.match(sql, /UPDATE c_viajes/);
|
|
||||||
return [{ affectedRows: 1 }];
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(sql, /INSERT INTO c_cambios_estado/);
|
|
||||||
return [{ insertId: 6, affectedRows: 1 }];
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await withServer(async (server) =>
|
|
||||||
requestJson({
|
|
||||||
port: server.address().port,
|
|
||||||
method: 'POST',
|
|
||||||
path: '/api/trips/248230/status',
|
|
||||||
authorization: `Bearer ${createToken()}`,
|
|
||||||
body: {
|
|
||||||
id_estado: 6,
|
|
||||||
latitud: '40.416775',
|
|
||||||
longitud: '-3.703790'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
assert.equal(response.body.success, true);
|
|
||||||
assert.deepEqual(response.body.agheera_push, {
|
|
||||||
trip_id: 248230,
|
|
||||||
attempted: true,
|
|
||||||
success: false,
|
|
||||||
http_status: 500,
|
|
||||||
error: 'Agheera push failed'
|
|
||||||
});
|
|
||||||
assert.equal(agheeraCalls.length, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /api/trips/:id/status estado intermedio con id_punto inválido => 400', async () => {
|
test('POST /api/trips/:id/status estado intermedio con id_punto inválido => 400', async () => {
|
||||||
db.query = async () => {
|
db.query = async () => {
|
||||||
throw new Error('db.query should not run for invalid id_punto');
|
throw new Error('db.query should not run for invalid id_punto');
|
||||||
@ -2795,7 +2523,7 @@ test('DELETE /api/trips/:id/status en modo dual no borra foto en remoto ni local
|
|||||||
const recorder = createSftpRecorder();
|
const recorder = createSftpRecorder();
|
||||||
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
tripStatusPhotoStorage.__setSftpClientFactoryForTests(createFakeSftpClientFactory(recorder));
|
||||||
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
process.env.TRIP_STATUS_PHOTO_STORAGE_MODE = 'dual';
|
||||||
process.env.TRIP_STATUS_SFTP_HOST = 'localhost';
|
process.env.TRIP_STATUS_SFTP_HOST = '194.164.175.51';
|
||||||
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
process.env.TRIP_STATUS_SFTP_PORT = '22';
|
||||||
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
process.env.TRIP_STATUS_SFTP_USERNAME = 'ssh_fotos_estado';
|
||||||
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
process.env.TRIP_STATUS_SFTP_PASSWORD = 'test-password';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user