netsuite_rails 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/Gemfile +4 -2
- data/README.md +186 -10
- data/circle.yml +3 -0
- data/lib/netsuite_rails/configuration.rb +7 -5
- data/lib/netsuite_rails/netsuite_rails.rb +24 -4
- data/lib/netsuite_rails/poll_trigger.rb +10 -9
- data/lib/netsuite_rails/record_sync/poll_manager.rb +23 -11
- data/lib/netsuite_rails/record_sync/pull_manager.rb +2 -0
- data/lib/netsuite_rails/record_sync/push_manager.rb +76 -38
- data/lib/netsuite_rails/record_sync.rb +28 -11
- data/lib/netsuite_rails/routines/company_contact_match.rb +98 -0
- data/lib/netsuite_rails/spec/disabler.rb +27 -0
- data/lib/netsuite_rails/spec/query_helpers.rb +93 -0
- data/lib/netsuite_rails/spec/spec_helper.rb +2 -79
- data/lib/netsuite_rails/sync_trigger.rb +40 -17
- data/lib/netsuite_rails/tasks/netsuite.rb +33 -4
- data/lib/netsuite_rails/transformations.rb +59 -19
- data/lib/netsuite_rails/url_helper.rb +45 -12
- data/netsuite_rails.gemspec +2 -2
- data/spec/models/configuration_spec.rb +11 -0
- data/spec/models/poll_manager_spec.rb +11 -2
- data/spec/models/poll_trigger_spec.rb +31 -11
- data/spec/models/record_sync/push_manager_spec.rb +51 -0
- data/spec/models/record_sync_spec.rb +16 -0
- data/spec/models/spec_helper_spec.rb +1 -2
- data/spec/models/transformations_spec.rb +62 -0
- data/spec/models/url_helper_spec.rb +20 -9
- data/spec/spec_helper.rb +19 -0
- data/spec/support/example_models.rb +33 -1
- metadata +19 -25
- data/.travis.yml +0 -3
- data/lib/netsuite_rails/netsuite_configure.rb +0 -14
- data/spec/support/netsuite_rails.rb +0 -1
@@ -8,6 +8,10 @@ module NetSuiteRails
|
|
8
8
|
# if a model has a pending/complete state we might want to only push on complete
|
9
9
|
|
10
10
|
def attach(klass)
|
11
|
+
# don't attach to non-AR backed models
|
12
|
+
# it is the user's responsibility to trigger `Model#netsuite_push` when ActiveRecord isn't used
|
13
|
+
return unless klass.ancestors.include?(ActiveRecord::Base)
|
14
|
+
|
11
15
|
if klass.include?(SubListSync)
|
12
16
|
klass.after_save { SyncTrigger.sublist_trigger(self) }
|
13
17
|
klass.after_destroy { SyncTrigger.sublist_trigger(self) }
|
@@ -17,20 +21,26 @@ module NetSuiteRails
|
|
17
21
|
klass.before_save do
|
18
22
|
@netsuite_sync_record_import = self.new_record? && self.netsuite_id.present?
|
19
23
|
|
24
|
+
if @netsuite_sync_record_import
|
25
|
+
# pull the record down if it has't been pulled yet
|
26
|
+
# this is useful when this is triggered by a save on a parent record which has this
|
27
|
+
# record as a related record
|
28
|
+
|
29
|
+
if !self.netsuite_pulled? && !self.netsuite_async_jobs?
|
30
|
+
SyncTrigger.record_pull_trigger(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
20
34
|
# if false record will not save
|
21
35
|
true
|
22
36
|
end
|
23
37
|
|
24
38
|
klass.after_save do
|
25
|
-
#
|
39
|
+
# this conditional is implemented as a save hook
|
26
40
|
# because the coordination class doesn't know about model persistence state
|
27
41
|
|
28
42
|
if @netsuite_sync_record_import
|
29
|
-
|
30
|
-
# this is useful when this is triggered by a save on a parent record which has this
|
31
|
-
# record as a related record
|
32
|
-
|
33
|
-
unless self.netsuite_pulled?
|
43
|
+
if !self.netsuite_pulled? && self.netsuite_async_jobs?
|
34
44
|
SyncTrigger.record_pull_trigger(self)
|
35
45
|
end
|
36
46
|
else
|
@@ -47,24 +57,32 @@ module NetSuiteRails
|
|
47
57
|
def record_pull_trigger(local)
|
48
58
|
return if NetSuiteRails::Configuration.netsuite_pull_disabled
|
49
59
|
|
60
|
+
sync_options = local.netsuite_sync_options
|
61
|
+
|
62
|
+
return if sync_options.has_key?(:pull_if) && !local.instance_exec(&sync_options[:pull_if])
|
63
|
+
|
50
64
|
record_trigger_action(local, :netsuite_pull)
|
51
65
|
end
|
52
66
|
|
53
|
-
def record_push_trigger(
|
67
|
+
def record_push_trigger(local)
|
54
68
|
# don't update when fields are updated because of a netsuite_pull
|
55
|
-
|
69
|
+
if local.netsuite_pulling?
|
70
|
+
Rails.logger.info "NetSuite: Push Stopped. Record is pulling. " +
|
71
|
+
"local_record=#{local.class}, local_record_id=#{local.id}"
|
72
|
+
return
|
73
|
+
end
|
56
74
|
|
57
75
|
return if NetSuiteRails::Configuration.netsuite_push_disabled
|
58
76
|
|
59
77
|
# don't update if a read only record
|
60
|
-
return if
|
78
|
+
return if local.netsuite_sync == :read
|
61
79
|
|
62
|
-
sync_options =
|
80
|
+
sync_options = local.netsuite_sync_options
|
63
81
|
|
64
82
|
# :if option is a block that returns a boolean
|
65
|
-
return if sync_options.has_key?(:if) && !
|
83
|
+
return if sync_options.has_key?(:if) && !local.instance_exec(&sync_options[:if])
|
66
84
|
|
67
|
-
record_trigger_action(
|
85
|
+
record_trigger_action(local, :netsuite_push)
|
68
86
|
end
|
69
87
|
|
70
88
|
def record_trigger_action(local, action)
|
@@ -79,15 +97,15 @@ module NetSuiteRails
|
|
79
97
|
end
|
80
98
|
|
81
99
|
# TODO need to pass off the credentials to the NS push command
|
82
|
-
|
100
|
+
|
83
101
|
# You can force sync mode in different envoirnments with the global configuration variables
|
84
102
|
|
85
|
-
if
|
103
|
+
if !local.netsuite_async_jobs?
|
86
104
|
local.send(action, action_options)
|
87
105
|
else
|
88
106
|
action_options[:modified_fields] = NetSuiteRails::RecordSync::PushManager.modified_local_fields(local).keys
|
89
107
|
|
90
|
-
# TODO support
|
108
|
+
# TODO support ActiveJob
|
91
109
|
|
92
110
|
if local.respond_to?(:delay)
|
93
111
|
local.delay.send(action, action_options)
|
@@ -98,12 +116,17 @@ module NetSuiteRails
|
|
98
116
|
end
|
99
117
|
|
100
118
|
def sublist_trigger(sublist_item_rep)
|
119
|
+
# TODO don't trigger a push if the parent record is still pulling
|
120
|
+
# often sublists are managed in a after_pull hook; we want to prevent auto-pushing
|
121
|
+
# if sublist records are being updated. However, the netsuite_pulling? state is not persisted
|
122
|
+
# so there is no gaurentee that it isn't being pulled by checking parent.netsuite_pulling?
|
123
|
+
|
101
124
|
parent = sublist_item_rep.send(sublist_item_rep.class.netsuite_sublist_parent)
|
102
|
-
|
125
|
+
|
103
126
|
if parent.class.include?(RecordSync)
|
104
127
|
record_push_trigger(parent)
|
105
128
|
end
|
106
129
|
end
|
107
130
|
|
108
131
|
end
|
109
|
-
end
|
132
|
+
end
|
@@ -5,11 +5,11 @@ namespace :netsuite do
|
|
5
5
|
skip_existing: ENV['SKIP_EXISTING'].present? && ENV['SKIP_EXISTING'] == "true"
|
6
6
|
}
|
7
7
|
|
8
|
-
if ENV['RECORD_MODELS'].
|
8
|
+
if !ENV['RECORD_MODELS'].nil?
|
9
9
|
opts[:record_models] = ENV['RECORD_MODELS'].split(',').map(&:constantize)
|
10
10
|
end
|
11
11
|
|
12
|
-
if ENV['LIST_MODELS'].
|
12
|
+
if !ENV['LIST_MODELS'].nil?
|
13
13
|
opts[:list_models] = ENV['LIST_MODELS'].split(',').map(&:constantize)
|
14
14
|
end
|
15
15
|
|
@@ -25,12 +25,15 @@ namespace :netsuite do
|
|
25
25
|
|
26
26
|
desc "Sync all NetSuite records using import_all"
|
27
27
|
task :fresh_sync => :environment do
|
28
|
-
NetSuiteRails::PollTimestamp.delete_all
|
29
|
-
|
30
28
|
if ENV['SKIP_EXISTING'].blank?
|
31
29
|
ENV['SKIP_EXISTING'] = "true"
|
32
30
|
end
|
33
31
|
|
32
|
+
opts = generate_options
|
33
|
+
opts[:record_models].each do |record_model|
|
34
|
+
NetSuiteRails::PollTimestamp.for_class(record_model).delete
|
35
|
+
end
|
36
|
+
|
34
37
|
Rake::Task["netsuite:sync"].invoke
|
35
38
|
end
|
36
39
|
|
@@ -47,4 +50,30 @@ namespace :netsuite do
|
|
47
50
|
NetSuiteRails::PollTrigger.update_local_records(generate_options)
|
48
51
|
end
|
49
52
|
|
53
|
+
task field_usage_report: :environment do |t|
|
54
|
+
Rails.application.eager_load!
|
55
|
+
|
56
|
+
NetSuiteRails::PollTrigger.instance_variable_get('@record_models').each do |record_model|
|
57
|
+
puts record_model.to_s
|
58
|
+
|
59
|
+
# TODO add the ability to document which fields
|
60
|
+
|
61
|
+
standard_fields = record_model.netsuite_field_map.values - [record_model.netsuite_field_map[:custom_field_list]]
|
62
|
+
custom_fields = record_model.netsuite_field_map[:custom_field_list].values
|
63
|
+
|
64
|
+
standard_fields.reject! { |f| f.is_a?(Proc) }
|
65
|
+
custom_fields.reject! { |f| f.is_a?(Proc) }
|
66
|
+
|
67
|
+
if custom_fields.present?
|
68
|
+
puts "Custom Fields: #{custom_fields.join(', ')}"
|
69
|
+
end
|
70
|
+
|
71
|
+
if standard_fields.present?
|
72
|
+
puts "Standard Fields: #{standard_fields.join(', ')}"
|
73
|
+
end
|
74
|
+
|
75
|
+
puts ""
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
50
79
|
end
|
@@ -2,43 +2,83 @@ module NetSuiteRails
|
|
2
2
|
module Transformations
|
3
3
|
class << self
|
4
4
|
|
5
|
-
def transform(type, value)
|
6
|
-
self.send(type, value)
|
5
|
+
def transform(type, value, direction)
|
6
|
+
self.send(type, value, direction)
|
7
7
|
end
|
8
8
|
|
9
9
|
# NS limits firstname fields to 33 characters
|
10
|
-
def firstname(firstname)
|
11
|
-
|
10
|
+
def firstname(firstname, direction = :push)
|
11
|
+
if direction == :push
|
12
|
+
firstname[0..33]
|
13
|
+
else
|
14
|
+
firstname
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
|
-
def phone(phone)
|
15
|
-
|
16
|
-
.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def phone(phone, direction = :push)
|
19
|
+
if direction == :push
|
20
|
+
formatted_phone = phone.
|
21
|
+
strip.
|
22
|
+
gsub(/ext(ension)?/, 'x').
|
23
|
+
# remove anything that isn't a extension indicator or a number
|
24
|
+
gsub(/[^0-9x]/, '').
|
25
|
+
# if the first part of the phone # is 10 characters long and starts with a 1 the 22 char error is thrown
|
26
|
+
gsub(/^1([0-9]{10})/, '\1')
|
21
27
|
|
22
|
-
|
28
|
+
# eliminate the extension if the number is still too long
|
29
|
+
formatted_phone.gsub!(/x.*$/, '') if formatted_phone.size > 22
|
30
|
+
|
31
|
+
formatted_phone
|
32
|
+
else
|
33
|
+
phone
|
34
|
+
end
|
23
35
|
end
|
24
36
|
|
25
37
|
# NS will throw an error if whitespace bumpers the email string
|
26
|
-
def email(email)
|
27
|
-
|
38
|
+
def email(email, direction = :push)
|
39
|
+
if direction == :push
|
40
|
+
# any whitespace will cause netsuite to throw a fatal error
|
41
|
+
email = email.gsub(' ', '')
|
42
|
+
|
43
|
+
# TODO consider throwing an exception instead of returning nil?
|
44
|
+
# netsuite will throw a fatal error if a valid email address is not sent
|
45
|
+
# http://stackoverflow.com/questions/742451/what-is-the-simplest-regular-expression-to-validate-emails-to-not-accept-them-bl
|
46
|
+
if email !~ /.+@.+\..+/
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# an error will be thrown if period is on the end of a sentence
|
51
|
+
email = email.gsub(/[^A-Za-z]+$/, '')
|
52
|
+
|
53
|
+
email
|
54
|
+
else
|
55
|
+
email
|
56
|
+
end
|
28
57
|
end
|
29
58
|
|
30
59
|
# https://www.reinteractive.net/posts/168-dealing-with-timezones-effectively-in-rails
|
31
60
|
# http://stackoverflow.com/questions/16818180/ruby-rails-how-do-i-change-the-timezone-of-a-time-without-changing-the-time
|
32
61
|
# http://alwayscoding.ca/momentos/2013/08/22/handling-dates-and-timezones-in-ruby-and-rails/
|
33
62
|
|
34
|
-
def date(date)
|
35
|
-
|
63
|
+
def date(date, direction = :push)
|
64
|
+
if direction == :push
|
65
|
+
# setting the hour to noon eliminates the chance that some strange timezone offset
|
66
|
+
# shifting would cause the date to drift into the next or previous day
|
67
|
+
date.to_datetime.change(offset: "-07:00", hour: 12)
|
68
|
+
else
|
69
|
+
date.change(offset: Time.zone.formatted_offset)
|
70
|
+
end
|
36
71
|
end
|
37
72
|
|
38
|
-
def datetime(datetime)
|
39
|
-
|
73
|
+
def datetime(datetime, direction = :push)
|
74
|
+
if direction == :push
|
75
|
+
datetime.change(offset: "-08:00", year: 1970, day: 01, month: 01) - (8 + NetSuiteRails::Configuration.netsuite_instance_time_zone_offset).hours
|
76
|
+
else
|
77
|
+
datetime = datetime.change(offset: Time.zone.formatted_offset) + (8 + NetSuiteRails::Configuration.netsuite_instance_time_zone_offset).hours
|
78
|
+
datetime
|
79
|
+
end
|
40
80
|
end
|
41
81
|
|
42
82
|
end
|
43
83
|
end
|
44
|
-
end
|
84
|
+
end
|
@@ -4,7 +4,8 @@ module NetSuiteRails
|
|
4
4
|
# TODO create a xxx_netsuite_url helper generator
|
5
5
|
|
6
6
|
def self.netsuite_url(record = self)
|
7
|
-
|
7
|
+
domain = NetSuite::Configuration.wsdl_domain.sub('webservices.', 'system.')
|
8
|
+
prefix = "https://#{domain}/app"
|
8
9
|
|
9
10
|
if record.class.to_s.start_with?('NetSuite::Records')
|
10
11
|
record_class = record.class
|
@@ -21,19 +22,51 @@ module NetSuiteRails
|
|
21
22
|
# https://system.sandbox.netsuite.com/app/common/scripting/scriptrecordlist.nl
|
22
23
|
# https://system.sandbox.netsuite.com/app/common/scripting/script.nl
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
# dependent record links
|
26
|
+
# https://system.na1.netsuite.com/core/pages/itemchildrecords.nl?id=12413&t=InvtItem%05ProjectCostCategory&rectype=-10
|
27
|
+
# https://system.na1.netsuite.com/app/accounting/transactions/payments.nl?id=91964&label=Customer+Refund&type=custrfnd&alllinks=T
|
28
|
+
|
29
|
+
suffix = if is_custom_record
|
30
|
+
"/common/custom/custrecordentry.nl?id=#{internal_id}&rectype=#{record.class.netsuite_custom_record_type_id}"
|
31
|
+
elsif [
|
32
|
+
NetSuite::Records::InventoryItem,
|
33
|
+
NetSuite::Records::NonInventorySaleItem,
|
34
|
+
NetSuite::Records::AssemblyItem,
|
35
|
+
NetSuite::Records::ServiceSaleItem,
|
36
|
+
NetSuite::Records::DiscountItem,
|
37
|
+
].include?(record_class)
|
38
|
+
"/common/item/item.nl?id=#{internal_id}"
|
28
39
|
elsif record_class == NetSuite::Records::Task
|
29
|
-
"
|
30
|
-
elsif record_class == NetSuite::Records::
|
31
|
-
"
|
32
|
-
elsif
|
33
|
-
|
34
|
-
|
35
|
-
|
40
|
+
"/crm/calendar/task.nl?id=#{internal_id}"
|
41
|
+
elsif record_class == NetSuite::Records::Roles
|
42
|
+
"/setup/role.nl?id=#{internal_id}"
|
43
|
+
elsif [
|
44
|
+
NetSuite::Records::Contact,
|
45
|
+
NetSuite::Records::Customer,
|
46
|
+
NetSuite::Records::Vendor,
|
47
|
+
NetSuite::Records::Partner,
|
48
|
+
NetSuite::Records::Employee
|
49
|
+
].include?(record_class)
|
50
|
+
"/common/entity/entity.nl?id=#{internal_id}"
|
51
|
+
elsif [
|
52
|
+
NetSuite::Records::SalesOrder,
|
53
|
+
NetSuite::Records::Invoice,
|
54
|
+
NetSuite::Records::CustomerRefund,
|
55
|
+
NetSuite::Records::CashSale,
|
56
|
+
NetSuite::Records::ItemFulfillment,
|
57
|
+
NetSuite::Records::CustomerDeposit,
|
58
|
+
NetSuite::Records::CustomerPayment,
|
59
|
+
NetSuite::Records::CreditMemo,
|
60
|
+
NetSuite::Records::Deposit
|
61
|
+
].include?(record_class)
|
62
|
+
"/accounting/transactions/transaction.nl?id=#{internal_id}"
|
63
|
+
elsif NetSuite::Records::Account == record_class
|
64
|
+
"/accounting/account/account.nl?id=#{internal_id}"
|
65
|
+
else
|
66
|
+
# TODO unsupported record type error?
|
36
67
|
end
|
68
|
+
|
69
|
+
prefix + suffix
|
37
70
|
end
|
38
71
|
|
39
72
|
end
|
data/netsuite_rails.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "netsuite_rails"
|
7
|
-
s.version = "0.
|
7
|
+
s.version = "0.3.1"
|
8
8
|
s.authors = ["Michael Bianco"]
|
9
9
|
s.email = ["mike@cliffsidemedia.com"]
|
10
10
|
s.summary = %q{Write Rails applications that integrate with NetSuite}
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
17
17
|
s.require_paths = ["lib"]
|
18
18
|
|
19
|
-
s.add_dependency 'netsuite', '
|
19
|
+
s.add_dependency 'netsuite', '> 0.5.2'
|
20
20
|
s.add_dependency 'rails', '>= 3.2.16'
|
21
21
|
|
22
22
|
s.add_development_dependency "bundler", "~> 1.6"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NetSuiteRails::Configuration do
|
4
|
+
it 'should disable netsuite push and pull' do
|
5
|
+
NetSuiteRails::Configuration.netsuite_push_disabled true
|
6
|
+
NetSuiteRails::Configuration.netsuite_pull_disabled false
|
7
|
+
|
8
|
+
expect(NetSuiteRails::Configuration.netsuite_pull_disabled).to eq(false)
|
9
|
+
expect(NetSuiteRails::Configuration.netsuite_push_disabled).to eq(true)
|
10
|
+
end
|
11
|
+
end
|
@@ -30,9 +30,10 @@ describe NetSuiteRails::RecordSync::PollManager do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
it "should sync only available local records" do
|
33
|
-
|
33
|
+
# disabling NS pull since this is treated as a record import (NS ID = exists, new_record? == true)
|
34
|
+
NetSuiteRails::Configuration.netsuite_pull_disabled true
|
34
35
|
StandardRecord.create! netsuite_id: 123
|
35
|
-
NetSuiteRails::Configuration.
|
36
|
+
NetSuiteRails::Configuration.netsuite_pull_disabled false
|
36
37
|
|
37
38
|
allow(NetSuite::Records::Customer).to receive(:get_list).and_return([OpenStruct.new(internal_id: 123)])
|
38
39
|
allow(NetSuiteRails::RecordSync::PollManager).to receive(:process_search_result_item)
|
@@ -42,4 +43,12 @@ describe NetSuiteRails::RecordSync::PollManager do
|
|
42
43
|
expect(NetSuite::Records::Customer).to have_received(:get_list)
|
43
44
|
end
|
44
45
|
|
46
|
+
it 'runs netsuite_pull on a newly created record with a netsuite_id defined' do
|
47
|
+
record = StandardRecord.new netsuite_id: 123
|
48
|
+
allow(record).to receive(:netsuite_pull)
|
49
|
+
|
50
|
+
record.save!
|
51
|
+
|
52
|
+
expect(record).to have_received(:netsuite_pull)
|
53
|
+
end
|
45
54
|
end
|
@@ -4,23 +4,43 @@ describe NetSuiteRails::PollTrigger do
|
|
4
4
|
include ExampleModels
|
5
5
|
|
6
6
|
it "should properly sync for the first time" do
|
7
|
-
|
7
|
+
allow(NetSuiteRails::RecordSync::PollManager).to receive(:poll)
|
8
8
|
|
9
|
-
|
9
|
+
NetSuiteRails::PollTrigger.sync list_models: [], record_models: [ StandardRecord ]
|
10
10
|
|
11
|
-
|
11
|
+
expect(NetSuiteRails::RecordSync::PollManager).to have_received(:poll)
|
12
12
|
end
|
13
13
|
|
14
|
-
it "should trigger syncing
|
15
|
-
|
14
|
+
it "should trigger syncing is triggered from the model when time passed is greater than frequency" do
|
15
|
+
allow(StandardRecord).to receive(:netsuite_poll)
|
16
|
+
allow(StandardRecord.netsuite_record_class).to receive(:search).and_return(OpenStruct.new(results: []))
|
16
17
|
|
17
|
-
|
18
|
-
timestamp = NetSuiteRails::PollTimestamp.for_class(StandardRecord)
|
19
|
-
timestamp.value = DateTime.now - 7.minutes
|
20
|
-
timestamp.save!
|
18
|
+
StandardRecord.netsuite_sync_options[:frequency] = 5.minutes
|
21
19
|
|
22
|
-
|
20
|
+
timestamp = NetSuiteRails::PollTimestamp.for_class(StandardRecord)
|
21
|
+
timestamp.value = DateTime.now - 6.minutes
|
22
|
+
timestamp.save!
|
23
23
|
|
24
|
-
|
24
|
+
NetSuiteRails::PollTrigger.sync list_models: [], record_models: [ StandardRecord ]
|
25
|
+
|
26
|
+
expect(StandardRecord).to have_received(:netsuite_poll)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not change the poll timestamp when sync does not occur' do
|
30
|
+
allow(StandardRecord).to receive(:netsuite_poll)
|
31
|
+
|
32
|
+
StandardRecord.netsuite_sync_options[:frequency] = 5.minutes
|
33
|
+
|
34
|
+
last_timestamp = DateTime.now - 3.minutes
|
35
|
+
|
36
|
+
timestamp = NetSuiteRails::PollTimestamp.for_class(StandardRecord)
|
37
|
+
timestamp.value = last_timestamp
|
38
|
+
timestamp.save!
|
39
|
+
|
40
|
+
NetSuiteRails::PollTrigger.sync list_models: [], record_models: [ StandardRecord ]
|
41
|
+
|
42
|
+
expect(StandardRecord).to_not have_received(:netsuite_poll)
|
43
|
+
timestamp = NetSuiteRails::PollTimestamp.find(timestamp.id)
|
44
|
+
expect(timestamp.value).to eq(last_timestamp)
|
25
45
|
end
|
26
46
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
describe NetSuiteRails::RecordSync::PushManager do
|
2
|
+
include ExampleModels
|
3
|
+
|
4
|
+
it 'should handle a modified field with a Proc instead of a netsuite field key' do
|
5
|
+
record = StandardRecord.new netsuite_id: 234
|
6
|
+
allow(record).to receive(:new_netsuite_record?).and_return(false)
|
7
|
+
|
8
|
+
ns_record = record.netsuite_record_class.new
|
9
|
+
|
10
|
+
NetSuiteRails::RecordSync::PushManager.push(record, { modified_fields: [ :company ] })
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should ignore modified fields if the record has not yet been pushed to NetSuite' do
|
14
|
+
record = StandardRecord.new
|
15
|
+
|
16
|
+
expect(NetSuiteRails::RecordSync::PushManager).to receive(:push_add).once
|
17
|
+
expect(NetSuiteRails::RecordSync::PushManager).to receive(:build_netsuite_record).with(instance_of(StandardRecord), hash_including({
|
18
|
+
:modified_fields => hash_including(:phone, :company)
|
19
|
+
}))
|
20
|
+
|
21
|
+
NetSuiteRails::RecordSync::PushManager.push(record, { modified_fields: [ :company ] })
|
22
|
+
end
|
23
|
+
|
24
|
+
context "AR" do
|
25
|
+
xit "should look at the NS ID of a has_one relationship on the record sync model"
|
26
|
+
|
27
|
+
xit "should properly determine the changed attributes"
|
28
|
+
end
|
29
|
+
|
30
|
+
context "not AR" do
|
31
|
+
xit "should execute properly for a simple active model class"
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'record building' do
|
36
|
+
it "should properly handle custom records" do
|
37
|
+
custom = CustomRecord.new netsuite_id: 234
|
38
|
+
record = NetSuiteRails::RecordSync::PushManager.build_netsuite_record_reference(custom)
|
39
|
+
|
40
|
+
expect(record.internal_id).to eq(234)
|
41
|
+
expect(record.rec_type.internal_id).to eq(123)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should properly handle records using external ID" do
|
45
|
+
local = ExternalIdRecord.new(netsuite_id: 123, phone: "234")
|
46
|
+
record = NetSuiteRails::RecordSync::PushManager.build_netsuite_record_reference(local, { use_external_id: true })
|
47
|
+
|
48
|
+
expect(record.external_id).to eq(local.netsuite_external_id)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NetSuiteRails::RecordSync do
|
4
|
+
include ExampleModels
|
5
|
+
|
6
|
+
context 'custom records' do
|
7
|
+
it "should properly pull the NS rep" do
|
8
|
+
allow(NetSuite::Records::CustomRecord).to receive(:get).with(hash_including(:internal_id => 234, type_id: 123))
|
9
|
+
|
10
|
+
custom_record = CustomRecord.new netsuite_id: 234
|
11
|
+
custom_record.netsuite_pull_record
|
12
|
+
|
13
|
+
expect(NetSuite::Records::CustomRecord).to have_received(:get)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -2,8 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
require 'netsuite_rails/spec/spec_helper'
|
4
4
|
|
5
|
-
describe
|
6
|
-
include NetSuiteRails::TestHelpers
|
5
|
+
describe 'netsuite_rails test helpers' do
|
7
6
|
include ExampleModels
|
8
7
|
|
9
8
|
let(:fake_search_results) { OpenStruct.new(results: [ OpenStruct.new(internal_id: 0) ]) }
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# TODO https://github.com/NetSweet/netsuite_rails/issues/16
|
4
|
+
# there are still some unresolved issues with NS datetime/date conversions
|
5
|
+
# the tests + implementation may still not be correct.
|
6
|
+
|
7
|
+
describe NetSuiteRails::Transformations do
|
8
|
+
before do
|
9
|
+
ENV['TZ'] = 'EST'
|
10
|
+
Rails.configuration.time_zone = 'Eastern Time (US & Canada)'
|
11
|
+
Time.zone = ActiveSupport::TimeZone[-5]
|
12
|
+
|
13
|
+
NetSuiteRails::Configuration.netsuite_instance_time_zone_offset -6
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'handles very long phone numbers' do
|
17
|
+
long_phone_number = '+1 (549)-880-4834 ext. 51077'
|
18
|
+
|
19
|
+
expect(NetSuiteRails::Transformations.phone(long_phone_number)).to eq('5498804834x51077')
|
20
|
+
|
21
|
+
weird_long_phone_number = '12933901964x89914'
|
22
|
+
expect(NetSuiteRails::Transformations.phone(weird_long_phone_number)).to eq('2933901964x89914')
|
23
|
+
end
|
24
|
+
|
25
|
+
it "translates local date into NS date" do
|
26
|
+
# from what I can tell, NetSuite stores dates with a -07:00 offset
|
27
|
+
# and subtracts (PST - NS instance timezone) hours from the stored datetime
|
28
|
+
|
29
|
+
local_date = DateTime.parse("2015-07-24T00:00:00.000-05:00")
|
30
|
+
transformed_date = NetSuiteRails::Transformations.date(local_date, :push)
|
31
|
+
expect(transformed_date.to_s).to eq("2015-07-24T12:00:00-07:00")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "translates local datetime into NS datetime" do
|
35
|
+
# TODO set local timezone
|
36
|
+
local_date = DateTime.parse('Fri May 29 11:52:47 EDT 2015')
|
37
|
+
NetSuiteRails::Configuration.netsuite_instance_time_zone_offset -6
|
38
|
+
|
39
|
+
transformed_date = NetSuiteRails::Transformations.datetime(local_date, :push)
|
40
|
+
# TODO this will break as PDT daylight savings is switched; need to freeze the system time for testing
|
41
|
+
expect(transformed_date.to_s).to eq('1970-01-01T09:52:47-08:00')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'transforms a datetime value pulled from netsuite correctly' do
|
45
|
+
# assuming that the date in CST is displayed as 5am
|
46
|
+
# in the rails backend we want to store the date as EST with a CST hour
|
47
|
+
|
48
|
+
netsuite_time = DateTime.parse('1970-01-01T03:00:00.000-08:00')
|
49
|
+
transformed_netsuite_time = NetSuiteRails::Transformations.datetime(netsuite_time, :pull)
|
50
|
+
expect(transformed_netsuite_time.to_s).to eq('1970-01-01T05:00:00-05:00')
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'transforms a invalid email' do
|
54
|
+
netsuite_email = ' hey@example.com. '
|
55
|
+
transformed_netsuite_email = NetSuiteRails::Transformations.email(netsuite_email, :push)
|
56
|
+
expect(transformed_netsuite_email.to_s).to eq('hey@example.com')
|
57
|
+
|
58
|
+
netsuite_email = ' example+second@example.family. '
|
59
|
+
transformed_netsuite_email = NetSuiteRails::Transformations.email(netsuite_email, :push)
|
60
|
+
expect(transformed_netsuite_email.to_s).to eq('example+second@example.family')
|
61
|
+
end
|
62
|
+
end
|
@@ -1,23 +1,34 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe NetSuiteRails::UrlHelper do
|
4
|
-
|
4
|
+
include ExampleModels
|
5
5
|
|
6
6
|
it 'should handle a netsuite record' do
|
7
|
-
|
8
|
-
|
7
|
+
NetSuite::Configuration.sandbox = true
|
8
|
+
c = NetSuite::Records::Customer.new internal_id: 123
|
9
|
+
url = NetSuiteRails::UrlHelper.netsuite_url(c)
|
9
10
|
|
10
|
-
|
11
|
+
expect(url).to eq('https://system.sandbox.netsuite.com/app/common/entity/entity.nl?id=123')
|
11
12
|
end
|
12
13
|
|
13
14
|
it "should handle a record sync enabled record" do
|
14
|
-
|
15
|
-
|
15
|
+
NetSuite::Configuration.sandbox = true
|
16
|
+
s = StandardRecord.new netsuite_id: 123
|
17
|
+
url = NetSuiteRails::UrlHelper.netsuite_url(s)
|
16
18
|
|
17
|
-
|
19
|
+
expect(url).to eq('https://system.sandbox.netsuite.com/app/common/entity/entity.nl?id=123')
|
18
20
|
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
+
xit "should handle a list sync enabled record" do
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should change the prefix URL when a non-sandbox datacenter is in use' do
|
27
|
+
NetSuite::Configuration.sandbox = false
|
28
|
+
|
29
|
+
s = StandardRecord.new netsuite_id: 123
|
30
|
+
url = NetSuiteRails::UrlHelper.netsuite_url(s)
|
31
|
+
|
32
|
+
expect(url).to eq('https://system.netsuite.com/app/common/entity/entity.nl?id=123')
|
22
33
|
end
|
23
34
|
end
|