瀏覽代碼

update migration

masarifyuli 3 天之前
父節點
當前提交
e0181050b7
共有 3 個文件被更改,包括 248 次插入119 次删除
  1. 1 1
      config/general-setting.yml
  2. 4 3
      config/migration.yml
  3. 243 115
      src/main/kotlin/com/datacomsolusindo/migration/MigrationEntity.kt

+ 1 - 1
config/general-setting.yml

@@ -182,7 +182,7 @@ redirectUrl:
 - http://localhost:4200/oauth2redirect
 - https://cmpd.telmessenger.com/oauth2redirect
 - https://app.insomnia.rest/oauth/redirect
-dataKey: RMyvjTZXDMBnCr3jymw9oHGUUzIJTHCULv9x%2B6xE4De56nu2OlXjkTWuk5%2B3QLfyE5PJQUBl9SIAuZOGmpIJBTHvNBFOUN%2BtwT0FzDUIKfD%2BdOcU4lJzNzo9mc2cMilw
+dataKey: jRA4iyjy1ZCaIdlJBe6Cm%2BRPGb%2BwcO85%2FWR7Z9%2B8AFVDTPuvnJwmCw6pPbMrQP92CNB%2BMwAPuisTRE8%2FccPGKB%2FuKWIvPxBslzLvfX58IuH3nZB8bPrFiSZH2n5Q7RgjOxObPvHvOIpk4r0P6%2B%2BcJw%3D%3D
 
 #database: 
 #  type: sqlserver

+ 4 - 3
config/migration.yml

@@ -25,8 +25,8 @@ pbx:
   ipPbx: ip_pbx
   pbxSupporter: pbx_supporter
   sharePath: share_path
-  appliedDate: ext_start
-  expiredDate: ext_end
+#  appliedDate: ext_start
+#  expiredDate: ext_end
 trunk:
   id: history.trunk_id
   code: trunk_code
@@ -62,6 +62,7 @@ webUser:
   password: login_password
   name: description
   phoneUser_id: phoneuser_code
+  loginPin: used_pin
 phoneUser:
   id: history.phoneuser_id
   code: phoneuser_code
@@ -70,7 +71,7 @@ phoneUser:
   whatsapp: phoneuser_mobile
   position: position
   emailOnOverBudget: sendemail_overbudget
-  pin: history.phon euser_pin
+  pin: history.phoneuser_pin
   extension: history.default_extension
   organization_id: history.organization_code
   costCenter_id: history.costcenter_code

+ 243 - 115
src/main/kotlin/com/datacomsolusindo/migration/MigrationEntity.kt

@@ -25,15 +25,14 @@ import io.github.semutkecil.simplecriteria.FilterData
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
 import org.springframework.security.crypto.factory.PasswordEncoderFactories
 import org.springframework.security.crypto.password.PasswordEncoder
 import org.springframework.stereotype.Service
 import org.springframework.transaction.annotation.Transactional
 import java.io.File
 import java.nio.charset.StandardCharsets
+import java.time.LocalDate
 import java.util.UUID
-import kotlin.time.measureTime
 import kotlin.time.measureTimedValue
 
 
@@ -48,6 +47,88 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
 
     private val logger = SimpleLogger.getLogger(this::class.java)
 
