FinalizedDataService.kt 33 KB

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