use strict;
#FS::cust_main:_Marketgear when they're ready to move to 2.1
use base qw( FS::cust_main::Packages FS::cust_main::Status
+ FS::cust_main::NationalID
FS::cust_main::Billing FS::cust_main::Billing_Realtime
FS::cust_main::Billing_Discount
FS::cust_main::Location
FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin
- FS::geocode_Mixin
+ FS::geocode_Mixin FS::Quotable_Mixin
FS::o2m_Common
FS::Record
);
use FS::cust_pkg;
use FS::cust_svc;
use FS::cust_bill;
+use FS::cust_bill_void;
use FS::legacy_cust_bill;
use FS::cust_pay;
use FS::cust_pay_pending;
warn " setting $l.custnum\n"
if $DEBUG > 1;
my $loc = $self->$l;
- $loc->set(custnum => $self->custnum);
- $error ||= $loc->replace;
+ unless ( $loc->custnum ) {
+ $loc->set(custnum => $self->custnum);
+ $error ||= $loc->replace;
+ }
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "Can't merge a customer into self" if $self->custnum == $new_custnum;
- unless ( qsearchs( 'cust_main', { 'custnum' => $new_custnum } ) ) {
- return "Invalid new customer number: $new_custnum";
- }
+ my $new_cust_main = qsearchs( 'cust_main', { 'custnum' => $new_custnum } )
+ or return "Invalid new customer number: $new_custnum";
+
+ return 'Access denied: "Merge customer across agents" access right required to merge into a customer of a different agent'
+ if $self->agentnum != $new_cust_main->agentnum
+ && ! $FS::CurrentUser::CurrentUser->access_right('Merge customer across agents');
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
tie my %financial_tables, 'Tie::IxHash',
'cust_bill' => 'invoices',
+ 'cust_bill_void' => 'voided invoices',
'cust_statement' => 'statements',
'cust_credit' => 'credits',
'cust_pay' => 'payments',
return "You are not permitted to create complimentary accounts.";
}
- # should be unnecessary--geocode will default to null on new locations
- #if ( $old->get('geocode') && $old->get('geocode') eq $self->get('geocode')
- # && $conf->exists('enable_taxproducts')
- # )
- #{
- # my $pre = ($conf->exists('tax-ship_address') && $self->ship_zip)
- # ? 'ship_' : '';
- # $self->set('geocode', '')
- # if $old->get($pre.'zip') ne $self->get($pre.'zip')
- # && length($self->get($pre.'zip')) >= 10;
- #}
-
- # set_coord/coord_auto stuff is now handled by cust_location
-
local($ignore_expired_card) = 1
if $old->payby =~ /^(CARD|DCRD)$/
&& $self->payby =~ /^(CARD|DCRD)$/
|| $self->ut_textn('custbatch')
|| $self->ut_name('last')
|| $self->ut_name('first')
- || $self->ut_snumbern('birthdate')
|| $self->ut_snumbern('signupdate')
+ || $self->ut_snumbern('birthdate')
+ || $self->ut_snumbern('spouse_birthdate')
+ || $self->ut_snumbern('anniversary_date')
|| $self->ut_textn('company')
|| $self->ut_anything('comments')
|| $self->ut_numbern('referral_custnum')
|| $self->ut_floatn('cdr_termination_percentage')
|| $self->ut_floatn('credit_limit')
|| $self->ut_numbern('billday')
+ || $self->ut_numbern('prorate_day')
|| $self->ut_enum('edit_subject', [ '', 'Y' ] )
|| $self->ut_enum('calling_list_exempt', [ '', 'Y' ] )
|| $self->ut_enum('invoice_noemail', [ '', 'Y' ] )
}
- #ship_ fields are gone
-
#$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/
# or return "Illegal payby: ". $self->payby;
#$self->payby($1);
L<FS::cust_pay_batch>), or, if the B<realtime> option is set to a true value,
runs the payment using a realtime gateway.
+Options may include:
+
+B<amount>: the amount to be paid; defaults to the customer's balance minus
+any payments in transit.
+
+B<payby>: the payment method; defaults to cust_main.payby
+
+B<realtime>: runs this as a realtime payment instead of adding it to a
+batch. Deprecated.
+
+B<invnum>: sets cust_pay_batch.invnum.
+
+B<address1>, B<address2>, B<city>, B<state>, B<zip>, B<country>: sets
+the billing address for the payment; defaults to the customer's billing
+location.
+
+B<payinfo>, B<paydate>, B<payname>: sets the payment account, expiration
+date, and name; defaults to those fields in cust_main.
+
=cut
sub batch_card {
'state' => $options{state} || $loc->state,
'zip' => $options{zip} || $loc->zip,
'country' => $options{country} || $loc->country,
- 'payby' => $options{payby} || $loc->payby,
- 'payinfo' => $options{payinfo} || $loc->payinfo,
- 'exp' => $options{paydate} || $loc->paydate,
- 'payname' => $options{payname} || $loc->payname,
+ 'payby' => $options{payby} || $self->payby,
+ 'payinfo' => $options{payinfo} || $self->payinfo,
+ 'exp' => $options{paydate} || $self->paydate,
+ 'payname' => $options{payname} || $self->payname,
'amount' => $amount, # consolidating
} );
'setuptax' => '', # or 'Y' for tax exempt
+ 'locationnum'=> 1234, # optional
+
#internal taxation
'taxclass' => 'Tax class',
my $no_auto = '';
my $cust_pkg_ref = '';
my ( $bill_now, $invoice_terms ) = ( 0, '' );
+ my $locationnum;
if ( ref( $_[0] ) ) {
$amount = $_[0]->{amount};
$quantity = exists($_[0]->{quantity}) ? $_[0]->{quantity} : 1;
$cust_pkg_ref = exists($_[0]->{cust_pkg_ref}) ? $_[0]->{cust_pkg_ref} : '';
$bill_now = exists($_[0]->{bill_now}) ? $_[0]->{bill_now} : '';
$invoice_terms = exists($_[0]->{invoice_terms}) ? $_[0]->{invoice_terms} : '';
+ $locationnum = $_[0]->{locationnum} || $self->ship_locationnum;
} else {
$amount = shift;
$quantity = 1;
'quantity' => $quantity,
'start_date' => $start_date,
'no_auto' => $no_auto,
+ 'locationnum'=> $locationnum,
} );
$error = $cust_pkg->insert;
=cut
+=item cust_bill_void
+
+Returns all the voided invoices (see L<FS::cust_bill_void>) for this customer.
+
+=cut
+
+sub cust_bill_void {
+ my $self = shift;
+
+ map { $_ } #return $self->num_cust_bill_void unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_bill_void', { 'custnum' => $self->custnum } )
+}
+
sub cust_statement {
my $self = shift;
my $opt = ref($_[0]) ? shift : { @_ };
=item cust_pay_batch [ OPTION => VALUE... | EXTRA_QSEARCH_PARAMS_HASHREF ]
-Returns all batched payments (see L<FS::cust_pay_void>) for this customer.
+Returns all batched payments (see L<FS::cust_pay_batch>) for this customer.
Optionally, a list or hashref of additional arguments to the qsearch call can
be passed.
'UPDATE h_cust_main SET paycvv = NULL WHERE paycvv IS NOT NULL',
);
+ #this seems to be the only expensive one.. why does it take so long?
unless ( FS::upgrade_journal->is_done('cust_main__signupdate') ) {
push @statements,
'UPDATE cust_main SET signupdate = (SELECT signupdate FROM h_cust_main WHERE signupdate IS NOT NULL AND h_cust_main.custnum = cust_main.custnum ORDER BY historynum DESC LIMIT 1) WHERE signupdate IS NULL';
foreach my $sql ( @statements ) {
my $sth = dbh->prepare($sql) or die dbh->errstr;
$sth->execute or die $sth->errstr;
- warn ( (time - $t). " seconds\n" );
- $t = time;
+ #warn ( (time - $t). " seconds\n" );
+ #$t = time;
}
local($ignore_expired_card) = 1;