Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorIvan Kohler <ivan@freeside.biz>
Sat, 21 Feb 2015 21:14:28 +0000 (13:14 -0800)
committerIvan Kohler <ivan@freeside.biz>
Sat, 21 Feb 2015 21:14:28 +0000 (13:14 -0800)
16 files changed:
FS/FS/Report/FCC_477.pm
FS/FS/Report/Table.pm
FS/FS/cust_pkg.pm
FS/FS/part_event/Condition/has_cust_payby_auto.pm
FS/FS/part_pkg/bulk.pm
FS/FS/part_pkg/bulk_Common.pm
FS/FS/part_pkg/bulk_simple.pm
httemplate/browse/part_pkg-fcc.html
httemplate/edit/process/bulk-part_pkg-fcc.html
httemplate/elements/change_history_common.html
httemplate/elements/popup_link_onclick.html
httemplate/misc/email-customers.html
httemplate/search/elements/checkbox-foot.html
httemplate/search/elements/cust_main_dayranges.html
httemplate/search/report_receivables.cgi
httemplate/view/cust_main/packages/status.html

index c93c919..75ddee0 100644 (file)
@@ -400,7 +400,8 @@ sub fbs_sql {
 
   my @select = (
     "$censustract AS censustract",
-    'technology',
+    '(technology - technology % 10) AS media_type',
+      # media types are multiples of 10
     'broadband_downstream',
     'broadband_upstream',
     "SUM($q)",
index 69686df..4797473 100644 (file)
@@ -495,12 +495,17 @@ sub _cust_bill_pkg_recurring {
 
   my @where = (
     '(pkgnum != 0 OR feepart IS NOT NULL)',
-    $self->with_classnum($opt{'classnum'}, $opt{'use_override'}),
     $self->with_report_option(%opt),
     $self->with_refnum(%opt),
     $self->with_cust_classnum(%opt)
   );
 
+  my $where_classnum = $self->with_classnum($opt{'classnum'}, $opt{'use_override'});
+  if ($opt{'project'}) {
+    $where_classnum =~ s/\bcust_bill_pkg/v_cust_bill_pkg/g;
+  }
+  push @where, $where_classnum;
+
   if ( $opt{'distribute'} ) {
     $where[0] = 'pkgnum != 0'; # specifically exclude fees
     push @where, "cust_main.agentnum = $agentnum" if $agentnum;
index 7678a02..b64d4dc 100644 (file)
@@ -784,6 +784,10 @@ to a different pkgpart or location, and probably shouldn't be in any other
 case.  If it's not set, the 'unused_credit_cancel' part_pkg option will 
 be used.
 
+=item delay_cancel - for internal use, to allow proper handling of
+supplemental packages when the main package is flagged to suspend 
+before cancelling
+
 =back
 
 If there is an error, returns the error, otherwise returns false.
@@ -823,7 +827,7 @@ sub cancel {
   my $date = $options{'date'} if $options{'date'}; # expire/cancel later
   $date = '' if ($date && $date <= $cancel_time);      # complain instead?
 
-  my $delay_cancel = undef;
+  my $delay_cancel = $options{'delay_cancel'};
   if ( !$date && $self->part_pkg->option('delay_cancel',1)
        && (($self->status eq 'active') || ($self->status eq 'suspended'))
   ) {
@@ -901,14 +905,13 @@ sub cancel {
         return $error;
       }
     }
-
   } #unless $date
 
   my %hash = $self->hash;
   if ( $date ) {
     $hash{'expire'} = $date;
     if ($delay_cancel) {
-      $hash{'susp'} = $cancel_time unless $self->susp;
+      # just to be sure these are clear
       $hash{'adjourn'} = undef;
       $hash{'resume'} = undef;
     }
@@ -935,22 +938,31 @@ sub cancel {
   }
 
   foreach my $supp_pkg ( $self->supplemental_pkgs ) {
-    if ($delay_cancel) {
-        $error = $supp_pkg->suspend(%options, 'from_main' => 1, 'reason' => undef);
-    } else {
-        $error = $supp_pkg->cancel(%options, 'from_main' => 1);
-    }
+    $error = $supp_pkg->cancel(%options, 
+      'from_main' => 1, 
+      'date' => $date, #in case it got changed by delay_cancel
+      'delay_cancel' => $delay_cancel,
+    );
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
       return "canceling supplemental pkg#".$supp_pkg->pkgnum.": $error";
     }
   }
 
-  foreach my $usage ( $self->cust_pkg_usage ) {
-    $error = $usage->delete;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "deleting usage pools: $error";
+  if ($delay_cancel && !$options{'from_main'}) {
+    $error = $new->suspend(
+      'from_cancel' => 1,
+      'time'        => $cancel_time
+    );
+  }
+
+  unless ($date) {
+    foreach my $usage ( $self->cust_pkg_usage ) {
+      $error = $usage->delete;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "deleting usage pools: $error";
+      }
     }
   }
 
@@ -1249,6 +1261,9 @@ separately.
 =item from_main - allows a supplemental package to be suspended, rather
 than redirecting the method call to its main package.  For internal use.
 
+=item from_cancel - used when suspending from the cancel method, forces
+this to skip everything besides basic suspension.  For internal use.
+
 =back
 
 If there is an error, returns the error, otherwise returns false.
@@ -1291,7 +1306,7 @@ sub suspend {
   }
 
   # some false laziness with sub cancel
-  if ( !$options{nobill} && !$date &&
+  if ( !$options{nobill} && !$date && !$options{'from_cancel'} &&
        $self->part_pkg->option('bill_suspend_as_cancel',1) ) {
     # kind of a kludge--'bill_suspend_as_cancel' to avoid having to 
     # make the entire cust_main->bill path recognize 'suspend' and 
@@ -1356,17 +1371,19 @@ sub suspend {
 
   unless ( $date ) { # then we are suspending now
 
-    # credit remaining time if appropriate
-    # (if required by the package def, or the suspend reason)
-    my $unused_credit = $self->part_pkg->option('unused_credit_suspend',1)
-                        || ( defined($reason) && $reason->unused_credit );
+    unless ($options{'from_cancel'}) {
+      # credit remaining time if appropriate
+      # (if required by the package def, or the suspend reason)
+      my $unused_credit = $self->part_pkg->option('unused_credit_suspend',1)
+                          || ( defined($reason) && $reason->unused_credit );
 
-    if ( $unused_credit ) {
-      warn "crediting unused time on pkg#".$self->pkgnum."\n" if $DEBUG;
-      my $error = $self->credit_remaining('suspend', $suspend_time);
-      if ($error) {
-        $dbh->rollback if $oldAutoCommit;
-        return $error;
+      if ( $unused_credit ) {
+        warn "crediting unused time on pkg#".$self->pkgnum."\n" if $DEBUG;
+        my $error = $self->credit_remaining('suspend', $suspend_time);
+        if ($error) {
+          $dbh->rollback if $oldAutoCommit;
+          return $error;
+        }
       }
     }
 
@@ -1397,7 +1414,7 @@ sub suspend {
     }
 
     my $conf = new FS::Conf;
-    if ( $conf->config('suspend_email_admin') ) {
+    if ( $conf->config('suspend_email_admin') && !$options{'from_cancel'} ) {
  
       my $error = send_email(
         'from'    => $conf->config('invoice_from', $self->cust_main->agentnum),
@@ -3381,6 +3398,9 @@ really the whole point of the delay_cancel option.
 
 sub is_status_delay_cancel {
   my ($self) = @_;
+  if ( $self->main_pkgnum and $self->pkglinknum ) {
+    return $self->main_pkg->is_status_delay_cancel;
+  }
   return 0 unless $self->part_pkg->option('delay_cancel',1);
   return 0 unless $self->status eq 'suspended';
   return 0 unless $self->expire;
index edfb7ec..dce97df 100644 (file)
@@ -3,6 +3,7 @@ package FS::part_event::Condition::has_cust_payby_auto;
 use strict;
 use Tie::IxHash;
 use FS::payby;
+use FS::Record qw(qsearch);
 
 use base qw( FS::part_event::Condition );
 
index 4a55858..bc42e20 100644 (file)
@@ -27,7 +27,7 @@ $me = '[FS::part_pkg::bulk]';
 sub _bulk_cust_svc {
   my( $self, $cust_pkg, $sdate ) = @_;
                        #   END      START
-  $cust_pkg->h_cust_svc( $$sdate, $cust_pkg->last_bill );
+  return $self->_only_svcs_filter($cust_pkg->h_cust_svc( $$sdate, $cust_pkg->last_bill ));
 }
 
 sub _bulk_setup {
index 67f2caf..a9231bc 100644 (file)
@@ -5,6 +5,7 @@ use strict;
 use vars qw($DEBUG $me %info);
 use Date::Format;
 use FS::Conf;
+use FS::Record qw(qsearchs);
 
 $DEBUG = 0;
 $me = '[FS::part_pkg::bulk_Common]';
@@ -23,8 +24,17 @@ $me = '[FS::part_pkg::bulk_Common]';
                                    'instead of a detailed list',
                          'type' => 'checkbox',
                        },
+    'only_svcs' => {
+      'name' => 'Only charge fees for these services',
+      'type' => 'select_multiple',
+      'select_table'  => 'part_svc',
+      'select_key'    => 'svcpart',
+      'select_label'  => 'svc',
+      'disable_empty' => 1,
+      'parse'         => sub { @_ }, #should this be the default in /edit/process/part_pkg.cgi?
+    },
   },
-  'fieldorder' => [ 'svc_setup_fee', 'svc_recur_fee',
+  'fieldorder' => [ 'svc_setup_fee', 'svc_recur_fee', 'only_svcs',
                     'summarize_svcs', 'no_prorate' ],
   'weight' => 51,
 );
@@ -123,5 +133,17 @@ sub is_free_options {
 
 sub can_usageprice { 0; }
 
+sub _only_svcs_filter {
+  my ($self, @cust_svc) = @_;
+  my @only_svcs = split(', ',$self->option('only_svcs',1));
+  if (@only_svcs) {
+    @cust_svc = grep { 
+      my $svcpart = $_->svcpart;
+      grep(/^$svcpart$/,@only_svcs);
+    } @cust_svc;
+  }
+  return @cust_svc;
+}
+
 1;
 
index 93944cc..6ed1250 100644 (file)
@@ -18,7 +18,7 @@ $me = '[FS::part_pkg::bulk]';
 
 sub _bulk_cust_svc {
   my( $self, $cust_pkg, $sdate ) = @_;
-  $cust_pkg->cust_svc;
+  return $self->_only_svcs_filter($cust_pkg->cust_svc);
 }
 
 sub _bulk_setup {
index bdfb99a..69e7d8f 100755 (executable)
@@ -197,6 +197,13 @@ my $html_form = qq!<FORM ACTION="${p}edit/process/bulk-part_pkg-fcc.html" METHOD
   ' )
   <BR><BR>';
 
+# pass the page selection through so we can jump back to the current spot
+if ( $cgi->param('maxrecords') =~ /^(\d+)$/ ) {
+  $html_form .= qq!<INPUT TYPE="hidden" NAME="maxrecords" VALUE="$1">!;
+}
+if ( $cgi->param('offset') =~ /^(\d+)$/ ) {
+  $html_form .= qq!<INPUT TYPE="hidden" NAME="offset" VALUE="$1">!;
+}
 
 # restore this only after creating $html_form
 $cgi->param('classnum', $classnum) if length($classnum);
@@ -228,7 +235,7 @@ my @menubar =
   }
 
   function filter_change() {
-    window.location = '! . $cgi->self_url . qq!?classnum='
+    window.location = '<% $cgi->self_url %>?classnum='
       + document.getElementById('classnum').value;
   }
 </script>
index 8ef3308..d060a24 100644 (file)
@@ -17,7 +17,7 @@
 %   }
 <% $cgi->redirect($fsurl.'browse/part_pkg-fcc.html?redirect='.$session) %>
 % } else {
-<% $cgi->redirect($fsurl.'browse/part_pkg-fcc.html?classnum='.$classnum.$jump) %>
+<% $cgi->redirect($dest) %>
 % }
 <%init>
 my $curuser = $FS::CurrentUser::CurrentUser;
@@ -38,10 +38,14 @@ foreach my $param ($cgi->param) {
   $error{$pkgpart} = $error if $error;
 }
 
-my $classnum = $cgi->param('classnum');
+my $dest = $fsurl.'browse/part_pkg-fcc.html?';
+foreach (qw(classnum maxrecords offset)) {
+  if ( $cgi->param($_) =~ /^(\d+)$/ ) {
+    $dest .= "$_=$1;";
+  }
+}
 
-my $jump = '';
 if ( $cgi->param('jump') =~ /^pkgpart(\d+)$/ ) {
-  $jump = '#'.$1;
+  $dest .= "#$1";
 }
 </%init>
index 9fc85aa..2d2c4c0 100644 (file)
@@ -191,7 +191,10 @@ my %h_table_labelsub = (
 my $discounts = {};
 my $discount_descripsub = sub {
   my($item) = @_;
-  $pkgpart{$item->pkgpart} ||= $item->part_pkg->pkg;
+  $pkgpart{$item->pkgpart} ||= qsearchs({
+    'table' => 'part_pkg',
+    'hashref' => {'pkgpart' => $item->pkgpart}
+  })->pkg;
   my $dnum = $item->discountnum;
   $discounts->{$dnum} ||= qsearchs({
     'table'=>'discount',
index 961f623..5173115 100644 (file)
@@ -8,6 +8,9 @@ Example:
 
     #required
     'action'         => 'content.html', # uri for content of popup
+
+    #alternately, use instead of action
+    'js_action'      => 'url',          # javascript variable or expression
    
     #strongly recommended
     'actionlabel     => 'You clicked',  # popup title
@@ -47,7 +50,8 @@ if (ref($_[0]) eq 'HASH') {
   $params = { @_ };
 }
 
-$action      = $params->{'action'}      if exists $params->{'action'};
+$action      = q(') . $params->{'action'} . q(') if exists $params->{'action'};
+$action      = $params->{'js_action'}   if exists $params->{'js_action'};
 $actionlabel = $params->{'actionlabel'} if exists $params->{'actionlabel'};
 $width       = $params->{'width'}       if exists $params->{'width'};
 $height      = $params->{'height'}      if exists $params->{'height'};
@@ -61,7 +65,7 @@ $scrolling   = $params->{'scrolling'}   if exists $params->{'scrolling'};
 my $popup_name = 'popup-'.time. "-$$-". rand() * 2**32;
 
 my $onclick =
-  "overlib( OLiframeContent('$action', $width, $height, '$popup_name', 0, '$scrolling' ), ".
+  "overlib( OLiframeContent($action, $width, $height, '$popup_name', 0, '$scrolling' ), ".
     "CAPTION, '$actionlabel', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, ".
     "DRAGGABLE, CLOSECLICK, ".
     "BGCOLOR, '$color', CGCOLOR, '$color', CLOSETEXT, '$closetext'".
index c74c15b..83e8615 100644 (file)
@@ -1,4 +1,9 @@
+% if ($popup) {
+<% include('/elements/header-popup.html', $title) %>
+% } else {
 <% include('/elements/header.html', $title) %>
+% }
+
 
 <FORM NAME="OneTrueForm" ACTION="email-customers.html" METHOD="POST">
 <INPUT TYPE="hidden" NAME="table" VALUE="<% $table %>">
@@ -7,6 +12,8 @@
 %# multi-valued search params.  We are no longer in search context, so we 
 %# pack the search into a Storable string for later use.
 <INPUT TYPE="hidden" NAME="search" VALUE="<% encode_base64(nfreeze(\%search)) %>">
+<INPUT TYPE="hidden" NAME="popup" VALUE="<% $popup %>">
+<INPUT TYPE="hidden" NAME="url" VALUE="<% $url | h %>">
 
 % if ( $cgi->param('action') eq 'send' ) { 
 
@@ -16,7 +23,7 @@
                  'OneTrueForm',
                  [ qw( search table from subject html_body text_body msgnum ) ],
                  'process/email-customers.html',
-                 { 'message' => "Notice sent" }, #would be nice to show #, but..
+                 $pdest,
               )
     %>
 
@@ -159,13 +166,18 @@ my $conf = FS::Conf->new;
 my $table = $cgi->param('table') or die "'table' required";
 my $agent_virt_agentnum = $cgi->param('agent_virt_agentnum') || '';
 
+my $popup = $cgi->param('popup');
+my $url   = $cgi->param('url');
+my $pdest = { 'message' => "Notice sent" };
+$pdest->{'url'} = $cgi->param('url') if $url;
+
 my %search;
 if ( $cgi->param('search') ) {
   %search = %{ thaw(decode_base64($cgi->param('search'))) };
 }
 else {
   %search = $cgi->Vars;
-  delete $search{$_} for qw( action table from subject html_body text_body );
+  delete $search{$_} for qw( action table from subject html_body text_body popup url );
   # FS::$table->search is expected to know which parameters might be 
   # multi-valued, and to accept scalar values for them also.  No good 
   # solution to this since CGI can't tell whether a parameter _might_
index cc4bac6..c470094 100644 (file)
@@ -75,6 +75,15 @@ function setAll(setTo) {
     checkboxes[i].checked = setTo;
   }
 }
+function toCGIString() {
+  var out = '';
+  for (var i = 0; i < checkboxes.length; i++) {
+    if (checkboxes[i].checked) {
+      out += '&' + checkboxes[i].name + '=' + checkboxes[i].value;
+    }
+  }
+  return out;
+}
 </SCRIPT>
 <%init>
 my %opt = @_;
index 5dbece8..e5b1f47 100644 (file)
@@ -5,6 +5,7 @@ Example:
   <& elements/cust_main_dayranges.html,
                  'title'       => 'Accounts Receivable Aging Summary',
                  'range_sub'   => $mysub,
+                 'email_link'  => 1,  #adds an action column with an email link if true
   &>
 
   my $mysub = sub {
@@ -20,6 +21,7 @@ Example:
                  'query'       => $sql_query,
                  'count_query' => $count_sql,
                  'header'      => [
+                                    @act_blank,
                                     @cust_header,
                                     '0-30',
                                     '30-60',
@@ -29,6 +31,7 @@ Example:
                                     @pay_head,
                                   ],
                  'footer'      => [
+                                    @act_blank,
                                     'Total',
                                     ( map '',( 1 .. $#cust_header ),),
                                     sprintf( $money_char.'%.2f',
@@ -44,6 +47,7 @@ Example:
                                     ('') x @pay_labels,
                                   ],
                  'fields'      => [
+                                    @act_fields,
                                     FS::UI::Web::cust_fields_subs(),
                                     format_rangecol('0_30'),
                                     format_rangecol('30_60'),
@@ -53,6 +57,7 @@ Example:
                                     @pay_labels,
                                   ],
                  'links'       => [
+                                    @act_blank,
                                     ( map { $_ ne 'Cust. Status' ? $clink : '' }
                                           @cust_header
                                     ),
@@ -63,32 +68,40 @@ Example:
                                     '',
                                     @pay_links,
                                   ],
-                 'align'       => FS::UI::Web::cust_aligns(). 
+                 'align'       => $act_align.
+                                  FS::UI::Web::cust_aligns(). 
                                    'rrrrr'.
                                   ('c' x @pay_labels),
-                 'size'        => [ ( map '', @cust_header ),
+                 'size'        => [ 
+                                    @act_blank,
+                                    ( map '', @cust_header ),
                                     #'-1', '', '', '', '',  '', ],
-                                    '', '', '', '', '',  '', 
+                                    '', '', '', '', '',
                                     ( map '', @pay_labels ),
-                                    ],
-                 'style'       => [ FS::UI::Web::cust_styles(),
+                                  ],
+                 'style'       => [ 
+                                    @act_blank,
+                                    FS::UI::Web::cust_styles(),
                                     #'b', '', '', '', '', 'b', ],
                                     '', '', '', '', 'b', 
                                     ( map '', @pay_labels ),
                                     ],
-                 'xls_format'  => [ (map '', FS::UI::Web::cust_styles),
+                 'xls_format'  => [ 
+                                    @act_blank,
+                                    (map '', FS::UI::Web::cust_styles),
                                     '', '', '', '', { bold => 1 },
                                   ],
                  'color'       => [
+                                    @act_blank,
                                     FS::UI::Web::cust_colors(),
                                     '',
                                     '',
                                     '',
                                     '',
                                     '',
-                                    '',
                                     ( map '', @pay_labels ),
                                   ],
+                 'html_foot'   => $html_foot,
                %opt,
 &>
 <%init>
@@ -235,6 +248,32 @@ if($opt{'payment_links'} && $curuser->access_right('Process payment') && @payby)
                          @payby );
 }
 
+my (@act_blank, @act_fields, $act_align, $html_foot);
+if (delete($opt{'email_checkboxes'})) {
+  my $email_link = q!var url = toCGIString(); !;
+  $email_link   .= q/if (!url) { alert('No customers selected'); return false; }; /;
+  $email_link   .= q!url = '!;
+  $email_link   .= "${p}misc/email-customers.html?table=cust_main";
+  $email_link   .= q!' + url + '&popup=1&url=javascript%3Awindow.top.location.reload%28%29%3B'; !;
+  $email_link   .= include('/elements/popup_link_onclick.html',
+    'js_action' => 'url',
+    'actionlabel' => 'Send Customer Email',
+    'width' => '900',
+    'height' => '500',
+  );
+  $html_foot = include('checkbox-foot.html',
+    label   => 'Email selected customers',
+    onclick => $email_link,
+  );
+  push @act_fields, sub { 
+    my $row = shift;
+    my $custnum = $row->custnum;
+    qq!<input type="checkbox" name="custnum" value="$custnum">!;
+  };
+  $act_align = 'l';
+  push @act_blank, '';
+}
+
 </%init>
 <%once>
 
index 9c5c0e2..adbbc85 100755 (executable)
@@ -2,6 +2,7 @@
                  'title'       => emt('Accounts Receivable Aging Summary'),
                  'range_sub'   => \&balance,
                  'payment_links' => 1,
+                 'email_checkboxes' => 1,
 &>
 <%init>
 
index f760d6f..3641964 100644 (file)
         <% pkg_status_row( $cust_pkg, emt('On Hold'), '', 'color'=>'7E0079', %opt ) %>
 
 %     } else { #status: suspended
-
-        <% pkg_status_row( $cust_pkg, emt('Suspended'), 'susp', 'color'=>'FF9900', %opt ) %>
-%       my $cpr = $cust_pkg->last_cust_pkg_reason('susp');
+%       my ($cpr,$susplabel);
+%       if ($cust_pkg->is_status_delay_cancel) {
+%         $cpr = $cust_pkg->last_cust_pkg_reason('expire');
+%         $susplabel = 'Suspended (Cancelled)';
+%       } else {
+%         $cpr = $cust_pkg->last_cust_pkg_reason('susp');
+%         $susplabel = 'Suspended';
+%       }
+        <% pkg_status_row( $cust_pkg, emt($susplabel), 'susp', 'color'=>'FF9900', %opt ) %>
         <% pkg_reason_row( $cust_pkg, $cpr, 'color' => 'FF9900', %opt ) %>
 
 %     }