secretariat 3.7.0 → 3.8.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: 7614825461a1901faaa9bd3e72fc6fdbde345acc68fdfff7c7649d5e9af56bd5
4
- data.tar.gz: 73ec613205ee0a40046c521baec838c4f2eb1fcbd15f12fccd0ec35d21071811
3
+ metadata.gz: f6d311cbeb3472a1af82827bb21d95e1f789fb21fe486995e1be364213761e44
4
+ data.tar.gz: e52061bd2915e2ef348c33c714a1f6ae74ee5b2ab3961c5adce7ea0ceac1a804
5
5
  SHA512:
6
- metadata.gz: 3ffff8d3135aae3aab1d2c71737b1bc80ce588748c3b3350218629673c1295126f9bcdbd17986c2da6f78a5f4bb51493df85bb32ec2bd3ebf16a7bd692200172
7
- data.tar.gz: 84ae46ef3f826f9af23ff62c4e8484bc003cb8e665d3edbb5c55468d8782d445d35b82afc892679cd6effa4782ed4178471902287267896fb33770c6b708ea9a
6
+ metadata.gz: 7ebfa196f6d6c1710bbdb6768526321e9ca1a729d9d4ff8117914ab7d84bf1b919eea3c212cc3dfcc4574aa2f9272c415ec319cf941912ffccd2ddfef03f08bd
7
+ data.tar.gz: 6cb203137f2af48c89635b4bea7860b0aafb02feaebbecf649f8aabf0c43ac56afa3a3932439fa0d516f790d96dae80c800f46a31eac556afbbdc421a6ff7215
@@ -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,9 @@ 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
51
55
  keyword_init: true
52
56
  ) do
53
57
 
@@ -57,8 +61,16 @@ module Secretariat
57
61
  @errors
58
62
  end
59
63
 
60
- def tax_reason_text
61
- tax_reason || TAX_EXEMPTION_REASONS[tax_category]
64
+ def tax_reason_text(tax)
65
+ tax_reason || TAX_EXEMPTION_REASONS[tax.tax_category || tax_category]
66
+ end
67
+
68
+ # ship_to: nil => use buyer (backwards compatibility)
69
+ # ship_to: false => ignore
70
+ def ship_to_or_buyer
71
+ return buyer if ship_to.nil?
72
+
73
+ ship_to
62
74
  end
63
75
 
64
76
  def tax_category_code(tax, version: 2)
@@ -82,11 +94,11 @@ module Secretariat
82
94
  line_items.each do |line_item|
83
95
  if line_item.tax_percent.nil?
84
96
  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
97
+ taxes['0'].base_amount += BigDecimal(line_item.net_amount) * line_item.billed_quantity
86
98
  else
87
99
  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
100
  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
101
+ taxes[line_item.tax_percent].base_amount += BigDecimal(line_item.net_amount) * line_item.billed_quantity
90
102
  end
91
103
  end
92
104
 
@@ -140,7 +152,7 @@ module Secretariat
140
152
  return false
141
153
  end
142
154
  line_item_sum = line_items.inject(BigDecimal(0)) do |m, item|
143
- m + BigDecimal(item.quantity.negative? ? -item.charge_amount : item.charge_amount)
155
+ m + BigDecimal(item.billed_quantity.negative? ? -item.charge_amount : item.charge_amount)
144
156
  end
145
157
  if line_item_sum != basis
146
158
  @errors << "Line items do not add up to basis amount #{line_item_sum} / #{basis}"
@@ -245,9 +257,9 @@ module Secretariat
245
257
  delivery = by_version(version, 'ApplicableSupplyChainTradeDelivery', 'ApplicableHeaderTradeDelivery')
246
258
 
247
259
  xml['ram'].send(delivery) do
248
- if version == 2
260
+ if version == 2 && ship_to_or_buyer
249
261
  xml['ram'].ShipToTradeParty do
250
- buyer.to_xml(xml, exclude_tax: true, version: version)
262
+ ship_to_or_buyer.to_xml(xml, exclude_tax: true, version: version)
251
263
  end
252
264
  end
253
265
  xml['ram'].ActualDeliverySupplyChainEvent do
@@ -260,6 +272,9 @@ module Secretariat
260
272
  end
261
273
  trade_settlement = by_version(version, 'ApplicableSupplyChainTradeSettlement', 'ApplicableHeaderTradeSettlement')
262
274
  xml['ram'].send(trade_settlement) do
275
+ if direct_debit_creditor_id
276
+ xml['ram'].CreditorReferenceID direct_debit_creditor_id # BT-90
277
+ end
263
278
  if payment_reference.present?
264
279
  xml['ram'].PaymentReference payment_reference
265
280
  end
@@ -278,13 +293,18 @@ module Secretariat
278
293
  xml['ram'].BICID payment_bic
279
294
  end
280
295
  end
296
+ if direct_debit_iban
297
+ xml['ram'].PayerPartyDebtorFinancialAccount do
298
+ xml['ram'].IBANID direct_debit_iban
299
+ end
300
+ end
281
301
  end
282
302
  taxes.each do |tax|
283
303
  xml['ram'].ApplicableTradeTax do
284
304
  Helpers.currency_element(xml, 'ram', 'CalculatedAmount', tax.tax_amount, currency_code, add_currency: version == 1)
285
305
  xml['ram'].TypeCode 'VAT'
286
- if tax_reason_text.present?
287
- xml['ram'].ExemptionReason tax_reason_text
306
+ if tax_reason_text(tax).present?
307
+ xml['ram'].ExemptionReason tax_reason_text(tax)
288
308
  end
289
309
  Helpers.currency_element(xml, 'ram', 'BasisAmount', tax.base_amount, currency_code, add_currency: version == 1)
290
310
  xml['ram'].CategoryCode tax_category_code(tax, version: version)
@@ -311,6 +331,9 @@ module Secretariat
311
331
  Helpers.date_element(xml, payment_due_date)
312
332
  end
313
333
  end
334
+ if direct_debit_mandate_reference_id
335
+ xml['ram'].DirectDebitMandateID direct_debit_mandate_reference_id
336
+ end
314
337
  end
315
338
 
316
339
  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)
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.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.7.0
4
+ version: 3.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Krutisch