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.
- checksums.yaml +7 -0
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/rspec.yml +33 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +4 -0
- data/README.md +120 -0
- data/Rakefile +1 -0
- data/lib/twinfield/abstract_model.rb +7 -0
- data/lib/twinfield/api/base_api.rb +50 -0
- data/lib/twinfield/api/finder.rb +45 -0
- data/lib/twinfield/api/o_auth_session.rb +58 -0
- data/lib/twinfield/api/process.rb +44 -0
- data/lib/twinfield/api/session.rb +170 -0
- data/lib/twinfield/browse/transaction/cost_center.rb +145 -0
- data/lib/twinfield/browse/transaction/customer.rb +413 -0
- data/lib/twinfield/browse/transaction/general_ledger.rb +144 -0
- data/lib/twinfield/configuration.rb +49 -0
- data/lib/twinfield/create/cost_center.rb +39 -0
- data/lib/twinfield/create/creditor.rb +88 -0
- data/lib/twinfield/create/debtor.rb +88 -0
- data/lib/twinfield/create/error.rb +30 -0
- data/lib/twinfield/create/general_ledger.rb +39 -0
- data/lib/twinfield/create/transaction.rb +97 -0
- data/lib/twinfield/customer.rb +612 -0
- data/lib/twinfield/helpers/parsers.rb +23 -0
- data/lib/twinfield/helpers/transaction_match.rb +40 -0
- data/lib/twinfield/request/find.rb +149 -0
- data/lib/twinfield/request/list.rb +66 -0
- data/lib/twinfield/request/read.rb +111 -0
- data/lib/twinfield/sales_invoice.rb +409 -0
- data/lib/twinfield/transaction.rb +112 -0
- data/lib/twinfield/version.rb +5 -0
- data/lib/twinfield.rb +89 -0
- data/script/boot.rb +58 -0
- data/script/console +2 -0
- data/spec/fixtures/cluster/finder/ivt.xml +1 -0
- data/spec/fixtures/cluster/processxml/columns/sales_transactions.xml +312 -0
- data/spec/fixtures/cluster/processxml/customer/create_success.xml +100 -0
- data/spec/fixtures/cluster/processxml/customer/read_success.xml +93 -0
- data/spec/fixtures/cluster/processxml/customer/update_success.xml +1 -0
- data/spec/fixtures/cluster/processxml/invoice/create_error.xml +8 -0
- data/spec/fixtures/cluster/processxml/invoice/create_final_error.xml +67 -0
- data/spec/fixtures/cluster/processxml/invoice/create_success.xml +64 -0
- data/spec/fixtures/cluster/processxml/invoice/read_not_found.xml +1 -0
- data/spec/fixtures/cluster/processxml/invoice/read_success.xml +106 -0
- data/spec/fixtures/cluster/processxml/read/deb.xml +12 -0
- data/spec/fixtures/cluster/processxml/response.xml +8 -0
- data/spec/fixtures/login/session/wsdl.xml +210 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/stubs/finder_stubs.rb +19 -0
- data/spec/stubs/processxml_stubs.rb +41 -0
- data/spec/stubs/session_stubs.rb +28 -0
- data/spec/twinfield/api/oauth_session_spec.rb +37 -0
- data/spec/twinfield/api/process_spec.rb +7 -0
- data/spec/twinfield/browse/transaction/cost_center_spec.rb +60 -0
- data/spec/twinfield/browse/transaction/general_ledger_spec.rb +60 -0
- data/spec/twinfield/browse/transaction/transaction_spec.rb +72 -0
- data/spec/twinfield/config_spec.rb +60 -0
- data/spec/twinfield/customer_spec.rb +326 -0
- data/spec/twinfield/request/find_spec.rb +24 -0
- data/spec/twinfield/request/list_spec.rb +58 -0
- data/spec/twinfield/request/read_spec.rb +26 -0
- data/spec/twinfield/sales_invoice_spec.rb +253 -0
- data/spec/twinfield/session_spec.rb +77 -0
- data/spec/twinfield/transaction_spec.rb +149 -0
- data/twinfieldrb.gemspec +24 -0
- data/wsdls/accounting/finder.wsdl +157 -0
- data/wsdls/accounting/process.wsdl +199 -0
- data/wsdls/accounting/session.wsdl +452 -0
- data/wsdls/accounting2/finder.wsdl +157 -0
- data/wsdls/accounting2/process.wsdl +199 -0
- data/wsdls/api.accounting/finder.wsdl +157 -0
- data/wsdls/api.accounting/process.wsdl +199 -0
- data/wsdls/session.wsdl +210 -0
- data/wsdls/update +10 -0
- 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
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
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,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
|