From 4f9a22c7efc8350ba02276d909cf282ac35a900d Mon Sep 17 00:00:00 2001 From: dcparker Date: Sat, 31 May 2008 20:32:41 +0000 Subject: [PATCH] Added correction for Quickbooks' find-by-date instead of by-time, in time-based queries. git-svn-id: http://quickbooks.rubyforge.org/svn@29 0eb1a872-461d-0410-a379-984e9e9c21cf --- README | 3 ++- Rakefile | 2 +- lib/qbxml/request.rb | 8 ++------ lib/qbxml/response.rb | 32 ++++++++++-------------------- lib/quickbooks/base.rb | 30 ++++++++++++++++++++++++++-- lib/quickbooks/model.rb | 3 +++ lib/quickbooks/models/list_item.rb | 2 +- lib/quickbooks/ruby_magic.rb | 2 +- 8 files changed, 49 insertions(+), 33 deletions(-) diff --git a/README b/README index d6608ed..e5504dc 100644 --- a/README +++ b/README @@ -49,4 +49,5 @@ This library is released under the terms of the MIT License. * 0.1.0 Bugfixes, a little more docs, and connection is a little more robust. * 0.1.1 Small bugfix. * 0.4.0 Ready for beta by anyone! -* 0.4.2 A few bugs worked out, added Quickbooks::Base.setup to initialize arguments for establish_connection(). \ No newline at end of file +* 0.4.2 A few bugs worked out, added Quickbooks::Base.setup to initialize arguments for establish_connection(). +* 0.4.3 A really important addition to the query() method that filters a Quickbooks response to correctly honor time-based filters. (Monitor whether we need to +1 day to incoming time-based filters so that Quickbooks will include all desired results.) \ No newline at end of file diff --git a/Rakefile b/Rakefile index 91e52fe..6ec87c3 100644 --- a/Rakefile +++ b/Rakefile @@ -6,7 +6,7 @@ require 'rake/gempackagetask' require 'rake/contrib/rubyforgepublisher' PKG_NAME = 'quickbooks' -PKG_VERSION = "0.4.2" +PKG_VERSION = "0.4.3" PKG_FILES = FileList[ "lib/**/*", "rspec/**/*", "[A-Z]*", "Rakefile", "doc/**/*" diff --git a/lib/qbxml/request.rb b/lib/qbxml/request.rb index da0e6d4..98d5f05 100644 --- a/lib/qbxml/request.rb +++ b/lib/qbxml/request.rb @@ -1,19 +1,16 @@ require 'qbxml/support' require 'builder' +require 'time' require 'hash_magic' module Qbxml VERSION = '6.0' - # module RequestSetArrayExt - # end - class RequestSet include Enumerable def set unless @set.is_a?(Array) @set = [] - # @set.extend(Qbxml::RequestSetArrayExt) end @set end @@ -190,8 +187,7 @@ def to_xml(as_set=true) inner_stuff.call end } - # puts req.target! - req.target! + req.target! # The xml string end private diff --git a/lib/qbxml/response.rb b/lib/qbxml/response.rb index cdb2311..79f8a2f 100644 --- a/lib/qbxml/response.rb +++ b/lib/qbxml/response.rb @@ -2,15 +2,11 @@ require 'formatted_string' module Qbxml - # module ResponseSetArrayExt - # end - class ResponseSet include Enumerable def set unless @set.is_a?(Array) @set = [] - # @set.extend(Qbxml::ResponseSetArrayExt) end @set end @@ -38,8 +34,6 @@ def initialize(xml_or_hash) end def append_from_xml(xml) - # puts "ResponseXML:" - # puts xml self.append_from_hash(xml.formatted(:xml).to_hash) end def append_from_hash(hsh) @@ -67,8 +61,7 @@ def from_hash(hsh) class Response attr_accessor :response_type, :status, :message, :severity, :ret_items - # For Development purposes: - attr_accessor :raw_response + attr_accessor :raw_response # (for development purposes) def initialize(xml_or_hash) if(xml_or_hash.is_a?(Hash)) @@ -87,24 +80,22 @@ def import_from_xml(xml) def import_from_hash(hsh) raise ArgumentError, "Hash passed to Qbxml::Response.from_hash must contain only one top-level key" unless hsh.keys.length == 1 name = hsh.keys.first - # for development - self.raw_response = hsh - # * * * * + self.raw_response = hsh # (for development purposes) hsh = hsh[name] self.status = hsh['statusCode'].to_i self.severity = hsh['statusSeverity'] self.message = hsh['statusMessage'] # if self.status == 0 # Status is good, proceed with eating the request. - # - # - # Customer - # 80000030-1203622308 - # 2008-02-21T14:31:48-05:00 - # 2008-03-18T17:31:12-05:00 - # Rachel Parker - # - # + # + # + # Customer + # 80000030-1203622308 + # 2008-02-21T14:31:48-05:00 + # 2008-03-18T17:31:12-05:00 + # Joe Schmoe + # + # if m = name.match(/^(List|Txn)Del(etedQuery)?Rs$/) # (List|Txn)DelRs, or (List|Txn)DeletedQueryRs - both return just a few attributes, like ListID / TxnID and TimeDeleted list_or_txn = m[1] @@ -121,7 +112,6 @@ def import_from_hash(hsh) # else # Status is bad. # end - # puts self.inspect self end diff --git a/lib/quickbooks/base.rb b/lib/quickbooks/base.rb index c88d592..e2fd669 100644 --- a/lib/quickbooks/base.rb +++ b/lib/quickbooks/base.rb @@ -162,9 +162,35 @@ def query(obj_or_args,*args) nil end objects = [] # This will hold and return the instantiated objects from the quickbooks response -# The following is subject to bugginess, IF the response contains more than one object: it will instantiate only the last one. + # The following line is subject to unexpected results: IF the response contains more than one object, it will re-instantiate only the last one. self.request(reinstantiate || self, *args).each { |response| objects << response.instantiate(reinstantiate) } # Does not instantiate if it's an error, but simply records response into response_log - objects.length == 1 ? objects[0] : objects + # Since Quickbooks only honors the Date in queries, we can filter the list further here to honor the Time requested. + # filters we're triggered on: created_before, created_after, updated_before, updated_after, deleted_before, deleted_after + if args[-1].is_a?(Hash) && !(time_filters = args.stringify_keys.only('created_before', 'created_after', 'updated_before', 'updated_after', 'deleted_before', 'deleted_after')).empty? + objects.reject! do |object| + passes = true + time_filters.each do |filter,time| + case filter + when 'created_before' + passes = Time.parse(object.time_created) < Time.parse(time.to_s) + when 'created_after' + passes = Time.parse(object.time_created) >= Time.parse(time.to_s) + when 'updated_before' + passes = Time.parse(object.time_modified) < Time.parse(time.to_s) + when 'updated_after' + passes = Time.parse(object.time_modified) >= Time.parse(time.to_s) + when 'deleted_before' + passes = Time.parse(object.time_deleted) < Time.parse(time.to_s) + when 'deleted_after' + passes = Time.parse(object.time_deleted) >= Time.parse(time.to_s) + end + break unless passes # Skip the rest of the tests if it fails one + end + !passes + end + end + # **** + objects.length == 1 ? objects[0] : objects # Here, we may rather always return an array, then return the first element by methods such as .first end # Generates a request using Qbxml::Request, sends it, and returns a Qbxml::ResponseSet object containing the response(s). diff --git a/lib/quickbooks/model.rb b/lib/quickbooks/model.rb index 525977f..f6b1646 100644 --- a/lib/quickbooks/model.rb +++ b/lib/quickbooks/model.rb @@ -79,7 +79,10 @@ def #{prop.class_leaf_name.underscore}; @#{prop.class_leaf_name.underscore}; end end end + # Lists the registered properties, first read_only properties, then read_write def properties + # Quickbooks seems to always include the read-only attributes first... if this isn't always true, + # the two above methods will need to also append the attribute names into a common properties list. read_only + read_write end end diff --git a/lib/quickbooks/models/list_item.rb b/lib/quickbooks/models/list_item.rb index 81df10f..0ec91f2 100644 --- a/lib/quickbooks/models/list_item.rb +++ b/lib/quickbooks/models/list_item.rb @@ -11,7 +11,7 @@ class ListItem < Base def self.inherited(klass) super - # :time_deleted only comes from ListDeleted, but that way all ListDeleted attributes can be instantiated into the respective ListItem model + # :time_deleted only comes from ListDeleted, but that way all ListDeleted attributes can be instantiated into their respective ListItem model klass.read_only :list_id, :full_name, :edit_sequence, :time_created, :time_modified, :time_deleted end diff --git a/lib/quickbooks/ruby_magic.rb b/lib/quickbooks/ruby_magic.rb index 7652385..bc97ef9 100644 --- a/lib/quickbooks/ruby_magic.rb +++ b/lib/quickbooks/ruby_magic.rb @@ -133,7 +133,7 @@ def -(v) end def only(*keys) - keys.flatten.inject(dup.clear) {|h,(k,v)| h[k] = self[k]} + keys.flatten.inject(dup.clear) {|h,(k,v)| h[k] = self[k]; h} end def only!(*keys) replace(only(*keys))