amsi 1.0.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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.ci +125 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +3 -0
  5. data/CHANGELOG.txt +20 -0
  6. data/CODEOWNERS +1 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +319 -0
  10. data/Rakefile +6 -0
  11. data/amsi.gemspec +32 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/config/multi_xml.rb +4 -0
  15. data/lib/amsi/attribute_parser/base.rb +23 -0
  16. data/lib/amsi/attribute_parser/boolean.rb +25 -0
  17. data/lib/amsi/attribute_parser/date.rb +28 -0
  18. data/lib/amsi/attribute_parser/date_time.rb +24 -0
  19. data/lib/amsi/attribute_parser/decimal.rb +15 -0
  20. data/lib/amsi/attribute_parser/integer.rb +15 -0
  21. data/lib/amsi/attribute_parser/string.rb +13 -0
  22. data/lib/amsi/attribute_parser.rb +45 -0
  23. data/lib/amsi/document_parser/base.rb +52 -0
  24. data/lib/amsi/document_parser/get_moveins.rb +66 -0
  25. data/lib/amsi/document_parser/guest_card_result.rb +31 -0
  26. data/lib/amsi/document_parser/leases.rb +46 -0
  27. data/lib/amsi/document_parser/properties.rb +103 -0
  28. data/lib/amsi/error/bad_request.rb +18 -0
  29. data/lib/amsi/error/base.rb +9 -0
  30. data/lib/amsi/error/invalid_response.rb +9 -0
  31. data/lib/amsi/error/request_fault.rb +18 -0
  32. data/lib/amsi/error/request_timeout.rb +9 -0
  33. data/lib/amsi/error/unparsable_response.rb +11 -0
  34. data/lib/amsi/model/address.rb +18 -0
  35. data/lib/amsi/model/base/attribute.rb +63 -0
  36. data/lib/amsi/model/base/attribute_store.rb +37 -0
  37. data/lib/amsi/model/base.rb +64 -0
  38. data/lib/amsi/model/guest_card.rb +18 -0
  39. data/lib/amsi/model/guest_card_result.rb +26 -0
  40. data/lib/amsi/model/lease.rb +63 -0
  41. data/lib/amsi/model/leasing_agent.rb +21 -0
  42. data/lib/amsi/model/manager.rb +16 -0
  43. data/lib/amsi/model/marketing_source.rb +21 -0
  44. data/lib/amsi/model/occupant.rb +32 -0
  45. data/lib/amsi/model/property.rb +35 -0
  46. data/lib/amsi/model/unit_type.rb +38 -0
  47. data/lib/amsi/parameter/contact_type.rb +10 -0
  48. data/lib/amsi/parameter/prospect.rb +19 -0
  49. data/lib/amsi/request/add_guest_card.rb +93 -0
  50. data/lib/amsi/request/base.rb +106 -0
  51. data/lib/amsi/request/get_moveins_by_first_marketing_source.rb +57 -0
  52. data/lib/amsi/request/get_property_list.rb +49 -0
  53. data/lib/amsi/request/get_property_residents.rb +45 -0
  54. data/lib/amsi/request_section/add_guest_card.rb +105 -0
  55. data/lib/amsi/request_section/auth.rb +22 -0
  56. data/lib/amsi/request_section/moveins_filter.rb +42 -0
  57. data/lib/amsi/request_section/property_list_filter.rb +45 -0
  58. data/lib/amsi/request_section/property_resident_filter.rb +32 -0
  59. data/lib/amsi/utils/request_fetcher.rb +37 -0
  60. data/lib/amsi/utils/request_generator.rb +54 -0
  61. data/lib/amsi/utils/snowflake_event_tracker.rb +113 -0
  62. data/lib/amsi/validator/base.rb +95 -0
  63. data/lib/amsi/validator/prospect_event_validator.rb +20 -0
  64. data/lib/amsi/validator/request_errors.rb +61 -0
  65. data/lib/amsi/validator/request_fault.rb +57 -0
  66. data/lib/amsi/validator/resident_event_validator.rb +20 -0
  67. data/lib/amsi/validator.rb +7 -0
  68. data/lib/amsi/version.rb +3 -0
  69. data/lib/amsi.rb +31 -0
  70. metadata +265 -0
