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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +5 -0
  3. data/README.md +124 -0
  4. data/config/locales/de.yml +15 -0
  5. data/config/locales/en.yml +15 -0
  6. data/config/locales/fr.yml +15 -0
  7. data/config/locales/it.yml +15 -0
  8. data/lib/prawn/swiss_qr_bill/bill.rb +20 -0
  9. data/lib/prawn/swiss_qr_bill/corner_box.rb +79 -0
  10. data/lib/prawn/swiss_qr_bill/cutting_lines.rb +74 -0
  11. data/lib/prawn/swiss_qr_bill/debug_section.rb +140 -0
  12. data/lib/prawn/swiss_qr_bill/extension.rb +18 -0
  13. data/lib/prawn/swiss_qr_bill/helpers/box_helper.rb +14 -0
  14. data/lib/prawn/swiss_qr_bill/helpers/number_helper.rb +16 -0
  15. data/lib/prawn/swiss_qr_bill/iban.rb +84 -0
  16. data/lib/prawn/swiss_qr_bill/images/scissor.png +0 -0
  17. data/lib/prawn/swiss_qr_bill/images/swiss_cross.png +0 -0
  18. data/lib/prawn/swiss_qr_bill/padded_box.rb +52 -0
  19. data/lib/prawn/swiss_qr_bill/qr/data.rb +105 -0
  20. data/lib/prawn/swiss_qr_bill/sections/payment_amount.rb +88 -0
  21. data/lib/prawn/swiss_qr_bill/sections/payment_further_information.rb +14 -0
  22. data/lib/prawn/swiss_qr_bill/sections/payment_information.rb +59 -0
  23. data/lib/prawn/swiss_qr_bill/sections/payment_title.rb +18 -0
  24. data/lib/prawn/swiss_qr_bill/sections/qr_code.rb +102 -0
  25. data/lib/prawn/swiss_qr_bill/sections/receipt_acceptance.rb +18 -0
  26. data/lib/prawn/swiss_qr_bill/sections/receipt_amount.rb +86 -0
  27. data/lib/prawn/swiss_qr_bill/sections/receipt_information.rb +51 -0
  28. data/lib/prawn/swiss_qr_bill/sections/receipt_title.rb +18 -0
  29. data/lib/prawn/swiss_qr_bill/sections/section.rb +88 -0
  30. data/lib/prawn/swiss_qr_bill/sections.rb +29 -0
  31. data/lib/prawn/swiss_qr_bill/specifications.rb +78 -0
  32. data/lib/prawn/swiss_qr_bill/specs.yml +106 -0
  33. data/lib/prawn/swiss_qr_bill/version.rb +7 -0
  34. data/lib/prawn/swiss_qr_bill.rb +43 -0
  35. data/prawn-swiss_qr_bill.gemspec +40 -0
  36. metadata +218 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: faa2f8fb8c38ea2681890f7573bb42ab424cadb413d57699e0e64145088c8911
