summary display of bulk packages with many services, RT#9821
authormark <mark>
Mon, 25 Oct 2010 22:22:42 +0000 (22:22 +0000)
committermark <mark>
Mon, 25 Oct 2010 22:22:42 +0000 (22:22 +0000)
FS/FS/Conf.pm
FS/FS/part_pkg/bulk.pm
FS/FS/part_pkg/voip_cdr.pm
httemplate/misc/unprovision.cgi
httemplate/search/cust_pkg_svc.html [new file with mode: 0644]
httemplate/search/cust_svc.html
httemplate/view/cust_main/packages.html
httemplate/view/cust_main/packages/services.html
httemplate/view/elements/svc_Common.html

index b0b44e2..72e38d3 100644 (file)
@@ -2632,6 +2632,13 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'cust_pkg-large_pkg_size',
+    'section'     => 'UI',
+    'description' => "In customer view, summarize packages with more than this many services.  Set to zero to never summarize packages.",
+    'type'        => 'text',
+  },
+
+  {
     'key'         => 'svc_acct-edit_uid',
     'section'     => 'shell',
     'description' => 'Allow UID editing.',
index 69fe98e..a346b90 100644 (file)
@@ -29,9 +29,13 @@ $me = '[FS::part_pkg::bulk]';
                                    ' of service at cancellation',
                          'type' => 'checkbox',
                        },
+    'summarize_svcs'=> { 'name' => 'Show a count of services on the invoice, '.
+                                   'instead of a detailed list',
+                         'type' => 'checkbox',
+                       },
   },
   'fieldorder' => [ 'setup_fee', 'recur_fee', 'svc_setup_fee', 'svc_recur_fee',
-                    'unused_credit', ],
+                    'unused_credit', 'summarize_svcs' ],
   'weight' => 50,
 );
 
@@ -50,6 +54,11 @@ sub calc_recur {
     unless $$sdate > $last_bill;
 
   my $total_svc_charge = 0;
+  my %n_setup = ();
+  my %n_recur = ();
+  my %part_svc_label = ();
+
+  my $summarize = $self->option('summarize_svcs',1);
 
   warn "$me billing for bulk services from ". time2str('%x', $last_bill).
                                       " to ". time2str('%x', $$sdate). "\n"
@@ -61,6 +70,7 @@ sub calc_recur {
     my @label = $h_cust_svc->label_long( $$sdate, $last_bill );
     die "fatal: no historical label found, wtf?" unless scalar(@label); #?
     my $svc_details = $label[0]. ': '. $label[1]. ': ';
+    $part_svc_label{$h_cust_svc->svcpart} ||= $label[0];
 
     my $svc_charge = 0;
 
@@ -70,6 +80,7 @@ sub calc_recur {
     } elsif ( $svc_setup_fee ) {
       $svc_charge += $svc_setup_fee;
       $svc_details .= $money_char. sprintf('%.2f setup, ', $svc_setup_fee);
+      $n_setup{$h_cust_svc->svcpart}++;
     }
 
     my $svc_end = $h_cust_svc->date_deleted;
@@ -85,11 +96,21 @@ sub calc_recur {
       if $recur_charge;
 
     $svc_charge += $recur_charge;
-
-    push @$details, $svc_details;
+    $n_recur{$h_cust_svc->svcpart}++;
+    push @$details, $svc_details if !$summarize;
     $total_svc_charge += $svc_charge;
 
   }
+  if ( $summarize ) {
+    foreach my $svcpart (keys %part_svc_label) {
+      push @$details, sprintf('Setup fee: %d @ '.$money_char.'%.2f',
+        $n_setup{$svcpart}, $svc_setup_fee )
+        if $svc_setup_fee and $n_setup{$svcpart};
+      push @$details, sprintf('%d services @ '.$money_char.'%.2f',
+        $n_recur{$svcpart}, $self->option('svc_recur_fee') )
+        if $n_recur{$svcpart};
+    }
+  }
 
   sprintf('%.2f', $self->base_recur($cust_pkg) + $total_svc_charge );
 }
index 9981da0..bcc2c6b 100644 (file)
@@ -226,6 +226,10 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
                            'type' => 'checkbox',
                          },
 
