invoice_printer 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/Gemfile +6 -0
- data/Gemfile.lock +63 -0
- data/README.md +1 -3
- data/benchmarks/render.yml +44 -0
- data/bin/invoice_printer +29 -18
- data/docs/COMMAND_LINE.md +5 -3
- data/docs/LIBRARY.md +8 -1
- data/docs/SERVER.md +6 -2
- data/examples/complex_invoice.rb +8 -1
- data/examples/czech_invoice_long.rb +92 -0
- data/lib/invoice_printer.rb +6 -2
- data/lib/invoice_printer/document/item.rb +6 -0
- data/lib/invoice_printer/pdf_document.rb +31 -6
- data/lib/invoice_printer/server.rb +23 -15
- data/lib/invoice_printer/version.rb +1 -1
- data/test/api_test.rb +1 -1
- data/test/cli_test.rb +42 -8
- data/test/inputs_test.rb +0 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 327ecc31f48a1b7a25b413c9423c47ce335c1a1b
|
4
|
+
data.tar.gz: bb454cbed1da8260b3f73c8f6ede4db3328326e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1aa09af511628b9dba7d3d5aa3497c33f5927c0d96b55964b5cf0a9b24219582dde29e3a8d8855fc8806ab99c77b4e69f4f34ad0cda1e52a520d8a3a5f362c95
|
7
|
+
data.tar.gz: 8de19983830a6263597e6799fd6c7738c81b5ee1b73dff6e44a504ef5b80c093da19eb7cbf25f73161987a9388314972df12f9e80e43f6f69be4a81aeecadbaa
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
invoice_printer (1.2.0)
|
5
|
+
json (~> 2.1)
|
6
|
+
prawn (= 2.1.0)
|
7
|
+
prawn-layout (= 0.8.4)
|
8
|
+
puma (~> 3.9)
|
9
|
+
roda (= 3.5.0)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
Ascii85 (1.0.3)
|
15
|
+
afm (0.2.2)
|
16
|
+
benchmark_driver (0.14.11)
|
17
|
+
benchmark_driver-output-gruff (0.3.1)
|
18
|
+
benchmark_driver (>= 0.12.0)
|
19
|
+
gruff
|
20
|
+
gruff (0.7.0)
|
21
|
+
rmagick (~> 2.13, >= 2.13.4)
|
22
|
+
hashery (2.1.2)
|
23
|
+
json (2.1.0)
|
24
|
+
minitest (5.11.3)
|
25
|
+
pdf-core (0.6.1)
|
26
|
+
pdf-inspector (1.3.0)
|
27
|
+
pdf-reader (>= 1.0, < 3.0.a)
|
28
|
+
pdf-reader (2.2.0)
|
29
|
+
Ascii85 (~> 1.0.0)
|
30
|
+
afm (~> 0.2.1)
|
31
|
+
hashery (~> 2.0)
|
32
|
+
ruby-rc4
|
33
|
+
ttfunk
|
34
|
+
prawn (2.1.0)
|
35
|
+
pdf-core (~> 0.6.1)
|
36
|
+
ttfunk (~> 1.4.0)
|
37
|
+
prawn-layout (0.8.4)
|
38
|
+
puma (3.12.0)
|
39
|
+
rack (2.0.6)
|
40
|
+
rack-test (1.1.0)
|
41
|
+
rack (>= 1.0, < 3)
|
42
|
+
rake (10.5.0)
|
43
|
+
rmagick (2.16.0)
|
44
|
+
roda (3.5.0)
|
45
|
+
rack
|
46
|
+
ruby-rc4 (0.1.5)
|
47
|
+
ttfunk (1.4.0)
|
48
|
+
|
49
|
+
PLATFORMS
|
50
|
+
ruby
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
benchmark_driver (= 0.14.11)
|
54
|
+
benchmark_driver-output-gruff
|
55
|
+
bundler (~> 1.7)
|
56
|
+
invoice_printer!
|
57
|
+
minitest
|
58
|
+
pdf-inspector
|
59
|
+
rack-test
|
60
|
+
rake (~> 10.0)
|
61
|
+
|
62
|
+
BUNDLED WITH
|
63
|
+
1.16.1
|
data/README.md
CHANGED
@@ -48,6 +48,4 @@ See more usecases in the `examples/` directory.
|
|
48
48
|
|
49
49
|
## Copyright
|
50
50
|
|
51
|
-
Copyright 2015-
|
52
|
-
|
53
|
-
Originally extracted from and created for an open source single-entry invoicing app [InvoiceBar](https://github.com/strzibny/invoicebar).
|
51
|
+
Copyright 2015-2019 © [Josef Strzibny](http://strzibny.name/). MIT licensed.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Run the benchmark with benchmark_driver as:
|
2
|
+
#
|
3
|
+
# $ benchmark-driver benchmarks/render.yml --output gruff --runner ips -e '/path/to/bin/ruby;/path/to/bin/ruby-2.6.0 --jit'
|
4
|
+
# $ benchmark-driver benchmarks/render.yml --output compare --runner memory -e '/path/to/bin/ruby;/path/to/bin/ruby-2.6.0 --jit--jit'
|
5
|
+
loop_count: 10000
|
6
|
+
prelude: |
|
7
|
+
lib = File.expand_path('../lib', __FILE__)
|
8
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
9
|
+
require 'invoice_printer'
|
10
|
+
|
11
|
+
item = InvoicePrinter::Document::Item.new(
|
12
|
+
name: 'Programming',
|
13
|
+
quantity: '10',
|
14
|
+
unit: 'hr',
|
15
|
+
price: '$ 90',
|
16
|
+
amount: '$ 900'
|
17
|
+
)
|
18
|
+
|
19
|
+
invoice = InvoicePrinter::Document.new(
|
20
|
+
number: 'NO. 198900000001',
|
21
|
+
provider_name: 'John White',
|
22
|
+
provider_street: '5th Avenue',
|
23
|
+
provider_street_number: '1',
|
24
|
+
provider_postcode: '747 05',
|
25
|
+
provider_city: 'NYC',
|
26
|
+
purchaser_name: 'Will Black',
|
27
|
+
purchaser_street: '7th Avenue',
|
28
|
+
purchaser_street_number: '1',
|
29
|
+
purchaser_postcode: '747 70',
|
30
|
+
purchaser_city: 'NYC',
|
31
|
+
issue_date: '05/03/2016',
|
32
|
+
due_date: '19/03/2016',
|
33
|
+
total: '$ 900',
|
34
|
+
bank_account_number: '156546546465',
|
35
|
+
items: [item],
|
36
|
+
note: 'This is a note at the end.'
|
37
|
+
)
|
38
|
+
benchmark:
|
39
|
+
render: |
|
40
|
+
InvoicePrinter.print(
|
41
|
+
document: invoice,
|
42
|
+
file_name: 'simple_invoice_a4.pdf',
|
43
|
+
page_size: :a4
|
44
|
+
)
|
data/bin/invoice_printer
CHANGED
@@ -16,11 +16,12 @@ def show_help
|
|
16
16
|
|
17
17
|
Options:
|
18
18
|
|
19
|
-
-l, --labels labels as JSON
|
20
19
|
-d, --document document as JSON
|
20
|
+
-l, --labels labels as JSON
|
21
|
+
--font path to font
|
21
22
|
-s, --stamp path to stamp
|
22
23
|
--logo path to logotype
|
23
|
-
|
24
|
+
--background path to background image
|
24
25
|
--page-size letter or a4 (letter is the default)
|
25
26
|
-f, --filename output path
|
26
27
|
-r, --render directly render PDF stream (filename option will be ignored)
|
@@ -33,12 +34,16 @@ end
|
|
33
34
|
options = {}
|
34
35
|
|
35
36
|
parser = OptionParser.new do|opts|
|
37
|
+
opts.on('-d', '--document JSON') do |json|
|
38
|
+
options[:document] = json
|
39
|
+
end
|
40
|
+
|
36
41
|
opts.on('-l', '--labels JSON') do |json|
|
37
42
|
options[:labels] = json
|
38
43
|
end
|
39
44
|
|
40
|
-
opts.on('
|
41
|
-
options[:
|
45
|
+
opts.on('--font PATH') do |path|
|
46
|
+
options[:font] = path
|
42
47
|
end
|
43
48
|
|
44
49
|
opts.on('-s', '--stamp PATH') do |path|
|
@@ -49,8 +54,8 @@ parser = OptionParser.new do|opts|
|
|
49
54
|
options[:logo] = path
|
50
55
|
end
|
51
56
|
|
52
|
-
opts.on('--
|
53
|
-
options[:
|
57
|
+
opts.on('--background PATH') do |path|
|
58
|
+
options[:background] = path
|
54
59
|
end
|
55
60
|
|
56
61
|
opts.on('--page-size OPTION') do |option|
|
@@ -89,8 +94,10 @@ begin
|
|
89
94
|
raise '--document not provided' unless options[:document]
|
90
95
|
|
91
96
|
begin
|
92
|
-
json = JSON.parse
|
97
|
+
json = JSON.parse(options[:document])
|
93
98
|
document = InvoicePrinter::Document.from_json(json)
|
99
|
+
labels = options[:labels] ? JSON.parse(options[:labels]) : {}
|
100
|
+
|
94
101
|
rescue => e
|
95
102
|
STDERR.puts "ERROR: parsing JSON failed. Invalid JSON?"
|
96
103
|
|
@@ -105,11 +112,13 @@ begin
|
|
105
112
|
|
106
113
|
if options[:render]
|
107
114
|
stream = InvoicePrinter.render(
|
108
|
-
document:
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
115
|
+
document: document,
|
116
|
+
labels: labels,
|
117
|
+
font: options[:font],
|
118
|
+
stamp: options[:stamp],
|
119
|
+
logo: options[:logo],
|
120
|
+
background: options[:background],
|
121
|
+
page_size: options[:page_size]
|
113
122
|
)
|
114
123
|
|
115
124
|
puts stream
|
@@ -117,12 +126,14 @@ begin
|
|
117
126
|
raise '--filename not provided. Use --render if you with to render to STDOUT.' unless options[:filename]
|
118
127
|
|
119
128
|
InvoicePrinter.print(
|
120
|
-
document:
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
129
|
+
document: document,
|
130
|
+
labels: labels,
|
131
|
+
font: options[:font],
|
132
|
+
stamp: options[:stamp],
|
133
|
+
logo: options[:logo],
|
134
|
+
file_name: options[:filename],
|
135
|
+
background: options[:background],
|
136
|
+
page_size: options[:page_size]
|
126
137
|
)
|
127
138
|
end
|
128
139
|
rescue => e
|
data/docs/COMMAND_LINE.md
CHANGED
@@ -10,12 +10,14 @@ Usage: invoice_printer [options]
|
|
10
10
|
|
11
11
|
Options:
|
12
12
|
|
13
|
-
-l, --labels labels as JSON
|
14
13
|
-d, --document document as JSON
|
14
|
+
-l, --labels labels as JSON
|
15
|
+
--font path to font
|
15
16
|
-s, --stamp path to stamp
|
16
17
|
--logo path to logotype
|
17
|
-
|
18
|
-
--
|
18
|
+
--background path to background image
|
19
|
+
--page-size letter or a4 (letter is the default)
|
19
20
|
-f, --filename output path
|
20
21
|
-r, --render directly render PDF stream (filename option will be ignored)
|
22
|
+
|
21
23
|
```
|
data/docs/LIBRARY.md
CHANGED
@@ -73,6 +73,10 @@ invoice = InvoicePrinter::Document.new(
|
|
73
73
|
)
|
74
74
|
```
|
75
75
|
|
76
|
+
**Note**: There is `variable` field that can be used for any
|
77
|
+
extra column. `tax2` and `tax3` for more complex taxes are
|
78
|
+
available as well.
|
79
|
+
|
76
80
|
### Ruby on Rails
|
77
81
|
|
78
82
|
If you want to use InvoicePrinter for printing PDF documents directly from Rails
|
@@ -170,6 +174,7 @@ InvoicePrinter.labels = {
|
|
170
174
|
issue_date: 'Issue date',
|
171
175
|
due_date: 'Due date',
|
172
176
|
item: 'Item',
|
177
|
+
variable: '',
|
173
178
|
quantity: 'Quantity',
|
174
179
|
unit: 'Unit',
|
175
180
|
price_per_item: 'Price per item',
|
@@ -181,6 +186,7 @@ InvoicePrinter.labels = {
|
|
181
186
|
total: 'Total'
|
182
187
|
}
|
183
188
|
```
|
189
|
+
**Note:** `variable` fields lack default label. You should provide one.
|
184
190
|
|
185
191
|
You can also use sublabels feature to provide the document in two languages:
|
186
192
|
|
@@ -201,6 +207,7 @@ sublabels = {
|
|
201
207
|
issue_date: 'Datum vydání',
|
202
208
|
due_date: 'Datum splatnosti',
|
203
209
|
item: 'Položka',
|
210
|
+
variable: '',
|
204
211
|
quantity: 'Počet',
|
205
212
|
unit: 'MJ',
|
206
213
|
price_per_item: 'Cena za položku',
|
@@ -215,7 +222,7 @@ labels.merge!({ sublabels: sublabels })
|
|
215
222
|
...
|
216
223
|
```
|
217
224
|
|
218
|
-
Now the document will have
|
225
|
+
Now the document will have little sublabels next to the original labels in Czech.
|
219
226
|
|
220
227
|
### Font
|
221
228
|
|
data/docs/SERVER.md
CHANGED
@@ -46,9 +46,11 @@ Options:
|
|
46
46
|
|
47
47
|
- `document` - JSON representation of the document
|
48
48
|
- `labels` - JSON for labels
|
49
|
+
- `font` - path to font file
|
49
50
|
- `stamp` - path to stamp file
|
50
51
|
- `logo` - path to logotype file
|
51
|
-
- `
|
52
|
+
- `background` - path to background file
|
53
|
+
- `page_size` - letter or A4 page size
|
52
54
|
|
53
55
|
On success a `200` response is returned:
|
54
56
|
|
@@ -84,9 +86,11 @@ Options:
|
|
84
86
|
|
85
87
|
- `document` - JSON representation of the document
|
86
88
|
- `labels` - JSON for labels
|
89
|
+
- `font` - path to font file
|
87
90
|
- `stamp` - path to stamp file
|
88
91
|
- `logo` - path to logotype file
|
89
|
-
- `
|
92
|
+
- `background` - path to background file
|
93
|
+
- `page_size` - letter or A4 page size
|
90
94
|
- `filename` - path for saving the generated output PDF
|
91
95
|
|
92
96
|
On success a `200` response is returned:
|
data/examples/complex_invoice.rb
CHANGED
@@ -6,11 +6,16 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
6
|
require 'invoice_printer'
|
7
7
|
|
8
8
|
labels = {
|
9
|
-
|
9
|
+
variable: 'Date',
|
10
|
+
tax: '10% VAT',
|
11
|
+
sublabels: {
|
12
|
+
variable: 'of work done'
|
13
|
+
}
|
10
14
|
}
|
11
15
|
|
12
16
|
item = InvoicePrinter::Document::Item.new(
|
13
17
|
name: 'Programming',
|
18
|
+
variable: 'June 2018',
|
14
19
|
quantity: '10',
|
15
20
|
unit: 'hr',
|
16
21
|
price: '$ 60',
|
@@ -20,6 +25,7 @@ item = InvoicePrinter::Document::Item.new(
|
|
20
25
|
|
21
26
|
item2 = InvoicePrinter::Document::Item.new(
|
22
27
|
name: 'Consulting',
|
28
|
+
variable: 'July 2018',
|
23
29
|
quantity: '10',
|
24
30
|
unit: 'hr',
|
25
31
|
price: '$ 30',
|
@@ -29,6 +35,7 @@ item2 = InvoicePrinter::Document::Item.new(
|
|
29
35
|
|
30
36
|
item3 = InvoicePrinter::Document::Item.new(
|
31
37
|
name: 'Support',
|
38
|
+
variable: 'September 2018',
|
32
39
|
quantity: '20',
|
33
40
|
unit: 'hr',
|
34
41
|
price: '$ 15',
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This is an example of a Czech invoice.
|
3
|
+
#
|
4
|
+
# Due to the special characters it requires Overpass-Regular.ttf font to be
|
5
|
+
# present in this directory.
|
6
|
+
|
7
|
+
lib = File.expand_path('../../lib', __FILE__)
|
8
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
9
|
+
require 'invoice_printer'
|
10
|
+
|
11
|
+
labels = {
|
12
|
+
name: 'Faktura',
|
13
|
+
provider: 'Prodejce',
|
14
|
+
purchaser: 'Kupující',
|
15
|
+
tax_id: 'IČ',
|
16
|
+
tax_id2: 'DIČ',
|
17
|
+
payment: 'Forma úhrady',
|
18
|
+
payment_by_transfer: 'Platba na následující účet:',
|
19
|
+
account_number: 'Číslo účtu',
|
20
|
+
issue_date: 'Datum vydání',
|
21
|
+
due_date: 'Datum splatnosti',
|
22
|
+
item: 'Položka',
|
23
|
+
quantity: 'Počet',
|
24
|
+
unit: 'MJ',
|
25
|
+
price_per_item: 'Cena za položku',
|
26
|
+
amount: 'Celkem bez daně',
|
27
|
+
subtotal: 'Cena bez daně',
|
28
|
+
tax: 'DPH 21 %',
|
29
|
+
total: 'Celkem'
|
30
|
+
}
|
31
|
+
|
32
|
+
first_item = InvoicePrinter::Document::Item.new(
|
33
|
+
name: 'Konzultace',
|
34
|
+
quantity: '2',
|
35
|
+
unit: 'hod',
|
36
|
+
price: 'Kč 500',
|
37
|
+
amount: 'Kč 1.000'
|
38
|
+
)
|
39
|
+
|
40
|
+
second_item = InvoicePrinter::Document::Item.new(
|
41
|
+
name: 'Programování',
|
42
|
+
quantity: '10',
|
43
|
+
unit: 'hod',
|
44
|
+
price: 'Kč 900',
|
45
|
+
amount: 'Kč 9.000'
|
46
|
+
)
|
47
|
+
|
48
|
+
invoice = InvoicePrinter::Document.new(
|
49
|
+
number: 'č. 198900000001',
|
50
|
+
provider_name: 'Petr Nový',
|
51
|
+
provider_tax_id: '56565656',
|
52
|
+
provider_tax_id2: 'CZ56565656',
|
53
|
+
provider_street: 'Rolnická',
|
54
|
+
provider_street_number: '1',
|
55
|
+
provider_postcode: '747 05',
|
56
|
+
provider_city: 'Opava',
|
57
|
+
provider_city_part: 'Kateřinky',
|
58
|
+
purchaser_name: 'Adam Černý',
|
59
|
+
purchaser_street: 'Ostravská',
|
60
|
+
purchaser_street_number: '1',
|
61
|
+
purchaser_postcode: '747 70',
|
62
|
+
purchaser_city: 'Opava',
|
63
|
+
purchaser_tax_id: '56565656',
|
64
|
+
purchaser_tax_id2: 'CZ56565656',
|
65
|
+
issue_date: '05/03/2016',
|
66
|
+
due_date: '19/03/2016',
|
67
|
+
subtotal: 'Kč 10.000',
|
68
|
+
tax: 'Kč 2.100',
|
69
|
+
total: 'Kč 12.100,-',
|
70
|
+
bank_account_number: '156546546465',
|
71
|
+
account_iban: 'IBAN464545645',
|
72
|
+
account_swift: 'SWIFT5456',
|
73
|
+
items: [first_item, second_item] * 20,
|
74
|
+
note: 'Osoba je zapsána v živnostenském rejstříku.'
|
75
|
+
)
|
76
|
+
|
77
|
+
InvoicePrinter.print(
|
78
|
+
document: invoice,
|
79
|
+
labels: labels,
|
80
|
+
font: File.expand_path('../Overpass-Regular.ttf', __FILE__),
|
81
|
+
logo: 'prawn.png',
|
82
|
+
file_name: 'czech_invoice_long.pdf'
|
83
|
+
)
|
84
|
+
|
85
|
+
InvoicePrinter.print(
|
86
|
+
document: invoice,
|
87
|
+
labels: labels,
|
88
|
+
font: File.expand_path('../Overpass-Regular.ttf', __FILE__),
|
89
|
+
logo: 'prawn.png',
|
90
|
+
file_name: 'czech_invoice_long_a4.pdf',
|
91
|
+
page_size: :a4
|
92
|
+
)
|
data/lib/invoice_printer.rb
CHANGED
@@ -62,12 +62,14 @@ module InvoicePrinter
|
|
62
62
|
# Print the given InvoicePrinter::Document to PDF file named +file_name+
|
63
63
|
#
|
64
64
|
# document - InvoicePrinter::Document object
|
65
|
-
# file_name - output file
|
66
65
|
# labels - labels to override
|
67
66
|
# font - font file to use
|
68
67
|
# stamp - stamp & signature (image)
|
69
68
|
# logo - logotype (image)
|
70
|
-
|
69
|
+
# background - background (image)
|
70
|
+
# page_size - :letter or :a4
|
71
|
+
# file_name - output file
|
72
|
+
def self.print(document:, labels: {}, font: nil, stamp: nil, logo: nil, background: nil, page_size: :letter, file_name:)
|
71
73
|
PDFDocument.new(
|
72
74
|
document: document,
|
73
75
|
labels: labels,
|
@@ -86,6 +88,8 @@ module InvoicePrinter
|
|
86
88
|
# font - font file to use
|
87
89
|
# stamp - stamp & signature (image)
|
88
90
|
# logo - logotype (image)
|
91
|
+
# background - background (image)
|
92
|
+
# page_size - :letter or :a4
|
89
93
|
def self.render(document:, labels: {}, font: nil, stamp: nil, logo: nil, background: nil, page_size: :letter)
|
90
94
|
PDFDocument.new(
|
91
95
|
document: document,
|
@@ -6,6 +6,7 @@ module InvoicePrinter
|
|
6
6
|
#
|
7
7
|
# item = InvoicePrinter::Document::Item.new(
|
8
8
|
# name: 'UX consultation',
|
9
|
+
# variable: 'June 2008',
|
9
10
|
# quantity: '4',
|
10
11
|
# unit: 'hours',
|
11
12
|
# price: '$ 25',
|
@@ -17,6 +18,7 @@ module InvoicePrinter
|
|
17
18
|
# but this is not enforced.
|
18
19
|
class Item
|
19
20
|
attr_reader :name,
|
21
|
+
:variable, # for anything required
|
20
22
|
:quantity,
|
21
23
|
:unit,
|
22
24
|
:price,
|
@@ -29,6 +31,7 @@ module InvoicePrinter
|
|
29
31
|
def from_json(json)
|
30
32
|
new(
|
31
33
|
name: json['name'],
|
34
|
+
variable: json['variable'],
|
32
35
|
quantity: json['quantity'],
|
33
36
|
unit: json['unit'],
|
34
37
|
price: json['price'],
|
@@ -41,6 +44,7 @@ module InvoicePrinter
|
|
41
44
|
end
|
42
45
|
|
43
46
|
def initialize(name: nil,
|
47
|
+
variable: nil,
|
44
48
|
quantity: nil,
|
45
49
|
unit: nil,
|
46
50
|
price: nil,
|
@@ -50,6 +54,7 @@ module InvoicePrinter
|
|
50
54
|
amount: nil)
|
51
55
|
|
52
56
|
@name = String(name)
|
57
|
+
@variable = String(variable)
|
53
58
|
@quantity = String(quantity)
|
54
59
|
@unit = String(unit)
|
55
60
|
@price = String(price)
|
@@ -62,6 +67,7 @@ module InvoicePrinter
|
|
62
67
|
def to_h
|
63
68
|
{
|
64
69
|
'name': @name,
|
70
|
+
'variable': @variable,
|
65
71
|
'quantity': @quantity,
|
66
72
|
'unit': @unit,
|
67
73
|
'price': @price,
|
@@ -37,6 +37,7 @@ module InvoicePrinter
|
|
37
37
|
issue_date: 'Issue date',
|
38
38
|
due_date: 'Due date',
|
39
39
|
item: 'Item',
|
40
|
+
variable: '',
|
40
41
|
quantity: 'Quantity',
|
41
42
|
unit: 'Unit',
|
42
43
|
price_per_item: 'Price per item',
|
@@ -46,7 +47,7 @@ module InvoicePrinter
|
|
46
47
|
amount: 'Amount',
|
47
48
|
subtotal: 'Subtotal',
|
48
49
|
total: 'Total',
|
49
|
-
sublabels: {}
|
50
|
+
sublabels: {}
|
50
51
|
}
|
51
52
|
|
52
53
|
PageSize = Struct.new(:name, :width, :height)
|
@@ -66,12 +67,12 @@ module InvoicePrinter
|
|
66
67
|
|
67
68
|
def initialize(document: Document.new, labels: {}, font: nil, stamp: nil, logo: nil, background: nil, page_size: :letter)
|
68
69
|
@document = document
|
69
|
-
@labels =
|
70
|
-
@page_size = PAGE_SIZES[page_size.to_sym]
|
71
|
-
@pdf = Prawn::Document.new(background: background, page_size: @page_size.name)
|
70
|
+
@labels = merge_custom_labels(labels)
|
72
71
|
@font = font
|
73
72
|
@stamp = stamp
|
74
73
|
@logo = logo
|
74
|
+
@page_size = page_size ? PAGE_SIZES[page_size.to_sym] : PAGE_SIZES[:letter]
|
75
|
+
@pdf = Prawn::Document.new(background: background, page_size: @page_size.name)
|
75
76
|
|
76
77
|
raise InvalidInput, 'document is not a type of InvoicePrinter::Document' \
|
77
78
|
unless @document.is_a?(InvoicePrinter::Document)
|
@@ -602,8 +603,9 @@ module InvoicePrinter
|
|
602
603
|
# |x | 2| hr| $2| $1| $4|
|
603
604
|
# =================================================================
|
604
605
|
#
|
606
|
+
# variable (2nd position), tax2 and tax3 (after tax) fields can be added
|
607
|
+
# as well if necessary. variable does not come with any default label.
|
605
608
|
# If a specific column miss data, it's omittted.
|
606
|
-
# tax2 and tax3 fields can be added as well if necessary.
|
607
609
|
#
|
608
610
|
# Using sublabels one can change the table to look as:
|
609
611
|
#
|
@@ -632,7 +634,8 @@ module InvoicePrinter
|
|
632
634
|
4 => :right,
|
633
635
|
5 => :right,
|
634
636
|
6 => :right,
|
635
|
-
7 => :right
|
637
|
+
7 => :right,
|
638
|
+
8 => :right
|
636
639
|
},
|
637
640
|
font_size: 10
|
638
641
|
}
|
@@ -645,6 +648,7 @@ module InvoicePrinter
|
|
645
648
|
items_params = {}
|
646
649
|
@document.items.each do |item|
|
647
650
|
items_params[:names] = true unless item.name.empty?
|
651
|
+
items_params[:variables] = true unless item.variable.empty?
|
648
652
|
items_params[:quantities] = true unless item.quantity.empty?
|
649
653
|
items_params[:units] = true unless item.unit.empty?
|
650
654
|
items_params[:prices] = true unless item.price.empty?
|
@@ -661,6 +665,7 @@ module InvoicePrinter
|
|
661
665
|
@document.items.map do |item|
|
662
666
|
line = []
|
663
667
|
line << item.name if items_params[:names]
|
668
|
+
line << item.variable if items_params[:variables]
|
664
669
|
line << item.quantity if items_params[:quantities]
|
665
670
|
line << item.unit if items_params[:units]
|
666
671
|
line << item.price if items_params[:prices]
|
@@ -676,6 +681,7 @@ module InvoicePrinter
|
|
676
681
|
def build_items_header(items_params)
|
677
682
|
headers = []
|
678
683
|
headers << { text: label_with_sublabel(:item) } if items_params[:names]
|
684
|
+
headers << { text: label_with_sublabel(:variable) } if items_params[:variables]
|
679
685
|
headers << { text: label_with_sublabel(:quantity) } if items_params[:quantities]
|
680
686
|
headers << { text: label_with_sublabel(:unit) } if items_params[:units]
|
681
687
|
headers << { text: label_with_sublabel(:price_per_item) } if items_params[:prices]
|
@@ -831,5 +837,24 @@ module InvoicePrinter
|
|
831
837
|
width_ratio = value / PAGE_SIZES[:letter].height
|
832
838
|
width_ratio * @page_size.height
|
833
839
|
end
|
840
|
+
|
841
|
+
def merge_custom_labels(labels = {})
|
842
|
+
custom_labels = if labels
|
843
|
+
hash_keys_to_symbols(labels)
|
844
|
+
else
|
845
|
+
{}
|
846
|
+
end
|
847
|
+
|
848
|
+
PDFDocument.labels.merge(custom_labels)
|
849
|
+
end
|
850
|
+
|
851
|
+
def hash_keys_to_symbols(value)
|
852
|
+
return value unless value.is_a? Hash
|
853
|
+
|
854
|
+
value.inject({}) do |memo, (k, v)|
|
855
|
+
memo[k.to_sym] = hash_keys_to_symbols(v)
|
856
|
+
memo
|
857
|
+
end
|
858
|
+
end
|
834
859
|
end
|
835
860
|
end
|
@@ -16,11 +16,13 @@ class InvoicePrinter::Server < Roda
|
|
16
16
|
r.halt
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
document = params[:document]
|
20
|
+
labels = params[:labels]
|
21
|
+
font = params[:font]
|
22
|
+
stamp = params[:stamp]
|
23
|
+
logo = params[:logo]
|
24
|
+
background = params[:background]
|
25
|
+
page_size = params[:page_size]
|
24
26
|
else
|
25
27
|
response.status = 400
|
26
28
|
response.write({ result: 'error', error: 'No JSON. Did you set Content-Type to application/json?' }.to_json)
|
@@ -46,11 +48,14 @@ class InvoicePrinter::Server < Roda
|
|
46
48
|
|
47
49
|
begin
|
48
50
|
InvoicePrinter.print(
|
49
|
-
document:
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
document: document,
|
52
|
+
labels: labels,
|
53
|
+
font: font,
|
54
|
+
stamp: stamp,
|
55
|
+
logo: logo,
|
56
|
+
background: background,
|
57
|
+
page_size: page_size,
|
58
|
+
file_name: filename
|
54
59
|
)
|
55
60
|
|
56
61
|
{ result: 'ok', path: filename }.to_json
|
@@ -65,10 +70,13 @@ class InvoicePrinter::Server < Roda
|
|
65
70
|
r.post 'render' do
|
66
71
|
begin
|
67
72
|
stream = InvoicePrinter.render(
|
68
|
-
document:
|
69
|
-
|
70
|
-
|
71
|
-
|
73
|
+
document: document,
|
74
|
+
labels: labels,
|
75
|
+
font: font,
|
76
|
+
stamp: stamp,
|
77
|
+
logo: logo,
|
78
|
+
background: background,
|
79
|
+
page_size: page_size
|
72
80
|
)
|
73
81
|
|
74
82
|
{ result: 'ok', data: Base64.encode64(stream) }.to_json
|
@@ -79,4 +87,4 @@ class InvoicePrinter::Server < Roda
|
|
79
87
|
end
|
80
88
|
end
|
81
89
|
end
|
82
|
-
end
|
90
|
+
end
|
data/test/api_test.rb
CHANGED
data/test/cli_test.rb
CHANGED
@@ -15,7 +15,12 @@ class CLITest < Minitest::Test
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def test_print_to_file
|
18
|
-
command = "bin/invoice_printer
|
18
|
+
command = "bin/invoice_printer " +
|
19
|
+
"--document '#{@invoice_as_json}' " +
|
20
|
+
"--labels '#{labels_hash.to_json}' " +
|
21
|
+
"--logo '#{logo_path}' " +
|
22
|
+
"--background '#{background_path}' " +
|
23
|
+
"--filename #{@output_path}"
|
19
24
|
|
20
25
|
exit_status, command_stdout, command_stderr = nil
|
21
26
|
|
@@ -33,9 +38,12 @@ class CLITest < Minitest::Test
|
|
33
38
|
expected_pdf_path = "#{@output_path}.expected_letter"
|
34
39
|
|
35
40
|
InvoicePrinter.print(
|
36
|
-
document:
|
37
|
-
|
38
|
-
|
41
|
+
document: @invoice,
|
42
|
+
labels: labels_hash,
|
43
|
+
logo: logo_path,
|
44
|
+
background: background_path,
|
45
|
+
page_size: :letter,
|
46
|
+
file_name: expected_pdf_path
|
39
47
|
)
|
40
48
|
|
41
49
|
similarity = (File.read(expected_pdf_path) == File.read(@output_path))
|
@@ -45,7 +53,13 @@ class CLITest < Minitest::Test
|
|
45
53
|
end
|
46
54
|
|
47
55
|
def test_print_to_file_a4
|
48
|
-
command = "bin/invoice_printer
|
56
|
+
command = "bin/invoice_printer " +
|
57
|
+
"--document '#{@invoice_as_json}' " +
|
58
|
+
"--labels '#{labels_hash.to_json}' " +
|
59
|
+
"--logo '#{logo_path}' " +
|
60
|
+
"--background '#{background_path}' " +
|
61
|
+
"--page-size a4 " +
|
62
|
+
"--filename #{@output_path}"
|
49
63
|
|
50
64
|
exit_status, command_stdout, command_stderr = nil
|
51
65
|
|
@@ -63,9 +77,12 @@ class CLITest < Minitest::Test
|
|
63
77
|
expected_pdf_path = "#{@output_path}.expected_a4"
|
64
78
|
|
65
79
|
InvoicePrinter.print(
|
66
|
-
document:
|
67
|
-
|
68
|
-
|
80
|
+
document: @invoice,
|
81
|
+
labels: labels_hash,
|
82
|
+
logo: logo_path,
|
83
|
+
background: background_path,
|
84
|
+
page_size: :a4,
|
85
|
+
file_name: expected_pdf_path
|
69
86
|
)
|
70
87
|
|
71
88
|
similarity = (File.read(expected_pdf_path) == File.read(@output_path))
|
@@ -73,4 +90,21 @@ class CLITest < Minitest::Test
|
|
73
90
|
|
74
91
|
File.unlink expected_pdf_path
|
75
92
|
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def labels_hash
|
97
|
+
sublabels = { sublabels: { name: 'Sublabel name' } }
|
98
|
+
|
99
|
+
{ provider: 'Default Provider',
|
100
|
+
purchaser: 'Default Purchaser' }.merge!(sublabels)
|
101
|
+
end
|
102
|
+
|
103
|
+
def logo_path
|
104
|
+
File.expand_path('../../examples/logo.png', __FILE__)
|
105
|
+
end
|
106
|
+
|
107
|
+
def background_path
|
108
|
+
File.expand_path('../../examples/background.png', __FILE__)
|
109
|
+
end
|
76
110
|
end
|
data/test/inputs_test.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: invoice_printer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josef Strzibny
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -119,10 +119,12 @@ extra_rdoc_files: []
|
|
119
119
|
files:
|
120
120
|
- ".gitignore"
|
121
121
|
- Gemfile
|
122
|
+
- Gemfile.lock
|
122
123
|
- LICENSE.txt
|
123
124
|
- README.md
|
124
125
|
- Rakefile
|
125
126
|
- assets/logo.png
|
127
|
+
- benchmarks/render.yml
|
126
128
|
- bin/invoice_printer
|
127
129
|
- bin/invoice_printer_server
|
128
130
|
- docs/COMMAND_LINE.md
|
@@ -133,6 +135,7 @@ files:
|
|
133
135
|
- examples/clients/node.js
|
134
136
|
- examples/complex_invoice.rb
|
135
137
|
- examples/czech_invoice.rb
|
138
|
+
- examples/czech_invoice_long.rb
|
136
139
|
- examples/international_invoice.rb
|
137
140
|
- examples/logo.png
|
138
141
|
- examples/long_invoice.rb
|