ticket.rb 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. require 'time_calculation'
  2. require 'sla'
  3. class Ticket < ApplicationModel
  4. before_create :number_generate, :check_defaults
  5. before_update :check_defaults
  6. before_destroy :destroy_dependencies
  7. belongs_to :group
  8. has_many :articles, :class_name => 'Ticket::Article', :after_add => :cache_update, :after_remove => :cache_update
  9. belongs_to :organization
  10. belongs_to :ticket_state, :class_name => 'Ticket::State'
  11. belongs_to :ticket_priority, :class_name => 'Ticket::Priority'
  12. belongs_to :owner, :class_name => 'User'
  13. belongs_to :customer, :class_name => 'User'
  14. belongs_to :created_by, :class_name => 'User'
  15. belongs_to :create_article_type, :class_name => 'Ticket::Article::Type'
  16. belongs_to :create_article_sender, :class_name => 'Ticket::Article::Sender'
  17. attr_accessor :callback_loop
  18. def self.number_check (string)
  19. self.number_adapter.number_check_item(string)
  20. end
  21. def agent_of_group
  22. Group.find( self.group_id ).users.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
  23. end
  24. def self.agents
  25. User.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
  26. end
  27. def self.attributes_to_change(params)
  28. if params[:ticket_id]
  29. params[:ticket] = self.find( params[:ticket_id] )
  30. end
  31. if params[:article_id]
  32. params[:article] = self.find( params[:article_id] )
  33. end
  34. # get ticket states
  35. ticket_state_ids = []
  36. if params[:ticket]
  37. ticket_state_type = params[:ticket].ticket_state.state_type
  38. end
  39. ticket_state_types = ['open', 'closed', 'pending action', 'pending reminder']
  40. if ticket_state_type && !ticket_state_types.include?(ticket_state_type.name)
  41. ticket_state_ids.push params[:ticket].ticket_state.id
  42. end
  43. ticket_state_types.each {|type|
  44. ticket_state_type = Ticket::StateType.where( :name => type ).first
  45. if ticket_state_type
  46. ticket_state_type.states.each {|ticket_state|
  47. ticket_state_ids.push ticket_state.id
  48. }
  49. end
  50. }
  51. # get owner
  52. owner_ids = []
  53. if params[:ticket]
  54. params[:ticket].agent_of_group.each { |user|
  55. owner_ids.push user.id
  56. }
  57. end
  58. # get group
  59. group_ids = []
  60. Group.where( :active => true ).each { |group|
  61. group_ids.push group.id
  62. }
  63. # get group / user relations
  64. agents = {}
  65. Ticket.agents.each { |user|
  66. agents[ user.id ] = 1
  67. }
  68. groups_users = {}
  69. group_ids.each {|group_id|
  70. groups_users[ group_id ] = []
  71. Group.find( group_id ).users.each {|user|
  72. next if !agents[ user.id ]
  73. groups_users[ group_id ].push user.id
  74. }
  75. }
  76. # get priorities
  77. ticket_priority_ids = []
  78. Ticket::Priority.where( :active => true ).each { |priority|
  79. ticket_priority_ids.push priority.id
  80. }
  81. ticket_article_type_ids = []
  82. if params[:ticket]
  83. ticket_article_types = ['note', 'phone']
  84. if params[:ticket].group.email_address_id
  85. ticket_article_types.push 'email'
  86. end
  87. ticket_article_types.each {|ticket_article_type_name|
  88. ticket_article_type = Ticket::Article::Type.lookup( :name => ticket_article_type_name )
  89. if ticket_article_type
  90. ticket_article_type_ids.push ticket_article_type.id
  91. end
  92. }
  93. end
  94. return {
  95. :ticket_article_type_id => ticket_article_type_ids,
  96. :ticket_state_id => ticket_state_ids,
  97. :ticket_priority_id => ticket_priority_ids,
  98. :owner_id => owner_ids,
  99. :group_id => group_ids,
  100. :group_id__owner_id => groups_users,
  101. }
  102. end
  103. def merge_to(data)
  104. # update articles
  105. Ticket::Article.where( :ticket_id => self.id ).update_all( ['ticket_id = ?', data[:ticket_id] ] )
  106. # update history
  107. # create new merge article
  108. Ticket::Article.create(
  109. :ticket_id => self.id,
  110. :ticket_article_type_id => Ticket::Article::Type.lookup( :name => 'note' ).id,
  111. :ticket_article_sender_id => Ticket::Article::Sender.lookup( :name => 'Agent' ).id,
  112. :body => 'merged',
  113. :internal => false
  114. )
  115. # add history to both
  116. # link tickets
  117. Link.add(
  118. :link_type => 'parent',
  119. :link_object_source => 'Ticket',
  120. :link_object_source_value => data[:ticket_id],
  121. :link_object_target => 'Ticket',
  122. :link_object_target_value => self.id
  123. )
  124. # set state to 'merged'
  125. self.ticket_state_id = Ticket::State.lookup( :name => 'merged' ).id
  126. # rest owner
  127. self.owner_id = User.where( :login => '-' ).first.id
  128. # save ticket
  129. self.save
  130. end
  131. # def self.agent
  132. # Role.where( :name => ['Agent'], :active => true ).first.users.where( :active => true ).uniq()
  133. # end
  134. def subject_build (subject)
  135. # clena subject
  136. subject = self.subject_clean(subject)
  137. ticket_hook = Setting.get('ticket_hook')
  138. ticket_hook_divider = Setting.get('ticket_hook_divider')
  139. # none position
  140. if Setting.get('ticket_hook_position') == 'none'
  141. return subject
  142. end
  143. # right position
  144. if Setting.get('ticket_hook_position') == 'right'
  145. return subject + " [#{ticket_hook}#{ticket_hook_divider}#{self.number}] "
  146. end
  147. # left position
  148. return "[#{ticket_hook}#{ticket_hook_divider}#{self.number}] " + subject
  149. end
  150. def subject_clean (subject)
  151. ticket_hook = Setting.get('ticket_hook')
  152. ticket_hook_divider = Setting.get('ticket_hook_divider')
  153. ticket_subject_size = Setting.get('ticket_subject_size')
  154. # remove all possible ticket hook formats with []
  155. subject = subject.gsub /\[#{ticket_hook}: #{self.number}\](\s+?|)/, ''
  156. subject = subject.gsub /\[#{ticket_hook}:#{self.number}\](\s+?|)/, ''
  157. subject = subject.gsub /\[#{ticket_hook}#{ticket_hook_divider}#{self.number}\](\s+?|)/, ''
  158. # remove all possible ticket hook formats without []
  159. subject = subject.gsub /#{ticket_hook}: #{self.number}(\s+?|)/, ''
  160. subject = subject.gsub /#{ticket_hook}:#{self.number}(\s+?|)/, ''
  161. subject = subject.gsub /#{ticket_hook}#{ticket_hook_divider}#{self.number}(\s+?|)/, ''
  162. # remove leading "..:\s" and "..[\d+]:\s" e. g. "Re: " or "Re[5]: "
  163. subject = subject.gsub /^(..(\[\d+\])?:\s)+/, ''
  164. # resize subject based on config
  165. if subject.length > ticket_subject_size.to_i
  166. subject = subject[ 0, ticket_subject_size.to_i ] + '[...]'
  167. end
  168. return subject
  169. end
  170. # ticket.permission(
  171. # :current_user => 123
  172. # )
  173. def permission (data)
  174. # check customer
  175. if data[:current_user].is_role('Customer')
  176. # access ok if its own ticket
  177. return true if self.customer_id == data[:current_user].id
  178. # access ok if its organization ticket
  179. if data[:current_user].organization_id && self.organization_id
  180. return true if self.organization_id == data[:current_user].organization_id
  181. end
  182. # no access
  183. return false
  184. end
  185. # check agent
  186. return true if self.owner_id == data[:current_user].id
  187. data[:current_user].groups.each {|group|
  188. return true if self.group.id == group.id
  189. }
  190. return false
  191. end
  192. # Ticket.search(
  193. # :current_user => 123,
  194. # :query => 'search something',
  195. # :limit => 15,
  196. # )
  197. def self.search (params)
  198. # get params
  199. query = params[:query]
  200. limit = params[:limit] || 12
  201. current_user = params[:current_user]
  202. conditions = []
  203. if current_user.is_role('Agent')
  204. group_ids = Group.select( 'groups.id' ).joins(:users).
  205. where( 'groups_users.user_id = ?', current_user.id ).
  206. where( 'groups.active = ?', true ).
  207. map( &:id )
  208. conditions = [ 'group_id IN (?)', group_ids ]
  209. else
  210. if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
  211. conditions = [ 'customer_id = ?', current_user.id ]
  212. else
  213. conditions = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
  214. end
  215. end
  216. # do query
  217. tickets_all = Ticket.select('DISTINCT(tickets.id)').
  218. where(conditions).
  219. where( '( `tickets`.`title` LIKE ? OR `tickets`.`number` LIKE ? OR `ticket_articles`.`body` LIKE ? OR `ticket_articles`.`from` LIKE ? OR `ticket_articles`.`to` LIKE ? OR `ticket_articles`.`subject` LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" ).
  220. joins(:articles).
  221. limit(limit).
  222. order('`tickets`.`created_at` DESC')
  223. # build result list
  224. tickets = []
  225. users = {}
  226. tickets_all.each do |ticket|
  227. ticket_tmp = Ticket.lookup( :id => ticket.id )
  228. tickets.push ticket_tmp
  229. end
  230. return tickets
  231. end
  232. # Ticket.overview_list(
  233. # :current_user => 123,
  234. # )
  235. def self.overview_list (data)
  236. # get customer overviews
  237. if data[:current_user].is_role('Customer')
  238. role = data[:current_user].is_role( 'Customer' )
  239. if data[:current_user].organization_id && data[:current_user].organization.shared
  240. overviews = Overview.where( :role_id => role.id, :active => true )
  241. else
  242. overviews = Overview.where( :role_id => role.id, :organization_shared => false, :active => true )
  243. end
  244. return overviews
  245. end
  246. # get agent overviews
  247. role = data[:current_user].is_role( 'Agent' )
  248. overviews = Overview.where( :role_id => role.id, :active => true )
  249. return overviews
  250. end
  251. # Ticket.overview(
  252. # :view => 'some_view_url',
  253. # :current_user => OBJECT,
  254. # )
  255. def self.overview (data)
  256. overviews = self.overview_list(data)
  257. # build up attributes hash
  258. overview_selected = nil
  259. overview_selected_raw = nil
  260. overviews.each { |overview|
  261. # remember selected view
  262. if data[:view] && data[:view] == overview.link
  263. overview_selected = overview
  264. overview_selected_raw = Marshal.load( Marshal.dump(overview.attributes) )
  265. end
  266. # replace e.g. 'current_user.id' with current_user.id
  267. overview.condition.each { |item, value |
  268. if value && value.class.to_s == 'String'
  269. parts = value.split( '.', 2 )
  270. if parts[0] && parts[1] && parts[0] == 'current_user'
  271. overview.condition[item] = data[:current_user][parts[1].to_sym]
  272. end
  273. end
  274. }
  275. }
  276. if data[:view] && !overview_selected
  277. return
  278. end
  279. # sortby
  280. # prio
  281. # state
  282. # group
  283. # customer
  284. # order
  285. # asc
  286. # desc
  287. # groupby
  288. # prio
  289. # state
  290. # group
  291. # customer
  292. # all = attributes[:myopenassigned]
  293. # all.merge( { :group_id => groups } )
  294. # @tickets = Ticket.where(:group_id => groups, attributes[:myopenassigned] ).limit(params[:limit])
  295. # get only tickets with permissions
  296. if data[:current_user].is_role('Customer')
  297. group_ids = Group.select( 'groups.id' ).
  298. where( 'groups.active = ?', true ).
  299. map( &:id )
  300. else
  301. group_ids = Group.select( 'groups.id' ).joins(:users).
  302. where( 'groups_users.user_id = ?', [ data[:current_user].id ] ).
  303. where( 'groups.active = ?', true ).
  304. map( &:id )
  305. end
  306. # overview meta for navbar
  307. if !overview_selected
  308. # loop each overview
  309. result = []
  310. overviews.each { |overview|
  311. # get count
  312. count = Ticket.where( :group_id => group_ids ).where( self._condition( overview.condition ) ).count()
  313. # get meta info
  314. all = {
  315. :name => overview.name,
  316. :prio => overview.prio,
  317. :link => overview.link,
  318. }
  319. # push to result data
  320. result.push all.merge( { :count => count } )
  321. }
  322. return result
  323. end
  324. # get result list
  325. if data[:array]
  326. order_by = overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s
  327. if overview_selected.group_by && !overview_selected.group_by.empty?
  328. order_by = overview_selected.group_by + '_id, ' + order_by
  329. end
  330. tickets = Ticket.select( 'id' ).
  331. where( :group_id => group_ids ).
  332. where( self._condition( overview_selected.condition ) ).
  333. order( order_by ).
  334. limit( 500 )
  335. ticket_ids = []
  336. tickets.each { |ticket|
  337. ticket_ids.push ticket.id
  338. }
  339. tickets_count = Ticket.where( :group_id => group_ids ).
  340. where( self._condition( overview_selected.condition ) ).
  341. count()
  342. return {
  343. :ticket_list => ticket_ids,
  344. :tickets_count => tickets_count,
  345. :overview => overview_selected_raw,
  346. }
  347. end
  348. # get tickets for overview
  349. data[:start_page] ||= 1
  350. tickets = Ticket.where( :group_id => group_ids ).
  351. where( self._condition( overview_selected.condition ) ).
  352. order( overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s )#.
  353. # limit( overview_selected.view[ data[:view_mode].to_sym ][:per_page] ).
  354. # offset( overview_selected.view[ data[:view_mode].to_sym ][:per_page].to_i * ( data[:start_page].to_i - 1 ) )
  355. tickets_count = Ticket.where( :group_id => group_ids ).
  356. where( self._condition( overview_selected.condition ) ).
  357. count()
  358. return {
  359. :tickets => tickets,
  360. :tickets_count => tickets_count,
  361. :overview => overview_selected_raw,
  362. }
  363. end
  364. def self._condition(condition)
  365. sql = ''
  366. bind = [nil]
  367. condition.each {|key, value|
  368. if sql != ''
  369. sql += ' AND '
  370. end
  371. if value.class == Array
  372. sql += " #{key} IN (?)"
  373. bind.push value
  374. elsif value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
  375. time = Time.now
  376. if value['area'] == 'minute'
  377. if value['direction'] == 'last'
  378. time -= value['count'].to_i * 60
  379. else
  380. time += value['count'].to_i * 60
  381. end
  382. elsif value['area'] == 'hour'
  383. if value['direction'] == 'last'
  384. time -= value['count'].to_i * 60 * 60
  385. else
  386. time += value['count'].to_i * 60 * 60
  387. end
  388. elsif value['area'] == 'day'
  389. if value['direction'] == 'last'
  390. time -= value['count'].to_i * 60 * 60 * 24
  391. else
  392. time += value['count'].to_i * 60 * 60 * 24
  393. end
  394. elsif value['area'] == 'month'
  395. if value['direction'] == 'last'
  396. time -= value['count'].to_i * 60 * 60 * 24 * 31
  397. else
  398. time += value['count'].to_i * 60 * 60 * 24 * 31
  399. end
  400. elsif value['area'] == 'year'
  401. if value['direction'] == 'last'
  402. time -= value['count'].to_i * 60 * 60 * 24 * 365
  403. else
  404. time += value['count'].to_i * 60 * 60 * 24 * 365
  405. end
  406. end
  407. if value['direction'] == 'last'
  408. sql += " #{key} > ?"
  409. bind.push time
  410. else
  411. sql += " #{key} < ?"
  412. bind.push time
  413. end
  414. else
  415. sql += " #{key} = ?"
  416. bind.push value
  417. end
  418. }
  419. bind[0] = sql
  420. return bind
  421. end
  422. def self.number_adapter
  423. # load backend based on config
  424. adapter_name = Setting.get('ticket_number')
  425. adapter = nil
  426. case adapter_name
  427. when Symbol, String
  428. require "ticket/number/#{adapter_name.to_s.downcase}"
  429. adapter = Ticket::Number.const_get("#{adapter_name.to_s.capitalize}")
  430. else
  431. raise "Missing number_adapter '#{adapter_name}'"
  432. end
  433. return adapter
  434. end
  435. def self.escalation_calculation_rebuild
  436. ticket_state_list_open = Ticket::State.where(
  437. :state_type_id => Ticket::StateType.where(
  438. :name => ['new','open', 'pending reminder', 'pending action']
  439. )
  440. )
  441. tickets = Ticket.where( :ticket_state_id => ticket_state_list_open )
  442. tickets.each {|ticket|
  443. ticket.escalation_calculation
  444. }
  445. end
  446. def _escalation_calculation_get_sla
  447. sla_selected = nil
  448. sla_list = Cache.get( 'SLA::List::Active' )
  449. if sla_list == nil
  450. sla_list = Sla.where( :active => true ).all
  451. Cache.write( 'SLA::List::Active', sla_list, { :expires_in => 1.hour } )
  452. end
  453. sla_list.each {|sla|
  454. if !sla.condition || sla.condition.empty?
  455. sla_selected = sla
  456. elsif sla.condition
  457. hit = false
  458. map = [
  459. [ 'tickets.ticket_priority_id', 'ticket_priority_id' ],
  460. [ 'tickets.group_id', 'group_id' ]
  461. ]
  462. map.each {|item|
  463. if sla.condition[ item[0] ]
  464. if sla.condition[ item[0] ].class == String
  465. sla.condition[ item[0] ] = [ sla.condition[ item[0] ] ]
  466. end
  467. if sla.condition[ item[0] ].include?( self[ item[1] ].to_s )
  468. hit = true
  469. else
  470. hit = false
  471. end
  472. end
  473. }
  474. if hit
  475. sla_selected = sla
  476. end
  477. end
  478. }
  479. # get and set calendar settings
  480. if sla_selected
  481. TimeCalculation.config( sla_selected.data )
  482. end
  483. return sla_selected
  484. end
  485. def _escalation_calculation_higher_time(escalation_time, check_time, done_time)
  486. return escalation_time if done_time
  487. return check_time if !escalation_time
  488. return escalation_time if !check_time
  489. return check_time if escalation_time > check_time
  490. return escalation_time
  491. end
  492. def escalation_calculation
  493. # set escalation off if ticket is already closed
  494. ticket_state = Ticket::State.lookup( :id => self.ticket_state_id )
  495. ticket_state_type = Ticket::StateType.lookup( :id => ticket_state.state_type_id )
  496. ignore_escalation = ['removed', 'closed', 'merged', 'pending action']
  497. if ignore_escalation.include?( ticket_state_type.name )
  498. self.escalation_time = nil
  499. # self.first_response_escal_date = nil
  500. # self.close_time_escal_date = nil
  501. self.callback_loop = true
  502. self.save
  503. return true
  504. end
  505. # get sla for ticket
  506. sla_selected = self._escalation_calculation_get_sla
  507. # reset escalation if no sla is set
  508. if !sla_selected
  509. self.escalation_time = nil
  510. # self.first_response_escal_date = nil
  511. # self.close_time_escal_date = nil
  512. self.callback_loop = true
  513. self.save
  514. return true
  515. end
  516. # puts sla_selected.inspect
  517. # puts days.inspect
  518. self.escalation_time = nil
  519. self.first_response_escal_date = nil
  520. self.update_time_escal_date = nil
  521. self.close_time_escal_date = nil
  522. # first response
  523. if sla_selected.first_response_time
  524. self.first_response_escal_date = TimeCalculation.dest_time( created_at, sla_selected.first_response_time )
  525. # set ticket escalation
  526. self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response )
  527. end
  528. if self.first_response# && !self.first_response_in_min
  529. self.first_response_in_min = TimeCalculation.business_time_diff( self.created_at, self.first_response )
  530. end
  531. # set sla time
  532. if sla_selected.first_response_time && self.first_response_in_min
  533. self.first_response_diff_in_min = sla_selected.first_response_time - self.first_response_in_min
  534. end
  535. # update time
  536. last_update = self.last_contact_agent
  537. if !last_update
  538. last_update = self.created_at
  539. end
  540. if sla_selected.update_time
  541. self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time )
  542. # set ticket escalation
  543. self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.update_time_escal_date, false )
  544. end
  545. if self.last_contact_agent
  546. self.update_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.last_contact_agent )
  547. end
  548. # set sla time
  549. if sla_selected.update_time && self.update_time_in_min
  550. self.update_time_diff_in_min = sla_selected.update_time - self.update_time_in_min
  551. end
  552. # close time
  553. if sla_selected.close_time
  554. self.close_time_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.close_time )
  555. # set ticket escalation
  556. self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time )
  557. end
  558. if self.close_time# && !self.close_time_in_min
  559. self.close_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.close_time )
  560. end
  561. # set sla time
  562. if sla_selected.close_time && self.close_time_in_min
  563. self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min
  564. end
  565. self.callback_loop = true
  566. self.save
  567. end
  568. private
  569. def number_generate
  570. return if self.number
  571. # generate number
  572. (1..25_000).each do |i|
  573. number = Ticket.number_adapter.number_generate_item()
  574. ticket = Ticket.where( :number => number ).first
  575. if ticket != nil
  576. number = Ticket.number_adapter.number_generate_item()
  577. else
  578. self.number = number
  579. return number
  580. end
  581. end
  582. end
  583. def check_defaults
  584. if !self.owner_id
  585. self.owner_id = 1
  586. end
  587. # if self.customer_id && ( !self.organization_id || self.organization_id.empty? )
  588. if self.customer_id
  589. customer = User.find( self.customer_id )
  590. if self.organization_id != customer.organization_id
  591. self.organization_id = customer.organization_id
  592. end
  593. end
  594. end
  595. def destroy_dependencies
  596. # delete history
  597. History.remove( 'Ticket', self.id )
  598. # delete articles
  599. self.articles.destroy_all
  600. end
  601. class Number
  602. end
  603. end