webtranslateit-payday 1.7.2 → 1.7.3

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: 59ef0d55e5bb70dab72d78727e46907e75b49c959346f3d6f9282c5ddde202df
4
- data.tar.gz: 221ddccb98e0ee6c239c770261b67f2e98d73a29f4862bda59eaed686cc88133
3
+ metadata.gz: 79e897436dac18ec0146ca7c556d2a1fd9a614fdc78b3b2fe4da6cff769184a5
4
+ data.tar.gz: 77f740456d34e88037867ca6890110bb39014073d476fedffed124fd698a844f
5
5
  SHA512:
6
- metadata.gz: 01a66a64216ead7641340f677b324e705459812759864043e02e48bc542fa71b02a76048c9981b5b64e2f7f3e592e525bb5a9080bc84d5046ab8ffdb67d87538
7
- data.tar.gz: 0b3c6eba860fb22dadae435310aed5d4d0a74812e27db53d6fe6580d3162f05ee7f017e487ff254e27fc67f233229feda3f77ecf0733f0cd2b5a7ec73b37640a
6
+ metadata.gz: 3253e3a0d51b8125df8fb0ee6341c18c320af4dbec9361376437082fc101b7552f8d25619f6d5ab79f914a2745c9a25a8952acce2a7cdeddbc71feeb7032afdf
7
+ data.tar.gz: 6af84ba49bb0171f4c969a13707b743e400694e897bdbf27ce494b2e1d18b7570ccee60da7c90789941c5e1669e82bde3d40b44b6088c772b3064aa30dd1bd91
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.7.3 (2026-02-16)
4
+
5
+ * Add retention support (e.g. Spanish IRPF). Set `retention_rate` on an invoice to apply a post-tax deduction calculated as a percentage of the subtotal. The retention amount is displayed as a negative line in the PDF and subtracted from the total. Customize the label with `retention_description` or via the `payday.invoice.retention` i18n key.
6
+
3
7
  ## 1.7.1 (2025-12-01)
4
8
 
5
9
  * Relax dependency on rqrcode.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- webtranslateit-payday (1.7.2)
4
+ webtranslateit-payday (1.7.3)
5
5
  activesupport (>= 7, < 9)
6
6
  i18n (~> 1.12, < 2)
7
7
  money (>= 6.16, < 8.0)
@@ -34,24 +34,24 @@ GEM
34
34
  bigdecimal (3.3.1)
35
35
  chunky_png (1.4.0)
36
36
  coderay (1.1.3)
37
- concurrent-ruby (1.3.5)
37
+ concurrent-ruby (1.3.6)
38
38
  connection_pool (2.5.4)
39
39
  css_parser (1.21.1)
40
40
  addressable
41
41
  diff-lcs (1.6.2)
42
42
  drb (2.2.3)
43
- ffi (1.17.1-arm64-darwin)
44
- ffi (1.17.1-x86_64-darwin)
45
- ffi (1.17.1-x86_64-linux-gnu)
46
- formatador (1.1.0)
47
- guard (2.19.1)
43
+ ffi (1.17.3-arm64-darwin)
44
+ ffi (1.17.3-x86_64-darwin)
45
+ ffi (1.17.3-x86_64-linux-gnu)
46
+ formatador (1.2.3)
47
+ reline
48
+ guard (2.20.1)
48
49
  formatador (>= 0.2.4)
49
50
  listen (>= 2.7, < 4.0)
50
51
  logger (~> 1.6)
51
52
  lumberjack (>= 1.0.12, < 2.0)
52
53
  nenv (~> 0.1)
53
54
  notiffany (~> 0.0)
54
- ostruct (~> 0.6)
55
55
  pry (>= 0.13.0)
56
56
  shellany (~> 0.0)
57
57
  thor (>= 0.18.1)
@@ -63,29 +63,30 @@ GEM
63
63
  guard-rubocop (1.5.0)
64
64
  guard (~> 2.0)
65
65
  rubocop (< 2.0)
66
- i18n (1.14.7)
66
+ i18n (1.14.8)
67
67
  concurrent-ruby (~> 1.0)
68
- json (2.16.0)
68
+ io-console (0.8.2)
69
+ json (2.18.1)
69
70
  language_server-protocol (3.17.0.5)
70
71
  lint_roller (1.1.0)
71
- listen (3.9.0)
72
+ listen (3.10.0)
73
+ logger
72
74
  rb-fsevent (~> 0.10, >= 0.10.3)
73
75
  rb-inotify (~> 0.9, >= 0.9.10)
74
76
  logger (1.7.0)
