invoice_printer 1.1.0 → 1.2.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +22 -290
- data/assets/logo.png +0 -0
- data/bin/invoice_printer +0 -3
- data/bin/invoice_printer_server +69 -0
- data/docs/COMMAND_LINE.md +21 -0
- data/docs/INSTALLATION.md +22 -0
- data/docs/LIBRARY.md +242 -0
- data/docs/SERVER.md +96 -0
- data/invoice_printer.gemspec +2 -1
- data/lib/invoice_printer.rb +5 -0
- data/lib/invoice_printer/document.rb +0 -2
- data/lib/invoice_printer/document/item.rb +0 -2
- data/lib/invoice_printer/server.rb +82 -0
- data/lib/invoice_printer/version.rb +1 -1
- data/test/api_test.rb +78 -0
- data/test/test_helper.rb +2 -0
- metadata +43 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8abb69910f861a930c17f4a59b2f00c5c7530556
|
4
|
+
data.tar.gz: b4b108dc6b68db259a24a9fca49db4eb78ab0a9b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 126cddc2f3e5cf893cf94f18abdd2b7966399f993f0ff29826adff0d1b77f26ea08c8de3feb6b036c4c5b12027ebbd90b41ef54f3b1b9fecc6e80bc88c9a530c
|
7
|
+
data.tar.gz: db9b42fb49fa1c303c0f6a6abeb1408cd2940960efc529107372f9581d21248eb982ce1f3eaf4e4bee23ce6d3bb759972aeabfd3f4611aec42cd295d961968e2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,22 @@
|
|
1
|
-
<
|
2
|
-
<img src="./docs/web/logo.png" width="300" />
|
3
|
-
</a>
|
1
|
+
<img src="./assets/logo.png" width="300" />
|
4
2
|
|
5
3
|
|
6
4
|
|
7
|
-
Super simple PDF invoicing
|
5
|
+
**Super simple PDF invoicing.** InvoicePrinter is a server, command line program and pure Ruby library to generate PDF invoices in no time. You can use Ruby or JSON as the invoice representation to build the final PDF.
|
8
6
|
|
9
|
-
|
7
|
+
## Philosophy
|
8
|
+
|
9
|
+
- **Simple**, no styling required, no calculation, no money formatting (bring your own)
|
10
|
+
- **Pure Ruby**, no dependency on system libraries or browsers
|
11
|
+
- **Fast**, so you can render invoice on the fly during request
|
12
|
+
|
13
|
+
## Examples
|
14
|
+
|
15
|
+
| Simple invoice |
|
16
|
+
| -------------- |
|
17
|
+
| <a href="https://github.com/strzibny/invoice_printer/raw/master/examples/promo.pdf"><img src="./examples/picture.jpg" width="180" /></a>|
|
18
|
+
|
19
|
+
See more usecases in the `examples/` directory.
|
10
20
|
|
11
21
|
## Features
|
12
22
|
|
@@ -26,296 +36,18 @@ InvoicePrinter is a Ruby library and a command line program. You can use Ruby or
|
|
26
36
|
- Note
|
27
37
|
- JSON format
|
28
38
|
- CLI
|
39
|
+
- Server
|
29
40
|
- Well tested
|
30
41
|
|
31
|
-
##
|
32
|
-
|
33
|
-
| Simple invoice |
|
34
|
-
| -------------- |
|
35
|
-
| <a href="https://github.com/strzibny/invoice_printer/raw/master/examples/promo.pdf"><img src="./examples/picture.jpg" width="180" /></a>|
|
36
|
-
|
37
|
-
See more usecases in the `examples/` directory.
|
38
|
-
|
39
|
-
## Installation
|
40
|
-
|
41
|
-
Add this line to your application's Gemfile:
|
42
|
-
|
43
|
-
```ruby
|
44
|
-
gem 'invoice_printer'
|
45
|
-
```
|
46
|
-
|
47
|
-
And then execute:
|
48
|
-
|
49
|
-
$ bundle
|
50
|
-
|
51
|
-
Or install it yourself as:
|
52
|
-
|
53
|
-
$ gem install invoice_printer
|
54
|
-
|
55
|
-
## Usage
|
56
|
-
|
57
|
-
The simplest way how to create your invoice PDF is to create an invoice object
|
58
|
-
and pass it to printer:
|
59
|
-
|
60
|
-
```ruby
|
61
|
-
item = InvoicePrinter::Document::Item.new(
|
62
|
-
...
|
63
|
-
)
|
64
|
-
|
65
|
-
invoice = InvoicePrinter::Document.new(
|
66
|
-
...
|
67
|
-
items: [item, ...]
|
68
|
-
)
|
69
|
-
|
70
|
-
InvoicePrinter.print(
|
71
|
-
document: invoice,
|
72
|
-
file_name: 'invoice.pdf'
|
73
|
-
)
|
74
|
-
|
75
|
-
# Or render PDF directly
|
76
|
-
InvoicePrinter.render(
|
77
|
-
document: invoice
|
78
|
-
)
|
79
|
-
```
|
80
|
-
|
81
|
-
Here is an full example for creating the document object:
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
item = InvoicePrinter::Document::Item.new(
|
85
|
-
name: 'Web consultation',
|
86
|
-
quantity: nil,
|
87
|
-
unit: 'hours',
|
88
|
-
price: '$ 25',
|
89
|
-
tax: '$ 1',
|
90
|
-
amount: '$ 100'
|
91
|
-
)
|
92
|
-
|
93
|
-
invoice = InvoicePrinter::Document.new(
|
94
|
-
number: '201604030001',
|
95
|
-
provider_name: 'Business s.r.o.',
|
96
|
-
provider_tax_id: '56565656',
|
97
|
-
provider_tax_id2: '465454',
|
98
|
-
provider_street: 'Rolnicka',
|
99
|
-
provider_street_number: '1',
|
100
|
-
provider_postcode: '747 05',
|
101
|
-
provider_city: 'Opava',
|
102
|
-
provider_city_part: 'Katerinky',
|
103
|
-
provider_extra_address_line: 'Czech Republic',
|
104
|
-
purchaser_name: 'Adam',
|
105
|
-
purchaser_tax_id: '',
|
106
|
-
purchaser_tax_id2: '',
|
107
|
-
purchaser_street: 'Ostravska',
|
108
|
-
purchaser_street_number: '1',
|
109
|
-
purchaser_postcode: '747 70',
|
110
|
-
purchaser_city: 'Opava',
|
111
|
-
purchaser_city_part: '',
|
112
|
-
purchaser_extra_address_line: '',
|
113
|
-
issue_date: '19/03/3939',
|
114
|
-
due_date: '19/03/3939',
|
115
|
-
subtotal: '175',
|
116
|
-
tax: '5',
|
117
|
-
tax2: '10',
|
118
|
-
tax3: '20',
|
119
|
-
total: '$ 200',
|
120
|
-
bank_account_number: '156546546465',
|
121
|
-
account_iban: 'IBAN464545645',
|
122
|
-
account_swift: 'SWIFT5456',
|
123
|
-
items: [item],
|
124
|
-
note: 'A note...'
|
125
|
-
)
|
126
|
-
```
|
127
|
-
|
128
|
-
### Ruby on Rails
|
129
|
-
|
130
|
-
If you want to use InvoicePrinter for printing PDF documents directly from Rails
|
131
|
-
actions, you can:
|
132
|
-
|
133
|
-
```ruby
|
134
|
-
# GET /invoices/1
|
135
|
-
def show
|
136
|
-
invoice = InvoicePrinter::Document.new(...)
|
137
|
-
|
138
|
-
respond_to do |format|
|
139
|
-
format.pdf {
|
140
|
-
@pdf = InvoicePrinter.render(
|
141
|
-
document: invoice
|
142
|
-
)
|
143
|
-
send_data @pdf, type: 'application/pdf', disposition: 'inline'
|
144
|
-
}
|
145
|
-
end
|
146
|
-
end
|
147
|
-
```
|
148
|
-
|
149
|
-
### JSON format
|
150
|
-
|
151
|
-
JSON format is supported via `from_json` method. JSON itself mimicks the original Ruby objects:
|
152
|
-
|
153
|
-
```ruby
|
154
|
-
json = InvoicePrinter::Document.new(...).to_json
|
155
|
-
document = InvoicePrinter::Document.from_json(json)
|
156
|
-
|
157
|
-
|
158
|
-
InvoicePrinter.print(
|
159
|
-
document: document,
|
160
|
-
...
|
161
|
-
)
|
162
|
-
|
163
|
-
```
|
164
|
-
|
165
|
-
## CLI
|
166
|
-
|
167
|
-
InvoicePrinter ships with a command line executable called `invoice_printer`.
|
168
|
-
|
169
|
-
It supports all features except it only accepts JSON as an input.
|
170
|
-
|
171
|
-
```
|
172
|
-
$ invoice_printer --help
|
173
|
-
Usage: invoice_printer [options]
|
174
|
-
|
175
|
-
Options:
|
176
|
-
|
177
|
-
-l, --labels labels as JSON
|
178
|
-
-d, --document document as JSON
|
179
|
-
-s, --stamp path to stamp
|
180
|
-
--logo path to logotype
|
181
|
-
--font path to font
|
182
|
-
--page_size letter or a4 (letter is the default)
|
183
|
-
-f, --filename output path
|
184
|
-
-r, --render directly render PDF stream (filename option will be ignored)
|
185
|
-
```
|
186
|
-
|
187
|
-
|
188
|
-
## Customization
|
189
|
-
|
190
|
-
### Page size
|
191
|
-
|
192
|
-
Both A4 and US letter is supported. Just pass `page_size` as an argument to `print` or `render` methods:
|
193
|
-
|
194
|
-
```ruby
|
195
|
-
InvoicePrinter.print(
|
196
|
-
document: invoice,
|
197
|
-
labels: labels,
|
198
|
-
page_size: :a4,
|
199
|
-
file_name: 'invoice.pdf'
|
200
|
-
)
|
201
|
-
```
|
202
|
-
|
203
|
-
`:letter` is the default.
|
204
|
-
|
205
|
-
|
206
|
-
### Localization
|
207
|
-
|
208
|
-
To localize your documents you can set both global defaults or instance
|
209
|
-
overrides:
|
210
|
-
|
211
|
-
```ruby
|
212
|
-
InvoicePrinter.labels = {
|
213
|
-
provider: 'Dodavatel'
|
214
|
-
}
|
215
|
-
|
216
|
-
labels = {
|
217
|
-
purchaser: 'Customer'
|
218
|
-
}
|
219
|
-
|
220
|
-
InvoicePrinter.print(
|
221
|
-
document: invoice,
|
222
|
-
labels: labels,
|
223
|
-
file_name: 'invoice.pdf'
|
224
|
-
)
|
225
|
-
```
|
226
|
-
|
227
|
-
Here is the full list of labels to configure. You can paste and edit this block
|
228
|
-
to `initializers/invoice_printer.rb` if you are using Rails.
|
229
|
-
|
230
|
-
```ruby
|
231
|
-
InvoicePrinter.labels = {
|
232
|
-
name: 'Invoice',
|
233
|
-
provider: 'Provider',
|
234
|
-
purchaser: 'Purchaser',
|
235
|
-
tax_id: 'Identification number',
|
236
|
-
tax_id2: 'Identification number',
|
237
|
-
payment: 'Payment',
|
238
|
-
payment_by_transfer: 'Payment by bank transfer on the account below:',
|
239
|
-
payment_in_cash: 'Payment in cash',
|
240
|
-
account_number: 'Account NO',
|
241
|
-
swift: 'SWIFT',
|
242
|
-
iban: 'IBAN',
|
243
|
-
issue_date: 'Issue date',
|
244
|
-
due_date: 'Due date',
|
245
|
-
item: 'Item',
|
246
|
-
quantity: 'Quantity',
|
247
|
-
unit: 'Unit',
|
248
|
-
price_per_item: 'Price per item',
|
249
|
-
amount: 'Amount',
|
250
|
-
tax: 'Tax',
|
251
|
-
tax2: 'Tax 2',
|
252
|
-
tax3: 'Tax 3',
|
253
|
-
subtotal: 'Subtotal',
|
254
|
-
total: 'Total'
|
255
|
-
}
|
256
|
-
```
|
257
|
-
|
258
|
-
You can also use sublabels feature to provide the document in two languages:
|
259
|
-
|
260
|
-
```ruby
|
261
|
-
labels = {
|
262
|
-
...
|
263
|
-
}
|
264
|
-
|
265
|
-
sublabels = {
|
266
|
-
name: 'Faktura',
|
267
|
-
provider: 'Prodejce',
|
268
|
-
purchaser: 'Kupující',
|
269
|
-
tax_id: 'IČ',
|
270
|
-
tax_id2: 'DIČ',
|
271
|
-
payment: 'Forma úhrady',
|
272
|
-
payment_by_transfer: 'Platba na následující účet:',
|
273
|
-
account_number: 'Číslo účtu',
|
274
|
-
issue_date: 'Datum vydání',
|
275
|
-
due_date: 'Datum splatnosti',
|
276
|
-
item: 'Položka',
|
277
|
-
quantity: 'Počet',
|
278
|
-
unit: 'MJ',
|
279
|
-
price_per_item: 'Cena za položku',
|
280
|
-
amount: 'Celkem bez daně',
|
281
|
-
subtotal: 'Cena bez daně',
|
282
|
-
tax: 'DPH 21 %',
|
283
|
-
total: 'Celkem'
|
284
|
-
}
|
285
|
-
|
286
|
-
labels.merge!({ sublabels: sublabels })
|
287
|
-
|
288
|
-
...
|
289
|
-
```
|
290
|
-
|
291
|
-
Now the document will have a little sublabels next to the original labels in Czech.
|
292
|
-
|
293
|
-
### Font
|
294
|
-
|
295
|
-
To support specific characters you might need to specify a TTF font to be used:
|
296
|
-
|
297
|
-
``` ruby
|
298
|
-
InvoicePrinter.print(
|
299
|
-
...
|
300
|
-
font: File.expand_path('../Overpass-Regular.ttf', __FILE__)
|
301
|
-
)
|
302
|
-
```
|
303
|
-
|
304
|
-
We recommend you DejaVuSans and Overpass fonts.
|
305
|
-
|
306
|
-
### Background
|
307
|
-
|
308
|
-
To include a background image you might need to create the file according to the size and resolution to be used (see: [examples/background.png](https://github.com/strzibny/invoice_printer/blob/master/examples/background.png)):
|
42
|
+
## Documentation
|
309
43
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
)
|
315
|
-
```
|
44
|
+
- [Installation](./docs/INSTALLATION.md)
|
45
|
+
- [Ruby library](./docs/LIBRARY.md)
|
46
|
+
- [Server](./docs/SERVER.md)
|
47
|
+
- [Command line](./docs/COMMAND_LINE.md)
|
316
48
|
|
317
49
|
## Copyright
|
318
50
|
|
319
|
-
Copyright 2015-
|
51
|
+
Copyright 2015-2018 © [Josef Strzibny](http://strzibny.name/). MIT licensed.
|
320
52
|
|
321
53
|
Originally extracted from and created for an open source single-entry invoicing app [InvoiceBar](https://github.com/strzibny/invoicebar).
|
data/assets/logo.png
ADDED
Binary file
|
data/bin/invoice_printer
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
$LOAD_PATH << File.expand_path('lib')
|
3
3
|
|
4
4
|
require 'optparse'
|
5
|
-
require 'json'
|
6
5
|
require 'invoice_printer'
|
7
6
|
|
8
7
|
def show_version
|
@@ -34,8 +33,6 @@ end
|
|
34
33
|
options = {}
|
35
34
|
|
36
35
|
parser = OptionParser.new do|opts|
|
37
|
-
opts.banner = "Usage: invoice_printer [options]"
|
38
|
-
|
39
36
|
opts.on('-l', '--labels JSON') do |json|
|
40
37
|
options[:labels] = json
|
41
38
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.expand_path('lib')
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'rack/handler/puma'
|
6
|
+
require 'invoice_printer/server'
|
7
|
+
|
8
|
+
def show_version
|
9
|
+
puts "InvoicePrinter v#{InvoicePrinter::VERSION}"
|
10
|
+
|
11
|
+
exit 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def show_help
|
15
|
+
puts <<~HELP
|
16
|
+
Usage: invoice_printer_server [options]
|
17
|
+
|
18
|
+
Options:
|
19
|
+
|
20
|
+
-h, --host hostname to listen on (default is 0.0.0.0)
|
21
|
+
-p, --port port to listen on (default is 9393)
|
22
|
+
-w, --workers number of Puma workers (default is 2)
|
23
|
+
|
24
|
+
HELP
|
25
|
+
|
26
|
+
exit 0
|
27
|
+
end
|
28
|
+
|
29
|
+
options = {}
|
30
|
+
|
31
|
+
parser = OptionParser.new do|opts|
|
32
|
+
opts.on('-h', '--hostname ADDRESS') do |address|
|
33
|
+
options[:Host] = address
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('-p', '--port NUMBER') do |number|
|
37
|
+
options[:Port] = number
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-w', '--workers NUMBER') do |number|
|
41
|
+
options[:workers] = number.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('--debug') do
|
45
|
+
options[:debug] = true
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on('-h', '--help') do
|
49
|
+
show_help
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
parser.parse!
|
54
|
+
|
55
|
+
puma_options = { :Host => '0.0.0.0', :Port => 9393, :workers => 2 }.merge(options)
|
56
|
+
|
57
|
+
begin
|
58
|
+
puts 'Starting InvoicePrinter Server...'
|
59
|
+
Rack::Handler::Puma.run(InvoicePrinter::Server.freeze.app, puma_options)
|
60
|
+
rescue => e
|
61
|
+
STDERR.puts "ERROR: #{e.message}"
|
62
|
+
|
63
|
+
if options[:debug]
|
64
|
+
STDERR.puts
|
65
|
+
STDERR.puts e.backtrace
|
66
|
+
end
|
67
|
+
|
68
|
+
exit 1
|
69
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# InvoicePrinter CLI
|
2
|
+
|
3
|
+
InvoicePrinter ships with a command line executable called `invoice_printer`.
|
4
|
+
|
5
|
+
It supports all features except it only accepts JSON as an input.
|
6
|
+
|
7
|
+
```
|
8
|
+
$ invoice_printer --help
|
9
|
+
Usage: invoice_printer [options]
|
10
|
+
|
11
|
+
Options:
|
12
|
+
|
13
|
+
-l, --labels labels as JSON
|
14
|
+
-d, --document document as JSON
|
15
|
+
-s, --stamp path to stamp
|
16
|
+
--logo path to logotype
|
17
|
+
--font path to font
|
18
|
+
--page_size letter or a4 (letter is the default)
|
19
|
+
-f, --filename output path
|
20
|
+
-r, --render directly render PDF stream (filename option will be ignored)
|
21
|
+
```
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Installation
|
2
|
+
|
3
|
+
## Via RubyGems
|
4
|
+
|
5
|
+
This requires Ruby to be installed.
|
6
|
+
|
7
|
+
Then install the gem as:
|
8
|
+
|
9
|
+
$ gem install invoice_printer
|
10
|
+
|
11
|
+
|
12
|
+
### With Bundler
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'invoice_printer'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
data/docs/LIBRARY.md
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
# InvoicePrinter Library
|
2
|
+
|
3
|
+
## Usage
|
4
|
+
|
5
|
+
The simplest way how to create your invoice PDF is to create an invoice object
|
6
|
+
and pass it to printer:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
item = InvoicePrinter::Document::Item.new(
|
10
|
+
...
|
11
|
+
)
|
12
|
+
|
13
|
+
invoice = InvoicePrinter::Document.new(
|
14
|
+
...
|
15
|
+
items: [item, ...]
|
16
|
+
)
|
17
|
+
|
18
|
+
InvoicePrinter.print(
|
19
|
+
document: invoice,
|
20
|
+
file_name: 'invoice.pdf'
|
21
|
+
)
|
22
|
+
|
23
|
+
# Or render PDF directly
|
24
|
+
InvoicePrinter.render(
|
25
|
+
document: invoice
|
26
|
+
)
|
27
|
+
```
|
28
|
+
|
29
|
+
Here is an full example for creating the document object:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
item = InvoicePrinter::Document::Item.new(
|
33
|
+
name: 'Web consultation',
|
34
|
+
quantity: nil,
|
35
|
+
unit: 'hours',
|
36
|
+
price: '$ 25',
|
37
|
+
tax: '$ 1',
|
38
|
+
amount: '$ 100'
|
39
|
+
)
|
40
|
+
|
41
|
+
invoice = InvoicePrinter::Document.new(
|
42
|
+
number: '201604030001',
|
43
|
+
provider_name: 'Business s.r.o.',
|
44
|
+
provider_tax_id: '56565656',
|
45
|
+
provider_tax_id2: '465454',
|
46
|
+
provider_street: 'Rolnicka',
|
47
|
+
provider_street_number: '1',
|
48
|
+
provider_postcode: '747 05',
|
49
|
+
provider_city: 'Opava',
|
50
|
+
provider_city_part: 'Katerinky',
|
51
|
+
provider_extra_address_line: 'Czech Republic',
|
52
|
+
purchaser_name: 'Adam',
|
53
|
+
purchaser_tax_id: '',
|
54
|
+
purchaser_tax_id2: '',
|
55
|
+
purchaser_street: 'Ostravska',
|
56
|
+
purchaser_street_number: '1',
|
57
|
+
purchaser_postcode: '747 70',
|
58
|
+
purchaser_city: 'Opava',
|
59
|
+
purchaser_city_part: '',
|
60
|
+
purchaser_extra_address_line: '',
|
61
|
+
issue_date: '19/03/3939',
|
62
|
+
due_date: '19/03/3939',
|
63
|
+
subtotal: '175',
|
64
|
+
tax: '5',
|
65
|
+
tax2: '10',
|
66
|
+
tax3: '20',
|
67
|
+
total: '$ 200',
|
68
|
+
bank_account_number: '156546546465',
|
69
|
+
account_iban: 'IBAN464545645',
|
70
|
+
account_swift: 'SWIFT5456',
|
71
|
+
items: [item],
|
72
|
+
note: 'A note...'
|
73
|
+
)
|
74
|
+
```
|
75
|
+
|
76
|
+
### Ruby on Rails
|
77
|
+
|
78
|
+
If you want to use InvoicePrinter for printing PDF documents directly from Rails
|
79
|
+
actions, you can:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# GET /invoices/1
|
83
|
+
def show
|
84
|
+
invoice = InvoicePrinter::Document.new(...)
|
85
|
+
|
86
|
+
respond_to do |format|
|
87
|
+
format.pdf {
|
88
|
+
@pdf = InvoicePrinter.render(
|
89
|
+
document: invoice
|
90
|
+
)
|
91
|
+
send_data @pdf, type: 'application/pdf', disposition: 'inline'
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
### JSON format
|
98
|
+
|
99
|
+
JSON format is supported via `from_json` method. JSON itself mimicks the original Ruby objects:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
json = InvoicePrinter::Document.new(...).to_json
|
103
|
+
document = InvoicePrinter::Document.from_json(json)
|
104
|
+
|
105
|
+
|
106
|
+
InvoicePrinter.print(
|
107
|
+
document: document,
|
108
|
+
...
|
109
|
+
)
|
110
|
+
|
111
|
+
```
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
## Customization
|
116
|
+
|
117
|
+
### Page size
|
118
|
+
|
119
|
+
Both A4 and US letter is supported. Just pass `page_size` as an argument to `print` or `render` methods:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
InvoicePrinter.print(
|
123
|
+
document: invoice,
|
124
|
+
labels: labels,
|
125
|
+
page_size: :a4,
|
126
|
+
file_name: 'invoice.pdf'
|
127
|
+
)
|
128
|
+
```
|
129
|
+
|
130
|
+
`:letter` is the default.
|
131
|
+
|
132
|
+
|
133
|
+
### Localization
|
134
|
+
|
135
|
+
To localize your documents you can set both global defaults or instance
|
136
|
+
overrides:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
InvoicePrinter.labels = {
|
140
|
+
provider: 'Dodavatel'
|
141
|
+
}
|
142
|
+
|
143
|
+
labels = {
|
144
|
+
purchaser: 'Customer'
|
145
|
+
}
|
146
|
+
|
147
|
+
InvoicePrinter.print(
|
148
|
+
document: invoice,
|
149
|
+
labels: labels,
|
150
|
+
file_name: 'invoice.pdf'
|
151
|
+
)
|
152
|
+
```
|
153
|
+
|
154
|
+
Here is the full list of labels to configure. You can paste and edit this block
|
155
|
+
to `initializers/invoice_printer.rb` if you are using Rails.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
InvoicePrinter.labels = {
|
159
|
+
name: 'Invoice',
|
160
|
+
provider: 'Provider',
|
161
|
+
purchaser: 'Purchaser',
|
162
|
+
tax_id: 'Identification number',
|
163
|
+
tax_id2: 'Identification number',
|
164
|
+
payment: 'Payment',
|
165
|
+
payment_by_transfer: 'Payment by bank transfer on the account below:',
|
166
|
+
payment_in_cash: 'Payment in cash',
|
167
|
+
account_number: 'Account NO',
|
168
|
+
swift: 'SWIFT',
|
169
|
+
iban: 'IBAN',
|
170
|
+
issue_date: 'Issue date',
|
171
|
+
due_date: 'Due date',
|
172
|
+
item: 'Item',
|
173
|
+
quantity: 'Quantity',
|
174
|
+
unit: 'Unit',
|
175
|
+
price_per_item: 'Price per item',
|
176
|
+
amount: 'Amount',
|
177
|
+
tax: 'Tax',
|
178
|
+
tax2: 'Tax 2',
|
179
|
+
tax3: 'Tax 3',
|
180
|
+
subtotal: 'Subtotal',
|
181
|
+
total: 'Total'
|
182
|
+
}
|
183
|
+
```
|
184
|
+
|
185
|
+
You can also use sublabels feature to provide the document in two languages:
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
labels = {
|
189
|
+
...
|
190
|
+
}
|
191
|
+
|
192
|
+
sublabels = {
|
193
|
+
name: 'Faktura',
|
194
|
+
provider: 'Prodejce',
|
195
|
+
purchaser: 'Kupující',
|
196
|
+
tax_id: 'IČ',
|
197
|
+
tax_id2: 'DIČ',
|
198
|
+
payment: 'Forma úhrady',
|
199
|
+
payment_by_transfer: 'Platba na následující účet:',
|
200
|
+
account_number: 'Číslo účtu',
|
201
|
+
issue_date: 'Datum vydání',
|
202
|
+
due_date: 'Datum splatnosti',
|
203
|
+
item: 'Položka',
|
204
|
+
quantity: 'Počet',
|
205
|
+
unit: 'MJ',
|
206
|
+
price_per_item: 'Cena za položku',
|
207
|
+
amount: 'Celkem bez daně',
|
208
|
+
subtotal: 'Cena bez daně',
|
209
|
+
tax: 'DPH 21 %',
|
210
|
+
total: 'Celkem'
|
211
|
+
}
|
212
|
+
|
213
|
+
labels.merge!({ sublabels: sublabels })
|
214
|
+
|
215
|
+
...
|
216
|
+
```
|
217
|
+
|
218
|
+
Now the document will have a little sublabels next to the original labels in Czech.
|
219
|
+
|
220
|
+
### Font
|
221
|
+
|
222
|
+
To support specific characters you might need to specify a TTF font to be used:
|
223
|
+
|
224
|
+
``` ruby
|
225
|
+
InvoicePrinter.print(
|
226
|
+
...
|
227
|
+
font: File.expand_path('../Overpass-Regular.ttf', __FILE__)
|
228
|
+
)
|
229
|
+
```
|
230
|
+
|
231
|
+
We recommend you DejaVuSans and Overpass fonts.
|
232
|
+
|
233
|
+
### Background
|
234
|
+
|
235
|
+
To include a background image you might need to create the file according to the size and resolution to be used (see: [examples/background.png](https://github.com/strzibny/invoice_printer/blob/master/examples/background.png)):
|
236
|
+
|
237
|
+
``` ruby
|
238
|
+
InvoicePrinter.print(
|
239
|
+
...
|
240
|
+
background: File.expand_path('../background.png', __FILE__)
|
241
|
+
)
|
242
|
+
```
|
data/docs/SERVER.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# InvoicePrinter Server
|
2
|
+
|
3
|
+
InvoicePrinter contains a built in server that can be run from a command line with `invoice_printer_server`.
|
4
|
+
|
5
|
+
Apart from this you can also manually mount the server inside of your Rack application.
|
6
|
+
|
7
|
+
## Running the server
|
8
|
+
|
9
|
+
### From command line
|
10
|
+
|
11
|
+
Once installed, InvoicePrinter provides `invoice_printer_server` executable that starts the Puma server:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
invoice_printer_server -h 0.0.0.0 -p 5000
|
15
|
+
```
|
16
|
+
|
17
|
+
`-h` defines a host and `-p` defines a port. For help you can run `--help`.
|
18
|
+
|
19
|
+
By default server binds to `0.0.0.0:9393`.
|
20
|
+
|
21
|
+
### As mountable Rack app
|
22
|
+
|
23
|
+
If you want you can always run the server from your custom program or mount it directly from a Rack app.
|
24
|
+
|
25
|
+
`InvoicePrinter::Server` is a Rack app as any other. Example:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'rack/handler/puma'
|
29
|
+
require 'invoice_printer/server'
|
30
|
+
|
31
|
+
Rack::Handler::Puma.run InvoicePrinter::Server.freeze.app
|
32
|
+
```
|
33
|
+
|
34
|
+
## Available API
|
35
|
+
|
36
|
+
Endpoints accept similar arguments as the corresponding methods to `InvoicePrinter`. `render` is used for directly getting the PDF output whereas `print` would accept `filename` option and save the document to that
|
37
|
+
file.
|
38
|
+
|
39
|
+
A content type is always `application/json` both for requests and responses.
|
40
|
+
|
41
|
+
### `POST /render`
|
42
|
+
|
43
|
+
Directly render PDF data.
|
44
|
+
|
45
|
+
Options:
|
46
|
+
|
47
|
+
- `document` - JSON representation of the document
|
48
|
+
- `labels` - JSON for labels
|
49
|
+
- `stamp` - path to stamp file
|
50
|
+
- `logo` - path to logotype file
|
51
|
+
- `font` - path to font file
|
52
|
+
|
53
|
+
On success a `200` response is returned:
|
54
|
+
|
55
|
+
```json
|
56
|
+
{ "result": "ok", "data": "base64 encoded PDF document" }
|
57
|
+
```
|
58
|
+
|
59
|
+
On error a `400` response is returned:
|
60
|
+
|
61
|
+
```json
|
62
|
+
{ "result": "error", "error": "error description" }
|
63
|
+
```
|
64
|
+
|
65
|
+
#### Example
|
66
|
+
|
67
|
+
Example of calling the API to render a document using `curl`:
|
68
|
+
|
69
|
+
```
|
70
|
+
$ curl -X POST http://0.0.0.0:9393/render -H "Content-Type: application/json" --data '{"document":{"number":"c. 198900000001","provider_name":"Petr Novy","provider_tax_id":"56565656","provider_tax_id2":"","provider_street":"Rolnicka","provider_street_number":"1","provider_postcode":"747 05","provider_city":"Opava","provider_city_part":"Katerinky","provider_extra_address_line":"","purchaser_name":"Adam Cerny","purchaser_tax_id":"","purchaser_tax_id2":"","purchaser_street":"Ostravska","purchaser_street_number":"1","purchaser_postcode":"747 70","purchaser_city":"Opava","purchaser_city_part":"","purchaser_extra_address_line":"","issue_date":"05/03/2016","due_date":"19/03/2016","subtotal":"Kc 10.000","tax":"Kc 2.100","tax2":"","tax3":"","total":"Kc 12.100,-","bank_account_number":"156546546465","account_iban":"IBAN464545645","account_swift":"SWIFT5456","items":[{"name":"Konzultace","quantity":"2","unit":"hod","price":"Kc 500","tax":"","tax2":"","tax3":"","amount":"Kc 1.000"},{"name":"Programovani","quantity":"10","unit":"hod","price":"Kc 900","tax":"","tax2":"","tax3":"","amount":"Kc 9.000"}],"note":"Osoba je zapsána v zivnostenském rejstríku."}}'
|
71
|
+
```
|
72
|
+
|
73
|
+
### `POST /print`
|
74
|
+
|
75
|
+
Print resulting document to a file.
|
76
|
+
|
77
|
+
Options:
|
78
|
+
|
79
|
+
- `document` - JSON representation of the document
|
80
|
+
- `labels` - JSON for labels
|
81
|
+
- `stamp` - path to stamp file
|
82
|
+
- `logo` - path to logotype file
|
83
|
+
- `font` - path to font file
|
84
|
+
- `filename` - path for saving the generated output PDF
|
85
|
+
|
86
|
+
On success a `200` response is returned:
|
87
|
+
|
88
|
+
```json
|
89
|
+
{ "result": "ok", "path": "/path/basically/what/was/sent/as/filepath" }
|
90
|
+
```
|
91
|
+
|
92
|
+
On error a `400` response is returned:
|
93
|
+
|
94
|
+
```json
|
95
|
+
{ "result": "error", "error": "error description" }
|
96
|
+
```
|
data/invoice_printer.gemspec
CHANGED
@@ -15,7 +15,6 @@ Gem::Specification.new do |spec|
|
|
15
15
|
# Remove .pdf files as they take a lot of space
|
16
16
|
package_files = `git ls-files -z`.split("\x0")
|
17
17
|
.reject{ |file| file.match /.*\.pdf/ }
|
18
|
-
.reject{ |file| file.match /docs\/.*/ }
|
19
18
|
|
20
19
|
spec.files = package_files
|
21
20
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -24,6 +23,8 @@ Gem::Specification.new do |spec|
|
|
24
23
|
spec.bindir = 'bin'
|
25
24
|
|
26
25
|
spec.add_dependency 'json', '~> 2.1'
|
26
|
+
spec.add_dependency 'roda', '3.5.0'
|
27
|
+
spec.add_dependency 'puma', '~> 3.9'
|
27
28
|
spec.add_dependency 'prawn', '2.1.0'
|
28
29
|
spec.add_dependency 'prawn-layout', '0.8.4'
|
29
30
|
|
data/lib/invoice_printer.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
require 'json'
|
1
2
|
require 'invoice_printer/version'
|
3
|
+
require 'invoice_printer/document'
|
2
4
|
require 'invoice_printer/document/item'
|
3
5
|
require 'invoice_printer/pdf_document'
|
4
6
|
|
7
|
+
# Skip warning for not specifying TTF font
|
8
|
+
Prawn::Font::AFM.hide_m17n_warning = true
|
9
|
+
|
5
10
|
# Create PDF versions of invoices or receipts using Prawn
|
6
11
|
#
|
7
12
|
# Example:
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'roda'
|
3
|
+
require 'invoice_printer'
|
4
|
+
|
5
|
+
class InvoicePrinter::Server < Roda
|
6
|
+
route do |r|
|
7
|
+
response['Content-Type'] = 'application/json'
|
8
|
+
|
9
|
+
# Assign params
|
10
|
+
if r.content_type == 'application/json'
|
11
|
+
begin
|
12
|
+
params = JSON.parse(r.body.read, symbolize_names: true)
|
13
|
+
rescue => e
|
14
|
+
response.status = 400
|
15
|
+
response.write({ result: 'error', error: 'Invalid JSON.' }.to_json)
|
16
|
+
r.halt
|
17
|
+
end
|
18
|
+
|
19
|
+
labels = params[:labels]
|
20
|
+
document = params[:document]
|
21
|
+
stamp = params[:stamp]
|
22
|
+
logo = params[:logo]
|
23
|
+
font = params[:font]
|
24
|
+
else
|
25
|
+
response.status = 400
|
26
|
+
response.write({ result: 'error', error: 'No JSON. Did you set Content-Type to application/json?' }.to_json)
|
27
|
+
r.halt
|
28
|
+
end
|
29
|
+
|
30
|
+
# Initialize InvoicePrinter::Document from given JSON
|
31
|
+
begin
|
32
|
+
document[:items] ||= []
|
33
|
+
items = document[:items].map { |item| InvoicePrinter::Document::Item.new(**item) }
|
34
|
+
document[:items] = items
|
35
|
+
|
36
|
+
document = InvoicePrinter::Document.new(**document)
|
37
|
+
rescue => e
|
38
|
+
response.status = 400
|
39
|
+
response.write({ result: 'error', error: 'Invalid JSON document.' }.to_json)
|
40
|
+
r.halt
|
41
|
+
end
|
42
|
+
|
43
|
+
# POST /print
|
44
|
+
r.post 'print' do
|
45
|
+
filename = params[:filename] || 'document.pdf'
|
46
|
+
|
47
|
+
begin
|
48
|
+
InvoicePrinter.print(
|
49
|
+
document: document,
|
50
|
+
font: font,
|
51
|
+
stamp: stamp,
|
52
|
+
logo: logo,
|
53
|
+
file_name: filename
|
54
|
+
)
|
55
|
+
|
56
|
+
{ result: 'ok', path: filename }.to_json
|
57
|
+
rescue => e
|
58
|
+
response.status = 400
|
59
|
+
response.write({ result: 'error', error: e.message }.to_json)
|
60
|
+
r.halt
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# POST /render
|
65
|
+
r.post 'render' do
|
66
|
+
begin
|
67
|
+
stream = InvoicePrinter.render(
|
68
|
+
document: document,
|
69
|
+
font: font,
|
70
|
+
stamp: stamp,
|
71
|
+
logo: logo
|
72
|
+
)
|
73
|
+
|
74
|
+
{ result: 'ok', data: Base64.encode64(stream) }.to_json
|
75
|
+
rescue => e
|
76
|
+
response.status = 400
|
77
|
+
response.write({ result: 'error', error: e.message }.to_json)
|
78
|
+
r.halt
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/test/api_test.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'invoice_printer/server'
|
3
|
+
|
4
|
+
class ApiTest < Minitest::Test
|
5
|
+
include Rack::Test::Methods
|
6
|
+
include InvoicePrinterHelpers
|
7
|
+
|
8
|
+
def app
|
9
|
+
InvoicePrinter::Server.freeze.app
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@test_dir = File.absolute_path('./tmp/invoice_printer_api')
|
14
|
+
FileUtils.mkdir_p @test_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown
|
18
|
+
FileUtils.rm_rf @test_dir if File.exists?(@test_dir)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Test POST /print
|
22
|
+
|
23
|
+
def test_print_with_invalid_json
|
24
|
+
header 'Content-Type', 'application/json'
|
25
|
+
post '/print', nil
|
26
|
+
|
27
|
+
body = JSON.parse last_response.body
|
28
|
+
|
29
|
+
assert !last_response.ok?
|
30
|
+
assert_equal body, { 'result' => 'error', 'error' => 'Invalid JSON.' }
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_print_with_valid_document
|
34
|
+
invoice = InvoicePrinter::Document.new(default_document_params)
|
35
|
+
|
36
|
+
json = {
|
37
|
+
'document' => invoice.to_h,
|
38
|
+
'filename' => "#{@test_dir}/test"
|
39
|
+
}.to_json
|
40
|
+
|
41
|
+
header 'Content-Type', 'application/json'
|
42
|
+
post '/print', json
|
43
|
+
|
44
|
+
body = JSON.parse last_response.body
|
45
|
+
|
46
|
+
assert last_response.ok?
|
47
|
+
assert_equal body, { 'result' => 'ok', 'path' => "#{@test_dir}/test" }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Test POST /render
|
51
|
+
|
52
|
+
def test_render_with_invalid_json
|
53
|
+
header 'Content-Type', 'application/json'
|
54
|
+
post '/render', nil
|
55
|
+
|
56
|
+
body = JSON.parse last_response.body
|
57
|
+
|
58
|
+
assert !last_response.ok?
|
59
|
+
assert_equal body, { 'result' => 'error', 'error' => 'Invalid JSON.' }
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_render_with_valid_document
|
63
|
+
invoice = InvoicePrinter::Document.new(default_document_params)
|
64
|
+
output = InvoicePrinter.render(document: invoice)
|
65
|
+
|
66
|
+
json = {
|
67
|
+
'document' => invoice.to_h
|
68
|
+
}.to_json
|
69
|
+
|
70
|
+
header 'Content-Type', 'application/json'
|
71
|
+
post '/render', json
|
72
|
+
|
73
|
+
body = JSON.parse last_response.body
|
74
|
+
|
75
|
+
assert last_response.ok?
|
76
|
+
assert_equal body, { 'result' => 'ok', 'data' => Base64.encode64(output) }
|
77
|
+
end
|
78
|
+
end
|
data/test/test_helper.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.2.0.alpha1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josef Strzibny
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: roda
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.5.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.5.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: puma
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.9'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.9'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: prawn
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -85,6 +113,7 @@ email:
|
|
85
113
|
- strzibny@strzibny.name
|
86
114
|
executables:
|
87
115
|
- invoice_printer
|
116
|
+
- invoice_printer_server
|
88
117
|
extensions: []
|
89
118
|
extra_rdoc_files: []
|
90
119
|
files:
|
@@ -93,7 +122,13 @@ files:
|
|
93
122
|
- LICENSE.txt
|
94
123
|
- README.md
|
95
124
|
- Rakefile
|
125
|
+
- assets/logo.png
|
96
126
|
- bin/invoice_printer
|
127
|
+
- bin/invoice_printer_server
|
128
|
+
- docs/COMMAND_LINE.md
|
129
|
+
- docs/INSTALLATION.md
|
130
|
+
- docs/LIBRARY.md
|
131
|
+
- docs/SERVER.md
|
97
132
|
- examples/background.png
|
98
133
|
- examples/complex_invoice.rb
|
99
134
|
- examples/czech_invoice.rb
|
@@ -110,7 +145,9 @@ files:
|
|
110
145
|
- lib/invoice_printer/document.rb
|
111
146
|
- lib/invoice_printer/document/item.rb
|
112
147
|
- lib/invoice_printer/pdf_document.rb
|
148
|
+
- lib/invoice_printer/server.rb
|
113
149
|
- lib/invoice_printer/version.rb
|
150
|
+
- test/api_test.rb
|
114
151
|
- test/background_test.rb
|
115
152
|
- test/cli_test.rb
|
116
153
|
- test/dates_box_test.rb
|
@@ -139,16 +176,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
176
|
version: '0'
|
140
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
178
|
requirements:
|
142
|
-
- - "
|
179
|
+
- - ">"
|
143
180
|
- !ruby/object:Gem::Version
|
144
|
-
version:
|
181
|
+
version: 1.3.1
|
145
182
|
requirements: []
|
146
183
|
rubyforge_project:
|
147
|
-
rubygems_version: 2.6.
|
184
|
+
rubygems_version: 2.6.14
|
148
185
|
signing_key:
|
149
186
|
specification_version: 4
|
150
187
|
summary: Super simple PDF invoicing in pure Ruby
|
151
188
|
test_files:
|
189
|
+
- test/api_test.rb
|
152
190
|
- test/background_test.rb
|
153
191
|
- test/cli_test.rb
|
154
192
|
- test/dates_box_test.rb
|