secretariat 3.7.0 → 3.8.1

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: 7614825461a1901faaa9bd3e72fc6fdbde345acc68fdfff7c7649d5e9af56bd5
4
- data.tar.gz: 73ec613205ee0a40046c521baec838c4f2eb1fcbd15f12fccd0ec35d21071811
3
+ metadata.gz: 173bbf980c0d297f49943505e9df853c2a7b44fd0bc51731498a3d1107cc29f4
4
+ data.tar.gz: b1fae313effb37e98fc48e21b6afab122f295661a62d551baf788ff35b46a279
5
5
  SHA512:
6
- metadata.gz: 3ffff8d3135aae3aab1d2c71737b1bc80ce588748c3b3350218629673c1295126f9bcdbd17986c2da6f78a5f4bb51493df85bb32ec2bd3ebf16a7bd692200172
7
- data.tar.gz: 84ae46ef3f826f9af23ff62c4e8484bc003cb8e665d3edbb5c55468d8782d445d35b82afc892679cd6effa4782ed4178471902287267896fb33770c6b708ea9a
6
+ metadata.gz: b3a8c6f02e353cb22d4dae54d7b1d0225e0b3db33bb75882528ee87b6f65be9d6c1a6c36d677d78a09664d2fede3b4c8b416028b5e8cd4add2e1b0d242c1cf2e
7
+ data.tar.gz: 8164ef6b8f052479ad00154ba3a69660c3886ea57d58b3dfd69ba851a8fcb05c965d4280a1819da4a8e238f84074168a1565e7b13dc299048561e28ed64da82d
@@ -53,6 +53,10 @@ module Secretariat
53
53
  :DEBITADVICE => "31",
54
54
  :CREDITCARD => "48",
55
55
  :DEBIT => "49",
56
+ :CREDITTRANSFER => "54",
57
+ :DIRECTDEBIT => "55",
58
+ :SEPACREDITTRANSFER => "58",
59
+ :SEPADIRECTDEBIT => "59",
56
60
  :COMPENSATION => "97"
57
61
  }
58
62
 
@@ -69,6 +73,7 @@ module Secretariat
69
73
  TAX_CALCULATION_METHODS = %i[HORIZONTAL VERTICAL NONE UNKNOWN].freeze
70
74
 
71
75
  UNIT_CODES = {
76
+ :YEAR => "ANN",
72
77
  :PIECE => "C62",
73
78
  :DAY => "DAY",
74
79
  :HECTARE => "HAR",
@@ -24,8 +24,9 @@ module Secretariat
24
24
  :issue_date,
25
25
  :service_period_start,
26
26
  :service_period_end,
27
- :seller,
28
- :buyer,
27
+ :seller, # TradeParty
28
+ :buyer, # TradeParty
29
+ :ship_to, # TradeParty or nil (buyer) or false (no ShipTo)
29
30
  :buyer_reference,
30
31
  :line_items,
31
32
  :currency_code,
@@ -48,6 +49,10 @@ module Secretariat
48
49
  :tax_calculation_method,
49
50
  :notes,
50
51
  :attachments,
52
+ :direct_debit_mandate_reference_id, # BT-89
53
+ :direct_debit_creditor_id, # BT-90
54
+ :direct_debit_iban, # BT-91,
55
+ :subject_code, # BT-21
51
56
  keyword_init: true
52
57
  ) do
53
58
 
@@ -57,8 +62,16 @@ module Secretariat
57
62
  @errors
58
63
  end
59
64
 
60
- def tax_reason_text
61
- tax_reason || TAX_EXEMPTION_REASONS[tax_category]
65
+ def tax_reason_text(tax)
66
+ tax_reason || TAX_EXEMPTION_REASONS[tax.tax_category || tax_category]
67
+ end
68
+
69
+ # ship_to: nil => use buyer (backwards compatibility)
70
+ # ship_to: false => ignore
71
+ def ship_to_or_buyer
72
+ return buyer if ship_to.nil?
73
+
74
+ ship_to
62
75
  end
63
76
 
64
77
  def tax_category_code(tax, version: 2)
@@ -82,11 +95,11 @@ module Secretariat
82
95
  line_items.each do |line_item|
83
96
  if line_item.tax_percent.nil?
84
97
  taxes['0'] = Tax.new(tax_percent: BigDecimal(0), tax_category: line_item.tax_category, tax_amount: BigDecimal(0)) if taxes['0'].nil?
85
- taxes['0'].base_amount += BigDecimal(line_item.net_amount) * line_item.quantity
98
+ taxes['0'].base_amount += BigDecimal(line_item.net_amount) * line_item.billed_quantity
86
99
  else
