twinfieldrb 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/codeql-analysis.yml +70 -0
  3. data/.github/workflows/rspec.yml +33 -0
  4. data/.gitignore +6 -0
  5. data/.rspec +1 -0
  6. data/CHANGELOG.md +15 -0
  7. data/Gemfile +4 -0
  8. data/README.md +120 -0
  9. data/Rakefile +1 -0
  10. data/lib/twinfield/abstract_model.rb +7 -0
  11. data/lib/twinfield/api/base_api.rb +50 -0
  12. data/lib/twinfield/api/finder.rb +45 -0
  13. data/lib/twinfield/api/o_auth_session.rb +58 -0
  14. data/lib/twinfield/api/process.rb +44 -0
  15. data/lib/twinfield/api/session.rb +170 -0
  16. data/lib/twinfield/browse/transaction/cost_center.rb +145 -0
  17. data/lib/twinfield/browse/transaction/customer.rb +413 -0
  18. data/lib/twinfield/browse/transaction/general_ledger.rb +144 -0
  19. data/lib/twinfield/configuration.rb +49 -0
  20. data/lib/twinfield/create/cost_center.rb +39 -0
  21. data/lib/twinfield/create/creditor.rb +88 -0
  22. data/lib/twinfield/create/debtor.rb +88 -0
  23. data/lib/twinfield/create/error.rb +30 -0
  24. data/lib/twinfield/create/general_ledger.rb +39 -0
  25. data/lib/twinfield/create/transaction.rb +97 -0
  26. data/lib/twinfield/customer.rb +612 -0
  27. data/lib/twinfield/helpers/parsers.rb +23 -0
  28. data/lib/twinfield/helpers/transaction_match.rb +40 -0
  29. data/lib/twinfield/request/find.rb +149 -0
  30. data/lib/twinfield/request/list.rb +66 -0
  31. data/lib/twinfield/request/read.rb +111 -0
  32. data/lib/twinfield/sales_invoice.rb +409 -0
  33. data/lib/twinfield/transaction.rb +112 -0
  34. data/lib/twinfield/version.rb +5 -0
  35. data/lib/twinfield.rb +89 -0
  36. data/script/boot.rb +58 -0
  37. data/script/console +2 -0
  38. data/spec/fixtures/cluster/finder/ivt.xml +1 -0
  39. data/spec/fixtures/cluster/processxml/columns/sales_transactions.xml +312 -0
  40. data/spec/fixtures/cluster/processxml/customer/create_success.xml +100 -0
  41. data/spec/fixtures/cluster/processxml/customer/read_success.xml +93 -0
  42. data/spec/fixtures/cluster/processxml/customer/update_success.xml +1 -0
  43. data/spec/fixtures/cluster/processxml/invoice/create_error.xml +8 -0
  44. data/spec/fixtures/cluster/processxml/invoice/create_final_error.xml +67 -0
  45. data/spec/fixtures/cluster/processxml/invoice/create_success.xml +64 -0
  46. data/spec/fixtures/cluster/processxml/invoice/read_not_found.xml +1 -0
  47. data/spec/fixtures/cluster/processxml/invoice/read_success.xml +106 -0
  48. data/spec/fixtures/cluster/processxml/read/deb.xml +12 -0
  49. data/spec/fixtures/cluster/processxml/response.xml +8 -0
  50. data/spec/fixtures/login/session/wsdl.xml +210 -0
  51. data/spec/spec_helper.rb +17 -0
  52. data/spec/stubs/finder_stubs.rb +19 -0
  53. data/spec/stubs/processxml_stubs.rb +41 -0
  54. data/spec/stubs/session_stubs.rb +28 -0
  55. data/spec/twinfield/api/oauth_session_spec.rb +37 -0
  56. data/spec/twinfield/api/process_spec.rb +7 -0
  57. data/spec/twinfield/browse/transaction/cost_center_spec.rb +60 -0
  58. data/spec/twinfield/browse/transaction/general_ledger_spec.rb +60 -0
  59. data/spec/twinfield/browse/transaction/transaction_spec.rb +72 -0
  60. data/spec/twinfield/config_spec.rb +60 -0
  61. data/spec/twinfield/customer_spec.rb +326 -0
  62. data/spec/twinfield/request/find_spec.rb +24 -0
  63. data/spec/twinfield/request/list_spec.rb +58 -0
  64. data/spec/twinfield/request/read_spec.rb +26 -0
  65. data/spec/twinfield/sales_invoice_spec.rb +253 -0
  66. data/spec/twinfield/session_spec.rb +77 -0
  67. data/spec/twinfield/transaction_spec.rb +149 -0
  68. data/twinfieldrb.gemspec +24 -0
  69. data/wsdls/accounting/finder.wsdl +157 -0
  70. data/wsdls/accounting/process.wsdl +199 -0
  71. data/wsdls/accounting/session.wsdl +452 -0
  72. data/wsdls/accounting2/finder.wsdl +157 -0
  73. data/wsdls/accounting2/process.wsdl +199 -0
  74. data/wsdls/api.accounting/finder.wsdl +157 -0
  75. data/wsdls/api.accounting/process.wsdl +199 -0
  76. data/wsdls/session.wsdl +210 -0
  77. data/wsdls/update +10 -0
  78. metadata +196 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e7198d5041d60aeaa7150c2175b914f55a6f712eeed2607ea417bbfd59a40619
