c80_estate 0.1.0.1 → 0.1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ body.admin_sevents {
2
+
3
+ &.index {
4
+
5
+ div#main_content {
6
+ opacity: 0;
7
+
8
+ -webkit-transition: opacity .2s ease-in;
9
+ -moz-transition: opacity .2s ease-in;
10
+ -ms-transition: opacity .2s ease-in;
11
+ -o-transition: opacity .2s ease-in;
12
+ transition: opacity .2s ease-in;
13
+
14
+ div#index_adds {
15
+ height: calc(183px + 15px);
16
+ min-height: calc(183px + 15px);
17
+ margin-bottom: 0;
18
+
19
+ > div {
20
+ float: left;
21
+ padding: 10px;
22
+ border-radius: 2px;
23
+ border: 2px solid #f1f1f1;
24
+ height: 183px;
25
+
26
+ &#ecoef {
27
+ width: 200px;
28
+ text-align: center;
29
+
30
+ p {
31
+ width: 100%;
32
+
33
+ &.val {
34
+ margin-top: 15px;
35
+ margin-bottom: 0;
36
+ font-size: 40px;
37
+ }
38
+
39
+ }
40
+ }
41
+
42
+ &#text_stats {
43
+ width: 400px;
44
+ margin-left: 30px;
45
+
46
+ ul {
47
+ padding: 0 5px;
48
+ list-style: none;
49
+ #title {
50
+ font-weight: bold;
51
+ }
52
+ }
53
+
54
+ }
55
+
56
+ &#graph {
57
+ /*width: calc(100% - 200px - 400px - 30px - 30px);*/
58
+ width: 100%;
59
+ height: 200px;
60
+ margin-top: 15px;
61
+
62
+ /*margin-left: 30px;*/
63
+ padding: 0;
64
+ display: none;
65
+ opacity: 0;
66
+
67
+ -webkit-transition: opacity .2s ease-in;
68
+ -moz-transition: opacity .2s ease-in;
69
+ -ms-transition: opacity .2s ease-in;
70
+ -o-transition: opacity .2s ease-in;
71
+ transition: opacity .2s ease-in;
72
+ }
73
+
74
+ }
75
+ }
76
+
77
+ }
78
+
79
+ .batch_actions_selector {
80
+ display: none;
81
+ }
82
+
83
+ .action_items {
84
+ display: none;
85
+ }
86
+
87
+ a.delete_link {
88
+ display: none;
89
+ }
90
+
91
+ .col-actions {
92
+ text-align: right;
93
+ }
94
+
95
+ a.edit_link {
96
+ display: none;
97
+ }
98
+
99
+ h2#page_title {
100
+ opacity: 0;
101
+
102
+ -webkit-transition: opacity .2s ease-in;
103
+ -moz-transition: opacity .2s ease-in;
104
+ -ms-transition: opacity .2s ease-in;
105
+ -o-transition: opacity .2s ease-in;
106
+ transition: opacity .2s ease-in;
107
+ }
108
+
109
+ }
110
+
111
+ }
@@ -13,5 +13,19 @@ module C80Estate
13
13
 
14
14
  end
15
15
 
16
+ def areas_ecoef
17
+
18
+ area_id = request.params[:area_id] == "" ? nil:request.params[:area_id]
19
+ start_date = request.params[:start_date] == "" ? nil:request.params[:start_date]
20
+ end_date = request.params[:end_date] == "" ? nil:request.params[:end_date]
21
+
22
+ obj = Sevent.ecoef(area_id: area_id, start_date: start_date, end_date: end_date)
23
+
24
+ respond_to do |format|
25
+ format.js { render json: obj, status: :ok }
26
+ # format.json
27
+ end
28
+ end
29
+
16
30
  end
17
31
  end
@@ -24,6 +24,11 @@ module C80Estate
24
24
  has_and_belongs_to_many :astatuses, # единственный статус: либо занята, либо свободна
25
25
  :join_table => 'c80_estate_areas_astatuses'
26
26
 
27
+ has_many :sevents, :dependent => :destroy
28
+
29
+ after_create :create_initial_sevent
30
+ after_update :check_and_generate_sevent
31
+
27
32
  def self.all_areas
28
33
  self.all
29
34
  end
@@ -60,6 +65,14 @@ module C80Estate
60
65
  res
61
66
  end
62
67
 
68
+ def astatus_id
69
+ res = -1
70
+ if astatuses.count > 0
71
+ res = astatuses.first.id
72
+ end
73
+ res
74
+ end
75
+
63
76
  def assigned_person_title
64
77
  res = "-"
65
78
  if assigned_person.present?
