mls 1.5.0 → 1.5.1
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.rb +9 -27
- data/lib/mls/account.rb +120 -16
- data/lib/mls/accounts_region.rb +6 -0
- data/lib/mls/action.rb +18 -0
- data/lib/mls/api_key.rb +5 -0
- data/lib/mls/coworking_space.rb +16 -2
- data/lib/mls/credit_card.rb +11 -0
- data/lib/mls/datum.rb +5 -0
- data/lib/mls/document.rb +8 -26
- data/lib/mls/email.rb +2 -1
- data/lib/mls/email_address.rb +5 -0
- data/lib/mls/email_digest.rb +10 -0
- data/lib/mls/event.rb +30 -5
- data/lib/mls/flyer.rb +11 -0
- data/lib/mls/image_ordering.rb +1 -1
- data/lib/mls/impression_count.rb +21 -0
- data/lib/mls/inquiry.rb +49 -1
- data/lib/mls/invoice.rb +16 -0
- data/lib/mls/lead.rb +1 -5
- data/lib/mls/listing.rb +57 -22
- data/lib/mls/membership.rb +27 -0
- data/lib/mls/metadatum.rb +3 -0
- data/lib/mls/mistake.rb +1 -1
- data/lib/mls/organization.rb +3 -1
- data/lib/mls/ownership.rb +8 -0
- data/lib/mls/phone.rb +7 -1
- data/lib/mls/property.rb +95 -71
- data/lib/mls/region.rb +6 -0
- data/lib/mls/session.rb +2 -2
- data/lib/mls/source.rb +3 -2
- data/lib/mls/subscription.rb +24 -0
- data/lib/mls/task.rb +14 -8
- data/lib/mls/team.rb +6 -0
- data/lib/mls/view.rb +24 -0
- data/lib/mls/webpage.rb +1 -1
- data/mls.gemspec +4 -1
- metadata +59 -8
- data/lib/mls/agency.rb +0 -6
- data/lib/mls/change.rb +0 -40
- data/lib/mls/event_action.rb +0 -6
- data/lib/mls/green_sheet.rb +0 -5
- data/lib/mls/unit.rb +0 -38
data/lib/mls/flyer.rb
ADDED
data/lib/mls/image_ordering.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
class ImpressionCount < MLS::Model
|
2
|
+
belongs_to :subject, polymorphic: true
|
3
|
+
|
4
|
+
def self.by_day(**body)
|
5
|
+
req = Net::HTTP::Get.new("/impression_counts/by_day")
|
6
|
+
req.body = body.to_json
|
7
|
+
connection.instance_variable_get(:@connection).send_request(req).body
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.by_week(**body)
|
11
|
+
req = Net::HTTP::Get.new("/impression_counts/by_week")
|
12
|
+
req.body = body.to_json
|
13
|
+
connection.instance_variable_get(:@connection).send_request(req).body
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.by_month(**body)
|
17
|
+
req = Net::HTTP::Get.new("/impression_counts/by_month")
|
18
|
+
req.body = body.to_json
|
19
|
+
connection.instance_variable_get(:@connection).send_request(req).body
|
20
|
+
end
|
21
|
+
end
|
data/lib/mls/inquiry.rb
CHANGED
@@ -1,11 +1,59 @@
|
|
1
1
|
class Inquiry < MLS::Model
|
2
2
|
|
3
3
|
has_many :emails
|
4
|
-
|
4
|
+
has_many :leads
|
5
5
|
belongs_to :subject, polymorphic: true
|
6
|
+
belongs_to :account
|
7
|
+
|
8
|
+
accepts_nested_attributes_for :account
|
6
9
|
|
7
10
|
def property
|
8
11
|
subject.is_a? MLS::Model::Listing ? subject.property : subject
|
9
12
|
end
|
13
|
+
|
14
|
+
def account_attributes=(account_attrs)
|
15
|
+
account_attrs = account_attrs&.with_indifferent_access
|
16
|
+
self.account = if account_attrs.nil?
|
17
|
+
nil
|
18
|
+
elsif account_attrs["id"]
|
19
|
+
accnt = Account.find(account_attrs.delete("id"))
|
20
|
+
accnt.assign_attributes(account_attrs)
|
21
|
+
accnt
|
22
|
+
else
|
23
|
+
if account_attrs["email_addresses_attributes"]
|
24
|
+
email_address = EmailAddress.filter(address: account_attrs["email_addresses_attributes"].map{|ea| ea["address"].downcase}, account_id: true).first
|
25
|
+
accnt = email_address.account
|
26
|
+
accnt.assign_attributes(account_attrs)
|
27
|
+
end
|
28
|
+
|
29
|
+
if !accnt && account_attrs["phones_attributes"]
|
30
|
+
phone = Phone.filter(number: account_attrs["phones_attributes"].map{|p| PhoneValidator.normalize(p["number"])}, account_id: true).first
|
31
|
+
accnt = phone.account
|
32
|
+
accnt.assign_attributes(account_attrs)
|
33
|
+
end
|
34
|
+
|
35
|
+
if !accnt
|
36
|
+
accnt = Account.new(account_attrs)
|
37
|
+
end
|
38
|
+
|
39
|
+
accnt
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.by_day(filter)
|
44
|
+
req = Net::HTTP::Get.new("/inquiries/by_day")
|
45
|
+
req.body = {
|
46
|
+
where: filter
|
47
|
+
}.to_json
|
48
|
+
connection.instance_variable_get(:@connection).send_request(req).body
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.by_week(filter)
|
52
|
+
req = Net::HTTP::Get.new("/inquiries/by_week")
|
53
|
+
req.body = {
|
54
|
+
where: filter
|
55
|
+
}.to_json
|
56
|
+
connection.instance_variable_get(:@connection).send_request(req).body
|
57
|
+
end
|
10
58
|
|
11
59
|
end
|
data/lib/mls/invoice.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class Invoice < MLS::Model
|
2
|
+
|
3
|
+
belongs_to :credit_card
|
4
|
+
belongs_to :membership
|
5
|
+
|
6
|
+
def amount
|
7
|
+
read_attribute(:amount) / 100.0 if read_attribute(:amount)
|
8
|
+
end
|
9
|
+
|
10
|
+
def status
|
11
|
+
return "refunded" if refunded_at
|
12
|
+
return "paid" if cleared_at
|
13
|
+
return "pending"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/mls/lead.rb
CHANGED
data/lib/mls/listing.rb
CHANGED
@@ -2,8 +2,10 @@ class Listing < MLS::Model
|
|
2
2
|
self.inheritance_column = nil
|
3
3
|
|
4
4
|
include MLS::Slugger
|
5
|
+
include MLS::Avatar
|
5
6
|
|
6
|
-
|
7
|
+
UNIT_TYPES = %w(unit floor building)
|
8
|
+
FLOORS = ["Basement", "Mezzanine", "Penthouse", "Concourse", "Lower Level"] + (1..150).to_a
|
7
9
|
TYPES = %w(Sale Lease Sublease)
|
8
10
|
TERMS = ['Full Service', 'Net Lease', 'NN', 'NNN', 'Absolute NNN', 'Gross Lease', 'Modified Gross', 'Industrial Gross', 'Absolute Gross', 'Ground Lease', 'Other']
|
9
11
|
SALE_TERMS = ['Cash to Seller', 'Purchase Money Mtg.', 'Owner Financing', 'Build-to-Suit', 'Sale/Leaseback', 'Other']
|
@@ -14,34 +16,45 @@ class Listing < MLS::Model
|
|
14
16
|
'/yr' => 'rate_per_year',
|
15
17
|
}
|
16
18
|
TERM_UNITS = ['years', 'months']
|
17
|
-
AMENITIES = %W(kitchen showers outdoor_space reception turnkey build_to_suit
|
18
|
-
|
19
|
+
AMENITIES = %W(kitchen showers outdoor_space reception turnkey build_to_suit
|
20
|
+
furniture natural_light high_ceilings plug_and_play additional_storage
|
21
|
+
storefront offices conference_rooms bathrooms)
|
19
22
|
|
20
23
|
belongs_to :flyer, :class_name => 'Document'
|
21
|
-
belongs_to :
|
24
|
+
belongs_to :floorplan, :class_name => 'Document'
|
22
25
|
belongs_to :property
|
23
26
|
|
24
|
-
has_many :
|
25
|
-
|
26
|
-
has_many :
|
27
|
-
has_many :
|
28
|
-
|
27
|
+
has_many :ownerships, as: :asset
|
28
|
+
has_many :accounts, through: :ownerships, source: :account, inverse_of: :listings
|
29
|
+
has_many :image_orderings, as: :subject
|
30
|
+
has_many :photos, through: :image_orderings, source: :image
|
31
|
+
|
32
|
+
has_and_belongs_to_many :uses
|
33
|
+
|
29
34
|
has_one :address
|
30
35
|
has_many :addresses
|
36
|
+
has_many :references, as: :subject
|
31
37
|
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
accepts_nested_attributes_for :uses, :ownerships, :image_orderings
|
39
|
+
|
40
|
+
def premium_property?
|
41
|
+
Subscription.filter(started_at: true, ends_at: false, type: "premium", subject_type: "Property", subject_id: self.property_id).count > 0
|
42
|
+
end
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
44
|
+
def contacts
|
45
|
+
if ownerships.loaded?
|
46
|
+
@contacts ||= ownerships.select{|o| o.receives_inquiries }.map(&:account)
|
47
|
+
else
|
48
|
+
@contacts ||= ownerships.eager_load(:account).filter(:receives_inquiries => true).map(&:account)
|
49
|
+
end
|
50
|
+
end
|
42
51
|
|
43
|
-
def
|
44
|
-
|
52
|
+
def lead_contact
|
53
|
+
if ownerships.loaded?
|
54
|
+
@lead_contact ||= ownerships.select{|o| o.lead}.first.try(:account)
|
55
|
+
else
|
56
|
+
@lead_contact ||= ownerships.eager_load(:account).filter(:lead => true).first.try(:account)
|
57
|
+
end
|
45
58
|
end
|
46
59
|
|
47
60
|
def rate(units=nil)
|
@@ -106,6 +119,18 @@ class Listing < MLS::Model
|
|
106
119
|
|
107
120
|
price.round(2)
|
108
121
|
end
|
122
|
+
|
123
|
+
def regions
|
124
|
+
Region.where(:id => self.region_ids)
|
125
|
+
end
|
126
|
+
|
127
|
+
def longitude
|
128
|
+
location.x
|
129
|
+
end
|
130
|
+
|
131
|
+
def latitude
|
132
|
+
location.y
|
133
|
+
end
|
109
134
|
|
110
135
|
def lease? # TODO: test me
|
111
136
|
type == 'Lease'
|
@@ -120,7 +145,17 @@ class Listing < MLS::Model
|
|
120
145
|
end
|
121
146
|
|
122
147
|
def name
|
123
|
-
return
|
124
|
-
|
148
|
+
return "New Listing" if !self.id
|
149
|
+
name = ""
|
150
|
+
if self.type == "building"
|
151
|
+
name += "Entire Building"
|
152
|
+
else
|
153
|
+
name = "Unit #{self.unit}" if self.unit.present?
|
154
|
+
name += " (" if self.unit.present? && self.floor.present?
|
155
|
+
name += "Floor #{self.floor}" if self.floor.present?
|
156
|
+
name += ")" if self.unit.present? && self.floor.present?
|
157
|
+
end
|
158
|
+
name = "Space" if name.blank?
|
159
|
+
name
|
125
160
|
end
|
126
161
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Membership < MLS::Model
|
2
|
+
self.inheritance_column = nil
|
3
|
+
|
4
|
+
has_many :accounts
|
5
|
+
has_many :invoices
|
6
|
+
has_many :subscriptions
|
7
|
+
belongs_to :organization
|
8
|
+
belongs_to :billing_contact, class_name: "Account"
|
9
|
+
belongs_to :credit_card
|
10
|
+
belongs_to :sourced_by, class_name: "Account"
|
11
|
+
has_and_belongs_to_many :invoice_recipients, class_name: 'EmailAddress'
|
12
|
+
|
13
|
+
accepts_nested_attributes_for :subscriptions
|
14
|
+
|
15
|
+
def rate
|
16
|
+
subscriptions.select{|x| !x.ends_at}.map(&:cost).compact.sum
|
17
|
+
end
|
18
|
+
|
19
|
+
def costs
|
20
|
+
(read_attribute(:costs) || {}).with_indifferent_access
|
21
|
+
end
|
22
|
+
|
23
|
+
def value
|
24
|
+
read_attribute(:value) / 100.0 if read_attribute(:value)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/mls/mistake.rb
CHANGED
data/lib/mls/organization.rb
CHANGED
@@ -2,7 +2,9 @@ class Organization < MLS::Model
|
|
2
2
|
include MLS::Avatar
|
3
3
|
include MLS::Slugger
|
4
4
|
|
5
|
-
has_many :
|
5
|
+
has_many :accounts, :class_name => 'Account'
|
6
|
+
has_many :listings, -> { distinct }, through: :accounts
|
7
|
+
has_many :references, as: :subject
|
6
8
|
has_and_belongs_to_many :regions
|
7
9
|
|
8
10
|
def name
|
data/lib/mls/phone.rb
CHANGED
@@ -4,5 +4,11 @@ class Phone < MLS::Model
|
|
4
4
|
TYPES = ['mobile', 'work', 'home', 'main', 'home fax' 'work fax', 'other fax', 'pager', 'other', 'office']
|
5
5
|
|
6
6
|
belongs_to :account
|
7
|
-
|
7
|
+
|
8
|
+
def number=(value)
|
9
|
+
write_attribute(:number, PhoneValidator.normalize(value))
|
10
|
+
write_attribute(:carrier_name, nil)
|
11
|
+
write_attribute(:carrier_type, nil)
|
12
|
+
end
|
13
|
+
|
8
14
|
end
|
data/lib/mls/property.rb
CHANGED
@@ -3,12 +3,26 @@ class Property < MLS::Model
|
|
3
3
|
include MLS::Slugger
|
4
4
|
include MLS::Avatar
|
5
5
|
|
6
|
-
|
6
|
+
LEED_CERTIFICATIONS = %w(None Certified Silver Gold Platinum)
|
7
|
+
AMENITIES = %W(parking_garage lobby_attendant gym common_kitchen
|
8
|
+
common_bike_storage onsite_parking key_card_access freight_elevator
|
9
|
+
ada_accessible on_site_security
|
10
|
+
elevators close_highway close_public_transit close_points_of_interest
|
11
|
+
parking_ratio number_of_buildings)
|
12
|
+
CONSTRUCTION_TYPES = %w(brick steel concrete masonry tiltwall wood glass)
|
13
|
+
|
7
14
|
has_many :references, as: :subject
|
8
15
|
has_many :listings
|
9
16
|
has_many :localities
|
17
|
+
has_many :coworking_spaces
|
10
18
|
has_many :regions, :through => :localities
|
11
19
|
has_many :image_orderings, as: :subject
|
20
|
+
has_many :data, as: :subject
|
21
|
+
has_many :photos, through: :image_orderings, source: :image
|
22
|
+
has_many :subscriptions, as: :subject
|
23
|
+
|
24
|
+
has_many :uses
|
25
|
+
# has_and_belongs_to_many :uses
|
12
26
|
|
13
27
|
has_many :addresses do
|
14
28
|
def primary
|
@@ -16,119 +30,129 @@ class Property < MLS::Model
|
|
16
30
|
end
|
17
31
|
end
|
18
32
|
|
19
|
-
|
20
|
-
image_orderings.sort_by(&:order).map(&:image)
|
21
|
-
end
|
33
|
+
accepts_nested_attributes_for :image_orderings, :addresses
|
22
34
|
|
23
|
-
def
|
24
|
-
@contact ||= listings.
|
25
|
-
.order(size: :desc).first.try(:
|
35
|
+
def contacts
|
36
|
+
@contact ||= listings.eager_load(:accounts => [:email_addresses, :phones, :organization]).filter(leased_at: nil, authorized: true, type: ['Lease', 'Sublease'], :touched_at => {:gte => 90.days.ago})
|
37
|
+
.order(size: :desc).first.try(:contacts)
|
26
38
|
end
|
27
39
|
|
28
40
|
def address
|
29
41
|
addresses.find(&:primary)
|
30
42
|
end
|
31
|
-
|
43
|
+
|
32
44
|
def longitude
|
33
45
|
location.x
|
34
46
|
end
|
35
|
-
|
47
|
+
|
36
48
|
def latitude
|
37
49
|
location.y
|
38
50
|
end
|
39
|
-
|
40
|
-
def
|
41
|
-
|
51
|
+
|
52
|
+
def display_description
|
53
|
+
return description if description.present?
|
54
|
+
return unless description_data_entry
|
55
|
+
# If has bullets
|
56
|
+
if description_data_entry && description_data_entry.split("\n").all? { |x| ['-','*', '•'].include?(x.strip[0]) }
|
57
|
+
<<~MD
|
58
|
+
##Features
|
59
|
+
#{description_data_entry}
|
60
|
+
MD
|
61
|
+
else description_data_entry
|
62
|
+
show_amenities = amenities.select{ |k,v| v }
|
63
|
+
<<~MD
|
64
|
+
#{description_data_entry}
|
65
|
+
#{"This building's amenities include " + show_amenities.map {|key, v| key.to_s.humanize.downcase }.to_sentence + "."}
|
66
|
+
MD
|
67
|
+
end
|
42
68
|
end
|
43
|
-
|
69
|
+
|
44
70
|
def internet_providers
|
45
|
-
|
71
|
+
idata = []
|
46
72
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
73
|
+
datum = data.select{|d| d.source == "broadbandmap.gov"}.first.try(:datum) if data.loaded?
|
74
|
+
datum ||= data.where(source: 'broadbandmap.gov').first.try(:datum)
|
75
|
+
return idata unless datum && datum['wirelineServices']
|
76
|
+
|
77
|
+
datum['wirelineServices'].sort_by{|p| p['technologies'].sort_by{|t| t['maximumAdvertisedDownloadSpeed']}.reverse.first['maximumAdvertisedDownloadSpeed']}.reverse.each do |provider|
|
78
|
+
tech = provider['technologies'].sort_by{|t| t['maximumAdvertisedDownloadSpeed']}.reverse.first
|
79
|
+
speedcase = -> (speedCode) {
|
80
|
+
case speedCode
|
81
|
+
when 1 then '200 kbps'
|
82
|
+
when 2 then '768 kbps'
|
83
|
+
when 3 then '1.5 Mb/s'
|
84
|
+
when 4 then '3 Mb/s'
|
85
|
+
when 5 then '6 Mb/s'
|
86
|
+
when 6 then '10 Mb/s'
|
87
|
+
when 7 then '25 Mb/s'
|
88
|
+
when 8 then '50 Mb/s'
|
89
|
+
when 9 then '100 Mb/s'
|
90
|
+
when 10 then '1 Gb/s'
|
91
|
+
when 11 then '10 Gb/s'
|
92
|
+
when 12 then '100 Gb/s'
|
93
|
+
when 13 then '100 Gb/s+'
|
94
|
+
else 'Unknown'
|
95
|
+
end
|
96
|
+
}
|
97
|
+
|
98
|
+
idata << {
|
99
|
+
provider_name: provider['doingBusinessAs'] || provider['providerName'],
|
100
|
+
provider_url: provider['providerURL'],
|
101
|
+
technology: case tech['technologyCode']
|
102
|
+
when 10 then 'DSL'
|
103
|
+
when 20 then 'DSL'
|
104
|
+
when 30 then 'Copper Wireline'
|
105
|
+
when 40 then 'Cable'
|
106
|
+
when 41 then 'Cable'
|
107
|
+
when 50 then 'Fiber'
|
108
|
+
when 90 then 'Power Line'
|
109
|
+
else 'Other'
|
110
|
+
end,
|
111
|
+
bandwidth: {
|
112
|
+
up: speedcase.call(tech['maximumAdvertisedUploadSpeed']),
|
113
|
+
down: speedcase.call(tech['maximumAdvertisedDownloadSpeed'])
|
114
|
+
}
|
115
|
+
}
|
88
116
|
end
|
89
117
|
|
90
|
-
|
118
|
+
idata
|
91
119
|
end
|
92
|
-
|
120
|
+
|
93
121
|
def closest_region
|
94
122
|
region = neighborhood_region
|
95
123
|
region ||= city_region
|
96
124
|
region ||= market
|
97
125
|
region
|
98
126
|
end
|
99
|
-
|
127
|
+
|
100
128
|
def neighborhood_region
|
101
129
|
return @neighborhood_region if defined? @neighborhood_region
|
102
130
|
params = {:query => neighborhood} if neighborhood
|
103
|
-
params
|
131
|
+
params ||= {:type => "Neighborhood"}
|
104
132
|
@neighborhood_region = fetch_region(params)
|
105
133
|
end
|
106
|
-
|
134
|
+
|
107
135
|
def city_region
|
108
136
|
return @city_region if defined? @city_region
|
109
137
|
@city_region = fetch_region(:type => "City")
|
110
138
|
end
|
111
|
-
|
139
|
+
|
112
140
|
def market
|
113
141
|
return @market if defined? @market
|
114
142
|
@market = fetch_region(:is_market => true)
|
115
143
|
end
|
116
|
-
|
144
|
+
|
117
145
|
def flagship
|
118
146
|
return @flagship if defined? @flagship
|
119
147
|
@flagship = fetch_region(:is_flagship => true)
|
120
148
|
end
|
121
|
-
|
149
|
+
|
122
150
|
def fetch_region(params)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
regions.to_a.find{|r| r.name == params[0][1]}
|
127
|
-
else
|
128
|
-
regions.to_a.find{|r| r[params[0][0]] == params[0][1]}
|
129
|
-
end
|
151
|
+
params = params.map{|k,v| [k, v]}
|
152
|
+
if params[0][0] == :query
|
153
|
+
regions.to_a.find{|r| r.name == params[0][1]}
|
130
154
|
else
|
131
|
-
regions.
|
155
|
+
regions.to_a.find{|r| r[params[0][0]] == params[0][1]}
|
132
156
|
end
|
133
157
|
end
|
134
158
|
end
|