historical (immutable) invoice details about services and other history infrastructure
[freeside.git] / httemplate / view / cust_main / packages.html
1 <%
2   my( $cust_main ) = @_;
3   my $conf = new FS::Conf;
4
5   my $packages = get_packages($cust_main, $conf);
6 %>
7
8 <A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A>
9
10 <%= include('order_pkg.html', $cust_main ) %>
11
12 <% if ( $conf->config('payby-default') ne 'HIDE' ) { %>
13   <%= include('quick-charge.html', $cust_main ) %>
14 <% } %>
15
16 <A HREF="<%= $p %>edit/cust_pkg.cgi?<%= $cust_main->custnum %>">Bulk order and cancel packages</A> (preserves services)
17 <BR><BR>
18
19 <% if ( @$packages ) { %>
20 Current packages
21 <% } %>
22
23 <% if ( $cust_main->num_cancelled_pkgs ) {
24      if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
25           || ( $conf->exists('hidecancelledpackages')
26                && ! $cgi->param('showcancelledpackages')
27              )
28         )
29      {
30        $cgi->param('showcancelledpackages', 1);
31 %>
32   ( <a href="<%= $cgi->self_url %>">show
33 <%   } else {
34        $cgi->param('showcancelledpackages', 0);
35 %>
36   ( <a href="<%= $cgi->self_url %>">hide
37 <%   } %>
38  cancelled packages</a> )
39 <% } %>
40
41 <% if ( @$packages ) { %>
42
43 <TABLE CLASS="package" BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
44 <TR>
45   <TH>Package</TH>
46   <TH>Status</TH>
47   <TH COLSPAN=2>Services</TH>
48 </TR>
49
50 <%
51 foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) {
52   my $rowspan = 0;
53
54   if ($pkg->{cancel}) {
55     $rowspan = 0;
56   } else {
57     foreach my $svcpart (@{$pkg->{svcparts}}) {
58       $rowspan += $svcpart->{count};
59       $rowspan++ if ($svcpart->{count} < $svcpart->{quantity});
60     }
61   } 
62 %>
63
64 <!--pkgnum: <%=$pkg->{pkgnum}%>-->
65 <TR>
66   <TD ROWSPAN=<%=$rowspan%>>
67     <A NAME="cust_pkg<%=$pkg->{pkgnum}%>"><%=$pkg->{pkgnum}%></A>:
68     <%=$pkg->{pkg}%> - <%=$pkg->{comment}%><BR>
69 <% unless ($pkg->{cancel}) { %>
70     (&nbsp;<%=pkg_change_link($pkg)%>&nbsp;)
71     (&nbsp;<%=pkg_dates_link($pkg)%>&nbsp;|&nbsp;<%=pkg_customize_link($pkg,$cust_main->custnum)%>&nbsp;)
72 <% } %>
73   </TD>
74 <%
75   #foreach (qw(setup last_bill next_bill susp expire cancel)) {
76   #  print qq!  <TD ROWSPAN=$rowspan>! . pkg_datestr($pkg,$_,$conf) . qq!</TD>\n!;
77   #}
78   print "<TD ROWSPAN=$rowspan>". &itable('');
79
80   sub myfreq {
81     my $part_pkg = shift;
82     my $freq = $part_pkg->freq_pretty;
83     $freq =~ s/ /&nbsp;/g;
84     $freq;
85   }
86
87   if ( $pkg->{cancel} ) { #status: cancelled
88
89     print '<TR><TD><FONT COLOR="#ff0000"><B>Cancelled&nbsp;</B></FONT></TD>'.
90           '<TD>'. pkg_datestr($pkg,'cancel',$conf). '</TD></TR>';
91     unless ( $pkg->{setup} ) {
92       print '<TR><TD COLSPAN=2>Never billed</TD></TR>';
93     } else {
94       print "<TR><TD>Setup&nbsp;</TD><TD>".
95             pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
96       print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
97             pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
98         if $pkg->{'last_bill'};
99       print "<TR><TD>Suspended&nbsp;</TD><TD>".
100             pkg_datestr($pkg, 'susp',$conf). '</TD></TR>'
101         if $pkg->{'susp'};
102     }
103
104   } else {
105
106     if ( $pkg->{susp} ) { #status: suspended
107       print '<TR><TD><FONT COLOR="#FF9900"><B>Suspended</B>&nbsp;</FONT></TD>'.
108             '<TD>'. pkg_datestr($pkg,'susp',$conf). '</TD></TR>';
109       unless ( $pkg->{setup} ) {
110         print '<TR><TD COLSPAN=2>Never billed</TD></TR>';
111       } else {
112         print "<TR><TD>Setup&nbsp;</TD><TD>". 
113               pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
114       }
115       print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
116             pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
117         if $pkg->{'last_bill'};
118       # next bill ??
119       print "<TR><TD>Expires&nbsp;</TD><TD>".
120             pkg_datestr($pkg, 'expire',$conf). '</TD></TR>'
121         if $pkg->{'expire'};
122       print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_unsuspend_link($pkg).
123             '&nbsp;|&nbsp;'. pkg_cancel_link($pkg). '&nbsp;)</TD></TR>';
124
125     } else { #status: active
126
127       unless ( $pkg->{setup} ) { #not setup
128
129         print '<TR><TD COLSPAN=2>Not&nbsp;yet&nbsp;billed&nbsp;(';
130         unless ( $pkg->{freq} ) {
131           print 'one-time&nbsp;charge)</TD></TR>';
132           print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_cancel_link($pkg).
133                 '&nbsp;)</TD</TR>';
134         } else {
135           print 'billed&nbsp;'. myfreq($pkg->{part_pkg}). ')</TD></TR>';
136         }
137
138       } else { #setup
139
140         unless ( $pkg->{freq} ) {
141           print "<TR><TD COLSPAN=2>One-time&nbsp;charge</TD></TR>".
142                 '<TR><TD>Billed&nbsp;</TD><TD>'.
143                 pkg_datestr($pkg,'setup',$conf). '</TD></TR>';
144         } else {
145           print '<TR><TD COLSPAN=2><FONT COLOR="#00CC00"><B>Active</B></FONT>'.
146                 ',&nbsp;billed&nbsp;'. myfreq($pkg->{part_pkg}). '</TD></TR>'.
147                 '<TR><TD>Setup&nbsp;</TD><TD>'.
148                 pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
149         }
150
151       }
152
153       print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
154             pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
155         if $pkg->{'last_bill'};
156       print "<TR><TD>Next&nbsp;bill&nbsp;</TD><TD>".
157             pkg_datestr($pkg, 'next_bill',$conf). '</TD></TR>'
158         if $pkg->{'next_bill'};
159       print "<TR><TD>Expires&nbsp;</TD><TD>".
160             pkg_datestr($pkg, 'expire',$conf). '</TD></TR>'
161         if $pkg->{'expire'};
162       if ( $pkg->{freq} ) {
163         print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_suspend_link($pkg).
164               '&nbsp;|&nbsp;'. pkg_cancel_link($pkg). '&nbsp;)</TD></TR>';
165       }
166
167     }
168
169   }
170
171   print "</TABLE></TD>\n";
172
173   if ($rowspan == 0) { print qq!</TR>\n!; next; }
174
175   my $cnt = 0;
176   foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
177     foreach my $service (@{$svcpart->{services}}) {
178       print '<TR>' if ($cnt > 0);
179 %>
180   <TD><%=svc_link($svcpart,$service)%></TD>
181   <TD><%=svc_label_link($svcpart,$service)%><BR>(&nbsp;<%=svc_unprovision_link($service)%>&nbsp;)</TD>
182 </TR>
183 <%
184       $cnt++;
185     }
186     if ($svcpart->{count} < $svcpart->{quantity}) {
187       print qq!<TR>\n! if ($cnt > 0);
188       print qq!  <TD COLSPAN=2>!.svc_provision_link($pkg, $svcpart, $conf).qq!</TD>\n</TR>\n!;
189     }
190   }
191 }
192 #end display packages
193 %>
194
195 </TABLE>
196 <% } else { %>
197 <BR>
198 <% } %>
199
200 <%
201 #subroutines
202
203 sub get_packages {
204   my $cust_main = shift or return undef;
205   my $conf = shift;
206   
207   my @packages = ();
208   my $method;
209   if (  $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
210      || ( $conf->exists('hidecancelledpackages')
211            && ! $cgi->param('showcancelledpackages') )
212      )
213   {
214     $method = 'ncancelled_pkgs';
215   } else {
216     $method = 'all_pkgs';
217   }
218   
219   foreach my $cust_pkg ( $cust_main->$method() ) {
220   
221     my $part_pkg = $cust_pkg->part_pkg;
222
223     my %pkg = ();
224
225     #to get back to the original object... should use it in the first place!!
226     $pkg{cust_pkg} = $cust_pkg;
227     $pkg{part_pkg} = $part_pkg;
228
229     $pkg{pkgnum} = $cust_pkg->pkgnum;
230     $pkg{pkg} = $part_pkg->pkg;
231     $pkg{pkgpart} = $part_pkg->pkgpart;
232     $pkg{comment} = $part_pkg->getfield('comment');
233     $pkg{freq} = $part_pkg->freq;
234     $pkg{setup} = $cust_pkg->getfield('setup');
235     $pkg{last_bill} = $cust_pkg->getfield('last_bill');
236     $pkg{next_bill} = $cust_pkg->getfield('bill');
237     $pkg{susp} = $cust_pkg->getfield('susp');
238     $pkg{expire} = $cust_pkg->getfield('expire');
239     $pkg{cancel} = $cust_pkg->getfield('cancel');
240
241   
242     my %svcparts = map {
243       $_->svcpart => {
244                        $_->part_svc->hash,
245                        'quantity' => $_->quantity,
246                        'count'    => $cust_pkg->num_cust_svc($_->svcpart),
247                        #'services' => [],
248                      };
249     } $part_pkg->pkg_svc;
250
251     foreach my $cust_svc ( $cust_pkg->cust_svc ) {
252       #warn "svcnum ". $cust_svc->svcnum. " / svcpart ". $cust_svc->svcpart. "\n";
253       my $svc = {
254         'svcnum' => $cust_svc->svcnum,
255         'label'  => ($cust_svc->label)[1],
256       };
257
258       #false laziness with above, to catch extraneous services.  whole
259       #damn thing should be OO...
260       my $svcpart = ( $svcparts{$cust_svc->svcpart} ||= {
261         $cust_svc->part_svc->hash,
262         'quantity' => 0,
263         'count'    => $cust_pkg->num_cust_svc($cust_svc->svcpart),
264         #'services' => [],
265       } );
266
267       push @{$svcpart->{services}}, $svc;
268
269     }
270
271     $pkg{svcparts} = [ values %svcparts ];
272
273     push @packages, \%pkg;
274   
275   }
276   
277   return \@packages;
278
279 }
280
281 sub svc_link {
282
283   my ($svcpart, $svc) = (shift,shift) or return '';
284   return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!;
285
286 }
287
288 sub svc_label_link {
289
290   my ($svcpart, $svc) = (shift,shift) or return '';
291   return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!;
292
293 }
294
295 sub svc_provision_link {
296   my ($pkg, $svcpart, $conf) = @_;
297   ( my $svc_nbsp = $svcpart->{svc} ) =~ s/\s+/&nbsp;/g;
298   my $num_left = $svcpart->{quantity} - $svcpart->{count};
299   my $pkgnum_svcpart = "pkgnum$pkg->{pkgnum}-svcpart$svcpart->{svcpart}";
300
301   my $url;
302   if ( $svcpart->{svcdb} eq 'svc_external'
303        && $conf->exists('svc_external-skip_manual')
304   ) {
305     $url = "${p}edit/process/$svcpart->{svcdb}.cgi?".
306            "pkgnum=$pkg->{pkgnum}&".
307            "svcpart=$svcpart->{svcpart}";
308   } else {
309     $url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart";
310   }
311
312   my $link = qq!<A CLASS="provision" HREF="$url">!.
313              "Provision&nbsp;$svc_nbsp&nbsp;($num_left)</A>";
314   if ( $conf->exists('legacy_link') ) {
315     $link .= '<BR>'.
316              qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!.
317              qq!$pkgnum_svcpart">!.
318             "Link&nbsp;to&nbsp;legacy&nbsp;$svc_nbsp&nbsp;($num_left)</A>";
319   }
320   $link;
321 }
322
323 sub svc_unprovision_link {
324   my $svc = shift or return '';
325   qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?$svc->{svcnum}',!.
326   qq!'Permanently unprovision and delete this service?')">Unprovision</A>!;
327 }
328
329 # This should be generalized to use config options to determine order.
330 sub pkgsort_pkgnum_cancel {
331   if ($a->{cancel} and $b->{cancel}) {
332     return ($a->{pkgnum} <=> $b->{pkgnum});
333   } elsif ($a->{cancel} or $b->{cancel}) {
334     return (-1) if ($b->{cancel});
335     return (1) if ($a->{cancel});
336     return (0);
337   } else {
338     return($a->{pkgnum} <=> $b->{pkgnum});
339   }
340 }
341
342 sub pkg_datestr {
343   my($pkg, $field, $conf) = @_ or return '';
344   return '&nbsp;' unless $pkg->{$field};
345   my $format = $conf->exists('pkg_showtimes')
346                ? '<B>%D</B>&nbsp;<FONT SIZE=-3>%l:%M:%S%P&nbsp;%z</FONT>'
347                : '<B>%b&nbsp;%o,&nbsp;%Y</B>';
348   ( my $strip = time2str($format, $pkg->{$field}) ) =~ s/ (\d)/$1/g;
349   $strip;
350 }
351
352 sub pkg_change_link {
353   my $pkg = shift or return '';
354   return qq!<a href="${p}misc/change_pkg.cgi?$pkg->{pkgnum}">!.
355          qq!Change&nbsp;package</a>!;
356 }
357
358 sub pkg_suspend_link {
359   my $pkg = shift or return '';
360   return qq!<a href="${p}misc/susp_pkg.cgi?$pkg->{pkgnum}">Suspend</a>!;
361 }
362
363 sub pkg_unsuspend_link {
364   my $pkg = shift or return '';
365   return qq!<a href="${p}misc/unsusp_pkg.cgi?$pkg->{pkgnum}">Unsuspend</a>!;
366 }
367
368 sub pkg_cancel_link {
369   my $pkg = shift or return '';
370   qq!<A HREF="javascript:areyousure('${p}misc/cancel_pkg.cgi?$pkg->{pkgnum}', !.
371   qq!'Permanently delete included services and cancel this package?')">!.
372   qq!Cancel now</A> | !.
373   qq!<A HREF="${p}misc/expire_pkg.cgi?$pkg->{pkgnum}">Cancel later</A>!;
374 }
375
376 sub pkg_dates_link {
377   my $pkg = shift or return '';
378   qq!<A HREF="${p}edit/REAL_cust_pkg.cgi?$pkg->{pkgnum}">Edit&nbsp;dates</A>!;
379 }
380
381 sub pkg_customize_link {
382   my $pkg = shift or return '';
383   my $custnum = shift;
384   qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};!.
385   qq!pkgnum=$pkg->{pkgnum}">Customize</A>!;
386 }
387
388 %>