import { ActionData, ActivationData } from "#domain/DTOs/objeniousapi.js" import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js" import { AxiosError } from "axios" import { Result } from "sim-shared/domain/Result.js" import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "sim-shared/domain/operationsRepository.port.js" import assert from "node:assert" import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js" import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js" import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js" import { ObjeniousLinesRepository } from "sim-shared/infrastructure/ObjeniousLinesRepository.js" import { error } from "node:console" import { ObjeniousLine, ObjeniousLineDb } from "sim-shared/domain/objeniousLine.js" // TODO: // - Pasar a un archivo de DTOs // - Mucha repeticion por funcion, deberia hacer una plantilla export class SimUseCases { private readonly httpClient: HttpClient private readonly objeniousRepository: ObjeniousOperationsRepository private readonly orderRepository: OrderRepository private readonly pauseRepository: PauseCancelTaskRepository private readonly objeniousLinesRepository: ObjeniousLinesRepository constructor(args: { httpClient: HttpClient, operationRepository: ObjeniousOperationsRepository, orderRepository: OrderRepository, pauseRepository: PauseCancelTaskRepository, objeniousLinesRepository: ObjeniousLinesRepository }) { this.httpClient = args.httpClient this.objeniousRepository = args.operationRepository this.orderRepository = args.orderRepository this.pauseRepository = args.pauseRepository this.objeniousLinesRepository = args.objeniousLinesRepository } private async logOperation(data: ObjeniousOperation) { await this.objeniousRepository.createOperation({ ...data }) } /** * Garantiza el flujo de todos los casos de uso de: * - Petición según la acción * - Control de errores * - Siempre devuelve un Result * - Almacena la operacion en la base de datos * - Actualiza el estado del order * * Necesita: * - Mas control según el codigo de error */ private generateUseCase< PAYLOAD, RESPONSETYPE extends { requestId: string } >(args: { correlation_id?: string, url: string, operation: string, operationPayload: PAYLOAD, iccid: string onError?: (_: any) => void // on code response?? }): () => Promise> { return async () => { const req = this.httpClient.client.post(args.url, { ...args.operationPayload }) try { const response = await req; if (response.status == 200) { assert(response.data.requestId != undefined) // Creacion de la operacion inicial, antes de tener los datos const operation: ObjeniousOperation = { operation: args.operation, iccids: String(args.iccid), status: "noMassID", request_id: response.data.requestId, correlation_id: args.correlation_id } // TODO: Esto tiene poco sentido si la operacion ya se // tenia que haber creado en el generador this.logOperation(operation) .then().catch(e => console.error("Error login operation", e)) if (args.correlation_id != undefined) { this.orderRepository.updateOrder({ correlation_id: args.correlation_id!, new_status: "running", // Siempre es runing la primera vez que se consume }) .then(e => console.log("Order actualizado: ", e)) .catch(e => console.error("Error actualizando order", args.correlation_id)) } return >{ error: undefined, data: true } } else { return { error: String(response.status), data: undefined } } } catch (error) { console.error(`[Sim.usecase] Error ${args.operation}`, (error as AxiosError).response?.status) return { error: "Error general de la peticion", data: undefined } } } } public activate(activationData: ActivationData): () => Promise> { const OPERATION_URL = "/actions/activateLine" return async () => { const iccid = activationData.identifier.identifiers // Comporbación excepcional para saber si la linea está suspendida const statusLinea = await this.objeniousRepository.getLinesAPI("ICCID", [String(iccid)]) if (statusLinea.data != undefined && statusLinea.data[0].status.networkStatus == "SUSPENDED") { const res = await this.reActivate(activationData)() return res; } const req = this.httpClient.client.post(OPERATION_URL, { dueDate: activationData.dueDate, identifier: activationData.identifier, customerAccountCode: activationData.customerAccountCode, offer: activationData.offer }) try { const response = await req if (response.status == 200) { console.log("Activacion con exito", response.data) const operation: ObjeniousOperation = { operation: "activate", iccids: String(activationData.identifier.identifiers), status: "noMassID", request_id: response.data.requestId } this.logOperation(operation).then().catch(e => console.error(e)) return >{ error: undefined, data: true } } else { // muy mejorable el control de errores return { error: String(response.status), data: undefined } } } catch (error) { console.error("[Sim.usecase] Error activando ", (error as AxiosError).response?.status) return { error: "Error general de la peticion", data: undefined } } } } public preActivate(preActivateData: ActionData): () => Promise> { const OPERATION_URL = "/actions/preactivateLine" return async () => { const req = this.httpClient.client.post(OPERATION_URL, { ...preActivateData }) try { const resp = await req if (resp.status == 200) { console.log("Sim preactivada con exito", resp.data) const operation: ObjeniousOperation = { correlation_id: preActivateData.correlation_id, operation: "preactivate", iccids: String(preActivateData.identifier.identifiers), status: "noMassID", request_id: resp.data.requestId } this.logOperation(operation).then().catch(e => console.error(e)) return >{ error: undefined, data: true } } else { return >{ error: String(resp.status), data: undefined } } } catch (error) { console.error("Error preactivacion", preActivateData) return >{ error: "Error preactivando la sim" + preActivateData.identifier, data: undefined } } } } public reActivate(reactivateData: ActionData): () => Promise> { const OPERATION_URL = "/actions/reactivateLine" return async () => { const req = this.httpClient.client.post(OPERATION_URL, { ...reactivateData }) try { const response = await req // Creacion de la operacion inicial, antes de tener los datos const operation: ObjeniousOperation = { operation: "reactivate", iccids: reactivateData.identifier.identifiers[0], status: "noMassID", request_id: response.data.requestId, correlation_id: reactivateData.correlation_id } // TODO: Esto tiene poco sentido si la operacion ya se // tenia que haber creado en el generador this.logOperation(operation) .then().catch(e => console.error("Error login operation", e)) if (response.status == 200) { console.log("[o] Sim solicitud de reactivacion ", response.data) return >{ error: undefined, data: true } } else { return { error: String(response.status), data: undefined } } } catch (error) { console.error("[x] Error reactivacion", (error as AxiosError).response?.status) return >{ error: "Error reactivando la sim" + reactivateData.identifier, data: undefined } } } } public suspend(suspendData: ActionData): () => Promise> { const OPERATION_URL = "/actions/suspendLine" return this.generateUseCase({ correlation_id: suspendData.correlation_id, operationPayload: { dueDate: suspendData.dueDate, identifier: suspendData.identifier }, url: OPERATION_URL, iccid: suspendData.identifier.identifiers[0], // operation: "suspend" }) } /** * Metodo muy especifico para obtener la fecha e activacion o en su defecto * la actual para saber cuando se va a completar el periodo de test de una linea */ private async findActivationDate(actionData: ActionData) { const iccid = actionData.identifier.identifiers const lineData = await this.objeniousRepository.getLinesAPI("ICCID", iccid) let activationDate = new Date() // Si no se pueden sacar datos de la linea guardo momentaneamente el error // pero no se cancela la operacion, el error puede ser de objenious y no nos // puede afectar //console.log("LineData", lineData.data) if (lineData.error != undefined) { console.error(lineData.error) } else { const activationDateStr = lineData.data[0].status.activationDate if (activationDateStr != undefined && activationDateStr != "") { activationDate = new Date(activationDateStr) } } return activationDate } /** * Paso previo a la suspension para evitar errores cuando el billing es test */ public stage_suspend(suspendData: ActionData): () => Promise> { return async (): Promise> => { const correlation_id = suspendData.correlation_id const iccid = suspendData.identifier.identifiers const operation: ObjeniousOperation = { operation: "suspend", iccids: iccid[0], status: "running", correlation_id: correlation_id } // No se registra hasta que no pase por la tabla de pausas // this.logOperation(operation) // .then().catch(e => console.error("Error login operation", e)) const fail = (error: string) => { console.error("[Sim.usecases]", error) if (correlation_id != undefined) { this.orderRepository.updateOrder({ correlation_id: correlation_id, new_status: "failed" }) } } // TODO REGISTRAR EL ORDER /* if (correlation_id != undefined) { await this.orderRepository.createOrder({ correlation_id: correlation_id, order_type: "pause" }) } */ let activationDate; try { activationDate = await this.findActivationDate(suspendData) } catch (e) { return { error: String(e) } } const newTask: CreatePauseCancelTaskDTO = { iccid: iccid[0], activation_date: activationDate, next_check: undefined, // Que se haga instantaneamente al ser la primera operation_type: "suspend", action_data: suspendData } const taskCreated = await this.pauseRepository.addTask(newTask) // Caso que la task no se pueda crear en la BDD if (taskCreated.error != undefined) { fail(taskCreated.error) return { error: taskCreated.error } } // Caso que se haya creado en la BDD if (correlation_id != undefined) { this.orderRepository.updateOrder({ correlation_id: correlation_id, new_status: "running" }) } return { data: true } } } /** * Paso previo a la suspension para evitar errores cuando el billing es test */ public stage_terminate(terminateData: ActionData): () => Promise> { return async (): Promise> => { const correlation_id = terminateData.correlation_id const iccid = terminateData.identifier.identifiers[0] const activationDate = await this.findActivationDate(terminateData) const newTask: CreatePauseCancelTaskDTO = { iccid: iccid, activation_date: activationDate, next_check: undefined, // Que se haga instantaneamente al ser la primera operation_type: "terminate", action_data: terminateData } const taskCreated = await this.pauseRepository.addTask(newTask) const operation: ObjeniousOperation = { operation: "terminate", iccids: iccid, status: "running", correlation_id: correlation_id } /** this.logOperation(operation) .then().catch(e => console.error("Error login operation", e)) */ // Caso que la task no se pueda crear en la BDD if (taskCreated.error != undefined) { console.error("[Sim.usecases]", taskCreated.error) if (correlation_id != undefined) { this.orderRepository.updateOrder({ correlation_id: correlation_id, new_status: "failed" }) } return { error: taskCreated.error } } // Caso que se haya creado en la BDD if (correlation_id != undefined) { this.orderRepository.updateOrder({ correlation_id: correlation_id, new_status: "running" }) } return { data: true } } } public terminate(terminationData: ActionData): () => Promise> { const OPERATION_URL = "/actions/terminateLine" return this.generateUseCase({ correlation_id: terminationData.correlation_id, operationPayload: { dueDate: terminationData.dueDate, identifier: terminationData.identifier }, url: OPERATION_URL, iccid: terminationData.identifier.identifiers[0], operation: "terminate" }) } /** * Calcula el tiempo que una linea ha estado en suspensión */ public async getSuspendedTime(iccid: string): Promise> { try { const result = await this.objeniousRepository.getSuspendedTime(iccid); if (result.error !== undefined) { return { error: result.error as string, data: undefined }; } return { data: { total_milliseconds: result.data!.total_milliseconds, total_days: result.data!.total_days } }; } catch (error) { console.error("[Sim.usecases] Error getting suspended time", error); return { error: "Error getting suspended time", data: undefined }; } } /** * Busqueda de líneas **en nuestro volcado** según una query y con paginacion */ public async getLinesByQuery(query: { status?: string | undefined }, pagination: { limit: number, offset: number }) : Promise> { try { const linesQuery = await this.objeniousLinesRepository.getLinesByStatus(query, pagination) return { data: linesQuery, } } catch (e) { return { error: String(e) } } } public async getLineByIccid(iccid: string): Promise> { const line = await this.objeniousRepository.getLineByIccid(iccid) if (line.error != undefined) { return { error: { msg: "Error general buscando la sim", code: 500 } } } if (line.data.length == 0) { return { error: { msg: "Sim no encontrada", code: 204 } } } return { data: line.data[0] } } }