netsuite 0.7.5 → 0.7.6

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.
@@ -4,6 +4,7 @@ require 'savon'
4
4
  require 'netsuite/version'
5
5
  require 'netsuite/errors'
6
6
  require 'netsuite/utilities'
7
+ require 'netsuite/utilities/data_center'
7
8
  require 'netsuite/rest/utilities/roles'
8
9
  require 'netsuite/rest/utilities/request'
9
10
  require 'netsuite/core_ext/string/lower_camelcase'
@@ -51,6 +52,7 @@ module NetSuite
51
52
  autoload :Delete, 'netsuite/actions/delete'
52
53
  autoload :DeleteList, 'netsuite/actions/delete_list'
53
54
  autoload :Get, 'netsuite/actions/get'
55
+ autoload :GetDeleted, 'netsuite/actions/get_deleted'
54
56
  autoload :GetAll, 'netsuite/actions/get_all'
55
57
  autoload :GetList, 'netsuite/actions/get_list'
56
58
  autoload :GetSelectValue, 'netsuite/actions/get_select_value'
@@ -0,0 +1,88 @@
1
+ module NetSuite
2
+ module Actions
3
+ class GetDeleted
4
+ include Support::Requests
5
+
6
+ def initialize(object = nil, options = {})
7
+ @object = object
8
+ @options = options
9
+ end
10
+
11
+ private
12
+
13
+ def request(credentials={})
14
+ NetSuite::Configuration.connection(
15
+ {namespaces: {
16
+ 'xmlns:platformMsgs' => "urn:messages_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
17
+ 'xmlns:platformCore' => "urn:core_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com"
18
+ }}, credentials
19
+ ).call :get_deleted, message: request_body
20
+ end
21
+
22
+ def soap_type
23
+ @object.class.to_s.split('::').last.lower_camelcase
24
+ end
25
+
26
+ # <soap:Body>
27
+ # <platformMsgs:getDeleted>
28
+ # <platformMsgs:pageIndex>1</platformMsgs:pageIndex>
29
+ # <platformMsgs:getDeletedFilter>
30
+ # <platformCore:deletedDate operator="within">
31
+ # <platformCore:searchValue>2016-12-01T00:00:00</platformCore:searchValue>
32
+ # <platformCore:searchValue2>2016-12-20T00:00:00</platformCore:searchValue2>
33
+ # </platformCore:deletedDate>
34
+ # <platformCore:type operator="anyOf">
35
+ # <platformCore:searchValue>invoice</platformCore:searchValue>
36
+ # </platformCore:type>
37
+ # </platformMsgs:getDeletedFilter>
38
+ # </platformMsgs:getDeleted>
39
+ # </soap:Body>
40
+ def request_body
41
+ criteria = @options[:criteria] || @options
42
+ filter_elements = {}
43
+
44
+ criteria.each do |c|
45
+ searchValue = { "@operator" => c[:operator] }
46
+
47
+ if c[:value].is_a?(Array) && c[:type] == 'SearchDateField'
48
+ searchValue["platformCore:searchValue"] = c[:value][0].to_s
49
+ searchValue["platformCore:searchValue2"] = c[:value][1].to_s
50
+ else
51
+ searchValue["platformCore:searchValue"] = c[:value]
52
+ end
53
+
54
+ filter_elements["platformCore:#{c[:field]}"] = searchValue
55
+ end
56
+
57
+ {
58
+ 'platformMsgs:pageIndex' => @options.fetch(:page, 1),
59
+ 'platformMsgs:getDeletedFilter' => filter_elements
60
+ }
61
+ end
62
+
63
+ def response_hash
64
+ @response.body[:get_deleted_response]
65
+ end
66
+
67
+ def success?
68
+ @success ||= response_body[:status][:@is_success] == 'true'
69
+ end
70
+
71
+ def response_body
72
+ @response_body ||= response_hash[:get_deleted_result]
73
+ end
74
+
75
+ module Support
76
+ def self.included(base)
77
+ base.extend(ClassMethods)
78
+ end
79
+
80
+ module ClassMethods
81
+ def get_deleted(options = { }, credentials={})
82
+ NetSuite::Actions::GetDeleted.call([self, options], credentials)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -4,6 +4,7 @@ module NetSuite
4
4
 
5
5
  def reset!
6
6
  NetSuite::Utilities.clear_cache!
7
+ clear_wsdl_cache
7
8
 
8
9
  attributes.clear
9
10
  end
@@ -13,8 +14,8 @@ module NetSuite
13
14
  end
14
15
 
15
16
  def connection(params={}, credentials={})
