quickeebooks 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/Gemfile.lock +42 -11
  2. data/Guardfile +8 -0
  3. data/HISTORY.md +5 -0
  4. data/coverage/index.html +6201 -2451
  5. data/lib/quickeebooks.rb +16 -1
  6. data/lib/quickeebooks/online/model/bill_payment.rb +26 -0
  7. data/lib/quickeebooks/online/model/bill_payment_header.rb +24 -0
  8. data/lib/quickeebooks/online/model/bill_payment_line_item.rb +13 -0
  9. data/lib/quickeebooks/online/model/customer.rb +0 -42
  10. data/lib/quickeebooks/online/model/id.rb +9 -0
  11. data/lib/quickeebooks/online/model/tracking_class.rb +43 -0
  12. data/lib/quickeebooks/online/service/bill_payment.rb +16 -0
  13. data/lib/quickeebooks/online/service/invoice.rb +3 -3
  14. data/lib/quickeebooks/online/service/service_base.rb +4 -4
  15. data/lib/quickeebooks/online/service/tracking_class.rb +13 -0
  16. data/lib/quickeebooks/version.rb +1 -1
  17. data/lib/quickeebooks/windows/model/id.rb +8 -2
  18. data/lib/quickeebooks/windows/model/id_set.rb +24 -0
  19. data/lib/quickeebooks/windows/model/intuit_type.rb +1 -1
  20. data/lib/quickeebooks/windows/model/ng_id.rb +24 -0
  21. data/lib/quickeebooks/windows/model/ng_id_set.rb +28 -0
  22. data/lib/quickeebooks/windows/model/payment.rb +14 -0
  23. data/lib/quickeebooks/windows/model/payment_header.rb +38 -12
  24. data/lib/quickeebooks/windows/model/payment_line_item.rb +2 -1
  25. data/lib/quickeebooks/windows/model/payment_method.rb +28 -0
  26. data/lib/quickeebooks/windows/model/sync_status_param.rb +28 -0
  27. data/lib/quickeebooks/windows/model/sync_status_request.rb +31 -0
  28. data/lib/quickeebooks/windows/model/sync_status_response.rb +156 -0
  29. data/lib/quickeebooks/windows/service/payment.rb +14 -0
  30. data/lib/quickeebooks/windows/service/payment_method.rb +24 -0
  31. data/lib/quickeebooks/windows/service/sync_status.rb +34 -0
  32. data/quickeebooks.gemspec +3 -1
  33. data/spec/quickeebooks/online/bill_payment_spec.rb +18 -0
  34. data/spec/quickeebooks/online/id_spec.rb +18 -0
  35. data/spec/quickeebooks/online/services/bill_payment_spec.rb +69 -0
  36. data/spec/quickeebooks/online/services/invoice_spec.rb +12 -0
  37. data/spec/quickeebooks/online/services/tracking_class_spec.rb +99 -0
  38. data/spec/quickeebooks/online/tracking_class_spec.rb +32 -0
  39. data/spec/quickeebooks/windows/id_spec.rb +18 -0
  40. data/spec/quickeebooks/windows/services/payment_method_spec.rb +36 -0
  41. data/spec/quickeebooks/windows/services/payment_spec.rb +30 -0
  42. data/spec/quickeebooks/windows/services/sync_status_spec.rb +35 -0
  43. data/spec/spec_helper.rb +1 -2
  44. data/spec/xml/online/bill_payment.xml +20 -0
  45. data/spec/xml/online/bill_payment2.xml +20 -0
  46. data/spec/xml/online/bill_payments.xml +26 -0
  47. data/spec/xml/online/deleted_invoice.xml +2 -0
  48. data/spec/xml/online/tracking_class.xml +11 -0
  49. data/spec/xml/online/tracking_class_updated.xml +11 -0
  50. data/spec/xml/online/tracking_classes.xml +26 -0
  51. data/spec/xml/windows/payment_create_success.xml +11 -0
  52. data/spec/xml/windows/payment_methods.xml +121 -0
  53. data/spec/xml/windows/sync_status_responses.xml +186 -0
  54. data/tmp/console.rb +10 -0
  55. metadata +71 -5
  56. data/quickeebooks-0.1.7.gem +0 -0
