concur_connect 0.0.1

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 (41) hide show
  1. data/.document +11 -0
  2. data/.gitignore +40 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +11 -0
  5. data/Guardfile +28 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +28 -0
  8. data/Rakefile +42 -0
  9. data/concur_connect.gemspec +41 -0
  10. data/lib/concur_connect.rb +23 -0
  11. data/lib/concur_connect/expense_finder.rb +30 -0
  12. data/lib/concur_connect/expense_report.rb +21 -0
  13. data/lib/concur_connect/expense_report_finder.rb +44 -0
  14. data/lib/concur_connect/finder.rb +9 -0
  15. data/lib/concur_connect/itinerary.rb +5 -0
  16. data/lib/concur_connect/itinerary_finder.rb +35 -0
  17. data/lib/concur_connect/session.rb +44 -0
  18. data/lib/concur_connect/user.rb +17 -0
  19. data/lib/concur_connect/user_finder.rb +28 -0
  20. data/lib/concur_connect/version.rb +3 -0
  21. data/spec/concur_connect/expense.rb +5 -0
  22. data/spec/concur_connect/expense_finder_spec.rb +77 -0
  23. data/spec/concur_connect/expense_report_finder_spec.rb +81 -0
  24. data/spec/concur_connect/expense_report_spec.rb +5 -0
  25. data/spec/concur_connect/itinerary_finder_spec.rb +43 -0
  26. data/spec/concur_connect/user_finder_spec.rb +26 -0
  27. data/spec/concur_connect/user_spec.rb +21 -0
  28. data/spec/concur_connect_spec.rb +30 -0
  29. data/spec/fixtures/vcr_cassettes/GET_user/v1_0/User.yml +40 -0
  30. data/spec/fixtures/vcr_cassettes/expense_list.yml +36 -0
  31. data/spec/fixtures/vcr_cassettes/expense_report_list_by_date.yml +35 -0
  32. data/spec/fixtures/vcr_cassettes/expense_report_list_by_status.yml +37 -0
  33. data/spec/fixtures/vcr_cassettes/itinerary_list.yml +37 -0
  34. data/spec/fixtures/vcr_cassettes/session_expense_reports.yml +35 -0
  35. data/spec/fixtures/vcr_cassettes/session_expenses.yml +36 -0
  36. data/spec/fixtures/vcr_cassettes/session_user.yml +38 -0
  37. data/spec/fixtures/vcr_cassettes/session_user_expense_reports.yml +34 -0
  38. data/spec/fixtures/vcr_cassettes/user.yml +40 -0
  39. data/spec/spec_helper.rb +12 -0
  40. data/spec/support/vcr.rb +6 -0
  41. metadata +230 -0