87
100
  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?
88
101
  taxes[line_item.tax_percent].tax_amount += BigDecimal(line_item.tax_amount)
89
- taxes[line_item.tax_percent].base_amount += BigDecimal(line_item.net_amount) * line_item.quantity
102
+ taxes[line_item.tax_percent].base_amount += BigDecimal(line_item.net_amount) * line_item.billed_quantity
90
103
  end
91
104
  end
92
105
 
@@ -108,12 +121,12 @@ module Secretariat
108
121
  @errors = []
109
122
  tax = BigDecimal(tax_amount)
110
123
  basis = BigDecimal(basis_amount)
111
- summed_tax_amount = taxes.sum(&:tax_amount)
124
+ summed_tax_amount = taxes.sum(&:tax_amount).round(2)
112
125
  if tax != summed_tax_amount
113
126
  @errors << "Tax amount and summed tax amounts deviate: #{tax_amount} / #{summed_tax_amount}"
114
127
  return false
115
128
  end
116
- summed_tax_base_amount = taxes.sum(&:base_amount)
129
+ summed_tax_base_amount = taxes.sum(&:base_amount).round(2)
117
130
  if basis != summed_tax_base_amount
118
131
  @errors << "Base amount and summed tax base amount deviate: #{basis} / #{summed_tax_base_amount}"
119
132
  return false
@@ -140,7 +153,7 @@ module Secretariat
140
153
  return false
141
154
  end
142
155
  line_item_sum = line_items.inject(BigDecimal(0)) do |m, item|
143
- m + BigDecimal(item.quantity.negative? ? -item.charge_amount : item.charge_amount)
156
+ m + BigDecimal(item.billed_quantity.negative? ? -item.charge_amount : item.charge_amount)
144
157
  end
145
158
  if line_item_sum != basis
146
159
  @errors << "Line items do not add up to basis amount #{line_item_sum} / #{basis}"
@@ -208,6 +221,7 @@ module Secretariat
208
221
  Array(self.notes).each do |note|
209
222
  xml['ram'].IncludedNote do
210
223
  xml['ram'].Content note
224
+ xml['ram'].SubjectCode subject_code if subject_code
211
225
  end
212
226
  end
213
227
  end
@@ -245,9 +259,9 @@ module Secretariat
245
259
  delivery = by_version(version, 'ApplicableSupplyChainTradeDelivery', 'ApplicableHeaderTradeDelivery')
246
260
 
247
261
  xml['ram'].send(delivery) do
248
- if version == 2
262
+ if version == 2 && ship_to_or_buyer
249
263
  xml['ram'].ShipToTradeParty do
250
- buyer.to_xml(xml, exclude_tax: true, version: version)
264
+ ship_to_or_buyer.to_xml(xml, exclude_tax: true, version: version)
251
265
  end
252
266
  end
253
267
  xml['ram'].ActualDeliverySupplyChainEvent do
@@ -260,6 +274,9 @@ module Secretariat
260
274
  end
261
275
  trade_settlement = by_version(version, 'ApplicableSupplyChainTradeSettlement', 'ApplicableHeaderTradeSettlement')
262
276
  xml['ram'].send(trade_settlement) do
277
+ if direct_debit_creditor_id
278
+ xml['ram'].CreditorReferenceID direct_debit_creditor_id # BT-90
279
+ end
263
280
  if payment_reference.present?
264
281
  xml['ram'].PaymentReference payment_reference
265
282
  end
@@ -278,13 +295,18 @@ module Secretariat
278
295
  xml['ram'].BICID payment_bic
279
296
  end
280
297
  end
298
+ if direct_debit_iban
299
+ xml['ram'].PayerPartyDebtorFinancialAccount do
300
+ xml['ram'].IBANID direct_debit_iban
301
+ end
302
+ end
281
303
  end
282
304
  taxes.each do |tax|
283
305
  xml['ram'].ApplicableTradeTax do
284
306
  Helpers.currency_element(xml, 'ram', 'CalculatedAmount', tax.tax_amount, currency_code, add_currency: version == 1)
285
307
  xml['ram'].TypeCode 'VAT'
286
- if tax_reason_text.present?
287
- xml['ram'].ExemptionReason tax_reason_text
308
+ if tax_reason_text(tax).present?
309
+ xml['ram'].ExemptionReason tax_reason_text(tax)
288
310
  end
289
311
  Helpers.currency_element(xml, 'ram', 'BasisAmount', tax.base_amount, currency_code, add_currency: version == 1)
