netsuite 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -20
  3. data/lib/netsuite.rb +11 -0
  4. data/lib/netsuite/actions/search.rb +1 -6
  5. data/lib/netsuite/actions/update_list.rb +109 -0
  6. data/lib/netsuite/records/bin_transfer.rb +38 -0
  7. data/lib/netsuite/records/bin_transfer_inventory.rb +20 -0
  8. data/lib/netsuite/records/bin_transfer_inventory_list.rb +10 -0
  9. data/lib/netsuite/records/cash_refund_item.rb +1 -1
  10. data/lib/netsuite/records/classification.rb +1 -1
  11. data/lib/netsuite/records/inbound_shipment.rb +33 -0
  12. data/lib/netsuite/records/inbound_shipment_item.rb +39 -0
  13. data/lib/netsuite/records/inbound_shipment_item_list.rb +11 -0
  14. data/lib/netsuite/records/inter_company_journal_entry.rb +48 -0
  15. data/lib/netsuite/records/inter_company_journal_entry_line.rb +28 -0
  16. data/lib/netsuite/records/inter_company_journal_entry_line_list.rb +14 -0
  17. data/lib/netsuite/records/inventory_item.rb +1 -1
  18. data/lib/netsuite/records/price_level.rb +26 -0
  19. data/lib/netsuite/records/return_authorization_item.rb +1 -1
  20. data/lib/netsuite/records/sales_order_item.rb +12 -5
  21. data/lib/netsuite/records/support_case.rb +1 -1
  22. data/lib/netsuite/support/actions.rb +2 -0
  23. data/lib/netsuite/support/search_result.rb +7 -3
  24. data/lib/netsuite/utilities.rb +26 -0
  25. data/lib/netsuite/version.rb +1 -1
  26. data/spec/netsuite/actions/update_list_spec.rb +107 -0
  27. data/spec/netsuite/records/basic_record_spec.rb +5 -1
  28. data/spec/netsuite/records/inter_company_journal_entry_line_list_spec.rb +26 -0
  29. data/spec/netsuite/records/inter_company_journal_entry_line_spec.rb +60 -0
  30. data/spec/netsuite/records/inter_company_journal_entry_spec.rb +156 -0
  31. data/spec/netsuite/records/inventory_item_spec.rb +57 -0
  32. data/spec/netsuite/records/price_level_spec.rb +16 -0
  33. data/spec/netsuite/records/return_authorization_item_spec.rb +1 -1
  34. data/spec/netsuite/records/sales_order_item_spec.rb +11 -5
  35. data/spec/netsuite/utilities_spec.rb +21 -0
  36. data/spec/support/fixtures/update_list/update_list_items.xml +22 -0
  37. data/spec/support/fixtures/update_list/update_list_one_item.xml +18 -0
  38. data/spec/support/fixtures/update_list/update_list_with_errors.xml +32 -0
  39. metadata +29 -2
