amsi 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ci +125 -0
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/CHANGELOG.txt +20 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +319 -0
- data/Rakefile +6 -0
- data/amsi.gemspec +32 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/multi_xml.rb +4 -0
- data/lib/amsi/attribute_parser/base.rb +23 -0
- data/lib/amsi/attribute_parser/boolean.rb +25 -0
- data/lib/amsi/attribute_parser/date.rb +28 -0
- data/lib/amsi/attribute_parser/date_time.rb +24 -0
- data/lib/amsi/attribute_parser/decimal.rb +15 -0
- data/lib/amsi/attribute_parser/integer.rb +15 -0
- data/lib/amsi/attribute_parser/string.rb +13 -0
- data/lib/amsi/attribute_parser.rb +45 -0
- data/lib/amsi/document_parser/base.rb +52 -0
- data/lib/amsi/document_parser/get_moveins.rb +66 -0
- data/lib/amsi/document_parser/guest_card_result.rb +31 -0
- data/lib/amsi/document_parser/leases.rb +46 -0
- data/lib/amsi/document_parser/properties.rb +103 -0
- data/lib/amsi/error/bad_request.rb +18 -0
- data/lib/amsi/error/base.rb +9 -0
- data/lib/amsi/error/invalid_response.rb +9 -0
- data/lib/amsi/error/request_fault.rb +18 -0
- data/lib/amsi/error/request_timeout.rb +9 -0
- data/lib/amsi/error/unparsable_response.rb +11 -0
- data/lib/amsi/model/address.rb +18 -0
- data/lib/amsi/model/base/attribute.rb +63 -0
- data/lib/amsi/model/base/attribute_store.rb +37 -0
- data/lib/amsi/model/base.rb +64 -0
- data/lib/amsi/model/guest_card.rb +18 -0
- data/lib/amsi/model/guest_card_result.rb +26 -0
- data/lib/amsi/model/lease.rb +63 -0
- data/lib/amsi/model/leasing_agent.rb +21 -0
- data/lib/amsi/model/manager.rb +16 -0
- data/lib/amsi/model/marketing_source.rb +21 -0
- data/lib/amsi/model/occupant.rb +32 -0
- data/lib/amsi/model/property.rb +35 -0
- data/lib/amsi/model/unit_type.rb +38 -0
- data/lib/amsi/parameter/contact_type.rb +10 -0
- data/lib/amsi/parameter/prospect.rb +19 -0
- data/lib/amsi/request/add_guest_card.rb +93 -0
- data/lib/amsi/request/base.rb +106 -0
- data/lib/amsi/request/get_moveins_by_first_marketing_source.rb +57 -0
- data/lib/amsi/request/get_property_list.rb +49 -0
- data/lib/amsi/request/get_property_residents.rb +45 -0
- data/lib/amsi/request_section/add_guest_card.rb +105 -0
- data/lib/amsi/request_section/auth.rb +22 -0
- data/lib/amsi/request_section/moveins_filter.rb +42 -0
- data/lib/amsi/request_section/property_list_filter.rb +45 -0
- data/lib/amsi/request_section/property_resident_filter.rb +32 -0
- data/lib/amsi/utils/request_fetcher.rb +37 -0
- data/lib/amsi/utils/request_generator.rb +54 -0
- data/lib/amsi/utils/snowflake_event_tracker.rb +113 -0
- data/lib/amsi/validator/base.rb +95 -0
- data/lib/amsi/validator/prospect_event_validator.rb +20 -0
- data/lib/amsi/validator/request_errors.rb +61 -0
- data/lib/amsi/validator/request_fault.rb +57 -0
- data/lib/amsi/validator/resident_event_validator.rb +20 -0
- data/lib/amsi/validator.rb +7 -0
- data/lib/amsi/version.rb +3 -0
- data/lib/amsi.rb +31 -0
- metadata +265 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
module Amsi
|
2
|
+
module RequestSection
|
3
|
+
# Generate the AddGuestCard elements of an Amsi request
|
4
|
+
class AddGuestCard
|
5
|
+
# Formats dates and times like "mm/dd/yyyy", "02/14/2015"
|
6
|
+
DATE_FORMAT = '%m/%d/%Y'
|
7
|
+
private_constant :DATE_FORMAT
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
appointment_date: nil,
|
11
|
+
comments: nil,
|
12
|
+
contact_datetime: nil,
|
13
|
+
contact_type:,
|
14
|
+
date_needed: nil,
|
15
|
+
initial_source_id: nil,
|
16
|
+
lease_term_desired: nil,
|
17
|
+
leasing_agent_id: nil,
|
18
|
+
property_id:,
|
19
|
+
prospect:,
|
20
|
+
requirements: nil,
|
21
|
+
qualified: nil,
|
22
|
+
unit_subtype: nil,
|
23
|
+
unit_type: nil
|
24
|
+
)
|
25
|
+
@appointment_date = appointment_date
|
26
|
+
@comments = comments
|
27
|
+
@contact_datetime = contact_datetime
|
28
|
+
@contact_type = contact_type
|
29
|
+
@date_needed = date_needed
|
30
|
+
@initial_source_id = initial_source_id
|
31
|
+
@lease_term_desired = lease_term_desired
|
32
|
+
@leasing_agent_id = leasing_agent_id
|
33
|
+
@property_id = property_id
|
34
|
+
@prospect = prospect
|
35
|
+
@requirements = requirements
|
36
|
+
@qualified = qualified
|
37
|
+
@unit_subtype = unit_subtype
|
38
|
+
@unit_type = unit_type
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate(xml_builder)
|
42
|
+
xml_builder.XMLData do |xml_data|
|
43
|
+
xml_data.cdata edex.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :appointment_date,
|
50
|
+
:comments,
|
51
|
+
:contact_datetime,
|
52
|
+
:contact_type,
|
53
|
+
:date_needed,
|
54
|
+
:initial_source_id,
|
55
|
+
:lease_term_desired,
|
56
|
+
:leasing_agent_id,
|
57
|
+
:property_id,
|
58
|
+
:prospect,
|
59
|
+
:requirements,
|
60
|
+
:qualified,
|
61
|
+
:unit_subtype,
|
62
|
+
:unit_type
|
63
|
+
|
64
|
+
def edex
|
65
|
+
Nokogiri::XML::Builder.new do |xml_builder|
|
66
|
+
xml_builder.EDEX do |edex|
|
67
|
+
edex.propertyid(property_id)
|
68
|
+
edex.contacttype(contact_type)
|
69
|
+
edex.contactdatetime(contact_time) if contact_datetime
|
70
|
+
edex.modifieddate(format_date(Date.today))
|
71
|
+
edex.initialsourceid(initial_source_id) if initial_source_id
|
72
|
+
edex.qualified(qualified ? 'Y' : 'N') unless qualified.nil?
|
73
|
+
edex.leasetermdesired(lease_term_desired) if lease_term_desired
|
74
|
+
edex.firstname(prospect.first_name) if prospect.first_name
|
75
|
+
edex.lastname(prospect.last_name)
|
76
|
+
edex.dateneeded(format_date(date_needed)) if date_needed
|
77
|
+
edex.daytimephone(prospect.daytime_phone) if prospect.daytime_phone
|
78
|
+
edex.email(prospect.email) if prospect.email
|
79
|
+
edex.appointmentdate(appointment) if appointment_date
|
80
|
+
edex.unittype(unit_type) if unit_type
|
81
|
+
edex.unitsubtype(unit_subtype) if unit_subtype
|
82
|
+
edex.leasingagentid(leasing_agent_id) if leasing_agent_id
|
83
|
+
edex.requirements(requirements) if requirements
|
84
|
+
edex.comments do |comments_xml|
|
85
|
+
comments_xml.comment_(comments, date: format_date(Date.today))
|
86
|
+
end if comments
|
87
|
+
end
|
88
|
+
end.doc.root
|
89
|
+
end
|
90
|
+
|
91
|
+
def contact_time
|
92
|
+
contact_datetime.strftime('%m/%d/%Y %0l:%M %p')
|
93
|
+
end
|
94
|
+
|
95
|
+
def appointment
|
96
|
+
format_date(appointment_date)
|
97
|
+
end
|
98
|
+
|
99
|
+
def format_date(date)
|
100
|
+
# Formats dates and times like "mm/dd/yyyy", "02/14/2015"
|
101
|
+
date.strftime(DATE_FORMAT)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Amsi
|
2
|
+
module RequestSection
|
3
|
+
# Generate the auth elements of an AMSI request
|
4
|
+
class Auth
|
5
|
+
def initialize(user_id:, password:, portfolio_name:)
|
6
|
+
@user_id = user_id
|
7
|
+
@password = password
|
8
|
+
@portfolio_name = portfolio_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate(xml_builder)
|
12
|
+
xml_builder.UserID user_id
|
13
|
+
xml_builder.Password password
|
14
|
+
xml_builder.PortfolioName portfolio_name
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :user_id, :password, :portfolio_name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Amsi
|
2
|
+
module RequestSection
|
3
|
+
# Generate the move in filter elements of an Amsi request
|
4
|
+
class MoveinsFilter
|
5
|
+
def initialize(
|
6
|
+
property_id:,
|
7
|
+
marketing_source_code:,
|
8
|
+
from_date:,
|
9
|
+
through_date:
|
10
|
+
)
|
11
|
+
@property_id = property_id
|
12
|
+
@marketing_source_code = marketing_source_code
|
13
|
+
@from_date = from_date
|
14
|
+
@through_date = through_date
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate(xml_builder)
|
18
|
+
xml_builder.XMLData do |xml_data|
|
19
|
+
xml_data.cdata edex.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def edex
|
26
|
+
Nokogiri::XML::Builder.new do |xml_builder|
|
27
|
+
xml_builder.EDEX do |edex|
|
28
|
+
edex.propertyid(property_id)
|
29
|
+
edex.marketingsourcecode(marketing_source_code)
|
30
|
+
edex.fromdate(from_date.to_s)
|
31
|
+
edex.thrudate(through_date.to_s)
|
32
|
+
end
|
33
|
+
end.doc.root
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :from_date,
|
37
|
+
:marketing_source_code,
|
38
|
+
:property_id,
|
39
|
+
:through_date
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Amsi
|
2
|
+
module RequestSection
|
3
|
+
# Generate the property list filter elements of an AMSI request
|
4
|
+
class PropertyListFilter
|
5
|
+
def initialize(
|
6
|
+
include_leasing_agents: false,
|
7
|
+
include_marketing_sources: false,
|
8
|
+
include_unit_types: false,
|
9
|
+
property_id: nil
|
10
|
+
)
|
11
|
+
@include_leasing_agents = include_leasing_agents
|
12
|
+
@include_marketing_sources = include_marketing_sources
|
13
|
+
@include_unit_types = include_unit_types
|
14
|
+
@property_id = property_id
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate(xml_builder)
|
18
|
+
xml_builder.XMLData do |xml_data|
|
19
|
+
xml_data.cdata edex.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def edex
|
26
|
+
Nokogiri::XML::Builder.new do |xml_builder|
|
27
|
+
xml_builder.EDEX do |edex|
|
28
|
+
edex.propertyid(property_id)
|
29
|
+
edex.includeamenities('0')
|
30
|
+
edex.includeclasses('0')
|
31
|
+
edex.includeincomecodes('0')
|
32
|
+
edex.includeleasingagents(include_leasing_agents ? '1' : '0')
|
33
|
+
edex.includemarketingsources(include_marketing_sources ? '1' : '0')
|
34
|
+
edex.includeunittypes(include_unit_types ? '1' : '0')
|
35
|
+
end
|
36
|
+
end.doc.root
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :include_leasing_agents,
|
40
|
+
:include_marketing_sources,
|
41
|
+
:include_unit_types,
|
42
|
+
:property_id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Amsi
|
2
|
+
module RequestSection
|
3
|
+
# Generate the property resident filter elements of an AMSI request
|
4
|
+
class PropertyResidentFilter
|
5
|
+
def initialize(property_id:, lease_status:, include_marketing_source: false)
|
6
|
+
@property_id = property_id
|
7
|
+
@lease_status = lease_status
|
8
|
+
@include_marketing_source = include_marketing_source
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate(xml_builder)
|
12
|
+
xml_builder.XMLData do |xml_data|
|
13
|
+
xml_data.cdata edex.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def edex
|
20
|
+
Nokogiri::XML::Builder.new do |xml_builder|
|
21
|
+
xml_builder.EDEX do |edex|
|
22
|
+
edex.propertyid property_id
|
23
|
+
edex.leasestatus lease_status
|
24
|
+
edex.includemarketingsource include_marketing_source ? '1' : '0'
|
25
|
+
end
|
26
|
+
end.doc.root
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :property_id, :lease_status, :include_marketing_source
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Amsi
|
4
|
+
module Utils
|
5
|
+
# Send a SOAP request to AMSI
|
6
|
+
class RequestFetcher
|
7
|
+
module Status
|
8
|
+
TIMEOUT = 504
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param generator [RequestGenerator] an instance of a RequestGenerator
|
12
|
+
def initialize(generator:, conn: Faraday.new)
|
13
|
+
@generator = generator
|
14
|
+
@conn = conn
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String] the XML response from AMSI
|
18
|
+
def fetch
|
19
|
+
response = conn.post(generator.url) do |request|
|
20
|
+
request.body = generator.body
|
21
|
+
request.headers = generator.headers
|
22
|
+
end
|
23
|
+
|
24
|
+
if response.status == Status::TIMEOUT
|
25
|
+
raise Error::RequestTimeout,
|
26
|
+
'The server did not respond in a reasonable timeframe'
|
27
|
+
end
|
28
|
+
|
29
|
+
response.body
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :generator, :conn
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Amsi
|
4
|
+
module Utils
|
5
|
+
# Generate a SOAP request for a specific action and sections
|
6
|
+
class RequestGenerator
|
7
|
+
ENVELOPE_ATTRIBUTES = {
|
8
|
+
'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/'
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
ACTION_ATTRIBUTES = {
|
12
|
+
'xmlns' => 'http://tempuri.org/'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
# @param url: [String] the URL to POST to
|
16
|
+
# @param soap_action: [String] the PascalCase action to request from AMSI.
|
17
|
+
# @param sections: [Array<RequestSection>] the setion generators that will
|
18
|
+
# be used to generate the body of the XML request
|
19
|
+
def initialize(url:, soap_action:, sections:)
|
20
|
+
@url = url
|
21
|
+
@soap_action = soap_action
|
22
|
+
@sections = sections
|
23
|
+
end
|
24
|
+
|
25
|
+
def headers
|
26
|
+
{
|
27
|
+
'Content-Type' => 'text/xml; charset=utf-8',
|
28
|
+
'SOAPAction' => "http://tempuri.org/#{soap_action}"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] the XML request for the specified action and sections
|
33
|
+
def body
|
34
|
+
Nokogiri::XML::Builder.new do |xml_builder|
|
35
|
+
xml_builder['soap'].Envelope(ENVELOPE_ATTRIBUTES) do
|
36
|
+
xml_builder['soap'].Body do
|
37
|
+
xml_builder.send(soap_action, ACTION_ATTRIBUTES) do
|
38
|
+
sections.each do |section|
|
39
|
+
section.generate(xml_builder)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end.to_xml
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :url
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :soap_action, :sections
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'event_tracker'
|
2
|
+
|
3
|
+
module Amsi
|
4
|
+
module Utils
|
5
|
+
class SnowflakeEventTracker
|
6
|
+
IMPORT_PMS_RESIDENT_EVENT = 'import_pms_resident'
|
7
|
+
IMPORT_PMS_PROSPECT_EVENT = 'import_pms_prospect'
|
8
|
+
|
9
|
+
def self.track_pms_resident_event(
|
10
|
+
remote_lease_id: nil,
|
11
|
+
import_resident_id:,
|
12
|
+
resident_type:,
|
13
|
+
request_params:,
|
14
|
+
move_in_date: nil,
|
15
|
+
lease_to: nil,
|
16
|
+
lease_from: nil,
|
17
|
+
first_name_present: false,
|
18
|
+
last_name_present: false,
|
19
|
+
email_present: false,
|
20
|
+
phones_count: 0,
|
21
|
+
unit_name:,
|
22
|
+
resident_id: nil,
|
23
|
+
error: nil
|
24
|
+
)
|
25
|
+
EventTracker.track_process_events(name: IMPORT_PMS_RESIDENT_EVENT) do |events|
|
26
|
+
events.add_imported_event(
|
27
|
+
EventTracker::ResourceFactory.build_pms_resident(
|
28
|
+
billing_import: EventTracker::BillingImportFactory.build_billing_import(
|
29
|
+
property_id: request_params[:property_id],
|
30
|
+
billing_config_id: request_params[:billing_config_id],
|
31
|
+
remote_id: request_params[:remote_id],
|
32
|
+
pms_type: 'amsi',
|
33
|
+
import_id: request_params[:import_id],
|
34
|
+
pmc_id: request_params[:pmc_id],
|
35
|
+
service: 'amsi'
|
36
|
+
),
|
37
|
+
remote_lease_id: remote_lease_id,
|
38
|
+
import_resident_id: import_resident_id,
|
39
|
+
resident_type: resident_type,
|
40
|
+
api_name: 'GetPropertyResidents',
|
41
|
+
request_params: EventTracker::ResourceFactory::PmsResident.build_request_params(
|
42
|
+
start_date: request_params[:start_date].nil? ? nil : request_params[:start_date].to_time,
|
43
|
+
end_date: request_params[:end_date].nil? ? nil : request_params[:end_date].to_time,
|
44
|
+
prospect_id: nil,
|
45
|
+
pmc_id: request_params[:pmc_id],
|
46
|
+
remote_id: request_params[:remote_id],
|
47
|
+
traffic_source_id: nil
|
48
|
+
),
|
49
|
+
move_in_date: move_in_date,
|
50
|
+
lease_to: lease_to,
|
51
|
+
lease_from: lease_from,
|
52
|
+
first_name_present: first_name_present,
|
53
|
+
last_name_present: last_name_present,
|
54
|
+
email_present: email_present,
|
55
|
+
phones_count: phones_count,
|
56
|
+
unit_name: unit_name,
|
57
|
+
resident_id: resident_id,
|
58
|
+
move_in_report_type: 'amsi_api',
|
59
|
+
error: error
|
60
|
+
)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.track_pms_prospect_event(
|
66
|
+
remote_lease_id: nil,
|
67
|
+
request_params:,
|
68
|
+
first_name_present:,
|
69
|
+
last_name_present:,
|
70
|
+
email_present:,
|
71
|
+
phone_present:,
|
72
|
+
contact_date: nil,
|
73
|
+
contact_source: nil,
|
74
|
+
remote_prospect_id: nil,
|
75
|
+
error: nil
|
76
|
+
)
|
77
|
+
EventTracker.track_process_events(name: IMPORT_PMS_PROSPECT_EVENT) do |events|
|
78
|
+
events.add_imported_event(
|
79
|
+
EventTracker::ResourceFactory.build_pms_prospect(
|
80
|
+
billing_import: EventTracker::BillingImportFactory.build_billing_import(
|
81
|
+
property_id: request_params[:property_id],
|
82
|
+
billing_config_id: request_params[:billing_config_id],
|
83
|
+
remote_id: request_params[:remote_id],
|
84
|
+
pms_type: 'amsi',
|
85
|
+
import_id: request_params[:import_id],
|
86
|
+
pmc_id: request_params[:pmc_id],
|
87
|
+
service: 'amsi'
|
88
|
+
),
|
89
|
+
remote_lease_id: remote_lease_id,
|
90
|
+
import_resident_id: request_params[:import_resident_id] || '',
|
91
|
+
resident_type: 'PRIMARY',
|
92
|
+
api_name: 'GetPropertyResidents',
|
93
|
+
request_params: EventTracker::ResourceFactory::PmsProspect.build_request_params(
|
94
|
+
pmc_id: request_params[:pmc_id],
|
95
|
+
remote_id: request_params[:remote_id],
|
96
|
+
prospect_id: request_params[:prospect_id],
|
97
|
+
first_name_present: first_name_present,
|
98
|
+
last_name_present: last_name_present,
|
99
|
+
email_present: email_present,
|
100
|
+
phone_present: phone_present,
|
101
|
+
),
|
102
|
+
contact_date: contact_date,
|
103
|
+
contact_source: contact_source,
|
104
|
+
remote_prospect_id: remote_prospect_id,
|
105
|
+
move_in_report_type: 'amsi_api',
|
106
|
+
error: error
|
107
|
+
)
|
108
|
+
)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'amsi/validator'
|
2
|
+
require 'amsi/utils/snowflake_event_tracker'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Amsi
|
6
|
+
module Validator
|
7
|
+
class Base
|
8
|
+
def send_pms_resident_event(lease:, params:, error_message: nil)
|
9
|
+
remote_lease_id, lease_to, lease_from = [ nil ] * 3
|
10
|
+
if lease
|
11
|
+
unit_name = lease.unit_id
|
12
|
+
move_in_date = lease.move_in_date
|
13
|
+
else
|
14
|
+
unit_name = ''
|
15
|
+
move_in_date = nil
|
16
|
+
end
|
17
|
+
lease.occupants.each_with_index do |occupant, index|
|
18
|
+
Utils::SnowflakeEventTracker.track_pms_resident_event(
|
19
|
+
remote_lease_id: remote_lease_id,
|
20
|
+
import_resident_id: import_resident_id(params),
|
21
|
+
resident_type: index == 0 ? 'PRIMARY': 'ROOMMATE',
|
22
|
+
request_params: pms_resident_request_params(params),
|
23
|
+
move_in_date: move_in_date&.to_time,
|
24
|
+
lease_to: lease_to,
|
25
|
+
lease_from: lease_from,
|
26
|
+
first_name_present: is_present?(occupant.first_name),
|
27
|
+
last_name_present: is_present?(occupant.last_name),
|
28
|
+
email_present: is_present?(occupant.email),
|
29
|
+
phones_count: [occupant.phone_1, occupant.phone_2].compact.count,
|
30
|
+
unit_name: unit_name,
|
31
|
+
resident_id: occupant.resident_id,
|
32
|
+
error: error_message
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def pms_resident_request_params(params)
|
38
|
+
{
|
39
|
+
start_date: params[:start_date]&.to_time,
|
40
|
+
end_date: params[:end_date]&.to_time,
|
41
|
+
prospect_id: nil,
|
42
|
+
pmc_id: params[:pmc_id],
|
43
|
+
remote_id: params[:remote_id],
|
44
|
+
traffic_source_id: nil,
|
45
|
+
import_id: params[:import_id],
|
46
|
+
billing_config_id: params[:billing_config]&.id,
|
47
|
+
property_id: params[:billing_config]&.property_id
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def send_pms_prospect_event(lease:, params:, error_message: nil)
|
52
|
+
lease.occupants.each do |occupant|
|
53
|
+
Utils::SnowflakeEventTracker.track_pms_prospect_event(
|
54
|
+
remote_lease_id: nil,
|
55
|
+
request_params: pms_prospect_request_params(params),
|
56
|
+
first_name_present: is_present?(occupant.first_name),
|
57
|
+
last_name_present: is_present?(occupant.last_name),
|
58
|
+
email_present: is_present?(occupant.email),
|
59
|
+
phone_present: is_present?(occupant.phone_1) || is_present?(occupant.phone_2),
|
60
|
+
contact_date: nil,
|
61
|
+
contact_source: lease.lead_source_code,
|
62
|
+
remote_prospect_id: nil,
|
63
|
+
error: error_message
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def pms_prospect_request_params(params)
|
69
|
+
{
|
70
|
+
billing_config_id: params[:billing_config]&.id,
|
71
|
+
property_id: params[:billing_config]&.property_id,
|
72
|
+
remote_id: params[:remote_id],
|
73
|
+
import_id: params[:import_id],
|
74
|
+
pmc_id: params[:pmc_id],
|
75
|
+
import_resident_id: import_resident_id(params),
|
76
|
+
prospect_id: nil
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def import_resident_id(params)
|
81
|
+
"#{params[:import_id] || ''}-#{SecureRandom.alphanumeric(15)}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def is_present?(value)
|
85
|
+
if value.is_a?(String)
|
86
|
+
!value.strip.empty?
|
87
|
+
elsif value.is_a?(Hash) || value.is_a?(Array)
|
88
|
+
!(value.empty? || value.nil?)
|
89
|
+
else
|
90
|
+
!value.nil?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'amsi/validator'
|
2
|
+
require_relative 'base'
|
3
|
+
|
4
|
+
module Amsi
|
5
|
+
module Validator
|
6
|
+
class ProspectEventValidator < Base
|
7
|
+
|
8
|
+
def initialize(response = nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate!
|
12
|
+
end
|
13
|
+
|
14
|
+
# Send parased response's individual prospect data to Snowflake via event tracker
|
15
|
+
def send_event(lease, params)
|
16
|
+
send_pms_prospect_event(lease: lease, params: params || {})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'amsi/error/bad_request'
|
2
|
+
|
3
|
+
module Amsi
|
4
|
+
module Validator
|
5
|
+
# Ensure there are no errors in the wrapped contents of the body.
|
6
|
+
#
|
7
|
+
# This validator works on responses that have the following format:
|
8
|
+
#
|
9
|
+
# <?xml version="1.0" encoding="utf-8"?>
|
10
|
+
# <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
11
|
+
# <soap:Body>
|
12
|
+
# <GetPropertyResidentsResponse xmlns="http://tempuri.org/">
|
13
|
+
# <GetPropertyResidentsResult><InternetTrafficResponse><webservice>Leasing</webservice><webmethod>GetPropertyResidents</webmethod><Error><ErrorCode>1</ErrorCode><ErrorDescription>Validation of user credentials failed.</ErrorDescription></Error></InternetTrafficResponse></GetPropertyResidentsResult>
|
14
|
+
# </GetPropertyResidentsResponse>
|
15
|
+
# </soap:Body>
|
16
|
+
# </soap:Envelope>
|
17
|
+
|
18
|
+
class RequestErrors
|
19
|
+
# @param response [Hash<String, Object>] the XML response parsed into a
|
20
|
+
# Hash
|
21
|
+
def initialize(response)
|
22
|
+
@response = response
|
23
|
+
end
|
24
|
+
|
25
|
+
# @raise [Amsi::Error::BadRequest] if the response has an error
|
26
|
+
# node in the contents
|
27
|
+
def validate!
|
28
|
+
return unless error?
|
29
|
+
raise Amsi::Error::BadRequest.new([error])
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :response
|
35
|
+
|
36
|
+
def error_contents
|
37
|
+
body = response['soap:Envelope']['soap:Body']
|
38
|
+
response_key = body.keys.detect { |key| key !~ /^xmlns/ }
|
39
|
+
contents_response = body[response_key]
|
40
|
+
result_key = contents_response.keys.grep(/Result$/).first
|
41
|
+
escaped_result = contents_response[result_key]
|
42
|
+
parsed_result = MultiXml.parse(escaped_result)
|
43
|
+
|
44
|
+
parsed_result['InternetTrafficResponse']
|
45
|
+
end
|
46
|
+
|
47
|
+
def error?
|
48
|
+
error_contents
|
49
|
+
end
|
50
|
+
|
51
|
+
def error
|
52
|
+
error = error_contents['Error']
|
53
|
+
|
54
|
+
Struct.new(:message, :error_code).new(
|
55
|
+
error['ErrorDescription'],
|
56
|
+
error['ErrorCode']
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Amsi
|
2
|
+
module Validator
|
3
|
+
# Ensure there is no 'Fault' node in the response body
|
4
|
+
class RequestFault
|
5
|
+
FaultDetails = Struct.new(:custom_message, :exception_message, :log_id)
|
6
|
+
private_constant :FaultDetails
|
7
|
+
|
8
|
+
# @param response [Hash<String, Object>] the XML response parsed into a
|
9
|
+
# Hash
|
10
|
+
def initialize(response)
|
11
|
+
@response = response
|
12
|
+
end
|
13
|
+
|
14
|
+
# @raise [Amsi::Error::RequestFault] if the response has a fault
|
15
|
+
def validate!
|
16
|
+
return unless error?
|
17
|
+
raise Error::RequestFault.new(error_message, fault_code, details)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :response
|
23
|
+
|
24
|
+
def fault
|
25
|
+
response['soap:Envelope']['soap:Body']['soap:Fault']
|
26
|
+
end
|
27
|
+
|
28
|
+
def details
|
29
|
+
return unless fault['detail']
|
30
|
+
app_fault = fault['detail'].values.first
|
31
|
+
FaultDetails.new(
|
32
|
+
app_fault['CustomMessage'],
|
33
|
+
app_fault['ExceptionMessage'],
|
34
|
+
app_fault['LogId']
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def error?
|
39
|
+
!fault.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def fault_code
|
43
|
+
return unless fault
|
44
|
+
code = fault['faultcode']
|
45
|
+
code = code['__content__'] if code.is_a?(Hash)
|
46
|
+
code.strip
|
47
|
+
end
|
48
|
+
|
49
|
+
def error_message
|
50
|
+
return unless fault
|
51
|
+
fault_string = fault['faultstring']
|
52
|
+
fault_string = fault_string['__content__'] if fault_string.is_a?(Hash)
|
53
|
+
fault_string.strip
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|