+    'bill_inactive_svcs' => { 'name' => 'Bill for all phone numbers that were active during the billing period',
+                              'type' => 'checkbox',
+                            },
+
     'count_available_phones' => { 'name' => 'Consider for tax purposes the number of lines to be svc_phones that may be provisioned rather than those that actually are.',
                            'type' => 'checkbox',
                          },
@@ -278,7 +282,7 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
                        use_duration
                        411_rewrite
                        output_format usage_mandate summarize_usage usage_section
-                       bill_every_call
+                       bill_every_call bill_inactive_svcs
                        count_available_phones
                      )
                   ],
@@ -365,11 +369,25 @@ sub calc_usage {
 
   my($svc_table, $svc_field) = split('\.', $cdr_svc_method);
 
-  foreach my $cust_svc (
-    grep { $_->part_svc->svcdb eq $svc_table } $cust_pkg->cust_svc
-  ) {
+  my @cust_svc;
+  if( $self->option('bill_inactive_svcs',1) ) {
+    #XXX in this mode do we need to restrict the set of CDRs by date also?
+    @cust_svc = $cust_pkg->h_cust_svc($$sdate, $last_bill);
+  }
+  else {
+    @cust_svc = $cust_pkg->cust_svc;
+  }
+  @cust_svc = grep { $_->part_svc->svcdb eq $svc_table } @cust_svc;
+
+  foreach my $cust_svc (@cust_svc) {
 
-    my $svc_x = $cust_svc->svc_x;
+    my $svc_x;
+    if( $self->option('bill_inactive_svcs',1) ) {
+      $svc_x = $cust_svc->h_svc_x($$sdate, $last_bill);
+    }
+    else {
+      $svc_x = $cust_svc->svc_x;
+    }
     my %options = (
         'disable_src'    => $self->option('disable_src'),
         'default_prefix' => $self->option('default_prefix'),
@@ -709,7 +727,7 @@ sub calc_usage {
         if ( $charge > 0 ) {
           #just use FS::cust_bill_pkg_detail objects?
           my $call_details;
-          my $phonenum = $cust_svc->svc_x->phonenum;
+          my $phonenum = $svc_x->phonenum;
 
           if ( scalar(@call_details) == 1 ) {
             $call_details =
index 4ab15fd..6f2c238 100755 (executable)
@@ -1,6 +1,8 @@
 %if ( $error ) {
 %  errorpage($error);
-%} else {
+%} elsif ( $pkgnum ) {
+<% $cgi->redirect(popurl(2)."search/cust_pkg_svc.html?svcpart=$svcpart;pkgnum=$pkgnum") %>
+%} else { # $custnum should always exist
 <% $cgi->redirect(popurl(2)."view/cust_main.cgi?$custnum") %>
 %}
 <%init>
@@ -9,18 +11,28 @@ die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('Unprovision customer service');
 
 #untaint svcnum
-my($query) = $cgi->keywords;
-$query =~ /^(\d+)$/;
-my $svcnum = $1;
+my @svcnums;
+my ($pkgnum, $svcpart, $custnum);
+if( $cgi->param('svcnum') ) {
+  @svcnums = grep { $_ } map { /^(\d+)$/ && $1 } $cgi->param('svcnum');
+  $pkgnum = $cgi->param('pkgnum');
+  $svcpart = $cgi->param('svcpart');
+  $custnum = $cgi->param('custnum');
+}
+else {
+  @svcnums = map { /^(\d+)$/ && $1 } $cgi->keywords;
+}
 
-#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum});
-#die "Unknown svcnum!" unless $svc_acct;
+my $error = '';
+foreach my $svcnum (@svcnums) {
 
-my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
-die "Unknown svcnum!" unless $cust_svc;
+  my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+  die "Unknown svcnum!" unless $cust_svc;
 
-my $custnum = $cust_svc->cust_pkg->custnum;
+  $custnum ||= $cust_svc->cust_pkg->custnum;
 
-my $error = $cust_svc->cancel;
+  $error .= $cust_svc->cancel;
+
+}
 
 </%init>
diff --git a/httemplate/search/cust_pkg_svc.html b/httemplate/search/cust_pkg_svc.html
new file mode 100644 (file)
index 0000000..4f27d66
--- /dev/null
@@ -0,0 +1,117 @@
+<% include( 'elements/search.html',
+              'title'       => $part_svc->svc.' services in package #'.$pkgnum,
+             'name'        => 'services',
+              'html_form'   => $html_form,
+             'query'       => $sql_query,
+             'count_query' => $count_query,
+             'redirect'    => $link,
+             'header'      => [ '#',
+                                 'Service',
+                                 '', #checkboxes
+                              ],
+             'fields'      => [ 'svcnum',
+                                 sub {
+                                   ($_[0]->label)[1]
+                                 },
+                                 sub {
+                                   $areboxes = 1;
+                                   '<INPUT TYPE="checkbox" NAME="svcnum" VALUE='.$_[0]->svcnum.'>'
+                                 },
+                              ],
+             'links'       => [ $link,
+                                $link,
+                                 '',
+                              ],
+              'align' => 'rrlc',
+              'color' => [ 
+                           ('')x4,
+                         ],
+              'style' => [ 
+                           ('')x4,
+                         ],
+              'html_foot' => sub { $areboxes ? $html_foot : '' }
+          )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $pkgnum = $cgi->param('pkgnum');
+$pkgnum =~ /^(\d+)$/ or die "invalid pkgnum: $pkgnum";
+my @extra_sql = ( "cust_svc.pkgnum = $pkgnum" );
+
+my $svcpart = $cgi->param('svcpart');
+$svcpart =~ /^(\d+)$/ or die "invalid svcpart: $svcpart";
+push @extra_sql, "cust_svc.svcpart = $svcpart";
+my $part_svc = qsearchs('part_svc', {svcpart => $svcpart});
+my $svcdb = $part_svc->svcdb;
+
+my $orderby = 'ORDER BY svcnum'; #others?
+
+my $addl_from = " LEFT JOIN part_svc USING (svcpart)
+LEFT JOIN cust_pkg USING (pkgnum)
+LEFT JOIN cust_main USING (custnum)
+INNER JOIN $svcdb USING (svcnum)";
+
+my $search_string;
+if ( length( $cgi->param('search_svc') ) ) {
+
+  $search_string = $cgi->param('search_svc');
+  $search_string =~ s/(^\s+|\s+$)//;
+  push @extra_sql, "FS::$svcdb"->search_sql($search_string);
+
+}
+
+#here is the agent virtualization
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( 
+                   'null_right' => 'View/link unlinked services'
+                 );
+
+my $extra_sql = ' WHERE '. join(' AND ', @extra_sql );
+
+my $sql_query = {
+  'select'     => join(', ',
+                    'cust_svc.*',
+                   'part_svc.svc',
+                  ),
+  'table'      => 'cust_svc',
+  'addl_from'  => $addl_from,
+  'hashref'    => {},
+  'extra_sql'  => "$extra_sql $orderby",
+};
+
+#warn Dumper($sql_query)."\n";
+
+my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql";
+
+my $link = sub {
+  my $cust_svc = shift;
+  my $url = svc_url(
+    'm'        => $m,
+    'action'   => 'view',
+    'svcdb'    => $svcdb,
+    'query'     => '',
+  );
+  [ $url, 'svcnum' ];
+};
+
+my $html_form = qq!
+<SCRIPT TYPE="text/javascript">
+function areyousure(obj) {
+  return confirm('Permanently delete the selected services?');
+}
+</SCRIPT>
+<FORM METHOD="POST" ACTION="${p}misc/unprovision.cgi" onsubmit="return areyousure()">!; 
+
+my $areboxes = 0;
+
+my $html_foot = qq!
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Unprovision selected">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE=$pkgnum>
+<INPUT TYPE="hidden" NAME="svcpart" VALUE=$svcpart>
+</FORM>!;
+
+
+</%init>
index 2c17561..61bfc70 100644 (file)
@@ -93,6 +93,10 @@ if ( length( $cgi->param('search_svc') ) ) {
   errorpage("No search term specified");
 }
 
+if ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+  push @extra_sql, "cust_svc.pkgnum = $1";
+}
+
 #here is the agent virtualization
 push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( 
                    'null_right' => 'View/link unlinked services'
@@ -113,6 +117,8 @@ my $sql_query = {
   'extra_sql'  => "$extra_sql $orderby",
 };
 
+warn Dumper($sql_query)."\n";
+
 my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql";
 
 my $link = sub {
index f0f156b..660d0ef 100755 (executable)
@@ -163,6 +163,7 @@ my %conf_opt = (
   'legacy_link'               => $conf->exists('legacy_link'),
   'svc_broadband-manage_link' => scalar($conf->config('svc_broadband-manage_link')),
   'maestro-status_test'       => $conf->exists('maestro-status_test'),
+  'cust_pkg-large_pkg_size'   => $conf->config('cust_pkg-large_pkg_size'),
 );
 
 #subroutines
index 6e30922..512efcc 100644 (file)
@@ -4,12 +4,40 @@
 
   <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
     <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+    <SCRIPT TYPE="text/javascript">
+function clearhint_search_cust_svc(obj, str) {
+  if (obj.value == str) obj.value = '';
+}
+    </SCRIPT>
 
 %  #foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
 %  foreach my $part_svc ( $cust_pkg->part_svc ) {
 
-%    #foreach my $service (@{$svcpart->{services}}) {
-%    foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) {
+%    if ( $opt{'cust_pkg-large_pkg_size'} > 0 and
+%         $opt{'cust_pkg-large_pkg_size'} <= $cust_pkg->num_svcs ) {
+%      # summarize
+      <TR>
+        <TD ALIGN="center" VALIGN="top">
+%       my $href="${p}search/cust_pkg_svc.html?svcpart=".$part_svc->svcpart.
+%          ";pkgnum=".$cust_pkg->pkgnum;
+        <A HREF="<% $href %>"><% $part_svc->svc %></A>&nbsp;
+        <A HREF="<% $href %>"><B>(view all <% $cust_pkg->num_svcs %>)</B></A>
+%     my $hint = $hints{$part_svc->svcdb};
+%     if ( $hint ) {
+        <BR>
+        <FORM name="svcpart<%$part_svc->svcpart%>_search" STYLE="display:inline"
+        ACTION="<%$p%>search/cust_pkg_svc.html" METHOD="GET">
+        <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%$part_svc->svcpart%>">
+        <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%$cust_pkg->pkgnum%>">
+        <INPUT TYPE="text" NAME="search_svc"
+        onfocus="clearhint_search_cust_svc(this, '<%$hint%>')" VALUE="<%$hint%>">
+        <INPUT TYPE="submit" VALUE="Search"></FORM>
+%     } #$hint
+        </TD>
+      </TR>
+%    }
+%    else {
+%      foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) {
 
       <TR>
         <TD ALIGN="right" VALIGN="top"><% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %></TD>
@@ -65,7 +93,8 @@
 
           </TD>
         </TR>
-%   } 
+%     } #foreach $cust_svc
+%   }
 
 %   if (    ! $cust_pkg->get('cancel')
 %        && $curuser->access_right('Provision customer service') 
@@ -137,4 +166,13 @@ sub svc_unprovision_link {
   qq!', 'Permanently unprovision and delete this service?')">Unprovision</A>!;
 }
 
+my %hints = (
+svc_acct      => '(user or email)',
+svc_domain    => '(domain)',
+svc_broadband => '(ip or mac)',
+svc_forward   => '(email)',
+svc_phone     => '(phone)',
+svc_pbx       => '(phone)',
+);
+
 </%init>
index 852640e..8a352f3 100644 (file)
          )
 
 </%doc>
+<SCRIPT>
+function areyousure(href) {
+  if (confirm("Permanently delete this <% $label %>?") == true)
+    window.location.href = href;
+}
+</SCRIPT>
+
 % if ( $custnum ) { 
 
   <% include("/elements/header.html","View $label: $value") %>
             "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')"
   )) %>
 
-  <SCRIPT>
-  function areyousure(href) {
-      if (confirm("Permanently delete this <% $label %>?") == true)
-          window.location.href = href;
-  }
-  </SCRIPT>
-
 % } 
 
 Service #<B><% $svcnum %></B>
 % my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
 | <A HREF="<%$url%><%$svcnum%>">Edit this <% $label %></A>
+| <A HREF="javascript:areyousure('<%$p.'misc/unprovision.cgi?'.$svcnum%>')">
+Unprovision this Service</A>
 <BR>
 
 <% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>