@@ -0,0 +1,33 @@
1
+ module NetSuite
2
+ module Records
3
+ class InboundShipment
4
+ include Support::Fields
5
+ include Support::RecordRefs
6
+ include Support::Records
7
+ include Support::Actions
8
+ include Namespaces::TranPurch
9
+
10
+ actions :get, :get_list, :add, :initialize, :delete, :update, :upsert, :upsert_list, :search, :update_list
11
+
12
+ fields :shipment_number, :external_document_number, :shipment_status, :expected_shipping_date,
13
+ :actual_shipping_date, :expected_delivery_date, :actual_delivery_date, :shipment_memo,
14
+ :vessel_number, :bill_of_lading
15
+
16
+ field :items_list, InboundShipmentItemList
17
+ field :custom_field_list, CustomFieldList
18
+
19
+ record_refs :custom_form
20
+
21
+ attr_reader :internal_id
22
+ attr_accessor :external_id
23
+ attr_accessor :search_joins
24
+
25
+ def initialize(attributes = {})
26
+ @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id)
27
+ @external_id = attributes.delete(:external_id) || attributes.delete(:@external_id)
28
+ initialize_from_attributes_hash(attributes)
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ module NetSuite
2
+ module Records
3
+ class InboundShipmentItem
4
+ include Support::Fields
5
+ include Support::RecordRefs
6
+ include Support::Records
7
+ include Namespaces::TranPurch
8
+
9
+ fields :id, :shipment_item_description, :po_vendor, :quantity_received, :quantity_expected,
10
+ :quantity_remaining, :po_rate, :expected_rate, :shipment_item_amount
11
+
12
+ field :custom_field_list, CustomFieldList
13
+
14
+ record_refs :purchase_order, :shipment_item, :receiving_location, :po_currency, :incoterm
15
+
16
+ def initialize(attributes_or_record = {})
17
+ case attributes_or_record
18
+ when Hash
19
+ initialize_from_attributes_hash(attributes_or_record)
20
+ when self.class
21
+ initialize_from_record(attributes_or_record)
22
+ end
23
+ end
24
+
25
+ def initialize_from_record(record)
26
+ self.attributes = record.send(:attributes)
27
+ end
28
+
29
+ def to_record
30
+ rec = super
31
+ if rec["#{record_namespace}:customFieldList"]
32
+ rec["#{record_namespace}:customFieldList!"] = rec.delete("#{record_namespace}:customFieldList")
33
+ end
34
+ rec
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ module NetSuite
2
+ module Records
3
+ class InboundShipmentItemList < Support::Sublist
4
+ include Namespaces::TranPurch
5
+
6
+ sublist :inbound_shipment_items, InboundShipmentItem
7
+
8
+ alias :items_list :inbound_shipment_items
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,48 @@
1
+ module NetSuite
2
+ module Records
3
+ class InterCompanyJournalEntry
4
+ include Support::Fields
5
+ include Support::RecordRefs
6
+ include Support::Records
7
+ include Support::Actions
8
+ include Namespaces::TranGeneral
9
+
10
+ actions :get, :get_list, :add, :delete, :search, :upsert
11
+
12
+ fields :approved, :created_date, :exchange_rate, :is_book_specific, :last_modified_date, :memo, :reversal_date, :reversal_defer,
13
+ :reversal_entry, :tran_date, :tran_id
14
+
15
+ field :custom_field_list, CustomFieldList
16
+ field :line_list, InterCompanyJournalEntryLineList
17
+
18
+ record_refs :created_from, :currency, :custom_form, :department, :klass, :location, :parent_expense_alloc,
19
+ :posting_period, :subsidiary, :to_subsidiary
20
+
21
+ attr_reader :internal_id
22
+ attr_accessor :external_id
23
+
24
+ def initialize(attributes = {})
25
+ @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id)
26
+ @external_id = attributes.delete(:external_id) || attributes.delete(:@external_id)
27
+ initialize_from_attributes_hash(attributes)
28
+ end
29
+
30
+ def to_record
31
+ rec = super
32
+ if rec["#{record_namespace}:customFieldList"]
33
+ rec["#{record_namespace}:customFieldList!"] = rec.delete("#{record_namespace}:customFieldList")
34
+ end
35
+ rec
36
+ end
37
+
38
+ def self.search_class_name
39
+ "Transaction"
40
+ end
41
+
42
+ def self.search_class_namespace
43
+ "tranSales"
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module NetSuite
2
+ module Records
3
+ class InterCompanyJournalEntryLine
4
+ include Support::Fields
5
+ include Support::RecordRefs
6
+ include Support::Records
7
+ include Namespaces::TranGeneral
8
+
9
+ fields :amortization_end_date, :amortization_residual, :amortiz_start_date, :credit, :debit, :eliminate, :end_date, :gross_amt, :memo,
10
+ :residual, :start_date, :tax1_amt, :tax_rate1
11
+ field :custom_field_list, CustomFieldList
12
+ record_refs :account, :department, :entity, :klass, :line_subsidiary, :location, :schedule, :schedule_num, :tax1_acct, :tax_code
13
+
14
+ def initialize(attributes = {})
15
+ initialize_from_attributes_hash(attributes)
16
+ end
17
+
18
+ def to_record
19
+ rec = super
20
+ if rec["#{record_namespace}:customFieldList"]
21
+ rec["#{record_namespace}:customFieldList!"] = rec.delete("#{record_namespace}:customFieldList")
22
+ end
23
+ rec
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ module NetSuite
2
+ module Records
3
+ class InterCompanyJournalEntryLineList < Support::Sublist
4
+ include Namespaces::TranGeneral
5
+
6
+ attr_accessor :replace_all
7
+
8
+ sublist :line, InterCompanyJournalEntryLine
9
+
10
+ alias :lines :line
11
+
12
+ end
13
+ end
14
+ end
@@ -20,7 +20,7 @@ module NetSuite
20
20
  # }
21
21
  # ]
22
22
  #
