internalize billco-upload and automate the transfer to the provider RT#5902
authorjeff <jeff>
Wed, 12 Aug 2009 14:58:50 +0000 (14:58 +0000)
committerjeff <jeff>
Wed, 12 Aug 2009 14:58:50 +0000 (14:58 +0000)
FS/FS/Conf.pm
FS/FS/Cron/upload.pm [new file with mode: 0644]
FS/bin/freeside-daily
FS/bin/freeside-monthly
Makefile
bin/billco-upload [deleted file]
httemplate/config/config-view.cgi

index 1da5583..66d8be9 100644 (file)
@@ -76,11 +76,23 @@ sub base_dir {
   $1;
 }
 
-=item config KEY [ AGENTNUM ]
+=item conf KEY [ AGENTNUM [ NODEFAULT ] ]
+
+Returns the L<FS::conf> record for the key and agent.
+
+=cut
+
+sub conf {
+  my $self = shift;
+  $self->_config(@_);
+}
+
+=item config KEY [ AGENTNUM [ NODEFAULT ] ]
 
 Returns the configuration value or values (depending on context) for key.
 The optional agent number selects an agent specific value instead of the
-global default if one is present.
+global default if one is present.  If NODEFAULT is true only the agent
+specific value(s) is returned.
 
 =cut
 
@@ -92,14 +104,13 @@ sub _usecompat {
   $compat->$method(@_);
 }
 
