secretariat 3.3.0 → 3.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4aede1e2eafc1189446e824cb5977b9c3844f71fa4c505214aa4d9b9f79e1101
4
- data.tar.gz: 533c4f81ae8b50977df60f6c2bc31565cbbfd93066c52d8cf1961215971319fd
3
+ metadata.gz: f1a42571c7c3ceb77877659b076fbd8974ee661843dbc4f4d294012163a4c99f
4
+ data.tar.gz: 871cf260400cdf9470c867572a77d6ac5d109886b30b9d99120bbbfa94b5f950
5
5
  SHA512:
6
- metadata.gz: a3358bd3eec524b4e76fc8f32945dca7cb665d683a256e69350fa31c340db2a75f77f3f64883978da387a52edd9beb4810ffe362d67341c81abe28579a979e1f
7
- data.tar.gz: 5f347f7253bc8f814a0e1524c968506679d7bb1108c9a4cf03d9716830829ae393e5cde2df2b9d83210a237b075354863ef06af10df2e3d984a699a9c1d758c3
6
+ metadata.gz: e69622419e9e48946ddf0dc4921515a02fa3e8c1f53cd1fd23cd14c89d7f2470718e8d220a4c9594142a768db4c143452efd93607917b230c28e9bb647ec537d
7
+ data.tar.gz: f5c1da6eea2d10742eebb2ae95993026f74ee6968ac630caaa2f38face0c26b03e401d6204752b14e816af8c408d8052843e77071bc6b68f37200bd3607099f6
data/README.md CHANGED
@@ -7,7 +7,7 @@ See tests for examples.
7
7
  1. This is an opinionated library optimised for my very specific usecase
8
8
  2. While I did start to add some validations to make sure you can't input absolute garbage into this, I cannot guarantee factual (as in taxation law) correctness of the resulting XML.
9
9
  1. The library, for ZUGFeRD 2.x, currently only supports the EN16931 variant. This is probably what you want as well. PRs welcome.