4
+ data.tar.gz: 2fa0db096bc0d9fb4060858dd0fe88ba187c20a54ae36babcb66848c6a102a33
5
+ SHA512:
6
+ metadata.gz: e1d30550f53e1ed3dc74a0256d826a25fa2290f06a9b4e282e8529dabeaf334907b87613e6babd52667ee3cf248098464dd9381743e4883014a7a85c8783c97d
7
+ data.tar.gz: 536068e41ad5d54e70dcebe9deeeb19a1984eb52b4bee5e020e5644bbdcf4e7c66453b449ae3e811b523f37dc2b3fc1a2dcaacd51c072562a1628b0b3ef509ea
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'http://rubygems.org'
4
+
5
+ gemspec
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # Prawn::SwissQRBill
2
+
3
+ A Ruby library for creating Swiss QR-bill payment slips inside a PDF. It is
4
+ built as a [Prawn](https://github.com/prawnpdf/prawn) extension.
5
+
6
+ ## Installation
7
+
8
+ Add the following line to your `Gemfile`:
9
+
10
+ ```ruby
11
+ gem 'prawn-swiss_qr_bill'
12
+ ```
13
+
14
+ or install it manually:
15
+
16
+ ```bash
17
+ gem install prawn-swiss_qr_bill
18
+ ```
19
+
20
+ ## Basic Usage
21
+
22
+ Define the relevant information for the Swiss QR-bill and render it inside the Prawn block:
23
+
24
+ ```ruby
25
+ require 'prawn'
26
+ require 'prawn/swiss_qr_bill'
27
+
28
+ @qr_data = {
29
+ creditor: {
30
+ iban: 'CH08 3080 8004 1110 4136 9',
31
+ address: {
32
+ name: 'Mischa Schindowski',
33
+ line1: 'Schybenächerweg 553',
34
+ line2: '5324 Full-Reuenthal',
35
+ country: 'CH'
36
+ }
37
+ },
38
+ amount: 9.90,
39
+ currency: 'CHF',
40
+ reference: '00 00000 00000 02202 20202 99991',
41
+ reference_type: 'QRR'
42
+ }
43
+
44
+ Prawn::Document.generate('output.pdf', page_size: 'A4') do
45
+ text 'A Swiss QR bill'
46
+
47
+ swiss_qr_bill(@qr_data)
48
+ end
49
+ ```
50
+
51
+ This will render the Swiss QR-bill at the bottom of the page:
52
+
53
+ ![Swiss QR-bill Example, PDF](./images/sqb_example_01.png)
54
+
55
+ ### Options
56
+
57
+ The following options are available:
58
+
59
+ ```ruby
60
+ # *: mandatory
61
+ @qr_data = {
62
+ creditor: {
63
+ iban: '<iban>', # *
64
+ address: { # *
65
+ type: '<K|S>', # default: K
66
+ name: '<name>', # *
67
+ line1: '<street> <nr>', # *
68
+ line2: '<line 2>',
69
+ postal_code: '<postal code>',
70
+ city: '<city>',
71
+ country: '<country>' # *
72
+ }
73
+ },
74
+ debtor: {
75
+ address: {
76
+ type: '<K|S>', # default: nil
77
+ name: '<name>',
78
+ line1: '<street> <nr>',
79
+ line2: '<line 2>',
80
+ postal_code: '<postal_code>',
81
+ city: '<city>',
82
+ country: '<country>'
83
+ }
84
+ },
85
+ amount: 9.90,
86
+ currency: '<CHF|EUR>', # *
87
+ reference: '<ref nr>',
88
+ reference_type: '<QRR|SCOR|NON>' # default: NON
89
+ }
90
+ ```
91
+
92
+ If `debtor` or `amount` amount is not given, a box will be printed.
93
+
94
+ ## Important
95
+
96
+ This library does not validate (yet) IBAN, reference or the given QR data.
97
+ Please refer to the implementation guidelines and the Swiss QR-bill validaton
98
+ portal by SIX below.
99
+
100
+ ## Contributing
101
+
102
+ If you miss a feature or you've found a bug, please [open a GitHub issue](https://github.com/mitosch/prawn-swiss_qr_bill/issues).
103
+
104
+ Pull requests are highly welcome:
105
+
106
+ * Fork the project
107
+ * Make your changes
108
+ * Send the pull request
109
+
110
+ ## Authors
111
+
112
+ Original author: Mischa Schindowski
113
+
114
+ ## Resources
115
+
116
+ * [Prawn](https://github.com/prawnpdf/prawn): Fast, Nimble PDF Generation For Ruby
117
+ * [Swiss QR-bill Validation Portal](https://validation.iso-payments.ch/qrrechnung)
118
+ * [Swiss Payment Standards](https://www.paymentstandards.ch):
119
+ * [Style Guide QR-bill](https://www.paymentstandards.ch/dam/downloads/style-guide-en.pdf)
120
+ * [Implementation Guidelines QR-bill](https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf)
121
+
122
+ ## Copyright
123
+
124
+ MIT License (http://www.opensource.org/licenses/mit-license.html)
@@ -0,0 +1,15 @@
1
+ de:
2
+ swiss_qr_bill:
3
+ payment: Zahlteil
4
+ creditor: Konto / Zahlbar an
5
+ reference: Referenz
6
+ additional_info: Zusätzliche Informationen
7
+ further_info: Weitere Informationen
8
+ currency: Währung
9
+ amount: Betrag
10
+ receipt: Empfangsschein
11
+ acceptance: Annahmestelle
12
+ separate: Vor der Einzahlung abzutrennen
13
+ debtor: Zahlbar durch
14
+ debtor_blank: Zahlbar durch (Name/Adresse)
15
+ debtor_favour: Zugunsten
@@ -0,0 +1,15 @@
1
+ en:
2
+ swiss_qr_bill:
3
+ payment: Payment part
4
+ creditor: Account / Payable to
5
+ reference: Reference
6
+ additional_info: Additional information
7
+ further_info: Further information
8
+ currency: Currency
9
+ amount: Amount
10
+ receipt: Receipt
11
+ acceptance: Acceptance point
12
+ separate: Separate before paying in
13
+ debtor: Payable by
14
+ debtor_blank: Payable by (name/address)
15
+ debtor_favour: In favour of
@@ -0,0 +1,15 @@
1
+ fr:
2
+ swiss_qr_bill:
3
+ payment: Section paiement
4
+ creditor: Compte / Payable à
5
+ reference: Référence
6
+ additional_info: Informations supplémentaires
7
+ further_info: Informations additionnelles
8
+ currency: Monnaie
9
+ amount: Montant
10
+ receipt: Récépissé
11
+ acceptance: Point de dépôt
12
+ separate: A détacher avant le versement
13
+ debtor: Payable par
14
+ debtor_blank: Payable par (nom/adresse)
15
+ debtor_favour: En faveur de
@@ -0,0 +1,15 @@
1
+ it:
2
+ swiss_qr_bill:
3
+ payment: Sezione pagamento
4
+ creditor: Conto / Pagabile a
5
+ reference: Riferimento
6
+ additional_info: Informazioni supplementari
7
+ further_info: Informazioni aggiuntive
8
+ currency: Valuta
9
+ amount: Importo
10
+ receipt: Ricevuta
11
+ acceptance: Punto di accettazione
12
+ separate: Da staccare prima del versamento
13
+ debtor: Pagabile da
14
+ debtor_blank: Pagabile da (nome/indirizzo)
15
+ debtor_favour: A favore di
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module SwissQRBill
5
+ # Bill renders the Swiss QR-bill at the bottom of a page
6
+ class Bill
7
+ def initialize(document, data)
8
+ @doc = document
9
+ @data = data
10
+ end
11
+
12
+ def draw
13
+ @doc.canvas do
14
+ Sections.draw_all(@doc, @data)
15
+ CuttingLines.new(@doc).draw
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module SwissQRBill
5
+ # Draws a box with corner ticks
6
+ class CornerBox
7
+ TICK_SIZE = 3.mm
8
+
9
+ LINE_WIDTH = 0.75
10
+
11
+ def initialize(document, point, options)
12
+ unless options.key?(:width) && options.key?(:height)
13
+ raise ArgumentError,
14
+ 'corner_box needs the :width and :height option to be set'
15
+ end
16
+
17
+ @document = document
18
+ @point = point
19
+ @width = options[:width]
20
+ @height = options[:height]
21
+
22
+ @brain = {}
23
+ end
24
+
25
+ def draw
26
+ set_styles
27
+ draw_lines
28
+ reset_styles
29
+ end
30
+
31
+ private
32
+
33
+ def set_styles
34
+ @brain[:line_width] = @document.line_width
35
+ @document.line_width = LINE_WIDTH
36
+ end
37
+
38
+ def reset_styles
39
+ @document.line_width = @brain[:line_width]
40
+ end
41
+
42
+ def draw_lines
43
+ point_x, point_y = @point
44
+
45
+ @document.stroke do
46
+ draw_horizontal_lines(point_x, point_y)
47
+ draw_vertical_lines(point_x, point_y)
48
+ end
49
+ end
50
+
51
+ def draw_horizontal_lines(point_x, point_y)
52
+ # upper lines
53
+ @document.horizontal_line point_x, point_x + TICK_SIZE,
54
+ at: point_y
55
+ @document.horizontal_line point_x + @width, point_x + @width - TICK_SIZE,
56
+ at: point_y
57
+ # lower lines
58
+ @document.horizontal_line point_x, point_x + TICK_SIZE,
59
+ at: point_y - @height
60
+ @document.horizontal_line point_x + @width, point_x + @width - TICK_SIZE,
61
+ at: point_y - @height
62
+ end
63
+
64
+ def draw_vertical_lines(point_x, point_y)
65
+ # upper lines
66
+ @document.vertical_line point_y, point_y - TICK_SIZE,
67
+ at: point_x
68
+ @document.vertical_line point_y, point_y - TICK_SIZE,
69
+ at: point_x + @width
70
+
71
+ # lower lines
72
+ @document.vertical_line point_y - @height, point_y - @height + TICK_SIZE,
73
+ at: point_x
74
+ @document.vertical_line point_y - @height, point_y - @height + TICK_SIZE,
75
+ at: point_x + @width
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module SwissQRBill
5
+ # Horizontal and vertical cutting lines with a scissor symbol
6
+ class CuttingLines
7
+ SCISSOR_FILE = File.expand_path("#{__dir__}/images/scissor.png")
8
+
9
+ SCISSOR_WIDTH = 5.mm
10
+ SCISSOR_HEIGHT = 3.mm
11
+
12
+ PAD_LEFT = 5.mm
13
+ PAD_TOP = 1.mm
14
+
15
+ attr_reader :doc, :receipt_width, :receipt_height
16
+
17
+ def initialize(document)
18
+ @doc = document
19
+
20
+ @brain = {}
21
+
22
+ load_specs
23
+ end
24
+
25
+ def draw
26
+ set_styles
27
+
28
+ draw_strokes
29
+ draw_scissors
30
+
31
+ reset_styles
32
+ end
33
+
34
+ private
35
+
36
+ def load_specs
37
+ specs = Specifications.new
38
+ @receipt_height = specs.get('receipt.height').mm
39
+ @receipt_width = specs.get('receipt.width').mm
40
+ end
41
+
42
+ def set_styles
43
+ @brain[:line_width] = doc.line_width
44
+
45
+ doc.line_width 0.5
46
+ doc.dash 2, space: 2
47
+ end
48
+
49
+ def draw_strokes
50
+ doc.stroke { doc.line [0, receipt_height], [doc.bounds.right, receipt_height] }
51
+ doc.stroke { doc.line [receipt_width, receipt_height], [receipt_width, doc.bounds.bottom] }
52
+ end
53
+
54
+ def draw_scissors
55
+ doc.bounding_box([doc.bounds.left + PAD_LEFT, receipt_height + (SCISSOR_HEIGHT / 2)],
56
+ width: SCISSOR_WIDTH, height: SCISSOR_HEIGHT) do
57
+ doc.image SCISSOR_FILE, width: SCISSOR_WIDTH
58
+ end
59
+
60
+ doc.bounding_box([receipt_width - (SCISSOR_HEIGHT / 2), receipt_height - PAD_TOP],
61
+ width: SCISSOR_WIDTH, height: SCISSOR_HEIGHT) do
62
+ doc.rotate(270, origin: [0, 0]) do
63
+ doc.image SCISSOR_FILE, width: SCISSOR_WIDTH
64
+ end
65
+ end
66
+ end
67
+
68
+ def reset_styles
69
+ doc.line_width = @brain[:line_width]
70
+ doc.undash
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module SwissQRBill
5
+ # Draws a blue debug section.
6
+ #
7
+ # Reference:
8
+ # https://www.paymentstandards.ch/dam/downloads/style-guide-en.pdf
9
+ #
10
+ # OPTIMIZE: move several general styling functions to helpers (bb-helper)
11
+ class DebugSection
12
+ SECTIONS = [
13
+ 'receipt',
14
+ 'receipt.title',
15
+ 'receipt.information',
16
+ 'receipt.amount',
17
+ 'receipt.acceptance',
18
+ 'payment',
19
+ 'payment.title',
20
+ 'payment.information',
21
+ 'payment.qr_code',
22
+ 'payment.qr_cross',
23
+ 'payment.amount',
24
+ 'payment.further_information'
25
+ ].freeze
26
+
27
+ attr_reader :doc, :properties
28
+
29
+ def initialize(document)
30
+ @doc = document
31
+
32
+ @brain = { font: {}, border: { color: nil, width: nil } }
33
+
34
+ @spec = Prawn::SwissQRBill::Specifications.new
35
+ end
36
+
37
+ def draw
38
+ SECTIONS.each do |section|
39
+ name = @spec.get(section)['name']
40
+ specs = @spec.get_specs(section)
41
+ point = specs.point
42
+ options = { width: specs.width, height: specs.height }.merge(
43
+ text: name,
44
+ background: false, border: true, font_size: 10
45
+ )
46
+
47
+ draw_debug_box(point, options)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # Draws a box like in the style guide.
54
+ #
55
+ # NOTE: Background removed for simplicity, color was: D4EDFC
56
+ def draw_debug_box(*args)
57
+ position = args[0]
58
+ text = args[1].delete(:text)
59
+ border_style = { color: '71C4E9', width: 0.75 } if args[1][:border]
60
+ debug_box_opts = args[1].merge(padding: 1.mm,
61
+ border: border_style,
62
+ font: { size: args[1][:font_size] })
63
+
64
+ draw_padded_box(position, debug_box_opts) do
65
+ doc.text text, color: '71C4E9'
66
+ end
67
+ end
68
+
69
+ def draw_padded_box(*args, &block)
70
+ padding = args[1].delete(:padding) || 0
71
+
72
+ doc.bounding_box(*args) do
73
+ height = doc.bounds.height
74
+ width = doc.bounds.width
75
+
76
+ ensure_styles(args[1])
77
+
78
+ doc.bounding_box([padding, height - padding],
79
+ width: width - (2 * padding),
80
+ height: height - (2 * padding)) { yield block }
81
+ end
82
+
83
+ reset_styles
84
+ end
85
+
86
+ # ensures setting the styles by meaningful options:
87
+ #
88
+ # {
89
+ # border: { color: '123456', width: 2 },
90
+ # font: { size: 10 }
91
+ # }
92
+ def ensure_styles(opts)
93
+ stroke_style(opts[:border])
94
+ font_style(opts[:font])
95
+ end
96
+
97
+ def stroke_style(opts)
98
+ return unless opts
99
+
100
+ remember_style(:border)
101
+
102
+ doc.line_width opts[:width]
103
+ doc.stroke_color opts[:color]
104
+ doc.stroke_bounds
105
+ end
106
+
107
+ def font_style(opts)
108
+ return unless opts
109
+
110
+ remember_style(:font)
111
+ doc.font_size opts[:size]
112
+ end
113
+
114
+ def remember_style(style_type)
115
+ case style_type
116
+ when :font
117
+ @brain[:font][:size] = doc.font_size
118
+ when :border
119
+ @brain[:border][:color] = doc.line_width
120
+ @brain[:border][:width] = doc.stroke_color
121
+ end
122
+ end
123
+
124
+ def reset_styles
125
+ reset_style(:font)
126
+ reset_style(:border)
127
+ end
128
+
129
+ def reset_style(style_type)
130
+ case style_type
131
+ when :font
132
+ doc.font_size @brain[:font][:size]
133
+ when :border
134
+ doc.line_width @brain[:border][:color]
135
+ doc.stroke_color @brain[:border][:width]
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module SwissQRBill
5
+ # Extend prawn with *swiss_qr_bill* methods
6
+ module Extension
7
+ def swiss_qr_bill(data)
8
+ Prawn::SwissQRBill::Bill.new(self, data).draw
9
+ end
10
+
11
+ def swiss_qr_bill_sections
12
+ canvas do
13
+ Prawn::SwissQRBill::DebugSection.new(self).draw
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module SwissQRBill
5
+ module Helpers
6
+ # Helpers for drawing boxes
7
+ module BoxHelper
8
+ def corner_box(doc, point, options)
9
+ Prawn::SwissQRBill::CornerBox.new(doc, point, options).draw
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module SwissQRBill
5
+ module Helpers
6
+ # Helpers to format numbers
7
+ module NumberHelper
8
+ def format_with_delimiter(number)
9
+ left, right = format('%.2f', number).split('.')
10
+ left.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/) { |d| "#{d} " }
11
+ [left, right].compact.join('.')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module SwissQRBill
5
+ # Check validity of IBAN.
6
+ #
7
+ # NOTE: *For Switzerland only*
8
+ #
9
+ # Simple implementation according to
10
+ # https://en.wikipedia.org/wiki/International_Bank_Account_Number#Algorithms
11
+ #
12
+ # TODO: validate QR-iban
13
+ class IBAN
14
+ attr_reader :code
15
+
16
+ def initialize(code)
17
+ @code = standardize(code)
18
+ end
19
+
20
+ def country_code
21
+ @code[0..1]
22
+ end
23
+
24
+ def check_digits
25
+ @code[2..3]
26
+ end
27
+
28
+ def bban
29
+ @code[4..-1]
30
+ end
31
+
32
+ # Convert Alpha-Numeric IBAN to numeric values (incl. rearrangement)
33
+ #
34
+ # Reference:
35
+ # https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
36
+ def to_i
37
+ "#{bban}#{country_code}#{check_digits}".gsub(/[A-Z]/) { |c| c.ord - 55 }.to_i
38
+ end
39
+
40
+ def prettify
41
+ @code.gsub(/(.{4})/, '\1 ').strip
42
+ end
43
+
44
+ # valid IBAN:
45
+ # CH77 8080 8004 1110 4136 9
46
+ #
47
+ # valid QR-IBAN:
48
+ # CH08 3080 8004 1110 4136 9
49
+ def valid?
50
+ valid_check_digits? && valid_swiss_length? && valid_country?
51
+ end
52
+
53
+ def valid_check_digits?
54
+ to_i % 97 == 1
55
+ end
56
+
57
+ # NOTE: Only the length for Switzerland is implemented because it is Swiss QR-bill specific
58
+ def valid_length?
59
+ valid_swiss_length?
60
+ end
61
+
62
+ def valid_swiss_length?
63
+ @code.length == 21
64
+ end
65
+
66
+ def valid_country?
67
+ country_code == 'CH'
68
+ end
69
+
70
+ private
71
+
72
+ # Make the given string standard:
73
+ #
74
+ # CH21 2345 2123 5543 5554
75
+ # ch21 2345 2123 5543 5554
76
+ # " ch21 2345 2123 5543 5554 "
77
+ #
78
+ # => CH212345212355435554
79
+ def standardize(code)
80
+ code.to_s.strip.gsub(/\s+/, '').upcase
81
+ end
82
+ end
83
+ end
84
+ end