-# needs a non _ name, called externally by config-view now (and elsewhere?)
 sub _config {
-  my($self,$name,$agentnum)=@_;
+  my($self,$name,$agentnum,$agentonly)=@_;
   my $hashref = { 'name' => $name };
   $hashref->{agentnum} = $agentnum;
   local $FS::Record::conf = undef;  # XXX evil hack prevents recursion
   my $cv = FS::Record::qsearchs('conf', $hashref);
-  if (!$cv && defined($agentnum) && $agentnum) {
+  if (!$agentonly && !$cv && defined($agentnum) && $agentnum) {
     $hashref->{agentnum} = '';
     $cv = FS::Record::qsearchs('conf', $hashref);
   }
@@ -110,12 +121,10 @@ sub config {
   my $self = shift;
   return $self->_usecompat('config', @_) if use_confcompat;
 
-  my($name, $agentnum)=@_;
-
-  carp "FS::Conf->config($name, $agentnum) called"
+  carp "FS::Conf->config(". join(', ', @_). ") called"
     if $DEBUG > 1;
 
-  my $cv = $self->_config($name, $agentnum) or return;
+  my $cv = $self->_config(@_) or return;
 
   if ( wantarray ) {
     my $v = $cv->value;
@@ -126,7 +135,7 @@ sub config {
   }
 }
 
-=item config_binary KEY [ AGENTNUM ]
+=item config_binary KEY [ AGENTNUM [ NODEFAULT ] ]
 
 Returns the exact scalar value for key.
 
@@ -136,12 +145,11 @@ sub config_binary {
   my $self = shift;
   return $self->_usecompat('config_binary', @_) if use_confcompat;
 
-  my($name,$agentnum)=@_;
-  my $cv = $self->_config($name, $agentnum) or return;
+  my $cv = $self->_config(@_) or return;
   decode_base64($cv->value);
 }
 
-=item exists KEY [ AGENTNUM ]
+=item exists KEY [ AGENTNUM [ NODEFAULT ] ]
 
 Returns true if the specified key exists, even if the corresponding value
 is undefined.
@@ -154,10 +162,10 @@ sub exists {
 
   my($name, $agentnum)=@_;
 
-  carp "FS::Conf->exists($name, $agentnum) called"
+  carp "FS::Conf->exists(". join(', ', @_). ") called"
     if $DEBUG > 1;
 
-  defined($self->_config($name, $agentnum));
+  defined($self->_config(@_));
 }
 
 =item config_orbase KEY SUFFIX
@@ -620,6 +628,40 @@ worry that config_items is freeside-specific and icky.
   },
 
   {
+    'key'         => 'billco-url',
+    'section'     => 'billing',
+    'description' => 'The url to use for performing uploads to the invoice mailing service.',
+    'type'        => 'text',
+    'per_agent'   => 1,
+  },
+
+  {
+    'key'         => 'billco-username',
+    'section'     => 'billing',
+    'description' => 'The login name to use for uploads to the invoice mailing service.',
+    'type'        => 'text',
+    'per_agent'   => 1,
+    'agentonly'   => 1,
+  },
+
+  {
+    'key'         => 'billco-password',
+    'section'     => 'billing',
+    'description' => 'The password to use for uploads to the invoice mailing service.',
+    'type'        => 'text',
+    'per_agent'   => 1,
+    'agentonly'   => 1,
+  },
+
+  {
+    'key'         => 'billco-clicode',
+    'section'     => 'billing',
+    'description' => 'The clicode to use for uploads to the invoice mailing service.',
+    'type'        => 'text',
+    'per_agent'   => 1,
+  },
+
+  {
     'key'         => 'business-onlinepayment',
     'section'     => 'billing',
     'description' => '<a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support, at least three lines: processor, login, and password.  An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\').    Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
diff --git a/FS/FS/Cron/upload.pm b/FS/FS/Cron/upload.pm
new file mode 100644 (file)
index 0000000..fea3d2c
--- /dev/null
@@ -0,0 +1,176 @@
+package FS::Cron::upload;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $me $DEBUG );
+use Exporter;
+use Date::Format;
+use FS::UID qw(dbh);
+use FS::Record qw( qsearch qsearchs );
+use FS::Conf;
+use FS::queue;
+use FS::agent;
+use LWP::UserAgent;
+use HTTP::Request;
+use HTTP::Request::Common;
+use HTTP::Response;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw ( upload );
+$DEBUG = 0;
+$me = '[FS::Cron::upload]';
+
+#freeside-daily %opt:
+#  -v: enable debugging
+#  -l: debugging level
+#  -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing.
+#  -r: Multi-process mode dry run option
+#  -a: Only process customers with the specified agentnum
+
+
+sub upload {
+  my %opt = @_;
+
+  my $debug = 0;
+  $debug = 1 if $opt{'v'};
+  $debug = $opt{'l'} if $opt{'l'};
+
+  local $DEBUG = $debug if $debug;
+
+  warn "$me upload called\n" if $DEBUG;
+
+  my $conf = new FS::Conf;
+  my @agent = grep { $conf->config( 'billco-username', $_->agentnum, 1 ) }
+              grep { $conf->config( 'billco-password', $_->agentnum, 1 ) }
+              qsearch( 'agent', {} );
+
+  my $date =  time2str('%Y%m%d%H%M%S', $^T); # more?
+
+  @agent = grep { $_ == $opt{'a'} } @agent if $opt{'a'};
+
+  foreach my $agent ( @agent ) {
+
+    my $agentnum = $agent->agentnum;
+
+    if ( $opt{'m'} ) {
+
+      if ( $opt{'r'} ) {
+        warn "DRY RUN: would add agent $agentnum for queued upload\n";
+      } else {
+
+        my $queue = new FS::queue {
+          'job'      => 'FS::Cron::upload::billco_upload',
+        };
+        my $error = $queue->insert(
+                                    'agentnum' => $agentnum,
+                                    'date'     => $date,
+                                    'l'        => $opt{'l'} || '',
+                                    'm'        => $opt{'m'} || '',
+                                    'v'        => $opt{'v'} || '',
+                                  );
+
+      }
+
+    } else {
+
+      eval "&billco_upload( 'agentnum' => $agentnum, 'date' => $date );";
+      warn "billco_upload failed: $@\n"
+        if ( $@ );
+
+    }
+
+  }
+
+}
+
+sub billco_upload {
+  my %opt = @_;
+
+  warn "$me billco_upload called\n" if $DEBUG;
+  my $conf = new FS::Conf;
+  my $dir = '%%%FREESIDE_EXPORT%%%/export.'. $FS::UID::datasrc. '/cust_bill';
+
+  my $agentnum = $opt{agentnum} or die "no agentnum provided\n";
+  my $url      = $conf->config( 'billco-url', $agentnum )
+    or die "no url for agent $agentnum\n";
+  my $username = $conf->config( 'billco-username', $agentnum, 1 )
+    or die "no username for agent $agentnum\n";
+  my $password = $conf->config( 'billco-password', $agentnum, 1 )
+    or die "no password for agent $agentnum\n";
+  my $clicode  = $conf->config( 'billco-clicode', $agentnum )
+    or die "no clicode for agent $agentnum\n";
+
+  die "no date provided\n" unless $opt{date};
+  my $zipfile  = "$dir/agentnum$agentnum-$opt{date}.zip";
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $agent = qsearchs( 'agent', { agentnum => $agentnum } )
+    or die "no such agent: $agentnum";
+  $agent->select_for_update; #mutex 
+
+  unless ( -f "$dir/agentnum$agentnum-header.csv" ||
+           -f "$dir/agentnum$agentnum-detail.csv" )
+  {
+    warn "$me neither $dir/agentnum$agentnum-header.csv nor ".
+         "$dir/agentnum$agentnum-detail.csv found\n" if $DEBUG;
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    return;
+  }
+
+  # a better way?
+  if ($opt{m}) {
+    my $sql = "SELECT count(*) FROM queue LEFT JOIN cust_main USING(custnum) ".
+      "WHERE queue.job='FS::cust_main::queued_bill' AND cust_main.agentnum = ?";
+    my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+    while (1) {
+      $sth->execute( $agentnum )
+        or die "Unexpected error executing statement $sql: ". $sth->errstr;
+      last if $sth->fetchow_arrayref->[0];
+      sleep 300;
+    }
+  }
+
+  foreach ( qw ( header detail ) ) {
+    rename "$dir/agentnum$agentnum-$_.csv",
+           "$dir/agentnum$agentnum-$opt{date}-$_.csv";
+  }
+
+  my $command = "cd $dir; zip $zipfile ".
+                "agentnum$agentnum-$opt{date}-header.csv ".
+                "agentnum$agentnum-$opt{date}-detail.csv";
+
+  system($command) and die "$command failed\n";
+
+  unlink "agentnum$agentnum-$opt{date}-header.csv",
+         "agentnum$agentnum-$opt{date}-detail.csv";
+
+  my $ua = new LWP::UserAgent;
+  my $res = $ua->request( POST( $url,
+                                'Content_Type' => 'form-data',
+                                'Content' => [ 'username' => $username,
+                                               'pass'     => $password,
+                                               'custid'   => $username,
+                                               'clicode'  => $clicode,
+                                               'file1'    => [ $zipfile ],
+                                             ],
+                              )
+                        );
+
+  die "upload failed: ". $res->status_line. "\n"
+    unless $res->is_success;
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+1;
index 119f93a..728fa96 100755 (executable)
@@ -15,6 +15,10 @@ adminsuidsetup $user;
 use FS::Cron::bill qw(bill);
 bill(%opt);
 
+#you can skip this just by not having the config
+use FS::Cron::upload qw(upload);
+upload(%opt);
+
 # Send alerts about upcoming credit card expiration.
 use FS::Cron::alert_expiration qw(alert_expiration);
 my $conf = new FS::Conf;
index 1e41b78..a81e3e9 100755 (executable)
@@ -15,6 +15,9 @@ adminsuidsetup $user;
 use FS::Cron::bill qw(bill);
 bill(%opt, 'check_freq'=>'1m' );
 
+use FS::Cron::upload qw(upload);
+upload(%opt);
+
 ###
 # subroutines
 ###
index 35d9044..79135e7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -200,6 +200,7 @@ perl-modules:
        perl -p -i -e "\
          s/%%%SELFSERVICE_USER%%%/${SELFSERVICE_USER}/g;\
          s/%%%SELFSERVICE_MACHINES%%%/${SELFSERVICE_MACHINES}/g;\
+         s|%%%FREESIDE_EXPORT%%%|${FREESIDE_EXPORT}|g;\
        " blib/lib/FS/Cron/*.pm;\
        perl -p -i -e "\
          s|%%%FREESIDE_EXPORT%%%|${FREESIDE_EXPORT}|g;\
diff --git a/bin/billco-upload b/bin/billco-upload
deleted file mode 100644 (file)
index 3a02ec8..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/sh
-
-AGENTNUMS="1 2 3 5 8 9 10"
-
-date=`date +"%Y%m%d"`
-dir="/usr/local/etc/freeside/export.DBI:Pg:dbname=freeside/cust_bill"
-lock=".billco-upload.lock"
-cd "$dir"
-
-failed_mutex()
-{
-  echo "billco-upload already running; exiting"
-  exit 1
-}
-
-#acquire mutex
-[ -f $lock ] && {
-  failed_mutex
-} || {
-  echo $$ > $lock
-  [ $(cat $lock 2>/dev/null) -eq $$ ] || failed_mutex
-}
-
-for AGENTNUM in $AGENTNUMS; do
-
-  for a in header detail; do
-    mv agentnum$AGENTNUM-$a.csv agentnum$AGENTNUM-$date-$a.csv
-  done
-
-  zip agentnum$AGENTNUM-$date.zip agentnum$AGENTNUM-$date-header.csv agentnum$AGENTNUM-$date-detail.csv
-
-# Remove if trying to find problems with billco upload files
-  rm *$AGENTNUM-$date*.csv
-
-  echo $dir/agentnum$AGENTNUM-$date.zip
-
-done
-
-#release mutex
-rm -f $lock
index 9e9e64e..0f6c992 100644 (file)
@@ -66,7 +66,7 @@ Click on a configuration value to change it.
 %     @agents = ( '' );
 %     if ( $i->per_agent ) {
 %       foreach my $agent (@all_agents) {
-%         if ( defined(_config_agentonly($conf, $i->key, $agent->agentnum)) ) {
+%         if ( defined($conf->conf( $i->key, $agent->agentnum, 1 ) ) ) {
 %           push @agents, $agent;
 %         } else {
 %           push @add_agents, $agent;
@@ -99,15 +99,14 @@ Click on a configuration value to change it.
                     )
           %>: <% $i->description %>
 %       if ( $agent && $cgi->param('showagent') ) {
-%         my $confnum =
-%           _config_agentonly($conf, $i->key, $agent->agentnum)->confnum;
+%         my $confnum = $conf->conf( $i->key, $agent->agentnum, 1 )->confnum;
           (<A HREF="javascript:areyousure('delete this agent override', 'config-delete.cgi?confnum=<% $confnum %>;redirect=config_view_showagent')">delete agent override</A>)
 %       } elsif ( $i->base_key
 %                 || ( $deleteable{$i->key} && $conf->exists($i->key) ) ) {
 %         my $confnum =
 %           $agent
-%             ? _config_agentonly($conf, $i->key, $agent->agentnum)->confnum
-%             : $conf->_config( $i->key )->confnum;
+%             ? $conf->conf( $i->key, $agent->agentnum, 1 )->confnum
+%             : $conf->conf( $i->key )->confnum;
 %         my $showagent = $cgi->param('showagent') ? '_showagent' : '';
           (<A HREF="javascript:areyousure('delete this configuration item', 'config-delete.cgi?confnum=<% $confnum %>;redirect=config_view<%$showagent%>')">delete configuration item</A>)
 %       }
@@ -286,18 +285,6 @@ Click on a configuration value to change it.
 </SCRIPT>
 
 </body></html>
-<%once>
-
-#should probably be a Conf method.  what else would need to use it?
-sub _config_agentonly {
-  my($self,$name,$agentnum)=@_;
-  my $hashref = { 'name' => $name };
-  $hashref->{agentnum} = $agentnum;
-  local $FS::Record::conf = undef;  # XXX evil hack prevents recursion
-  FS::Record::qsearchs('conf', $hashref);
-}
-
-</%once>
 <%init>
 
 die "access denied"
@@ -320,6 +307,7 @@ if ($cgi->param('agentnum') =~ /^(\d+)$/) {
 my $conf = new FS::Conf;
  
 my @config_items = grep { $page_agent ? $_->per_agent : 1 }
+                   grep { $page_agent ? 1 : !$_->agentonly }
                         $conf->config_items; 
 
 my @deleteable = qw( invoice_latexreturnaddress invoice_htmlreturnaddress );