reconcile breakage from stale accounts, RT#6407
authorivan <ivan>
Wed, 4 Nov 2009 01:04:35 +0000 (01:04 +0000)
committerivan <ivan>
Wed, 4 Nov 2009 01:04:35 +0000 (01:04 +0000)
FS/FS/Cron/breakage.pm
httemplate/config/config-process.cgi
httemplate/config/config-view.cgi

index 6312667..69758f9 100644 (file)
@@ -6,7 +6,7 @@ use vars qw( @EXPORT_OK );
 use FS::Conf;
 use FS::Record qw(qsearch);
 use FS::agent;
-#use FS::cust_main;
+use FS::cust_main;
 
 @EXPORT_OK = qw ( reconcile_breakage );
 
@@ -15,8 +15,7 @@ use FS::agent;
 # -l: debugging level
 
 sub reconcile_breakage {
-  return;
-  #nothing yet
+  my %opt = @_;
 
   my $conf = new FS::Conf;
 
@@ -25,14 +24,58 @@ sub reconcile_breakage {
     my $days = $conf->config('breakage-days', $agent->agentnum)
       or next;
 
-    #find customers w/a balance older than $days (and no activity since)
+    my $since = int( $^T - ($days * 86400) );
 
-    # - do a one time charge in the total amount of old unapplied payments.
-    #     'pkg' => 'Breakage', #or whatever.
-    #     'setuptax' => 'Y',
-    #     'classnum' => scalar($conf->config('breakage-pkg_class')),
-    # - use the new $cust_main->charge( 'bill_now' => 1 ) option to generate an invoice, etc.
-    # - apply_payments_and_credits
+    warn 'searching '. $agent->agent.  " for customers with unapplied payments more than $days days old\n"
+      if $opt{'v'};
+
+    #find customers w/negative balance older than $days (and no activity since)
+    # no invoices / payments (/credits/refunds?) newer than $since
+    #  (except antother breakage invoice???)
+
+    my $extra_sql = ' AND 0 > '. FS::cust_main->balance_sql;
+    $extra_sql .= " AND ". join(' AND ',
+      map {"
+            NOT EXISTS ( SELECT 1 FROM $_
+                           WHERE $_.custnum = cust_main.custnum
+                             AND _date >= $since
+                       )
+          ";}
+          qw( cust_bill cust_pay ) # cust_credit cust_refund );
+    );
+
+    my @customers = qsearch({
+      'table'     => 'cust_main',
+      'hashref'   => { 'agentnum' => $agent->agentnum,
+                       'payby'    => { op=>'!=', value=>'COMP', },
+                     },
+      'extra_sql' => $extra_sql,
+    });
+
+    #and then create a "breakage" charge & invoice for them
+
+    foreach my $cust_main ( @customers ) {
+
+      warn 'reconciling breakage for customer '. $cust_main->custnum.
+           ': '. $cust_main->name. "\n"
+        if $opt{'v'};
+
+      my $error =
+        $cust_main->charge({
+          'amount'   => sprintf('%.2f', 0 - $cust_main->balance ),
+          'pkg'      => 'Breakage',
+          'comment'  => 'breakage reconciliation',
+          'classnum' => scalar($conf->config('breakage-pkg_class')),
+          'setuptax' => 'Y',
+          'bill_now' => 1,
+        })
+        || $cust_main->apply_payments_and_credits;
+
+      if ( $error ) {
+        warn "error charging for breakage reconciliation: $error\n";
+      }
+
+    }
 
   }
 
index 50db40c..788d901 100644 (file)
@@ -1,3 +1,74 @@
+<% header('Configuration set') %>
+  <SCRIPT TYPE="text/javascript">
+%   my $n = 0;
+%   foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
+    var configCell = window.top.document.getElementById('<% $agentnum. $i->key. $n %>');
+    if ( ! configCell ) {
+      window.top.location.reload();
+    }
+    //alert('found cell ' + configCell);
+%     if (    $type eq 'textarea'
+%          || $type eq 'editlist'
+%          || $type eq 'selectmultiple' ) {
+        configCell.innerHTML =
+          '<font size="-2"><pre>' + "\n" +
+          <% encode_entities(join("\n",
+               map { length($_) > 88 ? substr($_,0,88).'...' : $_ }
+                   $conf->config($i->key, $agentnum)
+             ) )
+          |js_string %> +
+          '</pre></font>';
+
+%     } elsif ( $type eq 'checkbox' ) {
+%       if ( $conf->exists($i->key, $agentnum) ) {
+          configCell.style.backgroundColor = '#00ff00';
+          configCell.innerHTML = 'YES';
+%       } else {
+          configCell.style.backgroundColor = '#ff0000';
+          configCell.innerHTML = 'NO';
+%       }
+%     } elsif ( $type eq 'select' && $i->select_hash ) {
+%       my %hash;
+%       if ( ref($i->select_hash) eq 'ARRAY' ) {
+%         tie %hash, 'Tie::IxHash', '' => '', @{ $i->select_hash };
+%       } else {
+%         tie %hash, 'Tie::IxHash', '' => '', %{ $i->select_hash };
+%       }
+        configCell.innerHTML = <% $conf->exists($i->key, $agentnum) ? $hash{ $conf->config($i->key, $agentnum) } : '' |js_string %>;
+
+%     } elsif ( $type eq 'text' || $type eq 'select' ) {
+        configCell.innerHTML = <% $conf->exists($i->key, $agentnum) ? $conf->config($i->key, $agentnum) : '' |js_string %>;
+%     } elsif ( $type =~ /^select-(part_svc|part_pkg|pkg_class)$/ && ! $i->multiple ) {
+%       my $table = $1;
+%       my $namecol = $namecol{$table};
+%       my $pkey = dbdef->table($table)->primary_key;
+%       my $key = $conf->config($i->key, $agentnum);
+%       my $record = qsearchs($table, { $pkey => $key });
+%       my $value = $record ? "$key: ".$record->$namecol() : $key;
+        configCell.innerHTML = <% $value |js_string %>;
+%     } elsif ( $type eq 'select-sub' ) {
+        configCell.innerHTML =
+          <% $conf->config($i->key, $agentnum) |js_string %> + ': ' +
+          <% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) |js_string %>;
+%     } else {
+        //alert('unknown type <% $type %>');
+        window.top.location.reload();
+%     }
+
+%     $n++;
+%   }
+    parent.cClick();
+  </SCRIPT>
+</BODY>
+</HTML>
+<%once>
+#false laziness w/config-view.cgi
+my %namecol = (
+  'part_svc'  => 'svc',
+  'part_pkg'  => 'pkg',
+  'pkg_class' => 'classname',
+);
+</%once>
 <%init>
 die "access denied\n"
   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
