moss_generator 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: da606f58ca3548eb7ce5fbef96498730f1046575caff4c23e741f3a6d4699e70
4
+ data.tar.gz: babfb54c2a5c729390e336b243a2ddf86991ce84695922a744dfe00a2de2e00a
5
+ SHA512:
6
+ metadata.gz: 36de7663f16245f15ec8cddc423de8d686d5721fe5ce42f402f188a0e76a6582fe4b8df12eae36b6fd619af08927f7cafabc1fce6493c51fbd859262b936e222
7
+ data.tar.gz: 8cbbd70a3e2992ba354042813dc9a19e22ee62b38160466fd30ef0a3686114d8fa1fe26922e1f4d81ca08a9521e709af22c510ea1c0fa84464027bb207f10048
@@ -0,0 +1,37 @@
1
+ name: Gem push
2
+ on: [push]
3
+ jobs:
4
+ github:
5
+ name: Build + Publish to GitHub
6
+ runs-on: ubuntu-latest
7
+ steps:
8
+ - uses: actions/checkout@v2
9
+ - name: Set up Ruby 2.6
10
+ uses: actions/setup-ruby@v1
11
+ with:
12
+ version: 2.6.5
13
+ - name: Publish to GitHub
14
+ uses: jstastny/publish-gem-to-github@master
15
+ with:
16
+ token: ${{ secrets.GITHUB_TOKEN }}
17
+ owner: standout
18
+
19
+ rubygems:
20
+ name: Build + Publish to Rubygems
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ - name: Set up Ruby 2.6
25
+ uses: actions/setup-ruby@v1
26
+ with:
27
+ version: 2.6.x
28
+ - name: Publish to RubyGems
29
+ run: |
30
+ mkdir -p $HOME/.gem
31
+ touch $HOME/.gem/credentials
32
+ chmod 0600 $HOME/.gem/credentials
33
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
34
+ gem build *.gemspec
35
+ gem push *.gem
36
+ env:
37
+ GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
@@ -0,0 +1,18 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.6.5
14
+ - name: Run the default task
15
+ run: |
16
+ gem install bundler -v 2.2.3
17
+ bundle install
18
+ bundle exec rake
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,15 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rspec
4
+ - rubocop-rake
5
+
6
+ AllCops:
7
+ NewCops: enable
8
+ TargetRubyVersion: 2.5
9
+
10
+ Layout/LineLength:
11
+ Max: 80
12
+
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - 'spec/**/*'
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in moss_generator.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+
10
+ gem 'rspec', '~> 3.0'
11
+
12
+ gem 'rubocop', '~> 1.7'
13
+ gem 'rubocop-performance', '~> 1.9'
14
+ gem 'rubocop-rake', '~> 0.5'
15
+ gem 'rubocop-rspec', '~> 2.1'
@@ -0,0 +1,66 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ moss_generator (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.1)
10
+ diff-lcs (1.4.4)
11
+ parallel (1.20.1)
12
+ parser (3.0.0.0)
13
+ ast (~> 2.4.1)
14
+ rainbow (3.0.0)
15
+ rake (13.0.3)
16
+ regexp_parser (2.0.3)
17
+ rexml (3.2.4)
18
+ rspec (3.10.0)
19
+ rspec-core (~> 3.10.0)
20
+ rspec-expectations (~> 3.10.0)
21
+ rspec-mocks (~> 3.10.0)
22
+ rspec-core (3.10.1)
23
+ rspec-support (~> 3.10.0)
24
+ rspec-expectations (3.10.1)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.10.0)
27
+ rspec-mocks (3.10.1)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.10.0)
30
+ rspec-support (3.10.1)
31
+ rubocop (1.7.0)
32
+ parallel (~> 1.10)
33
+ parser (>= 2.7.1.5)
34
+ rainbow (>= 2.2.2, < 4.0)
35
+ regexp_parser (>= 1.8, < 3.0)
36
+ rexml
37
+ rubocop-ast (>= 1.2.0, < 2.0)
38
+ ruby-progressbar (~> 1.7)
39
+ unicode-display_width (>= 1.4.0, < 2.0)
40
+ rubocop-ast (1.3.0)
41
+ parser (>= 2.7.1.5)
42
+ rubocop-performance (1.9.1)
43
+ rubocop (>= 0.90.0, < 2.0)
44
+ rubocop-ast (>= 0.4.0)
45
+ rubocop-rake (0.5.1)
46
+ rubocop
47
+ rubocop-rspec (2.1.0)
48
+ rubocop (~> 1.0)
49
+ rubocop-ast (>= 1.1.0)
50
+ ruby-progressbar (1.10.1)
51
+ unicode-display_width (1.7.0)
52
+
53
+ PLATFORMS
54
+ x86_64-darwin-19
55
+
56
+ DEPENDENCIES
57
+ moss_generator!
58
+ rake (~> 13.0)
59
+ rspec (~> 3.0)
60
+ rubocop (~> 1.7)
61
+ rubocop-performance (~> 1.9)
62
+ rubocop-rake (~> 0.5)
63
+ rubocop-rspec (~> 2.1)
64
+
65
+ BUNDLED WITH
66
+ 2.2.3
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 frdrkolsson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,57 @@
1
+ # MossGenerator
2
+
3
+ Generate CSV-formatted string from specified data to be able to create a [MOSS (Mini One Stop Shop)](https://www.skatteverket.se/foretagochorganisationer/moms/deklareramoms/mossredovisningavmomspadigitalatjanster.4.3aa8c78a1466c5845876a05.html) file for Skatteverket.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'moss_generator'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install moss_generator
20
+
21
+ ## Usage
22
+
23
+ ### Stripe
24
+ ```ruby
25
+ # charges - array of charges in json-format from stripe
26
+ # vat_number - the company's vat number
27
+ # period - for which period of the year this report should be for
28
+ # year - corresponding year for the period
29
+ MossGenerator::Stripe.call(charges, vat_number, period, year)
30
+ ```
31
+
32
+ ## Development
33
+
34
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
35
+
36
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
+
38
+ ## Vat Rates
39
+
40
+ ```
41
+ * Current Vat rates fetched at 2021/01/11
42
+ * Vat rates in spec file from 2021/01/11
43
+ ```
44
+
45
+
46
+ Vat rates currently needs to be updated manually in `config/vat_rates.json`.
47
+ [VatLayer](https://vatlayer.com) has a nice API one could use (100 requests for free each month) to make this process automatic. Until then, do a request through their api and copy JSON-response to file and remove status from response.
48
+
49
+ Rates can also be found on the official website of the European Union [https://europa.eu/youreurope/business/taxation/vat/vat-rules-rates/index_en.htm#shortcut-8](https://europa.eu/youreurope/business/taxation/vat/vat-rules-rates/index_en.htm#shortcut-8), updates two times a year.
50
+
51
+ ## Contributing
52
+
53
+ Bug reports and pull requests are welcome on GitHub at https://github.com/standout/moss_generator.
54
+
55
+ ## License
56
+
57
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'moss_generator'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,383 @@
1
+ {
2
+ "AT": {
3
+ "country_name": "Austria",
4
+ "standard_rate": 20,
5
+ "reduced_rates": {
6
+ "foodstuffs": 10,
7
+ "books": 5,
8
+ "pharmaceuticals": 10,
9
+ "passenger transport": 10,
10
+ "newspapers": 5,
11
+ "admission to cultural events": 5,
12
+ "hotels": 5,
13
+ "admission to entertainment events": 5
14
+ }
15
+ },
16
+ "BE": {
17
+ "country_name": "Belgium",
18
+ "standard_rate": 21,
19
+ "reduced_rates": {
20
+ "restaurants": 12,
21
+ "foodstuffs": 6,
22
+ "water": 6,
23
+ "pharmaceuticals": 6,
24
+ "medical": 6,
25
+ "books": 6,
26
+ "newspapers": 6,
27
+ "hotels": 6,
28
+ "admission to cultural events": 6,
29
+ "admission to entertainment events": 6
30
+ }
31
+ },
32
+ "BG": {
33
+ "country_name": "Bulgaria",
34
+ "standard_rate": 20,
35
+ "reduced_rates": {
36
+ "hotels": 9
37
+ }
38
+ },
39
+ "HR": {
40
+ "country_name": "Croatia",
41
+ "standard_rate": 25,
42
+ "reduced_rates": {
43
+ "hotels": 13,
44
+ "newspapers": 13
45
+ }
46
+ },
47
+ "CY": {
48
+ "country_name": "Cyprus",
49
+ "standard_rate": 19,
50
+ "reduced_rates": {
51
+ "hotels": 9,
52
+ "restaurants": 9,
53
+ "foodstuffs": 5,
54
+ "books": 5,
55
+ "pharmaceuticals": 5,
56
+ "medical": 5,
57
+ "passenger transport": 5,
58
+ "newspapers": 5,
59
+ "admission to cultural events": 5,
60
+ "admission to entertainment events": 5,
61
+ "admission to sporting events": 5
62
+ }
63
+ },
64
+ "CZ": {
65
+ "country_name": "Czech Republic",
66
+ "standard_rate": 21,
67
+ "reduced_rates": {
68
+ "foodstuffs": 15,
69
+ "medical": 10,
70
+ "pharmaceuticals": 10,
71
+ "passenger transport": 15,
72
+ "newspapers": 15,
73
+ "admission to cultural events": 15,
74
+ "admission to sporting events": 15,
75
+ "admission to entertainment events": 15,
76
+ "hotels": 15,
77
+ "books": 10,
78
+ "baby foodstuffs": 10
79
+ }
80
+ },
81
+ "DK": {
82
+ "country_name": "Denmark",
83
+ "standard_rate": 25,
84
+ "reduced_rates": null
85
+ },
86
+ "EE": {
87
+ "country_name": "Estonia",
88
+ "standard_rate": 20,
89
+ "reduced_rates": {
90
+ "books": 9,
91
+ "pharmaceuticals": 9,
92
+ "medical": 9,
93
+ "hotels": 9
94
+ }
95
+ },
96
+ "FI": {
97
+ "country_name": "Finland",
98
+ "standard_rate": 24,
99
+ "reduced_rates": {
100
+ "foodstuffs": 14,
101
+ "restaurants": 14,
102
+ "books": 10,
103
+ "pharmaceuticals": 10,
104
+ "passenger transport": 10,
105
+ "newspapers": 10,
106
+ "admission to cultural events": 10,
107
+ "admission to sporting events": 10,
108
+ "admission to entertainment events": 10,
109
+ "hotels": 10
110
+ }
111
+ },
112
+ "FR": {
113
+ "country_name": "France",
114
+ "standard_rate": 20,
115
+ "reduced_rates": {
116
+ "pharmaceuticals": 2.1,
117
+ "passenger transport": 10,
118
+ "admission to cultural events": 10,
119
+ "admission to sporting events": 10,
120
+ "admission to entertainment events": 10,
121
+ "hotels": 10,
122
+ "accommodation": 10,
123
+ "restaurants": 10,
124
+ "medical": 5.5,
125
+ "foodstuffs": 5.5,
126
+ "e-books": 5.5,
127
+ "books": 5.5,
128
+ "newspapers": 2.1
129
+ }
130
+ },
131
+ "DE": {
132
+ "country_name": "Germany",
133
+ "standard_rate": 19,
134
+ "reduced_rates": {
135
+ "foodstuffs": 7,
136
+ "books": 7,
137
+ "medical": 7,
138
+ "passenger transport": 7,
139
+ "newspapers": 7,
140
+ "admission to cultural events": 7,
141
+ "admission to entertainment events": 7,
142
+ "hotels": 7
143
+ }
144
+ },
145
+ "GR": {
146
+ "country_name": "Greece",
147
+ "standard_rate": 24,
148
+ "reduced_rates": {
149
+ "foodstuffs": 13,
150
+ "pharmaceuticals": 13,
151
+ "medical": 13,
152
+ "admission to cultural events": 13,
153
+ "admission to sporting events": 13,
154
+ "admission to entertainment events": 13,
155
+ "books": 6.5,
156
+ "newspapers": 6.5,
157
+ "hotels": 6.5
158
+ }
159
+ },
160
+ "HU": {
161
+ "country_name": "Hungary",
162
+ "standard_rate": 27,
163
+ "reduced_rates": {
164
+ "foodstuffs": 18,
165
+ "hotels": 18,
166
+ "books": 5,
167
+ "pharmaceuticals": 5,
168
+ "medical": 5
169
+ }
170
+ },
171
+ "IE": {
172
+ "country_name": "Ireland",
173
+ "standard_rate": 21,
174
+ "reduced_rates": {
175
+ "medical": 13.5,
176
+ "newspapers": 9,
177
+ "admission to cultural events": 13.5,
178
+ "admission to sporting events": 0,
179
+ "admission to entertainment events": 13.5,
180
+ "hotels": 13.5,
181
+ "restaurants": 13.5,
182
+ "foodstuffs": 4.8,
183
+ "books": 0,
184
+ "childrens clothing": 0
185
+ }
186
+ },
187
+ "IT": {
188
+ "country_name": "Italy",
189
+ "standard_rate": 22,
190
+ "reduced_rates": {
191
+ "pharmaceuticals": 10,
192
+ "passenger transport": 10,
193
+ "admission to cultural events": 10,
194
+ "admission to entertainment events": 10,
195
+ "hotels": 10,
196
+ "restaurants": 10,
197
+ "foodstuffs": 4,
198
+ "medical": 4,
199
+ "books": 4,
200
+ "e-books": 4
201
+ }
202
+ },
203
+ "LV": {
204
+ "country_name": "Latvia",
205
+ "standard_rate": 21,
206
+ "reduced_rates": {
207
+ "books": 12,
208
+ "pharmaceuticals": 12,
209
+ "medical": 12,
210
+ "newspapers": 12,
211
+ "hotels": 12
212
+ }
213
+ },
214
+ "LT": {
215
+ "country_name": "Lithuania",
216
+ "standard_rate": 21,
217
+ "reduced_rates": {
218
+ "books": 9,
219
+ "pharmaceuticals": 5,
220
+ "medical": 5
221
+ }
222
+ },
223
+ "LU": {
224
+ "country_name": "Luxembourg",
225
+ "standard_rate": 17,
226
+ "reduced_rates": {
227
+ "wine": 14,
228
+ "domestic fuel": 14,
229
+ "advertising": 14,
230
+ "bikes": 8,
231
+ "domestic services": 8,
232
+ "books": 3,
233
+ "foodstuffs": 3,
234
+ "pharmaceuticals": 3,
235
+ "medical": 3,
236
+ "passenger transport": 3,
237
+ "newspapers": 3,
238
+ "admission to cultural events": 3,
239
+ "admission to sporting events": 3,
240
+ "admission to entertainment events": 3,
241
+ "hotels": 3,
242
+ "restaurants": 3,
243
+ "e-books": 3
244
+ }
245
+ },
246
+ "MT": {
247
+ "country_name": "Malta",
248
+ "standard_rate": 18,
249
+ "reduced_rates": {
250
+ "hotels": 7,
251
+ "books": 5,
252
+ "medical": 5,
253
+ "newspapers": 5,
254
+ "admission to cultural events": 5,
255
+ "e-books": 5,
256
+ "foodstuffs": 0,
257
+ "pharmaceuticals": 0
258
+ }
259
+ },
260
+ "NL": {
261
+ "country_name": "Netherlands",
262
+ "standard_rate": 21,
263
+ "reduced_rates": {
264
+ "foodstuffs": 9,
265
+ "books": 9,
266
+ "pharmaceuticals": 9,
267
+ "medical": 9,
268
+ "passenger transport": 9,
269
+ "admission to cultural events": 9,
270
+ "admission to entertainment events": 9,
271
+ "hotels": 9,
272
+ "accommodation": 9
273
+ }
274
+ },
275
+ "PL": {
276
+ "country_name": "Poland",
277
+ "standard_rate": 23,
278
+ "reduced_rates": {
279
+ "pharmaceuticals": 8,
280
+ "medical": 8,
281
+ "passenger transport": 8,
282
+ "newspapers": 8,
283
+ "hotels": 8,
284
+ "restaurants": 8,
285
+ "admission to cultural events": 8,
286
+ "admission to sporting events": 8,
287
+ "admission to entertainment events": 8,
288
+ "foodstuffs": 5
289
+ }
290
+ },
291
+ "PT": {
292
+ "country_name": "Portugal",
293
+ "standard_rate": 23,
294
+ "reduced_rates": {
295
+ "foodstuffs": 6,
296
+ "agricultural supplies": 13,
297
+ "books": 6,
298
+ "pharmaceuticals": 6,
299
+ "medical": 6,
300
+ "newspapers": 6,
301
+ "hotels": 6,
302
+ "passenger transport": 6
303
+ }
304
+ },
305
+ "RO": {
306
+ "country_name": "Romania",
307
+ "standard_rate": 19,
308
+ "reduced_rates": {
309
+ "books": 9,
310
+ "pharmaceuticals": 9,
311
+ "medical": 9,
312
+ "newspapers": 9,
313
+ "admission to cultural events": 9,
314
+ "admission to entertainment events": 9,
315
+ "hotels": 9,
316
+ "foodstuffs": 9,
317
+ "social housing": 5
318
+ }
319
+ },
320
+ "SK": {
321
+ "country_name": "Slovakia",
322
+ "standard_rate": 20,
323
+ "reduced_rates": {
324
+ "books": 10,
325
+ "foodstuffs": 10,
326
+ "medical": 10,
327
+ "pharmaceuticals": 10,
328
+ "admission to cultural events": 10,
329
+ "admission to entertainment events": 10
330
+ }
331
+ },
332
+ "SI": {
333
+ "country_name": "Slovenia",
334
+ "standard_rate": 22,
335
+ "reduced_rates": {
336
+ "foodstuffs": 9.5,
337
+ "books": 9.5,
338
+ "pharmaceuticals": 9.5,
339
+ "medical": 9.5,
340
+ "newspapers": 9.5,
341
+ "admission to sporting events": 9.5,
342
+ "admission to cultural events": 9.5,
343
+ "admission to entertainment events": 9.5,
344
+ "hotels": 9.5
345
+ }
346
+ },
347
+ "ES": {
348
+ "country_name": "Spain",
349
+ "standard_rate": 21,
350
+ "reduced_rates": {
351
+ "medical": 10,
352
+ "pharmaceuticals": 10,
353
+ "passenger transport": 10,
354
+ "admission to cultural events": 10,
355
+ "admission to sporting events": 10,
356
+ "admission to entertainment events": 10,
357
+ "foodstuffs": 4,
358
+ "newspapers": 4
359
+ }
360
+ },
361
+ "SE": {
362
+ "country_name": "Sweden",
363
+ "standard_rate": 25,
364
+ "reduced_rates": {
365
+ "foodstuffs": 12,
366
+ "books": 6
367
+ }
368
+ },
369
+ "GB": {
370
+ "country_name": "United Kingdom",
371
+ "standard_rate": 20,
372
+ "reduced_rates": {
373
+ "property renovations": 5,
374
+ "foodstuffs": 0,
375
+ "books": 0,
376
+ "pharmaceuticals": 0,
377
+ "medical": 0,
378
+ "passenger transport": 0,
379
+ "newspapers": 0,
380
+ "childrens clothing": 0
381
+ }
382
+ }
383
+ }
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'moss_generator/version'
4
+ require_relative 'moss_generator/vat_rate'
5
+ require_relative 'moss_generator/stripe'
6
+
7
+ # Generate CSV-formatted string for MOSS report
8
+ module MossGenerator
9
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csv'
4
+ require 'moss_generator/stripe_charge_row'
5
+
6
+ module MossGenerator
7
+ # Generate MOSS CSV string from Stripe charges
8
+ class Stripe
9
+ class NoTurnoverCountryError < StandardError; end
10
+
11
+ attr_reader :charges, :vat_number, :period, :year
12
+
13
+ def initialize(charges, vat_number, period, year)
14
+ @charges = charges
15
+ @vat_number = vat_number
16
+ @period = period
17
+ @year = year
18
+ end
19
+
20
+ def self.call(charges, vat_number, period, year)
21
+ new(charges, vat_number, period, year).call
22
+ end
23
+
24
+ def call
25
+ CSV.generate(csv_options) do |csv|
26
+ csv << first_row
27
+ csv << second_row
28
+ generate_charges_rows(csv)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def first_row
35
+ ['MOSS_001']
36
+ end
37
+
38
+ def second_row
39
+ [vat_number, period, year]
40
+ end
41
+
42
+ def generate_charges_rows(csv)
43
+ build_charges_rows.each { |row| csv << row }
44
+ end
45
+
46
+ def group_charges_rows
47
+ charges_rows = charges.map do |charge|
48
+ MossGenerator::StripeChargeRow.new(charge)
49
+ end
50
+ charges_rows.group_by(&:country_code)
51
+ end
52
+
53
+ def build_charges_rows
54
+ group_charges_rows.map do |country, charges|
55
+ next if country == 'SE' && turnover_country == 'SE'
56
+ next if charges.first.vat_rate.nil?
57
+
58
+ country_row(country, charges)
59
+ end.compact
60
+ end
61
+
62
+ def country_row(country, charges)
63
+ [turnover_country,
64
+ country,
65
+ format_to_two_decimals(charges.first.vat_rate),
66
+ format_to_two_decimals(charges.sum(&:amount)),
67
+ format_to_two_decimals(charges.sum(&:vat_amount))]
68
+ end
69
+
70
+ def csv_options
71
+ { col_sep: column_separator, row_sep: row_separator }
72
+ end
73
+
74
+ def row_separator
75
+ ";\r\n"
76
+ end
77
+
78
+ def column_separator
79
+ ';'
80
+ end
81
+
82
+ def format_to_two_decimals(number)
83
+ format('%.2f', number).sub('.', ',')
84
+ end
85
+
86
+ # vat_numbers first two characters shall contain the country code
87
+ def turnover_country
88
+ country_code = vat_number[0...2]
89
+ if country_code.nil?
90
+ raise NoTurnoverCountryError,
91
+ "vat_number: #{vat_number}"
92
+ end
93
+ return country_code if country_code == 'SE'
94
+
95
+ vat_number
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moss_generator/vat_rate'
4
+
5
+ module MossGenerator
6
+ # Parse charge data from single Stripe charge
7
+ class StripeChargeRow
8
+ class NoConsumptionCountryError < StandardError; end
9
+
10
+ class NoVatRateForCountryError < StandardError; end
11
+
12
+ attr_reader :charge
13
+
14
+ def initialize(charge)
15
+ @charge = charge
16
+ end
17
+
18
+ def country_code
19
+ return fetch_country_code unless fetch_country_code.nil?
20
+
21
+ raise NoConsumptionCountryError, "charge: #{charge}"
22
+ end
23
+
24
+ def amount
25
+ charge.dig('balance_transaction', 'amount')
26
+ end
27
+
28
+ def vat_rate
29
+ @vat_rate = MossGenerator::VatRate.for(country_code)
30
+ end
31
+
32
+ def vat_amount
33
+ amount * vat_rate_calculatable_percent
34
+ end
35
+
36
+ private
37
+
38
+ def vat_rate_calculatable_percent
39
+ (vat_rate.to_f / 100)
40
+ end
41
+
42
+ def fetch_country_code
43
+ charge.dig('payment_method_details', 'card', 'country') ||
44
+ charge.dig('billing_details', 'address', 'country')
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module MossGenerator
6
+ # Returns standard vat rate from vat_rates.json
7
+ class VatRate
8
+ class << self
9
+ def for(country_code)
10
+ vat_rates.dig(country_code, 'standard_rate')
11
+ end
12
+
13
+ private
14
+
15
+ def vat_rates
16
+ JSON.parse(File.read(vat_rates_path))
17
+ end
18
+
19
+ def vat_rates_path
20
+ File.join(File.dirname(File.expand_path(__FILE__)),
21
+ 'config/vat_rates.json')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MossGenerator
4
+ VERSION = '0.1.1'
5
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/moss_generator/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'moss_generator'
7
+ spec.version = MossGenerator::VERSION
8
+ spec.authors = ['frdrkolsson']
9
+ spec.email = ['fredrik.olsson@standout.se']
10
+
11
+ spec.summary = 'Skatteverket Mini One Stop Shop(MOSS) generator.'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/standout/moss_generator'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = spec.homepage
19
+ spec.metadata['changelog_uri'] = spec.homepage
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added
23
+ # into git.
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{\A(?:test|spec|features)/})
27
+ end
28
+ end
29
+ spec.bindir = 'exe'
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+
33
+ # Uncomment to register a new dependency of your gem
34
+ # spec.add_dependency "example-gem", "~> 1.0"
35
+
36
+ # For more information and examples about making a new gem, checkout our
37
+ # guide at: https://bundler.io/guides/creating_gem.html
38
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: moss_generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - frdrkolsson
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-01-12 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Skatteverket Mini One Stop Shop(MOSS) generator.
14
+ email:
15
+ - fredrik.olsson@standout.se
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".github/workflows/gempush.yml"
21
+ - ".github/workflows/main.yml"
22
+ - ".gitignore"
23
+ - ".rspec"
24
+ - ".rubocop.yml"
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - LICENSE.txt
28
+ - README.md
29
+ - Rakefile
30
+ - bin/console
31
+ - bin/setup
32
+ - config/vat_rates.json
33
+ - lib/moss_generator.rb
34
+ - lib/moss_generator/stripe.rb
35
+ - lib/moss_generator/stripe_charge_row.rb
36
+ - lib/moss_generator/vat_rate.rb
37
+ - lib/moss_generator/version.rb
38
+ - moss_generator.gemspec
39
+ homepage: https://github.com/standout/moss_generator
40
+ licenses:
41
+ - MIT
42
+ metadata:
43
+ homepage_uri: https://github.com/standout/moss_generator
44
+ source_code_uri: https://github.com/standout/moss_generator
45
+ changelog_uri: https://github.com/standout/moss_generator
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.5.0
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.1.4
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Skatteverket Mini One Stop Shop(MOSS) generator.
65
+ test_files: []