4
+ data.tar.gz: da01ddd6abd5bbf424b7caf8d124a270ab70f428c0a81e1359c2a9633b4e975b
5
+ SHA512:
6
+ metadata.gz: b12687672b183120a1d21e6d931618993802da9ca80ded48b5a951b232a8e7e431e79c0af83199adb784ec4c7723dbf927615d4ecc4585ae76e6bc75c2bc65d7
7
+ data.tar.gz: 42c6c9be13b1e1fa4a07d49813504e1bd632d85c500476a8377412abde3a0d94cb11b8aa299760bb85e33eda3e4797a9ec1ab4039cda9e7cd0019bee971244ba
@@ -0,0 +1,70 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ master ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ master ]
20
+ schedule:
21
+ - cron: '23 6 * * 2'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ security-events: write
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ language: [ 'ruby' ]
36
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
38
+
39
+ steps:
40
+ - name: Checkout repository
41
+ uses: actions/checkout@v2
42
+
43
+ # Initializes the CodeQL tools for scanning.
44
+ - name: Initialize CodeQL
45
+ uses: github/codeql-action/init@v1
46
+ with:
47
+ languages: ${{ matrix.language }}
48
+ # If you wish to specify custom queries, you can do so here or in a config file.
49
+ # By default, queries listed here will override any specified in a config file.
50
+ # Prefix the list here with "+" to use these queries and those in the config file.
51
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
52
+
53
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54
+ # If this step fails, then you should remove it and run the build manually (see below)
55
+ - name: Autobuild
56
+ uses: github/codeql-action/autobuild@v1
57
+
58
+ # ℹ️ Command-line programs to run using the OS shell.
59
+ # 📚 https://git.io/JvXDl
60
+
61
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62
+ # and modify them (or add more) to build your code if your project
63
+ # uses a compiled language
64
+
65
+ #- run: |
66
+ # make bootstrap
67
+ # make release
68
+
69
+ - name: Perform CodeQL Analysis
70
+ uses: github/codeql-action/analyze@v1
@@ -0,0 +1,33 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+
6
+ name: Ruby CI
7
+
8
+ on:
9
+ push:
10
+ branches: [ main, master ]
11
+ pull_request:
12
+ branches: [ main, master ]
13
+
14
+ jobs:
15
+ test:
16
+
17
+ runs-on: ubuntu-24.04
18
+
19
+ strategy:
20
+ matrix:
21
+ ruby-version: [ 3.1.6, 3.2.6, 3.3.6]
22
+
23
+ steps:
24
+ - uses: actions/checkout@v2
25
+ - name: Set up Ruby ${{ matrix.ruby-version }}
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ bundler-cache: true
29
+ ruby-version: ${{ matrix.ruby-version }}
30
+ - name: Install dependencies
31
+ run: bundle install
32
+ - name: Run tests
33
+ run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ script/config.rb
6
+ .project
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All changes to the project will be documented in this file.
4
+
5
+ ## Pending release
6
+
7
+ * [BREAKING] Ruby > 3
8
+ * [BREAKING] Invoices are now what Twinfield calls SalesInvoices, not SalesTransactions
9
+ * [FEATURE] Added different classes that act more like ruby native objects in the Twinfield domain, such as Twinfield::Customer, Twinfield::Invoice, Twinfield::Transaction and Twinfield::Browse::Transaction::Customer
10
+ * [FEATURE] Allowed for OAuthSession; which is the preferred way of creating sessions
11
+ * [BUGFIX] Finder code made to work
12
+
13
+ ## 0.0.x
14
+
15
+ Initial releases by Ernst Rijsdijk, Stephan van Diepen and Joris Reijrink.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in twinfield.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # TwinfieldRB
2
+
3
+ Twinfield is an international Web service for collaborative online accounting. The `twinfieldrb` gem is a simple client for their SOAP-based API (it is a continuation of the long forgotten `twinfield` gem)
4
+
5
+ This project started as a clone of the `twinfield`-gem, but [I've been working](CHANGELOG.md) to abstract the soap calls away. Perhaps I'll release this as a gem on its own. For now see "Installing > Using Twinfield".
6
+
7
+ ## Installing
8
+
9
+ ### Using Twinfield
10
+
11
+ Add Twinfield in `Gemfile` as a gem dependency:
12
+
13
+ gem "twinfieldrb", git: "https://github.com/murb/twinfield.git"
14
+
15
+ Run the following in your console to install with Bundler:
16
+
17
+ bundle install
18
+
19
+ For OAuth authentication, now the default As a companion gem a [`omniauth-twinfield`-gem](https://github.com/murb/omniauth-twinfield) was created to help setting up OAuth.
20
+
21
+ ## Configuration
22
+
23
+ Your application will authenticate to Twinfield using OAuth. A companion gem was created to help setting up OAuth communication with your app: [`omniauth-twinfield`-gem](https://github.com/murb/omniauth-twinfield).
24
+
25
+ Cluster and access token are retrieved in the OAuth flow:
26
+
27
+ Twinfield.configure do |config|
28
+ config.session_type = "Twinfield::Api::OAuthSession"
29
+ config.cluster = OAuthClient.last.token_data["twf.clusterUrl"]
30
+ config.access_token = OAuthClient.first.access_token
31
+ config.log_level = :debug
32
+ end
33
+
34
+ Twinfield::Request::List.offices
35
+
36
+ Now configure the company you're looking for:
37
+
38
+ Twinfield.configuration.company="NL123"
39
+
40
+ ## Examples
41
+
42
+ Here are some examples that may be useful when using this GEM for the first time.
43
+
44
+ ### List of all debtors
45
+
46
+ Twinfield::Request::List.dimensions({ dimtype: "DEB" })
47
+
48
+ ### Create a new debtor
49
+
50
+ debtor = Twinfield::Customer.new
51
+ debtor.code = Twinfield::Customer.next_unused_twinfield_customer_code # sorry
52
+ debtor.name = ""
53
+ debtor.shortname = ""
54
+ debtor.country = ""
55
+ debtor.banks = [Twinfield::Customer::Bank.new]
56
+ debtor.save
57
+
58
+ ### Create a new invoice
59
+
60
+ invoice = Twinfield::SalesInvoice.new(customer: 1003, invoicetype: "FACTUUR", currency: "EUR", invoicedate: Time.now, duedate: Time.now+1.month)
61
+ invoice.lines << Twinfield::SalesInvoice::Line.new(article: "A", unitspriceexcl: 100, allowdiscountorpremium: true, vatcode: "VH")
62
+ invoice.lines << Twinfield::SalesInvoice::Line.new(article: 0, unitspriceexcl: 100, description: "Spaartegoed", allowdiscountorpremium: true, vatcode: "VN")
63
+ invoice.lines << Twinfield::SalesInvoice::Line.new(article: 0, unitspriceexcl: 0, quantity: 0, description: "Custom article", allowdiscountorpremium: true, vatcode: "VH")
64
+
65
+ invoice.save
66
+
67
+ ### Read an invoice
68
+
69
+ The following should be enough to
70
+
71
+ invoice = Twinfield::SalesInvoice.find(12, invoicetype: "FACTUUR")
72
+ invoice.lines # returns the lines
73
+ invoice.vat_lines # returns the vat lines
74
+ invoice.total # returns the total amount (derived)
75
+ invoice.customer # returns an Twinfield::Customer
76
+ invoice.invoice_address # returns the customer's invoice address (Twinfield::Customer::Address, not just a code)
77
+ invoice.delivery_address # returns the full delivery address (Twinfield::Customer::Address, not just a code)
78
+
79
+ The matching transaction can be found using:
80
+
81
+ invoice.transaction
82
+
83
+ ### Find a transaction
84
+
85
+ transactions = Twinfield::Browse::Transaction::Customer.where(invoice_number: 12, code: "VRK")
86
+
87
+ ### Register a payment
88
+
89
+ trans = Twinfield::Transaction.new(code: "PIN", currency: "EUR")
90
+ trans.lines << Twinfield::Transaction::Line.new(type: :total, balance_code: "1230", value: 0.0, debitcredit: :debit)
91
+ trans.lines << Twinfield::Transaction::Line.new(type: :detail, balance_code: 1300, value: 60.5, debitcredit: :credit, customer_code: 1003, invoicenumber: 14)
92
+ trans.lines << Twinfield::Transaction::Line.new(type: :detail, balance_code: 1234, value: 60.5, debitcredit: :debit)
93
+ trans.save
94
+
95
+ Now this payment can be matched against the invoice (if amounts match)):
96
+
97
+ trans.match!(Twinfield::Browse::Transaction::Customer.find(invoice_number: 14, code: "VRK"))
98
+
99
+ ### Get the transactions for a customer
100
+
101
+ This gets a list of sales transactions for customer with code 1003
102
+
103
+ customer = Twinfield::Customer.find(1003)
104
+ customer.transactions(code: "VRK")
105
+
106
+ ### Store collect mandate
107
+
108
+ customer = Twinfield::Customer.find(1003)
109
+ customer.financials.collectmandate = Twinfield::Customer::CollectMandate.new(signaturedate: signed_on, id: sepa_proof_uuid)
110
+ customer.financials.payavailable = true # autosets meansofpayment to paymentfile; can be disabled to temporarily stop collecting through paymentfiles
111
+ customer.financials.paycode="SEPANLDD" # has to correspond to the countries default SEPA paymentfile
112
+
113
+ ### List office
114
+
115
+ Twinfield::Request::Read.office
116
+
117
+ ## Known issues
118
+
119
+ * The way configuration works may not work well in concurrent environments with multiple clients
120
+ * The latest development and use in practice has been using the OAuth-based approach; the old Twinfield::Api::Session may not even work anymore
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ module Twinfield
2
+ class AbstractModel
3
+ class << self
4
+ # private
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,50 @@
1
+ module Twinfield
2
+ module Api
3
+ class BaseApi
4
+ class << self
5
+ def session
6
+ @session ||= Twinfield.configuration.session_class.new
7
+ @session.logon
8
+ @session
9
+ end
10
+
11
+ def session= session
12
+ @client = nil
13
+ @session = session
14
+ end
15
+
16
+ def client
17
+ options = {
18
+ wsdl: wsdl,
19
+ env_namespace: :soap,
20
+ encoding: "UTF-8",
21
+ namespace_identifier: nil,
22
+ log: !!Twinfield.configuration.log_level,
23
+ log_level: Twinfield.configuration.log_level || :info
24
+ }
25
+ options[:logger] = Twinfield.configuration.logger if Twinfield.configuration.logger
26
+
27
+ @client ||= Savon.client(options)
28
+ end
29
+
30
+ def wsdl
31
+ raise "undefined .wsdl"
32
+ end
33
+
34
+ def cluster
35
+ session.cluster
36
+ end
37
+
38
+ def cluster_short_name
39
+ if cluster.match?("accounting2.")
40
+ "accounting2"
41
+ elsif cluster.match?("api.accounting")
42
+ "api.accounting"
43
+ else
44
+ "accounting"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ module Twinfield
2
+ module Api
3
+ class Finder < BaseApi
4
+ class << self
5
+ def wsdl
6
+ Twinfield::WSDLS[cluster_short_name][:finder]
7
+ end
8
+
9
+ def actions
10
+ @actions ||= client.operations
11
+ end
12
+
13
+ # request
14
+ # see: https://accounting.twinfield.com/webservices/documentation/#/ApiReference/Transactions/SalesInvoices
15
+ def request(type, options = {})
16
+ Twinfield.configuration.logger&.debug(" ↳ #{caller.find { |a| !a.match(/\/gems\/|\/ruby\/|<internal:/) }}")
17
+
18
+ first_row = options.delete(:first_row) || 1
19
+ pattern = options.delete(:pattern) || "*"
20
+ max_rows = options.delete(:max_rows) || 100
21
+
22
+ message = {
23
+ "type" => type,
24
+ "pattern" => pattern,
25
+ "field" => "0",
26
+ "firstRow" => first_row,
27
+ "maxRows" => max_rows,
28
+ "options" => {
29
+ "ArrayOfString" => options.map { |k, v| {"string" => [k, v]} }
30
+ }
31
+ }
32
+
33
+ xml = client.operation(:search).build(attributes: {xmlns: "http://www.twinfield.com/"}, soap_header: session.header, message: message).build_document
34
+
35
+ client.call(:search, xml: strip_global_namespace_from_xml(xml))
36
+ end
37
+
38
+ def strip_global_namespace_from_xml xml
39
+ # ugly enough xmlns is prefixed even though it suggests an element in the global document's namespace
40
+ xml.gsub("<xmlns:", "<").gsub("</xmlns:", "</")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ module Twinfield
2
+ module Api
3
+ class OAuthSession
4
+ HEADER_TEMPLATE = {
5
+ "Header" => {},
6
+ :attributes! => {
7
+ "Header" => {xmlns: "http://www.twinfield.com/"}
8
+ }
9
+ }
10
+
11
+ attr_accessor :cluster, :access_token
12
+
13
+ # sets up a new savon client which will be used for current Session
14
+ def initialize
15
+ @cluster = Twinfield.configuration.cluster
16
+ @access_token = Twinfield.configuration.access_token
17
+ end
18
+
19
+ # retrieve a session_id and cluster from twinfield
20
+ # relog is by default set to false so when logon is called on your
21
+ # current session, you wont lose your session_id
22
+ def logon(relog = false)
23
+ # no need to logon with OAuth
24
+ end
25
+
26
+ # call logon method with relog set to true
27
+ # this wil destroy the current session and cluster
28
+ def relog
29
+ logon(true)
30
+ end
31
+
32
+ # after a logon try you can ask the current status
33
+ def status
34
+ if connected?
35
+ "Ok"
36
+ else
37
+ "No access token"
38
+ end
39
+ end
40
+
41
+ # Returns true or false if current session has a session_id
42
+ # and cluster from twinfield
43
+ def connected?
44
+ !!@access_token && !!@cluster
45
+ end
46
+
47
+ def header
48
+ soap_header = HEADER_TEMPLATE
49
+
50
+ header_contents = {}
51
+ header_contents["AccessToken"] = access_token if access_token
52
+ header_contents["CompanyCode"] = Twinfield.configuration.company if Twinfield.configuration.company
53
+
54
+ soap_header.merge({"Header" => header_contents})
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,44 @@
1
+ module Twinfield
2
+ module Api
3
+ class Process < BaseApi
4
+ class << self
5
+ def wsdl
6
+ Twinfield::WSDLS[cluster_short_name][:process]
7
+ end
8
+
9
+ def actions
10
+ @actions ||= client.operations
11
+ end
12
+
13
+ def request(action = :process_xml_string, options = {}, &block)
14
+ Twinfield.configuration.logger&.debug(" ↳ #{caller.find { |a| !a.match(/\/gems\/|\/ruby\/|<internal:/) }}")
15
+
16
+ if actions.include?(action)
17
+ message = "<xmlRequest><![CDATA[#{block.call}]]></xmlRequest>"
18
+
19
+ client.call(action, attributes: {xmlns: "http://www.twinfield.com/"}, soap_header: session.header, message: message)
20
+ else
21
+ "action not found"
22
+ end
23
+ end
24
+
25
+ def read(element, options)
26
+ response = Twinfield::Api::Process.request(:process_xml_string) do
27
+ %(
28
+ <read>
29
+ <type>#{element}</type>
30
+ #{Twinfield::Api::Process.options_to_xml(options)}
31
+ </read>
32
+ )
33
+ end
34
+
35
+ Nokogiri::XML(response.body[:process_xml_string_response][:process_xml_string_result])
36
+ end
37
+
38
+ def options_to_xml(options)
39
+ options.map { |k, v| "<#{k}>#{v}</#{k}>" }.join("\n")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,170 @@
1
+ module Twinfield
2
+ module Api
3
+ class Session
4
+ HEADER_TEMPLATE = {
5
+ "Header" => {},
6
+ :attributes! => {
7
+ "Header" => {xmlns: "http://www.twinfield.com/"}
8
+ }
9
+ }
10
+
11
+ attr_accessor :session_id, :cluster
12
+
13
+ # sets up a new savon client which will be used for current Session
14
+ def initialize
15
+ @client = Savon.client(wsdl: Twinfield::WSDLS[:session],
16
+ log: !!Twinfield.configuration.log_level,
17
+ log_level: Twinfield.configuration.log_level || :info)
18
+ end
19
+
20
+ # retrieve a session_id and cluster from twinfield
21
+ # relog is by default set to false so when logon is called on your
22
+ # current session, you wont lose your session_id
23
+ def logon(relog = false)
24
+ if connected? && (relog == false)
25
+ "already connected"
26
+ else
27
+ response = @client.call(:logon, message: Twinfield.configuration.to_logon_hash)
28
+
29
+ if response.body[:logon_response][:logon_result] == "Ok"
30
+ @session_id = response.header[:header][:session_id]
31
+ @cluster = response.body[:logon_response][:cluster]
32
+
33
+ select_company(Twinfield.configuration.company)
34
+ end
35
+
36
+ @message = response.body[:logon_response][:logon_result]
37
+ end
38
+ end
39
+
40
+ # call logon method with relog set to true
41
+ # this wil destroy the current session and cluster
42
+ def relog
43
+ logon(true)
44
+ end
45
+
46
+ # after a logon try you can ask the current status
47
+ def status
48
+ @message
49
+ end
50
+
51
+ # Returns true or false if current session has a session_id
52
+ # and cluster from twinfield
53
+ def connected?
54
+ !!@session_id && !!@cluster
55
+ end
56
+
57
+ # Abandons the session.
58
+ def abandon
59
+ if session_id
60
+ message = "<Abandon xmlns='http://www.twinfield.com/' />"
61
+ @client.call(:Abandon, attributes: {xmlns: "http://www.twinfield.com/"}, soap_header: header, message: message)
62
+
63
+ # TODO: Return real status
64
+ # There is no message from twinfield if the action succeeded
65
+ "Ok"
66
+ else
67
+ "no session found"
68
+ end
69
+ end
70
+
71
+ # Keep the session alive, to prevent session time out. A session time out will occur after 20 minutes.
72
+ def keep_alive
73
+ @client.request :keep_alive do
74
+ soap.header = header
75
+ soap.body = "<KeepAliveResponse xmlns='http://www.twinfield.com/' />"
76
+ end
77
+ # TODO: Return real status
78
+ # There is no message from twinfield if the action succeeded
79
+ "Ok"
80
+ end
81
+
82
+ def header
83
+ soap_header = HEADER_TEMPLATE
84
+ soap_header = soap_header.merge({"Header" => {"SessionID" => session_id}}) if session_id
85
+ soap_header
86
+ end
87
+
88
+ # Gets the session's user role.
89
+ def get_role
90
+ # <?xml version="1.0" encoding="utf-8"?>
91
+ # <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
92
+ # <soap:Header>
93
+ # <Header xmlns="http://www.twinfield.com/">
94
+ # <SessionID>string</SessionID>
95
+ # </Header>
96
+ # </soap:Header>
97
+ # <soap:Body>
98
+ # <GetRole xmlns="http://www.twinfield.com/" />
99
+ # </soap:Body>
100
+ # </soap:Envelope>
101
+ raise NotImplementedError
102
+ end
103
+
104
+ # Sends the sms code.
105
+ def sms_send_code
106
+ # <?xml version="1.0" encoding="utf-8"?>
107
+ # <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
108
+ # <soap:Header>
109
+ # <Header xmlns="http://www.twinfield.com/">
110
+ # <SessionID>string</SessionID>
111
+ # </Header>
112
+ # </soap:Header>
113
+ # <soap:Body>
114
+ # <SmsSendCode xmlns="http://www.twinfield.com/" />
115
+ # </soap:Body>
116
+ # </soap:Envelope>
117
+ raise NotImplementedError
118
+ end
119
+
120
+ # Logs on with the sms code.
121
+ def sms_logon
122
+ # <?xml version="1.0" encoding="utf-8"?>
123
+ # <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
124
+ # <soap:Header>
125
+ # <Header xmlns="http://www.twinfield.com/">
126
+ # <SessionID>string</SessionID>
127
+ # </Header>
128
+ # </soap:Header>
129
+ # <soap:Body>
130
+ # <SmsLogon xmlns="http://www.twinfield.com/">
131
+ # <smsCode>string</smsCode>
132
+ # </SmsLogon>
133
+ # </soap:Body>
134
+ # </soap:Envelope>
135
+ raise NotImplementedError
136
+ end
137
+
138
+ # Changes the password.
139
+ def change_password
140
+ # <?xml version="1.0" encoding="utf-8"?>
141
+ # <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
142
+ # <soap:Header>
143
+ # <Header xmlns="http://www.twinfield.com/">
144
+ # <SessionID>string</SessionID>
145
+ # </Header>
146
+ # </soap:Header>
147
+ # <soap:Body>
148
+ # <ChangePassword xmlns="http://www.twinfield.com/">
149
+ # <currentPassword>string</currentPassword>
150
+ # <newPassword>string</newPassword>
151
+ # </ChangePassword>
152
+ # </soap:Body>
153
+ # </soap:Envelope>
154
+ raise NotImplementedError
155
+ end
156
+
157
+ # Selects a company.
158
+ def select_company(code)
159
+ message = "<company>#{code}</company>"
160
+
161
+ Savon.client(wsdl: "#{@cluster}/webservices/session.asmx?wsdl",
162
+ env_namespace: :soap,
163
+ encoding: "UTF-8",
164
+ namespace_identifier: nil).call(:select_company, attributes: {xmlns: "http://www.twinfield.com/"}, soap_header: header, message: message)
165
+
166
+ "Ok"
167
+ end
168
+ end
169
+ end
170
+ end