16
- Savon.client({
17
- wsdl: wsdl,
17
+ client = Savon.client({
18
+ wsdl: cached_wsdl || wsdl,
18
19
  read_timeout: read_timeout,
19
20
  namespaces: namespaces,
20
21
  soap_header: auth_header(credentials).update(soap_header),
@@ -24,6 +25,8 @@ module NetSuite
24
25
  log_level: log_level,
25
26
  log: !silent, # turn off logging entirely if configured
26
27
  }.update(params))
28
+ cache_wsdl(client)
29
+ return client
27
30
  end
28
31
 
29
32
  def filters(list = nil)
@@ -43,6 +46,35 @@ module NetSuite
43
46
  attributes[:filters] = list
44
47
  end
45
48
 
49
+ def wsdl_cache
50
+ @wsdl_cache ||= {}
51
+ end
52
+
53
+ def clear_wsdl_cache
54
+ @wsdl_cache = {}
55
+ end
56
+
57
+ def cached_wsdl
58
+ cached = wsdl_cache.fetch([api_version, wsdl], nil)
59
+ if cached.is_a? String
60
+ cached
61
+ elsif cached.is_a? Savon::Client
62
+ wsdl_cache[[api_version, wsdl]] = cached.instance_eval { @wsdl.xml }
63
+ end
64
+ end
65
+
66
+ def cache_wsdl(client)
67
+ # NOTE the Savon::Client doesn't pull the wsdl content upon
68
+ # instantiation; it pulls it when it recieves the #call method.
69
+ # If we force it to pull the wsdl here, it will duplicate the call later.
70
+ # So, we stash the entire client and fetch just the wsdl from it after
71
+ # it completes its call
72
+ # For reference, see:
73
+ # https://github.com/savonrb/savon/blob/d64925d3add33fa5531577ce9e3a28a7a93618b1/lib/savon/client.rb#L35-L37
74
+ # https://github.com/savonrb/savon/blob/d64925d3add33fa5531577ce9e3a28a7a93618b1/lib/savon/operation.rb#L22
75
+ wsdl_cache[[api_version, wsdl]] ||= client
76
+ end
77
+
46
78
  def api_version(version = nil)
47
79
  if version
48
80
  self.api_version = version
@@ -7,7 +7,7 @@ module NetSuite
7
7
  include Support::Actions
8
8
  include Namespaces::TranCust
9
9
 
10
- actions :get, :get_list, :add, :initialize, :delete, :update, :upsert, :search
10
+ actions :get, :get_deleted, :get_list, :add, :initialize, :delete, :update, :upsert, :search
11
11
 
12
12
  fields :alt_handling_cost, :alt_shipping_cost, :amount_paid, :amount_remaining, :auto_apply, :balance,
13
13
  :bill_address, :contrib_pct, :created_date, :currency_name, :deferred_revenue, :discount_rate, :email,
@@ -34,7 +34,7 @@ module NetSuite
34
34
 
35
35
  record_refs :account, :bill_address_list, :created_from, :custom_form, :department, :discount_item, :entity, :gift_cert,
36
36
  :handling_tax_code, :job, :klass, :lead_source, :location, :message_sel, :partner, :posting_period, :promo_code,
37
- :sales_group, :sales_rep, :ship_method, :shipping_tax_code, :subsidiary, :tax_item
37
+ :sales_group, :sales_rep, :ship_method, :shipping_tax_code, :subsidiary, :tax_item, :currency
38
38
 
39
39
  attr_reader :internal_id
40
40
  attr_accessor :external_id
@@ -22,8 +22,8 @@ module NetSuite
22
22
  :opening_balance, :opening_balance_account, :opening_balance_date,
23
23
  :password, :password2, :phone, :phonetic_name, :pref_cc_processor, :print_on_check_as,
24
24
  :print_transactions, :referrer, :reminder_days, :representing_subsidiary, :require_pwd_change, :resale_number,
25
- :sales_group, :sales_readiness, :sales_team_list, :salutation, :send_email, :ship_complete, :shipping_item,
26
- :stage, :start_date, :sync_partner_teams, :tax_exempt, :tax_item, :taxable,
25
+ :sales_group, :sales_readiness, :sales_team_list, :salutation, :send_email, :ship_complete,
26
+ :stage, :start_date, :sync_partner_teams, :tax_exempt, :taxable,
27
27
  :third_party_acct, :third_party_country, :third_party_zipcode, :title, :url,
28
28
  :vat_reg_number, :visits, :web_lead
29
29
 
@@ -32,14 +32,14 @@ module NetSuite
32
32
  field :contact_roles_list, ContactAccessRolesList
33
33
  field :currency_list, CustomerCurrencyList
34
34
  field :partners_list, CustomerPartnersList
35
-
35
+
36
36
  # TODO subscriptions_list
37
37
 
38
38
  read_only_fields :balance, :consol_balance, :deposit_balance, :consol_deposit_balance, :overdue_balance,
39
39
  :consol_overdue_balance, :unbilled_orders, :consol_unbilled_orders
40
40
 
41
41
  record_refs :access_role, :custom_form, :currency, :entity_status, :partner, :category, :lead_source,
42
- :price_level,:sales_rep, :subsidiary, :terms, :parent, :territory
42
+ :price_level,:sales_rep, :subsidiary, :terms, :parent, :territory, :tax_item, :shipping_item
43
43
 
44
44
  attr_reader :internal_id
45
45
  attr_accessor :external_id
@@ -9,7 +9,7 @@ module NetSuite
9
9
 
10
10
  # https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2014_1/schema/record/invoice.html?mode=package
11
11
 
12
- actions :get, :get_list, :initialize, :add, :update, :delete, :upsert, :search
12
+ actions :get, :get_deleted, :get_list, :initialize, :add, :update, :delete, :upsert, :search
13
13
 
14
14
  fields :balance, :bill_address,
15
15
  :billing_schedule, :contrib_pct, :created_date, :currency_name, :custom_field_list,
@@ -7,7 +7,7 @@ module NetSuite
7
7
  include Support::Actions
8
8
  include Namespaces::TranPurch
9
9
 
10
- actions :get, :get_list, :add, :initialize, :delete, :update, :upsert, :search
10
+ actions :get, :get_deleted, :get_list, :add, :initialize, :delete, :update, :upsert, :search
11
11
 
12
12
  fields :created_date, :currency_name, :exchange_rate, :landed_cost_per_line,
13
13
  :last_modified_date, :memo, :tran_date, :tran_id
@@ -7,7 +7,7 @@ module NetSuite
7
7
  include Support::Actions
8
8
  include Namespaces::ListAcct
9
9
 
10
- actions :get, :get_list, :add, :delete, :search, :upsert
10
+ actions :get, :get_list, :add, :delete, :search, :update, :upsert
11
11
 
12
12
  fields :available_to_partners, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :country_of_manufacture,
13
13
  :created_date, :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap,
@@ -26,7 +26,7 @@ module NetSuite
26
26
  record_refs :billing_schedule, :cost_category, :custom_form, :deferred_revenue_account, :department, :income_account,
27
27
  :issue_product, :item_options_list, :klass, :location, :parent, :pricing_group, :purchase_tax_code,
28
28
  :quantity_pricing_schedule, :rev_rec_schedule, :sale_unit, :sales_tax_code, :store_display_image,
29
- :store_display_thumbnail, :store_item_template, :tax_schedule, :units_type
29
+ :store_display_thumbnail, :store_item_template, :tax_schedule, :units_type, :subsidiary
30
30
 
31
31
  field :pricing_matrix, PricingMatrix
32
32
  field :custom_field_list, CustomFieldList
@@ -13,7 +13,7 @@ module NetSuite
13
13
  :handling_tax2_rate, :last_modified_date, :linked_tracking_numbers,
14
14
  :memo, :ship_complete, :ship_date, :ship_is_residential, :shipping_cost,
15
15
  :shipping_tax1_rate, :shipping_tax2_rate, :source, :status, :sub_total,
16
- :total, :tracking_numbers, :tran_date, :tran_id, :order_status
16
+ :total, :tracking_numbers, :tran_date, :tran_id, :order_status, :use_item_cost_as_transfer_cost
17
17
 
18
18
  record_refs :transfer_location, :shipping_tax_code, :subsidiary, :shipping_address,
19
19
  :ship_method, :employee, :handling_tax_code,
@@ -22,6 +22,8 @@ module NetSuite
22
22
  self.send(:include, NetSuite::Actions::Get::Support)
23
23
  when :get_all
24
24
  self.send(:include, NetSuite::Actions::GetAll::Support)
25
+ when :get_deleted
26
+ self.send(:include, NetSuite::Actions::GetDeleted::Support)
25
27
  when :get_list
26
28
  self.send(:include, NetSuite::Actions::GetList::Support)
27
29
  when :get_select_value
@@ -56,7 +56,12 @@ module NetSuite
56
56
  fresh_record = self.class.get(self.internal_id, credentials)
57
57
 
58
58
  self.attributes = fresh_record.send(:attributes)
59
- self.external_id = fresh_record.external_id
59
+
60
+ # gift cards do not have an external ID
61
+ if fresh_record.respond_to?(:external_id)
62
+ self.external_id = fresh_record.external_id
63
+ end
64
+
60
65
  self.errors = nil
61
66
 
62
67
  self
@@ -20,7 +20,7 @@ module NetSuite
20
20
  def initialize(response, result_class)
21
21
  @result_class = result_class
22
22
  @response = response
23
-
23
+
24
24
  @total_records = response.body[:total_records].to_i
25
25
  @total_pages = response.body[:total_pages].to_i
26
26
  @current_page = response.body[:page_index].to_i
@@ -52,12 +52,23 @@ module NetSuite
52
52
  # extract the value from <SearchValue/> to make results easier to work with
53
53
 
54
54
  if v.is_a?(Hash) && v.has_key?(:search_value)
55
- # search return values that are just internalIds are stored as attributes on the searchValue element
56
- if v[:search_value].is_a?(Hash) && v[:search_value].has_key?(:'@internal_id')
57
- record[search_group][k] = v[:search_value][:'@internal_id']
58
- else
59
- record[search_group][k] = v[:search_value]
60
- end
55
+ # Here's an example of a record ref and string response
56
+
57
+ # <platformCommon:entity>
58
+ # <platformCore:searchValue internalId="446515"/>
59
+ # </platformCommon:entity>
60
+ # <platformCommon:status>
61
+ # <platformCore:searchValue>open</platformCore:searchValue>
62
+ # </platformCommon:status>
63
+
64
+ # in both cases, header-level field's value should be set to the
65
+ # child `searchValue` result: if it's a record ref, the internalId
66
+ # attribute will be transitioned to the parent, and in the case
67
+ # of a string response the parent node's value will be to the string
68
+
69
+ record[search_group][k] = v[:search_value]
70
+ else
71
+ # NOTE need to understand this case more, in testing, only the namespace definition hits this condition
61
72
  end
62
73
  end
63
74
  end
@@ -97,4 +108,4 @@ module NetSuite
97
108
 
98
109
  end
99
110
  end
100
- end
111
+ end
@@ -7,6 +7,7 @@ module NetSuite
7
7
  def clear_cache!
8
8
  @netsuite_get_record_cache = {}
9
9
  @netsuite_find_record_cache = {}
10
+ DataCenter.clear_cache!
10
11
  end
11
12
 
12
13
  def append_memo(ns_record, added_memo, opts = {})
@@ -64,19 +65,18 @@ module NetSuite
64
65
  return errors.size > 0
65
66
  end
66
67
 
67
-
68
- def get_item(ns_item_internal_id)
68
+ def get_item(ns_item_internal_id, opts = {})
69
69
  # TODO add additional item types!
70
- ns_item = NetSuite::Utilities.get_record(NetSuite::Records::InventoryItem, ns_item_internal_id)
71
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::AssemblyItem, ns_item_internal_id)
72
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::NonInventorySaleItem, ns_item_internal_id)
73
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::NonInventoryResaleItem, ns_item_internal_id)
74
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::DiscountItem, ns_item_internal_id)
75
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::OtherChargeSaleItem, ns_item_internal_id)
76
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::ServiceSaleItem, ns_item_internal_id)
77
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::GiftCertificateItem, ns_item_internal_id)
78
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::KitItem, ns_item_internal_id)
79
- ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::SerializedInventoryItem, ns_item_internal_id)
70
+ ns_item = NetSuite::Utilities.get_record(NetSuite::Records::InventoryItem, ns_item_internal_id, opts)
71
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::AssemblyItem, ns_item_internal_id, opts)
72
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::NonInventorySaleItem, ns_item_internal_id, opts)
73
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::NonInventoryResaleItem, ns_item_internal_id, opts)
74
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::DiscountItem, ns_item_internal_id, opts)
75
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::OtherChargeSaleItem, ns_item_internal_id, opts)
76
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::ServiceSaleItem, ns_item_internal_id, opts)
77
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::GiftCertificateItem, ns_item_internal_id, opts)
78
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::KitItem, ns_item_internal_id, opts)
79
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::SerializedInventoryItem, ns_item_internal_id, opts)
80
80
 
