FinalizedDataService.kt 39 KB

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