netsuite 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Gemfile +0 -2
  2. data/README.md +154 -0
  3. data/lib/netsuite.rb +7 -0
  4. data/lib/netsuite/actions/add.rb +6 -21
  5. data/lib/netsuite/actions/delete.rb +7 -10
  6. data/lib/netsuite/actions/get.rb +7 -10
  7. data/lib/netsuite/actions/get_list.rb +77 -0
  8. data/lib/netsuite/actions/get_select_value.rb +71 -0
  9. data/lib/netsuite/actions/search.rb +151 -36
  10. data/lib/netsuite/actions/search_more_with_id.rb +3 -2
  11. data/lib/netsuite/actions/update.rb +16 -16
  12. data/lib/netsuite/configuration.rb +29 -15
  13. data/lib/netsuite/namespaces/list_support.rb +11 -0
  14. data/lib/netsuite/records/account.rb +1 -0
  15. data/lib/netsuite/records/base_ref_list.rb +33 -0
  16. data/lib/netsuite/records/cash_sale.rb +7 -0
  17. data/lib/netsuite/records/custom_field.rb +2 -1
  18. data/lib/netsuite/records/custom_field_list.rb +62 -12
  19. data/lib/netsuite/records/custom_record.rb +1 -7
  20. data/lib/netsuite/records/custom_record_ref.rb +7 -0
  21. data/lib/netsuite/records/customer.rb +4 -3
  22. data/lib/netsuite/records/phone_call.rb +29 -0
  23. data/lib/netsuite/records/record_ref.rb +1 -9
  24. data/lib/netsuite/records/sales_order.rb +2 -1
  25. data/lib/netsuite/records/support_case.rb +32 -0
  26. data/lib/netsuite/records/task.rb +4 -3
  27. data/lib/netsuite/records/transaction.rb +4 -21
  28. data/lib/netsuite/support/actions.rb +4 -0
  29. data/lib/netsuite/support/records.rb +3 -0
  30. data/lib/netsuite/support/search_result.rb +53 -29
  31. data/lib/netsuite/version.rb +1 -1
  32. data/netsuite.gemspec +1 -1
  33. data/spec/netsuite/actions/add_spec.rb +9 -13
  34. data/spec/netsuite/actions/delete_spec.rb +5 -8
  35. data/spec/netsuite/actions/get_spec.rb +39 -24
  36. data/spec/netsuite/actions/search_spec.rb +149 -0
  37. data/spec/netsuite/actions/update_spec.rb +9 -13
  38. data/spec/netsuite/configuration_spec.rb +3 -9
  39. data/spec/netsuite/records/custom_field_list_spec.rb +19 -5
  40. data/spec/netsuite/records/customer_spec.rb +2 -2
  41. data/spec/netsuite/records/phone_call_spec.rb +23 -0
  42. data/spec/netsuite/records/support_case_spec.rb +157 -0
  43. data/spec/support/fixtures/search/saved_search_customer.xml +54 -0
  44. data/spec/support/fixtures/search/saved_search_joined_custom_customer.xml +87 -0
  45. metadata +21 -4
@@ -1,4 +1,3 @@
1
- # TODO: Tests
2
1
  module NetSuite
3
2
  module Actions
4
3
  class Search
@@ -12,23 +11,20 @@ module NetSuite
12
11
  private
13
12
 
14
13
  def request
15
- # https://system.netsuite.com/help/helpcenter/en_US/Output/Help/SuiteFlex/WebServices/STP_SettingSearchPreferences.html#N2028598271-3
14
+ # https://system.netsuite.com/help/helpcenter/en_US/Output/Help/SuiteCloudCustomizationScriptingWebServices/SuiteTalkWebServices/SettingSearchPreferences.html
15
+ # https://webservices.netsuite.com/xsd/platform/v2012_2_0/messages.xsd
16
+
16
17
  preferences = NetSuite::Configuration.auth_header
