log.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. module Cti
  2. class Log < ApplicationModel
  3. include HasSearchIndexBackend
  4. self.table_name = 'cti_logs'
  5. store :preferences
  6. validates :state, format: { with: /\A(newCall|answer|hangup)\z/,  message: 'newCall|answer|hangup is allowed' }
  7. after_commit :push_caller_list_update
  8. =begin
  9. Cti::Log.create!(
  10. direction: 'in',
  11. from: '007',
  12. from_comment: '',
  13. to: '008',
  14. to_comment: 'BBB',
  15. call_id: '1',
  16. comment: '',
  17. state: 'newCall',
  18. done: true,
  19. )
  20. Cti::Log.create!(
  21. direction: 'in',
  22. from: '007',
  23. from_comment: '',
  24. to: '008',
  25. to_comment: '',
  26. call_id: '2',
  27. comment: '',
  28. state: 'answer',
  29. done: true,
  30. )
  31. Cti::Log.create!(
  32. direction: 'in',
  33. from: '009',
  34. from_comment: '',
  35. to: '010',
  36. to_comment: '',
  37. call_id: '3',
  38. comment: '',
  39. state: 'hangup',
  40. done: true,
  41. )
  42. example data, can be used for demo
  43. Cti::Log.create!(
  44. direction: 'in',
  45. from: '4930609854180',
  46. from_comment: 'Franz Bauer',
  47. to: '4930609811111',
  48. to_comment: 'Bob Smith',
  49. call_id: '435452113',
  50. comment: '',
  51. state: 'newCall',
  52. done: false,
  53. preferences: {
  54. from: [
  55. {
  56. caller_id: '4930726128135',
  57. comment: nil,
  58. level: 'known',
  59. object: 'User',
  60. o_id: 2,
  61. user_id: 2,
  62. },
  63. {
  64. caller_id: '4930726128135',
  65. comment: nil,
  66. level: 'maybe',
  67. object: 'User',
  68. o_id: 2,
  69. user_id: 3,
  70. },
  71. ]
  72. },
  73. created_at: Time.zone.now,
  74. )
  75. Cti::Log.create!(
  76. direction: 'out',
  77. from: '4930609854180',
  78. from_comment: 'Franz Bauer',
  79. to: '4930609811111',
  80. to_comment: 'Bob Smith',
  81. call_id: rand(999_999_999),
  82. comment: '',
  83. state: 'newCall',
  84. done: true,
  85. preferences: {
  86. to: [
  87. {
  88. caller_id: '4930726128135',
  89. comment: nil,
  90. level: 'known',
  91. object: 'User',
  92. o_id: 2,
  93. user_id: 2,
  94. }
  95. ]
  96. },
  97. created_at: Time.zone.now - 20.seconds,
  98. )
  99. Cti::Log.create!(
  100. direction: 'in',
  101. from: '4930609854180',
  102. from_comment: 'Franz Bauer',
  103. to: '4930609811111',
  104. to_comment: 'Bob Smith',
  105. call_id: rand(999_999_999),
  106. comment: '',
  107. state: 'answer',
  108. done: true,
  109. preferences: {
  110. from: [
  111. {
  112. caller_id: '4930726128135',
  113. comment: nil,
  114. level: 'known',
  115. object: 'User',
  116. o_id: 2,
  117. user_id: 2,
  118. }
  119. ]
  120. },
  121. initialized_at: Time.zone.now - 20.seconds,
  122. start_at: Time.zone.now - 30.seconds,
  123. duration_waiting_time: 20,
  124. created_at: Time.zone.now - 20.seconds,
  125. )
  126. Cti::Log.create!(
  127. direction: 'in',
  128. from: '4930609854180',
  129. from_comment: 'Franz Bauer',
  130. to: '4930609811111',
  131. to_comment: 'Bob Smith',
  132. call_id: rand(999_999_999),
  133. comment: '',
  134. state: 'hangup',
  135. comment: 'normalClearing',
  136. done: false,
  137. preferences: {
  138. from: [
  139. {
  140. caller_id: '4930726128135',
  141. comment: nil,
  142. level: 'known',
  143. object: 'User',
  144. o_id: 2,
  145. user_id: 2,
  146. }
  147. ]
  148. },
  149. initialized_at: Time.zone.now - 80.seconds,
  150. start_at: Time.zone.now - 45.seconds,
  151. end_at: Time.zone.now,
  152. duration_waiting_time: 35,
  153. duration_talking_time: 45,
  154. created_at: Time.zone.now - 80.seconds,
  155. )
  156. Cti::Log.create!(
  157. direction: 'in',
  158. from: '4930609854180',
  159. from_comment: 'Franz Bauer',
  160. to: '4930609811111',
  161. to_comment: 'Bob Smith',
  162. call_id: rand(999_999_999),
  163. comment: '',
  164. state: 'hangup',
  165. done: true,
  166. start_at: Time.zone.now - 15.seconds,
  167. end_at: Time.zone.now,
  168. preferences: {
  169. from: [
  170. {
  171. caller_id: '4930726128135',
  172. comment: nil,
  173. level: 'known',
  174. object: 'User',
  175. o_id: 2,
  176. user_id: 2,
  177. }
  178. ]
  179. },
  180. initialized_at: Time.zone.now - 5.minutes,
  181. start_at: Time.zone.now - 3.minutes,
  182. end_at: Time.zone.now - 20.seconds,
  183. duration_waiting_time: 120,
  184. duration_talking_time: 160,
  185. created_at: Time.zone.now - 5.minutes,
  186. )
  187. Cti::Log.create!(
  188. direction: 'in',
  189. from: '4930609854180',
  190. from_comment: 'Franz Bauer',
  191. to: '4930609811111',
  192. to_comment: '',
  193. call_id: rand(999_999_999),
  194. comment: '',
  195. state: 'hangup',
  196. done: true,
  197. start_at: Time.zone.now - 15.seconds,
  198. end_at: Time.zone.now,
  199. preferences: {
  200. from: [
  201. {
  202. caller_id: '4930726128135',
  203. comment: nil,
  204. level: 'known',
  205. object: 'User',
  206. o_id: 2,
  207. user_id: 2,
  208. }
  209. ]
  210. },
  211. initialized_at: Time.zone.now - 60.minutes,
  212. start_at: Time.zone.now - 59.minutes,
  213. end_at: Time.zone.now - 2.minutes,
  214. duration_waiting_time: 60,
  215. duration_talking_time: 3420,
  216. created_at: Time.zone.now - 60.minutes,
  217. )
  218. Cti::Log.create!(
  219. direction: 'in',
  220. from: '4930609854180',
  221. from_comment: 'Franz Bauer',
  222. to: '4930609811111',
  223. to_comment: 'Bob Smith',
  224. call_id: rand(999_999_999),
  225. comment: '',
  226. state: 'hangup',
  227. done: true,
  228. start_at: Time.zone.now - 15.seconds,
  229. end_at: Time.zone.now,
  230. preferences: {
  231. from: [
  232. {
  233. caller_id: '4930726128135',
  234. comment: nil,
  235. level: 'maybe',
  236. object: 'User',
  237. o_id: 2,
  238. user_id: 2,
  239. }
  240. ]
  241. },
  242. initialized_at: Time.zone.now - 240.minutes,
  243. start_at: Time.zone.now - 235.minutes,
  244. end_at: Time.zone.now - 222.minutes,
  245. duration_waiting_time: 300,
  246. duration_talking_time: 1080,
  247. created_at: Time.zone.now - 240.minutes,
  248. )
  249. Cti::Log.create!(
  250. direction: 'in',
  251. from: '4930609854180',
  252. to: '4930609811112',
  253. call_id: rand(999_999_999),
  254. comment: '',
  255. state: 'hangup',
  256. done: true,
  257. start_at: Time.zone.now - 20.seconds,
  258. end_at: Time.zone.now,
  259. preferences: {},
  260. initialized_at: Time.zone.now - 1440.minutes,
  261. start_at: Time.zone.now - 1430.minutes,
  262. end_at: Time.zone.now - 1429.minutes,
  263. duration_waiting_time: 600,
  264. duration_talking_time: 660,
  265. created_at: Time.zone.now - 1440.minutes,
  266. )
  267. =end
  268. =begin
  269. Cti::Log.log(current_user)
  270. returns
  271. {
  272. list: [log_record1, log_record2, log_record3],
  273. assets: {...},
  274. }
  275. =end
  276. def self.log(current_user)
  277. list = Cti::Log.log_records(current_user)
  278. # add assets
  279. assets = list.map(&:preferences)
  280. .map { |p| p.slice(:from, :to) }
  281. .map(&:values).flatten
  282. .map { |caller_id| caller_id[:user_id] }.compact
  283. .map { |user_id| User.lookup(id: user_id) }.compact
  284. .each.with_object({}) { |user, a| user.assets(a) }
  285. {
  286. list: list,
  287. assets: assets,
  288. }
  289. end
  290. =begin
  291. Cti::Log.log_records(current_user)
  292. returns
  293. [log_record1, log_record2, log_record3]
  294. =end
  295. def self.log_records(current_user)
  296. cti_config = Setting.get('cti_config')
  297. if cti_config[:notify_map].present?
  298. return Cti::Log.where(queue: queues_of_user(current_user, cti_config)).order(created_at: :desc).limit(60)
  299. end
  300. Cti::Log.order(created_at: :desc).limit(60)
  301. end
  302. =begin
  303. processes a incoming event
  304. Cti::Log.process(
  305. cause: '',
  306. event: 'newCall',
  307. user: 'user 1',
  308. from: '4912347114711',
  309. to: '4930600000000',
  310. callId: '43545211', # or call_id
  311. direction: 'in',
  312. queue: 'helpdesk', # optional
  313. )
  314. =end
  315. def self.process(params)
  316. cause = params['cause']
  317. event = params['event']
  318. user = params['user']
  319. queue = params['queue']
  320. call_id = params['callId'] || params['call_id']
  321. if user.class == Array
  322. user = user.join(', ')
  323. end
  324. from_comment = nil
  325. to_comment = nil
  326. preferences = nil
  327. done = true
  328. if params['direction'] == 'in'
  329. if user.present?
  330. to_comment = user
  331. elsif queue.present?
  332. to_comment = queue
  333. end
  334. from_comment, preferences = CallerId.get_comment_preferences(params['from'], 'from')
  335. if queue.blank?
  336. queue = params['to']
  337. end
  338. else
  339. from_comment = user
  340. to_comment, preferences = CallerId.get_comment_preferences(params['to'], 'to')
  341. if queue.blank?
  342. queue = params['from']
  343. end
  344. end
  345. log = find_by(call_id: call_id)
  346. case event
  347. when 'newCall'
  348. if params['direction'] == 'in'
  349. done = false
  350. end
  351. raise "call_id #{call_id} already exists!" if log
  352. log = create(
  353. direction: params['direction'],
  354. from: params['from'],
  355. from_comment: from_comment,
  356. to: params['to'],
  357. to_comment: to_comment,
  358. call_id: call_id,
  359. comment: cause,
  360. queue: queue,
  361. state: event,
  362. initialized_at: Time.zone.now,
  363. preferences: preferences,
  364. done: done,
  365. )
  366. when 'answer'
  367. raise "No such call_id #{call_id}" if !log
  368. return if log.state == 'hangup' # call is already hangup, ignore answer
  369. log.with_lock do
  370. log.state = 'answer'
  371. log.start_at = Time.zone.now
  372. log.duration_waiting_time = log.start_at.to_i - log.initialized_at.to_i
  373. if user
  374. log.to_comment = user
  375. end
  376. log.done = true
  377. log.comment = cause
  378. log.save
  379. end
  380. when 'hangup'
  381. raise "No such call_id #{call_id}" if !log
  382. log.with_lock do
  383. log.done = done
  384. if params['direction'] == 'in'
  385. if log.state == 'newCall' && cause != 'forwarded'
  386. log.done = false
  387. elsif log.to_comment == 'voicemail'
  388. log.done = false
  389. end
  390. end
  391. log.state = 'hangup'
  392. log.end_at = Time.zone.now
  393. if log.start_at
  394. log.duration_talking_time = log.end_at.to_i - log.start_at.to_i
  395. elsif !log.duration_waiting_time && log.initialized_at
  396. log.duration_waiting_time = log.end_at.to_i - log.initialized_at.to_i
  397. end
  398. log.comment = cause
  399. log.save
  400. end
  401. else
  402. raise ArgumentError, "Unknown event #{event.inspect}"
  403. end
  404. log
  405. end
  406. def self.push_caller_list_update?(record)
  407. list_ids = Cti::Log.order(created_at: :desc).limit(60).pluck(:id)
  408. return true if list_ids.include?(record.id)
  409. false
  410. end
  411. def push_caller_list_update
  412. return false if !Cti::Log.push_caller_list_update?(self)
  413. # send notify on create/update/delete
  414. users = User.with_permissions('cti.agent')
  415. users.each do |user|
  416. Sessions.send_to(
  417. user.id,
  418. {
  419. event: 'cti_list_push',
  420. },
  421. )
  422. end
  423. true
  424. end
  425. =begin
  426. cleanup caller logs
  427. Cti::Log.cleanup
  428. optional you can put the max oldest chat entries as argument
  429. Cti::Log.cleanup(12.months)
  430. =end
  431. def self.cleanup(diff = 12.months)
  432. Cti::Log.where('created_at < ?', Time.zone.now - diff).delete_all
  433. true
  434. end
  435. # adds virtual attributes when rendering #to_json
  436. # see http://api.rubyonrails.org/classes/ActiveModel/Serialization.html
  437. def attributes
  438. virtual_attributes = {
  439. 'from_pretty' => from_pretty,
  440. 'to_pretty' => to_pretty,
  441. }
  442. super.merge(virtual_attributes)
  443. end
  444. def from_pretty
  445. parsed = TelephoneNumber.parse(from&.sub(/^\+?/, '+'))
  446. parsed.send(parsed.valid? ? :international_number : :original_number)
  447. end
  448. def to_pretty
  449. parsed = TelephoneNumber.parse(to&.sub(/^\+?/, '+'))
  450. parsed.send(parsed.valid? ? :international_number : :original_number)
  451. end
  452. =begin
  453. returns queues of user
  454. ['queue1', 'queue2'] = Cti::Log.queues_of_user(User.find(123), config)
  455. =end
  456. def self.queues_of_user(user, config)
  457. queues = []
  458. config[:notify_map]&.each do |row|
  459. next if row[:user_ids].blank?
  460. next if !row[:user_ids].include?(user.id.to_s) && !row[:user_ids].include?(user.id)
  461. queues.push row[:queue]
  462. end
  463. if user.phone.present?
  464. caller_ids = Cti::CallerId.extract_numbers(user.phone)
  465. queues = queues.concat(caller_ids)
  466. end
  467. queues
  468. end
  469. =begin
  470. return best customer id of caller log
  471. log = Cti::Log.find(123)
  472. customer_id = log.best_customer_id_of_log_entry
  473. =end
  474. def best_customer_id_of_log_entry
  475. customer_id = nil
  476. if preferences[:from].present?
  477. preferences[:from].each do |entry|
  478. if customer_id.blank?
  479. customer_id = entry[:user_id]
  480. end
  481. next if entry[:level] != 'known'
  482. customer_id = entry[:user_id]
  483. break
  484. end
  485. end
  486. customer_id
  487. end
  488. end
  489. end