@@ -27,6 +27,20 @@ module Quickeebooks
27
27
  ensure_line_items_initialization
28
28
  end
29
29
 
30
+ def valid_for_create?
31
+ valid?
32
+ if header.nil?
33
+ errors.add(:header, "Missing Header field for Create")
34
+ # else
35
+ # # ensure header is valid
36
+ # unless header.valid?
37
+ # #errors.concat(header.errors)
38
+ # #errors[:base].each {|e| header.errors[:base] << e }
39
+ # end
40
+ end
41
+ errors.empty?
42
+ end
43
+
30
44
  private
31
45
 
32
46
  def after_parse
@@ -5,19 +5,45 @@ module Quickeebooks
5
5
  module Windows
6
6
  module Model
7
7
  class PaymentHeader < Quickeebooks::Windows::Model::IntuitType
8
+ include ActiveModel::Validations
9
+
8
10
  xml_name 'Header'
9
- xml_accessor :doc_number, :from => 'DocNumber'
10
- xml_accessor :txn_date, :from => 'TxnDate', :as => Time
11
- xml_accessor :note, :from => 'Note'
12
- xml_accessor :status, :from => 'Status'
13
- xml_accessor :customer_id, :from => 'CustomerId', :as => Quickeebooks::Windows::Model::Id
14
- xml_accessor :customer_name, :from => 'CustomerName'
15
- xml_accessor :deposit_to_account_id, :from => 'DepositToAccountId', :as => Quickeebooks::Windows::Model::Id
16
- xml_accessor :payment_method_id, :from => 'PaymentMethodId', :as => Quickeebooks::Windows::Model::Id
17
- xml_accessor :payment_method_name, :from => 'PaymentMethodName'
18
- xml_accessor :detail, :from => 'Detail', :as => Quickeebooks::Windows::Model::PaymentDetail
19
- xml_accessor :total_amount, :from => 'TotalAmt', :as => Float
20
- xml_accessor :process_payment, :from => 'ProcessPayment'
11
+ xml_accessor :doc_number, :from => 'DocNumber'
12
+ xml_accessor :txn_date, :from => 'TxnDate', :as => Time
13
+ xml_accessor :currency, :from => 'Currency'
14
+ xml_accessor :note, :from => 'Note'
15
+ xml_accessor :status, :from => 'Status'
16
+ xml_accessor :customer_id, :from => 'CustomerId', :as => Quickeebooks::Windows::Model::Id
17
+ xml_accessor :customer_name, :from => 'CustomerName'
18
+ xml_accessor :job_id, :from => 'JobId', :as => Quickeebooks::Windows::Model::Id
19
+ xml_accessor :job_name, :from => 'JobName'
20
+ xml_accessor :remit_to_id, :from => 'RemitToId', :as => Quickeebooks::Windows::Model::Id
21
+ xml_accessor :remit_to_name, :from => 'RemitToName'
22
+ xml_accessor :ar_account_id, :from => 'ARAccountId', :as => Quickeebooks::Windows::Model::Id
23
+ xml_accessor :ar_account_name, :from => 'ARAccountName'
24
+ xml_accessor :deposit_to_account_id, :from => 'DepositToAccountId', :as => Quickeebooks::Windows::Model::Id
25
+ xml_accessor :deposit_to_account_name, :from => 'DepositToAccountName'
26
+ xml_accessor :payment_method_id, :from => 'PaymentMethodId', :as => Quickeebooks::Windows::Model::Id
27
+ xml_accessor :payment_method_name, :from => 'PaymentMethodName'
28
+ xml_accessor :detail, :from => 'Detail', :as => Quickeebooks::Windows::Model::PaymentDetail
29
+ xml_accessor :total_amount, :from => 'TotalAmt', :as => Float
30
+ xml_accessor :process_payment, :from => 'ProcessPayment'
31
+
32
+ validate :customer_is_valid
33
+
34
+ private
35
+
36
+ def customer_is_valid
37
+ if customer_id.nil?
38
+ errors.add(:customer_id, "Missing customer_id")
39
+ else
40
+ # ensure its we have a non-blank Customer ID value
41
+ if customer_id.to_s.length == 0
42
+ errors.add(:customer_id, "customer_id is supplied but its value is empty")
43
+ end
44
+ end
45
+ end
46
+
21
47
  end
