+ if ( qsearch('cust_pay_pending', { 'custnum' => $self->custnum,
+ 'status' => { op=>'!=', value=>'done' },
+ }
+ )
+ ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Can't merge a customer with pending payments";
+ }
+
+ tie my %financial_tables, 'Tie::IxHash',
+ '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 ) {
+
+ my @records = $self->$table();
+
+ foreach my $record ( @records ) {
+ $record->custnum($new_custnum);
+ my $error = $record->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error merging ". $financial_tables{$table}. ": $error\n";
+ }
+ }
+
+ }
+
+ my $name = $self->ship_name; #?
+
+ my $locationnum = '';
+ foreach my $cust_pkg ( $self->all_pkgs ) {
+ $cust_pkg->custnum($new_custnum);
+
+ unless ( $cust_pkg->locationnum ) {
+ unless ( $locationnum ) {
+ my $cust_location = new FS::cust_location {
+ $self->location_hash,
+ 'custnum' => $new_custnum,
+ };
+ my $error = $cust_location->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $locationnum = $cust_location->locationnum;
+ }
+ $cust_pkg->locationnum($locationnum);
+ }
+
+ my $error = $cust_pkg->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ # add customer (ship) name to svc_phone.phone_name if blank
+ my @cust_svc = $cust_pkg->cust_svc;
+ foreach my $cust_svc (@cust_svc) {
+ my($label, $value, $svcdb) = $cust_svc->label;
+ next unless $svcdb eq 'svc_phone';
+ my $svc_phone = $cust_svc->svc_x;
+ next if $svc_phone->phone_name;
+ $svc_phone->phone_name($name);
+ my $error = $svc_phone->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ #not considered:
+ # cust_tax_exempt (texas tax exemptions)
+ # cust_recon (some sort of not-well understood thing for OnPac)
+
+ #these are moved over
+ foreach my $table (qw(
+ cust_tag cust_location contact cust_attachment cust_main_note
+ cust_tax_adjustment cust_pay_batch queue
+ )) {
+ foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) {
+ $record->custnum($new_custnum);
+ my $error = $record->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ #these aren't preserved
+ foreach my $table (qw(
+ cust_main_exemption cust_main_invoice
+ )) {
+ foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) {
+ my $error = $record->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+
+ my $sth = $dbh->prepare(
+ 'UPDATE cust_main SET referral_custnum = ? WHERE referral_custnum = ?'
+ ) or do {
+ my $errstr = $dbh->errstr;
+ $dbh->rollback if $oldAutoCommit;
+ return $errstr;
+ };
+ $sth->execute($new_custnum, $self->custnum) or do {
+ my $errstr = $sth->errstr;
+ $dbh->rollback if $oldAutoCommit;
+ return $errstr;
+ };
+
+ #tickets
+
+ my $ticket_dbh = '';
+ if ($conf->config('ticket_system') eq 'RT_Internal') {
+ $ticket_dbh = $dbh;
+ } elsif ($conf->config('ticket_system') eq 'RT_External') {
+ my ($datasrc, $user, $pass) = $conf->config('ticket_system-rt_external_datasrc');
+ $ticket_dbh = DBI->connect($datasrc, $user, $pass, { 'ChopBlanks' => 1 });
+ #or die "RT_External DBI->connect error: $DBI::errstr\n";
+ }
+
+ if ( $ticket_dbh ) {
+
+ my $ticket_sth = $ticket_dbh->prepare(
+ 'UPDATE Links SET Target = ? WHERE Target = ?'
+ ) or do {
+ my $errstr = $ticket_dbh->errstr;
+ $dbh->rollback if $oldAutoCommit;
+ return $errstr;
+ };
+ $ticket_sth->execute('freeside://freeside/cust_main/'.$new_custnum,
+ 'freeside://freeside/cust_main/'.$self->custnum)
+ or do {
+ my $errstr = $ticket_sth->errstr;
+ $dbh->rollback if $oldAutoCommit;
+ return $errstr;
+ };
+
+ }
+
+ #delete the customer record
+
+ my $error = $self->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item replace [ OLD_RECORD ] [ INVOICING_LIST_ARYREF ] [ , OPTION => VALUE ... ] ]
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+To change the customer's address, set the pseudo-fields C<bill_location> and
+C<ship_location>. The address will still only change if at least one of the
+address fields differs from the existing values.
+
+INVOICING_LIST_ARYREF: If you pass an arrarref to the insert method, it will
+be set as the invoicing list (see L<"invoicing_list">). Errors return as
+expected and rollback the entire transaction; it is not necessary to call
+check_invoicing_list first. Here's an example:
+
+ $new_cust_main->replace( $old_cust_main, [ $email, 'POST' ] );
+
+Currently available options are: I<tax_exemption>.
+
+The I<tax_exemption> option can be set to an arrayref of tax names or a hashref
+of tax names and exemption numbers. FS::cust_main_exemption records will be
+deleted and inserted as appropriate.
+
+=cut
+
+sub replace {
+ my $self = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $self->replace_old;
+
+ my @param = @_;
+
+ warn "$me replace called\n"
+ if $DEBUG;
+
+ my $curuser = $FS::CurrentUser::CurrentUser;
+ if ( $self->payby eq 'COMP'
+ && $self->payby ne $old->payby
+ && ! $curuser->access_right('Complimentary customer')
+ )
+ {
+ return "You are not permitted to create complimentary accounts.";
+ }
+
+ local($ignore_expired_card) = 1
+ if $old->payby =~ /^(CARD|DCRD)$/
+ && $self->payby =~ /^(CARD|DCRD)$/
+ && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+
+ local($ignore_banned_card) = 1
+ if ( $old->payby =~ /^(CARD|DCRD)$/ && $self->payby =~ /^(CARD|DCRD)$/
+ || $old->payby =~ /^(CHEK|DCHK)$/ && $self->payby =~ /^(CHEK|DCHK)$/ )
+ && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+
+ return "Invoicing locale is required"
+ if $old->locale
+ && ! $self->locale
+ && $conf->exists('cust_main-require_locale');
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ for my $l (qw(bill_location ship_location)) {
+ my $old_loc = $old->$l;
+ my $new_loc = $self->$l;
+
+ # find the existing location if there is one
+ $new_loc->set('custnum' => $self->custnum);
+ my $error = $new_loc->find_or_insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $self->set($l.'num', $new_loc->locationnum);
+ } #for $l
+
+ # replace the customer record
+ my $error = $self->SUPER::replace($old);
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ # now move packages to the new service location
+ $self->set('ship_location', ''); #flush cache
+ if ( $old->ship_locationnum and # should only be null during upgrade...
+ $old->ship_locationnum != $self->ship_locationnum ) {
+ $error = $old->ship_location->move_to($self->ship_location);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ # don't move packages based on the billing location, but
+ # disable it if it's no longer in use
+ if ( $old->bill_locationnum and
+ $old->bill_locationnum != $self->bill_locationnum ) {
+ $error = $old->bill_location->disable_if_unused;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ if ( @param && ref($param[0]) eq 'ARRAY' ) { # INVOICING_LIST_ARYREF
+ my $invoicing_list = shift @param;
+ $error = $self->check_invoicing_list( $invoicing_list );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $self->invoicing_list( $invoicing_list );
+ }
+
+ if ( $self->exists('tagnum') ) { #so we don't delete these on edit by accident