23
- actions :get, :get_list, :add, :delete, :search, :update, :upsert
23
+ actions :get, :get_list, :add, :delete, :search, :update, :upsert, :update_list
24
24
 
25
25
  fields :auto_lead_time, :auto_preferred_stock_level, :auto_reorder_point, :available_to_partners, :average_cost,
26
26
  :copy_description, :cost, :cost_estimate, :cost_estimate_type, :cost_estimate_units, :cost_units, :costing_method,
@@ -0,0 +1,26 @@
1
+ module NetSuite
2
+ module Records
3
+ class PriceLevel
4
+ include Support::Fields
5
+ include Support::Records
6
+ include Support::Actions
7
+ include Support::RecordRefs
8
+ include Namespaces::ListAcct
9
+
10
+ actions :get, :update, :get_list, :add, :delete, :search, :upsert
11
+
12
+ # http://www.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2017_1/schema/record/pricelevel.html
13
+ fields :discountpct, :name, :is_online, :update_existing_prices,
14
+ :is_inactive
15
+
16
+ attr_reader :internal_id
17
+ attr_accessor :external_id
18
+
19
+ def initialize(attributes = {})
20
+ @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id)
21
+ @external_id = attributes.delete(:external_id) || attributes.delete(:@external_id)
22
+ initialize_from_attributes_hash(attributes)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -10,7 +10,7 @@ module NetSuite
10
10
  :cost_estimate, :cost_estimate_rate, :cost_estimate_type, :days_before_expiration, :defer_rev_rec, :description,
11
11
  :gift_cert_from, :gift_cert_message, :gift_cert_recipient_email, :gift_cert_recipient_name, :id, :inventory_detail,
12
12
  :is_closed, :is_drop_shipment, :is_taxable, :is_vsoe_bundle, :item_subtype, :item_type, :line, :line_number,
13
- :matrix_type, :options, :print_items, :quantity, :quantity_billed, :quantity_received, :quantity_rev_committed,
13
+ :matrix_type, :options, :order_line, :print_items, :quantity, :quantity_billed, :quantity_received, :quantity_rev_committed,
14
14
  :rate, :rate_schedule, :rev_rec_end_date, :rev_rec_start_date, :tax_rate1, :vsoe_allocation, :vsoe_amount,
15
15
  :vsoe_deferral, :vsoe_delivered, :vsoe_is_estimate, :vsoe_permit_discount, :vsoe_price, :vsoe_sop_group
16
16
 
@@ -6,11 +6,18 @@ module NetSuite
6
6
  include Support::Records
7
7
  include Namespaces::TranSales
8
8
 
9
- fields :amount, :bin_numbers, :cost_estimate, :cost_estimate_type, :defer_rev_rec, :description, :gift_cert_from,
10
- :gift_cert_message, :gift_cert_number, :gift_cert_recipient_email, :gift_cert_recipient_name, :gross_amt, :is_taxable,
11
- :line, :order_line, :po_currency, :quantity, :rate, :rev_rec_end_date, :rev_rec_start_date, :rev_rec_term_in_months,
12
- :serial_numbers, :shipping_cost, :tax1_amt, :tax_rate1, :tax_rate2, :vsoe_allocation, :vsoe_amount, :vsoe_deferral,
13
- :vsoe_delivered, :vsoe_permit_discount, :vsoe_price, :is_closed, :quantity_commited, :quantity_fulfilled
9
+ fields :amount, :bin_numbers, :cost_estimate,
10
+ :cost_estimate_type, :defer_rev_rec, :description,
11
+ :expand_item_group, :gift_cert_from, :gift_cert_message,
12
+ :gift_cert_number, :gift_cert_recipient_email,
13
+ :gift_cert_recipient_name, :gross_amt, :is_closed,
14
+ :is_taxable, :line, :order_line, :po_currency, :quantity,
15
+ :quantity_back_ordered, :quantity_billed, :quantity_committed,
16
+ :quantity_fulfilled, :rate, :rev_rec_end_date,
17
+ :rev_rec_start_date, :rev_rec_term_in_months, :serial_numbers,
18
+ :shipping_cost, :tax1_amt, :tax_rate1, :tax_rate2,
19
+ :vsoe_allocation, :vsoe_amount, :vsoe_deferral,
20
+ :vsoe_delivered, :vsoe_permit_discount, :vsoe_price
14
21
 
15
22
  field :custom_field_list, CustomFieldList
16
23
 
@@ -7,7 +7,7 @@ module NetSuite
7
7
  include Support::Actions
