netsuite 0.7.5 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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