Merge branch 'main' into alarmas-objenious

This commit is contained in:
2026-04-07 09:05:39 +02:00
15 changed files with 642 additions and 72 deletions

View File

@@ -0,0 +1,20 @@
CREATE table if not exists objenious_lines (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
simId BIGINT UNIQUE,
status TEXT,
iccid TEXT NOT NULL,
msisdn TEXT,
imei TEXT,
imeiChangeDate TIMESTAMPTZ,
offerCode TEXT,
preactivationDate TIMESTAMPTZ, -- No viene con hora
activationDate TIMESTAMPTZ,
commercialStatus TEXT,
commercialStatusDate TIMESTAMPTZ,
billingStatus TEXT,
billingStatusChangeDate TIMESTAMPTZ,
billingActivationDate TIMESTAMPTZ,
createDate TIMESTAMPTZ,
raw JSONB,
hash TEXT
)

View File

@@ -5,16 +5,16 @@ meta {
} }
get { get {
url: https://api-getway.objenious.com/ws/lines?pageSize=10&identifier.identifierType=ICCID&identifier.identifiers=8933201125065160455 url: https://api-getway.objenious.com/ws/lines?pageSize=1000&simStatus=ACTIVATED
body: formUrlEncoded body: formUrlEncoded
auth: bearer auth: bearer
} }
params:query { params:query {
pageSize: 10 pageSize: 1000
identifier.identifierType: ICCID simStatus: ACTIVATED
identifier.identifiers: 8933201125065160455 ~identifier.identifierType: ICCID
~simStatus: ACTIVATED ~identifier.identifiers: 8933201125065160455
} }
auth:bearer { auth:bearer {

View File

@@ -7,6 +7,6 @@ OBJ_KID=xNfbMiyL1ORXGP8lElhcv8nVaG3EJKye4Lc1YoN3I1E
OBJ_BASE_URL=https://api-getway.objenious.com/ws OBJ_BASE_URL=https://api-getway.objenious.com/ws
# OBJ_BASE_URL=https://api-getway.objenious.com/ws/test # OBJ_BASE_URL=https://api-getway.objenious.com/ws/test
# NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail" NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
NOTIFICATION_URL="localhost" # NOTIFICATION_URL="localhost"
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6 SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6

View File

@@ -31,15 +31,15 @@ export const env = {
OBJ_KID: String(process.env.OBJ_KID), OBJ_KID: String(process.env.OBJ_KID),
OBJ_BASE_URL: String(process.env.OBJ_BASE_URL), OBJ_BASE_URL: String(process.env.OBJ_BASE_URL),
NOTIFICATION_URL: String(process.env.NOTIFICATION_URL), NOTIFICATION_URL: String(process.env.NOTIFICATION_URL ?? ""),
SIM_ACTIVATION_API_KEY: String(process.env.SIM_ACTIVATION_API_KEY) SIM_ACTIVATION_API_KEY: String(process.env.SIM_ACTIVATION_API_KEY ?? "")
}; };
// assert las partes criticas // assert las partes criticas
assert(env.RABBITMQ_PASSWORD != undefined) assert(env.RABBITMQ_PASSWORD != undefined)
assert(env.RABBITMQ_USER != undefined) assert(env.RABBITMQ_USER != undefined)
assert(env.SIM_ACTIVATION_API_KEY != undefined) assert(env.SIM_ACTIVATION_API_KEY != "")
assert(env.NOTIFICATION_URL != undefined) assert(env.NOTIFICATION_URL != "")
if (env.ENVIRONMENT == "production") { if (env.ENVIRONMENT == "production") {
assert(env.RABBITMQ_PASSWORD != "guest") assert(env.RABBITMQ_PASSWORD != "guest")
@@ -47,3 +47,5 @@ if (env.ENVIRONMENT == "production") {
} }
console.log("[i] verificado env")

View File

@@ -0,0 +1,20 @@
/**
* Cliente de postgres para la intranet. Se usa solo porque hace falta para el
* volcado de datos, si se usa en mas partes algo estás haciendo mal.
*/
import { Pool } from 'pg';
import { PgClient } from 'sim-shared/infrastructure/PgClient.js'
import { env } from './env/index.js';
export const pgPoolIntranet = new Pool({
user: env.POSTGRES_USER,
host: env.POSTGRES_HOST,
database: "intranet",
password: env.POSTGRES_PASSWORD,
port: Number(env.POSTGRES_PORT) || 5432,
});
export const postgresClientIntranet = new PgClient({
pool: pgPoolIntranet
})

View File

@@ -5,6 +5,9 @@ import { httpInstance } from "./config/httpClient.config.js"
import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js" import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js"
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js" import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js" import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
import { TaskVolcadoLineas } from "./tasks/volcado_lineas.js"
import { ObjeniousLinesRepository } from "./infranstructure/ObjeniousLinesRepository.js"
import { postgresClientIntranet } from "./config/intranetPostgresConfig.js"
async function startCron() { async function startCron() {
const commonSettings = { const commonSettings = {
@@ -14,10 +17,13 @@ async function startCron() {
const httpClient = httpInstance const httpClient = httpInstance
const pgClient = new PgClient({ pool: pgPool }) const pgClient = new PgClient({ pool: pgPool })
console.log("[i] Comprobando conexion con la BDD ")
await pgClient.checkDatabaseConnection() await pgClient.checkDatabaseConnection()
await pgClient.checkDatabaseConnection()
const operationRepository = new ObjeniousOperationsRepository(pgClient) const operationRepository = new ObjeniousOperationsRepository(pgClient)
const orderRepository = new OrderRepository(pgClient) const orderRepository = new OrderRepository(pgClient)
const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet)
const objTask = new CheckObjeniousRequests( const objTask = new CheckObjeniousRequests(
operationRepository, operationRepository,
@@ -25,23 +31,28 @@ async function startCron() {
httpClient, httpClient,
) )
await objTask.getPendingOperations() const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository)
const PERIODO_PETICIONES = 10 * 60 * 1000
const interval = setInterval(async () => { const interval = setInterval(async () => {
console.log("Updating...") try {
await objTask.getPendingOperations() await objTask.getPendingOperations()
console.log("Update finished") } catch (e) {
}, 10 * 60 * 1000) console.error("[x] Error de actualizacion de las lineas ")
/* }
const task = cron.createTask("* * * * *", async () => { }, PERIODO_PETICIONES)
}
, { const PERIODO_VOLCADO = 60 * 60 * 1000
...commonSettings, const volcadoInterval = setInterval(async () => {
name: "Test" try {
}) await volcadoLineasTask.loadLines()
*/ } catch (e) {
console.error("[x] Volcado de lineas de Objenious Fallido", e)
}
}, PERIODO_VOLCADO)
await volcadoLineasTask.loadLines()
//await objTask.getPendingOperations()
} }

View File

@@ -0,0 +1,59 @@
import test, { after, before, describe } from "node:test";
import { CreateObjeniousLineDTO } from "sim-shared/domain/objeniousLine.js";
import { ObjeniousLinesRepository } from "./ObjeniousLinesRepository.js";
import { postgrClient } from "../config/postgreConfig.js";
import assert from "node:assert";
describe("Line insertion test", async () => {
//const pgClient = postgreClientIntranet
const pgClient = postgrClient // En prod hay que usar el de Intrantet para usar la otra base de datos
const lineRepository = new ObjeniousLinesRepository(pgClient)
const lineaTest: CreateObjeniousLineDTO = {
simId: 1234,
iccid: "9999999999999",
msisdn: "34654674732",
imei: "219789481293",
imeiChangeDate: new Date(),
offerCode: "SAVEFAMILY1",
status: "ACTIVATED",
preactivationDate: new Date(),
activationDate: new Date(),
commercialStatus: "test",
commercialStatusDate: new Date(),
billingStatus: "test",
billingStatusChangeDate: new Date(),
billingActivationDate: new Date(),
createDate: new Date(),
raw: { test: "test" } as any // Para este test no hace falta
}
// Clean up before and after tests to ensure isolation
const cleanup = async () => {
await pgClient.query("DELETE FROM objenious_lines WHERE simId = 1234");
};
before(async () => {
await cleanup()
})
after(async () => {
await cleanup()
})
test("Should insert new line", async () => {
const res = await lineRepository.insertOrUpdate(lineaTest)
assert.ok(res != undefined, "The line wasn't created")
})
test("Should not update a line if the hash is the same", async () => {
const res = await lineRepository.insertOrUpdate(lineaTest)
assert.ok(res == undefined, "The line have been updated")
})
test("Should update a line if the hash changes", async () => {
const updated = structuredClone(lineaTest)
lineaTest.billingActivationDate = new Date()
const res = await lineRepository.insertOrUpdate(lineaTest)
assert.ok(res != undefined, "The line have been updated")
})
})

View File

@@ -0,0 +1,112 @@
/**
* Repositorio para el volcado de lineas de objenious en intranet
* solo para uso en el volcado.
*/
import { createHash } from "node:crypto";
import { PoolClient } from "pg";
import { CreateObjeniousLineDTO } from "sim-shared/domain/objeniousLine.js";
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
export class ObjeniousLinesRepository {
constructor(
private pgClient: PgClient
) {
}
private generateLineHash(data: CreateObjeniousLineDTO) {
try {
const lineStr = JSON.stringify(data)
const hash = createHash("sha256").update(lineStr).digest("base64url")
return hash
} catch (e) {
console.error("[x] Error generando el hash de la linea", data)
return undefined
}
}
public async insertOrUpdate(data: CreateObjeniousLineDTO) {
const query = `
INSERT INTO objenious_lines (
simId,
iccid,
msisdn,
imei,
imeiChangeDate,
offerCode,
status,
preactivationDate,
activationDate,
commercialStatus,
commercialStatusDate,
billingStatus,
billingStatusChangeDate,
billingActivationDate,
createDate,
raw,
hash
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17
)
ON CONFLICT (simId)
DO UPDATE SET
iccid = EXCLUDED.iccid,
msisdn = EXCLUDED.msisdn,
imei = EXCLUDED.imei,
imeiChangeDate = EXCLUDED.imeiChangeDate,
offerCode = EXCLUDED.offerCode,
status = EXCLUDED.status,
preactivationDate = EXCLUDED.preactivationDate,
activationDate = EXCLUDED.activationDate,
commercialStatus = EXCLUDED.commercialStatus,
commercialStatusDate = EXCLUDED.commercialStatusDate,
billingStatus = EXCLUDED.billingStatus,
billingStatusChangeDate = EXCLUDED.billingStatusChangeDate,
billingActivationDate = EXCLUDED.billingActivationDate,
raw = EXCLUDED.raw,
hash = EXCLUDED.hash
WHERE objenious_lines.hash IS DISTINCT FROM EXCLUDED.hash
RETURNING id;
`;
const lineHash = this.generateLineHash(data)
if (lineHash == undefined) {
console.error("[x] Ignorando linea ", data)
return;
}
const values = [
data.simId,
data.iccid,
data.msisdn,
data.imei,
data.imeiChangeDate,
data.offerCode,
data.status,
data.preactivationDate,
data.activationDate,
data.commercialStatus,
data.commercialStatusDate,
data.billingStatus,
data.billingStatusChangeDate,
data.billingActivationDate,
data.createDate || new Date(), // Default a ahora si no viene
JSON.stringify(data.raw), // El driver de pg requiere string o el objeto directo para JSONB
lineHash
];
let client: PoolClient | undefined = undefined;
try {
client = await this.pgClient.connect();
const res = await client.query<{ id: number }>(query, values);
return res.rows[0];
} catch (err) {
console.error('Error en la inserción:', err);
throw err;
} finally {
if (client != undefined) {
client.release()
}
}
}
}

View File

@@ -5,20 +5,6 @@
"description": "", "description": "",
"main": "index.ts", "main": "index.ts",
"imports": { "imports": {
"#config/*.js": {
"types": "./config/*.ts",
"default": "./config/*.js"
},
"#config/*": {
"types": "./config/*.ts",
"default": "./config/*.js"
},
"#shared/*.js": {
"default": "../sim-shared/*.js"
},
"#shared/*": {
"default": "../sim-shared/*.js"
},
"#adapters/*.js": { "#adapters/*.js": {
"types": "./infrastructure/*.ts", "types": "./infrastructure/*.ts",
"default": "./infrastructure/*.js" "default": "./infrastructure/*.js"
@@ -45,8 +31,8 @@
} }
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "node --import tsx --test ./**/*.test.ts",
"build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-objenious-cron/", "build": "tsc --build && tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-objenious-cron/",
"dev": "tsx watch index.ts", "dev": "tsx watch index.ts",
"start": "node ../../dist/packages/sim-objenious-cron/index.js" "start": "node ../../dist/packages/sim-objenious-cron/index.js"
}, },

View File

@@ -1,4 +1,4 @@
import { env } from "#config/env/index.js"; import { env } from "../config/env/index.js";
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"; import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
import axios from "axios"; import axios from "axios";
import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js"; import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js";
@@ -16,6 +16,7 @@ export class CheckObjeniousRequests {
* TODO: meter a una funcion a parte task con los 3 pasos * TODO: meter a una funcion a parte task con los 3 pasos
*/ */
public async getPendingOperations() { public async getPendingOperations() {
console.log("[i] Inicio revision de peticiones")
// 1. Se obtienen todas las operaciones pendientes de la BDD // 1. Se obtienen todas las operaciones pendientes de la BDD
const pendingOperations = await this.operationsRepository.getPendingOperations() const pendingOperations = await this.operationsRepository.getPendingOperations()
@@ -49,11 +50,14 @@ export class CheckObjeniousRequests {
console.log("[cron] Solicitando status para", merged.map(e => e.id)) console.log("[cron] Solicitando status para", merged.map(e => e.id))
const result = await this.getMassActionsStatus(merged) const result = await this.getMassActionsStatus(merged)
console.log("[o] Revisión de eventos completa")
} }
/** /**
* Para una lista de operaciones **con mass_action_id** se comprueba si han tenido alguna actualizacion * Para una lista de operaciones **con mass_action_id** se comprueba si han tenido alguna actualizacion
* Devuelve el numero de operaciones comprobadas. * Devuelve el numero de operaciones comprobadas.
* TODO: Esto va en un repositorio
*/ */
private async getMassActionsStatus(requestList: ObjeniousOperation[]) { private async getMassActionsStatus(requestList: ObjeniousOperation[]) {
if (requestList.length == 0) return 0; if (requestList.length == 0) return 0;
@@ -119,9 +123,6 @@ export class CheckObjeniousRequests {
if (uorStatus == "finished") { if (uorStatus == "finished") {
console.log(" ****> Status", uorStatus) console.log(" ****> Status", uorStatus)
if (uorStatus != "finished") {
console.error("!!! Notificando estado no finished")
}
const targetIccids = originalAction.iccids const targetIccids = originalAction.iccids
const lineData = await this.getLineData(targetIccids) const lineData = await this.getLineData(targetIccids)
console.log("[i] lineData", lineData.content[0]) console.log("[i] lineData", lineData.content[0])
@@ -136,7 +137,7 @@ export class CheckObjeniousRequests {
}) })
} }
if (originalAction.operation == "activation") { if (originalAction.operation == "activate") {
this.notifyFinalization({ this.notifyFinalization({
...originalAction, ...originalAction,
msisdn msisdn
@@ -215,7 +216,7 @@ export class CheckObjeniousRequests {
const PATH = "/actions/requests/" const PATH = "/actions/requests/"
const operationsList = structuredClone(requestList) const operationsList = structuredClone(requestList)
// TODO: El for es gigantesco hay que simplificar partes
for (const request of operationsList) { for (const request of operationsList) {
if (request.id == undefined) continue; if (request.id == undefined) continue;
@@ -228,13 +229,50 @@ export class CheckObjeniousRequests {
try { try {
res = await req res = await req
} catch (e) { } catch (e) {
console.error("Error comprobando el estado de ", request, e) console.error("[x] Error comprobando el estado de ", request, e)
//todo actualizar el estado para incluir el error continue;
}
// 2. Casos de error o id no generada
if (res.data.massActionIds.length == 0) {
// Si no hay es que *puede* que haya un problema o no se ha generado todavia
const reports = res.data.actionRequestReports
// Se entiende que no hay report ni id = está a la espera
if (reports.length == 0) continue;
// ! Hay minimo un report -> se considera error y se para
const updateData: ObjeniousOperationChange = {
operation_id: request.id,
new_status: "error",
error: JSON.stringify(reports[0].actionRequestReportDataDTOs)
}
const updateRes = await this.operationsRepository.updateOperation(updateData)
if (updateRes.error != undefined) {
console.error("[x] Error actualizando el estado de la operacion", updateData.error)
}
if (request.correlation_id != undefined) {
this.orderRepository.errorOrder({
correlation_id: request.correlation_id,
status: "failed",
error: "MassId no obtenida",
reason: "MassId no obtenida",
stackTrace: JSON.stringify(reports[0].actionRequestReportDataDTOs)
}).then(e => {
if (e.error != undefined) {
console.error("[x] Error actualizando el estado del Order con correlation_id: ", request.correlation_id)
console.error(e.error)
}
}).catch(e => {
console.error("[x] Error actualizando el estado del Order con correlation_id: ", request.correlation_id)
})
}
continue; continue;
} }
// 2. Modificacion del massId si ha habido un cambio
const massActionId = res.data.massActionIds[0] const massActionId = res.data.massActionIds[0]
// 3. Modificacion del massId si ha habido un cambio
try { try {
if (res.status == 200 && res.data != undefined && massActionId != undefined) { if (res.status == 200 && res.data != undefined && massActionId != undefined) {
const updateData: ObjeniousOperationChange = { const updateData: ObjeniousOperationChange = {
@@ -248,7 +286,7 @@ export class CheckObjeniousRequests {
request.mass_action_id = String(massActionId) request.mass_action_id = String(massActionId)
} }
} catch (e) { } catch (e) {
console.log("Error actualizando el estado de ", request) console.log("[x] Error actualizando el estado de ", request)
continue; continue;
} }
} }
@@ -262,6 +300,8 @@ export class CheckObjeniousRequests {
* al servicio que manda los mails * al servicio que manda los mails
*/ */
private async notifyFinalization(operation: ObjeniousOperation & { msisdn: string }) { private async notifyFinalization(operation: ObjeniousOperation & { msisdn: string }) {
console.log("[i] Enviando activacion a", env.NOTIFICATION_URL)
console.log("[i] Operation", operation)
const req = axios.post(env.NOTIFICATION_URL, { const req = axios.post(env.NOTIFICATION_URL, {
...operation, ...operation,
iccids: [operation.iccids] iccids: [operation.iccids]
@@ -270,7 +310,17 @@ export class CheckObjeniousRequests {
"x-apikey-sim-activation": env.SIM_ACTIVATION_API_KEY "x-apikey-sim-activation": env.SIM_ACTIVATION_API_KEY
} }
}) })
await req try {
const res = await req
if (res.status != 200) {
console.error("[x] Error enviando el mail de confirmacion para ", operation, " status ", res.status, res.statusText)
}
} catch (e) {
console.error("[x] Error enviando el mail de confirmacion para ", operation)
console.error(e)
}
} }
} }

View File

@@ -0,0 +1,133 @@
import assert from "node:assert";
import { lineToCreateLineDto, ObjeniousLine, ObjeniousLineResponse } from "sim-shared/domain/objeniousLine.js";
import { tryCatch, Result } from "sim-shared/domain/Result.js";
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js";
import { AxiosResponse } from "axios";
import { constants } from "node:buffer";
const MAX_PAGE_SIZE = 100
export class TaskVolcadoLineas {
constructor(
private readonly httpClient: HttpClient,
private readonly linesRepository: ObjeniousLinesRepository,
) {
}
/**
* Mover al repo
*/
private async * getLinesByStatus(args?: {
pageSize?: number,
pageNumber?: number,
status?: string
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
const path = "/lines"
const pageSize = args?.pageSize ?? MAX_PAGE_SIZE;
let currentPage = args?.pageNumber ?? 0;
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
const params: Record<string, string | number> = {}
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
if (args?.status != undefined) params["simStatus"] = args.status
params["pageSize"] = pageSize
params["pageNumber"] = page
console.log("Params", params)
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.httpClient.client.get(path, {
params: params
}))
if (nextPage.error != undefined) {
console.error(nextPage.error.msg)
return {
error: nextPage.error.msg.message
}
}
// Se aumenta para la siguiente ejecucion
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
totalPages = nextPage.data.data.totalPages
return {
data: nextPage.data.data.content
}
}
// El inicio se ejecuta siempre
const lines = await loadNextLine(currentPage)
if (lines.error != undefined) {
console.error("[x] Error obteniendo las lineas, cancelando operación");
return {
error: "Error cargando lineas"
}
}
currentPage++;
yield {
data: lines.data
}
// Copia para evitar bucles infinitos por error de la api
const maxPages = totalPages
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
console.log("maxPages", maxPages)
for (let i = currentPage; i < maxPages!; i++) {
console.log("Bucle i:", i, "page: ", currentPage)
yield await loadNextLine(currentPage);
currentPage++;
}
return {
data: []
}
}
private async saveLines(lines: ObjeniousLine[]) {
const linesToCreate = lines.map(lineToCreateLineDto)
let created: number[] = []
for (const line of linesToCreate) {
// Si es lento pasar a Promise.all
const res = await this.linesRepository.insertOrUpdate(line)
if (res?.id != undefined)
created.push(res.id)
}
}
public async loadLines() {
console.log("[i] Iniciando task de volcado de lineas de Objenious")
// Carga todas las lineas en memoria, hay que comprobar que no se gaste demasiada
const linesIterator = this.getLinesByStatus()
let lines = await linesIterator.next()
if (lines.value.error != undefined || lines.value.data == undefined) {
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
return;
}
await this.saveLines(lines.value.data)
while (!lines.done) {
console.log()
lines = await linesIterator.next()
if (lines.value.error != undefined || lines.value.data == undefined) {
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
return;
}
await this.saveLines(lines.value.data)
}
console.log("[i] Terminado task de volcado de lineas de Objenious")
}
}

View File

@@ -62,11 +62,14 @@ export type CreateOrderDTO = Pick<
'correlation_id' | 'exchange' | 'routing_key' | 'order_type' | 'payload' | 'webhook_host' | 'webhook_endpoint' 'correlation_id' | 'exchange' | 'routing_key' | 'order_type' | 'payload' | 'webhook_host' | 'webhook_endpoint'
>; >;
export type UpdateOrderDTO = type IdOrCorrelationID =
( (
{ id: number, correlation_id?: never } | { id: number, correlation_id?: never } |
{ id?: never, correlation_id: string } { id?: never, correlation_id: string }
) )
export type UpdateOrderDTO =
IdOrCorrelationID
& &
{ {
new_status: OrderStatus, new_status: OrderStatus,
@@ -74,12 +77,20 @@ export type UpdateOrderDTO =
} }
export type FinishOrderDTO = export type FinishOrderDTO =
( IdOrCorrelationID
{ id: number, correlation_id?: never } |
{ id?: never, correlation_id: string }
)
& &
{ {
reason?: string reason?: string
} }
export type ErrorOrderDTO =
IdOrCorrelationID
&
{
status: "failed" | "dlx",
reason: string,
error?: string,
stackTrace?: string
}

View File

@@ -0,0 +1,144 @@
export type ObjeniousLineResponse = {
content: ObjeniousLine[],
offset: number,
pageNumber: number,
pageSize: number,
paged: boolean,
totalPages: number,
totalElements: number
}
export type ObjeniousLine = {
identifier: {
simId: number,
iccid: string,
imsi: string,
msisdn: string,
amsisdn?: string,
imei: string
},
simCardType: {
code: string,
description: string
},
device: {
imei: string,
imeiChangeDate: string, //Fecha iso
deviceReference?: string | null,
manufacturer?: string | null,
},
customerAccount: {
code: string,
label: string,
address: {
address1: string,
address2: string,
address3: string,
zipCode: string,
city: string,
country: string,
state?: string | null
}
},
offer: {
code: string,
description: string,
},
party: {
name: string,
code: string,
contractReference: string,
partyType: string,
},
lineCustomFields: {
custom1: {
label: string | null,
value: string | null
},
custom2: {
label: string | null,
value: string | null
},
custom3: {
label: string | null,
value: string | null
},
custom4: {
label: string | null,
value: string | null
},
custom5: {
label: string | null,
value: string | null
},
custom6: {
label: string | null,
value: string | null
}
},
status: {
status: string,
preactivationDate: string | null, //"2026-03-17",
activationDate: string | null, //"2026-03-17T11:04:11.408+00:00",
commercialStatus: string, //"test",
commercialStatusDate: string, //"2026-03-17T11:41:01.493+00:00",
networkStatus: string, // "ACTIVATED",
billingStatus: string, //"TEST",
billingStatusChangeDate: string | null, // "2026-03-17T11:01:00.276+00:00",
billingActivationDate: string | null //,
createdDate: string | null,//"2026-01-30T01:50:02.060+00:00"
},
services: string | null
};
export type ObjeniousLineDb = {
id: number;
simId?: number;
iccid: string;
msisdn?: string;
imei?: string;
imeiChangeDate?: Date;
offerCode?: string;
status?: string;
preactivationDate?: Date | null;
activationDate?: Date | null;
commercialStatus?: string;
commercialStatusDate?: Date | null;
billingStatus?: string;
billingStatusChangeDate?: Date | null;
billingActivationDate?: Date | null;
createDate?: Date | null;
raw: ObjeniousLine;
}
// DTO para inserción (omite el ID autogenerado)
export type CreateObjeniousLineDTO = Omit<ObjeniousLineDb, 'id'>;
export function lineToCreateLineDto(line: ObjeniousLine): CreateObjeniousLineDTO {
const dateOrNull = (data: string | null) => {
if (data == null) return null;
return new Date(data)
}
const transformed: CreateObjeniousLineDTO = {
simId: line.identifier.simId,
iccid: line.identifier.iccid,
msisdn: line.identifier.msisdn,
imei: line.identifier.imei,
imeiChangeDate: new Date(line.device.imeiChangeDate),
offerCode: line.offer.code,
status: line.status.status,
preactivationDate: dateOrNull(line.status.preactivationDate),
activationDate: dateOrNull(line.status.activationDate),
commercialStatus: line.status.commercialStatus,
commercialStatusDate: dateOrNull(line.status.commercialStatusDate),
billingStatus: line.status.billingStatus,
billingStatusChangeDate: dateOrNull(line.status.activationDate),
billingActivationDate: dateOrNull(line.status.activationDate),
createDate: dateOrNull(line.status.activationDate),
raw: line
}
return transformed;
}

View File

@@ -12,7 +12,7 @@ export type ObjeniousOperation = {
id?: number; id?: number;
/** Uuid del mensaje asociado a la operacion */ /** Uuid del mensaje asociado a la operacion */
correlation_id?: string; correlation_id?: string;
operation: string; operation: "activate" | string; // TODO: completar y actualizar
retry_count?: number; retry_count?: number;
max_retry?: number; max_retry?: number;
max_date_retry?: string | null; max_date_retry?: string | null;
@@ -46,10 +46,34 @@ export namespace Objenious {
created: string, created: string,
status: "NEW" | "RUNNING" | "OK" | "KO" | "REPLAYED" | "CANCELLED" | "CLOSED" | "DISABLED", status: "NEW" | "RUNNING" | "OK" | "KO" | "REPLAYED" | "CANCELLED" | "CLOSED" | "DISABLED",
statusDate: string, statusDate: string,
actionType: "PREACTIVATION_AND_ACTIVATION" | string, // todo: añadir el resto actionType: ActionType
massActionIds: number[] massActionIds: number[],
actionRequestReports:
{
requestId: string,
actionRequestReportDataDTOs: [
{
data: string,
newData: string | null,
iccid: string,
dataStatus: DataStatus
}
]
}[],
} }
export type DataStatus = "DATA_INVALID_FORMAT" | "DATA_NOT_FOUND" | "DATA_NOT_ACTIVATED" | "SERVICE_DATA_NOT_ACTIVATED" |
"DATA_WRONG_STATUS" | "DATA_NOT_AUTHORIZED" | "DATA_CUSTOMER_ACCOUNT_NOT_AUTHORIZED" | "DATA_AMBIGUOUS" |
"NEW_DATA_INVALID_FORMAT" | "NEW_DATA_ALREADY_EXISTS" | "DUPLICATE_DATA" | "DATA_TERMINATION_VALIDATED" |
"DATA_TERMINATION_SECURISED" | "MAX_ALARM_INSTANCE" | "MAX_ALARM_INSTANCE_TO_CATCH_UP" |
"ACTIVATED_LINE_CANNOT_BE_TRANSFERED" | "ESIM_WRONG_STEP" | "ESIM_WRONG_PAIRED_VALUE" |
"ESIM_WRONG_DOWNLOAD_STATE" | "ESIM_WRONG_STATUS" | "ESIM_WRONG_FAMILY" | "ESIM_WRONG_CATEGORY" |
"ENTITY_STATUS_NOT_AUTHORIZED" | "LONG_LIFE_NOT_ALLOWED" | "RCARD_NOT_COMPATIBLE" | "APN_NOT_FOUND" |
"APN_OR_DNN_NOT_FOUND" | "APN_CONFIGURATION_NOT_FOUND" | "APN_CONFIGURATION_INVALID_PARAMETER_FILE" |
"IP_NOT_AVAILABLE" | "RADIUS_FIELD_LENGTH_NOT_ALLOWED" | "RADIUS_LOGIN_OR_PASSWORD_NOT_FOUND" | "RADIUS_PASSWORD_NOT_ALLOWED" |
"RADIUS_LOGIN_NOT_ALLOWED" | "NETWORK_NOT_ACTIVATED" | "CHANGE_CUSTOMER_ACCOUNT_NOT_AllOWED" | "CHANGE_OFFER_NOT_ALLOWED" |
"SIM_NOT_EUICC" | "OFFER_NOT_WSF_PALIER_FLOTTE_FR"
export type ActionType = "PREACTIVATION" | "PREACTIVATION_ACTIVATION" | "ACTIVATION" | export type ActionType = "PREACTIVATION" | "PREACTIVATION_ACTIVATION" | "ACTIVATION" |
"STATUS_CHANGE" | "ICCID_CHANGE" | "EUICC_NOTIFICATION" "STATUS_CHANGE" | "ICCID_CHANGE" | "EUICC_NOTIFICATION"
| "EUICC_AUDIT" | "MSISDN_CHANGE" | "ALARM_SETTING" | "EUICC_AUDIT" | "MSISDN_CHANGE" | "ALARM_SETTING"

View File

@@ -2,7 +2,7 @@
* TODO: Usar * TODO: Usar
*/ */
import { PoolClient, QueryResult, QueryResultRow } from "pg"; import { PoolClient, QueryResult, QueryResultRow } from "pg";
import { CreateOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js"; import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
import { Result } from "../domain/Result.js"; import { Result } from "../domain/Result.js";
import { PgClient } from "./PgClient.js"; import { PgClient } from "./PgClient.js";
import assert from "node:assert"; import assert from "node:assert";
@@ -353,22 +353,19 @@ export class OrderRepository {
} }
// TODO: tema de poder filtrar por correlation_id // TODO: tema de poder filtrar por correlation_id
public async errorOrder(args: { public async errorOrder(args: ErrorOrderDTO): Promise<Result<string, OrderTracking<any>>> {
id: number,
status: "failed" | "dlx",
reason: string,
error?: string,
stackTrace?: string
}) {
const client = await this.pgClient.connect(); const client = await this.pgClient.connect();
await client.query('BEGIN'); await client.query('BEGIN');
const idType = ('id' in args) ? "id" : "correlation_id"
const idValue = (args.id != undefined) ? args.id : args.correlation_id
// 1. Se consulta la order de base // 1. Se consulta la order de base
const qCurrentOrder = ` const qCurrentOrder = `
SELECT * FROM order_tracking SELECT * FROM order_tracking
WHERE id = $1 WHERE ${idType} = $1
` `
const vCurrentOrder = [args.id] const vCurrentOrder = [idValue]
const currentOrderResult = await this.getFirst(client.query<OrderTracking<any>>(qCurrentOrder, vCurrentOrder)) const currentOrderResult = await this.getFirst(client.query<OrderTracking<any>>(qCurrentOrder, vCurrentOrder))
@@ -378,6 +375,7 @@ export class OrderRepository {
return currentOrderResult return currentOrderResult
} }
const id = currentOrderResult.data.id // Saco el id para evitar busacr por correlation_id que es mas lento
const currentOrder = currentOrderResult.data! const currentOrder = currentOrderResult.data!
// 3. Si todo ok se actualiza el order // 3. Si todo ok se actualiza el order
@@ -395,7 +393,7 @@ export class OrderRepository {
WHERE id = $1 WHERE id = $1
RETURNING id, status, update_date; RETURNING id, status, update_date;
` `
const vOrderTracking = [args.id, args.status, args.error, args.stackTrace] const vOrderTracking = [id, args.status, args.error, args.stackTrace]
const updatedOrderResult = await this.getFirst( const updatedOrderResult = await this.getFirst(
client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking) client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking)
) )