prawn-swiss_qr_bill 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|