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.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +13 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +53 -0
  6. data/Rakefile +10 -0
  7. data/lib/tradedoc/code/agency.rb +8 -0
  8. data/lib/tradedoc/code/document_type.rb +95 -0
  9. data/lib/tradedoc/code/national_clearing_system.rb +97 -0
  10. data/lib/tradedoc/code/payment_means_type.rb +107 -0
  11. data/lib/tradedoc/code/tax_category.rb +97 -0
  12. data/lib/tradedoc/code.rb +12 -0
  13. data/lib/tradedoc/format/apeh.rb +36 -0
  14. data/lib/tradedoc/format/cii/coder/address.rb +55 -0
  15. data/lib/tradedoc/format/cii/coder/big_decimal.rb +21 -0
  16. data/lib/tradedoc/format/cii/coder/contact.rb +43 -0
  17. data/lib/tradedoc/format/cii/coder/date.rb +87 -0
  18. data/lib/tradedoc/format/cii/coder/document_reference.rb +33 -0
  19. data/lib/tradedoc/format/cii/coder/exchange_rate.rb +33 -0
  20. data/lib/tradedoc/format/cii/coder/financial_account.rb +45 -0
  21. data/lib/tradedoc/format/cii/coder/financial_institution.rb +39 -0
  22. data/lib/tradedoc/format/cii/coder/invoice.rb +127 -0
  23. data/lib/tradedoc/format/cii/coder/invoice_line.rb +88 -0
  24. data/lib/tradedoc/format/cii/coder/monetary_total.rb +36 -0
  25. data/lib/tradedoc/format/cii/coder/money.rb +27 -0
  26. data/lib/tradedoc/format/cii/coder/payment_means.rb +49 -0
  27. data/lib/tradedoc/format/cii/coder/period.rb +27 -0
  28. data/lib/tradedoc/format/cii/coder/remittance_advice.rb +130 -0
  29. data/lib/tradedoc/format/cii/coder/string.rb +21 -0
  30. data/lib/tradedoc/format/cii/coder/tax_subtotal.rb +35 -0
  31. data/lib/tradedoc/format/cii/coder/trade_party.rb +29 -0
  32. data/lib/tradedoc/format/cii/coder.rb +27 -0
  33. data/lib/tradedoc/format/cii.rb +12 -0
  34. data/lib/tradedoc/format/facturae.rb +51 -0
  35. data/lib/tradedoc/format/fatturapa/coder/invoice.rb +31 -0
  36. data/lib/tradedoc/format/fatturapa/coder.rb +8 -0
  37. data/lib/tradedoc/format/fatturapa.rb +13 -0
  38. data/lib/tradedoc/format/isdoc.rb +40 -0
  39. data/lib/tradedoc/format/ksef/coder/invoice.rb +32 -0
  40. data/lib/tradedoc/format/ksef/coder.rb +10 -0
  41. data/lib/tradedoc/format/ksef.rb +17 -0
  42. data/lib/tradedoc/format/mydata.rb +42 -0
  43. data/lib/tradedoc/format/nav.rb +38 -0
  44. data/lib/tradedoc/format/ubl/coder/address.rb +48 -0
  45. data/lib/tradedoc/format/ubl/coder/big_decimal.rb +21 -0
  46. data/lib/tradedoc/format/ubl/coder/contact.rb +29 -0
  47. data/lib/tradedoc/format/ubl/coder/country.rb +27 -0
  48. data/lib/tradedoc/format/ubl/coder/date.rb +21 -0
  49. data/lib/tradedoc/format/ubl/coder/document_reference.rb +55 -0
  50. data/lib/tradedoc/format/ubl/coder/exchange_rate.rb +33 -0
  51. data/lib/tradedoc/format/ubl/coder/financial_account.rb +32 -0
  52. data/lib/tradedoc/format/ubl/coder/financial_institution.rb +42 -0
  53. data/lib/tradedoc/format/ubl/coder/invoice.rb +92 -0
  54. data/lib/tradedoc/format/ubl/coder/invoice_line.rb +53 -0
  55. data/lib/tradedoc/format/ubl/coder/monetary_total.rb +39 -0
  56. data/lib/tradedoc/format/ubl/coder/money.rb +22 -0
  57. data/lib/tradedoc/format/ubl/coder/payment_means.rb +39 -0
  58. data/lib/tradedoc/format/ubl/coder/period.rb +27 -0
  59. data/lib/tradedoc/format/ubl/coder/product.rb +37 -0
  60. data/lib/tradedoc/format/ubl/coder/remittance_advice.rb +91 -0
  61. data/lib/tradedoc/format/ubl/coder/string.rb +21 -0
  62. data/lib/tradedoc/format/ubl/coder/tax_breakdown.rb +27 -0
  63. data/lib/tradedoc/format/ubl/coder/tax_category.rb +37 -0
  64. data/lib/tradedoc/format/ubl/coder/tax_subtotal.rb +29 -0
  65. data/lib/tradedoc/format/ubl/coder/trade_party.rb +37 -0
  66. data/lib/tradedoc/format/ubl/coder.rb +29 -0
  67. data/lib/tradedoc/format/ubl.rb +13 -0
  68. data/lib/tradedoc/format/zugferdv1/coder/invoice.rb +33 -0
  69. data/lib/tradedoc/format/zugferdv1/coder.rb +8 -0
  70. data/lib/tradedoc/format/zugferdv1.rb +21 -0
  71. data/lib/tradedoc/format.rb +110 -0
  72. data/lib/tradedoc/model/address.rb +21 -0
  73. data/lib/tradedoc/model/base.rb +153 -0
  74. data/lib/tradedoc/model/contact.rb +9 -0
  75. data/lib/tradedoc/model/country.rb +8 -0
  76. data/lib/tradedoc/model/country_subdivision.rb +8 -0
  77. data/lib/tradedoc/model/document.rb +9 -0
  78. data/lib/tradedoc/model/document_reference.rb +11 -0
  79. data/lib/tradedoc/model/exchange_rate.rb +17 -0
  80. data/lib/tradedoc/model/financial_account.rb +19 -0
  81. data/lib/tradedoc/model/financial_institution.rb +11 -0
  82. data/lib/tradedoc/model/invoice.rb +57 -0
  83. data/lib/tradedoc/model/invoice_line.rb +46 -0
  84. data/lib/tradedoc/model/monetary_total.rb +34 -0
  85. data/lib/tradedoc/model/payment_means.rb +27 -0
  86. data/lib/tradedoc/model/period.rb +8 -0
  87. data/lib/tradedoc/model/price.rb +21 -0
  88. data/lib/tradedoc/model/product.rb +18 -0
  89. data/lib/tradedoc/model/remittance_advice.rb +20 -0
  90. data/lib/tradedoc/model/remittance_advice_line.rb +24 -0
  91. data/lib/tradedoc/model/tax_breakdown.rb +11 -0
  92. data/lib/tradedoc/model/tax_category.rb +26 -0
  93. data/lib/tradedoc/model/tax_subtotal.rb +13 -0
  94. data/lib/tradedoc/model/trade_party.rb +9 -0
  95. data/lib/tradedoc/model.rb +33 -0
  96. data/lib/tradedoc/version.rb +5 -0
  97. data/lib/tradedoc/xml/reader.rb +163 -0
  98. data/lib/tradedoc/xml/writer.rb +56 -0
  99. data/lib/tradedoc/xml.rb +8 -0
  100. data/lib/tradedoc.rb +98 -0
  101. data/sig/tradedoc.rbs +4 -0
  102. metadata +187 -0