+//    fun <T> dataToMap(
+//        clazz: Class<T>,
+//        fields: Map<String, String>,
+//        unique: String,
+//        rootFile: File,
+//        historyFile: File? = null,
+//        groupFile: File? = null
+//    ): List<MutableMap<String, Any?>> {
+//        logger.info("prepare data migration class ${clazz.simpleName}")
+//        val process = measureTimedValue {
+//            val historyData = historyFile?.let { readQueryDataToMap(it) }
+//            val groupData = groupFile?.let { readQueryDataToMap(it) }
+//            val rootData = readQueryDataToMap(rootFile)
+//            val mapRootData = rootData.mapIndexed { index, map ->
+//                val data: MutableMap<String, Any?> = mutableMapOf()
+//                val fieldRoots = fields.toList().filterNot { f -> f.second.contains(".") }
+//                val joinRoots = fields.toList().filter { f -> f.second.contains(".") }
+//
+//                fieldRoots.forEach { f ->
+//                    data[f.first] = map[f.second]
+//                }
+//                joinRoots.sortedByDescending { it.second }.forEach { f ->
+//                    data[f.first] = when {
+//                        f.second.startsWith("history.") ->
+//                            getValueAnotherFile(fields, historyData, unique, data, f.first, f.second, "history")
+//
+//                        f.second.toString().startsWith("group.") ->
+//                            getValueAnotherFile(fields, groupData, unique, data, f.first, f.second, "group")
+//
+//                        else -> map[f.second]
+//                    }
+//                }
+//                data
+//            }
+//            mapRootData
+//        }
+//        logger.info("finish prepare data [${process.value.size}] migration class ${clazz.simpleName} takes time ${process.duration.inWholeMilliseconds}ms")
+//        return process.value
+//    }
+//
+//    private fun getValueAnotherFile(
+//        fields: Map<String, String>,
+//        dataFile: List<Map<String, Any?>>?,
+//        unique: String,
+//        rootData: Map<String, Any?>,
+//        keyRoot: String,
+//        valRoot: String,
+//        key: String
+//    ): Any? {
+//        val uniqueField = dataFile?.firstOrNull()?.let {
+//            if (it.any { a -> a.key == fields[unique] }) unique else "id"
+//        } ?: "id"
+//        val fieldUnique = fields[uniqueField]!!.split(".").last()
+//
+//        val value = dataFile?.firstOrNull { f ->
+//            f[fieldUnique].toString() == rootData[uniqueField].toString()
+//        }?.get(valRoot.replace("${key}.", ""))
+//
+//        return if (keyRoot.contains("_")) value?.let { id ->
+//            dataFile.firstOrNull { f ->
+//                f[fields["id"]!!.split(".").last()].toString() == id.toString()
+//            }?.get(fields["code"]) ?: value
+//        } else value
+//    }
+//
+//    private fun readQueryDataToMap(file: File): List<Map<String, Any?>> {
+//        val map = measureTimedValue {
+//            file.bufferedReader(StandardCharsets.UTF_8)
+//                .readLines().filter {
+//                    it.startsWith("INSERT")
+//                }.mapNotNull {
+//                    try {
+//                        insertSqlToMap(it)
+//                    } catch (_: Exception) {
+//                        null
+//                    }
+//                }
+//        }
+//        logger.info("migration read query data ${map.value.size} takes time ${map.duration.inWholeMilliseconds}")
+//        return map.value
+//    }
+
     fun <T> dataToMap(
         clazz: Class<T>,
         fields: Map<String, String>,
@@ -56,90 +137,94 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
         historyFile: File? = null,
         groupFile: File? = null
     ): List<MutableMap<String, Any?>> {
+
         logger.info("prepare data migration class ${clazz.simpleName}")
+
         val process = measureTimedValue {
+
+            // --- Read Files ---
             val historyData = historyFile?.let { readQueryDataToMap(it) }
             val groupData = groupFile?.let { readQueryDataToMap(it) }
             val rootData = readQueryDataToMap(rootFile)
-            val mapRootData = rootData.map { map ->
-                val data: MutableMap<String, Any?> = mutableMapOf()
-                val fieldRoots = fields.toList().filterNot { f -> f.second.contains(".") }
-                val joinRoots = fields.toList().filter { f -> f.second.contains(".") }
 
-                fieldRoots.forEach { f ->
-                    data[f.first] = map[f.second]
+            // --- Pre-calc field names (avoid split(".") repeatedly) ---
+            val fieldMapping = fields.mapValues { it.value.substringAfterLast(".") }
+            val uniqueField = fieldMapping[unique] ?: "id"
+
+            // --- Create Indexes (O(n)) ---
+            val historyIndex = historyData?.associateBy { it[uniqueField]?.toString() }
+            val groupIndex = groupData?.associateBy { it[uniqueField]?.toString() }
+
+            val fieldRoots = fields.filter { !it.value.contains(".") }
+            val joinRoots = fields.filter { it.value.contains(".") }
+
+            rootData.mapIndexed { index, row->
+                val data = mutableMapOf<String, Any?>()
+
+                // --- Direct Fields ---
+                fieldRoots.forEach { (targetKey, sourceKey) ->
+                    data[targetKey] = row[sourceKey]
                 }
-                joinRoots.sortedByDescending { it.second }.forEach { f ->
-                    data[f.first] = when {
-                        f.second.startsWith("history.") ->
-                            getValueAnotherFile(fields, historyData, unique, data, f.first, f.second, "history")
 
-                        f.second.toString().startsWith("group.") ->
-                            getValueAnotherFile(fields, groupData, unique, data, f.first, f.second, "group")
+                // --- Join Fields ---
+                joinRoots.forEach { (targetKey, sourceKeyFull) ->
 
-                        else -> map[f.second]
+                    val value = when {
+                        sourceKeyFull.startsWith("history.") -> {
+                            val sourceKey = sourceKeyFull.removePrefix("history.")
+                            historyIndex
+                                ?.get(row[uniqueField]?.toString())
+                                ?.get(sourceKey.substringAfterLast("."))
+                        }
+
+                        sourceKeyFull.startsWith("group.") -> {
+                            val sourceKey = sourceKeyFull.removePrefix("group.")
+                            groupIndex
+                                ?.get(row[uniqueField]?.toString())
+                                ?.get(sourceKey.substringAfterLast("."))
+                        }
+
+                        else -> row[sourceKeyFull]
                     }
-                }
 
-//                fields.forEach { (t, u) ->
-//                    data[t] = when {
-//                        u.startsWith("history.") ->
-//                            getValueAnotherFile(fields, historyData, unique, data, t, u, "history")
-//
-//                        u.startsWith("group.") -> getValueAnotherFile(fields, groupData, unique, data, t, u, "group")
-//                        else -> map[u]
-//                    }
-//                }
+                    data[targetKey] = value
+                }
                 data
             }
-            mapRootData
         }
-        logger.info("finish prepare data [${process.value.size}] migration class ${clazz.simpleName} takes time ${process.duration.inWholeMilliseconds}ms")
-        return process.value
-    }
 
-    private fun getValueAnotherFile(
-        fields: Map<String, String>,
-        dataFile: List<Map<String, Any?>>?,
-        unique: String,
-        rootData: Map<String, Any?>,
-        keyRoot: String,
-        valRoot: String,
-        key: String
-    ): Any? {
-        val uniqueField = dataFile?.firstOrNull()?.let {
-            if (it.any { a -> a.key == fields[unique] }) unique else "id"
-        } ?: "id"
-        val fieldUnique = fields[uniqueField]!!.split(".").last()
-
-        val value = dataFile?.firstOrNull { f ->
-            f[fieldUnique].toString() == rootData[uniqueField].toString()
-        }?.get(valRoot.replace("${key}.", ""))
-
-        return if (keyRoot.contains("_")) value?.let { id ->
-            dataFile.firstOrNull { f ->
-                f[fields["id"]!!.split(".").last()].toString() == id.toString()
-            }?.get(fields["code"]) ?: value
-        } else value
+        logger.info(
+            "finish prepare data [${process.value.size}] migration class ${clazz.simpleName} " +
+                    "takes time ${process.duration.inWholeMilliseconds}ms"
+        )
+
+        return process.value
     }
 
     private fun readQueryDataToMap(file: File): List<Map<String, Any?>> {
-        val map = measureTimedValue {
-            file.bufferedReader(StandardCharsets.UTF_8)
-                .readLines().filter {
-                    it.startsWith("INSERT")
-                }.mapNotNull {
-                    try {
-                        insertSqlToMap(it)
-                    } catch (_: Exception) {
-                        null
+
+        val result = measureTimedValue {
+            file.bufferedReader(StandardCharsets.UTF_8).useLines { lines ->
+                lines
+                    .filter { it.startsWith("INSERT") }
+                    .mapNotNull {
+                        try {
+                            insertSqlToMap(it)
+                        } catch (_: Exception) {
+                            null
+                        }
                     }
-                }
+                    .toList()
+            }
         }
-        logger.info("migration read query data ${map.value.size} takes time ${map.duration.inWholeMilliseconds}")
-        return map.value
+        logger.info(
+            "migration read query data ${result.value.size} takes time " +
+                    "${result.duration.inWholeMilliseconds}ms"
+        )
+        return result.value
     }
 
+
     private fun insertSqlToMap(sql: String): Map<String, Any?> {
         val splitSql = sql.split(") VALUES (")
         val fieldColumn = splitSql[0].split("(").last().split(",")
@@ -208,15 +293,6 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
     ): List<MutableMap<String, Any?>> {
         val data = dataToMap(clazz, fields, "code", rootFile, historyFile, groupFile)
         return data
-
-//        val process = measureTime { insertData(clazz, data) }
-//        logger.info(
-//            "finished process migration ${clazz.simpleName} " +
-//                    "data ${data.size} " +
-//                    "success ${data.size - failed.size} " +
-//                    "failed ${failed.size} " +
-//                    "takes time ${process.inWholeMilliseconds}ms"
-//        )
     }
 
     fun clazzEntity(migrationTarget: String): Class<out BaseEntity>? {
@@ -252,14 +328,13 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                 // budget
                 val budgetAnnual = finalizer["budget.maxCost"]?.toString()?.toDoubleOrNull()
                 val warningAnnual = finalizer["budget.warnCost"]?.toString()?.toDoubleOrNull()
-                val budgetMaxCost = budgetAnnual?.let {
-                    val monthBudget = it / 12.0
-                    List(12) { monthBudget }.joinToString(";")
+                val budgetMaxCost = budgetAnnual?.let { max ->
+                    List(12) { max }.joinToString(";")
                 }
-                val budgetWarnCost = warningAnnual?.let {
-                    val monthBudget = budgetAnnual?.let { b -> b / 12.0 } ?: 0.0
-                    val warnPercent = ((it / 12.0) / monthBudget) * 100
-                    List(12) { "%.2f".format(warnPercent) }.joinToString(";")
+                val budgetWarnCost = warningAnnual?.let { warn ->
+                    val monthBudget = budgetAnnual ?: 0.0
+                    val warnPercent = (warn / monthBudget) * 100
+                    List(12) { warnPercent.toInt() }.joinToString(";")
                 }
 
                 // phoneUserPbx
@@ -287,29 +362,21 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                             "warnCost"
                         ).any { a -> a == fi.key }
                     } as MutableMap<String, Any?>
+                    finalMap["corcos"] = if (phoneUserPbxIds.isEmpty()) "" else "[${
+                        phoneUserPbxIds.distinct().joinToString(",") { m ->
+                            "{\"pbx\":\"$m\",\"normal\":\"\",\"reducing\":\"\",\"block\":\"\"}"
+                        }
+                    }]"
+                    if (phoneUserPbxIds.isEmpty()) {
+                        finalMap["expiredDate"] = LocalDate.now().atStartOfDay()
+                    }
                     queryNativeService.insertDataWithNativeQuery(clazz, finalMap)
-//                    apiService.create(clazz, finalMap)
                 } else {
-//                    apiService.create(clazz, finalizer)
                     queryNativeService.insertDataWithNativeQuery(clazz, finalizer)
                 }
 
                 // create budget
                 if (BudgetUserType.entries.any { a -> a.name.snakeToCamel() == clazz.simpleName.camelCase() }) {
-//                    val id = clazz.collectAllField().findId().value(data)
-//                    val res = apiService.findById(clazz, id!!, listOf("uid"))
-//                    res?.get("uid")?.toString()?.let { uid ->
-//                    apiService.create(
-//                        Budget::class.java,
-//                        mutableMapOf(
-//                            "userType" to BudgetUserType.valueOf(
-//                                clazz.simpleName.camelCase().camelToSnake().uppercase()
-//                            ),
-//                            "userUid" to data,
-//                            "type" to "FLAT"
-//                        ),
-//                    )
-
                     queryNativeService.insertDataWithNativeQuery(
                         Budget::class.java, mutableMapOf(
                             "userType" to BudgetUserType.valueOf(
@@ -317,17 +384,18 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                             ).ordinal,
                             "userUid" to data,
                             "type" to BudgetType.FLAT.ordinal,
-                            "annualCost" to budgetAnnual,
+                            "annualCost" to (budgetAnnual?.let { it * 12 }?.toInt() ?: 0),
                             "accumulate" to 0,
                             "maxCost" to (budgetMaxCost ?: "0;0;0;0;0;0;0;0;0;0;0;0"),
                             "warnCostPercentage" to (budgetWarnCost ?: "0;0;0;0;0;0;0;0;0;0;0;0"),
-                            "tempCost" to "0;0;0;0;0;0;0;0;0;0;0;0"
+                            "tempCost" to "0;0;0;0;0;0;0;0;0;0;0;0",
+                            "maxAutoCalculate" to "1;1;1;1;1;1;1;1;1;1;1;1"
                         )
                     )
 
                     // create phoneUserPbx
                     if (phoneUserPbxIds.isNotEmpty()) {
-                        phoneUserPbxIds.forEach { pbxId ->
+                        phoneUserPbxIds.distinct().forEach { pbxId ->
                             queryNativeService.insertDataWithNativeQuery(
                                 PhoneUserPbx::class.java, mutableMapOf(
                                     "pin" to phoneUserPin,
@@ -336,17 +404,8 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                                     "phoneUser_id" to data,
                                 )
                             )
-//                            apiService.create(
-//                                PhoneUserPbx::class.java, mutableMapOf(
-//                                    "pin" to phoneUserPin,
-//                                    "extension" to phoneUserExtension,
-//                                    "pbx_id" to pbxId,
-//                                    "phoneUser_id" to id,
-//                                )
-//                            )
                         }
                     }
-//                    }
                 }
             } catch (e: Exception) {
                 failed.add(map)
@@ -362,13 +421,16 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
             map["code"] = it.ifBlank { UUID.randomUUID().toString().take(6) }
         }
         map["name"]?.toString()?.let {
-            map["name"] = it.ifBlank { "Auto ${(0..99999).toString().padStart(5, '0')}" }
+            map["name"] = it.ifBlank {
+                "Auto ${(0..99999).random().toString().padStart(5, '0')}"
+            }
         }
 
-        map["emailOnOverBudget"]?.let {
-            map["emailOnOverBudget"] = it.toString() == "1"
+        if (className == "phoneUser" || className == "costCenter" || className == "organization") {
+            map["emailOnOverBudget"] = map["emailOnOverBudget"]?.toString()?.toInt() ?: 0
         }
 
+
         map["direction"]?.toString()?.let {
             map["direction"] = if (className == "transaction") {
                 it.split("").mapNotNull { m ->
@@ -378,7 +440,7 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                         "I" -> Direction.INTERNAL.ordinal //"I"
                         else -> null
                     }
-                }.joinToString(";")
+                }.joinToString("")
             } else {
                 it.split("").mapNotNull { m ->
                     when (m.trim()) {
@@ -387,7 +449,7 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                         "I" -> "I"
                         else -> null
                     }
-                }.joinToString(";")
+                }.joinToString("")
             }
         }
 
@@ -411,7 +473,9 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
 
         mapFinalize["pin"]?.toString()?.let {
             mapFinalize["pin"] = if (it.isBlank()) null
-            else cpDecrypt.decrypt(it)?.let { p -> ToolAes.encrypt(p) }
+            else cpDecrypt.decrypt(it)?.let { p ->
+                if (className == "phoneUser") ToolAes.encrypt(p) else p
+            }
         }
 
         mapFinalize["password"]?.toString()?.let {
@@ -432,13 +496,50 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
 
         if (className == "webUser") {
             defaultProfile?.let { mapFinalize["profile_id"] = it }
+            mapFinalize["canRequest"] = 0
+            mapFinalize["numberRightsApproval"] = 0
+            mapFinalize["requestForOthers"] = 0
+            val pinPassword = map["loginPin"]?.toString()?.toInt()?.let { i ->
+                if (i == 1) {
+                    map["phoneUser_id"]?.toString()?.let { uid ->
+                        findPin(uid)?.let { phoneUserPbx ->
+                            mapFinalize["pinext_uid"] = phoneUserPbx.first
+                            ToolAes.decrypt(phoneUserPbx.second)
+                        }
+                    }
+                } else null
+            }
+            mapFinalize["password"] = when {
+                pinPassword != null -> {
+                    passwordEncoder.encode(pinPassword)
+                }
+
+                else -> if (mapFinalize["password"].toString().isEmpty()) {
+                    passwordEncoder.encode("12345")
+                } else mapFinalize["password"]
+            }
         }
 
         if (className == "corcos") {
             mapFinalize["name"] = "Corcos ${map["name"]}"
         }
 
-        return mapFinalize.mapValues { v -> v.value?.toString() } as MutableMap<String, Any?>
+        if (className == "trunk") {
+            mapFinalize["abonemen"] = 0
+        }
+
+        if (className == "organization") {
+            mapFinalize["memberLimit"] = 0
+        }
+
+        if (className == "phoneUser") {
+            mapFinalize["asApprover"] = 0
+            mapFinalize["bypassApproval"] = 0
+            mapFinalize["limitStatus"] = 0
+        }
+
+        return mapFinalize.filterNot { it.key == "loginPin" }
+            .mapValues { v -> v.value?.toString() } as MutableMap<String, Any?>
     }
 
     private val defaultProfile: Any? by lazy {
@@ -467,6 +568,33 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
         }
     }
 
+    private fun findPin(phoneUserCode: String): Pair<String, String>? {
+        return try {
+
+            val query = """
+            SELECT TOP 1 pin, pbx_uid 
+            FROM phoneuserpbx 
+            LEFT JOIN phone_user ph ON ph.uid = phoneuserpbx.phone_user_uid 
+            WHERE ph.code = :code
+        """.trimIndent()
+
+            val result = apiService.em
+                .createNativeQuery(query)
+                .setParameter("code", phoneUserCode)
+                .singleResult as Array<*>
+
+            val pin = result[0]?.toString()
+            val pbxUid = result[1]?.toString()
+
+            if (pin != null && pbxUid != null) {
+                Pair(pbxUid, pin)
+            } else null
+
+        } catch (_: Exception) {
+            null
+        }
+    }
+
 }
 
 fun String.camelToSnake(): String =