Merge branch 'patch-19' of https://github.com/gjones2/Freeside
[freeside.git] / httemplate / search / customer_accounting_summary.html
1 % if ( $cgi->param('_type') =~ /(xls)$/ ) {
2 <%perl>
3   # egregious false laziness w/ search/report_tax-xls.cgi
4   my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format;
5   my $filename = $cgi->url(-relative => 1);
6   $filename =~ s/\.html$//;
7   $filename .= $format->{extension};
8   http_header('Content-Type' => $format->{mime_type});
9   http_header('Content-Disposition' => qq!attachment;filename="$filename"!);
10
11   my $output = '';
12   my $XLS = IO::String->new($output);
13   my $workbook = $format->{class}->new($XLS)
14     or die "Error opening .xls file: $!";
15
16   my $worksheet = $workbook->add_worksheet('Summary');
17
18   my %format = (
19     header => {
20       size      => 11,
21       bold      => 1,
22       align     => 'center',
23       valign    => 'vcenter',
24       text_wrap => 1,
25     },
26     money => {
27       size      => 11,
28       align     => 'right',
29       valign    => 'bottom',
30       num_format=> 8,
31     },
32     '' => {},
33   );
34   my %default = (
35       font      => 'Calibri',
36       border    => 1,
37   );
38   foreach (keys %format) {
39     my %f = (%default, %{$format{$_}});
40     $format{$_} = $workbook->add_format(%f);
41     $format{"m_$_"} = $workbook->add_format(%f);
42   }
43
44   my ($r, $c) = (0, 0);
45   for my $row (@rows) {
46     $c = 0;
47     my $thisrow = shift @cells;
48     for my $cell (@$thisrow) {
49       if (!ref($cell)) {
50         # placeholder, so increment $c so that we write to the correct place
51         $c++;
52         next;
53       }
54       # format name
55       my $f = '';
56       $f = 'header' if $row->{header} or $cell->{header};
57       $f = 'money' if $cell->{format} eq 'money';
58       if ( $cell->{rowspan} > 1 or $cell->{colspan} > 1 ) {
59         my $range = xl_range_formula(
60           'Summary',
61           $r, $r - 1 + ($cell->{rowspan} || 1),
62           $c, $c - 1 + ($cell->{colspan} || 1)
63         );
64         #warn "merging $range\n";
65         $worksheet->merge_range($range, $cell->{value}, $format{"m_$f"});
66       } else {
67       #warn "writing ".xl_rowcol_to_cell($r, $c)."\n";
68         $worksheet->write( $r, $c, $cell->{value}, $format{$f} );
69       }
70       $c++;
71     } #$cell
72   $r++;
73   } #$row
74   $workbook->close;
75
76   http_header('Content-Length' => length($output));
77   $m->print($output);
78 </%perl>
79 % } else {
80 <& /elements/header.html, $title &>
81 % my $myself = $cgi->self_url;
82 <P ALIGN="right" CLASS="noprint">
83 Download full reports<BR>
84 as <A HREF="<% "$myself;_type=xls" %>">Excel spreadsheet</A><BR>
85 % # as <A HREF="<% "$myself;_type=csv" %>">CSV file</A> # is this still needed?
86 </P>
87 <style type="text/css">
88 .report * {
89   background-color: #f8f8f8;
90   border: 1px solid black;
91   padding: 2px;
92 }
93 .report td {
94   text-align: right;
95 }
96 .total * { background-color: #f5f6be; }
97 .shaded * { background-color: #c8c8c8; }
98 .totalshaded * { background-color: #bfc094; }
99 </style>
100 <table class="report" width="100%" cellspacing=0>
101 % foreach my $rowinfo (@rows) {
102   <tr<% $rowinfo->{class} ? ' class="'.$rowinfo->{class}.'"' : ''%>>
103 %   my $thisrow = shift @cells;
104 %   foreach my $cell (@$thisrow) {
105 %     next if !ref($cell); # placeholders
106 %     my $td = $cell->{header} ? 'th' : 'td';
107 %     my $style = '';
108 %     $style .= " rowspan=".$cell->{rowspan} if $cell->{rowspan} > 1;
109 %     $style .= " colspan=".$cell->{colspan} if $cell->{colspan} > 1;
110       <<%$td%><%$style%>><% $cell->{value} |h %></<%$td%>>
111 %   }
112   </tr>
113 % }
114 </table>
115
116 <& /elements/footer.html &>
117 % }
118 <%init>
119
120 die "access denied"
121   unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
122
123 my ($agentnum,$sel_agent);
124 if ( $cgi->param('agentnum') eq 'all' ) {
125   $agentnum = 0;
126 }
127 elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
128   $agentnum = $1;
129   $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } );
130   die "agentnum $agentnum not found!" unless $sel_agent;
131 }
132 my $title = $sel_agent ? $sel_agent->agent.' ' : '';
133
134 my ($refnum,$sel_part_referral);
135 if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
136   $refnum = $1;
137   $sel_part_referral = qsearchs('part_referral', { 'refnum' => $refnum } );
138   die "refnum $refnum not found!" unless $sel_part_referral;
139 }
140 $title .=  $sel_part_referral->referral.' '
141   if $sel_part_referral;
142
143 $title .= 'Customer Accounting Summary Report';
144
145 my @items  = ('netsales', 'cashflow');
146 my @params = ( [], [] );
147 my $setuprecur = '';
148 if ( $cgi->param('setuprecur') ) {
149   $setuprecur = 1;
150   # instead of 'cashflow' (payments - refunds), use 'receipts'
151   # (applied payments), because it's divisible into setup and recur.
152   @items = ('netsales', 'receipts', 'netsales', 'receipts');
153   @params = ( 
154     [ setuprecur => 'setup' ],
155     [ setuprecur => 'setup' ],
156     [ setuprecur => 'recur' ],
157     [ setuprecur => 'recur' ],
158   );
159 }
160 my @labels = ();
161 my @cross_params = ();
162 my @custnames = ();
163
164 my $status = $cgi->param('status');
165 die "invalid status" unless $status =~ /^\w+|$/;
166
167 my %search_hash;
168 foreach (qw(agentnum refnum status)) {
169   if ( defined $cgi->param($_) ) {
170     $search_hash{$_} = $cgi->param($_);
171   }
172 }
173 $search_hash{'classnum'} = [ $cgi->param('cust_classnum') ] 
174   if grep { $_ eq 'cust_classnum' } $cgi->param;
175
176 my $query = FS::cust_main::Search->search(\%search_hash);
177 my @custs = qsearch($query);
178
179 foreach my $cust_main ( @custs ) {
180   # XXX should do this in the qsearch
181   next unless ($status eq '' || $status eq $cust_main->status); 
182   next unless ($agentnum == 0 || $cust_main->agentnum eq $agentnum);
183   next unless ($refnum   == 0 || $cust_main->refnum eq $refnum);
184
185   push @custnames, $cust_main->name;
186
187   push @cross_params, [ ('custnum' => $cust_main->custnum) ];
188 }
189
190 my %opt = (
191   items         => \@items,
192   params        => \@params,
193   cross_params  => \@cross_params,
194   agentnum      => $agentnum,
195   refnum        => $refnum,
196 );
197 for ( qw(start_month start_year end_month end_year) ) {
198   if ( $cgi->param($_) =~ /^(\d+)$/ ) {
199     $opt{$_} = $1;
200   }
201 }
202
203 warn Dumper(OPTIONS => \%opt) if $cgi->param('debug');
204 my $report = FS::Report::Table::Monthly->new(%opt);
205 my $data = $report->data;
206 warn Dumper(DATA => $data) if $cgi->param('debug') >= 2;
207
208 my @total;
209
210 my @rows; # hashes of row info
211 my @cells; # arrayrefs of cell info
212 # We use Excel currency format, but not Excel dates, because
213 # these are whole months and there's no nice way to express that.
214 # This is the historical behavior for monthly reports.
215
216 # header row
217 $rows[0] = {};
218 $cells[0] = [
219   { header => 1, rowspan => 2, colspan => ($setuprecur ? 2 : 1) },
220   ($setuprecur ? '' : ()),
221   map {
222     { header => 1, colspan => 2, value => time2str('%b %Y', $_) },
223     ''
224   } @{ $data->{speriod} }
225 ];
226 my $ncols = scalar(@{ $data->{speriod} });
227
228 $rows[1] = {};
229 $cells[1] = [ '',
230   ($setuprecur ? '' : ()),
231   map { 
232   ( { header => 1, value => mt('Billed') },
233     { header => 1, value => mt('Paid') }
234   ) } (1..$ncols)
235 ];
236
237 # use PDL; # ha ha, I just might.
238 my $row = 0;
239 foreach my $name (@custnames) { # correspond to cross_params
240   my $skip = 1; # skip the customer iff ALL of their values are zero
241   for my $subrow (0..($setuprecur ? 1 : 0)) { # the setup/recur axis
242     push @rows, { class => $subrow ? 'shaded' : '' };
243     my @thisrow;
244     if ( $subrow == 0 ) {
245       # customer name
246       push @thisrow,
247         { value => $name,
248           header => 1,
249           rowspan => ($setuprecur ? 2 : 1) };
250     } else {
251       push @thisrow, '';
252     }
253     if ( $setuprecur ) {
254       # subheading
255       push @thisrow,
256         { value => $subrow ? mt('recurring') : mt('setup'),
257           header => 1 };
258     }
259     for my $col (0..$ncols-1) { # the month
260       for my $subcol (0..1) { # the billed/paid axis
261         my $item = $subrow * 2 + $subcol;
262         my $value = $data->{data}[$item][$col][$row];
263         $skip = 0 if abs($value) > 0.005;
264         push @thisrow, { value => sprintf('%0.2f', $value), format => 'money' };
265         $total[( ($ncols * $subrow) + $col ) * 2 + $subcol] += $value;
266       } #subcol
267     } #col
268     push @cells, \@thisrow;
269   } #subrow
270   if ( $skip ) {
271     # all values are zero--remove the rows we just added
272     pop @rows;
273     pop @cells;
274     if ( $setuprecur ) {
275       pop @rows;
276       pop @cells;
277     }
278   }
279   $row++;
280 }
281 for my $subrow (0..($setuprecur ? 1 : 0)) {
282   push @rows, { class => ($subrow ? 'totalshaded' : 'total') };
283   my @thisrow;
284   if ( $subrow == 0 ) {
285     push @thisrow,
286       { value => mt('Total'),
287         header => 1,
288         rowspan => ($setuprecur ? 2 : 1), };
289   } else {
290     push @thisrow, '';
291   }
292   if ( $setuprecur ) {
293     push @thisrow,
294       { value => $subrow ? mt('recurring') : mt('setup'),
295         header => 1 };
296   }
297   for my $col (0..($ncols * 2)-1) { # month and billed/paid axis
298     my $value = $total[($subrow * $ncols * 2) + $col];
299     push @thisrow, { value => sprintf('%0.2f', $value), format => 'money' };
300   }
301   push @cells, \@thisrow;
302 } #subrow
303
304 if ( $cgi->param('debug') >= 3 ) {
305   warn Dumper(\@rows, \@cells);
306 }
307
308 my $title = 'Customer Accounting Summary';
309 </%init>