22
48
  end
23
49
  end
@@ -6,9 +6,10 @@ module Quickeebooks
6
6
  class PaymentLineItem < Quickeebooks::Windows::Model::IntuitType
7
7
  xml_name 'Line'
8
8
  xml_accessor :id, :from => 'Id', :as => Integer
9
- xml_accessor :amount, :from => 'Amount', :as => Float
10
9
  xml_accessor :desc, :from => 'Desc'
10
+ xml_accessor :amount, :from => 'Amount', :as => Float
11
11
  xml_accessor :txn_id, :from => 'TxnId', :as => Quickeebooks::Windows::Model::Id
12
+ xml_accessor :txn_num, :from => 'TxnNum'
12
13
  end
13
14
  end
14
15
  end
@@ -0,0 +1,28 @@
1
+ module Quickeebooks
2
+ module Windows
3
+ module Model
4
+ class PaymentMethod < Quickeebooks::Windows::Model::IntuitType
5
+ include ActiveModel::Validations
6
+
7
+ XML_COLLECTION_NODE = 'PaymentMethods'
8
+ XML_NODE = 'PaymentMethod'
9
+
10
+ # https://services.intuit.com/sb/paymentmethod/v2/<realmID>
11
+ REST_RESOURCE = "paymentmethod"
12
+
13
+ xml_name 'PaymentMethod'
14
+ xml_convention :camelcase
15
+ xml_accessor :id, :from => 'Id', :as => Quickeebooks::Windows::Model::Id
16
+ xml_accessor :sync_token, :from => 'SyncToken', :as => Integer
17
+ xml_accessor :meta_data, :from => 'MetaData', :as => Quickeebooks::Windows::Model::MetaData
18
+ xml_accessor :external_key, :from => 'ExternalKey'
19
+ xml_accessor :custom_fields, :from => 'CustomField', :as => [Quickeebooks::Windows::Model::CustomField]
20
+ xml_accessor :name, :from => 'Name'
21
+ xml_accessor :active, :from => 'Active'
22
+ xml_accessor :type, :from => 'Type'
23
+
24
+ validates_length_of :name, :minimum => 1
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'quickeebooks/windows/model/id_set'
2
+
3
+ module Quickeebooks
4
+ module Windows
5
+ module Model
6
+ class SyncStatusParam < Quickeebooks::Windows::Model::IntuitType
7
+ xml_convention :camelcase
8
+ xml_accessor :id_set, :from => 'IdSet', :as => Quickeebooks::Windows::Model::IdSet
9
+ xml_accessor :sync_token, :from => 'SyncToken'
10
+ xml_accessor :object_type, :from => 'ObjectType'
11
+ xml_accessor :party_id, :from => 'PartyId'
12
+
13
+ def initialize(value = nil)
14
+ self.id_set = Quickeebooks::Windows::Model::IdSet.new(value)
15
+ end
16
+
17
+ def to_i
18
+ self.id_set ? self.id_set.to_i : "__uninitialized__"
19
+ end
20
+
21
+ def to_s
22
+ self.id_set ? self.id_set.to_s : "__uninitialized__"
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require 'quickeebooks/windows/model/id'
2
+ require 'quickeebooks/windows/model/id_set'
3
+ require 'quickeebooks/windows/model/sync_status_param'
4
+
5
+ module Quickeebooks
6
+ module Windows
7
+ module Model
8
+ class SyncStatusRequest < Quickeebooks::Windows::Model::IntuitType
9
+
10
+ DEFAULT_OFFERING_ID = 'ipp'
11
+
12
+ # https://services.intuit.com/sb/status/v2/<realmID>
13
+ REST_RESOURCE = "status"
14
+
15
+ xml_convention :camelcase
16
+ xml_accessor :offering_id
17
+ xml_accessor :sync_status_param, :from => 'SyncStatusParam', :as => Quickeebooks::Windows::Model::SyncStatusParam
18
+
19
+ def initialize(id = nil, type = nil)
20
+ self.offering_id = DEFAULT_OFFERING_ID
21
+
22
+ if id && type
23
+ self.sync_status_param = Quickeebooks::Windows::Model::SyncStatusParam.new(id)
24
+ self.sync_status_param.object_type = type
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,156 @@
1
+ module Quickeebooks
2
+ module Windows
3
+ module Model
4
+ class SyncStatusResponse < Quickeebooks::Windows::Model::IntuitType
5
+
6
+ XML_COLLECTION_NODE = 'SyncStatusResponses'
7
+ XML_NODE = 'SyncStatusResponse'
8
+ # https://services.intuit.com/sb/status/v2/<realmID>
9
+ REST_RESOURCE = "status"
10
+
11
+ STATE_CODES = {
12
+ 1 => {
13
+ :desc => 'Synchronized',
14
+ :verbose => 'Synchronized (successful). Object created in QuickBooks. Equivalent to StateCode 8 (for object created in Data Services).'
15
+ },
16
+ 2 => {
17
+ :desc => 'Source record created',
18
+ :verbose => 'Not synchronized. Object created in Quickbooks.'
19
+ },
20
+ 3 => {
21
+ :desc => 'Source record modified',
22
+ :verbose => 'Not synchronized. Object modified in Quickbooks.'
23
+ },
24
+ 4 => {
25
+ :desc => 'Next Gen record created',
26
+ :verbose => 'Not synchronized. Object created in Data Services. The same object subsequently modified on the cloud, without being sync\'d, will retain a StateCode 4 until it has been sync\'d with QuickBooks and then modified.'
27
+ },
28
+ 5 => {
29
+ :desc => 'Next Gen record modified',
30
+ :verbose => 'Not synchronized. Object modified in Data Services that has been sync\'d at least once with QuickBooks.'
31
+ },
32
+ 6 => {
33
+ :desc => 'Next Gen to Source, no ack',
34
+ :verbose => 'Not synchronzied. The synchronization is currently in progress; Sync Manager has not completed processing or acknowledged completion.'
35
+ },
36
+ 7 => {
37
+ :desc => 'Record has conflict',
38
+ :verbose => 'Not synchronized. To resolve this conflict, change the object in Data Services.'
39
+ },
40
+ 8 => {
41
+ :desc => 'Record netted',
42
+ :verbose => 'Synchronized. Object created in Data Services. Sync Manager has acknowledged synchronizing the object and mapped its NG ID to a QB ID in QuickBooks. Equivalent to StateCode 1 (for object created in QuickBooks).'
43
+ },
44
+ 9 => {
45
+ :desc => 'Record has conflict, unrecoverable',
46
+ :verbose => 'Not synchronized. The synchronization was rejected by Quickbooks because of a bad record or unsupported operation.'
47
+ }
48
+ }
49
+
50
+ MESSAGE_CODES = {
51
+ 10 => {
52
+ :desc => 'ADD success',
53
+ :verbose => 'Object to be added was successfully written to Data Services, awaiting synch with QuickBooks.'
54
+ },
55
+ 20 => {
56
+ :desc => 'MOD success',
57
+ :verbose => 'Object to be modified was successfully modified in Data Services, awaiting synch with QuickBooks.'
58
+ },
59
+ 30 => {
60
+ :desc => 'QBXML sent to desktop',
61
+ :verbose => 'Requests have been sent down from Data Services to the QuickBooks desktop company file for an attempted synch.'
62
+ },
63
+ 40 => {
64
+ :desc => 'WRTB success',
65
+ :verbose => 'The requests sent from Data Services to the QuickBooks company file were successfully synched into the company file.'
66
+ },
67
+ 50 => {
68
+ :desc => 'WRTB error',
69
+ :verbose => 'The requests sent from Data Services to the QuickBooks company file failed to synch and are not in the company file.'
70
+ },
71
+ 60 => {
72
+ :desc => 'ADD MOD error',
73
+ :verbose => 'The Add or Mod of an object in Data Services failed. The object was not added to Data Services or modified in Data Services.'
74
+ },
75
+ 80 => {
76
+ :desc => 'Auto_Revert',
77
+ :verbose => 'This error is returned whenever an object, which fails to synchronize due to write back errors, is reverted to a state prior to the error.'
78
+ },
79
+ 90 => {
80
+ :desc => 'DEL success',
81
+ :verbose => nil
82
+ },
83
+ 100 => {
84
+ :desc => 'Generic QB SDK communication failure',
85
+ :verbose => nil
86
+ },
87
+ 110 => {
88
+ :desc => 'Upload ADD success',
89
+ :verbose => nil
90
+ },
91
+ 120 => {
92
+ :desc => 'Upload MOD success',
93
+ :verbose => nil
94
+ },
95
+ 130 => {
96
+ :desc => 'Upload error',
97
+ :verbose => nil
98
+ },
99
+ 200 => {
100
+ :desc => 'Missing ExtAcctId with state = 1, forcing to state = 7',
101
+ :verbose => 'Objects now have StateCode 7.'
102
+ },
103
+ 210 => {
104
+ :desc => 'Orphaned state = 8, forcing to state = 7',
105
+ :verbose => 'Objects are orphaned when they existed on the cloud, synchronization with QuickBooks did not complete, but then the user restored an older company file from backup on the desktop, then sync\'d; the objects on the cloud have lost their corresponding QuickBooks identity and cannot be resolved, so they have been forced back to StateCode 7.'
106
+ },
107
+ 220 => {
108
+ :desc => 'Stuck while in state = 6, forcing to state = 7',
109
+ :verbose => 'Objects now have StateCode 7.'
110
+ },
111
+ 230 => {
112
+ :desc => 'Stuck while in state = 6, forcing to prior state',
113
+ :verbose => 'Objects have been rolled back to previous state. No synchronization occurred.'
114
+ },
115
+ 240 => {
116
+ :desc => 'Internal WRTB to ESB API ack error - Sync Manager failed validation',
117
+ :verbose => 'Objects now have StateCode 7.'
118
+ },
119
+ 250 => {
120
+ :desc => 'State Rollback all prior objects',
121
+ :verbose => 'Objects have been rolled back to previous state. No synchronization occurred.'
122
+ }
123
+ }
124
+
125
+ xml_convention :camelcase
126
+ xml_accessor :offering_id, :as => Quickeebooks::Windows::Model::Id
127
+ xml_accessor :ng_id_set, :as => Quickeebooks::Windows::Model::NgIdSet
128
+ xml_accessor :request_id, :from => 'RequestId'
129
+ xml_accessor :batch_id, :from => 'BatchId'
130
+
131
+ xml_accessor :state_code, :from => 'StateCode'
132
+ xml_accessor :state_desc, :from => 'StateDesc'
133
+ xml_accessor :message_code, :from => 'MessageCode'
134
+ xml_accessor :message_desc, :from => 'MessageDesc'
135
+ xml_accessor :response_log_tms, :from => 'ResponseLogTMS', :as => Time
136
+
137
+ def verbose_state_description
138
+ if STATE_CODES[state_code.to_i]
139
+ STATE_CODES[state_code.to_i][:verbose]
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
145
+ def verbose_message_description
146
+ if MESSAGE_CODES[message_code.to_i]
147
+ MESSAGE_CODES[message_code.to_i][:verbose]
148
+ else
149
+ nil
150
+ end
151
+ end
152
+
153
+ end
154
+ end
155
+ end
156
+ end
@@ -20,6 +20,20 @@ module Quickeebooks
20
20
  def list(filters = [], page = 1, per_page = 20, sort = nil, options = {})
