Skip to content

Commit 3b9e529

Browse files
authored
Merge pull request #22 from mnin/add-support-for-additional-referenced-documents
Add Support For Additional Referenced Documents
2 parents ae0038f + 4c49769 commit 3b9e529

7 files changed

Lines changed: 174 additions & 1 deletion

File tree

lib/secretariat.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424
require_relative 'secretariat/line_item'
2525
require_relative 'secretariat/validator'
2626
require_relative 'secretariat/tax'
27+
require_relative 'secretariat/attachment'

lib/secretariat/attachment.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
=begin
2+
Copyright Jan Krutisch
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
=end
17+
18+
19+
require 'mime/types'
20+
21+
module Secretariat
22+
Attachment = Struct.new('Attachment',
23+
:filename,
24+
:type_code,
25+
:base64,
26+
27+
keyword_init: true
28+
) do
29+
include Versioner
30+
31+
def errors
32+
@errors
33+
end
34+
35+
def valid?
36+
@errors = []
37+
38+
@errors << "the attribute filename needs to be present" if filename.nil? || filename == ''
39+
@errors << "the attribute type_code needs to be present" if type_code.nil? || type_code == ''
40+
@errors << "the attribute base64 needs to be present" if base64.nil? || base64 == ''
41+
42+
if type_code.to_i != 916
43+
@errors << "we only support type_code 916"
44+
return false
45+
end
46+
47+
if mime_code.nil?
48+
@errors << "cannot determine content type for filename: #{filename}"
49+
return false
50+
end
51+
52+
if !ALLOWED_MIME_TYPES.include?(mime_code)
53+
@errors << "the mime_code '#{mime_code}' is not allowed"
54+
return false
55+
end
56+
57+
return true
58+
end
59+
60+
def mime_code
61+
type_for = MIME::Types.type_for(filename).first
62+
return if type_for.nil?
63+
64+
type_for.content_type
65+
end
66+
67+
def to_xml(xml, attachment_index, version: 2, validate: true)
68+
if validate && !valid?
69+
pp errors
70+
raise ValidationError.new("Attachment #{attachment_index} is invalid", errors)
71+
end
72+
73+
xml['ram'].AdditionalReferencedDocument do
74+
xml['ram'].IssuerAssignedID filename
75+
xml['ram'].TypeCode type_code
76+
xml['ram'].Name filename
77+
xml['ram'].AttachmentBinaryObject(mimeCode: mime_code, filename: filename) do
78+
xml.text(base64)
79+
end
80+
end
81+
end
82+
end
83+
end

lib/secretariat/constants.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@
1515
=end
1616

1717
module Secretariat
18+
ALLOWED_MIME_TYPES = [
19+
"application/pdf",
20+
"application/vnd.oasis.opendocument.spreadsheet",
21+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
22+
"image/jpeg",
23+
"image/png",
24+
"text/csv"
25+
]
1826

1927
TAX_CATEGORY_CODES = {
2028
:STANDARDRATE => "S",

lib/secretariat/invoice.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module Secretariat
4141
:grand_total_amount,
4242
:due_amount,
4343
:paid_amount,
44+
:attachments,
4445

4546
keyword_init: true
4647
) do
@@ -193,6 +194,13 @@ def to_xml(version: 1, validate: true)
193194
xml['ram'].BuyerTradeParty do
194195
buyer.to_xml(xml, version: version)
195196
end
197+
if version == 2
198+
if Array(attachments).size > 0
199+
attachments.each_with_index do |attachment, index|
200+
attachment.to_xml(xml, index, version: version, validate: validate)
201+
end
202+
end
203+
end
196204
end
197205

198206
delivery = by_version(version, 'ApplicableSupplyChainTradeDelivery', 'ApplicableHeaderTradeDelivery')
@@ -275,7 +283,7 @@ def to_xml(version: 1, validate: true)
275283
end
276284
if version == 1
277285
line_items.each_with_index do |item, i|
278-
item.to_xml(xml, i + 1, version: version) # one indexed
286+
item.to_xml(xml, i + 1, version: version, validate: validate) # one indexed
279287
end
280288
end
281289
end

