@encrypted_fields
$import
$ignore_expired_card $ignore_banned_card $ignore_illegal_zip
+ $ignore_invalid_card
$skip_fuzzyfiles
@paytypes
);
use Locale::Country;
use FS::UID qw( dbh driver_name );
use FS::Record qw( qsearchs qsearch dbdef regexp_sql );
+use FS::Cursor;
use FS::Misc qw( generate_email send_email generate_ps do_print );
use FS::Msgcat qw(gettext);
use FS::CurrentUser;
use FS::Locales;
use FS::upgrade_journal;
use FS::sales;
+use FS::cust_payby;
# 1 is mostly method/subroutine entry and options
# 2 traces progress of some operations
$import = 0;
$ignore_expired_card = 0;
$ignore_banned_card = 0;
+$ignore_invalid_card = 0;
$skip_fuzzyfiles = 0;
install_callback FS::UID sub {
$conf = new FS::Conf;
#yes, need it for stuff below (prolly should be cached)
+ $ignore_invalid_card = $conf->exists('allow_invalid_cards');
};
sub _cache {
}
tie my %financial_tables, 'Tie::IxHash',
- 'cust_bill' => 'invoices',
- 'cust_bill_void' => 'voided invoices',
- 'cust_statement' => 'statements',
- 'cust_credit' => 'credits',
- 'cust_pay' => 'payments',
- 'cust_pay_void' => 'voided payments',
- 'cust_refund' => 'refunds',
+ 'cust_bill' => 'invoices',
+ 'cust_bill_void' => 'voided invoices',
+ 'cust_statement' => 'statements',
+ 'cust_credit' => 'credits',
+ 'cust_credit_void' => 'voided credits',
+ 'cust_pay' => 'payments',
+ 'cust_pay_void' => 'voided payments',
+ 'cust_refund' => 'refunds',
;
foreach my $table ( keys %financial_tables ) {
}
### start of stuff moved to cust_payby
+ # then mostly kept here to support upgrades (can remove in 5.x)
+ # but modified to allow everything to be empty
- #$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/
- # or return "Illegal payby: ". $self->payby;
- #$self->payby($1);
- FS::payby->can_payby($self->table, $self->payby)
- or return "Illegal payby: ". $self->payby;
+ if ( $self->payby ) {
+ FS::payby->can_payby($self->table, $self->payby)
+ or return "Illegal payby: ". $self->payby;
+ } else {
+ $self->payby('');
+ }
$error = $self->ut_numbern('paystart_month')
|| $self->ut_numbern('paystart_year')
# Need some kind of global flag to accept invalid cards, for testing
# on scrubbed data.
- if ( !$import && $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
+ if ( !$import && !$ignore_invalid_card && $check_payinfo &&
+ $self->payby =~ /^(CARD|DCRD)$/ ) {
my $payinfo = $self->payinfo;
$payinfo =~ s/\D//g;
$self->payissue('');
}
- } elsif ( $check_payinfo && $self->payby =~ /^(CHEK|DCHK)$/ ) {
+ } elsif ( !$ignore_invalid_card && $check_payinfo &&
+ $self->payby =~ /^(CHEK|DCHK)$/ ) {
my $payinfo = $self->payinfo;
$payinfo =~ s/[^\d\@\.]//g;
if ( $self->paydate eq '' || $self->paydate eq '-' ) {
return "Expiration date required"
# shouldn't payinfo_check do this?
- unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD|PPAL)$/;
+ unless ! $self->payby
+ || $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD|PPAL)$/;
$self->paydate('');
} else {
my( $m, $y );
) {
$self->payname( $self->first. " ". $self->getfield('last') );
} else {
- $self->payname =~ /^([\w \,\.\-\'\&]+)$/
+ $self->payname =~ /^([\w \,\.\-\'\&]*)$/
or return gettext('illegal_name'). " payname: ". $self->payname;
$self->payname($1);
}
qsearch('contact', { 'custnum' => $self->custnum } );
}
+=item cust_payby
+
+Returns all payment methods (see L<FS::cust_payby>) for this customer.
+
+=cut
+
+sub cust_payby {
+ my $self = shift;
+ qsearch({
+ 'table' => 'cust_payby',
+ 'hashref' => { 'custnum' => $self->custnum },
+ 'order_by' => 'ORDER BY weight ASC',
+ });
+}
+
=item unsuspend
Unsuspends all unflagged suspended packages (see L</unflagged_suspended_pkgs>
);
}
+=item cust_credit_void
+
+Returns all voided credits (see L<FS::cust_credit_void>) for this customer.
+
+=cut
+
+sub cust_credit_void {
+ my $self = shift;
+ map { $_ }
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_void', { 'custnum' => $self->custnum } )
+}
+
=item cust_pay
Returns all the payments (see L<FS::cust_pay>) for this customer.
# code2country($self->country);
#}
+sub bill_country_full {
+ my $self = shift;
+ code2country($self->bill_location->country);
+}
+
+sub ship_country_full {
+ my $self = shift;
+ code2country($self->ship_location->country);
+}
+
=item county_state_county [ PREFIX ]
Returns a string consisting of just the county, state and country.
my %opt = @_;
my $self = qsearchs('cust_main', { 'custnum' => $opt{custnum} } )
- or die "invalid customer number: " . $opt{custvnum};
+ or die "invalid customer number: " . $opt{custnum};
- my $error = $self->print( $opt{template} );
+ my $error = $self->print( { 'template' => $opt{template} } );
die $error if $error;
}
# JRNL seq scan of cust_main on paydate... index on substrings? maybe set an
# JRNL seq scan of cust_main on payinfo.. certainly not going toi ndex that...
# JRNL leading/trailing spaces in first, last, company
+# JRNL migrate to cust_payby
# - otaker upgrade? journal and call it good? (double check to make sure
# we're not still setting otaker here)
#
}
+ unless ( FS::upgrade_journal->is_done('cust_main__cust_payby') ) {
+
+ #we don't want to decrypt them, just stuff them as-is into cust_payby
+ local(@encrypted_fields) = ();
+
+ local($FS::cust_payby::ignore_expired_card) = 1;
+ local($FS::cust_payby::ignore_banned_card) = 1;
+
+ my @payfields = qw( payby payinfo paycvv paymask
+ paydate paystart_month paystart_year payissue
+ payname paystate paytype payip
+ );
+
+ my $search = new FS::Cursor {
+ 'table' => 'cust_main',
+ 'extra_sql' => " WHERE ( payby IS NOT NULL AND payby != '' ) ",
+ };
+
+ while (my $cust_main = $search->fetch) {
+
+ my $cust_payby = new FS::cust_payby {
+ 'custnum' => $cust_main->custnum,
+ 'weight' => 1,
+ map { $_ => $cust_main->$_(); } @payfields
+ };
+
+ my $error = $cust_payby->insert;
+ die $error if $error;
+
+ $cust_main->setfield($_, '') foreach @payfields;
+ $error = $cust_main->replace;
+ die $error if $error;
+
+ };
+
+ FS::upgrade_journal->set_done('cust_main__cust_payby');
+ }
+
$class->_upgrade_otaker(%opts);
}