X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=b58a52c45f9cfd2e85fc1cc050b2f32de08a688e;hb=3a17b276638200475d54201fa62566b7440e819a;hp=74d7bcbfba0c86862178488a72ca2a4489af4ebd;hpb=0696d72bf5c5d0294b1c73fe6283da47eb2ceae1;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 74d7bcbfb..b58a52c45 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1291,58 +1291,60 @@ sub check { } - my @addfields = qw( - last first company address1 address2 city county state zip - country daytime night fax - ); + if ( $self->has_ship_address + && scalar ( grep { $self->getfield($_) ne $self->getfield("ship_$_") } + $self->addr_fields ) + ) + { + my $error = + $self->ut_name('ship_last') + || $self->ut_name('ship_first') + || $self->ut_textn('ship_company') + || $self->ut_text('ship_address1') + || $self->ut_textn('ship_address2') + || $self->ut_text('ship_city') + || $self->ut_textn('ship_county') + || $self->ut_textn('ship_state') + || $self->ut_country('ship_country') + ; + return $error if $error; - if ( defined $self->dbdef_table->column('ship_last') ) { - if ( scalar ( grep { $self->getfield($_) ne $self->getfield("ship_$_") } - @addfields ) - && scalar ( grep { $self->getfield("ship_$_") ne '' } @addfields ) - ) - { - my $error = - $self->ut_name('ship_last') - || $self->ut_name('ship_first') - || $self->ut_textn('ship_company') - || $self->ut_text('ship_address1') - || $self->ut_textn('ship_address2') - || $self->ut_text('ship_city') - || $self->ut_textn('ship_county') - || $self->ut_textn('ship_state') - || $self->ut_country('ship_country') - ; - return $error if $error; + #false laziness with above + unless ( qsearchs('cust_main_county', { + 'country' => $self->ship_country, + 'state' => '', + } ) ) { + return "Unknown ship_state/ship_county/ship_country: ". + $self->ship_state. "/". $self->ship_county. "/". $self->ship_country + unless qsearch('cust_main_county',{ + 'state' => $self->ship_state, + 'county' => $self->ship_county, + 'country' => $self->ship_country, + } ); + } + #eofalse - #false laziness with above - unless ( qsearchs('cust_main_county', { - 'country' => $self->ship_country, - 'state' => '', - } ) ) { - return "Unknown ship_state/ship_county/ship_country: ". - $self->ship_state. "/". $self->ship_county. "/". $self->ship_country - unless qsearch('cust_main_county',{ - 'state' => $self->ship_state, - 'county' => $self->ship_county, - 'country' => $self->ship_country, - } ); - } - #eofalse - - $error = - $self->ut_phonen('ship_daytime', $self->ship_country) - || $self->ut_phonen('ship_night', $self->ship_country) - || $self->ut_phonen('ship_fax', $self->ship_country) - || $self->ut_zip('ship_zip', $self->ship_country) - ; - return $error if $error; + $error = + $self->ut_phonen('ship_daytime', $self->ship_country) + || $self->ut_phonen('ship_night', $self->ship_country) + || $self->ut_phonen('ship_fax', $self->ship_country) + || $self->ut_zip('ship_zip', $self->ship_country) + ; + return $error if $error; + + return "Unit # is required." + if $self->ship_address2 =~ /^\s*$/ + && $conf->exists('cust_main-require_address2'); + + } else { # ship_ info eq billing info, so don't store dup info in database + + $self->setfield("ship_$_", '') + foreach $self->addr_fields; + + return "Unit # is required." + if $self->address2 =~ /^\s*$/ + && $conf->exists('cust_main-require_address2'); - } else { # ship_ info eq billing info, so don't store dup info in database - $self->setfield("ship_$_", '') - foreach qw( last first company address1 address2 city county state zip - country daytime night fax ); - } } #$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/ @@ -1543,6 +1545,30 @@ sub check { $self->SUPER::check; } +=item addr_fields + +Returns a list of fields which have ship_ duplicates. + +=cut + +sub addr_fields { + qw( last first company + address1 address2 city county state zip country + daytime night fax + ); +} + +=item has_ship_address + +Returns true if this customer record has a separate shipping address. + +=cut + +sub has_ship_address { + my $self = shift; + scalar( grep { $self->getfield("ship_$_") ne '' } $self->addr_fields ); +} + =item all_pkgs Returns all packages (see L) for this customer. @@ -2140,8 +2166,7 @@ sub bill { # only for figuring next bill date, nothing else, so, reset $sdate again # here $sdate = $cust_pkg->bill || $cust_pkg->setup || $time; - $cust_pkg->last_bill($sdate) - if $cust_pkg->dbdef_table->column('last_bill'); + $cust_pkg->last_bill($sdate); if ( $part_pkg->freq =~ /^\d+$/ ) { $mon += $part_pkg->freq; @@ -3144,7 +3169,7 @@ sub realtime_bop { 'gatewaynum' => ( $payment_gateway ? $payment_gateway->gatewaynum : '' ), }; $cust_pay_pending->payunique( $options{payunique} ) - if length($options{payunique}); + if defined($options{payunique}) && length($options{payunique}); my $cpp_new_err = $cust_pay_pending->insert; #mutex lost when this is inserted return $cpp_new_err if $cpp_new_err; @@ -3297,7 +3322,8 @@ sub realtime_bop { 'paydate' => $paydate, } ); #doesn't hurt to know, even though the dup check is in cust_pay_pending now - $cust_pay->payunique( $options{payunique} ) if length($options{payunique}); + $cust_pay->payunique( $options{payunique} ) + if defined($options{payunique}) && length($options{payunique}); my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; @@ -4589,13 +4615,13 @@ otherwise returns false. =cut sub credit { - my( $self, $amount, $reason ) = @_; + my( $self, $amount, $reason, %options ) = @_; my $cust_credit = new FS::cust_credit { 'custnum' => $self->custnum, 'amount' => $amount, 'reason' => $reason, }; - $cust_credit->insert; + $cust_credit->insert(%options); } =item charge AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ] @@ -4607,13 +4633,14 @@ the error, otherwise returns false. sub charge { my $self = shift; - my ( $amount, $pkg, $comment, $taxclass, $additional ); + my ( $amount, $pkg, $comment, $taxclass, $additional, $classnum ); if ( ref( $_[0] ) ) { $amount = $_[0]->{amount}; $pkg = exists($_[0]->{pkg}) ? $_[0]->{pkg} : 'One-time charge'; $comment = exists($_[0]->{comment}) ? $_[0]->{comment} : '$'. sprintf("%.2f",$amount); $taxclass = exists($_[0]->{taxclass}) ? $_[0]->{taxclass} : ''; + $classnum = exists($_[0]->{classnum}) ? $_[0]->{classnum} : ''; $additional = $_[0]->{additional}; }else{ $amount = shift; @@ -4640,6 +4667,7 @@ sub charge { 'plan' => 'flat', 'freq' => 0, 'disabled' => 'Y', + 'classnum' => $classnum ? $classnum : '', 'taxclass' => $taxclass, } ); @@ -5082,58 +5110,99 @@ Returns an SQL fragment to retreive the balance. =cut sub balance_sql { " - COALESCE( ( SELECT SUM(charged) FROM cust_bill - WHERE cust_bill.custnum = cust_main.custnum ), 0) - - COALESCE( ( SELECT SUM(paid) FROM cust_pay - WHERE cust_pay.custnum = cust_main.custnum ), 0) - - COALESCE( ( SELECT SUM(amount) FROM cust_credit - WHERE cust_credit.custnum = cust_main.custnum ), 0) - + COALESCE( ( SELECT SUM(refund) FROM cust_refund - WHERE cust_refund.custnum = cust_main.custnum ), 0) + ( SELECT COALESCE( SUM(charged), 0 ) FROM cust_bill + WHERE cust_bill.custnum = cust_main.custnum ) + - ( SELECT COALESCE( SUM(paid), 0 ) FROM cust_pay + WHERE cust_pay.custnum = cust_main.custnum ) + - ( SELECT COALESCE( SUM(amount), 0 ) FROM cust_credit + WHERE cust_credit.custnum = cust_main.custnum ) + + ( SELECT COALESCE( SUM(refund), 0 ) FROM cust_refund + WHERE cust_refund.custnum = cust_main.custnum ) "; } -=item balance_date_sql TIME +=item balance_date_sql START_TIME [ END_TIME [ OPTION => VALUE ... ] ] Returns an SQL fragment to retreive the balance for this customer, only -considering invoices with date earlier than TIME. (total_owed_date minus total_credited minus -total_unapplied_payments). TIME is specified as an SQL fragment or a numeric -UNIX timestamp; see L). Also see L and -L for conversion functions. +considering invoices with date earlier than START_TIME, and optionally not +later than END_TIME (total_owed_date minus total_credited minus +total_unapplied_payments). + +Times are specified as SQL fragments or numeric +UNIX timestamps; see L). Also see L and +L for conversion functions. The empty string can be passed +to disable that time constraint completely. + +Available options are: + +=over 4 + +=item unapplied_date - set to true to disregard unapplied credits, payments and refunds outside the specified time period - by default the time period restriction only applies to invoices (useful for reporting, probably a bad idea for event triggering) + +=item total - set to true to remove all customer comparison clauses, for totals + +=item where - WHERE clause hashref (elements "AND"ed together) (typically used with the total option) + +=item join - JOIN clause (typically used with the total option) + +=item + +=back =cut sub balance_date_sql { - my( $class, $time ) = @_; + my( $class, $start, $end, %opt ) = @_; - my $owed_sql = FS::cust_bill->owed_sql; - my $unapp_refund_sql = FS::cust_refund->unapplied_sql; - #my $unapp_credit_sql = FS::cust_credit->unapplied_sql; - my $unapp_credit_sql = FS::cust_credit->credited_sql; - my $unapp_pay_sql = FS::cust_pay->unapplied_sql; + my $owed = FS::cust_bill->owed_sql; + my $unapp_refund = FS::cust_refund->unapplied_sql; + my $unapp_credit = FS::cust_credit->unapplied_sql; + my $unapp_pay = FS::cust_pay->unapplied_sql; - " - COALESCE( ( SELECT SUM($owed_sql) FROM cust_bill - WHERE cust_bill.custnum = cust_main.custnum - AND cust_bill._date <= $time ) - ,0 - ) - + COALESCE( ( SELECT SUM($unapp_refund_sql) FROM cust_refund - WHERE cust_refund.custnum = cust_main.custnum ) - ,0 - ) - - COALESCE( ( SELECT SUM($unapp_credit_sql) FROM cust_credit - WHERE cust_credit.custnum = cust_main.custnum ) - ,0 - ) - - COALESCE( ( SELECT SUM($unapp_pay_sql) FROM cust_pay - WHERE cust_pay.custnum = cust_main.custnum ) - ,0 - ) + my $j = $opt{'join'} || ''; + + my $owed_wh = $class->_money_table_where( 'cust_bill', $start,$end,%opt ); + my $refund_wh = $class->_money_table_where( 'cust_refund', $start,$end,%opt ); + my $credit_wh = $class->_money_table_where( 'cust_credit', $start,$end,%opt ); + my $pay_wh = $class->_money_table_where( 'cust_pay', $start,$end,%opt ); + " ( SELECT COALESCE(SUM($owed), 0) FROM cust_bill $j $owed_wh ) + + ( SELECT COALESCE(SUM($unapp_refund), 0) FROM cust_refund $j $refund_wh ) + - ( SELECT COALESCE(SUM($unapp_credit), 0) FROM cust_credit $j $credit_wh ) + - ( SELECT COALESCE(SUM($unapp_pay), 0) FROM cust_pay $j $pay_wh ) "; } +=item _money_table_where TABLE START_TIME [ END_TIME [ OPTION => VALUE ... ] ] + +Helper method for balance_date_sql; name (and usage) subject to change +(suggestions welcome). + +Returns a WHERE clause for the specified monetary TABLE (cust_bill, +cust_refund, cust_credit or cust_pay). + +If TABLE is "cust_bill" or the unapplied_date option is true, only +considers records with date earlier than START_TIME, and optionally not +later than END_TIME . + +=cut + +sub _money_table_where { + my( $class, $table, $start, $end, %opt ) = @_; + + my @where = (); + push @where, "cust_main.custnum = $table.custnum" unless $opt{'total'}; + if ( $table eq 'cust_bill' || $opt{'unapplied_date'} ) { + push @where, "$table._date <= $start" if defined($start) && length($start); + push @where, "$table._date > $end" if defined($end) && length($end); + } + push @where, @{$opt{'where'}} if $opt{'where'}; + my $where = scalar(@where) ? 'WHERE '. join(' AND ', @where ) : ''; + + $where; + +} + =item fuzzy_search FUZZY_HASHREF [ HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ ] Performs a fuzzy (approximate) search and returns the matching FS::cust_main @@ -5262,7 +5331,14 @@ sub smart_search { } - } elsif ( $search =~ /^\s*(\d+)\s*$/ ) { # customer # search + # custnum search (also try agent_custid), with some tweaking options if your + # legacy cust "numbers" have letters + } elsif ( $search =~ /^\s*(\d+)\s*$/ + || ( $conf->config('cust_main-agent_custid-format') eq 'ww?d+' + && $search =~ /^\s*(\w\w?\d+)\s*$/ + ) + ) + { push @cust_main, qsearch( { 'table' => 'cust_main', @@ -5270,6 +5346,12 @@ sub smart_search { 'extra_sql' => " AND $agentnums_sql", #agent virtualization } ); + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { 'agent_custid' => $1, %options }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualization + } ); + } elsif ( $search =~ /^\s*(\S.*\S)\s+\((.+), ([^,]+)\)\s*$/ ) { my($company, $last, $first) = ( $1, $2, $3 ); @@ -5567,6 +5649,18 @@ sub batch_import { svc_acct.username svc_acct._password ); $payby = 'BILL'; + } elsif ( $format eq 'extended-plus_company' ) { + @fields = qw( agent_custid refnum + last first company address1 address2 city state zip country + daytime night + ship_last ship_first ship_company ship_address1 ship_address2 + ship_city ship_state ship_zip ship_country + payinfo paycvv paydate + invoicing_list + cust_pkg.pkgpart + svc_acct.username svc_acct._password + ); + $payby = 'BILL'; } else { die "unknown format $format"; } @@ -5869,7 +5963,7 @@ sub notify { $FS::notify_template::_template::company_address = join("\n", $conf->config('company_address') ). "\n"; - my $paydate = $customer->paydate; + my $paydate = $customer->paydate || '2037-12-31'; $FS::notify_template::_template::first = $customer->first; $FS::notify_template::_template::last = $customer->last; $FS::notify_template::_template::company = $customer->company; @@ -5944,8 +6038,8 @@ sub generate_letter { my %letter_data = map { $_ => $self->$_ } $self->fields; $letter_data{payinfo} = $self->mask_payinfo; - #my $paydate = $self->paydate || '2037-12'; - my $paydate = $self->paydate =~ /^\S+$/ ? $self->paydate : '2037-12'; + #my $paydate = $self->paydate || '2037-12-31'; + my $paydate = $self->paydate =~ /^\S+$/ ? $self->paydate : '2037-12-31'; my $payby = $self->payby; my ($payyear,$paymonth,$payday) = split (/-/,$paydate);