sage_one 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.travis.yml +11 -0
- data/.yardopts +4 -0
- data/Gemfile +8 -0
- data/LICENSE +19 -0
- data/README.md +141 -0
- data/Rakefile +4 -0
- data/bin/autospec +16 -0
- data/bin/htmldiff +16 -0
- data/bin/ldiff +16 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/bin/yard +16 -0
- data/bin/yardoc +16 -0
- data/bin/yri +16 -0
- data/lib/faraday/request/oauth2.rb +25 -0
- data/lib/faraday/response/convert_sdata_to_headers.rb +70 -0
- data/lib/faraday/response/raise_sage_one_exception.rb +42 -0
- data/lib/sage_one/client/contacts.rb +14 -0
- data/lib/sage_one/client/sales_invoices.rb +77 -0
- data/lib/sage_one/client.rb +33 -0
- data/lib/sage_one/configuration.rb +81 -0
- data/lib/sage_one/connection.rb +44 -0
- data/lib/sage_one/error.rb +39 -0
- data/lib/sage_one/oauth.rb +49 -0
- data/lib/sage_one/request.rb +72 -0
- data/lib/sage_one/version.rb +3 -0
- data/lib/sage_one.rb +31 -0
- data/sage_one.gemspec +32 -0
- data/spec/faraday/request/oauth2_spec.rb +44 -0
- data/spec/faraday/response/convert_sdata_to_headers_spec.rb +113 -0
- data/spec/faraday/response/raise_sage_one_exception_spec.rb +45 -0
- data/spec/fixtures/contact.json +28 -0
- data/spec/fixtures/invalid_sales_invoice.json +28 -0
- data/spec/fixtures/oauth/invalid_client.json +1 -0
- data/spec/fixtures/oauth/invalid_grant.json +1 -0
- data/spec/fixtures/oauth/oauth_token.json +4 -0
- data/spec/fixtures/sales_invoice.json +43 -0
- data/spec/fixtures/sales_invoices.json +90 -0
- data/spec/sage_one/client/contacts_spec.rb +19 -0
- data/spec/sage_one/client/sales_invoices_spec.rb +53 -0
- data/spec/sage_one/client_spec.rb +41 -0
- data/spec/sage_one/configuration_spec.rb +88 -0
- data/spec/sage_one/connection_spec.rb +36 -0
- data/spec/sage_one/oauth_spec.rb +44 -0
- data/spec/sage_one/request_spec.rb +113 -0
- data/spec/sage_one/version_spec.rb +7 -0
- data/spec/sage_one_spec.rb +38 -0
- data/spec/spec_helper.rb +76 -0
- metadata +301 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Luke Brown, Chris Stainthorpe
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# Sage One [![Build Status](https://secure.travis-ci.org/customersure/sage_one.png?branch=master)][travis] [![Dependency Status](https://gemnasium.com/7b1e86c3d9e3583a684d326a97ba06d0.png)][gemnasium]
|
2
|
+
Faraday-based Ruby wrapper for the Sage One API
|
3
|
+
|
4
|
+
[travis]: http://travis-ci.org/customersure/sage_one
|
5
|
+
[gemnasium]: https://gemnasium.com/customersure/sage_one
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add `sage_one` gem to your `Gemfile`
|
9
|
+
|
10
|
+
gem 'sage_one'
|
11
|
+
|
12
|
+
## Documentation
|
13
|
+
[http://rdoc.info/gems/sage_one][documentation]
|
14
|
+
|
15
|
+
[documentation]: http://rdoc.info/gems/sage_one
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
### Authentication
|
19
|
+
This documentation assumes you have already obtained a client id and client secret from Sage.
|
20
|
+
|
21
|
+
To make any requests to the Sage One API, you must present an OAuth access token. The basic flow for obtaining a token for a user is as follows:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# The code below is over-simplified. Read Sage's own API docs and the documentation for SageOne::Oauth to get
|
25
|
+
# a better idea of how to implement this in the context of your own app.
|
26
|
+
SageOne.configure do |c|
|
27
|
+
c.client_id = "YOUR_CLIENT_ID_OBTAINED_FROM_SAGE"
|
28
|
+
c.client_secret = "YOUR_CLIENT_SECRET_OBTAINED_FROM_SAGE"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Redirect the current user to SageOne. This will give them the choice to link SageOne with your app.
|
32
|
+
# and subsequently redirect them back to your callback_url with an authorisation_code if they choose to do so.
|
33
|
+
redirect_to SageOne.authorize_url('https://www.example.com/your_callback_url')
|
34
|
+
|
35
|
+
# Then, in the callback URL controller, run get_access_token, i.e.
|
36
|
+
response = SageOne.get_access_token(params[:code], 'https://www.example.com/your_callback_url')
|
37
|
+
User.save_access_token(response.access_token) unless response.access_token.nil?
|
38
|
+
```
|
39
|
+
|
40
|
+
### Standard API requests
|
41
|
+
Once you have an access token, configure the client with it, along with your client id and secret, and you're good to go:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
SageOne.configure do |c|
|
45
|
+
c.client_id = "YOUR_CLIENT_ID_OBTAINED_FROM_SAGE"
|
46
|
+
c.client_secret = "YOUR_CLIENT_SECRET_OBTAINED_FROM_SAGE"
|
47
|
+
c.access_token = current_user.access_token
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get an array of all sales invoices
|
51
|
+
invoices = SageOne.sales_invoices
|
52
|
+
|
53
|
+
# Add search params
|
54
|
+
SageOne.sales_invoices(status: 2, contact: 65489)
|
55
|
+
|
56
|
+
# Dates in search params...
|
57
|
+
# The SageOne API requires that you specify dates as dd/mm/yyyy
|
58
|
+
SageOne.sales_invoices(from_date: '21/11/2011')
|
59
|
+
|
60
|
+
# We simplify this by also allowing you to specify a date-like object (anything that responds to strftime)
|
61
|
+
SageOne.sales_invoices(from_date: 2.weeks.ago) #rails
|
62
|
+
|
63
|
+
# Note that we can't protect you from doing the wrong thing with ambiguous dates..
|
64
|
+
SageOne.sales_invoices(from_date: '05/01/2012') # Hope that you meant 5th January and not 1st May
|
65
|
+
```
|
66
|
+
You can configure the `SageOne` client on the fly. For example, if you'd prefer to configure your client_id and secret in an
|
67
|
+
initializer then set the access_token in a controller:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
SageOne.new(access_token: current_user.access_token).sales_invoices
|
71
|
+
```
|
72
|
+
|
73
|
+
### Pagination
|
74
|
+
You can request any 'page' of results returned from the API by adding `start_index: n` to any API call:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
SageOne.sales_invoices(start_index: 30)
|
78
|
+
SageOne.sales_invoices(start_date: '28/02/2010', start_index: 50)
|
79
|
+
|
80
|
+
```
|
81
|
+
|
82
|
+
You can also turn on 'auto traversal' to have the client recursively get a full result set. Beware of using this on large result sets.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
# Recursively request ALL sales invoices, appending them to the body of the request
|
86
|
+
SageOne.new(auto_traversal: true).sales_invoices
|
87
|
+
```
|
88
|
+
|
89
|
+
### Other usage notees
|
90
|
+
- HTTP 1.1 requires that you set a Host: header. Whilst the gem will currently work fine without this, if conformance to the spec is important to you, set this with `request_host=`
|
91
|
+
- You can set a proxy server with `proxy=`
|
92
|
+
- To obtain raw, unprocessed responses back from the API, specify `raw_responses = true`. Read the documentation for more information on this.
|
93
|
+
|
94
|
+
|
95
|
+
## Open Source
|
96
|
+
See the [LICENSE][] file for more information.
|
97
|
+
|
98
|
+
We actively encourage contributions to this gem. Whilst we have provided a robust, tested, documented, full-featured core of an wrapper for this new API, we have to balance our time spent on this gem against [the day job][cs].
|
99
|
+
Therefore, if there are API endpoints we haven't covered yet, please fork us, add tests, documentation and coverage, and submit a pull request.
|
100
|
+
|
101
|
+
### Submitting a Pull Request
|
102
|
+
1. [Fork the repository.][fork]
|
103
|
+
2. [Create a topic branch.][branch]
|
104
|
+
3. Add specs for your unimplemented feature or bug fix.
|
105
|
+
4. Run `bundle exec rake spec`. If your specs pass, return to step 3.
|
106
|
+
5. Implement your feature or bug fix.
|
107
|
+
6. Run `bundle exec rake spec`. If your specs fail, return to step 5.
|
108
|
+
7. Run `open coverage/index.html`. If your changes are not completely covered
|
109
|
+
by your tests, return to step 3.
|
110
|
+
8. Add documentation for your feature or bug fix.
|
111
|
+
9. Run `bundle exec rake doc:yard`. If your changes are not 100% documented, go
|
112
|
+
back to step 8.
|
113
|
+
10. Add, commit, and push your changes.
|
114
|
+
11. [Submit a pull request.][pr]
|
115
|
+
|
116
|
+
[fork]: http://help.github.com/fork-a-repo/
|
117
|
+
[branch]: http://learn.github.com/p/branching.html
|
118
|
+
[pr]: http://help.github.com/send-pull-requests/
|
119
|
+
[cs]: http://www.customersure.com/
|
120
|
+
|
121
|
+
## Supported Ruby Versions
|
122
|
+
TODO
|
123
|
+
|
124
|
+
## Contributors and Inspiration
|
125
|
+
|
126
|
+
[SageOne][sageone] is a product of [The Sage Group][sage].
|
127
|
+
|
128
|
+
The `sage_one` gem was created by [Luke Brown][luke] and [Chris Stainthorpe][chris] whilst at [CustomerSure][cs], but is heavily inspired by the following gems: [Octokit][], [Twitter][], and [instagram-ruby-gem][].
|
129
|
+
|
130
|
+
[sage]: http://www.sage.com/
|
131
|
+
[sageone]: http://www.sageone.com/
|
132
|
+
[luke]: http://www.tsdbrown.com/
|
133
|
+
[chris]: http://www.randomcat.co.uk/
|
134
|
+
[octokit]: https://github.com/pengwynn/octokit/
|
135
|
+
[twitter]: https://github.com/sferik/twitter/
|
136
|
+
[instagram-ruby-gem]: https://github.com/Instagram/instagram-ruby-gem/
|
137
|
+
|
138
|
+
## Copyright
|
139
|
+
Copyright (c) 2012 Chris Stainthorpe, Luke Brown. See [LICENSE][] for details.
|
140
|
+
|
141
|
+
[license]: https://github.com/customersure/sage_one/blob/master/LICENSE
|
data/Rakefile
ADDED
data/bin/autospec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'autospec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('sage_one', 'autospec')
|
data/bin/htmldiff
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'htmldiff' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('sage_one', 'htmldiff')
|
data/bin/ldiff
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'ldiff' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('sage_one', 'ldiff')
|
data/bin/rake
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rake' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('sage_one', 'rake')
|
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('sage_one', 'rspec')
|
data/bin/yard
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'yard' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('yard', 'yard')
|
data/bin/yardoc
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'yardoc' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('yard', 'yardoc')
|
data/bin/yri
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'yri' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('yard', 'yri')
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
module FaradayMiddleware
|
5
|
+
|
6
|
+
AUTH_HEADER = 'Authorization'.freeze
|
7
|
+
|
8
|
+
# Simple middleware that adds the access token to each request.
|
9
|
+
#
|
10
|
+
# The access token is placed in the "Authorization" HTTP request header.
|
11
|
+
# However, an explicit "Authorization" header for the current request
|
12
|
+
# will not be overriden.
|
13
|
+
# @api private
|
14
|
+
class OAuth2 < Faraday::Middleware
|
15
|
+
def call(env)
|
16
|
+
env[:request_headers][AUTH_HEADER] ||= %(Bearer #{@token}) if @token
|
17
|
+
@app.call env
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(app, token = nil)
|
21
|
+
super(app)
|
22
|
+
@token = token
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware/response_middleware'
|
3
|
+
require 'addressable/uri'
|
4
|
+
|
5
|
+
# @api private
|
6
|
+
module FaradayMiddleware
|
7
|
+
|
8
|
+
# Middleware to strip out Sage's pagination SData from the body and place
|
9
|
+
# it in a custom response header instead (Using familiar 'Link' header syntax).
|
10
|
+
# This just leaves the resources in the body which can then be recursively
|
11
|
+
# collected later by following the links.
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
class ConvertSdataToHeaders < ResponseMiddleware
|
15
|
+
|
16
|
+
SDATA_START_INDEX = "$startIndex".freeze
|
17
|
+
SDATA_TOTAL_RESULTS = "$totalResults".freeze
|
18
|
+
SDATA_ITEMS_PER_PAGE = "$itemsPerPage".freeze
|
19
|
+
SDATA_RESOURCES = "$resources".freeze
|
20
|
+
SDATA_HEADER_NAME = "X-SData-Pagination-Links".freeze
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
@app.call(env).on_complete do
|
24
|
+
@response = env[:response]
|
25
|
+
|
26
|
+
# Only proceed if SData is actually present
|
27
|
+
next unless @response.body && @response.body.kind_of?(Hash) && @response.body[SDATA_TOTAL_RESULTS]
|
28
|
+
|
29
|
+
@url = Addressable::URI::parse(env[:url])
|
30
|
+
@url.query_values ||= {}
|
31
|
+
|
32
|
+
# Add 'next' link, if we're not on the last page
|
33
|
+
add_link(next_start_index, 'next') if next_start_index < @response.body[SDATA_TOTAL_RESULTS]
|
34
|
+
|
35
|
+
# Add 'prev' link if we're not on the first page
|
36
|
+
add_link(prev_start_index, 'prev') if prev_start_index >= 0
|
37
|
+
|
38
|
+
# Special case: If we're halfway through the first page, don't allow negative start indices
|
39
|
+
add_link(0, 'prev') if @response.body[SDATA_START_INDEX] != 0 && prev_start_index < 0
|
40
|
+
|
41
|
+
# Add the page links into the header
|
42
|
+
@response.headers[SDATA_HEADER_NAME] = @links.join(', ') unless @links.empty?
|
43
|
+
|
44
|
+
# Strip out the SData from the body
|
45
|
+
env[:body] = @response.body[SDATA_RESOURCES]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(app)
|
50
|
+
@links = []
|
51
|
+
super app
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def add_link(start_index, rel)
|
57
|
+
@url.query_values = @url.query_values.merge({ SDATA_START_INDEX => start_index })
|
58
|
+
@links << %Q{<#{@url.to_str}>; rel="#{rel}"}
|
59
|
+
end
|
60
|
+
|
61
|
+
def next_start_index
|
62
|
+
@response.body[SDATA_START_INDEX] + @response.body[SDATA_ITEMS_PER_PAGE]
|
63
|
+
end
|
64
|
+
|
65
|
+
def prev_start_index
|
66
|
+
@response.body[SDATA_START_INDEX] - @response.body[SDATA_ITEMS_PER_PAGE]
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
module FaradayMiddleware
|
5
|
+
|
6
|
+
# Checks the status of the API request and raises
|
7
|
+
# relevant exceptions when detected.
|
8
|
+
# @see SageOne::Error SageOne::Error for possible errors to rescue from.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class RaiseSageOneException < Faraday::Middleware
|
12
|
+
def call(env)
|
13
|
+
@app.call(env).on_complete do |response|
|
14
|
+
case response[:status].to_i
|
15
|
+
when 400
|
16
|
+
raise SageOne::BadRequest, error_message(response)
|
17
|
+
when 401
|
18
|
+
raise SageOne::Unauthorized, error_message(response)
|
19
|
+
when 403
|
20
|
+
raise SageOne::Forbidden, error_message(response)
|
21
|
+
when 404
|
22
|
+
raise SageOne::NotFound, error_message(response)
|
23
|
+
when 409
|
24
|
+
raise SageOne::Conflict, error_message(response)
|
25
|
+
when 422
|
26
|
+
raise SageOne::UnprocessableEntity, error_message(response)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def error_message(response)
|
34
|
+
JSON.unparse({
|
35
|
+
method: response[:method],
|
36
|
+
url: response[:url].to_s,
|
37
|
+
status: response[:status],
|
38
|
+
body: (response[:body].nil? ? "" : response[:body])
|
39
|
+
})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SageOne
|
2
|
+
class Client
|
3
|
+
module Contacts
|
4
|
+
# Get a contact record by ID
|
5
|
+
# @param [Integer] id Contact ID
|
6
|
+
# @return [Hashie::Mash] Contact record
|
7
|
+
# @example Get a contact:
|
8
|
+
# SageOne.contact(12345)
|
9
|
+
def contact(id)
|
10
|
+
get("contacts/#{id}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module SageOne
|
2
|
+
class Client
|
3
|
+
module SalesInvoices
|
4
|
+
|
5
|
+
# List sales invoices
|
6
|
+
# @param options [Hash] A customizable set of options.
|
7
|
+
# @option options [Integer] :contact Use this to filter by contact id
|
8
|
+
# @option options [Integer] :status Invoice payment status.
|
9
|
+
# @option options [String] :search Filter by contact_name or reference (not case sensitive)
|
10
|
+
# @option options [String, #strftime] :from_date Either a string formatted as 'dd/mm/yyyy' or an object which responds to strftime
|
11
|
+
# @option options [String, #strftime] :to_date Either a string formatted as 'dd/mm/yyyy' or an object which responds to strftime
|
12
|
+
# @option options [Integer] :start_index The start index in pagination to begin from.
|
13
|
+
# @return [Array<Invoice>] A list of all sales_invoices. Each invoice is a Hashie.
|
14
|
+
# @example Get all sales invoices
|
15
|
+
# SageOne.sales_invoices
|
16
|
+
def sales_invoices(options={})
|
17
|
+
get("sales_invoices", options)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a sales invoice
|
21
|
+
# @param options [Hash] A customizable set of options. Note that you don't have to wrap these,
|
22
|
+
# i.e. { sales_invoice: {options} }, just pass in the options hash
|
23
|
+
# @option options [String, #strftime] :date The invoice date either as a dd/mm/yyyy-formatted string or any object that responds to strftime
|
24
|
+
# @option options [Integer] :contact_id The ID of the contact associated with the sales invoice. This must be a customer (contact_type 1).
|
25
|
+
# @option options [String] :contact_name The name of the contact associated with the invoice. This should be the contact[name_and_company_name] from the ID of the specified contact.
|
26
|
+
# @option options [String] :main_address The address where the invoice is to be sent.
|
27
|
+
# @option options [Integer] :carriage_tax_code_id The ID of the tax_rate if sales_invoice[carriage] is supplied.
|
28
|
+
# @option options [Array<Hash>] :line_items_attributes An array of line items. Each Hash requires:
|
29
|
+
# * <b>:unit_price</b> (<tt>Float</tt>) The unit cost of the line item.
|
30
|
+
# * <b>:quantity</b> (<tt>Float</tt>) The number of units on the specified line item.
|
31
|
+
# * <b>:description</b> (<tt>String</tt>) The description of the specified line item, maximum 60 characters.
|
32
|
+
# * <b>:tax_code_id</b> (<tt>Integer</tt>) The ID of the tax_rate for the item line.
|
33
|
+
# * <b>:ledger_account_id</b> (<tt>Integer</tt>) The ID of the income_type.
|
34
|
+
# @return [Invoice] A Hashie of the created invoice
|
35
|
+
# @example Create a sales invoice:
|
36
|
+
# SageOne.create_sales_invoice({
|
37
|
+
# date: Time.now
|
38
|
+
# contact_id: 654
|
39
|
+
# contact_name: "Dave Regis"
|
40
|
+
# main_address: "Regis Enterprises, PO Box 123"
|
41
|
+
# carriage_tax_code_id: 5
|
42
|
+
# line_items_attributes: { unit_price: 12.34, quantity: 1.0, description: "Salmon steak", tax_code_id: 1, ledger_account_id: 987 }
|
43
|
+
# })
|
44
|
+
def create_sales_invoice(options)
|
45
|
+
post('sales_invoices', sales_invoice: options)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Retrieve a sales invoice
|
49
|
+
# @param id [Integer] The id of the invoice you want to retrieve.
|
50
|
+
# @return [Invoice] A Hashie of the requested invoice
|
51
|
+
# @example Load an invoice
|
52
|
+
# invoice = SageOne.sales_invoice(8754)
|
53
|
+
def sales_invoice(id)
|
54
|
+
get "sales_invoices/#{id}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Update a sales invoice
|
58
|
+
# @param id [Integer] The id of the invoice you want to update.
|
59
|
+
# @param (see #create_sales_invoice)
|
60
|
+
# @option (see #create_sales_invoice)
|
61
|
+
# @return [Invoice] A Hashie of the updated invoice
|
62
|
+
def update_sales_invoice(id, options)
|
63
|
+
put("sales_invoices/#{id}", sales_invoice: options)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Delete a sales invoice
|
67
|
+
# @param id [Integer] The id of the invoice you want to delete.
|
68
|
+
# @return [Invoice] A Hashie of the deleted invoice
|
69
|
+
# @example Delete an invoice
|
70
|
+
# invoice = SageOne.delete_sales_invoice!(12)
|
71
|
+
def delete_sales_invoice!(id)
|
72
|
+
delete("sales_invoices/#{id}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'sage_one/connection'
|
2
|
+
require 'sage_one/request'
|
3
|
+
|
4
|
+
require 'sage_one/oauth'
|
5
|
+
|
6
|
+
require 'sage_one/client/sales_invoices'
|
7
|
+
require 'sage_one/client/contacts'
|
8
|
+
|
9
|
+
module SageOne
|
10
|
+
class Client
|
11
|
+
attr_accessor(*Configuration::VALID_OPTIONS_KEYS)
|
12
|
+
|
13
|
+
# Creates an instance of Client configured with
|
14
|
+
# the current SageOne::Configuration options.
|
15
|
+
# Pass in a hash of any valid options to override
|
16
|
+
# them for this instance.
|
17
|
+
#
|
18
|
+
# @see SageOne::Configuration::VALID_OPTIONS_KEYS
|
19
|
+
# SageOne::Configuration::VALID_OPTIONS_KEYS
|
20
|
+
def initialize(options={})
|
21
|
+
options = SageOne.options.merge(options)
|
22
|
+
Configuration::VALID_OPTIONS_KEYS.each do |key|
|
23
|
+
send("#{key}=", options[key])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
include SageOne::Connection
|
28
|
+
include SageOne::Request
|
29
|
+
include SageOne::OAuth
|
30
|
+
include SageOne::Client::SalesInvoices
|
31
|
+
include SageOne::Client::Contacts
|
32
|
+
end
|
33
|
+
end
|