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,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include NSConnector
|
4
|
+
describe Invoice do
|
5
|
+
it 'should not explode on creation' do
|
6
|
+
Invoice.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should have a to_pdf method' do
|
10
|
+
Restlet.should_receive(:execute!).and_return(
|
11
|
+
[Base64::encode64('yay')]
|
12
|
+
)
|
13
|
+
expect{Invoice.new.to_pdf}.to raise_error(
|
14
|
+
::ArgumentError,
|
15
|
+
/could not find id/i
|
16
|
+
)
|
17
|
+
expect(Invoice.new(:id => 1).to_pdf).to eql('yay')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include NSConnector
|
3
|
+
|
4
|
+
describe Restlet do
|
5
|
+
context 'given a valid config' do
|
6
|
+
before(:each) do
|
7
|
+
NSConnector::Config.set_config! valid_config
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'executes a restlet, passing the options' do
|
11
|
+
options = {:opt1 => 'value1', :opt2 => 'value2'}
|
12
|
+
expected_body = options.merge(
|
13
|
+
:code => Restlet.restlet_code
|
14
|
+
).to_json
|
15
|
+
|
16
|
+
# We already test the auth header below. We simply test
|
17
|
+
# its existance here.
|
18
|
+
expected_request = {
|
19
|
+
:body => expected_body,
|
20
|
+
:headers => {
|
21
|
+
'Authorization' => /^NLAuth/,
|
22
|
+
'Content-Type'=>'application/json'
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
stub_request(:post, "https://netsuite:1234/restlet?").
|
27
|
+
with(expected_request).
|
28
|
+
to_return(
|
29
|
+
:status => 200,
|
30
|
+
:body => '["json"]',
|
31
|
+
)
|
32
|
+
expect(Restlet.execute!(options)).to eql(['json'])
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'fails gracefully with 500' do
|
36
|
+
stub_request(:post, "https://netsuite:1234/restlet?").
|
37
|
+
to_return(:status => 500, :body => 'message')
|
38
|
+
expect{Restlet.execute!({})}.to raise_error(
|
39
|
+
Restlet::RuntimeError,
|
40
|
+
/500: message/
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'tries to create a nice error from a 400' do
|
45
|
+
# Acutal netsuite responses as of 7 Jun 13
|
46
|
+
#
|
47
|
+
# RCRD_DSNT_EXIST -> NotFound
|
48
|
+
error = '{"error" : {"code" : "RCRD_DSNT_EXIST", '\
|
49
|
+
'"message" : "That record does not exist."}}'
|
50
|
+
|
51
|
+
stub_request(:post, "https://netsuite:1234/restlet?").
|
52
|
+
to_return(:status => 400, :body => error)
|
53
|
+
expect{Restlet.execute!({})}.to raise_error(
|
54
|
+
Errors::NotFound, /does not exist/
|
55
|
+
)
|
56
|
+
|
57
|
+
# ANYTHING represents 'CONTACT' or 'CUSTOMER', or
|
58
|
+
# Whatever
|
59
|
+
#
|
60
|
+
# ANYTHING_ALREADY_EXISTS -> Conflict
|
61
|
+
error = '{"error" : {"code" : '\
|
62
|
+
'"ANYTHING_ALREADY_EXISTS", '\
|
63
|
+
'"message" : "A contact record with this name '\
|
64
|
+
'already exists. Every contact record must have '\
|
65
|
+
'a unique name."}}'
|
66
|
+
|
67
|
+
stub_request(:post, "https://netsuite:1234/restlet?").
|
68
|
+
to_return(:status => 400, :body => error)
|
69
|
+
expect{Restlet.execute!({})}.to raise_error(
|
70
|
+
Errors::Conflict, /A contact record/
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'creates a Unknown error from parseable but unknown JSON' do
|
75
|
+
error = '{"error" : {"code" : "GREEN_ROOM", '\
|
76
|
+
'"message" : "This room is... Green."}}'
|
77
|
+
|
78
|
+
stub_request(:post, "https://netsuite:1234/restlet?").
|
79
|
+
to_return(:status => 400, :body => error)
|
80
|
+
expect{Restlet.execute!({})}.to raise_error(
|
81
|
+
Errors::Unknown, /room is/
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'creates a WTF error from unparseable JSON' do
|
86
|
+
stub_request(:post, "https://netsuite:1234/restlet?").
|
87
|
+
to_return(:status => 400, :body => 'omgwtf')
|
88
|
+
expect{Restlet.execute!({})}.to raise_error(
|
89
|
+
Errors::WTF, /omgwtf/
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'fails gracefully with bad JSON' do
|
94
|
+
stub_request(:post, "https://netsuite:1234/restlet?").
|
95
|
+
to_return(:status => 200, :body => 'omgwtf')
|
96
|
+
expect{Restlet.execute!({})}.to raise_error(
|
97
|
+
Restlet::RuntimeError,
|
98
|
+
/omgwtf.*unexpected token/
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
it 'generates a valid auth header' do
|
104
|
+
expect(Restlet.auth_header).to eql(
|
105
|
+
'NLAuth nlauth_account=account_id,' \
|
106
|
+
'nlauth_email=email@site,' \
|
107
|
+
'nlauth_role=123,' \
|
108
|
+
'nlauth_signature=pass%00word'
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'retrieves code' do
|
113
|
+
expect(Restlet.restlet_code).to be_a(String)
|
114
|
+
expect(Restlet.restlet_code).to_not be_empty
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'given an invalid config' do
|
119
|
+
before(:each) do
|
120
|
+
NSConnector::Config.set_config!(:invalid => true)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'everything raises ArgumentError' do
|
124
|
+
expect{Restlet.execute!({})}.
|
125
|
+
to raise_error(
|
126
|
+
NSConnector::Config::ArgumentError
|
127
|
+
)
|
128
|
+
|
129
|
+
expect{Restlet.auth_header}.
|
130
|
+
to raise_error(
|
131
|
+
NSConnector::Config::ArgumentError
|
132
|
+
)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
$: << File.join(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'pry'
|
5
|
+
require 'pp'
|
6
|
+
require 'rspec'
|
7
|
+
require 'webmock/rspec'
|
8
|
+
require 'ns_connector'
|
9
|
+
|
10
|
+
require 'support/mock_data'
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.order = "random"
|
14
|
+
config.color_enabled = true
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include NSConnector
|
3
|
+
|
4
|
+
describe SubListItem do
|
5
|
+
context 'new' do
|
6
|
+
before :each do
|
7
|
+
parent = double(
|
8
|
+
'contact', :type_id => 'contact', :id => '42'
|
9
|
+
)
|
10
|
+
@s = SubListItem.new('sublist', ['field1', 'field2'], parent)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has things we expect' do
|
14
|
+
expect(@s.field1).to eql(nil)
|
15
|
+
@s.field1 = 'yay'
|
16
|
+
expect(@s.field1).to eql('yay')
|
17
|
+
expect(@s.field2).to eql(nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'has a .store' do
|
21
|
+
@s.field1 = 'yay'
|
22
|
+
expect(@s.store).to eql('field1' => 'yay')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include NSConnector
|
3
|
+
|
4
|
+
describe SubList do
|
5
|
+
before :each do
|
6
|
+
@parent = double(
|
7
|
+
'contact', :type_id => 'contact', :id => '42'
|
8
|
+
)
|
9
|
+
@item1 = SubListItem.new(
|
10
|
+
'sublist', ['field1', 'field2'], @parent,
|
11
|
+
:field1 => 'data'
|
12
|
+
)
|
13
|
+
@item2 = SubListItem.new(
|
14
|
+
'sublist', ['field1', 'field2'], @parent,
|
15
|
+
:field2 => 'otherdata'
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'fetches' do
|
20
|
+
Restlet.should_receive(:execute!).
|
21
|
+
with({
|
22
|
+
:action => 'fetch_sublist',
|
23
|
+
:type_id => 'contact',
|
24
|
+
:parent_id => '42',
|
25
|
+
:sublist_id => 'sublist',
|
26
|
+
:fields => ['field1', 'field2'],
|
27
|
+
}).and_return(
|
28
|
+
[
|
29
|
+
{'field1' => 'new'},
|
30
|
+
{'field2' => 'new2'},
|
31
|
+
]
|
32
|
+
)
|
33
|
+
sublist = SubList.fetch(
|
34
|
+
@parent, 'sublist', ['field1', 'field2']
|
35
|
+
)
|
36
|
+
|
37
|
+
expect(sublist).to be_a(Array)
|
38
|
+
expect(sublist).to have(2).things
|
39
|
+
sublist.each do |item|
|
40
|
+
expect(item).to be_a(SubListItem)
|
41
|
+
end
|
42
|
+
expect(sublist.first.field1).to eql('new')
|
43
|
+
expect(sublist.last.field2).to eql('new2')
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.join(
|
3
|
+
File.dirname(__FILE__),
|
4
|
+
'..', 'lib'
|
5
|
+
)
|
6
|
+
|
7
|
+
require 'ns_connector'
|
8
|
+
require 'rspec/autorun'
|
9
|
+
|
10
|
+
unless ARGV.size == 1 then
|
11
|
+
warn "Usage: #{$0} <config as ruby code>"
|
12
|
+
warn "Warning! This script could destroy production data, it is only"\
|
13
|
+
" intended for testing"
|
14
|
+
end
|
15
|
+
|
16
|
+
# We shift the argument off here, or it gets passed to the RSpec runner
|
17
|
+
NSConnector::Config.set_config!(eval(ARGV.shift))
|
18
|
+
|
19
|
+
def customer_id
|
20
|
+
NSConnector::Config[:valid_customer_id]
|
21
|
+
end
|
22
|
+
|
23
|
+
# This only theoretically a read only live conformance test, run at your own
|
24
|
+
# peril! Seriously, if you run this on production data and the internet
|
25
|
+
# explodes, well, you're stupid, unlucky, and I warned you.
|
26
|
+
|
27
|
+
describe 'retrieve' do
|
28
|
+
it 'returns a Hash with an id' do
|
29
|
+
record = NSConnector::Restlet.execute!({
|
30
|
+
:action => 'retrieve',
|
31
|
+
:type_id => 'contact',
|
32
|
+
:fields => ['id', 'firstname', 'lastname'],
|
33
|
+
:data => {
|
34
|
+
:id => customer_id,
|
35
|
+
}
|
36
|
+
})
|
37
|
+
|
38
|
+
expect(record).to be_a(Hash)
|
39
|
+
expect(record['id']).to be_a(String)
|
40
|
+
expect(record.keys).to include('firstname')
|
41
|
+
expect(record.keys).to include('lastname')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'search' do
|
46
|
+
it 'returns a basic search by id' do
|
47
|
+
record = NSConnector::Restlet.execute!(
|
48
|
+
:action => 'search',
|
49
|
+
:type_id => 'contact',
|
50
|
+
:fields => ['id'],
|
51
|
+
:data => {
|
52
|
+
:filters => [
|
53
|
+
['internalid', nil, 'is', customer_id]
|
54
|
+
]
|
55
|
+
}
|
56
|
+
)
|
57
|
+
|
58
|
+
expect(record).to be_a(Array)
|
59
|
+
expect(record).to_not be_empty
|
60
|
+
expect(record.first['id']).to be_a(String)
|
61
|
+
expect(record.first['id']).to_not be_empty
|
62
|
+
end
|
63
|
+
end
|
data/support/restlet.js
ADDED
@@ -0,0 +1,384 @@
|
|
1
|
+
// How large a result set we try to return before splitting it up into smaller
|
2
|
+
// chunks
|
3
|
+
var CHUNK_SIZE = 100;
|
4
|
+
|
5
|
+
// Okay, so this is some dodgy meta shit that I probably really shouldn't be
|
6
|
+
// doing in javascript. It's not my fault the API is odd.
|
7
|
+
function apply_constructor(klass, opts) {
|
8
|
+
function applicator() {
|
9
|
+
// This seems to be the only way to call .apply on a
|
10
|
+
// constructor in javascript
|
11
|
+
return klass.apply(this, opts);
|
12
|
+
}
|
13
|
+
applicator.prototype = klass.prototype;
|
14
|
+
return new applicator();
|
15
|
+
}
|
16
|
+
|
17
|
+
// Stops execution, sending a HTTP 400 response
|
18
|
+
function argument_error(message)
|
19
|
+
{
|
20
|
+
throw nlapiCreateError('400', message);
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
// Returns a simple record as an associative array
|
25
|
+
function get_record_by_id(type_id, fields, id)
|
26
|
+
{
|
27
|
+
var record = nlapiLoadRecord(type_id, id);
|
28
|
+
var response = {};
|
29
|
+
|
30
|
+
for(var i = 0; i < fields.length; i++ ) {
|
31
|
+
response[fields[i]] = record.getFieldValue(fields[i]);
|
32
|
+
}
|
33
|
+
|
34
|
+
return(response);
|
35
|
+
}
|
36
|
+
|
37
|
+
// In order to update an item, we create a diff of what has actually changed,
|
38
|
+
// then commit just those changes.
|
39
|
+
function update(request)
|
40
|
+
{
|
41
|
+
if(!request.data.hasOwnProperty('id')) {
|
42
|
+
argument_error('update action requires an id');
|
43
|
+
}
|
44
|
+
|
45
|
+
var diff = {};
|
46
|
+
var record = nlapiLoadRecord(request.type_id, request.data.id);
|
47
|
+
|
48
|
+
for(var field in request.data) {
|
49
|
+
if(record.getFieldValue(field) != request.data[field]) {
|
50
|
+
diff[field] = request.data[field];
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
for(var field in diff) {
|
55
|
+
record.setFieldValue(field, diff[field]);
|
56
|
+
}
|
57
|
+
|
58
|
+
return(get_record_by_id(
|
59
|
+
request.type_id,
|
60
|
+
request.fields,
|
61
|
+
nlapiSubmitRecord(record, true)
|
62
|
+
));
|
63
|
+
}
|
64
|
+
|
65
|
+
// Return an array of hashes representing the result set.
|
66
|
+
function retrieve_result_set(results, fields)
|
67
|
+
{
|
68
|
+
var response = [];
|
69
|
+
// Can't seem to get all columns back without running nlapiLoadRecord,
|
70
|
+
// I tried a few different ways and netsuite either returned null
|
71
|
+
// responses or exploded spectacularly. So, we are inefficient for now.
|
72
|
+
//
|
73
|
+
// This is what has lead to the whole configurable chunking
|
74
|
+
// implementation.
|
75
|
+
for(var i = 0; i < results.length; i++ ) {
|
76
|
+
var result = results[i];
|
77
|
+
response.push(
|
78
|
+
get_record_by_id(
|
79
|
+
result.getRecordType(),
|
80
|
+
fields,
|
81
|
+
result.getId()
|
82
|
+
)
|
83
|
+
);
|
84
|
+
}
|
85
|
+
|
86
|
+
return response;
|
87
|
+
}
|
88
|
+
|
89
|
+
// Return the requested chunk offset, which should be incremented by one for
|
90
|
+
// each chunk.
|
91
|
+
function search_chunked(search, fields, chunk)
|
92
|
+
{
|
93
|
+
var offset = chunk * CHUNK_SIZE;
|
94
|
+
var results = search.runSearch().getResults(offset, offset + CHUNK_SIZE);
|
95
|
+
if(results.length == 0) {
|
96
|
+
throw nlapiCreateError('400', 'NO_MORE_CHUNKS');
|
97
|
+
}
|
98
|
+
return(retrieve_result_set(results, fields));
|
99
|
+
}
|
100
|
+
|
101
|
+
// Called if we aren't specifically asking for a chunk of the result set.
|
102
|
+
// Returns:: An array of records of length < CHUNK_SIZE
|
103
|
+
// Raises:: A 400, 'CHUNKY_MONKEY' if the result size is >= CHUNK_SIZE, it is
|
104
|
+
// expected that you now send a follow up request asking for chunks until
|
105
|
+
// you recive another error, 'NO_MORE_CHUNKS'
|
106
|
+
function search_no_chunked(search, fields)
|
107
|
+
{
|
108
|
+
var results = search.runSearch().getResults(0, CHUNK_SIZE);
|
109
|
+
|
110
|
+
// If there are CHUNK_SIZE results, we need to chunk
|
111
|
+
if (results.length == CHUNK_SIZE) {
|
112
|
+
// So we send an error message to signify that the client
|
113
|
+
// should make a different kind of search request
|
114
|
+
throw nlapiCreateError('400', 'CHUNKY_MONKEY');
|
115
|
+
}
|
116
|
+
|
117
|
+
return(retrieve_result_set(results, fields));
|
118
|
+
}
|
119
|
+
|
120
|
+
// Only return the columns requested, not whole objects.
|
121
|
+
// Returns:: Array of arrays containing all columns requested.
|
122
|
+
function raw_search(request)
|
123
|
+
{
|
124
|
+
var columns = [];
|
125
|
+
var filters = [];
|
126
|
+
var response = [];
|
127
|
+
|
128
|
+
// Generate filters
|
129
|
+
for(var i = 0; i < request.data.filters.length; i++ ) {
|
130
|
+
filters.push(
|
131
|
+
apply_constructor(
|
132
|
+
nlobjSearchFilter, request.data.filters[i]
|
133
|
+
)
|
134
|
+
);
|
135
|
+
}
|
136
|
+
|
137
|
+
// Doesn't work when we try to create columns then supply them at
|
138
|
+
// creation, so we create them after we create the search.
|
139
|
+
var search = nlapiCreateSearch(request.type_id, filters, []);
|
140
|
+
|
141
|
+
// Now, for whatever reason, we can add columns.
|
142
|
+
for(var i = 0; i < request.data.columns.length; i++ ) {
|
143
|
+
columns.push(apply_constructor(
|
144
|
+
nlobjSearchColumn, request.data.columns[i]
|
145
|
+
));
|
146
|
+
search.addColumn(columns[i]);
|
147
|
+
}
|
148
|
+
|
149
|
+
var search_result = search.runSearch();
|
150
|
+
// We go 1000 at a time as that is the maximum we are allowed
|
151
|
+
var MAX_PER_GET=1000;
|
152
|
+
for(var i = 0; true; i += MAX_PER_GET) {
|
153
|
+
var result_set = search_result.getResults(i, MAX_PER_GET);
|
154
|
+
for(var j = 0; j < result_set.length; j++) {
|
155
|
+
response.push([]);
|
156
|
+
for(var k = 0; k < columns.length; k++) {
|
157
|
+
response[j].push(
|
158
|
+
result_set[j].getValue(columns[k])
|
159
|
+
);
|
160
|
+
}
|
161
|
+
}
|
162
|
+
if(result_set.length < MAX_PER_GET) {
|
163
|
+
// No more results
|
164
|
+
break;
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
return(response);
|
169
|
+
}
|
170
|
+
|
171
|
+
function search(request)
|
172
|
+
{
|
173
|
+
// Maximum results to return before we split our response into a
|
174
|
+
// chunked, multi request response. Note, this has nothing to do with a
|
175
|
+
// HTTP chunked response, it's simply us requesting a different offest
|
176
|
+
// each time.
|
177
|
+
|
178
|
+
var filters = [];
|
179
|
+
for(var i = 0; i < request.data.filters.length; i++ ) {
|
180
|
+
filters.push(
|
181
|
+
apply_constructor(
|
182
|
+
nlobjSearchFilter, request.data.filters[i]
|
183
|
+
)
|
184
|
+
);
|
185
|
+
}
|
186
|
+
|
187
|
+
var search = nlapiCreateSearch(request.type_id, filters, []);
|
188
|
+
|
189
|
+
if(request.data.hasOwnProperty('chunk')){
|
190
|
+
return(search_chunked(
|
191
|
+
search, request.fields, request.data.chunk
|
192
|
+
));
|
193
|
+
} else {
|
194
|
+
return search_no_chunked(search, request.fields);
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
// Retrieve a single record by id
|
199
|
+
function retrieve(request)
|
200
|
+
{
|
201
|
+
if(!request.data.hasOwnProperty('id')) {
|
202
|
+
argument_error('retrieve action requires an id');
|
203
|
+
}
|
204
|
+
|
205
|
+
return(get_record_by_id(
|
206
|
+
request.type_id, request.fields, request.data.id)
|
207
|
+
);
|
208
|
+
}
|
209
|
+
|
210
|
+
// Load a list of fields for type specified by type_id
|
211
|
+
// Returns:: Array of strings
|
212
|
+
// Delete a record by id
|
213
|
+
function delete_id(request)
|
214
|
+
{
|
215
|
+
if(!request.data.hasOwnProperty('id')) {
|
216
|
+
argument_error('delete action requires an id');
|
217
|
+
}
|
218
|
+
|
219
|
+
// Return value is moot, should throw an error on failure
|
220
|
+
nlapiDeleteRecord(request.type_id, parseInt(request.data.id));
|
221
|
+
return([]);
|
222
|
+
}
|
223
|
+
|
224
|
+
// Create a new record
|
225
|
+
function create(request)
|
226
|
+
{
|
227
|
+
var record = nlapiCreateRecord(request.type_id);
|
228
|
+
var response = {};
|
229
|
+
|
230
|
+
for(var field in request.data) {
|
231
|
+
record.setFieldValue(field, request.data[field]);
|
232
|
+
}
|
233
|
+
|
234
|
+
for(var i = 0; i < request.fields.length; i++ ) {
|
235
|
+
response[request.fields[i]] = record.getFieldValue(
|
236
|
+
request.fields[i]
|
237
|
+
);
|
238
|
+
}
|
239
|
+
|
240
|
+
return(get_record_by_id(
|
241
|
+
request.type_id,
|
242
|
+
request.fields,
|
243
|
+
nlapiSubmitRecord(record, true)
|
244
|
+
));
|
245
|
+
}
|
246
|
+
|
247
|
+
// Given a record, sublist_id and array of fields, retrieve the whole sublist
|
248
|
+
// as an array of hashes
|
249
|
+
function get_sublist(record, sublist_id, fields)
|
250
|
+
{
|
251
|
+
var len = record.getLineItemCount(request.sublist_id);
|
252
|
+
var response = [];
|
253
|
+
for(var i = 1; i <= len; i++) {
|
254
|
+
list_item = {};
|
255
|
+
for(var j = 0; j < fields.length; j++) {
|
256
|
+
list_item[fields[j]] = record.getLineItemValue(
|
257
|
+
sublist_id, fields[j], i
|
258
|
+
)
|
259
|
+
}
|
260
|
+
response.push(list_item);
|
261
|
+
}
|
262
|
+
return(response);
|
263
|
+
}
|
264
|
+
|
265
|
+
// Basically a wrapper for get_sublist()
|
266
|
+
function fetch_sublist(request)
|
267
|
+
{
|
268
|
+
if(!request.hasOwnProperty('parent_id')) {
|
269
|
+
argument_error("Missing mandatory argument: parent_id");
|
270
|
+
}
|
271
|
+
|
272
|
+
if(!request.hasOwnProperty('sublist_id')) {
|
273
|
+
argument_error("Missing mandatory argument: sublist_id");
|
274
|
+
}
|
275
|
+
|
276
|
+
var record = nlapiLoadRecord(request.type_id, request.parent_id);
|
277
|
+
return(get_sublist(record, request.sublist_id, request.fields));
|
278
|
+
}
|
279
|
+
|
280
|
+
// Make sure we have the required arguments in our request object
|
281
|
+
function pre_flight_check(request)
|
282
|
+
{
|
283
|
+
delete(request['code']);
|
284
|
+
|
285
|
+
if (!request.hasOwnProperty('action')) {
|
286
|
+
argument_error("Missing mandatory argument: action");
|
287
|
+
}
|
288
|
+
|
289
|
+
// Some actions may not care about these
|
290
|
+
if (!request.hasOwnProperty('fields')) {
|
291
|
+
request.fields = [];
|
292
|
+
}
|
293
|
+
if (!request.hasOwnProperty('sublists')) {
|
294
|
+
request.sublists = {};
|
295
|
+
}
|
296
|
+
if (!request.hasOwnProperty('data')) {
|
297
|
+
request.data = {};
|
298
|
+
}
|
299
|
+
}
|
300
|
+
|
301
|
+
// Render a PDF invoice
|
302
|
+
//
|
303
|
+
// Arguments::
|
304
|
+
// invoice_id:: the ID of the invoice to render
|
305
|
+
//
|
306
|
+
// Returns:: An array with only element, a base64 encoded string of the
|
307
|
+
// generated PDF
|
308
|
+
function invoice_pdf(request) {
|
309
|
+
if(!request.hasOwnProperty('invoice_id')) {
|
310
|
+
argument_error('Missing mandatory argument: invoice_id');
|
311
|
+
};
|
312
|
+
|
313
|
+
var file = nlapiPrintRecord(
|
314
|
+
'TRANSACTION',
|
315
|
+
request.invoice_id,
|
316
|
+
'PDF',
|
317
|
+
null
|
318
|
+
);
|
319
|
+
|
320
|
+
return [file.getValue()];
|
321
|
+
}
|
322
|
+
|
323
|
+
// Attach all customers in data from ourselves
|
324
|
+
// Arguments::
|
325
|
+
// target_type_id:: target 'type_id'
|
326
|
+
// attachee_id:: id of record type 'type_id' to attach from
|
327
|
+
// data:: array of ids to attach
|
328
|
+
// attributes:: optional attributes
|
329
|
+
function attach(request) {
|
330
|
+
for(var i = 0; i < request.data.length; i++) {
|
331
|
+
nlapiAttachRecord(
|
332
|
+
request.type_id,
|
333
|
+
request.attachee_id,
|
334
|
+
request.target_type_id,
|
335
|
+
parseInt(request.data[i]),
|
336
|
+
request.attributes
|
337
|
+
);
|
338
|
+
}
|
339
|
+
return([]);
|
340
|
+
}
|
341
|
+
//
|
342
|
+
// Detach all customers in data from ourselves
|
343
|
+
// Arguments::
|
344
|
+
// target_type_id:: target 'type_id'
|
345
|
+
// attachee_id:: id of record type 'type_id' to detach from
|
346
|
+
// data:: array of ids to attach
|
347
|
+
// attributes:: optional attributes
|
348
|
+
function detach(request) {
|
349
|
+
for(var i = 0; i < request.data.length; i++) {
|
350
|
+
nlapiDetachRecord(
|
351
|
+
request.type_id,
|
352
|
+
request.attachee_id,
|
353
|
+
request.target_type_id,
|
354
|
+
parseInt(request.data[i])
|
355
|
+
);
|
356
|
+
}
|
357
|
+
return([]);
|
358
|
+
}
|
359
|
+
|
360
|
+
// As the last visible function, this is the one actually run and the return
|
361
|
+
// value is sent back to the client as JSON.
|
362
|
+
function main(request)
|
363
|
+
{
|
364
|
+
pre_flight_check(request);
|
365
|
+
|
366
|
+
var actions = {
|
367
|
+
'delete' : delete_id, // delete is a reserved word
|
368
|
+
'create' : create,
|
369
|
+
'retrieve' : retrieve,
|
370
|
+
'search' : search,
|
371
|
+
'update' : update,
|
372
|
+
'fetch_sublist' : fetch_sublist,
|
373
|
+
'invoice_pdf' : invoice_pdf,
|
374
|
+
'attach' : attach,
|
375
|
+
'detach' : detach,
|
376
|
+
'raw_search' : raw_search,
|
377
|
+
}
|
378
|
+
|
379
|
+
if(!(request.action in actions)) {
|
380
|
+
argument_error("Unknown action: " + request.action);
|
381
|
+
}
|
382
|
+
|
383
|
+
return actions[request.action](request);
|
384
|
+
}
|