#this used to apply a credit, but now we can void invoices...
foreach my $cust_bill (@cust_bill) {
- my $voiderror = $cust_bill->void();
- warn "Error voiding cust bill after decline: $voiderror";
+ my $voiderror = $cust_bill->void('automatic payment failed');
+ warn "Error voiding cust bill after decline: $voiderror" if $voiderror;
}
}
#this used to apply a credit, but now we can void invoices...
foreach my $cust_bill (@cust_bill) {
- my $voiderror = $cust_bill->void();
- warn "Error voiding cust bill after decline: $voiderror";
+ my $voiderror = $cust_bill->void('automatic payment failed');
+ warn "Error voiding cust bill after decline: $voiderror" if $voiderror;
}
#should check list for errors...
# "balance_date_range" unfortunately is unsuitable for this, since it
# cares about application dates. We want to know the sum of all
# _top-level transactions_ dated before the last invoice.
+ #
+ # still do this for the "Previous Balance" line of the summary block
my @sql =
map "$_ WHERE _date <= ? AND custnum = ?", (
"SELECT COALESCE( SUM(charged), 0 ) FROM cust_bill",
# longer stored in the database)
$invoice_data{'true_previous_balance'} = $last_bill_balance;
- # the change in balance from immediately after that invoice
- # to immediately before this one
- my $before_this_bill_balance = 0;
+ # Now, get all applications of credits/payments dated on or after the
+ # previous bill, to invoices before the current bill. (The
+ # credit/payment date restriction prevents these from intersecting
+ # the "Previous Balance" set.)
+ # These are "adjustments". The past due balance will be shown as
+ # Previous Balance - Adjustments.
+ my $adjustments = 0;
+ @sql = map {
+ "SELECT COALESCE(SUM(y.amount),0) FROM $_ JOIN cust_bill USING (invnum)
+ WHERE cust_bill._date < ?
+ AND x._date >= ?
+ AND cust_bill.custnum = ?"
+ } "cust_credit AS x JOIN cust_credit_bill y USING (crednum)",
+ "cust_pay AS x JOIN cust_bill_pay y USING (paynum)"
+ ;
foreach (@sql) {
my $delta = FS::Record->scalar_sql(
$_,
- $self->_date - 1,
+ $self->_date,
+ $last_bill->_date,
$self->custnum,
);
- $before_this_bill_balance += $delta;
+ $adjustments += $delta;
}
- $invoice_data{'balance_adjustments'} =
- sprintf("%.2f", $last_bill_balance - $before_this_bill_balance);
+ $invoice_data{'balance_adjustments'} = sprintf("%.2f", $adjustments);
warn sprintf("BALANCE ADJUSTMENTS: %.2f\n\n",
$invoice_data{'balance_adjustments'}
use strict;
use base qw( FS::cdr );
-use vars qw( %info );
+use vars qw( %info %cdrtypes);
use DateTime;
-use FS::Record qw( qsearchs );
+use FS::Record qw( qsearch );
use FS::cdr_type;
my ($tmp_mday, $tmp_mon, $tmp_year);
'type' => 'csv',
'sep_char' => ',',
'disabled' => 0,
+ 'header_buffer' => sub {
+
+ %cdrtypes = ( map { $_->cdrtypename => $_->cdrtypenum }
+ qsearch('cdr_type', {})
+ );
+ },
#listref of what to do with each field from the CDR, in order
'import_fields' => [
sub { # 5. Call Category (LOCAL, NATIONAL, FREECALL, MOBILE)
my ($cdr, $data) = @_;
$data ||= 'none';
-
- my $cdr_type = qsearchs('cdr_type', { 'cdrtypename' => $data } );
- $cdr->set('cdrtypenum', $cdr_type->cdrtypenum) if $cdr_type;
+ $cdr->cdrtypenum($cdrtypes{$data} || '');
$cdr->set('dcontext', $data);
},
sub { # 6. Start Date (DDMMYYYY
my @or =
map { my $table = $_;
my $search_sql = "FS::$table"->search_sql($string);
+ my $addl_from = "FS::$table"->search_sql_addl_from();
"SELECT $table.svcnum AS svcnum, '$table' AS svcdb ".
- "FROM $table WHERE $search_sql";
+ "FROM $table $addl_from WHERE $search_sql";
}
FS::part_svc->svc_tables;
queue
upgrade
upgrade_taxable_billpkgnum
+ freeside-paymentech-upload
+ freeside-paymentech-download
) );
=head1 NAME
END
],
},
+ { msgname => 'Refund receipt',
+ msgclass => 'email',
+ mime_type => 'text/html',
+ _conf => 'refund_receipt_msgnum',
+ _insert_args => [ subject => '{ $company_name } refund receipt',
+ body => <<'END',
+Dear {$first} {$last},<BR>
+<BR>
+The following refund has been applied to your account.<BR>
+<BR>
+Refund ID: {$refundnum}<BR>
+Date: {$date}<BR>
+Amount: {$refund}<BR>
+
+END
+ ],
+ },
];
}
use vars qw( %info );
use Time::Local qw( timelocal );
use List::Util qw( min );
+use FS::Record qw( qsearchs );
use FS::cust_pkg;
use FS::cust_bill_pkg_discount;
#my( $class, $string ) = @_;
'1 = 0'; #false
}
+sub search_sql_addl_from {
+ '';
+}
=item search HASHREF
$self->get('circuit_id');
}
+sub search_sql {
+ my ($class, $string) = @_;
+ my @where = ();
+ push @where, 'LOWER(svc_circuit.circuit_id) = \''.lc($string).'\'';
+ push @where, 'LOWER(circuit_provider.provider) = \''.lc($string).'\'';
+ push @where, 'LOWER(circuit_type.typename) = \''.lc($string).'\'';
+ '(' . join(' OR ', @where) . ')';
+}
+
+sub search_sql_addl_from {
+ 'LEFT JOIN circuit_provider USING ( providernum ) '.
+ 'LEFT JOIN circuit_type USING ( typenum )';
+}
+
=back
=head1 SEE ALSO
use FS::pay_batch;
use FS::cust_pay_batch;
use FS::Conf;
+use FS::Log;
use vars qw( $opt_t $opt_v $opt_a );
getopts('vta:');
#$Net::SFTP::Foreign::debug = -1;
+
+sub log_and_die {
+ my $message = shift;
+ my $log = FS::Log->new('freeside-paymenttech-download');
+ $log->error($message);
+ die $message;
+}
+
sub usage { "
Usage:
freeside-paymentech-download [ -v ] [ -t ] [ -a archivedir ] user\n
adminsuidsetup $user;
if ( $opt_a ) {
- die "no such directory: $opt_a\n"
+ log_and_die("no such directory: $opt_a\n")
unless -d $opt_a;
- die "archive directory $opt_a is not writable by the freeside user\n"
+ log_and_die("archive directory $opt_a is not writable by the freeside user\n")
unless -w $opt_a;
}
-my $unzip_check = `which unzip` or die "can't find unzip executable\n";
+my $unzip_check = `which unzip` or log_and_die("can't find unzip executable\n");
#my $tmpdir = File::Temp->newdir();
my $tmpdir = tempdir( CLEANUP => 1 ); #DIR=>somewhere?
my $conf = new FS::Conf;
my @batchconf = $conf->config('batchconfig-paymentech');
# BIN, terminalID, merchantID, username, password
-my $username = $batchconf[3] or die "no Paymentech batch username configured\n";
-my $password = $batchconf[4] or die "no Paymentech batch password configured\n";
+my $username = $batchconf[3] or log_and_die("no Paymentech batch username configured\n");
+my $password = $batchconf[4] or log_and_die("no Paymentech batch password configured\n");
my $host = ($opt_t ? 'orbitalbatchvar.paymentech.net'
: 'orbitalbatch.paymentech.net');
print STDERR "Connecting to $username\@$host...\n" if $opt_v;
-my $sftp = Net::SFTP::Foreign->new( host => $host,
- user => $username,
- password => $password,
- timeout => 30,
- );
-die "failed to connect to '$username\@$host'\n(".$sftp->error.")\n" if $sftp->error;
+my $sftp;
+my $ssh_retry = 25; # number of times to try connection, needs to be >= 1
+my $ssh_retry_wait = 60*5; # seconds to wait between tries
+while ($ssh_retry > 0) {
+ $sftp = Net::SFTP::Foreign->new( host => $host,
+ user => $username,
+ password => $password,
+ timeout => 30,
+ );
+ last unless $sftp->error;
+ $ssh_retry -= 1;
+ sleep($ssh_retry_wait) if $ssh_retry > 0;
+}
+
+log_and_die("failed to connect to '$username\@$host'\n(".$sftp->error.")\n") if $sftp->error;
my @files = map { $_->{filename} } @{ $sftp->ls('.', wanted => qr/_resp\.zip$/) };
-die "no response files found\n" if !@files;
+log_and_die("no response files found\n") if !@files;
BATCH: foreach my $filename (@files) {
use FS::pay_batch;
use FS::cust_pay_batch;
use FS::Conf;
+use FS::Log;
use vars qw( $opt_a $opt_t $opt_v $opt_p );
getopts('avtp:');
#$Net::SFTP::Foreign::debug = -1;
+sub log_and_die {
+ my $message = shift;
+ my $log = FS::Log->new('freeside-paymenttech-upload');
+ $log->error($message);
+ die $message;
+}
+
sub usage { "
Usage:
freeside-paymentech-upload [ -v ] [ -t ] user batchnum
my $user = shift or die &usage;
adminsuidsetup $user;
-my $zip_check = `which zip` or die "can't find zip executable\n";
+my $zip_check = `which zip` or log_and_die("can't find zip executable\n");
my @batches;
my %criteria = (status => 'O');
$criteria{'payby'} = uc($opt_p) if $opt_p;
@batches = qsearch('pay_batch', \%criteria);
- die "No open batches found".($opt_p ? " of type '$opt_p'" : '').".\n"
+ log_and_die("No open batches found".($opt_p ? " of type '$opt_p'" : '').".\n")
if !@batches;
}
else {
my $batchnum = shift;
- die &usage if !$batchnum;
+ log_and_die("batchnum not passed\n".&usage) if !$batchnum;
@batches = qsearchs('pay_batch', { batchnum => $batchnum } );
- die "Can't find payment batch '$batchnum'\n" if !@batches;
+ log_and_die("Can't find payment batch '$batchnum'\n") if !@batches;
}
my $conf = new FS::Conf;
my @batchconf = $conf->config('batchconfig-paymentech');
# BIN, terminalID, merchantID, username, password
-my $username = $batchconf[3] or die "no Paymentech batch username configured\n";
-my $password = $batchconf[4] or die "no Paymentech batch password configured\n";
+my $username = $batchconf[3] or log_and_die("no Paymentech batch username configured\n");
+my $password = $batchconf[4] or log_and_die("no Paymentech batch password configured\n");
#my $tmpdir = File::Temp->newdir();
my $tmpdir = tempdir( CLEANUP => 1 ); #DIR=>somewhere?
print STDERR "Exporting batch $batchnum to $filename...\n" if $opt_v;
my $text = $pay_batch->export_batch(format => 'paymentech');
$text =~ s!<fileID>FILEID</fileID>!<fileID>$filename</fileID>!
- or die "couldn't find FILEID tag\n";
+ or log_and_die("couldn't find FILEID tag\n");
open OUT, ">$tmpdir/$filename.xml";
print OUT $text;
close OUT;
system('zip', '-P', $password, '-q', '-j',
"$tmpdir/$filename.zip", "$tmpdir/$filename.xml");
- die "failed to create zip file\n" if (! -f "$tmpdir/$filename.zip" );
+ log_and_die("failed to create zip file\n") if (! -f "$tmpdir/$filename.zip" );
push @filenames, $filename;
}
: 'orbitalbatch.paymentech.net');
print STDERR "Connecting to $username\@$host...\n" if $opt_v;
-my $sftp = Net::SFTP::Foreign->new( host => $host,
- user => $username,
- password => $password,
- timeout => 30,
- );
-die "failed to connect to '$username\@$host'\n(".$sftp->error.")\n"
+my $sftp;
+my $ssh_retry = 25; # number of times to try connection, needs to be >= 1
+my $ssh_retry_wait = 60*5; # seconds to wait between tries
+while ($ssh_retry > 0) {
+ $sftp = Net::SFTP::Foreign->new( host => $host,
+ user => $username,
+ password => $password,
+ timeout => 30,
+ );
+ last unless $sftp->error;
+ $ssh_retry -= 1;
+ sleep($ssh_retry_wait) if $ssh_retry > 0;
+}
+
+log_and_die("failed to connect to '$username\@$host'\n(".$sftp->error.")\n")
if $sftp->error;
foreach my $filename (@filenames) {
$sftp->put("$tmpdir/$filename.zip", "$filename.zip")
- or die "failed to upload file (".$sftp->error.")\n";
+ or log_and_die("failed to upload file (".$sftp->error.")\n");
}
print STDERR "Finished!\n" if $opt_v;
function() {
overlib( OLresponseAJAX, CAPTION, 'Address standardization', STICKY,
AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH,
- 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399',
+ 650, HEIGHT, 368, BGCOLOR, '#333399', CGCOLOR, '#333399',
TEXTSIZE, 3 );
}, 0);
var newaddr = returned['new'];
var cf = document.<% $formname %>;
+ var crf = document.forms['confirm_replace_form'];
% foreach my $pre (@prefixes) {
var clean = newaddr['<% $pre %>addr_clean'] == 'Y';
+ var replace = true; // auto_standardize_address won't load the form, so just do it
+ if ( crf && crf['<% $pre %>replace'] ) {
+ replace = crf['<% $pre %>replace'].value == 'Y';
+ }
var error = newaddr['<% $pre %>error'];
- if ( clean ) {
+ if ( clean && replace ) {
% foreach my $field (qw(address1 address2 state zip addr_clean ),($conf->exists('cust_main-no_city_in_address') ? () : 'city')) {
cf.elements['<% $pre %><% $field %>'].value = newaddr['<% $pre %><% $field %>'];
% } #foreach $field
cf.elements['<% $pre %>longitude'].value = newaddr['<% $pre %>longitude'];
}
% if ( $withcensus ) {
- if ( clean && newaddr['<% $pre %>censustract'] ) {
+ var census_replace = true;
+ if ( crf && crf['census_replace'] ) {
+ census_replace = crf['census_replace'].value == 'Y';
+ }
+
+ if ( clean && census_replace && newaddr['<% $pre %>censustract'] ) {
cf.elements['<% $pre %>censustract'].value = newaddr['<% $pre %>censustract'];
}
% } #if $withcensus
<% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %>
% }
</B>
+% if ($part_svc->svcdb eq 'svc_circuit') {
+ <BR>Provider: <% $svc_x->circuit_provider->provider %>
+ <BR>Type: <% $svc_x->circuit_type->typename %>
+% }
% if ( $opt{after_svc_callback} ) {
<% &{ $opt{after_svc_callback} }( $cust_svc ) %>
% }
<STYLE type="text/css">
-th { line-height: 150% }
+th { line-height: 150%;
+ width: 45%;
+}
+.td_radio {
+ width: 5%;
+ vertical-align: middle;
+ text-align: center;
+}
</STYLE>
<CENTER><BR><B>
-% if ( $new{bill_error} or $new{ship_error} ) {
+% if ( $is_error ) {
Address standardization error
% }
% else {
% }
</B><BR><BR>
+<FORM ID="confirm_replace_form">
<TABLE WIDTH="100%">
-% my @prefixes = ('');
-% if ( $old{same} ) {
-% @prefixes = ('bill_');
-% } elsif ( $old{billship} ) {
-% @prefixes = ('bill_', 'ship_');
-% }
% for my $pre (@prefixes) {
% my $name = $pre eq 'bill_' ? 'billing' : 'service';
+% my $rows = 5;
% if ( $new{$pre.'error'} ) {
<TR>
- <TH>Entered <%$name%> address</TH>
+ <TD ROWSPAN=<% $rows %> CLASS="td_radio">
+ <INPUT TYPE="radio" NAME="<% $pre %>replace" VALUE="" CHECKED="Y">
+ </TD>
+ <TH>Entered <%$name%> address
+ </TH>
+ <TH></TH>
+ <TD CLASS="td_radio"></TD>
</TR>
-% if ( $old{$pre.'company'} ) {
<TR>
+% if ( $old{$pre.'company'} ) {
<TD><% $old{$pre.'company'} %></TD>
- </TR>
% }
+ </TR>
<TR>
<TD><% $old{$pre.'address1'} %></TD>
<TD ROWSPAN=3><FONT COLOR="#ff0000"><B><% $new{$pre.'error'} %></B></FONT></TD>
<TD><% $old{$pre.'city'} %>, <% $old{$pre.'state'} %> <% $old{$pre.'zip'} %></TD>
</TR>
% } else { # not an error
+% $rows++ if !$new{$pre.'addr_clean'};
<TR>
+ <TD ROWSPAN=<% $rows %> CLASS="td_radio">
+ <INPUT TYPE="radio" NAME="<% $pre %>replace" VALUE="">
+ </TD>
<TH>Entered <%$name%> address</TH>
<TH>Standardized <%$name%> address</TH>
+ <TD ROWSPAN=<% $rows %> CLASS="td_radio">
+ <INPUT TYPE="radio" NAME="<% $pre %>replace" VALUE="Y" CHECKED="Y">
+ </TD>
</TR>
% if ( !$new{$pre.'addr_clean'} ) {
<TR>
%# only do this part if address standardization provided a censustract
% my $pre = $old{same} ? 'bill_' : 'ship_';
% my $censustract = $new{$pre.'censustract'};
-% my $census_error = $new{$pre.'census_error'};
% if ( $censustract ) {
<TR>
+ <TD ROWSPAN=2 CLASS="td_radio">
+ <INPUT TYPE="radio" NAME="census_replace" VALUE="" <% $census_error ? 'CHECKED="Y"' : '' %>>
+ </TD>
<TH>Entered census tract</TH>
<TH>Calculated census tract</TH>
+ <TD ROWSPAN=2 CLASS="td_radio">
+ <INPUT TYPE="radio" NAME="census_replace" VALUE="Y" <% $census_error ? '' : 'CHECKED="Y"' %>>
+ </TD>
</TR>
<TR>
<TD><% $old{$pre.'censustract'} %></TD>
</TR>
% } #if censustract
-% if ( grep {$new{$_.'error'}} @prefixes ) {
<TR>
+ <TD> </TD>
<TD ALIGN="center">
- <BUTTON TYPE="button" STYLE="width:205px" onclick="confirm_manual_address();">
- <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered <%$addresses%>
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="replace_address();">
+ <IMG SRC="<%$p%>images/<% $is_error ? 'error.png' : 'tick.png' %>"
+ ALT=""> Use selected <%$addresses%>
</BUTTON></TD>
<TD ALIGN="center">
<BUTTON TYPE="button" STYLE="width:205px" onclick="submit_abort();">
<IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
</BUTTON></TD>
+ <TD> </TD>
</TR>
-% } else {
- <TR>
- <TD ALIGN="center">
- <BUTTON TYPE="button" STYLE="width:205px" onclick="confirm_manual_address();">
- <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered <%$addresses%>
- </BUTTON></TD>
- <TD ALIGN="center">
- <BUTTON TYPE="button" STYLE="width:205px" onclick="replace_address();">
- <IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized <%$addresses%>
- </BUTTON></TD>
- </TR>
- <TR ALIGN="center"><TD COLSPAN=2>
- <BUTTON TYPE="button" STYLE="width:205px" onclick="submit_abort();">
- <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
- </BUTTON>
- </TD></TR>
</TABLE>
-% } # !error
+</FORM>
<%init>
# slightly weird interface...
my $addresses = $old{billship} ? 'addresses' : 'address';
+my @prefixes = ('');
+if ( $old{same} ) {
+ @prefixes = ('bill_');
+} elsif ( $old{billship} ) {
+ @prefixes = ('bill_', 'ship_');
+}
+
+my $census_error = $new{'census_error'};
+my $is_error = $census_error || grep { $new{$_.'error'} } @prefixes;
+
</%init>
$x++;
$ws->write_string($y, $x, " \N{U+2212} ", $f->{bigmath}); # MINUS SIGN
$x++;
- $ws->write($y, $x, $row->{credit} || 0, $f->{currency});
+ $ws->write($y, $x, $row->{tax_credited} || 0, $f->{currency});
$x++;
$ws->write_string($y, $x, " = ", $f->{bigmath});
$x++;
- $ws->write($y, $x, $row->{tax} - $row->{credit}, $f->{currency});
+ $ws->write($y, $x, $row->{tax} - $row->{tax_credited}, $f->{currency});
$x++;
$ws->write($y, $x, $row->{tax_paid} || 0, $f->{currency});