ns_connector 0.0.6 → 0.0.7

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/CHANGELOG ADDED
@@ -0,0 +1,4 @@
1
+ v0.0.7:
2
+ * SubList creation support on new records (and only new records)
3
+ * Record transformation via transform!
4
+ * New resource type: CustomerPayment
data/README.rdoc CHANGED
@@ -21,13 +21,16 @@ How is this different to the other netsuite connector gems out there?
21
21
  * Multithreaded chunked searching for retrieving large datasets
22
22
  * ActiveRecord (vaguely) alike interface
23
23
  * Read only sublist support.
24
+ * SubList item creation on creating new records only. (>= v0.0.7)
24
25
  * Attaching and detaching records
25
26
  * Invoice PDF generation.
27
+ * Record transformations.
26
28
 
27
29
  == Supported NetSuite types
28
30
  It's pretty trivial to add a new one yourself. These have been tested to work:
29
31
  * Contact
30
32
  * Customer
33
+ * CustomerPayment
31
34
  * Invoice
32
35
 
33
36
  == Installation
@@ -107,6 +110,23 @@ Example:
107
110
  c.lastname
108
111
  => 'Abc'
109
112
 
113
+ SubLists can be added to a record on creation. Here is how that works:
114
+
115
+ include NSConnector
116
+ c = Contact.new(:firstname => 'name')
117
+ c.sublists
118
+ => {:addressbook=>
119
+ [:addr1,
120
+ ...
121
+
122
+ c.addressbook << c.new_addressbook_item({
123
+ :addr1 => 'address line 1',
124
+ :city => 'unicorn valley'
125
+ })
126
+
127
+ c.save!
128
+ => true
129
+
110
130
  === Reading
111
131
  You can find by any field or by internalId.
112
132
  Example:
@@ -170,6 +190,19 @@ Example:
170
190
  2. By Record
171
191
  c = Contact.find(42)
172
192
  c.delete!
193
+
194
+ == Record transformations
195
+ You can transform records into other records as per the supported transformation types in NetSuite [2]
196
+
197
+ e.g.
198
+ Invoice.find(2106).transform!(CustomerPayment) do |payment|
199
+ payment.ccnumber = '4222222222222'
200
+ end
201
+ => {"id"=>"2107",
202
+ "account"=>"",
203
+ "allowemptycards"=>nil,
204
+ "applied"=>"99.99" ...
205
+
173
206
 
174
207
  === Troubleshooting note:
175
208
  Should you run into an error something like:
@@ -189,3 +222,4 @@ MIT
189
222
 
190
223
  == References
191
224
  [1] {SuiteScript Records Browser}[https://system.netsuite.com/help/helpcenter/en_US/RecordsBrowser/2013_1/index.html]
225
+ [2] {Supported Transformation Types}[https://system.netsuite.com/help/helpcenter/en_US/Output/Help/SuiteFlex/SuiteScript/SSAPI_RecordAPIs.html#N1030776904-18]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.7
@@ -6,6 +6,7 @@ require 'ns_connector/hash'
6
6
  require 'ns_connector/sublist'
7
7
  require 'ns_connector/sublist_item'
8
8
  require 'ns_connector/attaching'
9
+ require 'ns_connector/transforming'
9
10
 
10
11
  # This is a 'meta' class that all our useful NetSuite classes inherit from,
11
12
  # overriding what they may need to. For example:
@@ -17,6 +18,7 @@ class NSConnector::Resource
17
18
  include NSConnector::FieldStore
18
19
  extend NSConnector::ChunkedSearching
19
20
  extend NSConnector::Attaching
21
+ extend NSConnector::Transforming
20
22
 
21
23
  attr_accessor :store
22
24
 
@@ -97,6 +99,16 @@ class NSConnector::Resource
97
99
  self.class.detach!(klass, id, ids)
98
100
  end
99
101
 
102
+ # Transform this instance into target klass
103
+ # Arguments::
104
+ # klass:: Target class, e.g. CustomerPayment
105
+ # &block:: optional block, will recieve an instance of target klass
106
+ # to perform optional modifications to the object before it is
107
+ # saved in NetSuite, like setting payment details.
108
+ def transform!(klass, &block)
109
+ self.class.transform!(klass, id, &block)
110
+ end
111
+
100
112
  # Format an object like: '#<NSConnector::PseudoResource:1>'
101
113
  def inspect
102
114
  "#<NSConnector::#{self.class}:#{id.inspect}>"
@@ -115,13 +127,21 @@ class NSConnector::Resource
115
127
  # Raises:: NSConnector::Errors various errors if something explodes
116
128
  # Returns:: true
117
129
  def save!
130
+ # Convert all of our sublist objects to hashes
131
+ sublists = Hash[@sublist_store.map {|sublist_id, objects|
132
+ [sublist_id, objects.map {|object|
133
+ object.to_hash
134
+ }]
135
+ }]
136
+
118
137
  @store = NSConnector::Restlet.execute!(
119
138
  :action => in_netsuite? ? 'update' : 'create',
120
139
  :type_id => type_id,
121
140
  :fields => fields,
122
- :data => @store
141
+ :data => @store,
142
+ :sublists => sublists,
123
143
  )
124
- # If we got this far, we're definitely in NetSuite
144
+ # If we got this far, we're probably in NetSuite
125
145
  @in_netsuite = true
126
146
 
127
147
  return true
@@ -282,6 +302,17 @@ class NSConnector::Resource
282
302
 
283
303
  @sublist_store[sublist_name] ||= []
284
304
  end
305
+
306
+ define_method(
307
+ "new_#{sublist_name}_item"
308
+ ) do |upstream_store = nil|
309
+ NSConnector::SubListItem.new(
310
+ sublist_name,
311
+ fields,
312
+ self,
313
+ upstream_store
314
+ )
315
+ end
285
316
  end
286
317
  end
287
318
  end
@@ -1,3 +1,4 @@
1
1
  require 'ns_connector/resources/contact'
2
2
  require 'ns_connector/resources/customer'
3
+ require 'ns_connector/resources/customer_payment'
3
4
  require 'ns_connector/resources/invoice'
@@ -0,0 +1,204 @@
1
+ require 'ns_connector/resource'
2
+
3
+ # == CustomerPayment resource
4
+ # === Fields
5
+ # * account
6
+ # * allowemptycards
7
+ # * applied
8
+ # * aracct
9
+ # * authcode
10
+ # * autoapply
11
+ # * balance
12
+ # * ccapproved
13
+ # * ccavsstreetmatch
14
+ # * ccavszipmatch
15
+ # * ccexpiredate
16
+ # * cchold
17
+ # * ccholdetails
18
+ # * cciavsmatch
19
+ # * ccispurchasecardbin
20
+ # * ccname
21
+ # * ccnumber
22
+ # * ccprocessoraccount
23
+ # * ccsecuritycode
24
+ # * ccsecuritycodematch
25
+ # * ccstreet
26
+ # * cczipcode
27
+ # * chargeit
28
+ # * checknum
29
+ # * consolidatebalance
30
+ # * createddate
31
+ # * creditcard
32
+ # * creditcardprocessor
33
+ # * currency
34
+ # * currencyname
35
+ # * currencysymbol
36
+ # * customer
37
+ # * customercode
38
+ # * customform
39
+ # * debitcardissueno
40
+ # * department
41
+ # * entitynexus
42
+ # * exchangerate
43
+ # * externalid
44
+ # * ignoreavs
45
+ # * ignorecsc
46
+ # * isbasecurrency
47
+ # * ispurchasecard
48
+ # * lastmodifieddate
49
+ # * location
50
+ # * memo
51
+ # * nexus
52
+ # * overridehold
53
+ # * overrideholdchecked
54
+ # * payment
55
+ # * paymenteventdate
56
+ # * paymenteventholdreason
57
+ # * paymenteventpurchasedatasent
58
+ # * paymenteventresult
59
+ # * paymenteventtype
60
+ # * paymenteventupdatedby
61
+ # * paymentmethod
62
+ # * pending
63
+ # * pnrefnum
64
+ # * postingperiod
65
+ # * softdescriptor
66
+ # * status
67
+ # * statusRef
68
+ # * subsidiary
69
+ # * threedstatuscode
70
+ # * tobeemailed
71
+ # * total
72
+ # * trandate
73
+ # * tranid
74
+ # * unapplied
75
+ # * undepfunds
76
+ # * validfrom
77
+ #
78
+ # === SubLists
79
+ # * apply (invoices)
80
+ # * credit
81
+ # * deposit
82
+
83
+ class NSConnector::CustomerPayment < NSConnector::Resource
84
+ @type_id = 'customerpayment'
85
+ @fields = [
86
+ :id,
87
+ :account,
88
+ :allowemptycards,
89
+ :applied,
90
+ :aracct,
91
+ :authcode,
92
+ :autoapply,
93
+ :balance,
94
+ :ccapproved,
95
+ :ccavsstreetmatch,
96
+ :ccavszipmatch,
97
+ :ccexpiredate,
98
+ :cchold,
99
+ :ccholdetails,
100
+ :cciavsmatch,
101
+ :ccispurchasecardbin,
102
+ :ccname,
103
+ :ccnumber,
104
+ :ccprocessoraccount,
105
+ :ccsecuritycode,
106
+ :ccsecuritycodematch,
107
+ :ccstreet,
108
+ :cczipcode,
109
+ :chargeit,
110
+ :checknum,
111
+ :consolidatebalance,
112
+ :createddate,
113
+ :creditcard,
114
+ :creditcardprocessor,
115
+ :currency,
116
+ :currencyname,
117
+ :currencysymbol,
118
+ :customer,
119
+ :customercode,
120
+ :customform,
121
+ :debitcardissueno,
122
+ :department,
123
+ :entitynexus,
124
+ :exchangerate,
125
+ :externalid,
126
+ :ignoreavs,
127
+ :ignorecsc,
128
+ :isbasecurrency,
129
+ :ispurchasecard,
130
+ :lastmodifieddate,
131
+ :location,
132
+ :memo,
133
+ :nexus,
134
+ :overridehold,
135
+ :overrideholdchecked,
136
+ :payment,
137
+ :paymenteventdate,
138
+ :paymenteventholdreason,
139
+ :paymenteventpurchasedatasent,
140
+ :paymenteventresult,
141
+ :paymenteventtype,
142
+ :paymenteventupdatedby,
143
+ :paymentmethod,
144
+ :pending,
145
+ :pnrefnum,
146
+ :postingperiod,
147
+ :softdescriptor,
148
+ :status,
149
+ :statusRef,
150
+ :subsidiary,
151
+ :threedstatuscode,
152
+ :tobeemailed,
153
+ :total,
154
+ :trandate,
155
+ :tranid,
156
+ :unapplied,
157
+ :undepfunds,
158
+ :validfrom,
159
+ ]
160
+ @sublists = {
161
+ :apply => [
162
+ :amount,
163
+ :apply,
164
+ :applydate,
165
+ :createdfrom,
166
+ :disc,
167
+ :discamt,
168
+ :discdate,
169
+ :doc,
170
+ :due,
171
+ :duedate,
172
+ :internalid,
173
+ :job,
174
+ :line,
175
+ :refnum,
176
+ :total,
177
+ :url,
178
+ ],
179
+ :credit => [
180
+ :amount,
181
+ :apply,
182
+ :createdfrom,
183
+ :creditdate,
184
+ :doc,
185
+ :due,
186
+ :duedate,
187
+ :internalid,
188
+ :line,
189
+ :refnum,
190
+ :total,
191
+ :url,
192
+ ],
193
+ :deposit => [
194
+ :amount,
195
+ :apply,
196
+ :currency,
197
+ :depositdate,
198
+ :doc,
199
+ :remaining,
200
+ :total,
201
+ :url,
202
+ ]
203
+ }
204
+ end
@@ -22,4 +22,8 @@ class NSConnector::SubListItem
22
22
  def inspect
23
23
  "#<NSConnector::#{self.class}:#{name}>"
24
24
  end
25
+
26
+ def to_hash
27
+ @store
28
+ end
25
29
  end
@@ -0,0 +1,34 @@
1
+ # Provide a transform! method
2
+ module NSConnector::Transforming
3
+ # Transform record to target class, given id.
4
+ # Optionally set fields on the target record before saving if passed
5
+ # block.
6
+ # Arguments::
7
+ # klass:: target class to transform into, e.g. CustomerPayment
8
+ # id:: internal id of source record to transform
9
+ # &block:: optional block, passed a newly created object of target
10
+ # klass, anything you set on this class will be set in netsuite
11
+ # before saving the newly created object.
12
+ # Example::
13
+ # Invoice.transform!(CustomerPayment, 500) do |payment|
14
+ # payment.ccnumber = '422222222'
15
+ # payment.ccexpiry = 'invalid'
16
+ # end
17
+ # => #<NSConnector::NSConnector::CustomerPayment:"123">
18
+ def transform!(klass, id, &block)
19
+ target = klass.new
20
+ if block_given? then
21
+ # User sets what they want on the target
22
+ yield target
23
+ end
24
+
25
+ NSConnector::Restlet.execute!(
26
+ :action => 'transform',
27
+ :source_type_id => type_id,
28
+ :target_type_id => klass.type_id,
29
+ :source_id => id,
30
+ :fields => klass.fields,
31
+ :data => target.store
32
+ )
33
+ end
34
+ end
@@ -84,13 +84,19 @@ describe PseudoResource do
84
84
  }
85
85
 
86
86
  @p.firstname = 'name'
87
+ @p.notes << @p.new_notes_item(:line => 'line1')
87
88
 
88
89
  Restlet.should_receive(:execute!).
89
90
  with({
90
91
  :action => 'create',
91
92
  :type_id => 'pseudoresource',
92
93
  :fields => ['id', 'firstname', 'lastname'],
93
- :data => {'firstname' => 'name'}
94
+ :data => {'firstname' => 'name'},
95
+ :sublists => {
96
+ :notes => [
97
+ {'line' => 'line1'}
98
+ ]
99
+ },
94
100
  }).
95
101
  once.
96
102
  and_return(ns_reply)
@@ -241,7 +247,8 @@ describe PseudoResource do
241
247
  'firstname' => 'new',
242
248
  'lastname' => 'orig',
243
249
  'id' => '1'
244
- }
250
+ },
251
+ :sublists => {}
245
252
  }).
