diff options
Diffstat (limited to 'FS/bin')
-rw-r--r-- | FS/bin/freeside-adduser | 56 | ||||
-rwxr-xr-x | FS/bin/freeside-cdr-sftp_and_import | 63 | ||||
-rw-r--r-- | FS/bin/freeside-cdrrated | 16 | ||||
-rw-r--r-- | FS/bin/freeside-cdrrewrited | 60 | ||||
-rw-r--r-- | FS/bin/freeside-censustract-update | 52 | ||||
-rwxr-xr-x | FS/bin/freeside-daily | 13 | ||||
-rw-r--r-- | FS/bin/freeside-deluser | 64 | ||||
-rwxr-xr-x | FS/bin/freeside-eftca-download | 11 | ||||
-rwxr-xr-x | FS/bin/freeside-email | 2 | ||||
-rwxr-xr-x | FS/bin/freeside-fetch | 2 | ||||
-rw-r--r-- | FS/bin/freeside-ipifony-download | 320 | ||||
-rwxr-xr-x | FS/bin/freeside-monthly | 6 | ||||
-rwxr-xr-x | FS/bin/freeside-phonenum_list | 86 | ||||
-rw-r--r-- | FS/bin/freeside-queued | 14 | ||||
-rw-r--r-- | FS/bin/freeside-selfservice-server | 29 | ||||
-rwxr-xr-x | FS/bin/freeside-selfservice-xmlrpcd | 4 | ||||
-rwxr-xr-x | FS/bin/freeside-setup | 2 | ||||
-rwxr-xr-x | FS/bin/freeside-upgrade | 4 | ||||
-rwxr-xr-x | FS/bin/freeside-username_list | 84 | ||||
-rwxr-xr-x | FS/bin/freeside-void-payments | 13 | ||||
-rwxr-xr-x | FS/bin/freeside-wkhtmltopdf | 8 |
21 files changed, 674 insertions, 235 deletions
diff --git a/FS/bin/freeside-adduser b/FS/bin/freeside-adduser index 530481377..6bfb759f8 100644 --- a/FS/bin/freeside-adduser +++ b/FS/bin/freeside-adduser @@ -7,46 +7,9 @@ use Getopt::Std; my $FREESIDE_CONF = "%%%FREESIDE_CONF%%%"; -getopts("s:g:n"); +getopts("g:"); my $user = shift or die &usage; -if ( $opt_s ) { - - #if ( -e "$FREESIDE_CONF/mapsecrets" ) { - # open(MAPSECRETS,"<$FREESIDE_CONF/mapsecrets") - # or die "can't open $FREESIDE_CONF/mapsecrets: $!"; - # while (<MAPSECRETS>) { - # /^(\S+) / or die "unparsable line in mapsecrets: $_"; - # die "user $user already exists\n" if $user eq $1; - # } - # close MAPSECRETS; - #} - - #insert new entry before a wildcard... - open(MAPSECRETS,"<$FREESIDE_CONF/mapsecrets") - and flock(MAPSECRETS,LOCK_EX) - or die "can't open $FREESIDE_CONF/mapsecrets: $!"; - open(NEW,">$FREESIDE_CONF/mapsecrets.new") - or die "can't open $FREESIDE_CONF/mapsecrets.new: $!"; - while(<MAPSECRETS>) { - if ( /^\*\s/ ) { - print NEW "$user $opt_s\n"; - } - print NEW $_; - } - close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!"; - close NEW or die "can't close $FREESIDE_CONF/mapsecrets.new: $!"; - rename("$FREESIDE_CONF/mapsecrets.new", "$FREESIDE_CONF/mapsecrets") - or die "can't move mapsecrets.new into place: $!"; - -} - -### - -exit if $opt_n; - -### - use FS::UID qw(adminsuidsetup); use FS::CurrentUser; use FS::access_user; @@ -58,7 +21,7 @@ adminsuidsetup $user; my $access_user = new FS::access_user { 'username' => $user, - '_password' => 'notyet', + '_password' => '', 'first' => 'Firstname', # $opt_f || 'last' => 'Lastname', # $opt_l || }; @@ -79,7 +42,7 @@ if ( $opt_g ) { ### sub usage { - die "Usage:\n\n freeside-adduser [ -n ] [ -s ] [ -g groupnum ] username [ password ]" + die "Usage:\n\n freeside-adduser [ -g groupnum ] username [ password ]" } =head1 NAME @@ -88,7 +51,7 @@ freeside-adduser - Command line interface to add (freeside) users. =head1 SYNOPSIS - freeside-adduser [ -n ] [ -s ] [ -g groupnum ] username [ password ] + freeside-adduser [ -g groupnum ] username [ password ] =head1 DESCRIPTION @@ -100,17 +63,6 @@ B<Configuration | Employees | View/Edit employees>. -g: initial groupnum - Development/multi-DB options: - - -s: alternate secrets file - - -n: no ACL added, for bootstrapping - -=head1 NOTE - -No explicit htpasswd options are available in 1.7 - passwords are now -maintained automatically. - =head1 SEE ALSO Base Freeside documentation diff --git a/FS/bin/freeside-cdr-sftp_and_import b/FS/bin/freeside-cdr-sftp_and_import index 7f2693fcb..aa1b3942c 100755 --- a/FS/bin/freeside-cdr-sftp_and_import +++ b/FS/bin/freeside-cdr-sftp_and_import @@ -12,8 +12,8 @@ use FS::cdr; # parse command line ### -use vars qw( $opt_m $opt_p $opt_r $opt_e $opt_d $opt_v $opt_P $opt_a $opt_c $opt_g ); -getopts('c:m:p:r:e:d:v:P:ag'); +use vars qw( $opt_m $opt_p $opt_r $opt_e $opt_d $opt_v $opt_P $opt_a $opt_c $opt_g $opt_s $opt_b ); +getopts('c:m:p:r:e:d:v:P:agsb'); $opt_e ||= 'csv'; #$opt_e = ".$opt_e" unless $opt_e =~ /^\./; @@ -116,31 +116,39 @@ foreach my $filename ( @$ls ) { $import_options->{'cdrtypenum'} = $opt_c if $opt_c; my $error = FS::cdr::batch_import($import_options); + if ( $error ) { - unlink "$cachedir/$filename"; - unlink "$cachedir/$ungziped" if $opt_g; - die $error; - } - if ( $opt_d ) { - if($opt_m eq 'ftp') { - my $ftp = ftp(); - $ftp->rename($filename, "$opt_d/$file_timestamp") - or do { - unlink "$cachedir/$filename"; - unlink "$cachedir/$ungziped" if $opt_g; - die "Can't move $filename to $opt_d: ".$ftp->message . "\n"; - }; + if ( $opt_s ) { + warn "$ungziped: $error\n"; + } else { + unlink "$cachedir/$filename"; + unlink "$cachedir/$ungziped" if $opt_g; + die $error; } - else { - my $sftp = sftp(); - $sftp->rename($filename, "$opt_d/$file_timestamp") - or do { - unlink "$cachedir/$filename"; - unlink "$cachedir/$ungziped" if $opt_g; - die "can't move $filename to $opt_d: ". $sftp->error . "\n"; - }; + + } else { + + if ( $opt_d ) { + if ( $opt_m eq 'ftp') { + my $ftp = ftp(); + $ftp->rename($filename, "$opt_d/$file_timestamp") + or do { + unlink "$cachedir/$filename"; + unlink "$cachedir/$ungziped" if $opt_g; + die "Can't move $filename to $opt_d: ".$ftp->message . "\n"; + }; + } else { + my $sftp = sftp(); + $sftp->rename($filename, "$opt_d/$file_timestamp") + or do { + unlink "$cachedir/$filename"; + unlink "$cachedir/$ungziped" if $opt_g; + die "can't move $filename to $opt_d: ". $sftp->error . "\n"; + }; + } } + } unlink "$cachedir/$filename"; @@ -168,6 +176,7 @@ sub ftp { or die "FTP connection to '$hostname' failed."; $ftp->login($ftp_user, $ftp_pass) or die "FTP login failed: ".$ftp->message; $ftp->cwd($opt_r) or die "can't chdir to $opt_r\n" if $opt_r; + $ftp->binary or die "can't set BINARY mode: ". $ftp->message if $opt_b; return $ftp; } @@ -192,7 +201,7 @@ freeside-cdr-sftp_and_import - Download CDR files from a remote server via SFTP cdr.sftp_and_import [ -m method ] [ -p prefix ] [ -e extension ] [ -r remotefolder ] [ -d donefolder ] [ -v level ] [ -P port ] - [ -a ] [ -c cdrtypenum ] user format [sftpuser@]servername + [ -a ] [ -g ] [ -s ] [ -c cdrtypenum ] user format [sftpuser@]servername =head1 DESCRIPTION @@ -213,11 +222,17 @@ or FTP and then import them into the database. -a: use ftp passive mode +-b: use ftp binary mode + -v: set verbosity level; this script only has one level, but it will be passed as the 'debug' argument to the transport method -c: cdrtypenum to set, defaults to none +-g: File is gzipped + +-s: Warn and skip files which could not be imported rather than abort + user: freeside username format: CDR format name diff --git a/FS/bin/freeside-cdrrated b/FS/bin/freeside-cdrrated index 131b56a7e..99ea67594 100644 --- a/FS/bin/freeside-cdrrated +++ b/FS/bin/freeside-cdrrated @@ -33,9 +33,11 @@ if ( @cdrtypenums ) { $extra_sql .= ' AND cdrtypenum IN ('. join(',', @cdrtypenums ). ')'; } -our %svcnum = (); -our %pkgpart = (); -our %part_pkg = (); +our %svcnum = (); # phonenum => svcnum +our %pkgnum = (); # phonenum => pkgnum +our %cust_pkg = (); # pkgnum => cust_pkg (NOT phonenum => cust_pkg!) +our %pkgpart = (); # phonenum => pkgpart +our %part_pkg = (); # phonenum => part_pkg #some false laziness w/freeside-cdrrewrited @@ -91,6 +93,9 @@ while (1) { next; } + $pkgnum{$number} = $cust_pkg->pkgnum; + $cust_pkg{$cust_pkg->pkgnum} ||= $cust_pkg; + #get the package, search through the part_pkg and linked for a voip_cdr def w/matching cdrtypenum (or no use_cdrtypenum) my @part_pkg = grep { $_->plan eq 'voip_cdr' @@ -126,10 +131,11 @@ while (1) { #} #XXX if $part_pkg->option('min_included') then we can't prerate this CDR - + my $error = $cdr->rate( 'part_pkg' => $part_pkg{ $pkgpart{$number} }, - 'svcnum' => $svcnum{ $number }, + 'cust_pkg' => $cust_pkg{ $pkgnum{$number} }, + 'svcnum' => $svcnum{$number}, ); if ( $error ) { #XXX ??? diff --git a/FS/bin/freeside-cdrrewrited b/FS/bin/freeside-cdrrewrited index f2c3926fb..16f931fbf 100644 --- a/FS/bin/freeside-cdrrewrited +++ b/FS/bin/freeside-cdrrewrited @@ -30,9 +30,9 @@ die "not running; cdr-asterisk_forward_rewrite, cdr-charged_party_rewrite ". #-- -my %accountcode_unmatch = (); -my $accountcode_retry = 4 * 60 * 60; # 4 hours -my $accountcode_giveup = 4 * 24 * 60 * 60; # 4 days +my %sessionnum_unmatch = (); +my $sessionnum_retry = 4 * 60 * 60; # 4 hours +my $sessionnum_giveup = 4 * 24 * 60 * 60; # 4 days my %cdr_type = map { lc($_->cdrtypename) => $_->cdrtypenum } qsearch('cdr_type',{}); @@ -45,8 +45,8 @@ while (1) { # instead of just doing this search like normal CDRs #hmm :/ - my @recent = grep { ($accountcode_unmatch{$_} + $accountcode_retry) > time } - keys %accountcode_unmatch; + my @recent = grep { ($sessionnum_unmatch{$_} + $sessionnum_retry) > time } + keys %sessionnum_unmatch; my $extra_sql = scalar(@recent) ? ' AND acctid NOT IN ('. join(',', @recent). ') ' : ''; @@ -136,45 +136,62 @@ while (1) { } - if ( $conf->exists('cdr-taqua-accountcode_rewrite') - && $cdr->lastapp eq 'acctcode' && $cdr->cdrtypenum == 1 + if ( $cdr->cdrtypenum == 1 + and $cdr->lastapp + and ( + $conf->exists('cdr-taqua-accountcode_rewrite') or + $conf->exists('cdr-taqua-callerid_rewrite') ) ) { #find the matching CDR - my $primary = qsearchs('cdr', { - 'sessionnum' => $cdr->sessionnum, - 'src' => $cdr->subscriber, - #'accountcode' => '', - }); + my %search = ( 'sessionnum' => $cdr->sessionnum ); + if ( $cdr->lastapp eq 'acctcode' ) { + $search{'src'} = $cdr->subscriber; + } elsif ( $cdr->lastapp eq 'CallerId' ) { + $search{'dst'} = $cdr->subscriber; + } + my $primary = qsearchs('cdr', \%search); unless ( $primary ) { my $cantfind = "can't find primary CDR with session ". $cdr->sessionnum. ", src ". $cdr->subscriber; - if ( $cdr->calldate_unix + $accountcode_giveup < time ) { + if ( $cdr->calldate_unix + $sessionnum_giveup < time ) { warn "ERROR: $cantfind; giving up\n"; - push @status, 'taqua-accountcode-NOTFOUND'; + push @status, 'taqua-sessionnum-NOTFOUND'; $cdr->status('done'); #so it doesn't try to rate - delete $accountcode_unmatch{$cdr->acctid}; #so it doesn't suck mem + delete $sessionnum_unmatch{$cdr->acctid}; #so it doesn't suck mem } else { warn "WARNING: $cantfind; will keep trying\n"; - $accountcode_unmatch{$cdr->acctid} = time; + $sessionnum_unmatch{$cdr->acctid} = time; next; } } else { - $primary->accountcode( $cdr->lastdata ); + if ( $cdr->lastapp eq 'acctcode' ) { + # lastdata contains the dialed account code + $primary->accountcode( $cdr->lastdata ); + push @status, 'taqua-accountcode'; + } elsif ( $cdr->lastapp eq 'CallerId' ) { + # lastdata contains "allowed" or "restricted" + # or case variants thereof + if ( lc($cdr->lastdata) eq 'restricted' ) { + $primary->clid( 'PRIVATE' ); + } + push @status, 'taqua-callerid'; + } else { + warn "unknown Taqua service name: ".$cdr->lastapp."\n"; + } #$primary->freesiderewritestatus( 'taqua-accountcode-primary' ); - my $error = $primary->replace; + my $error = $primary->replace if $primary->modified; if ( $error ) { warn "WARNING: error rewriting primary CDR (will retry): $error\n"; next; } $skip{$primary->acctid} = 1; - push @status, 'taqua-accountcode'; $cdr->status('done'); #so it doesn't try to rate } @@ -214,7 +231,10 @@ sub _shouldrun { $conf->exists('cdr-asterisk_forward_rewrite') || $conf->exists('cdr-asterisk_australia_rewrite') || $conf->exists('cdr-charged_party_rewrite') - || $conf->exists('cdr-taqua-accountcode_rewrite'); + || $conf->exists('cdr-taqua-accountcode_rewrite') + || $conf->exists('cdr-taqua-callerid_rewrite') + || 0 + ; } sub usage { diff --git a/FS/bin/freeside-censustract-update b/FS/bin/freeside-censustract-update index 8c6721b3e..af9ad749b 100644 --- a/FS/bin/freeside-censustract-update +++ b/FS/bin/freeside-censustract-update @@ -6,8 +6,8 @@ use Date::Parse 'str2time'; use FS::UID qw(adminsuidsetup); use FS::Record qw(qsearch dbh); use FS::Conf; -use FS::cust_main; -use FS::h_cust_main; +use FS::cust_location; +use FS::h_cust_location; my %opt; getopts('d:', \%opt); @@ -22,40 +22,48 @@ my $current_year = $conf->config('census_year') or die "No current census year configured.\n"; my $date = str2time($opt{d}) if $opt{d}; $date ||= time; -my %h_cust_main = map { $_->custnum => $_ } +# This now operates on cust_location, not cust_main. +# Find all locations that, as of $date, did not have +# censusyear = the current year. This includes those +# that have no censusyear. +my %h_cust_location = map { $_->locationnum => $_ } qsearch( - 'h_cust_main', + 'h_cust_location', { censusyear => { op => '!=', value => $current_year } }, - FS::h_cust_main->sql_h_search($date), - ) ; #the state of these customers as of $date + FS::h_cust_location->sql_h_search($date), + ) ; -my @cust_main = qsearch( 'cust_main', +# Find all locations that don't have censusyear = the current +# year as of now. +my @cust_location = qsearch( 'cust_location', { censusyear => { op => '!=', value => $current_year } }, -); # all possibly interesting customers +); -warn scalar(@cust_main)." records found.\n"; +warn scalar(@cust_location)." records found.\n"; my $queued = 0; my $updated = 0; -foreach my $cust_main (@cust_main) { +foreach my $cust_location (@cust_location) { my $error; - my $h = $h_cust_main{$cust_main->custnum}; - if ( defined($h) and $h->censustract eq $cust_main->censustract ) { - # the tract code hasn't been changed since $date - # so update it now + my $h = $h_cust_location{$cust_location->locationnum}; + if ( defined($h) and $h->censustract eq $cust_location->censustract ) { + # Then the location's censustract hasn't been changed since $date + # (or it didn't exist on $date, or $date is now). Queue a censustract + # update for it. my $job = FS::queue->new({ - job => 'FS::cust_main::process_censustract_update' + job => 'FS::cust_location::process_censustract_update' }); - $error = $job->insert($cust_main->custnum); + $error = $job->insert($cust_location->locationnum); $queued++; } - elsif ($cust_main->censusyear eq '') { - # the tract number is assumed current, so just set the year - $cust_main->set('censusyear', $current_year); - $error = $cust_main->replace; + elsif ($cust_location->censusyear eq '') { + # Then it's been updated since $date, but somehow has a null censusyear. + # (Is this still relevant?) + $cust_location->set('censusyear', $current_year); + $error = $cust_location->replace; $updated++; - } + } # Else it's been updated since $date, so leave it alone. if ( $error ) { $dbh->rollback; - die "error updating ".$cust_main->custnum.": $error\n"; + die "error updating ".$cust_location->locationnum.": $error\n"; } } warn "Queued $queued census code lookups, updated year in $updated records.\n"; diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily index 8e8ae4ff9..b6ee5188e 100755 --- a/FS/bin/freeside-daily +++ b/FS/bin/freeside-daily @@ -4,6 +4,7 @@ use strict; use Getopt::Std; use FS::UID qw(adminsuidsetup); use FS::Conf; +use FS::Log; &untaint_argv; #what it sounds like (eww) use vars qw(%opt); @@ -11,6 +12,8 @@ getopts("p:a:d:vl:sy:nmrkg:o", \%opt); my $user = shift or die &usage; adminsuidsetup $user; +my $log = FS::Log->new('daily'); +$log->info('start'); #you can skip this by not having a NetworkMonitoringSystem configured use FS::Cron::nms_report qw(nms_report); @@ -74,6 +77,12 @@ unlink <${deldir}.CGItemp*>; use FS::Cron::backup qw(backup); backup(); +#except we'd rather not start cleanup jobs until the backup is done +use FS::Cron::cleanup qw(cleanup); +cleanup(); + +$log->info('finish'); + ### # subroutines ### @@ -138,13 +147,13 @@ the bill and collect methods of a cust_main object. See L<FS::cust_main>. -l: debugging level - -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing. + -m: Multi-process mode uses the job queue for multi-process and/or multi-machine billing. -r: Multi-process mode dry run option -k: skip notify_flat_delay -user: From the mapsecrets file - see config.html from the base documentation +user: Typically "fs_daily" custnum: if one or more customer numbers are specified, only bills those customers. Otherwise, bills all customers. diff --git a/FS/bin/freeside-deluser b/FS/bin/freeside-deluser deleted file mode 100644 index a2a361a83..000000000 --- a/FS/bin/freeside-deluser +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use vars qw($opt_h); -use Fcntl qw(:flock); -use Getopt::Std; - -my $FREESIDE_CONF = "%%%FREESIDE_CONF%%%"; - -getopts("h:"); -my $user = shift or die &usage; - -if ( $opt_h ) { - open(HTPASSWD,"<$opt_h") - and flock(HTPASSWD,LOCK_EX) - or die "can't open $opt_h: $!"; - open(HTPASSWD_TMP,">$opt_h.tmp") or die "can't open $opt_h.tmp: $!"; - while (<HTPASSWD>) { - print HTPASSWD_TMP $_ unless /^$user:/; - } - close HTPASSWD_TMP; - rename "$opt_h.tmp", "$opt_h" or die $!; - flock(HTPASSWD,LOCK_UN); - close HTPASSWD; -} - -open(MAPSECRETS,"<$FREESIDE_CONF/mapsecrets") - and flock(MAPSECRETS,LOCK_EX) - or die "can't open $FREESIDE_CONF/mapsecrets: $!"; -open(MAPSECRETS_TMP,">>$FREESIDE_CONF/mapsecrets.tmp") - or die "can't open $FREESIDE_CONF/mapsecrets.tmp: $!"; -while (<MAPSECRETS>) { - print MAPSECRETS_TMP $_ unless /^$user\s/; -} -close MAPSECRETS_TMP; -rename "$FREESIDE_CONF/mapsecrets.tmp", "$FREESIDE_CONF/mapsecrets" or die $!; -flock(MAPSECRETS,LOCK_UN); -close MAPSECRETS; - -sub usage { - die "Usage:\n\n freeside-deluser [ -h htpasswd_file ] username" -} - -=head1 NAME - -freeside-deluser - Command line interface to add (freeside) users. - -=head1 SYNOPSIS - - freeside-deluser [ -h htpasswd_file ] username - -=head1 DESCRIPTION - -Adds a user to the Freeside billing system. This is for adding users (internal -sales/tech folks) to the web interface, not for adding customer accounts. - - -h: Also delete from the given htpasswd filename - -=head1 SEE ALSO - -L<freeside-adduser>, L<htpasswd>(1), base Freeside documentation - -=cut - diff --git a/FS/bin/freeside-eftca-download b/FS/bin/freeside-eftca-download index 702a80ca1..1b7653cb3 100755 --- a/FS/bin/freeside-eftca-download +++ b/FS/bin/freeside-eftca-download @@ -52,7 +52,7 @@ my $conf = new FS::Conf; my @agents; if ( $conf->exists('batch-spoolagent') ) { - @agents = qsearchs('agent', { 'disabled' => '' }); + @agents = qsearch('agent', { 'disabled' => '' }); } else { @agents = (1); } @@ -62,11 +62,14 @@ foreach my $agent (@agents) { my @batchconf; if ( $conf->exists('batch-spoolagent') ) { @batchconf = $conf->config('batchconfig-eft_canada', $agent->agentnum, 1); - next unless $batchconf[0]; + if ( !length($batchconf[0]) ) { + warn "agent '".$agent->agent."' has no batchconfig-eft_canada setting; skipped.\n"; + next; + } } else { @batchconf = $conf->config('batchconfig-eft_canada'); } - # BIN, terminalID, merchantID, username, password + # user, password, transaction code, delay days my $user = $batchconf[0] or die "no EFT Canada batch username configured\n"; my $pass = $batchconf[1] or die "no EFT Canada batch password configured\n"; @@ -82,7 +85,7 @@ foreach my $agent (@agents) { $sftp->setcwd('/Returns'); - my $files = $sftp->ls('.', wanted => qr/^ReturnFile/, names_only => 1); + my $files = $sftp->ls('.', wanted => qr/\.txt$/, names_only => 1); die "no response files found\n" if !@$files; FILE: foreach my $filename (@$files) { diff --git a/FS/bin/freeside-email b/FS/bin/freeside-email index 7a93f78ee..6e4e0fe6c 100755 --- a/FS/bin/freeside-email +++ b/FS/bin/freeside-email @@ -45,7 +45,7 @@ freeside-email - Prints email addresses of all users on STDOUT Prints the email addresses of all customers on STDOUT, separated by newlines. -user: From the mapsecrets file - see config.html from the base documentation +user: Freeside user =head1 BUGS diff --git a/FS/bin/freeside-fetch b/FS/bin/freeside-fetch index f689bfd93..c1ab78373 100755 --- a/FS/bin/freeside-fetch +++ b/FS/bin/freeside-fetch @@ -79,7 +79,7 @@ freeside-fetch - Send a freeside page to a list of employees. Fetches a web page specified by url as if employee and emails it to employee. Useful when run out of cron to send freeside web pages. - user: From the mapsecrets file - a user with access to the freeside database + user: Freeside user employee: the username of an employee to receive the emailed page. May be a comma separated list diff --git a/FS/bin/freeside-ipifony-download b/FS/bin/freeside-ipifony-download new file mode 100644 index 000000000..9df4db08a --- /dev/null +++ b/FS/bin/freeside-ipifony-download @@ -0,0 +1,320 @@ +#!/usr/bin/perl + +use strict; +use Getopt::Std; +use Date::Format qw(time2str); +use File::Temp qw(tempdir); +use Net::SFTP::Foreign; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch qsearchs); +use FS::cust_main; +use FS::Conf; +use File::Copy qw(copy); +use Text::CSV; + +my %opt; +getopts('va:P:C:e:', \%opt); + +# Product codes that are subject to flat rate E911 charges. For these +# products, the'quantity' field represents the number of lines. +my @E911_CODES = ( 'V-HPBX', 'V-TRUNK' ); + +# Map TAXNONVOICE/TAXVOICE to Freeside taxclass names +my %TAXCLASSES = ( + 'TAXNONVOICE' => 'Other', + 'TAXVOICE' => 'VoIP', +); + + +#$Net::SFTP::Foreign::debug = -1; +sub HELP_MESSAGE { ' + Usage: + freeside-ipifony-download + [ -v ] + [ -a archivedir ] + [ -P port ] + [ -C category ] + [ -e pkgpart ] + freesideuser sftpuser@hostname[:path] +' } + +my @fields = ( + 'custnum', + 'date_desc', + 'quantity', + 'unit_price', + 'classname', + 'taxclass', +); + +my $user = shift or die &HELP_MESSAGE; +my $dbh = adminsuidsetup $user; +$FS::UID::AutoCommit = 0; + +# for statistics +my $num_charges = 0; +my $num_errors = 0; +my $sum_charges = 0; +# cache classnums +my %classnum_of; + +if ( $opt{a} ) { + die "no such directory: $opt{a}\n" + unless -d $opt{a}; + die "archive directory $opt{a} is not writable by the freeside user\n" + unless -w $opt{a}; +} + +my $e911_part_pkg; +if ( $opt{e} ) { + $e911_part_pkg = FS::part_pkg->by_key($opt{e}) + or die "E911 pkgpart $opt{e} not found.\n"; + + if ( $e911_part_pkg->base_recur > 0 or $e911_part_pkg->freq ) { + die "E911 pkgpart $opt{e} must be a one-time charge.\n"; + } +} + +my $categorynum = ''; +if ( $opt{C} ) { + # find this category (don't auto-create it, it should exist already) + my $category = qsearchs('pkg_category', { categoryname => $opt{C} }); + if (!defined($category)) { + die "Package category '$opt{C}' does not exist.\n"; + } + $categorynum = $category->categorynum; +} + +#my $tmpdir = File::Temp->newdir(); +my $tmpdir = tempdir( CLEANUP => 1 ); #DIR=>somewhere? + +my $host = shift + or die &HELP_MESSAGE; +my ($sftpuser, $path); +$host =~ s/^(.+)\@//; +$sftpuser = $1 || $ENV{USER}; +$host =~ s/:(.*)//; +$path = $1; + +my $port = 22; +if ( $opt{P} =~ /^(\d+)$/ ) { + $port = $1; +} + +# for now assume SFTP download as the only method +print STDERR "Connecting to $sftpuser\@$host...\n" if $opt{v}; + +my $sftp = Net::SFTP::Foreign->new( + host => $host, + user => $sftpuser, + port => $port, + # for now we don't support passwords. use authorized_keys. + timeout => 30, + #more => ($opt{v} ? '-v' : ''), +); +die "failed to connect to '$sftpuser\@$host'\n(".$sftp->error.")\n" + if $sftp->error; + +$sftp->setcwd($path) if $path; + +my $files = $sftp->ls('ready', wanted => qr/\.csv$/, names_only => 1); +if (!@$files) { + print STDERR "No charge files found.\n" if $opt{v}; + exit(-1); +} + +my %cust_main; # cache +my %e911_qty; # custnum => sum of E911-subject quantity + +my %is_e911 = map {$_ => 1} @E911_CODES; + +FILE: foreach my $filename (@$files) { + print STDERR "Retrieving $filename\n" if $opt{v}; + $sftp->get("ready/$filename", "$tmpdir/$filename"); + if($sftp->error) { + warn "failed to download $filename\n"; + next FILE; + } + + # make sure server archive dir exists + if ( !$sftp->stat('done') ) { + print STDERR "Creating $path/done\n" if $opt{v}; + $sftp->mkdir('done'); + if($sftp->error) { + # something is seriously wrong + die "failed to create archive directory on server:\n".$sftp->error."\n"; + } + } + #move to server archive dir + $sftp->rename("ready/$filename", "done/$filename"); + if($sftp->error) { + warn "failed to archive $filename on server:\n".$sftp->error."\n"; + } # process it anyway, I guess/ + + #copy to local archive dir + if ( $opt{a} ) { + print STDERR "Copying $tmpdir/$filename to archive dir $opt{a}\n" + if $opt{v}; + copy("$tmpdir/$filename", $opt{a}); + warn "failed to copy $tmpdir/$filename to $opt{a}: $!" if $!; + } + + open my $fh, "<$tmpdir/$filename"; + my $csv = Text::CSV->new; # orthodox CSV + my %hash; + while (my $line = <$fh>) { + $csv->parse($line) or do { + warn "can't parse $filename: ".$csv->error_input."\n"; + next FILE; + }; + @hash{@fields} = $csv->fields(); + if ( $hash{custnum} =~ /^cust/ ) { + # there appears to be a header row + print STDERR "skipping header row\n" if $opt{v}; + next; + } + my $cust_main = + $cust_main{$hash{custnum}} ||= FS::cust_main->by_key($hash{custnum}); + if (!$cust_main) { + warn "customer #$hash{custnum} not found\n"; + next; + } + print STDERR "Found customer #$hash{custnum}: ".$cust_main->name."\n" + if $opt{v}; + + my $amount = sprintf('%.2f',$hash{quantity} * $hash{unit_price}); + # construct arguments for $cust_main->charge + my %charge_opt = ( + amount => $hash{unit_price}, + quantity => $hash{quantity}, + start_date => $cust_main->next_bill_date, + pkg => $hash{date_desc} . + ' (' . $hash{quantity} . ' @ $' . $hash{unit_price} . ' ea)', + taxclass => $TAXCLASSES{ $hash{taxclass} }, + ); + if (my $classname = $hash{classname}) { + if (!exists($classnum_of{$classname}) ) { + # then look it up + my $pkg_class = qsearchs('pkg_class', { + classname => $classname, + categorynum => $categorynum, + }); + if (!defined($pkg_class)) { + # then create it + $pkg_class = FS::pkg_class->new({ + classname => $classname, + categorynum => $categorynum, + }); + my $error = $pkg_class->insert; + die "Error creating package class for product code '$classname':\n". + "$error\n" + if $error; + } + + $classnum_of{$classname} = $pkg_class->classnum; + } + $charge_opt{classnum} = $classnum_of{$classname}; + } + print STDERR " Charging $hash{unit_price} * $hash{quantity}\n" + if $opt{v}; + my $error = $cust_main->charge(\%charge_opt); + if ($error) { + warn "Error creating charge: $error" if $error; + $num_errors++; + } else { + $num_charges++; + $sum_charges += $amount; + } + + if ( $opt{e} and $is_e911{$hash{classname}} ) { + $e911_qty{$hash{custnum}} ||= 0; + $e911_qty{$hash{custnum}} += $hash{quantity}; + } + } #while $line + close $fh; +} #FILE + +# Order E911 packages +my $num_e911 = 0; +my $num_lines = 0; +foreach my $custnum ( keys (%e911_qty) ) { + my $cust_main = $cust_main{$custnum}; + my $quantity = $e911_qty{$custnum}; + next if $quantity == 0; + my $cust_pkg = FS::cust_pkg->new({ + pkgpart => $opt{e}, + custnum => $custnum, + start_date => $cust_main->next_bill_date, + quantity => $quantity, + }); + my $error = $cust_main->order_pkg({ cust_pkg => $cust_pkg }); + if ( $error ) { + warn "Error creating e911 charge for customer $custnum: $error\n"; + $num_errors++; + } else { + $num_e911++; + $num_lines += $quantity; + } +} + +$dbh->commit; + +if ($opt{v}) { + print STDERR " +Finished! + Processed files: @$files + Created charges: $num_charges + Sum of charges: \$".sprintf('%0.2f', $sum_charges)." + E911 charges: $num_e911 + E911 lines: $num_lines + Errors: $num_errors +"; +} + +=head1 NAME + +freeside-ipifony-download - Download and import invoice items from IPifony. + +=head1 SYNOPSIS + + freeside-ipifony-download + [ -v ] + [ -a archivedir ] + [ -P port ] + [ -C category ] + [ -T taxclass ] + [ -e pkgpart ] + freesideuser sftpuser@hostname[:path] + +=head1 REQUIRED PARAMETERS + +I<freesideuser>: the Freeside user to run as. + +I<sftpuser>: the SFTP user to connect as. The 'freeside' system user should +have an authorization key to connect as that user. + +I<hostname>: the SFTP server. + +=head1 OPTIONAL PARAMETERS + +-v: Be verbose. + +-a I<archivedir>: Save a copy of the downloaded file to I<archivedir>. + +-P I<port>: Connect to that TCP port. + +-C I<category>: The name of a package category to use when creating package +classes. + +-e I<pkgpart>: The pkgpart (L<FS::part_pkg>) to use for E911 charges. A +package of this type will be ordered for each invoice that has E911-subject +line items. The 'quantity' field on this package will be set to the total +quantity of those line items. + +The E911 package must be a one-time package (flat rate, no frequency, no +recurring fee) with setup fee equal to the fee per line. + +=cut + +1; + diff --git a/FS/bin/freeside-monthly b/FS/bin/freeside-monthly index 0d6ea14a2..431fbd86f 100755 --- a/FS/bin/freeside-monthly +++ b/FS/bin/freeside-monthly @@ -7,7 +7,7 @@ use FS::UID qw(adminsuidsetup); &untaint_argv; #what it sounds like (eww) #use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y); use vars qw(%opt); -getopts("p:a:d:vsy:", \%opt); +getopts("p:a:d:vsy:m", \%opt); my $user = shift or die &usage; adminsuidsetup $user; @@ -72,7 +72,9 @@ the bill and collect methods of a cust_main object. See L<FS::cust_main>. -v: enable debugging -user: From the mapsecrets file - see config.html from the base documentation + -m: Experimental multi-process mode (delay upload jobs until billing jobs complete) + +user: Typically "fs_daily" custnum: if one or more customer numbers are specified, only bills those customers. Otherwise, bills all customers. diff --git a/FS/bin/freeside-phonenum_list b/FS/bin/freeside-phonenum_list new file mode 100755 index 000000000..19b564dee --- /dev/null +++ b/FS/bin/freeside-phonenum_list @@ -0,0 +1,86 @@ +#!/usr/bin/perl + +use strict; +use vars qw( $opt_c $opt_o $opt_l $opt_p $opt_b $opt_d $opt_s $opt_t ); +use Getopt::Std; +use FS::UID qw(adminsuidsetup); +use FS::Conf; +use FS::Record qw(qsearch); +use FS::svc_phone; + +getopts('colp:b:d:s:t:'); + +my $user = shift or &usage; +adminsuidsetup $user; + +my $conf = new FS::Conf; +my $default_locale = $conf->config('locale') || 'en_US'; + +my %search = (); + +$search{payby} = [ split(/\s*,\s*/, $opt_p) ] if $opt_p; +$search{balance} = $opt_b if $opt_b; +$search{balance_days} = $opt_d if $opt_d; +$search{svcpart} = [ split(/\s*,\s*/, $opt_s) ] if $opt_s; +$search{cust_status} = lc($opt_t) if $opt_t; + +my @svc_phone = qsearch( FS::svc_phone->search(\%search) ); + +foreach my $svc_phone (@svc_phone) { + print $svc_phone->countrycode if $opt_c; + print $svc_phone->phonenum; + print '@'. $svc_phone->domain if $opt_o; + if ( $opt_l ) { + my $cust_pkg = $svc_phone->cust_svc->cust_pkg; + print ','. ($cust_pkg && $cust_pkg->cust_main->locale || $default_locale); + } + print "\n"; +} + +sub usage { + die "usage: freeside-phonenum_list [ -c ] [ -o ] [ -l ] [ -p payby,payby... ] [ -b balance [ -d balance_days ] ] [ -s svcpart,svcpart... ] username \n"; +} + +=head1 NAME + +freeside-phonenum_list + +=head1 SYNOPSIS + freeside-phonenum_list [ -c ] [ -o ] [ -l ] [ -p payby,payby... ] [ -b balance [ -d balance_days ] ] [ -s svcpart,svcpart... ] username + +=head1 DESCRIPTION + +Command-line tool to list phone numbers. + +Display options: + +-c: Include country code + +-o: Include domain + +-l: Include customer locale + +Selection options: + +-p: Customer payby (CARD, BILL, etc.). Separate multiple values with commas. + +-b: Customer balance over (or equal to) this amount + +-d: Customer balance age over this many days + +-s: Service definition (svcpart). Separate multiple values with commas. + +-t: Customer status: prospect, active, ordered, inactive, suspended or cancelled + +username: Employee username + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::svc_phone>, L<FS::cust_main> + +=cut + +1; + diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued index 756b699d4..5eac06b24 100644 --- a/FS/bin/freeside-queued +++ b/FS/bin/freeside-queued @@ -11,6 +11,7 @@ use FS::Conf; use FS::Record qw(qsearch); use FS::queue; use FS::queue_depend; +use FS::Log; # no autoloading for non-FS classes... use Net::SSH 0.07; @@ -45,6 +46,7 @@ while ( $@ ) { } } +my $log = FS::Log->new('queue'); logfile( "%%%FREESIDE_LOG%%%/queuelog.". $FS::UID::datasrc ); warn "completing daemonization (detaching))\n" if $DEBUG; @@ -135,6 +137,8 @@ while (1) { foreach my $job ( @jobs ) { + $log->debug('locking queue job', object => $job); + my %hash = $job->hash; $hash{'status'} = 'locked'; my $ljob = new FS::queue ( \%hash ); @@ -186,7 +190,7 @@ while (1) { dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile'); #auto-use classes... - if ( $ljob->job =~ /(FS::(part_export|cust_main|cust_pkg)::\w+)::/ + if ( $ljob->job =~ /(FS::(part_export|cust_main|cust_pkg|Cron)::\w+)::/ || $ljob->job =~ /(FS::\w+)::/ ) { @@ -205,9 +209,13 @@ while (1) { } my $eval = "&". $ljob->job. '(@args);'; + # don't put @args in the log, may expose passwords + $log->info('starting job ('.$ljob->job.')'); warn 'running "&'. $ljob->job. '('. join(', ', @args). ")\n" if $DEBUG; + local $FS::UID::AutoCommit = 0; # so that we can clean up failures eval $eval; #throw away return value? suppose so if ( $@ ) { + dbh->rollback; my %hash = $ljob->hash; $hash{'statustext'} = $@; if ( $hash{'statustext'} =~ /\/misc\/queued_report/ ) { #use return? @@ -219,8 +227,10 @@ while (1) { my $fjob = new FS::queue( \%hash ); my $error = $fjob->replace($ljob); die $error if $error; + dbh->commit; # for the status change only } else { $ljob->delete; + dbh->commit; # for the job itself } if ( UNIVERSAL::can(dbh, 'sprintProfile') ) { @@ -286,7 +296,7 @@ Job queue daemon. Should be running at all times. -n: non-"secure" jobs only (other jobs) -user: from the mapsecrets file - see config.html from the base documentation +user: Typically "fs_queue" =head1 VERSION diff --git a/FS/bin/freeside-selfservice-server b/FS/bin/freeside-selfservice-server index c10623c96..8ce74d5c8 100644 --- a/FS/bin/freeside-selfservice-server +++ b/FS/bin/freeside-selfservice-server @@ -16,6 +16,7 @@ use FS::UID qw(adminsuidsetup forksuidsetup); use FS::ClientAPI qw( load_clientapi_modules ); use FS::ClientAPI_SessionCache; use FS::Record qw( qsearch qsearchs ); +use FS::TicketSystem; use FS::Conf; use FS::cust_svc; @@ -108,31 +109,7 @@ while (1) { if ( $keepalives && $keepalive_count++ > 10 ) { $keepalive_count = 0; lock_write; - nstore_fd( { _token => '_keepalive' }, $writer ); - -#commenting izoom stuff out until we can move it to a branch (or just remove) -# foreach my $agent ( qsearch( 'agent', { disabled => '' } ) ) { -# my $config = qsearchs( 'conf', { name => 'selfservice-bulk_ftp_dir', -# agentnum => $agent->agentnum, -# } ) -# or next; -# -# my $session = -# FS::ClientAPI->dispatch( 'Agent/agent_login', -# { username => $agent->username, -# password => $agent->_password, -# } -# ); -# -# nstore_fd( { _token => '_ftp_scan', -# dir => $config->value, -# session_id => $session->{session_id}, -# }, -# $writer -# ); -# } - unlock_write; } next; @@ -181,12 +158,10 @@ while (1) { warn "child $pid spawned\n" if $Debug; } else { #kid time - ##get new db handle $FS::UID::dbh->{InactiveDestroy} = 1; forksuidsetup($user); - #get db handle - #adminsuidsetup($user); + FS::TicketSystem->init(); my $type = $packet->{_packet}; warn "calling $type handler\n" if $Debug; diff --git a/FS/bin/freeside-selfservice-xmlrpcd b/FS/bin/freeside-selfservice-xmlrpcd index acf516abe..423d2c30b 100755 --- a/FS/bin/freeside-selfservice-xmlrpcd +++ b/FS/bin/freeside-selfservice-xmlrpcd @@ -28,6 +28,7 @@ use FS::UID qw( adminsuidsetup forksuidsetup dbh ); use FS::Conf; use FS::ClientAPI qw( load_clientapi_modules ); use FS::ClientAPI_XMLRPC; #FS::SelfService::XMLRPC; +use FS::TicketSystem; #freeside my $FREESIDE_LOG = "%%%FREESIDE_LOG%%%"; @@ -195,6 +196,9 @@ sub server_do_fork { #freeside db connection, etc. forksuidsetup($user); + #why isn't this needed ala freeside-selfservice-server?? + #FS::TicketSystem->init(); + return; } } diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index 155c74aa0..07da88dea 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -32,7 +32,7 @@ $config_dir =~ /^([\w.:=\/]+)$/ or die "unacceptable configuration directory name"; $config_dir = $1; -getsecrets($opt_u); +getsecrets(); #needs to match FS::Record my($dbdef_file) = "%%%FREESIDE_CONF%%%/dbdef.". datasrc; diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade index b08a8401f..5bd141538 100755 --- a/FS/bin/freeside-upgrade +++ b/FS/bin/freeside-upgrade @@ -5,7 +5,7 @@ use vars qw($opt_d $opt_s $opt_q $opt_v $opt_r); use vars qw($DEBUG $DRY_RUN); use Getopt::Std; use DBIx::DBSchema 0.31; #0.39 -use FS::UID qw(adminsuidsetup checkeuid datasrc driver_name); #getsecrets); +use FS::UID qw(adminsuidsetup checkeuid datasrc driver_name); use FS::CurrentUser; use FS::Schema qw( dbdef dbdef_dist reload_dbdef ); use FS::Misc::prune qw(prune_applications); @@ -123,6 +123,8 @@ my $cf; while ( $cf = $cfsth->fetchrow_hashref ) { my $tbl = $cf->{'dbtable'}; my $name = $cf->{'name'}; + $name = lc($name) unless driver_name =~ /^mysql/i; + @statements = grep { $_ !~ /^\s*ALTER\s+TABLE\s+(h_|)$tbl\s+DROP\s+COLUMN\s+cf_$name\s*$/i } @statements; push @statements, diff --git a/FS/bin/freeside-username_list b/FS/bin/freeside-username_list new file mode 100755 index 000000000..5352f02eb --- /dev/null +++ b/FS/bin/freeside-username_list @@ -0,0 +1,84 @@ +#!/usr/bin/perl + +use strict; +use vars qw( $opt_o $opt_l $opt_p $opt_b $opt_d $opt_s $opt_t ); +use Getopt::Std; +use FS::UID qw(adminsuidsetup); +use FS::Conf; +use FS::Record qw(qsearch); +use FS::svc_acct; + +getopts('olp:b:d:s:t:'); + +my $user = shift or &usage; +adminsuidsetup $user; + +my $conf = new FS::Conf; +my $default_locale = $conf->config('locale') || 'en_US'; + +my %search = (); + +$search{payby} = [ split(/\s*,\s*/, $opt_p) ] if $opt_p; +$search{balance} = $opt_b if $opt_b; +$search{balance_days} = $opt_d if $opt_d; +$search{svcpart} = [ split(/\s*,\s*/, $opt_s) ] if $opt_s; +$search{cust_status} = lc($opt_t) if $opt_t; + +my @svc_acct = qsearch( FS::svc_acct->search(\%search) ); + +foreach my $svc_acct (@svc_acct) { + print $svc_acct->username; + print '@'. $svc_acct->domain if $opt_o; + if ( $opt_l ) { + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + print ','. ($cust_pkg && $cust_pkg->cust_main->locale || $default_locale); + } + print "\n"; +} + +sub usage { + die "usage: freeside-username_list [ -c ] [ -l ] [ -p payby,payby... ] [ -b balance [ -d balance_days ] ] [ -s svcpart,svcpart... ] username \n"; +} + +=head1 NAME + +freeside-username_list + +=head1 SYNOPSIS + + freeside-username_list [ -c ] [ -l ] [ -p payby,payby... ] [ -b balance [ -d balance_days ] ] [ -s svcpart,svcpart... ] username + +=head1 DESCRIPTION + +Command-line tool to list usernames. + +Display options: + +-o: Include domain + +-l: Include customer locale + +Selection options: + +-p: Customer payby (CARD, BILL, etc.). Separate multiple values with commas. + +-b: Customer balance over (or equal to) this amount + +-d: Customer balance age over this many days + +-s: Service definition (svcpart). Separate multiple values with commas. + +-t: Customer status: prospect, active, ordered, inactive, suspended or cancelled + +username: Employee username + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::svc_acct>, L<FS::cust_main> + +=cut + +1; + diff --git a/FS/bin/freeside-void-payments b/FS/bin/freeside-void-payments index 8c1f3dbdf..49b74d388 100755 --- a/FS/bin/freeside-void-payments +++ b/FS/bin/freeside-void-payments @@ -90,8 +90,11 @@ my $notfound = 0; my $canceled = 0; print "Voiding ".scalar(@auths)." transactions:\n" if $opt{'v'}; foreach my $authnum (@auths) { - my $paybatch = $gatewaynum . $processor . ':' . $authnum; - my $cust_pay = qsearchs('cust_pay', { paybatch => $paybatch } ); + my $cust_pay = qsearchs('cust_pay', { + gatewaynum => $gatewaynum, + processor => $processor, + authorization => $authnum, + }); my $error; my $cancel_error; if($cust_pay) { @@ -103,7 +106,11 @@ foreach my $authnum (@auths) { } } else { - my $cpv = qsearchs('cust_pay_void', { paybatch => $paybatch }); + my $cpv = qsearchs('cust_pay_void', { + gatewaynum => $gatewaynum, + processor => $processor, + authorization => $authnum, + }); if($cpv) { $error = 'already voided '.time2str('%Y-%m-%d', $cpv->void_date) . ' by ' . $cpv->otaker; diff --git a/FS/bin/freeside-wkhtmltopdf b/FS/bin/freeside-wkhtmltopdf index c6c5531a5..f0c53e6da 100755 --- a/FS/bin/freeside-wkhtmltopdf +++ b/FS/bin/freeside-wkhtmltopdf @@ -1,7 +1,7 @@ #!/bin/sh -if [ $DISPLAY ] ; then - wkhtmltopdf $@ -else +#if [ $DISPLAY ] ; then +# wkhtmltopdf $@ +#else xvfb-run -- wkhtmltopdf $@ -fi +#fi |