From 56a2965996454a0649d43ecbc062beda61106e21 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 12 Aug 2009 14:58:50 +0000 Subject: [PATCH] internalize billco-upload and automate the transfer to the provider RT#5902 --- FS/FS/Conf.pm | 72 ++++++++++++---- FS/FS/Cron/upload.pm | 176 ++++++++++++++++++++++++++++++++++++++ FS/bin/freeside-daily | 4 + FS/bin/freeside-monthly | 3 + Makefile | 1 + bin/billco-upload | 40 --------- httemplate/config/config-view.cgi | 22 ++--- 7 files changed, 246 insertions(+), 72 deletions(-) create mode 100644 FS/FS/Cron/upload.pm delete mode 100644 bin/billco-upload diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 1da55837c..66d8be903 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -76,11 +76,23 @@ sub base_dir { $1; } -=item config KEY [ AGENTNUM ] +=item conf KEY [ AGENTNUM [ NODEFAULT ] ] + +Returns the L 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' => 'Business::OnlinePayment 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 index 000000000..fea3d2cc7 --- /dev/null +++ b/FS/FS/Cron/upload.pm @@ -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; diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily index 119f93a59..728fa969a 100755 --- a/FS/bin/freeside-daily +++ b/FS/bin/freeside-daily @@ -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; diff --git a/FS/bin/freeside-monthly b/FS/bin/freeside-monthly index 1e41b780e..a81e3e9ed 100755 --- a/FS/bin/freeside-monthly +++ b/FS/bin/freeside-monthly @@ -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 ### diff --git a/Makefile b/Makefile index 35d904496..79135e7a7 100644 --- 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 index 3a02ec8bb..000000000 --- a/bin/billco-upload +++ /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 diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi index 9e9e64eb2..0f6c99232 100644 --- a/httemplate/config/config-view.cgi +++ b/httemplate/config/config-view.cgi @@ -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; (delete agent override) % } 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' : ''; (delete configuration item) % } @@ -286,18 +285,6 @@ Click on a configuration value to change it. -<%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); -} - - <%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 ); -- 2.11.0