caller_id.rb 8.1 KB

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