Giter Club home page Giter Club logo

xeroizer's Introduction

Xeroizer API Library

Homepage: http://waynerobinson.github.com/xeroizer

Git: git://github.com/waynerobinson/xeroizer.git

Github: https://github.com/waynerobinson/xeroizer

Author: Wayne Robinson http://www.wayne-robinson.com

Contributors: See Contributors section below

Copyright: 2007-2013

License: MIT License

Introduction

This library is designed to help ruby/rails based applications communicate with the publicly available API for Xero.

If you are unfamiliar with the Xero API, you should first read the documentation located at http://developer.xero.com.

Installation

gem install xeroizer

Basic Usage

require 'rubygems'
require 'xeroizer'

# Create client (used to communicate with the API).
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID, YOUR_OAUTH2_CLIENT_SECRET)

# Retrieve list of contacts (note: all communication must be made through the client).
contacts = client.Contact.all(:order => 'Name')

Authentication

Example Rails Controller

class XeroSessionController < ApplicationController

	before_filter :get_xero_client

	public

		def new
			url = @xero_client.authorize_url(
				# The URL's domain must match that listed for your application
				# otherwise the user will see an invalid redirect_uri error
				redirect_uri: YOUR_CALLBACK_URL,
				# space separated, see all scopes at https://developer.xero.com/documentation/oauth2/scopes.
				# note that `offline_access` is required to get a refresh token, otherwise the access only lasts for 30 mins and cannot be refreshed.
				scope: "accounting.settings.read offline_access"
			)

			redirect_to url
		end

		def create
			token = @xero_client.authorize_from_code(
				params[:code],
				redirect_uri: YOUR_CALLBACK_URL
			)

			connections = @xero_client.current_connections

			session[:xero_auth] = {
				:access_token => token[:access_token],
				:refresh_token => token[:refresh_token],
				:tenant_id => connections[1][:tenant_id]
			}

		end

		def destroy
			session.data.delete(:xero_auth)
		end

	private

		def get_xero_client
			@xero_client = Xeroizer::OAuth2Application.new(
				YOUR_OAUTH2_CLIENT_ID,
				YOUR_OAUTH2_CLIENT_SECRET,
			)

			# Add AccessToken if authorised previously.
			if session[:xero_auth]
				@xero_client.tenant_id = session[:xero_auth][:tenant_id]

				@xero_client.authorize_from_access(session[:xero_auth][:acesss_token])
			end
		end
end

OAuth2 Applications

For more details, checkout Xero's documentation

  1. Generate the authorization url and redirect the user to authenticate
client = Xeroizer::OAuth2Application.new(
	YOUR_OAUTH2_CLIENT_ID,
	YOUR_OAUTH2_CLIENT_SECRET,
)
url = client.authorize_url(
	# The URL's domain must match that listed for your application
	# otherwise the user will see an invalid redirect_uri error
	redirect_uri: YOUR_CALLBACK_URL,
	# space separated, see all scopes at https://developer.xero.com/documentation/oauth2/scopes.
	# note that `offline_access` is required to get a refresh token, otherwise the access only lasts for 30 mins and cannot be refreshed.
	scope: "accounting.settings.read offline_access"
)

# Rails as an example
redirect_to url
  1. In the callback route, use the provided code to retrieve an access token.
token = client.authorize_from_code(
	params[:code],
	redirect_uri: YOUR_CALLBACK_URL
)
token.to_hash
# {
#   "token_type"=>"Bearer",
#   "scope"=>"accounting.transactions.read accounting.settings.read",
#   :access_token=>"...",
#   :refresh_token=>nil,
#   :expires_at=>1615220292
# }

# Save the access_token, refresh_token...
  1. Retrieve the tenant ids.
connections = client.current_connections
# returns Xeroizer::Connection instances

# Save the tenant ids
  1. Use access token and tenant ids to retrieve data.
client = Xeroizer::OAuth2Application.new(
	YOUR_OAUTH2_CLIENT_ID,
	YOUR_OAUTH2_CLIENT_SECRET,
	access_token: access_token,
	tenant_id: tenant_id
)
# OR
client = Xeroizer::OAuth2Application.new(
	YOUR_OAUTH2_CLIENT_ID,
	YOUR_OAUTH2_CLIENT_SECRET,
	tenant_id: tenant_id
).authorize_from_access(access_token)

# use the client
client.Organisation.first

AccessToken Renewal

Renewal of an access token requires the refresh token generated for this organisation. To renew:

client = Xeroizer::OAuth2Application.new(
	YOUR_OAUTH2_CLIENT_ID,
	YOUR_OAUTH2_CLIENT_SECRET,
	access_token: access_token,
	refresh_token: refresh_token
)

client.renew_access_token

If you lose these details at any stage you can always reauthorise by redirecting the user back to the Xero OAuth gateway.

Custom Connections

Custom Connections are a paid-for option for private M2M applications. The generated token expires and needs recreating if expired.

client = Xeroizer::OAuth2Application.new(
	YOUR_OAUTH2_CLIENT_ID,
	YOUR_OAUTH2_CLIENT_SECRET
)

token = client.authorize_from_client_credentials

You can check the status of the token with the expires? and expired? methods.

Retrieving Data

Each of the below record types is implemented within this library. To allow for multiple access tokens to be used at the same time in a single application, the model classes are accessed from the instance of OAuth2Application. All class-level operations occur on this singleton. For example:

xero = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID, YOUR_OAUTH2_CLIENT_SECRET, tenant_id: tenant_id)
xero.authorize_from_access(session[:xero_auth][:access_token])

contacts = xero.Contact.all(:order => 'Name')

new_contact = xero.Contact.build(:name => 'ABC Development')
saved = new_contact.save

#all([options])

Retrieves list of all records with matching options.

Note: Some records (Invoice, CreditNote) only return summary information for the contact and no line items when returning them this list operation. This library takes care of automatically retrieving the contact and line items from Xero on first access however, this first access has a large performance penalty and will count as an extra query towards your 5,000/day and 60/minute request per organisation limit.

Valid options are:

:modified_since

Records modified after this Time (must be specified in UTC).

:order

Field to order by. Should be formatted as Xero-based field (e.g. 'Name', 'ContactID', etc)

:status

Status field for PurchaseOrder. Should be a valid Xero purchase order status.

:date_from

DateFrom field for PurchaseOrder. Should be in YYYY-MM-DD format.

:date_to

DateTo field for PurchaseOrder. Should be in YYYY-MM-DD format.

:where

See Where Filters section below.

#first([options])

This is a shortcut method for all and actually runs all however, this method only returns the first entry returned by all and never an array.

#find(id)

Looks up a single record matching id. This ID can either be the internal GUID Xero uses for the record or, in the case of Invoice, CreditNote and Contact records, your own custom reference number used when creating these records.

Where filters

Hash

You can specify find filters by providing the :where option with a hash. For example:

invoices = Xero.Invoice.all(:where => {:type => 'ACCREC', :amount_due_is_not => 0})

will automatically create the Xero string:

Type=="ACCREC"&&AmountDue<>0

The default method for filtering is the equality '==' operator however, these can be overridden by modifying the postfix of the attribute name (as you can see for the :amount_due field above).

\{attribute_name}_is_not will use '<>'
\{attribute_name}_is_greater_than will use '>'
\{attribute_name}_is_greater_than_or_equal_to will use '>='
\{attribute_name}_is_less_than will use '<'
\{attribute_name}_is_less_than_or_equal_to will use '<='

The default is '=='

Note: Currently, the hash-conversion library only allows for AND-based criteria and doesn't take into account associations. For these, please use the custom filter method below.

Custom Xero-formatted string

Xero allows advanced custom filters to be added to a request. The where parameter can reference any XML element in the resulting response, including all nested XML elements.

Example 1: Retrieve all invoices for a specific contact ID:

	invoices = xero.Invoice.all(:where => 'Contact.ContactID.ToString()=="cd09aa49-134d-40fb-a52b-b63c6a91d712"')

Example 2: Retrieve all unpaid ACCREC Invoices against a particular Contact Name:

	invoices = xero.Invoice.all(:where => 'Contact.Name=="Basket Case" && Type=="ACCREC" && AmountDue<>0')

Example 3: Retrieve all Invoices PAID between certain dates

	invoices = xero.Invoice.all(:where => 'FullyPaidOnDate>=DateTime.Parse("2010-01-01T00:00:00")&&FullyPaidOnDate<=DateTime.Parse("2010-01-08T00:00:00")')

Example 4: Retrieve all Invoices using Paging (batches of 100)

	invoices = xero.Invoice.find_in_batches({page_number: 1}) do |invoice_batch|
	  invoice_batch.each do |invoice|
	    ...
	  end
	end

Example 5: Retrieve all Bank Accounts:

	accounts = xero.Account.all(:where => 'Type=="BANK"')

Example 6: Retrieve all DELETED or VOIDED Invoices:

	invoices = xero.Invoice.all(:where => 'Status=="VOIDED" OR Status=="DELETED"')

Example 7: Retrieve all contacts with specific text in the contact name:

	contacts = xero.Contact.all(:where => 'Name.Contains("Peter")')
	contacts = xero.Contact.all(:where => 'Name.StartsWith("Pet")')
	contacts = xero.Contact.all(:where => 'Name.EndsWith("er")')

Associations

Records may be associated with each other via two different methods, has_many and belongs_to.

has_many example:

invoice = xero.Invoice.find('cd09aa49-134d-40fb-a52b-b63c6a91d712')
invoice.line_items.each do | line_item |
	puts "Line Description: #{line_item.description}"
end

belongs_to example:

invoice = xero.Invoice.find('cd09aa49-134d-40fb-a52b-b63c6a91d712')
puts "Invoice Contact Name: #{invoice.contact.name}"

Attachments

Files or raw data can be attached to record types attach_data examples:

invoice = xero.Invoice.find('cd09aa49-134d-40fb-a52b-b63c6a91d712')
invoice.attach_data("example.txt", "This is raw data", "txt")
attach_data('cd09aa49-134d-40fb-a52b-b63c6a91d712', "example.txt", "This is raw data", "txt")

attach_file examples:

invoice = xero.Invoice.find('cd09aa49-134d-40fb-a52b-b63c6a91d712')
invoice.attach_file("example.png", "/path/to/image.png", "image/png")
attach_file('cd09aa49-134d-40fb-a52b-b63c6a91d712', "example.png", "/path/to/image.png", "image/png")

include with online invoice To include an attachment with an invoice set include_online parameter to true within the options hash

invoice = xero.Invoice.find('cd09aa49-134d-40fb-a52b-b63c6a91d712')
invoice.attach_file("example.png", "/path/to/image.png", "image/png", { include_online: true })

Creating/Updating Data

Creating

New records can be created like:

contact = xero.Contact.build(:name => 'Contact Name')
contact.first_name = 'Joe'
contact.last_name = 'Bloggs'
contact.add_address(:type => 'STREET', :line1 => '12 Testing Lane', :city => 'Brisbane')
contact.add_phone(:type => 'DEFAULT', :area_code => '07', :number => '3033 1234')
contact.add_phone(:type => 'MOBILE', :number => '0412 123 456')
contact.save

To add to a has_many association use the add_{association} method. For example:

contact.add_address(:type => 'STREET', :line1 => '12 Testing Lane', :city => 'Brisbane')

To add to a belongs_to association use the build_{association} method. For example:

invoice.build_contact(:name => 'ABC Company')

Updating

If the primary GUID for the record is present, the library will attempt to update the record instead of creating it. It is important that this record is downloaded from the Xero API first before attempting an update. For example:

contact = xero.Contact.find("cd09aa49-134d-40fb-a52b-b63c6a91d712")
contact.name = "Another Name Change"
contact.save

Have a look at the models in lib/xeroizer/models/ to see the valid attributes, associations and minimum validation requirements for each of the record types.

Some Xero endpoints, such as Payment, will only accept specific attributes for updates. Because the library does not have this knowledge encoded (and doesn't do dirty tracking of attributes), it's necessary to construct new objects instead of using the ones retrieved from Xero:

delete_payment = gateway.Payment.build(id: payment.id, status: 'DELETED')
delete_payment.save

Bulk Creates & Updates

Xero has a hard daily limit on the number of API requests you can make (currently 5,000 requests per account per day). To save on requests, you can batch creates and updates into a single PUT or POST call, like so:

contact1 = xero.Contact.create(some_attributes)
xero.Contact.batch_save do
  contact1.email_address = "[email protected]"
  contact2 = xero.Contact.build(some_other_attributes)
  contact3 = xero.Contact.build(some_more_attributes)
end

batch_save will issue one PUT request for every 2,000 unsaved records built within its block, and one POST request for every 2,000 existing records that have been altered within its block. If any of the unsaved records aren't valid, it'll return false before sending anything across the wire; otherwise, it returns true. batch_save takes one optional argument: the number of records to create/update per request. (Defaults to 2,000.)

If you'd rather build and send the records manually, there's a save_records method:

contact1 = xero.Contact.build(some_attributes)
contact2 = xero.Contact.build(some_other_attributes)
contact3 = xero.Contact.build(some_more_attributes)
xero.Contact.save_records([contact1, contact2, contact3])

It has the same return values as batch_save.

Errors

If a record doesn't match its internal validation requirements, the #save method will return false and the #errors attribute will be populated with what went wrong.

For example:

contact = xero.Contact.build
saved = contact.save

# contact.errors will contain [[:name, "can't be blank"]]

#errors_for(:attribute_name) is a helper method to return just the errors associated with that attribute. For example:

contact.errors_for(:name) # will contain ["can't be blank"]

If something goes really wrong and the particular validation isn't handled by the internal validators then the library may raise a Xeroizer::ApiException.

Example Use Cases

Creating & Paying an invoice:

contact = xero.Contact.first

# Build the Invoice, add a LineItem and save it
invoice = xero.Invoice.build(:type => "ACCREC", :contact => contact, :date => DateTime.new(2017,10,19), :due_date => DateTime.new(2017,11,19))

invoice.add_line_item(:description => 'test', :unit_amount => '200.00', :quantity => '1', :account_code => '200')

invoice.save

# An invoice created without a status will default to 'DRAFT'
invoice.approved?

# Payments can only be created against 'AUTHORISED' invoices
invoice.approve!

# Find the first bank account
bank_account = xero.Account.first(:where => {:type => 'BANK'})

# Create & save the payment
payment = xero.Payment.build(:invoice => invoice, :account => bank_account, :amount => '220.00')
payment.save

# Reload the invoice from the Xero API
invoice = xero.Invoice.find(invoice.id)

# Invoice status is now "PAID" & Payment details have been returned as well
invoice.status
invoice.payments.first
invoice.payments.first.date

Reports

All Xero reports except GST report can be accessed through Xeroizer.

Currently, only generic report access functionality exists. This will be extended to provide a more report-specific version of the data in the future (public submissions are welcome).

Reports are accessed like the following example:

trial_balance = xero.TrialBalance.get(:date => DateTime.new(2011,3,21))

profit_and_loss = xero.ProfitAndLoss.get(fromDate: Date.new(2019,4,1), toDate: Date.new(2019,5,1))

# Array containing report headings.
trial_balance.header.cells.map { | cell | cell.value }

# Report rows by section
trial_balance.sections.each do | section |
	puts "Section Title: #{section.title}"
	section.rows.each do | row |
		puts "\t#{row.cells.map { | cell | cell.value }.join("\t")}"
	end
end

# Summary row (if only one on the report)
trial_balance.summary.cells.map { | cell | cell.value }

# All report rows (including HeaderRow, SectionRow, Row and SummaryRow)
trial_balance.rows.each do | row |
	case row
		when Xeroizer::Report::HeaderRow
			# do something with header

		when Xeroizer::Report::SectionRow
			# do something with section, will need to step into the rows for this section

		when Xeroizer::Report::Row
			# do something for standard report rows

		when Xeroizer::Report::SummaryRow
			# do something for summary rows

	end
end

Xero API Rate Limits

The Xero API imposes the following limits on calls per organisation:

  • A limit of 60 API calls in any 60 second period
  • A limit of 5000 API calls in any 24 hour period

By default, the library will raise a Xeroizer::OAuth::RateLimitExceeded exception when one of these limits is exceeded.

If required, the library can handle these exceptions internally by sleeping for a configurable number of seconds and then repeating the last request. You can set this option when initializing an application:

# Sleep for 2 seconds every time the rate limit is exceeded.
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID,
                                         YOUR_OAUTH2_CLIENT_SECRET,
                                         :rate_limit_sleep => 2)

Xero API Nonce Used

The Xero API seems to reject requests due to conflicts on occasion.

By default, the library will raise a Xeroizer::OAuth::NonceUsed exception when one of these limits is exceeded.

If required, the library can handle these exceptions internally by sleeping 1 second and then repeating the last request. You can set this option when initializing an application:

# Sleep for 1 second and retry up to 3 times when Xero claims the nonce was used.
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID,
                                         YOUR_OAUTH2_CLIENT_SECRET,
                                         :nonce_used_max_attempts => 3)

Logging

You can add an optional parameter to the Xeroizer Application initialization, to pass a logger object that will need to respond_to :info. For example, in a rails app:

XeroLogger = Logger.new('log/xero.log', 'weekly')
client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID,
                                         YOUR_OAUTH2_CLIENT_SECRET,
                                         :logger => XeroLogger)

HTTP Callbacks

You can provide "before", "after" and "around" callbacks which will be invoked every time Xeroizer makes an HTTP request, which is potentially useful for both throttling and logging:

Xeroizer::OAuth2Application.new(
  credentials[:key], credentials[:secret],
  before_request: ->(request) { puts "Hitting this URL: #{request.url}" },
  after_request: ->(request, response) { puts "Got this response: #{response.code}" },
  around_request: -> (request, &block)  { puts "About to send request"; block.call; puts "After request"}
)

The request parameter is a custom Struct with url, headers, body, and params methods. The response parameter is a Net::HTTPResponse object.

Unit Price Precision

By default, the API accepts unit prices (UnitAmount) to two decimals places. If you require greater precision, you can opt-in to 4 decimal places by setting an optional parameter when initializing an application:

client = Xeroizer::OAuth2Application.new(YOUR_OAUTH2_CLIENT_ID,
                                         YOUR_OAUTH2_CLIENT_SECRET,
                                         :unitdp => 4)

This option adds the unitdp=4 query string parameter to all requests for models with line items - invoices, credit notes, bank transactions and receipts.

Tests

OAuth2 Tests

The tests within the repository can be run by setting up a OAuth2 App. You can create a Private App in the developer portal, it's suggested that you create it against the Demo Company (AU). Demo Company expires after 28 days, so you will need to reset it and re-connect to it if your Demo Company has expired. Make sure you create the Demo Company in Australia region.

export XERO_CLIENT_ID="asd"
export XERO_CLIENT_SECRET="asdfg"
export XERO_ACCESS_TOKEN="sadfsdf"
export XERO_TENANT_ID="asdfasdfasdfasd"

rake test

Contributors

Xeroizer was inspired by the https://github.com/tlconnor/xero_gateway gem created by Tim Connor and Nik Wakelin and portions of the networking and authentication code are based completely off this project. Copyright for these components remains held in the name of Tim Connor.

xeroizer's People

Contributors

andywendt avatar ben-biddington avatar bradleypriest avatar cameronbourgeois avatar clockwerx avatar colleenbprice avatar davidbasalla avatar davwards avatar fimmtiu avatar francois avatar gherry avatar isaac avatar jamesknelson avatar jono-booth avatar kcotugno avatar latentflip avatar layby42 avatar mhssmnn avatar nnc avatar pareeohnos avatar philals avatar radar avatar rjaus avatar ryanking1809 avatar sorchaabel avatar timdiggins avatar warrenchaudhry avatar wasabhi avatar wasabigeek avatar waynerobinson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xeroizer's Issues

Issue when creating a new Manual Journal

I've noticed some OAuth related issues when attempting to add journal entries in the 2.15.0 gem.

journal = xero.ManualJournal.build({
        :date => Date.today,
        :status => 'DRAFT',
        :narration => "A test journal entry.",
        :journal_lines => [
            {:line_amount => 100, :account_code => '200'},
            {:line_amount => -100, :account_code => '200'}
        ]
    })
journal.save

Results in:

Xeroizer::OAuth::UnknownError: signature_invalid:Failed to validate signature
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/xeroizer-2.15.0/lib/xeroizer/http.rb:124:in `handle_oauth_error!'
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/xeroizer-2.15.0/lib/xeroizer/http.rb:94:in `http_request'
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/xeroizer-2.15.0/lib/xeroizer/http.rb:35:in `http_put'
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/xeroizer-2.15.0/lib/xeroizer/application_http_proxy.rb:20:in `http_put'
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/xeroizer-2.15.0/lib/xeroizer/record/base.rb:103:in `create'
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/xeroizer-2.15.0/lib/xeroizer/record/base.rb:81:in `save'
        from (irb):20
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.3/lib/rails/commands/console.rb:47:in `start'
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.3/lib/rails/commands/console.rb:8:in `start'
        from /Users/adam/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.3/lib/rails/commands.rb:41:in `<top (required)>'
        from ./script/rails:6:in `require'
        from ./script/rails:6:in `<main>'

I haven't look at this yet but I'll be taking a look in a moment to see if I can spot anything.

Fetching ManualJournals (xero.ManualJournal.first) works fine.

Invoice void fails

The following code is failing

invoice.void!

returns

Error: undefined method `delete_or_void_invoice!' for #<Xeroizer::Record::Invoice:0x2d24c30>

Line item values are incorrect if the invoice was created "tax inclusive" in xero.

The calculation of line items's subtotal and total is incorrect if the invoice was created "tax inclusive" in Xero.

This seems to be because whether an invoice is tax inclusive/exclusive is defined at the invoice level, and Xeroizer isn't changing the way it calculates line item amounts depending on that setting. This doesn't affect an invoices total amount, just the amounts on the line items themselves.

I'm going to dig into this and get it fixed, but just wanted to raise it in case I'm missing something. @waynerobinson?

Use of ActiveSupport::Memoizable stops rails 2.3.11 render working

Commit 7c6c3ed , which introduces ActiveSupport::Memoizable stops implicit rendering of Xeroizer objects working in Rails 2.3.11 (and probably other 2.3.x version).

We use code like this fairly extensively in our project:

<%= render @xeroizer_instance %>

which has stopped working at the commit above. I now get 'attempt to modify a frozen object' whenever I make this call. Ive traced this down to the following code which render uses to determine the model name to render:

# lib/active_support/core_ext/module/model_naming.rb
module CoreExtensions
  module Module
    # Returns an ActiveSupport::ModelName object for module. It can be
    # used to retrieve all kinds of naming-related information.
    def model_name
      @model_name ||= ::ActiveSupport::ModelName.new(name)
    end
  end
end

This seems to be creating a problem with Xeroizer::Record::BaseModel.model_class (I'll leave a commit comment on the relevant lines), which is also probably due to the big chunk of class_eval code that ActiveSupport::Memoizable#memoize
does, which includes several freeze calls. See lib/active_support/memoizable.rb:56

Reverting Xeroizer::Record::BaseModel.model_class to a run of the mill ||= memoize fixes the issue for me.

How do you run the tests for Xeroizer?

We've been trying to run it but it seems it needs ENV["CONSUMER_KEY"], ENV["CONSUMER_SECRET"], and ENV["KEY_FILE"]

I assume key and secret are our applications api key and secret, but what is the key file? I'm thinking it is for partner/private apps, but what if your app is pubic? Why is it required?

Description Only line items

Is it possible to add line items that are description only?

I use the invoice.add_line_item and omit unit_amount and quantity. These are still sent through as 0.00 and 1 respectively (default values I guess).

API Docs say " A line item with just a description (i.e no unit amount or quantity) can be created by specifying just a element that contains at least 1 character"

No validation if invoice date is before end of year lock date

The users for our xero app use Xero's lock date feature and checking for an invoice's validity is giving us a 'true' even if it is past the lock date. This shouldn't be a problem if we processed invoices one at a time, but using batch save, it breaks because batch save returns true (since xeroizer thinks it is valid)

However if you try to save you get this error:

<ValidationErrors>
  <ValidationError>
    <Message>The document date cannot be before the end of year lock date, currently set at 31-Jul-2013</Message>
  </ValidationError>
</ValidationErrors>

Obtaining tracking category option ids

When I use api.xero.com to test retrieving TrackingCategories, the xml returned for options has a name and an ID.

...

e776a6f4-8fd1-4d47-94ef-2497b5326001
My option name

...

How do I access the tracking option id?

I've tried using xero_client.TrackingCategory.all[0].options[0].option, but the object returned is a Xeroizer::Record::Option with only a name attribute.

Why do we need to download a record before updating it?

from docs:

"If the primary GUID for the record is present, the library will attempt to update the record instead of creating it. It is important that this record is downloaded from the Xero API first before attempting an update. For example:"

contact = xero.Contact.find("cd09aa49-134d-40fb-a52b-b63c6a91d712")
contact.name = "Another Name Change"
contact.save

I am asking because our app supports 2-way syncing: either you put changes from Xero to the app or vice versa. Syncing is done as a job as a whole, so to save on API calls, we use the batch_save feature (by @fimmtiu). Problem with this is if there are 100+ records, you need to select them all by bulk. Otherwise you'll have to have a separate API call to download each of the records from Xero before updating them.

Based on the API though, it looks like Xero just accepts the id, no need to download the records. Is there an architectural issue why we need to download first? Thanks!

Attachment

Will we get to see Attachment support?

Invoice UpdatedDateUTC is being incorrectly parsed as a system-local Datetime

The UpdatedDateUTC element in Invoice is being parsed as a system-local time, rather than in UTC.

So, an UpdatedDateUTC element like this:

<UpdatedDateUTC>2012-11-20T12:43:24.713</UpdatedDateUTC>

Will produce a Ruby Time object in the system's local time zone (assuming Wellington time) of

irb(main):011:0> Time.now
=> Wed, 21 Nov 2012 08:20:10 NZDT +13:00
irb(main):012:0> t = Time.parse("2012-11-20T12:43:24.713")
=> Tue, 20 Nov 2012 12:43:24 NZDT +13:00
irb(main):013:0> t.getutc
=> Mon, 19 Nov 2012 23:43:24 UTC +00:00

Rather than

irb(main):021:0> ActiveSupport::TimeZone["UTC"].parse("2012-11-20T12:43:24.713")
=> Tue, 20 Nov 2012 12:43:24 UTC +00:00

The problematic line is https://github.com/waynerobinson/xeroizer/blob/master/lib/xeroizer/record/xml_helper.rb#L25

Can't create tax inclusive invoice

Hey, I'm trying to create an invoice being tax inclusive, but failed to do so:

    xero_invoice.line_amount_types = 'Inclusive'

On xero: Amounts are Tax Exclusive

Update: I can see on the xml generated that it's sending the line amount as Inclusive, but xero is not understanding it.

If Xero is down, 503 error not caught

Over the weekend, Xero upgraded their infrastructure and took their app offline for an extended period of time. I dont envision this happening again soon, however the Xero app was returning a 503 error, which resulted in an exception being generated:

https://gist.github.com/4654541

Would be great to catch these exceptions and inform the end user that the Xero service is having issues.

Bulk upload supported?

Is it possible to submit more than 1 invoice so as to avoid the 1000 API calls per day if I have many invoices using the summarizeErrors=false

/api.xro/2.0/Invoices?summarizeErrors=false

Can't update an invoices status to authorised

I have some code which mass approves draft invoices in Xero which has recently stopped working. I can't seem to get it to work, even with a simple example using the rails console and a Xero demo company.

As shown in the code sample below:

  • I pull down a draft invoice from the demo company,
  • Attempt to set the status to "AUTHORISED"
  • Then save it.

This returns true and doesn't return any errors. When I pull down the invoice a second time, the status doesn't seem to have been updated.

I'm pretty sure I was able to do this last month! I'm not sure if I'm going crazy or I'm doing something incorrectly.

irb(main):024:0> xero_invoice = xero.Invoice.first(:where => {:reference => 122343434})
=> #<Xeroizer::Record::Invoice :contact: #<Xeroizer::Record::Contact :contact_id: "f4bcc6c9-d955-4466-a54d-92bd02039a3c", :name: "Arabica Cafe">, :date: Thu, 11 Oct 2012, :due_date: Mon, 15 Oct 2012, :branding_theme_id: "7889a0ac-262a-40e3-8a63-9a769b1a18af", :status: "DRAFT", :line_amount_types: "Exclusive", :sub_total: #<BigDecimal:7fa954b35830,'0.25E3',9(18)>, :total_tax: #<BigDecimal:7fa954b353a8,'0.375E2',18(18)>, :total: #<BigDecimal:7fa954b34fe8,'0.2875E3',18(18)>, :updated_date_utc: 2012-10-02 03:33:05 +1300, :currency_code: "NZD", :type: "ACCREC", :invoice_id: "71535ce7-c215-4804-97db-8830e74cf229", :invoice_number: "INV-0040", :reference: "122343434", :amount_due: #<BigDecimal:7fa954b49510,'0.2875E3',18(18)>, :amount_paid: #<BigDecimal:7fa954b49010,'0.0',9(18)>>
irb(main):025:0> xero_invoice.status = "AUTHORISED"
=> "AUTHORISED"
irb(main):026:0> xero_invoice.save
=> true
irb(main):027:0> xero_invoice = xero.Invoice.first(:where => {:reference => 122343434})
=> #<Xeroizer::Record::Invoice :contact: #<Xeroizer::Record::Contact :contact_id: "f4bcc6c9-d955-4466-a54d-92bd02039a3c", :name: "Arabica Cafe">, :date: Thu, 11 Oct 2012, :due_date: Mon, 15 Oct 2012, :branding_theme_id: "7889a0ac-262a-40e3-8a63-9a769b1a18af", :status: "DRAFT", :line_amount_types: "Exclusive", :sub_total: #<BigDecimal:7fa9564ca930,'0.25E3',9(18)>, :total_tax: #<BigDecimal:7fa9564ca4a8,'0.375E2',18(18)>, :total: #<BigDecimal:7fa9564ca110,'0.2875E3',18(18)>, :updated_date_utc: 2012-10-02 03:34:59 +1300, :currency_code: "NZD", :type: "ACCREC", :invoice_id: "71535ce7-c215-4804-97db-8830e74cf229", :invoice_number: "INV-0040", :reference: "122343434", :amount_due: #<BigDecimal:7fa9564cfca0,'0.2875E3',18(18)>, :amount_paid: #<BigDecimal:7fa9564cf7a0,'0.0',9(18)>>

Am I missing something here?

Child nodes retain parent node tag?

In xml_helper there's code that will retain the parent xml tag name:

def to_xml(b = Builder::XmlMarkup.new(:indent => 2))
  optional_root_tag(parent.class.optional_xml_root_name, b) do |b|
    b.tag!(parent.class.xml_node_name || parent.model_name) { 
      attributes.each do | key, value |
        field = self.class.fields[key]
        value = self.send(key) if field[:calculated]
        xml_value_from_field(b, field, value) unless value.nil?
      end
    }
  end
end

This is causing XML like this for Invoices:

<Invoice>
  <Invoice>
    <ContactID>ac565f9c-9829-4f51-8a5b-0400de972bbb</ContactID>;
    <Name>PowerDirect</Name>
  </Invoice>

  <Type>ACCPAY</Type>
  <InvoiceID>20ff01b8-c2d8-49bb-8abf-a9486c9ea123</InvoiceID>
  ...
</Invoice>

Is there any reason for this logic? I was notified by the Xero developer team that we're submitting odd XML. It works, but it's not ideal. I'm wondering what case(s) is this actually used as written.

RuntimeError: Unknown response code: 503

Hello.

I'm getting this error:

RuntimeError: Unknown response code: 503

when calling the following line of code:

xero.Contact.build(contact.api_data)

503 is related to rate limiting imposed by Xero. However, in this case, I'm making only one API call, not many calls.

The strange thing is that this used to work, but has stopped working suddenly.

I'm using a test xero account with around 415 contacts already. I don't know if there's a limit on how many contacts can be added to a test account.

I'm also using xeroizer version 2.15.0, should I upgrade to 2.15.5 (the latest) to fix this?

Thanks for your help

Invoice: change status from DRAFT to AUTHORISED

Hello,

Based on the discussion (https://community.xero.com/developer/discussion/47901/) it's possible to change invoice status via Xero API (I've contacted Xero support and they've confirmed the following):

  • DRAFT to DELETED
  • DRAFT to SUBMITTED
  • DRAFT to AUTHORISED
  • SUBMITTED to DRAFT
  • SUBMITTED to DELETED
  • SUBMITTED to AUTHORISED
  • AUTHORISED to VOIDED

I'm trying to change DRAFT to AUTHORISED via xeroizer:
xero_invoice = gateway.Invoice.first(:where => {:invoice_number => invoice_number})

xero_invoice.status # --> DRAFT
xero_invoice.status = 'AUTHORISED'
xero_invoice.status # --> AUTHORISED
xero_invoice.save # --> true, no errors

xero_invoice.status # --> DRAFT

Status not get changed, no errors returned.

I'm trying to change status for the same invoice via https://api.xero.com/Preview/Invoices/POST using xml:

6a625cfc-bec8-4739-9d78-df75cd663e3c
AUTHORISED

status successfully changed. So it's not an issue on Xero side or with invoice data (invoice is perfectly OK to get status changed from DRAFT to AUTHORISED).

Could you please take a look at the issue?

Inquiring about constructing api_endpoint

Had trouble connecting to Demo App endpoints using Xeroizer, and when using .inspect, found that the endpoint is not properly constructed. Specifically, in my invoices controller, I have

def index
  @clients = xero_client.Contact.inspect
end

which returns something like this in the browser:

#<Xeroizer::Record::ContactModel:0xb588ba70 
@application=#<Xeroizer::PublicApplication:0xb588bbb0 
@xero_url="https://api.xero.com/api.xro/2.0"
...etc...
@Contact_cache=#<Xeroizer::Record::ContactModel:0xb588ba70 ...>>, 
@model_name="Contact">

How can one use the construction practices in xeroizer to add/update @xero_url such that the endpoint is accurate - something like: https://api.xer.com/api.xro/2.0/#{_ACCOUNT_}

Totals missing in BankTransactions

Hi - im trying to get the SPEND BankTransactions. I can get the BankTransactions but i the totals are not present. I think a change was made in Xero's API which may have broken your code.

This is an example BankTransaction (from the Demo account) im seeing from your gem. As you can see Total/SubTotal etc are all missing.

<Xeroizer::Record::BankTransaction :line_amount_types: "Inclusive", :contact: #<Xeroizer::Record::Contact :contact_id: "b89da77c-1a13-4893-8d65-71cd38fc623a", :name: "Fulton Airport Parking">, :date: Mon, 08 Jul 2013, :updated_date_utc: Mon, 08 Jul 2013, :bank_transaction_id: "8d9e73dd-2a51-4f2d-bd52-84bd56eb079b", :bank_account: #<Xeroizer::Record::BankAccount :account_id: "15cb3742-e516-451a-9468-2b362fd2b017">, :type: "SPEND", :is_reconciled: true>

Can't update invoice

When changing the value of an invoice item, I receive an error: <Message>The document total does not equal the sum of the lines.</Message>

XML:

Xeroizer::ApiException: ValidationException: A validation exception occurred
 Generated by the following XML:
 <ApiException xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ErrorNumber>10</ErrorNumber>
  <Type>ValidationException</Type>
  <Message>A validation exception occurred</Message>
  <Elements>
    <DataContractBase xsi:type="Invoice">
      <ValidationErrors>
        <ValidationError>
          <Message>The document total does not equal the sum of the lines.</Message>
        </ValidationError>
      </ValidationErrors>
      <Warnings />
      <Contact>
        <ContactID>c4a91373-0392-4cb2-b156-698273f9be24</ContactID>
        <ContactNumber>1</ContactNumber>
        <ContactStatus>ACTIVE</ContactStatus>
        <Name>Bruno Pinto</Name>
        <FirstName>Bruno</FirstName>
        <LastName>Pinto</LastName>
        <EmailAddress>**</EmailAddress>
        <BankAccountDetails />
        <Addresses>
          <Address>
            <AddressType>STREET</AddressType>
            <City />
            <Region />
            <PostalCode />
            <Country />
          </Address>
          <Address>
            <AddressType>POBOX</AddressType>
            <City />
            <Region />
            <PostalCode />
            <Country />
          </Address>
        </Addresses>
        <Phones>
          <Phone>
            <PhoneType>FAX</PhoneType>
            <PhoneNumber />
            <PhoneAreaCode />
            <PhoneCountryCode />
          </Phone>
          <Phone>
            <PhoneType>DEFAULT</PhoneType>
            <PhoneNumber />
            <PhoneAreaCode />
            <PhoneCountryCode />
          </Phone>
          <Phone>
            <PhoneType>DDI</PhoneType>
            <PhoneNumber />
            <PhoneAreaCode />
            <PhoneCountryCode />
          </Phone>
          <Phone>
            <PhoneType>MOBILE</PhoneType>
            <PhoneNumber />
            <PhoneAreaCode />
            <PhoneCountryCode />
          </Phone>
        </Phones>
        <UpdatedDateUTC>2013-06-17T22:10:19.207</UpdatedDateUTC>
        <ContactGroups />
        <IsSupplier>true</IsSupplier>
        <IsCustomer>true</IsCustomer>
      </Contact>
      <Date>2013-06-27T00:00:00</Date>
      <DueDate>2013-06-27T00:00:00</DueDate>
      <BrandingThemeID xsi:nil="true" />
      <Status>AUTHORISED</Status>
      <LineAmountTypes>Inclusive</LineAmountTypes>
      <LineItems>
        <LineItem>
          <ValidationErrors />
          <Description>Initial consultation and treatment</Description>
          <UnitAmount>24.00</UnitAmount>
          <TaxType>OUTPUT</TaxType>
          <TaxAmount>2.18</TaxAmount>
          <LineAmount>24.00</LineAmount>
          <Tracking />
          <Quantity>1.0000</Quantity>
          <DiscountRate xsi:nil="true" />
        </LineItem>
      </LineItems>
      <SubTotal>24.00</SubTotal>
      <TotalTax>0.00</TotalTax>
      <Total>24.00</Total>
      <UpdatedDateUTC>2013-06-27T04:45:47.93</UpdatedDateUTC>
      <CurrencyCode>AUD</CurrencyCode>
      <FullyPaidOnDate xsi:nil="true" />
      <Type>ACCREC</Type>
      <InvoiceID>9cab4df1-7a30-4fef-b7c5-b949b0eca7ae</InvoiceID>
      <InvoiceNumber>61</InvoiceNumber>
      <Reference />
      <Payments />
      <CreditNotes />
      <AmountDue>24.00</AmountDue>
      <AmountPaid>0.00</AmountPaid>
      <AmountCredited xsi:nil="true" />
      <Url>**</Url>
      <SentToContact>false</SentToContact>
      <CurrencyRate>1.000000</CurrencyRate>
      <TotalDiscount xsi:nil="true" />
      <HasAttachments xsi:nil="true" />
    </DataContractBase>
  </Elements>
</ApiException>

Issue when saving invoice

Following error appears when save method called for invoice
Xeroizer::OAuth::UnknownError: signature_invalid:Failed to validate signature

all other methods (get_invoice, get_tracking_category, Invoice.build, add_line_item, BrandingTheme.first) works fine

Please take a look, it's urgent!

Can't save address when creating a new contact

Hi.

I have a private applicant client handle "xero" and I have the following code to create a new Contact

contact = xero.Contact.build {:contact_number=>"John Smith 100274", :name=>"John Smith", :first_name=>"John", :last_name=>"Smith", :email_address=>"[email protected]", :addresses=>[{:type=>"Street", :attention_to=>nil, :line1=>"1066 Hastings Road", :line2=>nil, :line3=>nil, :line4=>nil, :city=>"Hastings", :region=>"Kent", :postal_code=>"12345", :country=>"US"}], :is_supplier=>true}
contact.save

I can create the contact successfully. BUT the address passed in is NOT saved.

How can I save an address when creating a contact?

Thank you.

Reading privatekey from environment variable

Our application runs on heroku and I didn't want to commit the private key to git.
I am documenting here what I did to provide a private key from an environment variable.

  1. Set the config variable in the heroku environment, e.g.
    heroku config:set PRIVATEKEY=$( cat private_key_file ) --app app_name
  2. In RoR
    @client = Xeroizer::PrivateApplication.new( OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET, '| echo "$PRIVATEKEY" ' )

Note: it is important to enclose the $PRIVATEKEY in double quotes to preserve the newlines in the PRIVATEKEY variable value.

This works well since in the oauth gem the "privatekeyfile" is opened with an IO.read(<..>) which can read from a subprocess echo in this instance.

Wrong xml when LineItem has Tracking Categories (v 0.4.0)

Hi,

When line item has tracking categories, gem generates wrong xml (to_xml):

  • line item start
  • tracking tag start <- not needed
  • tracking tag start
  • tracking category tag start
  • ...
  • tracking category tag end
  • tracking tag end
  • tracking tag end <- not needed
  • line item tag end
    please see example here http://blog.xero.com/developer/api/invoices/#LineItems

This issue cause PostDataInvalidException: Object reference not set to an instance of an object. Impossible to create invoice.

Best wishes,
Oxana

Could xeroizer 2.15.5 work with oauth 0.4.7 ?

Hi.

I updated xeroizer to the latest version 2.15.5, however, I noticed this depends on oauth 0.4.5.

So in our gem bundle, oauth was downgraded from 0.4.7 to 0.4.5 as a result of the xeroizer update.

Is there any reason for this specific dependency? In other words, can xeroizer be updated to work with oauth 0.4.7?

Would this oauth downgrade to 0.4.5 have any negative impact? for anyone who has done this.

Thanks!
Zack

Status of the tests?

Hi Wayne et al,

I am trying to contribute some more patches to xeroizer (I have a bunch in a personal fork already, as well as new support for transaction types that were launched yesterday) except I can't get the tests to run, and I don't want to be committing more code unless all the tests are actually running.

On a clean checkout of the repo master branch, and after bundle install:

$ bundle exec rake test:unit #=> 2 failures, 18 errors
$ bundle exec rake test       #=> 15 failures, 18 errors

Now I am sure some of these will be because I don't have the keys setup properly, but some are definitely code errors (mostly around passing nil/fake parents around it looks like.

Is this the current state of master for you as well, or is there another step I need to take?

It would be great if you could clarify in the readme the process for getting started with testing against the actual API too (what we need to do to setup a new app/what kind of app we need/where we put keys/etc).

If I can get to the point where I have passing tests I can definitely help maintain this lib as we use it in production.

Thanks!
Phil

Batch invoice sync: how to retrieve the id?

How to retrieve the saved invoice ids when syncing using the batch_save?

This code creates many Xeroizer::Record::Invoice instances and save them into the xero_invoices array.

connection.Invoice.batch_save do
  xero_invoices = invoices.map { |invoice| XeroInvoiceParser.from_invoice invoice }
end

I'm wrapping them inside the batch_save block to save them all at once. How can I retrieve the xero invoice id then?

When i try create Parthner Application Xeroizer and Oauth generate Errno::ECONNRESET Exception: Connection reset by peer

This Is my trace

/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/openssl/buffering.rb:174:in sysread_nonblock' /Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/openssl/buffering.rb:174:inread_nonblock'
/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/protocol.rb:141:in rbuf_fill' /Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/protocol.rb:122:inreaduntil'
/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/protocol.rb:132:in readline' /Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:2562:inread_status_line'
/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:2551:in read_new' /Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:1319:inblock in transport_request'
/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:1316:in catch' /Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:1316:intransport_request'
/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:1293:in request' rest-client (1.6.7) lib/restclient/net_http_ext.rb:51:inrequest'
/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:1286:in block in request' /Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:745:instart'
/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/net/http.rb:1284:in request' rest-client (1.6.7) lib/restclient/net_http_ext.rb:51:inrequest'
oauth (0.4.5) lib/oauth/consumer.rb:164:in request' oauth (0.4.5) lib/oauth/consumer.rb:197:intoken_request'
oauth (0.4.5) lib/oauth/consumer.rb:139:in get_request_token' xeroizer (2.15.3) lib/xeroizer/oauth.rb:72:inrequest_token'
app/controllers/settings/xero_controller.rb:22:in connect_to_xero' actionpack (3.1.1) lib/action_controller/metal/implicit_render.rb:4:insend_action'
actionpack (3.1.1) lib/abstract_controller/base.rb:167:in process_action' actionpack (3.1.1) lib/action_controller/metal/rendering.rb:10:inprocess_action'
actionpack (3.1.1) lib/abstract_controller/callbacks.rb:18:in block in process_action' activesupport (3.1.1) lib/active_support/callbacks.rb:499:in_run__3167553192074266300__process_action__2649562416224281667__callbacks'
activesupport (3.1.1) lib/active_support/callbacks.rb:386:in _run_process_action_callbacks' activesupport (3.1.1) lib/active_support/callbacks.rb:81:inrun_callbacks'
actionpack (3.1.1) lib/abstract_controller/callbacks.rb:17:in process_action' actionpack (3.1.1) lib/action_controller/metal/rescue.rb:17:inprocess_action'
actionpack (3.1.1) lib/action_controller/metal/instrumentation.rb:30:in block in process_action' activesupport (3.1.1) lib/active_support/notifications.rb:53:inblock in instrument'
activesupport (3.1.1) lib/active_support/notifications/instrumenter.rb:21:in instrument' activesupport (3.1.1) lib/active_support/notifications.rb:53:ininstrument'
actionpack (3.1.1) lib/action_controller/metal/instrumentation.rb:29:in process_action' actionpack (3.1.1) lib/action_controller/metal/params_wrapper.rb:201:inprocess_action'
activerecord (3.1.1) lib/active_record/railties/controller_runtime.rb:18:in process_action' actionpack (3.1.1) lib/abstract_controller/base.rb:121:inprocess'
actionpack (3.1.1) lib/abstract_controller/rendering.rb:45:in process' actionpack (3.1.1) lib/action_controller/metal.rb:193:indispatch'
actionpack (3.1.1) lib/action_controller/metal/rack_delegation.rb:14:in dispatch' actionpack (3.1.1) lib/action_controller/metal.rb:236:inblock in action'
actionpack (3.1.1) lib/action_dispatch/routing/route_set.rb:65:in call' actionpack (3.1.1) lib/action_dispatch/routing/route_set.rb:65:indispatch'
actionpack (3.1.1) lib/action_dispatch/routing/route_set.rb:29:in call' rack-mount (0.8.3) lib/rack/mount/route_set.rb:152:inblock in call'
rack-mount (0.8.3) lib/rack/mount/code_generation.rb:96:in block in recognize' rack-mount (0.8.3) lib/rack/mount/code_generation.rb:75:inoptimized_each'
rack-mount (0.8.3) lib/rack/mount/code_generation.rb:95:in recognize' rack-mount (0.8.3) lib/rack/mount/route_set.rb:141:incall'
actionpack (3.1.1) lib/action_dispatch/routing/route_set.rb:532:in call' sass (3.1.12) lib/sass/plugin/rack.rb:54:incall'
warden (1.1.0) lib/warden/manager.rb:35:in block in call' warden (1.1.0) lib/warden/manager.rb:34:incatch'
warden (1.1.0) lib/warden/manager.rb:34:in call' actionpack (3.1.1) lib/action_dispatch/middleware/best_standards_support.rb:17:incall'
rack (1.3.6) lib/rack/etag.rb:23:in call' rack (1.3.6) lib/rack/conditionalget.rb:35:incall'
actionpack (3.1.1) lib/action_dispatch/middleware/head.rb:14:in call' actionpack (3.1.1) lib/action_dispatch/middleware/params_parser.rb:21:incall'
actionpack (3.1.1) lib/action_dispatch/middleware/flash.rb:243:in call' rack (1.3.6) lib/rack/session/abstract/id.rb:195:incontext'
rack (1.3.6) lib/rack/session/abstract/id.rb:190:in call' actionpack (3.1.1) lib/action_dispatch/middleware/cookies.rb:331:incall'
activerecord (3.1.1) lib/active_record/query_cache.rb:62:in call' activerecord (3.1.1) lib/active_record/connection_adapters/abstract/connection_pool.rb:477:incall'
actionpack (3.1.1) lib/action_dispatch/middleware/callbacks.rb:29:in block in call' activesupport (3.1.1) lib/active_support/callbacks.rb:392:in_run_call_callbacks'
activesupport (3.1.1) lib/active_support/callbacks.rb:81:in run_callbacks' actionpack (3.1.1) lib/action_dispatch/middleware/callbacks.rb:28:incall'
actionpack (3.1.1) lib/action_dispatch/middleware/reloader.rb:68:in call' rack (1.3.6) lib/rack/sendfile.rb:101:incall'
actionpack (3.1.1) lib/action_dispatch/middleware/remote_ip.rb:48:in call' actionpack (3.1.1) lib/action_dispatch/middleware/show_exceptions.rb:47:incall'
railties (3.1.1) lib/rails/rack/logger.rb:13:in call' rack (1.3.6) lib/rack/methodoverride.rb:24:incall'
rack (1.3.6) lib/rack/runtime.rb:17:in call' activesupport (3.1.1) lib/active_support/cache/strategy/local_cache.rb:72:incall'
rack (1.3.6) lib/rack/lock.rb:15:in call' actionpack (3.1.1) lib/action_dispatch/middleware/static.rb:53:incall'
airbrake (3.1.4) lib/airbrake/rack.rb:41:in call' airbrake (3.1.4) lib/airbrake/user_informer.rb:12:incall'
railties (3.1.1) lib/rails/engine.rb:456:in call' railties (3.1.1) lib/rails/rack/content_length.rb:16:incall'
railties (3.1.1) lib/rails/rack/debugger.rb:21:in call' railties (3.1.1) lib/rails/rack/log_tailer.rb:14:incall'
rack (1.3.6) lib/rack/handler/webrick.rb:59:in service' /Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/httpserver.rb:138:inservice'
/Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/httpserver.rb:94:in run' /Users/vitaliyshevtsov/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/server.rb:191:inblock in start_thread'

Thanks for your help!

Base record hash setter inconsistent behaviour +temporary fix

Xeroizer 0.3.5

https://github.com/waynerobinson/xeroizer/blob/master/lib/xeroizer/record/base.rb#L53

invoice[:date] = Date.today
=> nil

The problem it seems, when compared to the working build method, is that []= is forgetting to convert the attribute setter string to a symbol.

If we change the line

self.send("#{attribute}=", value)

to

self.send("#{attribute}=".to_sym, value)

it appears to fix the issue:

invoice[:date] = Date.today 
=> Mon, 05 Sep 2011

I've had reports that it'll work sometimes, and not others. We're not sure of the exact cause at this stage.

Could be caused because of a loop we use:

new_attrs.each { |k,v| invoice[k] = v }

(side-note: a merge method would be great)

Issue when creating a new invoice

Hi! I'm having this error: Xeroizer::ApiException: Xeroizer::ApiException when I try to create a new Invoice with only two attributes, contact and type.

Also, i'm using a private application as the xero_client.

Here were the steps I took that resulted to this error:

  1. xero_client = Xeroizer::PrivateApplication.new(key, secret, private_key_path)
    (I confirmed this to be working by calling xero_client.Contact.all)

2, temp = xero_client.Invoice.build :type => "ACCPAY"

  1. temp.contact = xero_client.Contact.find(client.id).download_complete_record!
    (I confirmed that the contact was found)
  2. temp.save
  3. Error

I hope to hear your reply about this issue soon! Thanks!

Xeroizer::ApiException when saving invoice

I'm just getting started with the xero api and this gem, and while trying to create and save an invoice, I'm getting the above error. Here's my code:

 xero = Xeroizer::PrivateApplication.new("MY_CONSUMER_KEY", "MY_SECRET_KEY", "/path/to/my/privatekey.pem")

  # build contact
  contact = xero.Contact.build(
    :name => "company",
    :first_name => "john",
    :last_name =>  "smith",
    :email_address =>  "[email protected]",
    :contact_number => "123"
  )

  contact.add_address(
    :type => "STREET", 
    :line1 => "123 Any Street", 
    :city => "New York", 
    :region => "NY", 
    :postal_code => "11111", 
    :country => "United States"
  )
  contact.save

  # => success!

# build the invoice
invoice =  xero.Invoice.build(
    :type => "ACCREC", 
    :contact => contact,
    :date => "2010-10-01",
    :due_date => "2012-10-01",
    :line_amount_types => "NoTax",
    :invoice_number => "123",
    :status => "PAID"
  )

invoice.add_line_item(
  :description => "My Product",
  :quantity => "1",
  :unit_amount => "5.00",
  :item_code => "120"
)

invoice.save

# => Xeroizer::ApiException

Am I doing something wrong here? I'm not sure where to begin debugging this.

Thanks!

Manual Journal Entry Lines

Hi there.

It appears there is an issue when creating manual journal entries within the API. The API documentation expects the XML to contain JournalLines and JournalLine keys whereas the library expects them to be called ManualJournalLines and ManualJournalLine.

  • When getting information for a specific journal, a Xeroizer::AssociationTypeMismatch: Xeroizer::AssociationTypeMismatch error is raised.
  • When attempting to create a new journal a Xeroizer::ApiException: Xeroizer::ApiException error is raised which contains a validation error about manual journal entries requiring at least two lines.

I'm not sure on the best way actually resolve this so I'm hoping it's a simple job which you can either fix or point me in the right direction.

Thanks,
Adam

Item can't update purchase details/sales details

We've been trying to update a Xero Item's unit and cost prices by doing:

parts = @xero.Item.all
part = parts.first
part.attributes
=> {:item_id=>"75189459-e8ad-4007-bec3-10044e673996",
 :code=>"BOOK",
 :description=>"'Fish out of Water: Finding Your Brand'",
 :purchase_details=>
  #<Xeroizer::Record::ItemPurchaseDetails :unit_price: #<BigDecimal:7fa8ced86e80,'0.1595E2',18(18)>, :account_code: "300", :tax_type: "INPUT2">,
 :sales_details=>
  #<Xeroizer::Record::ItemSalesDetails :unit_price: #<BigDecimal:7fa8ced8d528,'0.1995E2',18(18)>, :account_code: "200", :tax_type: "OUTPUT2">}
part.purchase_details
=> #<Xeroizer::Record::ItemPurchaseDetails :unit_price: #<BigDecimal:7fa8ced86e80,'0.1595E2',18(18)>, :account_code: "300", :tax_type: "INPUT2">
part.purchase_details.update_attributes unit_price: 20.0
TypeError: nil is not a symbol
from /Users/dev/.rvm/gems/ruby-1.9.3-p194@spanner/gems/xeroizer-2.15.3/lib/xeroizer/record/base.rb:50:in `[]'

It looks like this is possible in the API, but not in xeroizer? Is there a way to update the unit price of an Item?

302 redirect on batch post throwing error

I'm pretty new to both Xero and Ruby, so I may have just not understood something, but I'm seeing my batch post of contacts return a 302 redirect which is causing xeroizer to raise an error.

== [2013-09-22 17:13:32 +0100] XeroGateway Request: POST /api.xro/2.0/Contacts?summarizeErrors=false
I, [2013-09-22T17:14:19.784266 #52386] INFO -- : == [2013-09-22 17:14:19 +0100] XeroGateway Response (302)
/Users/saul/.rvm/gems/ruby-1.9.3-p448/gems/xeroizer-2.15.5/lib/xeroizer/http.rb:115:in `http_request': Unknown response code: 302 (RuntimeError)

I, [2013-09-22T17:14:19.784421 #52386] INFO -- : == /api.xro/2.0/Contacts?summarizeErrors=false Response Body

<title>Object moved</title>

Object moved to here.

== End Response Body

How do I tell xeroizer to follow redirect so I can parse and deal with the errors in my contacts? Is this an option when instantiating the client? Or is there a change to the xeroizer required?

Can't order result

Documentation says the param used to order the result is "Order". But we use OrderBy params[:OrderBy] = options[:order] if options[:order].

So when I try to order the accounts they are unordered:

xero_connection.Account.all(order: :name)

Contact.find crushes with "undefined method `name' for nil:NilClass"

Hi,

We've got the issue today, last week everything was fine.

NoMethodError: undefined method name' for nil:NilClass /app/.bundle/gems/ruby/1.9.1/gems/xeroizer-2.15.1/lib/xeroizer/http.rb:139:inhandle_error!'
/app/.bundle/gems/ruby/1.9.1/gems/xeroizer-2.15.1/lib/xeroizer/http.rb:92:in http_request' /app/.bundle/gems/ruby/1.9.1/gems/xeroizer-2.15.1/lib/xeroizer/http.rb:15:inhttp_get'
/app/.bundle/gems/ruby/1.9.1/gems/xeroizer-2.15.1/lib/xeroizer/record/base_model.rb:111:in `find'

We are using v2.15.1. Could you please take a look? It's urgent, we cannot raise invoices.

Best wishes,
Oxana

View collection as JSON

I apologize in advance if this is the wrong place for this but I couldn't find a better place to reach out.

It's very possible I'm overlooking something obvious but I'm having a tough time trying to figure out how to output a collection of Invoices in JSON. I'm trying to return JSON to an AJAX request and I assumed it would be as simple as querying the Xero API and serializing the collection using .to_json but that doesn't seem to do the trick.

I was able to figure out that this works for single records

Invoice.find(xxxx).as_json

but it doesn't work for a collection of records

Invoice.all.as_json

Any thoughts?

BankTransaction's LineItems seems to be missing

Hi,

When querying for my BankTransaction's RECEIVE record, I can't seem to get back the LineItems I added through the API. It always return empty array even there is record in Xero itself :(

Payroll API Branch

I have started a branch (called payroll) for implementing the upcoming Xero Payroll API into Xeroizer.

This is created under a completely separate endpoint and will require some significant refactoring to implement given some of the new complex data structures and the addition of response paging.

TaxRate status

I noticed that the TaxRates don't have the status field available from xeroizer

    <TaxRate>
      <Name>My Rate</Name>
      <TaxType>TAX003</TaxType>
      <CanApplyToAssets>true</CanApplyToAssets>
      <CanApplyToEquity>true</CanApplyToEquity>
      <CanApplyToExpenses>true</CanApplyToExpenses>
      <CanApplyToLiabilities>true</CanApplyToLiabilities>
      <CanApplyToRevenue>true</CanApplyToRevenue>
      <DisplayTaxRate>3.2000</DisplayTaxRate>
      <EffectiveRate>3.2000</EffectiveRate>
      <Status>DELETED</Status>
    </TaxRate>

I'll try and send a pull request soon to fix it.

Can't seem to add a payment to an invoice

Hi,

Sorry but how do you actually add a payment to an Invoice? Based on the API, it needs an invoice id or account id, but doesn't the invoice already have both? I looked at the code and I don't see anything special being done for payments so not sure how to add it.

Here is my code:

invoice.payments.each do |payment|
  xero_invoice.add_payment(
    amount: payment.amount,
    date: payment.created_at
  )
end

xero_invoice.save

It actually returns true (the save) but when I look at the invoice from Xero, it doesn't have the payments. I've done this in console as well and doing xero_invoice.payments returns an empty array.

How do I actually save/add these payments? Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.