good_receipt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []