polish_invoicer 0.0.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.
- data/.gitignore +16 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +145 -0
- data/Rakefile +9 -0
- data/doc/invoice.html +1 -0
- data/doc/invoice.pdf +0 -0
- data/doc/invoice.rb +21 -0
- data/doc/proforma.html +1 -0
- data/doc/proforma.pdf +0 -0
- data/doc/proforma.rb +23 -0
- data/lib/polish_invoicer.rb +14 -0
- data/lib/polish_invoicer/invoice.rb +93 -0
- data/lib/polish_invoicer/presenter.rb +63 -0
- data/lib/polish_invoicer/validator.rb +125 -0
- data/lib/polish_invoicer/vat.rb +36 -0
- data/lib/polish_invoicer/version.rb +3 -0
- data/lib/polish_invoicer/writer.rb +34 -0
- data/polish_invoicer.gemspec +26 -0
- data/test/invoice_test.rb +115 -0
- data/test/presenter_test.rb +56 -0
- data/test/test_helper.rb +19 -0
- data/test/validator_test.rb +176 -0
- data/test/vat_test.rb +25 -0
- data/test/writer_test.rb +35 -0
- data/tpl/invoice.slim +136 -0
- metadata +149 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
module PolishInvoicer
|
2
|
+
class Vat
|
3
|
+
def self.rates
|
4
|
+
[23, 8, 5, 0, -1] # -1 oznacza zwolniony z VAT
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.valid?(rate)
|
8
|
+
rates.include?(rate)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Czy stawka VAT to "zwolniony"?
|
12
|
+
def self.zw?(rate)
|
13
|
+
rate == -1
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.to_s(rate)
|
17
|
+
hash.invert[rate]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Potrzebne do obliczeń netto/vat/brutto
|
21
|
+
def self.to_i(rate)
|
22
|
+
(rate != -1) ? rate : 0
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def self.hash
|
27
|
+
h = {}
|
28
|
+
rates.each do |r|
|
29
|
+
name = "#{r}%"
|
30
|
+
name = 'zw.' if r == -1
|
31
|
+
h[name] = r
|
32
|
+
end
|
33
|
+
h
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PolishInvoicer
|
2
|
+
class Writer
|
3
|
+
attr_accessor :invoice, :template_path
|
4
|
+
attr_accessor :logger, :wkhtmltopdf_command
|
5
|
+
|
6
|
+
def initialize(invoice)
|
7
|
+
@invoice = invoice
|
8
|
+
default_template_path = File.expand_path('../../../tpl/invoice.slim', __FILE__)
|
9
|
+
@template_path = @invoice.template_path || default_template_path
|
10
|
+
@logger = @invoice.logger
|
11
|
+
@wkhtmltopdf_command = @invoice.wkhtmltopdf_command
|
12
|
+
end
|
13
|
+
|
14
|
+
def save_to_html(path)
|
15
|
+
create_writer
|
16
|
+
@writer.save_to_html(path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def save_to_pdf(path)
|
20
|
+
create_writer
|
21
|
+
@writer.save_to_pdf(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def create_writer
|
26
|
+
@writer = Slim2pdf::Writer.new(template_path)
|
27
|
+
@writer.wkhtmltopdf_command = wkhtmltopdf_command
|
28
|
+
@writer.logger = logger
|
29
|
+
data = @invoice.to_hash
|
30
|
+
@writer.data = data
|
31
|
+
@writer.footer_text = data[:footer]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'polish_invoicer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "polish_invoicer"
|
8
|
+
spec.version = PolishInvoicer::VERSION
|
9
|
+
spec.authors = ["Piotr Macuk"]
|
10
|
+
spec.email = ["piotr@macuk.pl"]
|
11
|
+
spec.description = %q{Creates polish invoices and proforms as HTML or PDF files}
|
12
|
+
spec.summary = %q{Creates polish invoices and proforms as HTML or PDF files}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "simplecov"
|
24
|
+
|
25
|
+
spec.add_dependency 'slim2pdf', '~> 0.0.2'
|
26
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
module PolishInvoicer
|
5
|
+
class InvoiceTest < MiniTest::Unit::TestCase
|
6
|
+
def test_init
|
7
|
+
i = Invoice.new
|
8
|
+
assert i.is_a?(Invoice)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_set_available_param
|
12
|
+
i = Invoice.new({number: '1/2014'})
|
13
|
+
assert_equal '1/2014', i.number
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_set_unavailable_param
|
17
|
+
assert_raises(RuntimeError) { i = Invoice.new({test: 'abc'}) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_validation_delegation
|
21
|
+
i = Invoice.new
|
22
|
+
assert_equal false, i.valid?
|
23
|
+
assert i.errors[:number]
|
24
|
+
i.number = '1/2014'
|
25
|
+
i.valid?
|
26
|
+
assert_nil i.errors[:number]
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_net_value
|
30
|
+
i = Invoice.new({price: 123.45, gross_price: false, vat: 23})
|
31
|
+
assert_in_delta 123.45, i.net_value, 0.01
|
32
|
+
i.gross_price = true
|
33
|
+
assert_in_delta 100.37, i.net_value, 0.01
|
34
|
+
i.vat = 0
|
35
|
+
assert_in_delta 123.45, i.net_value, 0.01
|
36
|
+
i.vat = -1
|
37
|
+
assert_in_delta 123.45, i.net_value, 0.01
|
38
|
+
i.gross_price = false
|
39
|
+
i.vat = 0
|
40
|
+
assert_in_delta 123.45, i.net_value, 0.01
|
41
|
+
i.vat = -1
|
42
|
+
assert_in_delta 123.45, i.net_value, 0.01
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_vat_value
|
46
|
+
i = Invoice.new({price: 123.45, gross_price: false, vat: 23})
|
47
|
+
assert_in_delta 28.39, i.vat_value, 0.01
|
48
|
+
i.gross_price = true
|
49
|
+
assert_in_delta 23.08, i.vat_value, 0.01
|
50
|
+
i.vat = 0
|
51
|
+
assert_equal 0.00, i.vat_value
|
52
|
+
i.vat = -1
|
53
|
+
assert_equal 0.00, i.vat_value
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_gross_value
|
57
|
+
i = Invoice.new({price: 123.45, gross_price: false, vat: 23})
|
58
|
+
assert_in_delta 151.84, i.gross_value, 0.01
|
59
|
+
i.gross_price = true
|
60
|
+
assert_in_delta 123.45, i.gross_value, 0.01
|
61
|
+
i.vat = 0
|
62
|
+
assert_in_delta 123.45, i.gross_value, 0.01
|
63
|
+
i.vat = -1
|
64
|
+
assert_in_delta 123.45, i.gross_value, 0.01
|
65
|
+
i.gross_price = false
|
66
|
+
i.vat = 0
|
67
|
+
assert_in_delta 123.45, i.gross_value, 0.01
|
68
|
+
i.vat = -1
|
69
|
+
assert_in_delta 123.45, i.gross_value, 0.01
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_defaults
|
73
|
+
i = Invoice.new
|
74
|
+
assert i.gross_price
|
75
|
+
assert 23, i.vat
|
76
|
+
assert 'Przelew', i.payment_type
|
77
|
+
assert i.paid
|
78
|
+
assert_equal false, i.proforma
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_raise_when_save_to_html_and_not_valid
|
82
|
+
i = Invoice.new
|
83
|
+
assert_raises(RuntimeError) { i.save_to_html('/tmp/test.html') }
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_raise_when_save_to_pdf_and_not_valid
|
87
|
+
i = Invoice.new
|
88
|
+
assert_raises(RuntimeError) { i.save_to_pdf('/tmp/test.pdf') }
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_save_to_html
|
92
|
+
i = create_valid_invoice
|
93
|
+
path = '/tmp/test.html'
|
94
|
+
i.save_to_html(path)
|
95
|
+
assert File.exists?(path)
|
96
|
+
File.unlink(path)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_save_to_pdf
|
100
|
+
i = create_valid_invoice
|
101
|
+
path = '/tmp/test.pdf'
|
102
|
+
i.save_to_pdf(path)
|
103
|
+
assert File.exists?(path)
|
104
|
+
File.unlink(path)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_to_hash
|
108
|
+
i = Invoice.new({price: 123.45, gross_price: false})
|
109
|
+
h = i.to_hash
|
110
|
+
assert h[:paid] # default
|
111
|
+
assert_equal false, h[:gross_price] # params
|
112
|
+
assert_equal '123,45', h[:net_value] # presenter
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
module PolishInvoicer
|
5
|
+
class PresenterTest < MiniTest::Unit::TestCase
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@invoice = OpenStruct.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_format_dates
|
13
|
+
@invoice.trade_date = Date.parse('2014-01-01')
|
14
|
+
@invoice.create_date = Date.parse('2014-01-15')
|
15
|
+
@invoice.payment_date = Date.parse('2014-01-30')
|
16
|
+
data = Presenter.new(@invoice).data
|
17
|
+
assert_equal '01.01.2014', data[:trade_date]
|
18
|
+
assert_equal '15.01.2014', data[:create_date]
|
19
|
+
assert_equal '30.01.2014', data[:payment_date]
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_format_prices
|
23
|
+
@invoice.net_value = 123.4567
|
24
|
+
@invoice.vat_value = 23.9876
|
25
|
+
@invoice.gross_value = 456.3378
|
26
|
+
data = Presenter.new(@invoice).data
|
27
|
+
assert_equal '123,46', data[:net_value]
|
28
|
+
assert_equal '23,99', data[:vat_value]
|
29
|
+
assert_equal '456,34', data[:gross_value]
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_format_comments
|
33
|
+
@invoice.comments = nil
|
34
|
+
data = Presenter.new(@invoice).data
|
35
|
+
assert_equal [], data[:comments]
|
36
|
+
@invoice.comments = 'Test'
|
37
|
+
data = Presenter.new(@invoice).data
|
38
|
+
assert_equal ['Test'], data[:comments]
|
39
|
+
@invoice.comments = ['A', 'B']
|
40
|
+
data = Presenter.new(@invoice).data
|
41
|
+
assert_equal ['A', 'B'], data[:comments]
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_vat
|
45
|
+
@invoice.vat = 23
|
46
|
+
data = Presenter.new(@invoice).data
|
47
|
+
assert_equal '23%', data[:vat]
|
48
|
+
@invoice.vat = 0
|
49
|
+
data = Presenter.new(@invoice).data
|
50
|
+
assert_equal '0%', data[:vat]
|
51
|
+
@invoice.vat = -1
|
52
|
+
data = Presenter.new(@invoice).data
|
53
|
+
assert_equal 'zw.', data[:vat]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter '/test/'
|
4
|
+
end
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
7
|
+
require 'polish_invoicer'
|
8
|
+
require 'minitest/autorun'
|
9
|
+
|
10
|
+
def create_valid_invoice
|
11
|
+
invoice = PolishInvoicer::Invoice.new({
|
12
|
+
number: '1/2014', create_date: Date.today, trade_date: Date.today,
|
13
|
+
seller: ['Seller'], buyer: ['Buyer'],
|
14
|
+
seller_nip: '123-123-22-33', buyer_nip: '554-333-22-11',
|
15
|
+
item_name: 'Title', price: 123.45, payment_date: Date.today
|
16
|
+
})
|
17
|
+
assert invoice.valid?
|
18
|
+
invoice
|
19
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module PolishInvoicer
|
4
|
+
class ValidatorTest < MiniTest::Unit::TestCase
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@invoice = OpenStruct.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def check_error(field, value=nil)
|
12
|
+
@invoice.send("#{field}=", value)
|
13
|
+
v = Validator.new(@invoice)
|
14
|
+
v.valid?
|
15
|
+
assert v.errors[field]
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_ok(field, value=nil)
|
19
|
+
@invoice.send("#{field}=", value)
|
20
|
+
v = Validator.new(@invoice)
|
21
|
+
v.valid?
|
22
|
+
assert_nil v.errors[field]
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_number_validation
|
26
|
+
check_error(:number)
|
27
|
+
check_ok(:number, '1/2014')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_create_date_validation
|
31
|
+
check_error(:create_date)
|
32
|
+
check_error(:create_date, 'test')
|
33
|
+
check_error(:create_date, 100)
|
34
|
+
check_error(:create_date, '2014-01-01')
|
35
|
+
check_ok(:create_date, Date.parse('2014-01-01'))
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_trade_date_validation
|
39
|
+
check_error(:trade_date)
|
40
|
+
check_error(:trade_date, 'test')
|
41
|
+
check_error(:trade_date, 100)
|
42
|
+
check_error(:trade_date, '2014-01-01')
|
43
|
+
check_ok(:trade_date, Date.parse('2014-01-01'))
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_seller_validation
|
47
|
+
check_error(:seller)
|
48
|
+
check_error(:seller, 'test')
|
49
|
+
check_ok(:seller, ['Jan Nowak', 'Gdynia'])
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_buyer_validation
|
53
|
+
check_error(:buyer)
|
54
|
+
check_error(:buyer, 'test')
|
55
|
+
check_ok(:buyer, ['Jan Nowak', 'Gdynia'])
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_item_name_validation
|
59
|
+
check_error(:item_name)
|
60
|
+
check_ok(:item_name, 'test')
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_price_validation
|
64
|
+
check_error(:price)
|
65
|
+
check_error(:price, 'test')
|
66
|
+
check_error(:price, '100')
|
67
|
+
check_error(:price, -10)
|
68
|
+
check_ok(:price, 19.99)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_gross_price_validation
|
72
|
+
check_error(:gross_price)
|
73
|
+
check_error(:gross_price, 'test')
|
74
|
+
check_ok(:gross_price, true)
|
75
|
+
check_ok(:gross_price, false)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_vat_validation
|
79
|
+
check_error(:vat)
|
80
|
+
check_error(:vat, '23')
|
81
|
+
check_error(:vat, 100)
|
82
|
+
check_ok(:vat, 8)
|
83
|
+
check_ok(:vat, -1)
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_payment_type_validation
|
87
|
+
check_error(:payment_type)
|
88
|
+
check_ok(:payment_type, 'Przelew')
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_payment_date_validation
|
92
|
+
check_error(:payment_date)
|
93
|
+
check_error(:payment_date, 'test')
|
94
|
+
check_error(:payment_date, 100)
|
95
|
+
check_error(:payment_date, '2014-01-01')
|
96
|
+
check_ok(:payment_date, Date.parse('2014-01-01'))
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_paid_validation
|
100
|
+
check_error(:paid)
|
101
|
+
check_error(:paid, 'test')
|
102
|
+
check_ok(:paid, true)
|
103
|
+
check_ok(:paid, false)
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_proforma_validation
|
107
|
+
check_error(:proforma)
|
108
|
+
check_error(:proforma, 'test')
|
109
|
+
check_ok(:proforma, true)
|
110
|
+
check_ok(:proforma, false)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_proforma_could_not_be_paid
|
114
|
+
@invoice.paid = true
|
115
|
+
@invoice.proforma = true
|
116
|
+
v = Validator.new(@invoice)
|
117
|
+
v.valid?
|
118
|
+
assert v.errors[:paid]
|
119
|
+
@invoice.paid = false
|
120
|
+
v = Validator.new(@invoice)
|
121
|
+
v.valid?
|
122
|
+
assert_nil v.errors[:paid]
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_seller_and_buyer_nip_presence
|
126
|
+
check_error(:seller_nip)
|
127
|
+
check_error(:buyer_nip)
|
128
|
+
check_ok(:seller_nip, '123')
|
129
|
+
check_ok(:buyer_nip, '123')
|
130
|
+
end
|
131
|
+
|
132
|
+
def check_dates_ok(create_date, trade_date, msg=nil)
|
133
|
+
@invoice.create_date = Date.parse(create_date)
|
134
|
+
@invoice.trade_date = Date.parse(trade_date)
|
135
|
+
v = Validator.new(@invoice); v.valid?
|
136
|
+
assert_nil v.errors[:create_date], msg
|
137
|
+
end
|
138
|
+
|
139
|
+
def check_dates_error(create_date, trade_date, msg=nil)
|
140
|
+
@invoice.create_date = Date.parse(create_date)
|
141
|
+
@invoice.trade_date = Date.parse(trade_date)
|
142
|
+
v = Validator.new(@invoice); v.valid?
|
143
|
+
assert v.errors[:create_date], msg
|
144
|
+
end
|
145
|
+
|
146
|
+
# data wystawienia max 30 dni przed wykonaniem usługi
|
147
|
+
def test_create_and_trade_date_correlation_before_trade
|
148
|
+
check_dates_ok('2014-01-01', '2014-01-01', 'B1')
|
149
|
+
check_dates_ok('2014-01-01', '2014-01-31', 'B2')
|
150
|
+
check_dates_error('2014-01-01', '2014-02-01', 'B3')
|
151
|
+
check_dates_ok('2014-01-02', '2014-02-01', 'B4')
|
152
|
+
end
|
153
|
+
|
154
|
+
# data wystawienie max 15 dnia następnego miesiąca po wykonaniu usługi
|
155
|
+
def test_create_and_trade_date_correlation_after_trade
|
156
|
+
check_dates_ok('2014-02-15', '2014-01-01', 'A1')
|
157
|
+
check_dates_error('2014-02-16', '2014-01-01', 'A2')
|
158
|
+
check_dates_error('2014-02-16', '2014-01-02', 'A3')
|
159
|
+
check_dates_error('2014-02-16', '2014-01-31', 'A4')
|
160
|
+
check_dates_ok('2014-02-15', '2014-01-31', 'A5')
|
161
|
+
check_dates_ok('2014-03-15', '2014-02-15', 'A6')
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_no_vat_reason_presence
|
165
|
+
@invoice.vat = 23
|
166
|
+
v = Validator.new(@invoice); v.valid?
|
167
|
+
assert_nil v.errors[:no_vat_reason]
|
168
|
+
@invoice.vat = -1
|
169
|
+
v = Validator.new(@invoice); v.valid?
|
170
|
+
assert v.errors[:no_vat_reason]
|
171
|
+
@invoice.no_vat_reason = 'reason'
|
172
|
+
v = Validator.new(@invoice); v.valid?
|
173
|
+
assert_nil v.errors[:no_vat_reason]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|