10
- 3. This does not contain any code to attach the XML to a PDF file, mainly because I have yet to find a ruby library to do that. For software that does this, take a look at [this python library](https://github.com/akretion/factur-x).
10
+ 3. This does not contain any code to attach the XML to a PDF file, mainly because I have yet to find a ruby library to do that. For software that does this, take a look at [this python library](https://github.com/akretion/factur-x) or [this Java library which also does extended validation](https://mustangproject.org)
11
11
 
12
12
  ## Contributors
13
13
 
@@ -66,7 +66,7 @@ module Secretariat
66
66
 
67
67
  # For the background of vertical and horizontal tax calculation see https://hilfe.pacemo.de/de-form/articles/3489851-rundungsfehler-bei-rechnungen
68
68
  # 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.
69
- TAX_CALCULATION_METHODS = %i[HORIZONTAL VERTICAL UNKNOWN].freeze
69
+ TAX_CALCULATION_METHODS = %i[HORIZONTAL VERTICAL NONE UNKNOWN].freeze
70
70
 
71
71
  UNIT_CODES = {
72
72
  :PIECE => "C62",
@@ -65,11 +65,26 @@ module Secretariat
65
65
 
66
66
  def taxes
67
67
  taxes = {}
68
+ # Shortcut for cases where invoices only have one tax and the calculation is off by a cent because of rounding errors
69
+ # (This can happen if the VAT and the net amount is calculated backwards from a round gross amount)
70
+ if tax_calculation_method == :NONE
71
+ tax = Tax.new(tax_percent: BigDecimal(tax_percent || BigDecimal(0)), tax_category: tax_category)
72
+ tax.base_amount = BigDecimal(basis_amount)
73
+ tax.tax_amount = BigDecimal(tax_amount || 0)
74
+ return [tax]
75
+ end
76
+
68
77
  line_items.each do |line_item|
69
- taxes[line_item.tax_percent] = Tax.new(tax_percent: BigDecimal(line_item.tax_percent), tax_category: line_item.tax_category) if taxes[line_item.tax_percent].nil?
70
- taxes[line_item.tax_percent].tax_amount += BigDecimal(line_item.tax_amount)
71
- taxes[line_item.tax_percent].base_amount += BigDecimal(line_item.net_amount) * line_item.quantity
78
+ if line_item.tax_percent.nil?
79
+ taxes['0'] = Tax.new(tax_percent: BigDecimal(0), tax_category: line_item.tax_category, tax_amount: BigDecimal(0)) if taxes['0'].nil?
80
+ taxes['0'].base_amount += BigDecimal(line_item.net_amount) * line_item.quantity
81
+ else
82
+ taxes[line_item.tax_percent] = Tax.new(tax_percent: BigDecimal(line_item.tax_percent), tax_category: line_item.tax_category) if taxes[line_item.tax_percent].nil?
83
+ taxes[line_item.tax_percent].tax_amount += BigDecimal(line_item.tax_amount)
84
+ taxes[line_item.tax_percent].base_amount += BigDecimal(line_item.net_amount) * line_item.quantity
85
+ end
72
86
  end
87
+
73
88
  if tax_calculation_method == :VERTICAL
74
89
  taxes.values.map do |tax|
75
90
  tax.tax_amount = (tax.base_amount * tax.tax_percent / 100).round(2)
@@ -98,12 +113,14 @@ module Secretariat
98
113
  @errors << "Base amount and summed tax base amount deviate: #{basis} / #{summed_tax_base_amount}"
99
114
  return false
100
115
  end
101
- taxes.each do |tax|
102
- calc_tax = tax.base_amount * BigDecimal(tax.tax_percent) / BigDecimal(100)
103
- calc_tax = calc_tax.round(2)
104
- if tax.tax_amount != calc_tax
105
- @errors << "Tax amount and calculated tax amount deviate for rate #{tax.tax_percent}: #{tax.tax_amount} / #{calc_tax}"
106
- return false
116
+ if tax_calculation_method != :NONE
117
+ taxes.each do |tax|
118
+ calc_tax = tax.base_amount * BigDecimal(tax.tax_percent) / BigDecimal(100)
119
+ calc_tax = calc_tax.round(2)
120
+ if tax.tax_amount != calc_tax
121
+ @errors << "Tax amount and calculated tax amount deviate for rate #{tax.tax_percent}: #{tax.tax_amount} / #{calc_tax}"
122
+ return false
123
+ end
107
124
  end
108
125
  end
109
126
  grand_total = BigDecimal(grand_total_amount)
@@ -250,9 +267,10 @@ module Secretariat
250
267
  end
251
268
  Helpers.currency_element(xml, 'ram', 'BasisAmount', tax.base_amount, currency_code, add_currency: version == 1)
252
269
  xml['ram'].CategoryCode tax_category_code(tax, version: version)
253
-
254
- percent = by_version(version, 'ApplicablePercent', 'RateApplicablePercent')
255
- xml['ram'].send(percent, Helpers.format(tax.tax_percent))
270
+ # unless tax.untaxable?
271
+ percent = by_version(version, 'ApplicablePercent', 'RateApplicablePercent')
272
+ xml['ram'].send(percent, Helpers.format(tax.tax_percent))
273
+ # end
256
274
  end
257
275
  end
258
276
  if version == 2 && service_period_start && service_period_end
@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  =end
16
16
 
17
-
18
-
19
17
  require 'bigdecimal'
20
18
  module Secretariat
21
19
 
@@ -63,13 +61,15 @@ module Secretariat
63
61
  return false
64
62
  end
65
63
  end
66
-
67
- calculated_tax = charge_price * BigDecimal(tax_percent) / BigDecimal(100)
68
- calculated_tax = calculated_tax.round(2)
69
- calculated_tax = -calculated_tax if quantity.negative?
70
- if calculated_tax != tax
71
- @errors << "Tax and calculated tax deviate: #{tax} / #{calculated_tax}"
72
- return false
64
+ if tax_category != :UNTAXEDSERVICE
65
+ self.tax_percent ||= BigDecimal(0)
66
+ calculated_tax = charge_price * BigDecimal(tax_percent) / BigDecimal(100)
67
+ calculated_tax = calculated_tax.round(2)
68
+ calculated_tax = -calculated_tax if quantity.negative?
69
+ if calculated_tax != tax
70
+ @errors << "Tax and calculated tax deviate: #{tax} / #{calculated_tax}"
71
+ return false
72
+ end
73
73
  end
74
74
  return true
75
75
  end
@@ -85,11 +85,17 @@ module Secretariat
85
85
  TAX_CATEGORY_CODES[tax_category] || 'S'
86
86
  end
87
87
 
88
+ def untaxable?
89
+ tax_category == :UNTAXEDSERVICE
90
+ end
91
+
88
92
  def to_xml(xml, line_item_index, version: 2, validate: true)
89
93
  net_price = net_amount && BigDecimal(net_amount)
90
94
  gross_price = gross_amount && BigDecimal(gross_amount)
91
95
  charge_price = charge_amount && BigDecimal(charge_amount)
92
96
 
97
+ self.tax_percent ||= BigDecimal(0)
98
+
93
99
  if net_price&.zero?
94
100
  self.tax_percent = 0
95
101
  end
@@ -170,10 +176,10 @@ module Secretariat
170
176
  xml['ram'].ApplicableTradeTax do
171
177
  xml['ram'].TypeCode 'VAT'
172
178
  xml['ram'].CategoryCode tax_category_code(version: version)
173
-
174
- percent = by_version(version, 'ApplicablePercent', 'RateApplicablePercent')
175
- xml['ram'].send(percent,Helpers.format(tax_percent))
176
-
179
+ unless untaxable?
180
+ percent = by_version(version, 'ApplicablePercent', 'RateApplicablePercent')
181
+ xml['ram'].send(percent,Helpers.format(tax_percent))
182
+ end
177
183
  end
178
184
  monetary_summation = by_version(version, 'SpecifiedTradeSettlementMonetarySummation', 'SpecifiedTradeSettlementLineMonetarySummation')
179
185
  xml['ram'].send(monetary_summation) do
@@ -30,5 +30,10 @@ module Secretariat
30
30
  self.tax_amount = 0
31
31
  self.base_amount = 0
32
32
  end
33
+
34
+ def untaxable?
35
+ tax_category == :UNTAXEDSERVICE
36
+ end
37
+
33
38
  end
34
39
  end
@@ -16,10 +16,15 @@ limitations under the License.
16
16
 
17
17
  module Secretariat
18
18
  TradeParty = Struct.new('TradeParty',
19
- :name, :street1, :street2, :city, :postal_code, :country_id, :vat_id,
19
+ :name, :street1, :street2, :city, :postal_code, :country_id, :vat_id, :global_id, :global_id_scheme_id, :tax_id,
20
20
  keyword_init: true,
21
21
  ) do
22
22
  def to_xml(xml, exclude_tax: false, version: 2)
23
+ if global_id && global_id != '' && global_id_scheme_id && global_id_scheme_id != ''
24
+ xml['ram'].GlobalID(schemeID: global_id_scheme_id) do
25
+ xml.text(global_id)
26
+ end
27
+ end
23
28
  xml['ram'].Name name
24
29
  xml['ram'].PostalTradeAddress do
25
30
  xml['ram'].PostcodeCode postal_code
@@ -36,9 +41,13 @@ module Secretariat
36
41
  xml.text(vat_id)
37
42
  end
38
43
  end
44
+ elsif tax_id && tax_id != ''
45
+ xml['ram'].SpecifiedTaxRegistration do
46
+ xml['ram'].ID(schemeID: 'FC') do
47
+ xml.text(tax_id)
48
+ end
49
+ end
39
50
  end
40
51
  end
41
52
  end
42
-
43
-
44
53
  end
@@ -15,5 +15,5 @@ limitations under the License.
15
15
  =end
16
16
 
17
17
  module Secretariat
18
- VERSION = '3.3.0'
18
+ VERSION = '3.4.0'
19
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secretariat
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Krutisch