@@ -0,0 +1,29 @@
1
+ module Tradedoc
2
+ module Format
3
+ module UBL
4
+ module Coder
5
+ class TaxSubtotal
6
+ def self.ruby_type
7
+ Model::TaxSubtotal
8
+ end
9
+
10
+ def self.dump(w, obj, as: "cac:TaxSubtotal")
11
+ w.add(as) do
12
+ w.render(obj.taxable_amount, as: "TaxableAmount")
13
+ w.render(obj.tax_amount, as: "TaxAmount")
14
+ w.render(obj.tax_category, as: "cac:TaxCategory")
15
+ end
16
+ end
17
+
18
+ def self.parse(r)
19
+ ruby_type.new.tap do |subtotal|
20
+ r.parse("cbc:TaxableAmount", :Money) { subtotal.taxable_amount = it }
21
+ r.parse("cbc:TaxAmount", :Money) { subtotal.tax_amount = it }
22
+ r.parse("cac:TaxCategory", :TaxCategory) { subtotal.tax_category = it }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ module Tradedoc
2
+ module Format
3
+ module UBL
4
+ module Coder
5
+ class TradeParty
6
+ def self.ruby_type
7
+ Model::TradeParty
8
+ end
9
+
10
+ def self.dump(w, obj, as: "Party")
11
+ w.add(as) do
12
+ w.add("cac:PartyName") do
13
+ w.add("cbc:Name", obj.name)
14
+ end
15
+ w.render(obj.address, as: "PostalAddress")
16
+ w.render(obj.contact, as: "cac:Contact")
17
+ end
18
+ end
19
+
20
+ def self.parse(r)
21
+ obj = ruby_type.new
22
+
23
+ r.with_node("cac:Party") do
24
+ r.with_node("cac:PartyName") do
25
+ r.parse("cbc:Name", :String) { obj.name = it }
26
+ end
27
+ r.parse("cac:PostalAddress", :Address) { obj.address = it }
28
+ r.parse("cac:Contact", :Contact) { obj.contact = it }
29
+ end
30
+
31
+ obj
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ module Tradedoc
2
+ module Format
3
+ module Coder
4
+ end
5
+ end
6
+ end
7
+
8
+ require_relative "coder/address"
9
+ require_relative "coder/big_decimal"
10
+ require_relative "coder/contact"
11
+ require_relative "coder/country"
12
+ require_relative "coder/date"
13
+ require_relative "coder/document_reference"
14
+ require_relative "coder/exchange_rate"
15
+ require_relative "coder/financial_account"
16
+ require_relative "coder/financial_institution"
17
+ require_relative "coder/invoice"
18
+ require_relative "coder/invoice_line"
19
+ require_relative "coder/monetary_total"
20
+ require_relative "coder/money"
21
+ require_relative "coder/payment_means"
22
+ require_relative "coder/period"
23
+ require_relative "coder/product"
24
+ require_relative "coder/remittance_advice"
25
+ require_relative "coder/string"
26
+ require_relative "coder/tax_breakdown"
27
+ require_relative "coder/tax_category"
28
+ require_relative "coder/tax_subtotal"
29
+ require_relative "coder/trade_party"
@@ -0,0 +1,13 @@
1
+ module Tradedoc
2
+ module Format
3
+ # Universal Business Language
4
+ # A open set of standards designed by OASIS
5
+ module UBL
6
+ extend Accessors
7
+ extend Finders
8
+ extend XMLSerialization
9
+ end
10
+ end
11
+ end
12
+
13
+ require_relative "ubl/coder"
@@ -0,0 +1,33 @@
1
+ module Tradedoc
2
+ module Format
3
+ module ZugferdV1
4
+ module Coder
5
+ class Invoice
6
+ NS = {
7
+ "xmlns:ram" => "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12",
8
+ "xmlns:rsm" => "urn:ferd:CrossIndustryDocument:invoice:1p0",
9
+ "xmlns:udt" => "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15"
10
+ }
11
+ private_constant :NS
12
+
13
+ def self.namespaces
14
+ NS
15
+ end
16
+
17
+ # @param xmldoc [Nokogiri::XML::Document]
18
+ def self.can_parse?(xmldoc)
19
+ xmldoc.root.namespace&.href == namespaces.fetch("xmlns:rsm")
20
+ end
21
+
22
+ def self.dump(w, obj)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def self.parse(r)
27
+ raise NotImplementedError
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ module Tradedoc
2
+ module ZugferdV1
3
+ module Coder
4
+ end
5
+ end
6
+ end
7
+
8
+ require_relative "coder/invoice"
@@ -0,0 +1,21 @@
1
+ module Tradedoc
2
+ module Format
3
+ # ZUGFeRDv1 is a German standard that is the predecessor of CII and its
4
+ # data structures are quite similar and sometimes identical.
5
+ #
6
+ # Not officially obsolete (as of June 2026) but its website says:
7
+ # ...has never been officially deprecated but is likely to be discontinued
8
+ # and it's not compliant to EN16931
9
+ module ZugferdV1
10
+ extend Accessors
11
+ extend Finders
12
+ extend XMLSerialization
13
+
14
+ def self.label
15
+ "ZUGFeRDv1"
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ require_relative "zugferdv1/coder"
@@ -0,0 +1,110 @@
1
+ module Tradedoc
2
+ module Format
3
+ module Accessors
4
+ # User-friendly label for this format.
5
+ # @return [String]
6
+ def label
7
+ name.split("::").last
8
+ end
9
+
10
+ # List of classes that can dump and parse Ruby objects to/from XML
11
+ # @return [Array<Class>]
12
+ def coders
13
+ ns = self::Coder
14
+
15
+ ns.constants.filter_map do |const_name|
16
+ val = ns.const_get(const_name)
17
+ if val.respond_to?(:dump) && val.respond_to?(:parse)
18
+ val
19
+ end
20
+ end
21
+ end
22
+
23
+ # For faster detection of what files may be parsable by this format
24
+ def file_extensions
25
+ Set[".xml"]
26
+ end
27
+
28
+ # List of coders that are document types.
29
+ # These are the only ones that can be read/written to/from XML properly.
30
+ #
31
+ # @return [Array<Class>]
32
+ def document_coders
33
+ coders.select { it.respond_to?(:namespaces) && it.respond_to?(:can_parse?) }
34
+ end
35
+ end
36
+
37
+ module Finders
38
+ # Given a Ruby type, get the coder class for it.
39
+ #
40
+ # @param coder_ref [Symbol | Class]
41
+ # symbol: "bare" name of a type like :Date, :BigDecimal, :TradeParty
42
+ # class: one of:
43
+ # - Ruby type like `::Date` or `Model::TradeParty` to encode
44
+ # - A valid coder class (responds to .dump and .parse)
45
+ # @return [Class]
46
+ def coder_for(coder_ref)
47
+ matches = case coder_ref
48
+ in Class => coder if coders.include?(coder)
49
+ [coder]
50
+ in Class => ruby_type
51
+ coders.select { it.ruby_type == ruby_type }
52
+ in Symbol => type_sym
53
+ coders.select { it.ruby_type.name.split("::").last == type_sym.to_s }
54
+ end
55
+
56
+ if matches.none?
57
+ raise "couldn't find a coder for '#{coder_ref}'"
58
+ end
59
+
60
+ # This shouldn't happen unless there's a misconfigure in this library itself.
61
+ if matches.count > 1
62
+ raise "found multiple coders for #{coder_ref}: #{matches}"
63
+ end
64
+
65
+ matches[0]
66
+ end
67
+
68
+ def detect_document_coder(xmldoc)
69
+ document_coders.detect { it.can_parse?(xmldoc) }
70
+ end
71
+ end
72
+
73
+ module XMLSerialization
74
+ # @param obj [Class] A document-level model to serialize
75
+ # @return [Nokogiri::XML::Document]
76
+ def dump(obj)
77
+ encoding = Encoding::UTF_8.to_s
78
+ builder = Nokogiri::XML::Builder.new(encoding:) do |xml|
79
+ writer = XML::Writer.new(xml, format: self)
80
+ writer.render(obj)
81
+ end
82
+ builder.doc
83
+ end
84
+
85
+ # Given an XML document (as an object or string), detect the type
86
+ # and parse it into a model
87
+ #
88
+ # @param source [Nokogiri::XML::Document | String]
89
+ # @param coder [Class] If the coder is already known
90
+ # @return [Object] Model instance
91
+ def parse(source, coder: nil)
92
+ xml = Tradedoc.xml_from(source)
93
+ coder ||= detect_document_coder(xml)
94
+ reader = XML::Reader.new(xml, coder.namespaces, format: self)
95
+ coder.parse(reader)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ require_relative "format/apeh"
102
+ require_relative "format/cii"
103
+ require_relative "format/facturae"
104
+ require_relative "format/fatturapa"
105
+ require_relative "format/isdoc"
106
+ require_relative "format/ksef"
107
+ require_relative "format/mydata"
108
+ require_relative "format/nav"
109
+ require_relative "format/ubl"
110
+ require_relative "format/zugferdv1"
@@ -0,0 +1,21 @@
1
+ module Tradedoc
2
+ module Model
3
+ class Address < Base
4
+ has :city, String
5
+ has :postal_code, String
6
+ has :country, Country
7
+ has :subdivision, CountrySubdivision
8
+
9
+ # According to documentation and examples from UBL and CII, address lines
10
+ # are used when you can't or don't want to break-down the parts of an address
11
+ # to specific components like building number, suite/apartment number, PO Box,
12
+ # attention of, care of, department code, etc. Free-form lines are easier to work with.
13
+ has_many :lines, String
14
+
15
+ # Optionally, the specific components of the address lines.
16
+ has :street_name, String
17
+ has :building_number, String
18
+ has :additional_street_name, String
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,153 @@
1
+ module Tradedoc
2
+ module Model
3
+ # Provides a simple interface for models where you can define the attributes it has
4
+ # and the correct types for each, and providing some simple coercion.
5
+ #
6
+ # @example
7
+ # class DocumentLine < Base
8
+ # has :id, String
9
+ # has :amount, Money
10
+ # end
11
+ #
12
+ # class Document < Base
13
+ # has :id, String
14
+ # has :issue_date, Date
15
+ # has_many :lines, Line
16
+ # end
17
+ class Base
18
+ Element = Data.define(:name, :type, :many)
19
+
20
+ module Serialization
21
+ def dump(to_format)
22
+ fmt = Tradedoc.format_from(to_format)
23
+ fmt.dump(self)
24
+ end
25
+ end
26
+
27
+ def initialize(**attrs)
28
+ attrs.each { |key, value| public_send(:"#{key}=", value) }
29
+ end
30
+
31
+ class << self
32
+ def is_document
33
+ include(Serialization)
34
+ end
35
+
36
+ def has(name, type)
37
+ def_element(name, type, false)
38
+
39
+ ivar_name = :"@#{name}"
40
+
41
+ define_method(name) { instance_variable_get(ivar_name) }
42
+
43
+ define_method("#{name}=") do |value|
44
+ value_to_write = self.class.coerce_for(name, value)
45
+ instance_variable_set(ivar_name, value_to_write)
46
+ end
47
+ end
48
+
49
+ def has_many(name, type, default: -> { [] })
50
+ def_element(name, type, true)
51
+
52
+ ivar_name = :"@#{name}"
53
+
54
+ define_method(name) do
55
+ unless instance_variable_defined?(ivar_name)
56
+ instance_variable_set(ivar_name, default.call)
57
+ end
58
+
59
+ instance_variable_get(ivar_name)
60
+ end
61
+
62
+ define_method("#{name}=") do |values|
63
+ value_to_write = values.map { |value| self.class.coerce_for(name, value) }
64
+ instance_variable_set(ivar_name, value_to_write)
65
+ end
66
+ end
67
+
68
+ def inherited(subclass)
69
+ subclass.inherit_elements(elements)
70
+ super
71
+ end
72
+
73
+ def elements
74
+ @elements ||= {}
75
+ end
76
+
77
+ def inherit_elements(other)
78
+ @elements ||= {}
79
+ @elements = @elements.merge(other)
80
+ end
81
+
82
+ def coerce_for(element_name, value)
83
+ proper_type = elements.fetch(element_name).type
84
+
85
+ case value
86
+ in ^proper_type | nil => as_is
87
+ as_is
88
+ in other
89
+ if proper_type < Base
90
+ proper_type.new(**other)
91
+ elsif proper_type.respond_to?(:parse)
92
+ proper_type.parse(other)
93
+ else
94
+ proper_type.new(other)
95
+ end
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def def_element(name, type, many)
102
+ elements[name] = Element.new(name:, type:, many:)
103
+ end
104
+ end
105
+
106
+ def merge(other)
107
+ to_h.deep_merge(other.to_h)
108
+ end
109
+
110
+ def to_h
111
+ self.class.elements.keys.each_with_object({}) do |k, h|
112
+ h[k] = case send(k)
113
+ in nil
114
+ nil
115
+ in Array => arr
116
+ arr.map do |item|
117
+ item.respond_to?(:to_h) ? item.to_h : item
118
+ end
119
+ in obj if obj.respond_to?(:to_h)
120
+ obj.to_h
121
+ in other
122
+ other
123
+ end
124
+ end
125
+ end
126
+
127
+ def ==(other)
128
+ case other
129
+ in nil
130
+ false
131
+ in Hash
132
+ to_h == other
133
+ in obj if obj.respond_to?(:to_h)
134
+ to_h == other.to_h
135
+ in unmatched
136
+ raise "can't compare #{self.class.name} to #{unmatched}"
137
+ end
138
+ end
139
+
140
+ def hash
141
+ to_h.hash
142
+ end
143
+
144
+ def eql?(other)
145
+ to_h == other
146
+ end
147
+
148
+ def deconstruct_keys(keys)
149
+ to_h.slice(*keys)
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,9 @@
1
+ module Tradedoc
2
+ module Model
3
+ class Contact < Base
4
+ has :name, String
5
+ has :phone, String
6
+ has :email, String
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Tradedoc
2
+ module Model
3
+ class Country < Base
4
+ has :iso_code, String
5
+ has :name, String
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Tradedoc
2
+ module Model
3
+ class CountrySubdivision < Base
4
+ has :code, String
5
+ has :name, String
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Tradedoc
2
+ module Model
3
+ class Document < Base
4
+ has :id, String
5
+ has :issue_date, Date
6
+ has :note, String
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Tradedoc
2
+ module Model
3
+ class DocumentReference < Base
4
+ has :type, Code::DocumentType
5
+ has :id, String
6
+ has :uuid, String
7
+ has :issue_date, Date
8
+ has :note, String
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Tradedoc
2
+ module Model
3
+ class ExchangeRate < Base
4
+ has :source_currency_code, String
5
+ has :target_currency_code, String
6
+
7
+ # e.g. 1 USD = 0.92 EU, calculation rate is 0.92
8
+ has :rate, BigDecimal
9
+
10
+ # FX date
11
+ has :date, Date
12
+
13
+ # Where did the exchange rate come from?
14
+ has :market_id, String
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Tradedoc
2
+ module Model
3
+ class FinancialAccount < Base
4
+ # Identifies the kind of account numbering scheme
5
+ # Documentation for this is hard to come by. "IBAN" is for sure one of them,
6
+ # and "SWIFT" and "SWIFTBIC" have both been observed in samples and docs
7
+ has :scheme_name, String
8
+
9
+ # [BT-84]
10
+ has :account_number, String
11
+
12
+ # [BT-85]
13
+ has :name, String
14
+
15
+ # Only necessary for non-IBAN
16
+ has :financial_institution, FinancialInstitution
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module Tradedoc
2
+ module Model
3
+ class FinancialInstitution < Base
4
+ # For SWIFT, this is the BIC
5
+ # For national clearing systems, this is the member ID e.g. US Routing, UK Sort
6
+ has :id, String
7
+
8
+ has :national_clearing_system, Code::NationalClearingSystem
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ module Tradedoc
2
+ module Model
3
+ class Invoice < Base
4
+ is_document
5
+
6
+ # [BT-1]
7
+ has :invoice_number, String
8
+
9
+ # [BT-2]
10
+ has :issue_date, Date
11
+
12
+ # [BT-3]
13
+ has :invoice_type_code, String
14
+
15
+ # [BT-5]
16
+ has :currency_code, String
17
+
18
+ # [BT-9]
19
+ has :due_date, Date
20
+
21
+ # [BT-10] Identifier created by the buyer for internal routing e.g. cost centre
22
+ # If you're looking for a PO number, see BT-13
23
+ has :buyer_reference, String
24
+
25
+ # [BT-13] PO number created by the buyer for three-way matching.
26
+ has :purchase_order_number, String
27
+
28
+ # [BT-22]
29
+ has :note, String
30
+ has :payment_means, PaymentMeans
31
+
32
+ # [BT-24]
33
+ # Identifier for the set of rules, content, conformity, etc.
34
+ # The standard seen for EU invoice is `urn:cen.eu:en16931:2017`
35
+ has :specification_id, String
36
+
37
+ # [BG-5]
38
+ has :invoice_period, Period
39
+
40
+ # [BG-7]
41
+ has :supplier, TradeParty
42
+
43
+ # [BG-10]
44
+ has :buyer, TradeParty
45
+
46
+ # [BG-17]
47
+ has_many :payment_means, PaymentMeans
48
+
49
+ # [BG-22]
50
+ has :monetary_total, MonetaryTotal
51
+
52
+ # [BG-25]
53
+ has_many :lines, InvoiceLine
54
+ alias_method :line_items, :lines
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,46 @@
1
+ module Tradedoc
2
+ module Model
3
+ class InvoiceLine < Base
4
+ # [BT-126] Unique ID for the invoice line within the invoice itself
5
+ has :id, String
6
+
7
+ # [BT-127]
8
+ has :note, String
9
+
10
+ # [BT-129]
11
+ has :invoiced_quantity, BigDecimal
12
+
13
+ # [BT-131] Total after discounts and charges, excluding vat
14
+ has :total_excluding_tax, Money
15
+
16
+ has :product, Product
17
+
18
+ # [BG-29] Price breakdown / details
19
+ has :price, Price
20
+
21
+ # UBL and CII deal with line-level tax differently.
22
+ # UBL has a `TaxTotal` at the line level with the amount under it,
23
+ # then under `Item` has the `ClassifiedTaxCategory`, so they're totally separate.
24
+ #
25
+ # CII by contrast uses one `ApplicableTradeTax` element for `TaxSubtotal` which
26
+ # combines the total tax and tax category into one.
27
+ # To make access easier, we'll store the amount and category separately and then
28
+ # have accessors for reading and writing as a `TaxSubtotal`.
29
+ has :total_tax, Money
30
+
31
+ def tax_subtotal
32
+ TaxSubtotal.new(tax_amount: total_tax, tax_category: product.tax_category).freeze
33
+ end
34
+
35
+ def tax_subtotal=(ts)
36
+ if (total_tax = ts.tax_amount)
37
+ self.total_tax = total_tax
38
+ end
39
+
40
+ if (tax_category = ts.tax_category)
41
+ product.tax_category = tax_category
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end