entrata 1.1.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.
@@ -0,0 +1,21 @@
1
+ module Entrata
2
+ module Request
3
+ class Error < StandardError
4
+ def initialize(msg = nil, code = nil)
5
+ super(msg)
6
+ @code = code
7
+ @msg = msg
8
+ end
9
+
10
+ def to_s
11
+ if code
12
+ "message: #{msg}, code: #{code}"
13
+ else
14
+ msg
15
+ end
16
+ end
17
+
18
+ attr_reader :code, :msg
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ require 'entrata/request/base'
2
+
3
+ module Entrata
4
+ module Request
5
+ # Acquire an access token for a PMC and API client pair
6
+ class GetAccessToken < Base
7
+ def after_initialize(auth_code:, client_id:, client_secret:)
8
+ @auth_code = auth_code
9
+ @client_id = client_id
10
+ @client_secret = client_secret
11
+ end
12
+
13
+ def resource_name
14
+ 'getAccessToken'
15
+ end
16
+
17
+ def resource_path
18
+ '/api/oauth'
19
+ end
20
+
21
+ private
22
+
23
+ def resource_auth
24
+ {
25
+ type: 'oauth',
26
+ code: auth_code,
27
+ grant_type: 'authorization_code',
28
+ client_id: client_id,
29
+ client_secret: client_secret
30
+ }
31
+ end
32
+
33
+ attr_reader :auth_code, :client_id, :client_secret
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ require 'entrata/request/base'
2
+
3
+ module Entrata
4
+ module Request
5
+ # Gets info about a PMC (called a "Client" from Entrata's perspective),
6
+ # not to be confused with the API client.
7
+ class GetClientInfo < Base
8
+ private
9
+
10
+ def resource_path
11
+ '/api/ils/internetlisting'
12
+ end
13
+
14
+ def resource_name
15
+ 'getClientInfo'
16
+ end
17
+
18
+ attr_reader :auth_code, :client_id, :client_secret
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ require 'entrata/request/base'
2
+
3
+ module Entrata
4
+ module Request
5
+ # Get details for multiple properties within a PMC all at once; very slow
6
+ class GetIlsPropertiesData < Base
7
+ def after_initialize(property_ids:)
8
+ @property_ids = property_ids
9
+ end
10
+
11
+ def resource_name
12
+ 'getIlsPropertiesData'
13
+ end
14
+
15
+ def resource_params
16
+ { propertyIds: property_ids.join(',') }
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :property_ids
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require 'entrata/request/base'
2
+
3
+ module Entrata
4
+ module Request
5
+ # Get property details for a single property within a PMC
6
+ class GetPropertyInfo < Base
7
+ def after_initialize(property_id:)
8
+ @property_id = property_id
9
+ end
10
+
11
+ def resource_name
12
+ 'getPropertyInfo'
13
+ end
14
+
15
+ def resource_params
16
+ { propertyId: property_id }
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :property_id
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ require 'entrata/request/base'
2
+
3
+ module Entrata
4
+ module Request
5
+ # Inform Entrata that a single property has been onboarded
6
+ class ProcessPropertyActivation < Base
7
+ def after_initialize(property_id:, activation_status:)
8
+ @property_id = property_id
9
+ @activation_status = activation_status
10
+ end
11
+
12
+ def resource_name
13
+ 'processPropertyActivation'
14
+ end
15
+
16
+ def resource_params
17
+ {
18
+ propertyId: property_id,
19
+ activationStatus: activation_status
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :property_id, :activation_status
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,104 @@
1
+ require 'entrata/request/base'
2
+
3
+ module Entrata
4
+ module Request
5
+ # insert a lead that is assigned to Lea Lite and Log email communication to
6
+ # remove the lead from the assign agent/never contacted/contact needed queues
7
+ class SendInactiveLeads < Base
8
+ # Preferences are optional for inserting a lead into Entrata.
9
+ def after_initialize(customer:, lead_source_id:, leasing_agent_id:, preferences:, property_id:)
10
+ @customer = customer
11
+ @lead_source_id = lead_source_id
12
+ @leasing_agent_id = leasing_agent_id
13
+ @preferences = preferences
14
+ @property_id = property_id
15
+ end
16
+
17
+ def resource_auth
18
+ { type: 'basic' }
19
+ end
20
+
21
+ def resource_name
22
+ 'sendLeads'
23
+ end
24
+
25
+ def resource_path
26
+ '/api/leads'
27
+ end
28
+
29
+ def body
30
+ {
31
+ auth: resource_auth,
32
+ requestId: '15', # used in all requests for Lea(SA) product
33
+ method: {
34
+ name: resource_name,
35
+ params: resource_params
36
+ }
37
+ }.to_json
38
+ end
39
+
40
+ def resource_params
41
+ # all dates and times used in the API are assumed to be in Mountain Time (MST or MDT)
42
+ five_minutes_ago = 5.minutes.ago.in_time_zone('America/Denver').strftime('%m/%d/%YT%H:%M:%S')
43
+ {
44
+ propertyId: property_id, # Entrata remote property ID
45
+ doNotSendConfirmationEmail: '1', # Suppress email to renter - need to validate
46
+ isWaitList: '0',
47
+ prospects: {
48
+ prospect: [
49
+ {
50
+ leasingAgentId: leasing_agent_id, # Lea Lite’s agent ID in the client system to be acquired during onboarding
51
+ leadSource: {
52
+ originatingLeadSourceId: lead_source_id # ApartmentList source ID to be acquired during onboarding
53
+ },
54
+ createdDate: five_minutes_ago, # Now - 5 minutes
55
+ customers: { # Renter details
56
+ customer: [
57
+ {
58
+ name: {
59
+ firstName: customer.first_name,
60
+ lastName: customer.last_name
61
+ },
62
+ phone: {
63
+ cellPhoneNumber: customer.phone
64
+ },
65
+ email: customer.email
66
+ }
67
+ ]
68
+ },
69
+ customerPreferences: {
70
+ desiredMoveInDate: formatted_move_in_date,
71
+ comment: preferences.comments,
72
+ desiredNumBedrooms: preferences.beds
73
+ },
74
+ events: {
75
+ event: [
76
+ {
77
+ type: 'EmailToProspect', # Event necessary to remove guest card from “Contact needed” tab
78
+ date: five_minutes_ago, # Now - 5 minutes
79
+ emailAddresses: {
80
+ from: 'interests@apartmentlist.com', # irrelevant - not displayed or used
81
+ to: customer.email # prospect email
82
+ },
83
+ emailBody: 'not applicable' # irrelevant - not displayed or used
84
+ }
85
+ ]
86
+ }
87
+ }
88
+ ]
89
+ }
90
+ }
91
+ end
92
+
93
+ private
94
+
95
+ attr_reader :customer, :preferences, :property_id, :leasing_agent_id, :lead_source_id
96
+
97
+ # Entrata needs mm/dd/yyyy format for move in dates and will respond with
98
+ # an error if we send them a format they don't like.
99
+ def formatted_move_in_date
100
+ preferences.move_in_date.strftime('%m/%d/%Y')
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,64 @@
1
+ require 'entrata/request/base'
2
+
3
+ module Entrata
4
+ module Request
5
+ # Send renter information to a property
6
+ class SendLeadDetails < Base
7
+ # Preferences are optional for inserting a lead into Entrata.
8
+ def after_initialize(customer:, preferences:, property_id:)
9
+ @customer = customer
10
+ @preferences = preferences
11
+ @property_id = property_id
12
+ end
13
+
14
+ def resource_name
15
+ 'sendLeadDetails'
16
+ end
17
+
18
+ def resource_params
19
+ {
20
+ prospects: {
21
+ prospect: [
22
+ {
23
+ propertyId: property_id,
24
+ customer: {
25
+ name: {
26
+ firstName: customer.first_name,
27
+ lastName: customer.last_name
28
+ },
29
+ email: customer.email,
30
+ phone: customer.phone
31
+ },
32
+ customerPreferences: {
33
+ targetMoveInDate: formatted_move_in_date,
34
+ comments: preferences.comments,
35
+ desiredNumBedrooms: preferences.beds,
36
+ desiredNumBathrooms: preferences.baths,
37
+ desiredRent: {
38
+ min: preferences.min_price,
39
+ max: preferences.max_price,
40
+ },
41
+ propertyUnitId: preferences.preferred_unit_id,
42
+ propertyFloorplanId: preferences.preferred_floorplan_id,
43
+ desiredLeaseTerms: preferences.lease_terms,
44
+ pets: preferences.number_of_pets,
45
+ occupants: ''
46
+ }
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :customer, :preferences, :property_id
56
+
57
+ # Entrata needs mm/dd/yyyy format for move in dates and will respond with
58
+ # an error if we send them a format they don't like.
59
+ def formatted_move_in_date
60
+ preferences.move_in_date.strftime('%m/%d/%Y')
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,84 @@
1
+ require 'entrata/request/base'
2
+
3
+ module Entrata
4
+ module Request
5
+ # Schedule a manual follow up for NOW to move the guest card to the “contact needed” tab and log the handoff reason
6
+ class SendReactivateLeads < Base
7
+ # Preferences are optional for inserting a lead into Entrata.
8
+ def after_initialize(applicant_id:, application_id:, customer:, preferences:, property_id:)
9
+ @applicant_id = applicant_id
10
+ @application_id = application_id
11
+ @customer = customer
12
+ @preferences = preferences
13
+ @property_id = property_id
14
+ end
15
+
16
+ def resource_auth
17
+ { type: 'basic' }
18
+ end
19
+
20
+ def resource_name
21
+ 'updateLeads'
22
+ end
23
+
24
+ def resource_path
25
+ '/api/leads'
26
+ end
27
+
28
+ def body
29
+ {
30
+ auth: resource_auth,
31
+ requestId: '15', # used in all requests for Lea(SA) product
32
+ method: {
33
+ name: resource_name,
34
+ version: 'r2',
35
+ params: resource_params
36
+ }
37
+ }.to_json
38
+ end
39
+
40
+ def resource_params
41
+ # all dates and times used in the API are assumed to be in Mountain Time (MST or MDT)
42
+ five_minutes_ago = 5.minutes.ago.strftime('%m/%d/%YT%H:%M:%S')
43
+ {
44
+ propertyId: property_id, # Entrata remote property ID
45
+ doNotSendConfirmationEmail: '1', # Suppress email to renter - need to validate
46
+ isWaitList: '0',
47
+ prospects: {
48
+ prospect: [
49
+ {
50
+ applicationId: application_id, # acquired in sendLeads response
51
+ customers: { # Renter details
52
+ customer: [
53
+ {
54
+ applicantId: applicant_id
55
+ }
56
+ ]
57
+ },
58
+ events: {
59
+ event: [
60
+ {
61
+ typeId: '8', # consistent for all PMCs & properties
62
+ date: five_minutes_ago, # Now - 5 minutes
63
+ comments: preferences.comments
64
+ },
65
+ {
66
+ typeId: '15', # consistent for all PMCs & properties
67
+ date: five_minutes_ago,
68
+ notes: 'Contact needed',
69
+ scheduleDateTime: five_minutes_ago
70
+ }
71
+ ]
72
+ }
73
+ }
74
+ ]
75
+ }
76
+ }
77
+ end
78
+
79
+ private
80
+
81
+ attr_reader :customer, :preferences, :property_id, :applicant_id, :application_id
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,93 @@
1
+ require 'faraday'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'entrata/request/get_access_token'
5
+ require 'entrata/request/get_client_info'
6
+ require 'entrata/request/get_property_info'
7
+ require 'entrata/request/process_property_activation'
8
+ require 'entrata/request/get_ils_properties_data'
9
+ require 'entrata/utils/static_file_fetcher'
10
+
11
+ module Entrata
12
+ # TestClient is meant to be substituted for the real Client by consuming apps
13
+ # during test. It matches the interface of the real Client and can return
14
+ # successful or failure responses by providing special input params. See
15
+ # comments below or docs for specifics.
16
+ class TestClient
17
+ def initialize(subdomain:, token:); end
18
+
19
+ class << self
20
+ # Fails when auth_code == 'fail'
21
+ def get_access_token(auth_code:, client_id:, client_secret:, conn: nil)
22
+ if auth_code == 'fail'
23
+ raise Entrata::Request::Error, 'TestClient forced error'
24
+ else
25
+ StaticFileFetcher.parsed_response_for('get_access_token')
26
+ end
27
+ end
28
+
29
+ # Fails when token == 'fail'
30
+ def get_client_info(token, conn: nil)
31
+ if token == 'fail'
32
+ raise Entrata::Request::Error, 'TestClient forced error'
33
+ else
34
+ StaticFileFetcher.parsed_response_for('get_client_info')
35
+ end
36
+ end
37
+ end
38
+
39
+ # Fails when property_id == 'fail'
40
+ def get_property_info(property_id)
41
+ raise_forced_failure! if property_id == 'fail'
42
+ StaticFileFetcher.parsed_response_for('get_property_info')
43
+ end
44
+
45
+ # Fails when property_id == 'fail'
46
+ def process_property_activation(property_id, activation_status: 'approve')
47
+ raise_forced_failure! if property_id == 'fail'
48
+ StaticFileFetcher.parsed_response_for('process_property_activation')
49
+ end
50
+
51
+ # Fails when property_ids includes at lease one value == 'fail'
52
+ def get_ils_properties_data(property_ids)
53
+ raise_forced_failure! if property_ids.include?('fail')
54
+ StaticFileFetcher.parsed_response_for('get_ils_properties_data')
55
+ end
56
+
57
+ def send_lead_details(customer:, preferences:, property_id:)
58
+ raise_forced_failure! if property_id == 'fail'
59
+ StaticFileFetcher.parsed_response_for('send_lead_details')
60
+ end
61
+
62
+ def send_inactive_leads(customer:, lead_source_id:, leasing_agent_id:, preferences:, property_id:)
63
+ raise_forced_failure! if property_id == 'fail'
64
+ StaticFileFetcher.parsed_response_for('send_lead_details')
65
+ end
66
+
67
+ def send_reactivate_leads(applicant_id:, application_id:, customer:, preferences:, property_id:)
68
+ raise_forced_failure! if property_id == 'fail'
69
+ StaticFileFetcher.parsed_response_for('send_reactivate_leads')
70
+ end
71
+
72
+ def inactive_lead_request_body(customer:, lead_source_id:, leasing_agent_id:, preferences:, property_id:)
73
+ raise_forced_failure! if property_id == 'fail'
74
+ StaticFileFetcher.parsed_response_for('inactive_lead_request_body')
75
+ end
76
+
77
+ def reactivate_lead_request_body(applicant_id:, application_id:, customer:, preferences:, property_id:)
78
+ raise_forced_failure! if property_id == 'fail'
79
+ StaticFileFetcher.parsed_response_for('reactivate_lead_request_body')
80
+ end
81
+
82
+ def lead_details_request_body(customer:, preferences:, property_id:)
83
+ raise_forced_failure! if property_id == 'fail'
84
+ StaticFileFetcher.parsed_response_for('lead_details_request_body')
85
+ end
86
+
87
+ private
88
+
89
+ def raise_forced_failure!
90
+ raise Entrata::Request::Error, 'TestClient forced error'
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,25 @@
1
+ module StaticFileFetcher
2
+ def self.raw_response_for(method_name)
3
+ path = File.join(
4
+ File.dirname(__FILE__), '../../../', raw_response_path(method_name)
5
+ )
6
+
7
+ File.read(path)
8
+ end
9
+
10
+ def self.raw_response_path(filename)
11
+ "spec/support/fixtures/raw_responses/#{filename}/success.json"
12
+ end
13
+
14
+ def self.parsed_response_for(method_name)
15
+ path = File.join(
16
+ File.dirname(__FILE__), '../../../', parsed_response_path(method_name)
17
+ )
18
+
19
+ JSON.parse(File.read(path))
20
+ end
21
+
22
+ def self.parsed_response_path(filename)
23
+ "spec/support/fixtures/parsed_responses/#{filename}/success.json"
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Entrata
2
+ VERSION = '1.1.0'
3
+ end
data/lib/entrata.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'entrata/version'
2
+ require 'entrata/client'
3
+ require 'entrata/test_client'
4
+ require 'entrata/parameter/customer'
5
+ require 'entrata/parameter/customer_preferences'
6
+
7
+ module Entrata
8
+ end
@@ -0,0 +1,49 @@
1
+ REPO_NAME = 'entrata'
2
+ MODULE_NAME = 'entrata'
3
+
4
+ def adjust_module_name(str)
5
+ str.sub('Entratum', 'Entrata')
6
+ end
7
+
8
+ namespace :package do
9
+ require 'open3'
10
+ require 'pathname'
11
+ require 'active_support'
12
+ require_relative "../#{MODULE_NAME}/version"
13
+
14
+ repo_version = ActiveSupport::Inflector.constantize(
15
+ "#{adjust_module_name(ActiveSupport::Inflector.classify(MODULE_NAME))}::VERSION"
16
+ )
17
+ root_dir = Pathname.new(File.join(__dir__, '..', '..')).expand_path
18
+ output_filename = "pkg/#{REPO_NAME}-#{repo_version}.gem"
19
+
20
+ desc 'Check a credential file for rubygem publishing'
21
+ task :check_cred do
22
+ path_cred = File.join(Dir.home, '.gem', 'credentials')
23
+ raise "Credential file not found: #{path_cred}" unless File.exists?(path_cred)
24
+
25
+ perm_cred = File.stat(path_cred).mode.to_s(8).split("")[-4..-1].join.to_s
26
+ raise "File permission for credential should be 0600. Get #{perm_cred}" if perm_cred != '0600'
27
+
28
+ puts 'Credential looks good'
29
+ end
30
+
31
+ desc 'Publish this package to github'
32
+ task :publish_github do
33
+ Rake::Task['package:check_cred'].invoke
34
+ Rake::Task['build'].invoke
35
+
36
+ stdout, stderr, status = Open3.capture3(
37
+ "gem push --key github --host https://rubygems.pkg.github.com/apartmentlist #{output_filename}",
38
+ { chdir: root_dir }
39
+ )
40
+ unless status.success?
41
+ puts "OUT: #{stdout}"
42
+ puts "ERR: #{stderr}"
43
+ raise 'Failed to publish gem'
44
+ end
45
+
46
+ puts stdout
47
+ puts "#{output_filename} is now published at GitHub. Checkout https://github.com/apartmentlist/#{REPO_NAME}/packages/"
48
+ end
49
+ end