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;
 }
 
   $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
 
 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
 
 
 =cut
 
@@ -92,14 +104,13 @@ sub _usecompat {
   $compat->$method(@_);
 }
 
   $compat->$method(@_);
 }
 
-# needs a non _ name, called externally by config-view now (and elsewhere?)
 sub _config {
 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);
   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);
   }
     $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 $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;
 
     if $DEBUG > 1;
 
-  my $cv = $self->_config($name, $agentnum) or return;
+  my $cv = $self->_config(@_) or return;
 
   if ( wantarray ) {
     my $v = $cv->value;
 
   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.
 
 
 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 $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);
 }
 
   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.
 
 Returns true if the specified key exists, even if the corresponding value
 is undefined.
@@ -154,10 +162,10 @@ sub exists {
 
   my($name, $agentnum)=@_;
 
 
   my($name, $agentnum)=@_;
 
-  carp "FS::Conf->exists($name, $agentnum) called"
+  carp "FS::Conf->exists(". join(', ', @_). ") called"
     if $DEBUG > 1;
 
     if $DEBUG > 1;
 
-  defined($self->_config($name, $agentnum));
+  defined($self->_config(@_));
 }
 
 =item config_orbase KEY SUFFIX
 }
 
 =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.',
     '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);
 
 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;
 # 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::bill qw(bill);
 bill(%opt, 'check_freq'=>'1m' );
 
+use FS::Cron::upload qw(upload);
+upload(%opt);
+
 ###
 # subroutines
 ###
 ###
 # 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;\
        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;\
        " 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) {
 %     @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;
 %           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') ) {
                     )
           %>: <% $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
           (<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>)
 %       }
 %         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>
 </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"
 <%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 }
 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 );
                         $conf->config_items; 
 
 my @deleteable = qw( invoice_latexreturnaddress invoice_htmlreturnaddress );