diff --git a/deployment/database/base/xx-volcado-objenious.sql b/deployment/database/base/xx-volcado-objenious.sql new file mode 100644 index 0000000..4417b5c --- /dev/null +++ b/deployment/database/base/xx-volcado-objenious.sql @@ -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 +) diff --git a/docs/sim-objenious/Appel lines.bru b/docs/sim-objenious/Appel lines.bru index e10670f..afc428d 100644 --- a/docs/sim-objenious/Appel lines.bru +++ b/docs/sim-objenious/Appel lines.bru @@ -5,16 +5,16 @@ meta { } 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 auth: bearer } params:query { - pageSize: 10 - identifier.identifierType: ICCID - identifier.identifiers: 8933201125065160455 - ~simStatus: ACTIVATED + pageSize: 1000 + simStatus: ACTIVATED + ~identifier.identifierType: ICCID + ~identifier.identifiers: 8933201125065160455 } auth:bearer { diff --git a/packages/sim-objenious-cron/.env b/packages/sim-objenious-cron/.env index 4df9ec5..1121f37 100644 --- a/packages/sim-objenious-cron/.env +++ b/packages/sim-objenious-cron/.env @@ -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/test -# NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail" -NOTIFICATION_URL="localhost" +NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail" +# NOTIFICATION_URL="localhost" SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6 diff --git a/packages/sim-objenious-cron/config/env/index.ts b/packages/sim-objenious-cron/config/env/index.ts index 229480e..bf1b5f9 100644 --- a/packages/sim-objenious-cron/config/env/index.ts +++ b/packages/sim-objenious-cron/config/env/index.ts @@ -31,15 +31,15 @@ export const env = { OBJ_KID: String(process.env.OBJ_KID), OBJ_BASE_URL: String(process.env.OBJ_BASE_URL), - NOTIFICATION_URL: String(process.env.NOTIFICATION_URL), - SIM_ACTIVATION_API_KEY: String(process.env.SIM_ACTIVATION_API_KEY) + NOTIFICATION_URL: String(process.env.NOTIFICATION_URL ?? ""), + SIM_ACTIVATION_API_KEY: String(process.env.SIM_ACTIVATION_API_KEY ?? "") }; // assert las partes criticas assert(env.RABBITMQ_PASSWORD != undefined) assert(env.RABBITMQ_USER != undefined) -assert(env.SIM_ACTIVATION_API_KEY != undefined) -assert(env.NOTIFICATION_URL != undefined) +assert(env.SIM_ACTIVATION_API_KEY != "") +assert(env.NOTIFICATION_URL != "") if (env.ENVIRONMENT == "production") { assert(env.RABBITMQ_PASSWORD != "guest") @@ -47,3 +47,5 @@ if (env.ENVIRONMENT == "production") { } +console.log("[i] verificado env") + diff --git a/packages/sim-objenious-cron/config/intranetPostgresConfig.ts b/packages/sim-objenious-cron/config/intranetPostgresConfig.ts new file mode 100644 index 0000000..7797136 --- /dev/null +++ b/packages/sim-objenious-cron/config/intranetPostgresConfig.ts @@ -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 +}) diff --git a/packages/sim-objenious-cron/index.ts b/packages/sim-objenious-cron/index.ts index e9cf2ef..84e8cd7 100644 --- a/packages/sim-objenious-cron/index.ts +++ b/packages/sim-objenious-cron/index.ts @@ -5,6 +5,9 @@ import { httpInstance } from "./config/httpClient.config.js" import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js" import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.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() { const commonSettings = { @@ -14,10 +17,13 @@ async function startCron() { const httpClient = httpInstance const pgClient = new PgClient({ pool: pgPool }) + + console.log("[i] Comprobando conexion con la BDD ") await pgClient.checkDatabaseConnection() - await pgClient.checkDatabaseConnection() + const operationRepository = new ObjeniousOperationsRepository(pgClient) const orderRepository = new OrderRepository(pgClient) + const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet) const objTask = new CheckObjeniousRequests( operationRepository, @@ -25,23 +31,28 @@ async function startCron() { httpClient, ) - await objTask.getPendingOperations() + const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository) + const PERIODO_PETICIONES = 10 * 60 * 1000 const interval = setInterval(async () => { - console.log("Updating...") - await objTask.getPendingOperations() - console.log("Update finished") - }, 10 * 60 * 1000) - /* - const task = cron.createTask("* * * * *", async () => { - } - , { - ...commonSettings, - name: "Test" - }) -*/ + try { + await objTask.getPendingOperations() + } catch (e) { + console.error("[x] Error de actualizacion de las lineas ") + } + }, PERIODO_PETICIONES) + + const PERIODO_VOLCADO = 60 * 60 * 1000 + const volcadoInterval = setInterval(async () => { + try { + await volcadoLineasTask.loadLines() + } catch (e) { + console.error("[x] Volcado de lineas de Objenious Fallido", e) + } + }, PERIODO_VOLCADO) + + await volcadoLineasTask.loadLines() - //await objTask.getPendingOperations() } diff --git a/packages/sim-objenious-cron/infranstructure/ObjeniousLinesRepository.test.ts b/packages/sim-objenious-cron/infranstructure/ObjeniousLinesRepository.test.ts new file mode 100644 index 0000000..7fa5623 --- /dev/null +++ b/packages/sim-objenious-cron/infranstructure/ObjeniousLinesRepository.test.ts @@ -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") + }) +}) diff --git a/packages/sim-objenious-cron/infranstructure/ObjeniousLinesRepository.ts b/packages/sim-objenious-cron/infranstructure/ObjeniousLinesRepository.ts new file mode 100644 index 0000000..4bba217 --- /dev/null +++ b/packages/sim-objenious-cron/infranstructure/ObjeniousLinesRepository.ts @@ -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() + } + } + } +} diff --git a/packages/sim-objenious-cron/package.json b/packages/sim-objenious-cron/package.json index bf8da38..6e0d860 100644 --- a/packages/sim-objenious-cron/package.json +++ b/packages/sim-objenious-cron/package.json @@ -5,20 +5,6 @@ "description": "", "main": "index.ts", "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": { "types": "./infrastructure/*.ts", "default": "./infrastructure/*.js" @@ -45,8 +31,8 @@ } }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-objenious-cron/", + "test": "node --import tsx --test ./**/*.test.ts", + "build": "tsc --build && tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-objenious-cron/", "dev": "tsx watch index.ts", "start": "node ../../dist/packages/sim-objenious-cron/index.js" }, diff --git a/packages/sim-objenious-cron/tasks/check_objenious_request.ts b/packages/sim-objenious-cron/tasks/check_objenious_request.ts index 49c1f2a..05ba66b 100644 --- a/packages/sim-objenious-cron/tasks/check_objenious_request.ts +++ b/packages/sim-objenious-cron/tasks/check_objenious_request.ts @@ -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 axios from "axios"; 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 */ public async getPendingOperations() { + console.log("[i] Inicio revision de peticiones") // 1. Se obtienen todas las operaciones pendientes de la BDD const pendingOperations = await this.operationsRepository.getPendingOperations() @@ -49,11 +50,14 @@ export class CheckObjeniousRequests { console.log("[cron] Solicitando status para", merged.map(e => e.id)) 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 * Devuelve el numero de operaciones comprobadas. + * TODO: Esto va en un repositorio */ private async getMassActionsStatus(requestList: ObjeniousOperation[]) { if (requestList.length == 0) return 0; @@ -119,9 +123,6 @@ export class CheckObjeniousRequests { if (uorStatus == "finished") { console.log(" ****> Status", uorStatus) - if (uorStatus != "finished") { - console.error("!!! Notificando estado no finished") - } const targetIccids = originalAction.iccids const lineData = await this.getLineData(targetIccids) console.log("[i] lineData", lineData.content[0]) @@ -136,7 +137,7 @@ export class CheckObjeniousRequests { }) } - if (originalAction.operation == "activation") { + if (originalAction.operation == "activate") { this.notifyFinalization({ ...originalAction, msisdn @@ -215,7 +216,7 @@ export class CheckObjeniousRequests { const PATH = "/actions/requests/" const operationsList = structuredClone(requestList) - + // TODO: El for es gigantesco hay que simplificar partes for (const request of operationsList) { if (request.id == undefined) continue; @@ -228,13 +229,50 @@ export class CheckObjeniousRequests { try { res = await req } catch (e) { - console.error("Error comprobando el estado de ", request, e) - //todo actualizar el estado para incluir el error + console.error("[x] Error comprobando el estado de ", request, e) + 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; } - // 2. Modificacion del massId si ha habido un cambio const massActionId = res.data.massActionIds[0] + // 3. Modificacion del massId si ha habido un cambio try { if (res.status == 200 && res.data != undefined && massActionId != undefined) { const updateData: ObjeniousOperationChange = { @@ -248,7 +286,7 @@ export class CheckObjeniousRequests { request.mass_action_id = String(massActionId) } } catch (e) { - console.log("Error actualizando el estado de ", request) + console.log("[x] Error actualizando el estado de ", request) continue; } } @@ -262,6 +300,8 @@ export class CheckObjeniousRequests { * al servicio que manda los mails */ 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, { ...operation, iccids: [operation.iccids] @@ -270,7 +310,17 @@ export class CheckObjeniousRequests { "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) + } + + } } diff --git a/packages/sim-objenious-cron/tasks/volcado_lineas.ts b/packages/sim-objenious-cron/tasks/volcado_lineas.ts new file mode 100644 index 0000000..e8cbd8a --- /dev/null +++ b/packages/sim-objenious-cron/tasks/volcado_lineas.ts @@ -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, 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 = {} + + const loadNextLine = async (page: number): Promise> => { + 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>(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") + } + +} diff --git a/packages/sim-shared/domain/Order.ts b/packages/sim-shared/domain/Order.ts index 30ec573..7f3d35b 100644 --- a/packages/sim-shared/domain/Order.ts +++ b/packages/sim-shared/domain/Order.ts @@ -62,11 +62,14 @@ export type CreateOrderDTO = Pick< 'correlation_id' | 'exchange' | 'routing_key' | 'order_type' | 'payload' | 'webhook_host' | 'webhook_endpoint' >; -export type UpdateOrderDTO = +type IdOrCorrelationID = ( { id: number, correlation_id?: never } | { id?: never, correlation_id: string } ) + +export type UpdateOrderDTO = + IdOrCorrelationID & { new_status: OrderStatus, @@ -74,12 +77,20 @@ export type UpdateOrderDTO = } export type FinishOrderDTO = - ( - { id: number, correlation_id?: never } | - { id?: never, correlation_id: string } - ) + IdOrCorrelationID & { reason?: string } +export type ErrorOrderDTO = + IdOrCorrelationID + & + { + status: "failed" | "dlx", + reason: string, + error?: string, + stackTrace?: string + } + + diff --git a/packages/sim-shared/domain/objeniousLine.ts b/packages/sim-shared/domain/objeniousLine.ts new file mode 100644 index 0000000..c04eb30 --- /dev/null +++ b/packages/sim-shared/domain/objeniousLine.ts @@ -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; + +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; +} diff --git a/packages/sim-shared/domain/operationsRepository.port.ts b/packages/sim-shared/domain/operationsRepository.port.ts index 7af02c2..237210e 100644 --- a/packages/sim-shared/domain/operationsRepository.port.ts +++ b/packages/sim-shared/domain/operationsRepository.port.ts @@ -12,7 +12,7 @@ export type ObjeniousOperation = { id?: number; /** Uuid del mensaje asociado a la operacion */ correlation_id?: string; - operation: string; + operation: "activate" | string; // TODO: completar y actualizar retry_count?: number; max_retry?: number; max_date_retry?: string | null; @@ -46,10 +46,34 @@ export namespace Objenious { created: string, status: "NEW" | "RUNNING" | "OK" | "KO" | "REPLAYED" | "CANCELLED" | "CLOSED" | "DISABLED", statusDate: string, - actionType: "PREACTIVATION_AND_ACTIVATION" | string, // todo: añadir el resto - massActionIds: number[] + actionType: ActionType + 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" | "STATUS_CHANGE" | "ICCID_CHANGE" | "EUICC_NOTIFICATION" | "EUICC_AUDIT" | "MSISDN_CHANGE" | "ALARM_SETTING" diff --git a/packages/sim-shared/infrastructure/OrderRepository.ts b/packages/sim-shared/infrastructure/OrderRepository.ts index a6c23b5..15d18f8 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.ts @@ -2,7 +2,7 @@ * TODO: Usar */ 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 { PgClient } from "./PgClient.js"; import assert from "node:assert"; @@ -353,22 +353,19 @@ export class OrderRepository { } // TODO: tema de poder filtrar por correlation_id - public async errorOrder(args: { - id: number, - status: "failed" | "dlx", - reason: string, - error?: string, - stackTrace?: string - }) { + public async errorOrder(args: ErrorOrderDTO): Promise>> { const client = await this.pgClient.connect(); 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 const qCurrentOrder = ` SELECT * FROM order_tracking - WHERE id = $1 + WHERE ${idType} = $1 ` - const vCurrentOrder = [args.id] + const vCurrentOrder = [idValue] const currentOrderResult = await this.getFirst(client.query>(qCurrentOrder, vCurrentOrder)) @@ -378,6 +375,7 @@ export class OrderRepository { return currentOrderResult } + const id = currentOrderResult.data.id // Saco el id para evitar busacr por correlation_id que es mas lento const currentOrder = currentOrderResult.data! // 3. Si todo ok se actualiza el order @@ -395,7 +393,7 @@ export class OrderRepository { WHERE id = $1 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( client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking) )