17
- preferences = preferences.merge((@options[:preferences] || {}).inject({}) do |h, (k, v)|
18
- h[k.to_s.lower_camelcase] = v
19
- h
20
- end)
21
-
22
- NetSuite::Configuration.connection(
23
- namespaces: {
24
- 'xmlns:platformMsgs' => "urn:messages_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
25
- 'xmlns:platformCore' => "urn:core_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
26
- 'xmlns:platformCommon' => "urn:common_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
27
- 'xmlns:listRel' => "urn:relationships_#{NetSuite::Configuration.api_version}.lists.webservices.netsuite.com",
28
- 'xmlns:tranSales' => "urn:sales_#{NetSuite::Configuration.api_version}.transactions.webservices.netsuite.com",
29
- },
30
- soap_header: preferences
31
- ).call :search, :message => request_body
18
+ preferences = preferences.merge(
19
+ (@options[:preferences] || {}).inject({'platformMsgs:SearchPreferences' => {}}) do |h, (k, v)|
20
+ h['platformMsgs:SearchPreferences'][k.to_s.lower_camelcase] = v
21
+ h
22
+ end
23
+ )
24
+
25
+ NetSuite::Configuration
26
+ .connection(soap_header: preferences)
27
+ .call (@options.has_key?(:search_id)? :search_more_with_id : :search), :message => request_body
32
28
  end
33
29
 
34
30
  # basic search XML
@@ -46,41 +42,151 @@ module NetSuite
46
42
  # </soap:Body>
47
43
 
48
44
  def request_body
45
+ if @options.has_key?(:search_id)
46
+ return {
47
+ 'pageIndex' => @options[:page_index],
48
+ 'searchId' => @options[:search_id],
49
+ }
50
+ end
51
+
52
+ # columns is only needed for advanced search results
53
+ columns = @options[:columns] || {}
49
54
  criteria = @options[:criteria] || @options
50
55
 
51
- # TODO wish there was a cleaner way to do this: we need the namespace of the record
56
+ # TODO find cleaner solution for pulling the namespace of the record, which is a instance method
52
57
  example_instance = @klass.new
53
58
  namespace = example_instance.record_namespace
54
59
 
55
- # extract the class name without the module
60
+ # extract the class name
56
61
  class_name = @klass.to_s.split("::").last
57
62
 
58
- search_record = {}
63
+ criteria_structure = {}
64
+ columns_structure = columns
65
+ saved_search_id = criteria.delete(:saved)
59
66
 
60
- criteria.each_pair do |condition_category, conditions|
61
- search_record["#{namespace}:#{condition_category}"] = conditions.inject({}) do |h, condition|
62
- h["platformCommon:#{condition[:field]}"] = {
63
- "platformCore:searchValue" => condition[:value]
64
- }
67
+ # TODO this whole thing needs to be refactored so we can apply some of the same logic to the
68
+ # column creation xml
65
69
 
