prawn-swiss_qr_bill 0.4.1
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/Gemfile +5 -0
- data/README.md +124 -0
- data/config/locales/de.yml +15 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/fr.yml +15 -0
- data/config/locales/it.yml +15 -0
- data/lib/prawn/swiss_qr_bill/bill.rb +20 -0
- data/lib/prawn/swiss_qr_bill/corner_box.rb +79 -0
- data/lib/prawn/swiss_qr_bill/cutting_lines.rb +74 -0
- data/lib/prawn/swiss_qr_bill/debug_section.rb +140 -0
- data/lib/prawn/swiss_qr_bill/extension.rb +18 -0
- data/lib/prawn/swiss_qr_bill/helpers/box_helper.rb +14 -0
- data/lib/prawn/swiss_qr_bill/helpers/number_helper.rb +16 -0
- data/lib/prawn/swiss_qr_bill/iban.rb +84 -0
- data/lib/prawn/swiss_qr_bill/images/scissor.png +0 -0
- data/lib/prawn/swiss_qr_bill/images/swiss_cross.png +0 -0
- data/lib/prawn/swiss_qr_bill/padded_box.rb +52 -0
- data/lib/prawn/swiss_qr_bill/qr/data.rb +105 -0
- data/lib/prawn/swiss_qr_bill/sections/payment_amount.rb +88 -0
- data/lib/prawn/swiss_qr_bill/sections/payment_further_information.rb +14 -0
- data/lib/prawn/swiss_qr_bill/sections/payment_information.rb +59 -0
- data/lib/prawn/swiss_qr_bill/sections/payment_title.rb +18 -0
- data/lib/prawn/swiss_qr_bill/sections/qr_code.rb +102 -0
- data/lib/prawn/swiss_qr_bill/sections/receipt_acceptance.rb +18 -0
- data/lib/prawn/swiss_qr_bill/sections/receipt_amount.rb +86 -0
- data/lib/prawn/swiss_qr_bill/sections/receipt_information.rb +51 -0
- data/lib/prawn/swiss_qr_bill/sections/receipt_title.rb +18 -0
- data/lib/prawn/swiss_qr_bill/sections/section.rb +88 -0
- data/lib/prawn/swiss_qr_bill/sections.rb +29 -0
- data/lib/prawn/swiss_qr_bill/specifications.rb +78 -0
- data/lib/prawn/swiss_qr_bill/specs.yml +106 -0
- data/lib/prawn/swiss_qr_bill/version.rb +7 -0
- data/lib/prawn/swiss_qr_bill.rb +43 -0
- data/prawn-swiss_qr_bill.gemspec +40 -0
- metadata +218 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
# Attach padded_box to Prawn
|
5
|
+
#
|
6
|
+
# padded_box(point, options = {}, &block)
|
7
|
+
#
|
8
|
+
# NOTE: not in use, not working properly. it is not easy to stroke the outer box.
|
9
|
+
# was an idea for DebugSection, maybe deprecate/remove or make it work later.
|
10
|
+
#
|
11
|
+
# :nocov:
|
12
|
+
class Document
|
13
|
+
def padded_box(point, *args, &block)
|
14
|
+
init_padded_box(block) do |parent_box|
|
15
|
+
point = map_to_absolute(point)
|
16
|
+
@bounding_box = PaddedBox.new(self, parent_box, point, *args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def init_padded_box(user_block)
|
23
|
+
# binding.pry
|
24
|
+
parent_box = @bounding_box
|
25
|
+
|
26
|
+
yield(parent_box)
|
27
|
+
|
28
|
+
self.y = @bounding_box.absolute_top
|
29
|
+
|
30
|
+
pad = @bounding_box.padding
|
31
|
+
bounding_box([@bounding_box.left + pad, @bounding_box.top - pad],
|
32
|
+
width: @bounding_box.width - (2 * pad),
|
33
|
+
height: @bounding_box.height - (2 * pad)) do
|
34
|
+
user_block.call
|
35
|
+
end
|
36
|
+
|
37
|
+
@bounding_box = parent_box
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Bounding box with padding
|
42
|
+
class PaddedBox < Prawn::Document::BoundingBox
|
43
|
+
attr_reader :padding
|
44
|
+
|
45
|
+
def initialize(document, parent, point, options = {})
|
46
|
+
super
|
47
|
+
|
48
|
+
@padding = options[:padding] || 0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# :nocov:
|
52
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module QR
|
6
|
+
# The data of the Swiss QR-bill
|
7
|
+
#
|
8
|
+
# References:
|
9
|
+
# https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf
|
10
|
+
# * Chapter 4, Page 25
|
11
|
+
#
|
12
|
+
# TODO: implement skippable (alt schemas, bill info. fields after trailer)
|
13
|
+
# TODO: check if addr-type has to be mentioned if not given?
|
14
|
+
# OPTIMIZE: implement non-changable?
|
15
|
+
class Data
|
16
|
+
# Simple field structure:
|
17
|
+
# * :default => default value to be set, if key is not given
|
18
|
+
# * :format => Proc to call when generating output
|
19
|
+
# * :skippable => Do not output in QR data if not given
|
20
|
+
Field = Struct.new(:default, :format, :skippable)
|
21
|
+
|
22
|
+
# Available fields of the QR code data, ordered.
|
23
|
+
FIELDS = {
|
24
|
+
# fixed: SPC
|
25
|
+
qr_type: Field.new('SPC'),
|
26
|
+
version: Field.new('0200'),
|
27
|
+
# fixed: 1
|
28
|
+
coding: Field.new('1'),
|
29
|
+
# TODO: check for valid iban
|
30
|
+
# iban: Field.new(nil, ->(value) { value.delete(' ') }),
|
31
|
+
iban: Field.new,
|
32
|
+
# enum: S, K
|
33
|
+
creditor_address_type: Field.new('K'),
|
34
|
+
creditor_address_name: Field.new,
|
35
|
+
creditor_address_line1: Field.new,
|
36
|
+
creditor_address_line2: Field.new,
|
37
|
+
creditor_address_postal_code: Field.new,
|
38
|
+
creditor_address_city: Field.new,
|
39
|
+
creditor_address_country: Field.new,
|
40
|
+
# omit ultimate_creditor_* => for future use
|
41
|
+
# enum: S, K
|
42
|
+
ultimate_creditor_address_type: Field.new,
|
43
|
+
ultimate_creditor_address_name: Field.new,
|
44
|
+
ultimate_creditor_address_line1: Field.new,
|
45
|
+
ultimate_creditor_address_line2: Field.new,
|
46
|
+
ultimate_creditor_address_postal_code: Field.new,
|
47
|
+
ultimate_creditor_address_city: Field.new,
|
48
|
+
ultimate_creditor_address_country: Field.new,
|
49
|
+
amount: Field.new(nil, ->(value) { value && format('%.2f', value) }),
|
50
|
+
# enum: CHF, EUR
|
51
|
+
currency: Field.new('CHF'),
|
52
|
+
# enum: S, K
|
53
|
+
debtor_address_type: Field.new,
|
54
|
+
debtor_address_name: Field.new,
|
55
|
+
debtor_address_line1: Field.new,
|
56
|
+
debtor_address_line2: Field.new,
|
57
|
+
debtor_address_postal_code: Field.new,
|
58
|
+
debtor_address_city: Field.new,
|
59
|
+
debtor_address_country: Field.new,
|
60
|
+
# enum: QRR, SCOR, NON
|
61
|
+
reference_type: Field.new('NON'),
|
62
|
+
reference: Field.new(nil, ->(value) { value && value.delete(' ') }),
|
63
|
+
unstructured_message: Field.new,
|
64
|
+
# fixed: EPD
|
65
|
+
trailer: Field.new('EPD'),
|
66
|
+
|
67
|
+
# additional:
|
68
|
+
|
69
|
+
bill_information: Field.new(nil, nil, true),
|
70
|
+
# key-value pairs:
|
71
|
+
alternative_parameters: Field.new(nil, nil, true)
|
72
|
+
}.freeze
|
73
|
+
|
74
|
+
# TODO: check if all fields can be changed by user?
|
75
|
+
attr_accessor(*FIELDS.keys)
|
76
|
+
|
77
|
+
def initialize(fields = {})
|
78
|
+
# set defaults
|
79
|
+
FIELDS.each_key do |field|
|
80
|
+
instance_variable_set("@#{field}", FIELDS[field].default)
|
81
|
+
end
|
82
|
+
|
83
|
+
# set given
|
84
|
+
fields.each_key do |field|
|
85
|
+
instance_variable_set("@#{field}", fields[field])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def generate
|
90
|
+
stack = []
|
91
|
+
FIELDS.keys.map do |k|
|
92
|
+
var = instance_variable_get("@#{k}")
|
93
|
+
|
94
|
+
next if FIELDS[k][:skippable] && var.nil?
|
95
|
+
|
96
|
+
var = FIELDS[k][:format].call(var) if FIELDS[k][:format].is_a?(Proc)
|
97
|
+
|
98
|
+
stack << var
|
99
|
+
end
|
100
|
+
stack.join("\r\n")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# Amount section at payment
|
7
|
+
#
|
8
|
+
# TODO: allow EUR payment
|
9
|
+
# TODO: find solution to put measurements to specs.yml
|
10
|
+
# OPTIMIZE: refactor with receipt_amount
|
11
|
+
class PaymentAmount < Section
|
12
|
+
KEY = 'payment.amount'
|
13
|
+
|
14
|
+
# width of the currency field with enough space for both variants
|
15
|
+
CURRENCY_WIDTH = 14.mm
|
16
|
+
|
17
|
+
# width of amount when amount has to be displayed
|
18
|
+
AMOUNT_WIDTH = 29.mm
|
19
|
+
|
20
|
+
# height of the amount label when corner-box has to be displayed
|
21
|
+
AMOUNT_LABEL_HEIGHT = 3.5.mm
|
22
|
+
|
23
|
+
# measurement of amount corner-box
|
24
|
+
AMOUNT_BOX_WIDTH = 40.mm
|
25
|
+
AMOUNT_BOX_HEIGHT = 15.mm
|
26
|
+
|
27
|
+
include Helpers::NumberHelper
|
28
|
+
include Helpers::BoxHelper
|
29
|
+
|
30
|
+
def draw
|
31
|
+
box do
|
32
|
+
draw_currency
|
33
|
+
draw_amount
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def draw_currency
|
40
|
+
doc.bounding_box([doc.bounds.left, doc.bounds.top],
|
41
|
+
width: CURRENCY_WIDTH, height: specs.height) do
|
42
|
+
label I18n.t('currency', scope: i18n_scope)
|
43
|
+
content 'CHF'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def draw_amount
|
48
|
+
if @data.key?(:amount)
|
49
|
+
draw_amount_label_and_value
|
50
|
+
else
|
51
|
+
draw_amount_label
|
52
|
+
draw_amount_corner_box
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def draw_amount_label_and_value
|
57
|
+
left = specs.width - AMOUNT_WIDTH
|
58
|
+
|
59
|
+
doc.bounding_box([left, doc.bounds.top],
|
60
|
+
width: AMOUNT_WIDTH, height: specs.height) do
|
61
|
+
label I18n.t('amount', scope: i18n_scope)
|
62
|
+
# format_with_delimiter => NumberHelper
|
63
|
+
content format_with_delimiter(@data[:amount])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def draw_amount_label
|
68
|
+
# amount label is not left aligned with the corner-box, subtract
|
69
|
+
# the currency width for alignment
|
70
|
+
left = CURRENCY_WIDTH
|
71
|
+
|
72
|
+
# width is calculated by the leftover space
|
73
|
+
width = specs.width - CURRENCY_WIDTH
|
74
|
+
doc.bounding_box([left, doc.bounds.top],
|
75
|
+
width: width, height: AMOUNT_LABEL_HEIGHT) do
|
76
|
+
label I18n.t('amount', scope: i18n_scope)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def draw_amount_corner_box
|
81
|
+
corner_box(doc, [specs.width - AMOUNT_BOX_WIDTH, doc.bounds.top - AMOUNT_LABEL_HEIGHT],
|
82
|
+
width: AMOUNT_BOX_WIDTH,
|
83
|
+
height: AMOUNT_BOX_HEIGHT)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# Further information section at payment
|
7
|
+
class PaymentFurtherInformation < Section
|
8
|
+
KEY = 'payment.further_information'
|
9
|
+
|
10
|
+
def draw; end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# Information section at payment
|
7
|
+
class PaymentInformation < Section
|
8
|
+
KEY = 'payment.information'
|
9
|
+
|
10
|
+
include Helpers::BoxHelper
|
11
|
+
|
12
|
+
def draw
|
13
|
+
box do
|
14
|
+
draw_payable_to
|
15
|
+
draw_reference if @data.key?(:reference)
|
16
|
+
draw_additional_information if @data.key?(:additional_information)
|
17
|
+
draw_payable_by
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def draw_payable_to
|
24
|
+
doc.pad_top(2.1) { label I18n.t('creditor', scope: i18n_scope) }
|
25
|
+
content [@data[:creditor][:iban],
|
26
|
+
build_address(@data[:creditor][:address])].join("\n")
|
27
|
+
|
28
|
+
line_spacing
|
29
|
+
end
|
30
|
+
|
31
|
+
def draw_reference
|
32
|
+
label I18n.t('reference', scope: i18n_scope)
|
33
|
+
content @data[:reference]
|
34
|
+
|
35
|
+
line_spacing
|
36
|
+
end
|
37
|
+
|
38
|
+
def draw_additional_information
|
39
|
+
label I18n.t('additional_info', scope: i18n_scope)
|
40
|
+
content @data[:additional_information]
|
41
|
+
|
42
|
+
line_spacing
|
43
|
+
end
|
44
|
+
|
45
|
+
def draw_payable_by
|
46
|
+
if @data.key?(:debtor)
|
47
|
+
label I18n.t('debtor', scope: i18n_scope)
|
48
|
+
content build_address(@data[:debtor][:address])
|
49
|
+
else
|
50
|
+
label I18n.t('debtor_blank', scope: i18n_scope)
|
51
|
+
|
52
|
+
doc.move_down 2
|
53
|
+
corner_box(doc, [0, doc.cursor], width: 65.mm, height: 25.mm)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# Title section at payment
|
7
|
+
class PaymentTitle < Section
|
8
|
+
KEY = 'payment.title'
|
9
|
+
|
10
|
+
def draw
|
11
|
+
box do
|
12
|
+
content I18n.t('payment', scope: i18n_scope)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# QR code section
|
7
|
+
#
|
8
|
+
# The swiss cross is applied directly on the QR PNG. The PNG needs to be
|
9
|
+
# resized down, that the swiss cross overlaps exactly 9x9 modules for a
|
10
|
+
# QR-code of compression level :m.
|
11
|
+
#
|
12
|
+
# swiss_cross.png => 166px ~> 7.mm
|
13
|
+
# QR-code: 1090px <~ 46.mm
|
14
|
+
#
|
15
|
+
# TODO: iban check -> raise exception if configured
|
16
|
+
class QRCode < Section
|
17
|
+
KEY = 'payment.qr_code'
|
18
|
+
|
19
|
+
QR_PX_SIZE = 1160
|
20
|
+
|
21
|
+
SWISS_CROSS_FILE = File.expand_path("#{__dir__}/../images/swiss_cross.png")
|
22
|
+
|
23
|
+
MAPPING = {
|
24
|
+
creditor_address_type: %i[creditor address type],
|
25
|
+
creditor_address_name: %i[creditor address name],
|
26
|
+
creditor_address_line1: %i[creditor address line1],
|
27
|
+
creditor_address_line2: %i[creditor address line2],
|
28
|
+
creditor_address_postal_code: %i[creditor address postal_code],
|
29
|
+
creditor_address_country: %i[creditor address country],
|
30
|
+
debtor_address_type: %i[debtor address type],
|
31
|
+
debtor_address_name: %i[debtor address name],
|
32
|
+
debtor_address_line1: %i[debtor address line1],
|
33
|
+
debtor_address_line2: %i[debtor address line2],
|
34
|
+
debtor_address_postal_code: %i[debtor address postal_code],
|
35
|
+
debtor_address_city: %i[debtor address city],
|
36
|
+
debtor_address_country: %i[debtor address country],
|
37
|
+
amount: [:amount],
|
38
|
+
currency: [:currency],
|
39
|
+
reference: [:reference],
|
40
|
+
reference_type: [:reference_type]
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
def draw
|
44
|
+
png = qr_as_png(@data)
|
45
|
+
add_swiss_cross!(png)
|
46
|
+
|
47
|
+
io = StringIO.new(png.to_blob)
|
48
|
+
|
49
|
+
box do
|
50
|
+
doc.image io, width: specs.width
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def qr_as_png(data)
|
57
|
+
qr_data = generate_qr_data(data)
|
58
|
+
qr = RQRCode::QRCode.new(qr_data, level: :m)
|
59
|
+
png = qr.as_png(module_px_size: 20, border_modules: 0)
|
60
|
+
png.resize(QR_PX_SIZE, QR_PX_SIZE)
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_swiss_cross!(png)
|
64
|
+
swiss_cross = ChunkyPNG::Image.from_file(SWISS_CROSS_FILE)
|
65
|
+
|
66
|
+
png.compose!(
|
67
|
+
swiss_cross,
|
68
|
+
(png.width - swiss_cross.width) / 2,
|
69
|
+
(png.height - swiss_cross.height) / 2
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def generate_qr_data(data)
|
74
|
+
options = {}
|
75
|
+
MAPPING.each_key do |key|
|
76
|
+
# check if the exists
|
77
|
+
next unless deep_key?(data, MAPPING[key])
|
78
|
+
|
79
|
+
options[key] = data.dig(*MAPPING[key])
|
80
|
+
end
|
81
|
+
|
82
|
+
iban = IBAN.new(data[:creditor][:iban])
|
83
|
+
qr_data = QR::Data.new(options.merge(iban: iban.code))
|
84
|
+
qr_data.generate
|
85
|
+
end
|
86
|
+
|
87
|
+
# hash: hash to test key path for
|
88
|
+
# key_path: array of symbols to search the key
|
89
|
+
def deep_key?(hash, key_path)
|
90
|
+
path = key_path.dup
|
91
|
+
|
92
|
+
return false if path.empty?
|
93
|
+
return hash.key?(path[0]) if path.length == 1
|
94
|
+
|
95
|
+
last_key = path.pop
|
96
|
+
|
97
|
+
!!hash.dig(*path)&.key?(last_key)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# Acceptance point section at receipt
|
7
|
+
class ReceiptAcceptance < Section
|
8
|
+
KEY = 'receipt.acceptance'
|
9
|
+
|
10
|
+
def draw
|
11
|
+
box do
|
12
|
+
label I18n.t('acceptance', scope: i18n_scope), align: :right
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# Amount section at receipt
|
7
|
+
#
|
8
|
+
# TODO: allow EUR payment
|
9
|
+
# TODO: find solution to put measurements to specs.yml
|
10
|
+
# OPTIMIZE: refactor with payment_amount
|
11
|
+
class ReceiptAmount < Section
|
12
|
+
KEY = 'receipt.amount'
|
13
|
+
|
14
|
+
# width of the currency field with enough space for both variants
|
15
|
+
CURRENCY_WIDTH = 11.mm
|
16
|
+
|
17
|
+
# measurement of the box for the amount as value and as corner-box
|
18
|
+
AMOUNT_BOX_WIDTH = 30.mm
|
19
|
+
AMOUNT_BOX_HEIGHT = 10.mm
|
20
|
+
|
21
|
+
# padding of the amount label to corner-box
|
22
|
+
AMOUNT_LABEL_PAD = 1.mm
|
23
|
+
|
24
|
+
include Helpers::NumberHelper
|
25
|
+
include Helpers::BoxHelper
|
26
|
+
|
27
|
+
def draw
|
28
|
+
box do
|
29
|
+
draw_currency
|
30
|
+
draw_amount
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def draw_currency
|
37
|
+
# use the half width of the available size: space for amount-box
|
38
|
+
doc.bounding_box([doc.bounds.left, doc.bounds.top],
|
39
|
+
width: CURRENCY_WIDTH, height: specs.height) do
|
40
|
+
doc.pad_top(1.4) { label I18n.t('currency', scope: i18n_scope) }
|
41
|
+
doc.pad_top(2.5) { content 'CHF' }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def draw_amount
|
46
|
+
if @data.key?(:amount)
|
47
|
+
draw_amount_label_and_value
|
48
|
+
else
|
49
|
+
draw_amount_label
|
50
|
+
draw_amount_corner_box
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def draw_amount_label_and_value
|
55
|
+
# amount value is placed where the box would be, same width/height
|
56
|
+
left = specs.width - AMOUNT_BOX_WIDTH
|
57
|
+
doc.bounding_box([left, doc.bounds.top],
|
58
|
+
width: AMOUNT_BOX_WIDTH, height: specs.height) do
|
59
|
+
doc.pad_top(1.4) { label I18n.t('amount', scope: i18n_scope) }
|
60
|
+
# format_with_delimiter => NumberHelper
|
61
|
+
doc.pad_top(2.5) { content format_with_delimiter(@data[:amount]) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def draw_amount_label
|
66
|
+
# amount label is pushed to left, right after the currency box,
|
67
|
+
# which is the currency width
|
68
|
+
left = CURRENCY_WIDTH
|
69
|
+
|
70
|
+
# width is calculated by the left over space - 1.mm padding
|
71
|
+
width = specs.width - CURRENCY_WIDTH - AMOUNT_BOX_WIDTH - AMOUNT_LABEL_PAD
|
72
|
+
doc.bounding_box([left, doc.bounds.top],
|
73
|
+
width: width, height: specs.height) do
|
74
|
+
doc.pad_top(1.4) { label I18n.t('amount', scope: i18n_scope), align: :right }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def draw_amount_corner_box
|
79
|
+
corner_box(doc, [specs.width - AMOUNT_BOX_WIDTH, doc.bounds.top - 1.4],
|
80
|
+
width: AMOUNT_BOX_WIDTH,
|
81
|
+
height: AMOUNT_BOX_HEIGHT)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# Information section at receipt
|
7
|
+
class ReceiptInformation < Section
|
8
|
+
KEY = 'receipt.information'
|
9
|
+
|
10
|
+
include Helpers::BoxHelper
|
11
|
+
|
12
|
+
def draw
|
13
|
+
box do
|
14
|
+
draw_payable_to
|
15
|
+
draw_reference if @data.key?(:reference)
|
16
|
+
draw_payable_by
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def draw_payable_to
|
23
|
+
label I18n.t('creditor', scope: i18n_scope)
|
24
|
+
content [@data[:creditor][:iban],
|
25
|
+
build_address(@data[:creditor][:address])].join("\n")
|
26
|
+
|
27
|
+
line_spacing
|
28
|
+
end
|
29
|
+
|
30
|
+
def draw_reference
|
31
|
+
label I18n.t('reference', scope: i18n_scope)
|
32
|
+
content @data[:reference]
|
33
|
+
|
34
|
+
line_spacing
|
35
|
+
end
|
36
|
+
|
37
|
+
def draw_payable_by
|
38
|
+
if @data.key?(:debtor)
|
39
|
+
label I18n.t('debtor', scope: i18n_scope)
|
40
|
+
content build_address(@data[:debtor][:address])
|
41
|
+
else
|
42
|
+
label I18n.t('debtor_blank', scope: i18n_scope)
|
43
|
+
|
44
|
+
doc.move_down 2
|
45
|
+
corner_box(doc, [0, doc.cursor], width: 52.mm, height: 20.mm)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
module Sections
|
6
|
+
# Title section at receipt
|
7
|
+
class ReceiptTitle < Section
|
8
|
+
KEY = 'receipt.title'
|
9
|
+
|
10
|
+
def draw
|
11
|
+
box do
|
12
|
+
content I18n.t('receipt', scope: i18n_scope)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|