secretariat.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ Gem::Specification.new do |s|
2828

2929
s.add_runtime_dependency 'nokogiri', '~> 1.10'
3030
s.add_runtime_dependency 'bigdecimal', '~> 3.1'
31+
s.add_runtime_dependency 'mime-types', '~> 3.6.0'
3132

3233
s.add_development_dependency 'minitest', '~> 5.13'
3334
s.add_development_dependency 'rake', '~> 13.0'
35+
s.add_development_dependency 'base64', '~> 0.2.0'
3436
s.requirements << "To run the validator, Java must be installed"
3537
end

test/fixtures/example.pdf

6.58 KB
Binary file not shown.

test/invoice_test.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'test_helper'
22
require 'date'
3+
require 'base64'
34

45
module Secretariat
56
class InvoiceTest < Minitest::Test
@@ -55,6 +56,63 @@ def make_eu_invoice
5556
)
5657
end
5758

59+
def make_eu_invoice_with_attachment
60+
seller = TradeParty.new(
61+
name: 'Depfu inc',
62+
street1: 'Quickbornstr. 46',
63+
city: 'Hamburg',
64+
postal_code: '20253',
65+
country_id: 'DE',
66+
vat_id: 'DE304755032'
67+
)
68+
buyer = TradeParty.new(
69+
name: 'Depfu inc',
70+
street1: 'Quickbornstr. 46',
71+
city: 'Hamburg',
72+
postal_code: '20253',
73+
country_id: 'SE',
74+
vat_id: 'SE304755032'
75+
)
76+
line_item = LineItem.new(
77+
name: 'Depfu Starter Plan',
78+
quantity: 1,
79+
gross_amount: '29',
80+
net_amount: '29',
81+
unit: :PIECE,
82+
charge_amount: '29',
83+
tax_category: :REVERSECHARGE,
84+
tax_percent: 0,
85+
tax_amount: "0",
86+
origin_country_code: 'DE',
87+
currency_code: 'EUR'
88+
)
89+
attachment = Attachment.new(
90+
filename: 'example.pdf',
91+
type_code: 916,
92+
base64: Base64.encode64(open(File.join(__dir__, 'fixtures/example.pdf')).read)
93+
)
94+
Invoice.new(
95+
id: '12345',
96+
issue_date: Date.today,
97+
service_period_start: Date.today,
98+
service_period_end: Date.today + 30,
99+
seller: seller,
100+
buyer: buyer,
101+
line_items: [line_item],
102+
currency_code: 'USD',
103+
payment_type: :CREDITCARD,
104+
payment_text: 'Kreditkarte',
105+
tax_category: :REVERSECHARGE,
106+
tax_amount: '0',
107+
basis_amount: '29',
108+
grand_total_amount: 29,
109+
due_amount: 0,
110+
paid_amount: 29,
111+
payment_due_date: Date.today + 14,
112+
attachments: [attachment]
113+
)
114+
end
115+
58116
def make_de_invoice
59117
seller = TradeParty.new(
60118
name: 'Depfu inc',
@@ -225,6 +283,19 @@ def test_simple_eu_invoice_against_schematron
225283
assert_equal [], errors
226284
end
227285

286+
def test_simple_eu_invoice_with_attachment_against_schematron
287+
xml = make_eu_invoice_with_attachment.to_xml(version: 2)
288+
v = Validator.new(xml, version: 2)
289+
errors = v.validate_against_schematron
290+
if !errors.empty?
291+
puts xml
292+
errors.each do |error|
293+
puts "#{error}"
294+
end
295+
end
296+
assert_equal [], errors
297+
end
298+
228299
def test_simple_de_invoice_v2
229300
xml = make_de_invoice.to_xml(version: 2)
230301
v = Validator.new(xml, version: 2)

0 commit comments

Comments
 (0)