mls 0.8.2 → 0.9.0
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.
- checksums.yaml +4 -4
- data/lib/mls/models/listing.rb +112 -52
- data/mls.gemspec +1 -1
- data/test/units/models/test_listing.rb +80 -12
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a331b7024dd4a8a7511c62cdada76b4cf718d9a1
|
4
|
+
data.tar.gz: a5f2066901baacf9976b43c8648c23d6c68357df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4efe6ad46a1c9f8a5fc2eb8510f9cce67597d467c1e019ab551b0eff79b0ae5870b8eae54ae3b8391feb629616bd3a393ba331e774f8514d2951d5a955ddff6f
|
7
|
+
data.tar.gz: 956edb5fded23ff4dcdd15fc3a5ad10440ba574c10a0aac975733595a12e42e18633e366c50b50ca38e38d87f22dcb5ed36f9bf4a46eaed1140d941c6d38a7cf
|
data/lib/mls/models/listing.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
class MLS::Listing < MLS::Resource
|
2
2
|
|
3
|
-
|
4
|
-
LEASE_STATES = %w(listed leased)
|
3
|
+
STATES = %w(processing listed leased expired)
|
5
4
|
TYPES = %w(lease sublease coworking_space)
|
6
5
|
SPACE_TYPES = %w(unit floor building)
|
7
6
|
LEASE_TERMS = ['Full Service', 'NNN', 'Modified Gross']
|
@@ -9,7 +8,7 @@ class MLS::Listing < MLS::Resource
|
|
9
8
|
USES = ["Office", "Creative", "Loft", "Medical Office", "Flex Space", "R&D", "Office Showroom", "Industrial", "Retail"]
|
10
9
|
SOURCE_TYPES = %w(website flyer)
|
11
10
|
CHANNELS = %w(excavator mls staircase broker_dashboard)
|
12
|
-
|
11
|
+
|
13
12
|
property :id, Fixnum, :serialize => :false
|
14
13
|
property :address_id, Fixnum, :serialize => :false
|
15
14
|
property :slug, String, :serialize => :false
|
@@ -21,16 +20,16 @@ class MLS::Listing < MLS::Resource
|
|
21
20
|
property :source_type, String, :serialize => :if_present
|
22
21
|
property :channel, String, :serialize => :if_present
|
23
22
|
property :photo_ids, Array, :serialize => :if_present
|
24
|
-
|
23
|
+
|
25
24
|
property :name, String
|
26
25
|
property :type, String, :default => 'lease'
|
27
|
-
property :
|
28
|
-
property :
|
26
|
+
property :state, String, :default => 'listed'
|
27
|
+
property :visible, Boolean, :default => true
|
29
28
|
property :space_type, String, :default => 'unit'
|
30
29
|
property :unit, String
|
31
30
|
property :floor, Fixnum
|
32
31
|
property :description, String
|
33
|
-
|
32
|
+
|
34
33
|
property :size, Fixnum
|
35
34
|
property :maximum_contiguous_size, Fixnum
|
36
35
|
property :minimum_divisible_size, Fixnum
|
@@ -41,12 +40,8 @@ class MLS::Listing < MLS::Resource
|
|
41
40
|
property :rate_units, String, :default => '/sqft/mo'
|
42
41
|
property :low_rate, Decimal, :serialize => :false
|
43
42
|
property :high_rate, Decimal, :serialize => :false
|
44
|
-
property :rate_per_sqft_per_month, Decimal, :serialize => :false # need to make write methods for these that set rate to the according rate units. not accepted on api
|
45
|
-
property :rate_per_sqft_per_year, Decimal, :serialize => :false
|
46
|
-
property :rate_per_month, Decimal, :serialize => :false
|
47
|
-
property :rate_per_year, Decimal, :serialize => :false
|
48
43
|
property :sublease_expiration, DateTime
|
49
|
-
|
44
|
+
|
50
45
|
property :forecast_rate_per_year, Decimal, :serialize => :false
|
51
46
|
property :forecast_rate_per_month, Decimal, :serialize => :false
|
52
47
|
property :forecast_rate_per_sqft_per_month, Decimal, :serialize => :false
|
@@ -60,7 +55,7 @@ class MLS::Listing < MLS::Resource
|
|
60
55
|
property :offices, Fixnum
|
61
56
|
property :conference_rooms, Fixnum
|
62
57
|
property :bathrooms, Fixnum
|
63
|
-
|
58
|
+
|
64
59
|
property :kitchen, Boolean
|
65
60
|
property :showers, Boolean
|
66
61
|
property :patio, Boolean
|
@@ -69,7 +64,7 @@ class MLS::Listing < MLS::Resource
|
|
69
64
|
property :furniture_available, Boolean
|
70
65
|
property :natural_light, Boolean
|
71
66
|
property :high_ceilings, Boolean
|
72
|
-
|
67
|
+
|
73
68
|
property :created_at, DateTime, :serialize => :false
|
74
69
|
property :updated_at, DateTime, :serialize => :false
|
75
70
|
property :touched_at, DateTime, :serialize => :false
|
@@ -77,17 +72,17 @@ class MLS::Listing < MLS::Resource
|
|
77
72
|
property :photography_requested_on, DateTime, :serialize => :false
|
78
73
|
|
79
74
|
property :awesome_score, Fixnum
|
80
|
-
property :awesome_needs, Array, :serialize => :if_present
|
75
|
+
property :awesome_needs, Array, :serialize => :if_present
|
81
76
|
property :awesome_label, String
|
82
77
|
|
83
78
|
property :flyer_id, Fixnum, :serialize => :if_present
|
84
79
|
property :floorplan_id, Fixnum, :serialize => :if_present
|
85
|
-
|
80
|
+
|
86
81
|
property :avatar_digest, String, :serialize => false
|
87
|
-
|
82
|
+
|
88
83
|
# Counter Caches
|
89
84
|
property :photos_count, Fixnum, :serialize => :false
|
90
|
-
|
85
|
+
|
91
86
|
attr_accessor :address, :agents, :account, :photos, :flyer, :floorplan, :videos, :similar_photos
|
92
87
|
|
93
88
|
def avatar(size='150x100#', protocol='http')
|
@@ -97,42 +92,27 @@ class MLS::Listing < MLS::Resource
|
|
97
92
|
address.avatar(size, protocol)
|
98
93
|
end
|
99
94
|
end
|
100
|
-
|
101
|
-
def processing?
|
102
|
-
workflow_state == 'processing'
|
103
|
-
end
|
104
|
-
|
105
|
-
def leased?
|
106
|
-
lease_state == 'leased'
|
107
|
-
end
|
108
|
-
|
109
|
-
def active?
|
110
|
-
lease_state == 'listed' && workflow_state == 'visible'
|
111
|
-
end
|
112
|
-
|
113
|
-
def inactive?
|
114
|
-
!self.active?
|
115
|
-
end
|
116
|
-
|
95
|
+
|
117
96
|
def lease?
|
118
97
|
type == 'lease'
|
119
98
|
end
|
120
|
-
|
121
99
|
def sublease?
|
122
100
|
type == 'sublease'
|
123
101
|
end
|
124
|
-
|
125
102
|
def coworking?
|
126
103
|
type == 'coworking_space'
|
127
104
|
end
|
128
105
|
|
129
|
-
|
106
|
+
def leased?
|
107
|
+
state == 'leased'
|
108
|
+
end
|
109
|
+
|
130
110
|
def space_name
|
131
111
|
return name if !name.nil?
|
132
|
-
|
112
|
+
|
133
113
|
case space_type
|
134
114
|
when 'unit'
|
135
|
-
if unit
|
115
|
+
if unit
|
136
116
|
"Unit #{unit}"
|
137
117
|
elsif floor
|
138
118
|
"#{floor.ordinalize} Floor"
|
@@ -144,7 +124,7 @@ class MLS::Listing < MLS::Resource
|
|
144
124
|
when 'floor'
|
145
125
|
if floor
|
146
126
|
"#{floor.ordinalize} Floor"
|
147
|
-
elsif unit
|
127
|
+
elsif unit
|
148
128
|
"Unit #{unit}"
|
149
129
|
else
|
150
130
|
"Floor Lease"
|
@@ -152,8 +132,87 @@ class MLS::Listing < MLS::Resource
|
|
152
132
|
end
|
153
133
|
end
|
154
134
|
|
135
|
+
# TODO: remove /desk/mo conversions
|
136
|
+
def rate(units='/sqft/mo')
|
137
|
+
|
138
|
+
if rate_units == '/sqft/mo'
|
139
|
+
if units == '/sqft/mo'
|
140
|
+
@rate
|
141
|
+
elsif units == '/sqft/yr'
|
142
|
+
@rate * 12.0
|
143
|
+
elsif units == '/mo'
|
144
|
+
@rate * @size
|
145
|
+
elsif units == '/yr'
|
146
|
+
@rate * @size * 12.0
|
147
|
+
elsif units == '/desk/mo'
|
148
|
+
@rate * 200.0
|
149
|
+
else
|
150
|
+
raise "Invalid rate conversion (#{rate_units} => #{units})"
|
151
|
+
end
|
152
|
+
|
153
|
+
elsif rate_units == '/sqft/yr'
|
154
|
+
if units == '/sqft/mo'
|
155
|
+
@rate / 12.0
|
156
|
+
elsif units == '/sqft/yr'
|
157
|
+
@rate
|
158
|
+
elsif units == '/mo'
|
159
|
+
(@rate / 12.0) * @size
|
160
|
+
elsif units == '/yr'
|
161
|
+
@rate * @size
|
162
|
+
elsif units == '/desk/mo'
|
163
|
+
(@rate / 12.0) * 200.0
|
164
|
+
else
|
165
|
+
raise "Invalid rate conversion (#{rate_units} => #{units})"
|
166
|
+
end
|
167
|
+
|
168
|
+
elsif rate_units == '/mo'
|
169
|
+
if units == '/sqft/mo'
|
170
|
+
@rate / @size.to_f
|
171
|
+
elsif units == '/sqft/yr'
|
172
|
+
(@rate * 12) / @size.to_f
|
173
|
+
elsif units == '/mo'
|
174
|
+
@rate
|
175
|
+
elsif units == '/yr'
|
176
|
+
@rate * 12
|
177
|
+
elsif units == '/desk/mo'
|
178
|
+
(@rate / @size.to_f) * 200.0
|
179
|
+
else
|
180
|
+
raise "Invalid rate conversion (#{rate_units} => #{units})"
|
181
|
+
end
|
155
182
|
|
156
|
-
|
183
|
+
elsif rate_units == '/yr'
|
184
|
+
if units == '/sqft/mo'
|
185
|
+
(@rate / 12.0) / @size.to_f
|
186
|
+
elsif units == '/sqft/yr'
|
187
|
+
@rate / @size.to_f
|
188
|
+
elsif units == '/mo'
|
189
|
+
@rate / 12.0
|
190
|
+
elsif units == '/yr'
|
191
|
+
@rate
|
192
|
+
elsif units == '/desk/mo'
|
193
|
+
((@rate / 12.0) / @size.to_f) * 200.0
|
194
|
+
else
|
195
|
+
raise "Invalid rate conversion (#{rate_units} => #{units})"
|
196
|
+
end
|
197
|
+
|
198
|
+
elsif rate_units == '/desk/mo'
|
199
|
+
if units == '/sqft/mo'
|
200
|
+
@rate / 200.0
|
201
|
+
elsif units == '/sqft/yr'
|
202
|
+
((@rate / 200.0) * 12) / @size.to_f
|
203
|
+
elsif units == '/mo'
|
204
|
+
@rate
|
205
|
+
elsif units == '/yr'
|
206
|
+
@rate * 12
|
207
|
+
elsif units == '/desk/mo'
|
208
|
+
@rate
|
209
|
+
else
|
210
|
+
raise "Invalid rate conversion (#{rate_units} => #{units})"
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
157
216
|
# Creates a tour request for the listing.
|
158
217
|
#
|
159
218
|
# Paramaters::
|
@@ -174,13 +233,13 @@ class MLS::Listing < MLS::Resource
|
|
174
233
|
# listing = MLS::Listing.find(@id)
|
175
234
|
# info => {:company => 'name', :population => 10, :funding => 'string', :move_id => '2012-09-12'}
|
176
235
|
# listing.request_tour('name', 'email@address.com', info) # => #<MLS::Tour>
|
177
|
-
#
|
236
|
+
#
|
178
237
|
# listing.request_tour('', 'emai', info) # => #<MLS::Tour> will have errors on account
|
179
238
|
def request_tour(account, tour={})
|
180
239
|
MLS::Tour.create(id, account, tour)
|
181
240
|
end
|
182
|
-
|
183
|
-
|
241
|
+
|
242
|
+
|
184
243
|
def create
|
185
244
|
MLS.post('/listings', {:listing => to_hash}, 201, 400) do |response, code|
|
186
245
|
raise MLS::Exception::UnexpectedResponse if ![201, 400].include?(code)
|
@@ -193,7 +252,7 @@ class MLS::Listing < MLS::Resource
|
|
193
252
|
MLS.put("/listings/#{id}", {:listing => to_hash}, 400) do |response, code|
|
194
253
|
if code == 200 || code == 400
|
195
254
|
MLS::Listing::Parser.update(self, response.body)
|
196
|
-
code == 200
|
255
|
+
code == 200
|
197
256
|
else
|
198
257
|
raise MLS::Exception::UnexpectedResponse, code
|
199
258
|
end
|
@@ -208,7 +267,7 @@ class MLS::Listing < MLS::Resource
|
|
208
267
|
hash[:videos_attributes] = videos.map(&:to_hash) unless videos.blank?
|
209
268
|
hash
|
210
269
|
end
|
211
|
-
|
270
|
+
|
212
271
|
def to_param
|
213
272
|
"#{address.to_param}/#{id}"
|
214
273
|
end
|
@@ -233,7 +292,7 @@ class MLS::Listing < MLS::Resource
|
|
233
292
|
result
|
234
293
|
end
|
235
294
|
|
236
|
-
def similar
|
295
|
+
def similar
|
237
296
|
[] # Similar Listings not supported for now
|
238
297
|
end
|
239
298
|
|
@@ -244,7 +303,7 @@ class MLS::Listing < MLS::Resource
|
|
244
303
|
MLS::Listing::Parser.parse(response.body)
|
245
304
|
end
|
246
305
|
|
247
|
-
# currently supported options are filters, page, per_page, offset, order
|
306
|
+
# currently supported options are filters, page, per_page, offset, order
|
248
307
|
def all(options={})
|
249
308
|
response = MLS.get('/listings', options)
|
250
309
|
MLS::Listing::Parser.parse_collection(response.body)
|
@@ -270,7 +329,7 @@ end
|
|
270
329
|
|
271
330
|
|
272
331
|
class MLS::Listing::Parser < MLS::Parser
|
273
|
-
|
332
|
+
|
274
333
|
def photos=(photos)
|
275
334
|
@object.photos = photos.map {|p| MLS::Photo::Parser.build(p)}
|
276
335
|
end
|
@@ -292,12 +351,13 @@ class MLS::Listing::Parser < MLS::Parser
|
|
292
351
|
def flyer=(flyer)
|
293
352
|
@object.flyer = MLS::Flyer::Parser.build(flyer)
|
294
353
|
end
|
295
|
-
|
354
|
+
|
296
355
|
def address=(address)
|
297
356
|
@object.address = MLS::Address::Parser.build(address)
|
298
357
|
end
|
299
|
-
|
358
|
+
|
300
359
|
def agents=(agents)
|
301
360
|
@object.agents = agents.map {|a| MLS::Account::Parser.build(a) }
|
302
361
|
end
|
362
|
+
|
303
363
|
end
|
data/mls.gemspec
CHANGED
@@ -4,7 +4,7 @@ class TestListing < ::Test::Unit::TestCase
|
|
4
4
|
|
5
5
|
def test_properties
|
6
6
|
listing = MLS::Listing.new
|
7
|
-
|
7
|
+
|
8
8
|
assert listing.respond_to?(:id)
|
9
9
|
assert listing.respond_to?(:address_id)
|
10
10
|
assert listing.respond_to?(:use)
|
@@ -22,8 +22,6 @@ class TestListing < ::Test::Unit::TestCase
|
|
22
22
|
assert listing.respond_to?(:lease_terms)
|
23
23
|
assert listing.respond_to?(:rate)
|
24
24
|
assert listing.respond_to?(:rate_units)
|
25
|
-
assert listing.respond_to?(:rate_per_sqft_per_month)
|
26
|
-
assert listing.respond_to?(:rate_per_sqft_per_year)
|
27
25
|
assert listing.respond_to?(:sublease_expiration)
|
28
26
|
assert listing.respond_to?(:available_on)
|
29
27
|
assert listing.respond_to?(:maximum_term_length)
|
@@ -42,20 +40,20 @@ class TestListing < ::Test::Unit::TestCase
|
|
42
40
|
assert listing.respond_to?(:created_at)
|
43
41
|
assert listing.respond_to?(:updated_at)
|
44
42
|
end
|
45
|
-
|
43
|
+
|
46
44
|
def test_attr_accessors
|
47
45
|
listing = MLS::Listing.new
|
48
|
-
|
46
|
+
|
49
47
|
assert listing.respond_to?(:address)
|
50
48
|
assert listing.respond_to?(:agents)
|
51
49
|
end
|
52
|
-
|
50
|
+
|
53
51
|
def test_instance_methods
|
54
52
|
listing = MLS::Listing.new
|
55
|
-
|
53
|
+
|
56
54
|
assert listing.respond_to?(:photos)
|
57
55
|
end
|
58
|
-
|
56
|
+
|
59
57
|
def test_class_methods
|
60
58
|
assert MLS::Listing.respond_to?(:find)
|
61
59
|
end
|
@@ -65,8 +63,8 @@ class TestListing < ::Test::Unit::TestCase
|
|
65
63
|
@name = Faker::Name.name
|
66
64
|
@email = Faker::Internet.email
|
67
65
|
tr = @listing.request_tour(@name, @email)
|
68
|
-
|
69
|
-
|
66
|
+
|
67
|
+
|
70
68
|
assert_equal({}, tr.errors)
|
71
69
|
assert_equal({}, tr.account.errors)
|
72
70
|
# TODO assert_equal({}, tr.listing.errors)
|
@@ -87,7 +85,7 @@ class TestListing < ::Test::Unit::TestCase
|
|
87
85
|
test '#request_tour for email on an account' do
|
88
86
|
@account = FactoryGirl.create(:account)
|
89
87
|
@listing = FactoryGirl.create(:listing)
|
90
|
-
|
88
|
+
|
91
89
|
tr = @listing.request_tour(@account.name, @account.email)
|
92
90
|
assert_equal({}, tr.errors)
|
93
91
|
assert_equal({}, tr.account.errors)
|
@@ -164,7 +162,7 @@ class TestListing < ::Test::Unit::TestCase
|
|
164
162
|
|
165
163
|
info = {:company => '42Floors', :population => 10, :funding => 'string thing', :move_in_date => '2012-09-12'}
|
166
164
|
tr = @listing.request_tour(Faker::Name.name, Faker::Internet.email, info)
|
167
|
-
|
165
|
+
|
168
166
|
assert tr.id
|
169
167
|
assert_equal '42Floors', info[:company]
|
170
168
|
assert_equal 10, info[:population]
|
@@ -179,4 +177,74 @@ class TestListing < ::Test::Unit::TestCase
|
|
179
177
|
assert_equal '2012-09-12', info[:move_in_date]
|
180
178
|
end
|
181
179
|
|
180
|
+
def test_rate_per_sqft_per_month
|
181
|
+
listing = MLS::Listing.new(:rate => 10.5, :rate_units => '/sqft/mo', :size => 5)
|
182
|
+
|
183
|
+
assert_equal 10.5, listing.rate
|
184
|
+
assert_equal 10.5, listing.rate('/sqft/mo')
|
185
|
+
assert_equal 126, listing.rate('/sqft/yr')
|
186
|
+
assert_equal 52.5, listing.rate('/mo')
|
187
|
+
assert_equal 630, listing.rate('/yr')
|
188
|
+
assert_equal 2100, listing.rate('/desk/mo')
|
189
|
+
assert_raises RuntimeError do
|
190
|
+
listing.rate('/random')
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_rate_per_sqft_per_year
|
195
|
+
listing = MLS::Listing.new(:rate => 126, :rate_units => '/sqft/yr', :size => 5)
|
196
|
+
|
197
|
+
assert_equal 10.5, listing.rate
|
198
|
+
assert_equal 10.5, listing.rate('/sqft/mo')
|
199
|
+
assert_equal 126, listing.rate('/sqft/yr')
|
200
|
+
assert_equal 52.5, listing.rate('/mo')
|
201
|
+
assert_equal 630, listing.rate('/yr')
|
202
|
+
assert_equal 2100, listing.rate('/desk/mo')
|
203
|
+
assert_raises RuntimeError do
|
204
|
+
listing.rate('/random')
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_rate_per_month
|
209
|
+
listing = MLS::Listing.new(:rate => 52.5, :rate_units => '/mo', :size => 5)
|
210
|
+
|
211
|
+
assert_equal 10.5, listing.rate
|
212
|
+
assert_equal 10.5, listing.rate('/sqft/mo')
|
213
|
+
assert_equal 126, listing.rate('/sqft/yr')
|
214
|
+
assert_equal 52.5, listing.rate('/mo')
|
215
|
+
assert_equal 630, listing.rate('/yr')
|
216
|
+
assert_equal 2100, listing.rate('/desk/mo')
|
217
|
+
assert_raises RuntimeError do
|
218
|
+
listing.rate('/random')
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_rate_per_year
|
223
|
+
listing = MLS::Listing.new(:rate => 630, :rate_units => '/yr', :size => 5)
|
224
|
+
|
225
|
+
assert_equal 10.5, listing.rate
|
226
|
+
assert_equal 10.5, listing.rate('/sqft/mo')
|
227
|
+
assert_equal 126, listing.rate('/sqft/yr')
|
228
|
+
assert_equal 52.5, listing.rate('/mo')
|
229
|
+
assert_equal 630, listing.rate('/yr')
|
230
|
+
assert_equal 2100, listing.rate('/desk/mo')
|
231
|
+
assert_raises RuntimeError do
|
232
|
+
listing.rate('/random')
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_rate_per_desk_per_month
|
237
|
+
listing = MLS::Listing.new(:rate => 630, :rate_units => '/yr', :size => 5)
|
238
|
+
|
239
|
+
assert_equal 10.5, listing.rate
|
240
|
+
assert_equal 10.5, listing.rate('/sqft/mo')
|
241
|
+
assert_equal 126, listing.rate('/sqft/yr')
|
242
|
+
assert_equal 52.5, listing.rate('/mo')
|
243
|
+
assert_equal 630, listing.rate('/yr')
|
244
|
+
assert_equal 2100, listing.rate('/desk/mo')
|
245
|
+
assert_raises RuntimeError do
|
246
|
+
listing.rate('/random')
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
182
250
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James R. Bracy
|
@@ -273,7 +273,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
273
273
|
version: '0'
|
274
274
|
requirements: []
|
275
275
|
rubyforge_project: mls
|
276
|
-
rubygems_version: 2.0.
|
276
|
+
rubygems_version: 2.0.2
|
277
277
|
signing_key:
|
278
278
|
specification_version: 4
|
279
279
|
summary: 42Floors MLS Client
|