8
8
  include Namespaces::ListSupport
9
9
 
10
- actions :get, :get_list, :add, :delete, :update, :upsert
10
+ actions :get, :get_list, :add, :delete, :update, :upsert, :search
11
11
 
12
12
  fields :end_date, :incoming_message, :outgoing_message, :search_solution, :email_form,
13
13
  :internal_only, :title, :case_number, :start_date, :email, :phone, :inbound_email,
@@ -42,6 +42,8 @@ module NetSuite
42
42
  self.send(:include, NetSuite::Actions::DeleteList::Support)
43
43
  when :update
44
44
  self.send(:include, NetSuite::Actions::Update::Support)
45
+ when :update_list
46
+ self.send(:include, NetSuite::Actions::UpdateList::Support)
45
47
  when :initialize
46
48
  self.send(:include, NetSuite::Actions::Initialize::Support)
47
49
  else
@@ -17,9 +17,10 @@ module NetSuite
17
17
  # <platformCore:pageIndex>1</platformCore:pageIndex>
18
18
  # <platformCore:searchId>WEBSERVICES_738944_SB2_03012013650784545962753432_28d96bd280</platformCore:searchId>
19
19
 
20
- def initialize(response, result_class)
20
+ def initialize(response, result_class, credentials)
21
21
  @result_class = result_class
22
22
  @response = response
23
+ @credentials = credentials
23
24
 
24
25
  @total_records = response.body[:total_records].to_i
25
26
  @total_pages = response.body[:total_pages].to_i
@@ -98,8 +99,11 @@ module NetSuite
98
99
  yield results
99
100
 
100
101
  next_search = @result_class.search(
101
- search_id: @response.body[:search_id],
102
- page_index: @response.body[:page_index].to_i + 1
102
+ {
103
+ search_id: @response.body[:search_id],
104
+ page_index: @response.body[:page_index].to_i + 1
105
+ },
106
+ @credentials
103
107
  )
104
108
 
105
109
  @results = next_search.results
@@ -37,6 +37,28 @@ module NetSuite
37
37
  server_time_response.body[:get_server_time_response][:get_server_time_result][:server_time]
38
38
  end
39
39
 
40
+ def netsuite_data_center_urls(account_id)
41
+ data_center_call_response = NetSuite::Configuration.connection({
42
+ # NOTE force a production WSDL so the sandbox settings are ignored
43
+ wsdl: 'https://webservices.netsuite.com/wsdl/v2017_2_0/netsuite.wsdl',
44
+
45
+ # NOTE don't inherit default namespace settings, it includes the API version
46
+ namespaces: {
47
+ 'xmlns:platformCore' => "urn:core_2017_2.platform.webservices.netsuite.com"
48
+ },
49
+
50
+ soap_header: {}
51
+ }).call(:get_data_center_urls, message: {
52
+ 'platformMsgs:account' => account_id
53
+ })
54
+
55
+ if data_center_call_response.success?
56
+ data_center_call_response.body[:get_data_center_urls_response][:get_data_center_urls_result][:data_center_urls]
57
+ else
58
+ false
59
+ end
60
+ end
61
+
40
62
  def backoff(options = {})
41
63
  # TODO the default backoff attempts should be customizable the global config
42
64
  options[:attempts] ||= 8
@@ -86,9 +108,13 @@ module NetSuite
86
108
  !e.message.include?('com.netledger.common.exceptions.NLDatabaseOfflineException') &&
87
109
  !e.message.include?('com.netledger.database.NLConnectionUtil$NoCompanyDbsOnlineException') &&
88
110
  !e.message.include?('com.netledger.cache.CacheUnavailableException') &&
111
+ !e.message.include?('java.lang.IllegalStateException') &&
89
112
  !e.message.include?('An unexpected error occurred.') &&
113
+ !e.message.include?('An unexpected error has occurred. Technical Support has been alerted to this problem.') &&
90
114
  !e.message.include?('Session invalidation is in progress with different thread') &&
91
115
  !e.message.include?('SuiteTalk concurrent request limit exceeded. Request blocked.') &&
116
+ # maintenance is the new outage: this message is being used for intermittent errors
117
+ !e.message.include?('The account you are trying to access is currently unavailable while we undergo our regularly scheduled maintenance.') &&
92
118
  !e.message.include?('The Connection Pool is not intialized.') &&
93
119
  # it looks like NetSuite mispelled their error message...
94
120
  !e.message.include?('The Connection Pool is not intiialized.')