21
21
  fetch_collection(Quickeebooks::Windows::Model::Payment, nil, filters, page, per_page, sort, options)
22
22
  end
23
+
24
+ def create(payment)
25
+ raise InvalidModelException unless payment.valid_for_create?
26
+
27
+ # XML is a wrapped 'object' where the type is specified as an attribute
28
+ # <Object xsi:type="Invoice">
29
+ xml_node = payment.to_xml(:name => 'Object')
30
+ xml_node.set_attribute('xsi:type', 'Payment')
31
+ xml = Quickeebooks::Shared::Service::OperationNode.new.add do |content|
32
+ content << "<ExternalRealmId>#{self.realm_id}</ExternalRealmId>#{xml_node}"
33
+ end
34
+ perform_write(Quickeebooks::Windows::Model::Payment, xml)
35
+ end
36
+
23
37
  end
24
38
  end
25
39
  end
@@ -0,0 +1,24 @@
1
+ # require 'quickeebooks/windows/service/service_base'
2
+ # require 'quickeebooks/windows/model/payment'
3
+ # require 'quickeebooks/windows/model/payment_method'
4
+ # require 'quickeebooks/windows/model/payment_line_item'
5
+ # require 'quickeebooks/windows/model/payment_detail'
6
+ # require 'quickeebooks/windows/model/credit_card'
7
+ # require 'quickeebooks/windows/model/credit_charge_info'
8
+ # require 'quickeebooks/windows/model/credit_charge_response'
9
+ # require 'nokogiri'
10
+
11
+ module Quickeebooks
12
+ module Windows
13
+ module Service
14
+ class PaymentMethod < ServiceBase
15
+
16
+ def list(filters = [], page = 1, per_page = 20, sort = nil, options = {})
17
+ fetch_collection(Quickeebooks::Windows::Model::PaymentMethod, nil, filters, page, per_page, sort, options)
18
+ end
19
+
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ require 'quickeebooks/windows/service/service_base'
2
+ require 'quickeebooks/windows/model/sync_status_request'
3
+
4
+ module Quickeebooks
5
+ module Windows
6
+ module Service
7
+ class SyncStatus < Quickeebooks::Windows::Service::ServiceBase
8
+
9
+ def retrieve(sync_status_request, errored_objects_only = false)
10
+ unless sync_status_request.is_a?(Quickeebooks::Windows::Model::SyncStatusRequest)
11
+ raise ArgumentError.new("Expecting an +SyncStatusRequest+ instance as an argument")
12
+ end
13
+
14
+ xml_node = sync_status_request.to_xml
15
+
16
+ if errored_objects_only
17
+ xml_node.set_attribute('ErroredObjectsOnly', 'true')
18
+ end
19
+
20
+ xml_node.set_attribute('xmlns', 'http://www.intuit.com/sb/cdm/v2')
21
+ xml_node.set_attribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
22
+ xml_node.set_attribute('xsi:schemaLocation', 'http://www.intuit.com/sb/cdm/v2 RestDataFilter.xsd ')
23
+
24
+ xml = xml_node.to_s
25
+
26
+ model = Quickeebooks::Windows::Model::SyncStatusResponse
27
+ response = do_http_post(url_for_resource(model::REST_RESOURCE), xml, {}, {'Content-Type' => 'text/xml'})
28
+ parse_collection(response, model)
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -23,6 +23,8 @@ Gem::Specification.new do |gem|
23
23
  gem.add_development_dependency 'rake'
