good_receipt 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +0 -0
- data/lib/Gemfile +5 -0
- data/lib/Gemfile.lock +75 -0
- data/lib/example/receipt-1.pdf +0 -0
- data/lib/fonts/AvenirNext/bold.ttf +0 -0
- data/lib/fonts/AvenirNext/italic.ttf +0 -0
- data/lib/fonts/AvenirNext/regular.ttf +0 -0
- data/lib/good_receipt/base.rb +210 -0
- data/lib/good_receipt/constants.rb +10 -0
- data/lib/good_receipt/index.rb +6 -0
- data/lib/good_receipt/receipt.rb +119 -0
- data/lib/good_receipt/version.rb +3 -0
- data/lib/images/main.png +0 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8a4f2c955fc805eb006bb97a9c136c81a8bf197a2c808dfe2c696f2b10da80bf
|
4
|
+
data.tar.gz: 16e276be9bad452712df8bece0918fb61f869f4e8f08623e74fc31a57a875590
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e34061d4101b24604e6c54e611a088917f858916a4a9759f688fcb1cdacf7cae8290a098a3fd9f993af070b8f9a04a03d95821623636be90bd3983d94af1375a
|
7
|
+
data.tar.gz: d6674b42a1df209119dac673b7d4a2c86802dc6acfc0fb2184b635ae873dbc1bb48d6b384487ffb3c73913841d39fde80b4f0f19328153b65c5012a9dc41805c
|
data/README.md
ADDED
File without changes
|
data/lib/Gemfile
ADDED
data/lib/Gemfile.lock
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.7.0)
|
5
|
+
public_suffix (>= 2.0.2, < 5.0)
|
6
|
+
declarative (0.0.20)
|
7
|
+
declarative-option (0.1.0)
|
8
|
+
digest-crc (0.6.1)
|
9
|
+
rake (~> 13.0)
|
10
|
+
faraday (0.17.3)
|
11
|
+
multipart-post (>= 1.2, < 3)
|
12
|
+
google-api-client (0.32.1)
|
13
|
+
addressable (~> 2.5, >= 2.5.1)
|
14
|
+
googleauth (>= 0.5, < 0.10.0)
|
15
|
+
httpclient (>= 2.8.1, < 3.0)
|
16
|
+
mini_mime (~> 1.0)
|
17
|
+
representable (~> 3.0)
|
18
|
+
retriable (>= 2.0, < 4.0)
|
19
|
+
signet (~> 0.10)
|
20
|
+
google-cloud-core (1.3.2)
|
21
|
+
google-cloud-env (~> 1.0)
|
22
|
+
google-cloud-env (1.2.1)
|
23
|
+
faraday (~> 0.11)
|
24
|
+
google-cloud-storage (1.21.1)
|
25
|
+
addressable (~> 2.5)
|
26
|
+
digest-crc (~> 0.4)
|
27
|
+
google-api-client (~> 0.26)
|
28
|
+
google-cloud-core (~> 1.2)
|
29
|
+
googleauth (>= 0.6.2, < 0.10.0)
|
30
|
+
mini_mime (~> 1.0)
|
31
|
+
googleauth (0.9.0)
|
32
|
+
faraday (~> 0.12)
|
33
|
+
jwt (>= 1.4, < 3.0)
|
34
|
+
memoist (~> 0.16)
|
35
|
+
multi_json (~> 1.11)
|
36
|
+
os (>= 0.9, < 2.0)
|
37
|
+
signet (~> 0.7)
|
38
|
+
httpclient (2.8.3)
|
39
|
+
jwt (2.2.2)
|
40
|
+
memoist (0.16.2)
|
41
|
+
mini_mime (1.0.2)
|
42
|
+
multi_json (1.15.0)
|
43
|
+
multipart-post (2.1.1)
|
44
|
+
os (1.1.1)
|
45
|
+
pdf-core (0.7.0)
|
46
|
+
prawn (2.2.2)
|
47
|
+
pdf-core (~> 0.7.0)
|
48
|
+
ttfunk (~> 1.5)
|
49
|
+
prawn-table (0.2.2)
|
50
|
+
prawn (>= 1.3.0, < 3.0.0)
|
51
|
+
public_suffix (4.0.6)
|
52
|
+
rake (13.0.1)
|
53
|
+
representable (3.0.4)
|
54
|
+
declarative (< 0.1.0)
|
55
|
+
declarative-option (< 0.2.0)
|
56
|
+
uber (< 0.2.0)
|
57
|
+
retriable (3.1.2)
|
58
|
+
signet (0.11.0)
|
59
|
+
addressable (~> 2.3)
|
60
|
+
faraday (~> 0.9)
|
61
|
+
jwt (>= 1.5, < 3.0)
|
62
|
+
multi_json (~> 1.10)
|
63
|
+
ttfunk (1.5.1)
|
64
|
+
uber (0.1.0)
|
65
|
+
|
66
|
+
PLATFORMS
|
67
|
+
ruby
|
68
|
+
|
69
|
+
DEPENDENCIES
|
70
|
+
google-cloud-storage
|
71
|
+
prawn
|
72
|
+
prawn-table
|
73
|
+
|
74
|
+
BUNDLED WITH
|
75
|
+
2.4.19
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'prawn'
|
2
|
+
require 'prawn/table'
|
3
|
+
require_relative 'constants'
|
4
|
+
require_relative 'index'
|
5
|
+
|
6
|
+
module GoodReceipt
|
7
|
+
class Configuration
|
8
|
+
attr_accessor :business_name, :business_phone, :business_email, :storage_bucket, :storage_project_id,
|
9
|
+
:storage_credentials, :logo_path
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@business_name = 'Business Name'
|
13
|
+
@business_phone = '(000) 111-1234'
|
14
|
+
@business_email = 'email@email.com'
|
15
|
+
@storage_project_id = 'google-cloud-project'
|
16
|
+
@storage_bucket = 'google-cloud-bucket-name'
|
17
|
+
@storage_credentials = '/path/to/credentials'
|
18
|
+
@logo_path = nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ReceiptError < StandardError; end
|
23
|
+
|
24
|
+
class ReceiptDataError < ReceiptError; end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_accessor :configuration
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.configure
|
31
|
+
self.configuration ||= Configuration.new
|
32
|
+
yield(configuration)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@configuration = self.class.configuration
|
37
|
+
end
|
38
|
+
|
39
|
+
class Base
|
40
|
+
private
|
41
|
+
|
42
|
+
def pdf
|
43
|
+
@pdf ||= setup_pdf
|
44
|
+
end
|
45
|
+
|
46
|
+
def header_info_column(data)
|
47
|
+
pdf.make_table(data, cell_style: { border_width: 0 }) do
|
48
|
+
cells.style(size: 34)
|
49
|
+
cells.style do |c|
|
50
|
+
if (c.row % 2).zero?
|
51
|
+
c.text_color = Constants::COLOR_GRAY
|
52
|
+
else
|
53
|
+
c.font_style = :bold
|
54
|
+
c.padding_bottom = 15
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def header(title, data)
|
61
|
+
data = data.map { |col| col.map { |item| [item] } }
|
62
|
+
col1 = header_info_column(data[0])
|
63
|
+
col2 = pdf.make_table(data[1], position: :right, cell_style: { border_width: 0 }) do
|
64
|
+
cells.style(size: 35, align: :right)
|
65
|
+
end
|
66
|
+
|
67
|
+
pdf.table(
|
68
|
+
[
|
69
|
+
[title, '', { image: GoodReceipt.configuration&.logo_path || './images/main.png', position: :right }],
|
70
|
+
[col1, '', col2]
|
71
|
+
],
|
72
|
+
width: pdf.bounds.width,
|
73
|
+
cell_style: { border_width: 0 }
|
74
|
+
) do
|
75
|
+
row(0).font_style = :bold
|
76
|
+
row(0).size = 59
|
77
|
+
row(0).text_color = Constants::COLOR_DEFAULT
|
78
|
+
end
|
79
|
+
|
80
|
+
pdf.move_down 80
|
81
|
+
end
|
82
|
+
|
83
|
+
def report_header(title, meta_info)
|
84
|
+
if GoodReceipt.configuration.nil?
|
85
|
+
raise StandardError, 'Configuration not set. Please call GoodReceipt.configure to set the configuration.'
|
86
|
+
end
|
87
|
+
|
88
|
+
dt = meta_info[:date] || Time.now.getlocal('-06:00').strftime('%Y-%m-%d %I:%M%P').to_s
|
89
|
+
|
90
|
+
header(
|
91
|
+
title,
|
92
|
+
[
|
93
|
+
[
|
94
|
+
'Customer Name', meta_info[:customer_name],
|
95
|
+
'Date', dt
|
96
|
+
],
|
97
|
+
[
|
98
|
+
GoodReceipt.configuration.business_name, GoodReceipt.configuration.business_phone, GoodReceipt.configuration.business_email
|
99
|
+
]
|
100
|
+
]
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def space(height = 30)
|
105
|
+
pdf.move_down height
|
106
|
+
end
|
107
|
+
|
108
|
+
def hr_line
|
109
|
+
pdf.stroke do
|
110
|
+
pdf.stroke_color Constants::COLOR_DEFAULT
|
111
|
+
pdf.line_width 10
|
112
|
+
pdf.stroke_horizontal_rule
|
113
|
+
end
|
114
|
+
space
|
115
|
+
end
|
116
|
+
|
117
|
+
def section_title(text, underline: false)
|
118
|
+
pdf.text text, size: 60, color: Constants::COLOR_DEFAULT
|
119
|
+
space
|
120
|
+
hr_line if underline
|
121
|
+
end
|
122
|
+
|
123
|
+
def list(data, text_color: Constants::COLOR_DEFAULT)
|
124
|
+
pdf.table(data, width: pdf.bounds.width, cell_style: { inline_format: true }) do
|
125
|
+
cells.size = 39
|
126
|
+
cells.borders = [:bottom]
|
127
|
+
cells.border_color = Constants::COLOR_TABLE_BORDER
|
128
|
+
cells.border_width = 4
|
129
|
+
cells.padding_bottom = 20
|
130
|
+
cells.padding_top = 50
|
131
|
+
cells.font_style = :bold
|
132
|
+
|
133
|
+
columns(0).text_color = text_color
|
134
|
+
columns(0).padding_left = 30
|
135
|
+
columns(1).text_color = Constants::COLOR_GRAY
|
136
|
+
columns(1).padding_right = 30
|
137
|
+
columns(1).align = :right
|
138
|
+
end
|
139
|
+
|
140
|
+
space
|
141
|
+
end
|
142
|
+
|
143
|
+
def conditions_list(data, text_color: Constants::COLOR_DEFAULT)
|
144
|
+
hr_line
|
145
|
+
|
146
|
+
pdf.table(data, width: pdf.bounds.width, cell_style: { inline_format: true }) do
|
147
|
+
cells.size = 30
|
148
|
+
cells.borders = [:bottom]
|
149
|
+
cells.border_color = Constants::COLOR_TABLE_BORDER
|
150
|
+
cells.border_width = 4
|
151
|
+
cells.padding_bottom = 20
|
152
|
+
cells.padding_top = 50
|
153
|
+
cells.font_style = :bold
|
154
|
+
|
155
|
+
columns(0).text_color = text_color
|
156
|
+
columns(0).padding_left = 30
|
157
|
+
columns(1).text_color = Constants::COLOR_GRAY
|
158
|
+
columns(1).padding_right = 30
|
159
|
+
columns(1).align = :right
|
160
|
+
columns(2).align = :right
|
161
|
+
columns(3).align = :right
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def price_table(data)
|
166
|
+
cols = data.first.count
|
167
|
+
last_col = cols - 1
|
168
|
+
pdf.table(data, width: pdf.bounds.width, cell_style: { inline_format: true }) do
|
169
|
+
cells.size = 39
|
170
|
+
cells.borders = [:bottom]
|
171
|
+
cells.border_color = Constants::COLOR_TABLE_BORDER
|
172
|
+
cells.border_width = 4
|
173
|
+
cells.padding_bottom = 20
|
174
|
+
cells.padding_top = 50
|
175
|
+
cells.text_color = Constants::COLOR_GRAY
|
176
|
+
|
177
|
+
row(0).font_style = :bold
|
178
|
+
|
179
|
+
column(0).font_style = :bold
|
180
|
+
column(0).text_color = Constants::COLOR_DEFAULT
|
181
|
+
column(0).padding_left = 30
|
182
|
+
|
183
|
+
column(1..last_col).align = :right
|
184
|
+
column(1..last_col).width = 350
|
185
|
+
|
186
|
+
column(last_col).padding_right = 30
|
187
|
+
column(last_col).font_style = :bold
|
188
|
+
|
189
|
+
row(0).text_color = Constants::COLOR_BLACK
|
190
|
+
row(0).border_color = Constants::COLOR_BLACK
|
191
|
+
end
|
192
|
+
pdf.move_down 80
|
193
|
+
end
|
194
|
+
|
195
|
+
def setup_pdf
|
196
|
+
pdf = Prawn::Document.new(
|
197
|
+
page_size: [2480, 3508],
|
198
|
+
margin: [140, 180]
|
199
|
+
)
|
200
|
+
pdf.font_families.update('AvenirNext' => {
|
201
|
+
normal: './fonts/AvenirNext/regular.ttf',
|
202
|
+
bold: './fonts/AvenirNext/bold.ttf',
|
203
|
+
italic: './fonts/AvenirNext/italic.ttf'
|
204
|
+
})
|
205
|
+
|
206
|
+
pdf.font 'AvenirNext'
|
207
|
+
pdf
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative 'index'
|
2
|
+
require_relative 'base'
|
3
|
+
require 'google/cloud/storage'
|
4
|
+
|
5
|
+
module GoodReceipt
|
6
|
+
class Receipt < Base
|
7
|
+
def initialize(data)
|
8
|
+
super()
|
9
|
+
validate_data(data)
|
10
|
+
@data = data
|
11
|
+
end
|
12
|
+
|
13
|
+
def table_items
|
14
|
+
items = [
|
15
|
+
['Description', 'Qty', 'Unit Price', 'Line Total']
|
16
|
+
]
|
17
|
+
|
18
|
+
@data[:line_items].each do |line_item|
|
19
|
+
items << [line_item[:name], '', '', '']
|
20
|
+
|
21
|
+
total = 0.0
|
22
|
+
|
23
|
+
line_item[:items].each do |item|
|
24
|
+
total += item[:price].to_f
|
25
|
+
items << [item[:name], item[:quantity], "$#{item[:price]}",
|
26
|
+
"$#{(item[:price] * item[:quantity]).to_f.round(2)}"]
|
27
|
+
end
|
28
|
+
|
29
|
+
items << ['', '', '', "$#{total.round(2)}"]
|
30
|
+
end
|
31
|
+
|
32
|
+
items << ['', '', 'Discount', "- $#{@data[:discount].to_f.round(2)}"] if @data[:discount]
|
33
|
+
|
34
|
+
items << ['', '', 'Tax', "$#{@data[:tax]}"] if @data[:tax]
|
35
|
+
|
36
|
+
items << ['', '', 'Total Price', "$#{@data[:total_price]}"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def paid_line
|
40
|
+
pdf.table(
|
41
|
+
[
|
42
|
+
['', 'PAID']
|
43
|
+
],
|
44
|
+
width: pdf.bounds.width,
|
45
|
+
cell_style: { border_width: 0 }
|
46
|
+
) do
|
47
|
+
column(0).size = 40
|
48
|
+
|
49
|
+
column(1).align = :right
|
50
|
+
column(1).size = 70
|
51
|
+
column(1).text_color = Constants::COLOR_GREEN
|
52
|
+
column(1).font_style = :bold
|
53
|
+
column(1).padding_right = 50
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate
|
58
|
+
# Render PDF
|
59
|
+
report_header(
|
60
|
+
'Receipt',
|
61
|
+
{
|
62
|
+
customer_name: @data[:customer_name],
|
63
|
+
date: @data[:date]
|
64
|
+
}
|
65
|
+
)
|
66
|
+
price_table(table_items)
|
67
|
+
paid_line
|
68
|
+
|
69
|
+
# Save PDF into the file
|
70
|
+
path_name = "receipt-#{@data[:id]}.pdf"
|
71
|
+
pdf.render_file path_name
|
72
|
+
|
73
|
+
# Store PDF into the Google Cloud Storage
|
74
|
+
storage = ::Google::Cloud::Storage.new(
|
75
|
+
project_id: GoodReceipt.configuration.storage_project_id,
|
76
|
+
credentials: GoodReceipt.configuration.storage_credentials
|
77
|
+
)
|
78
|
+
|
79
|
+
bucket = storage.bucket GoodReceipt.configuration.storage_bucket
|
80
|
+
file = bucket.create_file path_name, "/#{@id}/receipt.pdf"
|
81
|
+
file.acl.public!
|
82
|
+
|
83
|
+
# Return the URL of the PDF
|
84
|
+
file.url
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def validate_data(data)
|
90
|
+
raise ReceiptDataError, "Invalid data format: #{data.inspect}. Data must be a hash." unless data.is_a?(Hash)
|
91
|
+
raise ReceiptDataError, "Invalid data format: #{data.inspect}" unless valid_data?(data)
|
92
|
+
end
|
93
|
+
|
94
|
+
def valid_data?(data)
|
95
|
+
expected_keys = %i[line_items customer_name discount total_price date id]
|
96
|
+
unless expected_keys.all? { |key| data.key?(key) }
|
97
|
+
raise ReceiptDataError,
|
98
|
+
"Missing or invalid key(s) in data: #{data.keys.inspect}. Expected keys: #{expected_keys.inspect}"
|
99
|
+
end
|
100
|
+
|
101
|
+
unless valid_line_items?(data[:line_items])
|
102
|
+
raise ReceiptDataError,
|
103
|
+
"Invalid format in line_items: #{data[:line_items].inspect}. Each item should be a hash with :name and :items keys."
|
104
|
+
end
|
105
|
+
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
def valid_line_items?(line_items)
|
110
|
+
unless line_items.is_a?(Array) &&
|
111
|
+
line_items.all? { |item| item.is_a?(Hash) && item.key?(:name) && item.key?(:items) }
|
112
|
+
raise ReceiptDataError,
|
113
|
+
"Invalid format in line_items: #{line_items.inspect}. Each item should be a hash with :name and :items keys."
|
114
|
+
end
|
115
|
+
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/images/main.png
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: good_receipt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sean Robenalt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-09-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: google-cloud-storage
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.21.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.21.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: prawn
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.2.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.2.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: prawn-table
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.2.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.2.2
|
55
|
+
description: This gem provides functionality for creating and managing receipts in
|
56
|
+
Ruby applications. It uses the Prawn gem for generation, and then uses the Google
|
57
|
+
Cloud Storage gem to send the generated receipts to the cloud.
|
58
|
+
email:
|
59
|
+
- srob0722@gmail.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- README.md
|
65
|
+
- lib/Gemfile
|
66
|
+
- lib/Gemfile.lock
|
67
|
+
- lib/example/receipt-1.pdf
|
68
|
+
- lib/fonts/AvenirNext/bold.ttf
|
69
|
+
- lib/fonts/AvenirNext/italic.ttf
|
70
|
+
- lib/fonts/AvenirNext/regular.ttf
|
71
|
+
- lib/good_receipt/base.rb
|
72
|
+
- lib/good_receipt/constants.rb
|
73
|
+
- lib/good_receipt/index.rb
|
74
|
+
- lib/good_receipt/receipt.rb
|
75
|
+
- lib/good_receipt/version.rb
|
76
|
+
- lib/images/main.png
|
77
|
+
homepage: https://github.com/seanrobenalt/good_receipt
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata:
|
81
|
+
allowed_push_host: https://rubygems.org
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 2.7.6
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubygems_version: 3.4.19
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Lightweight Ruby gem for generating and managing good receipt PDFs for your
|
101
|
+
business.
|
102
|
+
test_files: []
|