66
- (h[:attributes!] ||= {}).merge!({
67
- "platformCommon:#{condition[:field]}" => {
68
- 'operator' => condition[:operator]
70
+ criteria.each_pair do |condition_category, conditions|
71
+ criteria_structure["#{namespace}:#{condition_category}"] = conditions.inject({}) do |h, condition|
72
+ element_name = "platformCommon:#{condition[:field]}"
73
+
74
+ case condition[:field]
75
+ when 'recType'
76
+ # TODO this seems a bit brittle, look into a way to handle this better
77
+ h[element_name] = {
78
+ :@internalId => condition[:value].internal_id
79
+ }
80
+ when 'customFieldList'
81
+ # === START CUSTOM FIELD
82
+
83
+ # there isn't a clean way to do lists of the same element
84
+ # Gyoku doesn't seem support the nice :@attribute and :content! syntax for lists of elements of the same name
85
+ # https://github.com/savonrb/gyoku/issues/18#issuecomment-17825848
86
+
87
+ # TODO with the latest version of savon we can easily improve the code here, should be rewritten with new attribute syntax
88
+
89
+ custom_field_list = condition[:value].map do |h|
90
+ if h[:value].is_a?(Array) && h[:value].first.respond_to?(:to_record)
91
+ {
92
+ "platformCore:searchValue" => h[:value].map(&:to_record),
93
+ :attributes! => {
94
+ 'platformCore:searchValue' => {
95
+ 'internalId' => h[:value].map(&:internal_id)
96
+ }
97
+ }
98
+ }
99
+ elsif h[:value].respond_to?(:to_record)
100
+ {
101
+ "platformCore:searchValue" => {
102
+ :content! => h[:value].to_record,
103
+ :@internalId => h[:value].internal_id
104
+ }
105
+ }
106
+ else
107
+ { "platformCore:searchValue" => h[:value] }
108
+ end
109
+ end
110
+
111
+ h[element_name] = {
112
+ 'platformCore:customField' => custom_field_list,
113
+ :attributes! => {
114
+ 'platformCore:customField' => {
115
+ 'internalId' => condition[:value].map { |h| h[:field] },
116
+ 'operator' => condition[:value].map { |h| h[:operator] },
117
+ 'xsi:type' => condition[:value].map { |h| "platformCore:#{h[:type]}" }
118
+ }
119
+ }
69
120
  }
70
- })
121
+
122
+ # === END CUSTOM FIELD
123
+ else
124
+ if condition[:value].is_a?(Array) && condition[:value].first.respond_to?(:to_record)
125
+ # TODO need to update to the latest savon so we don't need to duplicate the same workaround above again
126
+ # TODO it's possible that this might break, not sure if platformCore:SearchMultiSelectField is the right type in every situation
127
+
128
+ h[element_name] = {
129
+ '@operator' => condition[:operator],
130
+ '@xsi:type' => 'platformCore:SearchMultiSelectField',
131
+ "platformCore:searchValue" => {
132
+ :content! => condition[:value].map(&:to_record),
133
+ '@internalId' => condition[:value].map(&:internal_id),
134
+ '@xsi:type' => 'platformCore:RecordRef',
135
+ '@type' => 'account'
136
+ }
137
+ }
138
+ elsif condition[:value].is_a?(Array) && condition[:type] == 'SearchDateField'
139
+ # date ranges are handled via searchValue (start range) and searchValue2 (end range)
140
+
141
+ h[element_name] = {
142
+ '@operator' => condition[:operator],
143
+ "platformCore:searchValue" => condition[:value].first.to_s,
144
+ "platformCore:searchValue2" => condition[:value].last.to_s
145
+ }
146
+ else
147
+ h[element_name] = {
148
+ :content! => { "platformCore:searchValue" => condition[:value] },
149
+ }
150
+
151
+ h[element_name][:@operator] = condition[:operator] if condition[:operator]
152
+ end
153
+ end
71
154
 
72
155
  h
73
156
  end
74
157
  end
75
158
 
76
- {
77
- 'searchRecord' => search_record,
78
- :attributes! => {
159
+ # TODO this needs to be DRYed up a bit
160
+
161
+ if saved_search_id
162
+ {
163
+ 'searchRecord' => {
164
+ '@savedSearchId' => saved_search_id,
165
+ '@xsi:type' => "#{namespace}:#{class_name}SearchAdvanced",
166
+ :content! => {
167
+ "#{namespace}:criteria" => criteria_structure
168
+ # TODO need to optionally support columns here
169
+ }
170
+ }
171
+ }
172
+ elsif !columns_structure.empty?
173
+ {
174
+ 'searchRecord' => {
175
+ '@xsi:type' => "#{namespace}:#{class_name}SearchAdvanced",
176
+ :content! => {
177
+ "#{namespace}:criteria" => criteria_structure,
178
+ "#{namespace}:columns" => columns_structure
179
+ }
180
+ }
181
+ }
182
+ else
183
+ {
79
184
  'searchRecord' => {
80
- 'xsi:type' => "#{namespace}:#{class_name}Search"
81
- },
185
+ :content! => criteria_structure,
186
+ '@xsi:type' => "#{namespace}:#{class_name}Search"
187
+ }
82
188
  }
83
- }
189
+ end
84
190
  end
85
191
 
86
192
  def response_header
@@ -96,13 +202,22 @@ module NetSuite
96
202
  end
97
203
 
98
204
  def search_result
99
- @search_result = @response.body[:search_response][:search_result]
205
+ @search_result = if @response.body.has_key?(:search_more_with_id_response)
206
+ @response.body[:search_more_with_id_response]
207
+ else
208
+ @response.body[:search_response]
209
+ end[:search_result]
100
210
  end
101
211
 
102
212
  def success?
103
213
  @success ||= search_result[:status][:@is_success] == 'true'
104
214
  end
105
215
 
216
+ protected
217
+ def method_name
218
+
219
+ end
220
+
106
221
  module Support
107
222
  def self.included(base)
108
223
  base.extend(ClassMethods)
@@ -1,5 +1,6 @@
1
- # TODO: Tests
2
- # TODO: DBC
1
+ # TODO Tests
2
+ # TODO this is broken with the current search implementation
3
+
3
4
  module NetSuite
4
5
  module Actions
5
6
  class SearchMoreWithId
@@ -9,17 +9,19 @@ module NetSuite
9
9
  end
10
10
 
11
11
  def request
12
+ api_version = NetSuite::Configuration.api_version
13
+
12
14
  NetSuite::Configuration.connection(
13
15
  namespaces: {
14
- 'xmlns:platformMsgs' => "urn:messages_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
15
- 'xmlns:platformCore' => "urn:core_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
16
- 'xmlns:listRel' => "urn:relationships_#{NetSuite::Configuration.api_version}.lists.webservices.netsuite.com",
17
- 'xmlns:tranSales' => "urn:sales_#{NetSuite::Configuration.api_version}.transactions.webservices.netsuite.com",
18
- 'xmlns:platformCommon' => "urn:common_#{NetSuite::Configuration.api_version}.platform.webservices.netsuite.com",
19
- 'xmlns:listAcct' => "urn:accounting_#{NetSuite::Configuration.api_version}.lists.webservices.netsuite.com",
20
- 'xmlns:actSched' => "urn:scheduling_#{NetSuite::Configuration.api_version}.activities.webservices.netsuite.com",
21
- 'xmlns:tranCust' => "urn:customers_#{NetSuite::Configuration.api_version}.transactions.webservices.netsuite.com",
22
- 'xmlns:setupCustom' => "urn:customization_#{NetSuite::Configuration.api_version}.setup.webservices.netsuite.com",
16
+ 'xmlns:platformMsgs' => "urn:messages_#{api_version}.platform.webservices.netsuite.com",
17
+ 'xmlns:platformCore' => "urn:core_#{api_version}.platform.webservices.netsuite.com",
18
+ 'xmlns:listRel' => "urn:relationships_#{api_version}.lists.webservices.netsuite.com",
19
+ 'xmlns:tranSales' => "urn:sales_#{api_version}.transactions.webservices.netsuite.com",
20
+ 'xmlns:platformCommon' => "urn:common_#{api_version}.platform.webservices.netsuite.com",
21
+ 'xmlns:listAcct' => "urn:accounting_#{api_version}.lists.webservices.netsuite.com",
22
+ 'xmlns:actSched' => "urn:scheduling_#{api_version}.activities.webservices.netsuite.com",
23
+ 'xmlns:tranCust' => "urn:customers_#{api_version}.transactions.webservices.netsuite.com",
24
+ 'xmlns:setupCustom' => "urn:customization_#{api_version}.setup.webservices.netsuite.com",
23
25
  },
24
26
  ).call :update, :message => request_body
25
27
  end
@@ -31,20 +33,18 @@ module NetSuite
31
33
  # </platformMsgs:update>
32
34
  def request_body
33
35
  hash = {
34
- 'platformMsgs:record' => updated_record.to_record,
35
- :attributes! => {
36
- 'platformMsgs:record' => {
37
- 'xsi:type' => updated_record.record_type
38
- }
36
+ 'platformMsgs:record' => {
37
+ :content! => updated_record.to_record,
38
+ '@xsi:type' => updated_record.record_type
39
39
  }
40
40
  }
41
41
 
42
42
  if updated_record.respond_to?(:internal_id) && updated_record.internal_id
43
- hash[:attributes!]['platformMsgs:record']['platformMsgs:internalId'] = updated_record.internal_id
43
+ hash['platformMsgs:record']['@platformMsgs:internalId'] = updated_record.internal_id
44
44
  end
45
45
 
46
46
  if updated_record.respond_to?(:external_id) && updated_record.external_id
47
- hash[:attributes!]['platformMsgs:record']['platformMsgs:externalId'] = updated_record.external_id
47
+ hash['platformMsgs:record']['@platformMsgs:externalId'] = updated_record.external_id
48
48
  end
49
49
 
50
50
  hash
@@ -12,12 +12,13 @@ module NetSuite
12
12
 
13
13
  def connection(params = {})
14
14
  Savon.client({
15
- wsdl: wsdl,
16
- read_timeout: read_timeout,
17
- soap_header: auth_header,
18
- pretty_print_xml: true,
19
- logger: logger
20
- # open_timeout: ???
15
+ wsdl: wsdl,
16
+ read_timeout: read_timeout,
17
+ namespaces: namespaces,
18
+ soap_header: auth_header,
19
+ pretty_print_xml: true,
20
+ logger: logger
21
+ # open_timeout: ???
21
22
  }.merge(params))
22
23
  end
23
24
 
@@ -70,23 +71,36 @@ module NetSuite
70
71
  'platformCore:email' => email,
71
72
  'platformCore:password' => password,
72
73
  'platformCore:account' => account.to_s,
73
- 'platformCore:role' => role.to_record,
74
- :attributes! => {
75
- 'platformCore:role' => role.attributes!
76
- }
74
+ 'platformCore:role' => { :'@type' => 'role', :@internalId => role }
77
75
  }
78
76
  }
79
77
  end
80
-
78
+
79
+ def namespaces
80
+ {
81
+ 'xmlns:platformMsgs' => "urn:messages_#{api_version}.platform.webservices.netsuite.com",
82
+ 'xmlns:platformCore' => "urn:core_#{api_version}.platform.webservices.netsuite.com",
83
+ 'xmlns:platformCommon' => "urn:common_#{api_version}.platform.webservices.netsuite.com",
84
+ 'xmlns:listRel' => "urn:relationships_#{api_version}.lists.webservices.netsuite.com",
85
+ 'xmlns:tranSales' => "urn:sales_#{api_version}.transactions.webservices.netsuite.com",
86
+ 'xmlns:actSched' => "urn:scheduling_#{api_version}.activities.webservices.netsuite.com",
87
+ 'xmlns:setupCustom' => "urn:customization_#{api_version}.setup.webservices.netsuite.com",
88
+ 'xmlns:listAcct' => "urn:accounting_#{api_version}.lists.webservices.netsuite.com",
89
+ 'xmlns:tranCust' => "urn:customers_#{api_version}.transactions.webservices.netsuite.com",
90
+ 'xmlns:listSupport' => "urn:support_#{api_version}.lists.webservices.netsuite.com",
91
+ 'xmlns:tranGeneral' => "urn:general_#{api_version}.transactions.webservices.netsuite.com",
92
+ }
93
+ end
94
+
81
95
  def role=(role)