81
81
  if ns_item.nil?
82
82
  fail NetSuite::RecordNotFound, "item with ID #{ns_item_internal_id} not found"
@@ -157,27 +157,8 @@ module NetSuite
157
157
  nil
158
158
  end
159
159
 
160
- def data_center_url(netsuite_account)
161
- begin
162
- data_center_call_response = NetSuite::Configuration.connection({}, {
163
- email: '',
164
- password: '',
165
- account: ''
166
- }).call(:get_data_center_urls, message: {
167
- 'platformMsgs:account' => netsuite_account
168
- })
169
-
170
- if data_center_call_response.success?
171
- return data_center_call_response.body[:get_data_center_urls_response][:get_data_center_urls_result][:data_center_urls][:webservices_domain]
172
- else
173
- # log.error "error getting data center url"
174
- end
175
- rescue Exception => e
176
- # log.error "error determining correct datacenter for account #{netsuite_account}. #{e.message}"
177
-
178
- # TODO silence this later: for now we need to investigate when this would occur
179
- raise(e)
180
- end
160
+ def data_center_url(*args)
161
+ DataCenter.get(*args)
181
162
  end
182
163
 
183
164
  # Warning this was developed with a Web Services user whose time zone was set to CST
@@ -0,0 +1,57 @@
1
+ module NetSuite
2
+ module Utilities
3
+ class DataCenter
4
+ class << self
5
+
6
+ def clear_cache!
7
+ @cache = {}
8
+ end
9
+
10
+ def get(netsuite_account, opts = {})
11
+ if opts[:cache] && wsdl=fetch_from_cache(netsuite_account)
12
+ return wsdl
13
+ end
14
+
15
+ response = make_data_center_call(netsuite_account)
16
+ if response.success?
17
+ wsdl = extract_wsdl_from_response(response)
18
+ cache[netsuite_account.to_s] = wsdl if opts[:cache]
19
+ return wsdl
20
+ else
21
+ return nil
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def cache
28
+ @cache ||= {}
29
+ end
30
+
31
+ def make_data_center_call(netsuite_account)
32
+ NetSuite::Configuration.connection({}, {
33
+ email: '',
34
+ password: '',
35
+ account: ''
36
+ }).call(:get_data_center_urls, message: {
37
+ 'platformMsgs:account' => netsuite_account
38
+ })
39
+ # allow errors to bubble up, log if patterns emerge
40
+ end
41
+
42
+ def fetch_from_cache(netsuite_account)
43
+ return cache.fetch(netsuite_account.to_s, nil)
44
+ end
45
+
46
+ def extract_wsdl_from_response(response)
47
+ response.body
48
+ .fetch(:get_data_center_urls_response)
49
+ .fetch(:get_data_center_urls_result)
50
+ .fetch(:data_center_urls)
51
+ .fetch(:webservices_domain)
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,3 +1,3 @@
1
1
  module Netsuite
