sage_one 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +14 -0
  2. data/.travis.yml +11 -0
  3. data/.yardopts +4 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +19 -0
  6. data/README.md +141 -0
  7. data/Rakefile +4 -0
  8. data/bin/autospec +16 -0
  9. data/bin/htmldiff +16 -0
  10. data/bin/ldiff +16 -0
  11. data/bin/rake +16 -0
  12. data/bin/rspec +16 -0
  13. data/bin/yard +16 -0
  14. data/bin/yardoc +16 -0
  15. data/bin/yri +16 -0
  16. data/lib/faraday/request/oauth2.rb +25 -0
  17. data/lib/faraday/response/convert_sdata_to_headers.rb +70 -0
  18. data/lib/faraday/response/raise_sage_one_exception.rb +42 -0
  19. data/lib/sage_one/client/contacts.rb +14 -0
  20. data/lib/sage_one/client/sales_invoices.rb +77 -0
  21. data/lib/sage_one/client.rb +33 -0
  22. data/lib/sage_one/configuration.rb +81 -0
  23. data/lib/sage_one/connection.rb +44 -0
  24. data/lib/sage_one/error.rb +39 -0
  25. data/lib/sage_one/oauth.rb +49 -0
  26. data/lib/sage_one/request.rb +72 -0
  27. data/lib/sage_one/version.rb +3 -0
  28. data/lib/sage_one.rb +31 -0
  29. data/sage_one.gemspec +32 -0
  30. data/spec/faraday/request/oauth2_spec.rb +44 -0
  31. data/spec/faraday/response/convert_sdata_to_headers_spec.rb +113 -0
  32. data/spec/faraday/response/raise_sage_one_exception_spec.rb +45 -0
  33. data/spec/fixtures/contact.json +28 -0
  34. data/spec/fixtures/invalid_sales_invoice.json +28 -0
  35. data/spec/fixtures/oauth/invalid_client.json +1 -0
  36. data/spec/fixtures/oauth/invalid_grant.json +1 -0
  37. data/spec/fixtures/oauth/oauth_token.json +4 -0
  38. data/spec/fixtures/sales_invoice.json +43 -0
  39. data/spec/fixtures/sales_invoices.json +90 -0
  40. data/spec/sage_one/client/contacts_spec.rb +19 -0
  41. data/spec/sage_one/client/sales_invoices_spec.rb +53 -0
  42. data/spec/sage_one/client_spec.rb +41 -0
  43. data/spec/sage_one/configuration_spec.rb +88 -0
  44. data/spec/sage_one/connection_spec.rb +36 -0
  45. data/spec/sage_one/oauth_spec.rb +44 -0
  46. data/spec/sage_one/request_spec.rb +113 -0
  47. data/spec/sage_one/version_spec.rb +7 -0
  48. data/spec/sage_one_spec.rb +38 -0
  49. data/spec/spec_helper.rb +76 -0
  50. metadata +301 -0
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ log/*
5
+ .rvmrc
6
+ .DS_Store
7
+ coverage
8
+ *.sublime
9
+ .yardoc
10
+ _yardoc
11
+ spec/reports
12
+ rdoc
13
+ doc/*
14
+ start_irb
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby-19mode
6
+ - rbx-19mode
7
+ - ruby-head
8
+ - jruby-head
9
+ before_install:
10
+ - gem update --system
11
+ - gem --version
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ --no-private
2
+ lib/**/*.rb
3
+ -
4
+ LICENSE
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ platforms :jruby do
4
+ gem "jruby-openssl", "~> 0.7"
5
+ end
6
+
7
+ # Specify your gem's dependencies in sage_one.gemspec
8
+ gemspec
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
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new(:spec)
4
+ task :default => :spec
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