vantaca 0.3.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,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ # Methods which allow posting and deletion of ledger entries.
8
+ module Ledger
9
+ # Create a ledger entry for a specific homeowner.
10
+ #
11
+ # @param account [String] The 7-9 character account number for the homeowner, e.g. 'ABC12345'
12
+ # @param type [String] Type of transaction. Possible values: Charge, Adjustment, Writeoff.
13
+ # @param charge_id [Fixnum] The association Charge ID associated with this owners account.
14
+ # @param date [String] The ledger date for this transaction, e.g. '2020-12-25'
15
+ # @param amount [Float] Transaction amount. Must be greater than 0.
16
+ # @param description [String] Description of the transaction.
17
+ def create_ledger_entry(account, type:, charge_id:, date:, amount:, description:) # rubocop:disable Metrics/ParameterLists
18
+ get(
19
+ '/write/createLedger',
20
+ account:, type:, assocChgID: charge_id, ledgerDate: date, amount:, Descr: description
21
+ )
22
+ end
23
+
24
+ def delete_ledger_entry(ledger_id)
25
+ get('/write/ledgerDelete', ownerLedgerID: ledger_id)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about an action category
9
+ class ActionCategory < Base
10
+ # @return [Integer] unique identifier for action category
11
+ def id = data['ActionCategoryID']
12
+
13
+ # @return [String] action category description
14
+ def description = data['Descr']
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about an action type
9
+ class ActionType < Base
10
+ # @return [Integer] unique identifier for action type
11
+ def id = data['ActionTypeID']
12
+
13
+ # @return [Integer] unique identifier for action category
14
+ def category_id = data['ActionCategoryID']
15
+
16
+ # @return [String] action type description
17
+ def description = data['Descr']
18
+
19
+ def created = Time.parse(data['CreatedDate'])
20
+
21
+ def updated = Time.parse(data['LastUpdatedDate'])
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about an address, received from the Vantaca API
9
+ class Address < Base
10
+ attr_reader :owner
11
+
12
+ def initialize(data, owner:)
13
+ super(data)
14
+
15
+ @owner = owner
16
+ end
17
+
18
+ def id = data['addrID']
19
+
20
+ def label = data['label']
21
+
22
+ def mailing? = data['isMailing']
23
+
24
+ def international? = data['isInternational']
25
+
26
+ def address1 = data['address1']
27
+
28
+ def address2 = data['address2']
29
+
30
+ def city = data['city']
31
+
32
+ def state = data['state']
33
+
34
+ def zip = data['zip']
35
+
36
+ def to_s
37
+ [
38
+ (data['address1'] if data['address1'].match?(/[[:graph:]]/)),
39
+ (data['address2'] if data['address2'].match?(/[[:graph:]]/)),
40
+ city_state_zip
41
+ ].compact.map(&:strip).join("\n")
42
+ end
43
+ alias formatted to_s
44
+
45
+ protected
46
+
47
+ def city_state_zip
48
+ # Foreign addresses do not have City/State/Zip
49
+ return unless data['city'] && data['state'] && data['zip']
50
+
51
+ "#{data['city']}, #{data['state']} #{data['zip']}"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # A basic class which all model classes inherit from, allowing easy access to the raw data.
9
+ class Base
10
+ attr_reader :data
11
+
12
+ def initialize(data)
13
+ @data = data
14
+ end
15
+
16
+ def dig(...) = @data.dig(...)
17
+
18
+ def [](key) = @data[key]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about a community, received from the Vantaca API
9
+ class Community < Base
10
+ def id = data['assocCode']
11
+
12
+ def name = data['assocName']
13
+
14
+ def tax_id = data['taxID']
15
+
16
+ def charges = data['charges'] || []
17
+
18
+ def late_fees = data['lateFees'] || []
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about a document, received from the Vantaca API
9
+ class Document < Base
10
+ SECURITY_LEVELS = %w[Public Homeowners Board Staff].freeze
11
+
12
+ def id = data['imgID']
13
+
14
+ def name = data['docName']
15
+
16
+ def folder_path = data['folderPath']
17
+
18
+ def security_id = data['securityID']
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about an email address, received from the Vantaca API
9
+ class Email < Base
10
+ attr_reader :owner
11
+
12
+ def initialize(data, owner:)
13
+ super(data)
14
+
15
+ @owner = owner
16
+ end
17
+
18
+ def id = data['emailID']
19
+
20
+ def label = data['label']
21
+
22
+ def email = data['email']
23
+
24
+ def primary? = data['isPrimary']
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about an owner, received from the Vantaca API
9
+ class Owner < Base
10
+ def id = data['hoid']
11
+
12
+ def account = data['accountNo']
13
+
14
+ def property_id = data['propertyID']
15
+
16
+ def in_collections? = data['collectionStatus'] != ''
17
+
18
+ def addresses
19
+ @addresses ||= data['addresses'].map { Vantaca::Models::Address.new(it, owner: self) }
20
+ end
21
+
22
+ def emails
23
+ @emails ||= data['emails'].map { Vantaca::Models::Email.new(it, owner: self) }
24
+ end
25
+
26
+ def phones
27
+ @phones ||= data['phones'].map { Vantaca::Models::Phone.new(it, owner: self) }
28
+ end
29
+
30
+ def offsite? = data['mailingAddressType'] == 'OffSiteMailingAddress'
31
+
32
+ # This is the *primary* mailing address, if different from the property address
33
+ def offsite_mailing_address = (addresses.find { it.id == data['mailingAddressID'] } if offsite?)
34
+
35
+ def alternate_mailing_addresses
36
+ primary_offsite_address_id = offsite? ? data['mailingAddressID'] : nil
37
+
38
+ addresses.select { it.mailing? && it.id != primary_offsite_address_id }
39
+ end
40
+
41
+ def move_in_date = (Time.parse data['settleDate'] if data['settleDate'])
42
+
43
+ def move_out_date = (Time.parse data['previousOwner'] if data['previousOwner'])
44
+
45
+ def payment_plan? = data['hasPaymentPlan']
46
+
47
+ def balance = data['balance']
48
+
49
+ def overall_balance = data['overallBalance']
50
+
51
+ def current_owner? = data['previousOwner'].nil?
52
+
53
+ def communication_preferences
54
+ {
55
+ communication: data['CommPref'],
56
+ billing: data['BillingPref']
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about a phone number, received from the Vantaca API
9
+ class Phone < Base
10
+ attr_reader :owner
11
+
12
+ def initialize(data, owner:)
13
+ super(data)
14
+
15
+ @owner = owner
16
+ end
17
+
18
+ def id = data['phoneID']
19
+
20
+ def label = data['label']
21
+
22
+ def phone = data['phone']
23
+
24
+ def primary? = data['isPrimary']
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # Information about a provider (vendor)
9
+ class Provider < Base
10
+ # @return [Integer] unique identifier for service provider
11
+ def id = @data['providerID']
12
+
13
+ # @return [String] service provider name
14
+ def name = @data['name']
15
+
16
+ # @return [Integer] service provider federal tax identification number
17
+ def tax_id = @data['taxID']
18
+
19
+ # @return [Boolean] if **true**, provider is compliant, else, **false**
20
+ def compliant? = @data['compliant']
21
+
22
+ # @return [Boolean]
23
+ def on_hold? = @data['onHold']
24
+
25
+ # @return [String] reason service provider is on hold
26
+ def hold_reason = @data['onHoldReason']
27
+
28
+ # @return [String] the grouping of the provider's compliance (this is an open-ended text field)
29
+ def compliance_group = @data['complianceGroup']
30
+
31
+ # @return [String] the status of the provider's compliance (open-ended text field)
32
+ def compliance_status = @data['complianceStatus']
33
+
34
+ # @return [Boolean]
35
+ def compliance_exempt? = @data['complianceExempt']
36
+
37
+ # @return [String] primary email
38
+ def email = @data['email']
39
+
40
+ # @return [String] primary phone
41
+ def phone = @data['phone']
42
+
43
+ # @return [String] primary fax
44
+ def fax = @data['fax']
45
+
46
+ # @return [Boolean]
47
+ def preferred? = @data['preferred']
48
+
49
+ # @return [Vantaca::Models::ProviderInsurance, nil] list of insurance policies for provider, or nil if not
50
+ # requested
51
+ def insurance
52
+ return unless @data['providerInsurance']
53
+
54
+ @insurance ||= @data['providerInsurance'].map { Vantaca::Models::ProviderInsurance.new(it, provider: self) }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ module Models
8
+ # A single insurance record - in our case, populated by Vive.
9
+ class ProviderInsurance < Base
10
+ # !@attribute [r] provider
11
+ # @return [Vantaca::Models::Provider]
12
+ attr_reader :provider
13
+
14
+ def initialize(data, provider:)
15
+ super(data)
16
+
17
+ @provider = provider
18
+ end
19
+
20
+ # @return [Integer]
21
+ def id = data['providerInsuranceID']
22
+
23
+ # @return [String]
24
+ def type = data['insuranceType']
25
+
26
+ # @return [String]
27
+ def account = data['accountNo']
28
+
29
+ # For now, I'm going to assume that all insurance records include an expiration date.
30
+ # @return [Time]
31
+ def expiration_date = Time.parse(data['expirationDate'])
32
+
33
+ # @return [Boolean]
34
+ def expired? = expiration_date < Time.now
35
+
36
+ # @return [Boolean]
37
+ def required? = @data['isRequired']
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ # Methods which load or modify one or more owners.
8
+ module Owners
9
+ OWNER_PARAMETERS = {
10
+ bk_list: :includeOwnerBKList,
11
+ charges: :includeOwnerChargeTransactions,
12
+ next_assessment: :includeNextAssessment,
13
+ transactions: :includeOwnerTransactions,
14
+ breakdown: :includebalanceByChargeType
15
+ }.freeze
16
+
17
+ # Load a list of all owners in a specific community
18
+ #
19
+ # @param assoc_code [String] the 3-4 character association code for a community
20
+ # @option opts [Symbol, Array<Symbol>] :include Include additional information, using keys from OWNER_PARAMETERS
21
+ # @return [Array<Vantaca::Models::Owner>] a list of all current and former owners in this community.
22
+ def community_owners(assoc_code, **options)
23
+ params = owner_parameters(assoc_code, options)
24
+
25
+ response = get('/read/Association', **params)
26
+
27
+ raise Vantaca::Errors::NotFoundError unless response
28
+
29
+ response.dig(0, 'owners').map { Vantaca::Models::Owner.new(it) }
30
+ end
31
+
32
+ # Loading an owner by their account number doesn't need a community code, since the account numbers are unique.
33
+ #
34
+ # @param account [String] the 7-9 character account number for a property's homeowner
35
+ # @option opts [Symbol, Array<Symbol>] :include Include additional information, using keys from OWNER_PARAMETERS
36
+ # @return [Vantaca::Models::Owner] a list of all current and former owners in this community.
37
+ def account_owner(account, **options)
38
+ params = owner_parameters(nil, options).merge(account:)
39
+
40
+ response = get('/read/Association', **params)
41
+
42
+ raise Vantaca::Errors::NotFoundError unless response
43
+
44
+ Vantaca::Models::Owner.new response.dig(0, 'owners', 0)
45
+ end
46
+
47
+ # Load a list of all owners for a specific property
48
+ #
49
+ # @param assoc_code [String] the 3-4 character association code for a community
50
+ # @param property_id [Fixnum] the internal Vantaca property ID
51
+ # @option opts [Symbol, Array<Symbol>] :include Include additional information, using keys from OWNER_PARAMETERS
52
+ # @return [Array<Vantaca::Models::Owner>] a list of all current and former owners for this property.
53
+ def property_owners(assoc_code, property_id, **options)
54
+ params = owner_parameters(assoc_code, options).merge(propertyID: property_id)
55
+
56
+ response = get('/read/Association', **params)
57
+
58
+ raise Vantaca::Errors::NotFoundError unless response
59
+
60
+ response.dig(0, 'owners').map { Vantaca::Models::Owner.new(it) }
61
+ end
62
+
63
+ # Load a specific homeowner - this currently loads only the first owner record, even if the owner has multiple
64
+ # properties in this community.
65
+ #
66
+ # @param assoc_code [String] the 3-4 character association code for a community
67
+ # @param owner_id [Fixnum] the internal Vantaca homeowner ID
68
+ # @option opts [Symbol, Array<Symbol>] :include Include additional information, using keys from OWNER_PARAMETERS
69
+ # @return [Vantaca::Models::Owner] the owner record for this homeowner
70
+ def owner(assoc_code, owner_id, **options)
71
+ params = owner_parameters(assoc_code, options).merge(Hoid: owner_id)
72
+
73
+ response = get('/read/Association', **params)
74
+
75
+ raise Vantaca::Errors::NotFoundError unless response
76
+
77
+ Vantaca::Models::Owner.new response.dig(0, 'owners', 0)
78
+ end
79
+
80
+ # This data is already included in the basic owner data - this endpoint isn't very useful.
81
+ def communication_preferences(owner_id)
82
+ response = get('/read/getCommPreference', hoID: owner_id)
83
+
84
+ raise Vantaca::Errors::NotFoundError unless response
85
+
86
+ {
87
+ communication: response['commpref'],
88
+ billing: response['billingpref']
89
+ }
90
+ end
91
+
92
+ # Update the communication preferences for a homeowner.
93
+ #
94
+ # @param owner_id [Fixnum] the internal Vantaca homeowner ID
95
+ # @param communication [String] the new general communication preference, accepted values: Paper, Email, App, Text
96
+ # @param billing [String] the internal the new billing communication preference, accepted values: Paper, Text, Email
97
+ # @return [true]
98
+ def update_communication_preferences(owner_id, communication: nil, billing: nil)
99
+ raise ArgumentError, 'At least communication or billing must be passed.' unless communication || billing
100
+
101
+ params = { hoid: owner_id, commpref: communication, billingpref: billing }
102
+
103
+ post('/write/commPrefUpdate', params.compact)
104
+ end
105
+
106
+ def update_name(owner_id, first_name:, last_name:, spouse_first_name: nil, spouse_last_name: nil, business_name: nil)
107
+ params = {
108
+ hoID: owner_id,
109
+ firstName: first_name,
110
+ lastName: last_name,
111
+ spouseFirstName: spouse_first_name,
112
+ spouseLastName: spouse_last_name,
113
+ businessName: business_name
114
+ }
115
+
116
+ post('/write/nameUpdate', params.compact)
117
+ end
118
+
119
+ protected
120
+
121
+ def owner_parameters(community, options)
122
+ params = { includeOwners: true, assocCode: community }
123
+
124
+ OWNER_PARAMETERS.values_at(*Array(options[:include])).compact.each do |vantaca_parameter|
125
+ params[vantaca_parameter] = true
126
+ end
127
+
128
+ params.compact
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ # Methods which allow creation, modification, and deletion of homeowner phone numbers.
8
+ module Phones
9
+ # @param homeowner_id [Integer] The ID of the homeowner
10
+ # @param phone_attrs [Hash] The phone number details
11
+ # @option phone_attrs [String] :phone (required) The ten-digit numeric phone number
12
+ # @option phone_attrs [Boolean] :isPrimary Whether this is the primary phone number
13
+ # @option phone_attrs [String] :label Optional label for the phone number (e.g., 'Mobile', 'Home')
14
+ # @return [Response] The API response from the phone creation endpoint
15
+ def create_phone_number(homeowner_id, phone_attrs)
16
+ post('/write/phoneCreate', phone_attrs.merge(hoid: homeowner_id))
17
+ end
18
+
19
+ # @param phone_number_id [Integer] The ID of the phone number to update
20
+ # @param phone_attrs [Hash] The updated phone number details
21
+ # @option phone_attrs [String] :phone (required) The ten-digit numeric phone number
22
+ # @option phone_attrs [Boolean] :isPrimary Whether this is the primary phone number
23
+ # @option phone_attrs [String] :label Optional label for the phone number (e.g., 'Mobile', 'Home')
24
+ # @return [Response] The API response from the phone update endpoint
25
+ def update_phone_number(phone_number_id, phone_attrs)
26
+ post('/write/phoneUpdate', phone_attrs.merge(phoneID: phone_number_id))
27
+ end
28
+
29
+ # @param phone_number_id [Integer] The ID of the phone number to delete
30
+ # @return [Response] The API response from the phone deletion endpoint
31
+ def delete_phone_number(phone_number_id)
32
+ post('/write/phoneDestroy', { phoneID: phone_number_id })
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ # Methods which load or modify one or more providers.
8
+ module Providers
9
+ PROVIDER_PARAMETERS = { insurance: :includeInsurance }.freeze
10
+
11
+ # Load a list of all providers.
12
+ #
13
+ # @option opts [Symbol, Array<Symbol>] :include Include additional information, using keys from PROVIDER_PARAMETERS
14
+ # @return [Array<Vantaca::Models::Provider>] a list of all providers.
15
+ def providers(**options)
16
+ response = get('/read/provider', **provider_parameters(options))
17
+
18
+ raise Vantaca::Errors::NotFoundError unless response
19
+
20
+ response.map { Vantaca::Models::Provider.new(it) }
21
+ end
22
+
23
+ # Load a single provider by their internal Vantaca ID.
24
+ #
25
+ # @param id [String] the internal Vantaca ID of a provider
26
+ # @option opts [Symbol, Array<Symbol>] :include Include additional information, using keys from PROVIDER_PARAMETERS
27
+ # @return [Vantaca::Models::Provider] a single provider record
28
+ # @raise [Vantaca::Errors::NotFoundError] if the provider does not exist
29
+ def provider(id, **options)
30
+ response = get('/read/providerSingle', **provider_parameters(options), providerID: id)
31
+
32
+ Vantaca::Models::Provider.new response
33
+ rescue Vantaca::Errors::ApiError
34
+ raise Vantaca::Errors::NotFoundError
35
+ end
36
+
37
+ # Update a provider by their internal Vantaca ID.
38
+ #
39
+ # @param id [String] the internal Vantaca ID of a provider
40
+ # @param attributes [Hash] a hash of attributes to update
41
+ # @return [nil] returns nil if the update was successful
42
+ # @raise [Vantaca::Errors::NotFoundError] if the provider does not exist
43
+ def update_provider(id, **provider_attributes)
44
+ post('/write/providerUpdate', provider_attributes.merge(providerID: id))
45
+ rescue Vantaca::Errors::ApiError
46
+ raise Vantaca::Errors::NotFoundError
47
+ end
48
+
49
+ protected
50
+
51
+ def provider_parameters(options)
52
+ params = {}
53
+
54
+ PROVIDER_PARAMETERS.values_at(*Array(options[:include])).compact.each do |vantaca_parameter|
55
+ params[vantaca_parameter] = true
56
+ end
57
+
58
+ params
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ module Vantaca
7
+ VERSION = '0.3.0'
8
+ end
data/lib/vantaca.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Valencia Management Group
4
+ # All rights reserved.
5
+
6
+ require 'httparty'
7
+ require 'json'
8
+ require 'tempfile'
9
+ require 'time'
10
+ require 'zeitwerk'
11
+
12
+ loader = Zeitwerk::Loader.for_gem
13
+ loader.setup
14
+
15
+ # The module we're all here for.
16
+ module Vantaca
17
+ class << self
18
+ attr_accessor :configuration
19
+ end
20
+
21
+ def self.configure
22
+ self.configuration ||= Configuration.new
23
+
24
+ yield(configuration) if block_given?
25
+
26
+ configuration
27
+ end
28
+ end