zugpferd 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.
@@ -0,0 +1,218 @@
1
+ require "nokogiri"
2
+ require "date"
3
+ require "bigdecimal"
4
+ require_relative "mapping"
5
+
6
+ module Zugpferd
7
+ module UBL
8
+ # Reads UBL 2.1 Invoice XML into {Model::Invoice}.
9
+ #
10
+ # @example
11
+ # invoice = Zugpferd::UBL::Reader.new.read(File.read("invoice.xml"))
12
+ class Reader
13
+ include Mapping
14
+
15
+ # Parses a UBL 2.1 Invoice XML string.
16
+ #
17
+ # @param xml_string [String] valid UBL 2.1 Invoice XML
18
+ # @return [Model::Invoice]
19
+ # @raise [Nokogiri::XML::SyntaxError] if the XML is malformed
20
+ def read(xml_string)
21
+ doc = Nokogiri::XML(xml_string) { |config| config.strict }
22
+ root = doc.root
23
+ build_invoice(root)
24
+ end
25
+
26
+ private
27
+
28
+ def build_invoice(root)
29
+ Model::Invoice.new(
30
+ number: text(root, INVOICE[:number]),
31
+ issue_date: parse_date(text(root, INVOICE[:issue_date])),
32
+ due_date: parse_date(text(root, INVOICE[:due_date])),
33
+ type_code: text(root, INVOICE[:type_code]),
34
+ currency_code: text(root, INVOICE[:currency_code]),
35
+ buyer_reference: text(root, INVOICE[:buyer_reference]),
36
+ customization_id: text(root, INVOICE[:customization_id]),
37
+ profile_id: text(root, INVOICE[:profile_id]),
38
+ note: text(root, INVOICE[:note]),
39
+ seller: build_party(root.at_xpath(SELLER, NS)),
40
+ buyer: build_party(root.at_xpath(BUYER, NS)),
41
+ line_items: root.xpath(INVOICE_LINE, NS).map { |n| build_line_item(n) },
42
+ allowance_charges: root.xpath(ALLOWANCE_CHARGE, NS).map { |n| build_allowance_charge(n) },
43
+ tax_breakdown: build_tax_breakdown(root.at_xpath(TAX_TOTAL, NS)),
44
+ monetary_totals: build_monetary_totals(root.at_xpath(MONETARY_TOTAL, NS)),
45
+ payment_instructions: build_payment_instructions(root),
46
+ )
47
+ end
48
+
49
+ def build_party(node)
50
+ return nil unless node
51
+
52
+ party = Model::TradeParty.new(
53
+ name: text(node, PARTY[:name]),
54
+ trading_name: text(node, PARTY[:trading_name]),
55
+ identifier: text(node, PARTY[:identifier]),
56
+ legal_registration_id: text(node, PARTY[:legal_registration_id]),
57
+ legal_form: text(node, PARTY[:legal_form]),
58
+ vat_identifier: text(node, PARTY[:vat_identifier]),
59
+ electronic_address: text(node, PARTY[:electronic_address]),
60
+ )
61
+
62
+ endpoint = node.at_xpath(PARTY[:electronic_address], NS)
63
+ party.electronic_address_scheme = endpoint["schemeID"] if endpoint
64
+
65
+ addr_node = node.at_xpath(POSTAL_ADDRESS, NS)
66
+ party.postal_address = build_postal_address(addr_node) if addr_node
67
+
68
+ contact_node = node.at_xpath(CONTACT, NS)
69
+ party.contact = build_contact(contact_node) if contact_node
70
+
71
+ party
72
+ end
73
+
74
+ def build_postal_address(node)
75
+ Model::PostalAddress.new(
76
+ country_code: text(node, ADDRESS[:country_code]),
77
+ street_name: text(node, ADDRESS[:street_name]),
78
+ city_name: text(node, ADDRESS[:city_name]),
79
+ postal_zone: text(node, ADDRESS[:postal_zone]),
80
+ )
81
+ end
82
+
83
+ def build_contact(node)
84
+ Model::Contact.new(
85
+ name: text(node, CONTACT_FIELDS[:name]),
86
+ telephone: text(node, CONTACT_FIELDS[:telephone]),
87
+ email: text(node, CONTACT_FIELDS[:email]),
88
+ )
89
+ end
90
+
91
+ def build_payment_instructions(root)
92
+ means_node = root.at_xpath(PAYMENT_MEANS, NS)
93
+ return nil unless means_node
94
+
95
+ # BT-90: In UBL, creditor reference is a PartyIdentification with schemeID="SEPA" on the seller
96
+ creditor_ref = root.at_xpath(
97
+ "#{SELLER}/cac:PartyIdentification/cbc:ID[@schemeID='SEPA']", NS
98
+ )&.text
99
+
100
+ Model::PaymentInstructions.new(
101
+ payment_means_code: text(means_node, PAYMENT[:payment_means_code]),
102
+ payment_id: text(means_node, PAYMENT[:payment_id]),
103
+ account_id: text(means_node, PAYMENT[:account_id]),
104
+ card_account_id: text(means_node, PAYMENT[:card_account_id]),
105
+ card_network_id: text(means_node, PAYMENT[:card_network_id]),
106
+ card_holder_name: text(means_node, PAYMENT[:card_holder_name]),
107
+ mandate_reference: text(means_node, PAYMENT[:mandate_reference]),
108
+ debited_account_id: text(means_node, PAYMENT[:debited_account_id]),
109
+ creditor_reference_id: creditor_ref,
110
+ note: text(root, PAYMENT_TERMS_NOTE),
111
+ )
112
+ end
113
+
114
+ def build_tax_breakdown(node)
115
+ return nil unless node
116
+
117
+ currency = node.at_xpath("cbc:TaxAmount/@currencyID", NS)&.text
118
+
119
+ breakdown = Model::TaxBreakdown.new(
120
+ tax_amount: text(node, "cbc:TaxAmount"),
121
+ currency_code: currency,
122
+ )
123
+
124
+ breakdown.subtotals = node.xpath(TAX_SUBTOTAL, NS).map do |sub|
125
+ sub_currency = sub.at_xpath("cbc:TaxableAmount/@currencyID", NS)&.text
126
+ Model::TaxSubtotal.new(
127
+ taxable_amount: text(sub, TAX[:taxable_amount]),
128
+ tax_amount: text(sub, TAX[:tax_amount]),
129
+ category_code: text(sub, TAX[:category_code]),
130
+ percent: parse_decimal(text(sub, TAX[:percent])),
131
+ currency_code: sub_currency,
132
+ exemption_reason: text(sub, TAX[:exemption_reason]),
133
+ exemption_reason_code: text(sub, TAX[:exemption_reason_code]),
134
+ )
135
+ end
136
+
137
+ breakdown
138
+ end
139
+
140
+ def build_monetary_totals(node)
141
+ return nil unless node
142
+
143
+ Model::MonetaryTotals.new(
144
+ line_extension_amount: text(node, TOTALS[:line_extension_amount]),
145
+ tax_exclusive_amount: text(node, TOTALS[:tax_exclusive_amount]),
146
+ tax_inclusive_amount: text(node, TOTALS[:tax_inclusive_amount]),
147
+ prepaid_amount: parse_decimal(text(node, TOTALS[:prepaid_amount])),
148
+ payable_rounding_amount: parse_decimal(text(node, TOTALS[:payable_rounding_amount])),
149
+ allowance_total_amount: parse_decimal(text(node, TOTALS[:allowance_total_amount])),
150
+ charge_total_amount: parse_decimal(text(node, TOTALS[:charge_total_amount])),
151
+ payable_amount: text(node, TOTALS[:payable_amount]),
152
+ )
153
+ end
154
+
155
+ def build_line_item(node)
156
+ item_node = node.at_xpath(ITEM, NS)
157
+ price_node = node.at_xpath(PRICE, NS)
158
+
159
+ Model::LineItem.new(
160
+ id: text(node, LINE[:id]),
161
+ invoiced_quantity: text(node, LINE[:invoiced_quantity]),
162
+ unit_code: node.at_xpath(LINE[:unit_code], NS)&.text,
163
+ line_extension_amount: text(node, LINE[:line_extension_amount]),
164
+ note: text(node, LINE[:note]),
165
+ item: build_item(item_node),
166
+ price: build_price(price_node),
167
+ )
168
+ end
169
+
170
+ def build_item(node)
171
+ return nil unless node
172
+
173
+ Model::Item.new(
174
+ name: text(node, ITEM_FIELDS[:name]),
175
+ description: text(node, ITEM_FIELDS[:description]),
176
+ sellers_identifier: text(node, ITEM_FIELDS[:sellers_identifier]),
177
+ tax_category: text(node, ITEM_FIELDS[:tax_category]),
178
+ tax_percent: parse_decimal(text(node, ITEM_FIELDS[:tax_percent])),
179
+ )
180
+ end
181
+
182
+ def build_price(node)
183
+ return nil unless node
184
+
185
+ Model::Price.new(
186
+ amount: text(node, PRICE_FIELDS[:amount]),
187
+ )
188
+ end
189
+
190
+ def build_allowance_charge(node)
191
+ currency = node.at_xpath("cbc:Amount/@currencyID", NS)&.text
192
+ Model::AllowanceCharge.new(
193
+ charge_indicator: text(node, ALLOWANCE_CHARGE_FIELDS[:charge_indicator]) == "true",
194
+ reason: text(node, ALLOWANCE_CHARGE_FIELDS[:reason]),
195
+ reason_code: text(node, ALLOWANCE_CHARGE_FIELDS[:reason_code]),
196
+ amount: text(node, ALLOWANCE_CHARGE_FIELDS[:amount]),
197
+ base_amount: parse_decimal(text(node, ALLOWANCE_CHARGE_FIELDS[:base_amount])),
198
+ multiplier_factor: parse_decimal(text(node, ALLOWANCE_CHARGE_FIELDS[:multiplier_factor])),
199
+ tax_category_code: text(node, ALLOWANCE_CHARGE_FIELDS[:tax_category_code]),
200
+ tax_percent: parse_decimal(text(node, ALLOWANCE_CHARGE_FIELDS[:tax_percent])),
201
+ currency_code: currency,
202
+ )
203
+ end
204
+
205
+ def text(node, xpath)
206
+ node.at_xpath(xpath, NS)&.text
207
+ end
208
+
209
+ def parse_date(str)
210
+ Date.parse(str) if str
211
+ end
212
+
213
+ def parse_decimal(str)
214
+ BigDecimal(str) if str
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,286 @@
1
+ require "nokogiri"
2
+ require_relative "mapping"
3
+
4
+ module Zugpferd
5
+ module UBL
6
+ # Writes {Model::Invoice} to UBL 2.1 Invoice XML.
7
+ #
8
+ # @example
9
+ # xml = Zugpferd::UBL::Writer.new.write(invoice)
10
+ class Writer
11
+ include Mapping
12
+
13
+ # Serializes an invoice to UBL 2.1 XML.
14
+ #
15
+ # @param invoice [Model::Invoice] the invoice to serialize
16
+ # @return [String] UTF-8 encoded XML string
17
+ def write(invoice)
18
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
19
+ xml.Invoice(xmlns: NS["ubl"],
20
+ "xmlns:cac" => NS["cac"],
21
+ "xmlns:cbc" => NS["cbc"]) do
22
+ build_invoice(xml, invoice)
23
+ end
24
+ end
25
+ builder.to_xml
26
+ end
27
+
28
+ private
29
+
30
+ def build_invoice(xml, inv)
31
+ xml["cbc"].CustomizationID inv.customization_id if inv.customization_id
32
+ xml["cbc"].ProfileID inv.profile_id if inv.profile_id
33
+ xml["cbc"].ID inv.number
34
+ xml["cbc"].IssueDate inv.issue_date.to_s
35
+ xml["cbc"].DueDate inv.due_date.to_s if inv.due_date
36
+ xml["cbc"].InvoiceTypeCode inv.type_code
37
+ xml["cbc"].Note inv.note if inv.note
38
+ xml["cbc"].DocumentCurrencyCode inv.currency_code
39
+ xml["cbc"].BuyerReference inv.buyer_reference if inv.buyer_reference
40
+
41
+ build_supplier(xml, inv.seller, inv.payment_instructions) if inv.seller
42
+ build_customer(xml, inv.buyer) if inv.buyer
43
+ build_payment_means(xml, inv.payment_instructions) if inv.payment_instructions
44
+ build_payment_terms(xml, inv.payment_instructions) if inv.payment_instructions&.note
45
+ inv.allowance_charges.each { |ac| build_allowance_charge(xml, ac, inv.currency_code) }
46
+ build_tax_total(xml, inv.tax_breakdown) if inv.tax_breakdown
47
+ build_monetary_total(xml, inv.monetary_totals, inv.currency_code) if inv.monetary_totals
48
+ inv.line_items.each { |li| build_invoice_line(xml, li, inv.currency_code) }
49
+ end
50
+
51
+ def build_supplier(xml, party, payment_instructions = nil)
52
+ xml["cac"].AccountingSupplierParty do
53
+ build_party(xml, party,
54
+ creditor_reference_id: payment_instructions&.creditor_reference_id)
55
+ end
56
+ end
57
+
58
+ def build_customer(xml, party)
59
+ xml["cac"].AccountingCustomerParty do
60
+ build_party(xml, party)
61
+ end
62
+ end
63
+
64
+ def build_party(xml, party, creditor_reference_id: nil)
65
+ xml["cac"].Party do
66
+ if party.electronic_address
67
+ attrs = {}
68
+ attrs[:schemeID] = party.electronic_address_scheme if party.electronic_address_scheme
69
+ xml["cbc"].EndpointID(party.electronic_address, attrs)
70
+ end
71
+
72
+ if party.identifier
73
+ xml["cac"].PartyIdentification do
74
+ xml["cbc"].ID party.identifier
75
+ end
76
+ end
77
+
78
+ if creditor_reference_id
79
+ xml["cac"].PartyIdentification do
80
+ xml["cbc"].ID(creditor_reference_id, schemeID: "SEPA")
81
+ end
82
+ end
83
+
84
+ if party.trading_name
85
+ xml["cac"].PartyName do
86
+ xml["cbc"].Name party.trading_name
87
+ end
88
+ end
89
+
90
+ build_postal_address(xml, party.postal_address) if party.postal_address
91
+
92
+ if party.vat_identifier
93
+ xml["cac"].PartyTaxScheme do
94
+ xml["cbc"].CompanyID party.vat_identifier
95
+ xml["cac"].TaxScheme do
96
+ xml["cbc"].ID "VAT"
97
+ end
98
+ end
99
+ end
100
+
101
+ xml["cac"].PartyLegalEntity do
102
+ xml["cbc"].RegistrationName party.name
103
+ xml["cbc"].CompanyID party.legal_registration_id if party.legal_registration_id
104
+ xml["cbc"].CompanyLegalForm party.legal_form if party.legal_form
105
+ end
106
+
107
+ build_contact(xml, party.contact) if party.contact
108
+ end
109
+ end
110
+
111
+ def build_postal_address(xml, addr)
112
+ xml["cac"].PostalAddress do
113
+ xml["cbc"].StreetName addr.street_name if addr.street_name
114
+ xml["cbc"].CityName addr.city_name if addr.city_name
115
+ xml["cbc"].PostalZone addr.postal_zone if addr.postal_zone
116
+ xml["cac"].Country do
117
+ xml["cbc"].IdentificationCode addr.country_code
118
+ end
119
+ end
120
+ end
121
+
122
+ def build_contact(xml, contact)
123
+ xml["cac"].Contact do
124
+ xml["cbc"].Name contact.name if contact.name
125
+ xml["cbc"].Telephone contact.telephone if contact.telephone
126
+ xml["cbc"].ElectronicMail contact.email if contact.email
127
+ end
128
+ end
129
+
130
+ def build_payment_means(xml, payment)
131
+ xml["cac"].PaymentMeans do
132
+ xml["cbc"].PaymentMeansCode payment.payment_means_code
133
+ xml["cbc"].PaymentID payment.payment_id if payment.payment_id
134
+ if payment.card_account_id
135
+ xml["cac"].CardAccount do
136
+ xml["cbc"].PrimaryAccountNumberID payment.card_account_id
137
+ xml["cbc"].NetworkID(payment.card_network_id || "mapped-from-cii")
138
+ xml["cbc"].HolderName payment.card_holder_name if payment.card_holder_name
139
+ end
140
+ end
141
+ if payment.account_id
142
+ xml["cac"].PayeeFinancialAccount do
143
+ xml["cbc"].ID payment.account_id
144
+ end
145
+ end
146
+ if payment.mandate_reference
147
+ xml["cac"].PaymentMandate do
148
+ xml["cbc"].ID payment.mandate_reference
149
+ if payment.debited_account_id
150
+ xml["cac"].PayerFinancialAccount do
151
+ xml["cbc"].ID payment.debited_account_id
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ def build_payment_terms(xml, payment)
160
+ xml["cac"].PaymentTerms do
161
+ xml["cbc"].Note payment.note
162
+ end
163
+ end
164
+
165
+ def build_tax_total(xml, breakdown)
166
+ xml["cac"].TaxTotal do
167
+ xml["cbc"].TaxAmount(format_decimal(breakdown.tax_amount),
168
+ currencyID: breakdown.currency_code)
169
+ breakdown.subtotals.each do |sub|
170
+ xml["cac"].TaxSubtotal do
171
+ xml["cbc"].TaxableAmount(format_decimal(sub.taxable_amount),
172
+ currencyID: sub.currency_code)
173
+ xml["cbc"].TaxAmount(format_decimal(sub.tax_amount),
174
+ currencyID: sub.currency_code)
175
+ xml["cac"].TaxCategory do
176
+ xml["cbc"].ID sub.category_code
177
+ xml["cbc"].Percent format_decimal(sub.percent) if sub.percent
178
+ xml["cbc"].TaxExemptionReasonCode sub.exemption_reason_code if sub.exemption_reason_code
179
+ xml["cbc"].TaxExemptionReason sub.exemption_reason if sub.exemption_reason
180
+ xml["cac"].TaxScheme do
181
+ xml["cbc"].ID "VAT"
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ def build_monetary_total(xml, totals, currency_code)
190
+ xml["cac"].LegalMonetaryTotal do
191
+ xml["cbc"].LineExtensionAmount(format_decimal(totals.line_extension_amount),
192
+ currencyID: currency_code)
193
+ xml["cbc"].TaxExclusiveAmount(format_decimal(totals.tax_exclusive_amount),
194
+ currencyID: currency_code)
195
+ xml["cbc"].TaxInclusiveAmount(format_decimal(totals.tax_inclusive_amount),
196
+ currencyID: currency_code)
197
+ if totals.allowance_total_amount
198
+ xml["cbc"].AllowanceTotalAmount(format_decimal(totals.allowance_total_amount),
199
+ currencyID: currency_code)
200
+ end
201
+ if totals.charge_total_amount
202
+ xml["cbc"].ChargeTotalAmount(format_decimal(totals.charge_total_amount),
203
+ currencyID: currency_code)
204
+ end
205
+ if totals.prepaid_amount
206
+ xml["cbc"].PrepaidAmount(format_decimal(totals.prepaid_amount),
207
+ currencyID: currency_code)
208
+ end
209
+ if totals.payable_rounding_amount
210
+ xml["cbc"].PayableRoundingAmount(format_decimal(totals.payable_rounding_amount),
211
+ currencyID: currency_code)
212
+ end
213
+ xml["cbc"].PayableAmount(format_decimal(totals.payable_amount),
214
+ currencyID: currency_code)
215
+ end
216
+ end
217
+
218
+ def build_allowance_charge(xml, ac, currency_code)
219
+ xml["cac"].AllowanceCharge do
220
+ xml["cbc"].ChargeIndicator ac.charge_indicator.to_s
221
+ xml["cbc"].AllowanceChargeReasonCode ac.reason_code if ac.reason_code
222
+ xml["cbc"].AllowanceChargeReason ac.reason if ac.reason
223
+ xml["cbc"].MultiplierFactorNumeric format_decimal(ac.multiplier_factor) if ac.multiplier_factor
224
+ xml["cbc"].Amount(format_decimal(ac.amount), currencyID: currency_code)
225
+ xml["cbc"].BaseAmount(format_decimal(ac.base_amount), currencyID: currency_code) if ac.base_amount
226
+ if ac.tax_category_code
227
+ xml["cac"].TaxCategory do
228
+ xml["cbc"].ID ac.tax_category_code
229
+ xml["cbc"].Percent format_decimal(ac.tax_percent) if ac.tax_percent
230
+ xml["cac"].TaxScheme do
231
+ xml["cbc"].ID "VAT"
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ def build_invoice_line(xml, line, currency_code)
239
+ xml["cac"].InvoiceLine do
240
+ xml["cbc"].ID line.id
241
+ xml["cbc"].Note line.note if line.note
242
+ xml["cbc"].InvoicedQuantity(format_decimal(line.invoiced_quantity),
243
+ unitCode: line.unit_code)
244
+ xml["cbc"].LineExtensionAmount(format_decimal(line.line_extension_amount),
245
+ currencyID: currency_code)
246
+ build_item(xml, line.item) if line.item
247
+ build_price(xml, line.price, currency_code) if line.price
248
+ end
249
+ end
250
+
251
+ def build_item(xml, item)
252
+ xml["cac"].Item do
253
+ xml["cbc"].Description item.description if item.description
254
+ xml["cbc"].Name item.name
255
+ if item.sellers_identifier
256
+ xml["cac"].SellersItemIdentification do
257
+ xml["cbc"].ID item.sellers_identifier
258
+ end
259
+ end
260
+ if item.tax_category
261
+ xml["cac"].ClassifiedTaxCategory do
262
+ xml["cbc"].ID item.tax_category
263
+ xml["cbc"].Percent format_decimal(item.tax_percent) if item.tax_percent
264
+ xml["cac"].TaxScheme do
265
+ xml["cbc"].ID "VAT"
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ def build_price(xml, price, currency_code)
273
+ xml["cac"].Price do
274
+ xml["cbc"].PriceAmount(format_decimal(price.amount), currencyID: currency_code)
275
+ end
276
+ end
277
+
278
+ def format_decimal(value)
279
+ return value.to_s unless value.is_a?(BigDecimal)
280
+ # Remove trailing zeros but keep at least one decimal
281
+ str = value.to_s("F")
282
+ str.sub(/\.?0+$/, "")
283
+ end
284
+ end
285
+ end
286
+ end
data/lib/zugpferd.rb ADDED
@@ -0,0 +1,20 @@
1
+ require_relative "zugpferd/model/invoice"
2
+ require_relative "zugpferd/model/trade_party"
3
+ require_relative "zugpferd/model/postal_address"
4
+ require_relative "zugpferd/model/contact"
5
+ require_relative "zugpferd/model/line_item"
6
+ require_relative "zugpferd/model/item"
7
+ require_relative "zugpferd/model/price"
8
+ require_relative "zugpferd/model/monetary_totals"
9
+ require_relative "zugpferd/model/tax_breakdown"
10
+ require_relative "zugpferd/model/tax_subtotal"
11
+ require_relative "zugpferd/model/payment_instructions"
12
+ require_relative "zugpferd/model/allowance_charge"
13
+ require_relative "zugpferd/ubl/reader"
14
+ require_relative "zugpferd/ubl/writer"
15
+ require_relative "zugpferd/cii/reader"
16
+ require_relative "zugpferd/cii/writer"
17
+
18
+ module Zugpferd
19
+ class Error < StandardError; end
20
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zugpferd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alexander Zeitler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-02-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bigdecimal
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.25'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.25'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ description:
70
+ email:
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/zugpferd.rb
76
+ - lib/zugpferd/cii/mapping.rb
77
+ - lib/zugpferd/cii/reader.rb
78
+ - lib/zugpferd/cii/writer.rb
79
+ - lib/zugpferd/model/allowance_charge.rb
80
+ - lib/zugpferd/model/contact.rb
81
+ - lib/zugpferd/model/invoice.rb
82
+ - lib/zugpferd/model/item.rb
83
+ - lib/zugpferd/model/line_item.rb
84
+ - lib/zugpferd/model/monetary_totals.rb
85
+ - lib/zugpferd/model/payment_instructions.rb
86
+ - lib/zugpferd/model/postal_address.rb
87
+ - lib/zugpferd/model/price.rb
88
+ - lib/zugpferd/model/tax_breakdown.rb
89
+ - lib/zugpferd/model/tax_subtotal.rb
90
+ - lib/zugpferd/model/trade_party.rb
91
+ - lib/zugpferd/ubl/mapping.rb
92
+ - lib/zugpferd/ubl/reader.rb
93
+ - lib/zugpferd/ubl/writer.rb
94
+ homepage: https://alexzeitler.github.io/zugpferd/
95
+ licenses:
96
+ - MIT
97
+ metadata:
98
+ source_code_uri: https://github.com/alexzeitler/zugpferd
99
+ homepage_uri: https://alexzeitler.github.io/zugpferd/
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '3.2'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubygems_version: 3.5.20
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: EN 16931 E-Invoice library for Ruby (UBL + CII)
119
+ test_files: []