@@ -65,62 +136,3 @@ $conf->touch($_, $agentnum) foreach @touch;
 $conf->delete($_, $agentnum) foreach @delete;
 
 </%init>
-<% header('Configuration set') %>
-  <SCRIPT TYPE="text/javascript">
-%   my $n = 0;
-%   foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
-    var configCell = window.top.document.getElementById('<% $agentnum. $i->key. $n %>');
-    if ( ! configCell ) {
-      window.top.location.reload();
-    }
-    //alert('found cell ' + configCell);
-%     if (    $type eq 'textarea'
-%          || $type eq 'editlist'
-%          || $type eq 'selectmultiple' ) {
-        configCell.innerHTML =
-          '<font size="-2"><pre>' + "\n" +
-          <% encode_entities(join("\n",
-               map { length($_) > 88 ? substr($_,0,88).'...' : $_ }
-                   $conf->config($i->key, $agentnum)
-             ) )
-          |js_string %> +
-          '</pre></font>';
-
-%     } elsif ( $type eq 'checkbox' ) {
-%       if ( $conf->exists($i->key, $agentnum) ) {
-          configCell.style.backgroundColor = '#00ff00';
-          configCell.innerHTML = 'YES';
-%       } else {
-          configCell.style.backgroundColor = '#ff0000';
-          configCell.innerHTML = 'NO';
-%       }
-%     } elsif ( $type eq 'select' && $i->select_hash ) {
-%       my %hash;
-%       if ( ref($i->select_hash) eq 'ARRAY' ) {
-%         tie %hash, 'Tie::IxHash', '' => '', @{ $i->select_hash };
-%       } else {
-%         tie %hash, 'Tie::IxHash', '' => '', %{ $i->select_hash };
-%       }
-        configCell.innerHTML = <% $conf->exists($i->key, $agentnum) ? $hash{ $conf->config($i->key, $agentnum) } : '' |js_string %>;
-
-%     } elsif ( $type eq 'text' || $type eq 'select' ) {
-        configCell.innerHTML = <% $conf->exists($i->key, $agentnum) ? $conf->config($i->key, $agentnum) : '' |js_string %>;
-%     } elsif ( $type =~ /^select-(part_svc|part_pkg|pkg_class)$/ && ! $i->multiple ) {
-        configCell.innerHTML =
-          <% $conf->config($i->key, $agentnum) |js_string %>
-%# + ': ' +
-%#          <% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) |js_string %>;
-%     } elsif ( $type eq 'select-sub' ) {
-        configCell.innerHTML =
-          <% $conf->config($i->key, $agentnum) |js_string %> + ': ' +
-          <% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) |js_string %>;
-%     } else {
-        //alert('unknown type <% $type %>');
-        window.top.location.reload();
-%     }
-
-%     $n++;
-%   }
-    parent.cClick();
-  </SCRIPT>
-  </BODY></HTML>
index 856a2ea..13286cf 100644 (file)
@@ -210,13 +210,21 @@ Click on a configuration value to change it.
             </tr>
 
 %   } elsif ( $type =~ /^select-(part_svc|part_pkg|pkg_class)$/ ) {
+%
+%     my $table = $1;
+%     my $namecol = $namecol{$table};
+%     my $pkey = dbdef->table($table)->primary_key;
+%
 %     my @keys = $conf->config($i->key, $agentnum);
 
             <tr>
               <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
-                <% join('<BR>', map { $_ # ': '. $svc, $pkg, whatever
-                                    }
-                                    @keys
+                <% join( '<BR>',
+                         map {
+                           my $key = $_;
+                           my $record = qsearchs($table, { $pkey => $key });
+                           $record ? "$key: ".$record->$namecol() : $key;
+                         } @keys
                        )
                 %>
               </td>
@@ -301,6 +309,14 @@ Click on a configuration value to change it.
 </SCRIPT>
 
 </body></html>
+<%once>
+#false laziness w/config-process.cgi
+my %namecol = (
+  'part_svc'  => 'svc',
+  'part_pkg'  => 'pkg',
+  'pkg_class' => 'classname',
+);
+</%once>
 <%init>
 
 die "access denied"
@@ -343,6 +359,5 @@ my @all_agents = ();
 if ( $cgi->param('showagent') ) {
   @all_agents = qsearch('agent', { 'disabled' => '' } );
 }
-warn 'all agents: '. join('-', @all_agents);
 
 </%init>