82
- attributes[:role] = NetSuite::Records::RecordRef.new(:internal_id => role, :type => 'role')
96
+ attributes[:role] = role
83
97
  end
84
-
98
+
85
99
  def role(role = nil)
86
100
  if role
87
101
  self.role = role
88
- else
89
- attributes[:role] ||= NetSuite::Records::RecordRef.new(:internal_id => '3', :type => 'role')
102
+ else
103
+ attributes[:role] ||= '3'
90
104
  end
91
105
  end
92
106
 
@@ -0,0 +1,11 @@
1
+ module NetSuite
2
+ module Namespaces
3
+ module ListSupport
4
+
5
+ def record_namespace
6
+ 'listSupport'
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -3,6 +3,7 @@ module NetSuite
3
3
  class Account
4
4
  include Support::Fields
5
5
  include Support::RecordRefs
6
+ include Support::Records
6
7
  include Support::Actions
7
8
 
8
9
  actions :get, :add, :delete
@@ -0,0 +1,33 @@
1
+ # TODO needs spec
2
+
3
+ module NetSuite
4
+ module Records
5
+ class BaseRefList
6
+ include Support::Fields
7
+ include Support::Actions
8
+ include Namespaces::PlatformCore
9
+
10
+ actions :get_select_value
11
+
12
+ fields :base_ref
13
+
14
+ def initialize(attrs = {})
15
+ initialize_from_attributes_hash(attrs)
16
+ end
17
+
18
+ def base_ref=(refs)
19
+ case refs
20
+ when Hash
21
+ self.base_ref << RecordRef.new(refs)
22
+ when Array
23
+ refs.each { |ref| self.base_ref << RecordRef.new(ref) }
24
+ end
25
+ end
26
+
27
+ def base_ref
28
+ @base_ref ||= []
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ module NetSuite
2
+ module Records
3
+ class CashSale < SalesOrder
4
+
5
+ end
6
+ end
7
+ end
@@ -4,7 +4,8 @@ module NetSuite
4
4
  include Support::Fields
