xeroizer 2.15.3 → 2.15.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source "http://rubygems.org"
3
3
  gem 'builder', '>= 2.1.2'
4
4
  gem 'oauth', '0.4.5'
5
5
  gem 'activesupport'
6
+ gem 'tzinfo'
6
7
  gem 'nokogiri'
7
8
  gem 'i18n'
8
9
 
data/Gemfile.lock CHANGED
@@ -35,6 +35,7 @@ GEM
35
35
  test-unit (2.4.8)
36
36
  turn (0.9.4)
37
37
  ansi
38
+ tzinfo (0.3.35)
38
39
  yard (0.7.5)
39
40
 
40
41
  PLATFORMS
@@ -54,4 +55,5 @@ DEPENDENCIES
54
55
  shoulda
55
56
  test-unit
56
57
  turn
58
+ tzinfo
57
59
  yard
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011 Wayne Robinson
1
+ Copyright (c) 2013 Wayne Robinson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -18,3 +18,24 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ -----------------------------------------------------------------------
23
+
24
+ Part of the HTTP communication and OAuth authentication library were
25
+ provided by the https://github.com/tlconnor/xero_gateway gem created by
26
+ Tim Connor (github.com/tlconnor) and Nik Wakelin (github.com/nikz).
27
+ The license for this Gem is included below:
28
+
29
+ Copyright (c) 2008 Tim Connor <tlconnor@gmail.com>
30
+
31
+ Permission to use, copy, modify, and/or distribute this software for any
32
+ purpose with or without fee is hereby granted, provided that the above
33
+ copyright notice and this permission notice appear in all copies.
34
+
35
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
36
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
37
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
38
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
39
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
40
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
41
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/README.md CHANGED
@@ -6,7 +6,7 @@ Xeroizer API Library ![Project status](http://stillmaintained.com/waynerobinson/
6
6
  **Github**: [https://github.com/waynerobinson/xeroizer](https://github.com/waynerobinson/xeroizer)
7
7
  **Author**: Wayne Robinson [http://www.wayne-robinson.com](http://www.wayne-robinson.com)
8
8
  **Contributors**: See Contributors section below
9
- **Copyright**: 2007-2010
9
+ **Copyright**: 2007-2013
10
10
  **License**: MIT License
11
11
 
12
12
  Introduction
@@ -435,9 +435,30 @@ contact.save
435
435
  Have a look at the models in `lib/xeroizer/models/` to see the valid attributes, associations and
436
436
  minimum validation requirements for each of the record types.
437
437
 
438
+ ### Bulk Creates & Updates
439
+
440
+ Xero has a hard daily limit on the number of API requests you can make (currently 1,000 requests
441
+ per account per day). To save on requests, you can batch creates and updates into a single PUT or
442
+ POST call, like so:
443
+
444
+ ```ruby
445
+ contact1 = xero.Contact.create(some_attributes)
446
+ xero.Contact.batch_save do
447
+ contact1.email_address = "foo@bar.com"
448
+ contact2 = xero.Contact.build(some_other_attributes)
449
+ contact3 = xero.Contact.build(some_more_attributes)
450
+ end
451
+ ```
452
+
453
+ `batch_save` will issue one PUT request for every 2,000 unsaved records built within its block, and one
454
+ POST request for evert 2,000 existing records that have been altered within its block. If any of the
455
+ unsaved records aren't valid, it'll return `false` before sending anything across the wire;
456
+ otherwise, it returns `true`. `batch_save` takes one optional argument: the number of records to
457
+ create/update per request. (Defaults to 2,000.)
458
+
438
459
  ### Errors
439
460
 
440
- If a record doesn't match it's internal validation requirements the `#save` method will return
461
+ If a record doesn't match its internal validation requirements, the `#save` method will return
441
462
  `false` and the `#errors` attribute will be populated with what went wrong.
442
463
 
443
464
  For example:
@@ -527,3 +548,9 @@ client = Xeroizer::PublicApplication.new(YOUR_OAUTH_CONSUMER_KEY,
527
548
  YOUR_OAUTH_CONSUMER_SECRET,
528
549
  :rate_limit_sleep => 2)
529
550
  ```
551
+
552
+
553
+ ### Contributors
554
+ Xeroizer was inspired by the https://github.com/tlconnor/xero_gateway gem created by Tim Connor
555
+ and Nik Wakelin and portions of the networking and authentication code are based completely off
556
+ this project. Copyright for these components remains held in the name of Tim Connor.
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
- require 'rake/rdoctask'
3
+ require 'rdoc/task'
4
4
  require 'rubygems'
5
5
  require 'yard'
6
6
 
@@ -52,4 +52,4 @@ end
52
52
  YARD::Rake::YardocTask.new do |t|
53
53
  # t.files = ['lib/**/*.rb', OTHER_PATHS] # optional
54
54
  # t.options = ['--any', '--extra', '--opts'] # optional
55
- end
55
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.15.3
1
+ 2.15.5
data/lib/xeroizer.rb CHANGED
@@ -2,7 +2,6 @@ require 'rubygems'
2
2
  require 'date'
3
3
  require 'forwardable'
4
4
  require 'active_support/inflector'
5
- require 'active_support/memoizable'
6
5
  # require "active_support/core_ext"
7
6
  require 'oauth'
8
7
  require 'oauth/signature/rsa/sha1'
@@ -1,12 +1,27 @@
1
+ # Copyright (c) 2008 Tim Connor <tlconnor@gmail.com>
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
1
15
  module Xeroizer
2
16
  class ApiException < StandardError
3
17
 
4
- attr_reader :type, :message, :xml, :request_body
18
+ attr_reader :type, :message, :xml, :parsed_xml, :request_body
5
19
 
6
- def initialize(type, message, xml, request_body)
20
+ def initialize(type, message, xml, parsed_xml, request_body)
7
21
  @type = type
8
22
  @message = message
9
23
  @xml = xml
24
+ @parsed_xml = parsed_xml
10
25
  @request_body = request_body
11
26
  end
12
27
 
data/lib/xeroizer/http.rb CHANGED
@@ -1,3 +1,17 @@
1
+ # Copyright (c) 2008 Tim Connor <tlconnor@gmail.com>
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
1
15
  module Xeroizer
2
16
  module Http
3
17
 
@@ -69,7 +83,7 @@ module Xeroizer
69
83
 
70
84
  begin
71
85
  attempts += 1
72
- logger.info("\n== [#{Time.now.to_s}] XeroGateway Request: #{uri.request_uri} ") if self.logger
86
+ logger.info("\n== [#{Time.now.to_s}] XeroGateway Request: #{method.to_s.upcase} #{uri.request_uri} ") if self.logger
73
87
 
74
88
  response = case method
75
89
  when :get then client.get(uri.request_uri, headers)
@@ -94,6 +108,8 @@ module Xeroizer
94
108
  handle_oauth_error!(response)
95
109
  when 404
96
110
  handle_object_not_found!(response, url)
111
+ when 503
112
+ handle_oauth_error!(response)
97
113
  else
98
114
  raise "Unknown response code: #{response.code.to_i}"
99
115
  end
@@ -131,16 +147,17 @@ module Xeroizer
131
147
 
132
148
  # XeroGenericApplication API Exceptions *claim* to be UTF-16 encoded, but fail REXML/Iconv parsing...
133
149
  # So let's ignore that :)
134
- # raw_response.gsub! '<?xml version="1.0" encoding="utf-16"?>', ''
150
+ raw_response.gsub! '<?xml version="1.0" encoding="utf-16"?>', ''
135
151
 
136
152
  # doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
137
153
  doc = Nokogiri::XML(raw_response)
138
154
 
139
155
  if doc && doc.root && doc.root.name == "ApiException"
140
156
 
141
- raise ApiException.new(doc.root.xpath("Type").text,
142
- doc.root.xpath("Message").text,
157
+ raise ApiException.new(doc.root.xpath("Type").text,
158
+ doc.root.xpath("Message").text,
143
159
  raw_response,
160
+ doc,
144
161
  request_body)
145
162
 
146
163
  else
@@ -8,20 +8,31 @@ module Xeroizer
8
8
  end
9
9
 
10
10
  class BankTransaction < Base
11
+
12
+ BANK_TRANSACTION_STATUS = {
13
+ 'ACTIVE' => 'Active bank transactions',
14
+ 'DELETED' => 'Deleted bank transactions',
15
+ } unless defined?(BANK_TRANSACTION_STATUS)
16
+ BANK_TRANSACTION_STATUSES = BANK_TRANSACTION_STATUS.keys.sort
17
+
18
+
11
19
  def initialize(parent)
12
20
  super parent
13
21
  self.line_amount_types = "Exclusive"
14
22
  end
15
23
 
16
24
  set_primary_key :bank_transaction_id
25
+ list_contains_summary_only true
26
+
17
27
  string :type
18
28
  date :date
19
29
 
20
- date :updated_date_utc, :api_name => "UpdatedDateUTC"
21
- date :fully_paid_on_date
22
- string :reference
23
- string :bank_transaction_id, :api_name => "BankTransactionID"
24
- boolean :is_reconciled
30
+ datetime_utc :updated_date_utc, :api_name => "UpdatedDateUTC"
31
+ date :fully_paid_on_date
32
+ string :reference
33
+ string :bank_transaction_id, :api_name => "BankTransactionID"
34
+ boolean :is_reconciled
35
+ string :status
25
36
 
26
37
  alias_method :reconciled?, :is_reconciled
27
38
 
@@ -36,6 +47,7 @@ module Xeroizer
36
47
  validates_inclusion_of :type,
37
48
  :in => %w{SPEND RECEIVE}, :allow_blanks => false,
38
49
  :message => "Invalid type. Expected either SPEND or RECEIVE."
50
+ validates_inclusion_of :status, :in => BANK_TRANSACTION_STATUSES, :unless => :new_record?
39
51
 
40
52
  validates_presence_of :contact, :bank_account, :allow_blanks => false
41
53
 
@@ -18,23 +18,23 @@ module Xeroizer
18
18
  set_possible_primary_keys :contact_id, :contact_number
19
19
  list_contains_summary_only true
20
20
 
21
- guid :contact_id
22
- string :contact_number
23
- string :contact_status
24
- string :name
25
- string :tax_number
26
- string :bank_account_details
27
- string :accounts_receivable_tax_type
28
- string :accounts_payable_tax_type
29
- string :first_name
30
- string :last_name
31
- string :email_address
32
- string :skype_user_name
33
- string :contact_groups
34
- string :default_currency
35
- datetime :updated_date_utc, :api_name => 'UpdatedDateUTC'
36
- boolean :is_supplier
37
- boolean :is_customer
21
+ guid :contact_id
22
+ string :contact_number
23
+ string :contact_status
24
+ string :name
25
+ string :tax_number
26
+ string :bank_account_details
27
+ string :accounts_receivable_tax_type
28
+ string :accounts_payable_tax_type
29
+ string :first_name
30
+ string :last_name
31
+ string :email_address
32
+ string :skype_user_name
33
+ string :contact_groups
34
+ string :default_currency
35
+ datetime_utc :updated_date_utc, :api_name => 'UpdatedDateUTC'
36
+ boolean :is_supplier
37
+ boolean :is_customer
38
38
 
39
39
  has_many :addresses, :list_complete => true
40
40
  has_many :phones, :list_complete => true
@@ -44,24 +44,24 @@ module Xeroizer
44
44
  set_possible_primary_keys :credit_note_id, :credit_note_number
45
45
  list_contains_summary_only true
46
46
 
47
- guid :credit_note_id
48
- string :credit_note_number
49
- string :reference
50
- string :type
51
- date :date
52
- date :due_date
53
- string :status
54
- string :line_amount_types
55
- decimal :sub_total, :calculated => true
56
- decimal :total_tax, :calculated => true
57
- decimal :total, :calculated => true
58
- datetime :updated_date_utc, :api_name => 'UpdatedDateUTC'
59
- string :currency_code
60
- datetime :fully_paid_on_date
61
- boolean :sent_to_contact
47
+ guid :credit_note_id
48
+ string :credit_note_number
49
+ string :reference
50
+ string :type
51
+ date :date
52
+ date :due_date
53
+ string :status
54
+ string :line_amount_types
55
+ decimal :sub_total, :calculated => true
56
+ decimal :total_tax, :calculated => true
57
+ decimal :total, :calculated => true
58
+ datetime_utc :updated_date_utc, :api_name => 'UpdatedDateUTC'
59
+ string :currency_code
60
+ datetime :fully_paid_on_date
61
+ boolean :sent_to_contact
62
62
 
63
- belongs_to :contact
64
- has_many :line_items
63
+ belongs_to :contact
64
+ has_many :line_items
65
65
 
66
66
  validates_inclusion_of :type, :in => CREDIT_NOTE_TYPES
67
67
  validates_inclusion_of :status, :in => CREDIT_NOTE_STATUSES, :allow_blanks => true
@@ -127,4 +127,4 @@ module Xeroizer
127
127
  end
128
128
 
129
129
  end
130
- end
130
+ end
@@ -2,7 +2,10 @@ module Xeroizer
2
2
  module Record
3
3
 
4
4
  class InvoiceModel < BaseModel
5
-
5
+ # To create a new invoice, use the folowing
6
+ # $xero_client.Invoice.build(type: 'ACCREC', ..., contact: {name: 'Foo Bar'},...)
7
+ # Note that we are not making an api request to xero just to get the contact
8
+
6
9
  set_permissions :read, :write, :update
7
10
 
8
11
  public
@@ -44,31 +47,31 @@ module Xeroizer
44
47
  set_possible_primary_keys :invoice_id, :invoice_number
45
48
  list_contains_summary_only true
46
49
 
47
- guid :invoice_id
48
- string :invoice_number
49
- string :reference
50
- guid :branding_theme_id
51
- string :url
52
- string :type
53
- date :date
54
- date :due_date
55
- string :status
56
- string :line_amount_types
57
- decimal :sub_total, :calculated => true
58
- decimal :total_tax, :calculated => true
59
- decimal :total, :calculated => true
60
- decimal :amount_due
61
- decimal :amount_paid
62
- decimal :amount_credited
63
- datetime :updated_date_utc, :api_name => 'UpdatedDateUTC'
64
- string :currency_code
65
- datetime :fully_paid_on_date
66
- boolean :sent_to_contact
50
+ guid :invoice_id
51
+ string :invoice_number
52
+ string :reference
53
+ guid :branding_theme_id
54
+ string :url
55
+ string :type
56
+ date :date
57
+ date :due_date
58
+ string :status
59
+ string :line_amount_types
60
+ decimal :sub_total, :calculated => true
61
+ decimal :total_tax, :calculated => true
62
+ decimal :total, :calculated => true
63
+ decimal :amount_due
64
+ decimal :amount_paid
65
+ decimal :amount_credited
66
+ datetime_utc :updated_date_utc, :api_name => 'UpdatedDateUTC'
67
+ string :currency_code
68
+ datetime :fully_paid_on_date
69
+ boolean :sent_to_contact
67
70
 
68
- belongs_to :contact
69
- has_many :line_items
70
- has_many :payments
71
- has_many :credit_notes
71
+ belongs_to :contact
72
+ has_many :line_items
73
+ has_many :payments
74
+ has_many :credit_notes
72
75
 
73
76
  validates_presence_of :date, :due_date, :unless => :new_record?
74
77
  validates_inclusion_of :type, :in => INVOICE_TYPES
@@ -107,14 +110,24 @@ module Xeroizer
107
110
  type == 'ACCREC'
108
111
  end
109
112
 
110
- # Swallow assignment of attributes that should only be calculated automatically.
111
- def sub_total=(value); raise SettingTotalDirectlyNotSupported.new(:sub_total); end
112
- def total_tax=(value); raise SettingTotalDirectlyNotSupported.new(:total_tax); end
113
- def total=(value); raise SettingTotalDirectlyNotSupported.new(:total); end
113
+ def sub_total=(sub_total)
114
+ @sub_total_is_set = true
115
+ attributes[:sub_total] = sub_total
116
+ end
117
+
118
+ def total_tax=(total_tax)
119
+ @total_tax_is_set = true
120
+ attributes[:total_tax] = total_tax
121
+ end
122
+
123
+ def total=(total)
124
+ @total_is_set = true
125
+ attributes[:total] = total
126
+ end
114
127
 
115
128
  # Calculate sub_total from line_items.
116
129
  def sub_total(always_summary = false)
117
- if !always_summary && (new_record? || (!new_record? && line_items && line_items.size > 0))
130
+ if !@sub_total_is_set && not_summary_or_loaded_record(always_summary)
118
131
  sum = (line_items || []).inject(BigDecimal.new('0')) { | sum, line_item | sum + line_item.line_amount }
119
132
 
120
133
  # If the default amount types are inclusive of 'tax' then remove the tax amount from this sub-total.
@@ -127,7 +140,7 @@ module Xeroizer
127
140
 
128
141
  # Calculate total_tax from line_items.
129
142
  def total_tax(always_summary = false)
130
- if !always_summary && (new_record? || (!new_record? && line_items && line_items.size > 0))
143
+ if !@total_tax_is_set && not_summary_or_loaded_record(always_summary)
131
144
  (line_items || []).inject(BigDecimal.new('0')) { | sum, line_item | sum + line_item.tax_amount }
132
145
  else
133
146
  attributes[:total_tax]
@@ -136,13 +149,22 @@ module Xeroizer
136
149
 
137
150
  # Calculate the total from line_items.
138
151
  def total(always_summary = false)
139
- unless always_summary
152
+ if !@total_is_set && not_summary_or_loaded_record(always_summary)
140
153
  sub_total + total_tax
141
154
  else
142
155
  attributes[:total]
143
156
  end
144
157
  end
145
158
 
159
+ def not_summary_or_loaded_record(always_summary)
160
+ !always_summary && loaded_record?
161
+ end
162
+
163
+ def loaded_record?
164
+ new_record? ||
165
+ (!new_record? && line_items && line_items.size > 0)
166
+ end
167
+
146
168
  # Retrieve the PDF version of this invoice.
147
169
  # @param [String] filename optional filename to store the PDF in instead of returning the data.
148
170
  def pdf(filename = nil)