on-demand vs. automatic cards & checks: added DCRD and DCHK payment types
[freeside.git] / FS / bin / freeside-tax-report
1 #!/usr/bin/perl -Tw
2
3
4 use strict;
5 use Date::Parse;
6 use Time::Local;
7 use Getopt::Std;
8 use Text::Template;
9 use Net::SMTP;
10 use Mail::Header;
11 use Mail::Internet;
12 use FS::Conf;
13 use FS::UID qw(adminsuidsetup);
14 use FS::Record qw(qsearch);
15 use FS::cust_bill;
16 use FS::cust_bill_pay;
17 use FS::cust_pay;
18
19
20 &untaint_argv;  #what it sounds like  (eww)
21 use vars qw($opt_v $opt_p $opt_m $opt_e $opt_t $opt_s $opt_f $report_lines $report_template @buf $header);
22 getopts("vpmef:s:");    #switches
23
24 #we're at now now (and later).
25 my($_finishdate)= $opt_f ? str2time($main::opt_f) : $^T;
26 my($_startdate)= $opt_s ? str2time($main::opt_s) : $^T;
27
28 # Get the current month
29 my ($ssec,$smin,$shour,$smday,$smon,$syear) =
30         (localtime($_startdate) )[0,1,2,3,4,5]; 
31 $smon++;
32 $syear += 1900;
33
34 # Get the current month
35 my ($fsec,$fmin,$fhour,$fmday,$fmon,$fyear) =
36         (localtime($_finishdate) )[0,1,2,3,4,5]; 
37 $fmon++;
38 $fyear += 1900;
39
40 # Login to the database
41 my $user = shift or die &usage;
42 adminsuidsetup $user;
43
44 # Get the needed configuration files
45 my $conf = new FS::Conf;
46 my $lpr = $conf->config('lpr');
47 my $email = $conf->config('email');
48 my $smtpmachine = $conf->config('smtpmachine');
49 my $mail_sender = $conf->exists('invoice_from') ? $conf->config('invoice_from') :
50   'postmaster';
51 my @report_template = $conf->config('report_template')
52   or die "cannot load config file report_template";
53 $report_lines = 0;
54 foreach ( grep /report_lines\(\d+\)/, @report_template ) { #kludgy :/
55   /report_lines\((\d+)\)/;
56   $report_lines += $1;
57 }
58 die "no report_lines() functions in template?" unless $report_lines;
59 $report_template = new Text::Template (
60   TYPE   => 'ARRAY',
61   SOURCE => [ map "$_\n", @report_template ],
62 ) or die "can't create new Text::Template object: $Text::Template::ERROR";
63
64
65 my(@cust_bills)=qsearch('cust_bill',{});
66 if (scalar(@cust_bills) == 0)
67 {
68         exit 1;
69 }
70
71 # Open print and email pipes
72 # $lpr and opt_p for printing
73 # $email and opt_m for email
74
75 if ($lpr && $main::opt_p)
76 {
77         open(LPR, "|$lpr");
78 }
79
80 if ($email && $main::opt_m)
81 {
82   $ENV{MAILADDRESS} = $mail_sender;
83   $header = new Mail::Header ( [
84     "From: Account Processor",
85     "To: $email",
86     "Sender: $mail_sender",
87     "Reply-To: $mail_sender",
88     "Subject: Sales Taxes Invoiced",
89   ] );
90 }
91
92 my $comped = 0;
93 my $comped_tax = 0;
94 my $other = 0;
95 my $other_tax = 0;
96 my $total = 0;
97 my $taxed = 0;
98 my $untaxed = 0;
99 my $total_tax = 0;
100
101 # Now I can start looping
102 foreach my $cust_bill (@cust_bills)
103 {
104         my $_date = $cust_bill->getfield('_date');
105         my $invnum = $cust_bill->getfield('invnum');
106         my $charged = $cust_bill->getfield('charged');
107
108         if ($_date >= $_startdate && $_date <= $_finishdate) {
109                 $total += $charged;
110
111                 # The following lines were used to produce rather verbose reports
112                 #my ($sec,$min,$hour,$mday,$mon,$year) =
113                 #       (localtime($_date) )[0,1,2,3,4,5]; 
114                 #$mon++;
115                 #$year -= 100 if $year >= 100;
116                 #$year = "0" . $year if $year < 10;
117
118                 my $invoice_amt =0;
119                 my $invoice_tax =0;
120                 my $invoice_comped =0;
121                 my(@cust_bill_pkgs)= $cust_bill->cust_bill_pkg;
122                 foreach my $cust_bill_pkg (@cust_bill_pkgs) {
123
124                         my $recur = $cust_bill_pkg->getfield('recur');
125                         my $setup = $cust_bill_pkg->getfield('setup');
126                         my $pkgnum = $cust_bill_pkg->getfield('pkgnum');
127                         
128                         if ($pkgnum == 0) {
129                                 # The following line was used to produce rather verbose reports
130                                 # push @buf, ('', sprintf(qq{%10s%15s%14.2f}, "$mon/$mday/$year", "Tax $invnum", $recur+$setup));
131                                 $invoice_tax += $recur;
132                                 $invoice_tax += $setup;
133                         } else {
134                                 # The following line was used to produce rather verbose reports
135                                 # push @buf, ('', sprintf(qq{%10s%15s%14.2f}, "$mon/$mday/$year", "Inv $invnum", $recur+$setup));
136                                 $invoice_amt += $recur;
137                                 $invoice_amt += $setup;
138                         }
139
140                 }
141
142                 my(@cust_bill_pays)= $cust_bill->cust_bill_pay;
143                 foreach my $cust_bill_pay (@cust_bill_pays) {
144                         my $payby = $cust_bill_pay->cust_pay->payby;
145                         my $paid = $cust_bill_pay->getfield('amount');
146                         if ($payby =~ 'COMP') {
147                                 $invoice_comped += $paid;
148                         }
149                 }
150
151                 if (abs($invoice_comped - ($invoice_amt + $invoice_tax)) < 0.0001){
152                         $comped += $invoice_amt;
153                         $comped_tax += $invoice_tax;
154                 } elsif ($invoice_comped > 0) {
155                         push @buf, sprintf(qq{\nInvoice %10d has inexpliciable complimentary payments of %14.9f\n}, $invnum, $invoice_comped);
156                         $other += $invoice_amt;
157                         $other_tax += $invoice_tax;
158                 } elsif ($invoice_tax > 0) {
159                         $total_tax += $invoice_tax;
160                         $taxed += $invoice_amt;
161                 } else {
162                         $untaxed += $invoice_amt;
163                 }
164
165         }
166
167 }
168
169 push @buf, ('', sprintf(qq{%25s%14.2f}, "Complimentary", $comped));
170 push @buf, sprintf(qq{%25s%14.2f}, "Complimentary Tax", $comped_tax);
171 push @buf, sprintf(qq{%25s%14.2f}, "Other", $other);
172 push @buf, sprintf(qq{%25s%14.2f}, "Other Tax", $other_tax);
173 push @buf, sprintf(qq{%25s%14.2f}, "Untaxed", $untaxed);
174 push @buf, sprintf(qq{%25s%14.2f}, "Taxed", $taxed);
175 push @buf, sprintf(qq{%25s%14.2f}, "Tax", $total_tax);
176 push @buf, ('', sprintf(qq{%39s}, "========="), sprintf(qq{%39.2f}, $total));
177
178 sub FS::tax_report::_template::report_lines {
179   my $lines = shift;
180   map {
181     scalar(@buf) ? shift @buf : '' ;
182   }
183   ( 1 .. $lines );
184 }
185
186 $FS::tax_report::_template::title = qq~SALES TAXES INVOICED for $smon/$smday/$syear through $fmon/$fmday/$fyear~;
187 $FS::tax_report::_template::title = $opt_t if $opt_t;
188 $FS::tax_report::_template::page = 1;
189 $FS::tax_report::_template::date = $^T;
190 $FS::tax_report::_template::date = $^T;
191 $FS::tax_report::_template::fdate = $_finishdate;
192 $FS::tax_report::_template::fdate = $_finishdate;
193 $FS::tax_report::_template::sdate = $_startdate;
194 $FS::tax_report::_template::sdate = $_startdate;
195 $FS::tax_report::_template::total_pages = 
196   int( scalar(@buf) / $report_lines);
197 $FS::tax_report::_template::total_pages++ if scalar(@buf) % $report_lines;
198
199 my @report;
200 while (@buf) {
201   push @report, split("\n", 
202     $report_template->fill_in( PACKAGE => 'FS::tax_report::_template' )
203   );
204   $FS::tax_report::_template::page++;
205 }
206
207 if ($opt_v) {
208   print map "$_\n", @report;
209 }
210 if($lpr && $opt_p)
211 {
212   print LPR map "$_\n", @report;
213   print LPR "\f" if $opt_e;
214   close LPR || die "Could not close printer: $lpr\n";
215 }
216 if($email && $opt_m)
217 {
218   my $message = new Mail::Internet (
219     'Header' => $header,
220     'Body' => [ (@report) ],
221   );
222   $!=0;
223   $message->smtpsend( Host => "$smtpmachine" )
224     or die "can't send report to $email via $smtpmachine: $!";
225 }
226
227
228 # subroutines
229 sub untaint_argv {
230   foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
231     $ARGV[$_] =~ /^([\w\-\/ :\.]*)$/ || die "Illegal argument \"$ARGV[$_]\"";
232     $ARGV[$_]=$1;
233   }
234 }
235
236 sub usage {
237   die "Usage:\n\n  freeside-tax-report [-v] [-p] [-e] user\n";
238 }
239
240 =head1 NAME
241
242 freeside-tax-report - Prints or emails sales taxes invoiced in a given period.
243
244 =head1 SYNOPSIS
245
246   freeside-tax-report [-v] [-p] [-m] [-e] [-t "title"] [-s date] [-f date] user
247
248 =head1 DESCRIPTION
249
250 Prints or emails sales taxes invoiced in a given period.
251
252 -v: Verbose - Prints records to STDOUT.
253
254 -p: Print to printer lpr as found in the conf directory.
255
256 -m: Email output to user found in the Conf email file.
257
258 -e: Print a final form feed to the printer.
259
260 -t: supply a title for the top of each page.
261
262 -s: starting date for inclusion
263
264 -f: final date for inclusion
265
266 user: From the mapsecrets file - see config.html from the base documentation
267
268 =head1 VERSION
269
270 $Id: freeside-tax-report,v 1.5 2002-09-09 22:57:34 ivan Exp $
271
272 =head1 BUGS
273
274 Yes..... Use at your own risk. No guarantees or warrantees of any
275 kind apply to this program. Parts of this program are hacked from
276 other GNU licensed software created mainly by Ivan Kohler.
277
278 This is released under the GNU Public License. See www.gnu.org
279 for more information regarding this license.
280
281 =head1 SEE ALSO
282
283 L<FS::cust_main>, config.html from the base documentation
284
285 =head1 AUTHOR
286
287 Jeff Finucane <jeff@cmh.net>
288
289 based on print-batch by Joel Griffiths <griff@aver-computer.com>
290
291 =cut
292