File Coverage

lib/Business/Payment/SwissESR/PaymentSlip.pm
Criterion Covered Total %
statement 118 129 91.4
branch 16 26 61.5
condition 3 9 33.3
subroutine 15 18 83.3
pod 3 4 75.0
total 155 186 83.3


line stmt bran cond sub pod time code
1             package Business::Payment::SwissESR::PaymentSlip;
2              
3             =head1 NAME
4              
5             Business::Payment::SwissESR::PaymentSlip - Class for creating Esr PDFs
6              
7             =head1 SYNOPSYS
8              
9             use Business::Payment::SwissESR::PaymentSlip;
10             my $nl = '\newline';
11             my $bs = '\\';
12             my $esr = Business::Payment::SwissESR::PaymentSlip->new(
13             shiftRightMm => $x,
14             shiftDownMm => $y,
15             senderAddressLaTeX => <<'LaTeX_End'
16             Oltner 2-Stunden Lauf\newline
17             Florastrasse 21\newline
18             4600 Olten
19             LaTeX_End
20             account => '01-17546-3',
21             );
22             $esr->add(
23             amount => 44.40,
24             account => '01-17546-3',
25             senderAddressLaTeX => 'Override',
26             recipientAddressLaTeX => <<'LaTeX_End',
27             Peter Müller\newline
28             Haldenweg 12b\newline
29             4600 Olten
30             LaTeX_End
31             bodyLaTeX => 'the boddy of the bill in latex format',
32             referenceNumber => 3423,
33             watermark => 'secret marker',
34             );
35              
36             my $pdf = $esr->renderPdf(showPaymentSlip=>1);
37              
38             =head1 DESCRIPTION
39              
40             This class let's you create Swiss ESR payment slips in PDF format both for
41             email and to to print on official ESR pre-prints forms. The content is modeled after:
42              
43             L
44             L
45              
46             =head1 PROPERTIES
47              
48             The SwissESR objects have the following properties:
49              
50             =cut
51              
52 1     1   1730 use vars qw($VERSION);
  1         3  
  1         48  
53 1     1   9 use Mojo::File;
  1         100992  
  1         47  
54 1     1   15 use Mojo::Base -base;
  1         3  
  1         7  
55 1     1   165 use Cwd;
  1         3  
  1         1451  
56              
57 1     1 0 2 our $VERSION = '0.13.3';
58              
59             =head2 luaLaTeX
60              
61             the lualatex binary to run
62              
63             =cut
64              
65 1     1   6 has luaLaTeX => sub {'lualatex'};
  1         1171  
66              
67             =head2 shiftRightMm
68              
69             Swiss Post is very picky about proper positioning of the text in the ESR
70             payment slip. Make sure you get one of the official transparencies to
71             verify that your printouts look ok. Even that may not suffice, to be
72             sure, send a bunch of printouts for verification to Swiss Post.
73              
74             With this property you can shift the payment slip right in milimeters.
75              
76             =cut
77              
78 1         52 has shiftRightMm => 0;
79              
80             =head2 shiftDownMm
81              
82             This is for shifting the payment slip down.
83              
84             =cut
85              
86 1         33 has shiftDownMm => 0;
87              
88             =head2 scale
89              
90             Some printers seem to not be able to accurately scale the output ... this lets you
91             scale the payment slip in the oposite direction.
92              
93             =cut
94              
95 1         29 has scale => 1;
96              
97             =head2 senderAddressLaTeX
98              
99             A default sender address for invoice and payment slip. This can be overridden in an individual basis
100              
101             =cut
102              
103 1     0   31 has senderAddressLaTeX => sub { 'no default' };
  0         0  