2
- VERSION = '0.7.5'
2
+ VERSION = '0.7.6'
3
3
  end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe NetSuite::Actions::GetDeleted do
4
+ before(:all) { savon.mock! }
5
+ after(:all) { savon.unmock! }
6
+
7
+ describe 'Invoice' do
8
+ before do
9
+ savon.expects(:get_deleted).with(:message => {
10
+ 'platformMsgs:pageIndex' => 1,
11
+ 'platformMsgs:getDeletedFilter' => {
12
+ "platformCore:type" => {
13
+ "@operator" =>"anyOf",
14
+ "platformCore:searchValue" => ["invoice"],
15
+ },
16
+ "platformCore:deletedDate" => {
17
+ "@operator" =>"within",
18
+ "platformCore:searchValue" => "2016-12-01T00:00:00",
19
+ "platformCore:searchValue2" => "2016-12-02T00:00:00",
20
+ }
21
+ }
22
+ }).returns(File.read('spec/support/fixtures/get_deleted/get_deleted_invoices.xml'))
23
+ end
24
+
25
+ it 'makes a valid request to the NetSuite API' do
26
+ NetSuite::Actions::GetDeleted.call([NetSuite::Records::Invoice, { page: 1, criteria: [
27
+ {
28
+ field: 'type',
29
+ operator: 'anyOf',
30
+ type: 'SearchEnumMultiSelectField',
31
+ value: ["invoice"]
32
+ },
33
+ {
34
+ field: 'deletedDate',
35
+ operator: "within",
36
+ type: 'SearchDateField',
37
+ value: ["2016-12-01T00:00:00","2016-12-02T00:00:00"]
38
+ }
39
+ ]}])
40
+ end
41
+
42
+ it 'returns a valid Response object' do
43
+ response = NetSuite::Actions::GetDeleted.call([NetSuite::Records::Invoice, { page: 1, criteria: [
44
+ {
45
+ field: 'type',
46
+ operator: 'anyOf',
47
+ type: 'SearchEnumMultiSelectField',
48
+ value: ["invoice"]
49
+ },
50
+ {
51
+ field: 'deletedDate',
52
+ operator: "within",
53
+ type: 'SearchDateField',
54
+ value: ["2016-12-01T00:00:00","2016-12-02T00:00:00"]
55
+ }
56
+ ]}])
57
+ expect(response).to be_kind_of(NetSuite::Response)
58
+ end
59
+ end
60
+ end
@@ -29,14 +29,34 @@ describe NetSuite::Configuration do
29
29
  end
