quickbooks-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 22455ec12ce95cceccc04131008bbcee20de080b
4
+ data.tar.gz: 0e87c10779104d91aa8a100297c738cb905861b0
5
+ SHA512:
6
+ metadata.gz: 74ba6b76bab8e057cb7dc3c6a706e1e70226f4459bfd78e6ac6308f752129f225e5cdb77546216d54e79d0f6d1f8e7abecc880da46cf070f7675c539598a3eef
7
+ data.tar.gz: 81652a925524f36c044d79e9c7f8bc9c33b739130cafbc6faf757376dd6969868ecc0cb04f36294758d9abcd0b058aa59485e59cac531354bb9806c1d61cb8f5
@@ -0,0 +1,81 @@
1
+ require 'roxml'
2
+ require 'logger'
3
+ require 'nokogiri'
4
+ require 'logger'
5
+ require 'active_model'
6
+ require 'cgi'
7
+ require 'date'
8
+ require 'forwardable'
9
+ require 'oauth'
10
+ require 'quickbooks/util/logging'
11
+
12
+ #== Models
13
+ require 'quickbooks/model/base_model'
14
+ require 'quickbooks/model/meta_data'
15
+ require 'quickbooks/model/custom_field'
16
+ require 'quickbooks/model/invoice_item_ref'
17
+ require 'quickbooks/model/sales_item_line_detail'
18
+ require 'quickbooks/model/sub_total_line_detail'
19
+ require 'quickbooks/model/customer_ref'
20
+ require 'quickbooks/model/discount_override'
21
+ require 'quickbooks/model/payment_line_detail'
22
+ require 'quickbooks/model/item'
23
+
24
+
25
+ require 'quickbooks/model/telephone_number'
26
+ require 'quickbooks/model/email_address'
27
+ require 'quickbooks/model/web_site_address'
28
+ require 'quickbooks/model/physical_address'
29
+ require 'quickbooks/model/linked_transaction'
30
+ require 'quickbooks/model/invoice_line_item'
31
+ require 'quickbooks/model/invoice'
32
+ require 'quickbooks/model/customer'
33
+
34
+ #== Services
35
+ require 'quickbooks/service/base_service'
36
+ require 'quickbooks/service/service_crud'
37
+ require 'quickbooks/service/customer'
38
+ require 'quickbooks/service/invoice'
39
+ require 'quickbooks/service/item'
40
+
41
+ class InvalidModelException < StandardError; end
42
+
43
+ module Quickbooks
44
+ @@logger = nil
45
+
46
+ class << self
47
+ def logger
48
+ @@logger ||= ::Logger.new($stdout) # TODO: replace with a real log file
49
+ end
50
+
51
+ def logger=(logger)
52
+ @@logger = logger
53
+ end
54
+
55
+ # set logging on or off
56
+ attr_writer :log
57
+
58
+ # Returns whether to log. Defaults to 'false'.
59
+ def log?
60
+ @log ||= false
61
+ end
62
+
63
+ def log(msg)
64
+ if log?
65
+ logger.info(msg)
66
+ logger.flush if logger.respond_to?(:flush)
67
+ end
68
+ end
69
+ end # << self
70
+
71
+ class Collection
72
+ attr_accessor :entries
73
+
74
+ # Legacy Attributes (v2)
75
+ attr_accessor :count, :current_page
76
+
77
+ # v3 Attributes
78
+ attr_accessor :start_position, :max_results, :total_count
79
+ end
80
+
81
+ end
@@ -0,0 +1,49 @@
1
+ # Intended to extend the Net::HTTP response object
2
+ # and adds support for decoding gzip and deflate encoded pages
3
+ #
4
+ # Author: Jason Stirk <http://griffin.oobleyboo.com>
5
+ # Home: http://griffin.oobleyboo.com/projects/http_encoding_helper
6
+ # Created: 5 September 2007
7
+ # Last Updated: 23 November 2007
8
+ #
9
+ # Usage:
10
+ #
11
+ # require 'net/http'
12
+ # require 'http_encoding_helper'
13
+ # headers={'Accept-Encoding' => 'gzip, deflate' }
14
+ # http = Net::HTTP.new('griffin.oobleyboo.com', 80)
15
+ # http.start do |h|
16
+ # request = Net::HTTP::Get.new('/', headers)
17
+ # response = http.request(request)
18
+ # content=response.plain_body # Method from our library
19
+ # puts "Transferred: #{response.body.length} bytes"
20
+ # puts "Compression: #{response['content-encoding']}"
21
+ # puts "Extracted: #{response.plain_body.length} bytes"
22
+ # end
23
+ #
24
+
25
+ require 'zlib'
26
+ require 'stringio'
27
+
28
+ class Net::HTTPResponse
29
+ # Return the uncompressed content
30
+ def plain_body
31
+ encoding=self['content-encoding']
32
+ content=nil
33
+ if encoding then
34
+ case encoding
35
+ when 'gzip'
36
+ i=Zlib::GzipReader.new(StringIO.new(self.body))
37
+ content=i.read
38
+ when 'deflate'
39
+ i=Zlib::Inflate.new
40
+ content=i.inflate(self.body)
41
+ else
42
+ raise "Unknown encoding - #{encoding}"
43
+ end
44
+ else
45
+ content=self.body
46
+ end
47
+ return content
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ module Quickbooks
2
+ module Model
3
+ class BaseModel
4
+ include ActiveModel::Validations
5
+ include ROXML
6
+ xml_convention :camelcase
7
+
8
+ # ROXML doesnt insert the namespaces into generated XML so we need to do it ourselves
9
+ # insert the static namespaces in the first opening tag that matches the +model_name+
10
+ def to_xml_inject_ns(model_name, options = {})
11
+ s = StringIO.new
12
+ xml = to_xml(options).write_to(s, :indent => 0, :indent_text => '')
13
+ destination_name = options.fetch(:destination_name, nil)
14
+ destination_name ||= model_name
15
+
16
+ sparse = options.fetch(:sparse, false)
17
+ sparse_string = %{sparse="#{sparse}"}
18
+ step1 = s.string.sub("<#{model_name}>", "<#{destination_name} #{Quickbooks::Service::BaseService::XML_NS} #{sparse_string}>")
19
+ step2 = step1.sub("</#{model_name}>", "</#{destination_name}>")
20
+ step2
21
+ end
22
+
23
+ def to_xml_ns(options = {})
24
+ to_xml_inject_ns(self.class::XML_NODE, options)
25
+ end
26
+
27
+ class << self
28
+
29
+ # These can be over-ridden in each model object as needed
30
+ def resource_for_collection
31
+ self::REST_RESOURCE
32
+ end
33
+
34
+ def resource_for_singular
35
+ self::REST_RESOURCE
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,9 @@
1
+ module Quickbooks
2
+ module Model
3
+ class CustomField < BaseModel
4
+ xml_accessor :id, :from => 'Id', :as => Integer
5
+ xml_accessor :name, :from => 'Name'
6
+ xml_accessor :type, :from => 'Type'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,118 @@
1
+ # == Business Rules
2
+ # * The DisplayName, GivenName, MiddleName, FamilyName, and PrintOnCheckName attributes must not contain a colon,":".
3
+ # * The DisplayName attribute must be unique across all other customers, employees, vendors, and other names.
4
+ # * The PrimaryEmailAddress attribute must contain an at sign, "@," and dot, ".".
5
+ # * Nested customer objects can be used to define sub-customers, jobs, or a combination of both, under a parent.
6
+ # * Up to four levels of nesting can be defined under a top-level customer object.
7
+ # * The Job attribute defines whether the object is a top-level customer or nested sub-customer/job.
8
+ # * Either the DisplayName attribute or at least one of Title, GivenName, MiddleName, FamilyName, Suffix, or FullyQualifiedName attributes are required during create.
9
+
10
+ module Quickbooks
11
+ module Model
12
+ class Customer < BaseModel
13
+ XML_COLLECTION_NODE = "Customer"
14
+ XML_NODE = "Customer"
15
+ REST_RESOURCE = 'customer'
16
+
17
+ xml_name XML_NODE
18
+ xml_accessor :id, :from => 'Id', :as => Integer
19
+ xml_accessor :sync_token, :from => 'SyncToken', :as => Integer
20
+ xml_accessor :meta_data, :from => 'MetaData', :as => Quickbooks::Model::MetaData
21
+ xml_accessor :title, :from => 'Title'
22
+ xml_accessor :given_name, :from => 'GivenName'
23
+ xml_accessor :middle_name, :from => 'MiddleName'
24
+ xml_accessor :family_name, :from => 'FamilyName'
25
+ xml_accessor :company_name, :from => 'CompanyName'
26
+ xml_accessor :display_name, :from => 'DisplayName'
27
+ xml_accessor :print_on_check_name, :from => 'PrintOnCheckName'
28
+ xml_accessor :active, :from => 'Active'
29
+ xml_accessor :primary_phone, :from => 'PrimaryPhone', :as => Quickbooks::Model::TelephoneNumber
30
+ xml_accessor :alternate_phone, :from => 'AlternatePhone', :as => Quickbooks::Model::TelephoneNumber
31
+ xml_accessor :mobile_phone, :from => 'Mobile', :as => Quickbooks::Model::TelephoneNumber
32
+ xml_accessor :fax_phone, :from => 'Fax', :as => Quickbooks::Model::TelephoneNumber
33
+ xml_accessor :primary_email_address, :from => 'PrimaryEmailAddr', :as => Quickbooks::Model::EmailAddress
34
+ xml_accessor :web_site, :from => 'WebAddr', :as => Quickbooks::Model::WebSiteAddress
35
+ xml_accessor :billing_address, :from => 'BillAddr', :as => Quickbooks::Model::PhysicalAddress
36
+ xml_accessor :shipping_address, :from => 'ShipAddr', :as => Quickbooks::Model::PhysicalAddress
37
+ xml_accessor :job, :from => 'Job'
38
+ xml_accessor :bill_with_parent, :from => 'BillWithParent'
39
+ xml_accessor :parent_ref, :from => 'ParentRef', :as => Integer
40
+ xml_accessor :level, :from => 'Level'
41
+ xml_accessor :sales_term_ref, :from => 'SalesTermRef', :as => Integer
42
+ xml_accessor :payment_method_ref, :from => 'PaymentMethodRef'
43
+ xml_accessor :balance, :from => 'Balance', :as => Float
44
+ xml_accessor :open_balance_date, :from => 'OpenBalanceDate', :as => Date
45
+ xml_accessor :balance_with_jobs, :from => 'BalanceWithJobs', :as => Float
46
+ xml_accessor :preferred_delivery_method, :from => 'PreferredDeliveryMethod'
47
+ xml_accessor :resale_num, :from => 'ResaleNum'
48
+ xml_accessor :suffix, :from => 'Suffix'
49
+ xml_accessor :fully_qualified_name, :from => 'FullyQualifiedName'
50
+ xml_accessor :taxable, :from => 'Taxable'
51
+ xml_accessor :notes, :from => 'Notes'
52
+
53
+ validate :names_cannot_contain_invalid_characters
54
+ validate :email_address_is_valid
55
+
56
+ def active?
57
+ active.to_s == 'true'
58
+ end
59
+
60
+ def job?
61
+ job.to_s == 'true'
62
+ end
63
+
64
+ def bill_with_parent?
65
+ bill_with_parent.to_s == 'true'
66
+ end
67
+
68
+ def taxable?
69
+ taxable.to_s == 'true'
70
+ end
71
+
72
+ def valid_for_update?
73
+ if sync_token.nil?
74
+ errors.add(:sync_token, "Missing required attribute SyncToken for update")
75
+ end
76
+ errors.empty?
77
+ end
78
+
79
+ def valid_for_create?
80
+ valid?
81
+ errors.empty?
82
+ end
83
+
84
+ def email_address=(email_address)
85
+ self.primary_email_address = Quickbooks::Model::EmailAddress.new(email_address)
86
+ end
87
+
88
+ def email_address
89
+ primary_email_address
90
+ end
91
+
92
+ # To delete an account Intuit requires we provide Id and SyncToken fields
93
+ def valid_for_deletion?
94
+ return false if(id.nil? || sync_token.nil?)
95
+ id.to_i > 0 && !sync_token.to_s.empty? && sync_token.to_i >= 0
96
+ end
97
+
98
+ def names_cannot_contain_invalid_characters
99
+ [:display_name, :given_name, :middle_name, :family_name, :print_on_check_name].each do |property|
100
+ value = send(property).to_s
101
+ if value.index(':')
102
+ errors.add(property, ":#{property} cannot contain a colon (:).")
103
+ end
104
+ end
105
+ end
106
+
107
+ def email_address_is_valid
108
+ if primary_email_address
109
+ address = primary_email_address.address
110
+ unless address.index('@') && address.index('.')
111
+ errors.add(:primary_email_address, "Email address must contain @ and . (dot)")
112
+ end
113
+ end
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,21 @@
1
+ module Quickbooks
2
+ module Model
3
+ class CustomerRef < BaseModel
4
+ xml_convention :camelcase
5
+ xml_accessor :name, :from => '@name' # Attribute with name 'name'
6
+ xml_accessor :value, :from => :content
7
+
8
+ def initialize(value = nil)
9
+ self.value = value
10
+ end
11
+
12
+ def to_i
13
+ self.value.to_i
14
+ end
15
+
16
+ def to_s
17
+ self.value.to_s
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module Quickbooks
2
+ module Model
3
+ class DiscountOverride < BaseModel
4
+ xml_accessor :discount_ref, :from => 'DiscountRef', :as => Integer
5
+ xml_accessor :percent_based, :from => 'PercentBased'
6
+ xml_accessor :discount_percent, :from => 'DiscountPercent', :as => Float
7
+ xml_accessor :discount_account_ref, :from => 'DiscountAccountRef', :as => Integer
8
+
9
+ def percent_based?
10
+ percent_based.to_s == 'true'
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Quickbooks
2
+ module Model
3
+ class EmailAddress < BaseModel
4
+ xml_accessor :address, :from => 'Address'
5
+
6
+ def to_xml(options = {})
7
+ return "" if address.to_s.empty?
8
+ super
9
+ end
10
+
11
+ def initialize(email_address = nil)
12
+ unless email_address.nil?
13
+ self.address = email_address
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,96 @@
1
+ # == Business Rules
2
+ # * An invoice must have at least one Line that describes an item.
3
+ # * An invoice must have CustomerRef populated.
4
+ # * The DocNumber attribute is populated automatically by the data service if not supplied.
5
+ # * If ShipAddr, BillAddr, or both are not provided, the appropriate customer address from Customer is used to fill those values.
6
+ # * DocNumber, if supplied, must be unique.
7
+
8
+ module Quickbooks
9
+ module Model
10
+ class Invoice < BaseModel
11
+
12
+ #== Constants
13
+ REST_RESOURCE = 'invoice'
14
+ XML_COLLECTION_NODE = "Invoice"
15
+ XML_NODE = "Invoice"
16
+
17
+ xml_accessor :id, :from => 'Id', :as => Integer
18
+ xml_accessor :sync_token, :from => 'SyncToken', :as => Integer
19
+ xml_accessor :meta_data, :from => 'MetaData', :as => Quickbooks::Model::MetaData
20
+ xml_accessor :custom_fields, :from => 'CustomField', :as => [Quickbooks::Model::CustomField]
21
+ xml_accessor :doc_number, :from => 'DocNumber'
22
+ xml_accessor :txn_date, :from => 'TxnDate', :as => Date
23
+ xml_accessor :private_note, :from => 'PrivateNote'
24
+ xml_accessor :linked_transactions, :from => 'LinkedTxn', :as => [Quickbooks::Model::LinkedTransaction]
25
+ xml_accessor :line_items, :from => 'Line', :as => [Quickbooks::Model::InvoiceLineItem]
26
+ xml_accessor :txn_tax_detail, :from => 'TxnTaxDetail'
27
+ xml_accessor :customer_ref, :from => 'CustomerRef', :as => Quickbooks::Model::CustomerRef
28
+ xml_accessor :customer_memo, :from => 'CustomerMemo'
29
+ xml_accessor :billing_address, :from => 'BillAddr', :as => Quickbooks::Model::PhysicalAddress
30
+ xml_accessor :shipping_address, :from => 'ShipAddr', :as => Quickbooks::Model::PhysicalAddress
31
+ xml_accessor :class_ref, :from => 'ClassRef'
32
+ xml_accessor :sales_term_ref, :from => 'SalesTermRef', :as => Integer
33
+ xml_accessor :due_date, :from => 'DueDate', :as => Date
34
+ xml_accessor :ship_method_ref, :from => 'ShipMethodRef'
35
+ xml_accessor :ship_date, :from => 'ShipDate', :as => Date
36
+ xml_accessor :tracking_num, :from => 'TrackingNum'
37
+ xml_accessor :ar_account_ref, :from => 'ARAccountRef', :as => Integer
38
+ xml_accessor :total_amount, :from => 'TotalAmt', :as => Float
39
+ xml_accessor :apply_tax_after_discount, :from => 'ApplyTaxAfterDiscount'
40
+ xml_accessor :print_status, :from => 'PrintStatus'
41
+ xml_accessor :email_status, :from => 'EmailStatus'
42
+ xml_accessor :balance, :from => 'Balance', :as => Float
43
+ xml_accessor :deposit, :from => 'Deposit', :as => Float
44
+ xml_accessor :department_ref, :from => 'DepartmentRef'
45
+ xml_accessor :allow_ipn_payment, :from => 'AllowIPNPayment'
46
+ xml_accessor :allow_online_payment, :from => 'AllowOnlinePayment'
47
+ xml_accessor :allow_online_credit_card_payment, :from => 'AllowOnlineCreditCardPayment'
48
+ xml_accessor :allow_online_ach_payment, :from => 'AllowOnlineACHPayment'
49
+
50
+ #== Validations
51
+ validates_length_of :line_items, :minimum => 1
52
+ validate :existence_of_customer_ref
53
+
54
+ def initialize
55
+ ensure_line_items_initialization
56
+ super
57
+ end
58
+
59
+ def apply_tax_after_discount?
60
+ apply_tax_after_discount.to_s == 'true'
61
+ end
62
+
63
+ def allow_ipn_payment?
64
+ allow_ipn_payment.to_s == 'true'
65
+ end
66
+
67
+ def allow_online_payment?
68
+ allow_online_payment.to_s == 'true'
69
+ end
70
+
71
+ def allow_online_credit_card_payment?
72
+ allow_online_credit_card_payment.to_s == 'true'
73
+ end
74
+
75
+ def allow_online_ach_payment?
76
+ allow_online_ach_payment.to_s == 'true'
77
+ end
78
+
79
+ def customer_id=(customer_id)
80
+ self.customer_ref = Quickbooks::Model::CustomerRef.new(customer_id)
81
+ end
82
+
83
+ private
84
+
85
+ def existence_of_customer_ref
86
+ if customer_ref.nil? || (customer_ref && customer_ref.value == 0)
87
+ errors.add(:customer_ref, "CustomerRef is required and must be a non-zero value.")
88
+ end
89
+ end
90
+
91
+ def ensure_line_items_initialization
92
+ self.line_items ||= []
93
+ end
94
+ end
95
+ end
96
+ end