@@ -68,5 +81,52 @@ module C80Estate
68
81
  res
69
82
  end
70
83
 
84
+ def owner_id
85
+ res = -1
86
+ if owner.present?
87
+ res = owner.id
88
+ end
89
+ res
90
+ end
91
+
92
+ protected
93
+
94
+ # при создании площади генерится начальное событие
95
+ def create_initial_sevent
96
+ Rails.logger.debug "<Area.create_initial_sevent>"
97
+
98
+ Sevent.create!({
99
+ area_id: self.id,
100
+ atype_id: self.atype_id,
101
+ property_id: self.property_id,
102
+ astatus_id: self.astatus_id,
103
+ auser_id: self.owner_id, # инициатор события - создатель Площади
104
+ auser_type: 'AdminUser'
105
+ })
106
+
107
+ end
108
+
109
+ def check_and_generate_sevent
110
+ Rails.logger.debug "<Area.check_and_generate_sevent>"
111
+
112
+ # находим последнее известное событие
113
+ # фиксируем его статус
114
+ last_known_sevent = self.sevents.last.astatus.tag
115
+
116
+ # если статус этого события отличен
117
+ # от нового статуса - генерим событие
118
+ if last_known_sevent != self.astatuses.first.tag
119
+ Sevent.create!({
120
+ area_id: self.id,
121
+ atype_id: self.atype_id,
122
+ property_id: self.property_id,
123
+ astatus_id: self.astatus_id,
124
+ auser_id: self.owner_id, # инициатор события - редактор Площади
125
+ auser_type: 'AdminUser'
126
+ })
127
+ end
128
+
129
+ end
130
+
71
131
  end
72
132
  end
@@ -2,5 +2,6 @@ module C80Estate
2
2
  class Astatus < ActiveRecord::Base
3
3
  has_and_belongs_to_many :areas, # Площадь имеет единственный статус: либо занята, либо свободна
4
4
  :join_table => 'c80_estate_areas_astatuses'
5
+ has_many :sevents#, :dependent => :nullify
5
6
  end
6
7
  end
@@ -14,6 +14,8 @@ module C80Estate
14
14
  # has_many :properties, :dependent => :destroy
15
15
  has_many :atphotos, :dependent => :destroy # одна или несколько фоток
16
16
 
17
+ has_many :sevents, :dependent => :nullify
18
+
17
19
  extend FriendlyId
18
20
  friendly_id :slug_candidates, :use => :slugged
19
21
 
@@ -38,6 +38,8 @@ module C80Estate
38
38
  # эта взаимосвязь трактуется, как "площадь, назначенная сотруднику"
39
39
  has_many :assigned_properties, :as => :assigned_person, :class_name => 'C80Estate::Property', :dependent => :nullify
40
40
 
41
+ has_many :sevents, :as => :auser, :class_name => 'C80Estate::Sevent', :dependent => :nullify
42
+
41
43
  after_create :create_role
42
44
 
43
45
  def create_role
@@ -12,6 +12,7 @@ module C80Estate
12
12
  :allow_destroy => true
13
13
  has_many :areas, :dependent => :destroy
14
14
  has_many :comments, :dependent => :destroy
15
+ has_many :sevents, :dependent => :destroy
15
16
 
16
17
  def assigned_person_title
17
18
  res = "-"
@@ -0,0 +1,368 @@
1
+ module C80Estate
2
+ class Sevent < ActiveRecord::Base
3
+ belongs_to :area
4
+ belongs_to :atype
5
+ belongs_to :property
6
+ belongs_to :astatus
7
+ belongs_to :auser, :polymorphic => true
8
+
9
+ =begin
10
+ def self.all_areas
11
+ self.all
12
+ end
13
+
14
+ def self.free_areas
15
+ self.joins(:astatuses).where(:c80_estate_astatuses => { tag: 'free'})
16
+ end
17
+
18
+ def self.busy_areas
19
+ self.joins(:astatuses).where(:c80_estate_astatuses => { tag: 'busy'})
20
+ end
21
+ =end
22
+
23
+ def self.ecoef(area_id: nil, prop_id: nil, atype_id: nil, start_date: nil, end_date: nil)
24
+ # start_date: строка вида 2015-12-12
25
+
26
+ result = {}
27
+
28
+ # unless end_date.present?
29
+ # end_date = Time.now.utc #.to_s(:db)
30
+ # end
31
+ # unless start_date.present?
32
+ # if prop_id.present?
33
+ # start_date =
34
+ # end
35
+ # end
36
+
37
+ # произведём выборку из базы в list согласно параметрам
38
+ list = []
39
+
40
+ # если ничего не подано - выбираем всё и считаем среднее
41
+ if area_id.nil? && prop_id.nil? && atype_id.nil? && start_date.nil? && end_date.nil?
42
+
43
+ # в result соберём хэш, где ключ - area_id
44
+ # а значение - объект вида {time_free, time_busy} -
45
+ # время, сколько площадь была в аренде или была свободна
46
+
47
+ # раскидаем все sevents по area_id related спискам
48
+ self.all.each do |sevent|
49
+ aid = sevent.area_id
50
+ unless result[aid].present?
51
+ result[aid] = {
52
+ time_free: 0,
53
+ time_busy: 0,
54
+ ecoef: 0,
55
+ start_date:self.first.created_at,
56
+ end_date:Time.now,
57
+ sevents: []
58
+ }
59
+ end
60
+ result[aid][:sevents] << sevent
61
+ end
62
+
63
+ # теперь пробежимся по спискам площадей и посчитаем время
64
+ result.each_key do |area_id|
65
+
66
+ # фиксируем area
67
+ a = result[area_id]
68
+
69
+ # считаем free busy time
70
+ t = self._calc_busy_time(a)
71
+ a[:time_free] = t[:time_free]
72
+ a[:time_busy] = t[:time_busy]
73
+ a[:ecoef] = t[:ecoef]
74
+
75
+ end
76
+
77
+ # теперь имеется result в котором посчитаны time_free и time_busy
78
+ # каждой площади
79
+ # пробежимся по нему и посчитаем коэф-ты
80
+ k = 0
81
+ summ = 0
82
+ result.each_key do |area_id|
83
+ a = result[area_id]
84
+ # a[:ecoef] = a[:time_busy] / (a[:time_busy] + a[:time_free])
85
+ Rails.logger.debug "<ecoef> area_id=#{area_id}, time_free=#{a[:time_free]}, time_busy=#{a[:time_busy]}, ecoef=#{a[:ecoef]}"
86
+ k += 1
87
+ summ += a[:ecoef]
88
+ end
89
+
90
+ result[:average_value] = sprintf "%.2f%", summ/k*100
91
+ result[:comment] = "<abbr title='Период рассчёта эффективности: с момента самого первого известного события до текущего дня'>C #{Time.at(self.first.created_at).strftime('%Y/%m/%d')} по #{Time.now.year}/#{sprintf "%02d", Time.now.month}/#{sprintf "%02d", Time.now.day}</abbr>"
92
+ result[:abbr] = 'Среднее значение для всех площадей за весь период'
93
+ result[:title] = 'Статистика - Все площади'
94
+ result[:props] = [
95
+ {tag:'all_areas_count', val: "Площадей всего: #{Area.all.count}"},
96
+ {tag:'free_areas_count', val: "Площадей свободно: #{Area.free_areas.count}"},
97
+ {tag:'busy_areas_count', val: "Площадей занято: #{Area.busy_areas.count}"}
98
+ ]
99
+
100
+
101
+ # если фильтруем по area
102
+ elsif area_id.present?
103
+
104
+ # фиксируем area
105
+ area = Area.find(area_id)
106
+
107
+ # обозначим диапазон фильтрации
108
+ area_created_at = Time.at(area.created_at)
109
+ time_now = Time.now
110
+ Rails.logger.debug("area_created_at = #{area_created_at}")
111
+ Rails.logger.debug("time_now = #{time_now}")
112
+
113
+ # если подана нижняя граница диапазона и она позже, чем время создания Площади,
114
+ # выравниваем период рассчета коэф-та по этой нижней границе диапазона
115
+ if start_date.present?
116
+ start_date_tt = Time.parse(start_date)
117
+ if start_date_tt > area_created_at
118
+ used_start_date = start_date_tt
119
+ Rails.logger.debug("start_date: используем аргумент: #{start_date_tt}")
120
+ else
121
+ used_start_date = area_created_at
122
+ Rails.logger.debug("start_date: используем время рождения Площади: #{area_created_at}")
123
+ end
124
+ else
125
+ used_start_date = area_created_at
126
+ Rails.logger.debug("start_date: используем время рождения Площади: #{area_created_at}")
127
+ end
128
+ used_start_date_str = used_start_date.strftime('%Y/%m/%d')
129
+
130
+ if end_date.present?
131
+ end_date_tt = Time.parse(end_date)
132
+ if end_date < time_now
133
+ used_end_date = end_date_tt
134
+ Rails.logger.debug("end_date: используем аргумент: #{end_date_tt}")
135
+ else
136
+ used_end_date = time_now
137
+ Rails.logger.debug("end_date: используем текущее время")
138
+ end
139
+ else
140
+ used_end_date = time_now
141
+ Rails.logger.debug("end_date: используем текущее время")
142
+ end
143
+ used_end_date_str = used_end_date.strftime('%Y/%m/%d')
144
+
145
+ Rails.logger.debug("start_date = #{start_date}; end_date = #{end_date}; used_start_date = #{used_start_date}; used_end_date = #{used_end_date}")
146
+ # sevents = self.where(:area_id => area_id).where(:created_at => used_start_date..used_end_date)
147
+ sevents = self.where(:area_id => area_id).where("created_at BETWEEN ? AND ?", used_start_date, used_end_date)
148
+
149
+ # если в этот промежуток небыло событий - значит промежуток целиком попал в какое-то событие
150
+ # найдем его
151
+ # заодно поднимем вспомогательный флаг, который обработаем во view
152
+ mark_whole = false
153
+ if sevents.count == 0
154
+ sevents = [self.where(:area_id => area_id).where("created_at < ?", used_start_date).last]
155
+ mark_whole = true
156
+ # sevents.each do |se|
157
+ # Rails.logger.debug "\t\t\t #{used_start_date - se.created_at}"
158
+ # end
159
+ end
160
+
161
+ t = _calc_busy_time({
162
+ time_free: 0,
163
+ time_busy: 0,
164
+ ecoef: 0,
165
+ sevents: sevents,
166
+ start_date:used_start_date,
167
+ end_date:used_end_date
168
+ })
169
+
170
+ result[area_id] = {
171
+ time_free: t[:time_free],
172
+ time_busy: t[:time_busy],
173
+ ecoef: t[:ecoef],
174
+ # sevents: self.where(:area_id => area_id)
175
+ sevents: sevents
176
+ }
177
+
178
+ result[:average_value] = sprintf "%.2f%", result[area_id][:ecoef]*100
179
+ result[:comment] = "<abbr title='Период рассчёта коэф-та эффективности'>C #{used_start_date_str} по #{used_end_date_str}</abbr>"
180
+ result[:abbr] = 'Коэф-т эффективности площади за указанный период'
181
+ result[:title] = "Статистика - #{area.title}"
182
+ result[:graph] = _parse_for_js_graph(sevents)
183
+
184
+ if mark_whole
185
+ if t[:time_busy] == 0
186
+ t[:time_free] = used_end_date - used_start_date
187
+ end
188
+ end
189
+
190
+ result[:props] = [
191
+ { tag: 'title', val: "#{area.title}" },
192
+ { tag: 'atype', val: "Тип: #{area.atype_title}" },
193
+ # { tag: 'born_date', val: "Дата создания: #{area.created_at.in_time_zone('Moscow')}" },
194
+ { tag: 'busy_time', val: "<abbr title='В указанный период'>Времени занята</abbr>: #{time_duration(t[:time_busy])}" },
195
+ { tag: 'free_time', val: "<abbr title='В указанный период'>Времени свободна</abbr>: #{time_duration(t[:time_free])}" },
196
+ { tag: 'all_time', val: "<abbr title='В указанный период'>Времени всего</abbr>: #{time_duration(t[:time_busy] + t[:time_free])}" },
197
+ { tag: 'assigned_person_title', val: "Ответственный: #{area.assigned_person_title}" },
198
+ { tag: 'property_title', val: "Объект: #{area.property_title}" }
199
+ ]
200
+
201
+ end
202
+
203
+ result
204
+
205
+ end
206
+
207
+ def area_title
208
+ res = "-"
209
+ if area.present?
210
+ res = area.title
211
+ end
212
+ res
213
+ end
214
+
215
+ def atype_title
216
+ res = "-"
217
+ if atype.present?
218
+ res = atype.title
219
+ end
220
+ res
221
+ end
222
+
223
+ def property_title
224
+ res = "-"
225
+ if property.present?
226
+ res = property.title
227
+ end
228
+ res
229
+ end
230
+
231
+ def astatus_title
232
+ res = "-"
233
+ if astatus.present?
234
+ res = astatus.title
235
+ end
236
+ res
237
+ end
238
+
239
+ def auser_title
240
+ res = "-"
241
+ if auser.present?
242
+ res = auser.email
243
+ end
244
+ res
245
+ end
246
+
247
+ private
248
+
249
+ def self._calc_busy_time(a)
250
+ Rails.logger.debug "<Sevent._calc_busy_time> START ---"
251
+ # {
252
+ # time_free: 0,
253
+ # time_busy: 0,
254
+ # ecoef:0,
255
+ # sevents: [],
256
+ # start_date: '2016-12-12' или nil,
257
+ # end_date: '2016-12-12' или nil
258
+ # }
259
+
260
+ res = {
261
+ time_free: 0,
262
+ time_busy: 0,
263
+ ecoef: 0
264
+ }
265
+
266
+ # переберём area related sevents
267
+ a[:sevents].each_with_index do |sevent, index|
268
+
269
+ # если это первый элемент (т.е. до меня нет никого)
270
+ if index == 0
271
+
272
+ # если это единственный элемент
273
+ if a[:sevents].count == 1
274
+
275
+ # считаем его статус до текущего времени
276
+ res = _calc_busy_time_las(res,sevent,index,a[:end_date])
277
+
278
+ end
279
+
280
+ # если это последний элемент - то добавляем, сколько времени была площадь в последнем известном статусе ДО текущего момента
281
+ elsif index == a[:sevents].count - 1
282
+ # TODO_MY:: учитывать end_date и передавать соответствующий mark_calc_to_now
283
+ res = _calc_busy_time_las(res,sevent,index,a[:end_date])
284
+
285
+ # в случае массива из 2 элементов
286
+ if a[:sevents].count == 2
287
+ prev_sevent = a[:sevents][index-1]
288
+ res = _calc_busy_time_bef(res,sevent,prev_sevent,index)
289
+ end
290
+
291
+ # если перед элементом есть кто-то
292
+ else
293
+ prev_sevent = a[:sevents][index-1]
294
+ res = _calc_busy_time_bef(res,sevent,prev_sevent,index)
295
+ end
296
+ end
297
+
298
+ res[:ecoef] = res[:time_busy] / (res[:time_busy] + res[:time_free])
299
+ Rails.logger.debug "<Sevent._calc_busy_time> END ---"
300
+
301
+ res
302
+ end
303
+
304
+ def self._calc_busy_time_las(res,sevent, index, end_date)
305
+
306
+ # TODO_MY:: добавить аргумент mark_to_now и фиксировать текущее время по условию
307
+ # TODO_MY:: или добавить обработчик end_date и учитывать его
308
+ # фиксируем текущее время
309
+ # tnow = Time.now
310
+ tnow = end_date
311
+ d = tnow - sevent.created_at
312
+ Rails.logger.debug "\t\t i=#{index}, last: #{sevent.astatus.tag}, dur: #{d}"
313
+
314
+ case sevent.astatus.tag
315
+ when 'free'
316
+ res[:time_free] += d
317
+ when 'busy'
318
+ res[:time_busy] += d
319
+ end
320
+ Rails.logger.debug "\t\t time_free=#{res[:time_free]}, time_busy = #{res[:time_busy]}"
321
+ res
322
+ end
323
+
324
+ def self._calc_busy_time_bef(res,sevent,prev_sevent,index)
325
+
326
+ # и считаем его длительность
327
+ d = sevent.created_at - prev_sevent.created_at
328
+ Rails.logger.debug "\t\t i=#{index}, prev_sevent: #{prev_sevent.astatus.tag}, dur: #{d}"
329
+
330
+ case prev_sevent.astatus.tag
331
+ when 'free'
332
+ res[:time_free] += d
333
+ when 'busy'
334
+ res[:time_busy] += d
335
+ end
336
+ Rails.logger.debug "\t\t time_free=#{res[:time_free]}, time_busy = #{res[:time_busy]}"
337
+ res
338
+ end
339
+
340
+ def self.time_duration(t)
341
+ mm, ss = t.divmod(60) #=> [4515, 21]
342
+ hh, mm = mm.divmod(60) #=> [75, 15]
343
+ dd, hh = hh.divmod(24) #=> [3, 3]
344
+ # puts "%d days, %d hours, %d minutes and %d seconds" % [dd, hh, mm, ss]
345
+ "%dд %dч %dмин % dс" % [dd,hh,mm,ss]
346
+ end
347
+
348
+ def self._parse_for_js_graph(sevents)
349
+ # res = [
350
+ # ['Year', 'Sales', 'Expenses'],
351
+ # ['2013', 1000, 400],
352
+ # ['2014', 1170, 460],
353
+ # ['2015', 660, 1120],
354
+ # ['2016/12/12', 1030, 540]
355
+ # ]
356
+ res = []
357
+ sevents.each do |sevent|
358
+ v = 0
359
+ if sevent.astatus.tag == 'free'
360
+ v = 1
361
+ end
362
+ res << [ sevent.created_at.strftime('%Y/%m/%d'), v ]
363
+ end
364
+ res
365
+ end
366
+
367
+ end
368
+ end