30
30
 
31
31
  describe '#connection' do
32
- it 'returns a Savon::Client object that allows requests to the service' do
32
+ before(:each) do
33
33
  # reset clears out the password info
34
- config.email 'me@example.com'
35
- config.password 'me@example.com'
36
- config.account 1023
34
+ config.email 'me@example.com'
35
+ config.password 'me@example.com'
36
+ config.account 1023
37
+ config.wsdl "my_wsdl"
38
+ config.api_version "2012_2"
39
+ end
37
40
 
41
+ it 'returns a Savon::Client object that allows requests to the service' do
38
42
  expect(config.connection).to be_kind_of(Savon::Client)
39
43
  end
44
+
45
+ it 'caches the client' do
46
+ expect(config.wsdl_cache).to be_empty
47
+ conn = config.connection
48
+
49
+ expect(
50
+ config.wsdl_cache.fetch([config.api_version, config.wsdl])
51
+ ).to eq(conn)
52
+ end
53
+
54
+ it 'uses cached wsdls' do
55
+ allow(config).to receive(:cached_wsdl)
56
+ config.connection
57
+
58
+ expect(config).to have_received(:cached_wsdl)
59
+ end
40
60
  end
41
61
 
42
62
  describe '#wsdl' do
@@ -73,6 +93,77 @@ describe NetSuite::Configuration do
73
93
  expect(config.wsdl).to eql('https://system.na1.netsuite.com/wsdl/v2014_1_0/netsuite.wsdl')
74
94
  end
75
95
  end