246
253
  once.
247
254
  and_return({'firstname' => 'New'})
@@ -314,6 +321,26 @@ describe PseudoResource do
314
321
  end
315
322
  end
316
323
 
324
+ context 'transform' do
325
+ include_context 'found_resource'
326
+ it 'transform! s' do
327
+ Restlet.should_receive(:execute!).
328
+ with({
329
+ :action=>"transform",
330
+ :source_type_id=>"pseudoresource",
331
+ :target_type_id=>"otherresource",
332
+ :source_id=>"1",
333
+ :fields=>["id", "fax"],
334
+ :data=>{"fax"=>"123"}
335
+ })
336
+
337
+ @found_resource.transform!(OtherResource) do |o|
338
+ expect(o).to be_a(OtherResource)
339
+ o.fax = '123'
340
+ end
341
+ end
342
+ end
343
+
317
344
  context 'link aliases' do
318
345
  include_context 'found_resource'
319
346
 
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ include NSConnector
4
+ describe CustomerPayment do
5
+ it 'should not explode on creation' do
6
+ CustomerPayment.new
7
+ end
8
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ class MagicResource
4
+ extend NSConnector::Transforming
5
+ def initialize
6
+ end
7
+ def self.type_id
8
+ 'magic'
9
+ end
10
+ end
11
+
12
+ class MagicTarget
13
+ attr_reader :store
14
+ extend NSConnector::Transforming
15
+ def initialize
16
+ @store = {}
17
+ end
18
+ def field1=(v)
19
+ @store[:field1] = v
20
+ end
21
+ class << self
22
+ def type_id
23
+ 'target'
24
+ end
25
+ def fields
26
+ [:field1]
27
+ end
28
+ end
29
+ end
30
+
31
+ describe NSConnector::Transforming do
32
+ it 'transform! with no block works' do
33
+ NSConnector::Restlet.should_receive(:execute!).with({
34
+ :action => 'transform',
35
+ :source_type_id => 'magic',
36
+ :target_type_id => 'target',
37
+ :source_id => 42,
38
+ :data => {},
39
+ :fields => [:field1]
40
+ })
41
+
42
+ MagicResource.transform!(MagicTarget, 42)
43
+ end
44
+ it 'transform! with no block works' do
45
+ NSConnector::Restlet.should_receive(:execute!).with({
46
+ :action => 'transform',
47
+ :source_type_id => 'magic',
48
+ :target_type_id => 'target',
49
+ :source_id => 42,
50
+ :data => {:field1 => 'value'},
51
+ :fields => [:field1]
52
+ })
53
+
54
+ MagicResource.transform!(MagicTarget, 42) do |magic_target|
55
+ magic_target.field1 = 'value'
56
+ end
57
+ end
58
+ end
data/support/restlet.js CHANGED
@@ -34,6 +34,33 @@ function get_record_by_id(type_id, fields, id)
34
34
  return(response);
