fix ACL name for service provisioning and prevent disabled service provisioning from...
[freeside.git] / httemplate / view / cust_main / packages.html
1 <%
2   my( $cust_main ) = @_;
3   my $conf = new FS::Conf;
4
5   my $curuser = $FS::CurrentUser::CurrentUser;
6
7   my $packages = get_packages($cust_main, $conf);
8 %>
9
10 <STYLE TYPE="text/css">
11 .package .provision { font-weight: bold }
12 </STYLE>
13
14 <A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A>
15
16 <% if ( $curuser->access_right('Order customer package') ) { %>
17   <%= include('order_pkg.html', $cust_main ) %>
18 <% } %>
19
20 <% if ( $curuser->access_right('One-time charge')
21         && $conf->config('payby-default') ne 'HIDE'
22       ) {
23 %>
24   <%= include('quick-charge.html', $cust_main ) %>
25 <% } %>
26
27 <% if ( $curuser->access_right('Bulk change customer packages') ) { %>
28   <A HREF="<%= $p %>edit/cust_pkg.cgi?<%= $cust_main->custnum %>">Bulk order and cancel packages</A> (preserves services)
29 <% } %>
30
31 <BR><BR>
32
33 <% if ( @$packages ) { %>
34 Current packages
35 <% } %>
36
37 <% if ( $cust_main->num_cancelled_pkgs ) {
38      if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
39           || ( $conf->exists('hidecancelledpackages')
40                && ! $cgi->param('showcancelledpackages')
41              )
42         )
43      {
44        $cgi->param('showcancelledpackages', 1);
45 %>
46   ( <a href="<%= $cgi->self_url %>">show
47 <%   } else {
48        $cgi->param('showcancelledpackages', 0);
49 %>
50   ( <a href="<%= $cgi->self_url %>">hide
51 <%   } %>
52  cancelled packages</a> )
53 <% } %>
54
55 <% if ( @$packages ) { %>
56
57 <TABLE CLASS="package" BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
58 <TR>
59   <TH>Package</TH>
60   <TH>Status</TH>
61   <TH COLSPAN=2>Services</TH>
62 </TR>
63
64 <%
65 foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) {
66   my $rowspan = 0;
67
68   if ($pkg->{cancel}) {
69     $rowspan = 0;
70   } else {
71     foreach my $svcpart (@{$pkg->{svcparts}}) {
72       $rowspan += $svcpart->{count};
73       $rowspan++ if ($svcpart->{count} < $svcpart->{quantity});
74     }
75   } 
76 %>
77
78 <!--pkgnum: <%=$pkg->{pkgnum}%>-->
79 <TR>
80   <TD ROWSPAN=<%= $rowspan || 1 %>>
81     <A NAME="cust_pkg<%=$pkg->{pkgnum}%>"><%=$pkg->{pkgnum}%></A>:
82     <%=$pkg->{pkg}%> - <%=$pkg->{comment}%><BR>
83     <FONT SIZE=-1>
84       <% unless ( $pkg->{cancel} ) { %>
85         <% if ( $curuser->access_right('Change customer package') ) { %>
86             (&nbsp;<%=pkg_change_link($pkg)%>&nbsp;)
87         <% } %>
88         <% if ( $curuser->access_right('Edit customer package dates') ) { %>
89             (&nbsp;<%=pkg_dates_link($pkg)%>&nbsp;)
90         <% } %>
91         <% if ( $curuser->access_right('Customize customer package') ) { %>
92             (&nbsp;<%=pkg_customize_link($pkg,$cust_main->custnum)%>&nbsp;)
93         <% } %>
94       <% } %>
95     </FONT>
96   </TD>
97   <TD ROWSPAN=<%= $rowspan || 1 %>>
98     <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
99
100 <%
101   sub myfreq {
102     my $part_pkg = shift;
103     my $freq = $part_pkg->freq_pretty;
104     $freq =~ s/ /&nbsp;/g;
105     $freq;
106   }
107
108   #this should use cust_pkg->status and cust_pkg->statuscolor eventually
109
110   my $colspan = $conf->exists('cust_pkg-display_times') ? 8 : 4;
111   my $width = $conf->exists('cust_pkg-display_times') ? '38%' : '56%';
112
113   #false laziness w/edit/REAL_cust_pkg.cgi
114   my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until );
115   unless ( $pkg->{'part_pkg'}->is_prepaid ) {
116     $billed_or_prepaid = 'billed';
117     $last_bill_or_renewed = 'Last&nbsp;bill';
118     $next_bill_or_prepaid_until = 'Next&nbsp;bill';
119   } else {
120     $billed_or_prepaid = 'prepaid';
121     $last_bill_or_renewed = 'Renewed';
122     $next_bill_or_prepaid_until = 'Prepaid&nbsp;until';
123   }
124
125 %>
126
127 <% if ( $pkg->{cancel} ) { %> <!-- #status: cancelled -->
128
129   <TR>
130     <TD WIDTH="<%=$width%>" ALIGN="right"><FONT COLOR="#ff0000"><B>Cancelled&nbsp;</B></FONT></TD>
131     <%= pkg_datestr($pkg,'cancel',$conf) %>
132   </TR>
133
134   <% unless ( $pkg->{setup} ) { %>
135
136     <TR>
137       <TD COLSPAN=<%=$colspan%>>Never billed</TD>
138     </TR>
139
140   <% } else { %>
141
142     <TR>
143       <TD WIDTH="<%=$width%>" ALIGN="right">Setup&nbsp;</TD>
144       <%= pkg_datestr($pkg, 'setup',$conf) %>
145     </TR>
146
147     <% if ( $pkg->{'last_bill'} ) { %>
148       <TR>
149         <TD WIDTH="<%=$width%>" ALIGN="right"><%= $last_bill_or_renewed %>&nbsp;</TD>
150         <%= pkg_datestr($pkg, 'last_bill',$conf) %>
151       </TR>
152     <% } %>
153
154     <% if ( $pkg->{'susp'} ) { %>
155       <TR>
156         <TD WIDTH="<%=$width%>" ALIGN="right">Suspended&nbsp;</TD>
157         <%= pkg_datestr($pkg, 'susp',$conf) %>
158       </TR>
159     <% } %>
160
161   <% } %>
162
163 <% } else { %>
164
165   <% if ( $pkg->{susp} ) { %> <!-- #status: suspended -->
166
167     <TR>
168       <TD WIDTH="<%=$width%>" ALIGN="right"><FONT COLOR="#FF9900"><B>Suspended</B>&nbsp;</FONT></TD>
169       <%= pkg_datestr($pkg,'susp',$conf) %>
170     </TR>
171
172     <% unless ( $pkg->{setup} ) { %>
173
174       <TR>
175         <TD COLSPAN=<%=$colspan%>>Never billed</TD>
176       </TR>
177
178     <% } else { %>
179
180       <TR>
181         <TD WIDTH="<%=$width%>" ALIGN="right">Setup&nbsp;</TD>
182         <%= pkg_datestr($pkg, 'setup',$conf) %>
183       </TR>
184       
185     <% } %>
186
187     <% if ( $pkg->{'last_bill'} ) { %>
188       <TR>
189         <TD WIDTH="<%=$width%>" ALIGN="right"><%= $last_bill_or_renewed %>&nbsp;</TD>
190         <%= pkg_datestr($pkg, 'last_bill',$conf) %>
191       </TR>
192     <% } %>
193
194     <!-- # next bill ?? -->
195
196     <% if ( $pkg->{'expire'} ) { %>
197       <TR>
198         <TD WIDTH="<%=$width%>" ALIGN="right">Expires&nbsp;</TD>
199         <%= pkg_datestr($pkg, 'expire',$conf) %>
200       </TR>
201     <% } %>
202
203     <TR>
204       <TD COLSPAN=<%=$colspan%>>
205         <FONT SIZE=-1>
206           <% if ( $curuser->access_right('Unsuspend customer package') ) { %>
207             (&nbsp;<%= pkg_unsuspend_link($pkg) %>&nbsp;)
208           <% } %>
209           <% if ( $curuser->access_right('Cancel customer package') ) { %>
210             (&nbsp;<%= pkg_cancel_link($pkg) %>&nbsp;)
211           <% } %>
212         </FONT>
213       </TD>
214     </TR>
215
216   <% } else { %> <!-- #status: active -->
217
218     <% unless ( $pkg->{setup} ) { %> <!-- #not setup -->
219
220       <% unless ( $pkg->{'freq'} ) { %>
221
222         <TR>
223           <TD COLSPAN=<%=$colspan%>>Not&nbsp;yet&nbsp;billed&nbsp;(one-time&nbsp;charge)</TD>
224         </TR>
225
226         <TR>
227           <TD COLSPAN=<%=$colspan%>>
228             <FONT SIZE=-1>
229               <% if ( $curuser->access_right('Cancel customer package immediately') ) { %>
230                 (&nbsp;<%= pkg_cancel_link($pkg) %>&nbsp;)
231               <% } %>
232             </FONT>
233           </TD>
234         </TR>
235
236       <% } else { %>
237
238         <TR>
239           <TD COLSPAN=<%=$colspan%>>Not&nbsp;yet&nbsp;billed&nbsp;(<%= $billed_or_prepaid %>&nbsp;<%= myfreq($pkg->{part_pkg}) %>)</TD>
240         </TR>
241
242       <% } %>
243
244     <% } else { %> <!-- #setup -->
245
246       <% unless ( $pkg->{freq} ) { %>
247
248         <TR>
249           <TD COLSPAN=<%=$colspan%>>One-time&nbsp;charge</TD>
250         </TR>
251
252         <TR>
253           <TD WIDTH="<%=$width%>" ALIGN="right">Billed&nbsp;</TD>
254           <%= pkg_datestr($pkg,'setup',$conf) %>
255         </TR>
256
257       <% } else { %>
258
259         <TR>
260           <TD COLSPAN=<%=$colspan%>><FONT COLOR="#00CC00"><B>Active</B></FONT>,&nbsp;<%= $billed_or_prepaid %>&nbsp;<%= myfreq($pkg->{part_pkg}) %></TD>
261         </TR>
262
263         <TR>
264           <TD WIDTH="<%=$width%>" ALIGN="right">Setup&nbsp;</TD>
265           <%= pkg_datestr($pkg, 'setup',$conf) %>
266         </TR>
267
268       <% } %>
269
270     <% } %>
271
272     <% if ( $pkg->{'last_bill'} ) { %>
273       <TR>
274         <TD WIDTH="<%=$width%>" ALIGN="right"><%= $last_bill_or_renewed %>&nbsp;</TD>
275         <%= pkg_datestr($pkg, 'last_bill',$conf) %>
276       </TR>
277     <% } %>
278
279     <% if ( $pkg->{'next_bill'} ) { %>
280       <TR>
281         <TD WIDTH="<%=$width%>" ALIGN="right"><%= $next_bill_or_prepaid_until %>&nbsp;</TD>
282         <%= pkg_datestr($pkg, 'next_bill',$conf) %>
283       </TR>
284     <% } %>
285
286     <% if ( $pkg->{'expire'} ) { %>
287       <TR>
288         <TD WIDTH="<%=$width%>" ALIGN="right">Expires&nbsp;</TD>
289         <%= pkg_datestr($pkg, 'expire',$conf) %>
290       </TR>
291     <% } %>
292
293     <% if ( $pkg->{freq} ) { %>
294       <TR>
295         <TD COLSPAN=<%=$colspan%>>
296           <FONT SIZE=-1>
297             <% if ( $curuser->access_right('Suspend customer package') ) { %>
298               (&nbsp;<%= pkg_suspend_link($pkg) %>&nbsp;)
299             <% } %>
300             <% if ( $curuser->access_right('Cancel customer package immediately') ) { %>
301               (&nbsp;<%= pkg_cancel_link($pkg) %>&nbsp;)
302             <% } %>
303             <% if ( $curuser->access_right('Cancel customer package later') ) { %>
304               (&nbsp;<%= pkg_expire_link($pkg) %>&nbsp;)
305             <% } %>
306           <FONT>
307         </TD>
308       </TR>
309     <% } %>
310
311   <% } %>
312
313 <% } %>
314
315 </TABLE>
316 </TD>
317
318 <%
319   if ($rowspan == 0) { print qq!</TR>\n!; next; }
320
321   my $cnt = 0;
322   foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
323     foreach my $service (@{$svcpart->{services}}) {
324       print '<TR>' if ($cnt > 0);
325 %>
326   <TD><%=svc_link($svcpart,$service)%></TD>
327   <TD><%=svc_label_link($svcpart,$service)%>
328     <% if ( $curuser->access_right('Unprovision customer service') ) { %>
329       <BR>(&nbsp;<%=svc_unprovision_link($service)%>&nbsp;)
330     <% } %>
331   </TD>
332 </TR>
333 <%
334       $cnt++;
335     }
336     if ( $svcpart->{count} < $svcpart->{quantity} ) {
337       print '<TR>' if ($cnt > 0);
338       if ( $curuser->access_right('Provision customer service') ) {
339         print '<TD COLSPAN=2>'.
340               svc_provision_link($pkg, $svcpart, $conf, $curuser).
341               '</TD></TR>';
342       } else {
343         #print '<TD COLSPAN=2>&nbsp;</TD></TR>';
344         print '<TD COLSPAN=2></TD></TR>';
345       }
346     }
347
348   }
349 }
350 #end display packages
351 %>
352
353 </TABLE>
354 <% } else { %>
355 <BR>
356 <% } %>
357
358 <%
359 #subroutines
360
361 sub get_packages {
362   my $cust_main = shift or return undef;
363   my $conf = shift;
364   
365   my @packages = ();
366   my $method;
367   if (  $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
368      || ( $conf->exists('hidecancelledpackages')
369            && ! $cgi->param('showcancelledpackages') )
370      )
371   {
372     $method = 'ncancelled_pkgs';
373   } else {
374     $method = 'all_pkgs';
375   }
376   
377   foreach my $cust_pkg ( $cust_main->$method() ) {
378   
379     my $part_pkg = $cust_pkg->part_pkg;
380
381     my %pkg = ();
382
383     #to get back to the original object... should use it in the first place!!
384     $pkg{cust_pkg} = $cust_pkg;
385     $pkg{part_pkg} = $part_pkg;
386
387     $pkg{pkgnum} = $cust_pkg->pkgnum;
388     $pkg{pkg} = $part_pkg->pkg;
389     $pkg{pkgpart} = $part_pkg->pkgpart;
390     $pkg{comment} = $part_pkg->getfield('comment');
391     $pkg{freq} = $part_pkg->freq;
392     $pkg{setup} = $cust_pkg->getfield('setup');
393     $pkg{last_bill} = $cust_pkg->getfield('last_bill');
394     $pkg{next_bill} = $cust_pkg->getfield('bill');
395     $pkg{susp} = $cust_pkg->getfield('susp');
396     $pkg{expire} = $cust_pkg->getfield('expire');
397     $pkg{cancel} = $cust_pkg->getfield('cancel');
398
399   
400     my %svcparts = map {
401       $_->svcpart => {
402                        $_->part_svc->hash,
403                        'quantity' => $_->quantity,
404                        'count'    => $cust_pkg->num_cust_svc($_->svcpart),
405                        #'services' => [],
406                      };
407     } $part_pkg->pkg_svc;
408
409     foreach my $cust_svc ( $cust_pkg->cust_svc ) {
410       #warn "svcnum ". $cust_svc->svcnum. " / svcpart ". $cust_svc->svcpart. "\n";
411       my $svc = {
412         'svcnum' => $cust_svc->svcnum,
413         'label'  => ($cust_svc->label)[1],
414       };
415
416       #false laziness with above, to catch extraneous services.  whole
417       #damn thing should be OO...
418       my $svcpart = ( $svcparts{$cust_svc->svcpart} ||= {
419         $cust_svc->part_svc->hash,
420         'quantity' => 0,
421         'count'    => $cust_pkg->num_cust_svc($cust_svc->svcpart),
422         #'services' => [],
423       } );
424
425       push @{$svcpart->{services}}, $svc;
426
427     }
428
429     $pkg{svcparts} = [ values %svcparts ];
430
431     push @packages, \%pkg;
432   
433   }
434   
435   return \@packages;
436
437 }
438
439 sub svc_link {
440
441   my ($svcpart, $svc) = (shift,shift) or return '';
442   return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!;
443
444 }
445
446 sub svc_label_link {
447
448   my ($svcpart, $svc) = (shift,shift) or return '';
449   return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!;
450
451 }
452
453 sub svc_provision_link {
454   my ($pkg, $svcpart, $conf, $curuser) = @_;
455   ( my $svc_nbsp = $svcpart->{svc} ) =~ s/\s+/&nbsp;/g;
456   my $num_left = $svcpart->{quantity} - $svcpart->{count};
457   my $pkgnum_svcpart = "pkgnum$pkg->{pkgnum}-svcpart$svcpart->{svcpart}";
458
459   my $url;
460   if ( $svcpart->{svcdb} eq 'svc_external'
461        && $conf->exists('svc_external-skip_manual')
462   ) {
463     $url = "${p}edit/process/$svcpart->{svcdb}.cgi?".
464            "pkgnum=$pkg->{pkgnum}&".
465            "svcpart=$svcpart->{svcpart}";
466   } else {
467     $url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart";
468   }
469
470   my $link = qq!<A CLASS="provision" HREF="$url">!.
471              "Provision&nbsp;$svc_nbsp&nbsp;($num_left)</A>";
472   if ( $conf->exists('legacy_link')
473        && $curuser->access_right('View/link unlinked services')
474      )
475   {
476     $link .= '<BR>'.
477              qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!.
478              qq!$pkgnum_svcpart">!.
479             "Link&nbsp;to&nbsp;legacy&nbsp;$svc_nbsp&nbsp;($num_left)</A>";
480   }
481   $link;
482 }
483
484 sub svc_unprovision_link {
485   my $svc = shift or return '';
486   qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?$svc->{svcnum}',!.
487   qq!'Permanently unprovision and delete this service?')">Unprovision</A>!;
488 }
489
490 # This should be generalized to use config options to determine order.
491 sub pkgsort_pkgnum_cancel {
492   if ($a->{cancel} and $b->{cancel}) {
493     return ($a->{pkgnum} <=> $b->{pkgnum});
494   } elsif ($a->{cancel} or $b->{cancel}) {
495     return (-1) if ($b->{cancel});
496     return (1) if ($a->{cancel});
497     return (0);
498   } else {
499     return($a->{pkgnum} <=> $b->{pkgnum});
500   }
501 }
502
503 sub pkg_datestr {
504   my($pkg, $field, $conf) = @_ or return '';
505   return '&nbsp;' unless $pkg->{$field};
506   my $format = '<TD align="left"><B>%b</B></TD>'.
507                '<TD align="right"><B>&nbsp;%o,</B></TD>'.
508                '<TD align="right"><B>&nbsp;%Y</B></TD>';
509   #$format .= '&nbsp;<FONT SIZE=-3>%l:%M:%S%P&nbsp;%z</FONT>'
510   $format .= '<TD ALIGN="right"><B>&nbsp;%l</TD>'.
511              '<TD ALIGN="center"><B>:</B></TD>'.
512              '<TD ALIGN="left"><B>%M</B></TD>'.
513              '<TD ALIGN="left"><B>&nbsp;%P</B></TD>'
514     if $conf->exists('cust_pkg-display_times');
515   ( my $strip = time2str($format, $pkg->{$field}) ) =~ s/ (\d)/$1/g;
516   $strip;
517 }
518
519 sub pkg_change_link {
520   my $pkg = shift or return '';
521   return qq!<a href="${p}misc/change_pkg.cgi?$pkg->{pkgnum}">!.
522          qq!Change&nbsp;package</a>!;
523 }
524
525 sub pkg_suspend_link {
526   my $pkg = shift or return '';
527   return qq!<a href="${p}misc/susp_pkg.cgi?$pkg->{pkgnum}">Suspend</a>!;
528 }
529
530 sub pkg_unsuspend_link {
531   my $pkg = shift or return '';
532   return qq!<a href="${p}misc/unsusp_pkg.cgi?$pkg->{pkgnum}">Unsuspend</a>!;
533 }
534
535 sub pkg_cancel_link {
536   my $pkg = shift or return '';
537   qq!<A HREF="javascript:areyousure('${p}misc/cancel_pkg.cgi?$pkg->{pkgnum}', !.
538   qq!'Permanently delete included services and cancel this package?')">!.
539   qq!Cancel now</A>!;
540 }
541
542 sub pkg_expire_link {
543   my $pkg = shift or return '';
544   qq!<A HREF="${p}misc/expire_pkg.cgi?$pkg->{pkgnum}">Cancel later</A>!;
545 }
546
547 sub pkg_dates_link {
548   my $pkg = shift or return '';
549   qq!<A HREF="${p}edit/REAL_cust_pkg.cgi?$pkg->{pkgnum}">Edit&nbsp;dates</A>!;
550 }
551
552 sub pkg_customize_link {
553   my $pkg = shift or return '';
554   my $custnum = shift;
555   qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};!.
556   qq!pkgnum=$pkg->{pkgnum}">Customize</A>!;
557 }
558
559 %>