Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/secretariat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
require_relative 'secretariat/validation_error'
require_relative 'secretariat/invoice'
require_relative 'secretariat/trade_party'
require_relative 'secretariat/trade_contact'
require_relative 'secretariat/line_item'
require_relative 'secretariat/validator'
require_relative 'secretariat/tax'
Expand Down
5 changes: 5 additions & 0 deletions lib/secretariat/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ module Secretariat
:UNTAXEDSERVICE => 'Not subject to VAT'
}

ELECTRONIC_ADDRESS_SCHEME_CODES = {
# For other possible values: https://ec.europa.eu/digital-building-blocks/sites/spaces/DIGITAL/pages/467108974/Registry+of+supporting+artefacts+to+implement+EN16931
:EMAIL => "EM"
}

# For the background of vertical and horizontal tax calculation see https://hilfe.pacemo.de/de-form/articles/3489851-rundungsfehler-bei-rechnungen
# The idea of introducing an unknown value is that this could be inferred from the given invoice total and line items by probing both variants and selecting the matching one - or reporting a taxation error if neither matches.
TAX_CALCULATION_METHODS = %i[HORIZONTAL VERTICAL NONE UNKNOWN].freeze
Expand Down
4 changes: 2 additions & 2 deletions lib/secretariat/invoice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

module Secretariat
using ObjectExtensions

