bbmb 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/LICENSE.txt +339 -0
- data/Manifest.txt +91 -0
- data/README.txt +25 -0
- data/Rakefile +28 -0
- data/bin/admin +71 -0
- data/bin/bbmbd +61 -0
- data/lib/bbmb.rb +9 -0
- data/lib/bbmb/config.rb +106 -0
- data/lib/bbmb/html/state/change_password.rb +50 -0
- data/lib/bbmb/html/state/current_order.rb +81 -0
- data/lib/bbmb/html/state/customer.rb +109 -0
- data/lib/bbmb/html/state/customers.rb +52 -0
- data/lib/bbmb/html/state/favorites.rb +19 -0
- data/lib/bbmb/html/state/favorites_result.rb +21 -0
- data/lib/bbmb/html/state/global.rb +62 -0
- data/lib/bbmb/html/state/history.rb +95 -0
- data/lib/bbmb/html/state/info.rb +23 -0
- data/lib/bbmb/html/state/json.rb +16 -0
- data/lib/bbmb/html/state/login.rb +76 -0
- data/lib/bbmb/html/state/order.rb +21 -0
- data/lib/bbmb/html/state/orders.rb +19 -0
- data/lib/bbmb/html/state/result.rb +64 -0
- data/lib/bbmb/html/state/show_pass.rb +16 -0
- data/lib/bbmb/html/state/viral/admin.rb +41 -0
- data/lib/bbmb/html/state/viral/customer.rb +143 -0
- data/lib/bbmb/html/util/known_user.rb +51 -0
- data/lib/bbmb/html/util/multilingual.rb +18 -0
- data/lib/bbmb/html/util/session.rb +52 -0
- data/lib/bbmb/html/util/validator.rb +55 -0
- data/lib/bbmb/html/view/backorder.rb +24 -0
- data/lib/bbmb/html/view/change_password.rb +42 -0
- data/lib/bbmb/html/view/copyleft.rb +41 -0
- data/lib/bbmb/html/view/current_order.rb +482 -0
- data/lib/bbmb/html/view/customer.rb +152 -0
- data/lib/bbmb/html/view/customers.rb +145 -0
- data/lib/bbmb/html/view/favorites.rb +162 -0
- data/lib/bbmb/html/view/favorites_result.rb +26 -0
- data/lib/bbmb/html/view/foot.rb +21 -0
- data/lib/bbmb/html/view/head.rb +32 -0
- data/lib/bbmb/html/view/history.rb +60 -0
- data/lib/bbmb/html/view/info.rb +33 -0
- data/lib/bbmb/html/view/json.rb +20 -0
- data/lib/bbmb/html/view/list_prices.rb +51 -0
- data/lib/bbmb/html/view/login.rb +46 -0
- data/lib/bbmb/html/view/multilingual.rb +17 -0
- data/lib/bbmb/html/view/navigation.rb +30 -0
- data/lib/bbmb/html/view/order.rb +123 -0
- data/lib/bbmb/html/view/orders.rb +50 -0
- data/lib/bbmb/html/view/result.rb +107 -0
- data/lib/bbmb/html/view/search.rb +28 -0
- data/lib/bbmb/html/view/show_pass.rb +60 -0
- data/lib/bbmb/html/view/template.rb +61 -0
- data/lib/bbmb/model/customer.rb +96 -0
- data/lib/bbmb/model/order.rb +255 -0
- data/lib/bbmb/model/product.rb +99 -0
- data/lib/bbmb/model/promotion.rb +52 -0
- data/lib/bbmb/model/quota.rb +27 -0
- data/lib/bbmb/model/subject.rb +46 -0
- data/lib/bbmb/persistence/none.rb +6 -0
- data/lib/bbmb/persistence/odba.rb +42 -0
- data/lib/bbmb/persistence/odba/model/customer.rb +47 -0
- data/lib/bbmb/persistence/odba/model/order.rb +50 -0
- data/lib/bbmb/persistence/odba/model/product.rb +26 -0
- data/lib/bbmb/persistence/odba/model/quota.rb +12 -0
- data/lib/bbmb/util/invoicer.rb +126 -0
- data/lib/bbmb/util/mail.rb +140 -0
- data/lib/bbmb/util/multilingual.rb +57 -0
- data/lib/bbmb/util/numbers.rb +67 -0
- data/lib/bbmb/util/password_generator.rb +44 -0
- data/lib/bbmb/util/polling_manager.rb +135 -0
- data/lib/bbmb/util/server.rb +159 -0
- data/lib/bbmb/util/target_dir.rb +36 -0
- data/lib/bbmb/util/transfer_dat.rb +34 -0
- data/lib/bbmb/util/updater.rb +27 -0
- data/test/data/ydim.yml +2 -0
- data/test/model/test_customer.rb +75 -0
- data/test/model/test_order.rb +426 -0
- data/test/model/test_product.rb +238 -0
- data/test/model/test_promotion.rb +40 -0
- data/test/stub/persistence.rb +57 -0
- data/test/suite.rb +12 -0
- data/test/test_bbmb.rb +18 -0
- data/test/util/test_invoicer.rb +189 -0
- data/test/util/test_mail.rb +359 -0
- data/test/util/test_money.rb +71 -0
- data/test/util/test_password_generator.rb +27 -0
- data/test/util/test_polling_manager.rb +312 -0
- data/test/util/test_server.rb +189 -0
- data/test/util/test_target_dir.rb +82 -0
- data/test/util/test_transfer_dat.rb +45 -0
- metadata +190 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Customers -- bbmb.ch -- 18.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/global'
|
5
|
+
require 'bbmb/html/view/customers'
|
6
|
+
require 'bbmb/model/customer'
|
7
|
+
require 'ostruct'
|
8
|
+
|
9
|
+
module BBMB
|
10
|
+
module Html
|
11
|
+
module State
|
12
|
+
class Customers < Global
|
13
|
+
DIRECT_EVENT = :customers
|
14
|
+
VIEW = View::Customers
|
15
|
+
FILTER = [ :customer_id, :organisation, :plz, :city, :email ]
|
16
|
+
def init
|
17
|
+
start = Time.now
|
18
|
+
@model = BBMB.persistence.all(Model::Customer)
|
19
|
+
@sortby = [:organisation]
|
20
|
+
BBMB.logger.debug('State') {
|
21
|
+
sprintf("Customers#init: loaded %i customers in %1.5fs",
|
22
|
+
@model.size, Time.now - start)
|
23
|
+
}
|
24
|
+
@filter = Proc.new { |model|
|
25
|
+
if(filter = @session.event_bound_user_input(:filter))
|
26
|
+
pattern = Regexp.new(filter.gsub("*", ".*"), 'i')
|
27
|
+
model = model.select { |customer|
|
28
|
+
FILTER.any? { |key| pattern.match(customer.send(key).to_s) }
|
29
|
+
}
|
30
|
+
end
|
31
|
+
get_sortby!
|
32
|
+
model.sort! { |a, b| compare_entries(a, b) }
|
33
|
+
@sort_reverse && model.reverse!
|
34
|
+
page(model)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
def page(model)
|
38
|
+
page = OpenStruct.new
|
39
|
+
index = @session.user_input(:index).to_i
|
40
|
+
step = @session.user.pagestep || BBMB.config.pagestep
|
41
|
+
page.index = index
|
42
|
+
page.first = index + 1
|
43
|
+
page.step = step
|
44
|
+
page.total = model.size
|
45
|
+
page.customers = model[index, step]
|
46
|
+
page.last = index + page.customers.size
|
47
|
+
page
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Favorites -- bbmb.ch -- 28.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/global'
|
5
|
+
require 'bbmb/html/view/favorites'
|
6
|
+
|
7
|
+
module BBMB
|
8
|
+
module Html
|
9
|
+
module State
|
10
|
+
class Favorites < Global
|
11
|
+
DIRECT_EVENT = :favorites
|
12
|
+
VIEW = View::Favorites
|
13
|
+
def init
|
14
|
+
@model = _customer.favorites
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Result -- bbmb.ch -- 21.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/result'
|
5
|
+
require 'bbmb/html/view/favorites_result'
|
6
|
+
|
7
|
+
module BBMB
|
8
|
+
module Html
|
9
|
+
module State
|
10
|
+
class FavoritesResult < Result
|
11
|
+
VIEW = View::FavoritesResult
|
12
|
+
def direct_event
|
13
|
+
[:search_favorites, {:query => @query}]
|
14
|
+
end
|
15
|
+
def _order
|
16
|
+
_customer.favorites
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Global -- bbmb.ch -- 18.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'sbsm/state'
|
5
|
+
require 'bbmb/html/state/login'
|
6
|
+
require 'encoding/character/utf-8'
|
7
|
+
|
8
|
+
module BBMB
|
9
|
+
module Html
|
10
|
+
module State
|
11
|
+
class Global < SBSM::State
|
12
|
+
class << self
|
13
|
+
def mandatory(*keys)
|
14
|
+
define_method(:_mandatory) { keys }
|
15
|
+
define_method(:mandatory) { _mandatory }
|
16
|
+
define_method(:mandatory?) { |key| mandatory.include?(key) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def logout
|
20
|
+
@session.logout
|
21
|
+
State::Login.new(@session, nil)
|
22
|
+
end
|
23
|
+
def direct_arguments
|
24
|
+
if(keys = direct_argument_keys)
|
25
|
+
keys.inject({}) { |memo, key|
|
26
|
+
memo.store(key, @session.user_input(key))
|
27
|
+
memo
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
def direct_argument_keys
|
32
|
+
end
|
33
|
+
def direct_request?(event)
|
34
|
+
requested_event(event) == direct_event
|
35
|
+
end
|
36
|
+
def requested_event(event)
|
37
|
+
if(args = direct_arguments)
|
38
|
+
[ event, args ]
|
39
|
+
else
|
40
|
+
event
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def trigger(event)
|
44
|
+
if(direct_request?(event))
|
45
|
+
self
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
def user_input(*args)
|
51
|
+
data = super
|
52
|
+
data.each { |key, val|
|
53
|
+
if(val.is_a?(String))
|
54
|
+
data.store(key, val.empty? ? nil : u(val))
|
55
|
+
end
|
56
|
+
}
|
57
|
+
data
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::History -- bbmb.ch -- 03.10.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/global'
|
5
|
+
require 'bbmb/html/view/history'
|
6
|
+
|
7
|
+
module BBMB
|
8
|
+
module Html
|
9
|
+
module State
|
10
|
+
class History < Global
|
11
|
+
VIEW = View::History
|
12
|
+
class PositionFacade
|
13
|
+
attr_reader :quantity, :total
|
14
|
+
def initialize
|
15
|
+
@positions = []
|
16
|
+
@quantity = 0
|
17
|
+
@prices = []
|
18
|
+
@total = 0
|
19
|
+
end
|
20
|
+
def add(position)
|
21
|
+
@positions.push(position)
|
22
|
+
@quantity += position.quantity
|
23
|
+
@prices.push(position.price)
|
24
|
+
@total = position.total + @total
|
25
|
+
end
|
26
|
+
def order_count
|
27
|
+
@positions.size
|
28
|
+
end
|
29
|
+
def price_extremes
|
30
|
+
[@prices.min, @prices.max]
|
31
|
+
end
|
32
|
+
def respond_to?(key)
|
33
|
+
super || @positions.last.respond_to?(key)
|
34
|
+
end
|
35
|
+
def method_missing(key, *args, &block)
|
36
|
+
@positions.last.send(key, *args, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
class HistoryFacade
|
40
|
+
include Enumerable
|
41
|
+
attr_accessor :sorted
|
42
|
+
def initialize
|
43
|
+
@map = {}
|
44
|
+
@positions = []
|
45
|
+
end
|
46
|
+
def add(order)
|
47
|
+
order.each { |position|
|
48
|
+
add_position(position)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
def add_position(position)
|
52
|
+
key = position.article_number
|
53
|
+
pos = @map.fetch(key) {
|
54
|
+
pos = @map.store(key, PositionFacade.new)
|
55
|
+
@positions = @map.values
|
56
|
+
pos
|
57
|
+
}
|
58
|
+
pos.add(position)
|
59
|
+
end
|
60
|
+
def each(&block)
|
61
|
+
@positions.each(&block)
|
62
|
+
end
|
63
|
+
def empty?
|
64
|
+
@positions.empty?
|
65
|
+
end
|
66
|
+
def reverse!
|
67
|
+
@positions.reverse!
|
68
|
+
end
|
69
|
+
def sort!(*args, &block)
|
70
|
+
@positions.sort!(*args, &block)
|
71
|
+
end
|
72
|
+
def turnover
|
73
|
+
@positions.inject(0) { |inj, position|
|
74
|
+
position.total + inj
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
def init
|
79
|
+
@model = HistoryFacade.new
|
80
|
+
if(@customer = _customer)
|
81
|
+
@customer.orders.sort_by { |order| order.commit_time }.each { |order|
|
82
|
+
@model.add(order)
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
def direct_argument_keys
|
87
|
+
[:customer_id]
|
88
|
+
end
|
89
|
+
def direct_event
|
90
|
+
[:history, {:customer_id => @customer.customer_id}]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Info -- bbmb.ch -- 30.11.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/global'
|
5
|
+
require 'bbmb/html/view/info'
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
module BBMB
|
9
|
+
module Html
|
10
|
+
module State
|
11
|
+
class Info < Global
|
12
|
+
VIEW = Html::View::Info
|
13
|
+
def init
|
14
|
+
model = OpenStruct.new
|
15
|
+
model.event = @model[:event]
|
16
|
+
model.message = @model[:message]
|
17
|
+
@model = model
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Json -- bbmb.ch -- 26.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/view/json'
|
5
|
+
require 'sbsm/state'
|
6
|
+
|
7
|
+
module BBMB
|
8
|
+
module Html
|
9
|
+
module State
|
10
|
+
class Json < SBSM::State
|
11
|
+
VIEW = View::Json
|
12
|
+
VOLATILE = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Login -- bbmb.ch -- 15.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/info'
|
5
|
+
require 'bbmb/html/state/viral/admin'
|
6
|
+
require 'bbmb/html/state/viral/customer'
|
7
|
+
require 'bbmb/html/view/login'
|
8
|
+
require 'bbmb/util/mail'
|
9
|
+
require 'sbsm/state'
|
10
|
+
require 'yus/session'
|
11
|
+
|
12
|
+
module BBMB
|
13
|
+
module Html
|
14
|
+
module State
|
15
|
+
class Login < SBSM::State
|
16
|
+
VIEW = View::Login
|
17
|
+
def login
|
18
|
+
reconsider_permissions(@session.login)
|
19
|
+
trigger(:home)
|
20
|
+
rescue Yus::UnknownEntityError
|
21
|
+
@errors.store(:email, create_error(:e_authentication_error, :email, nil))
|
22
|
+
self
|
23
|
+
rescue Yus::AuthenticationError
|
24
|
+
@errors.store(:pass, create_error(:e_authentication_error, :pass, nil))
|
25
|
+
self
|
26
|
+
end
|
27
|
+
def home
|
28
|
+
self
|
29
|
+
end
|
30
|
+
def request_access
|
31
|
+
lnf = @session.lookandfeel
|
32
|
+
if(lnf.enabled?(:request_access, false))
|
33
|
+
keys = [ :firstname, :lastname, :organisation, :address1, :plz, :city,
|
34
|
+
:phone_business, :email, :customer_id, ]
|
35
|
+
input = user_input(keys, keys)
|
36
|
+
if(error?)
|
37
|
+
@errors.clear
|
38
|
+
@errors.store(:all, create_error(:e_need_all_fields, :all, nil))
|
39
|
+
self
|
40
|
+
else
|
41
|
+
body = <<-EOS
|
42
|
+
Vorname: #{input[:firstname]}
|
43
|
+
Name: #{input[:lastname]}
|
44
|
+
Tierarztpraxis: #{input[:organisation]}
|
45
|
+
Strasse / Nr.: #{input[:address1]}
|
46
|
+
PLZ: #{input[:plz]}
|
47
|
+
Ort: #{input[:city]}
|
48
|
+
Tel. Praxis: #{input[:phone_business]}
|
49
|
+
E-Mail Adresse: #{input[:email]}
|
50
|
+
TVS/Virbac-Nr: #{input[:customer_id]}
|
51
|
+
EOS
|
52
|
+
BBMB::Util::Mail.send_request(input[:email], input[:organisation], body)
|
53
|
+
Info.new(@session, :message => :request_sent, :event => :logout)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
private
|
58
|
+
def reconsider_permissions(user)
|
59
|
+
viral_modules(user) { |mod|
|
60
|
+
self.extend(mod)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
def viral_modules(user)
|
64
|
+
[
|
65
|
+
['.Admin', State::Viral::Admin],
|
66
|
+
['.Customer', State::Viral::Customer],
|
67
|
+
].each { |key, mod|
|
68
|
+
if(user.allowed?("login", BBMB.config.auth_domain + key))
|
69
|
+
yield mod
|
70
|
+
end
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Order -- bbmb.ch -- 27.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/global'
|
5
|
+
require 'bbmb/html/view/order'
|
6
|
+
|
7
|
+
module BBMB
|
8
|
+
module Html
|
9
|
+
module State
|
10
|
+
class Order < Global
|
11
|
+
VIEW = View::Order
|
12
|
+
def direct_argument_keys
|
13
|
+
[:order_id]
|
14
|
+
end
|
15
|
+
def direct_event
|
16
|
+
[:order, {:order_id => @model.order_id}]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Orders -- bbmb.ch -- 27.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/global'
|
5
|
+
require 'bbmb/html/view/orders'
|
6
|
+
|
7
|
+
module BBMB
|
8
|
+
module Html
|
9
|
+
module State
|
10
|
+
class Orders < Global
|
11
|
+
DIRECT_EVENT = :orders
|
12
|
+
VIEW = View::Orders
|
13
|
+
def init
|
14
|
+
@model = _customer.orders
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Html::State::Result -- bbmb.ch -- 21.09.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'bbmb/html/state/global'
|
5
|
+
require 'bbmb/html/view/result'
|
6
|
+
|
7
|
+
module BBMB
|
8
|
+
module Html
|
9
|
+
module State
|
10
|
+
class Result < Global
|
11
|
+
class Result
|
12
|
+
include Enumerable
|
13
|
+
attr_reader :order, :products
|
14
|
+
def initialize(order, products)
|
15
|
+
@order, @products = order, products
|
16
|
+
end
|
17
|
+
def each(&block)
|
18
|
+
@products.each(&block)
|
19
|
+
end
|
20
|
+
def empty?
|
21
|
+
@products.empty?
|
22
|
+
end
|
23
|
+
def ordered_quantity(article_number)
|
24
|
+
@order.quantity(article_number)
|
25
|
+
end
|
26
|
+
def quota(article_id)
|
27
|
+
@order.quota(article_id)
|
28
|
+
end
|
29
|
+
def reverse!
|
30
|
+
@products.reverse!
|
31
|
+
end
|
32
|
+
def size
|
33
|
+
@products.size
|
34
|
+
end
|
35
|
+
def sort_by(&block)
|
36
|
+
@products = @products.sort_by(&block)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
def sort!(*args, &block)
|
40
|
+
@products.sort!(*args, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
VIEW = View::Result
|
44
|
+
def init
|
45
|
+
@query = @session.persistent_user_input(:query)
|
46
|
+
products = Model::Product.search_by_description(@query)
|
47
|
+
unless(@session.lookandfeel.enabled? :free_products, false)
|
48
|
+
products = products.select { |product| product.price }
|
49
|
+
end
|
50
|
+
@model = Result.new _order, products
|
51
|
+
end
|
52
|
+
def direct_argument_keys
|
53
|
+
[:query]
|
54
|
+
end
|
55
|
+
def direct_event
|
56
|
+
[:search, {:query => @query}]
|
57
|
+
end
|
58
|
+
def _order
|
59
|
+
_customer.current_order
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|