@@ -1,3 +1,3 @@
1
1
  module NetSuite
2
- VERSION = '0.8.2'
2
+ VERSION = '0.8.3'
3
3
  end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe NetSuite::Actions::UpdateList do
4
+ before { savon.mock! }
5
+ after { savon.unmock! }
6
+
7
+ context 'Items' do
8
+ context 'one item' do
9
+ let(:item) do
10
+ [
11
+ NetSuite::Records::InventoryItem.new(internal_id: '624113', item_id: 'Target', upccode: 'Target')
12
+ ]
13
+ end
14
+
15
+ before do
16
+ savon.expects(:update_list).with(:message =>
17
+ {
18
+ 'record' => [{
19
+ 'listAcct:itemId' => 'Target',
20
+ '@xsi:type' => 'listAcct:InventoryItem',
21
+ '@internalId' => '624113'
22
+ }]
23
+ }).returns(File.read('spec/support/fixtures/update_list/update_list_one_item.xml'))
24
+ end
25
+
26
+ it 'makes a valid request to the NetSuite API' do
27
+ NetSuite::Actions::UpdateList.call(item)
28
+ end
29
+
30
+ it 'returns a valid Response object' do
31
+ response = NetSuite::Actions::UpdateList.call(item)
32
+ expect(response).to be_kind_of(NetSuite::Response)
33
+ expect(response).to be_success
34
+ end
35
+ end
36
+
37
+ context 'two items' do
38
+ let(:items) do
39
+ [
40
+ NetSuite::Records::InventoryItem.new(internal_id: '624172', item_id: 'Shutter Fly', upccode: 'Shutter Fly, Inc.'),
41
+ NetSuite::Records::InventoryItem.new(internal_id: '624113', item_id: 'Target', upccode: 'Target')
42
+ ]
43
+ end
44
+
45
+ before do
46
+ savon.expects(:update_list).with(:message =>
47
+ {
48
+ 'record' => [{
49
+ 'listAcct:itemId' => 'Shutter Fly',
50
+ '@xsi:type' => 'listAcct:InventoryItem',
51
+ '@internalId' => '624172'
52
+ },
53
+ {
54
+ 'listAcct:itemId' => 'Target',
55
+ '@xsi:type' => 'listAcct:InventoryItem',
56
+ '@internalId' => '624113'
57
+ }
58
+ ]
59
+ }).returns(File.read('spec/support/fixtures/update_list/update_list_items.xml'))
60
+ end
61
+
62
+ it 'makes a valid request to the NetSuite API' do
63
+ NetSuite::Actions::UpdateList.call(items)
64
+ end
65
+
66
+ it 'returns a valid Response object' do
67
+ response = NetSuite::Actions::UpdateList.call(items)
68
+ expect(response).to be_kind_of(NetSuite::Response)
69
+ expect(response).to be_success
70
+ end
71
+ end
72
+ end
73
+
74
+ context 'with errors' do
75
+ let(:items) do
76
+ [
77
+ NetSuite::Records::InventoryItem.new(internal_id: '624172-bad', item_id: 'Shutter Fly', upccode: 'Shutter Fly, Inc.'),
78
+ NetSuite::Records::InventoryItem.new(internal_id: '624113-bad', item_id: 'Target', upccode: 'Target')
79
+ ]
80
+ end
81
+
82
+ before do
83
+ savon.expects(:update_list).with(:message =>
84
+ {
85
+ 'record' => [{
86
+ 'listAcct:itemId' => 'Shutter Fly',
87
+ '@xsi:type' => 'listAcct:InventoryItem',
88
+ '@internalId' => '624172-bad'
89
+ },
90
+ {
91
+ 'listAcct:itemId' => 'Target',
92
+ '@xsi:type' => 'listAcct:InventoryItem',
93
+ '@internalId' => '624113-bad'
94
+ }
95
+ ]
96
+ }).returns(File.read('spec/support/fixtures/update_list/update_list_with_errors.xml'))
97
+ end
98
+
99
+ it 'constructs error objects' do
100
+ response = NetSuite::Actions::UpdateList.call(items)
101
+ expect(response.errors.keys).to match_array(['624172', '624113'])
102
+ expect(response.errors['624172'].first.code).to eq('USER_ERROR')
103
+ expect(response.errors['624172'].first.message).to eq('Please enter value(s) for: ItemId')
104
+ expect(response.errors['624172'].first.type).to eq('ERROR')
105
+ end
106
+ end
107
+ end