FinalizedDataService.kt 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. package com.datacomsolusindo.migration.data
  2. import com.datacomsolusindo.cpx_shared_code.entity.*
  3. import com.datacomsolusindo.cpx_shared_code.service.ApiService
  4. import com.datacomsolusindo.cpx_shared_code.service.CpDecrypt
  5. import com.datacomsolusindo.cpx_shared_code.utility.*
  6. import com.datacomsolusindo.migration.*
  7. import com.datacomsolusindo.migration.model.MigrationFileData
  8. import io.azam.ulidj.ULID
  9. import io.github.semutkecil.simplecriteria.FilterData
  10. import org.springframework.security.crypto.password.PasswordEncoder
  11. import org.springframework.stereotype.Service
  12. import org.springframework.transaction.annotation.Transactional
  13. import java.time.LocalDateTime
  14. import java.time.format.DateTimeFormatter
  15. import java.util.*
  16. import kotlin.math.log
  17. import kotlin.time.measureTimedValue
  18. val temporaryPrepareCallTransactionCallTo: MutableMap<String, Map<String, Any?>?> = mutableMapOf()
  19. val temporaryPrepareCallTransactionCallFrom: MutableMap<String, String?> = mutableMapOf()
  20. var temporaryHlrData: MutableMap<String, Map<String, Any?>?> = mutableMapOf()
  21. @Service
  22. class FinalizedDataService(
  23. private val cpDecrypt: CpDecrypt,
  24. private val passwordEncoder: PasswordEncoder,
  25. val apiService: ApiService,
  26. private val insertDataService: InsertDataService,
  27. private val migrationSettingService: MigrationSettingService
  28. ) {
  29. private val logger = SimpleLogger.getLogger(this::class.java)
  30. fun processData(
  31. migrationFileData: MigrationFileData
  32. ): List<MutableMap<String, Any?>> {
  33. val mappedData = measureTimedValue {
  34. val fields = migrationFileData.fields
  35. val fieldMapping = fields.mapValues { it.value.substringAfterLast(".") }
  36. val hasParent = fieldMapping.any { it.key == "parent_id" }
  37. val uniqueField = fieldMapping["code"] ?: "id"
  38. val uniqueFieldId = fieldMapping["id"]
  39. val historyIndex = buildHistoryIndex(migrationFileData.historyData, uniqueField, uniqueFieldId)
  40. val historyIndexById =
  41. buildHistoryIndex(migrationFileData.historyData, uniqueField, uniqueFieldId, byId = true)
  42. val groupIndex = buildGroupIndex(migrationFileData.groupData, uniqueFieldId ?: uniqueField)
  43. val fieldRoots = fields.filterValues { !it.contains(".") }
  44. val joinRoots = fields.filterValues { it.contains(".") }
  45. migrationFileData.rootData!!
  46. // .take(10)
  47. .map { row ->
  48. buildRow(
  49. row,
  50. fieldRoots,
  51. joinRoots,
  52. historyIndex,
  53. historyIndexById,
  54. groupIndex,
  55. uniqueField,
  56. uniqueFieldId
  57. )
  58. }.let { data ->
  59. if (hasParent) {
  60. data.sortedBy { it["structure"].toString().length }
  61. } else {
  62. data.map { postProcessPassword(it) }
  63. }
  64. }
  65. }
  66. logger.info(
  67. "finalized data service table ${migrationFileData.table} " +
  68. "takes time ${mappedData.duration.inWholeMilliseconds}ms " +
  69. "with all data ${mappedData.value.size}"
  70. )
  71. return mappedData.value
  72. }
  73. private fun buildHistoryIndex(
  74. historyData: List<Map<String, Any?>>?,
  75. uniqueField: String,
  76. uniqueFieldId: String?,
  77. byId: Boolean = false
  78. ): Map<Any?, Map<String, Any?>>? {
  79. if (historyData == null || uniqueFieldId == null) return null
  80. return historyData
  81. .groupBy { if (byId) it[uniqueFieldId] else it[uniqueField] }
  82. .mapValues { (_, items) ->
  83. items.maxByOrNull {
  84. it[uniqueFieldId.removePrefix("history.")]
  85. ?.toString()
  86. ?.toIntOrNull() ?: 0
  87. } as Map<String, Any?>
  88. }
  89. }
  90. private fun buildGroupIndex(groupData: List<Map<String, Any?>>?, key: String): Map<String?, Map<String, Any?>>? {
  91. return groupData?.associateBy { it[key]?.toString() }
  92. }
  93. private fun buildRow(
  94. row: Map<String, Any?>,
  95. fieldRoots: Map<String, String>,
  96. joinRoots: Map<String, String>,
  97. historyIndex: Map<Any?, Map<String, Any?>>?,
  98. historyIndexById: Map<Any?, Map<String, Any?>>?,
  99. groupIndex: Map<String?, Map<String, Any?>>?,
  100. uniqueField: String,
  101. uniqueFieldId: String?
  102. ): MutableMap<String, Any?> {
  103. val data = mutableMapOf<String, Any?>()
  104. // Direct fields
  105. fieldRoots.forEach { (target, source) ->
  106. data[target] = when {
  107. target == "pbx__default" -> source
  108. else -> row[source]
  109. }
  110. }
  111. // Join fields
  112. joinRoots.forEach { (target, sourceFull) ->
  113. val value = when {
  114. sourceFull.startsWith("history.") -> {
  115. val key = sourceFull.removePrefix("history.")
  116. val valueHistories = historyIndex
  117. ?.get(row[uniqueField])
  118. ?.get(key.substringAfterLast("."))
  119. valueHistories?.let { valHis ->
  120. if (target.contains("_")) {
  121. val codeKey = uniqueField.removePrefix("history.")
  122. val joinCode = historyIndexById?.get(valHis)?.get(codeKey)
  123. joinCode ?: valHis
  124. } else valHis
  125. }
  126. }
  127. sourceFull.startsWith("group.") -> {
  128. val key = sourceFull.removePrefix("group.")
  129. val idKey = historyIndex
  130. ?.get(row[uniqueField])
  131. ?.get(uniqueFieldId)
  132. ?: row[uniqueFieldId]
  133. groupIndex
  134. ?.get(idKey?.toString())
  135. ?.get(key.substringAfterLast("."))
  136. }
  137. else -> row[sourceFull]
  138. }
  139. data[target] = value
  140. }
  141. return data
  142. }
  143. private fun postProcessPassword(data: MutableMap<String, Any?>): MutableMap<String, Any?> {
  144. val raw = data["password"]?.toString() ?: return data
  145. data["password"] = when {
  146. raw.isBlank() -> ""
  147. else -> cpDecrypt.decrypt(raw)?.let { plain ->
  148. tempPassword[plain] ?: passwordEncoder.encode(plain).also {
  149. tempPassword[plain] = it
  150. }
  151. } ?: ""
  152. }
  153. return data
  154. }
  155. fun finalizeMap(table: String, map: MutableMap<String, Any?>): MutableMap<String, Any?> {
  156. val mapFinalize: MutableMap<String, Any?> = mutableMapOf()
  157. map["code"]?.toString()?.let {
  158. map["code"] = it.ifBlank { UUID.randomUUID().toString().take(6) }
  159. }
  160. map["name"]?.toString()?.let {
  161. map["name"] = it.ifBlank {
  162. "Auto ${(0..99999).random().toString().padStart(5, '0')}"
  163. }
  164. }
  165. if (table == "phone_user" || table == "cost_center" || table == "organization") {
  166. map["emailOnOverBudget"] = map["emailOnOverBudget"]?.toString()?.toInt() ?: 0
  167. }
  168. map["direction"]?.toString()?.let {
  169. map["direction"] = if (table == "calltransaction") {
  170. it.split("").mapNotNull { m ->
  171. when (m.trim()) {
  172. "C" -> Direction.INCOMING.ordinal //"C"
  173. "G" -> Direction.OUTGOING.ordinal //"G"
  174. "I" -> Direction.INTERNAL.ordinal //"I"
  175. else -> null
  176. }
  177. }.joinToString("")
  178. } else {
  179. it.split("").mapNotNull { m ->
  180. when (m.trim()) {
  181. "C" -> "C"
  182. "G" -> "G"
  183. "I" -> "I"
  184. else -> null
  185. }
  186. }.joinToString("")
  187. }
  188. }
  189. map.filterNot { it.key == "id" || it.key == "structure" }.forEach { (t, u) ->
  190. when {
  191. t.contains("__") -> {
  192. mapFinalize[t] = u
  193. }
  194. t.contains("_") -> {
  195. val isParent = t.startsWith("parent")
  196. val value = if (t == "pbx_id") {
  197. findUidByCode("pbx", u ?: "PBX01")
  198. } else u?.toString()?.let { code ->
  199. val clazzEntity = if (isParent) table else t.split("_")[0]
  200. val field = if (t.split("_")[0].lowercase().startsWith("corcos")) "command" else "code"
  201. findUidByCode(clazzEntity, code, field)
  202. }
  203. if (value != null) {
  204. mapFinalize[t] = value
  205. }
  206. }
  207. else -> {
  208. mapFinalize[t] = u
  209. }
  210. }
  211. }
  212. mapFinalize["pin"]?.toString()?.let {
  213. mapFinalize["pin"] = if (it.isBlank()) null
  214. else cpDecrypt.decrypt(it)?.let { p ->
  215. if (table == "phone_user") ToolAes.encrypt(p) else p
  216. }
  217. }
  218. if (table == "calltransaction") {
  219. val to = mapFinalize["extTransferTo"]?.toString() ?: ""
  220. val from = mapFinalize["extTransferFrom"]?.toString() ?: ""
  221. mapFinalize["transferType"] = when {
  222. to.isBlank() && from.isBlank() -> TransferType.DIRECT.ordinal
  223. to.isNotBlank() && from.isBlank() -> TransferType.TRANSFER_TO.ordinal
  224. to.isBlank() && from.isNotBlank() -> TransferType.TRANSFER_FROM.ordinal
  225. else -> TransferType.TRANSFER_FROM_AND_TRANSFER_TO.ordinal
  226. }
  227. }
  228. if (table == "web_user") {
  229. defaultProfile?.let { mapFinalize["profile_id"] = it }
  230. mapFinalize["canRequest"] = 0
  231. mapFinalize["numberRightsApproval"] = 0
  232. mapFinalize["requestForOthers"] = 0
  233. val pinPassword = map["loginPin"]?.toString()?.toInt()?.let { i ->
  234. if (i == 1) {
  235. map["phoneUser_id"]?.toString()?.let { uid ->
  236. findPinPhonePbx(uid)?.let { phoneUserPbx ->
  237. mapFinalize["pinext_uid"] = phoneUserPbx.first
  238. ToolAes.decrypt(phoneUserPbx.second)
  239. }
  240. }
  241. } else null
  242. }
  243. mapFinalize["password"] = when {
  244. pinPassword != null -> {
  245. tempPassword[pinPassword] ?: run {
  246. val pass = passwordEncoder.encode(pinPassword)
  247. tempPassword[pinPassword] = pass
  248. pass
  249. }
  250. }
  251. else -> if (mapFinalize["password"].toString().isEmpty()) {
  252. tempPassword["12345"] ?: run {
  253. val pass = passwordEncoder.encode("12345")
  254. tempPassword["12345"] = pass
  255. pass
  256. }
  257. } else mapFinalize["password"]
  258. }
  259. }
  260. if (table == "corcos") {
  261. mapFinalize["name"] = "Corcos ${map["name"]}"
  262. }
  263. if (table == "trunk") {
  264. mapFinalize["abonemen"] = 0
  265. }
  266. if (table == "organization") {
  267. mapFinalize["memberLimit"] = 0
  268. }
  269. if (table == "phone_user") {
  270. mapFinalize["asApprover"] = 0
  271. mapFinalize["bypassApproval"] = 0
  272. mapFinalize["limitStatus"] = 0
  273. }
  274. if (table == "account") {
  275. mapFinalize["number"] = "9"
  276. }
  277. return mapFinalize.filterNot { it.key == "loginPin" }
  278. .mapValues { v -> v.value?.toString() } as MutableMap<String, Any?>
  279. }
  280. private fun findUidByCode(table: String, value: Any, field: String = "code", select: String = "uid"): String? {
  281. return if (value.toString().isBlank()) null else {
  282. val tableName = General.toTableName(table)
  283. val keyCode = "$tableName;$select;$value"
  284. if (temporaryDataByCode.any { a -> a.key == keyCode }) {
  285. temporaryDataByCode[keyCode]
  286. } else {
  287. var dataUid = try {
  288. val fd = if (table == "trunk") " AND (subscribed_no != 'new trunk' OR subscribed_no IS NULL) " else " "
  289. apiService.transaction { em ->
  290. val result = em.createNativeQuery("SELECT $select FROM $tableName WHERE $field = :$field $fd")
  291. .setParameter(field, value.toString())
  292. .resultList
  293. .first() as String?
  294. result
  295. }
  296. } catch (e: Exception) {
  297. logger.info("failed find uid $tableName $field $value")
  298. null
  299. }
  300. if (tableName == "corcos" && dataUid == null && migrationSettingService.autoCreateCorcos) {
  301. dataUid = insertDataService.insertDataWithNativeQuery(tableName, "command", mutableMapOf(
  302. "command" to value,
  303. "name" to value,
  304. "description" to "auto create corcos"
  305. ))
  306. }
  307. temporaryDataByCode[keyCode] = dataUid
  308. dataUid
  309. }
  310. // temporaryDataByCode["$tableName;$select;$value"] ?: run {
  311. // try {
  312. // val fd = if (table == "trunk") " AND subscribed_no != 'new trunk' " else " "
  313. // apiService.transaction { em ->
  314. // val result = em.createNativeQuery("SELECT $select FROM $tableName WHERE $field = :$field $fd")
  315. // .setParameter(field, value.toString())
  316. // .resultList
  317. // .first() as String?
  318. // temporaryDataByCode["$tableName;$select;$value"] = result
  319. // result
  320. // }
  321. // } catch (e: Exception) {
  322. // logger.info("failed find uid $tableName $field $value")
  323. // null
  324. // }
  325. // }
  326. }
  327. }
  328. private fun findCodeByUid(table: String, value: Any, field: String = "uid", select: String = "code"): String? {
  329. return if (value.toString().isBlank()) null else {
  330. val tableName = General.toTableName(table)
  331. val keyCode = "$tableName;$select;$value"
  332. if (temporaryDataByCode.any { a -> a.key == keyCode }) {
  333. temporaryDataByCode[keyCode]
  334. } else {
  335. try {
  336. apiService.transaction { em ->
  337. val result = em.createNativeQuery("SELECT $select FROM $tableName WHERE $field = :$field")
  338. .setParameter(field, value.toString())
  339. .resultList
  340. .first() as String?
  341. temporaryDataByCode[keyCode] = result
  342. result
  343. }
  344. } catch (e: Exception) {
  345. temporaryDataByCode[keyCode] = null
  346. logger.info("failed find code $tableName $field $value")
  347. null
  348. }
  349. }
  350. // temporaryDataByCode["$tableName;$select;$value"] ?: run {
  351. // try {
  352. // apiService.transaction { em ->
  353. // val result = em.createNativeQuery("SELECT $select FROM $tableName WHERE $field = :$field")
  354. // .setParameter(field, value.toString())
  355. // .resultList
  356. // .first() as String?
  357. // temporaryDataByCode["$tableName;$select;$value"] = result
  358. // result
  359. // }
  360. // } catch (e: Exception) {
  361. // logger.info("failed find code $tableName $field $value")
  362. // null
  363. // }
  364. // }
  365. }
  366. }
  367. private val defaultProfile: Any? by lazy {
  368. apiService.findListPage(
  369. Rights::class.java,
  370. listOf("uid"),
  371. FilterData.filter("name", FilterData.FILTEROP.EQ, "Default")
  372. ).firstOrNull()?.get("uid")
  373. }
  374. private fun findPinPhonePbx(phoneUserCode: String): Pair<String, String>? {
  375. val key = "${PhoneUserPbx::class.java.simpleName};$phoneUserCode"
  376. val tmpData = temporaryDataEntity[key] ?: run {
  377. apiService.findListAll(
  378. PhoneUserPbx::class.java,
  379. listOf("pin", "pbx.uid", "phoneUser.code")
  380. ).forEach { dt ->
  381. temporaryDataEntity["${PhoneUserPbx::class.java.simpleName};${dt["phoneUser.code"]!!.toString()}"] = dt
  382. }
  383. //.associateBy { it["phoneUser.code"]!!.toString() }
  384. temporaryDataEntity[key]
  385. }
  386. return tmpData?.let { it["pbx.uid"].toString() to it["pin"].toString() }
  387. }
  388. fun buildToInsertData(table: String, map: MutableMap<String, Any?>, uniqueTable: String?): Int {
  389. return try {
  390. val finalizer = finalizeMap(table, map)
  391. val phoneUserPin = finalizer["pin"]
  392. val phoneUserExtension = finalizer["extension"]
  393. val corcosNormal = finalizer["corcosNormal_id"]
  394. val corcosReducing = finalizer["corcosReducing_id"]
  395. val corcosBlock = finalizer["corcosBlock_id"]
  396. // budget
  397. val budgetAnnual = (finalizer["budget.maxCost"]?.toString()
  398. ?: finalizer["budget__maxCost"]?.toString())?.toDoubleOrNull()
  399. val warningAnnual = (finalizer["budget.warnCost"]?.toString()
  400. ?: finalizer["budget__warnCost"]?.toString())?.toDoubleOrNull()
  401. val budgetMaxCost = budgetAnnual?.let { max ->
  402. List(12) { max }.joinToString(";")
  403. }
  404. val budgetWarnCost = warningAnnual?.let { warn ->
  405. val monthBudget = budgetAnnual ?: 0.0
  406. val warnPercent = (warn / monthBudget) * 100
  407. List(12) { warnPercent.toInt() }.joinToString(";")
  408. }
  409. // phoneUserPbx
  410. var phoneUserPbxIds: MutableList<String> = mutableListOf()
  411. val finalMapEntity = when (table) {
  412. "calltransaction" -> {
  413. val callMap = prepareDataCallTransaction(finalizer)
  414. callMap["additionalData1"] =
  415. "migration-${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))}"
  416. callMap
  417. }
  418. "phone_user" -> {
  419. val pbxId = finalizer["pbx.id"]?.toString() ?: finalizer["pbx.list"]?.toString() ?: ""
  420. val pbxGroup = finalizer["pbx__list"]?.toString()?.split(";")
  421. ?.mapNotNull { findUidByCode("pbx", it) } ?: listOf()
  422. if (pbxId.isBlank() && pbxGroup.isEmpty()) {
  423. finalizer["pbx__default"]?.toString()?.let { pbx ->
  424. findUidByCode("pbx", pbx)?.let { pbxDefault ->
  425. phoneUserPbxIds.add(pbxDefault)
  426. }
  427. }
  428. } else {
  429. if (pbxId.isNotBlank()) {
  430. phoneUserPbxIds.addAll(pbxId.split(";"))
  431. }
  432. if (pbxGroup.isNotEmpty()) {
  433. phoneUserPbxIds.addAll(pbxGroup)
  434. }
  435. }
  436. val finalMap = finalizer.filterNot { fi ->
  437. listOf(
  438. "pbx.list",
  439. "pbx__list",
  440. "pbx__default",
  441. "pbx_id",
  442. "extension",
  443. "pin",
  444. "budget.maxCost",
  445. "budget__maxCost",
  446. "budget.warnCost",
  447. "budget__warnCost",
  448. "maxCost",
  449. "warnCost",
  450. "corcosNormal_id",
  451. "corcosReducing_id",
  452. "corcosBlock_id"
  453. ).any { a -> a == fi.key }
  454. } as MutableMap<String, Any?>
  455. finalMap["corcos"] = if (phoneUserPbxIds.isEmpty()) "" else "[${
  456. phoneUserPbxIds.distinct().joinToString(",") { m ->
  457. "{\"pbx\":\"$m\"," +
  458. "\"normal\":\"${corcosNormal ?: ""}\"," +
  459. "\"reducing\":\"${corcosReducing ?: ""}\"," +
  460. "\"block\":\"${corcosBlock ?: ""}\"}"
  461. }
  462. }]"
  463. finalMap
  464. }
  465. else -> finalizer
  466. }
  467. insertDataService.insertDataWithNativeQuery(
  468. table,
  469. uniqueTable,
  470. finalMapEntity,
  471. functionAfter = { uid, isUpdate ->
  472. // create budget
  473. if (BudgetUserType.entries.any { a -> a.name == table.uppercase() }) {
  474. insertDataService.insertDataWithNativeQuery(
  475. "budget", uniqueTable = null, mutableMapOf(
  476. "userType" to BudgetUserType.valueOf(table.uppercase()).ordinal,
  477. "userUid" to uid,
  478. "type" to BudgetType.FLAT.ordinal,
  479. "annualCost" to (budgetAnnual?.let { it * 12 }?.toInt() ?: 0),
  480. "accumulate" to 0,
  481. "maxCost" to (budgetMaxCost ?: "0;0;0;0;0;0;0;0;0;0;0;0"),
  482. "warnCostPercentage" to (budgetWarnCost ?: "0;0;0;0;0;0;0;0;0;0;0;0"),
  483. "tempCost" to "0;0;0;0;0;0;0;0;0;0;0;0",
  484. "maxAutoCalculate" to "1;1;1;1;1;1;1;1;1;1;1;1"
  485. )
  486. )
  487. }
  488. // create phoneUserPbx
  489. if (phoneUserPbxIds.isNotEmpty()) {
  490. phoneUserPbxIds.distinct().forEach { pbxId ->
  491. insertDataService.insertDataWithNativeQuery(
  492. "phoneuserpbx", uniqueTable = null, mutableMapOf(
  493. "pin" to phoneUserPin,
  494. "extension" to phoneUserExtension,
  495. "pbx_id" to pbxId,
  496. "phoneUser_id" to uid,
  497. "corcos_normal_uid" to corcosNormal,
  498. "corcos_reducing_uid" to corcosReducing,
  499. "corcos_block_uid" to corcosBlock
  500. )
  501. )
  502. }
  503. }
  504. })
  505. 1
  506. } catch (e: Exception) {
  507. // println(Util.mapper.writeValueAsString(map))
  508. logger.error("failed insert data $table", e)
  509. 0
  510. }
  511. }
  512. private fun buildRawData(data: MutableMap<String, Any?>): List<Any?> {
  513. val seg1 = "CDR"
  514. val seg2 = data["pbx_id"]?.let { findCodeByUid("pbx", it) ?: "" } ?: ""
  515. val seg3 = data["direction"]?.let { Direction.entries[it.toString().toInt()].code } ?: ""
  516. val startOfCall = data["startOfCall"]?.toString()?.let {
  517. LocalDateTime.parse(it, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"))
  518. }
  519. val endOfCall = startOfCall?.plusSeconds(data["duration"]?.toString()?.toLong() ?: 0)
  520. val seg4 = startOfCall?.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) ?: ""
  521. val seg5 = startOfCall?.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) ?: ""
  522. val seg6 = endOfCall?.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) ?: ""
  523. val seg7 = ""
  524. val seg8 = data["duration"]
  525. val seg9 = data["accessNumber"] ?: ""
  526. val seg10 = ""
  527. val seg11 = data["trunk_id"]?.let { findCodeByUid("trunk", it) ?: "" } ?: ""
  528. val seg12 = ""
  529. val seg13 = data["number"] ?: ""
  530. val seg14 = data["callerNumber"] ?: ""
  531. val seg15 = data["pin"] ?: ""
  532. val seg16 = ""
  533. val seg17 = data["extension"] ?: ""
  534. val seg18 = data["extTransferFrom"] ?: ""
  535. val seg19 = data["extTransferTo"] ?: ""
  536. val seg20 = data["transferType"]?.let {
  537. when (it as TransferType) {
  538. TransferType.DIRECT -> "D"
  539. TransferType.TRANSFER_TO -> "TT"
  540. TransferType.TRANSFER_FROM -> "TF"
  541. else -> "F"
  542. }
  543. } ?: ""
  544. val seg21 = "" //data["conferenceType"]?.let { (it as ConferenceType).code } ?: ""
  545. val seg22 = "" //data["communicationType"]?.let { (it as CommunicationType).code } ?: ""
  546. val seg23 = data["redirectReason"] ?: ""
  547. val seg24 = data["terminationCode"] ?: ""
  548. val seg25 = data["additionalData1"] ?: ""
  549. val seg26 = data["additionalData2"] ?: ""
  550. return listOf(
  551. seg1, seg2, seg3, seg4, seg5, seg6, seg7, seg8, seg9, seg10,
  552. seg11, seg12, seg13, seg14, seg15, seg16, seg17, seg18, seg19, seg20,
  553. seg21, seg22, seg23, seg24, seg25, seg26
  554. )
  555. }
  556. private fun prepareDataCallTransaction(data: MutableMap<String, Any?>): MutableMap<String, Any?> {
  557. data["transferType"] = when {
  558. (data["extTransferFrom"]?.toString()?.isBlank() ?: false)
  559. && (data["extTransferTo"]?.toString()?.isBlank() ?: false) -> TransferType.DIRECT
  560. (data["extTransferFrom"]?.toString()?.isBlank() ?: false)
  561. && (data["extTransferTo"]?.toString()?.isNotBlank() ?: false) -> TransferType.TRANSFER_TO
  562. (data["extTransferFrom"]?.toString()?.isNotBlank() ?: false)
  563. && (data["extTransferTo"]?.toString()?.isBlank() ?: false) -> TransferType.TRANSFER_FROM
  564. else -> null
  565. }
  566. val buildRawData = buildRawData(data)
  567. val rawData = buildRawData.joinToString(",")
  568. data["rawData"] = rawData
  569. getCallTo(buildRawData[12].toString())?.forEach { (t, u) -> data[t] = u }
  570. //
  571. // data["area_uid"]?.toString()?.let { area ->
  572. // val domainFrom = getCallFrom(area, buildRawData[10]?.toString(), buildRawData[1]?.toString())
  573. // data["zone_uid"]?.toString()?.let {
  574. // val zoneDomain = if (data["domain"] == domainFrom) "LOC" else "NDD"
  575. // data["zone_uid"] = findUidByCode("zone", zoneDomain)
  576. // }
  577. // }
  578. data["transferType"]?.let {
  579. data["transferType"] = (it as TransferType).ordinal
  580. }
  581. data.remove("domain")
  582. return data
  583. }
  584. private fun getCallTo(number: String): Map<String, Any?>? {
  585. val keyCode = "getCall-$number"
  586. return if (temporaryPrepareCallTransactionCallTo.any { a -> a.key == keyCode })
  587. temporaryPrepareCallTransactionCallTo[keyCode]
  588. else {
  589. val matchedPrefix = temporaryHlrData.entries
  590. .filter { number.startsWith(it.key) }
  591. .maxByOrNull { it.key.length }
  592. ?.value
  593. val result = matchedPrefix?.let {
  594. mapOf(
  595. "prefix" to it["prefix"]?.toString(),
  596. "provider_to_uid" to it["provider.uid"]?.toString(),
  597. "area_uid" to it["area.uid"]?.toString(),
  598. "phone_type" to it["phoneType"]?.let { i -> (i as PhoneType).ordinal },
  599. "zone_uid" to it["zone.uid"]?.toString(),
  600. "domain" to it["domain"]?.toString()
  601. )
  602. }
  603. temporaryPrepareCallTransactionCallTo[keyCode] = result
  604. result
  605. }
  606. }
  607. private fun getCallFrom(areaUid: String, trunkCode: String?, pbxCode: String?): String? {
  608. val keyCode = "getCallFrom-$areaUid-${trunkCode ?: "#"}-${pbxCode ?: "#"}"
  609. return if (temporaryPrepareCallTransactionCallFrom.any { a -> a.key == keyCode })
  610. temporaryPrepareCallTransactionCallFrom[keyCode]
  611. else {
  612. try {
  613. val trunk = trunkCode?.let { findUidByCode("trunk", it, select = "provider_uid") }
  614. val pbx = pbxCode?.let { findUidByCode("pbx", it, select = "provider_uid") }
  615. apiService.transaction { em ->
  616. val hlr = em.createNativeQuery(
  617. "SELECT TOP 1 domain \n" +
  618. "FROM hlr\n" +
  619. "WHERE area_uid = '$areaUid' AND provider_uid = '${trunk ?: pbx ?: "#uid"}' \n"
  620. ).singleResult as Array<Any?>
  621. val result = hlr[0].toString()
  622. temporaryPrepareCallTransactionCallFrom[keyCode] = result
  623. result
  624. }
  625. } catch (e: Exception) {
  626. temporaryPrepareCallTransactionCallFrom[keyCode] = null
  627. logger.info("failed get call from attribute area ${areaUid} trunk $trunkCode pbx $pbxCode")
  628. null
  629. }
  630. }
  631. }
  632. fun prepareDataCallTransaction() {
  633. apiService.findListAll(PhoneUser::class.java, select = listOf("uid", "code")).forEach {
  634. val keyCode = "phone_user;uid;${it["code"] ?: "#"}"
  635. temporaryDataByCode[keyCode] = it["uid"]?.toString()
  636. }
  637. apiService.findListAll(Organization::class.java, select = listOf("uid", "code")).forEach {
  638. val keyCode = "organization;uid;${it["code"] ?: "#"}"
  639. temporaryDataByCode[keyCode] = it["uid"]?.toString()
  640. }
  641. apiService.findListAll(CostCenter::class.java, select = listOf("uid", "code")).forEach {
  642. val keyCode = "cost_center;uid;${it["code"] ?: "#"}"
  643. temporaryDataByCode[keyCode] = it["uid"]?.toString()
  644. }
  645. apiService.findListAll(Pbx::class.java, select = listOf("uid", "code")).forEach {
  646. val keyCode = "pbx;uid;${it["code"] ?: "#"}"
  647. temporaryDataByCode[keyCode] = it["uid"]?.toString()
  648. }
  649. apiService.findListAll(Trunk::class.java, select = listOf("uid", "code")).forEach {
  650. val keyCode = "trunk;uid;${it["code"] ?: "#"}"
  651. temporaryDataByCode[keyCode] = it["uid"]?.toString()
  652. }
  653. apiService.findListAll(Account::class.java, select = listOf("uid", "code")).forEach {
  654. val keyCode = "account;uid;${it["code"] ?: "#"}"
  655. temporaryDataByCode[keyCode] = it["uid"]?.toString()
  656. }
  657. // hlr-data
  658. temporaryHlrData = apiService.findListAll(
  659. Hlr::class.java,
  660. select = listOf("prefix", "provider.uid", "area.uid", "phoneType", "zone.uid", "domain")
  661. ).distinctBy { it["prefix"] }.associateBy { it["prefix"].toString() }.toMutableMap()
  662. }
  663. }
  664. @Service
  665. @Transactional
  666. class InsertDataService(
  667. val apiService: ApiService,
  668. val migrationSettingService: MigrationSettingService
  669. ) {
  670. private fun findParentStructure(table: String, uid: String): String? {
  671. val parentStructure = temporaryDataByCode["$table;structure;$uid"] ?: run {
  672. try {
  673. apiService.transaction { em ->
  674. val result = em.createNativeQuery("SELECT structure FROM $table WHERE uid = :uid")
  675. .setParameter("uid", uid)
  676. .resultList
  677. .first() as String?
  678. temporaryDataByCode["$table;structure;$uid"] = result ?: uid
  679. result ?: uid
  680. }
  681. } catch (e: Exception) {
  682. SimpleLogger.getLogger(this::class.java).error("failed get parent structure $table $uid", e)
  683. uid
  684. }
  685. }
  686. return parentStructure
  687. }
  688. fun insertDataWithNativeQuery(
  689. table: String,
  690. uniqueTable: String?,
  691. mapData: MutableMap<String, Any?>,
  692. functionAfter: ((uid: String, isUpdate: Boolean) -> Unit)? = null
  693. ): String? {
  694. val fieldUnique = when (table) {
  695. "phoneuserpbx" -> "phone_user_uid;pbx_uid"
  696. "budget" -> "user_uid"
  697. else -> uniqueTable
  698. }
  699. val fields = if (table == "calltransaction") mutableListOf() else mutableListOf("uid")
  700. val finalMap = mapData
  701. .filterNot { it.key == "cpid" }
  702. .mapValues { it.value?.toString()?.ifEmpty { null } }
  703. .toMutableMap()
  704. finalMap.keys.forEach {
  705. val t = it.replace("_id", "_uid")
  706. fields.add(t)
  707. }
  708. val fieldKey = fieldUnique?.split(";")?.mapNotNull { m -> mapData[m]?.toString() }?.joinToString(";")
  709. val uidFromDb = temporaryDataByCode["$table;uid;$fieldKey"]
  710. val uid = uidFromDb ?: ULID.random()
  711. val isUpdate = uidFromDb != null
  712. if (isUpdate) {
  713. finalMap.remove("uid")
  714. }
  715. val structure = finalMap["parent_id"]?.let {
  716. findParentStructure(table, it)?.let { parentUid ->
  717. fields.add("structure")
  718. "$parentUid|$uid"
  719. }
  720. }
  721. if (isUpdate) {
  722. fields.remove("uid")
  723. }
  724. val query =
  725. if (table == "calltransaction") "INSERT INTO $table (${fields.joinToString() { it.camelToSnake() }}) " +
  726. "VALUES (${fields.joinToString() { ":$it" }})"
  727. else {
  728. fieldUnique?.let { fu ->
  729. val uniqueOn = fu.split(";").joinToString(" AND ") { m -> "t.$m = s.$m" }
  730. """
  731. MERGE INTO $table WITH (HOLDLOCK) AS t
  732. USING (VALUES (${fields.joinToString() { ":$it" }}))
  733. AS s(${fields.joinToString() { it.camelToSnake() }})
  734. ON $uniqueOn
  735. WHEN MATCHED THEN
  736. UPDATE SET
  737. ${fields.joinToString(",\n") { "t.${it.camelToSnake()} = s.${it.camelToSnake()}" }}
  738. WHEN NOT MATCHED THEN
  739. INSERT (${fields.joinToString { it.camelToSnake() }})
  740. VALUES (${fields.joinToString { "s.${it.camelToSnake()}" }});
  741. """.trimIndent()
  742. } ?: ("INSERT INTO $table (${fields.joinToString() { it.camelToSnake() }}) " +
  743. "VALUES (${fields.joinToString() { ":$it" }})")
  744. }
  745. val sqlNative = apiService.em.createNativeQuery(query)
  746. if (table != "calltransaction" && !isUpdate) {
  747. sqlNative.setParameter("uid", uid)
  748. }
  749. structure?.let { sqlNative.setParameter("structure", structure) }
  750. finalMap.forEach { (t, u) -> sqlNative.setParameter(t.replace("_id", "_uid"), u) }
  751. sqlNative.executeUpdate()
  752. functionAfter?.invoke(uid, isUpdate)
  753. return uid
  754. }
  755. fun createTableCallTransactionTemporary() {
  756. try {
  757. apiService.transaction { em ->
  758. em.createNativeQuery(
  759. "IF OBJECT_ID('dbo.calltransaction_temp', 'U') IS NOT NULL\n" +
  760. "BEGIN\n" +
  761. " TRUNCATE TABLE dbo.calltransaction_temp;\n" +
  762. "END\n" +
  763. "ELSE\n" +
  764. "BEGIN\n" +
  765. " SELECT TOP 0 *\n" +
  766. " INTO dbo.calltransaction_temp\n" +
  767. " FROM dbo.calltransaction;\n" +
  768. "END"
  769. )
  770. .executeUpdate()
  771. }
  772. } catch (e: Exception) {
  773. SimpleLogger.getLogger(this::class.java).error("failed create table call transaction temporary", e)
  774. }
  775. }
  776. fun updateRawData() {
  777. try {
  778. apiService.transaction { em ->
  779. em.createNativeQuery(
  780. "UPDATE ct\n" +
  781. "SET raw_data = CONCAT_WS(',',\n" +
  782. "\t'CDR',\n" +
  783. " ISNULL(pbx.code, ''),\n" +
  784. " CASE \n" +
  785. " WHEN ct.direction = 0 THEN 'G'\n" +
  786. " WHEN ct.direction = 1 THEN 'C'\n" +
  787. " ELSE 'I'\n" +
  788. " END,\n" +
  789. " CONVERT(VARCHAR, ct.start_of_call, 120),\n" +
  790. " CONVERT(VARCHAR, ct.start_of_call, 120),\n" +
  791. " CONVERT(VARCHAR, DATEADD(SECOND, ct.duration, ct.start_of_call), 120),\n" +
  792. " '',\n" +
  793. " ct.duration,\n" +
  794. " ISNULL(ct.access_number, ''),\n" +
  795. " '',\n" +
  796. " ISNULL(trunk.code, ''),\n" +
  797. " '',\n" +
  798. " ISNULL(ct.number,''),\n" +
  799. " ISNULL(ct.caller_number,''),\n" +
  800. " ISNULL(ct.pin,''),\n" +
  801. " '',\n" +
  802. " ISNULL(ct.extension,''),\n" +
  803. " ISNULL(ct.ext_transfer_from,''),\n" +
  804. " ISNULL(ct.ext_transfer_to,''),\n" +
  805. " ISNULL(CASE WHEN ct.transfer_type = 0 THEN 'D'\n" +
  806. "\tWHEN ct.transfer_type = 1 THEN 'TF'\n" +
  807. "\tWHEN ct.transfer_type = 2 THEN 'TT'\n" +
  808. "\tELSE 'F' END,''),\n" +
  809. " '',\n" +
  810. " '',\n" +
  811. " ISNULL(ct.redirect_reason,''),\n" +
  812. " ISNULL(ct.termination_code,''),\n" +
  813. " CASE \n" +
  814. " WHEN ct.additional_data1 = 'migration' THEN ''\n" +
  815. " ELSE ISNULL(ct.additional_data1,'')\n" +
  816. " END,\n" +
  817. " ISNULL(ct.additional_data2,'')\n" +
  818. ")\n" +
  819. "FROM calltransaction ct\n" +
  820. "LEFT JOIN pbx pbx ON pbx.uid = ct.pbx_uid\n" +
  821. "LEFT JOIN trunk trunk ON trunk.uid = ct.trunk_uid\n" +
  822. "WHERE ct.raw_data IS NULL OR ct.raw_data = ''"
  823. )
  824. }.executeUpdate()
  825. } catch (e: Exception) {
  826. SimpleLogger.getLogger(this::class.java).error("failed update raw data blank data", e)
  827. }
  828. }
  829. }