35
35
  }
36
36
 
37
+ // Transfrom from a record type, to another, given ID. The set a bunch of
38
+ // fields on that new record type.
39
+ // Arguments::
40
+ // source_id:: source record internal id
41
+ // source_type_id:: source record type, e.g. invoice
42
+ // target_type_id:: target record type, e.g. customerpayment
43
+ // data:: hash of key/values to set before saving the new deferred record
44
+ // fields:: array of fields for target type
45
+ function transform(request)
46
+ {
47
+ var deferred = nlapiTransformRecord(
48
+ request.source_type_id,
49
+ request.source_id,
50
+ request.target_type_id
51
+ );
52
+
53
+ for(var field in request.data) {
54
+ deferred.setFieldValue(field, request.data[field]);
55
+ }
56
+
57
+ return(get_record_by_id(
58
+ request.target_type_id,
59
+ request.fields,
60
+ nlapiSubmitRecord(deferred, true)
61
+ ));
62
+ }
63
+
37
64
  // In order to update an item, we create a diff of what has actually changed,
38
65
  // then commit just those changes.
39
66
  function update(request)
@@ -42,6 +69,8 @@ function update(request)
42
69
  argument_error('update action requires an id');
43
70
  }
44
71
 
72
+ return(nlapiSubmitRecord(newrec, true));
73
+
45
74
  var diff = {};
