default to a session cookie instead of setting an explicit timeout, weird timezone...
[freeside.git] / FS / FS / Misc / DepositSlip.pm
1 package FS::Misc::DepositSlip;
2 use base 'Exporter';
3
4 use strict;
5 use warnings;
6 use vars qw( @EXPORT_OK );
7
8 #use Date::Format;
9 use IPC::Run qw( run timeout );   # for _xelatex
10 use Text::Template;
11
12 @EXPORT_OK = qw( deposit_slip_pdf );
13
14 =item deposit_slip_pdf
15
16 =cut
17
18 sub deposit_slip_pdf {
19   my %arg = @_;
20   my $conf = $arg{'conf'};
21   my @cust_pay = @{ $arg{'cust_pay'} };
22
23   if ( scalar(@cust_pay) > 25 ) {
24     return 'ERROR: Maxiumum of 25 items per deposit slip at this time';
25   }
26
27   my $text_template = new Text::Template(
28     TYPE       => 'STRING',
29     SOURCE     => slip_template(),
30     DELIMITERS => [ '[@--', '--@]' ],
31   );
32
33   $text_template->compile() or die $Text::Template::ERROR;
34
35   my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
36   chdir($dir);
37
38   #my $date = time2str( $self->conf->config('date_format_long') || '%b %o, %Y',
39   #                     time #XXX future deposit date
40   #                   );
41
42   my $total = 0;
43   foreach my $cust_pay (@cust_pay) {
44     $total += $cust_pay->paid;
45   }
46   $total = sprintf('%.2f', $total + 0.00000001); #so FP math errors round out
47   my ($total_dollars, $total_cents) = split(/\./, $total);
48
49   my $gtotal = sprintf('%11.2f', $total);
50   $gtotal =~ s/\.//;
51   my @gtotal = split('', $gtotal);
52
53   #XXX agent virt for company name, address
54   my %fill_in = (
55     'company_name'    => _latex_escape($conf->config('company_name')),
56     'company_address' => join('\\\\', map _latex_escape($_),
57                            $conf->config('company_address') ),
58
59     'bank_name'       => _latex_escape($conf->config('deposit_slip-bank_name')),
60     'bank_address'    => join('\\\\', map _latex_escape($_),
61                            $conf->config('deposit_slip-bank_address') ),
62
63     'bank_routingnumber' => _latex_escape(
64                               $conf->config('deposit_slip-bank_routingnumber')),
65     'bank_accountnumber' => _latex_escape(
66                               $conf->config('deposit_slip-bank_accountnumber')),
67
68     #already defaulting to today
69     #'depositdate'     => _latex_escape($date),
70
71     'currency_dollars'   => '',
72     'currency_cents'     => '',
73     'coin_dollars'       => '',
74     'coin_cents'         => '',
75     'checks_dollars'     => '',
76     'checks_cents'       => '',
77
78     'reverse_dollars'    => '',
79     'reverse_cents'      => '',
80     'total_dollars'      => $total_dollars,
81     'total_cents'        => $total_cents,
82
83     'cust_pay'           => \@cust_pay,
84
85     'totalitems'         => scalar(@cust_pay),
86
87     'grandtotalboxone'   => $gtotal[0],
88     'grandtotalboxtwo'   => $gtotal[1],
89     'grandtotalboxthree' => $gtotal[2],
90     'grandtotalboxfour'  => $gtotal[3],
91     'grandtotalboxfive'  => $gtotal[4],
92     'grandtotalboxsix'   => $gtotal[5],
93     'grandtotalboxseven' => $gtotal[6],
94     'grandtotalboxeight' => $gtotal[7],
95     'grandtotalboxnine'  => $gtotal[8],
96     'grandtotalboxten'   => $gtotal[9],
97   );
98
99   #XXX better unique filename
100   my $file = "deposit$$";
101
102   open(DEPOSIT_TEX, ">$file.tex") or die $!;
103   print DEPOSIT_TEX $text_template->fill_in( HASH => \%fill_in );
104   close DEPOSIT_TEX or die $!;
105
106   _xelatex($file);
107
108   #XXX use File::Slurp
109   my $pdf = '';
110   open(PDF, "<$file.pdf") or die $!;
111
112   unlink("$file.log", "$file.aux", "$file.pdf", "$file.tex");
113
114   while (<PDF>) {
115     $pdf .= $_;
116   }
117   close PDF;
118
119   return $pdf;
120
121
122
123 #some false laziness w/_pslatex in Misc.pm
124 sub _xelatex {
125   my $file = shift;
126
127   #my $sfile = shell_quote $file;
128
129   my @cmd = (
130     'xelatex',
131     '-interaction=batchmode',
132     "$file.tex"
133   );
134
135   my $timeout = 30; #? should be more than enough
136
137   for ( 1, 2 ) {
138
139     local($SIG{CHLD}) = sub {};
140     run( \@cmd, '>'=>'/dev/null', '2>'=>'/dev/null', timeout($timeout) )
141       or warn "bad exit status from xelatex pass $_\n";
142
143   }
144
145   return if -e "$file.pdf" && -s "$file.pdf";
146   die "xelatex $file.tex failed, see $file.log for details?\n";
147
148 }
149
150 #false laxiness w/Template_Mixin.pm
151 sub _latex_escape {
152   my $value = shift;
153   $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( ( defined($2) && length($2) ) ? "\\$2" : '' )/ge;
154   $value =~ s/([<>])/\$$1\$/g;
155   $value;
156 }
157
158 sub slip_template { <<'__END__';
159
160 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
161 % Freeside Deposit Slip
162 % LaTeX Template
163 % Version 1.1 (May 9, 2020)
164 %
165 % This template was created by:
166 % Vel (enquiries@latextypesetting.com)
167 % LaTeXTypesetting.com
168 %
169 %!TEX program = xelatex
170 % Note: this template must be compiled with XeLaTeX rather than PDFLaTeX
171 % due to the custom fonts used. The line above should ensure this happens
172 % automatically, but if it doesn't, your LaTeX editor should have a simple toggle
173 % to switch to using XeLaTeX.
174 %
175 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
176
177 %----------------------------------------------------------------------------------------
178 %       PACKAGES AND OTHER DOCUMENT CONFIGURATIONS
179 %----------------------------------------------------------------------------------------
180
181 \documentclass{article}
182
183 %----------------------------------------------------------------------------------------
184 %       REQUIRED PACKAGES AND MISC CONFIGURATIONS
185 %----------------------------------------------------------------------------------------
186
187 \setlength{\parindent}{0pt} % Stop paragraph indentation
188
189 \usepackage{tikz} % Required for custom graphics
190 \usetikzlibrary{calc} % Required for coordinate calculations within TikZ
191
192 % Suppress hyphenation across the whole document
193 \hyphenpenalty=10000
194 \exhyphenpenalty=10000
195
196 %----------------------------------------------------------------------------------------
197 %       MARGINS
198 %----------------------------------------------------------------------------------------
199
200 \usepackage[
201         paperwidth=8.5in,
202         paperheight=3.25in,
203         top=0cm, % Top margin
204         bottom=1cm, % Bottom margin
205         left=1cm, % Left margin
206         right=1cm, % Right margin
207         footskip=0.6cm, % Space from the bottom margin to the baseline of the footer
208         headsep=0.8cm, % Space from the top margin to the baseline of the header
209         headheight=0.5cm, % Height of the header
210         %showframe % Uncomment to show the frames around the margins for debugging purposes
211 ]{geometry}
212
213 %----------------------------------------------------------------------------------------
214 %       FONTS
215 %----------------------------------------------------------------------------------------
216
217 \usepackage{fontspec} % Required for specifying custom fonts
218
219 \defaultfontfeatures{Ligatures=TeX} % To support LaTeX ligatures (`` and --)
220 \defaultfontfeatures{Path=/usr/local/etc/freeside/} % Specify the location of font files
221
222 \newfontface\GNUMicr{GnuMICR.otf} % MICR font from file
223
224 \usepackage[default]{sourcesanspro} % Use the Source Sans Pro font for the document body
225
226 %----------------------------------------------------------------------------------------
227 %       HEADERS AND FOOTERS
228 %----------------------------------------------------------------------------------------
229
230 \usepackage{fancyhdr} % Required for customising headers and footers
231 \pagestyle{fancy} % Enable custom headers and footers
232
233 \renewcommand{\headrulewidth}{0pt} % Remove default top horizontal rule
234
235 \fancyhf{} % Clear default headers/footers
236
237 \fancyfoot[C]{{\Large\GNUMicr\MICR}} % Centre footer
238
239 %----------------------------------------------------------------------------------------
240 %       TABLES
241 %----------------------------------------------------------------------------------------
242
243 \usepackage{booktabs} % Required for better horizontal rules in tables
244
245 \usepackage{array} % Required for manipulating table columns
246
247 \renewcommand{\arraystretch}{1.35} % Increase the space between table rows
248
249 \newcolumntype{R}[1]{>{\raggedleft\arraybackslash}p{#1}} % Define a new right-aligned paragraph column type
250 \newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}} % Define a new left-aligned (no justification) paragraph column type
251 \newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}} % Define a new centred paragraph column type
252
253 %----------------------------------------------------------------------------------------
254 %       CUSTOM COMMANDS
255 %----------------------------------------------------------------------------------------
256
257 \newcommand{\MICR}[1]{\renewcommand{\MICR}{#1}}
258 \newcommand{\disclaimer}[1]{\renewcommand{\disclaimer}{#1}}
259 \newcommand{\companyaddress}[1]{\renewcommand{\companyaddress}{#1}}
260 \newcommand{\bankaddress}[1]{\renewcommand{\bankaddress}{#1}}
261 \newcommand{\totalitems}[1]{\renewcommand{\totalitems}{#1}}
262 \newcommand{\grandtotalboxone}[1]{\renewcommand{\grandtotalboxone}{#1}}
263 \newcommand{\grandtotalboxtwo}[1]{\renewcommand{\grandtotalboxtwo}{#1}}
264 \newcommand{\grandtotalboxthree}[1]{\renewcommand{\grandtotalboxthree}{#1}}
265 \newcommand{\grandtotalboxfour}[1]{\renewcommand{\grandtotalboxfour}{#1}}
266 \newcommand{\grandtotalboxfive}[1]{\renewcommand{\grandtotalboxfive}{#1}}
267 \newcommand{\grandtotalboxsix}[1]{\renewcommand{\grandtotalboxsix}{#1}}
268 \newcommand{\grandtotalboxseven}[1]{\renewcommand{\grandtotalboxseven}{#1}}
269 \newcommand{\grandtotalboxeight}[1]{\renewcommand{\grandtotalboxeight}{#1}}
270 \newcommand{\grandtotalboxnine}[1]{\renewcommand{\grandtotalboxnine}{#1}}
271 \newcommand{\grandtotalboxten}[1]{\renewcommand{\grandtotalboxten}{#1}}
272 \newcommand{\depositdate}[1]{\renewcommand{\depositdate}{#1}}
273
274 %\newcommand{\command}[1]{\renewcommand{\command}{#1}}
275
276 %----------------------------------------------------------------------------------------
277 %       DEPOSIT SLIP INFORMATION
278 %----------------------------------------------------------------------------------------
279
280 \MICR{A[@-- $bank_routingnumber --@]A  [@-- $bank_accountnumber --@]C} % Displayed at the bottom of the slip
281
282 \disclaimer{Checks and other items are received for deposit subject to the provisions of the Uniform Commercial Code and any applicable collection agreement. Deposits May Not Be Available For Immediate Withdrawal.}
283
284 \companyaddress{\textbf{[@-- $company_name --@]}\\[@-- $company_address --@]}
285
286 \bankaddress{\textbf{[@-- $bank_name --@]}\\ [@-- $bank_address --@]}
287
288 \totalitems{[@-- $totalitems --@]}
289
290 \grandtotalboxone{[@-- $grandtotalboxone --@]}
291 \grandtotalboxtwo{[@-- $grandtotalboxtwo --@]}
292 \grandtotalboxthree{[@-- $grandtotalboxthree --@]}
293 \grandtotalboxfour{[@-- $grandtotalboxfour --@]}
294 \grandtotalboxfive{[@-- $grandtotalboxfive --@]}
295 \grandtotalboxsix{[@-- $grandtotalboxsix --@]}
296 \grandtotalboxseven{[@-- $grandtotalboxseven --@]}
297 \grandtotalboxeight{[@-- $grandtotalboxeight --@]}
298 \grandtotalboxnine{[@-- $grandtotalboxnine --@]}
299 \grandtotalboxten{[@-- $grandtotalboxten --@]}
300
301 \depositdate{\today}
302
303 %----------------------------------------------------------------------------------------
304
305 \begin{document}
306
307 %----------------------------------------------------------------------------------------
308 %       DEPOSIT SLIP
309 %----------------------------------------------------------------------------------------
310
311 \begin{tikzpicture}[remember picture, overlay]
312         \node [anchor=north, rotate=90, xshift=-0.5\paperheight, yshift=-0.4cm, inner sep=0pt] (title) at (current page.north west) {\textbf{DEPOSIT TICKET}}; % Deposit ticket title text
313         
314         \node [anchor=north, rotate=90, yshift=-0.4cm, inner sep=0pt] (dateline) at (title.south) {DATE: \rule{0.58\paperheight}{1pt}}; % Date line
315         \node [anchor=south, rotate=90, yshift=-0.2cm, inner sep=0pt] (date) at (dateline.north) {\depositdate}; % Date
316         
317         \node [anchor=north west, rotate=90, yshift=-0.25cm, text width=0.8\paperheight, inner sep=0pt] (disclaimer) at (dateline.south west) {\fontsize{6pt}{7pt}\selectfont \disclaimer\par}; % Disclaimer text
318         
319         \node [anchor=north east, rotate=90, inner sep=0pt] (table) at (disclaimer.south east) {% Table
320                 \begin{tabular}{| L{1.8cm} | L{1cm} | L{0.5cm}}
321                         \cline{2-3}
322                         \multicolumn{1}{R{1.8cm} |}{} & \scriptsize DOLLARS & \scriptsize CENTS \\\cline{2-3}
323                         \multicolumn{1}{R{1.8cm} |}{\scriptsize CURRENCY} & [@-- $currency_dollars --@] & [@-- $currency_cents --@]\\\cline{2-3}
324                         \multicolumn{1}{R{1.8cm} |}{\scriptsize COIN} & [@-- $coin_dollars --@] & [@-- $coin_cents --@]\\\cline{2-3}
325                         \multicolumn{1}{| R{1.8cm} |}{\vspace{-1.2\baselineskip}\scriptsize CHECKS \newline \tiny (ENTER SEPARATELY)} & [@-- $checks_dollars --@] & [@-- $checks_cents --@]\\[-3pt]\cline{1-3}
326 [@--
327   for (0 .. 24) {
328     if ( scalar(@cust_pay) ) {
329       my $cust_pay = shift @cust_pay;
330       my ($dollars, $cents) = split(/\./, $cust_pay->paid);
331       $OUT .= $cust_pay->payinfo. " & $dollars & $cents". '\\\\\\cline{1-3}'. "\n";
332     } else {
333       $OUT .= ' & & \\\\\\cline{1-3}'. "\n";
334     }
335   }
336 --@]
337                         \fontsize{6pt}{6pt}\selectfont TOTAL OF \newline REVERSE SIDE & [@-- $reverse_dollars --@] & [@-- $reverse_cents --@] \\\cline{1-3}
338                         \fontsize{10pt}{10pt}\selectfont\textbf{TOTAL} \newline DEPOSIT & [@-- $total_dollars --@] & [@-- $total_cents --@] \\\cline{1-3}
339                 \end{tabular}
340         }; % Table
341         \node [anchor=north east, rotate=90, yshift=-0.07cm, inner sep=0pt] (totalguidetext) at (table.south east) {\fontsize{6pt}{6pt}\selectfont PLEASE ENTER TOTAL HERE}; % Total guide text
342         \draw [->] ($(totalguidetext.west)-(0, 0.1cm)$) -- ($(totalguidetext.west)-(0cm, 2.7cm)$); % Total guide text arrow
343         
344         \node [anchor=south west, xshift=0.3cm, text width=0.3\paperwidth, inner sep=0pt] (companyaddress) at (disclaimer.south west) {\Large\companyaddress\par}; % Company and address
345         
346         \node [anchor=north west, text width=0.3\paperwidth, inner sep=0pt] (bankaddress) at (companyaddress.north east) {\footnotesize\bankaddress\par}; % Financial institution address
347         
348         \node [anchor=south west, xshift=0.5cm, yshift=1.4cm, text width=0.75cm, inner sep=0pt] (totalitemstext) at (current page.south) {\fontsize{6pt}{6pt}\selectfont TOTAL ITEMS\par}; % Total items text
349         \node [rectangle, anchor=west, draw=black, line width=1pt, minimum width=1cm, minimum height=0.5cm, inner sep=0pt] (totalitemsbox) at (totalitemstext.east) {\totalitems}; % Total items box
350         
351         \node [anchor=west, xshift=1cm, text width=0.4cm, inner sep=0pt] (dollarsign) at (totalitemsbox.east) {\Large\textbf{\$}}; % Dollar sign text
352         \node [rectangle, anchor=west, fill=black!7, minimum width=6.7cm, minimum height=1.25cm, inner sep=0pt] (totalbox) at (dollarsign.east) {}; % Grand total grey box
353         \node [rectangle, anchor=west, fill=white, xshift=0.3cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxone) at (totalbox.west) {\grandtotalboxone}; % White box 1
354         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxtwo) at (whiteboxone.east) {\grandtotalboxtwo}; % White box 2
355         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxthree) at (whiteboxtwo.east) {\grandtotalboxthree}; % White box 3
356         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxfour) at (whiteboxthree.east) {\grandtotalboxfour}; % White box 4
357         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxfive) at (whiteboxfour.east) {\grandtotalboxfive}; % White box 5
358         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxsix) at (whiteboxfive.east) {\grandtotalboxsix}; % White box 6
359         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxseven) at (whiteboxsix.east) {\grandtotalboxseven}; % White box 7
360         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxeight) at (whiteboxseven.east) {\grandtotalboxeight}; % White box 8
361         \node [anchor=west, xshift=-0.5pt, inner sep=0pt] (centsperiod) at (whiteboxeight.south east) {.}; % Cents period
362         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxnine) at (whiteboxeight.east) {\grandtotalboxnine}; % White box 9
363         \node [rectangle, anchor=west, fill=white, xshift=0.05cm, minimum width=0.55cm, minimum height=0.55cm, inner sep=0pt] (whiteboxten) at (whiteboxnine.east) {\grandtotalboxten}; % White box 10
364         \draw [fill=white, draw=white] ($(whiteboxtwo.south east)+(0.02cm, 0cm)$) -- ($(whiteboxtwo.south east)-(0.03cm, 0.1cm)$) -- ($(whiteboxtwo.south east)+(0.07cm, -0.1cm)$) -- cycle; % Triangle 1
365         \draw [fill=white, draw=white] ($(whiteboxfive.south east)+(0.02cm, 0cm)$) -- ($(whiteboxfive.south east)-(0.03cm, 0.1cm)$) -- ($(whiteboxfive.south east)+(0.07cm, -0.1cm)$) -- cycle; % Triangle 2
366 \end{tikzpicture}
367
368 %----------------------------------------------------------------------------------------
369
370 \end{document}
371
372 __END__
373
374 }
375
376 1;