From 5b999695fecc32c394f9787067827aeda4ef8404 Mon Sep 17 00:00:00 2001 From: Andrew Konchin Date: Thu, 16 May 2019 17:07:29 +0300 Subject: [PATCH 01/46] Fix issue with empty `record_list` node in search result --- lib/netsuite/support/search_result.rb | 15 +++++++++++-- spec/netsuite/support/search_result_spec.rb | 24 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 spec/netsuite/support/search_result_spec.rb diff --git a/lib/netsuite/support/search_result.rb b/lib/netsuite/support/search_result.rb index ddf1f2938..901c46edf 100644 --- a/lib/netsuite/support/search_result.rb +++ b/lib/netsuite/support/search_result.rb @@ -29,8 +29,19 @@ def initialize(response, result_class, credentials) if @total_records > 0 if response.body.has_key?(:record_list) # basic search results - record_list = response.body[:record_list][:record] - record_list = [record_list] unless record_list.is_a?(Array) + + # `recordList` node can contain several nested `record` nodes, only one node or be empty + # so we have to handle all these cases: + # * { record_list: nil } + # * { record_list: { record: => {...} } } + # * { record_list: { record: => [{...}, {...}, ...] } } + record_list = if response.body[:record_list].nil? + [] + elsif response.body[:record_list][:record].is_a?(Array) + response.body[:record_list][:record] + else + [response.body[:record_list][:record]] + end record_list.each do |record| results << result_class.new(record) diff --git a/spec/netsuite/support/search_result_spec.rb b/spec/netsuite/support/search_result_spec.rb new file mode 100644 index 000000000..53256e58c --- /dev/null +++ b/spec/netsuite/support/search_result_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe NetSuite::Support::SearchResult do + describe '#results' do + context 'empty page' do + it 'returns empty array' do + response_body = { + :status => {:@is_success=>"true"}, + :total_records => "242258", + :page_size => "10", + :total_pages => "24226", + :page_index => "99", + :search_id => "WEBSERVICES_4132604_SB1_051620191060155623420663266_336cbf12", + :record_list => nil, + :"@xmlns:platform_core" => "urn:core_2016_2.platform.webservices.netsuite.com" + } + response = NetSuite::Response.new(body: response_body) + + results = described_class.new(response, NetSuite::Actions::Search, {}).results + expect(results).to eq [] + end + end + end +end From 2e58c99eea33b2921b75f39a4e25e10247743a9f Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Sat, 18 May 2019 08:20:16 -0600 Subject: [PATCH 02/46] Adding guide to finding the account ID --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2797000ce..9c1e997ec 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ This gem is built for ruby 1.9.x+, checkout the [1-8-stable](https://github.com/ ## Configuration -Not sure how to find your account id? Search for "web service preferences" in the NetSuite global search. +Not sure how to find your account id? [Here's a guide.](http://mikebian.co/find-netsuite-web-services-account-number/) ```ruby NetSuite.configure do From 56413df0437b4ce8220b14698daaa231b33c15e6 Mon Sep 17 00:00:00 2001 From: Antoine Saliba Date: Mon, 20 May 2019 10:33:04 -0400 Subject: [PATCH 03/46] Add direct_revenue_posting field to non-inventory sale item model --- lib/netsuite/records/non_inventory_sale_item.rb | 2 +- spec/netsuite/records/non_inventory_sale_item_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/netsuite/records/non_inventory_sale_item.rb b/lib/netsuite/records/non_inventory_sale_item.rb index 75d49770b..7699b4637 100644 --- a/lib/netsuite/records/non_inventory_sale_item.rb +++ b/lib/netsuite/records/non_inventory_sale_item.rb @@ -10,7 +10,7 @@ class NonInventorySaleItem actions :get, :get_list, :add, :delete, :search, :update, :upsert fields :available_to_partners, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :country_of_manufacture, - :created_date, :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, + :created_date, :direct_revenue_posting, :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, :featured_description, :handling_cost, :handling_cost_units, :include_children, :is_donation_item, :is_fulfillable, :is_gco_compliant, :is_inactive, :is_online, :is_taxable, :item_id, :last_modified_date, :manufacturer, :manufacturer_addr1, :manufacturer_city, :manufacturer_state, :manufacturer_tariff, :manufacturer_tax_id, diff --git a/spec/netsuite/records/non_inventory_sale_item_spec.rb b/spec/netsuite/records/non_inventory_sale_item_spec.rb index 6e5902233..723324f39 100644 --- a/spec/netsuite/records/non_inventory_sale_item_spec.rb +++ b/spec/netsuite/records/non_inventory_sale_item_spec.rb @@ -6,7 +6,7 @@ it 'has the right fields' do [ :available_to_partners, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :country_of_manufacture, :created_date, - :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, + :direct_revenue_posting, :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, :featured_description, :handling_cost, :handling_cost_units, :include_children, :is_donation_item, :is_fulfillable, :is_gco_compliant, :is_inactive, :is_online, :is_taxable, :item_id, :last_modified_date, :manufacturer, :manufacturer_addr1, :manufacturer_city, :manufacturer_state, :manufacturer_tariff, :manufacturer_tax_id, :manufacturer_zip, :matrix_option_list, From 7684482d6762714e6f24b0aceeb623d65e0d9316 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Sun, 26 May 2019 13:46:01 -0600 Subject: [PATCH 04/46] Improving configuration documentation --- README.md | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9c1e997ec..fbddf116e 100644 --- a/README.md +++ b/README.md @@ -60,14 +60,36 @@ This gem is built for ruby 1.9.x+, checkout the [1-8-stable](https://github.com/ ## Configuration -Not sure how to find your account id? [Here's a guide.](http://mikebian.co/find-netsuite-web-services-account-number/) +The most important thing you'll need is your NetSuite account ID. Not sure how to find your account id? [Here's a guide.](http://mikebian.co/find-netsuite-web-services-account-number/) + +For most use-cases, the following configuration will be sufficient: ```ruby NetSuite.configure do reset! - # optional, defaults to 2011_2 - api_version '2012_1' + account 'TSTDRV1576318' + api_version '2018_2' + + email 'email@example.com' + password 'password' + role 10 + + # use `NetSuite::Utilities.netsuite_data_center_urls('TSTDRV1576318')` to retrieve the URL + # you'll want to do this in a background proces and strip the protocol out of the return string + wsdl_domain 'tstdrv1576318.suitetalk.api.netsuite.com' +end +``` + +The `wsdl_domain` configuration is most important. Note that if you use `wsdl` or other configuration options below, you'll want to look at the configuration source to understand more about how the different options interact with each other. Some of the configuration options will mutate the state of other options. + +Here's the various options that are are available for configuration: + +```ruby +NetSuite.configure do + reset! + + api_version '2018_2' # optionally specify full wsdl URL (to switch to sandbox, for example) wsdl "https://webservices.sandbox.netsuite.com/wsdl/v#{api_version}_0/netsuite.wsdl" @@ -79,21 +101,18 @@ NetSuite.configure do # construct the full wsdl location - e.g. "https://#{wsdl_domain}/wsdl/v#{api_version}_0/netsuite.wsdl" wsdl_domain "webservices.na2.netsuite.com" - # or specify the sandbox flag if you don't want to deal with specifying a full URL - sandbox true - # often the netsuite servers will hang which would cause a timeout exception to be raised - # if you don't mind waiting (e.g. processing NS via DJ), increasing the timeout should fix the issue - read_timeout 100000 + # if you don't mind waiting (e.g. processing NS via a background worker), increasing the timeout should fix the issue + read_timeout 100_000 # you can specify a file or file descriptor to send the log output to (defaults to STDOUT) log File.join(Rails.root, 'log/netsuite.log') - # login information - email 'email@domain.com' - password 'password' + # password-based login information + email 'email@domain.com' + password 'password' account '12345' - role 1111 + role 1111 # optional, ensures that read-only fields don't cause API errors soap_header 'platformMsgs:preferences' => { From df796ee3afe06d10357488cacf5cf8c9140c121a Mon Sep 17 00:00:00 2001 From: Mike Bianco Date: Tue, 28 May 2019 09:55:29 -0600 Subject: [PATCH 05/46] Adding subsidiary_list to OtherChargeSaleItem --- lib/netsuite/records/other_charge_sale_item.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/netsuite/records/other_charge_sale_item.rb b/lib/netsuite/records/other_charge_sale_item.rb index cbb649e1f..874643327 100644 --- a/lib/netsuite/records/other_charge_sale_item.rb +++ b/lib/netsuite/records/other_charge_sale_item.rb @@ -52,11 +52,11 @@ class OtherChargeSaleItem :units_type, :sales_tax_code, :sale_unit, :tax_schedule, :parent field :custom_field_list, CustomFieldList - # :pricing_matrix, + field :pricing_matrix, PricingMatrix # :translations_list, # :matrix_option_list, # :item_options_list - # :subsidiary_list, + field :subsidiary_list, RecordRefList def initialize(attributes = {}) @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id) From ba4a55bd286f599ddaa8882b238e411a6b314847 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Wed, 29 May 2019 07:41:24 -0600 Subject: [PATCH 06/46] Adding new netsuite error --- lib/netsuite/utilities.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/netsuite/utilities.rb b/lib/netsuite/utilities.rb index 1b5c0bc03..fab9d674a 100644 --- a/lib/netsuite/utilities.rb +++ b/lib/netsuite/utilities.rb @@ -115,6 +115,7 @@ def backoff(options = {}) # https://github.com/stripe/stripe-netsuite/issues/815 if !e.message.include?("Only one request may be made against a session at a time") && !e.message.include?('java.util.ConcurrentModificationException') && + !e.message.include?('java.lang.NullPointerException') && !e.message.include?('java.lang.IllegalStateException') && !e.message.include?('java.lang.reflect.InvocationTargetException') && !e.message.include?('com.netledger.common.exceptions.NLDatabaseOfflineException') && From b0d53965d44aea6ef9076a3814c4d53ff101c373 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Thu, 30 May 2019 11:44:58 -0600 Subject: [PATCH 07/46] Adding lot numbered item to get_item --- lib/netsuite/utilities.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/netsuite/utilities.rb b/lib/netsuite/utilities.rb index fab9d674a..72f4534c4 100644 --- a/lib/netsuite/utilities.rb +++ b/lib/netsuite/utilities.rb @@ -174,6 +174,7 @@ def get_item(ns_item_internal_id, opts = {}) ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::KitItem, ns_item_internal_id, opts) ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::SerializedInventoryItem, ns_item_internal_id, opts) ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::LotNumberedAssemblyItem, ns_item_internal_id, opts) + ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::LotNumberedInventoryItem, ns_item_internal_id, opts) if ns_item.nil? fail NetSuite::RecordNotFound, "item with ID #{ns_item_internal_id} not found" From 1a6081d1621b4ace2c385187eafb70411c04604f Mon Sep 17 00:00:00 2001 From: Jeremy Michael Crosbie Date: Mon, 10 Jun 2019 12:15:26 -0600 Subject: [PATCH 08/46] Added support case types and messages --- lib/netsuite.rb | 2 + lib/netsuite/records/message.rb | 30 ++++++++++++ lib/netsuite/records/support_case_type.rb | 26 ++++++++++ spec/netsuite/records/message_spec.rb | 49 +++++++++++++++++++ .../records/support_case_type_spec.rb | 22 +++++++++ 5 files changed, 129 insertions(+) create mode 100644 lib/netsuite/records/message.rb create mode 100644 lib/netsuite/records/support_case_type.rb create mode 100644 spec/netsuite/records/message_spec.rb create mode 100644 spec/netsuite/records/support_case_type_spec.rb diff --git a/lib/netsuite.rb b/lib/netsuite.rb index 19aa1a2ef..fad4f2d55 100644 --- a/lib/netsuite.rb +++ b/lib/netsuite.rb @@ -209,6 +209,7 @@ module Records autoload :LotNumberedInventoryItem, 'netsuite/records/lot_numbered_inventory_item' autoload :MatrixOptionList, 'netsuite/records/matrix_option_list' autoload :MemberList, 'netsuite/records/member_list' + autoload :Message, 'netsuite/records/message' autoload :NonInventorySaleItem, 'netsuite/records/non_inventory_sale_item' autoload :NonInventoryPurchaseItem, 'netsuite/records/non_inventory_purchase_item' autoload :NonInventoryResaleItem, 'netsuite/records/non_inventory_resale_item' @@ -262,6 +263,7 @@ module Records autoload :Subsidiary, 'netsuite/records/subsidiary' autoload :SubtotalItem, 'netsuite/records/subtotal_item' autoload :SupportCase, 'netsuite/records/support_case' + autoload :SupportCaseType, 'netsuite/records/support_case_type' autoload :TaxType, 'netsuite/records/tax_type' autoload :TaxGroup, 'netsuite/records/tax_group' autoload :Task, 'netsuite/records/task' diff --git a/lib/netsuite/records/message.rb b/lib/netsuite/records/message.rb new file mode 100644 index 000000000..e924f9df9 --- /dev/null +++ b/lib/netsuite/records/message.rb @@ -0,0 +1,30 @@ +module NetSuite + module Records + class Message + include Support::Fields + include Support::RecordRefs + include Support::Records + include Support::Actions + include Namespaces::CommGeneral + + actions :get, :add, :delete, :search + + fields :bcc, :cc, :compress_attachments, :date_time, :emailed, :incoming, + :message, :record_name, :record_type_name, :subject + + read_only_fields :last_modified_date, :message_date + + record_refs :activity, :author, :recipient, :transaction + + attr_reader :internal_id + attr_accessor :external_id + + def initialize(attributes_or_record = {}) + @internal_id = attributes_or_record.delete(:internal_id) || attributes_or_record.delete(:@internal_id) + @external_id = attributes_or_record.delete(:external_id) || attributes_or_record.delete(:@external_id) + initialize_from_attributes_hash(attributes_or_record) + end + + end + end +end diff --git a/lib/netsuite/records/support_case_type.rb b/lib/netsuite/records/support_case_type.rb new file mode 100644 index 000000000..63152d3ce --- /dev/null +++ b/lib/netsuite/records/support_case_type.rb @@ -0,0 +1,26 @@ +module NetSuite + module Records + class SupportCaseType + include Support::Fields + include Support::RecordRefs + include Support::Records + include Support::Actions + include Namespaces::ListSupport + + actions :get + + fields :description, :is_inactive, :name + + record_refs :insert_before + + attr_reader :internal_id + attr_accessor :external_id + + def initialize(attributes = {}) + @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id) + @external_id = attributes.delete(:external_id) || attributes.delete(:@external_id) + initialize_from_attributes_hash(attributes) + end + end + end +end diff --git a/spec/netsuite/records/message_spec.rb b/spec/netsuite/records/message_spec.rb new file mode 100644 index 000000000..7093db7fd --- /dev/null +++ b/spec/netsuite/records/message_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe NetSuite::Records::Message do + let(:message) { NetSuite::Records::Message.new } + + it 'has all the right fields' do + [ + :bcc, :cc, :compress_attachments, :date_time, :emailed, + :incoming, :message, :record_name, :record_type_name, :subject, + :last_modified_date, :message_date + ].each do |field| + expect(message).to have_field(field) + end + end + + it 'has the right record_refs' do + [ + :activity, :author, :recipient, :transaction + ].each do |record_ref| + expect(message).to have_record_ref(record_ref) + end + end + + describe '.get' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => {message: 'message text'})} + + it 'returns a Message instance populated with data from the response object' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::Message, :external_id => 1], {}).and_return(response) + + message = NetSuite::Records::Message.get(:external_id => 1) + expect(message).to be_kind_of(NetSuite::Records::Message) + expect(message.message).to eq('message text') + end + end + + context 'when response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + it 'raises a RecordNotFound exception' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::Message, :external_id => 1], {}).and_return(response) + expect { + NetSuite::Records::Message.get(:external_id => 1) + }.to raise_error(NetSuite::RecordNotFound, + /NetSuite::Records::Message with OPTIONS=(.*) could not be found/) + end + end + end + +end diff --git a/spec/netsuite/records/support_case_type_spec.rb b/spec/netsuite/records/support_case_type_spec.rb new file mode 100644 index 000000000..0259e52c7 --- /dev/null +++ b/spec/netsuite/records/support_case_type_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe NetSuite::Records::SupportCaseType do + let(:support_case_type) { NetSuite::Records::SupportCaseType.new } + + it 'has all the right fields' do + [ + :description, :is_inactive, :name + ].each do |field| + expect(support_case_type).to have_field(field) + end + end + + it 'has the right record_refs' do + [ + :insert_before + ].each do |record_ref| + expect(support_case_type).to have_record_ref(record_ref) + end + end + +end From 3a5ae3edbdacfef2cd67aeb545aedf94aac21305 Mon Sep 17 00:00:00 2001 From: Joe Rzepiejewski Date: Wed, 10 Jul 2019 17:23:35 -0700 Subject: [PATCH 09/46] added more properties to matrix_option_list --- lib/netsuite/records/matrix_option_list.rb | 16 +++++++++++---- lib/netsuite/version.rb | 2 +- .../records/matrix_option_list_spec.rb | 20 ++++++++++++++----- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib/netsuite/records/matrix_option_list.rb b/lib/netsuite/records/matrix_option_list.rb index 1d8fe4403..af0790205 100644 --- a/lib/netsuite/records/matrix_option_list.rb +++ b/lib/netsuite/records/matrix_option_list.rb @@ -15,10 +15,14 @@ class MatrixOptionList # # # - # + # + # foo + # # # - # + # + # bar + # # # # @@ -27,13 +31,17 @@ def initialize(attributes = {}) when Hash options << OpenStruct.new( type_id: attributes[:matrix_option][:value][:'@type_id'], - value_id: attributes[:matrix_option][:value][:'@internal_id'] + value_id: attributes[:matrix_option][:value][:'@internal_id'], + script_id: attributes[:matrix_option][:@script_id], + name: attributes[:matrix_option][:value][:name] ) when Array attributes[:matrix_option].each do |option| options << OpenStruct.new( type_id: option[:value][:'@type_id'], - value_id: option[:value][:'@internal_id'] + value_id: option[:value][:'@internal_id'], + script_id: option[:@script_id], + name: option[:value][:name] ) end end diff --git a/lib/netsuite/version.rb b/lib/netsuite/version.rb index 23293d429..115ab03a1 100644 --- a/lib/netsuite/version.rb +++ b/lib/netsuite/version.rb @@ -1,3 +1,3 @@ module NetSuite - VERSION = '0.8.4' + VERSION = '0.8.4.1' end diff --git a/spec/netsuite/records/matrix_option_list_spec.rb b/spec/netsuite/records/matrix_option_list_spec.rb index 174b74499..f0ccc1cc3 100644 --- a/spec/netsuite/records/matrix_option_list_spec.rb +++ b/spec/netsuite/records/matrix_option_list_spec.rb @@ -5,20 +5,30 @@ module NetSuite module Records describe MatrixOptionList do it "deals with hash properly" do - hash = {:value=>{:@internal_id=>"1", :@type_id=>"36"}} + hash = {:value=>{:@internal_id=>"1", :@type_id=>"36", :name=>"some value"}, :@script_id=>'cust_field_1'} list = described_class.new({ matrix_option: hash }) - expect(list.options.first.value_id).to eq "1" + option = list.options.first + expect(option.value_id).to eq "1" + expect(option.type_id).to eq "36" + expect(option.name).to eq "some value" + expect(option.script_id).to eq "cust_field_1" end it "deals with arrays properly" do array = [ - {:value=>{:@internal_id=>"2", :@type_id=>"28"}}, - {:value=>{:@internal_id=>"1", :@type_id=>"29"}} + {:value=>{:@internal_id=>"2", :@type_id=>"28", :name=>"some value 28"}, :@script_id=>'cust_field_28'}, + {:value=>{:@internal_id=>"1", :@type_id=>"29", :name=>"some value 29"}, :@script_id=>'cust_field_29'} ] list = described_class.new({ matrix_option: array }) - expect(list.options.first.value_id).to eq "2" + expect(list.options.count).to eq 2 + + option = list.options.first + expect(option.value_id).to eq "2" + expect(option.type_id).to eq "28" + expect(option.name).to eq "some value 28" + expect(option.script_id).to eq "cust_field_28" end end end From 659d4634e8ab2ae26d8a860ecb5fc47a803da7c7 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Wed, 10 Jul 2019 21:02:22 -0600 Subject: [PATCH 10/46] Version bump --- lib/netsuite/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/netsuite/version.rb b/lib/netsuite/version.rb index 115ab03a1..763b660d7 100644 --- a/lib/netsuite/version.rb +++ b/lib/netsuite/version.rb @@ -1,3 +1,3 @@ module NetSuite - VERSION = '0.8.4.1' + VERSION = '0.8.5' end From 5dfa2efc4a17576afcae68e2669e8341f18286db Mon Sep 17 00:00:00 2001 From: Jeremy Michael Crosbie Date: Wed, 24 Jul 2019 14:56:08 -0600 Subject: [PATCH 11/46] Add credit cards to customer --- lib/netsuite.rb | 2 ++ lib/netsuite/records/customer.rb | 3 +- lib/netsuite/records/customer_credit_cards.rb | 36 +++++++++++++++++++ .../records/customer_credit_cards_list.rb | 10 ++++++ .../customer_credit_cards_list_spec.rb | 23 ++++++++++++ spec/netsuite/records/customer_spec.rb | 23 +++++++++++- 6 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 lib/netsuite/records/customer_credit_cards.rb create mode 100644 lib/netsuite/records/customer_credit_cards_list.rb create mode 100644 spec/netsuite/records/customer_credit_cards_list_spec.rb diff --git a/lib/netsuite.rb b/lib/netsuite.rb index fad4f2d55..0e28e5711 100644 --- a/lib/netsuite.rb +++ b/lib/netsuite.rb @@ -112,6 +112,8 @@ module Records autoload :CustomerAddressbook, 'netsuite/records/customer_addressbook' autoload :CustomerAddressbookList, 'netsuite/records/customer_addressbook_list' autoload :CustomerCategory, 'netsuite/records/customer_category' + autoload :CustomerCreditCards, 'netsuite/records/customer_credit_cards' + autoload :CustomerCreditCardsList, 'netsuite/records/customer_credit_cards_list' autoload :CustomerCurrency, 'netsuite/records/customer_currency' autoload :CustomerCurrencyList, 'netsuite/records/customer_currency_list' autoload :CustomerDeposit, 'netsuite/records/customer_deposit' diff --git a/lib/netsuite/records/customer.rb b/lib/netsuite/records/customer.rb index 2155dc5bd..055088713 100644 --- a/lib/netsuite/records/customer.rb +++ b/lib/netsuite/records/customer.rb @@ -13,7 +13,7 @@ class Customer fields :account_number, :aging, :alt_email, :alt_name, :alt_phone, :bill_pay, :buying_reason, :buying_time_frame, :campaign_category, :click_stream, :comments, :company_name, - :consol_aging, :consol_days_overdue, :contrib_pct, :credit_cards_list, :credit_hold_override, + :consol_aging, :consol_days_overdue, :contrib_pct, :credit_hold_override, :credit_limit, :date_created, :days_overdue, :default_address, :download_list, :email, :email_preference, :email_transactions, :end_date, :entity_id, :estimated_budget, :fax, :fax_transactions, :first_name, :first_visit, :give_access, :global_subscription_status, @@ -28,6 +28,7 @@ class Customer :vat_reg_number, :visits, :web_lead field :addressbook_list, CustomerAddressbookList + field :credit_cards_list, CustomerCreditCardsList field :custom_field_list, CustomFieldList field :contact_roles_list, ContactAccessRolesList field :currency_list, CustomerCurrencyList diff --git a/lib/netsuite/records/customer_credit_cards.rb b/lib/netsuite/records/customer_credit_cards.rb new file mode 100644 index 000000000..caeb1b786 --- /dev/null +++ b/lib/netsuite/records/customer_credit_cards.rb @@ -0,0 +1,36 @@ +module NetSuite + module Records + class CustomerCreditCards + include Support::Fields + include Support::RecordRefs + include Support::Records + + # https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2017_1/schema/other/customercreditcards.html?mode=package + + fields :cc_default, :cc_expire_date, :cc_memo, :cc_name, :cc_number, :debitcard_issue_no, :state_from, :validfrom + record_refs :card_state, :payment_method + + attr_reader :internal_id + + def initialize(attributes_or_record = {}) + case attributes_or_record + when self.class + initialize_from_record(attributes_or_record) + when Hash + initialize_from_attributes_hash(attributes_or_record) + end + end + + def initialize_from_record(obj) + self.cc_default = obj.cc_default + self.cc_expire_date = obj.cc_expire_date + self.cc_memo = obj.cc_memo + self.cc_name = obj.cc_name + self.cc_number = obj.cc_number + self.debitcard_issue_no = obj.debitcard_issue_no + self.state_from = obj.state_from + self.validfrom = obj.validfrom + end + end + end +end diff --git a/lib/netsuite/records/customer_credit_cards_list.rb b/lib/netsuite/records/customer_credit_cards_list.rb new file mode 100644 index 000000000..b2e060dfc --- /dev/null +++ b/lib/netsuite/records/customer_credit_cards_list.rb @@ -0,0 +1,10 @@ +module NetSuite + module Records + class CustomerCreditCardsList < Support::Sublist + include Namespaces::ListRel + + sublist :credit_cards, CustomerCreditCards + alias :credit_card :credit_cards + end + end +end diff --git a/spec/netsuite/records/customer_credit_cards_list_spec.rb b/spec/netsuite/records/customer_credit_cards_list_spec.rb new file mode 100644 index 000000000..91729274f --- /dev/null +++ b/spec/netsuite/records/customer_credit_cards_list_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe NetSuite::Records::CustomerCreditCardsList do + let(:list) { NetSuite::Records::CustomerCreditCardsList.new } + + it 'has a credit_cards attribute' do + expect(list.credit_cards).to be_kind_of(Array) + end + + describe '#to_record' do + it 'can represent itself as a SOAP record' do + list.replace_all = true + + record = { + 'listRel:creditCards' => [], + 'listRel:replaceAll' => true + } + + expect(list.to_record).to eql(record) + end + end + +end diff --git a/spec/netsuite/records/customer_spec.rb b/spec/netsuite/records/customer_spec.rb index 8e1eec751..37c1add94 100644 --- a/spec/netsuite/records/customer_spec.rb +++ b/spec/netsuite/records/customer_spec.rb @@ -8,7 +8,7 @@ :account_number, :aging, :alt_email, :alt_name, :alt_phone, :balance, :bill_pay, :buying_reason, :buying_time_frame, :campaign_category, :click_stream, :comments, :company_name, :consol_aging, :consol_balance, :consol_days_overdue, :consol_deposit_balance, :consol_overdue_balance, - :consol_unbilled_orders, :contrib_pct, :credit_cards_list, :credit_hold_override, :credit_limit, + :consol_unbilled_orders, :contrib_pct, :credit_hold_override, :credit_limit, :date_created, :days_overdue, :default_address, :deposit_balance, :download_list, :email, :email_preference, :email_transactions, :end_date, :entity_id, :estimated_budget, :fax, :fax_transactions, :first_name, :first_visit, :give_access, :global_subscription_status, @@ -65,6 +65,27 @@ end end + describe '#credit_cards_list' do + it 'can be set from attributes' do + customer.credit_cards_list = { + :credit_cards => { + :internal_id => '1234567', + :cc_default => true, + :cc_expire_date => '2099-12-01T00:00:00.000-08:00' + } + } + + expect(customer.credit_cards_list).to be_kind_of(NetSuite::Records::CustomerCreditCardsList) + expect(customer.credit_cards_list.credit_cards.length).to eql(1) + end + + it 'can be set from a CustomerCreditCardsList object' do + customer_credit_cards_list = NetSuite::Records::CustomerCreditCardsList.new + customer.credit_cards_list = customer_credit_cards_list + expect(customer.credit_cards_list).to eql(customer_credit_cards_list) + end + end + describe '#custom_field_list' do it 'can be set from attributes' do attributes = { From af26673868cccc7457675798bf03f006d6ffedfc Mon Sep 17 00:00:00 2001 From: Shai Coleman Date: Wed, 4 Sep 2019 13:25:42 +0100 Subject: [PATCH 12/46] rescue Exception => rescue StandardError --- lib/netsuite/utilities.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/netsuite/utilities.rb b/lib/netsuite/utilities.rb index 72f4534c4..c0b0f2d91 100644 --- a/lib/netsuite/utilities.rb +++ b/lib/netsuite/utilities.rb @@ -78,7 +78,7 @@ def backoff(options = {}) begin count += 1 yield - rescue Exception => e + rescue StandardError => e exceptions_to_retry = [ Errno::ECONNRESET, Errno::ETIMEDOUT, From cd350c2bb858ba726a2ef6b852a240586b139aed Mon Sep 17 00:00:00 2001 From: "Kevin L. Dayton" Date: Wed, 11 Sep 2019 09:31:33 -0500 Subject: [PATCH 13/46] Add cost to ServiceResaleItem. --- lib/netsuite/records/service_resale_item.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/netsuite/records/service_resale_item.rb b/lib/netsuite/records/service_resale_item.rb index fc04cc5a6..d5ee833b5 100644 --- a/lib/netsuite/records/service_resale_item.rb +++ b/lib/netsuite/records/service_resale_item.rb @@ -9,7 +9,7 @@ class ServiceResaleItem actions :get, :get_list, :add, :update, :delete, :upsert, :search - fields :available_to_partners, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :create_job, :created_date, + fields :available_to_partners, :cost, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :create_job, :created_date, :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, :featured_description, :include_children, :is_donation_item, :is_fulfillable, :is_gco_compliant, :is_inactive, :is_online, :is_taxable, :item_id, :last_modified_date, :matrix_option_list, :matrix_type, :max_donation_amount, :meta_tag_html, From e48f808301d52751e22672c8366a397c048c8ade Mon Sep 17 00:00:00 2001 From: "Kevin L. Dayton" Date: Wed, 11 Sep 2019 09:31:58 -0500 Subject: [PATCH 14/46] Add spec for ServiceResaleItem. --- .../records/service_resale_item_spec.rb | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 spec/netsuite/records/service_resale_item_spec.rb diff --git a/spec/netsuite/records/service_resale_item_spec.rb b/spec/netsuite/records/service_resale_item_spec.rb new file mode 100644 index 000000000..b59f99176 --- /dev/null +++ b/spec/netsuite/records/service_resale_item_spec.rb @@ -0,0 +1,134 @@ +require 'spec_helper' + +describe NetSuite::Records::ServiceResaleItem do + let(:item) { NetSuite::Records::ServiceResaleItem.new } + + it 'has the right fields' do + [ + :available_to_partners, :cost, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :create_job, :created_date, + :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, :featured_description, + :include_children, :is_donation_item, :is_fulfillable, :is_gco_compliant, :is_inactive, :is_online, :is_taxable, + :item_id, :last_modified_date, :matrix_option_list, :matrix_type, :max_donation_amount, :meta_tag_html, + :minimum_quantity, :minimum_quantity_units, :no_price_message, :offer_support, :on_special, :out_of_stock_behavior, + :out_of_stock_message, :overall_quantity_pricing_type, :page_title, :presentation_item_list, :prices_include_tax, + :show_default_donation_amount, :site_category_list, :sitemap_priority, :soft_descriptor, :specials_description, + :store_description, :store_detailed_description, :store_display_name, :translations_list, :upc_code, :url_component, + :use_marginal_rates, :vsoe_deferral, :vsoe_delivered, :vsoe_permit_discount, :vsoe_price, :vsoe_sop_group + ].each do |field| + expect(item).to have_field(field) + end + + # TODO there is a probably a more robust way to test this + expect(item.custom_field_list.class).to eq(NetSuite::Records::CustomFieldList) + expect(item.pricing_matrix.class).to eq(NetSuite::Records::PricingMatrix) + expect(item.subsidiary_list.class).to eq(NetSuite::Records::RecordRefList) + end + + it 'has the right record_refs' do + [ + :billing_schedule, :cost_category, :custom_form, :deferred_revenue_account, :department, :income_account, + :issue_product, :item_options_list, :klass, :location, :parent, :pricing_group, :purchase_tax_code, + :quantity_pricing_schedule, :rev_rec_schedule, :sale_unit, :sales_tax_code, :store_display_image, + :store_display_thumbnail, :store_item_template, :tax_schedule, :units_type + ].each do |record_ref| + expect(item).to have_record_ref(record_ref) + end + end + + describe '.get' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :item_id => 'penguins' }) } + + it 'returns a ServiceResaleItem instance populated with the data from the response object' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::ServiceResaleItem, :external_id => 20], {}).and_return(response) + customer = NetSuite::Records::ServiceResaleItem.get(:external_id => 20) + expect(customer).to be_kind_of(NetSuite::Records::ServiceResaleItem) + expect(customer.item_id).to eql('penguins') + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'raises a RecordNotFound exception' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::ServiceResaleItem, :external_id => 20], {}).and_return(response) + expect { + NetSuite::Records::ServiceResaleItem.get(:external_id => 20) + }.to raise_error(NetSuite::RecordNotFound, + /NetSuite::Records::ServiceResaleItem with OPTIONS=(.*) could not be found/) + end + end + end + + describe '#add' do + let(:item) { NetSuite::Records::ServiceResaleItem.new(:cost => 100, :is_inactive => false) } + + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + expect(NetSuite::Actions::Add).to receive(:call). + with([item], {}). + and_return(response) + expect(item.add).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + expect(NetSuite::Actions::Add).to receive(:call). + with([item], {}). + and_return(response) + expect(item.add).to be_falsey + end + end + end + + describe '#delete' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + expect(NetSuite::Actions::Delete).to receive(:call). + with([item], {}). + and_return(response) + expect(item.delete).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + expect(NetSuite::Actions::Delete).to receive(:call). + with([item], {}). + and_return(response) + expect(item.delete).to be_falsey + end + end + end + + describe '#to_record' do + before do + item.item_id = 'penguins' + item.is_online = true + end + + it 'can represent itself as a SOAP record' do + record = { + 'listAcct:itemId' => 'penguins', + 'listAcct:isOnline' => true + } + expect(item.to_record).to eql(record) + end + end + + describe '#record_type' do + it 'returns a string of the SOAP type' do + expect(item.record_type).to eql('listAcct:ServiceResaleItem') + end + end + +end From ec46a26c38fd3271e3841a75639ddb1491259410 Mon Sep 17 00:00:00 2001 From: Ben Ricker Date: Mon, 9 Sep 2019 12:42:53 -0400 Subject: [PATCH 15/46] Update data center url method name in README This was pluralized in the README, but it looks like it won't work with the s in there. This updates the README to show the working method name correct method name, remove 'netsuite' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbddf116e..c3b8afd92 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ NetSuite.configure do password 'password' role 10 - # use `NetSuite::Utilities.netsuite_data_center_urls('TSTDRV1576318')` to retrieve the URL + # use `NetSuite::Utilities.data_center_url('TSTDRV1576318')` to retrieve the URL # you'll want to do this in a background proces and strip the protocol out of the return string wsdl_domain 'tstdrv1576318.suitetalk.api.netsuite.com' end From 35f393a3c7cca5f7d07c63aed86791514214199d Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Thu, 24 Oct 2019 20:07:57 -0400 Subject: [PATCH 16/46] Support passing streams as log option --- lib/netsuite/configuration.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/netsuite/configuration.rb b/lib/netsuite/configuration.rb index 20acf88df..e130d52bd 100644 --- a/lib/netsuite/configuration.rb +++ b/lib/netsuite/configuration.rb @@ -350,7 +350,10 @@ def log(path = nil) def logger(value = nil) if value.nil? - attributes[:logger] ||= ::Logger.new((log && !log.empty?) ? log : $stdout) + # if passed a IO object (like StringIO) `empty?` won't exist + valid_log = log && !(log.respond_to?(:empty?) && log.empty?) + + attributes[:logger] ||= ::Logger.new(valid_log ? log : $stdout) else attributes[:logger] = value end From a5ba9213482afd60d4a31c2545707a8275a98cd1 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Fri, 25 Oct 2019 10:46:04 -0400 Subject: [PATCH 17/46] Bumping ruby version --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index 276cbf9e2..ec1cf33c3 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.0 +2.6.3 From cf9f5c239419b565df6d0b38a2aba139702c7599 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Fri, 25 Oct 2019 10:46:25 -0400 Subject: [PATCH 18/46] Adding spec for using IO stream as a log destination --- spec/netsuite/configuration_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/netsuite/configuration_spec.rb b/spec/netsuite/configuration_spec.rb index 53cd6b753..d38350bb8 100644 --- a/spec/netsuite/configuration_spec.rb +++ b/spec/netsuite/configuration_spec.rb @@ -371,6 +371,25 @@ end end + describe "#log" do + it 'allows a file path to be set as the log destination' do + file_path = Tempfile.new.path + config.log = file_path + config.logger.info "foo" + + log_contents = open(file_path).read + expect(log_contents).to include("foo") + end + + it 'allows an IO device to bet set as the log destination' do + stream = StringIO.new + config.log = stream + config.logger.info "foo" + + expect(stream.string).to include("foo") + end + end + describe 'timeouts' do it 'has defaults' do expect(config.read_timeout).to eql(60) From b404f1895fc047fc0f9226ad0bfbbc276c93f4f2 Mon Sep 17 00:00:00 2001 From: Jordan Moncharmont Date: Tue, 19 Nov 2019 18:42:12 -0800 Subject: [PATCH 19/46] Add the status field to ItemFulfillments --- lib/netsuite/records/item_fulfillment.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/netsuite/records/item_fulfillment.rb b/lib/netsuite/records/item_fulfillment.rb index fd5e691d3..aa37b4546 100644 --- a/lib/netsuite/records/item_fulfillment.rb +++ b/lib/netsuite/records/item_fulfillment.rb @@ -11,7 +11,7 @@ class ItemFulfillment fields :tran_date, :tran_id, :shipping_cost, :memo, :ship_company, :ship_attention, :ship_addr1, :ship_addr2, :ship_city, :ship_state, :ship_zip, :ship_phone, :ship_is_residential, - :ship_status, :last_modified_date, :created_date + :ship_status, :last_modified_date, :created_date, :status read_only_fields :handling_cost From de0286b4a16974033a5b1ae8e7e80f08475edf86 Mon Sep 17 00:00:00 2001 From: toyhammered Date: Tue, 3 Mar 2020 19:00:43 -0800 Subject: [PATCH 20/46] update classification to include parent record_ref This was done to keep Classification consistent with Location and Department segments --- lib/netsuite/records/classification.rb | 2 ++ spec/netsuite/records/classification_spec.rb | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/netsuite/records/classification.rb b/lib/netsuite/records/classification.rb index f74fd0b20..b3cda11d9 100644 --- a/lib/netsuite/records/classification.rb +++ b/lib/netsuite/records/classification.rb @@ -16,6 +16,8 @@ class Classification attr_reader :internal_id attr_accessor :external_id + record_refs :parent + def initialize(attributes = {}) @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id) @external_id = attributes.delete(:external_id) || attributes.delete(:@external_id) diff --git a/spec/netsuite/records/classification_spec.rb b/spec/netsuite/records/classification_spec.rb index 8ee89f301..a0b788ae8 100644 --- a/spec/netsuite/records/classification_spec.rb +++ b/spec/netsuite/records/classification_spec.rb @@ -13,6 +13,14 @@ expect(classification.subsidiary_list.class).to eq(NetSuite::Records::RecordRefList) end + it 'has all the right record refs' do + [ + :parent + ].each do |record_ref| + expect(classification).to have_record_ref(record_ref) + end + end + describe '.get' do context 'when the response is successful' do let(:response) { NetSuite::Response.new(:success => true, :body => { :name => 'Retail' }) } From 1292ddce014df1ec2311d87fce49461c4db4b43f Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Wed, 4 Mar 2020 08:53:04 -0700 Subject: [PATCH 21/46] Fixing classification parent and custom field field definition --- lib/netsuite/records/classification.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/netsuite/records/classification.rb b/lib/netsuite/records/classification.rb index b3cda11d9..745cc2e0b 100644 --- a/lib/netsuite/records/classification.rb +++ b/lib/netsuite/records/classification.rb @@ -9,15 +9,16 @@ class Classification actions :add, :get, :get_list, :delete, :upsert, :search - fields :name, :include_children, :is_inactive, :class_translation_list, :custom_field_list, :parent + fields :name, :include_children, :is_inactive, :class_translation_list field :subsidiary_list, RecordRefList + field :custom_field_list, CustomFieldList + + record_refs :parent attr_reader :internal_id attr_accessor :external_id - record_refs :parent - def initialize(attributes = {}) @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id) @external_id = attributes.delete(:external_id) || attributes.delete(:@external_id) From a2b0376cd14f95e1d447b5aae3e3aeb90e0f0082 Mon Sep 17 00:00:00 2001 From: toyhammered Date: Thu, 5 Mar 2020 19:12:50 -0800 Subject: [PATCH 22/46] Fixes Issues #450 add parent to custom record --- lib/netsuite/records/custom_record.rb | 2 +- spec/netsuite/records/custom_record_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/netsuite/records/custom_record.rb b/lib/netsuite/records/custom_record.rb index d12fa0915..60fd4f1de 100644 --- a/lib/netsuite/records/custom_record.rb +++ b/lib/netsuite/records/custom_record.rb @@ -18,7 +18,7 @@ class CustomRecord field :custom_field_list, CustomFieldList - record_refs :custom_form, :owner, :rec_type + record_refs :custom_form, :owner, :rec_type, :parent attr_reader :internal_id attr_accessor :external_id diff --git a/spec/netsuite/records/custom_record_spec.rb b/spec/netsuite/records/custom_record_spec.rb index bfaf6732b..a3b8994ef 100644 --- a/spec/netsuite/records/custom_record_spec.rb +++ b/spec/netsuite/records/custom_record_spec.rb @@ -18,7 +18,7 @@ it 'has all the right record_refs' do [ - :custom_form, :owner + :custom_form, :owner, :rec_type, :parent ].each do |record_ref| expect(record).to have_record_ref(record_ref) end From 4d6ac2f5b4e2dd5b30b2c02bf71192a9374bff5b Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Thu, 5 Mar 2020 20:27:19 -0700 Subject: [PATCH 23/46] Fixing classification test --- spec/netsuite/records/classification_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/netsuite/records/classification_spec.rb b/spec/netsuite/records/classification_spec.rb index a0b788ae8..f193d3480 100644 --- a/spec/netsuite/records/classification_spec.rb +++ b/spec/netsuite/records/classification_spec.rb @@ -5,12 +5,13 @@ it 'has all the right fields' do [ - :name, :include_children, :is_inactive, :class_translation_list, :custom_field_list + :name, :include_children, :is_inactive, :class_translation_list ].each do |field| expect(classification).to have_field(field) end expect(classification.subsidiary_list.class).to eq(NetSuite::Records::RecordRefList) + expect(classification.custom_field_list.class).to eq(NetSuite::Records::CustomFieldList) end it 'has all the right record refs' do From 1585ca880b8b00117a09ab11994864a6f1c5f446 Mon Sep 17 00:00:00 2001 From: Neil Kulbiski Date: Fri, 24 Apr 2020 22:27:08 -0500 Subject: [PATCH 24/46] add item vendor list to non inventory resale item --- .../records/non_inventory_resale_item.rb | 1 + .../records/non_inventory_resale_item_spec.rb | 165 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 spec/netsuite/records/non_inventory_resale_item_spec.rb diff --git a/lib/netsuite/records/non_inventory_resale_item.rb b/lib/netsuite/records/non_inventory_resale_item.rb index 0628c8c6a..832dda4f0 100644 --- a/lib/netsuite/records/non_inventory_resale_item.rb +++ b/lib/netsuite/records/non_inventory_resale_item.rb @@ -33,6 +33,7 @@ class NonInventoryResaleItem field :custom_field_list, CustomFieldList field :pricing_matrix, PricingMatrix field :subsidiary_list, RecordRefList + field :item_vendor_list, ItemVendorList attr_reader :internal_id diff --git a/spec/netsuite/records/non_inventory_resale_item_spec.rb b/spec/netsuite/records/non_inventory_resale_item_spec.rb new file mode 100644 index 000000000..2edd5d143 --- /dev/null +++ b/spec/netsuite/records/non_inventory_resale_item_spec.rb @@ -0,0 +1,165 @@ +require 'spec_helper' + +describe NetSuite::Records::NonInventoryResaleItem do + let(:item) { NetSuite::Records::NonInventoryResaleItem.new } + + it 'has the right fields' do + [ + :available_to_partners, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :country_of_manufacture, :created_date, + :display_name, :dont_show_price, :enforce_min_qty_internally, :exclude_from_sitemap, + :featured_description, :handling_cost, :handling_cost_units, :include_children, :is_donation_item, :is_fulfillable, + :is_gco_compliant, :is_inactive, :is_online, :is_taxable, :item_id, :last_modified_date, :manufacturer, :manufacturer_addr1, + :manufacturer_city, :manufacturer_state, :manufacturer_tariff, :manufacturer_tax_id, :manufacturer_zip, :matrix_option_list, + :matrix_type, :max_donation_amount, :meta_tag_html, :minimum_quantity, :minimum_quantity_units, :mpn, + :mult_manufacture_addr, :nex_tag_category, :no_price_message, :offer_support, :on_special, :out_of_stock_behavior, + :out_of_stock_message, :overall_quantity_pricing_type, :page_title, :preference_criterion, :presentation_item_list, + :prices_include_tax, :producer, :product_feed_list, :rate, :related_items_description, :sales_description, + :schedule_b_code, :schedule_b_number, :schedule_b_quantity, :search_keywords, :ship_individually, :shipping_cost, + :shipping_cost_units, :shopping_dot_com_category, :shopzilla_category_id, :show_default_donation_amount, + :site_category_list, :sitemap_priority, :soft_descriptor, :specials_description, :stock_description, :store_description, + :store_detailed_description, :store_display_name, :translations_list, :upc_code, :url_component, :use_marginal_rates, + :vsoe_deferral, :vsoe_delivered, :vsoe_permit_discount, :vsoe_price, :weight, :weight_unit, :weight_units + ].each do |field| + expect(item).to have_field(field) + end + + # TODO there is a probably a more robust way to test this + expect(item.custom_field_list.class).to eq(NetSuite::Records::CustomFieldList) + expect(item.pricing_matrix.class).to eq(NetSuite::Records::PricingMatrix) + expect(item.subsidiary_list.class).to eq(NetSuite::Records::RecordRefList) + expect(item.item_vendor_list.class).to eq(NetSuite::Records::ItemVendorList) + + end + + it 'has the right record_refs' do + [ + :billing_schedule, :cost_category, :custom_form, :deferred_revenue_account, :department, :income_account, + :issue_product, :item_options_list, :klass, :location, :parent, :pricing_group, :purchase_tax_code, + :quantity_pricing_schedule, :rev_rec_schedule, :sale_unit, :sales_tax_code, :ship_package, :store_display_image, + :store_display_thumbnail, :store_item_template, :tax_schedule, :units_type, :expense_account + ].each do |record_ref| + expect(item).to have_record_ref(record_ref) + end + end + + describe '.get' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :manufacturer_zip => '90401' }) } + + it 'returns a NonInventoryResaleItem instance populated with the data from the response object' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::NonInventoryResaleItem, {:external_id => 20}], {}).and_return(response) + customer = NetSuite::Records::NonInventoryResaleItem.get(:external_id => 20) + expect(customer).to be_kind_of(NetSuite::Records::NonInventoryResaleItem) + expect(customer.manufacturer_zip).to eql('90401') + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'raises a RecordNotFound exception' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::NonInventoryResaleItem, {:external_id => 20}], {}).and_return(response) + expect { + NetSuite::Records::NonInventoryResaleItem.get(:external_id => 20) + }.to raise_error(NetSuite::RecordNotFound, + /NetSuite::Records::NonInventoryResaleItem with OPTIONS=(.*) could not be found/) + end + end + end + + describe '#add' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + expect(NetSuite::Actions::Add).to receive(:call). + with([item], {}). + and_return(response) + expect(item.add).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + expect(NetSuite::Actions::Add).to receive(:call). + with([item], {}). + and_return(response) + expect(item.add).to be_falsey + end + end + end + + describe '#update' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + expect(NetSuite::Actions::Update).to receive(:call). + with([item.class, {external_id: 'foo'}], {}). + and_return(response) + item.external_id = 'foo' + expect(item.update).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + expect(NetSuite::Actions::Update).to receive(:call). + with([item.class, {external_id: 'foo'}], {}). + and_return(response) + item.external_id = 'foo' + expect(item.update).to be_falsey + end + end + end + + describe '#delete' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + expect(NetSuite::Actions::Delete).to receive(:call). + with([item], {}). + and_return(response) + expect(item.delete).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + expect(NetSuite::Actions::Delete).to receive(:call). + with([item], {}). + and_return(response) + expect(item.delete).to be_falsey + end + end + end + + describe '#to_record' do + before do + item.handling_cost = 100.0 + item.is_online = true + end + + it 'can represent itself as a SOAP record' do + record = { + 'listAcct:handlingCost' => 100.0, + 'listAcct:isOnline' => true + } + expect(item.to_record).to eql(record) + end + end + + describe '#record_type' do + it 'returns a string of the SOAP type' do + expect(item.record_type).to eql('listAcct:NonInventoryResaleItem') + end + end + +end From f13ef9c2273e06f1ed27291091270c84c3faa03b Mon Sep 17 00:00:00 2001 From: Jordan Moncharmont Date: Thu, 23 Apr 2020 10:51:32 -0700 Subject: [PATCH 25/46] Add amount as a read_only_field to Invoice The Invoice object has an amount property in netsuite that can be returned in transaction searches. It should be available here too. --- lib/netsuite/records/invoice.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/netsuite/records/invoice.rb b/lib/netsuite/records/invoice.rb index 99ea37250..60f25aba5 100644 --- a/lib/netsuite/records/invoice.rb +++ b/lib/netsuite/records/invoice.rb @@ -39,7 +39,7 @@ class Invoice field :shipping_address, Address field :billing_address, Address - read_only_fields :sub_total, :discount_total, :total, :recognized_revenue, :amount_remaining, :amount_paid, + read_only_fields :sub_total, :discount_total, :total, :recognized_revenue, :amount_remaining, :amount_paid, :amount, :alt_shipping_cost, :gift_cert_applied, :handling_cost, :alt_handling_cost record_refs :account, :bill_address_list, :custom_form, :department, :entity, :klass, :partner, From f900e5d2f252ce019e7748e46c538bc6fe4312d5 Mon Sep 17 00:00:00 2001 From: Justin Catalana Date: Sun, 10 May 2020 11:10:26 -0700 Subject: [PATCH 26/46] Adding search join to CustomerPayment --- lib/netsuite/records/customer_payment.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/netsuite/records/customer_payment.rb b/lib/netsuite/records/customer_payment.rb index 9baab81fc..e6a4bb141 100644 --- a/lib/netsuite/records/customer_payment.rb +++ b/lib/netsuite/records/customer_payment.rb @@ -25,6 +25,7 @@ class CustomerPayment attr_reader :internal_id attr_accessor :external_id + attr_accessor :search_joins def initialize(attributes = {}) @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id) From 653532f0269b2355776764ae3807b277859c83bf Mon Sep 17 00:00:00 2001 From: Daniel Levenson Date: Thu, 2 Jul 2020 14:44:42 -0400 Subject: [PATCH 27/46] Typo: proces -> process in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3b8afd92..5bf4e941f 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ NetSuite.configure do role 10 # use `NetSuite::Utilities.data_center_url('TSTDRV1576318')` to retrieve the URL - # you'll want to do this in a background proces and strip the protocol out of the return string + # you'll want to do this in a background process and strip the protocol out of the return string wsdl_domain 'tstdrv1576318.suitetalk.api.netsuite.com' end ``` From 73a24dc8a981daa92e9b25e44e4fc8fe96d384d0 Mon Sep 17 00:00:00 2001 From: Daniel Levenson Date: Mon, 3 Aug 2020 18:45:02 -0400 Subject: [PATCH 28/46] Fix deprecation warnings in custom_field_list_spec.rb * Specify the `NoMethodError` to keep RSpec happy and to ensure that other bugs don't sneak by as a passing test * Upgrade single instance of `should` syntax to `expect` --- spec/netsuite/records/custom_field_list_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/netsuite/records/custom_field_list_spec.rb b/spec/netsuite/records/custom_field_list_spec.rb index ea8316c77..f483e30f8 100644 --- a/spec/netsuite/records/custom_field_list_spec.rb +++ b/spec/netsuite/records/custom_field_list_spec.rb @@ -37,9 +37,11 @@ context 'writing convience methods' do it "should create a custom field entry when none exists" do list.custrecord_somefield = 'a value' - list.custom_fields.size.should == 1 - list.custom_fields.first.value.should == 'a value' - list.custom_fields.first.type.should == 'platformCore:StringCustomFieldRef' + custom_fields = list.custom_fields + + expect(custom_fields.size).to eq(1) + expect(custom_fields.first.value).to eq('a value') + expect(custom_fields.first.type).to eq('platformCore:StringCustomFieldRef') end # https://github.com/NetSweet/netsuite/issues/325 @@ -116,7 +118,7 @@ end it "should raise an error if custom field entry does not exist" do - expect{ list.nonexisting_custom_field }.to raise_error + expect{ list.nonexisting_custom_field }.to raise_error(NoMethodError) end end end From a1dfef78b600e20efbf93e0ee868630f633e3c41 Mon Sep 17 00:00:00 2001 From: Daniel Levenson Date: Mon, 3 Aug 2020 19:17:37 -0400 Subject: [PATCH 29/46] Backfill tests for Configuration#log_level & #log_level= --- spec/netsuite/configuration_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/netsuite/configuration_spec.rb b/spec/netsuite/configuration_spec.rb index d38350bb8..0484d9a7c 100644 --- a/spec/netsuite/configuration_spec.rb +++ b/spec/netsuite/configuration_spec.rb @@ -390,6 +390,26 @@ end end + describe '#log_level' do + it 'defaults to :debug' do + expect(config.log_level).to eq(:debug) + end + + it 'can be initially set to any log level' do + config.log_level(:info) + + expect(config.log_level).to eq(:info) + end + end + + describe '#log_level=' do + it 'can set the initial log_level' do + config.log_level = :info + + expect(config.log_level).to eq(:info) + end + end + describe 'timeouts' do it 'has defaults' do expect(config.read_timeout).to eql(60) From 49bdd032fd539ac9daa381c056e5b276155b676d Mon Sep 17 00:00:00 2001 From: Daniel Levenson Date: Mon, 3 Aug 2020 19:26:56 -0400 Subject: [PATCH 30/46] Add failing tests: can not override log level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s surprising to not be able to, in a rails console, increase the verbosity of the gem’s logging without needing to: 1. Tweak an ENV variable or otherwise change an initializer 2. Directly set `Configuration#attributes` Aka, I would expect the following to work, but it does not. ``` app:staging> NetSuite::Configuration.log_level => :info app:staging> NetSuite::Configuration.log_level = :debug => :debug app:staging> NetSuite::Configuration.log_level => :info ``` Seems like the only way to increase the verbosity is to directly set the appropriate attribute: ``` gravity:staging> NetSuite::Configuration.attributes[:log_level] => :info gravity:staging> NetSuite::Configuration.attributes[:log_level] = :debug => :debug gravity:staging> NetSuite::Configuration.log_level => :debug ``` Moreover, this seems inconsistent with other configuration, for example `Configuration#logger` and `#logger=` --- spec/netsuite/configuration_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/netsuite/configuration_spec.rb b/spec/netsuite/configuration_spec.rb index 0484d9a7c..2a683e214 100644 --- a/spec/netsuite/configuration_spec.rb +++ b/spec/netsuite/configuration_spec.rb @@ -400,6 +400,16 @@ expect(config.log_level).to eq(:info) end + + it 'can override itself' do + config.log_level = :info + + expect(config.log_level).to eq(:info) + + config.log_level(:debug) + + expect(config.log_level).to eq(:debug) + end end describe '#log_level=' do @@ -408,6 +418,16 @@ expect(config.log_level).to eq(:info) end + + it 'can override a previously set log level' do + config.log_level = :info + + expect(config.log_level).to eq(:info) + + config.log_level = :debug + + expect(config.log_level).to eq(:debug) + end end describe 'timeouts' do From 0b014312f7138ef7679b3b2d1f60c2463160df74 Mon Sep 17 00:00:00 2001 From: Daniel Levenson Date: Mon, 3 Aug 2020 19:45:34 -0400 Subject: [PATCH 31/46] Configuration: Allow for log_level overriding * See 8fc2f2bea46c0d2390124a67b056d586a5f03cff for motivation * Always set `attributes[:log_level]` when `log_level=` is called * Support `log_level(value)` syntax via a short-circuit, similar to what's done in `#log(path)` and `#silent(value)` --- lib/netsuite/configuration.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/netsuite/configuration.rb b/lib/netsuite/configuration.rb index e130d52bd..89ebb4d66 100644 --- a/lib/netsuite/configuration.rb +++ b/lib/netsuite/configuration.rb @@ -373,12 +373,13 @@ def silent=(value) end def log_level(value = nil) - self.log_level = value || :debug - attributes[:log_level] + self.log_level = value if value + + attributes[:log_level] || :debug end def log_level=(value) - attributes[:log_level] ||= value + attributes[:log_level] = value end end end From 0d0ba9fc968eaaa8b06722596a8cddaa34a947f0 Mon Sep 17 00:00:00 2001 From: Daniel Levenson Date: Mon, 3 Aug 2020 19:52:59 -0400 Subject: [PATCH 32/46] Explicitly and consistently mark pending specs in configuration_spec.rb --- spec/netsuite/configuration_spec.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/spec/netsuite/configuration_spec.rb b/spec/netsuite/configuration_spec.rb index 2a683e214..7b48a0c6f 100644 --- a/spec/netsuite/configuration_spec.rb +++ b/spec/netsuite/configuration_spec.rb @@ -326,15 +326,11 @@ describe "#credentials" do context "when none are defined" do - skip "should properly create the auth credentials" do - - end + skip "should properly create the auth credentials" end context "when they are defined" do - it "should properly replace the default auth credentials" do - - end + skip "should properly replace the default auth credentials" end end From abdc7de390e74f4354987905862baf05d861dc7e Mon Sep 17 00:00:00 2001 From: Daniel Levenson Date: Mon, 3 Aug 2020 20:04:56 -0400 Subject: [PATCH 33/46] Docs: Document log_level in README and provide suggestion for Rails apps. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5bf4e941f..672fb4241 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,14 @@ NetSuite.configure do read_timeout 100_000 # you can specify a file or file descriptor to send the log output to (defaults to STDOUT) + # If using within a Rails app, consider setting to `Rails.logger` to leverage existing + # application-level log configuration log File.join(Rails.root, 'log/netsuite.log') + # Defaults to :debug level logging for Savon API calls. Decrease the verbosity + # by setting log_level to `:info`, for example + # log_level :debug + # password-based login information email 'email@domain.com' password 'password' From 67ad6bc4308c681752a1d9d82bb286a0be9e73ad Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Mon, 3 Aug 2020 19:28:36 -0600 Subject: [PATCH 34/46] Bumping CI build dependencies to remove vulnerability warnings --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index ac23ef3eb..a60184ce0 100644 --- a/Gemfile +++ b/Gemfile @@ -11,5 +11,5 @@ gem 'tzinfo', '1.2.5' # gem 'tzinfo', '2.0.0' # required for CircleCI to build properly with ruby 1.9.3 -gem 'json', '~> 1.8.3' -gem 'rack', '~> 1.6.4' +gem 'json', '~> 2.3.0' +gem 'rack', '~> 2.1.4' From 48a8fb3c434f338cc683714f564045153da7b2f0 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Mon, 3 Aug 2020 19:35:35 -0600 Subject: [PATCH 35/46] circle ci v2 --- circle.yml | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/circle.yml b/circle.yml index c1f128423..8ea66105e 100644 --- a/circle.yml +++ b/circle.yml @@ -1,17 +1,29 @@ -# https://leonid.shevtsov.me/post/multiple-rubies-on-circleci/ +version: 2.1 -machine: - environment: - RUBY_VERSIONS: 2.0.0,2.1.10,2.2.9,2.3.7,2.4.4,2.5.1,2.6.1 +orbs: + ruby: circleci/ruby@1.1 -dependencies: - override: - - gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB - - rvm get head - - rvm install $RUBY_VERSIONS - - rvm $RUBY_VERSIONS --verbose do gem install bundler -v 1.17.3 - - rvm $RUBY_VERSIONS --verbose do bundle install +jobs: + build: + docker: + - image: cimg/ruby:2.7 + steps: + - checkout + test: + docker: + - image: cimg/ruby:2.7 + steps: + - checkout + - ruby/install-deps: + bundler-version: '1.17.2' + with-cache: false + - ruby/rspec-test -test: - override: - - rvm $RUBY_VERSIONS --verbose do bundle exec rspec spec +workflows: + version: 2 + build_and_test: + jobs: + - build + - test: + requires: + - build \ No newline at end of file From 2e0bbc0e9c317174fc39c2c0a82570d8b7d23eaa Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Mon, 3 Aug 2020 20:41:11 -0600 Subject: [PATCH 36/46] Adding rspec_junit_formatter for circleci ruby orb --- Gemfile | 1 + circle.yml | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index a60184ce0..ba0ce65e2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' gemspec gem 'simplecov', :require => false +gem 'rspec_junit_formatter' gem 'pry-nav' gem 'pry-rescue' diff --git a/circle.yml b/circle.yml index 8ea66105e..664e435a9 100644 --- a/circle.yml +++ b/circle.yml @@ -1,17 +1,19 @@ version: 2.1 orbs: + # orbs are basically bundles of pre-written build scripts that work for common cases + # https://github.com/CircleCI-Public/ruby-orb ruby: circleci/ruby@1.1 jobs: - build: - docker: - - image: cimg/ruby:2.7 - steps: - - checkout + # skipping build step because Gemfile.lock is not included in the source + # this makes the bundler caching step a noop test: + parameters: + ruby-version: + type: string docker: - - image: cimg/ruby:2.7 + - image: cimg/ruby:<< parameters.ruby-version >> steps: - checkout - ruby/install-deps: @@ -19,11 +21,16 @@ jobs: with-cache: false - ruby/rspec-test +# strangely, there seems to be very little documentation about exactly how martix builds work. +# By defining a param inside your job definition, Circle CI will automatically spawn a job for +# unique param value passed via `matrix`. Neat! +# https://circleci.com/blog/circleci-matrix-jobs/ workflows: - version: 2 build_and_test: jobs: - - build - test: - requires: - - build \ No newline at end of file + matrix: + parameters: + # https://github.com/CircleCI-Public/cimg-ruby + # only supports the last three ruby versions + ruby-version: ["2.5", "2.6", "2.7"] \ No newline at end of file From d65aa395f0d2e57bc32ead23851988ec4a1dcf09 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Mon, 3 Aug 2020 22:03:41 -0600 Subject: [PATCH 37/46] Bump ruby version --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index ec1cf33c3..338a5b5d8 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.3 +2.6.6 From cc6c3187968cb86d1861b2e76e45b340b6048e58 Mon Sep 17 00:00:00 2001 From: Neil Kulbiski Date: Fri, 14 Aug 2020 13:41:00 -0500 Subject: [PATCH 38/46] add subsidiary to partner record --- lib/netsuite/records/partner.rb | 2 +- spec/netsuite/records/partner_spec.rb | 141 ++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 spec/netsuite/records/partner_spec.rb diff --git a/lib/netsuite/records/partner.rb b/lib/netsuite/records/partner.rb index 04d0b93eb..58232e3a9 100644 --- a/lib/netsuite/records/partner.rb +++ b/lib/netsuite/records/partner.rb @@ -16,7 +16,7 @@ class Partner :partner_code, :is_person, :company_name, :eligible_for_commission, :entity_id, :last_modified_date, :date_created, :title, :mobile_phone, :comments, :middle_name, :send_email, :password, :password2 - record_refs :klass, :access_role, :department + record_refs :klass, :access_role, :department, :subsidiary attr_reader :internal_id attr_accessor :external_id diff --git a/spec/netsuite/records/partner_spec.rb b/spec/netsuite/records/partner_spec.rb new file mode 100644 index 000000000..197a2ef54 --- /dev/null +++ b/spec/netsuite/records/partner_spec.rb @@ -0,0 +1,141 @@ +require 'spec_helper' + +describe NetSuite::Records::Partner do + let(:partner) { NetSuite::Records::Partner.new } + + it 'has all the right fields' do + [ + :phone, :home_phone, :first_name, :last_name, :alt_name, :is_inactive, :email, :give_access, + :partner_code, :is_person, :company_name, :eligible_for_commission, :entity_id, :last_modified_date, + :date_created, :title, :mobile_phone, :comments, :middle_name, :send_email, :password, :password2 + ].each do |field| + expect(partner).to have_field(field) + end + end + + it 'has all the right record refs' do + [ + :klass, :access_role, :department, :subsidiary + ].each do |record_ref| + expect(partner).to have_record_ref(record_ref) + end + end + + describe '.get' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :account_number => 7 }) } + + it 'returns an Partner instance populated with the data from the response object' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::Partner, { :external_id => 1 }], {}).and_return(response) + Partner = NetSuite::Records::Partner.get(:external_id => 1) + expect(Partner).to be_kind_of(NetSuite::Records::Partner) + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'raises a RecordNotFound exception' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::Partner, { :external_id => 1 }], {}).and_return(response) + expect { + NetSuite::Records::Partner.get(:external_id => 1) + }.to raise_error(NetSuite::RecordNotFound, + /NetSuite::Records::Partner with OPTIONS=(.*) could not be found/) + end + end + end + + describe '#add' do + let(:partner) { NetSuite::Records::Partner.new(:email => 'dale.cooper@example.com') } + + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + expect(NetSuite::Actions::Add).to receive(:call). + with([partner], {}). + and_return(response) + expect(partner.add).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + expect(NetSuite::Actions::Add).to receive(:call). + with([partner], {}). + and_return(response) + expect(partner.add).to be_falsey + end + end + end + + describe '#delete' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + expect(NetSuite::Actions::Delete).to receive(:call). + with([partner], {}). + and_return(response) + expect(partner.delete).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + expect(NetSuite::Actions::Delete).to receive(:call). + with([partner], {}). + and_return(response) + expect(partner.delete).to be_falsey + end + end + end + + describe '.update' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :email => 'leland.palmer@example.com' }) } + + it 'returns true' do + expect(NetSuite::Actions::Update).to receive(:call).with([NetSuite::Records::Partner, { :internal_id => 1, :email => 'leland.palmer@example.com' }], {}).and_return(response) + partner = NetSuite::Records::Partner.new(:internal_id => 1) + expect(partner.update(:email => 'leland.palmer@example.com')).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'raises a RecordNotFound exception' do + expect(NetSuite::Actions::Update).to receive(:call).with([NetSuite::Records::Partner, { :internal_id => 1, :account_number => 7 }], {}).and_return(response) + partner = NetSuite::Records::Partner.new(:internal_id => 1) + expect(partner.update(:account_number => 7)).to be_falsey + end + end + end + + describe '#to_record' do + let(:partner) { NetSuite::Records::Partner.new(:email => 'bob@example.com') } + + it 'returns a hash of attributes that can be used in a SOAP request' do + expect(partner.to_record).to eql({ 'listRel:email' => 'bob@example.com' }) + end + end + + describe '#record_type' do + it 'returns a string type for the record to be used in a SOAP request' do + expect(partner.record_type).to eql('listRel:Partner') + end + end + + it 'has the right record_refs' do + [ + :klass, :access_role, :department, :subsidiary + ].each do |record_ref| + expect(partner).to have_record_ref(record_ref) + end + end +end From 6f7322138436f3cdb3c24e4d9d4a5164cc7eadda Mon Sep 17 00:00:00 2001 From: Neil Kulbiski Date: Fri, 14 Aug 2020 13:41:20 -0500 Subject: [PATCH 39/46] add sales_role to employee record --- lib/netsuite/records/employee.rb | 2 +- spec/netsuite/records/employee_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/netsuite/records/employee.rb b/lib/netsuite/records/employee.rb index 5b59cbc71..4df2f03d0 100644 --- a/lib/netsuite/records/employee.rb +++ b/lib/netsuite/records/employee.rb @@ -19,7 +19,7 @@ class Employee :phonetic_name, :purchase_order_approval_limit, :purchase_order_approver, :purchase_order_limit, :release_date, :resident_status, :salutation, :social_security_number, :visa_exp_date, :visa_type - record_refs :currency, :department, :location, :subsidiary, :employee_type, :employee_status, :supervisor + record_refs :currency, :department, :location, :sales_role, :subsidiary, :employee_type, :employee_status, :supervisor field :custom_field_list, CustomFieldList field :roles_list, RoleList diff --git a/spec/netsuite/records/employee_spec.rb b/spec/netsuite/records/employee_spec.rb index 861fe5d2f..ffb9c9ca0 100644 --- a/spec/netsuite/records/employee_spec.rb +++ b/spec/netsuite/records/employee_spec.rb @@ -21,7 +21,7 @@ it 'has all the right record refs' do [ - :location, :employee_status, :employee_type + :currency, :department, :location, :sales_role, :subsidiary, :employee_type, :employee_status, :supervisor ].each do |record_ref| expect(employee).to have_record_ref(record_ref) end @@ -144,7 +144,7 @@ it 'has the right record_refs' do [ - :currency, :department, :location, :subsidiary + :currency, :department, :location, :sales_role, :subsidiary, :employee_type, :employee_status, :supervisor ].each do |record_ref| expect(employee).to have_record_ref(record_ref) end From 3dffc392a1af44a5ce78eb3f936a071ba4f0379b Mon Sep 17 00:00:00 2001 From: John Perkins Date: Tue, 1 Sep 2020 13:57:00 -0400 Subject: [PATCH 40/46] Adding Estimate support for Quotes --- lib/netsuite.rb | 3 + lib/netsuite/records/estimate.rb | 42 ++++ lib/netsuite/records/estimate_item.rb | 40 ++++ lib/netsuite/records/estimate_item_list.rb | 11 + .../records/estimate_item_list_spec.rb | 26 +++ spec/netsuite/records/estimate_item_spec.rb | 40 ++++ spec/netsuite/records/estimate_spec.rb | 216 ++++++++++++++++++ 7 files changed, 378 insertions(+) create mode 100644 lib/netsuite/records/estimate.rb create mode 100644 lib/netsuite/records/estimate_item.rb create mode 100644 lib/netsuite/records/estimate_item_list.rb create mode 100644 spec/netsuite/records/estimate_item_list_spec.rb create mode 100644 spec/netsuite/records/estimate_item_spec.rb create mode 100644 spec/netsuite/records/estimate_spec.rb diff --git a/lib/netsuite.rb b/lib/netsuite.rb index 0e28e5711..b6bcba512 100644 --- a/lib/netsuite.rb +++ b/lib/netsuite.rb @@ -158,6 +158,9 @@ module Records autoload :Duration, 'netsuite/records/duration' autoload :Employee, 'netsuite/records/employee' autoload :EntityCustomField, 'netsuite/records/entity_custom_field' + autoload :Estimate, 'netsuite/records/estimate' + autoload :EstimateItem, 'netsuite/records/estimate_item' + autoload :EstimateItemList, 'netsuite/records/estimate_item_list' autoload :File, 'netsuite/records/file' autoload :GiftCertificate, 'netsuite/records/gift_certificate' autoload :GiftCertificateItem, 'netsuite/records/gift_certificate_item' diff --git a/lib/netsuite/records/estimate.rb b/lib/netsuite/records/estimate.rb new file mode 100644 index 000000000..cc1602f59 --- /dev/null +++ b/lib/netsuite/records/estimate.rb @@ -0,0 +1,42 @@ +module NetSuite + module Records + class Estimate + include Support::Fields + include Support::RecordRefs + include Support::Records + include Support::Actions + include Namespaces::TranSales + + actions :get, :get_list, :add, :initialize, :delete, :update, :upsert, :search + + fields :alt_handling_cost, :alt_sales_total, :alt_shipping_cost, :balance, + :bill_address, :billing_address, :billing_schedule, :bill_is_residential, + :created_date, :currency_name, :discount_rate, :email, :end_date, + :est_gross_profit, :exchange_rate, :handling_cost, :handling_tax1_rate, :is_taxable, + :last_modified_date, :memo, :message, :other_ref_num, :ship_date, :shipping_cost, + :shipping_tax1_rate, :source, :start_date, :status, :sync_partner_teams, :sync_sales_teams, + :to_be_emailed, :to_be_faxed, :to_be_printed, :total_cost_estimate, :tran_date, :tran_id, + :linked_tracking_numbers, :is_multi_ship_to + + field :shipping_address, Address + field :billing_address, Address + + field :item_list, EstimateItemList + field :custom_field_list, CustomFieldList + + record_refs :bill_address_list, :created_from, :currency, :custom_form, :department, :discount_item, :entity, + :handling_tax_code, :job, :klass, :lead_source, :location, :message_sel, :opportunity, :partner, + :promo_code, :sales_group, :sales_rep, :ship_method, :shipping_tax_code, :subsidiary, :terms + + attr_reader :internal_id + attr_accessor :external_id + attr_accessor :search_joins + + def initialize(attributes = {}) + @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id) + @external_id = attributes.delete(:external_id) || attributes.delete(:@external_id) + initialize_from_attributes_hash(attributes) + end + end + end +end diff --git a/lib/netsuite/records/estimate_item.rb b/lib/netsuite/records/estimate_item.rb new file mode 100644 index 000000000..433725af4 --- /dev/null +++ b/lib/netsuite/records/estimate_item.rb @@ -0,0 +1,40 @@ +module NetSuite + module Records + class EstimateItem + include Support::Fields + include Support::RecordRefs + include Support::Records + include Namespaces::TranSales + + fields :amount, :cost_estimate, + :cost_estimate_type, :defer_rev_rec, :description, + :is_taxable, :line, :quantity, + :rate, :tax_rate1 + + field :custom_field_list, CustomFieldList + + record_refs :item, :job, :price, :tax_code, :units + + def initialize(attributes_or_record = {}) + case attributes_or_record + when Hash + initialize_from_attributes_hash(attributes_or_record) + when self.class + initialize_from_record(attributes_or_record) + end + end + + def initialize_from_record(record) + self.attributes = record.send(:attributes) + end + + def to_record + rec = super + if rec["#{record_namespace}:customFieldList"] + rec["#{record_namespace}:customFieldList!"] = rec.delete("#{record_namespace}:customFieldList") + end + rec + end + end + end +end diff --git a/lib/netsuite/records/estimate_item_list.rb b/lib/netsuite/records/estimate_item_list.rb new file mode 100644 index 000000000..7c191cbff --- /dev/null +++ b/lib/netsuite/records/estimate_item_list.rb @@ -0,0 +1,11 @@ +module NetSuite + module Records + class EstimateItemList < Support::Sublist + include Namespaces::TranSales + + sublist :item, EstimateItem + + alias :items :item + end + end +end diff --git a/spec/netsuite/records/estimate_item_list_spec.rb b/spec/netsuite/records/estimate_item_list_spec.rb new file mode 100644 index 000000000..094f076c2 --- /dev/null +++ b/spec/netsuite/records/estimate_item_list_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe NetSuite::Records::EstimateItemList do + let(:list) { NetSuite::Records::EstimateItemList.new } + + it 'has a items attribute' do + expect(list.items).to be_kind_of(Array) + end + + describe '#to_record' do + before do + list.items << NetSuite::Records::EstimateItem.new( + :rate => 10 + ) + end + + it 'can represent itself as a SOAP record' do + record = { + 'tranSales:item' => [{ + 'tranSales:rate' => 10 + }] + } + expect(list.to_record).to eql(record) + end + end +end diff --git a/spec/netsuite/records/estimate_item_spec.rb b/spec/netsuite/records/estimate_item_spec.rb new file mode 100644 index 000000000..dd6bc6bd7 --- /dev/null +++ b/spec/netsuite/records/estimate_item_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe NetSuite::Records::EstimateItem do + let(:item) { NetSuite::Records::EstimateItem.new } + + it 'has all the right fields' do + [ + :amount, :cost_estimate, :cost_estimate_type, + :defer_rev_rec, :description, + :is_taxable, :line, :quantity, + :rate, :tax_rate1 + ].each do |field| + expect(item).to have_field(field) + end + end + + it 'has all the right record refs' do + [ + :item, :job, :price, :tax_code, :units + ].each do |record_ref| + expect(item).to have_record_ref(record_ref) + end + end + + describe '#options' do + it 'can be set from attributes' + it 'can be set from a CustomFieldList object' + end + + describe '#inventory_detail' do + it 'can be set from attributes' + it 'can be set from an InventoryDetail object' + end + + describe '#custom_field_list' do + it 'can be set from attributes' + it 'can be set from a CustomFieldList object' + end + +end diff --git a/spec/netsuite/records/estimate_spec.rb b/spec/netsuite/records/estimate_spec.rb new file mode 100644 index 000000000..9c6546696 --- /dev/null +++ b/spec/netsuite/records/estimate_spec.rb @@ -0,0 +1,216 @@ +require 'spec_helper' + +describe NetSuite::Records::Estimate do + let(:estimate) { NetSuite::Records::Estimate.new } + let(:customer) { NetSuite::Records::Customer.new } + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'has all the right fields' do + [ + :alt_handling_cost, :alt_shipping_cost, + :balance, :bill_address, :created_date, :currency_name, + :discount_rate, :email, :end_date, :est_gross_profit, + :exchange_rate, + :handling_cost, :handling_tax1_rate, :is_taxable, + :last_modified_date, :memo, :message, :other_ref_num, + :shipping_cost, :shipping_tax1_rate, + :source, :start_date, :status, :sync_partner_teams, + :sync_sales_teams, :to_be_emailed, :to_be_faxed, :to_be_printed, + :total_cost_estimate, :tran_date, :tran_id + ].each do |field| + expect(estimate).to have_field(field) + end + end + + it 'has all the right record refs' do + [ + :bill_address_list, :created_from, :currency, :custom_form, :department, :discount_item, + :entity, :handling_tax_code, :job, :klass, :lead_source, :location, :message_sel, + :opportunity, :partner, :promo_code, :sales_group, :sales_rep, + :ship_method, :shipping_tax_code, :subsidiary + ].each do |record_ref| + expect(estimate).to have_record_ref(record_ref) + end + end + + describe '#order_status' do + it 'can be set from attributes' + it 'can be set from a EstimateStatus object' + end + + describe '#item_list' do + it 'can be set from attributes' do + attributes = { + :item => { + :amount => 10 + } + } + estimate.item_list = attributes + expect(estimate.item_list).to be_kind_of(NetSuite::Records::EstimateItemList) + expect(estimate.item_list.items.length).to eql(1) + end + + it 'can be set from a EstimateItemList object' do + item_list = NetSuite::Records::EstimateItemList.new + estimate.item_list = item_list + expect(estimate.item_list).to eql(item_list) + end + end + + describe '#transaction_bill_address' do + it 'can be set from attributes' + it 'can be set from a BillAddress object' + end + + describe '#transaction_ship_address' do + it 'can be set from attributes' + it 'can be set from a ShipAddress object' + end + + describe '#revenue_status' do + it 'can be set from attributes' + it 'can be set from a RevenueStatus object' + end + + describe '#sales_team_list' do + it 'can be set from attributes' + it 'can be set from a EstimateSalesTeamList object' + end + + describe '#partners_list' do + it 'can be set from attributes' + it 'can be set from a EstimatePartnersList object' + end + + describe '#custom_field_list' do + it 'can be set from attributes' + it 'can be set from a CustomFieldList object' + end + + describe '.get' do + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :alt_shipping_cost => 100 }) } + + it 'returns a Estimate instance populated with the data from the response object' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::Estimate, :external_id => 1], {}).and_return(response) + estimate = NetSuite::Records::Estimate.get(:external_id => 1) + expect(estimate).to be_kind_of(NetSuite::Records::Estimate) + expect(estimate.alt_shipping_cost).to eql(100) + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'raises a RecordNotFound exception' do + expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::Estimate, :external_id => 1], {}).and_return(response) + expect { + NetSuite::Records::Estimate.get(:external_id => 1) + }.to raise_error(NetSuite::RecordNotFound, + /NetSuite::Records::Estimate with OPTIONS=(.*) could not be found/) + end + end + end + + describe '.initialize' do + context 'when the request is successful' do + it 'returns an initialized sales order from the customer entity' do + expect(NetSuite::Actions::Initialize).to receive(:call).with([NetSuite::Records::Estimate, customer], {}).and_return(response) + estimate = NetSuite::Records::Estimate.initialize(customer) + expect(estimate).to be_kind_of(NetSuite::Records::Estimate) + end + end + + context 'when the response is unsuccessful' do + skip + end + end + + describe '#add' do + let(:test_data) { { :email => 'test@example.com', :fax => '1234567890' } } + + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + estimate = NetSuite::Records::Estimate.new(test_data) + expect(NetSuite::Actions::Add).to receive(:call). + with([estimate], {}). + and_return(response) + expect(estimate.add).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + estimate = NetSuite::Records::Estimate.new(test_data) + expect(NetSuite::Actions::Add).to receive(:call). + with([estimate], {}). + and_return(response) + expect(estimate.add).to be_falsey + end + end + end + + describe '#delete' do + let(:test_data) { { :internal_id => '1' } } + + context 'when the response is successful' do + let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '1' }) } + + it 'returns true' do + estimate = NetSuite::Records::Estimate.new(test_data) + expect(NetSuite::Actions::Delete).to receive(:call). + with([estimate], {}). + and_return(response) + expect(estimate.delete).to be_truthy + end + end + + context 'when the response is unsuccessful' do + let(:response) { NetSuite::Response.new(:success => false, :body => {}) } + + it 'returns false' do + estimate = NetSuite::Records::Estimate.new(test_data) + expect(NetSuite::Actions::Delete).to receive(:call). + with([estimate], {}). + and_return(response) + expect(estimate.delete).to be_falsey + end + end + end + + describe '#to_record' do + before do + estimate.email = 'something@example.com' + estimate.tran_id = '4' + end + it 'can represent itself as a SOAP record' do + record = { + 'tranSales:email' => 'something@example.com', + 'tranSales:tranId' => '4' + } + expect(estimate.to_record).to eql(record) + end + end + + describe '#record_type' do + it 'returns a string representation of the SOAP type' do + expect(estimate.record_type).to eql('tranSales:Estimate') + end + end + + skip "closing a sales order" do + it "closes each line to close the sales order" do + attributes = sales_order.attributes + attributes[:item_list].items.each do |item| + item.is_closed = true + item.attributes = item.attributes.slice(:line, :is_closed) + end + + sales_order.update({ item_list: attributes[:item_list] }) + end + end +end From c2d7933030ba3c07d5fd71c1a6111345df0fcb2b Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Wed, 23 Sep 2020 15:55:02 -0600 Subject: [PATCH 41/46] Version bump --- lib/netsuite/version.rb | 2 +- netsuite.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/netsuite/version.rb b/lib/netsuite/version.rb index 763b660d7..438623e3c 100644 --- a/lib/netsuite/version.rb +++ b/lib/netsuite/version.rb @@ -1,3 +1,3 @@ module NetSuite - VERSION = '0.8.5' + VERSION = '0.8.6' end diff --git a/netsuite.gemspec b/netsuite.gemspec index ef89fd4e5..9061d9c09 100644 --- a/netsuite.gemspec +++ b/netsuite.gemspec @@ -3,7 +3,7 @@ require File.expand_path('../lib/netsuite/version', __FILE__) Gem::Specification.new do |gem| gem.authors = ['Ryan Moran', 'Michael Bianco'] - gem.email = ['ryan.moran@gmail.com', 'mike@cliffsidemedia.com'] + gem.email = ['ryan.moran@gmail.com', 'mike@mikebian.co'] gem.description = %q{NetSuite SuiteTalk API Wrapper} gem.summary = %q{NetSuite SuiteTalk API (SOAP) Wrapper} gem.homepage = 'https://github.com/NetSweet/netsuite' From 8960d58779409fc96b243be6db27e13d5df5af16 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Wed, 23 Sep 2020 15:58:00 -0600 Subject: [PATCH 42/46] Addressing gemspec warnings. Ensure savon is less than 2.11 Search seemed to break on versions greater than 2.11 --- netsuite.gemspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netsuite.gemspec b/netsuite.gemspec index 9061d9c09..fb4ca5452 100644 --- a/netsuite.gemspec +++ b/netsuite.gemspec @@ -2,6 +2,7 @@ require File.expand_path('../lib/netsuite/version', __FILE__) Gem::Specification.new do |gem| + gem.licenses = ['MIT'] gem.authors = ['Ryan Moran', 'Michael Bianco'] gem.email = ['ryan.moran@gmail.com', 'mike@mikebian.co'] gem.description = %q{NetSuite SuiteTalk API Wrapper} @@ -15,7 +16,7 @@ Gem::Specification.new do |gem| gem.require_paths = ['lib'] gem.version = NetSuite::VERSION - gem.add_dependency 'savon', '>= 2.3.0' + gem.add_dependency 'savon', '>= 2.3.0', '<= 2.11.1' gem.add_development_dependency 'rspec', '~> 3.8.0' end From 0b430cc776beacebc73b4b43b48e0463b79797c0 Mon Sep 17 00:00:00 2001 From: Alexander Lukin Date: Wed, 5 Jun 2019 17:05:43 +0300 Subject: [PATCH 43/46] [#2101] add runSuiteScriptAndTriggerWorkflow header support on Update action --- lib/netsuite/actions/update.rb | 17 +++++++++++++---- lib/netsuite/configuration.rb | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/netsuite/actions/update.rb b/lib/netsuite/actions/update.rb index 37e822349..b7fe001ac 100644 --- a/lib/netsuite/actions/update.rb +++ b/lib/netsuite/actions/update.rb @@ -8,11 +8,14 @@ class Update def initialize(klass, attributes) @klass = klass + @web_services_preferences = {} + @web_services_preferences[:run_suite_scripts] = attributes.delete(:run_suite_scripts) @attributes = attributes end def request(credentials={}) - NetSuite::Configuration.connection({}, credentials).call :update, :message => request_body + connection_params = { web_services_preferences: @web_services_preferences } + NetSuite::Configuration.connection(connection_params, credentials).call :update, :message => request_body end # @@ -70,19 +73,25 @@ def errors end module Support - def update(options = {}, credentials={}) - options[:internal_id] = internal_id if respond_to?(:internal_id) && internal_id + def update(options = {}, credentials = {}, web_services_preferences = {}) + + if web_services_preferences.has_key?(:run_suite_scripts) + options.merge!(run_suite_scripts: web_services_preferences[:run_suite_scripts]) + end + + options.merge!(:internal_id => internal_id) if respond_to?(:internal_id) && internal_id if !options.include?(:external_id) && (respond_to?(:external_id) && external_id) options[:external_id] = external_id end + options.merge!(:external_id => external_id) if respond_to?(:external_id) && external_id + response = NetSuite::Actions::Update.call([self.class, options], credentials) @errors = response.errors response.success? end end - end end end diff --git a/lib/netsuite/configuration.rb b/lib/netsuite/configuration.rb index 89ebb4d66..ddce8013c 100644 --- a/lib/netsuite/configuration.rb +++ b/lib/netsuite/configuration.rb @@ -15,12 +15,14 @@ def attributes end def connection(params={}, credentials={}) + preferences = params.delete(:web_services_preferences) + client = Savon.client({ wsdl: cached_wsdl || wsdl, read_timeout: read_timeout, open_timeout: open_timeout, namespaces: namespaces, - soap_header: auth_header(credentials).update(soap_header), + soap_header: auth_header(credentials).update(soap_header_with_web_preferences_headers(preferences)), pretty_print_xml: true, filters: filters, logger: logger, @@ -381,5 +383,19 @@ def log_level(value = nil) def log_level=(value) attributes[:log_level] = value end + + def soap_header_with_web_preferences_headers(web_services_preferences) + base_soap_header = soap_header + + return base_soap_header if web_services_preferences.nil? + + if web_services_preferences.has_key?(:run_suite_scripts) + base_soap_header['platformMsgs:preferences'] ||= {} + run_scripts_tag = 'platformMsgs:runServerSuiteScriptAndTriggerWorkflows' + base_soap_header['platformMsgs:preferences'][run_scripts_tag] = web_services_preferences[:run_suite_scripts] + end + + base_soap_header + end end end From 0c9902f32c308ee82dc235dae23e5f302ea79928 Mon Sep 17 00:00:00 2001 From: Alexander Lukin Date: Thu, 6 Jun 2019 14:09:48 +0300 Subject: [PATCH 44/46] do not pass run_suite_scripts flag with nil value --- lib/netsuite/actions/update.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/netsuite/actions/update.rb b/lib/netsuite/actions/update.rb index b7fe001ac..c7e684274 100644 --- a/lib/netsuite/actions/update.rb +++ b/lib/netsuite/actions/update.rb @@ -9,7 +9,11 @@ class Update def initialize(klass, attributes) @klass = klass @web_services_preferences = {} - @web_services_preferences[:run_suite_scripts] = attributes.delete(:run_suite_scripts) + + if attributes.has_key?(:run_suite_scripts) + @web_services_preferences[:run_suite_scripts] = attributes.delete(:run_suite_scripts) + end + @attributes = attributes end From ca636da32d0fbc542eb379684245949501be9571 Mon Sep 17 00:00:00 2001 From: Alexander Lukin Date: Thu, 6 Jun 2019 17:05:38 +0300 Subject: [PATCH 45/46] dup soap header when setting per request web preferences configuration --- lib/netsuite/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/netsuite/configuration.rb b/lib/netsuite/configuration.rb index ddce8013c..38130750b 100644 --- a/lib/netsuite/configuration.rb +++ b/lib/netsuite/configuration.rb @@ -385,7 +385,7 @@ def log_level=(value) end def soap_header_with_web_preferences_headers(web_services_preferences) - base_soap_header = soap_header + base_soap_header = soap_header.dup return base_soap_header if web_services_preferences.nil? From eee4d44aba8b1436d19190d9921d1d61cc0dc682 Mon Sep 17 00:00:00 2001 From: Dmytro Vasin Date: Fri, 17 Jun 2022 12:37:09 +0200 Subject: [PATCH 46/46] [#13198] Updated exceptions to retry list --- lib/netsuite/utilities.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/netsuite/utilities.rb b/lib/netsuite/utilities.rb index c0b0f2d91..b0813a3ea 100644 --- a/lib/netsuite/utilities.rb +++ b/lib/netsuite/utilities.rb @@ -103,6 +103,10 @@ def backoff(options = {}) exceptions_to_retry << OpenSSL::SSL::SSLErrorWaitReadable if defined?(OpenSSL::SSL::SSLErrorWaitReadable) # depends on the http library chosen + exceptions_to_retry << HTTPClient::TimeoutError if defined?(HTTPClient::TimeoutError) + exceptions_to_retry << HTTPClient::ConnectTimeoutError if defined?(HTTPClient::ConnectTimeoutError) + exceptions_to_retry << HTTPClient::ReceiveTimeoutError if defined?(HTTPClient::ReceiveTimeoutError) + exceptions_to_retry << HTTPClient::SendTimeoutError if defined?(HTTPClient::SendTimeoutError) exceptions_to_retry << Excon::Error::Timeout if defined?(Excon::Error::Timeout) exceptions_to_retry << Excon::Error::Socket if defined?(Excon::Error::Socket)