lib/xero_gateway/invoice.rb in xero_gateway-2.3.0 vs lib/xero_gateway/invoice.rb in xero_gateway-2.4.0

- old
+ new

@@ -1,199 +1,189 @@ module XeroGateway class Invoice include Dates include Money include LineItemCalculations - + INVOICE_TYPE = { 'ACCREC' => 'Accounts Receivable', 'ACCPAY' => 'Accounts Payable' } unless defined?(INVOICE_TYPE) - + LINE_AMOUNT_TYPES = { "Inclusive" => 'Invoice lines are inclusive tax', "Exclusive" => 'Invoice lines are exclusive of tax (default)', "NoTax" => 'Invoices lines have no tax' } unless defined?(LINE_AMOUNT_TYPES) - + INVOICE_STATUS = { 'AUTHORISED' => 'Approved invoices awaiting payment', 'DELETED' => 'Draft invoices that are deleted', 'DRAFT' => 'Invoices saved as draft or entered via API', 'PAID' => 'Invoices approved and fully paid', 'SUBMITTED' => 'Invoices entered by an employee awaiting approval', 'VOID' => 'Approved invoices that are voided' } unless defined?(INVOICE_STATUS) - + GUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ unless defined?(GUID_REGEX) - + # Xero::Gateway associated with this invoice. attr_accessor :gateway - + # Any errors that occurred when the #valid? method called. # Or errors that were within the XML payload from Xero attr_accessor :errors # Represents whether the line_items have been downloaded when getting from GET /API.XRO/2.0/INVOICES attr_accessor :line_items_downloaded - + # All accessible fields - attr_accessor :invoice_id, :invoice_number, :invoice_type, :invoice_status, :date, :due_date, :reference, :branding_theme_id, :line_amount_types, :currency_code, :line_items, :contact, :payments, :fully_paid_on, :amount_due, :amount_paid, :amount_credited, :sent_to_contact, :url, :updated_date_utc + attr_accessor :invoice_id, :invoice_number, :invoice_type, :invoice_status, :date, :due_date, :reference, :branding_theme_id, + :line_amount_types, :currency_code, :currency_rate, :payments, :fully_paid_on, :amount_due, :amount_paid, :amount_credited, + :sent_to_contact, :url, :updated_date_utc + attr_writer :contact, :line_items def initialize(params = {}) @errors ||= [] @payments ||= [] - + # Check if the line items have been downloaded. @line_items_downloaded = (params.delete(:line_items_downloaded) == true) - + params = { :line_amount_types => "Exclusive" }.merge(params) - + params.each do |k,v| self.send("#{k}=", v) end - + @line_items ||= [] end - + # Validate the Address record according to what will be valid by the gateway. # - # Usage: + # Usage: # address.valid? # Returns true/false - # + # # Additionally sets address.errors array to an array of field/error. def valid? @errors = [] - + if !INVOICE_TYPE[invoice_type] @errors << ['invoice_type', "must be one of #{INVOICE_TYPE.keys.join('/')}"] end if !invoice_id.nil? && invoice_id !~ GUID_REGEX @errors << ['invoice_id', 'must be blank or a valid Xero GUID'] end - + if invoice_status && !INVOICE_STATUS[invoice_status] @errors << ['invoice_status', "must be one of #{INVOICE_STATUS.keys.join('/')}"] end if line_amount_types && !LINE_AMOUNT_TYPES[line_amount_types] @errors << ['line_amount_types', "must be one of #{LINE_AMOUNT_TYPES.keys.join('/')}"] end - + unless date @errors << ['invoice_date', "can't be blank"] end - + # Make sure contact is valid. unless @contact && @contact.valid? @errors << ['contact', 'is invalid'] end - + # Make sure all line_items are valid. unless line_items.all? { | line_item | line_item.valid? } @errors << ['line_items', "at least one line item invalid"] end - + @errors.size == 0 end - + # Helper method to create the associated contact object. def build_contact(params = {}) self.contact = gateway ? gateway.build_contact(params) : Contact.new(params) end - + def contact @contact ||= build_contact end - + # Helper method to check if the invoice is accounts payable. def accounts_payable? invoice_type == 'ACCPAY' end - + # Helper method to check if the invoice is accounts receivable. def accounts_receivable? invoice_type == 'ACCREC' end - + # Whether or not the line_items have been downloaded (GET/invoices does not download line items). def line_items_downloaded? @line_items_downloaded end - %w(sub_total tax_total total).each do |line_item_total_type| - define_method("#{line_item_total_type}=") do |new_total| - instance_variable_set("@#{line_item_total_type}", new_total) unless line_items_downloaded? - end - end - # If line items are not downloaded, then attempt a download now (if this record was found to begin with). def line_items if line_items_downloaded? @line_items - elsif invoice_id =~ GUID_REGEX && @gateway # There is an invoice_id so we can assume this record was loaded from Xero. # Let's attempt to download the line_item records (if there is a gateway) - response = @gateway.get_invoice(invoice_id) - raise InvoiceNotFoundError, "Invoice with ID #{invoice_id} not found in Xero." unless response.success? && response.invoice.is_a?(XeroGateway::Invoice) - - @line_items = response.invoice.line_items - @line_items_downloaded = true - - @line_items - - # Otherwise, this is a new invoice, so return the line_items reference. + @line_items = download_line_items else + # Otherwise, this is a new invoice, so return the line_items reference. @line_items end end - + def ==(other) - ["invoice_number", "invoice_type", "invoice_status", "reference", "currency_code", "line_amount_types", "contact", "line_items"].each do |field| + ["invoice_number", "invoice_type", "invoice_status", "reference", "currency_code", "currency_rate", "line_amount_types", "contact", "line_items"].each do |field| return false if send(field) != other.send(field) end - + ["date", "due_date"].each do |field| return false if send(field).to_s != other.send(field).to_s end return true end - + # General purpose create/save method. # If invoice_id is nil then create, otherwise, attempt to save. def save if invoice_id.nil? create else update end end - + # Creates this invoice record (using gateway.create_invoice) with the associated gateway. # If no gateway set, raise a NoGatewayError exception. def create raise NoGatewayError unless gateway gateway.create_invoice(self) end - + # Updates this invoice record (using gateway.update_invoice) with the associated gateway. # If no gateway set, raise a NoGatewayError exception. def update raise NoGatewayError unless gateway gateway.update_invoice(self) end - + def to_xml(b = Builder::XmlMarkup.new) b.Invoice { b.InvoiceID self.invoice_id if self.invoice_id b.InvoiceNumber self.invoice_number if invoice_number b.Type self.invoice_type - b.CurrencyCode self.currency_code if self.currency_code + b.CurrencyCode currency_code if currency_code + b.CurrencyRate currency_rate if currency_rate contact.to_xml(b) b.Date Invoice.format_date(self.date || Date.today) b.DueDate Invoice.format_date(self.due_date) if self.due_date b.Status self.invoice_status if self.invoice_status b.Reference self.reference if self.reference @@ -205,20 +195,21 @@ end } b.Url url if url } end - + #TODO UpdatedDateUTC def self.from_xml(invoice_element, gateway = nil, options = {}) invoice = Invoice.new(options.merge({:gateway => gateway})) invoice_element.children.each do |element| case(element.name) when "InvoiceID" then invoice.invoice_id = element.text - when "InvoiceNumber" then invoice.invoice_number = element.text + when "InvoiceNumber" then invoice.invoice_number = element.text when "Type" then invoice.invoice_type = element.text when "CurrencyCode" then invoice.currency_code = element.text + when "CurrencyRate" then invoice.currency_rate = BigDecimal.new(element.text) when "Contact" then invoice.contact = Contact.from_xml(element) when "Date" then invoice.date = parse_date(element.text) when "DueDate" then invoice.due_date = parse_date(element.text) when "UpdatedDateUTC" then invoice.updated_date_utc = parse_date(element.text) when "Status" then invoice.invoice_status = element.text @@ -227,12 +218,10 @@ when "LineAmountTypes" then invoice.line_amount_types = element.text when "LineItems" then element.children.each {|line_item| invoice.line_items_downloaded = true; invoice.line_items << LineItem.from_xml(line_item) } when "SubTotal" then invoice.sub_total = BigDecimal.new(element.text) when "TotalTax" then invoice.total_tax = BigDecimal.new(element.text) when "Total" then invoice.total = BigDecimal.new(element.text) - when "InvoiceID" then invoice.invoice_id = element.text - when "InvoiceNumber" then invoice.invoice_number = element.text when "Payments" then element.children.each { | payment | invoice.payments << Payment.from_xml(payment) } when "AmountDue" then invoice.amount_due = BigDecimal.new(element.text) when "AmountPaid" then invoice.amount_paid = BigDecimal.new(element.text) when "AmountCredited" then invoice.amount_credited = BigDecimal.new(element.text) when "SentToContact" then invoice.sent_to_contact = (element.text.strip.downcase == "true") @@ -240,7 +229,17 @@ when "ValidationErrors" then invoice.errors = element.children.map { |error| Error.parse(error) } end end invoice end + + private + + def download_line_items + response = @gateway.get_invoice(invoice_id) + raise InvoiceNotFoundError, "Invoice with ID #{invoice_id} not found in Xero." unless response.success? && response.invoice.is_a?(XeroGateway::Invoice) + + @line_items_downloaded = true + @line_items = response.invoice.line_items + end end end