290
312
  xml['ram'].CategoryCode tax_category_code(tax, version: version)
@@ -311,6 +333,9 @@ module Secretariat
311
333
  Helpers.date_element(xml, payment_due_date)
312
334
  end
313
335
  end
336
+ if direct_debit_mandate_reference_id
337
+ xml['ram'].DirectDebitMandateID direct_debit_mandate_reference_id
338
+ end
314
339
  end
315
340
 
316
341
  monetary_summation = by_version(version, 'SpecifiedTradeSettlementMonetarySummation', 'SpecifiedTradeSettlementHeaderMonetarySummation')
@@ -20,7 +20,7 @@ module Secretariat
20
20
 
21
21
  LineItem = Struct.new('LineItem',
22
22
  :name,
23
- :quantity,
23
+ :billed_quantity,
24
24
  :unit,
25
25
  :gross_amount,
26
26
  :net_amount,
@@ -32,11 +32,38 @@ module Secretariat
32
32
  :charge_amount,
33
33
  :origin_country_code,
34
34
  :currency_code,
35
+ :service_period_start, # if start present start & end are required
36
+ :service_period_end, # end has to be on or after start (secretariat does not validate this)
37
+ :basis_quantity,
35
38
  keyword_init: true
36
39
  ) do
37
40
 
38
41
  include Versioner
39
42
 
43
+ def initialize(**kwargs)
44
+ if kwargs.key?(:quantity) && !kwargs.key?(:billed_quantity)
45
+ kwargs[:billed_quantity] = kwargs.delete(:quantity)
46
+ end
47
+ super(**kwargs)
48
+ end
49
+
50
+ def quantity
51
+ warn_once_quantity
52
+ billed_quantity
53
+ end
54
+
55
+ def quantity=(val)
56
+ warn_once_quantity
57
+ self.billed_quantity = val
58
+ end
59
+
60
+ def warn_once_quantity
61
+ unless @__warned_quantity
62
+ Kernel.warn("[secretariat] LineItem#quantity is deprecated; use #billed_quantity")
63
+ @__warned_quantity = true
64
+ end
65
+ end
66
+
40
67
  def errors
41
68
  @errors
42
69
  end
@@ -47,7 +74,7 @@ module Secretariat
47
74
  gross_price = BigDecimal(gross_amount)
48
75
  charge_price = BigDecimal(charge_amount)
49
76
  tax = BigDecimal(tax_amount)
50
- unit_price = net_price * BigDecimal(quantity.abs)
77
+ unit_price = (net_price * BigDecimal(billed_quantity.abs)).round(2)
51
78
 
52
79
  if charge_price != unit_price
53
80
  @errors << "charge price and gross price times quantity deviate: #{charge_price} / #{unit_price}"
@@ -65,7 +92,7 @@ module Secretariat
65
92
  self.tax_percent ||= BigDecimal(0)
66
93
  calculated_tax = charge_price * BigDecimal(tax_percent) / BigDecimal(100)
67
94
  calculated_tax = calculated_tax.round(2)
68
- calculated_tax = -calculated_tax if quantity.negative?
95
+ calculated_tax = -calculated_tax if billed_quantity.negative?
69
96
  if calculated_tax != tax
70
97
  @errors << "Tax and calculated tax deviate: #{tax} / #{calculated_tax}"
71
98
  return false
@@ -78,6 +105,13 @@ module Secretariat
78
105
  UNIT_CODES[unit] || 'C62'
79
106
  end
80
107
 
108
+ # If not provided, BasisQuantity should be 1.0 (price per 1 unit)
109
+ def effective_basis_quantity
110
+ q = basis_quantity.nil? ? BigDecimal("1.0") : BigDecimal(basis_quantity.to_s)
111
+ raise ArgumentError, "basis_quantity must be > 0" if q <= 0
112
+ q
113
+ end
114
+
81
115
  def tax_category_code(version: 2)
82
116
  if version == 1
83
117
  return TAX_CATEGORY_CODES_1[tax_category] || 'S'
@@ -103,7 +137,7 @@ module Secretariat
103
137
  if net_price&.negative?
104
138
  # Zugferd doesn't allow negative amounts at the item level.
105
139
  # Instead, a negative quantity is used.
106
- self.quantity = -quantity
140
+ self.billed_quantity = -billed_quantity
107
141
  self.gross_amount = gross_price&.abs
108
142
  self.net_amount = net_price&.abs
109
143
  self.charge_amount = charge_price&.abs
@@ -132,7 +166,7 @@ module Secretariat
132
166
  Helpers.currency_element(xml, 'ram', 'ChargeAmount', gross_amount, currency_code, add_currency: version == 1, digits: 4)
