tradedoc 0.1.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/.editorconfig +13 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +10 -0
- data/lib/tradedoc/code/agency.rb +8 -0
- data/lib/tradedoc/code/document_type.rb +95 -0
- data/lib/tradedoc/code/national_clearing_system.rb +97 -0
- data/lib/tradedoc/code/payment_means_type.rb +107 -0
- data/lib/tradedoc/code/tax_category.rb +97 -0
- data/lib/tradedoc/code.rb +12 -0
- data/lib/tradedoc/format/apeh.rb +36 -0
- data/lib/tradedoc/format/cii/coder/address.rb +55 -0
- data/lib/tradedoc/format/cii/coder/big_decimal.rb +21 -0
- data/lib/tradedoc/format/cii/coder/contact.rb +43 -0
- data/lib/tradedoc/format/cii/coder/date.rb +87 -0
- data/lib/tradedoc/format/cii/coder/document_reference.rb +33 -0
- data/lib/tradedoc/format/cii/coder/exchange_rate.rb +33 -0
- data/lib/tradedoc/format/cii/coder/financial_account.rb +45 -0
- data/lib/tradedoc/format/cii/coder/financial_institution.rb +39 -0
- data/lib/tradedoc/format/cii/coder/invoice.rb +127 -0
- data/lib/tradedoc/format/cii/coder/invoice_line.rb +88 -0
- data/lib/tradedoc/format/cii/coder/monetary_total.rb +36 -0
- data/lib/tradedoc/format/cii/coder/money.rb +27 -0
- data/lib/tradedoc/format/cii/coder/payment_means.rb +49 -0
- data/lib/tradedoc/format/cii/coder/period.rb +27 -0
- data/lib/tradedoc/format/cii/coder/remittance_advice.rb +130 -0
- data/lib/tradedoc/format/cii/coder/string.rb +21 -0
- data/lib/tradedoc/format/cii/coder/tax_subtotal.rb +35 -0
- data/lib/tradedoc/format/cii/coder/trade_party.rb +29 -0
- data/lib/tradedoc/format/cii/coder.rb +27 -0
- data/lib/tradedoc/format/cii.rb +12 -0
- data/lib/tradedoc/format/facturae.rb +51 -0
- data/lib/tradedoc/format/fatturapa/coder/invoice.rb +31 -0
- data/lib/tradedoc/format/fatturapa/coder.rb +8 -0
- data/lib/tradedoc/format/fatturapa.rb +13 -0
- data/lib/tradedoc/format/isdoc.rb +40 -0
- data/lib/tradedoc/format/ksef/coder/invoice.rb +32 -0
- data/lib/tradedoc/format/ksef/coder.rb +10 -0
- data/lib/tradedoc/format/ksef.rb +17 -0
- data/lib/tradedoc/format/mydata.rb +42 -0
- data/lib/tradedoc/format/nav.rb +38 -0
- data/lib/tradedoc/format/ubl/coder/address.rb +48 -0
- data/lib/tradedoc/format/ubl/coder/big_decimal.rb +21 -0
- data/lib/tradedoc/format/ubl/coder/contact.rb +29 -0
- data/lib/tradedoc/format/ubl/coder/country.rb +27 -0
- data/lib/tradedoc/format/ubl/coder/date.rb +21 -0
- data/lib/tradedoc/format/ubl/coder/document_reference.rb +55 -0
- data/lib/tradedoc/format/ubl/coder/exchange_rate.rb +33 -0
- data/lib/tradedoc/format/ubl/coder/financial_account.rb +32 -0
- data/lib/tradedoc/format/ubl/coder/financial_institution.rb +42 -0
- data/lib/tradedoc/format/ubl/coder/invoice.rb +92 -0
- data/lib/tradedoc/format/ubl/coder/invoice_line.rb +53 -0
- data/lib/tradedoc/format/ubl/coder/monetary_total.rb +39 -0
- data/lib/tradedoc/format/ubl/coder/money.rb +22 -0
- data/lib/tradedoc/format/ubl/coder/payment_means.rb +39 -0
- data/lib/tradedoc/format/ubl/coder/period.rb +27 -0
- data/lib/tradedoc/format/ubl/coder/product.rb +37 -0
- data/lib/tradedoc/format/ubl/coder/remittance_advice.rb +91 -0
- data/lib/tradedoc/format/ubl/coder/string.rb +21 -0
- data/lib/tradedoc/format/ubl/coder/tax_breakdown.rb +27 -0
- data/lib/tradedoc/format/ubl/coder/tax_category.rb +37 -0
- data/lib/tradedoc/format/ubl/coder/tax_subtotal.rb +29 -0
- data/lib/tradedoc/format/ubl/coder/trade_party.rb +37 -0
- data/lib/tradedoc/format/ubl/coder.rb +29 -0
- data/lib/tradedoc/format/ubl.rb +13 -0
- data/lib/tradedoc/format/zugferdv1/coder/invoice.rb +33 -0
- data/lib/tradedoc/format/zugferdv1/coder.rb +8 -0
- data/lib/tradedoc/format/zugferdv1.rb +21 -0
- data/lib/tradedoc/format.rb +110 -0
- data/lib/tradedoc/model/address.rb +21 -0
- data/lib/tradedoc/model/base.rb +153 -0
- data/lib/tradedoc/model/contact.rb +9 -0
- data/lib/tradedoc/model/country.rb +8 -0
- data/lib/tradedoc/model/country_subdivision.rb +8 -0
- data/lib/tradedoc/model/document.rb +9 -0
- data/lib/tradedoc/model/document_reference.rb +11 -0
- data/lib/tradedoc/model/exchange_rate.rb +17 -0
- data/lib/tradedoc/model/financial_account.rb +19 -0
- data/lib/tradedoc/model/financial_institution.rb +11 -0
- data/lib/tradedoc/model/invoice.rb +57 -0
- data/lib/tradedoc/model/invoice_line.rb +46 -0
- data/lib/tradedoc/model/monetary_total.rb +34 -0
- data/lib/tradedoc/model/payment_means.rb +27 -0
- data/lib/tradedoc/model/period.rb +8 -0
- data/lib/tradedoc/model/price.rb +21 -0
- data/lib/tradedoc/model/product.rb +18 -0
- data/lib/tradedoc/model/remittance_advice.rb +20 -0
- data/lib/tradedoc/model/remittance_advice_line.rb +24 -0
- data/lib/tradedoc/model/tax_breakdown.rb +11 -0
- data/lib/tradedoc/model/tax_category.rb +26 -0
- data/lib/tradedoc/model/tax_subtotal.rb +13 -0
- data/lib/tradedoc/model/trade_party.rb +9 -0
- data/lib/tradedoc/model.rb +33 -0
- data/lib/tradedoc/version.rb +5 -0
- data/lib/tradedoc/xml/reader.rb +163 -0
- data/lib/tradedoc/xml/writer.rb +56 -0
- data/lib/tradedoc/xml.rb +8 -0
- data/lib/tradedoc.rb +98 -0
- data/sig/tradedoc.rbs +4 -0
- metadata +187 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d87493b2d1156cf4362761b4dc3398dad0c6348ce3170826baec0bfba4828e89
|
|
4
|
+
data.tar.gz: 24977f17f5d160e53db9bce2515ebf54f8230803dfd29421b70b0481999f9d93
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c557ebc83e0c24369c8fa95290fe21814709f4d87b6b0f7877d98dd6cf5ea6d1805a4a5ba6e087a25b00c5a2ded4fbaabdc237eddfafc802792e5603f3eb3489
|
|
7
|
+
data.tar.gz: 85ef013be857e6ee3d4038c94aec51f06552d0e34daa75cd63a1e6755df6ed27accb484e5cc63478383382d85c3c6afd52547ef148034dcc95814b9a45616f43
|
data/.editorconfig
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mroach
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Tradedoc
|
|
2
|
+
|
|
3
|
+
A Ruby library for reading and writing business trade documents such as invoices,
|
|
4
|
+
credit notes, remittance advice, etc.
|
|
5
|
+
|
|
6
|
+
## Formats
|
|
7
|
+
|
|
8
|
+
There are multiple formats for business documents in current use around the world
|
|
9
|
+
and this library aims to be able to support them natively or easily add support for more.
|
|
10
|
+
|
|
11
|
+
Currently supported:
|
|
12
|
+
|
|
13
|
+
* **UN/CEFACT** also known as *CII* (Cross-Industry Invoice)
|
|
14
|
+
* **UBL**, Universal Business Language
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Add it to your Gemfile
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem "tradedoc"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Development
|
|
25
|
+
|
|
26
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
27
|
+
|
|
28
|
+
### Style
|
|
29
|
+
|
|
30
|
+
This project uses [Standard](https://github.com/standardrb/standard) as the code style
|
|
31
|
+
and all pull requests must be linted using `standardrb`.
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dknmz/tradedoc.
|
|
36
|
+
|
|
37
|
+
## Resources
|
|
38
|
+
|
|
39
|
+
### EN 16931
|
|
40
|
+
|
|
41
|
+
* List of business terms: https://peppolvalidator.com/en16931-business-terms
|
|
42
|
+
|
|
43
|
+
### UBL
|
|
44
|
+
|
|
45
|
+
* XSD and sample XML files: https://docs.oasis-open.org/ubl/os-UBL-2.4/
|
|
46
|
+
|
|
47
|
+
### UN/CEFACT / CII
|
|
48
|
+
|
|
49
|
+
* XSD: https://unece.org/trade/uncefact/mainstandards
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Code
|
|
3
|
+
# Both CII and UBL share the same document type list and codes.
|
|
4
|
+
# If other formats use different codes, they can be added here to avoid
|
|
5
|
+
# gnarly and verbose mapping when generating and parsing documents.
|
|
6
|
+
class DocumentType
|
|
7
|
+
class << self
|
|
8
|
+
def get(input)
|
|
9
|
+
case input
|
|
10
|
+
in String => cefact_id if cefact_id.match?(/^\d{3}/)
|
|
11
|
+
@values.values.detect { it.cefact_id == cefact_id }
|
|
12
|
+
in Symbol => id
|
|
13
|
+
@values[id]
|
|
14
|
+
in other
|
|
15
|
+
raise "can't lookup by '#{other.class}'"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get!(input)
|
|
20
|
+
if (v = get(input))
|
|
21
|
+
return v
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
raise NotFoundError, "no document type could be found from '#{input}'"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def parse(input)
|
|
28
|
+
get!(input)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def register(cefact_id, id, label: nil)
|
|
34
|
+
@values ||= {}
|
|
35
|
+
if @values.key?(id)
|
|
36
|
+
raise ArgumentError, "id '#{id}' already registered"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@values[id] = new(id:, cefact_id:, label:)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
attr_reader :id, :cefact_id, :label
|
|
44
|
+
|
|
45
|
+
# @param id [Symbol] Our own identifier for the document type
|
|
46
|
+
# @param cefact_id [String]
|
|
47
|
+
# Numeric code from UN/CEFACT: https://vocabulary.uncefact.org/DocumentCodeList
|
|
48
|
+
# @param label [String] Optional user-friendly label
|
|
49
|
+
def initialize(id:, cefact_id:, label: nil)
|
|
50
|
+
@id = id
|
|
51
|
+
@cefact_id = cefact_id
|
|
52
|
+
@label = label || id.to_s.tr("_", " ").capitalize
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_h
|
|
56
|
+
{id:, cefact_id:, label:}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def deconstruct_keys(keys)
|
|
60
|
+
to_h
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def hash
|
|
64
|
+
to_h.hash
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ==(other)
|
|
68
|
+
case other
|
|
69
|
+
in DocumentType => t
|
|
70
|
+
t.hash == hash
|
|
71
|
+
in String | Symbol => pattern
|
|
72
|
+
self == self.class.get(pattern)
|
|
73
|
+
in Hash => h
|
|
74
|
+
to_h == h
|
|
75
|
+
in _
|
|
76
|
+
false
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def eql?(other)
|
|
81
|
+
self == other
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# https://vocabulary.uncefact.org/DocumentCodeList
|
|
85
|
+
register "326", :partial_invoice
|
|
86
|
+
register "380", :commercial_invoice
|
|
87
|
+
register "381", :credit_note
|
|
88
|
+
register "383", :debit_note
|
|
89
|
+
register "384", :corrected_invoice
|
|
90
|
+
register "386", :prepayment_invoice
|
|
91
|
+
register "389", :self_billed_invoice
|
|
92
|
+
register "481", :remittance_advice
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Code
|
|
3
|
+
# Most bank accounts aside from IBAN will have two components: account number + clearing ID
|
|
4
|
+
# For example in the US, the clearing ID is the routing number.
|
|
5
|
+
#
|
|
6
|
+
# For CII/CEFACT, each type gets mapped to an XML element name. Though this is technically
|
|
7
|
+
# "not the right" place for this, it's less bad than defining a list and then a mapping separately.
|
|
8
|
+
#
|
|
9
|
+
# This list comes from ISO-20022 `ExternalClearingSystemIdentificationCode`
|
|
10
|
+
class NationalClearingSystem
|
|
11
|
+
attr_reader :iso_code, :cefact_id, :label
|
|
12
|
+
|
|
13
|
+
def initialize(iso_code:, cefact_id:, label: nil)
|
|
14
|
+
@iso_code = iso_code
|
|
15
|
+
@cefact_id = cefact_id
|
|
16
|
+
@label = label || cefact_id.sub(/ID$/, "")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_h
|
|
20
|
+
{iso_code:, cefact_id:, label:}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def deconstruct_keys(keys)
|
|
24
|
+
to_h
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def hash
|
|
28
|
+
to_h.hash
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ==(other)
|
|
32
|
+
case other
|
|
33
|
+
in NationalClearingSystem => t
|
|
34
|
+
t.hash == hash
|
|
35
|
+
in String | Symbol => pattern
|
|
36
|
+
self == self.class.get(pattern)
|
|
37
|
+
in Hash => h
|
|
38
|
+
to_h == h
|
|
39
|
+
in _
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def eql?(other)
|
|
45
|
+
self == other
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
def all
|
|
50
|
+
@values.values
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def find_by_iso_code(input)
|
|
54
|
+
@values[input]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find_by_cefact_id(cefact_id)
|
|
58
|
+
@values.detect { it.cefact_id == cefact_id }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def parse(input)
|
|
62
|
+
if (v = find_by_iso_code(input))
|
|
63
|
+
return v
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
raise "couldn't parse '#{input}' to a clearing code"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def register(iso_code, cefact_id, label: nil)
|
|
72
|
+
@values ||= {}
|
|
73
|
+
if @values.key?(iso_code)
|
|
74
|
+
raise ArgumentError, "'#{iso_code}' already registered"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@values[iso_code] = new(iso_code:, cefact_id:, label:).freeze
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def lock_registry!
|
|
81
|
+
@values.freeze
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
register "AUBSB", "AustralianBSBID"
|
|
86
|
+
register "CACPA", "CanadianPaymentsAssociationID"
|
|
87
|
+
register "GBDSC", "UKSortCodeID"
|
|
88
|
+
register "HKNCC", "HongKongBankID"
|
|
89
|
+
register "NZRSA", "NewZealandNCCID"
|
|
90
|
+
register "USABA", "FedwireRoutingNumberID"
|
|
91
|
+
register "USPID", "CHIPSUniversalID"
|
|
92
|
+
register "ZANCC", "SouthAfricanNCCID"
|
|
93
|
+
|
|
94
|
+
lock_registry!
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Code
|
|
3
|
+
class PaymentMeansType
|
|
4
|
+
class << self
|
|
5
|
+
def get(input)
|
|
6
|
+
case input
|
|
7
|
+
in String => code
|
|
8
|
+
@values.values.detect { it.code == code }
|
|
9
|
+
in Symbol => id
|
|
10
|
+
@values[id]
|
|
11
|
+
in other
|
|
12
|
+
raise "can't lookup by '#{other.class}'"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get!(input)
|
|
17
|
+
if (v = get(input))
|
|
18
|
+
return v
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
raise NotFoundError, "no payment means type could be found from '#{input}'"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse(input)
|
|
25
|
+
get!(input)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def register(code, id, label: nil)
|
|
29
|
+
@values ||= {}
|
|
30
|
+
if @values.key?(id)
|
|
31
|
+
raise ArgumentError, "id '#{id}' already registered"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@values[id] = new(id:, code:, label:)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attr_reader :id, :code, :label
|
|
39
|
+
|
|
40
|
+
# @param id [Symbol] Our own internal identifier
|
|
41
|
+
# @param code [String]
|
|
42
|
+
# UN/ECE 4461: https://unece.org/fileadmin/DAM/trade/untdid/d16b/tred/tred4461.htm
|
|
43
|
+
# @param label [String] Optional user-friendly label.
|
|
44
|
+
def initialize(id:, code:, label: nil)
|
|
45
|
+
@id = id
|
|
46
|
+
@code = code
|
|
47
|
+
@label = label || id.to_s.tr("_", " ").capitalize
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_h
|
|
51
|
+
{id:, code:, label:}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def deconstruct_keys(keys)
|
|
55
|
+
to_h
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def hash
|
|
59
|
+
to_h.hash
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def ==(other)
|
|
63
|
+
case other
|
|
64
|
+
in PaymentMeansType => t
|
|
65
|
+
t.hash == hash
|
|
66
|
+
in String | Symbol => pattern
|
|
67
|
+
self == self.class.get(pattern)
|
|
68
|
+
in Hash => h
|
|
69
|
+
to_h == h
|
|
70
|
+
in _
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def eql?(other)
|
|
76
|
+
self == other
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
register "1", :instrument_not_defined
|
|
80
|
+
register "2", :ach_credit, label: "ACH credit"
|
|
81
|
+
register "3", :ach_debit, label: "ACH debit"
|
|
82
|
+
register "8", :hold
|
|
83
|
+
register "9", :national_clearing
|
|
84
|
+
register "10", :cash
|
|
85
|
+
register "20", :cheque
|
|
86
|
+
register "30", :credit_transfer
|
|
87
|
+
register "31", :debit_transfer
|
|
88
|
+
register "42", :payment_to_bank_account
|
|
89
|
+
register "48", :bank_card
|
|
90
|
+
register "50", :postgiro
|
|
91
|
+
register "52", :urgent_commercial_payment
|
|
92
|
+
register "54", :credit_card
|
|
93
|
+
register "55", :debit_card
|
|
94
|
+
register "56", :bankgiro
|
|
95
|
+
register "57", :standing_agreement
|
|
96
|
+
register "58", :sepa_credit_transfer, label: "SEPA credit transfer"
|
|
97
|
+
register "59", :sepa_direct_debit, label: "SEPA direct debit"
|
|
98
|
+
register "68", :online_payment_service
|
|
99
|
+
|
|
100
|
+
# From the docs:
|
|
101
|
+
# A code assigned within a code list to be used on an
|
|
102
|
+
# interim basis and as defined among trading partners
|
|
103
|
+
# until a precise code can be assigned to the code list.
|
|
104
|
+
register "ZZZ", :mutually_defined
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Code
|
|
3
|
+
# UN/EDIFACT 5305 Duty or tax or fee category code
|
|
4
|
+
# https://unece.org/fileadmin/DAM/trade/untdid/d16b/tred/tred5305.htm
|
|
5
|
+
class TaxCategory
|
|
6
|
+
class << self
|
|
7
|
+
def get(input)
|
|
8
|
+
case input
|
|
9
|
+
in String => edifact_id if edifact_id.match?(/^[A-Z]{1,2}$/)
|
|
10
|
+
@values.values.detect { it.edifact_id == edifact_id }
|
|
11
|
+
in Symbol => id
|
|
12
|
+
@values[id]
|
|
13
|
+
in other
|
|
14
|
+
raise "can't lookup by '#{other.class}'"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get!(input)
|
|
19
|
+
if (v = get(input))
|
|
20
|
+
return v
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
raise NotFoundError, "no tax category could be found from '#{input}'"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse(input)
|
|
27
|
+
get!(input)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def register(edifact_id, id)
|
|
33
|
+
@values ||= {}
|
|
34
|
+
@values[id] = new(id:, edifact_id:)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attr_reader :id, :edifact_id
|
|
39
|
+
|
|
40
|
+
def initialize(id:, edifact_id:)
|
|
41
|
+
@id = id
|
|
42
|
+
@edifact_id = edifact_id
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_h
|
|
46
|
+
{id:, edifact_id:}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def deconstruct_keys(keys)
|
|
50
|
+
to_h
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def hash
|
|
54
|
+
to_h.hash
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ==(other)
|
|
58
|
+
case other
|
|
59
|
+
in TaxCategory => tc
|
|
60
|
+
tc.hash == hash
|
|
61
|
+
in String | Symbol => pattern
|
|
62
|
+
self == self.class.get(pattern)
|
|
63
|
+
in Hash => h
|
|
64
|
+
to_h == h
|
|
65
|
+
in _
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def eql?(other)
|
|
71
|
+
self == other
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
register "A", :mixed_rate
|
|
75
|
+
register "AA", :lower_rate
|
|
76
|
+
register "AB", :exempt_for_resale
|
|
77
|
+
register "AC", :not_due_now
|
|
78
|
+
register "AD", :due_from_previous
|
|
79
|
+
register "AE", :reverse_charge
|
|
80
|
+
register "B", :transferred
|
|
81
|
+
register "C", :duty_paid_by_supplier
|
|
82
|
+
register "D", :travel_agent
|
|
83
|
+
register "E", :exempt
|
|
84
|
+
register "F", :second_hand_goods
|
|
85
|
+
register "G", :free_export
|
|
86
|
+
register "H", :higher_rate
|
|
87
|
+
register "I", :works_of_art
|
|
88
|
+
register "J", :collectables_and_antiques
|
|
89
|
+
register "K", :eea_intra_community_exempt
|
|
90
|
+
register "L", :canary_islands_general_indirect
|
|
91
|
+
register "M", :ceuta_melilla
|
|
92
|
+
register "O", :services_outside_scope
|
|
93
|
+
register "S", :standard
|
|
94
|
+
register "Z", :zero_rated
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Code
|
|
3
|
+
Error = Class.new(Tradedoc::Error)
|
|
4
|
+
NotFoundError = Class.new(Error)
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require_relative "code/agency"
|
|
9
|
+
require_relative "code/document_type"
|
|
10
|
+
require_relative "code/national_clearing_system"
|
|
11
|
+
require_relative "code/payment_means_type"
|
|
12
|
+
require_relative "code/tax_category"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Format
|
|
3
|
+
# Legacy Hungarian invoice format that still appears embedded in PDFs on occasion.
|
|
4
|
+
# We're unlikely to implement support for it, but being able to detect it is a nice feature.
|
|
5
|
+
module APEH
|
|
6
|
+
extend Accessors
|
|
7
|
+
extend Finders
|
|
8
|
+
extend XMLSerialization
|
|
9
|
+
|
|
10
|
+
module Coder
|
|
11
|
+
class Invoice
|
|
12
|
+
NS = {
|
|
13
|
+
"xmlns" => "http://www.apeh.hu/2005/szamla"
|
|
14
|
+
}
|
|
15
|
+
private_constant :NS
|
|
16
|
+
|
|
17
|
+
def self.namespaces
|
|
18
|
+
NS
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.can_parse?(xmldoc)
|
|
22
|
+
xmldoc.root.namespace&.href == namespaces.fetch("xmlns")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.dump(w, obj)
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.parse(r)
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Format
|
|
3
|
+
module CII
|
|
4
|
+
module Coder
|
|
5
|
+
class Address
|
|
6
|
+
# Address lines are limited to 5, and each node gets its own name including
|
|
7
|
+
# the number in English.
|
|
8
|
+
LINE_WORDS = ["One", "Two", "Three", "Four", "Five"]
|
|
9
|
+
private_constant :LINE_WORDS
|
|
10
|
+
|
|
11
|
+
def self.ruby_type
|
|
12
|
+
Model::Address
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.dump(w, obj, as:)
|
|
16
|
+
w.add(as) do
|
|
17
|
+
w.render(obj.postal_code, as: "PostcodeCode")
|
|
18
|
+
obj.lines.each_with_index do |line, ix|
|
|
19
|
+
w.add("Line#{LINE_WORDS[ix]}", line)
|
|
20
|
+
end
|
|
21
|
+
w.render(obj.street_name, as: "StreetName")
|
|
22
|
+
w.render(obj.city, as: "CityName")
|
|
23
|
+
w.render(obj.country.iso_code, as: "CountryID")
|
|
24
|
+
w.render(obj.country.name, as: "CountryName")
|
|
25
|
+
w.render(obj.subdivision&.code, as: "CountrySubDivisionID")
|
|
26
|
+
w.render(obj.subdivision&.name, as: "CountrySubDivisionName")
|
|
27
|
+
w.render(obj.building_number, as: "BuildingNumber")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.parse(r)
|
|
32
|
+
ruby_type.new.tap do |obj|
|
|
33
|
+
obj.subdivision = {}
|
|
34
|
+
r.parse("ram:PostcodeCode", :String) { obj.postal_code = it }
|
|
35
|
+
r.parse("ram:StreetName", :String) { obj.street_name = it }
|
|
36
|
+
r.parse("ram:CityName", :String) { obj.city = it }
|
|
37
|
+
r.parse("ram:CountrySubDivisionName", :String) { obj.subdivision.name = it }
|
|
38
|
+
r.parse("ram:CountrySubDivisionID", :String) { obj.subdivision.code = it }
|
|
39
|
+
r.parse("ram:BuildingNumber", :String) { obj.building_number = it }
|
|
40
|
+
|
|
41
|
+
obj.country = Model::Country.new.tap do |c|
|
|
42
|
+
r.parse("ram:CountryID", :String) { c.iso_code = it }
|
|
43
|
+
r.parse("ram:CountryName", :String) { c.name = it }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
LINE_WORDS.each do |word|
|
|
47
|
+
r.with_node("ram:Line#{word}") { obj.lines.push(r.text) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Format
|
|
3
|
+
module CII
|
|
4
|
+
module Coder
|
|
5
|
+
class BigDecimal
|
|
6
|
+
def self.ruby_type
|
|
7
|
+
::BigDecimal
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.dump(w, obj, as:)
|
|
11
|
+
w.add(as, obj.to_s("F"))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.parse(r)
|
|
15
|
+
ruby_type.interpret_loosely(r.text)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Tradedoc
|
|
2
|
+
module Format
|
|
3
|
+
module CII
|
|
4
|
+
module Coder
|
|
5
|
+
class Contact
|
|
6
|
+
def self.ruby_type
|
|
7
|
+
Model::Contact
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.dump(w, obj, as: "ram:DefinedTradeContact")
|
|
11
|
+
w.add(as) do
|
|
12
|
+
w.render(obj.name, as: "ram:PersonName")
|
|
13
|
+
|
|
14
|
+
if (tel = obj.phone)
|
|
15
|
+
w.add("ram:TelephoneUniversalCommunication") do
|
|
16
|
+
w.render(tel, as: "ram:CompleteNumber")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if (email = obj.email)
|
|
21
|
+
w.add("ram:EmailURIUniversalCommunication") do
|
|
22
|
+
w.render(email, as: "ram:URIID")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.parse(r)
|
|
29
|
+
ruby_type.new.tap do |c|
|
|
30
|
+
r.parse("ram:PersonName", :String) { c.name = it }
|
|
31
|
+
r.with_node("ram:TelephoneUniversalCommunication") do
|
|
32
|
+
r.parse("ram:CompleteNumber", :String) { c.phone = it }
|
|
33
|
+
end
|
|
34
|
+
r.with_node("ram:EmailURIUniversalCommunication") do
|
|
35
|
+
r.parse("ram:URIID", :String) { c.email = it }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|