104              
105             =head2 account
106              
107             The default account to be printed on the payment slips.
108              
109             =cut
110              
111 1         30 has 'account';
112              
113             =head2 preambleAddons
114              
115             Additional lines for the latex preable
116              
117             =cut
118              
119             has preambleAddons => sub {
120 0     0   0 return '';
121 1         45 };
122              
123              
124             has tasks => sub {
125 1     1   9 [];
126 1         36 };
127              
128             # where lualatex can run to create the pdfs
129              
130             has tmpDir => sub {
131 1     1   10 my $tmpDir = '/tmp/SwissESR'.$$;
132 1 50       57 if (not -d $tmpDir){
133 1 50       85 mkdir $tmpDir or die "Failed to create $tmpDir";
134 1         25 chmod 0700, $tmpDir;
135             }
136 1         7 return $tmpDir;
137 1         30 };
138              
139             # clean up the temp data
140              
141             sub DESTROY {
142 1     1   5 my $self = shift;
143 1         10 unlink glob $self->tmpDir.'/*';
144 1         163 unlink glob $self->tmpDir.'/.??*';
145 1         42 rmdir $self->tmpDir;
146             }
147              
148             # where to find our resource files
149              
150             has moduleBase => sub {
151 1     1   7 my $path = $INC{'Business/Payment/SwissESR/PaymentSlip.pm'};
152 1         9 $path =~ s{/[^/]+$}{};
153 1         3 return $path;
154 1         30 };
155              
156             =head1 METHODS
157              
158             The SwissERS objects have the following methods.
159              
160             =head2 add(key=>value, ...)
161              
162             Adds an invoice. Specify the following properties for each invoice:
163              
164             amount => 44.40,
165             account => '01-17546-3',
166             recipientAddressLaTeX => <<'LaTeX_End',
167             Peter Müller\newline
168             Haldenweg 12b\newline
169             4600 Olten
170             LaTeX_End
171             bodyLaTeX => 'complete body of the letter including all addrssing',
172              
173             referenceNumber => 3423,
174              
175             these two properties are optional
176              
177             senderAddressLaTeX => 'Override',
178             watermark => 'small marker to be printed on the invoice',
179              
180             You can call add multiple times to generate a buch of invoices in one pdf file.
181              
182             =cut
183              
184             sub add {
185 2     2 1 5307 my $self = shift;
186 2         18 my $cfg = { @_ };
187 2   33     19 $cfg->{senderAddressLaTeX} //= $self->senderAddressLaTeX;
188 2   33     28 $cfg->{account} //= $self->account;
189              
190 2         4 push @{$self->tasks}, $cfg;
  2         9  
191             }
192              
193             # execute lualatex with the given source file and return the resulting pdf or die
194              
195             my $runLaTeX = sub {
196 1     1   2 my $self = shift;
197 1         2 my $src = shift;
198 1         4 my $tmpdir = $self->tmpDir;
199 1 50       60 open my $out, ">:utf8", "$tmpdir/esr.tex" or die "Failed to create esr.tex";
200 1         32 print $out $src;
201 1         36 close $out;
202 1         1464 my $cwd = cwd();
203 1 50       27 chdir $tmpdir or die "Failed to chdir to $tmpdir";
204 1         18 open my $latex, '-|', $self->luaLaTeX,'esr';
205 1         22 chdir $cwd;
206 1         35 my $latexOut = join '', <$latex>;
207 1         7 close $latex;
208 1 50 33     28 if (not -e $tmpdir.'/esr.pdf' or -z $tmpdir.'/esr.pdf'){
209 1         33 die $latexOut;
210             }
211 0         0 my $pdf = Mojo::File->new($tmpdir.'/esr.pdf')->slurp;
212 0         0 return $pdf;
213 1         29 };
214              
215             # this is that very cool algorithm to calculate the checksum
216             # used in the
217              
218             my $calcEsrChecksum = sub {
219 3     3   6 my $self = shift;
220 3         5 my $input = shift;
221 3         12 my @map = ( 0, 9, 4, 6, 8, 2, 7, 1, 3, 5 );
222 3         5 my $keep = 0;
223 3         24 for my $number ($input =~ m/(\d)/g){
224 43         81 $keep = $map[($keep+$number) % 10 ];
225             }
226 3         13 return ((10 - $keep) % 10);
227 1         5 };
228              
229             # generate the latex code for the esr
230              
231             my $makeEsrLaTeX = sub {
232 1     1   2 my $self = shift;
233 1         4 my $electronic = shift;
234 1         4 my $root = $self->moduleBase;
235 1         5 my %docSet = (
236             root => $root,
237             shiftDownMm => $self->shiftDownMm,
238             shiftRightMm => $self->shiftRightMm,
239             scale => $self->scale,
240             preambleAddons => $self->preambleAddons
241             );
242              
243 1         20 my $doc = <<'TEX_END';
244             \nonstopmode
245             \documentclass[10pt]{article}
246             \usepackage[a4paper,margin=2cm,top=1.5cm,bottom=1.5cm]{geometry}
247             \usepackage{color}
248             \usepackage{fontspec}
249             \newfontface\ocrb[Path = ${root}/ ]{ocrb10.otf}
250             \setmainfont{DejaVu Sans Condensed}
251             \usepackage{graphicx}
252             \usepackage{calc}
253             \pagestyle{empty}
254             \setlength{\unitlength}{1mm}
255             \setlength{\parindent}{0ex}
256             \setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
257             ${preambleAddons}
258             \begin{document}
259             TEX_END
260 1         13 $doc =~ s/\$\{(\S+?)\}/$docSet{$1}/eg;
  2         11  
261              
262 1         3 for my $task (@{$self->tasks}) {
  1         10  
263 2         14 my %cfg = %$task;
264 2         6 my $value = '042'; #ESR+
265 2         4 my $printValue;
266 2 100       6 if ($cfg{amount}){
267 1         8 $value = sprintf("01%010d",$cfg{amount}*100);
268 1         3 $value .= $self->$calcEsrChecksum($value);
269 1 100       16 $printValue = join '', map { $_ eq '.' ? '\hspace{1.43em}' :'\makebox[1.43em][c]{'.$_.'}' } split '',sprintf('%.2f',$cfg{amount});
  7         24  
270             }
271 2         7 $cfg{root} = $root;
272 2         5 $cfg{bs} = '\\';
273 2 50       8 $cfg{template} = $electronic
274             ? '\put(0,0){\includegraphics{'.$root.'/esrTemplate.pdf}}'
275             .'\put(65,8){\textbf{\color{red}Dieser Einzahlungsschein ist nur für elektronische Einzahlungen geeignet!}}'
276             : '';
277 2         11 my ($pc_base,$pc_nr) = $cfg{account} =~ /(\d\d)-(.+)/;
278 2         9 $pc_nr =~ s/[^\d]//g;
279 2         15 my $ref = $cfg{referenceNumber};
280 2 100       10 $ref = ('0' x (( length($ref) <= 15 ? 15 : 26 ) - length($ref))) . $ref;
281 2         6 $ref .= $self->$calcEsrChecksum($cfg{referenceNumber});
282 2         12 $cfg{code} = $value.'>'
283             . $ref
284             . '+\hspace{0.1in}'
285             . sprintf('%02d%07d',$pc_base,$pc_nr).'>';
286 2         3 $cfg{referenceNumber} = '';
287 2         11 while ($ref =~ s/(\d{1,5})$//){
288 10         45 $cfg{referenceNumber} = $1 . '\hspace{1ex}' . $cfg{referenceNumber};
289             }
290              
291 2         4 my $page = <<'DOC_END';
292             \raisebox{-\paperheight+1in+\voffset+\topmargin+\headheight+\headsep+\baselineskip - ${shiftDownMm}mm}[0pt][0pt]{%
293             \makebox[0pt][l]{\hspace*{-\hoffset}\hspace{-\oddsidemargin}\hspace{-1in}\hspace{${shiftRightMm}mm}\scalebox{${scale}}{\begin{picture}(0,0)
294             \put(180,29){\rule{0.5pt}{0.5pt}}
295             DOC_END
296              
297 2         14 $page =~ s/\$\{(\S+?)\}/$docSet{$1}/eg;
  6         25  
298              
299 2         6 $page .= <<'DOC_END';
300             ${template}
301             % the reference number ... positioning this properly is THE crucial element
302             \put(202.5,17){\makebox[0pt][r]{\ocrb \fontsize{10pt}{16pt}\selectfont ${code}}}
303             \put(7,93){\parbox[t]{5cm}{\small ${senderAddressLaTeX}}}
304             \put(63,93){\parbox[t]{8cm}{\small ${senderAddressLaTeX}}}
305             \put(7,41){\scriptsize ${referenceNumber}}
306             \put(7,35){\footnotesize\parbox[t]{5cm}{${recipientAddressLaTeX}}}
307             \put(127,54){\footnotesize\parbox[t]{7cm}{${recipientAddressLaTeX}}}
308             \put(28,60.5){\small ${account}}
309             \put(89,60.5){\small ${account}}
310             \put(205,69){\small\makebox[0pt][r]{\ocrb ${referenceNumber}}}
311             DOC_END
312 2 100       6 if ($printValue){
313 1         7 $page .= '\put(58,51.5){\ocrb\makebox[0pt][r]{ '.$printValue.'}}';
314 1         4 $page .= '\put(119,51.5){\ocrb\makebox[0pt][r]{ '.$printValue.'}}';
315             }
316 2 50       9 $page .= <<'DOC_END' if $cfg{watermark};
317             \put(200,110){\makebox[0pt][r]{\scriptsize ${watermark}}}
318             DOC_END
319              
320 2         4 $page .= <<'DOC_END';
321             \end{picture}}}}%
322             \enlargethispage{-10cm}%
323             %\begin{figure}[!btp]
324             %\vspace{10cm}
325             %\end{figure}
326             ${bodyLaTeX}
327             \newpage
328              
329             DOC_END
330             my $resolve = sub {
331 24         45 my $v = shift;
332 24 50       49 if (not defined $cfg{$v}){
333 0         0 print STDERR "No data for $v\n"; return ''
  0         0  
334             }
335             else {
336 24         92 return $cfg{$v}
337             }
338 2         10 };
339 2         20 $page =~ s/\$\{(\S+?)\}/$resolve->($1)/eg;
  24         50  
340 2         14 $doc .= $page;
341             }
342 1         6 $doc .= '\end{document}'."\n";
343 1         7 return $doc;
344 1         5 };
345              
346             =head2 renderPdf(showPaymentSlip => 1|0)
347              
348             Render the invoice in pdf format.
349              
350             If the C option is set, the invoice will contain a grey
351             rendering of the official ESR payment slip. For payment at the Post Office
352             counter, the invoice and payment slip have to be printed on 'official'
353             paper containing a pre-printed ESR slip.
354              
355             =cut
356              
357             sub renderPdf {
358 1     1 1 7 my $self = shift;
359 1         3 my %args = @_;
360 1         6 return $self->$runLaTeX($self->$makeEsrLaTeX($args{showPaymentSlip}));
361             }
362              
363             =head2 $p->quoteLaTeX($str)
364              
365             return the string with 'magic' latex characters escaped (eg & -> \&).
366              
367             =cut
368              
369             sub quoteLaTeX {
370 0 0   0 1   my $self = shift if ref $_[0];
371 0           my $str = shift;
372 0           $str =~ s/\\/\\texbackslash/g;
373 0           $str =~ s/([#$%^&_}{~])/\$1/g;
374 0           return $str;
375             }
376              
377 1         7 1;
378              
379             __END__