46
75
  var record = nlapiLoadRecord(request.type_id, request.data.id);
47
76
 
@@ -225,16 +254,31 @@ function delete_id(request)
225
254
  function create(request)
226
255
  {
227
256
  var record = nlapiCreateRecord(request.type_id);
228
- var response = {};
229
257
 
230
258
  for(var field in request.data) {
231
259
  record.setFieldValue(field, request.data[field]);
232
260
  }
233
261
 
234
- for(var i = 0; i < request.fields.length; i++ ) {
235
- response[request.fields[i]] = record.getFieldValue(
236
- request.fields[i]
237
- );
262
+ // request.sublists is a hash that looks like this:
263
+ // {'addressbook' => [{'addr1' => 'line 1 of address'}]
264
+ //
265
+ // Note: For some reason when you try to commit the line item, as the
266
+ // documentation says you *must*, the item is not saved.
267
+ //
268
+ // So, we don't. And it works.
269
+ for(var sublist_id in request.sublists) {
270
+ for(var i = 0; i < request.sublists[sublist_id].length; i++) {
271
+ var item = request.sublists[sublist_id][i];
272
+ record.insertLineItem(sublist_id, i + 1);
273
+ for(var subfield in item) {
274
+ record.setLineItemValue(
275
+ sublist_id,
276
+ subfield,
277
+ i + 1,
278
+ item[subfield]
279
+ );
280
+ }
281
+ }
238
282
  }
239
283
 
240
284
  return(get_record_by_id(
@@ -374,6 +418,7 @@ function main(request)
374
418
  'attach' : attach,
375
419
  'detach' : detach,
376
420
  'raw_search' : raw_search,
421
+ 'transform' : transform
377
422
  }
378
423
 
379
424
  if(!(request.action in actions)) {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ns_connector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-17 00:00:00.000000000 Z
12
+ date: 2013-06-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -149,6 +149,7 @@ extra_rdoc_files:
149
149
  - LICENSE.txt
150
150
  - README.rdoc
151
151
  files:
152
+ - CHANGELOG
152
153
  - Gemfile
153
154
  - Gemfile.lock
154
155
  - Guardfile
@@ -168,10 +169,12 @@ files:
168
169
  - lib/ns_connector/resources.rb
169
170
  - lib/ns_connector/resources/contact.rb
170
171
  - lib/ns_connector/resources/customer.rb
172
+ - lib/ns_connector/resources/customer_payment.rb
171
173
  - lib/ns_connector/resources/invoice.rb
172
174
  - lib/ns_connector/restlet.rb
173
175
  - lib/ns_connector/sublist.rb
174
176
  - lib/ns_connector/sublist_item.rb
177
+ - lib/ns_connector/transforming.rb
175
178
  - misc/failed_sublist_saving_patch
176
179
  - scripts/run_restlet
177
180
  - scripts/test_shell
@@ -180,6 +183,7 @@ files:
180
183
  - spec/config_spec.rb
181
184
  - spec/resource_spec.rb
182
185
  - spec/resources/contact_spec.rb
186
+ - spec/resources/customer_payment_spec.rb
183
187
  - spec/resources/customer_spec.rb
184
188
  - spec/resources/invoice_spec.rb
185
189
  - spec/restlet_spec.rb
@@ -187,6 +191,7 @@ files:
187
191
  - spec/sublist_item_spec.rb
188
192
  - spec/sublist_spec.rb
189
193
  - spec/support/mock_data.rb
194
+ - spec/transforming_spec.rb
190
195
  - support/read_only_test
191
196
  - support/restlet.js
192
197
  - support/super_dangerous_write_test
@@ -205,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
205
210
  version: '0'
206
211
  segments:
207
212
  - 0
208
- hash: 2425652984816094111
213
+ hash: 3862389985768265126
209
214
  required_rubygems_version: !ruby/object:Gem::Requirement
210
215
  none: false
211
216
  requirements: