mycrm 0.0.41

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c2d05640d72f98ddd72af6846858fbbd30e8acd6
4
+ data.tar.gz: fe35fee156ef2586a2badf552716e5ebff67c916
5
+ SHA512:
6
+ metadata.gz: e2dbbe09306e7f9455ed9b4516bab95407c6a2df10adb09a3938c426a3cb62edef108d2a828b14f6d8df2ede2aa2cd35699ce3bd392b1cf6dcf3099ff4844c1c
7
+ data.tar.gz: dd87fc2be3e1d11f458abf02c6026d76b2f621ee4a95bc0239067e2bd9be1bba6cc74aa48d38ac14392e6f17a8cb46629fb37870e58eb5f2ac6f4e261fed48ff
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Danilo Lo Santo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,112 @@
1
+ # Mycrm Gem
2
+
3
+ A library to simplify communication with the MyCrm data service.
4
+
5
+ ## Configuration
6
+
7
+ ```
8
+ storage = Redis.new({"host"=>"localhost", "port"=>6379, "namespace"=>"loan-market-platform-development"})
9
+ Mycrm.configure do |conf|
10
+ conf.base_uri = 'https://api.endopoint.com'
11
+ conf.username = 'some-user-name'
12
+ conf.password = 'very-secret'
13
+ conf.logger = ::Logger.new(STDOUT)
14
+ conf.storage = storage
15
+ conf.token_storage_expiry = 600 # in seconds default 10 minutes (600)
16
+ # it overrides the default domains' values
17
+ conf.domains = {
18
+ sources: {
19
+ lm_contact_us: -9
20
+ },
21
+ purposes: {
22
+ first_home: 17207
23
+ },
24
+ loan_features:{
25
+ other: 16928
26
+ },
27
+ adviser_sources:{
28
+ loan_market_careers_website: 16
29
+ },
30
+ adviser_types:{
31
+ mortgage_broker: 7829
32
+ },
33
+ other_option:{
34
+ other: 36928
35
+ }
36
+ }
37
+ end
38
+ ```
39
+
40
+ ## Create a new simple lead
41
+
42
+ ```
43
+ lead = Mycrm::Facades::Lead.new
44
+ lead.first_name = 'Michael'
45
+ lead.last_name = 'Stephens'
46
+ lead.email = 'michael.stephens@example.com'
47
+ lead.mobile = '0555123457'
48
+ lead.home_address = '135 King Street, 2000, Sydney, NSW, Australia'
49
+ lead.mobile_validated = true
50
+ lead.allocated_broker_id = 858860
51
+ lead.source = 'Lm Contact Us'
52
+ lead.activity_title = 'Gem Enquiry!'
53
+ lead.activity_details = 'I would like to ask...'
54
+
55
+ lead.create!
56
+ ```
57
+
58
+ ## Create a new lead with loan application
59
+
60
+ ```
61
+ lead = Mycrm::Facades::LeadApplication.new
62
+ lead.first_name = 'Michael'
63
+ lead.last_name = 'Stephens'
64
+ lead.email = 'michael.stephens@example.com'
65
+ lead.mobile = '0555123457'
66
+ lead.home_address = '135 King Street, Sydney, 2000, NSW'
67
+ lead.mobile_validated = true
68
+ lead.allocated_broker_id = 858860
69
+ lead.source = 'Lm Contact Us'
70
+ lead.settlement_date = "2017-06-06T23:38:18.609Z"
71
+ lead.add_purpose 'First Home'
72
+ lead.add_feature 'Other'
73
+ lead.loan_term = 120
74
+ lead.loan_amount = 35000
75
+ lead.activity_title = 'Gem Enquiry!'
76
+ lead.activity_details = 'I would like to ask...'
77
+
78
+ lead.create!
79
+ ```
80
+
81
+ ## Create a new prospect adviser
82
+ ```
83
+ adviser = Mycrm::Models::Adviser.new
84
+ adviser.first_name = 'James'
85
+ adviser.last_name = 'Smith'
86
+ adviser.email = 'james.smith@example.com'
87
+ adviser.mobile = '0555123457'
88
+ adviser.business_phone = '0405666555'
89
+ adviser.business_address = '135 King Street, Sydney, 2000, NSW'
90
+ adviser.summary = "I'm looking for job!"
91
+ adviser.source = 'Loan Market Careers Website'
92
+ adviser.reporting_country = 'Australia'
93
+ adviser.type = 'Mortgage Broker'
94
+
95
+ adviser.create!
96
+ ```
97
+
98
+ ## Development
99
+
100
+ ### Running tests in development
101
+
102
+ Some of the tests run against the test instance of the actual My CRM API.
103
+
104
+ To run these locally add a `.env.local` file with `API_BASE_URI`, `API_USERNAME`, and `API_PASSWORD`:
105
+
106
+ ```
107
+ API_BASE_URI=https://testapi.nzfsg.co.nz
108
+ API_USERNAME=$api-user-email
109
+ API_PASSWORD=$api-user-password
110
+ ```
111
+
112
+ Get the propper details off one of the Loan Market developers.
@@ -0,0 +1,16 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ # require 'rdoc/task'
8
+
9
+ # RDoc::Task.new(:rdoc) do |rdoc|
10
+ # rdoc.rdoc_dir = 'rdoc'
11
+ # rdoc.title = 'Mycrm'
12
+ # rdoc.options << '--line-numbers'
13
+ # rdoc.rdoc_files.include('lib/**/*.rb')
14
+ # end
15
+
16
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+ require 'mycrm/core_ext'
3
+
4
+ require 'mycrm/attributes'
5
+ require 'mycrm/connectable'
6
+ require 'mycrm/domains'
7
+ require 'mycrm/facades'
8
+ require 'mycrm/models'
9
+ require 'mycrm/version'
10
+
11
+ module Mycrm
12
+ class << self
13
+ attr_reader :configuration
14
+
15
+ private
16
+
17
+ attr_writer :configuration
18
+
19
+ def set_domains
20
+ configuration.domains = YAML.load_file(domains_file)
21
+ .depp_merge(configuration.domains.to_h.stringify_keys_deep!)
22
+ .stringify_keys_deep!
23
+ end
24
+
25
+ def domains_file
26
+ File.join(File.dirname(__FILE__), 'mycrm/resources', 'domains.yml')
27
+ end
28
+ end
29
+
30
+ def self.configure
31
+ self.configuration = Configuration.new
32
+ yield(configuration)
33
+ set_domains
34
+ end
35
+
36
+ def self.log(level, message)
37
+ return unless configuration.logger
38
+ configuration.logger.send level, message
39
+ end
40
+
41
+ class Configuration
42
+ attr_accessor :base_uri, :username, :password, :logger, :domains, :storage, :token_storage_expiry
43
+ end
44
+
45
+ class ApiError < StandardError; end
46
+ class ResponseError < StandardError; end
47
+ class ConnectionError < StandardError; end
48
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'attributes/name'
2
+ require_relative 'attributes/date'
3
+ require_relative 'attributes/address'
4
+ require_relative 'attributes/deposit'
5
+ require_relative 'attributes/organization'
@@ -0,0 +1,13 @@
1
+ require 'virtus'
2
+
3
+ module Mycrm
4
+ module Attributes
5
+ class Address
6
+ include Virtus.model
7
+
8
+ attribute :home, String
9
+ attribute :mail, String
10
+ attribute :business, String
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'virtus'
2
+
3
+ module Mycrm
4
+ module Attributes
5
+ class Date
6
+ include Virtus.model
7
+
8
+ attribute :day, String
9
+ attribute :month, String
10
+ attribute :year, String
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ require 'virtus'
2
+
3
+ module Mycrm
4
+ module Attributes
5
+ class Deposit
6
+ include Virtus.model
7
+
8
+ attribute :amount, Integer
9
+ attribute :source_id, Integer
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ require 'virtus'
2
+
3
+ module Mycrm
4
+ module Attributes
5
+ class Name
6
+ include Virtus.model
7
+
8
+ attribute :display, String #, default: :default_display_name
9
+ attribute :first, String
10
+ attribute :last, String
11
+ attribute :middle, String
12
+
13
+ def set_default_display_name
14
+ self.display = [first, last].join(' ') if first || last
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require 'virtus'
2
+
3
+ module Mycrm
4
+ module Attributes
5
+ class Organization
6
+ include Virtus.model
7
+
8
+ attribute :id, Integer
9
+ attribute :name, String
10
+ attribute :website, String
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,107 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Mycrm
5
+ # this module adds connectivity methods to the descendant's class
6
+ module Connectable
7
+ TOKEN_KEY = 'mycrm_auth_token_key'
8
+ DEFAULT_TOKEN_EXPIRE_IN_STORAGE = 600 # 10 minutes
9
+
10
+ def self.extended(descendant)
11
+ descendant.send(:extend, ExtendedMethods)
12
+ end
13
+
14
+ class << self
15
+ #create the connection with basic configurations
16
+ def connection
17
+ Faraday.new(url: Mycrm.configuration.base_uri) do |faraday|
18
+ faraday.request :url_encoded
19
+ if Mycrm.configuration.logger
20
+ faraday.response :logger, Mycrm.configuration.logger, bodies: true
21
+ end
22
+ yield(faraday) if block_given?
23
+ faraday.adapter Faraday.default_adapter
24
+ end
25
+ rescue Errno::ECONNREFUSED => e
26
+ raise ConnectionError, e
27
+ end
28
+
29
+ def token
30
+ @token || fetch_token
31
+ end
32
+
33
+ def fetch_token
34
+ storage = Mycrm.configuration.storage # Redis or any strategy that implement set and get
35
+
36
+ if storage
37
+ token = storage.get(TOKEN_KEY)
38
+ if token && !(token.nil? || token.empty?)
39
+ token
40
+ else
41
+ token_storage_expiry = Mycrm.configuration.token_storage_expiry || DEFAULT_TOKEN_EXPIRE_IN_STORAGE
42
+ token = fetch_token_from_mycrm
43
+ storage.set(TOKEN_KEY, token, ex: token_storage_expiry)
44
+ token
45
+ end
46
+ else
47
+ fetch_token_from_mycrm
48
+ end
49
+ end
50
+
51
+ def fetch_token_from_mycrm
52
+ connection.post('/Login', credentials).body
53
+ end
54
+
55
+ def credentials
56
+ %w(username password).each_with_object({}) do |f, o|
57
+ o[f] = Mycrm.configuration.send(f)
58
+ end
59
+ end
60
+
61
+ def session
62
+ @token = token
63
+ yield if block_given?
64
+ ensure
65
+ @token = nil
66
+ end
67
+ end
68
+
69
+ # private module to be extended
70
+ module ExtendedMethods
71
+
72
+ def connection
73
+ Mycrm::Connectable.connection do |faraday|
74
+ faraday.headers['Authorization'] = "Bearer #{skim(Mycrm::Connectable.token)}"
75
+ end
76
+ end
77
+
78
+ def respond(response)
79
+ raise ResponseError, skim(response.body) unless response.success?
80
+ raise ResponseError, 'Not found' if 'null'.eql?(skim(response.body))
81
+ parse(response.body)
82
+ end
83
+
84
+ def parse(body)
85
+ JSON.parse(body)
86
+ rescue JSON::ParserError
87
+ raise ResponseError, "#{skim(body)} is not in a valid format"
88
+ end
89
+
90
+ def skim(string)
91
+ string.to_s.gsub(/\A"|"\Z/, '')
92
+ end
93
+
94
+ %w(get post put delete).each do |method|
95
+ define_method method do |uri, body = {}, query = {}|
96
+ response = connection.send(method, uri, query) do |req|
97
+ req.headers['Content-Type'] = 'application/json'
98
+ req.body = JSON.generate(body)
99
+ req.options.timeout = 120
100
+ req.options.open_timeout = 20
101
+ end
102
+ respond(response)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,7 @@
1
+ require 'mycrm/core_ext/string'
2
+ require 'mycrm/core_ext/hash'
3
+
4
+ String.include Mycrm::CoreExt::String
5
+ Symbol.include Mycrm::CoreExt::String
6
+
7
+ Hash.include Mycrm::CoreExt::Hash
@@ -0,0 +1,26 @@
1
+ module Mycrm
2
+ module CoreExt
3
+ class Address
4
+ FIELDS = [:formatted, :street, :suburb, :state, :postcode, :country]
5
+
6
+ attr_reader *FIELDS
7
+
8
+ alias to_s formatted
9
+
10
+ def initialize(formatted, locale = :en_au)
11
+ @formatted = formatted
12
+ expand(locale)
13
+ end
14
+
15
+ def to_h
16
+ FIELDS.reject{ |f| f == :formatted }.each_with_object({}){ |f, out| out[f] = send(f) }
17
+ end
18
+
19
+ private
20
+
21
+ def expand(locale)
22
+ @street, @suburb, @state, @postcode, @country = *formatted.split(',').map(&:strip)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module Mycrm
2
+ module CoreExt
3
+ module Hash
4
+ def symbolize_keys_deep!
5
+ self.replace modify_keys_deep(:to_sym)
6
+ end
7
+
8
+ def stringify_keys_deep!
9
+ self.replace modify_keys_deep(:to_s)
10
+ end
11
+
12
+ def modify_keys_deep(method = :to_sym )
13
+ self.each_with_object({}) do |(key, value), copy|
14
+ copy[key.send(method)] = value.kind_of?(Hash) ? value.modify_keys_deep(method) : value
15
+ end
16
+ end
17
+
18
+ def depp_merge(newhash)
19
+ return self.dup unless newhash
20
+ # merger = -> (key, oldval, newval) { oldval.is_a? Hash ? oldval.recursive_merge(newval) : newval } # not working :(
21
+ new_hash = (newhash.keys - keys).each_with_object({}) { |k, nh| nh[k] = newhash[k] }
22
+ self.each_with_object(new_hash) do |(key, value), nh|
23
+ nh[key] = value.kind_of?(Hash) ? value.depp_merge(newhash[key]) : (newhash[key] || value)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end