otrs.rb 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463
  1. require 'base64'
  2. module Import
  3. end
  4. module Import::OTRS
  5. =begin
  6. result = request_json(Subaction: 'List', 1)
  7. return
  8. { some json structure }
  9. result = request_json(Subaction: 'List')
  10. return
  11. "some data string"
  12. =end
  13. def self.request_json(data, data_only = false)
  14. response = post(data)
  15. if !response
  16. raise "Can't connect to Zammad Migrator"
  17. end
  18. if !response.success?
  19. raise "Can't connect to Zammad Migrator"
  20. end
  21. result = json(response)
  22. if !result
  23. raise 'Invalid response'
  24. end
  25. if data_only
  26. result['Result']
  27. else
  28. result
  29. end
  30. end
  31. =begin
  32. start get request to backend, add auth data automatically
  33. result = request('Subaction=List')
  34. return
  35. "some data string"
  36. =end
  37. def self.request(part)
  38. url = Setting.get('import_otrs_endpoint') + part + ';Key=' + Setting.get('import_otrs_endpoint_key')
  39. log 'GET: ' + url
  40. response = UserAgent.get(
  41. url,
  42. {},
  43. {
  44. open_timeout: 10,
  45. read_timeout: 60,
  46. total_timeout: 180,
  47. user: Setting.get('import_otrs_user'),
  48. password: Setting.get('import_otrs_password'),
  49. },
  50. )
  51. if !response.success?
  52. log "ERROR: #{response.error}"
  53. return
  54. end
  55. response
  56. end
  57. =begin
  58. start post request to backend, add auth data automatically
  59. result = request('Subaction=List')
  60. return
  61. "some data string"
  62. =end
  63. def self.post(data, url = nil)
  64. if !url
  65. url = Setting.get('import_otrs_endpoint')
  66. data['Action'] = 'ZammadMigrator'
  67. end
  68. data['Key'] = Setting.get('import_otrs_endpoint_key')
  69. log 'POST: ' + url
  70. log 'PARAMS: ' + data.inspect
  71. open_timeout = 10
  72. read_timeout = 120
  73. total_timeout = 360
  74. if data.empty?
  75. open_timeout = 6
  76. read_timeout = 20
  77. total_timeout = 120
  78. end
  79. response = UserAgent.post(
  80. url,
  81. data,
  82. {
  83. open_timeout: open_timeout,
  84. read_timeout: read_timeout,
  85. total_timeout: total_timeout,
  86. user: Setting.get('import_otrs_user'),
  87. password: Setting.get('import_otrs_password'),
  88. },
  89. )
  90. if !response.success?
  91. log "ERROR: #{response.error}"
  92. return
  93. end
  94. response
  95. end
  96. =begin
  97. start post request to backend, add auth data automatically
  98. result = json('some response string')
  99. return
  100. {}
  101. =end
  102. def self.json(response)
  103. data = Encode.conv('utf8', response.body.to_s)
  104. JSON.parse(data)
  105. end
  106. =begin
  107. start auth on OTRS - just for experimental reasons
  108. result = auth(username, password)
  109. return
  110. { ..user structure.. }
  111. =end
  112. def self.auth(username, password)
  113. url = Setting.get('import_otrs_endpoint')
  114. url.gsub!('ZammadMigrator', 'ZammadSSO')
  115. response = post( { Action: 'ZammadSSO', Subaction: 'Auth', User: username, Pw: password }, url )
  116. return if !response
  117. return if !response.success?
  118. result = json(response)
  119. result
  120. end
  121. =begin
  122. request session data - just for experimental reasons
  123. result = session(session_id)
  124. return
  125. { ..session structure.. }
  126. =end
  127. def self.session(session_id)
  128. url = Setting.get('import_otrs_endpoint')
  129. url.gsub!('ZammadMigrator', 'ZammadSSO')
  130. response = post( { Action: 'ZammadSSO', Subaction: 'SessionCheck', SessionID: session_id }, url )
  131. return if !response
  132. return if !response.success?
  133. result = json(response)
  134. result
  135. end
  136. =begin
  137. load objects from otrs
  138. result = load('SysConfig')
  139. return
  140. [
  141. { ..object1.. },
  142. { ..object2.. },
  143. { ..object3.. },
  144. ]
  145. =end
  146. def self.load( object, limit = '', offset = '', diff = 0 )
  147. request_json( { Subaction: 'Export', Object: object, Limit: limit, Offset: offset, Diff: diff }, 1 )
  148. end
  149. =begin
  150. start get request to backend to check connection
  151. result = connection_test
  152. return
  153. true | false
  154. =end
  155. def self.connection_test
  156. request_json({})
  157. end
  158. =begin
  159. get object statistic from remote server ans save it in cache
  160. result = statistic('Subaction=List')
  161. return
  162. {
  163. 'Ticket' => 1234,
  164. 'User' => 123,
  165. 'SomeObject' => 999,
  166. }
  167. =end
  168. def self.statistic
  169. # check cache
  170. cache = Cache.get('import_otrs_stats')
  171. if cache
  172. return cache
  173. end
  174. # retrive statistic
  175. statistic = request_json( { Subaction: 'List' }, 1)
  176. if statistic
  177. Cache.write('import_otrs_stats', statistic)
  178. end
  179. statistic
  180. end
  181. =begin
  182. return current import state
  183. result = current_state
  184. return
  185. {
  186. Ticket: {
  187. total: 1234,
  188. done: 13,
  189. },
  190. Base: {
  191. total: 1234,
  192. done: 13,
  193. },
  194. }
  195. =end
  196. def self.current_state
  197. data = statistic
  198. base = Group.count + Ticket::State.count + Ticket::Priority.count
  199. base_total = data['Queue'] + data['State'] + data['Priority']
  200. user = User.count
  201. user_total = data['User'] + data['CustomerUser']
  202. data = {
  203. Base: {
  204. done: base,
  205. total: base_total || 0,
  206. },
  207. User: {
  208. done: user,
  209. total: user_total || 0,
  210. },
  211. Ticket: {
  212. done: Ticket.count,
  213. total: data['Ticket'] || 0,
  214. },
  215. }
  216. data
  217. end
  218. #
  219. # start import
  220. #
  221. # Import::OTRS.start
  222. #
  223. def self.start
  224. log 'Start import...'
  225. # check if system is in import mode
  226. if !Setting.get('import_mode')
  227. raise 'System is not in import mode!'
  228. end
  229. result = request_json({})
  230. if !result['Success']
  231. raise 'API key not valid!'
  232. end
  233. # set settings
  234. settings = load('SysConfig')
  235. setting(settings)
  236. # dynamic fields
  237. dynamic_fields = load('DynamicField')
  238. #settings(dynamic_fields, settings)
  239. # email accounts
  240. #accounts = load('PostMasterAccount')
  241. #account(accounts)
  242. # email filter
  243. #filters = load('PostMasterFilter')
  244. #filter(filters)
  245. # create states
  246. states = load('State')
  247. ActiveRecord::Base.transaction do
  248. state(states)
  249. end
  250. # create priorities
  251. priorities = load('Priority')
  252. ActiveRecord::Base.transaction do
  253. priority(priorities)
  254. end
  255. # create groups
  256. queues = load('Queue')
  257. ActiveRecord::Base.transaction do
  258. ticket_group(queues)
  259. end
  260. # get agents groups
  261. groups = load('Group')
  262. # get agents roles
  263. roles = load('Role')
  264. # create agents
  265. users = load('User')
  266. ActiveRecord::Base.transaction do
  267. user(users, groups, roles, queues)
  268. end
  269. # create organizations
  270. organizations = load('Customer')
  271. ActiveRecord::Base.transaction do
  272. organization(organizations)
  273. end
  274. # create customers
  275. count = 0
  276. steps = 50
  277. run = true
  278. while run
  279. count += steps
  280. records = load('CustomerUser', steps, count - steps)
  281. if !records || !records[0]
  282. log 'all customers imported.'
  283. run = false
  284. next
  285. end
  286. customer(records, organizations)
  287. end
  288. Thread.abort_on_exception = true
  289. thread_count = 8
  290. threads = {}
  291. steps = 20
  292. (1..thread_count).each { |thread|
  293. threads[thread] = Thread.new {
  294. log "Started import thread# #{thread} ..."
  295. Thread.current[:thread_no] = thread
  296. Thread.current[:loop_count] = 0
  297. loop do
  298. # get the offset for the current thread and loop count
  299. thread_offset_base = (Thread.current[:thread_no] - 1) * steps
  300. thread_step = thread_count * steps
  301. offset = Thread.current[:loop_count] * thread_step + thread_offset_base
  302. log "loading... thread# #{thread} ..."
  303. records = load( 'Ticket', steps, offset)
  304. if !records || !records[0]
  305. log "... thread# #{thread}, no more work."
  306. break
  307. end
  308. _ticket_result(records, thread)
  309. Thread.current[:loop_count] += 1
  310. end
  311. ActiveRecord::Base.connection.close
  312. }
  313. }
  314. (1..thread_count).each {|thread|
  315. threads[thread].join
  316. }
  317. true
  318. end
  319. =begin
  320. start import in background
  321. Import::OTRS.start_bg
  322. =end
  323. def self.start_bg
  324. Setting.reload
  325. Import::OTRS.connection_test
  326. # start thread to observe current state
  327. status_update_thread = Thread.new {
  328. loop do
  329. result = {
  330. data: current_state,
  331. result: 'in_progress',
  332. }
  333. Cache.write('import:state', result, expires_in: 10.minutes)
  334. sleep 8
  335. end
  336. }
  337. sleep 2
  338. # start import data
  339. begin
  340. Import::OTRS.start
  341. rescue => e
  342. status_update_thread.exit
  343. status_update_thread.join
  344. Rails.logger.error e.message
  345. Rails.logger.error e.backtrace.inspect
  346. result = {
  347. message: e.message,
  348. result: 'error',
  349. }
  350. Cache.write('import:state', result, expires_in: 10.hours)
  351. return false
  352. end
  353. sleep 16 # wait until new finished import state is on client
  354. status_update_thread.exit
  355. status_update_thread.join
  356. result = {
  357. result: 'import_done',
  358. }
  359. Cache.write('import:state', result, expires_in: 10.hours)
  360. Setting.set('system_init_done', true)
  361. Setting.set('import_mode', false)
  362. end
  363. =begin
  364. get import state from background process
  365. result = Import::OTRS.status_bg
  366. =end
  367. def self.status_bg
  368. state = Cache.get('import:state')
  369. return state if state
  370. {
  371. message: 'not running',
  372. }
  373. end
  374. def self.diff_worker
  375. return if !Setting.get('import_mode')
  376. return if Setting.get('import_otrs_endpoint') == 'http://otrs_host/otrs'
  377. diff
  378. end
  379. def self.diff
  380. log 'Start diff...'
  381. # check if system is in import mode
  382. if !Setting.get('import_mode')
  383. raise 'System is not in import mode!'
  384. end
  385. # create states
  386. states = load('State')
  387. state(states)
  388. # create priorities
  389. priorities = load('Priority')
  390. priority(priorities)
  391. # create groups
  392. queues = load('Queue')
  393. ticket_group(queues)
  394. # get agents groups
  395. groups = load('Group')
  396. # get agents roles
  397. roles = load('Role')
  398. # create agents
  399. users = load('User')
  400. user(users, groups, roles, queues)
  401. # create organizations
  402. organizations = load('Customer')
  403. organization(organizations)
  404. # get changed tickets
  405. ticket_diff
  406. end
  407. def self.ticket_diff
  408. count = 0
  409. run = true
  410. steps = 20
  411. while run
  412. count += steps
  413. log 'loading... diff ...'
  414. records = load( 'Ticket', steps, count - steps, 1 )
  415. if !records || !records[0]
  416. log '... no more work.'
  417. run = false
  418. next
  419. end
  420. _ticket_result(records)
  421. end
  422. end
  423. def self._ticket_result(result, _thread = '-')
  424. map = {
  425. Ticket: {
  426. Changed: :updated_at,
  427. Created: :created_at,
  428. CreateBy: :created_by_id,
  429. TicketNumber: :number,
  430. QueueID: :group_id,
  431. StateID: :state_id,
  432. PriorityID: :priority_id,
  433. Owner: :owner,
  434. CustomerUserID: :customer,
  435. Title: :title,
  436. TicketID: :id,
  437. FirstResponse: :first_response,
  438. #FirstResponseTimeDestinationDate: :first_response_escal_date,
  439. #FirstResponseInMin: :first_response_in_min,
  440. #FirstResponseDiffInMin: :first_response_diff_in_min,
  441. Closed: :close_time,
  442. #SoltutionTimeDestinationDate: :close_time_escal_date,
  443. #CloseTimeInMin: :close_time_in_min,
  444. #CloseTimeDiffInMin: :close_time_diff_in_min,
  445. },
  446. Article: {
  447. SenderType: :sender,
  448. ArticleType: :type,
  449. TicketID: :ticket_id,
  450. ArticleID: :id,
  451. Body: :body,
  452. From: :from,
  453. To: :to,
  454. Cc: :cc,
  455. Subject: :subject,
  456. InReplyTo: :in_reply_to,
  457. MessageID: :message_id,
  458. #ReplyTo: :reply_to,
  459. References: :references,
  460. Changed: :updated_at,
  461. Created: :created_at,
  462. ChangedBy: :updated_by_id,
  463. CreatedBy: :created_by_id,
  464. },
  465. }
  466. result.each {|record|
  467. # cleanup values
  468. _cleanup(record)
  469. _utf8_encode(record)
  470. ticket_new = {
  471. title: '',
  472. created_by_id: 1,
  473. updated_by_id: 1,
  474. }
  475. map[:Ticket].each { |key, value|
  476. next if !record.key?(key.to_s)
  477. ticket_new[value] = record[key.to_s]
  478. }
  479. # find owner
  480. if ticket_new[:owner]
  481. user = User.find_by(login: ticket_new[:owner].downcase)
  482. ticket_new[:owner_id] = if user
  483. user.id
  484. else
  485. 1
  486. end
  487. ticket_new.delete(:owner)
  488. end
  489. # find customer
  490. if ticket_new[:customer]
  491. user = User.lookup(login: ticket_new[:customer].downcase)
  492. ticket_new[:customer_id] = if user
  493. user.id
  494. else
  495. 1
  496. end
  497. ticket_new.delete(:customer)
  498. else
  499. ticket_new[:customer_id] = 1
  500. end
  501. # update or create ticket
  502. ticket_old = Ticket.find_by(id: ticket_new[:id])
  503. if ticket_old
  504. log "update Ticket.find(#{ticket_new[:id]})"
  505. ticket_old.update_attributes(ticket_new)
  506. else
  507. log "add Ticket.find(#{ticket_new[:id]})"
  508. begin
  509. ticket = Ticket.new(ticket_new)
  510. ticket.id = ticket_new[:id]
  511. ticket.save
  512. _reset_pk('tickets')
  513. rescue ActiveRecord::RecordNotUnique
  514. log "Ticket #{ticket_new[:id]} is handled by another thead, skipping."
  515. next
  516. end
  517. end
  518. # utf8 encode
  519. record['Articles'].each { |article|
  520. _utf8_encode(article)
  521. }
  522. # lookup customers to create first
  523. record['Articles'].each { |article|
  524. _article_based_customers(article)
  525. }
  526. record['Articles'].each do |article|
  527. retries = 3
  528. begin
  529. ActiveRecord::Base.transaction do
  530. # get article values
  531. article_new = {
  532. created_by_id: 1,
  533. updated_by_id: 1,
  534. }
  535. map[:Article].each { |key, value|
  536. next if !article.key?(key.to_s)
  537. article_new[value] = article[key.to_s]
  538. }
  539. if article_new[:sender] == 'customer'
  540. article_new[:sender_id] = Ticket::Article::Sender.lookup(name: 'Customer').id
  541. article_new.delete(:sender)
  542. end
  543. if article_new[:sender] == 'agent'
  544. article_new[:sender_id] = Ticket::Article::Sender.lookup(name: 'Agent').id
  545. article_new.delete(:sender)
  546. end
  547. if article_new[:sender] == 'system'
  548. article_new[:sender_id] = Ticket::Article::Sender.lookup(name: 'System').id
  549. article_new.delete(:sender)
  550. end
  551. if article_new[:type] == 'email-external'
  552. article_new[:type_id] = Ticket::Article::Type.lookup(name: 'email').id
  553. article_new[:internal] = false
  554. elsif article_new[:type] == 'email-internal'
  555. article_new[:type_id] = Ticket::Article::Type.lookup(name: 'email').id
  556. article_new[:internal] = true
  557. elsif article_new[:type] == 'note-external'
  558. article_new[:type_id] = Ticket::Article::Type.lookup(name: 'note').id
  559. article_new[:internal] = false
  560. elsif article_new[:type] == 'note-internal'
  561. article_new[:type_id] = Ticket::Article::Type.lookup(name: 'note').id
  562. article_new[:internal] = true
  563. elsif article_new[:type] == 'phone'
  564. article_new[:type_id] = Ticket::Article::Type.lookup(name: 'phone').id
  565. article_new[:internal] = false
  566. elsif article_new[:type] == 'webrequest'
  567. article_new[:type_id] = Ticket::Article::Type.lookup(name: 'web').id
  568. article_new[:internal] = false
  569. else
  570. article_new[:type_id] = 9
  571. end
  572. article_new.delete(:type)
  573. article_object = Ticket::Article.find_by(id: article_new[:id])
  574. # set state types
  575. if article_object
  576. log "update Ticket::Article.find(#{article_new[:id]})"
  577. article_object.update_attributes(article_new)
  578. else
  579. log "add Ticket::Article.find(#{article_new[:id]})"
  580. begin
  581. article_object = Ticket::Article.new(article_new)
  582. article_object.id = article_new[:id]
  583. article_object.save
  584. _reset_pk('ticket_articles')
  585. rescue ActiveRecord::RecordNotUnique
  586. log "Ticket #{ticket_new[:id]} (article #{article_new[:id]}) is handled by another thead, skipping."
  587. next
  588. end
  589. end
  590. next if !article['Attachments']
  591. next if article['Attachments'].empty?
  592. # TODO: refactor
  593. # check if there are attachments present
  594. if !article_object.attachments.empty?
  595. # skip attachments if count is equal
  596. next if article_object.attachments.count == article['Attachments'].count
  597. # if the count differs delete all so we
  598. # can have a fresh start
  599. article_object.attachments.each(&:delete)
  600. end
  601. # import article attachments
  602. article['Attachments'].each { |attachment|
  603. filename = Base64.decode64(attachment['Filename'])
  604. Store.add(
  605. object: 'Ticket::Article',
  606. o_id: article_object.id,
  607. filename: filename,
  608. data: Base64.decode64(attachment['Content']),
  609. preferences: {
  610. 'Mime-Type' => attachment['ContentType'],
  611. 'Content-ID' => attachment['ContentID'],
  612. 'content-alternative' => attachment['ContentAlternative'],
  613. },
  614. created_by_id: 1,
  615. )
  616. }
  617. end
  618. rescue ActiveRecord::RecordNotUnique => e
  619. log "Ticket #{ticket_new[:id]} - RecordNotUnique: #{e}"
  620. sleep rand 3
  621. retry if !(retries -= 1).zero?
  622. raise
  623. end
  624. end
  625. #puts "HS: #{record['History'].inspect}"
  626. record['History'].each { |history|
  627. begin
  628. if history['HistoryType'] == 'NewTicket'
  629. History.add(
  630. id: history['HistoryID'],
  631. o_id: history['TicketID'],
  632. history_type: 'created',
  633. history_object: 'Ticket',
  634. created_at: history['CreateTime'],
  635. created_by_id: history['CreateBy']
  636. )
  637. elsif history['HistoryType'] == 'StateUpdate'
  638. data = history['Name']
  639. # "%%new%%open%%"
  640. from = nil
  641. to = nil
  642. if data =~ /%%(.+?)%%(.+?)%%/
  643. from = $1
  644. to = $2
  645. state_from = Ticket::State.lookup(name: from)
  646. state_to = Ticket::State.lookup(name: to)
  647. if state_from
  648. from_id = state_from.id
  649. end
  650. if state_to
  651. to_id = state_to.id
  652. end
  653. end
  654. History.add(
  655. id: history['HistoryID'],
  656. o_id: history['TicketID'],
  657. history_type: 'updated',
  658. history_object: 'Ticket',
  659. history_attribute: 'state',
  660. value_from: from,
  661. id_from: from_id,
  662. value_to: to,
  663. id_to: to_id,
  664. created_at: history['CreateTime'],
  665. created_by_id: history['CreateBy']
  666. )
  667. elsif history['HistoryType'] == 'Move'
  668. data = history['Name']
  669. # "%%Queue1%%5%%Postmaster%%1"
  670. from = nil
  671. to = nil
  672. if data =~ /%%(.+?)%%(.+?)%%(.+?)%%(.+?)$/
  673. from = $1
  674. from_id = $2
  675. to = $3
  676. to_id = $4
  677. end
  678. History.add(
  679. id: history['HistoryID'],
  680. o_id: history['TicketID'],
  681. history_type: 'updated',
  682. history_object: 'Ticket',
  683. history_attribute: 'group',
  684. value_from: from,
  685. value_to: to,
  686. id_from: from_id,
  687. id_to: to_id,
  688. created_at: history['CreateTime'],
  689. created_by_id: history['CreateBy']
  690. )
  691. elsif history['HistoryType'] == 'PriorityUpdate'
  692. data = history['Name']
  693. # "%%3 normal%%3%%5 very high%%5"
  694. from = nil
  695. to = nil
  696. if data =~ /%%(.+?)%%(.+?)%%(.+?)%%(.+?)$/
  697. from = $1
  698. from_id = $2
  699. to = $3
  700. to_id = $4
  701. end
  702. History.add(
  703. id: history['HistoryID'],
  704. o_id: history['TicketID'],
  705. history_type: 'updated',
  706. history_object: 'Ticket',
  707. history_attribute: 'priority',
  708. value_from: from,
  709. value_to: to,
  710. id_from: from_id,
  711. id_to: to_id,
  712. created_at: history['CreateTime'],
  713. created_by_id: history['CreateBy']
  714. )
  715. elsif history['ArticleID'] && !history['ArticleID'].to_i.zero?
  716. History.add(
  717. id: history['HistoryID'],
  718. o_id: history['ArticleID'],
  719. history_type: 'created',
  720. history_object: 'Ticket::Article',
  721. related_o_id: history['TicketID'],
  722. related_history_object: 'Ticket',
  723. created_at: history['CreateTime'],
  724. created_by_id: history['CreateBy']
  725. )
  726. end
  727. rescue ActiveRecord::RecordNotUnique
  728. log "Ticket #{ticket_new[:id]} (history #{history['HistoryID']}) is handled by another thead, skipping."
  729. next
  730. end
  731. }
  732. }
  733. end
  734. # sync ticket states
  735. def self.state(records)
  736. map = {
  737. ChangeTime: :updated_at,
  738. CreateTime: :created_at,
  739. CreateBy: :created_by_id,
  740. ChangeBy: :updated_by_id,
  741. Name: :name,
  742. ID: :id,
  743. ValidID: :active,
  744. Comment: :note,
  745. }
  746. # rename states to handle not uniq issues
  747. Ticket::State.all.each {|state|
  748. state.name = state.name + '_tmp'
  749. state.save
  750. }
  751. records.each { |state|
  752. _set_valid(state)
  753. # get new attributes
  754. state_new = {
  755. created_by_id: 1,
  756. updated_by_id: 1,
  757. }
  758. map.each { |key, value|
  759. next if !state.key?(key.to_s)
  760. state_new[value] = state[key.to_s]
  761. }
  762. # check if state already exists
  763. state_old = Ticket::State.lookup(id: state_new[:id])
  764. # set state types
  765. if state['TypeName'] == 'pending auto'
  766. state['TypeName'] = 'pending action'
  767. end
  768. state_type = Ticket::StateType.lookup(name: state['TypeName'])
  769. state_new[:state_type_id] = state_type.id
  770. if state_old
  771. state_old.update_attributes(state_new)
  772. else
  773. state = Ticket::State.new(state_new)
  774. state.id = state_new[:id]
  775. state.save
  776. _reset_pk('ticket_states')
  777. end
  778. }
  779. end
  780. # sync ticket priorities
  781. def self.priority(records)
  782. map = {
  783. ChangeTime: :updated_at,
  784. CreateTime: :created_at,
  785. CreateBy: :created_by_id,
  786. ChangeBy: :updated_by_id,
  787. Name: :name,
  788. ID: :id,
  789. ValidID: :active,
  790. Comment: :note,
  791. }
  792. records.each { |priority|
  793. _set_valid(priority)
  794. # get new attributes
  795. priority_new = {
  796. created_by_id: 1,
  797. updated_by_id: 1,
  798. }
  799. map.each { |key, value|
  800. next if !priority.key?(key.to_s)
  801. priority_new[value] = priority[key.to_s]
  802. }
  803. # check if state already exists
  804. priority_old = Ticket::Priority.lookup(id: priority_new[:id])
  805. # set state types
  806. if priority_old
  807. priority_old.update_attributes(priority_new)
  808. else
  809. priority = Ticket::Priority.new(priority_new)
  810. priority.id = priority_new[:id]
  811. priority.save
  812. _reset_pk('ticket_priorities')
  813. end
  814. }
  815. end
  816. # sync ticket groups / queues
  817. def self.ticket_group(records)
  818. map = {
  819. ChangeTime: :updated_at,
  820. CreateTime: :created_at,
  821. CreateBy: :created_by_id,
  822. ChangeBy: :updated_by_id,
  823. Name: :name,
  824. QueueID: :id,
  825. ValidID: :active,
  826. Comment: :note,
  827. }
  828. records.each { |group|
  829. _set_valid(group)
  830. # get new attributes
  831. group_new = {
  832. created_by_id: 1,
  833. updated_by_id: 1,
  834. }
  835. map.each { |key, value|
  836. next if !group.key?(key.to_s)
  837. group_new[value] = group[key.to_s]
  838. }
  839. # check if state already exists
  840. group_old = Group.lookup(id: group_new[:id])
  841. # set state types
  842. if group_old
  843. group_old.update_attributes(group_new)
  844. else
  845. group = Group.new(group_new)
  846. group.id = group_new[:id]
  847. group.save
  848. _reset_pk('groups')
  849. end
  850. }
  851. end
  852. # sync agents
  853. def self.user(records, groups, roles, queues)
  854. map = {
  855. ChangeTime: :updated_at,
  856. CreateTime: :created_at,
  857. CreateBy: :created_by_id,
  858. ChangeBy: :updated_by_id,
  859. UserID: :id,
  860. ValidID: :active,
  861. Comment: :note,
  862. UserEmail: :email,
  863. UserFirstname: :firstname,
  864. UserLastname: :lastname,
  865. UserLogin: :login,
  866. UserPw: :password,
  867. }
  868. records.each { |user|
  869. _set_valid(user)
  870. # get roles
  871. role_ids = get_roles_ids(user, groups, roles, queues)
  872. # get groups
  873. group_ids = get_queue_ids(user, groups, roles, queues)
  874. # get new attributes
  875. user_new = {
  876. created_by_id: 1,
  877. updated_by_id: 1,
  878. source: 'OTRS Import',
  879. role_ids: role_ids,
  880. group_ids: group_ids,
  881. }
  882. map.each { |key, value|
  883. next if !user.key?(key.to_s)
  884. user_new[value] = user[key.to_s]
  885. }
  886. # set pw
  887. if user_new[:password]
  888. user_new[:password] = "{sha2}#{user_new[:password]}"
  889. end
  890. # check if agent already exists
  891. user_old = User.lookup(id: user_new[:id])
  892. # check if login is already used
  893. login_in_use = User.where( "login = ? AND id != #{user_new[:id]}", user_new[:login].downcase ).count
  894. if login_in_use > 0
  895. user_new[:login] = "#{user_new[:login]}_#{user_new[:id]}"
  896. end
  897. # create / update agent
  898. if user_old
  899. log "update User.find(#{user_old[:id]})"
  900. # only update roles if different (reduce sql statements)
  901. if user_old.role_ids == user_new[:role_ids]
  902. user_new.delete(:role_ids)
  903. end
  904. user_old.update_attributes(user_new)
  905. else
  906. log "add User.find(#{user_new[:id]})"
  907. user = User.new(user_new)
  908. user.id = user_new[:id]
  909. user.save
  910. _reset_pk('users')
  911. end
  912. }
  913. end
  914. def self.get_queue_ids(user, _groups, _roles, queues)
  915. queue_ids = []
  916. # lookup by groups
  917. user['GroupIDs'].each {|group_id, permissions|
  918. queues.each {|queue_lookup|
  919. next if queue_lookup['GroupID'] != group_id
  920. next if !permissions
  921. next if !permissions.include?('rw')
  922. queue_ids.push queue_lookup['QueueID']
  923. }
  924. }
  925. # lookup by roles
  926. # roles of user
  927. # groups of roles
  928. # queues of group
  929. queue_ids
  930. end
  931. def self.get_roles_ids(user, groups, roles, _queues)
  932. local_roles = ['Agent']
  933. local_role_ids = []
  934. # apply group permissions
  935. user['GroupIDs'].each {|group_id, permissions|
  936. groups.each {|group_lookup|
  937. next if group_id != group_lookup['ID']
  938. next if !permissions
  939. if group_lookup['Name'] == 'admin' && permissions.include?('rw')
  940. local_roles.push 'Admin'
  941. end
  942. next if group_lookup['Name'] !~ /^(stats|report)/
  943. next if !( permissions.include?('ro') || permissions.include?('rw') )
  944. local_roles.push 'Report'
  945. }
  946. }
  947. # apply role permissions
  948. user['RoleIDs'].each {|role_id|
  949. # get groups of role
  950. roles.each {|role|
  951. next if role['ID'] != role_id
  952. # verify group names
  953. role['GroupIDs'].each {|group_id, permissions|
  954. groups.each {|group_lookup|
  955. next if group_id != group_lookup['ID']
  956. next if !permissions
  957. if group_lookup['Name'] == 'admin' && permissions.include?('rw')
  958. local_roles.push 'Admin'
  959. end
  960. next if group_lookup['Name'] !~ /^(stats|report)/
  961. next if !( permissions.include?('ro') || permissions.include?('rw') )
  962. local_roles.push 'Report'
  963. }
  964. }
  965. }
  966. }
  967. local_roles.each {|role|
  968. role_lookup = Role.lookup(name: role)
  969. next if !role_lookup
  970. local_role_ids.push role_lookup.id
  971. }
  972. local_role_ids
  973. end
  974. # sync customers
  975. def self.customer(records, organizations)
  976. map = {
  977. ChangeTime: :updated_at,
  978. CreateTime: :created_at,
  979. CreateBy: :created_by_id,
  980. ChangeBy: :updated_by_id,
  981. ValidID: :active,
  982. UserComment: :note,
  983. UserEmail: :email,
  984. UserFirstname: :firstname,
  985. UserLastname: :lastname,
  986. UserLogin: :login,
  987. UserPassword: :password,
  988. UserPhone: :phone,
  989. UserFax: :fax,
  990. UserMobile: :mobile,
  991. UserStreet: :street,
  992. UserZip: :zip,
  993. UserCity: :city,
  994. UserCountry: :country,
  995. }
  996. role_agent = Role.lookup(name: 'Agent')
  997. role_customer = Role.lookup(name: 'Customer')
  998. records.each { |user|
  999. _set_valid(user)
  1000. # get new attributes
  1001. user_new = {
  1002. created_by_id: 1,
  1003. updated_by_id: 1,
  1004. source: 'OTRS Import',
  1005. organization_id: get_organization_id(user, organizations),
  1006. role_ids: [ role_customer.id ],
  1007. }
  1008. map.each { |key, value|
  1009. next if !user.key?(key.to_s)
  1010. user_new[value] = user[key.to_s]
  1011. }
  1012. # check if customer already exists
  1013. user_old = User.lookup(login: user_new[:login])
  1014. # create / update agent
  1015. if user_old
  1016. # do not update user if it is already agent
  1017. if !user_old.role_ids.include?(role_agent.id)
  1018. # only update roles if different (reduce sql statements)
  1019. if user_old.role_ids == user_new[:role_ids]
  1020. user_new.delete(:role_ids)
  1021. end
  1022. log "update User.find(#{user_old[:id]})"
  1023. user_old.update_attributes(user_new)
  1024. end
  1025. else
  1026. log "add User.find(#{user_new[:id]})"
  1027. user = User.new(user_new)
  1028. user.save
  1029. _reset_pk('users')
  1030. end
  1031. }
  1032. end
  1033. def self.get_organization_id(user, organizations)
  1034. organization_id = nil
  1035. if user['UserCustomerID']
  1036. organizations.each {|organization|
  1037. next if user['UserCustomerID'] != organization['CustomerID']
  1038. organization = Organization.lookup(name: organization['CustomerCompanyName'])
  1039. organization_id = organization.id
  1040. }
  1041. end
  1042. organization_id
  1043. end
  1044. # sync organizations
  1045. def self.organization(records)
  1046. map = {
  1047. ChangeTime: :updated_at,
  1048. CreateTime: :created_at,
  1049. CreateBy: :created_by_id,
  1050. ChangeBy: :updated_by_id,
  1051. CustomerCompanyName: :name,
  1052. ValidID: :active,
  1053. CustomerCompanyComment: :note,
  1054. }
  1055. records.each { |organization|
  1056. _set_valid(organization)
  1057. # get new attributes
  1058. organization_new = {
  1059. created_by_id: 1,
  1060. updated_by_id: 1,
  1061. }
  1062. map.each { |key, value|
  1063. next if !organization.key?(key.to_s)
  1064. organization_new[value] = organization[key.to_s]
  1065. }
  1066. # check if state already exists
  1067. organization_old = Organization.lookup(name: organization_new[:name])
  1068. # set state types
  1069. if organization_old
  1070. organization_old.update_attributes(organization_new)
  1071. else
  1072. organization = Organization.new(organization_new)
  1073. organization.id = organization_new[:id]
  1074. organization.save
  1075. _reset_pk('organizations')
  1076. end
  1077. }
  1078. end
  1079. # sync settings
  1080. def self.setting(records)
  1081. records.each { |setting|
  1082. # fqdn
  1083. if setting['Key'] == 'FQDN'
  1084. Setting.set('fqdn', setting['Value'])
  1085. end
  1086. # http type
  1087. if setting['Key'] == 'HttpType'
  1088. Setting.set('http_type', setting['Value'])
  1089. end
  1090. # system id
  1091. if setting['Key'] == 'SystemID'
  1092. Setting.set('system_id', setting['Value'])
  1093. end
  1094. # organization
  1095. if setting['Key'] == 'Organization'
  1096. Setting.set('organization', setting['Value'])
  1097. end
  1098. # sending emails
  1099. if setting['Key'] == 'SendmailModule'
  1100. # TODO
  1101. end
  1102. # number generater
  1103. if setting['Key'] == 'Ticket::NumberGenerator'
  1104. if setting['Value'] == 'Kernel::System::Ticket::Number::DateChecksum'
  1105. Setting.set('ticket_number', 'Ticket::Number::Date')
  1106. Setting.set('ticket_number_date', { checksum: true })
  1107. elsif setting['Value'] == 'Kernel::System::Ticket::Number::Date'
  1108. Setting.set('ticket_number', 'Ticket::Number::Date')
  1109. Setting.set('ticket_number_date', { checksum: false })
  1110. end
  1111. end
  1112. # ticket hook
  1113. if setting['Key'] == 'Ticket::Hook'
  1114. Setting.set('ticket_hook', setting['Value'])
  1115. end
  1116. }
  1117. end
  1118. # log
  1119. def self.log(message)
  1120. thread_no = Thread.current[:thread_no] || '-'
  1121. Rails.logger.info "thread##{thread_no}: #{message}"
  1122. end
  1123. # set translate valid ids to active = true|false
  1124. def self._set_valid(record)
  1125. # map
  1126. record['ValidID'] = if record['ValidID'].to_s == '3'
  1127. false
  1128. elsif record['ValidID'].to_s == '2'
  1129. false
  1130. elsif record['ValidID'].to_s == '1'
  1131. true
  1132. elsif record['ValidID'].to_s == '0'
  1133. false
  1134. # fallback
  1135. else
  1136. true
  1137. end
  1138. end
  1139. # cleanup invalid values
  1140. def self._cleanup(record)
  1141. record.each {|key, value|
  1142. if value == '0000-00-00 00:00:00'
  1143. record[key] = nil
  1144. end
  1145. }
  1146. # fix OTRS 3.1 bug, no close time if ticket is created
  1147. if record['StateType'] == 'closed' && (!record['Closed'] || record['Closed'].empty?)
  1148. record['Closed'] = record['Created']
  1149. end
  1150. end
  1151. # utf8 convert
  1152. def self._utf8_encode(data)
  1153. data.each { |key, value|
  1154. next if !value
  1155. next if value.class != String
  1156. data[key] = Encode.conv('utf8', value)
  1157. }
  1158. end
  1159. # reset primary key sequences
  1160. def self._reset_pk(table)
  1161. return if ActiveRecord::Base.connection_config[:adapter] != 'postgresql'
  1162. ActiveRecord::Base.connection.reset_pk_sequence!(table)
  1163. end
  1164. # create customers for article
  1165. def self._article_based_customers(article)
  1166. # create customer/sender if needed
  1167. return if article['sender'] != 'customer'
  1168. return if article['created_by_id'].to_i != 1
  1169. return if article['from'].empty?
  1170. email = nil
  1171. begin
  1172. email = Mail::Address.new(article['from']).address
  1173. rescue
  1174. email = article['from']
  1175. if article['from'] =~ /<(.+?)>/
  1176. email = $1
  1177. end
  1178. end
  1179. user = User.lookup(email: email)
  1180. if !user
  1181. user = User.lookup(login: email)
  1182. end
  1183. if !user
  1184. begin
  1185. display_name = Mail::Address.new( article['from'] ).display_name ||
  1186. ( Mail::Address.new( article['from'] ).comments && Mail::Address.new( article['from'] ).comments[0] )
  1187. rescue
  1188. display_name = article['from']
  1189. end
  1190. # do extra decoding because we needed to use field.value
  1191. display_name = Mail::Field.new('X-From', display_name).to_s
  1192. roles = Role.lookup(name: 'Customer')
  1193. begin
  1194. user = User.create(
  1195. login: email,
  1196. firstname: display_name,
  1197. lastname: '',
  1198. email: email,
  1199. password: '',
  1200. active: true,
  1201. role_ids: [roles.id],
  1202. updated_by_id: 1,
  1203. created_by_id: 1,
  1204. )
  1205. rescue ActiveRecord::RecordNotUnique
  1206. log "User #{email} was handled by another thread, taking this."
  1207. user = User.lookup(login: email)
  1208. if !user
  1209. log "User #{email} wasn't created sleep and retry."
  1210. sleep rand 3
  1211. retry
  1212. end
  1213. end
  1214. end
  1215. article['created_by_id'] = user.id
  1216. true
  1217. end
  1218. end