75
- lumberjack (1.2.10)
77
+ lumberjack (1.4.2)
76
78
  matrix (0.4.3)
77
79
  method_source (1.1.0)
78
80
  minitest (5.26.0)
79
- money (7.0.0)
81
+ money (7.0.2)
80
82
  bigdecimal
81
83
  i18n (~> 1.9)
82
84
  nenv (0.3.0)
83
85
  notiffany (0.1.3)
84
86
  nenv (~> 0.1)
85
87
  shellany (~> 0.0)
86
- ostruct (0.6.1)
87
88
  parallel (1.27.0)
88
- parser (3.3.10.0)
89
+ parser (3.3.10.1)
89
90
  ast (~> 2.4.1)
90
91
  racc
91
92
  pdf-core (0.10.0)
@@ -100,10 +101,11 @@ GEM
100
101
  rexml (>= 3.4.2, < 4)
101
102
  prawn-table (0.2.2)
102
103
  prawn (>= 1.3.0, < 3.0.0)
103
- prism (1.6.0)
104
- pry (0.15.2)
104
+ prism (1.9.0)
105
+ pry (0.16.0)
105
106
  coderay (~> 1.1)
106
107
  method_source (~> 1.0)
108
+ reline (>= 0.6.0)
107
109
  public_suffix (6.0.2)
108
110
  racc (1.8.1)
109
111
  rainbow (3.1.1)
@@ -111,6 +113,8 @@ GEM
111
113
  rb-inotify (0.11.1)
112
114
  ffi (~> 1.0)
113
115
  regexp_parser (2.11.3)
116
+ reline (0.6.3)
117
+ io-console (~> 0.5)
114
118
  rexml (3.4.4)
115
119
  rqrcode (3.1.1)
116
120
  chunky_png (~> 1.0)
@@ -129,7 +133,7 @@ GEM
129
133
  diff-lcs (>= 1.2.0, < 2.0)
130
134
  rspec-support (~> 3.13.0)
131
135
  rspec-support (3.13.6)
132
- rubocop (1.81.7)
136
+ rubocop (1.84.2)
133
137
  json (~> 2.3)
134
138
  language_server-protocol (~> 3.17.0.2)
135
139
  lint_roller (~> 1.1.0)
@@ -137,30 +141,30 @@ GEM
137
141
  parser (>= 3.3.0.2)
138
142
  rainbow (>= 2.2.2, < 4.0)
139
143
  regexp_parser (>= 2.9.3, < 3.0)
140
- rubocop-ast (>= 1.47.1, < 2.0)
144
+ rubocop-ast (>= 1.49.0, < 2.0)
141
145
  ruby-progressbar (~> 1.7)
142
146
  unicode-display_width (>= 2.4.0, < 4.0)
143
- rubocop-ast (1.48.0)
147
+ rubocop-ast (1.49.0)
144
148
  parser (>= 3.3.7.2)
145
- prism (~> 1.4)
149
+ prism (~> 1.7)
146
150
  rubocop-performance (1.26.1)
147
151
  lint_roller (~> 1.1)
148
152
  rubocop (>= 1.75.0, < 2.0)
149
153
  rubocop-ast (>= 1.47.1, < 2.0)
150
- rubocop-rspec (3.8.0)
154
+ rubocop-rspec (3.9.0)
151
155
  lint_roller (~> 1.1)
152
156
  rubocop (~> 1.81)
153
157
  ruby-progressbar (1.13.0)
154
158
  securerandom (0.4.1)
155
159
  shellany (0.0.1)
156
- thor (1.3.2)
160
+ thor (1.5.0)
157
161
  ttfunk (1.8.0)
158
162
  bigdecimal (~> 3.1)
159
163
  tzinfo (2.0.6)
160
164
  concurrent-ruby (~> 1.0)
161
165
  unicode-display_width (3.2.0)
162
166
  unicode-emoji (~> 4.1)
163
- unicode-emoji (4.1.0)
167
+ unicode-emoji (4.2.0)
164
168
  zeitwerk (2.7.1)
165
169
 
166
170
  PLATFORMS
@@ -168,6 +172,7 @@ PLATFORMS
168
172
  arm64-darwin-22
169
173
  arm64-darwin-23
170
174
  arm64-darwin-24
175
+ arm64-darwin-25
171
176
  x86_64-darwin-21
172
177
  x86_64-linux
173
178
 
@@ -14,6 +14,7 @@ de:
14
14
  subtotal: "Zwischensumme:"
15
15
  shipping: "Versand:"
16
16
  tax: "Steuern:"