5
5
  include Support::Records
6
6
 
7
- attr_reader :internal_id, :type
7
+ attr_reader :internal_id
8
+ attr_accessor :type
8
9
 
9
10
  def initialize(attributes = {})
10
11
  @internal_id = attributes.delete(:internal_id) || attributes.delete(:@internal_id)
@@ -20,39 +20,89 @@ module NetSuite
20
20
  end
21
21
 
22
22
  def method_missing(sym, *args, &block)
23
- return @custom_fields_assoc[sym] if @custom_fields_assoc.include? sym
23
+ # read custom field if already set
24
+ if @custom_fields_assoc.include?(sym)
25
+ return @custom_fields_assoc[sym]
26
+ end
27
+
28
+ # write custom field
29
+ if sym.to_s.end_with?('=')
30
+ return create_custom_field(sym.to_s[0..-2], args.first)
31
+ end
32
+
24
33
  super(sym, *args, &block)
25
34
  end
26
35
 
27
36
  def respond_to?(sym, include_private = false)
28
- return true if @custom_fields_assoc.include? sym
37
+ return true if @custom_fields_assoc.include?(sym)
29
38
  super
30
39
  end
31
40
 
32
41
  def to_record
33
- # TODO this is the best way I could find to handle this, there *has* to be a better way
34
- # http://stackoverflow.com/questions/7001957/savon-array-of-xml-tags
35
-
36
42
  {
37
- "#{record_namespace}:customField" => custom_fields.map(&:to_record),
38
- :attributes! => {
39
- "#{record_namespace}:customField" => {
40
- 'internalId' => custom_fields.map(&:internal_id),
41
- 'xsi:type' => custom_fields.map(&:type)
43
+ "#{record_namespace}:customField" => custom_fields.map do |custom_field|
44
+ if custom_field.value.respond_to?(:to_record)
45
+ custom_field_value = custom_field.value.to_record
46
+ else
47
+ custom_field_value = custom_field.value.to_s
48
+ end
49
+
50
+ {
51
+ "platformCore:value" => custom_field_value,
52
+ '@internalId' => custom_field.internal_id,
53
+ '@xsi:type' => custom_field.type
42
54
  }
43
- }
55
+ end
44
56
  }
45
57
  end
46
58
 
47
59
  private
48
60
  def extract_custom_field(custom_field_data)
49
- # TODO this needs to be cleaned up & tested; very messy
61
+ # TODO this seems brittle, but might sufficient, watch out for this if something breaks
50
62
  if custom_field_data[:"@xsi:type"] == "platformCore:SelectCustomFieldRef"
51
63
  custom_field_data[:value] = CustomRecordRef.new(custom_field_data.delete(:value))
52
64
  end
53
65
 
54
66
  custom_fields << CustomField.new(custom_field_data)
55
67
  end
68
+
69
+ def create_custom_field(internal_id, field_value)
70
+ # all custom fields need types; infer type based on class sniffing
71
+ field_type = case
72
+ when field_value.is_a?(Hash)
73
+ 'SelectCustomFieldRef'
74
+ when field_value.is_a?(DateTime),
75
+ field_value.is_a?(Time),
76
+ field_value.is_a?(Date)
77
+ 'DateCustomFieldRef'
78
+ when field_value.is_a?(FalseClass),
79
+ field_value.is_a?(TrueClass)
80
+ 'BooleanCustomFieldRef'
81
+ else
82
+ 'StringCustomFieldRef'
83
+ end
84
+
85
+ # TODO seems like DateTime doesn't need the iso8601 call
86
+ # not sure if this is specific to my env though
87
+
88
+ custom_field_value = case
89
+ when field_value.is_a?(Hash)
90
+ CustomRecordRef.new(field_value)
91
+ when field_value.is_a?(Time)
92
+ field_value.iso8601
93
+ else
94
+ field_value
95
+ end
96
+
97
+ custom_field = CustomField.new(
98
+ internal_id: internal_id,
99
+ value: custom_field_value,
100
+ type: "#{record_namespace}:#{field_type}"
101
+ )
102
+
103
+ custom_fields << custom_field
104
+ @custom_fields_assoc[internal_id.to_sym] = custom_field
105
+ end
56
106
  end
57
107
  end
58
108
  end