c80_estate 0.1.0.1 → 0.1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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