concur_connect 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +11 -0
- data/.gitignore +40 -0
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/Guardfile +28 -0
- data/LICENSE +20 -0
- data/README.rdoc +28 -0
- data/Rakefile +42 -0
- data/concur_connect.gemspec +41 -0
- data/lib/concur_connect.rb +23 -0
- data/lib/concur_connect/expense_finder.rb +30 -0
- data/lib/concur_connect/expense_report.rb +21 -0
- data/lib/concur_connect/expense_report_finder.rb +44 -0
- data/lib/concur_connect/finder.rb +9 -0
- data/lib/concur_connect/itinerary.rb +5 -0
- data/lib/concur_connect/itinerary_finder.rb +35 -0
- data/lib/concur_connect/session.rb +44 -0
- data/lib/concur_connect/user.rb +17 -0
- data/lib/concur_connect/user_finder.rb +28 -0
- data/lib/concur_connect/version.rb +3 -0
- data/spec/concur_connect/expense.rb +5 -0
- data/spec/concur_connect/expense_finder_spec.rb +77 -0
- data/spec/concur_connect/expense_report_finder_spec.rb +81 -0
- data/spec/concur_connect/expense_report_spec.rb +5 -0
- data/spec/concur_connect/itinerary_finder_spec.rb +43 -0
- data/spec/concur_connect/user_finder_spec.rb +26 -0
- data/spec/concur_connect/user_spec.rb +21 -0
- data/spec/concur_connect_spec.rb +30 -0
- data/spec/fixtures/vcr_cassettes/GET_user/v1_0/User.yml +40 -0
- data/spec/fixtures/vcr_cassettes/expense_list.yml +36 -0
- data/spec/fixtures/vcr_cassettes/expense_report_list_by_date.yml +35 -0
- data/spec/fixtures/vcr_cassettes/expense_report_list_by_status.yml +37 -0
- data/spec/fixtures/vcr_cassettes/itinerary_list.yml +37 -0
- data/spec/fixtures/vcr_cassettes/session_expense_reports.yml +35 -0
- data/spec/fixtures/vcr_cassettes/session_expenses.yml +36 -0
- data/spec/fixtures/vcr_cassettes/session_user.yml +38 -0
- data/spec/fixtures/vcr_cassettes/session_user_expense_reports.yml +34 -0
- data/spec/fixtures/vcr_cassettes/user.yml +40 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/vcr.rb +6 -0
- metadata +230 -0
data/.document
ADDED
@@ -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
|
data/.gitignore
ADDED
@@ -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
data/Guardfile
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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,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
|