17
+ retention: "Einbehaltung:"
17
18
  total: "Gesamt:"
18
19
  notes: "Anmerkungen"
19
20
  line_item:
@@ -14,6 +14,7 @@ en:
14
14
  subtotal: "Subtotal:"
15
15
  shipping: "Shipping:"
16
16
  tax: "Tax:"
17
+ retention: "Retention:"
17
18
  total: "Total:"
18
19
  notes: "Notes:"
19
20
  line_item:
@@ -14,6 +14,7 @@ es:
14
14
  subtotal: "Subtotal:"
15
15
  shipping: "Envío:"
16
16
  tax: "Impuesto:"
17
+ retention: "Retención IRPF:"
17
18
  total: "Total:"
18
19
  notes: "Notas:"
19
20
  line_item:
@@ -14,6 +14,7 @@ fr:
14
14
  subtotal: "Sous-total :"
15
15
  shipping: "Livraison :"
16
16
  tax: "Taxes :"
17
+ retention: "Retenue :"
17
18
  total: "Total :"
18
19
  notes: "Notes :"
19
20
  line_item:
@@ -13,6 +13,7 @@ nl:
13
13
  subtotal: "Subtotaal:"
14
14
  shipping: "Levering:"
15
15
  tax: "Btw:"
16
+ retention: "Inhouding:"
16
17
  total: "Totaal:"
17
18
  notes: "Opmerkingen"
18
19
  line_item:
@@ -12,6 +12,7 @@ zh-CN:
12
12
  subtotal: "小计:"
13
13
  shipping: "邮费:"
14
14
  tax: "税费:"
15
+ retention: "扣缴:"
15
16
  total: "总计:"
16
17
  notes: "备注"
17
18
  line_item:
@@ -4,6 +4,9 @@ class CreatePaydayTables < ActiveRecord::Migration
4
4
  # invoices will work without anything but bill_to, but there are quite a few options for the fields you can save, like ship_to
5
5
  # due_at, refunded_at, and paid_at
6
6
  t.string :bill_to
7
+ t.decimal :tax_rate
8
+ t.decimal :shipping_rate
9
+ t.decimal :retention_rate
7
10
 
8
11
  t.timestamps
9
12
  end
@@ -8,10 +8,10 @@ module Payday
8
8
  include Payday::Invoiceable
9
9
 
10
10
  attr_accessor :invoice_number, :bill_to, :ship_to, :notes, :line_items, :shipping_description,
11
- :tax_description, :due_at, :paid_at, :refunded_at, :currency, :invoice_details, :invoice_date,
12
- :qr_code
11
+ :tax_description, :retention_description, :due_at, :paid_at, :refunded_at, :currency,
12
+ :invoice_details, :invoice_date, :qr_code
13
13
 
14
- attr_reader :tax_rate, :shipping_rate
14
+ attr_reader :tax_rate, :shipping_rate, :retention_rate
15
15
 
16
16
  # rubocop:todo Metrics/PerceivedComplexity
17
17
  # rubocop:todo Metrics/MethodLength
@@ -26,6 +26,8 @@ module Payday
26
26
  self.shipping_description = options[:shipping_description] || nil
27
27
  self.tax_rate = options[:tax_rate] || nil
28
28
  self.tax_description = options[:tax_description] || nil
29
+ self.retention_rate = options[:retention_rate] || nil
30
+ self.retention_description = options[:retention_description] || nil
29
31
  self.due_at = options[:due_at] || nil
30
32
  self.paid_at = options[:paid_at] || nil
31
33
  self.refunded_at = options[:refunded_at] || nil
@@ -50,6 +52,12 @@ module Payday
50
52
  @shipping_rate = BigDecimal(value.to_s)
51
53
  end
52
54
 
55
+ # Retention rate (e.g. IRPF). Applied as a percentage of the subtotal and deducted from the total.
56
+ def retention_rate=(value)
57
+ value = 0 if value.to_s.blank?
58
+ @retention_rate = BigDecimal(value.to_s)
59
+ end
60
+
53
61
  # Adds a line item
54
62
  def add_line_item(options = {})
55
63
  line_items << Payday::LineItem.new(options)
@@ -49,9 +49,19 @@ module Payday
49
49
  end
50
50
  end
51
51
 
52
+ # Calculates the retention (e.g. IRPF) as a percentage of the subtotal.
53
+ # This is a post-tax deduction subtracted from the total.
54
+ def retention
55
+ if defined?(retention_rate)
56
+ subtotal * retention_rate / 100
57
+ else
58
+ 0
59
+ end
60
+ end
61
+
52
62
  # Calculates the total for this invoice.