Invoice = Struct.new("Invoice",
:id,
:issue_date,
Expand Down Expand Up @@ -261,7 +261,7 @@ def to_xml(version: 1, validate: true)
xml['ram'].send(delivery) do
if version == 2 && ship_to_or_buyer
xml['ram'].ShipToTradeParty do
ship_to_or_buyer.to_xml(xml, exclude_tax: true, version: version)
ship_to_or_buyer.to_xml(xml, exclude_tax: true, exclude_email: true, exclude_contact: true, version: version)
end
end
xml['ram'].ActualDeliverySupplyChainEvent do
Expand Down
34 changes: 34 additions & 0 deletions lib/secretariat/trade_contact.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

module Secretariat

TradeContact = Struct.new('TradeContact', :person_name, :department, :telephone, :email, keyword_init: true) do
def to_xml(xml, version: 2)
xml['ram'].DefinedTradeContact do
if person_name
xml['ram'].PersonName do
xml.text(person_name)
end
end
if department
xml['ram'].DepartmentName do
xml.text(department)
end
end
if telephone
xml['ram'].TelephoneUniversalCommunication do
xml['ram'].CompleteNumber do
xml.text(telephone)
end
end
end
if email
xml['ram'].EmailURIUniversalCommunication do
xml['ram'].URIID do
xml.text(email)
end
end
end
end
end
end
end
15 changes: 12 additions & 3 deletions lib/secretariat/trade_party.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ module Secretariat
TradeParty = Struct.new('TradeParty',
:id,
:name, :street1, :street2, :city, :postal_code, :country_id, :vat_id, :global_id, :global_id_scheme_id, :tax_id,
:person_name, :legal_organization,
:person_name, :legal_organization, :email, :trade_contact,
keyword_init: true,
) do
def to_xml(xml, exclude_tax: false, version: 2)
def to_xml(xml, exclude_tax: false, exclude_email: false, exclude_contact: false, version: 2)
if id && !exclude_tax
xml['ram'].ID id # BT-46
end
Expand All @@ -40,7 +40,9 @@ def to_xml(xml, exclude_tax: false, version: 2)
end
end
end
if person_name
if !exclude_contact && trade_contact.present?
trade_contact.to_xml(xml, version: version)
elsif !exclude_contact && person_name
xml['ram'].DefinedTradeContact do
xml['ram'].PersonName person_name
end
Expand All @@ -54,6 +56,13 @@ def to_xml(xml, exclude_tax: false, version: 2)
xml['ram'].CityName city
xml['ram'].CountryID country_id
end
if version == 2 && !exclude_email && email.present?
xml['ram'].URIUniversalCommunication do
xml['ram'].URIID(schemeID: ELECTRONIC_ADDRESS_SCHEME_CODES[:EMAIL]) do
xml.text(email)
end
end
end
if !exclude_tax && vat_id.present?
xml['ram'].SpecifiedTaxRegistration do
xml['ram'].ID(schemeID: 'VA') do
Expand Down
106 changes: 95 additions & 11 deletions test/invoice_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ def make_eu_invoice(tax_category: :REVERSECHARGE, ship_to: nil)
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
vat_id: 'DE304755032'
vat_id: 'DE304755032',
email: 'seller@example.com',
trade_contact: TradeContact.new(
person_name: 'Alex B.',
department: 'Invoicing',
telephone: '+49012345678',
email: 'invoicing@example.com'
)
)
buyer = TradeParty.new(
id: 'Kunde 4711',
Expand All @@ -21,7 +28,14 @@ def make_eu_invoice(tax_category: :REVERSECHARGE, ship_to: nil)
city: 'Hamburg',
postal_code: '20253',
country_id: 'SE',
vat_id: 'SE304755032'
vat_id: 'SE304755032',
email: 'buyer@example.com',
trade_contact: TradeContact.new(
person_name: 'Bob C.',
department: 'Purchasing',
telephone: '+49012345678',
email: 'purchasing@example.com'
)
)
line_item = LineItem.new(
name: 'Depfu Starter Plan',
Expand Down Expand Up @@ -183,13 +197,27 @@ def make_foreign_invoice(tax_category: :TAXEXEMPT)
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
email: 'seller@example.com',
trade_contact: TradeContact.new(
person_name: 'Alex B.',
department: 'Invoicing',
telephone: '+49012345678',
email: 'invoicing@example.com'
)
)
buyer = TradeParty.new(
name: 'Another Corp Inc.',
street1: 'Example Street 12',
city: 'Hamburg',
postal_code: 'NH-2003',
country_id: 'US',
email: 'buyer@example.com',
trade_contact: TradeContact.new(
person_name: 'Bob C.',
department: 'Purchasing',
telephone: '+49012345678',
email: 'purchasing@example.com'
)
)
line_item = LineItem.new(
name: 'Depfu Starter Plan',
Expand Down Expand Up @@ -233,15 +261,29 @@ def make_eu_invoice_with_attachment
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
vat_id: 'DE304755032'
vat_id: 'DE304755032',
email: 'seller@example.com',
trade_contact: TradeContact.new(
person_name: 'Alex B.',
department: 'Invoicing',
telephone: '+49012345678',
email: 'invoicing@example.com'
)
)
buyer = TradeParty.new(
name: 'Depfu inc',
street1: 'Quickbornstr. 46',
city: 'Hamburg',
postal_code: '20253',
country_id: 'SE',
vat_id: 'SE304755032'
vat_id: 'SE304755032',
email: 'buyer@example.com',
trade_contact: TradeContact.new(
person_name: 'Bob C.',
department: 'Purchasing',
telephone: '+49012345678',
email: 'purchasing@example.com'
)
)
line_item = LineItem.new(
name: 'Depfu Starter Plan',
Expand Down Expand Up @@ -291,7 +333,14 @@ def make_de_invoice
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
vat_id: 'DE304755032'
vat_id: 'DE304755032',
email: 'seller@example.com',
trade_contact: TradeContact.new(
person_name: 'Alex B.',
department: 'Invoicing',
telephone: '+49012345678',
email: 'invoicing@example.com'
)
)
buyer = TradeParty.new(
name: 'Depfu inc',
Expand All @@ -300,7 +349,14 @@ def make_de_invoice
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
vat_id: 'DE304755032'
vat_id: 'DE304755032',
email: 'buyer@example.com',
trade_contact: TradeContact.new(
person_name: 'Bob C.',
department: 'Purchasing',
telephone: '+49012345678',
email: 'purchasing@example.com'
)
)
line_item = LineItem.new(
name: 'Depfu Starter Plan',
Expand Down Expand Up @@ -350,15 +406,29 @@ def make_de_invoice_with_multiple_tax_rates
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
vat_id: 'DE304755032'
vat_id: 'DE304755032',
email: 'seller@example.com',
trade_contact: TradeContact.new(
person_name: 'Alex B.',
department: 'Invoicing',
telephone: '+49012345678',
email: 'invoicing@example.com'
)
)
buyer = TradeParty.new(
name: 'Depfu inc',
street1: 'Quickbornstr. 46',
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
vat_id: 'DE304755032'
vat_id: 'DE304755032',
email: 'buyer@example.com',
trade_contact: TradeContact.new(
person_name: 'Bob C.',
department: 'Purchasing',
telephone: '+49012345678',
email: 'purchasing@example.com'
)
)
line_item = LineItem.new(
name: 'Depfu Starter Plan',
Expand Down Expand Up @@ -431,15 +501,29 @@ def make_negative_de_invoice
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
vat_id: 'DE304755032'
vat_id: 'DE304755032',
email: 'seller@example.com',
trade_contact: TradeContact.new(
person_name: 'Alex B.',
department: 'Invoicing',
telephone: '+49012345678',
email: 'invoicing@example.com'
)
)
buyer = TradeParty.new(
name: 'Depfu inc',
street1: 'Quickbornstr. 46',
city: 'Hamburg',
postal_code: '20253',
country_id: 'DE',
vat_id: 'DE304755032'
vat_id: 'DE304755032',
email: 'buyer@example.com',
trade_contact: TradeContact.new(
person_name: 'Bob C.',
department: 'Purchasing',
telephone: '+49012345678',
email: 'purchasing@example.com'
)
)
line_item = LineItem.new(
name: 'Depfu Starter Plan',
Expand Down Expand Up @@ -851,14 +935,14 @@ def test_invoice_object_extensions
xml = invoice.to_xml(version: 2)

assert_match(/<ram:PaymentReference>#{invoice.payment_reference}<\/ram:PaymentReference>/, xml)
assert_match(%r{<ram:DefinedTradeContact>\s*<ram:PersonName>Max Mustermann</ram:PersonName>\s*</ram:DefinedTradeContact>}, xml)
assert_match(/<ram:Reason>/, xml)
end

def test_fr_invoice
invoice = make_fr_invoice
xml = invoice.to_xml(version: 2)
assert_match(%r{<ram:SpecifiedLegalOrganization>\s*<ram:ID schemeID="0002">304755032</ram:ID>\s*</ram:SpecifiedLegalOrganization>}, xml)
assert_match(%r{<ram:DefinedTradeContact>\s*<ram:PersonName>Max Mustermann</ram:PersonName>\s*</ram:DefinedTradeContact>}, xml)
end

def test_invoice_with_quantity_causing_sub_cent_amounts
Expand Down