ns_connector 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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: