lib/xero_gateway/gateway.rb in xero_gateway-float-2.0.18 vs lib/xero_gateway/gateway.rb in xero_gateway-float-2.1.1

- old
+ new

@@ -1,19 +1,19 @@ module XeroGateway - + class Gateway include Http include Dates - + attr_accessor :client, :xero_url, :logger - + extend Forwardable - def_delegators :client, :request_token, :access_token, :authorize_from_request, :authorize_from_access, :authorization_expires_at + def_delegators :client, :request_token, :access_token, :authorize_from_request, :authorize_from_access, :expires_at, :authorization_expires_at # # The consumer key and secret here correspond to those provided - # to you by Xero inside the API Previewer. + # to you by Xero inside the API Previewer. def initialize(consumer_key, consumer_secret, options = {}) @xero_url = options[:xero_url] || "https://api.xero.com/api.xro/2.0" @client = OAuth.new(consumer_key, consumer_secret, options) end @@ -32,44 +32,44 @@ options[:modified_since] = options.delete(:updated_after) end request_params[:ContactID] = options[:contact_id] if options[:contact_id] request_params[:ContactNumber] = options[:contact_number] if options[:contact_number] - request_params[:OrderBy] = options[:order] if options[:order] + request_params[:OrderBy] = options[:order] if options[:order] request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since] request_params[:where] = options[:where] if options[:where] - + response_xml = http_get(@client, "#{@xero_url}/Contacts", request_params) - + parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contacts'}) end - + # Retrieve a contact from Xero - # Usage get_contact_by_id(contact_id) + # Usage get_contact_by_id(contact_id) def get_contact_by_id(contact_id) get_contact(contact_id) end # Retrieve a contact from Xero - # Usage get_contact_by_id(contact_id) + # Usage get_contact_by_id(contact_id) def get_contact_by_number(contact_number) get_contact(nil, contact_number) end - + # Factory method for building new Contact objects associated with this gateway. def build_contact(contact = {}) case contact when Contact then contact.gateway = self when Hash then contact = Contact.new(contact.merge({:gateway => self})) end contact end - + # # Creates a contact in Xero # - # Usage : + # Usage : # # contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT #{Time.now.to_i}") # contact.email = "whoever@something.com" # contact.phone.number = "12345" # contact.address.line_1 = "LINE 1 OF THE ADDRESS" @@ -83,28 +83,28 @@ # # create_contact(contact) def create_contact(contact) save_contact(contact) end - + # # Updates an existing Xero contact # - # Usage : + # Usage : # # contact = xero_gateway.get_contact(some_contact_id) # contact.email = "a_new_email_ddress" # - # xero_gateway.update_contact(contact) + # xero_gateway.update_contact(contact) def update_contact(contact) raise "contact_id or contact_number is required for updating contacts" if contact.contact_id.nil? and contact.contact_number.nil? save_contact(contact) end - + # # Updates an array of contacts in a single API operation. - # + # # Usage : # contacts = [XeroGateway::Contact.new(:name => 'Joe Bloggs'), XeroGateway::Contact.new(:name => 'Jane Doe')] # result = gateway.update_contacts(contacts) # # Will update contacts with matching contact_id, contact_number or name or create if they don't exist. @@ -114,81 +114,93 @@ request_xml = b.Contacts { contacts.each do | contact | contact.to_xml(b) end } - + response_xml = http_post(@client, "#{@xero_url}/Contacts", request_xml, {}) response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'POST/contacts'}) response.contacts.each_with_index do | response_contact, index | contacts[index].contact_id = response_contact.contact_id if response_contact && response_contact.contact_id end response end - + # Retrieves all invoices from Xero # # Usage : get_invoices # get_invoices(:invoice_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9") # # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10) def get_invoices(options = {}) - + request_params = {} - + request_params[:InvoiceID] = options[:invoice_id] if options[:invoice_id] request_params[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number] - request_params[:OrderBy] = options[:order] if options[:order] + request_params[:OrderBy] = options[:order] if options[:order] request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since] request_params[:where] = options[:where] if options[:where] response_xml = http_get(@client, "#{@xero_url}/Invoices", request_params) parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoices'}) end - + # Retrieves a single invoice # + # You can get a PDF-formatted invoice by specifying :pdf as the format argument + # # Usage : get_invoice("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID # get_invoice("OIT-12345") # By number - def get_invoice(invoice_id_or_number) + def get_invoice(invoice_id_or_number, format = :xml) request_params = {} - + headers = {} + + headers.merge!("Accept" => "application/pdf") if format == :pdf + url = "#{@xero_url}/Invoices/#{URI.escape(invoice_id_or_number)}" - - response_xml = http_get(@client, url, request_params) - parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoice'}) + response = http_get(@client, url, request_params, headers) + + if format == :pdf + Tempfile.open(invoice_id_or_number) do |f| + f.write(response) + f + end + else + parse_response(response, {:request_params => request_params}, {:request_signature => 'GET/Invoice'}) + end end - + # Factory method for building new Invoice objects associated with this gateway. def build_invoice(invoice = {}) case invoice when Invoice then invoice.gateway = self when Hash then invoice = Invoice.new(invoice.merge(:gateway => self)) end invoice end - + # Creates an invoice in Xero based on an invoice object. # # Invoice and line item totals are calculated automatically. # - # Usage : + # Usage : # # invoice = XeroGateway::Invoice.new({ # :invoice_type => "ACCREC", # :due_date => 1.month.from_now, # :invoice_number => "YOUR INVOICE NUMBER", # :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)", # :line_amount_types => "Inclusive" # }) # invoice.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT") # invoice.contact.phone.number = "12345" - # invoice.contact.address.line_1 = "LINE 1 OF THE ADDRESS" + # invoice.contact.address.line_1 = "LINE 1 OF THE ADDRESS" # invoice.line_items << XeroGateway::LineItem.new( # :description => "THE DESCRIPTION OF THE LINE ITEM", # :unit_amount => 100, # :tax_amount => 12.5, # :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM", @@ -201,25 +213,25 @@ end # # Updates an existing Xero invoice # - # Usage : + # Usage : # # invoice = xero_gateway.get_invoice(some_invoice_id) # invoice.due_date = Date.today # - # xero_gateway.update_invoice(invoice) + # xero_gateway.update_invoice(invoice) def update_invoice(invoice) raise "invoice_id is required for updating invoices" if invoice.invoice_id.nil? save_invoice(invoice) end # # Creates an array of invoices with a single API request. - # + # # Usage : # invoices = [XeroGateway::Invoice.new(...), XeroGateway::Invoice.new(...)] # result = gateway.create_invoices(invoices) # def create_invoices(invoices) @@ -227,13 +239,13 @@ request_xml = b.Invoices { invoices.each do | invoice | invoice.to_xml(b) end } - - response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {}) + response_xml = http_put(@client, "#{@xero_url}/Invoices?SummarizeErrors=false", request_xml, {}) + response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/invoices'}) response.invoices.each_with_index do | response_invoice, index | invoices[index].invoice_id = response_invoice.invoice_id if response_invoice && response_invoice.invoice_id end response @@ -244,64 +256,64 @@ # Usage : get_credit_notes # get_credit_notes(:credit_note_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9") # # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10) def get_credit_notes(options = {}) - + request_params = {} - + request_params[:CreditNoteID] = options[:credit_note_id] if options[:credit_note_id] request_params[:CreditNoteNumber] = options[:credit_note_number] if options[:credit_note_number] - request_params[:OrderBy] = options[:order] if options[:order] + request_params[:OrderBy] = options[:order] if options[:order] request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since] request_params[:where] = options[:where] if options[:where] response_xml = http_get(@client, "#{@xero_url}/CreditNotes", request_params) parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNotes'}) end - + # Retrieves a single credit_note # # Usage : get_credit_note("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID # get_credit_note("OIT-12345") # By number def get_credit_note(credit_note_id_or_number) request_params = {} - + url = "#{@xero_url}/CreditNotes/#{URI.escape(credit_note_id_or_number)}" - + response_xml = http_get(@client, url, request_params) parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNote'}) end - + # Factory method for building new CreditNote objects associated with this gateway. def build_credit_note(credit_note = {}) case credit_note when CreditNote then credit_note.gateway = self when Hash then credit_note = CreditNote.new(credit_note.merge(:gateway => self)) end credit_note end - + # Creates an credit_note in Xero based on an credit_note object. # # CreditNote and line item totals are calculated automatically. # - # Usage : + # Usage : # # credit_note = XeroGateway::CreditNote.new({ # :credit_note_type => "ACCREC", # :due_date => 1.month.from_now, # :credit_note_number => "YOUR CREDIT_NOTE NUMBER", # :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)", # :line_amount_types => "Inclusive" # }) # credit_note.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT") # credit_note.contact.phone.number = "12345" - # credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS" + # credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS" # credit_note.line_items << XeroGateway::LineItem.new( # :description => "THE DESCRIPTION OF THE LINE ITEM", # :unit_amount => 100, # :tax_amount => 12.5, # :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM", @@ -311,25 +323,25 @@ # create_credit_note(credit_note) def create_credit_note(credit_note) request_xml = credit_note.to_xml response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml) response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_note'}) - + # Xero returns credit_notes inside an <CreditNotes> tag, even though there's only ever # one for this request response.response_item = response.credit_notes.first - + if response.success? && response.credit_note && response.credit_note.credit_note_id - credit_note.credit_note_id = response.credit_note.credit_note_id + credit_note.credit_note_id = response.credit_note.credit_note_id end - + response end - + # # Creates an array of credit_notes with a single API request. - # + # # Usage : # credit_notes = [XeroGateway::CreditNote.new(...), XeroGateway::CreditNote.new(...)] # result = gateway.create_credit_notes(credit_notes) # def create_credit_notes(credit_notes) @@ -337,11 +349,11 @@ request_xml = b.CreditNotes { credit_notes.each do | credit_note | credit_note.to_xml(b) end } - + response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml, {}) response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_notes'}) response.credit_notes.each_with_index do | response_credit_note, index | credit_notes[index].credit_note_id = response_credit_note.credit_note_id if response_credit_note && response_credit_note.credit_note_id @@ -417,18 +429,68 @@ url = "#{@xero_url}/BankTransactions/#{URI.escape(bank_transaction_id)}" response_xml = http_get(@client, url, request_params) parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransaction'}) end + # Creates a manual journal in Xero based on a manual journal object. # + # Manual journal and line item totals are calculated automatically. + # + # Usage : # TODO + + def create_manual_journal(manual_journal) + save_manual_journal(manual_journal) + end + + # + # Updates an existing Xero manual journal + # + # Usage : + # + # manual_journal = xero_gateway.get_manual_journal(some_manual_journal_id) + # + # xero_gateway.update_manual_journal(manual_journal) + def update_manual_journal(manual_journal) + raise "manual_journal_id is required for updating manual journals" if manual_journal.manual_journal_id.nil? + save_manual_journal(manual_journal) + end + + # Retrieves all manual journals from Xero + # + # Usage : get_manual_journal + # getmanual_journal(:manual_journal_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9") + # + # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10) + def get_manual_journals(options = {}) + request_params = {} + request_params[:ManualJournalID] = options[:manual_journal_id] if options[:manual_journal_id] + request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since] + + response_xml = http_get(@client, "#{@xero_url}/ManualJournals", request_params) + + parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/ManualJournals'}) + end + + # Retrieves a single manual journal + # + # Usage : get_manual_journal("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID + # get_manual_journal("OIT-12345") # By number + def get_manual_journal(manual_journal_id) + request_params = {} + url = "#{@xero_url}/ManualJournals/#{URI.escape(manual_journal_id)}" + response_xml = http_get(@client, url, request_params) + parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/ManualJournal'}) + end + + # # Gets all accounts for a specific organization in Xero. # def get_accounts response_xml = http_get(@client, "#{xero_url}/Accounts") parse_response(response_xml, {}, {:request_signature => 'GET/accounts'}) end - + # # Returns a XeroGateway::AccountsList object that makes working with # the Xero list of accounts easier and allows caching the results. # def get_accounts_list(load_on_init = true) @@ -449,46 +511,61 @@ # def get_organisation response_xml = http_get(@client, "#{xero_url}/Organisation") parse_response(response_xml, {}, {:request_signature => 'GET/organisation'}) end - + # # Gets all currencies for a specific organisation in Xero # def get_currencies response_xml = http_get(@client, "#{xero_url}/Currencies") parse_response(response_xml, {}, {:request_signature => 'GET/currencies'}) end - + # # Gets all Tax Rates for a specific organisation in Xero # def get_tax_rates response_xml = http_get(@client, "#{xero_url}/TaxRates") parse_response(response_xml, {}, {:request_signature => 'GET/tax_rates'}) end + # + # Get report for a specific organisation in Xero + # def get_report(report_name, request_params = {}) response_xml = http_get(@client, "#{@xero_url}/Reports/#{report_name}", request_params) end + # + # Create Payment record in Xero + # + def create_payment(payment) + b = Builder::XmlMarkup.new + request_xml = b.Payments do + payment.to_xml(b) + end + + response_xml = http_put(@client, "#{xero_url}/Payments", request_xml) + parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/payments'}) + end + private - def get_contact(contact_id = nil, contact_number = nil) + def get_contact(contact_id = nil, contact_number = nil) request_params = contact_id ? { :contactID => contact_id } : { :contactNumber => contact_number } response_xml = http_get(@client, "#{@xero_url}/Contacts/#{URI.escape(contact_id||contact_number)}", request_params) parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contact'}) end - # Create or update a contact record based on if it has a contact_id or contact_number. def save_contact(contact) request_xml = contact.to_xml - + response_xml = nil create_or_save = nil if contact.contact_id.nil? && contact.contact_number.nil? # Create new contact record. response_xml = http_put(@client, "#{@xero_url}/Contacts", request_xml, {}) @@ -505,11 +582,11 @@ end # Create or update an invoice record based on if it has an invoice_id. def save_invoice(invoice) request_xml = invoice.to_xml - + response_xml = nil create_or_save = nil if invoice.invoice_id.nil? # Create new invoice record. response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {}) @@ -519,19 +596,19 @@ response_xml = http_post(@client, "#{@xero_url}/Invoices", request_xml, {}) create_or_save = :save end response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/invoice"}) - + # Xero returns invoices inside an <Invoices> tag, even though there's only ever # one for this request response.response_item = response.invoices.first - + if response.success? && response.invoice && response.invoice.invoice_id - invoice.invoice_id = response.invoice.invoice_id + invoice.invoice_id = response.invoice.invoice_id end - + response end # Create or update a bank transaction record based on if it has an bank_transaction_id. def save_bank_transaction(bank_transaction) @@ -560,63 +637,88 @@ end response end + # Create or update a manual journal record based on if it has an manual_journal_id. + def save_manual_journal(manual_journal) + request_xml = manual_journal.to_xml + response_xml = nil + create_or_save = nil + + if manual_journal.manual_journal_id.nil? + # Create new manual journal record. + response_xml = http_put(@client, "#{@xero_url}/ManualJournals", request_xml, {}) + create_or_save = :create + else + # Update existing manual journal record. + response_xml = http_post(@client, "#{@xero_url}/ManualJournals", request_xml, {}) + create_or_save = :save + end + + response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/ManualJournals"}) + + # Xero returns manual journals inside an <ManualJournals> tag, even though there's only ever + # one for this request + response.response_item = response.manual_journals.first + + manual_journal.manual_journal_id = response.manual_journal.manual_journal_id if response.success? && response.manual_journal && response.manual_journal.manual_journal_id + + response + end + def parse_response(raw_response, request = {}, options = {}) response = XeroGateway::Response.new doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all) - + # check for responses we don't understand raise UnparseableResponse.new(doc.root.name) unless doc.root.name == "Response" response_element = REXML::XPath.first(doc, "/Response") - + response_element.children.reject { |e| e.is_a? REXML::Text }.each do |element| case(element.name) when "ID" then response.response_id = element.text when "Status" then response.status = element.text when "ProviderName" then response.provider = element.text when "DateTimeUTC" then response.date_time = element.text when "Contact" then response.response_item = Contact.from_xml(element, self) when "Invoice" then response.response_item = Invoice.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"}) when "BankTransaction" response.response_item = BankTransaction.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"}) + when "ManualJournal" + response.response_item = ManualJournal.from_xml(element, self, {:journal_lines_downloaded => options[:request_signature] != "GET/ManualJournals"}) when "Contacts" then element.children.each {|child| response.response_item << Contact.from_xml(child, self) } when "Invoices" then element.children.each {|child| response.response_item << Invoice.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"}) } when "BankTransactions" element.children.each do |child| response.response_item << BankTransaction.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"}) end + when "ManualJournals" + element.children.each do |child| + response.response_item << ManualJournal.from_xml(child, self, {:journal_lines_downloaded => options[:request_signature] != "GET/ManualJournals"}) + end when "CreditNotes" then element.children.each {|child| response.response_item << CreditNote.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/CreditNotes"}) } when "Accounts" then element.children.each {|child| response.response_item << Account.from_xml(child) } when "TaxRates" then element.children.each {|child| response.response_item << TaxRate.from_xml(child) } when "Currencies" then element.children.each {|child| response.response_item << Currency.from_xml(child) } when "Organisations" then response.response_item = Organisation.from_xml(element.children.first) # Xero only returns the Authorized Organisation when "TrackingCategories" then element.children.each {|child| response.response_item << TrackingCategory.from_xml(child) } - when "Errors" then element.children.each { |error| parse_error(error, response) } + when "Errors" then response.errors = element.children.map { |error| Error.parse(error) } end end if response_element - + # If a single result is returned don't put it in an array if response.response_item.is_a?(Array) && response.response_item.size == 1 response.response_item = response.response_item.first end - + response.request_params = request[:request_params] response.request_xml = request[:request_xml] response.response_xml = raw_response response - end - - def parse_error(error_element, response) - response.errors << Error.new( - :description => REXML::XPath.first(error_element, "Description").text, - :date_time => REXML::XPath.first(error_element, "//DateTime").text, - :type => REXML::XPath.first(error_element, "//ExceptionType").text, - :message => REXML::XPath.first(error_element, "//Message").text - ) end - end + + end end