133
167
  if version == 2 && discount_amount
134
168
  xml['ram'].BasisQuantity(unitCode: unit_code) do
135
- xml.text(Helpers.format(quantity, digits: 4))
169
+ xml.text(Helpers.format(effective_basis_quantity, digits: 4))
136
170
  end
137
171
  xml['ram'].AppliedTradeAllowanceCharge do
138
172
  xml['ram'].ChargeIndicator do
@@ -156,7 +190,7 @@ module Secretariat
156
190
  Helpers.currency_element(xml, 'ram', 'ChargeAmount', net_amount, currency_code, add_currency: version == 1, digits: 4)
157
191
  if version == 2
158
192
  xml['ram'].BasisQuantity(unitCode: unit_code) do
159
- xml.text(Helpers.format(quantity, digits: 4))
193
+ xml.text(Helpers.format(effective_basis_quantity, digits: 4))
160
194
  end
161
195
  end
162
196
  end
@@ -166,7 +200,7 @@ module Secretariat
166
200
 
167
201
  xml['ram'].send(delivery) do
168
202
  xml['ram'].BilledQuantity(unitCode: unit_code) do
169
- xml.text(Helpers.format(quantity, digits: 4))
203
+ xml.text(Helpers.format(billed_quantity, digits: 4))
170
204
  end
171
205
  end
172
206
 
@@ -181,9 +215,25 @@ module Secretariat
181
215
  xml['ram'].send(percent,Helpers.format(tax_percent))
182
216
  end
183
217
  end
218
+
219
+ if version == 2 && self.service_period_start && self.service_period_end
220
+ xml['ram'].BillingSpecifiedPeriod do
221
+ xml['ram'].StartDateTime do
222
+ xml['udt'].DateTimeString(format: '102') do
223
+ xml.text(service_period_start.strftime("%Y%m%d"))
224
+ end
225
+ end
226
+ xml['ram'].EndDateTime do
227
+ xml['udt'].DateTimeString(format: '102') do
228
+ xml.text(service_period_end.strftime("%Y%m%d"))
229
+ end
230
+ end
231
+ end
232
+ end
233
+
184
234
  monetary_summation = by_version(version, 'SpecifiedTradeSettlementMonetarySummation', 'SpecifiedTradeSettlementLineMonetarySummation')
185
235
  xml['ram'].send(monetary_summation) do
186
- Helpers.currency_element(xml, 'ram', 'LineTotalAmount', (quantity.negative? ? -charge_amount : charge_amount), currency_code, add_currency: version == 1)
236
+ Helpers.currency_element(xml, 'ram', 'LineTotalAmount', (billed_quantity.negative? ? -charge_amount : charge_amount), currency_code, add_currency: version == 1)
187
237
  end
188
238
  end
189
239
 
@@ -18,16 +18,26 @@ module Secretariat
18
18
  using ObjectExtensions
19
19
 
20
20
  TradeParty = Struct.new('TradeParty',
21
+ :id,
21
22
  :name, :street1, :street2, :city, :postal_code, :country_id, :vat_id, :global_id, :global_id_scheme_id, :tax_id,
23
+ :person_name,
22
24
  keyword_init: true,
23
25
  ) do
24
26
  def to_xml(xml, exclude_tax: false, version: 2)
27
+ if id && !exclude_tax
28
+ xml['ram'].ID id # BT-46
29
+ end
25
30
  if global_id.present? && global_id_scheme_id.present?
26
31
  xml['ram'].GlobalID(schemeID: global_id_scheme_id) do
27
32
  xml.text(global_id)
28
33
  end
29
34
  end
30
35
  xml['ram'].Name name
36
+ if person_name
37
+ xml['ram'].DefinedTradeContact do
38
+ xml['ram'].PersonName person_name
39
+ end
40
+ end
31
41
  xml['ram'].PostalTradeAddress do
32
42
  xml['ram'].PostcodeCode postal_code
33
43
  xml['ram'].LineOne street1
@@ -53,3 +63,5 @@ module Secretariat
53
63
  end
54
64
  end
55
65
  end
66
+
67
+ # assert_match(%r{<ram:DefinedTradeContact>\s*<ram:PersonName>Max Mustermann</ram:PersonName>\s*</ram:DefinedTradeContact>}, xml)
@@ -15,5 +15,5 @@ limitations under the License.
15
15
  =end
16
16
 
17
17
  module Secretariat
18
- VERSION = "3.7.0"
18
+ VERSION = "3.8.1"
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.7.0
4
+ version: 3.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Krutisch