Orders en los consumidores y gestion de los demas casos de uso
This commit is contained in:
@@ -4,7 +4,6 @@ import { SimUseCases } from "./Sim.usecases.js";
|
||||
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { env } from "#config/env/index.js";
|
||||
import { s } from "node_modules/vitest/dist/chunks/reporters.d.CWXNI2jG.js";
|
||||
|
||||
/**
|
||||
* La clase usa generadores de funciones para mantener el contexto
|
||||
@@ -84,7 +83,7 @@ export class SimController {
|
||||
throw new Error("Error activando la sim, no se ha especificado la oferta")
|
||||
}
|
||||
|
||||
const resp = this.tryUseCase(msg, this.useCases.activate({
|
||||
const resp = await this.tryUseCase(msg, this.useCases.activate({
|
||||
dueDate: this.genDueDate(DUE_DATE_SECONDS).toISOString(),
|
||||
customerAccountCode: env.OBJ_CUSTOMER_CODE,
|
||||
identifier: {
|
||||
@@ -118,7 +117,7 @@ export class SimController {
|
||||
}
|
||||
|
||||
const iccid = msgData.payload.iccid
|
||||
this.tryUseCase(msg, this.useCases.preActivate({
|
||||
const res = await this.tryUseCase(msg, this.useCases.preActivate({
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
@@ -144,7 +143,7 @@ export class SimController {
|
||||
}
|
||||
|
||||
const iccid = msgData.payload.iccid
|
||||
this.tryUseCase(msg, this.useCases.suspend({
|
||||
const res = await this.tryUseCase(msg, this.useCases.suspend({
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
@@ -169,7 +168,7 @@ export class SimController {
|
||||
}
|
||||
|
||||
const iccid = msgData.payload.iccid
|
||||
this.tryUseCase(msg, this.useCases.suspend({
|
||||
const res = await this.tryUseCase(msg, this.useCases.suspend({
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
@@ -194,7 +193,7 @@ export class SimController {
|
||||
}
|
||||
const iccid = msgData.payload.iccid
|
||||
console.log("Mensaje procesado", String(msgData))
|
||||
this.tryUseCase(msg, this.useCases.terminate({
|
||||
const res = await this.tryUseCase(msg, this.useCases.terminate({
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
|
||||
@@ -27,6 +27,8 @@ export class SimRouter {
|
||||
/**
|
||||
* Enruta el mensaje a la acción correspondiente basándose en la routing key
|
||||
* TODO: No estoy seguro que deba meter el nack aqui
|
||||
* - De moemento el ack-nack se gestiona en los controller, por si acaso hay casos
|
||||
* limite en
|
||||
*/
|
||||
public route = async (msg: ConsumeMessage | null): Promise<void> => {
|
||||
if (!msg) {
|
||||
|
||||
@@ -3,6 +3,8 @@ 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"
|
||||
|
||||
// TODO:
|
||||
// - Pasar a un archivo de DTOs
|
||||
@@ -11,12 +13,16 @@ import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort }
|
||||
export class SimUseCases {
|
||||
private readonly httpClient: HttpClient
|
||||
private readonly operationRepository: OperationsRepositoryPort
|
||||
private readonly orderRepository: OrderRepository
|
||||
|
||||
constructor(args: {
|
||||
httpClient: HttpClient,
|
||||
operationRepository: OperationsRepositoryPort
|
||||
operationRepository: OperationsRepositoryPort,
|
||||
orderRepository: OrderRepository
|
||||
}) {
|
||||
this.httpClient = args.httpClient
|
||||
this.operationRepository = args.operationRepository
|
||||
this.orderRepository = args.orderRepository
|
||||
}
|
||||
|
||||
private async logOperation(data: ObjeniousOperation) {
|
||||
@@ -25,6 +31,81 @@ export class SimUseCases {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Result<string, boolean>> {
|
||||
return async () => {
|
||||
const req = this.httpClient.client.post<RESPONSETYPE>(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
|
||||
}
|
||||
|
||||
this.logOperation(operation)
|
||||
.then().catch(e => console.error(e))
|
||||
|
||||
if (args.correlation_id != undefined) {
|
||||
this.orderRepository.updateOrder({
|
||||
correlation_id: args.correlation_id!,
|
||||
new_status: "running",
|
||||
})
|
||||
.then(e => console.log("Order actualizado: ", e))
|
||||
.catch(e => console.error("Error actualizando order", args.correlation_id))
|
||||
}
|
||||
|
||||
return <Result<string, boolean>>{
|
||||
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<Result<string, boolean>> {
|
||||
const OPERATION_URL = "/actions/activateLine"
|
||||
return async () => {
|
||||
@@ -51,8 +132,6 @@ export class SimUseCases {
|
||||
error: undefined,
|
||||
data: true
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
// muy mejorable el control de errores
|
||||
return {
|
||||
@@ -117,14 +196,22 @@ export class SimUseCases {
|
||||
})
|
||||
|
||||
try {
|
||||
const e = await req
|
||||
console.log("Sim reactivada con exito", e.data)
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
const response = await req
|
||||
|
||||
if (response.status == 200) {
|
||||
console.log("[o] Sim solicitud de reactivacion ", response.data)
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: String(response.status),
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error reactivacion", error)
|
||||
console.error("[x] Error reactivacion", (error as AxiosError).response?.status)
|
||||
return <Result<string, boolean>>{
|
||||
error: "Error reactivando la sim" + pauseData.identifier,
|
||||
data: undefined
|
||||
@@ -141,11 +228,26 @@ export class SimUseCases {
|
||||
})
|
||||
|
||||
try {
|
||||
const e = await req
|
||||
console.log("Sim pausada/suspendida con exito", e.data)
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
const response = await req
|
||||
if (response.status == 200) {
|
||||
console.log("[o] Sim pausada/suspendida con exito", response.data)
|
||||
const operation: ObjeniousOperation = {
|
||||
operation: "susupend",
|
||||
iccids: String(suspendData.identifier.identifiers),
|
||||
status: "noMassID",
|
||||
request_id: response.data.requestId
|
||||
}
|
||||
this.logOperation(operation).then().catch(e => console.error(e))
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
}
|
||||
} else {
|
||||
// muy mejorable el control de errores
|
||||
return {
|
||||
error: String(response.status),
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Pausa Use case] Error pausa")
|
||||
@@ -164,18 +266,30 @@ export class SimUseCases {
|
||||
...terminationData
|
||||
})
|
||||
|
||||
// TODO: para cuando estemos listos.
|
||||
throw new Error("Peticion no reversible desactivada de momento")
|
||||
|
||||
try {
|
||||
const e = await req
|
||||
console.log("Sim cancelada con exito", e.data)
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
const response = await req
|
||||
|
||||
if (response.status == 200) {
|
||||
console.log("[o] Sim solicitud de cancelacion con exito", response.data)
|
||||
const operation: ObjeniousOperation = {
|
||||
operation: "terminate",
|
||||
iccids: String(terminationData.identifier.identifiers),
|
||||
status: "noMassID",
|
||||
request_id: response.data.requestId
|
||||
}
|
||||
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: String(response.status),
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error pausa", error)
|
||||
console.error("[x ] Error en la solicitud de terminacion", error)
|
||||
return <Result<string, boolean>>{
|
||||
error: "Error cancelando/terminate la sim" + terminationData.identifier,
|
||||
data: undefined
|
||||
|
||||
@@ -23,6 +23,9 @@ export class SimUsecases {
|
||||
this.orderRepository = args.orderRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Añade un id de mensaje (correlation_id en ala base de datos)
|
||||
*/
|
||||
private addMessage_id(event: SimEvents.general): SimEvents.general & { headers: { message_id: string } } {
|
||||
const uuid = uuidv7()
|
||||
return {
|
||||
@@ -158,6 +161,7 @@ export class SimUsecases {
|
||||
}
|
||||
}
|
||||
const pauseWithId = this.addMessage_id(pauseEvent)
|
||||
console.log("[d] Cancelation ", pauseWithId)
|
||||
await this.eventBus.publish([pauseWithId])
|
||||
await this.saveOrder(pauseWithId)
|
||||
return pauseWithId
|
||||
|
||||
@@ -27,7 +27,7 @@ async function startCron() {
|
||||
console.log("Updating...")
|
||||
await objTask.getPendingOperations()
|
||||
console.log("Update finished")
|
||||
}, 60 * 1000)
|
||||
}, 10 * 60 * 1000)
|
||||
/*
|
||||
const task = cron.createTask("* * * * *", async () => {
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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";
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
@@ -6,6 +7,7 @@ import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
export class CheckObjeniousRequests {
|
||||
constructor(
|
||||
private readonly operationsRepository: IOperationsRepository,
|
||||
private readonly orderRepository: OrderRepository,
|
||||
private readonly httpClient: HttpClient
|
||||
) {
|
||||
}
|
||||
@@ -91,8 +93,10 @@ export class CheckObjeniousRequests {
|
||||
// 2. Se comprueba si ha habido un cambio de estado
|
||||
const { id, status, info } = data
|
||||
|
||||
if (status != originalAction.objenious_status) {
|
||||
const hasStatusChanged = status != originalAction.objenious_status
|
||||
if (hasStatusChanged) {
|
||||
console.log("[cron] Actualizando", originalAction.id, originalAction.iccids, status)
|
||||
/** Status convertido al que se usa en la aplicacion */
|
||||
const uorStatus = this.mapStatus(status)
|
||||
const updateData: ObjeniousOperationChange = {
|
||||
operation_id: originalAction.id!,
|
||||
@@ -102,27 +106,33 @@ export class CheckObjeniousRequests {
|
||||
previous_status: originalAction.status
|
||||
}
|
||||
|
||||
originalAction.status = uorStatus;
|
||||
originalAction.objenious_status = status;
|
||||
originalAction.last_change_date = new Date().toISOString()
|
||||
originalAction.end_date = originalAction.last_change_date
|
||||
console.log(" ----> Status", uorStatus)
|
||||
const updatedAction = structuredClone(originalAction)
|
||||
|
||||
updatedAction.status = uorStatus;
|
||||
updatedAction.objenious_status = status;
|
||||
updatedAction.last_change_date = new Date().toISOString()
|
||||
updatedAction.end_date = originalAction.last_change_date
|
||||
|
||||
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("lineData", lineData.content[0])
|
||||
console.log("[i] lineData", lineData.content[0])
|
||||
const msisdn = lineData.content[0].identifier.msisdn
|
||||
|
||||
this.notifyFinalization({
|
||||
...originalAction,
|
||||
msisdn
|
||||
})
|
||||
// TODO la accion no siempre es activacion!
|
||||
.then(e => {
|
||||
console.log("Notificada la activacion de ", originalAction.iccids)
|
||||
console.log("[o] Notificada la activacion de ", originalAction.iccids)
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("Error enviando la activacion de ", originalAction)
|
||||
console.error("[x] Error enviando la activacion de ", originalAction)
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
@@ -132,12 +142,12 @@ export class CheckObjeniousRequests {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("Subiendo un update")
|
||||
console.log("[i] Subiendo un update")
|
||||
console.log(updateData)
|
||||
await this.operationsRepository.updateOperation(updateData)
|
||||
updated.push(originalAction)
|
||||
} catch (e) {
|
||||
console.error("Error actualizando el estado de ", originalAction, e)
|
||||
console.error("[x] Error actualizando el estado de ", originalAction, e)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,3 +61,14 @@ export type CreateOrderDTO = Pick<
|
||||
OrderTracking<any>, // Aqui realmente no importan los campos
|
||||
'correlation_id' | 'exchange' | 'routing_key' | 'order_type' | 'payload' | 'webhook_host' | 'webhook_endpoint'
|
||||
>;
|
||||
|
||||
export type UpdateOrderDTO =
|
||||
(
|
||||
{ id: number, correlation_id?: never } |
|
||||
{ id?: never, correlation_id: string }
|
||||
)
|
||||
&
|
||||
{
|
||||
new_status: OrderStatus,
|
||||
reason?: string
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
export type Result<E, D> =
|
||||
{
|
||||
error: E,
|
||||
data: undefined
|
||||
data?: undefined
|
||||
}
|
||||
|
|
||||
{
|
||||
error: undefined,
|
||||
error?: undefined,
|
||||
data: D
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
* TODO: Usar
|
||||
*/
|
||||
import { PoolClient, QueryResult, QueryResultRow } from "pg";
|
||||
import { CreateOrderDTO, OrderTracking } from "../domain/Order.js";
|
||||
import { CreateOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
|
||||
import { Result } from "../domain/Result.js";
|
||||
import { PgClient } from "./PgClient.js";
|
||||
import assert from "node:assert";
|
||||
import { error } from "node:console";
|
||||
|
||||
/**
|
||||
* Agrupa todas las operaciones de *Order*.
|
||||
@@ -164,25 +166,35 @@ export class OrderRepository {
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Actualizacion "correcta" del estado de un order
|
||||
*/
|
||||
public async updateOrder(args: {
|
||||
id: number,
|
||||
new_status: string,
|
||||
reason: string
|
||||
}) {
|
||||
public async updateOrder(args: UpdateOrderDTO): Promise<Result<string, OrderTracking<any>>> {
|
||||
// XOR id o correlation_id
|
||||
assert((args.id != undefined) != (args.correlation_id != undefined))
|
||||
const client = await this.pgClient.connect();
|
||||
await client.query('BEGIN');
|
||||
|
||||
const idType = ('id' in args) ? "correlation_id" : "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<OrderTracking<any>>(qCurrentOrder, vCurrentOrder))
|
||||
const orderId = currentOrderResult.data?.id
|
||||
|
||||
if (orderId == undefined) {
|
||||
return {
|
||||
error: "El order a actualizar no existe " + idType + ": " + idValue
|
||||
}
|
||||
}
|
||||
|
||||
if (currentOrderResult.error != undefined) {
|
||||
await client.query("ROLLBACK")
|
||||
@@ -201,7 +213,7 @@ export class OrderRepository {
|
||||
WHERE id = $1
|
||||
RETURNING id, status, update_date;
|
||||
`
|
||||
const vOrderTracking = [args.id, args.new_status]
|
||||
const vOrderTracking = [orderId, args.new_status]
|
||||
const updatedOrderResult = await this.getFirst(
|
||||
client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user