@@ -0,0 +1,11 @@
1
+ # .document is used by rdoc and yard to know how to generate documentation
2
+ # for example, it can be used to control how rdoc gets built when you do `gem install foo`
3
+
4
+ README.rdoc
5
+ lib/**/*.rb
6
+ bin/*
7
+
8
+ # Files below this - are treated as 'extra files', and aren't parsed for ruby code
9
+ -
10
+ features/**/*.feature
11
+ LICENSE
@@ -0,0 +1,40 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ # rcov generated
6
+ coverage
7
+
8
+ # rdoc generated
9
+ rdoc
10
+
11
+ # yard generated
12
+ doc
13
+ .yardoc
14
+
15
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
16
+ #
17
+ # * Create a file at ~/.gitignore
18
+ # * Include files you want ignored
19
+ # * Run: git config --global core.excludesfile ~/.gitignore
20
+ #
21
+ # After doing this, these files will be ignored in all your git projects,
22
+ # saving you from having to 'pollute' every project you touch with them
23
+ #
24
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
25
+ #
26
+ # For MacOS:
27
+ #
28
+ #.DS_Store
29
+ #
30
+ # For TextMate
31
+ #*.tmproj
32
+ #tmtags
33
+ #
34
+ # For emacs:
35
+ #*~
36
+ #\#*
37
+ #.\#*
38
+ #
39
+ # For vim:
40
+ #*.swp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'guard'
6
+ gem 'guard-bundler'
7
+ gem 'guard-rspec'
8
+ if RUBY_PLATFORM =~ /darwin/
9
+ gem 'rb-fsevent'
10
+ gem 'growl'
11
+ end
@@ -0,0 +1,28 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'bundler' do
5
+ watch('Gemfile')
6
+ # Uncomment next line if Gemfile contain `gemspec' command
7
+ # watch(/^.+\.gemspec/)
8
+ end
9
+
10
+ guard 'rspec', :version => 2 do
11
+ watch(%r{^spec/.+_spec\.rb$})
12
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
13
+ watch('spec/spec_helper.rb') { "spec" }
14
+
15
+ # Rails example
16
+ watch(%r{^spec/.+_spec\.rb$})
17
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
18
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
19
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
20
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
21
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
22
+ watch('spec/spec_helper.rb') { "spec" }
23
+ watch('config/routes.rb') { "spec/routing" }
24
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
25
+ # Capybara request specs
26
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
27
+ end
28
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Derek Kastner
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,28 @@
1
+ = concur_connect
2
+
3
+ ConcurConnect makes using the Concur Connect API (http://developer.concur.com) a breeze to use.
4
+
5
+ == Installation
6
+
7
+ gem install concur_connect
8
+
9
+ == Quick start
10
+
11
+ require 'concur_connect'
12
+ concur = ConcurConnect.session 'token', 'secret', 'company ID'
13
+ reports = concur.expense_reports Date.new(2011, 9, 1) # all approved reports since 9/1/2011
14
+ puts reports.first.expenses.inspect
15
+
16
+ == Note on Patches/Pull Requests
17
+
18
+ * Fork the project.
19
+ * Make your feature addition or bug fix.
20
+ * Add tests for it. This is important so I don't break it in a
21
+ future version unintentionally.
22
+ * Commit, do not mess with rakefile, version, or history.
23
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
24
+ * Send me a pull request. Bonus points for topic branches.
25
+
26
+ == Copyright
27
+
28
+ Copyright (c) 2011 Derek Kastner. See LICENSE for details.
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'bundler'
5
+ rescue LoadError
6
+ $stderr.puts "You must install bundler - run `gem install bundler`"
7
+ end
8
+
9
+ begin
10
+ Bundler.setup
11
+ rescue Bundler::BundlerError => e
12
+ $stderr.puts e.message
13
+ $stderr.puts "Run `bundle install` to install missing gems"
14
+ exit e.status_code
15
+ end
16
+ require 'rake'
17
+
18
+ require 'bueller'
19
+ Bueller::Tasks.new
20
+
21
+ require 'rspec/core/rake_task'
22
+ RSpec::Core::RakeTask.new(:examples) do |examples|
23
+ examples.rspec_opts = '-Ispec'
24
+ end
25
+
26
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
27
+ spec.rspec_opts = '-Ispec'
28
+ spec.rcov = true
29
+ end
30
+
31
+ task :default => :examples
32
+
33
+ require 'rake/rdoctask'
34
+ Rake::RDocTask.new do |rdoc|
35
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
36
+
37
+ rdoc.main = 'README.rdoc'
38
+ rdoc.rdoc_dir = 'rdoc'
39
+ rdoc.title = "concur_connect #{version}"
40
+ rdoc.rdoc_files.include('README*')
41
+ rdoc.rdoc_files.include('lib/**/*.rb')
42
+ end
@@ -0,0 +1,41 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'concur_connect/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'concur_connect'
6
+ s.version = ConcurConnect::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.date = '2011-10-13'
9
+ s.authors = ['Derek Kastner']
10
+ s.email = 'dkastner@gmail.com'
11
+ s.homepage = 'http://github.com/dkastner/concur_connect'
12
+ s.summary = %Q{Wrapper for ConcurConnect API}
13
+ s.description = %Q{Access your Concur data via OAuth}
14
+ s.extra_rdoc_files = [
15
+ 'LICENSE',
16
+ 'README.rdoc',
17
+ ]
18
+
19
+ s.required_rubygems_version = Gem::Requirement.new('>= 1.3.7')
20
+ s.rubygems_version = '1.3.7'
21
+ s.specification_version = 3
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ['lib']
27
+
28
+ s.add_dependency 'faraday', '~> 0.6.0'
29
+ s.add_dependency 'faraday_middleware'
30
+ s.add_dependency 'multi_xml'
31
+ s.add_dependency 'simple_oauth'
32
+
33
+ s.add_development_dependency 'rspec'
34
+ s.add_development_dependency 'bundler'
35
+ s.add_development_dependency 'bueller'
36
+ s.add_development_dependency 'fakeweb'
37
+ s.add_development_dependency 'rake'
38
+ s.add_development_dependency 'rcov'
39
+ s.add_development_dependency 'vcr'
40
+ end
41
+
@@ -0,0 +1,23 @@
1
+ require 'concur_connect/session'
2
+ require 'concur_connect/version'
3
+
4
+ # ConcurConnect makes using the Concur Connect API
5
+ # a breeze to use.
6
+ #
7
+ # Quick start:
8
+ #
9
+ # concur = ConcurConnect.session 'token', 'secret', 'company ID'
10
+ # reports = concur.expense_reports Date.new(2011, 9, 1) # all approved reports since 9/1/2011
11
+ # puts reports.first.expenses.inspect
12
+ module ConcurConnect
13
+ extend self
14
+
15
+ # Start a connection session to ConcurConnect
16
+ #
17
+ # token: OAuth token
18
+ # secret: OAuth secret
19
+ # company_id: your ConcurConnect company ID (usually your domain)
20
+ def session(token, secret, company_id)
21
+ Session.new token, secret, company_id
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ require 'concur_connect/expense'
2
+ require 'concur_connect/finder'
3
+
4
+ module ConcurConnect
5
+ class ExpenseFinder
6
+ include Finder
7
+
8
+ def find(report_id)
9
+ url = "/api/expense/expensereport/v1.1/report/#{report_id}/entries"
10
+ response = session.get url
11
+ build_expenses response.body
12
+ end
13
+
14
+ def build_expenses(data)
15
+ list = []
16
+ if data['ExpenseEntriesList'] && data['ExpenseEntriesList']['ExpenseEntrySummary']
17
+ items = data['ExpenseEntriesList']['ExpenseEntrySummary']
18
+ items = [items] unless items.is_a?(Array)
19
+ items.each do |datum|
20
+ expense = Expense.new
21
+ expense.type = datum['ExpenseName']
22
+ expense.amount = datum['TransactionAmount']
23
+ expense.vendor = datum['VendorListName']
24
+ list << expense
25
+ end
26
+ end
27
+ list
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ require 'concur_connect/expense_finder'
2
+
3
+ module ConcurConnect
4
+ class ExpenseReport
5
+ attr_accessor :id, :name, :date, :details_url,
6
+ :expenses,
7
+ :session
8
+
9
+ def expense_finder
10
+ @expense_finder ||= ExpenseFinder.new session
11
+ end
12
+
13
+ def api_id
14
+ File.basename details_url
15
+ end
16
+
17
+ def expenses
18
+ @expenses ||= expense_finder.find api_id
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,44 @@
1
+ require "concur_connect/expense_finder"
2
+ require 'concur_connect/expense_report'
3
+ require 'concur_connect/finder'
4
+
5
+ module ConcurConnect
6
+ class ExpenseReportFinder
7
+ include Finder
8
+
9
+ STATUSES = %w{RECENT UNSUBMITTED PENDING APPROVED PROCESSED PAID
10
+ PAYMENTCONFIRMED FORTHISMONTH FORLASTMONTH FORTHISQUARTER
11
+ FORLASTQUARTER FORTHISYEAR FORLASTYEAR TOAPPROVE APPROVEDTHISMONTH
12
+ APPROVEDLASTMONTH APPROVEDTHISQUARTER APPROVEDLASTQUARTER
13
+ APPROVEDTHISYEAR APPROVEDLASTYEAR}
14
+
15
+ # status: status string defined in STATUSES
16
+ # date: a Date/Time object for filtering
17
+ def find(user_id = nil, status = 'APPROVED', date = nil)
18
+ url = "/api/expense/expensereport/v1.1/reportslist/#{status}"
19
+ url += "/LastModified?date=#{date.strftime('%Y-%m-%d')}" if date
20
+ response = session.get url do |g|
21
+ g.headers['X-UserID'] = user_id if user_id
22
+ end
23
+ build_expense_reports response.body
24
+ end
25
+
26
+ def build_expense_reports(data)
27
+ list = []
28
+ if data['ReportsList'] && data['ReportsList']['ReportSummary']
29
+ items = data['ReportsList']['ReportSummary']
30
+ items = [items] unless items.is_a?(Array)
31
+ items.each do |datum|
32
+ expense_report = ExpenseReport.new
33
+ expense_report.id = datum['ReportId']
34
+ expense_report.name = datum['ReportName']
35
+ expense_report.date = datum['ReportDate']
36
+ expense_report.details_url = datum['Report_Details_Url']
37
+ expense_report.session = session
38
+ list << expense_report
39
+ end
40
+ end
41
+ list
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ module ConcurConnect
2
+ module Finder
3
+ attr_accessor :session
4
+
5
+ def initialize(session)
6
+ self.session = session
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module ConcurConnect
2
+ class Itinerary
3
+ attr_accessor :concur_id, :name, :start_date, :end_date
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ require 'concur_connect/itinerary'
2
+
3
+ module ConcurConnect
4
+ class ItineraryFinder
5
+ attr_accessor :session
6
+
7
+ def initialize(session)
8
+ self.session = session
9
+ end
10
+
11
+ def find(user_id, start_date = nil)
12
+ url = 'travel/trip/v1.0/'
13
+ url += "?startDate=#{start_date.strftime('%Y-%m-%d')}" if start_date
14
+ response = session.get url do |g|
15
+ g.headers['X-UserID'] = user_id
16
+ end
17
+ build_itineraries response.body
18
+ end
19
+
20
+ def build_itineraries(data)
21
+ list = []
22
+ if data['ItineraryInfoList'] && data['ItineraryInfoList']['ItineraryInfo']
23
+ data['ItineraryInfoList']['ItineraryInfo'].each do |datum|
24
+ itinerary = Itinerary.new
25
+ itinerary.concur_id = datum['id']
26
+ itinerary.name = datum['TripName']
27
+ itinerary.start_date = datum['StartDateLocal']
28
+ itinerary.end_date = datum['EndDateLocal']
29
+ list << itinerary
30
+ end
31
+ end
32
+ list
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ require "concur_connect/expense_report_finder"
2
+ require "concur_connect/itinerary_finder"
3
+ require 'concur_connect/user_finder'
4
+ require 'delegate'
5
+ require 'faraday/request/oauth'
6
+ require 'faraday/response/parse_xml'
7
+
8
+ module ConcurConnect
9
+ class Session < SimpleDelegator
10
+ attr_accessor :consumer_key, :consumer_secret, :company_id, :debug
11
+
12
+ def initialize(consumer_key, consumer_secret, company_id, debug = false)
13
+ self.consumer_key = consumer_key
14
+ self.consumer_secret = consumer_secret
15
+ self.company_id = company_id
16
+ self.debug = debug
17
+ @user_finder = UserFinder.new self
18
+ @expense_report_finder = ExpenseReportFinder.new self
19
+
20
+ faraday = Faraday.new(:url => 'https://www.concursolutions.com/api') do |builder|
21
+ builder.request :OAuth, {
22
+ :consumer_key => consumer_key,
23
+ :consumer_secret => consumer_secret
24
+ }
25
+ builder.request :url_encoded
26
+ builder.request :json
27
+ builder.response :logger if debug?
28
+ builder.adapter :net_http
29
+
30
+ builder.use Faraday::Response::ParseXml
31
+ end
32
+ __setobj__ faraday # wrap Faraday in a loving embrace
33
+ end
34
+ alias :debug? :debug
35
+
36
+ def user(id)
37
+ @user ||= @user_finder.find id
38
+ end
39
+
40
+ def expense_reports(date, status = 'APPROVED')
41
+ @expense_reports ||= @expense_report_finder.find nil, status, date
42
+ end
43
+ end
44
+ end