quickbooks-ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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