diff options
author | Ivan Kohler <ivan@freeside.biz> | 2016-04-25 09:58:14 -0700 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2016-04-25 09:58:14 -0700 |
commit | 46fe3dbcb3ca97d1f3c70d49351846cf0ab6461d (patch) | |
tree | 58588305290e276ceb9848ffe5f98ebe5b9041f2 /FS | |
parent | cc4caa54e9974ea3d6ac7cf55cf45863d2a8905e (diff) | |
parent | 55447b350a12dc435730bd8ab7ef300991ac4660 (diff) |
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Diffstat (limited to 'FS')
27 files changed, 915 insertions, 114 deletions
diff --git a/FS/FS/API.pm b/FS/FS/API.pm index 32400f7c3..9d0ef2515 100644 --- a/FS/FS/API.pm +++ b/FS/FS/API.pm @@ -8,6 +8,7 @@ use FS::cust_location; use FS::cust_pay; use FS::cust_credit; use FS::cust_refund; +use FS::cust_pkg; =head1 NAME @@ -556,6 +557,87 @@ sub location_info { return \%return; } +=item change_package_location + +Updates package location. Takes a list of keys and values +as paramters with the following keys: + +pkgnum + +secret + +locationnum - pass this, or the following keys (don't pass both) + +locationname + +address1 + +address2 + +city + +county + +state + +zip + +addr_clean + +country + +censustract + +censusyear + +location_type + +location_number + +location_kind + +incorporated + +On error, returns a hashref with an 'error' key. +On success, returns a hashref with 'pkgnum' and 'locationnum' keys, +containing the new values. + +=cut + +sub change_package_location { + my $class = shift; + my %opt = @_; + return _shared_secret_error() unless _check_shared_secret($opt{'secret'}); + + my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} }) + or return { 'error' => 'Unknown pkgnum' }; + + my %changeopt; + + foreach my $field ( qw( + locationnum + locationname + address1 + address2 + city + county + state + zip + addr_clean + country + censustract + censusyear + location_type + location_number + location_kind + incorporated + )) { + $changeopt{$field} = $opt{$field} if $opt{$field}; + } + + $cust_pkg->API_change(%changeopt); +} + =item bill_now OPTION => VALUE, ... Bills a single customer now, in the same fashion as the "Bill now" link in the diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index d6fa8652c..ed7e35317 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -3605,11 +3605,6 @@ sub adjust_ticket_priority { my($context, $session, $custnum) = _custoragent_session_custnum($p); return { 'error' => $session } if $context eq 'error'; - # temporary instrumentation for RT#39536 - local $DEBUG = 1; - local $FS::TicketSystem::RT_Internal::DEBUG = 1; - warn "[adjust_ticket_priority]\n" . Dumper($p->{'values'}) . "\n\n"; - # warn "$me adjust_ticket_priority: initializing ticket system\n" if $DEBUG; # FS::TicketSystem->init; my $ss_priority = FS::TicketSystem->selfservice_priority; diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index df9ee88e5..5010ec97d 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -7,7 +7,7 @@ use Data::Dumper; use Tie::RefHash; use Digest::SHA qw(sha512_hex); use FS::Conf; -use FS::Record qw(qsearch qsearchs dbdef); +use FS::Record qw(qsearch qsearchs dbdef dbh); use FS::CGI qw(popurl); use FS::Msgcat qw(gettext); use FS::Misc qw(card_types); @@ -31,6 +31,25 @@ use FS::cust_payby; $DEBUG = 1; $me = '[FS::ClientAPI::Signup]'; +=head1 NAME + +FS::ClientAPI::Signup - Front-end API for signing up customers + +=head1 DESCRIPTION + +This module provides the ClientAPI functions for talking to a signup +server. The signup server is open to the public, i.e. does not require a +login. The back-end Freeside server creates customers, orders packages and +services, and processes initial payments. + +=head1 METHODS + +=over 4 + +=cut + +# document the rest of this as we work on it + sub clear_cache { warn "$me clear_cache called\n" if $DEBUG; my $cache = new FS::ClientAPI_SessionCache( { @@ -499,21 +518,8 @@ sub new_customer { #possibly some validation will be needed } - my $agentnum; - if ( exists $packet->{'session_id'} ) { - my $cache = new FS::ClientAPI_SessionCache( { - 'namespace' => 'FS::ClientAPI::Agent', - } ); - my $session = $cache->get($packet->{'session_id'}); - if ( $session ) { - $agentnum = $session->{'agentnum'}; - } else { - return { 'error' => "Can't resume session" }; #better error message - } - } else { - $agentnum = $packet->{agentnum} - || $conf->config('signup_server-default_agentnum'); - } + my $agentnum = get_agentnum($packet); + return $agentnum if ref($agentnum); my ($bill_hash, $ship_hash); foreach my $f (FS::cust_main->location_fields) { @@ -924,21 +930,8 @@ sub new_customer_minimal { #possibly some validation will be needed } - my $agentnum; - if ( exists $packet->{'session_id'} ) { - my $cache = new FS::ClientAPI_SessionCache( { - 'namespace' => 'FS::ClientAPI::Agent', - } ); - my $session = $cache->get($packet->{'session_id'}); - if ( $session ) { - $agentnum = $session->{'agentnum'}; - } else { - return { 'error' => "Can't resume session" }; #better error message - } - } else { - $agentnum = $packet->{agentnum} - || $conf->config('signup_server-default_agentnum'); - } + my $agentnum = get_agentnum($packet); + return $agentnum if ref($agentnum); #shares some stuff with htdocs/edit/process/cust_main.cgi... take any # common that are still here and library them. @@ -1220,4 +1213,186 @@ sub capture_payment { } +=item get_agentnum PACKET + +Given a PACKET from the signup server, looks up the agentnum to use for signing +up a customer. This will use 'session_id' if the agent is authenticated, +otherwise 'agentnum', otherwise the 'signup_server-default_agentnum' config. If +the agent can't be found, returns an error packet. + +=cut + +sub get_agentnum { + my $packet = shift; + my $conf = new FS::Conf; + my $agentnum; + if ( exists $packet->{'session_id'} ) { + my $cache = new FS::ClientAPI_SessionCache( { + 'namespace' => 'FS::ClientAPI::Agent', + } ); + my $session = $cache->get($packet->{'session_id'}); + if ( $session ) { + $agentnum = $session->{'agentnum'}; + } else { + return { 'error' => "Can't resume session" }; #better error message + } + } else { + $agentnum = $packet->{agentnum} + || $conf->config('signup_server-default_agentnum'); + } + if ( $agentnum and FS::agent->count('agentnum = ?', $agentnum) ) { + return $agentnum; + } + return { 'error' => 'Signup is not configured' }; +} + +=item new_prospect PACKET + +Creates a new L<FS::prospect_main> entry. PACKET must contain: + +- either agentnum or session_id; if not, signup_server-default_agentnum will +be used and must not be empty + +- either refnum or referral_title; if not, signup_server-default_refnum will +be used and must not be empty + +- last and first (names), and optionally company and title + +- address1, city, state, country, zip, and optionally address2 + +- emailaddress + +and can also contain: + +- one or more of phone_daytime, phone_night, phone_mobile, and phone_fax + +- a 'comment' (will be attached to the contact) + +State and country will be normalized to Freeside state/country codes if +necessary. + +=cut + +sub new_prospect { + + my $packet = shift; + warn "$me new_prospect called\n".Dumper($packet) if $DEBUG; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + my $conf = FS::Conf->new; + + my $error; + + my $agentnum = get_agentnum($packet); + return $agentnum if ref $agentnum; + my $refnum; + if ( my $title = $packet->{referral_title} ) { + my $part_referral = qsearchs('part_referral', { + 'agentnum' => $agentnum, + 'title' => $title, + }); + $part_referral ||= qsearchs('part_referral', { + 'agentnum' => '', + 'title' => $title, + }); + if (!$part_referral) { + return { error => "Unknown referral type: '$title'" }; + } + $refnum = $part_referral->refnum; + } elsif ( $packet->{refnum} ) { + $refnum = $packet->{refnum}; + } + $refnum ||= $conf->config('signup_server-default_refnum'); + return { error => "Signup referral type is not configured" } if !$refnum; + + my $prospect = FS::prospect_main->new({ + 'agentnum' => $agentnum, + 'refnum' => $refnum, + 'company' => $packet->{company}, + }); + + my $location = FS::cust_location->new; + foreach ( qw(address1 address2 city county zip ) ) { + $location->set($_, $packet->{$_}); + } + # normalize country and state if they're not already ISO codes + # easier than doing it on the client side--we already have the tables here + my $country = $packet->{country}; + my $state = $packet->{state}; + if (length($country) > 2) { + # it likes title case + $country = join(' ', map ucfirst, split(/\s+/, $country)); + my $lsc = Locale::SubCountry->new($country); + if ($lsc) { + $country = uc($lsc->country_code); + + if ($lsc->has_sub_countries) { + if ( $lsc->full_name($state) eq 'unknown' ) { + # then we were probably given a full name, so resolve it + $state = $lsc->code($state); + if ( $state eq 'unknown' ) { + # doesn't resolve as a full name either, return an error + $error = "Unknown state: ".$packet->{state}; + } else { + $state = uc($state); + } + } + } # else state doesn't matter + } else { + # couldn't find the country in LSC + $error = "Unknown country: $country"; + } + } + $location->set('country', $country); + $location->set('state', $state); + $prospect->set('cust_location', $location); + + $error ||= $prospect->insert; # also does location + return { error => $error } if $error; + + my $contact = FS::contact->new({ + prospectnum => $prospect->prospectnum, + locationnum => $location->locationnum, + invoice_dest => 'Y', + }); + # use emailaddress pseudo-field behavior here + foreach (qw(last first title emailaddress comment)) { + $contact->set($_, $packet->{$_}); + } + $error = $contact->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return { error => $error }; + } + + foreach my $phone_type (qsearch('phone_type', {})) { + my $key = 'phone_' . lc($phone_type->typename); + my $phonenum = $packet->{$key}; + if ( $phonenum ) { + # just to not have to supply country code from the other end + my $number = Number::Phone->new($location->country, $phonenum); + if (!$number) { + $error = 'invalid phone number'; + } else { + my $phone = FS::contact_phone->new({ + contactnum => $contact->contactnum, + phonenum => $phonenum, + countrycode => $number->country_code, + phonetypenum => $phone_type->phonetypenum, + }); + $error = $phone->insert; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return { error => $phone_type->typename . ' phone: ' . $error }; + } + } + } # foreach $phone_type + + $dbh->commit if $oldAutoCommit; + return { prospectnum => $prospect->prospectnum }; +} + 1; diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index 2dea80129..622f3df05 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -192,6 +192,7 @@ sub ss2clientapi { 'new_customer_minimal' => 'Signup/new_customer_minimal', 'capture_payment' => 'Signup/capture_payment', 'clear_signup_cache' => 'Signup/clear_cache', + 'new_prospect' => 'Signup/new_prospect', 'new_agent' => 'Agent/new_agent', 'agent_login' => 'Agent/agent_login', 'agent_logout' => 'Agent/agent_logout', diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index d0b1180cb..745315816 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1389,7 +1389,7 @@ and customer address. Include units.', { 'key' => 'invoice_latexextracouponspace', 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice textheight space to reserve for a tear off coupon. Include units. Default is 3.6cm', + 'description' => 'Optional LaTeX invoice textheight space to reserve for a tear off coupon. Include units. Default is 2.7 inches.', 'type' => 'text', 'per_agent' => 1, 'validate' => sub { shift =~ @@ -1401,7 +1401,7 @@ and customer address. Include units.', { 'key' => 'invoice_latexcouponfootsep', 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice separation between tear off coupon and footer. Include units.', + 'description' => 'Optional LaTeX invoice separation between bottom of coupon address and footer. Include units. Default is 0.2 inches.', 'type' => 'text', 'per_agent' => 1, 'validate' => sub { shift =~ @@ -1413,7 +1413,7 @@ and customer address. Include units.', { 'key' => 'invoice_latexcouponamountenclosedsep', 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice separation between total due and amount enclosed line. Include units.', + 'description' => 'Optional LaTeX invoice separation between total due and amount enclosed line. Include units. Default is 2.25 em.', 'type' => 'text', 'per_agent' => 1, 'validate' => sub { shift =~ @@ -1424,7 +1424,7 @@ and customer address. Include units.', { 'key' => 'invoice_latexcoupontoaddresssep', 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice separation between invoice data and the to address (usually invoice_latexreturnaddress). Include units.', + 'description' => 'Optional LaTeX invoice separation between invoice data and the address (usually invoice_latexreturnaddress). Include units. Default is 1 inch.', 'type' => 'text', 'per_agent' => 1, 'validate' => sub { shift =~ @@ -1972,7 +1972,7 @@ and customer address. Include units.', 'description' => 'Enables the automatic unsuspension of suspended packages when a customer\'s balance due is at or below the specified amount after a payment or credit', 'type' => 'select', 'select_enum' => [ - '', 'Zero', 'Latest invoice charges' + '', 'Zero', 'Latest invoice charges', 'Charges not past due' ], }, @@ -5306,7 +5306,7 @@ and customer address. Include units.', my @part_export = map { qsearch( 'part_export', {exporttype => $_ } ) } keys %{FS::part_export::export_info('cust_main')}; - map { $_->exportnum => $_->exporttype.' to '.$_->machine } @part_export; + map { $_->exportnum => $_->exportname } @part_export; }, 'option_sub' => sub { require FS::Record; @@ -5315,7 +5315,7 @@ and customer address. Include units.', 'part_export', { 'exportnum' => shift } ); $part_export - ? $part_export->exporttype.' to '.$part_export->machine + ? $part_export->exportname : ''; }, }, @@ -5324,7 +5324,7 @@ and customer address. Include units.', { 'key' => 'cust_location-exports', 'section' => '', - 'description' => 'Export(s) to call on cust_location insert, modification and deletion.', + 'description' => 'Export(s) to call on cust_location insert or modification', 'type' => 'select-sub', 'multiple' => 1, 'options_sub' => sub { @@ -5333,7 +5333,7 @@ and customer address. Include units.', my @part_export = map { qsearch( 'part_export', {exporttype => $_ } ) } keys %{FS::part_export::export_info('cust_location')}; - map { $_->exportnum => $_->exporttype.' to '.$_->machine } @part_export; + map { $_->exportnum => $_->exportname } @part_export; }, 'option_sub' => sub { require FS::Record; @@ -5342,7 +5342,7 @@ and customer address. Include units.', 'part_export', { 'exportnum' => shift } ); $part_export - ? $part_export->exporttype.' to '.$part_export->machine + ? $part_export->exportname : ''; }, }, diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index aa9d3bebf..7b4db9932 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -146,7 +146,8 @@ if ( -e $addl_handler_use_file ) { use FS::Report::Table; use FS::Report::Table::Monthly; use FS::Report::Table::Daily; - use FS::Report::Tax; + use FS::Report::Tax::ByName; + use FS::Report::Tax::All; use FS::TicketSystem; use FS::NetworkMonitoringSystem; use FS::Tron qw( tron_lint ); diff --git a/FS/FS/Report/Tax/All.pm b/FS/FS/Report/Tax/All.pm new file mode 100644 index 000000000..26dbf5f0f --- /dev/null +++ b/FS/FS/Report/Tax/All.pm @@ -0,0 +1,110 @@ +package FS::Report::Tax::All; + +use strict; +use vars qw($DEBUG); +use FS::Record qw(dbh qsearch qsearchs group_concat_sql); +use FS::Report::Tax::ByName; +use Date::Format qw( time2str ); + +use Data::Dumper; + +$DEBUG = 0; + +=item report OPTIONS + +Constructor. Generates a tax report using the internal tax rate system, +showing all taxes, broken down by tax name and country. + +Required parameters: +- beginning, ending: the date range as Unix timestamps. + +Optional parameters: +- debug: sets the debug level. 1 will warn the data collected for the report; +2 will also warn all of the SQL statements. + +=cut + +# because there's not yet a "DBIx::DBSchema::View"... + +sub report { + my $class = shift; + my %opt = @_; + + $DEBUG ||= $opt{debug}; + + my($beginning, $ending) = @opt{'beginning', 'ending'}; + + # figure out which reports we need to run + my @taxname_and_country = qsearch({ + table => 'cust_main_county', + select => 'country, taxname', + hashref => { + tax => { op => '>', value => '0' } + }, + order_by => 'GROUP BY country, taxname ORDER BY country, taxname', + }); + my @table; + foreach (@taxname_and_country) { + my $taxname = $_->taxname || 'Tax'; + my $country = $_->country; + my $report = FS::Report::Tax::ByName->report( + %opt, + taxname => $taxname, + country => $country, + total_only => 1, + ); + # will have only one total row (should be only one row at all) + my ($total_row) = grep { $_->{total} } $report->table; + $total_row->{total} = 0; # but in this context it's a detail row + $total_row->{taxname} = $taxname; + $total_row->{country} = $country; + $total_row->{label} = "$country - $taxname"; + push @table, $total_row; + } + my $self = bless { + 'opt' => \%opt, + 'table' => \@table, + }, $class; + + $self; +} + +sub opt { + my $self = shift; + $self->{opt}; +} + +sub data { + my $self = shift; + $self->{data}; +} + +# sub fetchall_array... + +sub table { + my $self = shift; + @{ $self->{table} }; +} + +sub title { + my $self = shift; + my $string = ''; + if ( $self->{opt}->{agentnum} ) { + my $agent = qsearchs('agent', { agentnum => $self->{opt}->{agentnum} }); + $string .= $agent->agent . ' '; + } + $string .= 'Tax Report: '; # XXX localization + if ( $self->{opt}->{beginning} ) { + $string .= time2str('%h %o %Y ', $self->{opt}->{beginning}); + } + $string .= 'through '; + if ( $self->{opt}->{ending} and $self->{opt}->{ending} < 4294967295 ) { + $string .= time2str('%h %o %Y', $self->{opt}->{ending}); + } else { + $string .= 'now'; + } + $string .= ' - all taxes'; + return $string; +} + +1; diff --git a/FS/FS/Report/Tax.pm b/FS/FS/Report/Tax/ByName.pm index f1f6be38e..88695b909 100644 --- a/FS/FS/Report/Tax.pm +++ b/FS/FS/Report/Tax/ByName.pm @@ -1,4 +1,4 @@ -package FS::Report::Tax; +package FS::Report::Tax::ByName; use strict; use vars qw($DEBUG); @@ -9,10 +9,12 @@ use Data::Dumper; $DEBUG = 0; -=item report_internal OPTIONS +=item report OPTIONS Constructor. Generates a tax report using the internal tax rate system -(L<FS::cust_main_county>). +(L<FS::cust_main_county>), showing all taxes with a specified tax name, +broken down by state/county. Optionally, the taxes can be broken down further +by city/district, tax class, or package class. Required parameters: @@ -22,21 +24,22 @@ Required parameters: Optional parameters: - agentnum: limit to this agentnum.num. -- breakdown: hashref of the fields to group by. Keys can be 'city', 'district', - 'pkgclass', or 'taxclass'; values should be true. +- breakdown: hashref of the fields to group by. Keys can be 'city', +'district', 'pkgclass', or 'taxclass'; values should be true. +- total_only: don't run the tax group queries, only the totals queries. +Returns one row, except in the unlikely event you're using breakdown by +package class. - debug: sets the debug level. 1 will warn the data collected for the report; - 2 will also warn all of the SQL statements. +2 will also warn all of the SQL statements. =cut -sub report_internal { +sub report { my $class = shift; my %opt = @_; $DEBUG ||= $opt{debug}; - my $conf = new FS::Conf; - my($beginning, $ending) = @opt{'beginning', 'ending'}; my ($taxname, $country, %breakdown); diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 2cb942524..d94f963a6 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -923,12 +923,13 @@ sub tables_hashref { '_date', @date_type, '', '', 'status', 'varchar', '', $char_d, '', '', 'statustext', 'text', 'NULL', '', '', '', + 'no_action', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'eventnum', #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ], 'unique' => [], 'index' => [ ['eventpart'], ['tablenum'], ['status'], - ['statustext'], ['_date'], + ['statustext'], ['_date'], ['no_action'], ], 'foreign_keys' => [ { columns => [ 'eventpart' ], @@ -3590,10 +3591,11 @@ sub tables_hashref { 'refnum', 'serial', '', '', '', '', 'referral', 'varchar', '', $char_d, '', '', 'disabled', 'char', 'NULL', 1, '', '', - 'agentnum', 'int', 'NULL', '', '', '', + 'agentnum', 'int', 'NULL', '', '', '', + 'title', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'refnum', - 'unique' => [], + 'unique' => [ ['agentnum', 'title'] ], 'index' => [ ['disabled'], ['agentnum'], ], 'foreign_keys' => [ { columns => [ 'agentnum' ], diff --git a/FS/FS/TicketSystem/RT_Internal.pm b/FS/FS/TicketSystem/RT_Internal.pm index 6b2d3544c..1c4513e6d 100644 --- a/FS/FS/TicketSystem/RT_Internal.pm +++ b/FS/FS/TicketSystem/RT_Internal.pm @@ -468,9 +468,6 @@ sub get_ticket_object { } my $Tickets = RT::Tickets->new($session->{CurrentUser}); $Tickets->FromSQL($query); - if ( $DEBUG ) { # temporary for RT#39536 - warn "[get_ticket_object] " . $Tickets->BuildSelectQuery . "\n\n"; - } return $Tickets->First; } diff --git a/FS/FS/cust_event.pm b/FS/FS/cust_event.pm index 1d8af1e6e..3edfaefde 100644 --- a/FS/FS/cust_event.pm +++ b/FS/FS/cust_event.pm @@ -54,6 +54,13 @@ L<Time::Local> and L<Date::Parse> for conversion functions. =item statustext - additional status detail (i.e. error or progress message) +=item no_action - 'Y' if the event action wasn't performed. Some actions +contain an internal check to see if the action is going to be impossible (for +example, emailing a notice to a customer who has no email address), and if so, +won't attempt the action. It shouldn't be reported as a failure because +there's no need to retry it. However, the action should set no_action = 'Y' +so that there's a record. + =back =head1 METHODS @@ -141,6 +148,7 @@ sub check { || $self->ut_number('_date') || $self->ut_enum('status', [qw( new locked done failed initial)]) || $self->ut_anything('statustext') + || $self->ut_flag('no_action') ; return $error if $error; @@ -372,11 +380,46 @@ sub search_sql_where { push @search, "cust_event._date <= $1"; } - if ( $param->{'failed'} ) { - push @search, "statustext != ''", - "statustext IS NOT NULL", - "statustext != 'N/A'"; - } + #if ( $param->{'failed'} ) { + # push @search, "statustext != ''", + # "statustext IS NOT NULL", + # "statustext != 'N/A'"; + #} + # huh? + + if ( $param->{'event_status'} ) { + + my @status; + my ($done_Y, $done_N); + foreach (@{ $param->{'event_status'} }) { + if ($_ eq 'done_Y') { + $done_Y = 1; + } elsif ( $_ eq 'done_N' ) { + $done_N = 1; + } else { + push @status, $_; + } + } + if ( $done_Y or $done_N ) { + push @status, 'done'; + } + if ( @status ) { + push @search, "cust_event.status IN(" . + join(',', map "'$_'", @status) . + ')'; + } + + if ( $done_Y and not $done_N ) { + push @search, "cust_event.no_action IS NULL"; + } elsif ( $done_N and not $done_Y ) { + push @search, "cust_event.no_action = 'Y'"; + } # else they're both true, so don't add a constraint, or both false, + # and it doesn't matter. + + } # event_status + + # always hide initialization + push @search, 'cust_event.status != \'initial\''; if ( $param->{'custnum'} =~ /^(\d+)$/ ) { push @search, "cust_main.custnum = '$1'"; diff --git a/FS/FS/cust_location.pm b/FS/FS/cust_location.pm index 2b8a5c88d..f38e8efcd 100644 --- a/FS/FS/cust_location.pm +++ b/FS/FS/cust_location.pm @@ -249,20 +249,22 @@ sub insert { # cust_location exports #my $export_args = $options{'export_args'} || []; - my @part_export = - map qsearch( 'part_export', {exportnum=>$_} ), - $conf->config('cust_location-exports'); #, $agentnum - - foreach my $part_export ( @part_export ) { - my $error = $part_export->export_insert($self); #, @$export_args); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "exporting to ". $part_export->exporttype. - " (transaction rolled back): $error"; + # don't export custnum_pending cases, let follow-up replace handle that + if ($self->custnum || $self->prospectnum) { + my @part_export = + map qsearch( 'part_export', {exportnum=>$_} ), + $conf->config('cust_location-exports'); #, $agentnum + + foreach my $part_export ( @part_export ) { + my $error = $part_export->export_insert($self); #, @$export_args); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "exporting to ". $part_export->exporttype. + " (transaction rolled back): $error"; + } } } - $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } @@ -311,20 +313,22 @@ sub replace { # cust_location exports #my $export_args = $options{'export_args'} || []; - my @part_export = - map qsearch( 'part_export', {exportnum=>$_} ), - $conf->config('cust_location-exports'); #, $agentnum - - foreach my $part_export ( @part_export ) { - my $error = $part_export->export_replace($self, $old); #, @$export_args); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "exporting to ". $part_export->exporttype. - " (transaction rolled back): $error"; + # don't export custnum_pending cases, let follow-up replace handle that + if ($self->custnum || $self->prospectnum) { + my @part_export = + map qsearch( 'part_export', {exportnum=>$_} ), + $conf->config('cust_location-exports'); #, $agentnum + + foreach my $part_export ( @part_export ) { + my $error = $part_export->export_replace($self, $old); #, @$export_args); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "exporting to ". $part_export->exporttype. + " (transaction rolled back): $error"; + } } } - $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } @@ -724,7 +728,7 @@ sub label_prefix { $prefix; } -=item county_state_county +=item county_state_country Returns a string consisting of just the county, state and country. diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index 9a2a9d7b1..bbba8c5f7 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -669,11 +669,25 @@ sub unsuspend_balance { my $maxbalance; if ($setting eq 'Zero') { $maxbalance = 0; + + # kind of a pain to load/check all cust_bill instead of just open ones, + # but if for some reason payment gets applied to later bills before + # earlier ones, we still want to consider the later ones as allowable balance } elsif ($setting eq 'Latest invoice charges') { my @cust_bill = $cust_main->cust_bill(); my $cust_bill = $cust_bill[-1]; #always want the most recent one - return unless $cust_bill; - $maxbalance = $cust_bill->charged || 0; + if ($cust_bill) { + $maxbalance = $cust_bill->charged || 0; + } else { + $maxbalance = 0; + } + } elsif ($setting eq 'Charges not past due') { + my $now = time; + $maxbalance = 0; + foreach my $cust_bill ($cust_main->cust_bill()) { + next unless $now <= ($cust_bill->due_date || $cust_bill->_date); + $maxbalance += $cust_bill->charged || 0; + } } elsif (length($setting)) { warn "Unrecognized unsuspend_balance setting $setting"; return; diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 1bd18e015..1cc82357e 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -442,6 +442,21 @@ sub insert { my $conf = new FS::Conf; + if ($self->locationnum) { + my @part_export = + map qsearch( 'part_export', {exportnum=>$_} ), + $conf->config('cust_location-exports'); #, $agentnum + + foreach my $part_export ( @part_export ) { + my $error = $part_export->export_pkg_location($self); #, @$export_args); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "exporting to ". $part_export->exporttype. + " (transaction rolled back): $error"; + } + } + } + if ( ! $import && $conf->config('ticket_system') && $options{ticket_subject} ) { #this init stuff is still inefficient, but at least its limited to @@ -696,6 +711,24 @@ sub replace { } } + # also run exports if removing locationnum? + # doesn't seem to happen, and we don't export blank locationnum on insert... + if ($new->locationnum and ($new->locationnum != $old->locationnum)) { + my $conf = new FS::Conf; + my @part_export = + map qsearch( 'part_export', {exportnum=>$_} ), + $conf->config('cust_location-exports'); #, $agentnum + + foreach my $part_export ( @part_export ) { + my $error = $part_export->export_pkg_location($new); #, @$export_args); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "exporting to ". $part_export->exporttype. + " (transaction rolled back): $error"; + } + } + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -2132,7 +2165,7 @@ sub change { my $time = time; - $hash{'setup'} = $time if $self->setup; + $hash{'setup'} = $time if $self->get('setup'); $hash{'change_date'} = $time; $hash{"change_$_"} = $self->$_() @@ -2153,16 +2186,18 @@ sub change { my $unused_credit = 0; my $keep_dates = $opt->{'keep_dates'}; - # Special case. If the pkgpart is changing, and the customer is - # going to be credited for remaining time, don't keep setup, bill, - # or last_bill dates, and DO pass the flag to cancel() to credit - # the customer. + # Special case. If the pkgpart is changing, and the customer is going to be + # credited for remaining time, don't keep setup, bill, or last_bill dates, + # and DO pass the flag to cancel() to credit the customer. If the old + # package had a setup date, set the new package's setup to the package + # change date so that it has the same status as before. if ( $opt->{'pkgpart'} and $opt->{'pkgpart'} != $self->pkgpart and $self->part_pkg->option('unused_credit_change', 1) ) { $unused_credit = 1; $keep_dates = 0; - $hash{$_} = '' foreach qw(setup bill last_bill); + $hash{'last_bill'} = ''; + $hash{'bill'} = ''; } if ( $keep_dates ) { diff --git a/FS/FS/cust_pkg/API.pm b/FS/FS/cust_pkg/API.pm index f87eed345..837cf40cc 100644 --- a/FS/FS/cust_pkg/API.pm +++ b/FS/FS/cust_pkg/API.pm @@ -10,4 +10,66 @@ sub API_getinfo { } +# currently only handles location change... +# eventually have it handle all sorts of package changes +sub API_change { + my $self = shift; + my %opt = @_; + + return { 'error' => 'Cannot change canceled package' } + if $self->get('cancel'); + + my %changeopt; + + # update location--accepts raw fields OR location + my %location_hash; + foreach my $field ( qw( + locationname + address1 + address2 + city + county + state + zip + addr_clean + country + censustract + censusyear + location_type + location_number + location_kind + incorporated + ) ) { + $location_hash{$field} = $opt{$field} if $opt{$field}; + } + return { 'error' => 'Cannot pass both locationnum and location fields' } + if $opt{'locationnum'} && %location_hash; + + if (%location_hash) { + my $cust_location = FS::cust_location->new({ + 'custnum' => $self->custnum, + %location_hash, + }); + $changeopt{'cust_location'} = $cust_location; + } elsif ($opt{'locationnum'}) { + $changeopt{'locationnum'} = $opt{'locationnum'}; + } + + # not quite "nothing changed" because passed changes might be identical to current record, + # we don't currently check for that, don't want to imply that we do...but maybe we should? + return { 'error' => 'No changes passed to method' } + unless $changeopt{'cust_location'} || $changeopt{'locationnum'}; + + $changeopt{'keep_dates'} = 1; + + my $pkg_or_error = $self->change( \%changeopt ); + my $error = ref($pkg_or_error) ? '' : $pkg_or_error; + + return { 'error' => $error } if $error; + + # return all fields? we don't yet expose them through FS::API + return { map { $_ => $pkg_or_error->get($_) } qw( pkgnum locationnum ) }; + +} + 1; diff --git a/FS/FS/part_event/Action/cust_bill_email.pm b/FS/FS/part_event/Action/cust_bill_email.pm index 3331a4cb6..80bcaa1a7 100644 --- a/FS/FS/part_event/Action/cust_bill_email.pm +++ b/FS/FS/part_event/Action/cust_bill_email.pm @@ -20,12 +20,18 @@ sub option_fields { sub default_weight { 51; } sub do_action { - my( $self, $cust_bill ) = @_; + my( $self, $cust_bill, $cust_event ) = @_; my $cust_main = $cust_bill->cust_main; $cust_bill->set('mode' => $self->option('modenum')); - $cust_bill->email unless $cust_main->invoice_noemail; + if ( $cust_main->invoice_noemail ) { + # what about if the customer has no email dest? + $cust_event->set('no_action', 'Y'); + return "customer has invoice_noemail flag"; + } else { + $cust_bill->email; + } } 1; diff --git a/FS/FS/part_event/Action/cust_bill_print.pm b/FS/FS/part_event/Action/cust_bill_print.pm index b94e882ff..e6a27a34e 100644 --- a/FS/FS/part_event/Action/cust_bill_print.pm +++ b/FS/FS/part_event/Action/cust_bill_print.pm @@ -24,14 +24,22 @@ sub option_fields { sub default_weight { 51; } sub do_action { - my( $self, $cust_bill ) = @_; + my( $self, $cust_bill, $cust_event ) = @_; #my $cust_main = $self->cust_main($cust_bill); my $cust_main = $cust_bill->cust_main; $cust_bill->set('mode' => $self->option('modenum')); - $cust_bill->print unless $self->option('skip_nopost') - && ! grep { $_ eq 'POST' } $cust_main->invoicing_list; + if ( $self->option('skip_nopost') + && ! grep { $_ eq 'POST' } $cust_main->invoicing_list + ) { + # then skip customers + $cust_event->set('no_action', 'Y'); + return "customer doesn't receive postal invoices"; # as statustext + + } else { + $cust_bill->print; + } } 1; diff --git a/FS/FS/part_export/bandwidth_com.pm b/FS/FS/part_export/bandwidth_com.pm index 4fbc2de21..6d868e640 100644 --- a/FS/FS/part_export/bandwidth_com.pm +++ b/FS/FS/part_export/bandwidth_com.pm @@ -56,6 +56,16 @@ with this IP address exists, one will be created.</P> <P>If you are operating a central SIP gateway to receive traffic for all (or a subset of) customers, you should configure a phone service with a fixed value, or a list of fixed values, for the sip_server field.</P> +<P>To find your account ID and site ID: + <UL> + <LI>Login to <a target="_blank" href="https://dashboard.bandwidth.com">the Dashboard. + </a></LI> + <LI>Under "Your subaccounts", find the subaccount (site) that you want to use + for exported DIDs. Click the "manage sub-account" link.</LI> + <LI>Look at the URL. It will end in <i>{"a":xxxxxxx,"s":yyyy}</i>.</LI> + <LI>Your account ID is <i>xxxxxxx</i>, and the site ID is <i>yyyy</i>.</LI> + </UL> +</P> END ); diff --git a/FS/FS/part_export/cust_http.pm b/FS/FS/part_export/cust_http.pm index f72d00698..c13e18db1 100644 --- a/FS/FS/part_export/cust_http.pm +++ b/FS/FS/part_export/cust_http.pm @@ -55,7 +55,7 @@ tie %options, 'Tie::IxHash', ; %info = ( - 'svc' => [qw( cust_main cust_location )], + 'svc' => [qw( cust_main )], 'desc' => 'Send an HTTP or HTTPS GET or POST request, for customers.', 'options' => \%options, 'no_machine' => 1, diff --git a/FS/FS/part_export/cust_location_http.pm b/FS/FS/part_export/cust_location_http.pm new file mode 100644 index 000000000..fe7722340 --- /dev/null +++ b/FS/FS/part_export/cust_location_http.pm @@ -0,0 +1,196 @@ +package FS::part_export::cust_location_http; + +use strict; +use base qw( FS::part_export::http ); +use vars qw( %options %info ); + +my @location_fields = qw( + custnum + prospectnum + locationname + address1 + address2 + city + county + state + zip + country + latitude + longitude + censustract + censusyear + district + geocode + location_type + location_number + location_kind + incorporated +); + +tie %options, 'Tie::IxHash', + 'method' => { label =>'Method', + type =>'select', + #options =>[qw(POST GET)], + options =>[qw(POST)], + default =>'POST' }, + 'location_url' => { label => 'Location URL' }, + 'package_url' => { label => 'Package URL' }, + 'ssl_no_verify' => { label => 'Skip SSL certificate validation', + type => 'checkbox', + }, + 'include_fields' => { 'label' => 'Include fields', + 'type' => 'select', + 'multiple' => 1, + 'options' => [ @location_fields ] }, + 'location_data' => { 'label' => 'Location data', + 'type' => 'textarea' }, + 'package_data' => { 'label' => 'Package data', + 'type' => 'textarea' }, + 'success_regexp' => { + label => 'Success Regexp', + default => '', + }, +; + +%info = ( + 'svc' => [qw( cust_location )], + 'desc' => 'Send an HTTP or HTTPS GET or POST request, for customer locations', + 'options' => \%options, + 'no_machine' => 1, + 'notes' => <<'END', +Send an HTTP or HTTPS GET or POST to the specified URLs on customer location +creation/update (action 'location') and package location assignment/change (action 'package'). +Leave a URL blank to skip that action. +Always sends locationnum, action, and fields specified in the export options. +Action 'package' also sends pkgnum and change_pkgnum (the previous pkgnum, +because location changes usually instigate a pkgnum change.) +Simple field values can be selected in 'Include fields', and more complex +values can be specified in the data field options as perl code using vars +$cust_location, $cust_main and (where relevant) $cust_pkg. +Action 'location' only sends on update if a specified field changed. +Note that scheduled future package changes are currently sent when the change is scheduled +(this may not be the case in future versions of this export.) +For HTTPS support, <a href="http://search.cpan.org/dist/Crypt-SSLeay">Crypt::SSLeay</a> +or <a href="http://search.cpan.org/dist/IO-Socket-SSL">IO::Socket::SSL</a> is required. +END +); + +# we don't do anything on deletion because we generally don't delete locations +# +# we don't send blank custnum/prospectnum because we do a lot of inserting/replacing +# with blank values and then immediately overwriting, but that unfortunately +# makes it difficult to indicate if this is the first time we've sent the location +# to the customer--hence we don't distinguish insert from update in the cgi vars + +# gets invoked by FS::part_export::http _export_insert +sub _export_command { + my( $self, $action, $cust_location ) = @_; + + # redundant--cust_location exports don't get invoked by cust_location->delete, + # or by any status trigger, but just to be clear, since http export has other actions... + return '' unless $action eq 'insert'; + + $self->_http_queue_standard( + 'action' => 'location', + (map { $_ => $cust_location->get($_) } ('locationnum', $self->_include_fields)), + $self->_eval_replace('location_data',$cust_location,$cust_location->cust_main), + ); + +} + +sub _export_replace { + my( $self, $new, $old ) = @_; + + my $changed = 0; + + # even if they don't want custnum/prospectnum exported, + # inserts that lack custnum/prospectnum don't trigger exports, + # so we might not have previously reported these + $changed = 1 if $new->custnum && !$old->custnum; + $changed = 1 if $new->prospectnum && !$old->prospectnum; + + foreach my $field ($self->_include_fields) { + last if $changed; + next if $new->get($field) eq $old->get($field); + next if ($field =~ /latitude|longitude/) and $new->get($field) == $old->get($field); + $changed = 1; + } + + my %old_eval; + unless ($changed) { + %old_eval = $self->_eval_replace('location_data', $old, $old->cust_main), + } + + my %eval = $self->_eval_replace('location_data', $new, $new->cust_main); + + foreach my $key (keys %eval) { + last if $changed; + next if $eval{$key} eq $old_eval{$key}; + $changed = 1; + } + + return '' unless $changed; + + $self->_http_queue_standard( + 'action' => 'location', + (map { $_ => $new->get($_) } ('locationnum', $self->_include_fields)), + %eval, + ); +} + +# not to be confused with export_pkg_change, which is for svcs +sub export_pkg_location { + my ($self, $cust_pkg) = @_; + + return '' unless $cust_pkg->locationnum; + + my $cust_location = $cust_pkg->cust_location; + + $self->_http_queue_standard( + 'action' => 'package', + (map { $_ => $cust_pkg->get($_) } ('pkgnum', 'change_pkgnum', 'locationnum')), + (map { $_ => $cust_location->get($_) } $self->_include_fields), + $self->_eval_replace('package_data',$cust_location,$cust_pkg->cust_main,$cust_pkg), + ); +} + +sub _http_queue_standard { + my $self = shift; + my %opts = @_; + my $url; + if ($opts{'action'} eq 'location') { + $url = $self->option('location_url'); + return '' unless $url; + } elsif ($opts{'action'} eq 'package') { + $url = $self->option('package_url'); + return '' unless $url; + } else { + return "Bad action ".$opts{'action'}; + } + $self->http_queue( '', + ( $self->option('ssl_no_verify') ? 'ssl_no_verify' : '' ), + $self->option('method'), + $url, + $self->option('success_regexp'), + %opts + ); +} + +sub _include_fields { + my $self = shift; + split( /\s+/, $self->option('include_fields') ); +} + +sub _eval_replace { + my ($self,$option,$cust_location,$cust_main,$cust_pkg) = @_; + return + map { + /^\s*(\S+)\s+(.*)$/ or /()()/; + my( $field, $value_expression ) = ( $1, $2 ); + my $value = eval $value_expression; + die $@ if $@; + ( $field, $value ); + } split(/\n/, $self->option($option) ); +} + +1; diff --git a/FS/FS/part_export/portaone.pm b/FS/FS/part_export/portaone.pm index 8d5e19e8a..986a556ba 100644 --- a/FS/FS/part_export/portaone.pm +++ b/FS/FS/part_export/portaone.pm @@ -40,7 +40,7 @@ tie my %options, 'Tie::IxHash', 'customer_name' => { label => 'Customer Name', default => 'FREESIDE CUST $custnum' }, 'account_id' => { label => 'Account ID', - default => 'FREESIDE SVC $svcnum' }, + default => 'SVC$svcnum' }, 'product_id' => { label => 'Account Product ID' }, 'debug' => { type => 'checkbox', label => 'Enable debug warnings' }, diff --git a/FS/FS/part_referral.pm b/FS/FS/part_referral.pm index e4a582374..2df8a7571 100644 --- a/FS/FS/part_referral.pm +++ b/FS/FS/part_referral.pm @@ -44,6 +44,9 @@ The following fields are currently supported: =item agentnum - Optional agentnum (see L<FS::agent>) +=item title - an optional external string that identifies this +referral source, such as an advertising campaign code. + =back =head1 NOTE @@ -101,6 +104,7 @@ sub check { || $self->ut_text('referral') || $self->ut_enum('disabled', [ '', 'Y' ] ) #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum') + || $self->ut_textn('title') || ( $setup_hack ? $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum' ) : $self->ut_agentnum_acl('agentnum', 'Edit global advertising sources') diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index 612c59013..621a55410 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -590,6 +590,26 @@ sub num_cust_svc { $sth->fetchrow_arrayref->[0]; } +=item num_cust_svc_cancelled + +Returns the number of associated customer services that are +attached to cancelled packages. + +=cut + +sub num_cust_svc_cancelled { + my $self = shift; + my $sth = dbh->prepare( + "SELECT COUNT(*) FROM cust_svc + LEFT JOIN cust_pkg USING ( pkgnum ) + WHERE svcpart = ? + AND cust_pkg.cancel IS NOT NULL" + ) or die dbh->errstr; + $sth->execute($self->svcpart) + or die $sth->errstr; + $sth->fetchrow_arrayref->[0]; +} + =item svc_x Returns a list of associated FS::svc_* records. diff --git a/FS/FS/reason_Mixin.pm b/FS/FS/reason_Mixin.pm index 9c436ab1e..a1b32f2b5 100644 --- a/FS/FS/reason_Mixin.pm +++ b/FS/FS/reason_Mixin.pm @@ -22,13 +22,8 @@ voided payment / voided invoice. This can no longer be used to set the sub reason { my $self = shift; - my $reason_text; - if ( $self->reasonnum ) { - my $reason = FS::reason->by_key($self->reasonnum); - $reason_text = $reason->reason; - } else { # in case one of these somehow still exists - $reason_text = $self->get('reason'); - } + my $reason_text = $self->reason_only; + if ( $self->get('addlinfo') ) { $reason_text .= ' ' . $self->get('addlinfo'); } @@ -36,6 +31,28 @@ sub reason { return $reason_text; } +=item reason_only + +Returns only the text of the associated reason, +absent any addlinfo that is included by L</reason>. +(Currently only affects credit and credit void reasons.) + +=cut + +# a bit awkward, but much easier to invoke this in the few reports +# that need separate fields than to update every place +# that displays them together + +sub reason_only { + my $self = shift; + if ( $self->reasonnum ) { + my $reason = FS::reason->by_key($self->reasonnum); + return $reason->reason; + } else { # in case one of these somehow still exists + return $self->get('reason'); + } +} + # Used by FS::Upgrade to migrate reason text fields to reasonnum. # Note that any new tables that get reasonnum fields do NOT need to be # added here unless they have previously had a free-text "reason" field. diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 748bcae12..1dd9ffb63 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -1387,11 +1387,17 @@ Parameters: =item order_by +=item cancelled - if true, only returns svcs attached to cancelled pkgs; +if defined and false, only returns svcs not attached to cancelled packages + =back =cut -# svc_broadband::search should eventually use this instead +### Don't call the 'cancelled' option 'Service Status' +### There is no such thing +### See cautionary note in httemplate/browse/part_svc.cgi + sub search { my ($class, $params) = @_; @@ -1495,6 +1501,14 @@ sub search { push @where, "exportnum = $1"; } + if ( defined($params->{'cancelled'}) ) { + if ($params->{'cancelled'}) { + push @where, "cust_pkg.cancel IS NOT NULL"; + } else { + push @where, "cust_pkg.cancel IS NULL"; + } + } + # # sector and tower # my @where_sector = $class->tower_sector_sql($params); # if ( @where_sector ) { diff --git a/FS/MANIFEST b/FS/MANIFEST index d0bf99b85..83359f118 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -56,6 +56,8 @@ FS/Report.pm FS/Report/FCC_477.pm FS/Report/Table.pm FS/Report/Table/Monthly.pm +FS/Report/Tax/All.pm +FS/Report/Tax/ByName.pm FS/SearchCache.pm FS/UI/Web.pm FS/UID.pm diff --git a/FS/bin/freeside-cdr-evariste-import b/FS/bin/freeside-cdr-evariste-import index 0487ae539..d5e13f98c 100755 --- a/FS/bin/freeside-cdr-evariste-import +++ b/FS/bin/freeside-cdr-evariste-import @@ -100,7 +100,7 @@ while (my $row = $csth->fetchrow_hashref) { 'cdrbatchnum' => $cdr_batch->cdrbatchnum, 'uniqueid' => $row->{'id'}, 'src' => $row->{'src'}, - 'dst' => $row->{'dest'}, + 'dst' => $row->{'routing_target'} || $row->{'dest'}, # dest_orig? dest_trans? 'startdate' => int(str2time($row->{'start_time'})), 'answerdate' => int(str2time($row->{'answer_time'})), 'enddate' => int(str2time($row->{'end_time'})), |