FS::o2m_Common
FS::Record
);
-use vars qw( $DEBUG $me $conf
+use vars qw( $DEBUG $me $conf $default_agent_custid $custnum_display_length
@encrypted_fields
$import
$ignore_expired_card $ignore_banned_card $ignore_illegal_zip
#use Date::Manip;
use File::Temp; #qw( tempfile );
use Business::CreditCard 0.28;
-use Locale::Country;
use FS::UID qw( getotaker dbh driver_name );
use FS::Record qw( qsearchs qsearch dbdef regexp_sql );
-use FS::Misc qw( generate_email send_email generate_ps do_print );
+use FS::Misc qw( generate_email send_email generate_ps do_print money_pretty card_types );
use FS::Msgcat qw(gettext);
use FS::CurrentUser;
use FS::TicketSystem;
#$FS::UID::callback{'FS::cust_main'} = sub {
install_callback FS::UID sub {
$conf = new FS::Conf;
- #yes, need it for stuff below (prolly should be cached)
+ $default_agent_custid = $conf->exists('cust_main-default_agent_custid');
+ $custnum_display_length = $conf->config('cust_main-custnum-display_length');
};
sub _cache {
$self->auto_agent_custid()
if $conf->config('cust_main-auto_agent_custid') && ! $self->agent_custid;
- my $error = $self->SUPER::insert;
+ my $error = $self->check_payinfo_cardtype
+ || $self->SUPER::insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
#return "inserting cust_main record (transaction rolled back): $error";
|| $old->payby =~ /^(CHEK|DCHK)$/ && $self->payby =~ /^(CHEK|DCHK)$/ )
&& ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+ if ( $self->payby =~ /^(CARD|DCRD)$/
+ && $old->payinfo ne $self->payinfo
+ && $old->paymask ne $self->paymask )
+ {
+ my $error = $self->check_payinfo_cardtype;
+ return $error if $error;
+ }
+
return "Invoicing locale is required"
if $old->locale
&& ! $self->locale
$self->SUPER::check;
}
+sub check_payinfo_cardtype {
+ my $self = shift;
+
+ return '' unless $self->payby =~ /^(CARD|DCRD)$/;
+
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/\D//g;
+
+ return '' if $payinfo =~ /^99\d{14}$/; #token
+
+ my %bop_card_types = map { $_=>1 } values %{ card_types() };
+ my $cardtype = cardtype($payinfo);
+
+ return "$cardtype not accepted" unless $bop_card_types{$cardtype};
+
+ '';
+
+}
+
=item replace_check
Additional checks for replace only.
}else{
$amount = sprintf("%.2f", $self->balance - $self->in_transit_payments);
}
- return '' unless $amount > 0;
+ if ($amount <= 0) {
+ warn(sprintf("Customer balance %.2f - in transit amount %.2f is <= 0.\n",
+ $self->balance,
+ $self->in_transit_payments
+ ));
+ return;
+ }
my $invnum = delete $options{invnum};
my $payby = $options{payby} || $self->payby; #still dubious
foreach my $cust_pay_batch ( qsearch('cust_pay_batch', {
'batchnum' => $pay_batch->batchnum,
'custnum' => $self->custnum,
+ 'status' => '',
} ) ) {
$in_transit_payments += $cust_pay_batch->amount;
}
sub display_custnum {
my $self = shift;
+ return $self->agent_custid
+ if $default_agent_custid && $self->agent_custid;
+
my $prefix = $conf->config('cust_main-custnum-display_prefix', $self->agentnum) || '';
- if ( my $special = $conf->config('cust_main-custnum-display_special') ) {
- if ( $special eq 'CoStAg' ) {
- $prefix = uc( join('',
- $self->country,
- ($self->state =~ /^(..)/),
- $prefix || ($self->agent->agent =~ /^(..)/)
- ) );
- }
- elsif ( $special eq 'CoStCl' ) {
- $prefix = uc( join('',
- $self->country,
- ($self->state =~ /^(..)/),
- ($self->classnum ? $self->cust_class->classname =~ /^(..)/ : '__')
- ) );
- }
- # add any others here if needed
- }
- my $length = $conf->config('cust_main-custnum-display_length');
- if ( $conf->exists('cust_main-default_agent_custid') && $self->agent_custid ){
- return $self->agent_custid;
- } elsif ( $prefix ) {
- $length = 8 if !defined($length);
+ if ( $prefix ) {
return $prefix .
- sprintf('%0'.$length.'d', $self->custnum)
- } elsif ( $length ) {
- return sprintf('%0'.$length.'d', $self->custnum);
+ sprintf('%0'.($custnum_display_length||8).'d', $self->custnum)
+ } elsif ( $custnum_display_length ) {
+ return sprintf('%0'.$custnum_display_length.'d', $self->custnum);
} else {
return $self->custnum;
}
$contact->get('first') . ' '. $contact->get('last');
}
-#XXX this doesn't work in 3.x+
-#=item country_full
-#
-#Returns this customer's full country name
-#
-#=cut
-#
-#sub country_full {
-# my $self = shift;
-# code2country($self->country);
-#}
-
sub bill_country_full {
my $self = shift;
- code2country($self->bill_location->country);
+ $self->bill_location->country_full;
}
sub ship_country_full {
my $self = shift;
- code2country($self->ship_location->country);
+ $self->ship_location->country_full;
}
=item county_state_county [ PREFIX ]
sub cust_status {
my $self = shift;
+ return $self->hashref->{cust_status} if $self->hashref->{cust_status};
for my $status ( FS::cust_main->statuses() ) {
my $method = $status.'_sql';
my $numnum = ( my $sql = $self->$method() ) =~ s/cust_main\.custnum/?/g;
my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr;
$sth->execute( ($self->custnum) x $numnum )
or die "Error executing 'SELECT $sql': ". $sth->errstr;
- return $status if $sth->fetchrow_arrayref->[0];
+ if ( $sth->fetchrow_arrayref->[0] ) {
+ $self->hashref->{cust_status} = $status;
+ return $status;
+ }
}
}
(@tickets);
}
+=item appointments [ STATUS ]
+
+Returns an array of hashes representing the customer's RT tickets which
+are appointments.
+
+=cut
+
+sub appointments {
+ my $self = shift;
+ my $status = ( @_ && $_[0] ) ? shift : '';
+
+ return () unless $conf->config('ticket_system');
+
+ my $queueid = $conf->config('ticket_system-appointment-queueid');
+
+ @{ FS::TicketSystem->customer_tickets( $self->custnum,
+ 99,
+ undef,
+ $status,
+ $queueid,
+ )
+ };
+}
+
# Return services representing svc_accts in customer support packages
sub support_services {
my $self = shift;
=cut
# Caution: this gets used by FS::ClientAPI::MyAccount::billing_history,
-# and also payment_history_text, which should both be kept customer-friendly.
+# and also for sending customer statements, which should both be kept customer-friendly.
# If you add anything that shouldn't be passed on through the API or exposed
# to customers, add a new option to include it, don't include it by default
sub payment_history {
'amount' => sprintf('%.2f', $_->setup + $_->recur ),
'charged' => sprintf('%.2f', $_->setup + $_->recur ),
'date' => $cust_bill->_date,
- 'date_pretty' => time2str('%m/%d/%Y', $cust_bill->_date ),
+ 'date_pretty' => $self->time2str_local('short', $cust_bill->_date ),
}
foreach $cust_bill->cust_bill_pkg;
'amount' => sprintf('%.2f', $_->charged ),
'charged' => sprintf('%.2f', $_->charged ),
'date' => $_->_date,
- 'date_pretty' => time2str('%m/%d/%Y', $_->_date ),
+ 'date_pretty' => $self->time2str_local('short', $_->_date ),
}
foreach $self->cust_bill;
'amount' => sprintf('%.2f', 0 - $_->paid ),
'paid' => sprintf('%.2f', $_->paid ),
'date' => $_->_date,
- 'date_pretty' => time2str('%m/%d/%Y', $_->_date ),
+ 'date_pretty' => $self->time2str_local('short', $_->_date ),
}
foreach $self->cust_pay;
'amount' => sprintf('%.2f', 0 -$_->amount ),
'credit' => sprintf('%.2f', $_->amount ),
'date' => $_->_date,
- 'date_pretty' => time2str('%m/%d/%Y', $_->_date ),
+ 'date_pretty' => $self->time2str_local('short', $_->_date ),
}
foreach $self->cust_credit;
'amount' => $_->refund,
'refund' => $_->refund,
'date' => $_->_date,
- 'date_pretty' => time2str('%m/%d/%Y', $_->_date ),
+ 'date_pretty' => $self->time2str_local('short', $_->_date ),
}
foreach $self->cust_refund;
}
$$item{'balance'} = sprintf("%.2f",$balance);
foreach my $key ( qw(amount balance) ) {
- $$item{$key.'_pretty'} = $$item{$key};
- $$item{$key.'_pretty'} =~ s/^(-?)/$1$money_char/;
+ $$item{$key.'_pretty'} = money_pretty($$item{$key});
}
push(@out,$item);
}
'description' => 'Previous balance',
'amount' => sprintf("%.2f",$previous),
'balance' => sprintf("%.2f",$previous),
+ 'date' => $$opt{'start_date'},
+ 'date_pretty' => $self->time2str_local('short', $$opt{'start_date'} ),
};
#false laziness with above
foreach my $key ( qw(amount balance) ) {
return @out;
}
-=item payment_history_text
-
-Accepts the same options as L</payment_history> and returns those
-results as a string table with fixed-width columns, max width 80 char.
-
-=cut
-
-sub payment_history_text {
- my $self = shift;
- my $opt = ref($_[0]) ? $_[0] : { @_ };
- my $out = sprintf("%-12s",'Date');
- $out .= sprintf("%11s",'Amount') . ' ';
- $out .= sprintf("%11s",'Balance') . ' ';
- $out .= 'Description'; #don't need to pad with spaces
- $out .= "\n";
- foreach my $item ($self->payment_history($opt)) {
- $out .= sprintf("%-10.10s",$$item{'date_pretty'}) . ' '; #12 width
- $out .= sprintf("%11.11s",$$item{'amount_pretty'}) . ' '; #13 width
- $out .= sprintf("%11.11s",$$item{'balance_pretty'}) . ' '; #13 width
- $out .= sprintf("%.42s",$$item{'description'}); #max 42 width
- $out .= "\n";
- }
- return $out;
-}
-
=back
=head1 CLASS METHODS
into the template. These values may override values mentioned below
and those from the customer record.
+I<template_text> - if present, ignores TEMPLATE_NAME and uses the provided text
+
The following variables are available in the template instead of or in addition
to the fields of the customer record.
sub generate_letter {
my ($self, $template, %options) = @_;
- return unless $conf->exists($template);
+ warn "Template $template does not exist" && return
+ unless $conf->exists($template) || $options{'template_text'};
+
+ my $template_source = $options{'template_text'}
+ ? [ $options{'template_text'} ]
+ : [ map "$_\n", $conf->config($template) ];
my $letter_template = new Text::Template
( TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", $conf->config($template)],
+ SOURCE => $template_source,
DELIMITERS => [ '[@--', '--@]' ],
)
or die "can't create new Text::Template object: Text::Template::ERROR";