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 + + + + + +