53
63
  def total
54
- subtotal + tax + shipping
64
+ subtotal + tax + shipping - retention
55
65
  end
56
66
 
57
67
  def overdue?
@@ -223,8 +223,9 @@ module Payday
223
223
  end
224
224
  # rubocop:enable Metrics/MethodLength
225
225
 
226
+ # rubocop:todo Metrics/PerceivedComplexity
226
227
  # rubocop:todo Metrics/MethodLength
227
- def self.totals_lines(invoice, pdf) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
228
+ def self.totals_lines(invoice, pdf) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
228
229
  table_data = []
229
230
  table_data << [
230
231
  bold_cell(pdf, I18n.t('payday.invoice.subtotal', default: 'Subtotal:')),
@@ -255,6 +256,21 @@ module Payday
255
256
  align: :right)
256
257
  ]
257
258
  end
259
+
260
+ if invoice.retention_rate.positive?
261
+ retention_description = if invoice.retention_description.nil?
262
+ I18n.t('payday.invoice.retention', default: 'Retention:')
263
+ else
264
+ invoice.retention_description
265
+ end
266
+
267
+ table_data << [
268
+ bold_cell(pdf, retention_description),
269
+ cell(pdf, number_to_currency(-invoice.retention, invoice),
270
+ align: :right)
271
+ ]
272
+ end
273
+
258
274
  table_data << [
259
275
  bold_cell(pdf, I18n.t('payday.invoice.total', default: 'Total:'),
260
276
  size: 12),
@@ -268,6 +284,7 @@ module Payday
268
284
  end
269
285
  end
270
286
  # rubocop:enable Metrics/MethodLength
287
+ # rubocop:enable Metrics/PerceivedComplexity
271
288
 
272
289
  def self.notes(invoice, pdf) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
273
290
  return unless defined?(invoice.notes) && invoice.notes
data/payday.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'webtranslateit-payday'
5
- s.version = '1.7.2'
5
+ s.version = '1.7.3'
6
6
  s.required_ruby_version = '>= 3.1'
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ['Alan Johnson', 'Edouard Briere']
data/spec/invoice_spec.rb CHANGED
@@ -75,6 +75,41 @@ module Payday # rubocop:todo Metrics/ModuleLength
75
75
  expect(i.total).to eq(BigDecimal(1243))
76
76
  end
77
77
 
78
+ it 'returns zero retention when no retention_rate is set' do
79
+ i = described_class.new
80
+ i.line_items << LineItem.new(price: 100, quantity: 1, description: 'Service')
81
+
82
+ expect(i.retention).to eq(BigDecimal(0))
83
+ end
84
+
85
+ it 'calculates retention correctly as a percentage of the subtotal' do
86
+ i = described_class.new(retention_rate: 15.0)
87
+ i.line_items << LineItem.new(price: 200, quantity: 5, description: 'Service')
88
+
89
+ expect(i.retention).to eq(BigDecimal(150))
90
+ end
91
+
92
+ # rubocop:todo RSpec/MultipleExpectations
93
+ it 'calculates total with retention deducted (IRPF example: 500 + 21% VAT - 19% IRPF = 510)' do # rubocop:todo RSpec/ExampleLength, RSpec/MultipleExpectations
94
+ # rubocop:enable RSpec/MultipleExpectations
95
+ i = described_class.new(tax_rate: 21.0, retention_rate: 19.0)
96
+ i.line_items << LineItem.new(price: 500, quantity: 1, description: 'Consulting')
97
+
98
+ expect(i.subtotal).to eq(BigDecimal(500))
99
+ expect(i.tax).to eq(BigDecimal(105))
100
+ expect(i.retention).to eq(BigDecimal(95))
101
+ expect(i.total).to eq(BigDecimal(510))
102
+ end
103
+
104
+ it 'calculates total with tax, shipping, and retention combined' do
105
+ i = described_class.new(tax_rate: 10.0, shipping_rate: 20, retention_rate: 5.0)
106
+ i.line_items << LineItem.new(price: 100, quantity: 10, description: 'Widgets')
107
+
108
+ # subtotal: 1000, tax: 100, shipping: 20, retention: 50
109
+ # total: 1000 + 100 + 20 - 50 = 1070
110
+ expect(i.total).to eq(BigDecimal(1070))
111
+ end
112
+
78
113
  it "is overdue when it's past date and unpaid" do
79
114
  i = described_class.new(due_at: Date.today - 1)
80
115
  expect(i.overdue?).to be(true)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webtranslateit-payday
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alan Johnson