diff --git a/Gemfile b/Gemfile
index ac23ef3eb..e1cecd98b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,11 +5,15 @@ gem 'simplecov', :require => false
gem 'pry-nav'
gem 'pry-rescue'
+gem 'pry'
# optional dependency for more accurate timezone conversion
-gem 'tzinfo', '1.2.5'
+# gem 'tzinfo', '1.2.5'
# gem 'tzinfo', '2.0.0'
+gem 'tzinfo', '>= 1.2.10'
# required for CircleCI to build properly with ruby 1.9.3
-gem 'json', '~> 1.8.3'
-gem 'rack', '~> 1.6.4'
+# gem 'json', '~> 1.8.3'
+# gem 'rack', '~> 1.6.4'
+gem 'json', '>= 2.3.0'
+gem 'rack', '>= 2.1.4'
\ No newline at end of file
diff --git a/README.md b/README.md
index fbddf116e..298e85f25 100644
--- a/README.md
+++ b/README.md
@@ -591,3 +591,23 @@ states.to_array.first[:get_all_response][:get_all_result][:record_list][:record]
# About SuiteSync
[SuiteSync, the Stripe-NetSuite integration](http://suitesync.io) uses this gem and funds the majority of it's development and maintenance.
+
+
+
+
+# Cloudsnap Additions
+
+I generally refer to the Netsuite Schema Browser as a source of truth over the actions, records, and fields that you see in this gem. That documentation can be found here: https://www.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2018_1/schema/record/account.html?mode=package
+
+**One note is to be mindful of the version that you are looking at in the schema browser and the version that you are using when you configure the netsuite credentials.
+
+### Actions
+attach.rb--> lib/netsuite/actions/attach.rb
+Based on the Netsuite Schema documentation, we added in the attach action functionality. This is primarily used to attach records considered File Cabinate
+
+
+### Records
+If you see the record in the netsuite schema browser, you can easily add it to the records in this gem. Use the fields and sub lists mentioned in the Netsuite schema browser to build out the record file following the same format as the other record files.
+
+### Other Cloudsnap Changes
+We made a few changes to the configuration.rb file. The check credentials method and set attributes methods were added to support the updated way of passing credentials. In the connection method, we also added the ability to set the wsdl and the endpoint level for the Savon client that is configured. This was required to connect to Netsuite api versions 2020+
diff --git a/lib/netsuite.rb b/lib/netsuite.rb
index 0e28e5711..d1c981f19 100644
--- a/lib/netsuite.rb
+++ b/lib/netsuite.rb
@@ -49,6 +49,8 @@ module Support
module Actions
autoload :Add, 'netsuite/actions/add'
+ autoload :AsyncAddList, 'netsuite/actions/async_add_list'
+ autoload :Attach, 'netsuite/actions/attach'
autoload :Delete, 'netsuite/actions/delete'
autoload :DeleteList, 'netsuite/actions/delete_list'
autoload :Get, 'netsuite/actions/get'
@@ -74,6 +76,7 @@ module Records
autoload :Account, 'netsuite/records/account'
autoload :AccountingPeriod, 'netsuite/records/accounting_period'
autoload :Address, 'netsuite/records/address'
+ autoload :AttachBasicReference, 'netsuite/records/attach_basic_reference'
autoload :BaseRefList, 'netsuite/records/base_ref_list'
autoload :BillAddress, 'netsuite/records/bill_address'
autoload :BillingSchedule, 'netsuite/records/billing_schedule'
@@ -103,6 +106,7 @@ module Records
autoload :CustomField, 'netsuite/records/custom_field'
autoload :CustomFieldList, 'netsuite/records/custom_field_list'
autoload :CustomList, 'netsuite/records/custom_list'
+ autoload :CustomSegment, 'netsuite/records/custom_segment'
autoload :CustomRecord, 'netsuite/records/custom_record'
autoload :CustomRecordRef, 'netsuite/records/custom_record_ref'
autoload :CustomRecordType, 'netsuite/records/custom_record_type'
@@ -156,8 +160,16 @@ module Records
autoload :DescriptionItem, 'netsuite/records/description_item'
autoload :DiscountItem, 'netsuite/records/discount_item'
autoload :Duration, 'netsuite/records/duration'
+ autoload :EmployeeAddressbookList, 'netsuite/records/employee_addressbook_list'
+ autoload :EmployeeAddressbook, 'netsuite/records/employee_addressbook'
autoload :Employee, 'netsuite/records/employee'
autoload :EntityCustomField, 'netsuite/records/entity_custom_field'
+ autoload :ExpenseCategory, 'netsuite/records/expense_category'
+ autoload :ExpenseCategoryRate, 'netsuite/records/expense_category_rate'
+ autoload :ExpenseCategoryRatesList, 'netsuite/records/expense_category_rates_list'
+ autoload :ExpenseReport, 'netsuite/records/expense_report'
+ autoload :ExpenseReportExpense, 'netsuite/records/expense_report_expense'
+ autoload :ExpenseReportExpenseList, 'netsuite/records/expense_report_expense_list'
autoload :File, 'netsuite/records/file'
autoload :GiftCertificate, 'netsuite/records/gift_certificate'
autoload :GiftCertificateItem, 'netsuite/records/gift_certificate_item'
@@ -186,15 +198,19 @@ module Records
autoload :Invoice, 'netsuite/records/invoice'
autoload :InvoiceItem, 'netsuite/records/invoice_item'
autoload :InvoiceItemList, 'netsuite/records/invoice_item_list'
+ autoload :ItemOptionCustomField, 'netsuite/records/item_option_custom_field'
autoload :ItemFulfillment, 'netsuite/records/item_fulfillment'
autoload :ItemFulfillmentItem, 'netsuite/records/item_fulfillment_item'
autoload :ItemFulfillmentItemList, 'netsuite/records/item_fulfillment_item_list'
autoload :ItemFulfillmentPackage, 'netsuite/records/item_fulfillment_package'
autoload :ItemFulfillmentPackageList, 'netsuite/records/item_fulfillment_package_list'
+ autoload :Item, 'netsuite/records/item'
autoload :ItemGroup, 'netsuite/records/item_group'
autoload :ItemMember, 'netsuite/records/item_member'
autoload :ItemMemberList, 'netsuite/records/item_member_list'
autoload :ItemReceipt, 'netsuite/records/item_receipt'
+ autoload :ItemReceiptExpenseList, 'netsuite/records/item_receipt_expense_list'
+ autoload :ItemReceiptExpense, 'netsuite/records/item_receipt_expense'
autoload :ItemReceiptItemList, 'netsuite/records/item_receipt_item_list'
autoload :ItemReceiptItem, 'netsuite/records/item_receipt_item'
autoload :ItemVendor, 'netsuite/records/item_vendor'
@@ -217,10 +233,12 @@ module Records
autoload :NonInventoryResaleItem, 'netsuite/records/non_inventory_resale_item'
autoload :Note, 'netsuite/records/note'
autoload :NoteType, 'netsuite/records/note_type'
+ autoload :NullFieldList, 'netsuite/records/null_field_list'
autoload :Opportunity, 'netsuite/records/opportunity'
autoload :OpportunityItem, 'netsuite/records/opportunity_item'
autoload :OpportunityItemList, 'netsuite/records/opportunity_item_list'
autoload :OtherChargeSaleItem, 'netsuite/records/other_charge_sale_item'
+ autoload :OtherCustomField, 'netsuite/records/other_custom_field'
autoload :Partner, 'netsuite/records/partner'
autoload :PaymentItem, 'netsuite/records/payment_item'
autoload :PaymentMethod, 'netsuite/records/payment_method'
@@ -235,6 +253,8 @@ module Records
autoload :PromotionsList, 'netsuite/records/promotions_list'
autoload :Promotions, 'netsuite/records/promotions'
autoload :PurchaseOrder, 'netsuite/records/purchase_order'
+ autoload :PurchaseOrderExpenseList, 'netsuite/records/purchase_order_expense_list'
+ autoload :PurchaseOrderExpense, 'netsuite/records/purchase_order_expense'
autoload :PurchaseOrderItemList, 'netsuite/records/purchase_order_item_list'
autoload :PurchaseOrderItem, 'netsuite/records/purchase_order_item'
autoload :Roles, 'netsuite/records/roles'
@@ -252,6 +272,7 @@ module Records
autoload :SalesOrderItemList, 'netsuite/records/sales_order_item_list'
autoload :SalesRole, 'netsuite/records/sales_role'
autoload :SalesTaxItem, 'netsuite/records/sales_tax_item'
+ autoload :ServicePurchaseItem, 'netsuite/records/service_purchase_item'
autoload :ServiceResaleItem, 'netsuite/records/service_resale_item'
autoload :ServiceSaleItem, 'netsuite/records/service_sale_item'
autoload :SerializedAssemblyItem, 'netsuite/records/serialized_assembly_item'
diff --git a/lib/netsuite/actions/add.rb b/lib/netsuite/actions/add.rb
index 4cc352661..6405aceaf 100644
--- a/lib/netsuite/actions/add.rb
+++ b/lib/netsuite/actions/add.rb
@@ -73,11 +73,10 @@ def errors
module Support
def add(credentials={})
response = NetSuite::Actions::Add.call([self], credentials)
-
@errors = response.errors
if response.success?
- @internal_id = response.body[:@internal_id]
+ @internal_id = response.body.dig(:@internal_id) unless response.body.class == Nori::StringIOFile
true
else
false
diff --git a/lib/netsuite/actions/async_add_list.rb b/lib/netsuite/actions/async_add_list.rb
new file mode 100644
index 000000000..132d9c21d
--- /dev/null
+++ b/lib/netsuite/actions/async_add_list.rb
@@ -0,0 +1,100 @@
+# https://system.netsuite.com/help/helpcenter/en_US/Output/Help/SuiteCloudCustomizationScriptingWebServices/SuiteTalkWebServices/asyncAddList.html
+module NetSuite
+ module Actions
+ class AsyncAddList
+ include Support::Requests
+
+ def initialize(*objects)
+ @objects = objects
+ end
+
+ private
+
+ def request(credentials={})
+ NetSuite::Configuration.connection(
+ {}, credentials
+ ).call(:async_add_list, message: request_body)
+ end
+
+ #
+ #
+ #
+ # Shutter Fly
+ # Shutter Fly, Inc
+ #
+ #
+ # Target
+ # Target
+ #
+ #
+ #
+ def request_body
+ attrs = @objects.map do |o|
+ hash = o.to_record.merge({
+ '@xsi:type' => o.record_type
+ })
+
+ if o.respond_to?(:internal_id) && o.internal_id
+ hash['@internalId'] = o.internal_id
+ end
+
+ if o.respond_to?(:external_id) && o.external_id
+ hash['@externalId'] = o.external_id
+ end
+
+ hash
+ end
+
+ { 'record' => attrs }
+ end
+
+ def response_hash
+ @response_hash ||= Array[@response.body[:async_add_list_response][:async_status_result]]
+ end
+
+ def response_body
+ @response_body ||= response_hash #.map { |h| h[:job_id] }
+ end
+
+ def response_errors
+ @response_errors ||= response_hash unless success?
+ # if response_hash[0].any? { |h| h[:status] && h[:status][:status_detail] }
+ # @response_errors ||= errors
+ # end
+ end
+
+ def errors
+ # errors = response_hash.select { |h| h[:status] && h[:status][:status_detail] }.map do |obj|
+ # error_obj = obj[:status][:status_detail]
+ # error_obj = [error_obj] if error_obj.class == Hash
+ # errors = error_obj.map do |error|
+ # NetSuite::Error.new(error)
+ # end
+
+ # [obj[:base_ref][:@external_id], errors]
+ # end
+ # Hash[errors]
+ end
+
+ def success?
+ !response_hash[0][:job_id].nil?
+ end
+
+ module Support
+ def async_add_list(credentials={})
+ response = NetSuite::Actions::AsyncAddList.call([self], credentials)
+ return response
+ # @errors = response.errors
+
+ # if response.success?
+ # response.body unless response.body.class == Nori::StringIOFile
+ # true
+ # else
+ # false
+ # end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/netsuite/actions/attach.rb b/lib/netsuite/actions/attach.rb
new file mode 100644
index 000000000..d7af3f4cd
--- /dev/null
+++ b/lib/netsuite/actions/attach.rb
@@ -0,0 +1,87 @@
+module NetSuite
+ module Actions
+ class Attach
+ include Support::Requests
+
+ attr_reader :response_hash
+
+ def initialize(object=nil)
+ @object = object
+ end
+
+ private
+
+ def request(credentials={})
+ NetSuite::Configuration.connection({}, credentials).call(:attach, :message => request_body)
+ end
+
+ #please note, reference is spelled referece and is documented that way in NetSuite's documentation
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+
+ def request_body
+ hash = {
+ 'platformMsgs:attachReferece' => {
+ :content! => @object.to_record,
+ '@xsi:type' => @object.record_type
+ }
+ }
+ #not crazy about this solution but not sure how else to get the 'xsi:type' var in the hash
+ hash["platformMsgs:attachReferece"][:content!][:attributes!]["platformCore:attachTo"]["xsi:type"] = "platformCore:RecordRef"
+ hash["platformMsgs:attachReferece"][:content!][:attributes!]["platformCore:attachedRecord"]["xsi:type"] = "platformCore:RecordRef"
+
+ hash
+ end
+
+ def success?
+ @success ||= response_hash[:status][:@is_success] == 'true'
+ end
+
+ def response_body
+ @response_body ||= response_hash[:base_ref]
+ end
+
+ def response_errors
+ if response_hash[:status] && response_hash[:status][:status_detail]
+ @response_errors ||= errors
+ end
+ end
+
+ def response_hash
+ @response_hash ||= @response.to_hash[:attach_response][:write_response]
+ end
+
+ def errors
+ error_obj = response_hash[:status][:status_detail]
+ error_obj = [error_obj] if error_obj.class == Hash
+ error_obj.map do |error|
+ NetSuite::Error.new(error)
+ end
+ end
+
+
+ module Support
+ def attach(credentials={})
+ response = NetSuite::Actions::Attach.call([self], credentials)
+
+ @errors = response.errors
+
+ if response.success?
+ @internal_id = response.body[:@internal_id]
+ true
+ else
+ false
+ end
+ end
+ end
+
+
+ end
+ end
+end
diff --git a/lib/netsuite/actions/get.rb b/lib/netsuite/actions/get.rb
index 6674ef0c9..f76190b8d 100644
--- a/lib/netsuite/actions/get.rb
+++ b/lib/netsuite/actions/get.rb
@@ -56,6 +56,17 @@ def response_hash
@response_hash = @response.body[:get_response][:read_response]
end
+ def response_errors
+ if response_hash[:status] && response_hash[:status][:status_detail]
+ @response_errors ||= errors
+ end
+ end
+
+ def errors
+ error_obj = response_hash.dig(:status,:status_detail)
+ OpenStruct.new(status: 404, status_detail: error_obj)
+ end
+
module Support
def self.included(base)
@@ -71,7 +82,10 @@ def get(options = {}, credentials = {})
if response.success?
new(response.body)
else
- raise RecordNotFound, "#{self} with OPTIONS=#{options.inspect} could not be found"
+ NetSuite::Error.new(
+ code: response.errors.status_detail[:code],
+ message: response.errors.status_detail[:message]
+ )
end
end
diff --git a/lib/netsuite/actions/get_list.rb b/lib/netsuite/actions/get_list.rb
index 2fc18105f..dde5efc72 100644
--- a/lib/netsuite/actions/get_list.rb
+++ b/lib/netsuite/actions/get_list.rb
@@ -65,8 +65,8 @@ def response_body
end
def success?
- # each returned record has its own status;
- if @options[:allow_incomplete]
+ # each returned record has its own status;
+ if @options[:allow_incomplete]
@success ||= !response_body.detect { |r| r[:status][:@is_success] == 'true' }.nil?
else
@success ||= response_body.detect { |r| r[:status][:@is_success] != 'true' }.nil?
@@ -82,14 +82,17 @@ module ClassMethods
def get_list(options = { }, credentials={})
response = NetSuite::Actions::GetList.call([self, options], credentials)
- if response.success?
- response.body.inject([]) do |arr, record|
- arr << new(record[:record]) unless record[:status][:@is_success] != 'true'
- arr
- end
- else
- false
+ response.errors = response.body.inject([]) do |arr, record|
+ arr << record if record[:status][:@is_success] != 'true'
+ arr
end
+
+ response.body = response.body.inject([]) do |arr, record|
+ arr << new(record[:record]) unless record[:status][:@is_success] != 'true'
+ arr
+ end
+
+ response
end
end
end
diff --git a/lib/netsuite/actions/get_select_value.rb b/lib/netsuite/actions/get_select_value.rb
index f4938f7b2..6a7cf0daf 100644
--- a/lib/netsuite/actions/get_select_value.rb
+++ b/lib/netsuite/actions/get_select_value.rb
@@ -25,7 +25,16 @@ def success?
end
def response_body
- @response_body ||= response_hash[:base_ref_list]
+ @response_body ||= build_response_hash
+ end
+
+ def build_response_hash
+ return response_hash if response_hash[:base_ref_list].blank?
+ {
+ total_records: response_hash[:total_records],
+ total_pages: response_hash[:total_pages],
+ base_ref: response_hash.dig(:base_ref_list, :base_ref)
+ }
end
def response_hash
@@ -47,7 +56,6 @@ def get_select_value(options = {}, credentials={})
}
response = NetSuite::Actions::GetSelectValue.call([self, message], credentials)
-
if response.success?
new(response.body)
else
diff --git a/lib/netsuite/actions/search.rb b/lib/netsuite/actions/search.rb
index e5f8420dd..0e319175b 100644
--- a/lib/netsuite/actions/search.rb
+++ b/lib/netsuite/actions/search.rb
@@ -244,7 +244,7 @@ def search(options = { }, credentials={})
if response.success?
NetSuite::Support::SearchResult.new(response, self, credentials)
else
- false
+ NetSuite::Error.new(response.body[:status][:status_detail])
end
end
end
diff --git a/lib/netsuite/actions/update.rb b/lib/netsuite/actions/update.rb
index 37e822349..a7b261682 100644
--- a/lib/netsuite/actions/update.rb
+++ b/lib/netsuite/actions/update.rb
@@ -36,6 +36,12 @@ def request_body
hash['platformMsgs:record']['@platformMsgs:externalId'] = updated_record.external_id
end
+ inner = hash.dig("platformMsgs:record", :content!)
+ if inner.keys.grep(/(.*):nullFieldList?/).any?
+ null_field_key = inner.keys.grep(/(.*):nullFieldList?/)
+ hash["platformMsgs:record"][:content!]["platformCore:nullFieldList"] = hash["platformMsgs:record"][:content!].delete null_field_key[0]
+ end
+
hash
end
diff --git a/lib/netsuite/configuration.rb b/lib/netsuite/configuration.rb
index 20acf88df..b1e9af1ee 100644
--- a/lib/netsuite/configuration.rb
+++ b/lib/netsuite/configuration.rb
@@ -15,6 +15,8 @@ def attributes
end
def connection(params={}, credentials={})
+ check_credentials(credentials)
+
client = Savon.client({
wsdl: cached_wsdl || wsdl,
read_timeout: read_timeout,
@@ -27,10 +29,28 @@ def connection(params={}, credentials={})
log_level: log_level,
log: !silent, # turn off logging entirely if configured
}.update(params))
+ client.wsdl.endpoint = client.wsdl.endpoint.to_s.sub('//webservices.netsuite.com/', "//#{wsdl_domain}/")
cache_wsdl(client)
return client
end
+ def check_credentials(creds)
+ if !creds.blank?
+ set_attributes(creds)
+ end
+ end
+
+ def set_attributes(credentials)
+ account(credentials[:account])
+ consumer_key(credentials[:consumer_key])
+ consumer_secret(credentials[:consumer_secret])
+ token_id(credentials[:token_id])
+ token_secret(credentials[:token_secret])
+ api_version(credentials[:api_version])
+ wsdl_domain(credentials[:wsdl_domain])
+ soap_header(credentials[:soap_header])
+ end
+
def filters(list = nil)
if list
self.filters = list
@@ -81,7 +101,7 @@ def api_version(version = nil)
if version
self.api_version = version
else
- attributes[:api_version] ||= '2015_1'
+ attributes[:api_version] ||= '2018_2'
end
end
@@ -323,7 +343,7 @@ def read_timeout(timeout = nil)
if timeout
self.read_timeout = timeout
else
- attributes[:read_timeout] ||= 60
+ attributes[:read_timeout] ||= 120
end
end
diff --git a/lib/netsuite/records/account.rb b/lib/netsuite/records/account.rb
index b749f9596..13acb8743 100644
--- a/lib/netsuite/records/account.rb
+++ b/lib/netsuite/records/account.rb
@@ -16,6 +16,8 @@ class Account
field :subsidiary_list, RecordRefList
+ field :custom_field_list, CustomFieldList
+
attr_reader :internal_id
attr_accessor :external_id
attr_accessor :search_joins
diff --git a/lib/netsuite/records/accounting_period.rb b/lib/netsuite/records/accounting_period.rb
index 5dd04b346..afae5305f 100644
--- a/lib/netsuite/records/accounting_period.rb
+++ b/lib/netsuite/records/accounting_period.rb
@@ -8,7 +8,7 @@ class AccountingPeriod
actions :get, :get_list, :add, :delete, :upsert, :search
- fields :allow_non_gl_changes, :end_date, :is_adjust, :is_quarter, :is_year, :period_name, :start_date
+ fields :allow_non_gl_changes, :end_date, :is_adjust, :is_quarter, :is_year, :period_name, :start_date, :all_locked, :ap_locked, :ar_locked, :closed, :closed_on_date, :payroll_locked
record_refs :parent
diff --git a/lib/netsuite/records/attach_basic_reference.rb b/lib/netsuite/records/attach_basic_reference.rb
new file mode 100644
index 000000000..f52d2569d
--- /dev/null
+++ b/lib/netsuite/records/attach_basic_reference.rb
@@ -0,0 +1,20 @@
+module NetSuite
+ module Records
+ class AttachBasicReference
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Support::Actions
+ include Namespaces::PlatformCore
+
+ actions :attach
+
+ record_refs :attach_to, :attached_record
+
+ def initialize(attributes = {})
+ initialize_from_attributes_hash(attributes)
+ end
+
+ end
+ end
+end
diff --git a/lib/netsuite/records/base_ref_list.rb b/lib/netsuite/records/base_ref_list.rb
index c7d56a155..2212c9fcc 100644
--- a/lib/netsuite/records/base_ref_list.rb
+++ b/lib/netsuite/records/base_ref_list.rb
@@ -8,6 +8,8 @@ class BaseRefList < Support::Sublist
actions :get_select_value
+ fields :total_pages, :total_records, :status
+
sublist :base_ref, RecordRef
alias :base_refs :base_ref
diff --git a/lib/netsuite/records/contact.rb b/lib/netsuite/records/contact.rb
index 2934453f0..eb963ddb0 100644
--- a/lib/netsuite/records/contact.rb
+++ b/lib/netsuite/records/contact.rb
@@ -17,6 +17,7 @@ class Contact
field :custom_field_list, CustomFieldList
# field :subscriptions_list, SubscriptionsList
# field :category_list, CategoryList
+ field :null_field_list, NullFieldList
read_only_fields :last_modified_date, :date_created
diff --git a/lib/netsuite/records/credit_memo.rb b/lib/netsuite/records/credit_memo.rb
index b9999c869..444109af1 100644
--- a/lib/netsuite/records/credit_memo.rb
+++ b/lib/netsuite/records/credit_memo.rb
@@ -23,6 +23,7 @@ class CreditMemo
field :item_list, CreditMemoItemList
field :apply_list, CreditMemoApplyList
field :ship_group_list, SalesOrderShipGroupList
+ field :null_field_list, NullFieldList
# field :bill_address_list,
field :transaction_bill_address, BillAddress
diff --git a/lib/netsuite/records/currency.rb b/lib/netsuite/records/currency.rb
index 53b58800b..d83f1bb34 100644
--- a/lib/netsuite/records/currency.rb
+++ b/lib/netsuite/records/currency.rb
@@ -10,7 +10,7 @@ class Currency
# https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2014_1/schema/record/currency.html
- actions :get, :get_list, :get_all, :add, :update, :upsert, :upsert_list, :delete
+ actions :get, :get_list, :get_all, :add, :update, :upsert, :upsert_list, :delete, :search
fields :name, :symbol, :is_base_currency, :is_inactive, :override_currency_format, :display_symbol, :symbol_placement,
:locale, :formatSample, :exchangeRate, :fx_rate_update_timezone, :incl_in_fx_rate_updates, :currency_precision
diff --git a/lib/netsuite/records/custom_field_list.rb b/lib/netsuite/records/custom_field_list.rb
index 81a7cc555..8e6150956 100644
--- a/lib/netsuite/records/custom_field_list.rb
+++ b/lib/netsuite/records/custom_field_list.rb
@@ -113,12 +113,15 @@ def extract_custom_field(custom_field_data)
if type == "platformCore:SelectCustomFieldRef"
attrs[:value] = CustomRecordRef.new(custom_field_data[:value])
elsif type == 'platformCore:MultiSelectCustomFieldRef'
- attrs[:value] = custom_field_data[:value].map do |entry|
- CustomRecordRef.new(entry)
+ # if only one value of multiselect is selected it will be a hash, not an array
+ if attrs[:value].is_a?(Array)
+ attrs[:value] = custom_field_data[:value].map { |entry| CustomRecordRef.new(entry) }
+ else
+ attrs[:value] = CustomRecordRef.new(custom_field_data[:value])
end
end
-
custom_fields << CustomField.new(attrs)
+
end
end
diff --git a/lib/netsuite/records/custom_record_type.rb b/lib/netsuite/records/custom_record_type.rb
index 355ca73f3..fb0581d75 100644
--- a/lib/netsuite/records/custom_record_type.rb
+++ b/lib/netsuite/records/custom_record_type.rb
@@ -15,6 +15,9 @@ class CustomRecordType
:show_owner_on_list, :use_permissions
record_ref :owner
+ record_ref :forms_list
+ record_ref :permissions_list
+ record_ref :custom_field_list
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/custom_segment.rb b/lib/netsuite/records/custom_segment.rb
new file mode 100644
index 000000000..4ea828008
--- /dev/null
+++ b/lib/netsuite/records/custom_segment.rb
@@ -0,0 +1,33 @@
+module NetSuite
+ module Records
+ class CustomSegment
+ include Support::Fields
+ include Support::Records
+ include Support::Actions
+ include Namespaces::SetupCustom
+
+ actions :get, :get_list
+
+ # https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2021_2/schema/record/customsegment.html
+ fields :label,
+ :script_id,
+ :record_script_id,
+ :record_type,
+ :field_type,
+ :is_inactive,
+ :show_in_list,
+ :has_lg_impact,
+ :is_mandatory
+
+
+ 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/lib/netsuite/records/customer.rb b/lib/netsuite/records/customer.rb
index 055088713..b08ee4a85 100644
--- a/lib/netsuite/records/customer.rb
+++ b/lib/netsuite/records/customer.rb
@@ -35,6 +35,7 @@ class Customer
field :partners_list, CustomerPartnersList
field :subscriptions_list, CustomerSubscriptionsList
field :sales_team_list, CustomerSalesTeamList
+ field :null_field_list, NullFieldList
read_only_fields :balance, :consol_balance, :deposit_balance, :consol_deposit_balance, :overdue_balance,
:consol_overdue_balance, :unbilled_orders, :consol_unbilled_orders
diff --git a/lib/netsuite/records/customer_payment.rb b/lib/netsuite/records/customer_payment.rb
index 9baab81fc..7ff356040 100644
--- a/lib/netsuite/records/customer_payment.rb
+++ b/lib/netsuite/records/customer_payment.rb
@@ -17,6 +17,7 @@ class CustomerPayment
field :custom_field_list, CustomFieldList
field :apply_list, CustomerPaymentApplyList
+ field :null_field_list, NullFieldList
read_only_fields :applied, :balance, :pending, :total, :unapplied
diff --git a/lib/netsuite/records/department.rb b/lib/netsuite/records/department.rb
index 1c09659a8..c09d1a6d7 100644
--- a/lib/netsuite/records/department.rb
+++ b/lib/netsuite/records/department.rb
@@ -1,3 +1,5 @@
+#https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2021_2/schema/record/department.html
+
module NetSuite
module Records
class Department
@@ -9,9 +11,10 @@ class Department
actions :get, :get_list, :get_select_value, :add, :delete, :upsert,
:search, :update
- fields :name, :is_inactive
+ fields :name, :is_inactive, :include_children
+ field :custom_field_list, CustomFieldList
- record_refs :parent
+ record_refs :parent, :subsidiary_list
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/employee.rb b/lib/netsuite/records/employee.rb
index 5b59cbc71..8933b230e 100644
--- a/lib/netsuite/records/employee.rb
+++ b/lib/netsuite/records/employee.rb
@@ -17,12 +17,14 @@ class Employee
:bill_pay, :comments, :date_created, :direct_deposit, :entity_id, :password, :password2,
:expense_limit, :fax, :is_job_resource, :job_description, :labor_cost, :last_modified_date, :mobile_phone, :pay_frequency,
: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
+ :resident_status, :salutation, :social_security_number, :visa_exp_date, :visa_type, :default_address
- record_refs :currency, :department, :location, :subsidiary, :employee_type, :employee_status, :supervisor
+ record_refs :currency, :department, :location, :subsidiary, :employee_type, :employee_status, :supervisor, :klass
field :custom_field_list, CustomFieldList
+ field :addressbook_list, EmployeeAddressbookList
field :roles_list, RoleList
+ field :null_field_list, NullFieldList
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/employee_addressbook.rb b/lib/netsuite/records/employee_addressbook.rb
new file mode 100644
index 000000000..1baa25feb
--- /dev/null
+++ b/lib/netsuite/records/employee_addressbook.rb
@@ -0,0 +1,65 @@
+module NetSuite
+ module Records
+ class EmployeeAddressbook
+ include Support::Fields
+ include Support::Records
+ include Namespaces::ListEmp
+
+ # address implementation changed
+ # https://github.com/NetSweet/netsuite/pull/213
+
+ # https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2015_1/schema/other/customeraddressbook.html?mode=package
+
+ fields :default_shipping, :default_billing, :is_residential, :label, :internal_id
+
+ # NOTE API < 2014_2
+ fields :attention, :addressee, :phone, :addr1, :addr2, :addr3, :city, :zip, :override, :state
+ field :country, NetSuite::Support::Country
+ read_only_fields :addr_text
+
+ # NOTE API >= 2014_2
+ field :addressbook_address, NetSuite::Records::Address
+
+ def initialize(attributes_or_record = {})
+ case attributes_or_record
+ when self.class
+ initialize_from_record(attributes_or_record)
+ when Hash
+ attributes_or_record = attributes_or_record[:addressbook] if attributes_or_record[:addressbook]
+ initialize_from_attributes_hash(attributes_or_record)
+ end
+ end
+
+ def initialize_from_record(obj)
+ if NetSuite::Configuration.api_version < "2014_2"
+ self.default_shipping = obj.default_shipping
+ self.default_billing = obj.default_billing
+ self.is_residential = obj.is_residential
+ self.label = obj.label
+ self.attention = obj.attention
+ self.addressee = obj.addressee
+ self.phone = obj.phone
+ self.addr1 = obj.addr1
+ self.addr2 = obj.addr2
+ self.addr3 = obj.addr3
+ self.city = obj.city
+ self.zip = obj.zip
+ self.country = obj.country
+ self.addr_text = obj.addr_text
+ self.override = obj.override
+ self.state = obj.state
+ self.internal_id = obj.internal_id
+ else
+ self.addressbook_address = obj.addressbook_address
+ self.default_billing = obj.default_billing
+ self.default_shipping = obj.default_shipping
+ self.internal_id = obj.internal_id
+ self.is_residential = obj.is_residential
+ self.label = obj.label
+ end
+ end
+
+ end
+ end
+ end
+
\ No newline at end of file
diff --git a/lib/netsuite/records/employee_addressbook_list.rb b/lib/netsuite/records/employee_addressbook_list.rb
new file mode 100644
index 000000000..2ef2f3036
--- /dev/null
+++ b/lib/netsuite/records/employee_addressbook_list.rb
@@ -0,0 +1,12 @@
+module NetSuite
+ module Records
+ class EmployeeAddressbookList < Support::Sublist
+ include Namespaces::ListEmp
+
+ sublist :addressbook, EmployeeAddressbook
+
+ alias :addressbooks :addressbook
+ end
+ end
+ end
+
\ No newline at end of file
diff --git a/lib/netsuite/records/expense_category.rb b/lib/netsuite/records/expense_category.rb
new file mode 100644
index 000000000..39ecd9149
--- /dev/null
+++ b/lib/netsuite/records/expense_category.rb
@@ -0,0 +1,40 @@
+module NetSuite
+ module Records
+ class ExpenseCategory
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Support::Actions
+ include Namespaces::ListAcct
+
+ actions :add, :get, :search, :update
+
+ fields :default_rate, :description, :is_inactive, :name, :rate_required
+
+ field :custom_field_list, CustomFieldList
+ field :rates_list, ExpenseCategoryRatesList
+
+ record_refs :custom_form, :expense_acct
+
+ 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
+
+ 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/expense_category_rate.rb b/lib/netsuite/records/expense_category_rate.rb
new file mode 100644
index 000000000..7169029aa
--- /dev/null
+++ b/lib/netsuite/records/expense_category_rate.rb
@@ -0,0 +1,28 @@
+module NetSuite
+ module Records
+ class ExpenseCategoryRate
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Namespaces::ListAcct
+
+ fields :currency, :default_rate
+
+ record_refs :subsidiary
+
+ 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
+
+ end
+ end
+end
diff --git a/lib/netsuite/records/expense_category_rates_list.rb b/lib/netsuite/records/expense_category_rates_list.rb
new file mode 100644
index 000000000..fbcaf2e7e
--- /dev/null
+++ b/lib/netsuite/records/expense_category_rates_list.rb
@@ -0,0 +1,9 @@
+module NetSuite
+ module Records
+ class ExpenseCategoryRatesList < Support::Sublist
+ include Namespaces::ListAcct
+
+ sublist :expense_category, ExpenseCategoryRate
+ end
+ end
+end
diff --git a/lib/netsuite/records/expense_report.rb b/lib/netsuite/records/expense_report.rb
new file mode 100644
index 000000000..de2c56db4
--- /dev/null
+++ b/lib/netsuite/records/expense_report.rb
@@ -0,0 +1,50 @@
+module NetSuite
+ module Records
+ class ExpenseReport
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Support::Actions
+ include Namespaces::TranEmp
+
+ actions :add, :get, :get_list, :search, :update
+
+ fields :accounting_approval, :advance, :amount, :complete, :created_date, :due_date, :last_modified_date, :memo, :status, :supervisor_approval, :tax1_amt, :tax2_amt, :total, :tran_date, :tran_id, :use_multi_currency
+
+ # todo
+ # field :accounting_book_detail_list, AccountingBookDetailList
+ field :custom_field_list, CustomFieldList
+ field :expense_list, ExpenseReportExpenseList
+ field :null_field_list, NullFieldList
+
+ record_refs :account, :approval_status, :klass, :custom_form, :department, :entity, :location, :next_approver, :posting_period, :subsidiary
+
+ 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
+
+ def to_record
+ rec = super
+ if rec["#{record_namespace}:customFieldList"]
+ rec["#{record_namespace}:customFieldList!"] = rec.delete("#{record_namespace}:customFieldList")
+ end
+ rec
+ end
+
+ def self.search_class_name
+ "Transaction"
+ end
+
+ def self.search_class_namespace
+ 'tranEmp'
+ end
+
+ end
+ end
+end
diff --git a/lib/netsuite/records/expense_report_expense.rb b/lib/netsuite/records/expense_report_expense.rb
new file mode 100644
index 000000000..26a6be350
--- /dev/null
+++ b/lib/netsuite/records/expense_report_expense.rb
@@ -0,0 +1,38 @@
+module NetSuite
+ module Records
+ class ExpenseReportExpense
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Namespaces::TranEmp
+
+ fields :amount, :exchange_rate, :expense_date, :foreign_amount, :gross_amt, :is_billable, :is_non_reimburable, :line, :memo, :quantity, :rate, :receipt, :ref_number, :tax1_amt, :tax_rate1, :tax_rate2
+
+ field :custom_field_list, CustomFieldList
+
+ record_refs :category, :klass, :currency, :customer, :department, :location, :tax_code, :exp_media_item
+
+ 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/expense_report_expense_list.rb b/lib/netsuite/records/expense_report_expense_list.rb
new file mode 100644
index 000000000..f875f3753
--- /dev/null
+++ b/lib/netsuite/records/expense_report_expense_list.rb
@@ -0,0 +1,9 @@
+module NetSuite
+ module Records
+ class ExpenseReportExpenseList < Support::Sublist
+ include Namespaces::TranEmp
+
+ sublist :expense, ExpenseReportExpense
+ end
+ end
+end
diff --git a/lib/netsuite/records/gift_certificate_item.rb b/lib/netsuite/records/gift_certificate_item.rb
index beb52f11b..cffa66a64 100644
--- a/lib/netsuite/records/gift_certificate_item.rb
+++ b/lib/netsuite/records/gift_certificate_item.rb
@@ -64,7 +64,7 @@ class GiftCertificateItem
field :custom_field_list, CustomFieldList
record_refs :klass, :custom_form, :department, :income_account,
- :issue_product, :location, :parent, :sales_tax_code, :tax_schedule
+ :issue_product, :liability_account, :location, :parent, :sales_tax_code, :tax_schedule
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/inventory_adjustment.rb b/lib/netsuite/records/inventory_adjustment.rb
index 74cc61a5b..97fc54686 100644
--- a/lib/netsuite/records/inventory_adjustment.rb
+++ b/lib/netsuite/records/inventory_adjustment.rb
@@ -13,6 +13,7 @@ class InventoryAdjustment
field :inventory_list, InventoryAdjustmentInventoryList
field :custom_field_list, CustomFieldList
+ field :null_field_list, NullFieldList
record_refs :account, :adj_location, :customer, :posting_period, :location, :department,
:subsidiary, :custom_form, :klass
diff --git a/lib/netsuite/records/invoice.rb b/lib/netsuite/records/invoice.rb
index 99ea37250..743632c61 100644
--- a/lib/netsuite/records/invoice.rb
+++ b/lib/netsuite/records/invoice.rb
@@ -38,6 +38,7 @@ class Invoice
field :custom_field_list, CustomFieldList
field :shipping_address, Address
field :billing_address, Address
+ field :null_field_list, NullFieldList
read_only_fields :sub_total, :discount_total, :total, :recognized_revenue, :amount_remaining, :amount_paid,
:alt_shipping_cost, :gift_cert_applied, :handling_cost, :alt_handling_cost
diff --git a/lib/netsuite/records/item.rb b/lib/netsuite/records/item.rb
new file mode 100644
index 000000000..1e4e93b5d
--- /dev/null
+++ b/lib/netsuite/records/item.rb
@@ -0,0 +1,45 @@
+module NetSuite
+ module Records
+ class Item
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Support::Actions
+ include Namespaces::ListAcct
+
+ actions :search
+
+ fields :created_date,
+ :last_modified_date,
+ :purchase_description,
+ :expense_account,
+ :item_id,
+ :display_name,
+ :include_children,
+ :is_inactive,
+ :tax_schedule,
+ :deferral_account,
+ :is_fulfillable,
+ :generate_accruals,
+ :currency
+
+ field :custom_field_list, CustomFieldList
+
+ 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)
+ @xsi_type = attributes.delete(:"xsi:type") || attributes.delete(:"@xsi:type")
+ @item_type = determin_item_type(attributes) if @xsi_type
+ initialize_from_attributes_hash(attributes)
+ end
+
+ def determin_item_type(attributes)
+ @xsi_type.split(":").last
+ end
+
+ end
+ end
+end
diff --git a/lib/netsuite/records/item_fulfillment.rb b/lib/netsuite/records/item_fulfillment.rb
index fd5e691d3..8e892bbf5 100644
--- a/lib/netsuite/records/item_fulfillment.rb
+++ b/lib/netsuite/records/item_fulfillment.rb
@@ -27,6 +27,7 @@ class ItemFulfillment
field :item_list, ItemFulfillmentItemList
field :package_list, ItemFulfillmentPackageList
field :custom_field_list, CustomFieldList
+ field :null_field_list, NullFieldList
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/item_option_custom_field.rb b/lib/netsuite/records/item_option_custom_field.rb
new file mode 100644
index 000000000..97631ce95
--- /dev/null
+++ b/lib/netsuite/records/item_option_custom_field.rb
@@ -0,0 +1,52 @@
+module NetSuite
+ module Records
+ class ItemOptionCustomField
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Support::Actions
+ include Namespaces::SetupCustom
+
+ actions :get, :get_list, :add, :delete, :update, :upsert, :upsert_list
+
+ # http://www.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2017_1/schema/record/ItemOptionCustomField.html
+ fields(
+ :access_level,
+ :col_all_items,
+ :col_kit_item,
+ :col_opportunity,
+ :col_option_label,
+ :col_purchase,
+ :col_sale,
+ :col_store,
+ :col_store_hidden,
+ :col_transfer_order,
+ :default_checked,
+ :default_value,
+ :description,
+ :display_height,
+ :display_width,
+ :help,
+ :is_formula,
+ :is_mandatory,
+ :label,
+ :link_text,
+ :max_length,
+ :max_value,
+ :min_value,
+ :store_value
+ )
+
+ record_refs :owner, :source_list, :select_record_type, :source_filter_by, :source_from, :search_default, :search_compare_field, :insert_before, :default_selection
+
+ 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/lib/netsuite/records/item_receipt.rb b/lib/netsuite/records/item_receipt.rb
index 798b2b70c..ff5b0a577 100644
--- a/lib/netsuite/records/item_receipt.rb
+++ b/lib/netsuite/records/item_receipt.rb
@@ -22,7 +22,9 @@ class ItemReceipt
# :landed_costs_list
field :item_list, ItemReceiptItemList
+ field :expense_list, ItemReceiptExpenseList
field :custom_field_list, CustomFieldList
+ field :null_field_list, NullFieldList
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/item_receipt_expense.rb b/lib/netsuite/records/item_receipt_expense.rb
new file mode 100644
index 000000000..a5633fe80
--- /dev/null
+++ b/lib/netsuite/records/item_receipt_expense.rb
@@ -0,0 +1,36 @@
+module NetSuite
+ module Records
+ class ItemReceiptExpense
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Namespaces::TranPurch
+
+ fields :account, :amount, :line, :mark_received, :memo, :order_line
+
+ field :options, CustomFieldList
+ field :custom_field_list, CustomFieldList
+
+ 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/item_receipt_expense_list.rb b/lib/netsuite/records/item_receipt_expense_list.rb
new file mode 100644
index 000000000..74ed27909
--- /dev/null
+++ b/lib/netsuite/records/item_receipt_expense_list.rb
@@ -0,0 +1,11 @@
+module NetSuite
+ module Records
+ class ItemReceiptExpenseList < Support::Sublist
+ include Namespaces::TranPurch
+
+ sublist :expense, ItemReceiptExpense
+
+ alias :expenses :expense
+ end
+ end
+end
diff --git a/lib/netsuite/records/journal_entry.rb b/lib/netsuite/records/journal_entry.rb
index 30504e445..722662bad 100644
--- a/lib/netsuite/records/journal_entry.rb
+++ b/lib/netsuite/records/journal_entry.rb
@@ -7,16 +7,17 @@ class JournalEntry
include Support::Actions
include Namespaces::TranGeneral
- actions :get, :get_list, :add, :delete, :search, :upsert
+ actions :get, :get_list, :add, :async_add_list, :delete, :search, :upsert, :update
fields :approved, :created_date, :exchange_rate, :last_modified_date, :memo, :reversal_date, :reversal_defer, :reversal_entry,
- :tran_date, :tran_id
+ :tran_date, :tran_id, :is_book_specific
field :custom_field_list, CustomFieldList
field :line_list, JournalEntryLineList
+ field :null_field_list, NullFieldList
record_refs :created_from, :currency, :custom_form, :department, :klass, :location, :parent_expense_alloc,
- :posting_period, :subsidiary, :to_subsidiary
+ :posting_period, :subsidiary, :to_subsidiary, :accounting_book
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/journal_entry_line.rb b/lib/netsuite/records/journal_entry_line.rb
index a8dbe1c2d..764c1f9e2 100644
--- a/lib/netsuite/records/journal_entry_line.rb
+++ b/lib/netsuite/records/journal_entry_line.rb
@@ -6,9 +6,11 @@ class JournalEntryLine
include Support::Records
include Namespaces::TranGeneral
- fields :credit, :debit, :eliminate, :end_date, :gross_amt, :memo, :residual, :start_date, :tax1_amt, :tax_rate1
+ fields :credit, :debit, :eliminate, :end_date, :gross_amt, :memo, :residual, :start_date, :tax1_amt, :tax_rate1, :line
+
field :custom_field_list, CustomFieldList
- record_refs :account, :department, :entity, :klass, :location, :schedule, :schedule_num, :tax1_acct, :tax_code
+
+ record_refs :account, :department, :entity, :klass, :location, :schedule, :schedule_num, :tax1_acct, :tax_code, :revenue_recognition_rule
def initialize(attributes = {})
initialize_from_attributes_hash(attributes)
diff --git a/lib/netsuite/records/lot_numbered_assembly_item.rb b/lib/netsuite/records/lot_numbered_assembly_item.rb
index 67c7d07f3..4623a404b 100644
--- a/lib/netsuite/records/lot_numbered_assembly_item.rb
+++ b/lib/netsuite/records/lot_numbered_assembly_item.rb
@@ -34,7 +34,7 @@ class LotNumberedAssemblyItem
record_refs :alternate_demand_source_item, :asset_account, :bill_exch_rate_variance_acct, :bill_price_variance_acct,
:bill_qty_variance_acct, :billing_schedule, :cogs_account, :cost_category, :custom_form, :deferred_revenue_account,
- :demand_source, :department, :expense_account, :gain_loss_account, :income_account, :issue_product, :klass, :location,
+ :demand_source, :department, :dropship_expense_account, :gain_loss_account, :income_account, :issue_product, :klass, :location,
:parent, :preferred_location, :pricing_group, :purchase_price_variance_acct, :purchase_tax_code, :purchase_unit,
:quantity_pricing_schedule, :rev_rec_schedule, :sale_unit, :sales_tax_code, :ship_package, :soft_descriptor,
:stock_unit, :store_display_image, :store_display_thumbnail, :store_item_template, :supply_lot_sizing_method,
diff --git a/lib/netsuite/records/non_inventory_purchase_item.rb b/lib/netsuite/records/non_inventory_purchase_item.rb
index f321910aa..dcd76c8be 100644
--- a/lib/netsuite/records/non_inventory_purchase_item.rb
+++ b/lib/netsuite/records/non_inventory_purchase_item.rb
@@ -25,7 +25,7 @@ class NonInventoryPurchaseItem
:translations_list, :upc_code, :url_component, :use_marginal_rates, :vsoe_deferral, :vsoe_delivered,
:vsoe_permit_discount, :vsoe_price, :weight, :weight_unit, :weight_units
- record_refs :billing_schedule, :cost_category, :custom_form, :deferred_revenue_account, :department, :income_account,
+ record_refs :billing_schedule, :cost_category, :custom_form, :deferred_revenue_account, :department, :expense_account, :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
diff --git a/lib/netsuite/records/non_inventory_resale_item.rb b/lib/netsuite/records/non_inventory_resale_item.rb
index 0628c8c6a..cbfab0f9e 100644
--- a/lib/netsuite/records/non_inventory_resale_item.rb
+++ b/lib/netsuite/records/non_inventory_resale_item.rb
@@ -25,7 +25,7 @@ class NonInventoryResaleItem
:translations_list, :upc_code, :url_component, :use_marginal_rates, :vsoe_deferral, :vsoe_delivered,
:vsoe_permit_discount, :vsoe_price, :weight, :weight_unit, :weight_units
- record_refs :billing_schedule, :cost_category, :custom_form, :deferred_revenue_account, :department, :income_account,
+ record_refs :billing_schedule, :cost_category, :custom_form, :deferred_revenue_account, :department, :expense_account, :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
diff --git a/lib/netsuite/records/null_field_list.rb b/lib/netsuite/records/null_field_list.rb
new file mode 100644
index 000000000..c970a4904
--- /dev/null
+++ b/lib/netsuite/records/null_field_list.rb
@@ -0,0 +1,18 @@
+module NetSuite
+ module Records
+ class NullFieldList
+ include Support::Fields
+ include Support::Records
+ include Namespaces::PlatformCore
+
+ attr_accessor :type
+
+ fields :name
+
+ def initialize(attributes={})
+ initialize_from_attributes_hash(attributes)
+ end
+
+ end
+ end
+end
diff --git a/lib/netsuite/records/other_custom_field.rb b/lib/netsuite/records/other_custom_field.rb
new file mode 100644
index 000000000..1ad991c5b
--- /dev/null
+++ b/lib/netsuite/records/other_custom_field.rb
@@ -0,0 +1,67 @@
+module NetSuite
+ module Records
+ class OtherCustomField
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Support::Actions
+ include Namespaces::SetupCustom
+
+ actions :add, :delete, :delete_list, :get, :get_list, :get_select_value, :update, :update_list, :upsert, :upsert_list
+
+ # https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2021_2/schema/record/othercustomfield.html
+
+
+ fields :access_level,
+ :check_spelling,
+ :custom_Ssgment,
+ :default_checked,
+ :default_selection,
+ :default_value,
+ :dept_access_list,
+ :description,
+ :display_height,
+ :display_type,
+ :display_width,
+ :dynamic_default,
+ :filter_list,
+ :help,
+ :insert_before,
+ :is_formula,
+ :is_mandatory,
+ :label,
+ :link_text,
+ :max_length,
+ :max_value,
+ :min_value,
+ :role_access_list,
+ :search_level,
+ :show_in_list,
+ :store_value,
+ :sub_access_list,
+ :translations_list
+
+
+ record_refs :owner,
+ :rec_type,
+ :search_compare_field,
+ :search_default,
+ :select_record_type,
+ :source_filter_by,
+ :source_from,
+ :source_list
+
+
+ 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/lib/netsuite/records/purchase_order.rb b/lib/netsuite/records/purchase_order.rb
index fe5c0d12d..ff880987f 100644
--- a/lib/netsuite/records/purchase_order.rb
+++ b/lib/netsuite/records/purchase_order.rb
@@ -20,6 +20,8 @@ class PurchaseOrder
field :shipping_address, Address
field :custom_field_list, CustomFieldList
field :item_list, PurchaseOrderItemList
+ field :expense_list, PurchaseOrderExpenseList
+ field :null_field_list, NullFieldList
# TODO custom lists
# :ship_address_list
diff --git a/lib/netsuite/records/purchase_order_expense.rb b/lib/netsuite/records/purchase_order_expense.rb
new file mode 100644
index 000000000..d04be63c9
--- /dev/null
+++ b/lib/netsuite/records/purchase_order_expense.rb
@@ -0,0 +1,37 @@
+module NetSuite
+ module Records
+ class PurchaseOrderExpense
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Namespaces::TranPurch
+
+ fields :amount, :gross_amt, :is_billable, :is_closed, :line, :memo, :tax1_amt, :tax_amount, :tax_details_reference, :tax_rate1, :tax_rate2
+
+ field :custom_field_list, CustomFieldList
+
+ record_refs :account, :category, :klass, :created_from, :customer, :department, :linked_order_list, :location, :tax_code
+
+ 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/purchase_order_expense_list.rb b/lib/netsuite/records/purchase_order_expense_list.rb
new file mode 100644
index 000000000..4411698aa
--- /dev/null
+++ b/lib/netsuite/records/purchase_order_expense_list.rb
@@ -0,0 +1,11 @@
+module NetSuite
+ module Records
+ class PurchaseOrderExpenseList < Support::Sublist
+ include Namespaces::TranPurch
+
+ sublist :expense, PurchaseOrderExpense
+
+ alias :expenses :expense
+ end
+ end
+end
diff --git a/lib/netsuite/records/sales_order.rb b/lib/netsuite/records/sales_order.rb
index 8b6059e47..53f09f8b2 100644
--- a/lib/netsuite/records/sales_order.rb
+++ b/lib/netsuite/records/sales_order.rb
@@ -18,7 +18,7 @@ class SalesOrder
:start_date, :status, :sync_partner_teams, :sync_sales_teams, :tax2_total, :tax_rate, :to_be_emailed, :to_be_faxed,
:to_be_printed, :total_cost_estimate, :tran_date, :tran_id, :tran_is_vsoe_bundle, :vat_reg_num,
:linked_tracking_numbers, :vsoe_auto_calc, :quantity, :bill_city, :bill_state, :ship_city, :ship_state, :cost_estimate,
- :amount, :is_ship_address, :auth_code, :pn_ref_num, :is_multi_ship_to
+ :amount, :is_ship_address, :auth_code, :pn_ref_num, :is_multi_ship_to, :interco_status
# NOTE API >= 2014_2 only
field :shipping_address, Address
@@ -33,13 +33,14 @@ class SalesOrder
field :gift_cert_redemption_list, GiftCertRedemptionList
field :ship_group_list, SalesOrderShipGroupList
field :promotions_list, PromotionsList
+ field :null_field_list, NullFieldList
read_only_fields :applied, :discount_total, :sub_total, :tax_total, :total, :unapplied,
:est_gross_profit_percent
record_refs :account, :bill_address_list, :created_from, :currency, :custom_form, :department, :discount_item, :entity, :gift_cert,
:handling_tax_code, :job, :klass, :lead_source, :location, :message_sel, :opportunity, :partner, :posting_period, :promo_code,
- :sales_group, :sales_rep, :ship_method, :shipping_tax_code, :subsidiary, :terms, :tax_item, :payment_method, :ship_address_list
+ :sales_group, :sales_rep, :ship_method, :shipping_tax_code, :subsidiary, :terms, :tax_item, :payment_method, :ship_address_list, :interco_transaction
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/sales_order_item.rb b/lib/netsuite/records/sales_order_item.rb
index 0b659a5ea..9a5d4661d 100644
--- a/lib/netsuite/records/sales_order_item.rb
+++ b/lib/netsuite/records/sales_order_item.rb
@@ -21,7 +21,7 @@ class SalesOrderItem
field :custom_field_list, CustomFieldList
- record_refs :department, :item, :job, :klass, :location, :price, :rev_rec_schedule, :tax_code, :units
+ record_refs :department, :item, :job, :klass, :location, :price, :rev_rec_schedule, :tax_code, :units, :inventory_location, :inventory_subsidiary
def initialize(attributes_or_record = {})
case attributes_or_record
diff --git a/lib/netsuite/records/service_purchase_item.rb b/lib/netsuite/records/service_purchase_item.rb
new file mode 100644
index 000000000..fab343de1
--- /dev/null
+++ b/lib/netsuite/records/service_purchase_item.rb
@@ -0,0 +1,34 @@
+module NetSuite
+ module Records
+ class ServicePurchaseItem
+ include Support::Fields
+ include Support::RecordRefs
+ include Support::Records
+ include Support::Actions
+ include Namespaces::ListAcct
+
+ actions :get, :get_list, :add, :delete, :search, :upsert
+
+ fields :amortization_period, :available_to_partners, :cost, :cost_units, :created_date, :currency, :display_name, :generate_accrurals, :include_children, :is_fulfillable, :is_inactive, :is_taxable, :item_id, :last_modified_date, :manufacturing_charge_item, :purchase_description, :purchase_order_amount, :purchase_order_quantity, :purchase_order_quantity_diff, :receipt_amount, :receipt_quantity, :receipt_quantity_diff, :residual, :upc_code, :vendor_name
+
+ record_refs :klass, :cost_category, :custom_form, :department, :expense_account, :issue_product, :location, :parent, :purchase_tax_code, :sales_tax_code, :tax_schedule, :units_type
+
+ field :custom_field_list, CustomFieldList
+ field :subsidiary_list, RecordRefList
+
+
+ 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
+
+ def self.search_class_name
+ "Item"
+ end
+ end
+ end
+end
diff --git a/lib/netsuite/records/time_bill.rb b/lib/netsuite/records/time_bill.rb
index bad36d2f3..ae0ed6174 100644
--- a/lib/netsuite/records/time_bill.rb
+++ b/lib/netsuite/records/time_bill.rb
@@ -14,6 +14,7 @@ class TimeBill
field :custom_field_list, CustomFieldList
field :hours, Duration
+ field :null_field_list, NullFieldList
record_refs :employee, :customer, :case_task_event, :payroll_item, :workplace, :item, :department, :location, :price,
:subsidiary, :temporary_local_jurisdiction, :temporary_state_jurisdiction
diff --git a/lib/netsuite/records/transaction_column_custom_field.rb b/lib/netsuite/records/transaction_column_custom_field.rb
index 3f3b2eadd..4e2e7ae3b 100644
--- a/lib/netsuite/records/transaction_column_custom_field.rb
+++ b/lib/netsuite/records/transaction_column_custom_field.rb
@@ -7,7 +7,7 @@ class TransactionColumnCustomField
include Support::Actions
include Namespaces::SetupCustom
- actions :get, :get_list, :add, :delete, :update, :upsert, :upsert_list
+ actions :get, :get_list, :add, :delete, :update, :upsert, :upsert_list, :get_select_value
# http://www.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2017_1/schema/record/transactioncolumncustomfield.html
fields(
@@ -44,7 +44,7 @@ class TransactionColumnCustomField
:script_id
)
- record_refs :owner
+ record_refs :owner, :source_list, :select_record_type, :source_filter_by, :source_from, :custom_segment, :role_access_list
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/transfer_order.rb b/lib/netsuite/records/transfer_order.rb
index db43ce630..691734197 100644
--- a/lib/netsuite/records/transfer_order.rb
+++ b/lib/netsuite/records/transfer_order.rb
@@ -21,6 +21,7 @@ class TransferOrder
field :custom_field_list, CustomFieldList
field :item_list, TransferOrderItemList
+ field :null_field_list, NullFieldList
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/vendor.rb b/lib/netsuite/records/vendor.rb
index e1491c6b1..ac3546842 100644
--- a/lib/netsuite/records/vendor.rb
+++ b/lib/netsuite/records/vendor.rb
@@ -24,6 +24,7 @@ class Vendor
field :custom_field_list, CustomFieldList
# TODO should change name to VendorAddressBookList
field :addressbook_list, CustomerAddressbookList
+ field :null_field_list, NullFieldList
read_only_fields :balance_primary, :balance, :last_modified_date, :unbilled_orders,
:unbilled_orders_primary
diff --git a/lib/netsuite/records/vendor_bill.rb b/lib/netsuite/records/vendor_bill.rb
index cebd7de7e..8d3438ae7 100644
--- a/lib/netsuite/records/vendor_bill.rb
+++ b/lib/netsuite/records/vendor_bill.rb
@@ -17,6 +17,7 @@ class VendorBill
field :expense_list, VendorBillExpenseList
field :item_list, VendorBillItemList
field :purchase_order_list, RecordRefList
+ field :null_field_list, NullFieldList
read_only_fields :status
diff --git a/lib/netsuite/records/vendor_bill_item.rb b/lib/netsuite/records/vendor_bill_item.rb
index 40ec3daec..f890a7db0 100644
--- a/lib/netsuite/records/vendor_bill_item.rb
+++ b/lib/netsuite/records/vendor_bill_item.rb
@@ -7,13 +7,14 @@ class VendorBillItem
include Namespaces::TranPurch
fields :amortization_end_date, :amortization_residual, :amortiz_start_date, :bin_numbers, :bill_variance_status,
- :description, :expiration_date, :gross_amt, :inventory_detail, :is_billable, :landed_cost, :line,
+ :description, :expiration_date, :gross_amt, :is_billable, :landed_cost, :line,
:order_doc, :order_line, :quantity, :serial_numbers, :tax_rate_1, :tax_rate_2, :tax_1_amt, :vendor_name,
:rate
field :bill_receipts_list, RecordRefList
field :custom_field_list, CustomFieldList
field :options, CustomFieldList
+ field :inventory_detail, InventoryDetail
read_only_fields :amount
diff --git a/lib/netsuite/records/vendor_credit.rb b/lib/netsuite/records/vendor_credit.rb
index 02b844dd4..b33243554 100644
--- a/lib/netsuite/records/vendor_credit.rb
+++ b/lib/netsuite/records/vendor_credit.rb
@@ -15,22 +15,14 @@ class VendorCredit
:currency_name, :tran_date, :exchange_rate,
:memo
- field :custom_form, RecordRef
- field :account, RecordRef
- field :bill_address_list, RecordRef
- field :created_from, RecordRef
- field :entity, RecordRef
- field :currency, RecordRef
- field :posting_period, RecordRef
- field :department, RecordRef
- field :klass, RecordRef
- field :location, RecordRef
- field :subsidiary, RecordRef
+ record_refs :custom_form, :account, :bill_address_list, :created_from, :entity, :currency, :post_period, :department, :klass, :location, :subsidiary
+
field :billing_address, Address
field :expense_list, VendorCreditExpenseList
field :item_list, VendorCreditItemList
field :apply_list, VendorCreditApplyList
field :custom_field_list, CustomFieldList
+ field :null_field_list, NullFieldList
attr_reader :internal_id
attr_accessor :external_id
diff --git a/lib/netsuite/records/vendor_credit_expense.rb b/lib/netsuite/records/vendor_credit_expense.rb
index 6c2261b93..41929a551 100644
--- a/lib/netsuite/records/vendor_credit_expense.rb
+++ b/lib/netsuite/records/vendor_credit_expense.rb
@@ -2,6 +2,7 @@ module NetSuite
module Records
class VendorCreditExpense
include Support::Fields
+ include Support::RecordRefs
include Support::Records
include Namespaces::TranPurch
@@ -12,18 +13,29 @@ class VendorCreditExpense
:amortization_end_date,
:amortization_residual
- field :category, RecordRef
- field :taxCode, RecordRef
- field :account, RecordRef
- field :department, RecordRef
- field :klass, RecordRef
- field :amortizationSched, RecordRef
- field :location, RecordRef
- field :customer, RecordRef
field :custom_field_list, CustomFieldList
- def initialize(attributes = {})
- initialize_from_attributes_hash(attributes)
+ record_refs :account, :category, :customer, :department, :item, :location, :units, :tax_code, :klass
+
+ 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
diff --git a/lib/netsuite/records/vendor_credit_item.rb b/lib/netsuite/records/vendor_credit_item.rb
index 19f2a6920..df2bd5062 100644
--- a/lib/netsuite/records/vendor_credit_item.rb
+++ b/lib/netsuite/records/vendor_credit_item.rb
@@ -2,6 +2,7 @@ module NetSuite
module Records
class VendorCreditItem
include Support::Fields
+ include Support::RecordRefs
include Support::Records
include Namespaces::TranPurch
@@ -14,19 +15,32 @@ class VendorCreditItem
:amortization_end_date,
:amortization_residual
- field :item, RecordRef
- field :units, RecordRef
- field :department, RecordRef
- field :customer, RecordRef
- field :location, RecordRef
- field :tax_code, RecordRef
- field :serial_numbers_list, RecordRefList
- field :inventory_detail, InventoryDetail
- field :custom_field_list, CustomFieldList
- field :options, CustomFieldList
+ field :serial_numbers_list, RecordRefList
+ field :inventory_detail, InventoryDetail
+ field :custom_field_list, CustomFieldList
+ field :options, CustomFieldList
- def initialize(attributes = {})
- initialize_from_attributes_hash(attributes)
+ record_refs :item, :units, :department, :customer, :location, :tax_code, :klass
+
+ 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
diff --git a/lib/netsuite/records/vendor_payment.rb b/lib/netsuite/records/vendor_payment.rb
index dbf08cbf7..073f74517 100644
--- a/lib/netsuite/records/vendor_payment.rb
+++ b/lib/netsuite/records/vendor_payment.rb
@@ -16,6 +16,7 @@ class VendorPayment
field :apply_list, VendorPaymentApplyList
field :custom_field_list, CustomFieldList
+ field :null_field_list, NullFieldList
record_refs :account, :ap_acct, :currency, :custom_form, :department, :entity, :klass, :location, :posting_period,
:subsidiary, :void_journal
diff --git a/lib/netsuite/support/actions.rb b/lib/netsuite/support/actions.rb
index 19d62c552..47d2488ee 100644
--- a/lib/netsuite/support/actions.rb
+++ b/lib/netsuite/support/actions.rb
@@ -46,6 +46,10 @@ def action(name)
self.send(:include, NetSuite::Actions::UpdateList::Support)
when :initialize
self.send(:include, NetSuite::Actions::Initialize::Support)
+ when :attach
+ self.send(:include, NetSuite::Actions::Attach::Support)
+ when :async_add_list
+ self.send(:include, NetSuite::Actions::AsyncAddList::Support)
else
raise "Unknown action: #{name.inspect}"
end
diff --git a/lib/netsuite/support/attributes.rb b/lib/netsuite/support/attributes.rb
index 6d1e29825..52fd31e0d 100644
--- a/lib/netsuite/support/attributes.rb
+++ b/lib/netsuite/support/attributes.rb
@@ -14,7 +14,8 @@ def initialize_from_attributes_hash(attributes = {})
attributes.select { |k,v| self.class.fields.include?(k) }.each do |k,v|
send("#{k}=", v)
end
- self.klass = attributes[:class] if attributes[:class]
+
+ self.klass = attributes[:class] if attributes[:class] && self.respond_to?(:klass)
end
end
diff --git a/lib/netsuite/utilities.rb b/lib/netsuite/utilities.rb
index c0b0f2d91..9fa3b6b65 100644
--- a/lib/netsuite/utilities.rb
+++ b/lib/netsuite/utilities.rb
@@ -169,6 +169,7 @@ def get_item(ns_item_internal_id, opts = {})
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::NonInventoryResaleItem, ns_item_internal_id, opts)
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::DiscountItem, ns_item_internal_id, opts)
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::OtherChargeSaleItem, ns_item_internal_id, opts)
+ ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::ServicePurchaseItem, ns_item_internal_id, opts)
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::ServiceSaleItem, ns_item_internal_id, opts)
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::GiftCertificateItem, ns_item_internal_id, opts)
ns_item ||= NetSuite::Utilities.get_record(NetSuite::Records::KitItem, ns_item_internal_id, opts)
diff --git a/spec/netsuite/actions/async_add_list_spec.rb b/spec/netsuite/actions/async_add_list_spec.rb
new file mode 100644
index 000000000..e99752324
--- /dev/null
+++ b/spec/netsuite/actions/async_add_list_spec.rb
@@ -0,0 +1,113 @@
+require 'spec_helper'
+require 'lib/netsuite/actions/async_add_list'
+
+describe NetSuite::Actions::AsyncAddList do
+ before { savon.mock! }
+ after { savon.unmock! }
+
+ context 'Customers' do
+ context 'one customer' do
+ let(:customers) do
+ [
+ NetSuite::Records::Customer.new(external_id: 'ext2', entity_id: 'Target', company_name: 'Target')
+ ]
+ end
+
+ before do
+ savon.expects(:async_add_list).with(:message =>
+ {
+ 'record' => [{
+ 'listRel:entityId' => 'Target',
+ 'listRel:companyName' => 'Target',
+ '@xsi:type' => 'listRel:Customer',
+ '@externalId' => 'ext2'
+ }]
+ }).returns(File.read('spec/support/fixtures/async_add_list/async_add_list_response.xml'))
+ end
+
+ it 'makes a valid request to the NetSuite API' do
+ NetSuite::Actions::AsyncAddList.call(customers)
+ end
+
+ it 'returns a valid Response object' do
+ response = NetSuite::Actions::AsyncAddList.call(customers)
+ expect(response).to be_kind_of(NetSuite::Response)
+ expect(response).to be_success
+ end
+ end
+
+ context 'two customers' do
+ let(:customers) do
+ [
+ NetSuite::Records::Customer.new(external_id: 'ext1', entity_id: 'Shutter Fly', company_name: 'Shutter Fly, Inc.'),
+ NetSuite::Records::Customer.new(external_id: 'ext2', entity_id: 'Target', company_name: 'Target')
+ ]
+ end
+
+ before do
+ savon.expects(:async_add_list).with(:message =>
+ {
+ 'record' => [{
+ 'listRel:entityId' => 'Shutter Fly',
+ 'listRel:companyName' => 'Shutter Fly, Inc.',
+ '@xsi:type' => 'listRel:Customer',
+ '@externalId' => 'ext1'
+ },
+ {
+ 'listRel:entityId' => 'Target',
+ 'listRel:companyName' => 'Target',
+ '@xsi:type' => 'listRel:Customer',
+ '@externalId' => 'ext2'
+ }
+ ]
+ }).returns(File.read('spec/support/fixtures/async_add_list/async_add_list_response.xml'))
+ end
+
+ it 'makes a valid request to the NetSuite API' do
+ NetSuite::Actions::AsyncAddList.call(customers)
+ end
+
+ it 'returns a valid Response object' do
+ response = NetSuite::Actions::AsyncAddList.call(customers)
+ expect(response).to be_kind_of(NetSuite::Response)
+ expect(response).to be_success
+ end
+ end
+ end
+
+ context 'with errors' do
+ let(:customers) do
+ [
+ NetSuite::Records::Customer.new(external_id: 'ext1-bad', entity_id: 'Shutter Fly', company_name: 'Shutter Fly, Inc.'),
+ NetSuite::Records::Customer.new(external_id: 'ext2-bad', entity_id: 'Target', company_name: 'Target')
+ ]
+ end
+
+ # before do
+ # savon.expects(:async_add_list).with(:message =>
+ # {
+ # 'record' => [{
+ # 'listRel:entityId' => 'Shutter Fly',
+ # 'listRel:companyName' => 'Shutter Fly, Inc.',
+ # '@xsi:type' => 'listRel:Customer',
+ # '@externalId' => 'ext1-bad'
+ # },
+ # {
+ # 'listRel:entityId' => 'Target',
+ # 'listRel:companyName' => 'Target',
+ # '@xsi:type' => 'listRel:Customer',
+ # '@externalId' => 'ext2-bad'
+ # }
+ # ]
+ # }).returns(File.read('spec/support/fixtures/upsert_list/upsert_list_with_errors.xml'))
+ # end
+
+ # it 'constructs error objects' do
+ # response = NetSuite::Actions::AsyncAddList.call(customers)
+ # expect(response.errors.keys).to match_array(['ext1', 'ext2'])
+ # expect(response.errors['ext1'].first.code).to eq('USER_ERROR')
+ # expect(response.errors['ext1'].first.message).to eq('Please enter value(s) for: Item')
+ # expect(response.errors['ext1'].first.type).to eq('ERROR')
+ # end
+ end
+end
diff --git a/spec/netsuite/actions/attach_spec.rb b/spec/netsuite/actions/attach_spec.rb
new file mode 100644
index 000000000..79f01a9e0
--- /dev/null
+++ b/spec/netsuite/actions/attach_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe NetSuite::Actions::Attach do
+ before(:all) { savon.mock! }
+ after(:all) { savon.unmock! }
+
+ context "AttachBasicReference" do
+ let(:attach_basic_reference) do
+ NetSuite::Records::AttachBasicReference.new(:attach_to => {internal_id: "11111", type: "VendorBill"}, :attached_record => {internal_id: "22222", type: "File"})
+ end
+
+ before do
+ savon.expects(:attach).with(:message => {
+ 'platformMsgs:attachReferece' => {
+ :content! => {
+ :attributes! => {
+ "platformCore:attachTo" => {
+ "internalId"=>"11111",
+ "type"=>"vendorBill", "xsi:type"=>"platformCore:RecordRef"
+ },
+ "platformCore:attachedRecord"=> {
+ "internalId"=>"22222",
+ "type"=>"file",
+ "xsi:type"=>"platformCore:RecordRef"
+ }
+ },
+ "platformCore:attachTo"=>{},
+ "platformCore:attachedRecord"=>{}
+ },
+ "@xsi:type"=>"platformCore:AttachBasicReference"
+ }
+ }).returns(File.read('spec/support/fixtures/attach/attach_file_to_bill.xml'))
+ end
+
+
+ it "makes a valid request to the NetSuite API" do
+ NetSuite::Actions::Attach.call([attach_basic_reference])
+ end
+
+ it "returns a valid Response object" do
+ response = NetSuite::Actions::Attach.call([attach_basic_reference])
+ expect(response).to be_kind_of(NetSuite::Response)
+ expect(response).to be_success
+ end
+
+ context "when not successful" do
+ before do
+ savon.expects(:attach).with(:message => {
+ 'platformMsgs:attachReferece' => {
+ :content! => {
+ :attributes! => {
+ "platformCore:attachTo" => {
+ "internalId"=>"11111",
+ "type"=>"vendorBill", "xsi:type"=>"platformCore:RecordRef"
+ },
+ "platformCore:attachedRecord"=> {
+ "internalId"=>"22222",
+ "type"=>"file",
+ "xsi:type"=>"platformCore:RecordRef"
+ }
+ },
+ "platformCore:attachTo"=>{},
+ "platformCore:attachedRecord"=>{}
+ },
+ "@xsi:type"=>"platformCore:AttachBasicReference"
+ }
+ }).returns(File.read('spec/support/fixtures/attach/attach_file_to_bill_error.xml'))
+ end
+
+ it 'provides an error method on the object with details about the error' do
+ #i'm not sure why the only way to get the errors is to attach twice
+ attach_basic_reference.attach
+ attach_basic_reference.attach
+ error = attach_basic_reference.errors.first
+ expect(error).to be_kind_of(NetSuite::Error)
+ expect(error.type).to eq('ERROR')
+ expect(error.code).to eq('USER_ERROR')
+ expect(error.message).to eq('Invalid Attachment record combination')
+ end
+ end
+ end
+end
diff --git a/spec/netsuite/records/attach_basic_reference_spec.rb b/spec/netsuite/records/attach_basic_reference_spec.rb
new file mode 100644
index 000000000..72d2ff034
--- /dev/null
+++ b/spec/netsuite/records/attach_basic_reference_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe NetSuite::Records::AttachBasicReference do
+ let(:attach_basic_reference) { NetSuite::Records::AttachBasicReference.new }
+
+ it "has all the right record refs" do
+ [:attach_to, :attached_record].each do |record_ref|
+ expect(attach_basic_reference).to have_record_ref(record_ref)
+ end
+ end
+
+ describe '#attach' do
+ let(:test_data) { {:attach_to => {internal_id: "11111", type: "VendorBill"}, :attached_record => {internal_id: "22222", type: "File"} } }
+ context 'when the response is successful' do
+ let(:response) { NetSuite::Response.new(:success => true, :body => { :internal_id => '11111' }) }
+
+ it "returns true" do
+ attach_basic_reference = NetSuite::Records::AttachBasicReference.new(test_data)
+
+ expect(NetSuite::Actions::Attach).to receive(:call).with([attach_basic_reference], {}).and_return(response)
+ expect(attach_basic_reference.attach).to be_truthy
+ end
+ end
+
+ context 'when the response is unsuccessful' do
+ let(:response) { NetSuite::Response.new(:success => false, :body => {}) }
+
+ it 'returns false' do
+ attach_basic_reference = NetSuite::Records::AttachBasicReference.new(test_data)
+
+ expect(NetSuite::Actions::Attach).to receive(:call).with([attach_basic_reference], {}).and_return(response)
+ expect(attach_basic_reference.attach).to be_falsey
+ end
+ end
+
+ end
+
+end
diff --git a/spec/netsuite/records/expense_report_expense_list_spec.rb b/spec/netsuite/records/expense_report_expense_list_spec.rb
new file mode 100644
index 000000000..50fc6f209
--- /dev/null
+++ b/spec/netsuite/records/expense_report_expense_list_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe NetSuite::Records::ExpenseReportExpense do
+ let(:expense) { NetSuite::Records::ExpenseReportExpense.new }
+
+ it 'has all the right fields' do
+ [
+ :amount, :exchange_rate, :expense_date, :foreign_amount, :gross_amt, :is_billable, :is_non_reimburable,
+ :line, :memo, :quantity, :rate, :receipt, :ref_number, :tax1_amt, :tax_rate1, :tax_rate2
+ ].each do |field|
+ expect(expense).to have_field(field)
+ end
+ end
+
+ it 'has all the right record refs' do
+ [
+ :category, :klass, :currency, :customer, :department, :location, :tax_code, :exp_media_item
+ ].each do |record_ref|
+ expect(expense).to have_record_ref(record_ref)
+ end
+ end
+
+ describe '#custom_field_list' do
+ it 'can be set from attributes' do
+ attributes = {
+ :custom_field => {
+ :amount => 10,
+ :internal_id => 'custfield_amount'
+ }
+ }
+ expense.custom_field_list = attributes
+ expect(expense.custom_field_list).to be_kind_of(NetSuite::Records::CustomFieldList)
+ expect(expense.custom_field_list.custom_fields.length).to eql(1)
+ end
+
+ it 'can be set from a CustomFieldList object' do
+ custom_field_list = NetSuite::Records::CustomFieldList.new
+ expense.custom_field_list = custom_field_list
+ expect(expense.custom_field_list).to eql(custom_field_list)
+ end
+ end
+
+ describe '#to_record' do
+ let(:expense) { NetSuite::Records::ExpenseReportExpense.new(:memo => 'This is a memo', :is_billable => true) }
+
+ it 'returns a hash of attributes that can be used in a SOAP request' do
+ expect(expense.to_record).to eql({
+ 'tranEmp:memo' => 'This is a memo',
+ 'tranEmp:isBillable' => true
+ })
+ end
+ end
+
+ describe '#record_type' do
+ it 'returns a string type for the record to be used in a SOAP request' do
+ expect(expense.record_type).to eql('tranEmp:ExpenseReportExpense')
+ end
+ end
+
+end
diff --git a/spec/netsuite/records/expense_report_expense_spec.rb b/spec/netsuite/records/expense_report_expense_spec.rb
new file mode 100644
index 000000000..b8f76a97e
--- /dev/null
+++ b/spec/netsuite/records/expense_report_expense_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe NetSuite::Records::ExpenseReportExpenseList do
+ let(:list) { NetSuite::Records::ExpenseReportExpenseList.new }
+
+ it 'has a custom_fields attribute' do
+ expect(list.expense).to be_kind_of(Array)
+ end
+
+ describe '#to_record' do
+ before do
+ list.expense << NetSuite::Records::ExpenseReportExpense.new(:memo => 'This is a memo')
+ end
+
+ it 'can represent itself as a SOAP record' do
+ record =
+ {
+ 'tranEmp:expense' => [{
+ 'tranEmp:memo' => 'This is a memo'
+ }]
+ }
+ expect(list.to_record).to eql(record)
+ end
+ end
+
+end
diff --git a/spec/netsuite/records/expense_report_spec.rb b/spec/netsuite/records/expense_report_spec.rb
new file mode 100644
index 000000000..dfcc1d29b
--- /dev/null
+++ b/spec/netsuite/records/expense_report_spec.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe NetSuite::Records::ExpenseReport do
+ let(:entry) { NetSuite::Records::ExpenseReport.new }
+
+ it 'has all the right fields' do
+ [
+ :accounting_approval, :advance, :amount, :complete, :created_date,
+ :due_date, :last_modified_date, :memo, :status, :supervisor_approval,
+ :tax1_amt, :tax2_amt, :total, :tran_date, :tran_id, :use_multi_currency
+ ].each do |field|
+ expect(entry).to have_field(field)
+ end
+ end
+
+ it 'has all the right record refs' do
+ [
+ :account, :approval_status, :klass, :custom_form, :department, :entity,
+ :location, :next_approver, :posting_period, :subsidiary
+ ].each do |record_ref|
+ expect(entry).to have_record_ref(record_ref)
+ end
+ end
+
+ describe '#custom_field_list' do
+ it 'can be set from attributes' do
+ attributes = {
+ :custom_field => {
+ :amount => 10,
+ :script_id => 'custfield_amount'
+ }
+ }
+ entry.custom_field_list = attributes
+ expect(entry.custom_field_list).to be_kind_of(NetSuite::Records::CustomFieldList)
+ expect(entry.custom_field_list.custom_fields.length).to eql(1)
+ expect(entry.custom_field_list.custfield_amount.attributes[:amount]).to eq(10)
+ end
+
+ it 'can be set from a CustomFieldList object' do
+ custom_field_list = NetSuite::Records::CustomFieldList.new
+ entry.custom_field_list = custom_field_list
+ expect(entry.custom_field_list).to eql(custom_field_list)
+ end
+ end
+
+ describe '#expense_list' do
+ it 'can be set from attributes' do
+ attributes = {
+ :expense => {
+
+ }
+ }
+ entry.expense_list = attributes
+ expect(entry.expense_list).to be_kind_of(NetSuite::Records::ExpenseReportExpenseList)
+ expect(entry.expense_list.expense.length).to eql(1)
+ end
+
+ it 'can be set from a ExpenseReportExpenseList object' do
+ expense_list = NetSuite::Records::ExpenseReportExpenseList.new
+ entry.expense_list = expense_list
+ expect(entry.expense_list).to eql(expense_list)
+ end
+ end
+
+ describe '.get' do
+ context 'when the response is successful' do
+ let(:response) { NetSuite::Response.new(:success => true, :body => { :accounting_approval => true }) }
+
+ it 'returns a ExpenseReport instance populated with the data from the response object' do
+ expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::ExpenseReport, {:external_id => 1}], {}).and_return(response)
+ expense = NetSuite::Records::ExpenseReport.get(:external_id => 1)
+ expect(expense).to be_kind_of(NetSuite::Records::ExpenseReport)
+ expect(expense.accounting_approval).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
+ binding.pry
+ expect(NetSuite::Actions::Get).to receive(:call).with([NetSuite::Records::ExpenseReport, {:external_id => 1}], {}).and_return(response)
+ expect {
+ NetSuite::Records::ExpenseReport.get(:external_id => 1)
+ }.to raise_error(NetSuite::RecordNotFound,
+ /NetSuite::Records::ExpenseReport with OPTIONS=(.*) could not be found/)
+ end
+ end
+ end
+
+ describe '#add' do
+ let(:entry) { NetSuite::Records::ExpenseReport.new(:approved => true) }
+
+ 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([entry], {}).
+ and_return(response)
+ expect(entry.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([entry], {}).
+ and_return(response)
+ expect(entry.add).to be_falsey
+ end
+ end
+ end
+
+ describe '#to_record' do
+ let(:entry) { NetSuite::Records::ExpenseReport.new(:tran_id => '1234', :accounting_approval => true) }
+
+ it 'returns a hash of attributes that can be used in a SOAP request' do
+ expect(entry.to_record).to eql({
+ 'tranEmp:tranId' => '1234',
+ 'tranEmp:accountingApproval' => true
+ })
+ end
+ end
+
+ describe '#record_type' do
+ it 'returns a string type for the record to be used in a SOAP request' do
+ expect(entry.record_type).to eql('tranEmp:ExpenseReport')
+ end
+ end
+
+end
diff --git a/spec/netsuite/records/item_option_custom_field_spec.rb b/spec/netsuite/records/item_option_custom_field_spec.rb
new file mode 100644
index 000000000..f6de0eb0a
--- /dev/null
+++ b/spec/netsuite/records/item_option_custom_field_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe NetSuite::Records::ItemOptionCustomField do
+
+ describe ".get" do
+ let(:response) do
+ NetSuite::Response.new(
+ success: true,
+ body: {
+ label: "Value of Label",
+ }
+ )
+ end
+
+ it "returns a ItemOptionCustomField instance with populated fields" do
+ expect(NetSuite::Actions::Get)
+ .to receive(:call)
+ .with([NetSuite::Records::ItemOptionCustomField, internal_id: 1], {})
+ .and_return(response)
+
+ record = NetSuite::Records::ItemOptionCustomField.get(internal_id: 1)
+
+ expect(record.label).to eql("Value of Label")
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/spec/netsuite/records/journal_entry_line_spec.rb b/spec/netsuite/records/journal_entry_line_spec.rb
index 37809fa72..c46f2e056 100644
--- a/spec/netsuite/records/journal_entry_line_spec.rb
+++ b/spec/netsuite/records/journal_entry_line_spec.rb
@@ -13,7 +13,7 @@
it 'has all the right record refs' do
[
- :account, :department, :entity, :klass, :location, :schedule, :schedule_num, :tax1_acct, :tax_code
+ :account, :department, :entity, :klass, :location, :schedule, :schedule_num, :tax1_acct, :tax_code, :revenue_recognition_rule
].each do |record_ref|
expect(line).to have_record_ref(record_ref)
end
diff --git a/spec/netsuite/records/journal_entry_spec.rb b/spec/netsuite/records/journal_entry_spec.rb
index 48d014e05..b2a038b99 100644
--- a/spec/netsuite/records/journal_entry_spec.rb
+++ b/spec/netsuite/records/journal_entry_spec.rb
@@ -6,7 +6,7 @@
it 'has all the right fields' do
[
:approved, :created_date, :exchange_rate, :last_modified_date, :reversal_date,
- :reversal_defer, :reversal_entry, :tran_date, :tran_id
+ :reversal_defer, :reversal_entry, :tran_date, :tran_id, :is_book_specific
].each do |field|
expect(entry).to have_field(field)
end
@@ -15,7 +15,7 @@
it 'has all the right record refs' do
[
:created_from, :currency, :custom_form, :department, :klass, :location, :parent_expense_alloc, :posting_period,
- :subsidiary, :to_subsidiary
+ :subsidiary, :to_subsidiary, :accounting_book
].each do |record_ref|
expect(entry).to have_record_ref(record_ref)
end
diff --git a/spec/support/fixtures/async_add_list/async_add_list_response.xml b/spec/support/fixtures/async_add_list/async_add_list_response.xml
new file mode 100644
index 000000000..9c2cca97e
--- /dev/null
+++ b/spec/support/fixtures/async_add_list/async_add_list_response.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ WEBSERVICES_3392464_1220201115821392011296470399_67055c545d0
+
+
+
+
+
+ ASYNCWEBSERVICES_563214_053120061943428686160042948_4bee0685
+ pending
+ 0.0
+ 0.0
+
+
+
diff --git a/spec/support/fixtures/attach/attach_file_to_bill.xml b/spec/support/fixtures/attach/attach_file_to_bill.xml
new file mode 100644
index 000000000..77bbbddba
--- /dev/null
+++ b/spec/support/fixtures/attach/attach_file_to_bill.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ WEBSERVICES_3392464_1220201115821392011296470399_67055c545d0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spec/support/fixtures/attach/attach_file_to_bill_error.xml b/spec/support/fixtures/attach/attach_file_to_bill_error.xml
new file mode 100644
index 000000000..0c0aa47d5
--- /dev/null
+++ b/spec/support/fixtures/attach/attach_file_to_bill_error.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ WEBSERVICES_5102954_SB1_110820191277209614860972177_6400640b
+
+
+
+
+
+
+
+ USER_ERROR
+ Invalid Attachment record combination
+
+
+
+
+
+