96
+
97
+ context '#cache_wsdl' do
98
+ it 'stores the client' do
99
+ expect(config.wsdl_cache).to be_empty
100
+ config.cache_wsdl("whatevs")
101
+ expect(config.wsdl_cache).to eq(
102
+ {[config.api_version, config.wsdl] => "whatevs"}
103
+ )
104
+ end
105
+
106
+ it 'doesnt write over old values' do
107
+ config.class_exec(config.api_version, config.wsdl) do |api, wsdl|
108
+ wsdl_cache[[api, wsdl]] = "old value"
109
+ end
110
+ config.cache_wsdl("new value")
111
+
112
+ expect(config.wsdl_cache.values.first).to eq("old value")
113
+ end
114
+
115
+ it 'handles a nil cache' do
116
+ config.class_eval { @wsdl_cache = nil }
117
+ config.cache_wsdl("whatevs")
118
+ expect(config.wsdl_cache).to eq(
119
+ {[config.api_version, config.wsdl] => "whatevs"}
120
+ )
121
+ end
122
+
123
+ it 'can cache multiple values' do
124
+ config.class_exec("2020_2", "fake wsdl") do |api, wsdl|
125
+ wsdl_cache[[api, wsdl]] = "old value"
126
+ end
127
+ expect(config.wsdl_cache.keys.count).to eq 1
128
+ config.cache_wsdl("new value")
129
+
130
+ expect(config.wsdl_cache.keys.count).to eq 2
131
+ end
132
+ end
133
+
134
+ context '#cached_wsdl' do
135
+ it 'returns wsdl (xml)' do
136
+ config.class_exec(config.api_version, config.wsdl) do |api, wsdl|
137
+ wsdl_cache[[api, wsdl]] = "xml wsdl string"
138
+ end
139
+ expect( config.cached_wsdl ).to eq "xml wsdl string"
140
+ end
141
+
142
+ it 'stores client xml' do
143
+ client = double(:savon_client)
144
+ allow(client).to receive(:is_a?).with(String).and_return(false)
145
+ allow(client).to receive(:is_a?).with(Savon::Client).and_return(true)
146
+ wsdl_dbl = double(:wsdl, xml: "xml wsdl")
147
+ client.instance_exec(wsdl_dbl) {|wsdl| @wsdl = wsdl }
148
+ config.class_exec(config.api_version, config.wsdl, client) do |api, wsdl, c|
149
+ wsdl_cache[[api, wsdl]] = c
150
+ end
151
+
152
+ expect( config.wsdl_cache.values.first ).to eq client
153
+ expect( config.cached_wsdl ).to eq "xml wsdl"
154
+ expect( config.wsdl_cache.values.first ).to eq "xml wsdl"
155
+ end
156
+
157
+ it 'handles a nil cache' do
158
+ config.class_eval { @wsdl_cache = nil }
159
+ expect( config.cached_wsdl ).to eq nil
160
+ end
161
+
162
+ it 'handles an empty cache' do
163
+ expect(config.wsdl_cache).to be_empty
164
+ expect( config.cached_wsdl ).to eq nil
165
+ end
166
+ end
76
167
  end
77
168
 
78
169
  describe '#auth_header' do
@@ -17,8 +17,8 @@ describe NetSuite::Records::Customer do
17
17
  :opening_balance, :opening_balance_account, :opening_balance_date, :overdue_balance,
18
18
  :password, :password2, :phone, :phonetic_name, :pref_cc_processor,:print_on_check_as,
19
19
  :print_transactions, :referrer, :reminder_days, :representing_subsidiary, :require_pwd_change, :resale_number,
20
- :sales_group, :sales_readiness, :sales_team_list, :salutation, :send_email, :ship_complete, :shipping_item,
21
- :stage, :start_date, :sync_partner_teams, :tax_exempt, :tax_item, :taxable,
20
+ :sales_group, :sales_readiness, :sales_team_list, :salutation, :send_email, :ship_complete,
21
+ :stage, :start_date, :sync_partner_teams, :tax_exempt, :taxable,
22
22
  :third_party_acct, :third_party_country, :third_party_zipcode, :title, :unbilled_orders, :url,
23
23
  :vat_reg_number, :visits, :web_lead
24
24
  ].each do |field|
@@ -28,7 +28,7 @@ describe NetSuite::Records::Customer do
28
28
 
29
29
  it 'has the right record_refs' do
30
30
  [
31
- :access_role, :currency, :custom_form, :entity_status, :partner, :sales_rep, :terms, :parent, :territory
31
+ :access_role, :currency, :custom_form, :entity_status, :partner, :sales_rep, :terms, :parent, :territory, :shipping_item, :tax_item
32
32
  ].each do |record_ref|
33
33
  expect(customer).to have_record_ref(record_ref)
34
34
  end
@@ -91,6 +91,32 @@ describe NetSuite::Records::NonInventorySaleItem do
91
91
  end
92
92
  end
93
93
 
94
+ describe '#update' do
95
+ context 'when the response is successful' do
96
+ let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) }
97
+
98
+ it 'returns true' do
99
+ expect(NetSuite::Actions::Update).to receive(:call).
100
+ with([item.class, {external_id: 'foo'}], {}).
101
+ and_return(response)
102
+ item.external_id = 'foo'
103
+ expect(item.update).to be_truthy
104
+ end
105
+ end
106
+
107
+ context 'when the response is unsuccessful' do
108
+ let(:response) { NetSuite::Response.new(:success => false, :body => {}) }
109
+
110
+ it 'returns false' do
111
+ expect(NetSuite::Actions::Update).to receive(:call).
112
+ with([item.class, {external_id: 'foo'}], {}).
113
+ and_return(response)
114
+ item.external_id = 'foo'
115
+ expect(item.update).to be_falsey
116
+ end
117
+ end
118
+ end
119
+
94
120
  describe '#delete' do
95
121
  context 'when the response is successful' do