@@ -0,0 +1,18 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class GuestCard < Base
6
+ string_attrs :email,
7
+ :first_name,
8
+ :guest_card_id,
9
+ :initial_source_id,
10
+ :last_name,
11
+ :property_id
12
+
13
+ date_time_attrs :create_date
14
+
15
+ alias :id :guest_card_id
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class GuestCardResult < Base
6
+ module Status
7
+ FAILURE = 'FAILURE'.freeze
8
+ SUCCESS = 'SUCCESS'.freeze
9
+ end
10
+
11
+ string_attrs *%i[
12
+ guest_card_id
13
+ property_id
14
+ status
15
+ ]
16
+
17
+ integer_attrs :contact_seq_no
18
+
19
+ alias_method :contact_sequence_number, :contact_seq_no
20
+
21
+ def success?
22
+ status == Status::SUCCESS
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,63 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class Lease < Base
6
+ module Status
7
+ CURRENT = 'C'.freeze
8
+ APPLICANT = 'A'.freeze
9
+ INTENT_TO_TRANSFER = 'I'.freeze
10
+ LEASED = 'L'.freeze
11
+ NOTICE = 'N'.freeze
12
+ PREVIOUS = 'P'.freeze
13
+ TRANSFER = 'T'.freeze
14
+ APPROVED = 'V'.freeze
15
+ CANCELLED = 'X'.freeze
16
+ end
17
+
18
+ date_attrs *%i[
19
+ application_date
20
+ lease_begin_date
21
+ lease_end_date
22
+ lease_sign_date
23
+ move_in_date
24
+ ]
25
+
26
+ string_attrs *%i[
27
+ bldg_id
28
+ external_reference_id
29
+ occu_status_code
30
+ occu_status_code_description
31
+ property_id
32
+ resi_id
33
+ unit_id
34
+ lease_marketing_source
35
+ guest_card_no
36
+ ]
37
+
38
+ decimal_attrs :rent_amount
39
+
40
+ date_time_attrs :guest_first_contact_date
41
+
42
+ alias_method :begin_date, :lease_begin_date
43
+ alias_method :end_date, :lease_end_date
44
+ alias_method :sign_date, :lease_sign_date
45
+ alias_method :lead_id, :guest_card_no
46
+ alias_method :lead_date, :guest_first_contact_date
47
+ alias_method :lead_source_code, :lease_marketing_source
48
+ alias_method :building_id, :bldg_id
49
+ alias_method :resident_id, :resi_id
50
+ alias_method :occupant_status_code, :occu_status_code
51
+ alias_method :occupant_status_code_description,
52
+ :occu_status_code_description
53
+
54
+ attr_accessor :occupants
55
+ attr_accessor :guest_card
56
+ attr_writer :matched_guest_cards
57
+
58
+ def matched_guest_cards
59
+ @matched_guest_cards ||= []
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class LeasingAgent < Base
6
+ string_attrs *%i[
7
+ agent_active_flag
8
+ agent_code
9
+ agent_name
10
+ property_id
11
+ ]
12
+
13
+ alias_method :code, :agent_code
14
+ alias_method :name, :agent_name
15
+
16
+ def active?
17
+ agent_active_flag == 'Y'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class Manager < Base
6
+ string_attrs *%i[
7
+ fax
8
+ first_name
9
+ last_name
10
+ middle_name
11
+ phone
12
+ salutation
13
+ ]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class MarketingSource < Base
6
+ string_attrs *%i[
7
+ property_id
8
+ source_code
9
+ source_desc
10
+ source_active_flag
11
+ ]
12
+
13
+ alias_method :code, :source_code
14
+ alias_method :description, :source_desc
15
+
16
+ def active?
17
+ source_active_flag == 'Y'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class Occupant < Base
6
+ string_attrs *%i[
7
+ bldg_id
8
+ email
9
+ external_reference_id
10
+ occu_first_name
11
+ occu_last_name
12
+ occu_seq_no
13
+ phone_1_no
14
+ phone_2_no
15
+ property_id
16
+ resi_id
17
+ responsible_flag
18
+ unit_id
19
+ ]
20
+
21
+ decimal_attrs :rent_amount
22
+
23
+ alias_method :building_id, :bldg_id
24
+ alias_method :resident_id, :resi_id
25
+ alias_method :sequence_number, :occu_seq_no
26
+ alias_method :first_name, :occu_first_name
27
+ alias_method :last_name, :occu_last_name
28
+ alias_method :phone_1, :phone_1_no
29
+ alias_method :phone_2, :phone_2_no
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class Property < Base
6
+ date_attrs :live_date
7
+
8
+ string_attrs *%i[
9
+ property_id
10
+ property_name_1
11
+ property_name_2
12
+ property_addr_email
13
+ live_flag
14
+ ]
15
+
16
+ attr_accessor *%i[
17
+ address
18
+ leasing_agents
19
+ manager
20
+ marketing_sources
21
+ remit_to_address
22
+ unit_types
23
+ ]
24
+
25
+ alias_method :email, :property_addr_email
26
+ alias_method :id, :property_id
27
+ alias_method :name, :property_name_1
28
+ alias_method :name_2, :property_name_2
29
+
30
+ def live?
31
+ live_flag == 'Y'
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ require_relative 'base'
2
+
3
+ module Amsi
4
+ module Model
5
+ class UnitType < Base
6
+ decimal_attrs *%i[
7
+ baths
8
+ market_rent
9
+ sec_min
10
+ standard_renew
11
+ mtm_renew
12
+ other_renew
13
+ ]
14
+
15
+ integer_attrs *%i[
16
+ bedrooms
17
+ rooms
18
+ sqftg
19
+ unit_type_count
20
+ ]
21
+
22
+ string_attrs *%i[
23
+ property_id
24
+ unit_subtype
25
+ unit_type
26
+ unit_type_desc
27
+ ]
28
+
29
+ alias_method :count, :unit_type_count
30
+ alias_method :description, :unit_type_desc
31
+ alias_method :minimum_security_deposit, :sec_min
32
+ alias_method :month_to_month_renew, :mtm_renew
33
+ alias_method :sqft, :sqftg
34
+ alias_method :subtype, :unit_subtype
35
+ alias_method :type, :unit_type
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,10 @@
1
+ module Amsi
2
+ module Parameter
3
+ class ContactType
4
+ INTERNET = 'I'.freeze
5
+ PHONE = 'P'.freeze
6
+ RETURN_VISIT = 'R'.freeze
7
+ VISIT = 'V'.freeze
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module Amsi
2
+ module Parameter
3
+ class Prospect
4
+ attr_reader :daytime_phone, :email, :first_name, :last_name
5
+
6
+ def initialize(
7
+ daytime_phone: nil,
8
+ email: nil,
9
+ first_name: nil,
10
+ last_name:
11
+ )
12
+ @daytime_phone = daytime_phone
13
+ @email = email
14
+ @first_name = first_name
15
+ @last_name = last_name
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,93 @@
1
+ require_relative 'base'
2
+
3
+ require 'amsi/document_parser/guest_card_result'
4
+ require 'amsi/request_section/add_guest_card'
5
+
6
+ module Amsi
7
+ module Request
8
+ # Retrieve resident leases for a given property. Returns current leases only
9
+ # by default.
10
+ #
11
+ # Required initialization parameters:
12
+ # @property_id [String] AMSI property id
13
+ # @contact_type [String] values in Amsi::Paramter::ContactType
14
+ # @prospect [Amsi::Parameter::Prospect] the prospect to create the guest
15
+ # card for
16
+ # @see request/base.rb for additional params required by every request.
17
+ class AddGuestCard < Base
18
+ def after_initialize(
19
+ appointment_date: nil,
20
+ comments: nil,
21
+ contact_datetime: nil,
22
+ contact_type:,
23
+ date_needed: nil,
24
+ initial_source_id: nil,
25
+ lease_term_desired: nil,
26
+ leasing_agent_id: nil,
27
+ property_id:,
28
+ prospect:,
29
+ qualified: nil,
30
+ requirements: nil,
31
+ unit_subtype: nil,
32
+ unit_type: nil
33
+ )
34
+ @appointment_date = appointment_date
35
+ @comments = comments
36
+ @contact_datetime = contact_datetime
37
+ @contact_type = contact_type
38
+ @date_needed = date_needed
39
+ @initial_source_id = initial_source_id
40
+ @lease_term_desired = lease_term_desired
41
+ @leasing_agent_id = leasing_agent_id
42
+ @property_id = property_id
43
+ @prospect = prospect
44
+ @qualified = qualified
45
+ @requirements = requirements
46
+ @unit_subtype = unit_subtype
47
+ @unit_type = unit_type
48
+ end
49
+
50
+ private
51
+
52
+ def sections
53
+ [
54
+ RequestSection::AddGuestCard.new(
55
+ appointment_date: appointment_date,
56
+ comments: comments,
57
+ contact_type: contact_type,
58
+ contact_datetime: contact_datetime,
59
+ date_needed: date_needed,
60
+ initial_source_id: initial_source_id,
61
+ lease_term_desired: lease_term_desired,
62
+ leasing_agent_id: leasing_agent_id,
63
+ property_id: property_id,
64
+ prospect: prospect,
65
+ qualified: qualified,
66
+ requirements: requirements,
67
+ unit_subtype: unit_subtype,
68
+ unit_type: unit_type
69
+ )
70
+ ]
71
+ end
72
+
73
+ def parser
74
+ DocumentParser::GuestCardResult.new
75
+ end
76
+
77
+ attr_reader :appointment_date,
78
+ :comments,
79
+ :contact_datetime,
80
+ :contact_type,
81
+ :date_needed,
82
+ :initial_source_id,
83
+ :lease_term_desired,
84
+ :leasing_agent_id,
85
+ :property_id,
86
+ :prospect,
87
+ :qualified,
88
+ :requirements,
89
+ :unit_subtype,
90
+ :unit_type
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,106 @@
1
+ require 'amsi/request_section/auth'
2
+ require 'amsi/utils/request_fetcher'
3
+ require 'amsi/utils/request_generator'
4
+
5
+ module Amsi
6
+ module Request
7
+ # Base class for actions that fetch and parse specific AMSI SOAP
8
+ # actions.
9
+ #
10
+ # When subclassing this base class, you may override the following methods
11
+ # to extend functionality:
12
+ #
13
+ # * after_initialize(params) - a hook into the initializer to give
14
+ # subclasses access to the parameters passed in. If the subclass uses
15
+ # any parameters that the base class is agnostic to, this is the place
16
+ # the set these values to instance variables
17
+ # * sections (returns Array<RequestSection>) - add additional request
18
+ # sections to the request XML document. The 'auth' section will always
19
+ # be inserted into the XML document by this class, so there is no need
20
+ # to add it to the response of this method.
21
+ #
22
+ # Additionally, this base class provides the #soap_action method, which
23
+ # returns the CamelCased name of the SOAP action being requested. This
24
+ # method assumes that the subclass will be named the same as the action.
25
+ class Base
26
+ # Required initialization parameters:
27
+ # @param user_id [String] AMSI account username
28
+ # @param password [String] AMSI account password
29
+ # @param portfolio_name [String] unique identifier for property in AMSI
30
+ # @param web_service_url [String] AMSI Url to the leasing.asmx resource
31
+ def initialize(
32
+ user_id:,
33
+ password:,
34
+ portfolio_name:,
35
+ web_service_url:,
36
+ conn: Faraday.new,
37
+ **other_args
38
+ )
39
+
40
+ @user_id = user_id
41
+ @password = password
42
+ @portfolio_name = portfolio_name
43
+ @web_service_url = web_service_url
44
+ @conn = conn
45
+
46
+ after_initialize(other_args)
47
+ end
48
+
49
+ # @return [Amsi::Model|Array<Amsi::Model>] the parsed response
50
+ # from AMSI
51
+ # @raise [Amsi::Error] an error when validating the response
52
+ def perform
53
+ parser.parse(xml)
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :user_id, :password, :portfolio_name, :web_service_url, :conn
59
+
60
+ # @return [String] the XMl response from Amsi
61
+ def xml
62
+ generator =
63
+ Utils::RequestGenerator.new(
64
+ sections: [auth_section, *sections],
65
+ soap_action: soap_action,
66
+ url: web_service_url
67
+ )
68
+
69
+ Utils::RequestFetcher.new(generator: generator, conn: conn).fetch
70
+ end
71
+
72
+ # A hook into the initializer to give subclasses access to the parameters
73
+ # passed in. If the subclass uses any parameters that the base class is
74
+ # agnostic to, this is the place the set these values to instance
75
+ # variables
76
+ def after_initialize(params)
77
+ # No-op, this method is a call back for subclasses
78
+ end
79
+
80
+ def parser
81
+ # No-op, this method is a call back for subclasses
82
+ end
83
+
84
+ # A hook to add additonal request sections to the request XML document.
85
+ # The 'auth' section will always be inserted into the XML document by this
86
+ # class, so there is no need to add it to the response of this method.
87
+ def sections
88
+ []
89
+ end
90
+
91
+ # The CamelCased name of the SOAP action
92
+ def soap_action
93
+ self.class.name.split('::').last
94
+ end
95
+
96
+ def auth_section
97
+ RequestSection::Auth.new(
98
+ user_id: user_id, password: password,
99
+ portfolio_name: portfolio_name
100
+ )
101
+ end
102
+ end
103
+
104
+ private_constant :Base
105
+ end
106
+ end
@@ -0,0 +1,57 @@
1
+ require_relative 'base'
2
+
3
+ require 'amsi/request_section/moveins_filter'
4
+ require 'amsi/document_parser/get_moveins'
5
+
6
+ module Amsi
7
+ module Request
8
+ # Retrieve move ins for a property between a specified data range
9
+ # and for a specified marketing source code.
10
+ #
11
+ # Required initializer parameters:
12
+ # @param property_id [String] Max of 50 characters according to their
13
+ # docs, this is AMSI's ID of the property we want to fetch move ins for.
14
+ # @param marketing_source_code [String] Max of 3 characters according to
15
+ # their docs, this is the code that corresponds to the marketing source
16
+ # for which we want to fetch move ins. The list of marketing sources
17
+ # that a given property supports can be found with a GetPropertyList
18
+ # request.
19
+ # @param from_date, through_date [Date] Together, these two params define
20
+ # the interval from which to fetch move ins.
21
+ class GetMoveinsByFirstMarketingSource < Base
22
+ def after_initialize(
23
+ from_date:,
24
+ marketing_source_code:,
25
+ property_id:,
26
+ through_date:
27
+ )
28
+ @from_date = from_date
29
+ @marketing_source_code = marketing_source_code
30
+ @property_id = property_id
31
+ @through_date = through_date
32
+ end
33
+
34
+ private
35
+
36
+ def parser
37
+ DocumentParser::GetMoveins.new
38
+ end
39
+
40
+ def sections
41
+ [
42
+ RequestSection::MoveinsFilter.new(
43
+ property_id: property_id,
44
+ marketing_source_code: marketing_source_code,
45
+ from_date: from_date,
46
+ through_date: through_date
47
+ )
48
+ ]
49
+ end
50
+
51
+ attr_reader :from_date,
52
+ :marketing_source_code,
53
+ :property_id,
54
+ :through_date
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,49 @@
1
+ require_relative 'base'
2
+
3
+ require 'amsi/request_section/property_list_filter'
4
+ require 'amsi/document_parser/properties'
5
+
6
+ module Amsi
7
+ module Request
8
+ # Retrieve resident leases for a given property. Returns current leases only
9
+ # by default.
10
+ #
11
+ # No custom required initialization parameters; @see request/base.rb for
12
+ # params required by every request.
13
+ class GetPropertyList < Base
14
+ def after_initialize(
15
+ include_leasing_agents: false,
16
+ include_marketing_sources: false,
17
+ include_unit_types: false,
18
+ property_id: nil
19
+ )
20
+ @include_leasing_agents = include_leasing_agents
21
+ @include_marketing_sources = include_marketing_sources
22
+ @include_unit_types = include_unit_types
23
+ @property_id = property_id
24
+ end
25
+
26
+ private
27
+
28
+ def parser
29
+ DocumentParser::Properties.new
30
+ end
31
+
32
+ def sections
33
+ [
34
+ RequestSection::PropertyListFilter.new(
35
+ include_leasing_agents: include_leasing_agents,
36
+ include_marketing_sources: include_marketing_sources,
37
+ include_unit_types: include_unit_types,
38
+ property_id: property_id
39
+ )
40
+ ]
41
+ end
42
+
43
+ attr_reader :include_leasing_agents,
44
+ :include_marketing_sources,
45
+ :include_unit_types,
46
+ :property_id
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ require_relative 'base'
2
+
3
+ require 'amsi/request_section/property_resident_filter'
4
+ require 'amsi/document_parser/leases'
5
+
6
+ module Amsi
7
+ module Request
8
+ # Retrieve resident leases for a given property. Returns current leases only
9
+ # by default.
10
+ #
11
+ # Custom required initialization parameters:
12
+ # @property_id [String] AMSI property id
13
+ # @lease_status [String] Lease status filter.
14
+ # @include_marketing_source [Boolean] flag to include marketing sources
15
+ # @params [Hash] extra configuration fields
16
+ # Valid values in: Amsi::Model::Lease::Status
17
+ # @see request/base.rb for additional params required by every request.
18
+ class GetPropertyResidents < Base
19
+ def after_initialize(property_id:, lease_status:, include_marketing_source: false, params:)
20
+ @property_id = property_id
21
+ @lease_status = lease_status
22
+ @include_marketing_source = include_marketing_source
23
+ @params = params
24
+ end
25
+
26
+ private
27
+
28
+ def parser
29
+ DocumentParser::Leases.new(@params)
30
+ end
31
+
32
+ def sections
33
+ [
34
+ RequestSection::PropertyResidentFilter.new(
35
+ property_id: property_id,
36
+ lease_status: lease_status,
37
+ include_marketing_source: include_marketing_source
38
+ )
39
+ ]
40
+ end
41
+
42
+ attr_reader :property_id, :lease_status, :include_marketing_source, :params
43
+ end
44
+ end
45
+ end