@javalabs/prisma-client 1.0.18 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +14 -0
- package/Dockerfile +23 -0
- package/README.md +269 -269
- package/dist/index.d.ts +1 -1
- package/dist/prisma.service.d.ts +1 -1
- package/dist/scripts/add-uuid-to-table.js +32 -32
- package/dist/scripts/data-migration/batch-migrator.js +12 -12
- package/dist/scripts/data-migration/data-transformer.js +14 -14
- package/dist/scripts/data-migration/dependency-resolver.js +23 -23
- package/dist/scripts/data-migration/entity-discovery.js +68 -68
- package/dist/scripts/data-migration/foreign-key-manager.js +23 -23
- package/dist/scripts/data-migration/migration-tool.js +5 -5
- package/dist/scripts/data-migration/schema-utils.js +74 -74
- package/dist/scripts/data-migration/typecast-manager.js +4 -4
- package/dist/scripts/database-initializer.js +5 -5
- package/dist/scripts/drop-database.js +5 -5
- package/dist/scripts/fix-data-types.js +53 -53
- package/dist/scripts/fix-enum-values.js +34 -34
- package/dist/scripts/fix-schema-discrepancies.js +40 -40
- package/dist/scripts/fix-table-indexes.js +81 -81
- package/dist/scripts/migrate-schema-structure.js +4 -4
- package/dist/scripts/migrate-uuid.js +19 -19
- package/dist/scripts/post-migration-validator.js +49 -49
- package/dist/scripts/pre-migration-validator.js +107 -107
- package/dist/scripts/reset-database.js +21 -21
- package/dist/scripts/retry-failed-migrations.js +28 -28
- package/dist/scripts/run-migration.js +5 -5
- package/dist/scripts/schema-sync.js +18 -18
- package/dist/scripts/sequence-sync-cli.js +55 -55
- package/dist/scripts/sequence-synchronizer.js +20 -20
- package/dist/scripts/sync-enum-types.js +30 -30
- package/dist/scripts/sync-enum-values.js +52 -52
- package/dist/scripts/truncate-database.js +10 -10
- package/dist/scripts/verify-migration-setup.js +10 -10
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/migration-config.json +63 -63
- package/migration-config.json.bk +95 -95
- package/package.json +44 -44
- package/prisma/migrations/add_accepts_partial_payments_to_users.sql +19 -0
- package/prisma/migrations/add_amount_received_to_manual_payments.sql +19 -0
- package/prisma/migrations/add_commission_fields.sql +33 -0
- package/prisma/migrations/add_uuid_to_transactions.sql +13 -13
- package/prisma/migrations/complete_partial_payments_migration.sql +53 -0
- package/prisma/migrations/create_settlements_table.sql +60 -0
- package/prisma/schema.prisma +47 -1
- package/src/index.ts +23 -23
- package/src/prisma-factory.service.ts +40 -40
- package/src/prisma.module.ts +9 -9
- package/src/prisma.service.ts +16 -16
- package/src/scripts/add-uuid-to-table.ts +138 -138
- package/src/scripts/create-tenant-schemas.ts +145 -145
- package/src/scripts/data-migration/batch-migrator.ts +248 -248
- package/src/scripts/data-migration/data-transformer.ts +426 -426
- package/src/scripts/data-migration/db-connector.ts +120 -120
- package/src/scripts/data-migration/dependency-resolver.ts +174 -174
- package/src/scripts/data-migration/entity-discovery.ts +196 -196
- package/src/scripts/data-migration/foreign-key-manager.ts +277 -277
- package/src/scripts/data-migration/migration-config.json +63 -63
- package/src/scripts/data-migration/migration-tool.ts +509 -509
- package/src/scripts/data-migration/schema-utils.ts +248 -248
- package/src/scripts/data-migration/tenant-migrator.ts +201 -201
- package/src/scripts/data-migration/typecast-manager.ts +193 -193
- package/src/scripts/data-migration/types.ts +113 -113
- package/src/scripts/database-initializer.ts +49 -49
- package/src/scripts/drop-database.ts +104 -104
- package/src/scripts/dump-source-db.sh +61 -61
- package/src/scripts/encrypt-user-passwords.ts +36 -36
- package/src/scripts/error-handler.ts +117 -117
- package/src/scripts/fix-data-types.ts +241 -241
- package/src/scripts/fix-enum-values.ts +357 -357
- package/src/scripts/fix-schema-discrepancies.ts +317 -317
- package/src/scripts/fix-table-indexes.ts +601 -601
- package/src/scripts/migrate-schema-structure.ts +90 -90
- package/src/scripts/migrate-uuid.ts +76 -76
- package/src/scripts/post-migration-validator.ts +526 -526
- package/src/scripts/pre-migration-validator.ts +610 -610
- package/src/scripts/reset-database.ts +263 -263
- package/src/scripts/retry-failed-migrations.ts +416 -416
- package/src/scripts/run-migration.ts +707 -707
- package/src/scripts/schema-sync.ts +128 -128
- package/src/scripts/sequence-sync-cli.ts +416 -416
- package/src/scripts/sequence-synchronizer.ts +127 -127
- package/src/scripts/sync-enum-types.ts +170 -170
- package/src/scripts/sync-enum-values.ts +563 -563
- package/src/scripts/truncate-database.ts +123 -123
- package/src/scripts/verify-migration-setup.ts +135 -135
- package/tsconfig.json +17 -17
- package/dist/scripts/data-migration/dependency-manager.d.ts +0 -9
- package/dist/scripts/data-migration/dependency-manager.js +0 -86
- package/dist/scripts/data-migration/dependency-manager.js.map +0 -1
- package/dist/scripts/data-migration/migration-config.json +0 -63
- package/dist/scripts/data-migration/migration-phases.d.ts +0 -5
- package/dist/scripts/data-migration/migration-phases.js +0 -55
- package/dist/scripts/data-migration/migration-phases.js.map +0 -1
- package/dist/scripts/data-migration/multi-source-migrator.d.ts +0 -17
- package/dist/scripts/data-migration/multi-source-migrator.js +0 -130
- package/dist/scripts/data-migration/multi-source-migrator.js.map +0 -1
- package/dist/scripts/data-migration/phase-generator.d.ts +0 -15
- package/dist/scripts/data-migration/phase-generator.js +0 -187
- package/dist/scripts/data-migration/phase-generator.js.map +0 -1
- package/dist/scripts/data-migration.d.ts +0 -22
- package/dist/scripts/data-migration.js +0 -593
- package/dist/scripts/data-migration.js.map +0 -1
- package/dist/scripts/multi-db-migration.d.ts +0 -1
- package/dist/scripts/multi-db-migration.js +0 -55
- package/dist/scripts/multi-db-migration.js.map +0 -1
|
@@ -1,563 +1,563 @@
|
|
|
1
|
-
import { Logger } from "@nestjs/common";
|
|
2
|
-
import { Pool } from "pg";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import * as fs from "fs";
|
|
5
|
-
import * as dotenv from "dotenv";
|
|
6
|
-
|
|
7
|
-
dotenv.config();
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Clase para sincronizar valores entre enums específicos y enums consolidados
|
|
11
|
-
* Esta herramienta asegura que los valores en los enums específicos (deprecados)
|
|
12
|
-
* sean compatibles con los enums consolidados durante la migración.
|
|
13
|
-
*/
|
|
14
|
-
export class EnumValueSynchronizer {
|
|
15
|
-
private readonly logger = new Logger("EnumValueSynchronizer");
|
|
16
|
-
private readonly pool: Pool;
|
|
17
|
-
|
|
18
|
-
// Mapeo de enums específicos a enums consolidados
|
|
19
|
-
private readonly enumMappings: Record<string, string> = {
|
|
20
|
-
enum_credit_requests_status: "enum_transaction_status",
|
|
21
|
-
enum_global_user_transactions_status: "enum_transaction_status",
|
|
22
|
-
enum_global_user_transactions_transaction_type: "enum_transaction_type",
|
|
23
|
-
enum_invoices_status: "enum_transaction_status",
|
|
24
|
-
enum_payin_status: "enum_transaction_status",
|
|
25
|
-
enum_payout_status: "enum_transaction_status",
|
|
26
|
-
enum_pending_references_status: "enum_transaction_status",
|
|
27
|
-
enum_toku_status: "enum_transaction_status",
|
|
28
|
-
enum_transaction_updates_new_status: "enum_transaction_status",
|
|
29
|
-
enum_transactions_account_type: "enum_account_type",
|
|
30
|
-
enum_transactions_status: "enum_transaction_status",
|
|
31
|
-
enum_transactions_transaction_type: "enum_transaction_type",
|
|
32
|
-
enum_transactions_typetransaction: "enum_transaction_type",
|
|
33
|
-
enum_transactions_user_type_account: "enum_account_type",
|
|
34
|
-
enum_transactions_usertypeaccount: "enum_account_type",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Mapeo de valores de texto a valores de enum válidos
|
|
38
|
-
private readonly valueNormalizationMap: Record<
|
|
39
|
-
string,
|
|
40
|
-
Record<string, string>
|
|
41
|
-
> = {
|
|
42
|
-
// Mapeos para enum_transaction_status
|
|
43
|
-
enum_transaction_status: {
|
|
44
|
-
PENDING: "PENDING",
|
|
45
|
-
pending: "PENDING",
|
|
46
|
-
COMPLETED: "COMPLETED",
|
|
47
|
-
completed: "COMPLETED",
|
|
48
|
-
FAILED: "FAILED",
|
|
49
|
-
failed: "FAILED",
|
|
50
|
-
PROCESSING: "PROCESSING",
|
|
51
|
-
processing: "PROCESSING",
|
|
52
|
-
CANCELLED: "CANCELLED",
|
|
53
|
-
cancelled: "CANCELLED",
|
|
54
|
-
canceled: "CANCELLED",
|
|
55
|
-
CANCELED: "CANCELLED",
|
|
56
|
-
REJECTED: "REJECTED",
|
|
57
|
-
rejected: "REJECTED",
|
|
58
|
-
APPROVED: "APPROVED",
|
|
59
|
-
approved: "APPROVED",
|
|
60
|
-
EXPIRED: "EXPIRED",
|
|
61
|
-
expired: "EXPIRED",
|
|
62
|
-
REFUNDED: "REFUNDED",
|
|
63
|
-
refunded: "REFUNDED",
|
|
64
|
-
PARTIAL_REFUND: "PARTIAL_REFUND",
|
|
65
|
-
partial_refund: "PARTIAL_REFUND",
|
|
66
|
-
},
|
|
67
|
-
// Mapeos para enum_transaction_type
|
|
68
|
-
enum_transaction_type: {
|
|
69
|
-
DEPOSIT: "DEPOSIT",
|
|
70
|
-
deposit: "DEPOSIT",
|
|
71
|
-
WITHDRAWAL: "WITHDRAWAL",
|
|
72
|
-
withdrawal: "WITHDRAWAL",
|
|
73
|
-
TRANSFER: "TRANSFER",
|
|
74
|
-
transfer: "TRANSFER",
|
|
75
|
-
PAYMENT: "PAYMENT",
|
|
76
|
-
payment: "PAYMENT",
|
|
77
|
-
REFUND: "REFUND",
|
|
78
|
-
refund: "REFUND",
|
|
79
|
-
FEE: "FEE",
|
|
80
|
-
fee: "FEE",
|
|
81
|
-
ADJUSTMENT: "ADJUSTMENT",
|
|
82
|
-
adjustment: "ADJUSTMENT",
|
|
83
|
-
},
|
|
84
|
-
// Mapeos para enum_account_type
|
|
85
|
-
enum_account_type: {
|
|
86
|
-
PERSONAL: "PERSONAL",
|
|
87
|
-
personal: "PERSONAL",
|
|
88
|
-
BUSINESS: "BUSINESS",
|
|
89
|
-
business: "BUSINESS",
|
|
90
|
-
MERCHANT: "MERCHANT",
|
|
91
|
-
merchant: "MERCHANT",
|
|
92
|
-
PLATFORM: "PLATFORM",
|
|
93
|
-
platform: "PLATFORM",
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Caché para valores de enum
|
|
98
|
-
private enumValuesCache: Record<string, string[]> = {};
|
|
99
|
-
|
|
100
|
-
constructor(
|
|
101
|
-
private readonly sourceUrl: string = process.env.SOURCE_DATABASE_URL,
|
|
102
|
-
private readonly targetUrl: string = process.env.DATABASE_URL
|
|
103
|
-
) {
|
|
104
|
-
this.pool = new Pool({
|
|
105
|
-
connectionString: this.targetUrl,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Sincroniza los valores entre enums específicos y enums consolidados
|
|
111
|
-
* y normaliza los valores de texto a valores de enum válidos
|
|
112
|
-
*/
|
|
113
|
-
async synchronizeEnumValues() {
|
|
114
|
-
try {
|
|
115
|
-
this.logger.log("Iniciando sincronización de valores de enum");
|
|
116
|
-
|
|
117
|
-
// Crear directorio para logs si no existe
|
|
118
|
-
const logsDir = path.join(process.cwd(), "migration-logs");
|
|
119
|
-
if (!fs.existsSync(logsDir)) {
|
|
120
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Obtener todos los tipos enum en la base de datos
|
|
124
|
-
const enumsResult = await this.pool.query(`
|
|
125
|
-
SELECT t.typname AS enum_name
|
|
126
|
-
FROM pg_type t
|
|
127
|
-
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
128
|
-
WHERE t.typtype = 'e' -- enum types
|
|
129
|
-
AND n.nspname = 'public'
|
|
130
|
-
ORDER BY t.typname
|
|
131
|
-
`);
|
|
132
|
-
|
|
133
|
-
this.logger.log(
|
|
134
|
-
`Encontrados ${enumsResult.rows.length} tipos enum en la base de datos`
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// Procesar cada enum específico y su correspondiente enum consolidado
|
|
138
|
-
for (const [specificEnum, consolidatedEnum] of Object.entries(
|
|
139
|
-
this.enumMappings
|
|
140
|
-
)) {
|
|
141
|
-
await this.synchronizeSpecificEnum(specificEnum, consolidatedEnum);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Después de sincronizar los enums, normalizar los valores en las tablas
|
|
145
|
-
await this.normalizeEnumValuesInTables();
|
|
146
|
-
|
|
147
|
-
this.logger.log("Sincronización de valores de enum completada con éxito");
|
|
148
|
-
} catch (error) {
|
|
149
|
-
this.logger.error(
|
|
150
|
-
`Error durante la sincronización de valores de enum: ${error.message}`,
|
|
151
|
-
error.stack
|
|
152
|
-
);
|
|
153
|
-
throw error;
|
|
154
|
-
} finally {
|
|
155
|
-
await this.pool.end();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Sincroniza los valores entre un enum específico y su enum consolidado
|
|
161
|
-
*/
|
|
162
|
-
private async synchronizeSpecificEnum(
|
|
163
|
-
specificEnum: string,
|
|
164
|
-
consolidatedEnum: string
|
|
165
|
-
) {
|
|
166
|
-
try {
|
|
167
|
-
this.logger.log(
|
|
168
|
-
`Sincronizando valores entre ${specificEnum} y ${consolidatedEnum}`
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
// Verificar si ambos enums existen
|
|
172
|
-
const specificEnumExists = await this.enumExists(specificEnum);
|
|
173
|
-
const consolidatedEnumExists = await this.enumExists(consolidatedEnum);
|
|
174
|
-
|
|
175
|
-
if (!specificEnumExists || !consolidatedEnumExists) {
|
|
176
|
-
this.logger.warn(
|
|
177
|
-
`No se puede sincronizar: ${
|
|
178
|
-
!specificEnumExists ? specificEnum : consolidatedEnum
|
|
179
|
-
} no existe`
|
|
180
|
-
);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Obtener valores de ambos enums
|
|
185
|
-
const specificValues = await this.getEnumValues(specificEnum);
|
|
186
|
-
const consolidatedValues = await this.getEnumValues(consolidatedEnum);
|
|
187
|
-
|
|
188
|
-
this.logger.log(
|
|
189
|
-
`Valores en ${specificEnum}: ${specificValues.join(", ")}\n` +
|
|
190
|
-
`Valores en ${consolidatedEnum}: ${consolidatedValues.join(", ")}`
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
// Verificar valores que están en el enum específico pero no en el consolidado
|
|
194
|
-
const missingValues = specificValues.filter(
|
|
195
|
-
(value) => !consolidatedValues.includes(value)
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
if (missingValues.length === 0) {
|
|
199
|
-
this.logger.log(
|
|
200
|
-
`No hay valores faltantes entre ${specificEnum} y ${consolidatedEnum}`
|
|
201
|
-
);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
this.logger.log(
|
|
206
|
-
`Encontrados ${
|
|
207
|
-
missingValues.length
|
|
208
|
-
} valores en ${specificEnum} que faltan en ${consolidatedEnum}: ${missingValues.join(
|
|
209
|
-
", "
|
|
210
|
-
)}`
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
// Agregar los valores faltantes al enum consolidado
|
|
214
|
-
for (const value of missingValues) {
|
|
215
|
-
await this.addValueToEnum(consolidatedEnum, value);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
this.logger.log(
|
|
219
|
-
`Sincronización entre ${specificEnum} y ${consolidatedEnum} completada`
|
|
220
|
-
);
|
|
221
|
-
} catch (error) {
|
|
222
|
-
this.logger.error(
|
|
223
|
-
`Error sincronizando ${specificEnum} con ${consolidatedEnum}: ${error.message}`
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Verifica si un enum existe en la base de datos
|
|
230
|
-
*/
|
|
231
|
-
private async enumExists(enumName: string): Promise<boolean> {
|
|
232
|
-
const result = await this.pool.query(
|
|
233
|
-
`
|
|
234
|
-
SELECT 1
|
|
235
|
-
FROM pg_type t
|
|
236
|
-
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
237
|
-
WHERE t.typname = $1
|
|
238
|
-
AND t.typtype = 'e' -- enum types
|
|
239
|
-
AND n.nspname = 'public'
|
|
240
|
-
`,
|
|
241
|
-
[enumName]
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
return result.rows.length > 0;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Obtiene los valores de un enum
|
|
249
|
-
*/
|
|
250
|
-
private async getEnumValues(enumName: string): Promise<string[]> {
|
|
251
|
-
const result = await this.pool.query(
|
|
252
|
-
`
|
|
253
|
-
SELECT e.enumlabel
|
|
254
|
-
FROM pg_enum e
|
|
255
|
-
JOIN pg_type t ON e.enumtypid = t.oid
|
|
256
|
-
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
257
|
-
WHERE t.typname = $1
|
|
258
|
-
AND n.nspname = 'public'
|
|
259
|
-
ORDER BY e.enumsortorder
|
|
260
|
-
`,
|
|
261
|
-
[enumName]
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
return result.rows.map((row) => row.enumlabel);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Agrega un valor a un enum existente
|
|
269
|
-
*/
|
|
270
|
-
private async addValueToEnum(enumName: string, value: string) {
|
|
271
|
-
try {
|
|
272
|
-
// Escapar comillas simples en el valor
|
|
273
|
-
const escapedValue = value.replace(/'/g, "''");
|
|
274
|
-
|
|
275
|
-
// Agregar el valor al enum
|
|
276
|
-
await this.pool.query(`
|
|
277
|
-
ALTER TYPE ${enumName} ADD VALUE IF NOT EXISTS '${escapedValue}'
|
|
278
|
-
`);
|
|
279
|
-
|
|
280
|
-
// Actualizar la caché de valores de enum
|
|
281
|
-
if (this.enumValuesCache[enumName]) {
|
|
282
|
-
if (!this.enumValuesCache[enumName].includes(value)) {
|
|
283
|
-
this.enumValuesCache[enumName].push(value);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
this.logger.log(`Valor '${value}' agregado al enum ${enumName}`);
|
|
288
|
-
} catch (error) {
|
|
289
|
-
this.logger.error(
|
|
290
|
-
`Error agregando valor '${value}' al enum ${enumName}: ${error.message}`
|
|
291
|
-
);
|
|
292
|
-
throw error;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Normaliza los valores de enum en todas las tablas
|
|
298
|
-
*/
|
|
299
|
-
private async normalizeEnumValuesInTables() {
|
|
300
|
-
this.logger.log("Iniciando normalización de valores de enum en tablas");
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
// Obtener todos los schemas excepto los del sistema
|
|
304
|
-
const schemas = await this.getSchemas();
|
|
305
|
-
this.logger.log(`Encontrados ${schemas.length} schemas para procesar`);
|
|
306
|
-
|
|
307
|
-
// Para cada schema, normalizar los valores de enum en sus tablas
|
|
308
|
-
for (const schema of schemas) {
|
|
309
|
-
await this.normalizeEnumValuesInSchema(schema);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
this.logger.log("Normalización de valores de enum en tablas completada");
|
|
313
|
-
} catch (error) {
|
|
314
|
-
this.logger.error(
|
|
315
|
-
`Error durante la normalización de valores de enum: ${error.message}`,
|
|
316
|
-
error.stack
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Obtiene todos los schemas de la base de datos excepto los del sistema
|
|
323
|
-
*/
|
|
324
|
-
private async getSchemas(): Promise<string[]> {
|
|
325
|
-
const result = await this.pool.query(`
|
|
326
|
-
SELECT schema_name
|
|
327
|
-
FROM information_schema.schemata
|
|
328
|
-
WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
|
|
329
|
-
AND schema_name NOT LIKE 'pg_%'
|
|
330
|
-
`);
|
|
331
|
-
|
|
332
|
-
return result.rows.map((row) => row.schema_name);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Normaliza los valores de enum en un schema específico
|
|
337
|
-
*/
|
|
338
|
-
private async normalizeEnumValuesInSchema(schema: string) {
|
|
339
|
-
this.logger.log(`Procesando valores de enum en schema: ${schema}`);
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
// Obtener todas las columnas enum en este schema
|
|
343
|
-
const enumColumnsQuery = `
|
|
344
|
-
SELECT
|
|
345
|
-
c.table_name,
|
|
346
|
-
c.column_name,
|
|
347
|
-
c.udt_name
|
|
348
|
-
FROM
|
|
349
|
-
information_schema.columns c
|
|
350
|
-
WHERE
|
|
351
|
-
c.table_schema = $1
|
|
352
|
-
AND c.data_type = 'USER-DEFINED'
|
|
353
|
-
AND c.udt_name LIKE 'enum_%'
|
|
354
|
-
`;
|
|
355
|
-
|
|
356
|
-
const enumColumnsResult = await this.pool.query(enumColumnsQuery, [
|
|
357
|
-
schema,
|
|
358
|
-
]);
|
|
359
|
-
|
|
360
|
-
if (enumColumnsResult.rows.length === 0) {
|
|
361
|
-
this.logger.log(`No se encontraron columnas enum en schema: ${schema}`);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
this.logger.log(
|
|
366
|
-
`Encontradas ${enumColumnsResult.rows.length} columnas enum en schema: ${schema}`
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
// Procesar cada columna enum
|
|
370
|
-
for (const row of enumColumnsResult.rows) {
|
|
371
|
-
const tableName = row.table_name;
|
|
372
|
-
const columnName = row.column_name;
|
|
373
|
-
const enumTypeName = row.udt_name;
|
|
374
|
-
|
|
375
|
-
await this.normalizeEnumColumn(
|
|
376
|
-
schema,
|
|
377
|
-
tableName,
|
|
378
|
-
columnName,
|
|
379
|
-
enumTypeName
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
this.logger.log(
|
|
384
|
-
`Completado procesamiento de valores enum para schema: ${schema}`
|
|
385
|
-
);
|
|
386
|
-
} catch (error) {
|
|
387
|
-
this.logger.error(
|
|
388
|
-
`Error procesando valores enum para schema ${schema}: ${error.message}`,
|
|
389
|
-
error.stack
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Normaliza los valores en una columna enum específica
|
|
396
|
-
*/
|
|
397
|
-
private async normalizeEnumColumn(
|
|
398
|
-
schema: string,
|
|
399
|
-
tableName: string,
|
|
400
|
-
columnName: string,
|
|
401
|
-
enumTypeName: string
|
|
402
|
-
) {
|
|
403
|
-
this.logger.log(
|
|
404
|
-
`Normalizando columna enum ${columnName} (${enumTypeName}) en tabla ${schema}.${tableName}`
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
try {
|
|
408
|
-
// Obtener los valores válidos para el enum si no están en caché
|
|
409
|
-
if (!this.enumValuesCache[enumTypeName]) {
|
|
410
|
-
this.enumValuesCache[enumTypeName] = await this.getEnumValues(
|
|
411
|
-
enumTypeName
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
const validEnumValues = this.enumValuesCache[enumTypeName];
|
|
415
|
-
|
|
416
|
-
if (validEnumValues.length === 0) {
|
|
417
|
-
this.logger.warn(
|
|
418
|
-
`No se encontraron valores para el enum ${enumTypeName}. Omitiendo.`
|
|
419
|
-
);
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Obtener valores actuales en la columna
|
|
424
|
-
const currentValuesQuery = `
|
|
425
|
-
SELECT DISTINCT "${columnName}"
|
|
426
|
-
FROM "${schema}"."${tableName}"
|
|
427
|
-
WHERE "${columnName}" IS NOT NULL
|
|
428
|
-
`;
|
|
429
|
-
|
|
430
|
-
const currentValuesResult = await this.pool.query(currentValuesQuery);
|
|
431
|
-
const currentValues = currentValuesResult.rows.map((r) => r[columnName]);
|
|
432
|
-
|
|
433
|
-
// Encontrar valores inválidos
|
|
434
|
-
const invalidValues = currentValues.filter(
|
|
435
|
-
(val) => !validEnumValues.includes(val)
|
|
436
|
-
);
|
|
437
|
-
|
|
438
|
-
if (invalidValues.length === 0) {
|
|
439
|
-
this.logger.log(
|
|
440
|
-
`No se encontraron valores inválidos para columna enum ${columnName} en tabla ${schema}.${tableName}`
|
|
441
|
-
);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
this.logger.log(
|
|
446
|
-
`Encontrados ${
|
|
447
|
-
invalidValues.length
|
|
448
|
-
} valores inválidos para enum ${enumTypeName} en ${schema}.${tableName}: ${invalidValues.join(
|
|
449
|
-
", "
|
|
450
|
-
)}`
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
// Para cada valor inválido, intentar normalizarlo
|
|
454
|
-
for (const invalidValue of invalidValues) {
|
|
455
|
-
const normalizedValue = this.getNormalizedEnumValue(
|
|
456
|
-
enumTypeName,
|
|
457
|
-
invalidValue
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
if (normalizedValue && normalizedValue !== invalidValue) {
|
|
461
|
-
// Actualizar los registros con el valor normalizado
|
|
462
|
-
const updateQuery = `
|
|
463
|
-
UPDATE "${schema}"."${tableName}"
|
|
464
|
-
SET "${columnName}" = $1
|
|
465
|
-
WHERE "${columnName}" = $2
|
|
466
|
-
`;
|
|
467
|
-
|
|
468
|
-
const result = await this.pool.query(updateQuery, [
|
|
469
|
-
normalizedValue,
|
|
470
|
-
invalidValue,
|
|
471
|
-
]);
|
|
472
|
-
|
|
473
|
-
this.logger.log(
|
|
474
|
-
`Actualizado valor '${invalidValue}' a '${normalizedValue}' en ${result.rowCount} filas de ${schema}.${tableName}.${columnName}`
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
// Si el valor normalizado no está en el enum, agregarlo
|
|
478
|
-
if (!validEnumValues.includes(normalizedValue)) {
|
|
479
|
-
await this.addValueToEnum(enumTypeName, normalizedValue);
|
|
480
|
-
}
|
|
481
|
-
} else if (!normalizedValue) {
|
|
482
|
-
// Si no se puede normalizar, establecer a NULL
|
|
483
|
-
const updateQuery = `
|
|
484
|
-
UPDATE "${schema}"."${tableName}"
|
|
485
|
-
SET "${columnName}" = NULL
|
|
486
|
-
WHERE "${columnName}" = $1
|
|
487
|
-
`;
|
|
488
|
-
|
|
489
|
-
const result = await this.pool.query(updateQuery, [invalidValue]);
|
|
490
|
-
|
|
491
|
-
this.logger.log(
|
|
492
|
-
`Establecido valor '${invalidValue}' a NULL en ${result.rowCount} filas de ${schema}.${tableName}.${columnName}`
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
} catch (error) {
|
|
497
|
-
this.logger.error(
|
|
498
|
-
`Error normalizando columna enum ${columnName} en ${schema}.${tableName}: ${error.message}`,
|
|
499
|
-
error.stack
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* Obtiene un valor de enum normalizado basado en mapeos predefinidos
|
|
506
|
-
*/
|
|
507
|
-
private getNormalizedEnumValue(
|
|
508
|
-
enumTypeName: string,
|
|
509
|
-
value: string
|
|
510
|
-
): string | null {
|
|
511
|
-
// Si el valor ya es válido, devolverlo tal cual
|
|
512
|
-
if (this.enumValuesCache[enumTypeName]?.includes(value)) {
|
|
513
|
-
return value;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Buscar en el mapa de normalización
|
|
517
|
-
if (
|
|
518
|
-
this.valueNormalizationMap[enumTypeName] &&
|
|
519
|
-
this.valueNormalizationMap[enumTypeName][value]
|
|
520
|
-
) {
|
|
521
|
-
return this.valueNormalizationMap[enumTypeName][value];
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Intentar normalizar basado en similitud (convertir a mayúsculas, quitar espacios, etc.)
|
|
525
|
-
const normalizedInput = value.toUpperCase().replace(/\s+/g, "_");
|
|
526
|
-
|
|
527
|
-
// Verificar si la versión normalizada existe en el mapa
|
|
528
|
-
if (
|
|
529
|
-
this.valueNormalizationMap[enumTypeName] &&
|
|
530
|
-
this.valueNormalizationMap[enumTypeName][normalizedInput]
|
|
531
|
-
) {
|
|
532
|
-
return this.valueNormalizationMap[enumTypeName][normalizedInput];
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Buscar coincidencias parciales en los valores válidos
|
|
536
|
-
const validValues = this.enumValuesCache[enumTypeName] || [];
|
|
537
|
-
for (const validValue of validValues) {
|
|
538
|
-
// Comparar ignorando mayúsculas/minúsculas
|
|
539
|
-
if (validValue.toLowerCase() === value.toLowerCase()) {
|
|
540
|
-
return validValue;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// Si no se encuentra coincidencia, devolver null
|
|
545
|
-
return null;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Ejecutar el script si se llama directamente
|
|
550
|
-
if (require.main === module) {
|
|
551
|
-
const run = async () => {
|
|
552
|
-
const synchronizer = new EnumValueSynchronizer(
|
|
553
|
-
process.env.SOURCE_DATABASE_URL,
|
|
554
|
-
process.env.DATABASE_URL
|
|
555
|
-
);
|
|
556
|
-
await synchronizer.synchronizeEnumValues();
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
run().catch((error) => {
|
|
560
|
-
console.error("Error ejecutando sincronización de valores de enum:", error);
|
|
561
|
-
process.exit(1);
|
|
562
|
-
});
|
|
563
|
-
}
|
|
1
|
+
import { Logger } from "@nestjs/common";
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as dotenv from "dotenv";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Clase para sincronizar valores entre enums específicos y enums consolidados
|
|
11
|
+
* Esta herramienta asegura que los valores en los enums específicos (deprecados)
|
|
12
|
+
* sean compatibles con los enums consolidados durante la migración.
|
|
13
|
+
*/
|
|
14
|
+
export class EnumValueSynchronizer {
|
|
15
|
+
private readonly logger = new Logger("EnumValueSynchronizer");
|
|
16
|
+
private readonly pool: Pool;
|
|
17
|
+
|
|
18
|
+
// Mapeo de enums específicos a enums consolidados
|
|
19
|
+
private readonly enumMappings: Record<string, string> = {
|
|
20
|
+
enum_credit_requests_status: "enum_transaction_status",
|
|
21
|
+
enum_global_user_transactions_status: "enum_transaction_status",
|
|
22
|
+
enum_global_user_transactions_transaction_type: "enum_transaction_type",
|
|
23
|
+
enum_invoices_status: "enum_transaction_status",
|
|
24
|
+
enum_payin_status: "enum_transaction_status",
|
|
25
|
+
enum_payout_status: "enum_transaction_status",
|
|
26
|
+
enum_pending_references_status: "enum_transaction_status",
|
|
27
|
+
enum_toku_status: "enum_transaction_status",
|
|
28
|
+
enum_transaction_updates_new_status: "enum_transaction_status",
|
|
29
|
+
enum_transactions_account_type: "enum_account_type",
|
|
30
|
+
enum_transactions_status: "enum_transaction_status",
|
|
31
|
+
enum_transactions_transaction_type: "enum_transaction_type",
|
|
32
|
+
enum_transactions_typetransaction: "enum_transaction_type",
|
|
33
|
+
enum_transactions_user_type_account: "enum_account_type",
|
|
34
|
+
enum_transactions_usertypeaccount: "enum_account_type",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Mapeo de valores de texto a valores de enum válidos
|
|
38
|
+
private readonly valueNormalizationMap: Record<
|
|
39
|
+
string,
|
|
40
|
+
Record<string, string>
|
|
41
|
+
> = {
|
|
42
|
+
// Mapeos para enum_transaction_status
|
|
43
|
+
enum_transaction_status: {
|
|
44
|
+
PENDING: "PENDING",
|
|
45
|
+
pending: "PENDING",
|
|
46
|
+
COMPLETED: "COMPLETED",
|
|
47
|
+
completed: "COMPLETED",
|
|
48
|
+
FAILED: "FAILED",
|
|
49
|
+
failed: "FAILED",
|
|
50
|
+
PROCESSING: "PROCESSING",
|
|
51
|
+
processing: "PROCESSING",
|
|
52
|
+
CANCELLED: "CANCELLED",
|
|
53
|
+
cancelled: "CANCELLED",
|
|
54
|
+
canceled: "CANCELLED",
|
|
55
|
+
CANCELED: "CANCELLED",
|
|
56
|
+
REJECTED: "REJECTED",
|
|
57
|
+
rejected: "REJECTED",
|
|
58
|
+
APPROVED: "APPROVED",
|
|
59
|
+
approved: "APPROVED",
|
|
60
|
+
EXPIRED: "EXPIRED",
|
|
61
|
+
expired: "EXPIRED",
|
|
62
|
+
REFUNDED: "REFUNDED",
|
|
63
|
+
refunded: "REFUNDED",
|
|
64
|
+
PARTIAL_REFUND: "PARTIAL_REFUND",
|
|
65
|
+
partial_refund: "PARTIAL_REFUND",
|
|
66
|
+
},
|
|
67
|
+
// Mapeos para enum_transaction_type
|
|
68
|
+
enum_transaction_type: {
|
|
69
|
+
DEPOSIT: "DEPOSIT",
|
|
70
|
+
deposit: "DEPOSIT",
|
|
71
|
+
WITHDRAWAL: "WITHDRAWAL",
|
|
72
|
+
withdrawal: "WITHDRAWAL",
|
|
73
|
+
TRANSFER: "TRANSFER",
|
|
74
|
+
transfer: "TRANSFER",
|
|
75
|
+
PAYMENT: "PAYMENT",
|
|
76
|
+
payment: "PAYMENT",
|
|
77
|
+
REFUND: "REFUND",
|
|
78
|
+
refund: "REFUND",
|
|
79
|
+
FEE: "FEE",
|
|
80
|
+
fee: "FEE",
|
|
81
|
+
ADJUSTMENT: "ADJUSTMENT",
|
|
82
|
+
adjustment: "ADJUSTMENT",
|
|
83
|
+
},
|
|
84
|
+
// Mapeos para enum_account_type
|
|
85
|
+
enum_account_type: {
|
|
86
|
+
PERSONAL: "PERSONAL",
|
|
87
|
+
personal: "PERSONAL",
|
|
88
|
+
BUSINESS: "BUSINESS",
|
|
89
|
+
business: "BUSINESS",
|
|
90
|
+
MERCHANT: "MERCHANT",
|
|
91
|
+
merchant: "MERCHANT",
|
|
92
|
+
PLATFORM: "PLATFORM",
|
|
93
|
+
platform: "PLATFORM",
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Caché para valores de enum
|
|
98
|
+
private enumValuesCache: Record<string, string[]> = {};
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
private readonly sourceUrl: string = process.env.SOURCE_DATABASE_URL,
|
|
102
|
+
private readonly targetUrl: string = process.env.DATABASE_URL
|
|
103
|
+
) {
|
|
104
|
+
this.pool = new Pool({
|
|
105
|
+
connectionString: this.targetUrl,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sincroniza los valores entre enums específicos y enums consolidados
|
|
111
|
+
* y normaliza los valores de texto a valores de enum válidos
|
|
112
|
+
*/
|
|
113
|
+
async synchronizeEnumValues() {
|
|
114
|
+
try {
|
|
115
|
+
this.logger.log("Iniciando sincronización de valores de enum");
|
|
116
|
+
|
|
117
|
+
// Crear directorio para logs si no existe
|
|
118
|
+
const logsDir = path.join(process.cwd(), "migration-logs");
|
|
119
|
+
if (!fs.existsSync(logsDir)) {
|
|
120
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Obtener todos los tipos enum en la base de datos
|
|
124
|
+
const enumsResult = await this.pool.query(`
|
|
125
|
+
SELECT t.typname AS enum_name
|
|
126
|
+
FROM pg_type t
|
|
127
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
128
|
+
WHERE t.typtype = 'e' -- enum types
|
|
129
|
+
AND n.nspname = 'public'
|
|
130
|
+
ORDER BY t.typname
|
|
131
|
+
`);
|
|
132
|
+
|
|
133
|
+
this.logger.log(
|
|
134
|
+
`Encontrados ${enumsResult.rows.length} tipos enum en la base de datos`
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Procesar cada enum específico y su correspondiente enum consolidado
|
|
138
|
+
for (const [specificEnum, consolidatedEnum] of Object.entries(
|
|
139
|
+
this.enumMappings
|
|
140
|
+
)) {
|
|
141
|
+
await this.synchronizeSpecificEnum(specificEnum, consolidatedEnum);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Después de sincronizar los enums, normalizar los valores en las tablas
|
|
145
|
+
await this.normalizeEnumValuesInTables();
|
|
146
|
+
|
|
147
|
+
this.logger.log("Sincronización de valores de enum completada con éxito");
|
|
148
|
+
} catch (error) {
|
|
149
|
+
this.logger.error(
|
|
150
|
+
`Error durante la sincronización de valores de enum: ${error.message}`,
|
|
151
|
+
error.stack
|
|
152
|
+
);
|
|
153
|
+
throw error;
|
|
154
|
+
} finally {
|
|
155
|
+
await this.pool.end();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sincroniza los valores entre un enum específico y su enum consolidado
|
|
161
|
+
*/
|
|
162
|
+
private async synchronizeSpecificEnum(
|
|
163
|
+
specificEnum: string,
|
|
164
|
+
consolidatedEnum: string
|
|
165
|
+
) {
|
|
166
|
+
try {
|
|
167
|
+
this.logger.log(
|
|
168
|
+
`Sincronizando valores entre ${specificEnum} y ${consolidatedEnum}`
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Verificar si ambos enums existen
|
|
172
|
+
const specificEnumExists = await this.enumExists(specificEnum);
|
|
173
|
+
const consolidatedEnumExists = await this.enumExists(consolidatedEnum);
|
|
174
|
+
|
|
175
|
+
if (!specificEnumExists || !consolidatedEnumExists) {
|
|
176
|
+
this.logger.warn(
|
|
177
|
+
`No se puede sincronizar: ${
|
|
178
|
+
!specificEnumExists ? specificEnum : consolidatedEnum
|
|
179
|
+
} no existe`
|
|
180
|
+
);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Obtener valores de ambos enums
|
|
185
|
+
const specificValues = await this.getEnumValues(specificEnum);
|
|
186
|
+
const consolidatedValues = await this.getEnumValues(consolidatedEnum);
|
|
187
|
+
|
|
188
|
+
this.logger.log(
|
|
189
|
+
`Valores en ${specificEnum}: ${specificValues.join(", ")}\n` +
|
|
190
|
+
`Valores en ${consolidatedEnum}: ${consolidatedValues.join(", ")}`
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Verificar valores que están en el enum específico pero no en el consolidado
|
|
194
|
+
const missingValues = specificValues.filter(
|
|
195
|
+
(value) => !consolidatedValues.includes(value)
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (missingValues.length === 0) {
|
|
199
|
+
this.logger.log(
|
|
200
|
+
`No hay valores faltantes entre ${specificEnum} y ${consolidatedEnum}`
|
|
201
|
+
);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.logger.log(
|
|
206
|
+
`Encontrados ${
|
|
207
|
+
missingValues.length
|
|
208
|
+
} valores en ${specificEnum} que faltan en ${consolidatedEnum}: ${missingValues.join(
|
|
209
|
+
", "
|
|
210
|
+
)}`
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Agregar los valores faltantes al enum consolidado
|
|
214
|
+
for (const value of missingValues) {
|
|
215
|
+
await this.addValueToEnum(consolidatedEnum, value);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.logger.log(
|
|
219
|
+
`Sincronización entre ${specificEnum} y ${consolidatedEnum} completada`
|
|
220
|
+
);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
this.logger.error(
|
|
223
|
+
`Error sincronizando ${specificEnum} con ${consolidatedEnum}: ${error.message}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Verifica si un enum existe en la base de datos
|
|
230
|
+
*/
|
|
231
|
+
private async enumExists(enumName: string): Promise<boolean> {
|
|
232
|
+
const result = await this.pool.query(
|
|
233
|
+
`
|
|
234
|
+
SELECT 1
|
|
235
|
+
FROM pg_type t
|
|
236
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
237
|
+
WHERE t.typname = $1
|
|
238
|
+
AND t.typtype = 'e' -- enum types
|
|
239
|
+
AND n.nspname = 'public'
|
|
240
|
+
`,
|
|
241
|
+
[enumName]
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return result.rows.length > 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Obtiene los valores de un enum
|
|
249
|
+
*/
|
|
250
|
+
private async getEnumValues(enumName: string): Promise<string[]> {
|
|
251
|
+
const result = await this.pool.query(
|
|
252
|
+
`
|
|
253
|
+
SELECT e.enumlabel
|
|
254
|
+
FROM pg_enum e
|
|
255
|
+
JOIN pg_type t ON e.enumtypid = t.oid
|
|
256
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
257
|
+
WHERE t.typname = $1
|
|
258
|
+
AND n.nspname = 'public'
|
|
259
|
+
ORDER BY e.enumsortorder
|
|
260
|
+
`,
|
|
261
|
+
[enumName]
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
return result.rows.map((row) => row.enumlabel);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Agrega un valor a un enum existente
|
|
269
|
+
*/
|
|
270
|
+
private async addValueToEnum(enumName: string, value: string) {
|
|
271
|
+
try {
|
|
272
|
+
// Escapar comillas simples en el valor
|
|
273
|
+
const escapedValue = value.replace(/'/g, "''");
|
|
274
|
+
|
|
275
|
+
// Agregar el valor al enum
|
|
276
|
+
await this.pool.query(`
|
|
277
|
+
ALTER TYPE ${enumName} ADD VALUE IF NOT EXISTS '${escapedValue}'
|
|
278
|
+
`);
|
|
279
|
+
|
|
280
|
+
// Actualizar la caché de valores de enum
|
|
281
|
+
if (this.enumValuesCache[enumName]) {
|
|
282
|
+
if (!this.enumValuesCache[enumName].includes(value)) {
|
|
283
|
+
this.enumValuesCache[enumName].push(value);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.logger.log(`Valor '${value}' agregado al enum ${enumName}`);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
this.logger.error(
|
|
290
|
+
`Error agregando valor '${value}' al enum ${enumName}: ${error.message}`
|
|
291
|
+
);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Normaliza los valores de enum en todas las tablas
|
|
298
|
+
*/
|
|
299
|
+
private async normalizeEnumValuesInTables() {
|
|
300
|
+
this.logger.log("Iniciando normalización de valores de enum en tablas");
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
// Obtener todos los schemas excepto los del sistema
|
|
304
|
+
const schemas = await this.getSchemas();
|
|
305
|
+
this.logger.log(`Encontrados ${schemas.length} schemas para procesar`);
|
|
306
|
+
|
|
307
|
+
// Para cada schema, normalizar los valores de enum en sus tablas
|
|
308
|
+
for (const schema of schemas) {
|
|
309
|
+
await this.normalizeEnumValuesInSchema(schema);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
this.logger.log("Normalización de valores de enum en tablas completada");
|
|
313
|
+
} catch (error) {
|
|
314
|
+
this.logger.error(
|
|
315
|
+
`Error durante la normalización de valores de enum: ${error.message}`,
|
|
316
|
+
error.stack
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Obtiene todos los schemas de la base de datos excepto los del sistema
|
|
323
|
+
*/
|
|
324
|
+
private async getSchemas(): Promise<string[]> {
|
|
325
|
+
const result = await this.pool.query(`
|
|
326
|
+
SELECT schema_name
|
|
327
|
+
FROM information_schema.schemata
|
|
328
|
+
WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
|
|
329
|
+
AND schema_name NOT LIKE 'pg_%'
|
|
330
|
+
`);
|
|
331
|
+
|
|
332
|
+
return result.rows.map((row) => row.schema_name);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Normaliza los valores de enum en un schema específico
|
|
337
|
+
*/
|
|
338
|
+
private async normalizeEnumValuesInSchema(schema: string) {
|
|
339
|
+
this.logger.log(`Procesando valores de enum en schema: ${schema}`);
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
// Obtener todas las columnas enum en este schema
|
|
343
|
+
const enumColumnsQuery = `
|
|
344
|
+
SELECT
|
|
345
|
+
c.table_name,
|
|
346
|
+
c.column_name,
|
|
347
|
+
c.udt_name
|
|
348
|
+
FROM
|
|
349
|
+
information_schema.columns c
|
|
350
|
+
WHERE
|
|
351
|
+
c.table_schema = $1
|
|
352
|
+
AND c.data_type = 'USER-DEFINED'
|
|
353
|
+
AND c.udt_name LIKE 'enum_%'
|
|
354
|
+
`;
|
|
355
|
+
|
|
356
|
+
const enumColumnsResult = await this.pool.query(enumColumnsQuery, [
|
|
357
|
+
schema,
|
|
358
|
+
]);
|
|
359
|
+
|
|
360
|
+
if (enumColumnsResult.rows.length === 0) {
|
|
361
|
+
this.logger.log(`No se encontraron columnas enum en schema: ${schema}`);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.logger.log(
|
|
366
|
+
`Encontradas ${enumColumnsResult.rows.length} columnas enum en schema: ${schema}`
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Procesar cada columna enum
|
|
370
|
+
for (const row of enumColumnsResult.rows) {
|
|
371
|
+
const tableName = row.table_name;
|
|
372
|
+
const columnName = row.column_name;
|
|
373
|
+
const enumTypeName = row.udt_name;
|
|
374
|
+
|
|
375
|
+
await this.normalizeEnumColumn(
|
|
376
|
+
schema,
|
|
377
|
+
tableName,
|
|
378
|
+
columnName,
|
|
379
|
+
enumTypeName
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
this.logger.log(
|
|
384
|
+
`Completado procesamiento de valores enum para schema: ${schema}`
|
|
385
|
+
);
|
|
386
|
+
} catch (error) {
|
|
387
|
+
this.logger.error(
|
|
388
|
+
`Error procesando valores enum para schema ${schema}: ${error.message}`,
|
|
389
|
+
error.stack
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Normaliza los valores en una columna enum específica
|
|
396
|
+
*/
|
|
397
|
+
private async normalizeEnumColumn(
|
|
398
|
+
schema: string,
|
|
399
|
+
tableName: string,
|
|
400
|
+
columnName: string,
|
|
401
|
+
enumTypeName: string
|
|
402
|
+
) {
|
|
403
|
+
this.logger.log(
|
|
404
|
+
`Normalizando columna enum ${columnName} (${enumTypeName}) en tabla ${schema}.${tableName}`
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
// Obtener los valores válidos para el enum si no están en caché
|
|
409
|
+
if (!this.enumValuesCache[enumTypeName]) {
|
|
410
|
+
this.enumValuesCache[enumTypeName] = await this.getEnumValues(
|
|
411
|
+
enumTypeName
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const validEnumValues = this.enumValuesCache[enumTypeName];
|
|
415
|
+
|
|
416
|
+
if (validEnumValues.length === 0) {
|
|
417
|
+
this.logger.warn(
|
|
418
|
+
`No se encontraron valores para el enum ${enumTypeName}. Omitiendo.`
|
|
419
|
+
);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Obtener valores actuales en la columna
|
|
424
|
+
const currentValuesQuery = `
|
|
425
|
+
SELECT DISTINCT "${columnName}"
|
|
426
|
+
FROM "${schema}"."${tableName}"
|
|
427
|
+
WHERE "${columnName}" IS NOT NULL
|
|
428
|
+
`;
|
|
429
|
+
|
|
430
|
+
const currentValuesResult = await this.pool.query(currentValuesQuery);
|
|
431
|
+
const currentValues = currentValuesResult.rows.map((r) => r[columnName]);
|
|
432
|
+
|
|
433
|
+
// Encontrar valores inválidos
|
|
434
|
+
const invalidValues = currentValues.filter(
|
|
435
|
+
(val) => !validEnumValues.includes(val)
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (invalidValues.length === 0) {
|
|
439
|
+
this.logger.log(
|
|
440
|
+
`No se encontraron valores inválidos para columna enum ${columnName} en tabla ${schema}.${tableName}`
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
this.logger.log(
|
|
446
|
+
`Encontrados ${
|
|
447
|
+
invalidValues.length
|
|
448
|
+
} valores inválidos para enum ${enumTypeName} en ${schema}.${tableName}: ${invalidValues.join(
|
|
449
|
+
", "
|
|
450
|
+
)}`
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// Para cada valor inválido, intentar normalizarlo
|
|
454
|
+
for (const invalidValue of invalidValues) {
|
|
455
|
+
const normalizedValue = this.getNormalizedEnumValue(
|
|
456
|
+
enumTypeName,
|
|
457
|
+
invalidValue
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
if (normalizedValue && normalizedValue !== invalidValue) {
|
|
461
|
+
// Actualizar los registros con el valor normalizado
|
|
462
|
+
const updateQuery = `
|
|
463
|
+
UPDATE "${schema}"."${tableName}"
|
|
464
|
+
SET "${columnName}" = $1
|
|
465
|
+
WHERE "${columnName}" = $2
|
|
466
|
+
`;
|
|
467
|
+
|
|
468
|
+
const result = await this.pool.query(updateQuery, [
|
|
469
|
+
normalizedValue,
|
|
470
|
+
invalidValue,
|
|
471
|
+
]);
|
|
472
|
+
|
|
473
|
+
this.logger.log(
|
|
474
|
+
`Actualizado valor '${invalidValue}' a '${normalizedValue}' en ${result.rowCount} filas de ${schema}.${tableName}.${columnName}`
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// Si el valor normalizado no está en el enum, agregarlo
|
|
478
|
+
if (!validEnumValues.includes(normalizedValue)) {
|
|
479
|
+
await this.addValueToEnum(enumTypeName, normalizedValue);
|
|
480
|
+
}
|
|
481
|
+
} else if (!normalizedValue) {
|
|
482
|
+
// Si no se puede normalizar, establecer a NULL
|
|
483
|
+
const updateQuery = `
|
|
484
|
+
UPDATE "${schema}"."${tableName}"
|
|
485
|
+
SET "${columnName}" = NULL
|
|
486
|
+
WHERE "${columnName}" = $1
|
|
487
|
+
`;
|
|
488
|
+
|
|
489
|
+
const result = await this.pool.query(updateQuery, [invalidValue]);
|
|
490
|
+
|
|
491
|
+
this.logger.log(
|
|
492
|
+
`Establecido valor '${invalidValue}' a NULL en ${result.rowCount} filas de ${schema}.${tableName}.${columnName}`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
} catch (error) {
|
|
497
|
+
this.logger.error(
|
|
498
|
+
`Error normalizando columna enum ${columnName} en ${schema}.${tableName}: ${error.message}`,
|
|
499
|
+
error.stack
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Obtiene un valor de enum normalizado basado en mapeos predefinidos
|
|
506
|
+
*/
|
|
507
|
+
private getNormalizedEnumValue(
|
|
508
|
+
enumTypeName: string,
|
|
509
|
+
value: string
|
|
510
|
+
): string | null {
|
|
511
|
+
// Si el valor ya es válido, devolverlo tal cual
|
|
512
|
+
if (this.enumValuesCache[enumTypeName]?.includes(value)) {
|
|
513
|
+
return value;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Buscar en el mapa de normalización
|
|
517
|
+
if (
|
|
518
|
+
this.valueNormalizationMap[enumTypeName] &&
|
|
519
|
+
this.valueNormalizationMap[enumTypeName][value]
|
|
520
|
+
) {
|
|
521
|
+
return this.valueNormalizationMap[enumTypeName][value];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Intentar normalizar basado en similitud (convertir a mayúsculas, quitar espacios, etc.)
|
|
525
|
+
const normalizedInput = value.toUpperCase().replace(/\s+/g, "_");
|
|
526
|
+
|
|
527
|
+
// Verificar si la versión normalizada existe en el mapa
|
|
528
|
+
if (
|
|
529
|
+
this.valueNormalizationMap[enumTypeName] &&
|
|
530
|
+
this.valueNormalizationMap[enumTypeName][normalizedInput]
|
|
531
|
+
) {
|
|
532
|
+
return this.valueNormalizationMap[enumTypeName][normalizedInput];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Buscar coincidencias parciales en los valores válidos
|
|
536
|
+
const validValues = this.enumValuesCache[enumTypeName] || [];
|
|
537
|
+
for (const validValue of validValues) {
|
|
538
|
+
// Comparar ignorando mayúsculas/minúsculas
|
|
539
|
+
if (validValue.toLowerCase() === value.toLowerCase()) {
|
|
540
|
+
return validValue;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Si no se encuentra coincidencia, devolver null
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Ejecutar el script si se llama directamente
|
|
550
|
+
if (require.main === module) {
|
|
551
|
+
const run = async () => {
|
|
552
|
+
const synchronizer = new EnumValueSynchronizer(
|
|
553
|
+
process.env.SOURCE_DATABASE_URL,
|
|
554
|
+
process.env.DATABASE_URL
|
|
555
|
+
);
|
|
556
|
+
await synchronizer.synchronizeEnumValues();
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
run().catch((error) => {
|
|
560
|
+
console.error("Error ejecutando sincronización de valores de enum:", error);
|
|
561
|
+
process.exit(1);
|
|
562
|
+
});
|
|
563
|
+
}
|