twinfieldrb 0.3.0

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.
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