caller_id.rb 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. module Cti
  3. class CallerId < ApplicationModel
  4. self.table_name = 'cti_caller_ids'
  5. DEFAULT_COUNTRY_ID = '49'.freeze
  6. # adopt/orphan matching Cti::Log records
  7. # (see https://github.com/zammad/zammad/issues/2057)
  8. after_commit :update_cti_logs, on: :destroy, unless: -> { BulkImportInfo.enabled? }
  9. after_commit :update_cti_logs_with_fg_optimization, on: :create, unless: -> { BulkImportInfo.enabled? }
  10. =begin
  11. Cti::CallerId.maybe_add(
  12. caller_id: '49123456789',
  13. comment: 'Hairdresser Bob Smith, San Francisco', #optional
  14. level: 'maybe', # known|maybe
  15. user_id: 1, # optional
  16. object: 'Ticket',
  17. o_id: 123,
  18. )
  19. =end
  20. def self.maybe_add(data)
  21. record = find_or_initialize_by(
  22. caller_id: data[:caller_id],
  23. level: data[:level],
  24. object: data[:object],
  25. o_id: data[:o_id],
  26. user_id: data[:user_id],
  27. )
  28. return record if !record.new_record?
  29. record.comment = data[:comment]
  30. record.save!
  31. end
  32. =begin
  33. get items (users) for a certain caller ID
  34. caller_id_records = Cti::CallerId.lookup('49123456789')
  35. returns
  36. [record1, record2, ...]
  37. =end
  38. def self.lookup(caller_id)
  39. lookup_ids =
  40. ['known', 'maybe', nil].lazy.map do |level|
  41. Cti::CallerId.select('MAX(id) as caller_id')
  42. .where({ caller_id: caller_id, level: level }.compact)
  43. .group(:user_id)
  44. .order(Arel.sql('caller_id DESC')) # not used as `caller_id: :desc` because is needed for `as caller_id`
  45. .limit(20)
  46. .map(&:caller_id)
  47. end.find(&:present?)
  48. Cti::CallerId.where(id: lookup_ids).order(id: :desc).to_a
  49. end
  50. =begin
  51. Cti::CallerId.build(ticket)
  52. =end
  53. def self.build(record)
  54. map = config
  55. level = nil
  56. model = nil
  57. map.each do |item|
  58. next if item[:model] != record.class
  59. level = item[:level]
  60. model = item[:model]
  61. end
  62. return if !level || !model
  63. build_item(record, model, level)
  64. end
  65. =begin
  66. Cti::CallerId.build_item(record, model, level)
  67. =end
  68. def self.build_item(record, model, level)
  69. # use first customer article
  70. if model == Ticket
  71. article = record.articles.first
  72. return if !article
  73. return if article.sender.name != 'Customer'
  74. record = article
  75. end
  76. # set user id
  77. user_id = record[:created_by_id]
  78. if model == User
  79. if record.destroyed?
  80. Cti::CallerId.where(user_id: user_id).destroy_all
  81. return
  82. end
  83. user_id = record.id
  84. end
  85. return if !user_id
  86. # get caller IDs
  87. caller_ids = []
  88. attributes = record.attributes
  89. attributes.each_value do |value|
  90. next if value.class != String
  91. next if value.blank?
  92. local_caller_ids = Cti::CallerId.extract_numbers(value)
  93. next if local_caller_ids.blank?
  94. caller_ids.concat(local_caller_ids)
  95. end
  96. # search for caller IDs to keep
  97. caller_ids_to_add = []
  98. existing_record_ids = Cti::CallerId.where(object: model.to_s, o_id: record.id).pluck(:id)
  99. caller_ids.uniq.each do |caller_id|
  100. existing_record_id = Cti::CallerId.where(
  101. object: model.to_s,
  102. o_id: record.id,
  103. caller_id: caller_id,
  104. level: level,
  105. user_id: user_id,
  106. ).pluck(:id)
  107. if existing_record_id[0]
  108. existing_record_ids.delete(existing_record_id[0])
  109. next
  110. end
  111. caller_ids_to_add.push caller_id
  112. end
  113. # delete not longer existing caller IDs
  114. existing_record_ids.each do |record_id|
  115. Cti::CallerId.destroy(record_id)
  116. end
  117. # create new caller IDs
  118. caller_ids_to_add.each do |caller_id|
  119. Cti::CallerId.maybe_add(
  120. caller_id: caller_id,
  121. level: level,
  122. object: model.to_s,
  123. o_id: record.id,
  124. user_id: user_id,
  125. )
  126. end
  127. true
  128. end
  129. =begin
  130. Cti::CallerId.rebuild
  131. =end
  132. def self.rebuild
  133. transaction do
  134. delete_all
  135. config.each do |item|
  136. level = item[:level]
  137. model = item[:model]
  138. item[:model].find_each(batch_size: 500) do |record|
  139. build_item(record, model, level)
  140. end
  141. end
  142. end
  143. end
  144. =begin
  145. Cti::CallerId.config
  146. returns
  147. [
  148. {
  149. model: User,
  150. level: 'known',
  151. },
  152. {
  153. model: Ticket,
  154. level: 'maybe',
  155. },
  156. ]
  157. =end
  158. def self.config
  159. [
  160. {
  161. model: User,
  162. level: 'known',
  163. },
  164. {
  165. model: Ticket,
  166. level: 'maybe',
  167. },
  168. ]
  169. end
  170. =begin
  171. caller_ids = Cti::CallerId.extract_numbers('...')
  172. returns
  173. ['49123456789', '49987654321']
  174. =end
  175. def self.extract_numbers(text)
  176. # see specs for example
  177. return [] if !text.is_a?(String)
  178. text.scan(%r{([\d\s\-(|)]{6,26})}).map do |match|
  179. normalize_number(match[0])
  180. end
  181. end
  182. def self.normalize_number(number)
  183. number = number.gsub(%r{[\s-]}, '')
  184. number.gsub!(%r{^(00)?(\+?\d\d)\(0?(\d*)\)}, '\\1\\2\\3')
  185. number.gsub!(%r{\D}, '')
  186. case number
  187. when %r{^00}
  188. number[2..]
  189. when %r{^0}
  190. DEFAULT_COUNTRY_ID + number[1..]
  191. else
  192. number
  193. end
  194. end
  195. =begin
  196. from_comment, preferences = Cti::CallerId.get_comment_preferences('00491710000000', 'from')
  197. returns
  198. [
  199. "Bob Smith",
  200. {
  201. "from"=>[
  202. {
  203. "id"=>1961634,
  204. "caller_id"=>"491710000000",
  205. "comment"=>nil,
  206. "level"=>"known",
  207. "object"=>"User",
  208. "o_id"=>3,
  209. "user_id"=>3,
  210. "preferences"=>nil,
  211. "created_at"=>Mon, 24 Sep 2018 15:19:48 UTC +00:00,
  212. "updated_at"=>Mon, 24 Sep 2018 15:19:48 UTC +00:00,
  213. }
  214. ]
  215. }
  216. ]
  217. =end
  218. def self.get_comment_preferences(caller_id, direction)
  219. from_comment_known = ''
  220. from_comment_maybe = ''
  221. preferences_known = {}
  222. preferences_known[direction] = []
  223. preferences_maybe = {}
  224. preferences_maybe[direction] = []
  225. lookup(extract_numbers(caller_id)).each do |record|
  226. if record.level == 'known'
  227. preferences_known[direction].push record.attributes
  228. else
  229. preferences_maybe[direction].push record.attributes
  230. end
  231. comment = ''
  232. if record.user_id
  233. user = User.lookup(id: record.user_id)
  234. if user
  235. comment += user.fullname
  236. end
  237. elsif record.comment.present?
  238. comment += record.comment
  239. end
  240. if record.level == 'known'
  241. if from_comment_known.present?
  242. from_comment_known += ','
  243. end
  244. from_comment_known += comment
  245. else
  246. if from_comment_maybe.present?
  247. from_comment_maybe += ','
  248. end
  249. from_comment_maybe += comment
  250. end
  251. end
  252. return [from_comment_known, preferences_known] if from_comment_known.present?
  253. return ["maybe #{from_comment_maybe}", preferences_maybe] if from_comment_maybe.present?
  254. nil
  255. end
  256. =begin
  257. return users by caller_id
  258. [user1, user2] = Cti::CallerId.known_agents_by_number('491234567')
  259. =end
  260. def self.known_agents_by_number(number)
  261. users = []
  262. caller_ids = Cti::CallerId.extract_numbers(number)
  263. caller_id_records = Cti::CallerId.lookup(caller_ids)
  264. caller_id_records.each do |caller_id_record|
  265. next if caller_id_record.level != 'known'
  266. user = User.find_by(id: caller_id_record.user_id)
  267. next if !user
  268. next if !user.permissions?('cti.agent')
  269. users.push user
  270. end
  271. users
  272. end
  273. def update_cti_logs
  274. return if object != 'User'
  275. UpdateCtiLogsByCallerJob.perform_later(caller_id)
  276. end
  277. def update_cti_logs_with_fg_optimization
  278. return if Setting.get('import_mode')
  279. return if object != 'User'
  280. return if level != 'known'
  281. UpdateCtiLogsByCallerJob.perform_now(caller_id, limit: 20)
  282. UpdateCtiLogsByCallerJob.perform_later(caller_id, limit: 40, offset: 20)
  283. end
  284. end
  285. end