webtranslateit-payday 2.0.0 → 2.1.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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +29 -0
- data/lib/payday/appendix.rb +47 -0
- data/lib/payday/invoiceable.rb +6 -5
- data/lib/payday/pdf_renderer.rb +41 -8
- data/payday.gemspec +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4cb98acc78809a09e4945f8d8525aee1bbafd3ed17a45234be6fb0a8836f3135
|
|
4
|
+
data.tar.gz: b3379b263fb55f5d7fdb8ec48d66924e0f0b1110325b43e15bbd0a764fbc6154
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c0473911f660484293361d1fbfb7020efed4614c06dd89b000fc7995f90e52b995c763781f175609578fd42090923ed9969baab0d4bffb85140f887356964690
|
|
7
|
+
data.tar.gz: 0a401edbb42eb073bf35602caaf6c2b7b762450dff19a04f2a8ee4d3ae8aca0ecf2ceab4485300a0647a2005b22116edd2cd1eba0be60a07117f5d44e5637b39
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 2.1.0 (2026-08-27)
|
|
4
|
+
|
|
5
|
+
* Add `Payday::Appendix`, for compiling pages of your own onto the end of an invoice. `render_pdf`, `render_pdf_to_file`, `PdfRenderer.render` and `PdfRenderer.render_to_file` all take an `appendix:` keyword; every signature stays backward compatible and an invoice rendered without one is byte-for-byte what it was.
|
|
6
|
+
|
|
7
|
+
The appendix carries Typst `source`, plus `inputs` that reach it as sys_inputs JSON and `dependencies` it can resolve by name. The source is concatenated onto Payday's template rather than compiled separately, so the appended pages keep the invoice's page setup and the numbering footer counts them: a one-page invoice with an appendix numbers "1 / 2" and "2 / 2". Merging a second PDF in could do neither.
|
|
8
|
+
|
|
9
|
+
Data belongs in `inputs`, where Typst treats it as literal text. Interpolating invoice data into `source` would give it the run of the document, which is the same rule the invoice template already follows.
|
|
10
|
+
|
|
11
|
+
An appendix that reuses a name the invoice has taken — the `invoice` input, or the logo and QR code files — raises `ArgumentError` instead of replacing it.
|
|
12
|
+
|
|
3
13
|
## 2.0.0 (2026-08-26)
|
|
4
14
|
|
|
5
15
|
**Breaking:** Payday now renders with [Typst](https://typst.app) instead of Prawn. The `prawn`, `prawn-table` and `prawn-svg` dependencies are gone, replaced by the single Apache-2.0 licensed `typst` gem, which ships precompiled native builds for macOS and Linux on both x86_64 and arm64.
|
data/README.md
CHANGED
|
@@ -115,6 +115,35 @@ end
|
|
|
115
115
|
|
|
116
116
|
Be sure to restart your server after you edit the mime_types initializer. The updated setting won't take effect until you do.
|
|
117
117
|
|
|
118
|
+
Appending your own pages
|
|
119
|
+
===
|
|
120
|
+
Pass a `Payday::Appendix` to add pages of your own to the end of an invoice — a per-item breakdown, a delivery note, terms and conditions:
|
|
121
|
+
|
|
122
|
+
``` ruby
|
|
123
|
+
appendix = Payday::Appendix.new(
|
|
124
|
+
source: File.read('appendix.typ'),
|
|
125
|
+
inputs: {'shipments' => shipments.to_json},
|
|
126
|
+
dependencies: {'chart.svg' => File.binread('chart.svg')}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
invoice.render_pdf(appendix: appendix)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`source` is [Typst](https://typst.app) markup appended to Payday's own template, so it is compiled as part of the invoice rather than as a second PDF you would then have to merge in. Two things follow from that: your pages inherit the invoice's page setup and margins, and the page counter runs across the whole document, so the numbering footer counts your pages too. A one-page invoice with one appended page comes out numbered "1 / 2" and "2 / 2".
|
|
133
|
+
|
|
134
|
+
`inputs` reach the template as [sys_inputs](https://typst.app/docs/reference/foundations/sys/), read with `json(bytes(sys.inputs.shipments))`. `dependencies` are files the source can reach by name, with `image("chart.svg")` and the like. Reusing a name the invoice has already taken — the `invoice` input, or the logo and QR code files — raises an `ArgumentError` rather than quietly replacing it.
|
|
135
|
+
|
|
136
|
+
**Pass your data through `inputs`, never by interpolating it into `source`.** Typst treats a string arriving through `sys.inputs` as literal text, which is what stops a customer-supplied field from affecting the layout. `source` is code: interpolating invoice data into it hands anyone who can set a field on an invoice a way to write Typst.
|
|
137
|
+
|
|
138
|
+
An appendix that reads its data and prints a heading:
|
|
139
|
+
|
|
140
|
+
``` typst
|
|
141
|
+
#let a = json(bytes(sys.inputs.shipments))
|
|
142
|
+
|
|
143
|
+
#pagebreak()
|
|
144
|
+
#text(size: 16pt, weight: "bold")[#a.title]
|
|
145
|
+
```
|
|
146
|
+
|
|
118
147
|
I18n
|
|
119
148
|
===
|
|
120
149
|
Payday uses the i18n gem to provide support for custom labels and internationalized applications. You can change the default labels by adding a YAML file in the `config/locales` directory of your Rails app. Here are the default labels you can customize:
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Payday
|
|
4
|
+
|
|
5
|
+
# Extra pages compiled onto the end of an invoice.
|
|
6
|
+
#
|
|
7
|
+
# appendix = Payday::Appendix.new(
|
|
8
|
+
# source: File.read('appendix.typ'),
|
|
9
|
+
# inputs: {'shipments' => shipments.to_json},
|
|
10
|
+
# dependencies: {'chart.svg' => File.binread('chart.svg')}
|
|
11
|
+
# )
|
|
12
|
+
# invoice.render_pdf(appendix: appendix)
|
|
13
|
+
#
|
|
14
|
+
# +source+ is Typst markup appended to Payday's own template, so it is compiled as part of
|
|
15
|
+
# the invoice rather than as a second document that would then have to be merged in. Two
|
|
16
|
+
# things follow from that. The template's +#set page+ rules stay in force, so the appended
|
|
17
|
+
# pages keep the invoice's margins; and the page counter runs across the whole document, so
|
|
18
|
+
# the numbering footer counts the appended pages. A one-page invoice with one appended page
|
|
19
|
+
# numbers "1 / 2", and a three-page invoice continues into "4 / 4".
|
|
20
|
+
#
|
|
21
|
+
# That is also why the source is concatenated rather than +#include+d: an included file is
|
|
22
|
+
# its own module, and the page rules set inside the template would stop at its edge.
|
|
23
|
+
#
|
|
24
|
+
# **Pass your data through +inputs+, never through +source+.** Inputs reach the template as
|
|
25
|
+
# sys_inputs JSON, where Typst treats every string as literal text, which is what stops a
|
|
26
|
+
# customer-supplied field from affecting the layout. Source is code. Interpolating invoice
|
|
27
|
+
# data into it gives that data back the run of the document, and hands anyone who can set a
|
|
28
|
+
# field on an invoice a way to write Typst.
|
|
29
|
+
#
|
|
30
|
+
# +dependencies+ are files the source can reach by name -- +image("chart.svg")+ and the
|
|
31
|
+
# like. Keys are file names, values are the bytes.
|
|
32
|
+
Appendix = Data.define(:source, :inputs, :dependencies) do
|
|
33
|
+
|
|
34
|
+
# Accepts an Appendix, a Hash of the same attributes, or nil.
|
|
35
|
+
def self.wrap(value)
|
|
36
|
+
return value if value.nil? || value.is_a?(Appendix)
|
|
37
|
+
|
|
38
|
+
new(**value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize(source:, inputs: {}, dependencies: {})
|
|
42
|
+
super(source: source, inputs: inputs.transform_keys(&:to_s), dependencies: dependencies.transform_keys(&:to_s))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
data/lib/payday/invoiceable.rb
CHANGED
|
@@ -78,14 +78,15 @@ module Payday
|
|
|
78
78
|
defined?(paid_at) && !!paid_at
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
# Renders this invoice to pdf as a string
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
# Renders this invoice to pdf as a string. Pass a Payday::Appendix to compile pages of
|
|
82
|
+
# your own onto the end of it.
|
|
83
|
+
def render_pdf(appendix: nil)
|
|
84
|
+
Payday::PdfRenderer.render(self, appendix: appendix)
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
# Renders this invoice to pdf
|
|
87
|
-
def render_pdf_to_file(path)
|
|
88
|
-
Payday::PdfRenderer.render_to_file(self, path)
|
|
88
|
+
def render_pdf_to_file(path, appendix: nil)
|
|
89
|
+
Payday::PdfRenderer.render_to_file(self, path, appendix: appendix)
|
|
89
90
|
end
|
|
90
91
|
|
|
91
92
|
# Iterates through the details on this invoiceable. The block given should accept
|
data/lib/payday/pdf_renderer.rb
CHANGED
|
@@ -13,13 +13,14 @@ module Payday
|
|
|
13
13
|
FONTS = %w[NotoSans-Regular.ttf NotoSans-Bold.ttf].freeze
|
|
14
14
|
|
|
15
15
|
# Renders the given invoice as a pdf on disk
|
|
16
|
-
def self.render_to_file(invoice, path)
|
|
17
|
-
File.binwrite(path, render(invoice))
|
|
16
|
+
def self.render_to_file(invoice, path, appendix: nil)
|
|
17
|
+
File.binwrite(path, render(invoice, appendix: appendix))
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Renders the given invoice as a pdf, returning a string
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
# Renders the given invoice as a pdf, returning a string. See Payday::Appendix for
|
|
21
|
+
# appending pages of your own to it.
|
|
22
|
+
def self.render(invoice, appendix: nil)
|
|
23
|
+
new(invoice, appendix: appendix).render
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# Converts this number to a formatted currency string
|
|
@@ -39,8 +40,9 @@ module Payday
|
|
|
39
40
|
end
|
|
40
41
|
private_class_method :currency_for
|
|
41
42
|
|
|
42
|
-
def initialize(invoice)
|
|
43
|
+
def initialize(invoice, appendix: nil)
|
|
43
44
|
@invoice = invoice
|
|
45
|
+
@appendix = Appendix.wrap(appendix)
|
|
44
46
|
@dependencies = {}
|
|
45
47
|
end
|
|
46
48
|
|
|
@@ -49,13 +51,44 @@ module Payday
|
|
|
49
51
|
register_qr_code(data[:qr_code])
|
|
50
52
|
data[:logo] = logo
|
|
51
53
|
|
|
52
|
-
Typst(body:
|
|
53
|
-
sys_inputs: {'invoice' => data.to_json})
|
|
54
|
+
Typst(body: body, dependencies: dependencies, fonts: fonts, sys_inputs: sys_inputs(data))
|
|
54
55
|
.compile(:pdf).bytes.flatten.pack('C*')
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
private
|
|
58
59
|
|
|
60
|
+
# An appendix is concatenated rather than +#include+d, which would make it a module of its
|
|
61
|
+
# own and leave the template's page rules -- the margins, and the numbering footer --
|
|
62
|
+
# stopping at its edge.
|
|
63
|
+
def body
|
|
64
|
+
template = File.read(TEMPLATE)
|
|
65
|
+
return template if @appendix.nil?
|
|
66
|
+
|
|
67
|
+
"#{template}\n#{@appendix.source}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def sys_inputs(data)
|
|
71
|
+
merge({'invoice' => data.to_json}, @appendix&.inputs, 'sys_input')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Read after the logo and the QR code have registered theirs, so an appendix reusing one
|
|
75
|
+
# of those names is caught rather than silently replacing the file.
|
|
76
|
+
def dependencies
|
|
77
|
+
merge(@dependencies, @appendix&.dependencies, 'dependency')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def merge(ours, theirs, what)
|
|
81
|
+
return ours if theirs.nil?
|
|
82
|
+
|
|
83
|
+
taken = ours.keys & theirs.keys
|
|
84
|
+
if taken.any?
|
|
85
|
+
raise ArgumentError,
|
|
86
|
+
"appendix #{what} #{taken.map(&:inspect).join(', ')} is reserved by the invoice"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
ours.merge(theirs)
|
|
90
|
+
end
|
|
91
|
+
|
|
59
92
|
def fonts
|
|
60
93
|
FONTS.to_h { |name| [name, File.binread(File.join(FONT_DIR, name))] }
|
|
61
94
|
end
|
data/payday.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: webtranslateit-payday
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alan Johnson
|
|
@@ -147,6 +147,7 @@ files:
|
|
|
147
147
|
- lib/generators/payday/setup/templates/line_item.rb
|
|
148
148
|
- lib/generators/payday/setup/templates/migration.rb
|
|
149
149
|
- lib/payday.rb
|
|
150
|
+
- lib/payday/appendix.rb
|
|
150
151
|
- lib/payday/assets/default_logo.png
|
|
151
152
|
- lib/payday/config.rb
|
|
152
153
|
- lib/payday/invoice.rb
|