96
122
  let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) }
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe NetSuite::Utilities::DataCenter do
4
+ describe '#get' do
5
+ let(:wsdl) { "https://webservices.na1.netsuite.com" }
6
+ let(:account) { "BOGUSACCT" }
7
+ let(:response) do
8
+ double(
9
+ success?: true,
10
+ body: {
11
+ get_data_center_urls_response: {
12
+ get_data_center_urls_result: {
13
+ data_center_urls: { webservices_domain: wsdl }
14
+ }
15
+ }
16
+ }
17
+ )
18
+ end
19
+
20
+ context 'without caching' do
21
+ it 'hits the API more than once' do
22
+ allow(described_class).to receive(:make_data_center_call)
23
+ .with(account)
24
+ .and_return(response)
25
+ 10.times { described_class.get(account) }
26
+
27
+ expect(described_class).to have_received(:make_data_center_call)
28
+ .with(account).exactly(10).times
29
+ end
30
+
31
+ end
32
+
33
+ context 'when caching is enabled' do
34
+
35
+ it 'doesnt hit the API when cached response is present' do
36
+ described_class.class_exec(account) {|acct| cache[acct] = "wsdl" }
37
+ allow(described_class).to receive(:make_data_center_call)
38
+ described_class.get(account, cache: true)
39
+
40
+ expect(described_class).to_not have_received(:make_data_center_call)
41
+ end
42
+
43
+ it 'hits the API at most once' do
44
+ allow(described_class).to receive(:make_data_center_call)
45
+ .with(account)
46
+ .and_return(response)
47
+ 10.times { described_class.get(account, cache: true) }
48
+
49
+ expect(described_class).to have_received(:make_data_center_call)
50
+ .with(account).exactly(1).times
51
+ end
52
+
53
+ it 'hits the API after its cache is cleared' do
54
+ described_class.class_exec(account) {|acct| cache[acct] = "wsdl" }
55
+ allow(described_class).to receive(:make_data_center_call)
56
+ .with(account)
57
+ .and_return(response)
58
+ described_class.get(account, cache: true)
59
+
60
+ expect(described_class).to_not have_received(:make_data_center_call)
61
+
62
+ described_class.clear_cache!
63
+ described_class.get(account, cache: true)
64
+
65
+ expect(described_class).to have_received(:make_data_center_call)
66
+ .with(account).exactly(1).times
67
+ end
68
+
69
+ it 'hits the API if the cache contains another response' do
70
+ described_class.class_eval { cache["BOGUS"] = "wsdl" }
71
+ allow(described_class).to receive(:make_data_center_call)
72
+ .with(account)
73
+ .and_return(response)
74
+ expect(account).to_not eq("BOGUS")
75
+ described_class.get(account, cache: true)
76
+
77
+ expect(described_class).to have_received(:make_data_center_call)
78
+ .with(account).exactly(1).times
79
+ end
80
+ end
81
+
82
+ context 'when cache is empty' do
83
+
84
+ it 'does hit the API' do
85
+ expect( described_class.class_eval { cache }).to eq({})
86
+ allow(described_class).to receive(:make_data_center_call)
87
+ .with(account)
88
+ .and_return(response)
89
+
90
+ described_class.get(account, cache: true)
91
+
92
+ expect(described_class).to have_received(:make_data_center_call)
93
+ .with(account).exactly(1).times
94
+ end
95
+
96
+ it 'stores API response' do
97
+ expect( described_class.class_eval { cache }).to eq({})
98
+ allow(described_class).to receive(:make_data_center_call)
99
+ .with(account)
100
+ .and_return(response)
101
+
102
+ described_class.get(account, cache: true)
103
+
104
+ expect( described_class.class_eval { cache }).to eq(
105
+ { account => wsdl }
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,38 @@
1
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2
+ <soapenv:Header>
3
+ <platformMsgs:documentInfo xmlns:platformMsgs="urn:messages_2016_1.platform.webservices.netsuite.com">
4
+ <platformMsgs:nsId>WEBSERVICES_3868171_122020162073815481885806769_24761121647f</platformMsgs:nsId>
5
+ </platformMsgs:documentInfo>
6
+ </soapenv:Header>
7
+ <soapenv:Body>
8
+ <getDeletedResponse xmlns="urn:messages_2016_1.platform.webservices.netsuite.com">
9
+ <platformCore:getDeletedResult xmlns:platformCore="urn:core_2016_1.platform.webservices.netsuite.com">
10
+ <platformCore:status isSuccess="true"/>
11
+ <platformCore:totalRecords>3</platformCore:totalRecords>
12
+ <platformCore:pageSize>1000</platformCore:pageSize>
13
+ <platformCore:totalPages>1</platformCore:totalPages>
14
+ <platformCore:pageIndex>1</platformCore:pageIndex>
15
+ <platformCore:deletedRecordList>
16
+ <platformCore:deletedRecord>
17
+ <platformCore:deletedDate>2016-12-15T15:00:00.000-08:00</platformCore:deletedDate>
18
+ <platformCore:record internalId="1" type="invoice" xsi:type="platformCore:RecordRef">
19
+ <platformCore:name>REDACTED</platformCore:name>
20
+ </platformCore:record>
21
+ </platformCore:deletedRecord>
22
+ <platformCore:deletedRecord>
23
+ <platformCore:deletedDate>2016-12-09T13:00:00.000-08:00</platformCore:deletedDate>
24
+ <platformCore:record internalId="2" type="invoice" xsi:type="platformCore:RecordRef">
25
+ <platformCore:name>REDACTED</platformCore:name>
26
+ </platformCore:record>
27
+ </platformCore:deletedRecord>
28
+ <platformCore:deletedRecord>
29
+ <platformCore:deletedDate>2016-12-15T15:00:00.000-08:00</platformCore:deletedDate>
30
+ <platformCore:record internalId="3" type="invoice" xsi:type="platformCore:RecordRef">
31
+ <platformCore:name>REDACTED</platformCore:name>
32
+ </platformCore:record>
33
+ </platformCore:deletedRecord>
34
+ </platformCore:deletedRecordList>
35
+ </platformCore:getDeletedResult>
36
+ </getDeletedResponse>
37
+ </soapenv:Body>
38
+ </soapenv:Envelope>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: netsuite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.5
4
+ version: 0.7.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-11-17 00:00:00.000000000 Z
13
+ date: 2017-01-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: savon
@@ -66,6 +66,7 @@ files:
66
66
  - lib/netsuite/actions/delete_list.rb
67
67
  - lib/netsuite/actions/get.rb
68
68
  - lib/netsuite/actions/get_all.rb
69
+ - lib/netsuite/actions/get_deleted.rb
69
70
  - lib/netsuite/actions/get_list.rb
70
71
  - lib/netsuite/actions/get_select_value.rb
71
72
  - lib/netsuite/actions/initialize.rb
@@ -291,11 +292,13 @@ files:
291
292
  - lib/netsuite/support/search_result.rb
292
293
  - lib/netsuite/support/sublist.rb
293
294
  - lib/netsuite/utilities.rb
295
+ - lib/netsuite/utilities/data_center.rb
294
296
  - lib/netsuite/version.rb
295
297
  - netsuite.gemspec
296
298
  - spec/netsuite/actions/add_spec.rb
297
299
  - spec/netsuite/actions/delete_list_spec.rb
298
300
  - spec/netsuite/actions/delete_spec.rb
301
+ - spec/netsuite/actions/get_deleted_spec.rb
299
302
  - spec/netsuite/actions/get_list_spec.rb
300
303
  - spec/netsuite/actions/get_spec.rb
301
304
  - spec/netsuite/actions/initialize_spec.rb
@@ -421,6 +424,7 @@ files:
421
424
  - spec/netsuite/support/records_spec.rb
422
425
  - spec/netsuite/support/requests_spec.rb
423
426
  - spec/netsuite/support/sublist_spec.rb
427
+ - spec/netsuite/utilities/data_center_spec.rb
424
428
  - spec/netsuite/utilities/request_spec.rb
425
429
  - spec/netsuite/utilities/roles_spec.rb
426
430
  - spec/netsuite/utilities_spec.rb
@@ -438,6 +442,7 @@ files:
438
442
  - spec/support/fixtures/delete_list/delete_list_customers_with_errors.xml
439
443
  - spec/support/fixtures/get/get_customer.xml
440
444
  - spec/support/fixtures/get/get_invoice.xml
445
+ - spec/support/fixtures/get_deleted/get_deleted_invoices.xml
441
446
  - spec/support/fixtures/initialize/initialize_invoice_from_customer.xml
442
447
  - spec/support/fixtures/login/failure_concurrent_requests.xml
443
448
  - spec/support/fixtures/login/failure_invalid_credentials.xml
@@ -486,6 +491,7 @@ test_files:
486
491
  - spec/netsuite/actions/add_spec.rb
487
492
  - spec/netsuite/actions/delete_list_spec.rb
488
493
  - spec/netsuite/actions/delete_spec.rb
494
+ - spec/netsuite/actions/get_deleted_spec.rb
489
495
  - spec/netsuite/actions/get_list_spec.rb
490
496
  - spec/netsuite/actions/get_spec.rb
491
497
  - spec/netsuite/actions/initialize_spec.rb
@@ -611,6 +617,7 @@ test_files:
611
617
  - spec/netsuite/support/records_spec.rb
612
618
  - spec/netsuite/support/requests_spec.rb
613
619
  - spec/netsuite/support/sublist_spec.rb
620
+ - spec/netsuite/utilities/data_center_spec.rb
614
621
  - spec/netsuite/utilities/request_spec.rb
615
622
  - spec/netsuite/utilities/roles_spec.rb
616
623
  - spec/netsuite/utilities_spec.rb
@@ -628,6 +635,7 @@ test_files:
628
635
  - spec/support/fixtures/delete_list/delete_list_customers_with_errors.xml
629
636
  - spec/support/fixtures/get/get_customer.xml
630
637
  - spec/support/fixtures/get/get_invoice.xml
638
+ - spec/support/fixtures/get_deleted/get_deleted_invoices.xml
631
639
  - spec/support/fixtures/initialize/initialize_invoice_from_customer.xml
632
640
  - spec/support/fixtures/login/failure_concurrent_requests.xml
633
641
  - spec/support/fixtures/login/failure_invalid_credentials.xml