omega-tariffs-base 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1 -0
- data/Manifest +30 -0
- data/README.rdoc +10 -0
- data/Rakefile +14 -0
- data/lib/computer_class_filter.rb +37 -0
- data/lib/deny_filter.rb +24 -0
- data/lib/filter.rb +43 -0
- data/lib/omega-tariffs-base.rb +13 -0
- data/lib/packet_filter.rb +111 -0
- data/lib/rate_filter.rb +31 -0
- data/lib/regioned_calculation.rb +61 -0
- data/lib/repeating_filter.rb +188 -0
- data/lib/settings.rb +42 -0
- data/lib/single_time_filter.rb +67 -0
- data/lib/tarif_builder.rb +16 -0
- data/lib/weekday_filter.rb +53 -0
- data/omega-tariffs-base.gemspec +36 -0
- data/spec/complex_tarif_spec.rb +54 -0
- data/spec/computer_class_filter_spec.rb +82 -0
- data/spec/deny_filter_spec.rb +23 -0
- data/spec/filter_spec.rb +36 -0
- data/spec/lib/active_record_classes_stub.rb +151 -0
- data/spec/lib/init.rb +5 -0
- data/spec/lib/spec_helper.rb +9 -0
- data/spec/packet_filter_spec.rb +287 -0
- data/spec/rate_filter_spec.rb +36 -0
- data/spec/repeating_filter_spec.rb +250 -0
- data/spec/settings_spec.rb +62 -0
- data/spec/single_time_filter_spec.rb +122 -0
- data/spec/tarif_builder_spec.rb +43 -0
- data/spec/weekday_filter_spec.rb +144 -0
- metadata +147 -0
data/CHANGELOG
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
v0.1. Initial code commit. Everything is working and tested.
|
data/Manifest
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
Manifest
|
3
|
+
README.rdoc
|
4
|
+
Rakefile
|
5
|
+
lib/computer_class_filter.rb
|
6
|
+
lib/deny_filter.rb
|
7
|
+
lib/filter.rb
|
8
|
+
lib/omega-tariffs-base.rb
|
9
|
+
lib/packet_filter.rb
|
10
|
+
lib/rate_filter.rb
|
11
|
+
lib/regioned_calculation.rb
|
12
|
+
lib/repeating_filter.rb
|
13
|
+
lib/settings.rb
|
14
|
+
lib/single_time_filter.rb
|
15
|
+
lib/tarif_builder.rb
|
16
|
+
lib/weekday_filter.rb
|
17
|
+
spec/complex_tarif_spec.rb
|
18
|
+
spec/computer_class_filter_spec.rb
|
19
|
+
spec/deny_filter_spec.rb
|
20
|
+
spec/filter_spec.rb
|
21
|
+
spec/lib/active_record_classes_stub.rb
|
22
|
+
spec/lib/init.rb
|
23
|
+
spec/lib/spec_helper.rb
|
24
|
+
spec/packet_filter_spec.rb
|
25
|
+
spec/rate_filter_spec.rb
|
26
|
+
spec/repeating_filter_spec.rb
|
27
|
+
spec/settings_spec.rb
|
28
|
+
spec/single_time_filter_spec.rb
|
29
|
+
spec/tarif_builder_spec.rb
|
30
|
+
spec/weekday_filter_spec.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
= Uniq Systems Omega Sector Generic Tariffs Gem
|
2
|
+
|
3
|
+
This gem provides basic code to support Omega Sector tariffs. In order for them to work you've got to redefine
|
4
|
+
several classes to support real persistent database storage instead of the in-memory one.
|
5
|
+
|
6
|
+
These classes are:
|
7
|
+
* PacketFilterRepository
|
8
|
+
* SingleTimeFilterRepository
|
9
|
+
|
10
|
+
Without those every time you restart the system you'll lose the state, which is probably not what you really want.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('omega-tariffs-base', '0.1.5') do |p|
|
6
|
+
p.description = "Omega Sector base tariffs"
|
7
|
+
p.url = "http://uniqsys.ru/"
|
8
|
+
p.author = "Uniq Systems"
|
9
|
+
p.email = "ivan@uniqsystems.ru"
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
11
|
+
p.runtime_dependencies = p.development_dependencies = ["activesupport =2.3.8"]
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
|
4
|
+
class ComputerClassFilter < Filter
|
5
|
+
def start_permitted?(computer_id, login_id, options={})
|
6
|
+
computer_class(computer_id).start_permitted?(computer_id, login_id, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def permitted?(computer_id, login_id, options={})
|
10
|
+
computer_class(computer_id).permitted?(computer_id, login_id, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
14
|
+
computer_class(computer_id).calculate_cost(computer_id, login_id, started_at, amount, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_activity(unprocessed_activity, ft, options={})
|
18
|
+
computer_class(unprocessed_activity.computer_id).process_activity(unprocessed_activity, ft, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def computer_class(computer_id)
|
23
|
+
@classes[Computer.find(computer_id).computer_class_id]
|
24
|
+
end
|
25
|
+
|
26
|
+
def after_initialize
|
27
|
+
raise "Some classes left undefined." unless ComputerClass.all.map { |klass| klass.id }.to_a.sort == settings.to_hash.keys.sort
|
28
|
+
@classes = [ ]
|
29
|
+
settings.to_hash.each do |computer_class_id, settings|
|
30
|
+
@classes[computer_class_id] = TarifBuilder.build Settings.new(settings), tarif_id
|
31
|
+
end
|
32
|
+
@classes.compact.size == ComputerClass.count
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/deny_filter.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
|
4
|
+
class DenyFilter < Filter
|
5
|
+
def start_permitted?(computer_id, login_id, options={})
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
def permitted?(computer_id, login_id, options={})
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
14
|
+
Integer::MAX
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_activity(unprocessed_activity, ft, options={})
|
18
|
+
# TODO: add logging, so it doesn't just silently swallow residues of the activities
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/filter.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
|
4
|
+
class Filter
|
5
|
+
def initialize(settings, tarif_id)
|
6
|
+
@tarif_id = tarif_id
|
7
|
+
@settings = settings
|
8
|
+
after_initialize
|
9
|
+
end
|
10
|
+
|
11
|
+
def start_permitted?(computer_id, login_id, options={})
|
12
|
+
raise "Unimplemented"
|
13
|
+
end
|
14
|
+
|
15
|
+
def permitted?(computer_id, login_id, options={})
|
16
|
+
raise "Unimplemented"
|
17
|
+
end
|
18
|
+
|
19
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
20
|
+
raise "Unimplemented"
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_activity(unprocessed_activity, ft, options={})
|
24
|
+
raise "Unimplemented"
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
attr_accessor :tarif_id
|
29
|
+
attr_accessor :settings
|
30
|
+
|
31
|
+
def after_initialize
|
32
|
+
end
|
33
|
+
|
34
|
+
def alter_activity_period(unprocessed_activity, new_starts_at, new_amount)
|
35
|
+
unprocessed_activity.dup.tap do |activity|
|
36
|
+
activity.created_at = new_starts_at
|
37
|
+
activity.amount = new_amount
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# guerilla patch the Integer class
|
2
|
+
class Integer
|
3
|
+
N_BYTES = [42].pack('i').size
|
4
|
+
N_BITS = N_BYTES * 8
|
5
|
+
MAX = 2 ** (N_BITS - 2) - 1
|
6
|
+
MIN = -MAX - 1
|
7
|
+
end
|
8
|
+
|
9
|
+
# load libraries
|
10
|
+
[ :settings, :regioned_calculation, :tarif_builder, :filter, :deny_filter, :weekday_filter,
|
11
|
+
:rate_filter, :repeating_filter, :single_time_filter, :computer_class_filter, :packet_filter ].each do |lib|
|
12
|
+
require File.join(File.dirname(__FILE__), lib.to_s)
|
13
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
|
4
|
+
class PacketFilterRepository
|
5
|
+
class <<self
|
6
|
+
def register_region(tarif_id, login_id, moment, period_duration)
|
7
|
+
@payments ||= { }
|
8
|
+
@payments[[tarif_id, login_id]] ||= [ ]
|
9
|
+
@payments[[tarif_id, login_id]] << { :period => (moment ... moment + period_duration), :paid => false }
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_should_end(tarif_id, login_id, time)
|
13
|
+
@end_at ||= { }
|
14
|
+
@end_at[[tarif_id, login_id]] = time
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def should_end?(tarif_id, login_id)
|
19
|
+
return false if @end_at[[tarif_id, login_id]].nil?
|
20
|
+
Time.now > @end_at[[tarif_id, login_id]]
|
21
|
+
end
|
22
|
+
|
23
|
+
def pay_region(tarif_id, login_id, time=Time.now)
|
24
|
+
region = find_region(tarif_id, login_id, time)
|
25
|
+
return false unless region
|
26
|
+
return true if region[:paid]
|
27
|
+
region[:paid] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def region_registered?(*args)
|
31
|
+
!find_region(*args).nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
def region_spans?(tarif_id, login_id, moment=Time.now, duration=1)
|
35
|
+
region = find_region(tarif_id, login_id, moment)
|
36
|
+
return false if region.nil?
|
37
|
+
!(region[:period] === (moment + duration))
|
38
|
+
end
|
39
|
+
|
40
|
+
def region_paid?(tarif_id, login_id, moment=Time.now, duration=1)
|
41
|
+
region = find_region(tarif_id, login_id, moment)
|
42
|
+
!region.nil? && region[:period] === (moment + duration) && region[:paid]
|
43
|
+
end
|
44
|
+
|
45
|
+
def reset
|
46
|
+
@payments = { }
|
47
|
+
@end_at = { }
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
def find_region(tarif_id, login_id, time=Time.now)
|
52
|
+
return nil unless @payments
|
53
|
+
return nil unless @payments[[tarif_id, login_id]]
|
54
|
+
@payments[[tarif_id, login_id]].find { |r| r[:period] === time }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
class PacketFilter < Filter
|
61
|
+
def start_permitted?(computer_id, login_id, options={})
|
62
|
+
single_payment_unneeded?(login_id) and PacketFilterRepository.set_should_end(tarif_id, login_id, nil)
|
63
|
+
end
|
64
|
+
|
65
|
+
def permitted?(computer_id, login_id, options={})
|
66
|
+
return false if PacketFilterRepository.should_end?(tarif_id, login_id)
|
67
|
+
single_payment_unneeded? login_id
|
68
|
+
end
|
69
|
+
|
70
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
71
|
+
return Integer::MAX if PacketFilterRepository.region_spans?(tarif_id, login_id, started_at, amount)
|
72
|
+
PacketFilterRepository.region_paid?(tarif_id, login_id, started_at) ?
|
73
|
+
0 : @price
|
74
|
+
end
|
75
|
+
|
76
|
+
def process_activity(unprocessed_activity, ft, options={})
|
77
|
+
if PacketFilterRepository.region_paid?(tarif_id, unprocessed_activity.login_id, unprocessed_activity.created_at)
|
78
|
+
true
|
79
|
+
else
|
80
|
+
make_single_payment(unprocessed_activity.created_at, ft, unprocessed_activity.login_id) and
|
81
|
+
PacketFilterRepository.set_should_end(tarif_id, unprocessed_activity.login_id, unprocessed_activity.created_at+@length)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
def single_payment_unneeded?(login_id)
|
87
|
+
PacketFilterRepository.region_paid?(tarif_id, login_id) || can_make_single_payment?(login_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
def can_make_single_payment?(login_id)
|
91
|
+
Login.find(login_id).balance - @price >= 0
|
92
|
+
end
|
93
|
+
|
94
|
+
def make_single_payment(time, ft, login_id)
|
95
|
+
return false unless can_make_single_payment?(login_id)
|
96
|
+
|
97
|
+
PacketFilterRepository.register_region(tarif_id, login_id, time, @length)
|
98
|
+
|
99
|
+
ft = ft.dup
|
100
|
+
ft.volume = @price
|
101
|
+
ft.save and PacketFilterRepository.pay_region(tarif_id, login_id, time)
|
102
|
+
end
|
103
|
+
|
104
|
+
def after_initialize
|
105
|
+
@price = settings.price
|
106
|
+
@length = settings.length
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
data/lib/rate_filter.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
|
4
|
+
class RateFilter < Filter
|
5
|
+
def start_permitted?(computer_id, login_id, options={})
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
def permitted?(computer_id, login_id, options={})
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
14
|
+
amount/3600*@rate
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_activity(unprocessed_activity, ft, options={})
|
18
|
+
ft = ft.dup
|
19
|
+
ft.volume = unprocessed_activity.amount/3600*@rate
|
20
|
+
ft.save!
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def after_initialize
|
26
|
+
@rate = settings.rate
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Math
|
2
|
+
# optimized version of find_root, performs 10000 roots' calculations per sec on Core 2 Duo 2.67
|
3
|
+
def self.find_root(a, b, precision=0.01, allow_oob_roots=false, &blk)
|
4
|
+
fa, fb = yield(a).to_f, yield(b).to_f
|
5
|
+
sa, sb = fa >= 0 ? 1 : -1, fb >= 0 ? 1 : -1
|
6
|
+
while true do
|
7
|
+
# puts "f(#{a},#{(a/precision).truncate})=#{fa}, f(#{b},#{(b/precision).truncate})=#{fb}"
|
8
|
+
raise "Looking for roots out of bounds" if !allow_oob_roots && sa == sb
|
9
|
+
return a if (a-b).abs < precision || fa.abs <= precision
|
10
|
+
return b if fb.abs <= precision
|
11
|
+
c = (a.to_f + b.to_f) / 2
|
12
|
+
fc = yield(c)
|
13
|
+
sc = fc >= 0 ? 1 : -1
|
14
|
+
if sa != sc
|
15
|
+
b, fb, sb = c, fc, sc
|
16
|
+
else
|
17
|
+
a, fa, sa = c, fc, sc
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Range
|
24
|
+
def intersection(other)
|
25
|
+
raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
|
26
|
+
raise ArgumentError, 'must be the same end inclusive/non-inclusive type' unless exclude_end? == other.exclude_end?
|
27
|
+
|
28
|
+
min, max = first, last
|
29
|
+
other_min, other_max = other.first, other.last
|
30
|
+
|
31
|
+
new_min = self === other_min ? other_min : other === min ? min : min == other_min ? min : nil
|
32
|
+
new_max = self === other_max ? other_max : other === max ? max : max == other_max ? max : nil
|
33
|
+
|
34
|
+
new_min && new_max ? (exclude_end? ? new_min...new_max : new_min..new_max) : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def subtract(range)
|
38
|
+
return [ self ] unless range
|
39
|
+
raise ArgumentError, 'must be the same end inclusive/non-inclusive type' unless exclude_end? == range.exclude_end?
|
40
|
+
|
41
|
+
common = self & range
|
42
|
+
return [ self ] unless common
|
43
|
+
|
44
|
+
# отрезок вычли полностью
|
45
|
+
return [ ] if common == self
|
46
|
+
|
47
|
+
if self.end == common.end
|
48
|
+
return [ Range.new( self.begin, common.begin, exclude_end? ) ]
|
49
|
+
end
|
50
|
+
|
51
|
+
if self.begin == common.begin
|
52
|
+
return [ Range.new( common.end, self.end, exclude_end? ) ]
|
53
|
+
end
|
54
|
+
|
55
|
+
[ Range.new( self.begin, common.begin, exclude_end? ),
|
56
|
+
Range.new( common.end, self.end, exclude_end? ) ]
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_method :&, :intersection
|
60
|
+
alias_method :-, :subtract
|
61
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module UniqSysOmega
|
2
|
+
module Tariffs
|
3
|
+
require 'mathn'
|
4
|
+
|
5
|
+
class SortableRange < Range
|
6
|
+
attr_accessor :filter
|
7
|
+
|
8
|
+
def <=>(other)
|
9
|
+
self.begin <=> other.begin
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_range
|
13
|
+
Range.new(self.begin, self.end, self.exclude_end?)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class RepeatingFilterPeriod
|
18
|
+
attr_accessor :starts_at
|
19
|
+
attr_accessor :length
|
20
|
+
attr_accessor :period
|
21
|
+
attr_accessor :filter
|
22
|
+
|
23
|
+
def initialize(starts_at, length, period, filter)
|
24
|
+
raise "period should be more than length, but this assertion is not met: #{length} <= #{period}" if length > period
|
25
|
+
@starts_at = starts_at.to_i
|
26
|
+
@length = length
|
27
|
+
@period = period
|
28
|
+
@filter = filter
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.from_hash(hash, filter=nil)
|
32
|
+
self.new(hash[:starts_at], hash[:length], hash[:period], filter || hash[:filter])
|
33
|
+
end
|
34
|
+
|
35
|
+
def ===(value)
|
36
|
+
includes?(value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def local_period(value)
|
40
|
+
local_period_start = @starts_at + ((value.to_i-@starts_at)/@period).floor * @period
|
41
|
+
SortableRange.new(local_period_start, local_period_start+@length, true).tap do |range|
|
42
|
+
range.filter = filter
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def includes?(value)
|
47
|
+
local_period(value).include?(value.to_i)
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_in_range(start, finish)
|
51
|
+
start, finish = start.to_i, finish.to_i
|
52
|
+
[ ].tap do |ranges|
|
53
|
+
local_period_start = @starts_at + ((start-@starts_at).to_f/@period.to_f).ceil * @period
|
54
|
+
while local_period_start+@length <= finish
|
55
|
+
ranges << SortableRange.new(local_period_start, local_period_start+@length, true).tap { |r| r.filter = filter }
|
56
|
+
local_period_start += @period
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
"RepeatingFilterPeriod(starts_at=#{starts_at}, length=#{length}, period=#{period})"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module RepeatingFilterPeriodsVerification
|
67
|
+
private
|
68
|
+
def fail_because_of_gaps!
|
69
|
+
periods_text = @periods.map { |p| " - starting: #{p.starts_at}, length: #{p.length}, period: #{p.period}" }.join("\n")
|
70
|
+
gaps_text = gapped_periods.map { |g| " - starting: #{g.begin}, length: #{g.end-g.begin}" }.join("\n")
|
71
|
+
raise "Inconsistent periods:\n#{periods_text}\n\nGaps:\n#{gaps_text}\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
def fail_because_of_intersections!
|
75
|
+
periods_text = intersecting_periods.map { |p| " - #{p[0]} and #{p[1]}" }.join("\n")
|
76
|
+
raise "Intersectings periods:\n#{periods_text}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def total_period_start
|
80
|
+
@periods.map { |p| p.starts_at }.min
|
81
|
+
end
|
82
|
+
|
83
|
+
def total_period_length
|
84
|
+
max_period_length = @periods.map { |p| p.starts_at + p.length }.max - total_period_start
|
85
|
+
[ @periods.map { |p| p.period }.inject(&:lcm), max_period_length ].max
|
86
|
+
end
|
87
|
+
|
88
|
+
def repeating_periods_chunks
|
89
|
+
@periods.map { |period| period.generate_in_range(total_period_start, total_period_start + total_period_length) }.flatten
|
90
|
+
end
|
91
|
+
|
92
|
+
def gapped_periods
|
93
|
+
@gapped_periods = @gapped_periods || [ (total_period_start ... total_period_start + total_period_length) ].tap do |uncovered_periods|
|
94
|
+
repeating_periods_chunks.each do |period|
|
95
|
+
uncovered_periods.map! { |uncovered_period| uncovered_period - (uncovered_period & period) }
|
96
|
+
uncovered_periods.flatten!
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def periods_consistent?
|
102
|
+
gapped_periods.size == 0
|
103
|
+
end
|
104
|
+
|
105
|
+
def intersecting_periods
|
106
|
+
@intersecting_periods = @intersecting_periods || [].tap do |intersecting|
|
107
|
+
chunks = repeating_periods_chunks.sort
|
108
|
+
while chunks.size >= 2
|
109
|
+
intersecting << [chunks[0], chunks[1]] unless (chunks[0] & chunks[1]).nil?
|
110
|
+
chunks.shift
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def periods_not_intersecting?
|
116
|
+
intersecting_periods.size == 0
|
117
|
+
end
|
118
|
+
|
119
|
+
def ensure_periods_are_correct!
|
120
|
+
fail_because_of_intersections! unless periods_not_intersecting?
|
121
|
+
fail_because_of_gaps! unless periods_consistent?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class RepeatingFilter < Filter
|
126
|
+
def start_permitted?(computer_id, login_id, options={})
|
127
|
+
period = period_matching_time(Time.now)
|
128
|
+
period.filter.start_permitted?(computer_id, login_id, options.merge({ :period => period.as_range }))
|
129
|
+
end
|
130
|
+
|
131
|
+
def permitted?(computer_id, login_id, options={})
|
132
|
+
period = period_matching_time(Time.now)
|
133
|
+
period.filter.permitted?(computer_id, login_id, options.merge({ :period => period.as_range }))
|
134
|
+
end
|
135
|
+
|
136
|
+
def calculate_cost(computer_id, login_id, started_at, amount, options={})
|
137
|
+
actual_periods(started_at, amount).inject(0) do |overall, p|
|
138
|
+
overall += p[:actual].filter.calculate_cost(computer_id, login_id, p[:actual].begin, p[:actual].end-p[:actual].begin, options.merge({ :period => p[:covering].as_range }))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def process_activity(unprocessed_activity, ft, options={})
|
143
|
+
actual_periods(unprocessed_activity.created_at, unprocessed_activity.amount).each do |p|
|
144
|
+
p[:actual].filter.process_activity(
|
145
|
+
alter_activity_period(unprocessed_activity, p[:actual].begin, p[:actual].end-p[:actual].begin),
|
146
|
+
ft,
|
147
|
+
options.merge({ :period => p[:covering].as_range})) or return false
|
148
|
+
end
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
protected
|
153
|
+
include RepeatingFilterPeriodsVerification
|
154
|
+
|
155
|
+
def after_initialize
|
156
|
+
@periods = [ ]
|
157
|
+
settings.to_hash.each do |period, settings|
|
158
|
+
@periods << RepeatingFilterPeriod.from_hash(period, TarifBuilder.build(Settings.new(settings), tarif_id))
|
159
|
+
end
|
160
|
+
ensure_periods_are_correct!
|
161
|
+
end
|
162
|
+
|
163
|
+
def actual_periods(started_at, amount)
|
164
|
+
covering_periods(started_at, started_at+amount).map do |period|
|
165
|
+
actual_period = ( period.begin ... period.end ) & ( started_at.to_i ... started_at.to_i+amount )
|
166
|
+
{
|
167
|
+
:covering => period,
|
168
|
+
:actual =>
|
169
|
+
SortableRange.new(Time.at(actual_period.begin),
|
170
|
+
Time.at(actual_period.end),
|
171
|
+
actual_period.exclude_end?).tap { |p| p.filter = period.filter }
|
172
|
+
} unless actual_period.nil?
|
173
|
+
end.compact
|
174
|
+
end
|
175
|
+
|
176
|
+
def covering_periods(time_start, time_end)
|
177
|
+
time_start -= total_period_length
|
178
|
+
time_end += total_period_length
|
179
|
+
@periods.map { |period| period.generate_in_range(time_start, time_end) }.flatten.sort
|
180
|
+
end
|
181
|
+
|
182
|
+
def period_matching_time(time)
|
183
|
+
@periods.detect { |p| p === time }.local_period(time)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|