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.
@@ -0,0 +1,11 @@
1
+ class Flyer < MLS::Model
2
+
3
+ belongs_to :account
4
+ belongs_to :listing
5
+ belongs_to :document
6
+
7
+ def fields
8
+ read_attribute(:fields).with_indifferent_access
9
+ end
10
+
11
+ end
@@ -1,6 +1,6 @@
1
1
  class ImageOrdering < MLS::Model
2
2
 
3
- belongs_to :subject, :counter_cache => :photos_count, :polymorphic => true
3
+ belongs_to :subject, :polymorphic => true
4
4
  belongs_to :image
5
5
 
6
6
  end
@@ -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
@@ -1,11 +1,59 @@
1
1
  class Inquiry < MLS::Model
2
2
 
3
3
  has_many :emails
4
- belongs_to :lead
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
@@ -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
@@ -1,10 +1,6 @@
1
1
  class Lead < MLS::Model
2
2
 
3
3
  belongs_to :account
4
- belongs_to :agent, class_name: "Account"
5
-
6
- has_many :recommendations
7
- # Removing lead_listings after recommendations release
8
- has_many :lead_listings
4
+ belongs_to :inquiry
9
5
 
10
6
  end
@@ -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
- SPACE_TYPES = %w(unit floor building)
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 furniture
18
- natural_light high_ceilings plug_and_play additional_storage storefront)
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 :unit
24
+ belongs_to :floorplan, :class_name => 'Document'
22
25
  belongs_to :property
23
26
 
24
- has_many :photos, -> { order(:order => :asc) }, :as => :subject, :inverse_of => :subject
25
-
26
- has_many :agencies, -> { order(:order) }
27
- has_many :agents, -> { order('agencies.order') }, :through => :agencies, :source => :agent
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
- # has_many :comments
33
- # has_many :regions
34
- # has_many :agents
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
- # has_many :favoritizations, :foreign_key => :favorite_id
37
- # has_many :accounts, :through => :favoritizations
38
- # has_many :inquiries, :as => :subject, :inverse_of => :subject
39
- # has_many :agencies, -> { order('"order"') }, :dependent => :destroy, :inverse_of => :subject, :as => :subject
40
- # has_many :agents, -> { order('agencies.order') }, :through => :agencies, :inverse_of => :listings, :source => :agent
41
- # has_many :lead_listings, :dependent => :delete_all
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 contact
44
- @contact ||= agents.first
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 read_attribute(:name) if read_attribute(:name)
124
- unit.name
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
@@ -0,0 +1,3 @@
1
+ class Metadatum < MLS::Model
2
+ has_many :actions, foreign_key: :event_id, primary_key: :event_id
3
+ end
@@ -4,7 +4,7 @@ class Mistake < MLS::Model
4
4
  TYPES = %w(missing spelling incorrect other)
5
5
 
6
6
  belongs_to :task
7
- belongs_to :change
7
+ belongs_to :action
8
8
  belongs_to :account
9
9
 
10
10
  end
@@ -2,7 +2,9 @@ class Organization < MLS::Model
2
2
  include MLS::Avatar
3
3
  include MLS::Slugger
4
4
 
5
- has_many :agents, :class_name => 'Account'
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
@@ -0,0 +1,8 @@
1
+ class Ownership < MLS::Model
2
+
3
+ belongs_to :account
4
+ belongs_to :asset, polymorphic: true
5
+
6
+ accepts_nested_attributes_for :account
7
+
8
+ end
@@ -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
@@ -3,12 +3,26 @@ class Property < MLS::Model
3
3
  include MLS::Slugger
4
4
  include MLS::Avatar
5
5
 
6
- has_many :units
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
- def photos
20
- image_orderings.sort_by(&:order).map(&:image)
21
- end
33
+ accepts_nested_attributes_for :image_orderings, :addresses
22
34
 
23
- def contact
24
- @contact ||= listings.where(leased_at: nil, authorized: true, type: ['Lease', 'Sublease'])
25
- .order(size: :desc).first.try(:contact)
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 automated_description
41
- external_data['narrativescience.com']
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
- data = []
71
+ idata = []
46
72
 
47
- if external_data['broadbandmap.gov']
48
- if external_data['broadbandmap.gov']['wirelineServices']
49
- external_data['broadbandmap.gov']['wirelineServices'].sort_by{|p| p['technologies'].sort_by{|t| t['maximumAdvertisedDownloadSpeed']}.reverse.first['maximumAdvertisedDownloadSpeed']}.reverse.each do |provider|
50
- tech = provider['technologies'].sort_by{|t| t['maximumAdvertisedDownloadSpeed']}.reverse.first
51
- speedcase = -> (speedCode) {
52
- case speedCode
53
- when 1 then '200 kbps'
54
- when 2 then '768 kbps'
55
- when 3 then '1.5 Mb/s'
56
- when 4 then '3 Mb/s'
57
- when 5 then '6 Mb/s'
58
- when 6 then '10 Mb/s'
59
- when 7 then '25 Mb/s'
60
- when 8 then '50 Mb/s'
61
- when 9 then '100 Mb/s'
62
- when 10 then '1 Gb/s'
63
- when 11 then '1 Gb/s+'
64
- else 'Unknown'
65
- end
66
- }
67
-
68
- data << {
69
- provider_name: provider['doingBusinessAs'] || provider['providerName'],
70
- provider_url: provider['providerURL'],
71
- technology: case tech['technologyCode']
72
- when 10 then 'DSL'
73
- when 20 then 'DSL'
74
- when 30 then 'Copper Wireline'
75
- when 40 then 'Cable'
76
- when 41 then 'Cable'
77
- when 50 then 'Fiber'
78
- when 90 then 'Power Line'
79
- else 'Other'
80
- end,
81
- bandwidth: {
82
- up: speedcase.call(tech['maximumAdvertisedUploadSpeed']),
83
- down: speedcase.call(tech['maximumAdvertisedDownloadSpeed'])
84
- }
85
- }
86
- end
87
- end
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
- data
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 = {:type => "Neighborhood"}
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
- if regions.loaded?
124
- params = params.map{|k,v| [k, v]}
125
- if params[0][0] == :query
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.where(params).first
155
+ regions.to_a.find{|r| r[params[0][0]] == params[0][1]}
132
156
  end
133
157
  end
134
158
  end