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 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
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'google-cloud-storage'
4
+ gem 'prawn'
5
+ gem 'prawn-table'
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,10 @@
1
+ module Constants
2
+ COLOR_DEFAULT = 'FFA500'
3
+ COLOR_RED = 'E35555'
4
+ COLOR_GREEN = '4DA63D'
5
+ COLOR_YELLOW = 'FFE83D'
6
+ COLOR_GRAY = '888888'
7
+ COLOR_BLACK = '000000'
8
+ COLOR_TABLE_BORDER = 'EEEEEE'
9
+ COLOR_WHITE = '4D92DF'
10
+ end
@@ -0,0 +1,6 @@
1
+ require 'date'
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'prawn'
5
+ require 'prawn/table'
6
+ require 'uri'
@@ -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
@@ -0,0 +1,3 @@
1
+ module GoodReceipt
2
+ VERSION = '1.0.0'.freeze
3
+ end
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: []