24
24
  gem.add_development_dependency 'simplecov'
25
25
  gem.add_development_dependency 'rr', '~> 1.0.2'
26
- gem.add_development_dependency 'rspec', '2.11.0'
26
+ gem.add_development_dependency 'rspec', '2.13.0'
27
27
  gem.add_development_dependency 'fakeweb'
28
+ gem.add_development_dependency 'guard', '1.8.0'
29
+ gem.add_development_dependency 'guard-rspec', '3.0.0'
28
30
  end
@@ -0,0 +1,18 @@
1
+ describe "Quickeebooks::Online::Model::BillPayment" do
2
+
3
+ describe "parse bill payment from XML" do
4
+ it "should properly parse bill payment from XML" do
5
+ xml = onlineFixture("bill_payment.xml")
6
+ bill_payment = Quickeebooks::Online::Model::BillPayment.from_xml(xml)
7
+ bill_payment.id.value.should == '13'
8
+ bill_payment.sync_token.should == 0
9
+ bill_payment.header.doc_number.should == '15'
10
+ bill_payment.header.entity_id.value.should == '1'
11
+ bill_payment.header.total_amount.should == 5000.0
12
+ bill_payment.line_items.size.should == 1
13
+ bill_payment.line_items[0].amount.should == 5000.0
14
+ bill_payment.line_items[0].txn_id.value.should == '12'
15
+ end
16
+ end
17
+
18
+ end