time_accountings_controller.rb 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class TimeAccountingsController < ApplicationController
  3. prepend_before_action { authentication_check(permission: 'admin.time_accounting') }
  4. def by_ticket
  5. year = params[:year] || Time.zone.now.year
  6. month = params[:month] || Time.zone.now.month
  7. start_periode = Time.zone.parse("#{year}-#{month}-01")
  8. end_periode = start_periode.end_of_month
  9. time_unit = {}
  10. Ticket::TimeAccounting.where('created_at >= ? AND created_at <= ?', start_periode, end_periode).pluck(:ticket_id, :time_unit, :created_by_id).each do |record|
  11. if !time_unit[record[0]]
  12. time_unit[record[0]] = {
  13. time_unit: 0,
  14. agent_id: record[2],
  15. }
  16. end
  17. time_unit[record[0]][:time_unit] += record[1]
  18. end
  19. customers = {}
  20. organizations = {}
  21. agents = {}
  22. results = []
  23. time_unit.each do |ticket_id, local_time_unit|
  24. ticket = Ticket.lookup(id: ticket_id)
  25. next if !ticket
  26. if !customers[ticket.customer_id]
  27. customers[ticket.customer_id] = '-'
  28. if ticket.customer_id
  29. customer_user = User.lookup(id: ticket.customer_id)
  30. if customer_user
  31. customers[ticket.customer_id] = customer_user.fullname
  32. end
  33. end
  34. end
  35. if !organizations[ticket.organization_id]
  36. organizations[ticket.organization_id] = '-'
  37. if ticket.organization_id
  38. organization = Organization.lookup(id: ticket.organization_id)
  39. if organization
  40. organizations[ticket.organization_id] = organization.name
  41. end
  42. end
  43. end
  44. if !agents[local_time_unit[:agent_id]]
  45. agent_user = User.lookup(id: local_time_unit[:agent_id])
  46. agent = '-'
  47. if agent_user
  48. agents[local_time_unit[:agent_id]] = agent_user.fullname
  49. end
  50. end
  51. result = {
  52. ticket: ticket.attributes,
  53. time_unit: local_time_unit[:time_unit],
  54. customer: customers[ticket.customer_id],
  55. organization: organizations[ticket.organization_id],
  56. agent: agents[local_time_unit[:agent_id]],
  57. }
  58. results.push result
  59. end
  60. if params[:download]
  61. header = [
  62. {
  63. name: 'Ticket#',
  64. width: 15,
  65. },
  66. {
  67. name: 'Title',
  68. width: 30,
  69. },
  70. {
  71. name: 'Customer',
  72. width: 20,
  73. },
  74. {
  75. name: 'Organization',
  76. width: 20,
  77. },
  78. {
  79. name: 'Agent',
  80. width: 20,
  81. },
  82. {
  83. name: 'Time Units',
  84. width: 10,
  85. },
  86. {
  87. name: 'Time Units Total',
  88. width: 10,
  89. },
  90. {
  91. name: 'Created at',
  92. width: 18,
  93. },
  94. {
  95. name: 'Closed at',
  96. width: 18,
  97. },
  98. {
  99. name: 'Close Escalation At',
  100. width: 18,
  101. },
  102. {
  103. name: 'Close In Min',
  104. width: 10,
  105. },
  106. {
  107. name: 'Close Diff In Min',
  108. width: 10,
  109. },
  110. {
  111. name: 'First Response At',
  112. width: 18,
  113. },
  114. {
  115. name: 'First Response Escalation At',
  116. width: 18,
  117. },
  118. {
  119. name: 'First Response In Min',
  120. width: 10,
  121. },
  122. {
  123. name: 'First Response Diff In Min',
  124. width: 10,
  125. },
  126. {
  127. name: 'Update Escalation At',
  128. width: 18,
  129. },
  130. {
  131. name: 'Update In Min',
  132. width: 10,
  133. },
  134. {
  135. name: 'Update Diff In Min',
  136. width: 10,
  137. },
  138. {
  139. name: 'Last Contact At',
  140. width: 18,
  141. },
  142. {
  143. name: 'Last Contact Agent At',
  144. width: 18,
  145. },
  146. {
  147. name: 'Last Contact Customer At',
  148. width: 18,
  149. },
  150. {
  151. name: 'Article Count',
  152. width: 10,
  153. },
  154. {
  155. name: 'Escalation At',
  156. width: 18,
  157. },
  158. ]
  159. objects = ObjectManager::Attribute.where(editable: true,
  160. active: true,
  161. to_create: false,
  162. object_lookup_id: ObjectLookup.lookup(name: 'Ticket').id)
  163. .pluck(:name, :display, :data_type, :data_option)
  164. .map { |name, display, data_type, data_option| { name: name, display: display, data_type: data_type, data_option: data_option } }
  165. objects.each do |object|
  166. header.push({ name: object[:display], width: 18 })
  167. end
  168. result = []
  169. results.each do |row|
  170. result_row = [
  171. row[:ticket]['number'],
  172. row[:ticket]['title'],
  173. row[:customer],
  174. row[:organization],
  175. row[:agent],
  176. row[:time_unit],
  177. row[:ticket]['time_unit'],
  178. row[:ticket]['created_at'],
  179. row[:ticket]['close_at'],
  180. row[:ticket]['close_escalation_at'],
  181. row[:ticket]['close_in_min'],
  182. row[:ticket]['close_diff_in_min'],
  183. row[:ticket]['first_response_at'],
  184. row[:ticket]['first_response_escalation_at'],
  185. row[:ticket]['first_response_in_min'],
  186. row[:ticket]['first_response_diff_in_min'],
  187. row[:ticket]['update_escalation_at'],
  188. row[:ticket]['update_in_min'],
  189. row[:ticket]['update_diff_in_min'],
  190. row[:ticket]['last_contact_at'],
  191. row[:ticket]['last_contact_agent_at'],
  192. row[:ticket]['last_contact_customer_at'],
  193. row[:ticket]['article_count'],
  194. row[:ticket]['escalation_at'],
  195. ]
  196. # Object Manager attributes
  197. # We already queried ObjectManager::Attributes, so we just use objects
  198. objects.each do |object|
  199. key = object[:name]
  200. case object[:data_type]
  201. when 'boolean', 'select'
  202. value = row[:ticket][key]
  203. if object[:data_option] && object[:data_option]['options'] && object[:data_option]['options'][row[:ticket][key]]
  204. value = object[:data_option]['options'][row[:ticket][key]]
  205. end
  206. value.present? ? result_row.push(value) : result_row.push('')
  207. else
  208. # for text, integer and tree select
  209. row[:ticket][key].present? ? result_row.push(row[:ticket][key]) : result_row.push('')
  210. end
  211. end
  212. result.push result_row
  213. end
  214. content = sheet("By Ticket #{year}-#{month}", header, result)
  215. send_data(
  216. content,
  217. filename: "by_ticket-#{year}-#{month}.xls",
  218. type: 'application/vnd.ms-excel',
  219. disposition: 'attachment'
  220. )
  221. return
  222. end
  223. render json: results
  224. end
  225. def by_customer
  226. year = params[:year] || Time.zone.now.year
  227. month = params[:month] || Time.zone.now.month
  228. start_periode = Time.zone.parse("#{year}-#{month}-01")
  229. end_periode = start_periode.end_of_month
  230. time_unit = {}
  231. Ticket::TimeAccounting.where('created_at >= ? AND created_at <= ?', start_periode, end_periode).pluck(:ticket_id, :time_unit, :created_by_id).each do |record|
  232. if !time_unit[record[0]]
  233. time_unit[record[0]] = {
  234. time_unit: 0,
  235. agent_id: record[2],
  236. }
  237. end
  238. time_unit[record[0]][:time_unit] += record[1]
  239. end
  240. customers = {}
  241. time_unit.each do |ticket_id, local_time_unit|
  242. ticket = Ticket.lookup(id: ticket_id)
  243. next if !ticket
  244. if !customers[ticket.customer_id]
  245. organization = nil
  246. if ticket.organization_id
  247. organization = Organization.lookup(id: ticket.organization_id).attributes
  248. end
  249. customers[ticket.customer_id] = {
  250. customer: User.lookup(id: ticket.customer_id).attributes,
  251. organization: organization,
  252. time_unit: local_time_unit[:time_unit],
  253. }
  254. next
  255. end
  256. customers[ticket.customer_id][:time_unit] += local_time_unit[:time_unit]
  257. end
  258. results = []
  259. customers.each_value do |content|
  260. results.push content
  261. end
  262. if params[:download]
  263. header = [
  264. {
  265. name: 'Customer',
  266. width: 30,
  267. },
  268. {
  269. name: 'Organization',
  270. width: 30,
  271. },
  272. {
  273. name: 'Time Units',
  274. width: 10,
  275. }
  276. ]
  277. result = []
  278. results.each do |row|
  279. customer_name = User.find(row[:customer]['id']).fullname
  280. organization_name = ''
  281. if row[:organization].present?
  282. organization_name = row[:organization]['name']
  283. end
  284. result_row = [customer_name, organization_name, row[:time_unit]]
  285. result.push result_row
  286. end
  287. content = sheet("By Customer #{year}-#{month}", header, result)
  288. send_data(
  289. content,
  290. filename: "by_customer-#{year}-#{month}.xls",
  291. type: 'application/vnd.ms-excel',
  292. disposition: 'attachment'
  293. )
  294. return
  295. end
  296. render json: results
  297. end
  298. def by_organization
  299. year = params[:year] || Time.zone.now.year
  300. month = params[:month] || Time.zone.now.month
  301. start_periode = Time.zone.parse("#{year}-#{month}-01")
  302. end_periode = start_periode.end_of_month
  303. time_unit = {}
  304. Ticket::TimeAccounting.where('created_at >= ? AND created_at <= ?', start_periode, end_periode).pluck(:ticket_id, :time_unit, :created_by_id).each do |record|
  305. if !time_unit[record[0]]
  306. time_unit[record[0]] = {
  307. time_unit: 0,
  308. agent_id: record[2],
  309. }
  310. end
  311. time_unit[record[0]][:time_unit] += record[1]
  312. end
  313. organizations = {}
  314. time_unit.each do |ticket_id, local_time_unit|
  315. ticket = Ticket.lookup(id: ticket_id)
  316. next if !ticket
  317. next if !ticket.organization_id
  318. if !organizations[ticket.organization_id]
  319. organizations[ticket.organization_id] = {
  320. organization: Organization.lookup(id: ticket.organization_id).attributes,
  321. time_unit: local_time_unit[:time_unit],
  322. }
  323. next
  324. end
  325. organizations[ticket.organization_id][:time_unit] += local_time_unit[:time_unit]
  326. end
  327. results = []
  328. organizations.each_value do |content|
  329. results.push content
  330. end
  331. if params[:download]
  332. header = [
  333. {
  334. name: 'Organization',
  335. width: 40,
  336. },
  337. {
  338. name: 'Time Units',
  339. width: 20,
  340. }
  341. ]
  342. result = []
  343. results.each do |row|
  344. organization_name = ''
  345. if row[:organization].present?
  346. organization_name = row[:organization]['name']
  347. end
  348. result_row = [organization_name, row[:time_unit]]
  349. result.push result_row
  350. end
  351. content = sheet("By Organization #{year}-#{month}", header, result)
  352. send_data(
  353. content,
  354. filename: "by_organization-#{year}-#{month}.xls",
  355. type: 'application/vnd.ms-excel',
  356. disposition: 'attachment'
  357. )
  358. return
  359. end
  360. render json: results
  361. end
  362. private
  363. def sheet(title, header, result)
  364. params[:timezone] ||= Setting.get('timezone_default')
  365. # Create a new Excel workbook
  366. temp_file = Tempfile.new('time_tracking.xls')
  367. workbook = WriteExcel.new(temp_file)
  368. # Add a worksheet
  369. worksheet = workbook.add_worksheet
  370. # Add and define a format
  371. format = workbook.add_format # Add a format
  372. format.set_bold
  373. format.set_size(14)
  374. format.set_color('black')
  375. format_time = workbook.add_format(num_format: 'yyyy-mm-dd hh:mm:ss')
  376. format_date = workbook.add_format(num_format: 'yyyy-mm-dd')
  377. format_footer = workbook.add_format
  378. format_footer.set_italic
  379. format_footer.set_color('gray')
  380. format_footer.set_size(8)
  381. worksheet.set_row(0, 18, header.count)
  382. # Write a formatted and unformatted string, row and column notation.
  383. worksheet.write_string(0, 0, title, format)
  384. format_header = workbook.add_format # Add a format
  385. format_header.set_italic
  386. format_header.set_bg_color('gray')
  387. format_header.set_color('white')
  388. count = 0
  389. header.each do |item|
  390. if item[:width]
  391. worksheet.set_column(count, count, item[:width])
  392. end
  393. worksheet.write_string(2, count, item[:name], format_header)
  394. count += 1
  395. end
  396. row_count = 2
  397. result.each do |row|
  398. row_count += 1
  399. row_item_count = 0
  400. row.each do |item|
  401. if item.acts_like?(:time)
  402. worksheet.write_date_time(row_count, row_item_count, time_in_localtime_for_excel(item, params[:timezone]), format_time) if item.present?
  403. elsif item.acts_like?(:date)
  404. worksheet.write_date_time(row_count, row_item_count, item.to_s, format_date) if item.present?
  405. elsif item.is_a?(Integer) || item.is_a?(Float)
  406. worksheet.write_number(row_count, row_item_count, item)
  407. else
  408. worksheet.write_string(row_count, row_item_count, item.to_s)
  409. end
  410. row_item_count += 1
  411. end
  412. end
  413. row_count += 2
  414. worksheet.write_string(row_count, 0, "#{Translation.translate(current_user.locale, 'Timezone')}: #{params[:timezone]}", format_footer)
  415. workbook.close
  416. # read file again
  417. file = File.new(temp_file, 'r')
  418. contents = file.read
  419. file.close
  420. contents
  421. end
  422. def time_in_localtime_for_excel(time, timezone)
  423. return if time.blank?
  424. if timezone.present?
  425. offset = time.in_time_zone(timezone).utc_offset
  426. time -= offset
  427. end
  428. time.utc.iso8601.to_s.sub(/Z$/, '')
  429. end
  430. end