ns_connector 0.0.6
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.
- data/Gemfile +13 -0
- data/Gemfile.lock +80 -0
- data/Guardfile +9 -0
- data/HACKING +31 -0
- data/LICENSE.txt +7 -0
- data/README.rdoc +191 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/ns_connector.rb +4 -0
- data/lib/ns_connector/attaching.rb +42 -0
- data/lib/ns_connector/chunked_searching.rb +111 -0
- data/lib/ns_connector/config.rb +66 -0
- data/lib/ns_connector/errors.rb +79 -0
- data/lib/ns_connector/field_store.rb +19 -0
- data/lib/ns_connector/hash.rb +11 -0
- data/lib/ns_connector/resource.rb +288 -0
- data/lib/ns_connector/resources.rb +3 -0
- data/lib/ns_connector/resources/contact.rb +279 -0
- data/lib/ns_connector/resources/customer.rb +355 -0
- data/lib/ns_connector/resources/invoice.rb +466 -0
- data/lib/ns_connector/restlet.rb +137 -0
- data/lib/ns_connector/sublist.rb +21 -0
- data/lib/ns_connector/sublist_item.rb +25 -0
- data/misc/failed_sublist_saving_patch +547 -0
- data/scripts/run_restlet +25 -0
- data/scripts/test_shell +21 -0
- data/spec/attaching_spec.rb +48 -0
- data/spec/chunked_searching_spec.rb +75 -0
- data/spec/config_spec.rb +43 -0
- data/spec/resource_spec.rb +340 -0
- data/spec/resources/contact_spec.rb +8 -0
- data/spec/resources/customer_spec.rb +8 -0
- data/spec/resources/invoice_spec.rb +20 -0
- data/spec/restlet_spec.rb +135 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/sublist_item_spec.rb +25 -0
- data/spec/sublist_spec.rb +45 -0
- data/spec/support/mock_data.rb +10 -0
- data/support/read_only_test +63 -0
- data/support/restlet.js +384 -0
- data/support/super_dangerous_write_test +85 -0
- metadata +221 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
require 'json'
|
4
|
+
require 'ns_connector/config'
|
5
|
+
require 'ns_connector/errors'
|
6
|
+
|
7
|
+
# Connects to the RESTlet configured in NetSuite to make generic requests.
|
8
|
+
# Example usage:
|
9
|
+
# NSConnector::Restlet.execute!(
|
10
|
+
# :action => 'retrieve',
|
11
|
+
# :type_id => type_id,
|
12
|
+
# :data => {'_id' => Integer(id)}
|
13
|
+
# )
|
14
|
+
# => parsed json results
|
15
|
+
module NSConnector::Restlet
|
16
|
+
RuntimeError = Class.new(Exception)
|
17
|
+
ArgumentError = Class.new(Exception)
|
18
|
+
|
19
|
+
# Build a HTTP request to connect to NetSuite RESTlets, then request
|
20
|
+
# our magic restlet that can do everything useful.
|
21
|
+
#
|
22
|
+
# Returns:: The parsed JSON response, i.e. JSON.parse(response.body)
|
23
|
+
def self.execute! options
|
24
|
+
NSConnector::Config.check_valid!
|
25
|
+
|
26
|
+
# Build our request up, a bit ugly
|
27
|
+
uri = URI(NSConnector::Config[:restlet_url])
|
28
|
+
|
29
|
+
unless uri.scheme then
|
30
|
+
raise NSConnector::Restlet::ArgumentError,
|
31
|
+
'Configuration value restlet_url must at '\
|
32
|
+
'least contain a scheme (i.e. http://)'
|
33
|
+
end
|
34
|
+
|
35
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
36
|
+
|
37
|
+
if NSConnector::Config[:debug] then
|
38
|
+
http.set_debug_output $stderr
|
39
|
+
end
|
40
|
+
|
41
|
+
http.use_ssl = (uri.scheme == 'https')
|
42
|
+
|
43
|
+
request = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
|
44
|
+
|
45
|
+
request['Content-Type'] = 'application/json'
|
46
|
+
request['Authorization'] = NSConnector::Restlet.auth_header
|
47
|
+
|
48
|
+
begin
|
49
|
+
options[:code] = restlet_code
|
50
|
+
request.body = JSON.dump(options)
|
51
|
+
rescue JSON::GeneratorError => current
|
52
|
+
exception = NSConnector::Restlet::ArgumentError.new(
|
53
|
+
"Failed to convert options (#{options}) " \
|
54
|
+
"into JSON: #{current.message}"
|
55
|
+
)
|
56
|
+
|
57
|
+
exception.set_backtrace(current.backtrace)
|
58
|
+
raise exception
|
59
|
+
end
|
60
|
+
|
61
|
+
response = http.request(request)
|
62
|
+
|
63
|
+
# Netsuite seems to use HTTP 400 (bad requests) for all runtime
|
64
|
+
# errors. Hah.
|
65
|
+
if response.kind_of? Net::HTTPBadRequest then
|
66
|
+
# So let's try and raise this exception as something
|
67
|
+
# nicer.
|
68
|
+
NSConnector::Errors.try_handle_response!(response)
|
69
|
+
end
|
70
|
+
|
71
|
+
unless response.kind_of? Net::HTTPSuccess then
|
72
|
+
raise NSConnector::Restlet::RuntimeError.new(
|
73
|
+
'Restlet execution failed, expected a '\
|
74
|
+
'HTTP 2xx response, got a HTTP '\
|
75
|
+
"#{response.code}: #{response.body}"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
if response.body.nil? then
|
80
|
+
raise NSConnector::Restlet::RuntimeError.new(
|
81
|
+
"Recieved a blank response from RESTlet"
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
begin
|
86
|
+
return JSON.parse(response.body)
|
87
|
+
rescue JSON::ParserError => current
|
88
|
+
exception = NSConnector::Restlet::RuntimeError.new(
|
89
|
+
'Failed to parse response ' \
|
90
|
+
'from Restlet as JSON '\
|
91
|
+
"(#{response.body}): " \
|
92
|
+
"#{current.message}"
|
93
|
+
)
|
94
|
+
exception.set_backtrace(current.backtrace)
|
95
|
+
raise exception
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Generate a NetSuite specific Authorization header.
|
100
|
+
#
|
101
|
+
# From the NetSuite documentation:
|
102
|
+
#
|
103
|
+
# NLAuth passes in the following login credentials:
|
104
|
+
# +nlauth_account+:: NetSuite company ID (required)
|
105
|
+
# +nlauth_email+:: NetSuite user name (required)
|
106
|
+
# +nclauth_signature+:: NetSuite password (required)
|
107
|
+
# +nlauth_role+:: internal ID of the role used to log in to
|
108
|
+
# NetSuite (optional)
|
109
|
+
# The Authorization header should be formatted as:
|
110
|
+
# NLAuth<space><comma-separated parameters>
|
111
|
+
#
|
112
|
+
# For example:
|
113
|
+
# Authorization: NLAuth nlauth_account=123456, \
|
114
|
+
# nlauth_email=jsmith@ABC.com, nlauth_signature=xxxxxxxx, \
|
115
|
+
# nlauth_role=41
|
116
|
+
#
|
117
|
+
# Returns:: String with the contents of the header
|
118
|
+
def self.auth_header
|
119
|
+
c = NSConnector::Config
|
120
|
+
c.check_valid!
|
121
|
+
|
122
|
+
"NLAuth nlauth_account=#{c[:account_id]}," \
|
123
|
+
"nlauth_email=#{c[:email]}," \
|
124
|
+
"nlauth_role=#{c[:role]}," \
|
125
|
+
"nlauth_signature=#{URI.escape(c[:password])}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Retrieve restlet code from support/restlet.js
|
129
|
+
# Returns:: String
|
130
|
+
def self.restlet_code
|
131
|
+
restlet_location = File.join(
|
132
|
+
File.dirname(__FILE__),
|
133
|
+
'..', '..', 'support', 'restlet.js'
|
134
|
+
)
|
135
|
+
File.read(restlet_location)
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Provides a method for grabbing sublists
|
2
|
+
module NSConnector::SubList
|
3
|
+
# Grab sublist_id from NetSuite
|
4
|
+
# Returns:: An array of SubListItems
|
5
|
+
def self.fetch parent, sublist_id, fields
|
6
|
+
NSConnector::Restlet.execute!(
|
7
|
+
:action => 'fetch_sublist',
|
8
|
+
:type_id => parent.type_id,
|
9
|
+
:parent_id => parent.id,
|
10
|
+
:fields => fields,
|
11
|
+
:sublist_id => sublist_id
|
12
|
+
).map do |upstream_store|
|
13
|
+
NSConnector::SubListItem.new(
|
14
|
+
sublist_id,
|
15
|
+
fields,
|
16
|
+
parent,
|
17
|
+
upstream_store
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'ns_connector/field_store'
|
2
|
+
require 'ns_connector/hash'
|
3
|
+
|
4
|
+
# Represents one SubListItem under a Resource
|
5
|
+
class NSConnector::SubListItem
|
6
|
+
include NSConnector::FieldStore
|
7
|
+
attr_accessor :store
|
8
|
+
attr_accessor :parent
|
9
|
+
attr_accessor :name
|
10
|
+
attr_accessor :fields
|
11
|
+
|
12
|
+
def initialize name, fields, parent, upstream_store = nil
|
13
|
+
upstream_store.stringify_keys! if upstream_store
|
14
|
+
@store = (upstream_store || {})
|
15
|
+
@parent = parent
|
16
|
+
@fields = fields
|
17
|
+
@name = name
|
18
|
+
|
19
|
+
create_store_accessors!
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<NSConnector::#{self.class}:#{name}>"
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,547 @@
|
|
1
|
+
Why is this stored here and not in git?
|
2
|
+
|
3
|
+
I'm planning rebase into a single commit, prefer not to lose this as a reference.
|
4
|
+
|
5
|
+
diff --git a/lib/ns_connector/resource.rb b/lib/ns_connector/resource.rb
|
6
|
+
index 92bbeab..3e49d7b 100644
|
7
|
+
--- a/lib/ns_connector/resource.rb
|
8
|
+
+++ b/lib/ns_connector/resource.rb
|
9
|
+
@@ -94,17 +94,6 @@ class NSConnector::Resource
|
10
|
+
# If we got this far, we're definitely in NetSuite
|
11
|
+
@in_netsuite = true
|
12
|
+
|
13
|
+
- # Now we save our sublist(s)
|
14
|
+
- @sublist_store.each do |sublist_id, sublist_items|
|
15
|
+
- # Overwriting the current item
|
16
|
+
- @sublist_store[sublist_id] = NSConnector::SubList.save!(
|
17
|
+
- sublist_items,
|
18
|
+
- self,
|
19
|
+
- sublist_id,
|
20
|
+
- sublists[sublist_id]
|
21
|
+
- )
|
22
|
+
- end
|
23
|
+
-
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
|
27
|
+
@@ -228,12 +217,7 @@ class NSConnector::Resource
|
28
|
+
private
|
29
|
+
# Given a sublist of {:addressbook => ['fields']} we want a method
|
30
|
+
# addressbook that looks up the sublist if we have an ID, otherwise
|
31
|
+
- # returns the empty array.
|
32
|
+
- #
|
33
|
+
- # And finally we need a method to create new sublist objects that is
|
34
|
+
- # generic and not too crazy. So we have new_addressbook that returns a
|
35
|
+
- # SubList object that can be stored and later turned into a hash to
|
36
|
+
- # send to NetSuite.
|
37
|
+
+ # returns an empty array.
|
38
|
+
def create_sublist_accessors!
|
39
|
+
sublists.each do |sublist_name, fields|
|
40
|
+
self.class.class_eval do
|
41
|
+
@@ -250,21 +234,6 @@ class NSConnector::Resource
|
42
|
+
|
43
|
+
@sublist_store[sublist_name] ||= []
|
44
|
+
end
|
45
|
+
-
|
46
|
+
- define_method("#{sublist_name}=") do |value|
|
47
|
+
- @sublist_store[sublist_name] = value
|
48
|
+
- end
|
49
|
+
-
|
50
|
+
- define_method(
|
51
|
+
- "new_#{sublist_name}_item"
|
52
|
+
- ) do |upstream_store = nil|
|
53
|
+
- NSConnector::SubListItem.new(
|
54
|
+
- sublist_name,
|
55
|
+
- fields,
|
56
|
+
- self,
|
57
|
+
- upstream_store
|
58
|
+
- )
|
59
|
+
- end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
diff --git a/lib/ns_connector/sublist.rb b/lib/ns_connector/sublist.rb
|
64
|
+
index b24f0c1..94d9c2f 100644
|
65
|
+
--- a/lib/ns_connector/sublist.rb
|
66
|
+
+++ b/lib/ns_connector/sublist.rb
|
67
|
+
@@ -1,27 +1,5 @@
|
68
|
+
# Provides a method for saving a bunch of SubListItems
|
69
|
+
module NSConnector::SubList
|
70
|
+
- # We save our array of SubListItems in the order in which they appear.
|
71
|
+
- # Arguments:: An array of SubListItem, the parent object and the fields
|
72
|
+
- # Returns:: An array of SubListItem that have been saved
|
73
|
+
- def self.save! sublist_items, parent, sublist_id, fields
|
74
|
+
- data = sublist_items.uniq.map do |item|
|
75
|
+
- item.store
|
76
|
+
- end
|
77
|
+
-
|
78
|
+
- NSConnector::Restlet.execute!(
|
79
|
+
- :action => 'save_sublist',
|
80
|
+
- :type_id => parent.type_id,
|
81
|
+
- :parent_id => parent.id,
|
82
|
+
- :fields => fields,
|
83
|
+
- :sublist_id => sublist_id,
|
84
|
+
- :data => data
|
85
|
+
- )
|
86
|
+
-
|
87
|
+
- # We have to do this in a second request as NetSuite needs a
|
88
|
+
- # short time to think about any added records.
|
89
|
+
- return NSConnector::SubList.fetch(parent, sublist_id, fields)
|
90
|
+
- end
|
91
|
+
-
|
92
|
+
# Grab sublist_id from NetSuite
|
93
|
+
# Returns:: An array of SubListItems
|
94
|
+
def self.fetch parent, sublist_id, fields
|
95
|
+
diff --git a/misc/failed_sublist_saving_patch b/misc/failed_sublist_saving_patch
|
96
|
+
index 5393f12..e69de29 100644
|
97
|
+
--- a/misc/failed_sublist_saving_patch
|
98
|
+
+++ b/misc/failed_sublist_saving_patch
|
99
|
+
@@ -1,214 +0,0 @@
|
100
|
+
-diff --git a/lib/ns_connector/resource.rb b/lib/ns_connector/resource.rb
|
101
|
+
-index 92bbeab..ef0f649 100644
|
102
|
+
---- a/lib/ns_connector/resource.rb
|
103
|
+
-+++ b/lib/ns_connector/resource.rb
|
104
|
+
-@@ -94,17 +94,6 @@ class NSConnector::Resource
|
105
|
+
- # If we got this far, we're definitely in NetSuite
|
106
|
+
- @in_netsuite = true
|
107
|
+
-
|
108
|
+
-- # Now we save our sublist(s)
|
109
|
+
-- @sublist_store.each do |sublist_id, sublist_items|
|
110
|
+
-- # Overwriting the current item
|
111
|
+
-- @sublist_store[sublist_id] = NSConnector::SubList.save!(
|
112
|
+
-- sublist_items,
|
113
|
+
-- self,
|
114
|
+
-- sublist_id,
|
115
|
+
-- sublists[sublist_id]
|
116
|
+
-- )
|
117
|
+
-- end
|
118
|
+
--
|
119
|
+
- return true
|
120
|
+
- end
|
121
|
+
-
|
122
|
+
-@@ -250,21 +239,6 @@ class NSConnector::Resource
|
123
|
+
-
|
124
|
+
- @sublist_store[sublist_name] ||= []
|
125
|
+
- end
|
126
|
+
--
|
127
|
+
-- define_method("#{sublist_name}=") do |value|
|
128
|
+
-- @sublist_store[sublist_name] = value
|
129
|
+
-- end
|
130
|
+
--
|
131
|
+
-- define_method(
|
132
|
+
-- "new_#{sublist_name}_item"
|
133
|
+
-- ) do |upstream_store = nil|
|
134
|
+
-- NSConnector::SubListItem.new(
|
135
|
+
-- sublist_name,
|
136
|
+
-- fields,
|
137
|
+
-- self,
|
138
|
+
-- upstream_store
|
139
|
+
-- )
|
140
|
+
-- end
|
141
|
+
- end
|
142
|
+
- end
|
143
|
+
- end
|
144
|
+
-diff --git a/lib/ns_connector/sublist.rb b/lib/ns_connector/sublist.rb
|
145
|
+
-index b24f0c1..94d9c2f 100644
|
146
|
+
---- a/lib/ns_connector/sublist.rb
|
147
|
+
-+++ b/lib/ns_connector/sublist.rb
|
148
|
+
-@@ -1,27 +1,5 @@
|
149
|
+
- # Provides a method for saving a bunch of SubListItems
|
150
|
+
- module NSConnector::SubList
|
151
|
+
-- # We save our array of SubListItems in the order in which they appear.
|
152
|
+
-- # Arguments:: An array of SubListItem, the parent object and the fields
|
153
|
+
-- # Returns:: An array of SubListItem that have been saved
|
154
|
+
-- def self.save! sublist_items, parent, sublist_id, fields
|
155
|
+
-- data = sublist_items.uniq.map do |item|
|
156
|
+
-- item.store
|
157
|
+
-- end
|
158
|
+
--
|
159
|
+
-- NSConnector::Restlet.execute!(
|
160
|
+
-- :action => 'save_sublist',
|
161
|
+
-- :type_id => parent.type_id,
|
162
|
+
-- :parent_id => parent.id,
|
163
|
+
-- :fields => fields,
|
164
|
+
-- :sublist_id => sublist_id,
|
165
|
+
-- :data => data
|
166
|
+
-- )
|
167
|
+
--
|
168
|
+
-- # We have to do this in a second request as NetSuite needs a
|
169
|
+
-- # short time to think about any added records.
|
170
|
+
-- return NSConnector::SubList.fetch(parent, sublist_id, fields)
|
171
|
+
-- end
|
172
|
+
--
|
173
|
+
- # Grab sublist_id from NetSuite
|
174
|
+
- # Returns:: An array of SubListItems
|
175
|
+
- def self.fetch parent, sublist_id, fields
|
176
|
+
-diff --git a/support/restlet.js b/support/restlet.js
|
177
|
+
-index 4569862..afc8df2 100644
|
178
|
+
---- a/support/restlet.js
|
179
|
+
-+++ b/support/restlet.js
|
180
|
+
-@@ -193,133 +193,6 @@ function create(request)
|
181
|
+
- ));
|
182
|
+
- }
|
183
|
+
-
|
184
|
+
--// Save a sublist, trying to merge changes nicely.
|
185
|
+
--//
|
186
|
+
--// Returns:: True, note that you will need to fetch the updated items yourself,
|
187
|
+
--// as netsuite seems to need a little while to think about any newly added
|
188
|
+
--// records or it freaks out.
|
189
|
+
--//
|
190
|
+
--// Raises:: An invalid sublist operation error, often, as apparently you're not
|
191
|
+
--// allowed to delete certain sublists even though you can append to them. Kind
|
192
|
+
--// of really annoying. Not much I can do about it.
|
193
|
+
--//
|
194
|
+
--// Also seems to raise a 500 on modifying existing items if it doesn't want you
|
195
|
+
--// to.
|
196
|
+
--//
|
197
|
+
--// We have the minor problem here of only being able to modify existing line
|
198
|
+
--// items in place, insert new ones and delete old ones, not rewrite the whole
|
199
|
+
--// list. This problem is easily dealt with if we simply don't worry about
|
200
|
+
--// order. So, new items should be appended to the end, and the existing order
|
201
|
+
--// shouldn't be messed with, however if you wanted to say, pull the whole list
|
202
|
+
--// out and reverse the order, you're out of luck.
|
203
|
+
--//
|
204
|
+
--// It's possible to do, I think. I have other things to work on just now.
|
205
|
+
--//
|
206
|
+
--// So we have three basic steps:
|
207
|
+
--//
|
208
|
+
--// 1. Modify all existing records that have changed, identifying by id.
|
209
|
+
--// 2. If the existing record does not appear in the new list, delete it.
|
210
|
+
--// 3. Append new records to the end.
|
211
|
+
--function save_sublist(request)
|
212
|
+
--{
|
213
|
+
-- if(!request.hasOwnProperty('parent_id')) {
|
214
|
+
-- argument_error("Missing mandatory argument: parent_id");
|
215
|
+
-- }
|
216
|
+
--
|
217
|
+
-- if(!request.hasOwnProperty('sublist_id')) {
|
218
|
+
-- argument_error("Missing mandatory argument: sublist_id");
|
219
|
+
-- }
|
220
|
+
--
|
221
|
+
-- var record = nlapiLoadRecord(request.type_id, request.parent_id);
|
222
|
+
-- var sublist_len = record.getLineItemCount(request.sublist_id);
|
223
|
+
-- var downstream_items = request.data;
|
224
|
+
-- var we_touched_something = false;
|
225
|
+
--
|
226
|
+
-- // 1. Modify all existing records that have been changed.
|
227
|
+
-- for(var i = 1; i <= sublist_len; i++) {
|
228
|
+
-- var upstream_id = record.getLineItemValue(
|
229
|
+
-- request.sublist_id, 'id', i
|
230
|
+
-- );
|
231
|
+
-- // Search for an id within downstream items.
|
232
|
+
-- for(var j = 0; j < downstream_items.length; j++){
|
233
|
+
-- if(!downstream_items[j].hasOwnProperty('id')) {
|
234
|
+
-- // New record, not interested in this yet
|
235
|
+
-- continue;
|
236
|
+
-- }
|
237
|
+
--
|
238
|
+
-- if(downstream_items[j]['id'] == upstream_id){
|
239
|
+
-- // We have a match, we need to merge now
|
240
|
+
-- for(var field in downstream_items[j]) {
|
241
|
+
-- var upstream_value = record.getLineItemValue(
|
242
|
+
-- request.sublist_id, field, i
|
243
|
+
-- )
|
244
|
+
--
|
245
|
+
-- // Set if changed
|
246
|
+
-- if(downstream_items[j][field] != upstream_value) {
|
247
|
+
-- record.setLineItemValue(
|
248
|
+
-- request.sublist_id,
|
249
|
+
-- field,
|
250
|
+
-- i,
|
251
|
+
-- downstream_items[j][field]
|
252
|
+
-- )
|
253
|
+
-- we_touched_something = true;
|
254
|
+
-- }
|
255
|
+
-- }
|
256
|
+
-- }
|
257
|
+
-- }
|
258
|
+
-- }
|
259
|
+
--
|
260
|
+
-- // 2. Delete all existing records that aren't in our new dataset
|
261
|
+
-- for(var i = sublist_len; i > 0; i--) {
|
262
|
+
-- var match = false;
|
263
|
+
-- var upstream_id = record.getLineItemValue(
|
264
|
+
-- request.sublist_id, 'id', i
|
265
|
+
-- );
|
266
|
+
--
|
267
|
+
-- for(var j = 0; j < downstream_items.length; j++){
|
268
|
+
-- if(!downstream_items[j].hasOwnProperty('id')) {
|
269
|
+
-- continue;
|
270
|
+
-- }
|
271
|
+
-- if(downstream_items[j]['id'] == upstream_id) {
|
272
|
+
-- match = true;
|
273
|
+
-- }
|
274
|
+
-- }
|
275
|
+
--
|
276
|
+
-- // Delete this item if it wasn't in the downstream data set
|
277
|
+
-- if(!match) {
|
278
|
+
-- record.removeLineItem(request.sublist_id, i);
|
279
|
+
-- we_touched_something = true;
|
280
|
+
-- sublist_len--;
|
281
|
+
-- }
|
282
|
+
-- }
|
283
|
+
--
|
284
|
+
-- // 3. Append items
|
285
|
+
-- for(var i = 0; i < downstream_items.length; i++) {
|
286
|
+
-- if(downstream_items[i].hasOwnProperty('id')) {
|
287
|
+
-- continue;
|
288
|
+
-- }
|
289
|
+
-- // New item, append it.
|
290
|
+
-- record.insertLineItem(request.sublist_id, ++sublist_len);
|
291
|
+
-- we_touched_something = true;
|
292
|
+
--
|
293
|
+
-- for(var field in downstream_items[i]) {
|
294
|
+
-- value = downstream_items[i][field];
|
295
|
+
-- record.setLineItemValue(
|
296
|
+
-- request.sublist_id, field, i, value
|
297
|
+
-- );
|
298
|
+
-- }
|
299
|
+
-- }
|
300
|
+
--
|
301
|
+
-- // NetSuite explodes if we try to commit a sublist without altering it
|
302
|
+
-- // in any way, so we have to keep this silly flag around.
|
303
|
+
-- if(we_touched_something) {
|
304
|
+
-- record.commitLineItem(request.sublist_id);
|
305
|
+
-- nlapiSubmitRecord(record, true);
|
306
|
+
-- };
|
307
|
+
--
|
308
|
+
-- return([]);
|
309
|
+
--}
|
310
|
+
--
|
311
|
+
- // Given a record, sublist_id and array of fields, retrieve the whole sublist
|
312
|
+
- // as an array of hashes
|
313
|
+
- function get_sublist(record, sublist_id, fields)
|
314
|
+
diff --git a/spec/resource_spec.rb b/spec/resource_spec.rb
|
315
|
+
index 2d95382..7eaf835 100644
|
316
|
+
--- a/spec/resource_spec.rb
|
317
|
+
+++ b/spec/resource_spec.rb
|
318
|
+
@@ -67,16 +67,6 @@ describe PseudoResource do
|
319
|
+
|
320
|
+
@p.firstname = 'name'
|
321
|
+
|
322
|
+
- note_item1 = @p.new_notes_item(:line => 'line 1')
|
323
|
+
- note_item2 = @p.new_notes_item(:line => 'line 2')
|
324
|
+
-
|
325
|
+
- @p.notes << note_item1
|
326
|
+
- @p.notes << note_item2
|
327
|
+
-
|
328
|
+
- # Replace them, backways to test.
|
329
|
+
- SubList.should_receive(:save!).
|
330
|
+
- and_return([note_item2, note_item1])
|
331
|
+
-
|
332
|
+
Restlet.should_receive(:execute!).
|
333
|
+
with({
|
334
|
+
:action => 'create',
|
335
|
+
@@ -91,9 +81,6 @@ describe PseudoResource do
|
336
|
+
|
337
|
+
expect(@p.firstname).to eql('Name')
|
338
|
+
expect(@p.lastname).to eql('nothing')
|
339
|
+
-
|
340
|
+
- # Backwards now
|
341
|
+
- expect(@p.notes.first.line).to eql('line 2')
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'has a pretty inspect' do
|
345
|
+
@@ -266,14 +253,9 @@ describe PseudoResource do
|
346
|
+
end
|
347
|
+
|
348
|
+
context 'sublists on new object' do
|
349
|
+
- it 'can append a new list to blank object' do
|
350
|
+
+ it 'is empty' do
|
351
|
+
p = PseudoResource.new
|
352
|
+
- expect(p.new_notes_item).to be_a(NSConnector::SubListItem)
|
353
|
+
- p.notes << p.new_notes_item(:line => 'line 1')
|
354
|
+
- p.notes << p.new_notes_item(:line => 'line 2')
|
355
|
+
-
|
356
|
+
- expect(p.notes.pop.line).to eql('line 2')
|
357
|
+
- expect(p.notes.pop.line).to eql('line 1')
|
358
|
+
+ expect(p.notes).to be_empty
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
diff --git a/spec/sublist_spec.rb b/spec/sublist_spec.rb
|
363
|
+
index 38b0147..ef2aea3 100644
|
364
|
+
--- a/spec/sublist_spec.rb
|
365
|
+
+++ b/spec/sublist_spec.rb
|
366
|
+
@@ -16,35 +16,6 @@ describe SubList do
|
367
|
+
)
|
368
|
+
end
|
369
|
+
|
370
|
+
- it 'saves' do
|
371
|
+
- Restlet.should_receive(:execute!).
|
372
|
+
- with({
|
373
|
+
- :action => 'save_sublist',
|
374
|
+
- :type_id => 'contact',
|
375
|
+
- :parent_id => '42',
|
376
|
+
- :sublist_id => 'sublist',
|
377
|
+
- :fields => ['field1', 'field2'],
|
378
|
+
- :data => [
|
379
|
+
- {'field1' => 'data'},
|
380
|
+
- {'field2' => 'otherdata'}
|
381
|
+
- ]
|
382
|
+
- }).and_return(
|
383
|
+
- 'true'
|
384
|
+
- )
|
385
|
+
-
|
386
|
+
- SubList.should_receive(:fetch).and_return('hai')
|
387
|
+
- # Duplicates should be ignored, I figure? I'll be a little
|
388
|
+
- # confused if duplicates ever appear in NetSuite
|
389
|
+
- saved = SubList.save!(
|
390
|
+
- [@item1, @item1, @item2],
|
391
|
+
- @parent,
|
392
|
+
- 'sublist',
|
393
|
+
- ['field1', 'field2']
|
394
|
+
- )
|
395
|
+
-
|
396
|
+
- expect(saved).to eql('hai')
|
397
|
+
- end
|
398
|
+
-
|
399
|
+
it 'fetches' do
|
400
|
+
Restlet.should_receive(:execute!).
|
401
|
+
with({
|
402
|
+
diff --git a/support/restlet.js b/support/restlet.js
|
403
|
+
index 4569862..3a7ad1b 100644
|
404
|
+
--- a/support/restlet.js
|
405
|
+
+++ b/support/restlet.js
|
406
|
+
@@ -193,133 +193,6 @@ function create(request)
|
407
|
+
));
|
408
|
+
}
|
409
|
+
|
410
|
+
-// Save a sublist, trying to merge changes nicely.
|
411
|
+
-//
|
412
|
+
-// Returns:: True, note that you will need to fetch the updated items yourself,
|
413
|
+
-// as netsuite seems to need a little while to think about any newly added
|
414
|
+
-// records or it freaks out.
|
415
|
+
-//
|
416
|
+
-// Raises:: An invalid sublist operation error, often, as apparently you're not
|
417
|
+
-// allowed to delete certain sublists even though you can append to them. Kind
|
418
|
+
-// of really annoying. Not much I can do about it.
|
419
|
+
-//
|
420
|
+
-// Also seems to raise a 500 on modifying existing items if it doesn't want you
|
421
|
+
-// to.
|
422
|
+
-//
|
423
|
+
-// We have the minor problem here of only being able to modify existing line
|
424
|
+
-// items in place, insert new ones and delete old ones, not rewrite the whole
|
425
|
+
-// list. This problem is easily dealt with if we simply don't worry about
|
426
|
+
-// order. So, new items should be appended to the end, and the existing order
|
427
|
+
-// shouldn't be messed with, however if you wanted to say, pull the whole list
|
428
|
+
-// out and reverse the order, you're out of luck.
|
429
|
+
-//
|
430
|
+
-// It's possible to do, I think. I have other things to work on just now.
|
431
|
+
-//
|
432
|
+
-// So we have three basic steps:
|
433
|
+
-//
|
434
|
+
-// 1. Modify all existing records that have changed, identifying by id.
|
435
|
+
-// 2. If the existing record does not appear in the new list, delete it.
|
436
|
+
-// 3. Append new records to the end.
|
437
|
+
-function save_sublist(request)
|
438
|
+
-{
|
439
|
+
- if(!request.hasOwnProperty('parent_id')) {
|
440
|
+
- argument_error("Missing mandatory argument: parent_id");
|
441
|
+
- }
|
442
|
+
-
|
443
|
+
- if(!request.hasOwnProperty('sublist_id')) {
|
444
|
+
- argument_error("Missing mandatory argument: sublist_id");
|
445
|
+
- }
|
446
|
+
-
|
447
|
+
- var record = nlapiLoadRecord(request.type_id, request.parent_id);
|
448
|
+
- var sublist_len = record.getLineItemCount(request.sublist_id);
|
449
|
+
- var downstream_items = request.data;
|
450
|
+
- var we_touched_something = false;
|
451
|
+
-
|
452
|
+
- // 1. Modify all existing records that have been changed.
|
453
|
+
- for(var i = 1; i <= sublist_len; i++) {
|
454
|
+
- var upstream_id = record.getLineItemValue(
|
455
|
+
- request.sublist_id, 'id', i
|
456
|
+
- );
|
457
|
+
- // Search for an id within downstream items.
|
458
|
+
- for(var j = 0; j < downstream_items.length; j++){
|
459
|
+
- if(!downstream_items[j].hasOwnProperty('id')) {
|
460
|
+
- // New record, not interested in this yet
|
461
|
+
- continue;
|
462
|
+
- }
|
463
|
+
-
|
464
|
+
- if(downstream_items[j]['id'] == upstream_id){
|
465
|
+
- // We have a match, we need to merge now
|
466
|
+
- for(var field in downstream_items[j]) {
|
467
|
+
- var upstream_value = record.getLineItemValue(
|
468
|
+
- request.sublist_id, field, i
|
469
|
+
- )
|
470
|
+
-
|
471
|
+
- // Set if changed
|
472
|
+
- if(downstream_items[j][field] != upstream_value) {
|
473
|
+
- record.setLineItemValue(
|
474
|
+
- request.sublist_id,
|
475
|
+
- field,
|
476
|
+
- i,
|
477
|
+
- downstream_items[j][field]
|
478
|
+
- )
|
479
|
+
- we_touched_something = true;
|
480
|
+
- }
|
481
|
+
- }
|
482
|
+
- }
|
483
|
+
- }
|
484
|
+
- }
|
485
|
+
-
|
486
|
+
- // 2. Delete all existing records that aren't in our new dataset
|
487
|
+
- for(var i = sublist_len; i > 0; i--) {
|
488
|
+
- var match = false;
|
489
|
+
- var upstream_id = record.getLineItemValue(
|
490
|
+
- request.sublist_id, 'id', i
|
491
|
+
- );
|
492
|
+
-
|
493
|
+
- for(var j = 0; j < downstream_items.length; j++){
|
494
|
+
- if(!downstream_items[j].hasOwnProperty('id')) {
|
495
|
+
- continue;
|
496
|
+
- }
|
497
|
+
- if(downstream_items[j]['id'] == upstream_id) {
|
498
|
+
- match = true;
|
499
|
+
- }
|
500
|
+
- }
|
501
|
+
-
|
502
|
+
- // Delete this item if it wasn't in the downstream data set
|
503
|
+
- if(!match) {
|
504
|
+
- record.removeLineItem(request.sublist_id, i);
|
505
|
+
- we_touched_something = true;
|
506
|
+
- sublist_len--;
|
507
|
+
- }
|
508
|
+
- }
|
509
|
+
-
|
510
|
+
- // 3. Append items
|
511
|
+
- for(var i = 0; i < downstream_items.length; i++) {
|
512
|
+
- if(downstream_items[i].hasOwnProperty('id')) {
|
513
|
+
- continue;
|
514
|
+
- }
|
515
|
+
- // New item, append it.
|
516
|
+
- record.insertLineItem(request.sublist_id, ++sublist_len);
|
517
|
+
- we_touched_something = true;
|
518
|
+
-
|
519
|
+
- for(var field in downstream_items[i]) {
|
520
|
+
- value = downstream_items[i][field];
|
521
|
+
- record.setLineItemValue(
|
522
|
+
- request.sublist_id, field, i, value
|
523
|
+
- );
|
524
|
+
- }
|
525
|
+
- }
|
526
|
+
-
|
527
|
+
- // NetSuite explodes if we try to commit a sublist without altering it
|
528
|
+
- // in any way, so we have to keep this silly flag around.
|
529
|
+
- if(we_touched_something) {
|
530
|
+
- record.commitLineItem(request.sublist_id);
|
531
|
+
- nlapiSubmitRecord(record, true);
|
532
|
+
- };
|
533
|
+
-
|
534
|
+
- return([]);
|
535
|
+
-}
|
536
|
+
-
|
537
|
+
// Given a record, sublist_id and array of fields, retrieve the whole sublist
|
538
|
+
// as an array of hashes
|
539
|
+
function get_sublist(record, sublist_id, fields)
|
540
|
+
@@ -393,7 +266,6 @@ function main(request)
|
541
|
+
'retrieve' : retrieve,
|
542
|
+
'search' : search,
|
543
|
+
'update' : update,
|
544
|
+
- 'save_sublist' : save_sublist,
|
545
|
+
'fetch_sublist' : fetch_sublist
|
546
|
+
}
|
547
|
+
|