diff options
59 files changed, 678 insertions, 223 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index ca96eb52f..4c99c7cb8 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -236,7 +236,7 @@ tie my %rights, 'Tie::IxHash', # customer voiding rights.. ### 'Customer payment void rights' => [ - { rightname=>'Credit card void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. cc-void + { rightname=>'Credit card void', desc=>'Enable local-only voiding of credit card payments in addition to refunds against the payment gateway.' }, #aka. cc-void { rightname=>'Echeck void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. echeck-void 'Void payments', { rightname=>'Unvoid payments', desc=>'Enable unvoiding of voided payments' }, #aka. unvoid diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index db50d4250..2aeecc1b2 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -2801,13 +2801,16 @@ sub myaccount_passwd { } ) or return { 'error' => "Service not found" }; - if ( exists($p->{'old_password'}) ) { - return { 'error' => "Incorrect password." } - unless $svc_acct->check_password($p->{'old_password'}); - } + my $error = ''; + + my $conf = new FS::Conf; + $error = 'Password too short.' + if length($p->{'new_password'}) < ($conf->config('passwordmin') || 6); + $error = 'Password too long.' + if length($p->{'new_password'}) > ($conf->config('passwordmax') || 8); $svc_acct->set_password($p->{'new_password'}); - my $error = $svc_acct->replace(); + $error ||= $svc_acct->replace(); my($label, $value) = $svc_acct->cust_svc->label; diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index eed84fc52..0eed8ee5d 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1724,6 +1724,21 @@ and customer address. Include units.', }, { + 'key' => 'sip_passwordmin', + 'section' => 'telephony', + 'description' => 'Minimum SIP password length (default 6)', + 'type' => 'text', + }, + + { + 'key' => 'sip_passwordmax', + 'section' => 'telephony', + 'description' => 'Maximum SIP password length (default 8) (don\'t set this over 12 if you need to import or export crypt() passwords)', + 'type' => 'text', + }, + + + { 'key' => 'password-noampersand', 'section' => 'password', 'description' => 'Disallow ampersands in passwords', @@ -2040,6 +2055,13 @@ and customer address. Include units.', }, { + 'key' => 'show_ship_company', + 'section' => 'UI', + 'description' => 'Turns on display/collection of a "service company name" field for customers.', + 'type' => 'checkbox', + }, + + { 'key' => 'show_ss', 'section' => 'UI', 'description' => 'Turns on display/collection of social security numbers in the web interface. Sometimes required by electronic check (ACH) processors.', @@ -4745,6 +4767,17 @@ and customer address. Include units.', }, { + 'key' => 'svc_phone-radius-password', + 'section' => 'telephony', + 'description' => 'Password when exporting svc_phone records to RADIUS', + 'type' => 'select', + 'select_hash' => [ + '' => 'Use default from svc_phone-radius-default_password config', + 'countrycode_phonenum' => 'Phone number (with country code)', + ], + }, + + { 'key' => 'svc_phone-radius-default_password', 'section' => 'telephony', 'description' => 'Default password when exporting svc_phone records to RADIUS', diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 1a88c3acd..493734729 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -42,6 +42,7 @@ our $me = '[FS::Record]'; our $nowarn_identical = 0; our $nowarn_classload = 0; our $no_update_diff = 0; +our $no_history = 0; our $no_check_foreign = 1; #well, not inefficiently in perl by default anymore @@ -1250,7 +1251,7 @@ sub insert { } my $h_sth; - if ( defined dbdef->table('h_'. $table) ) { + if ( defined( dbdef->table('h_'. $table) ) && ! $no_history ) { my $h_statement = $self->_h_statement('insert'); warn "[debug]$me $h_statement\n" if $DEBUG > 2; $h_sth = dbh->prepare($h_statement) or do { @@ -3003,7 +3004,7 @@ You should generally not have to worry about calling this, as the system handles sub encrypt { my ($self, $value) = @_; - my $encrypted; + my $encrypted = $value; if ($conf->exists('encryption')) { if ($self->is_encrypted($value)) { diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm index 1d60b6420..da4916176 100644 --- a/FS/FS/Report/Table.pm +++ b/FS/FS/Report/Table.pm @@ -757,7 +757,7 @@ sub with_cust_classnum { return 'cust_main.classnum in('. join(',',@$classnums) .')' if @$classnums; } - ''; + (); } diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 8ba60200e..bc9d37aed 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3,7 +3,7 @@ package FS::Schema; use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache); use subs qw(reload_dbdef); use Exporter; -use DBIx::DBSchema 0.43; #0.43 for foreign keys +use DBIx::DBSchema 0.44; #for foreign keys with MATCH / ON DELETE/UPDATE use DBIx::DBSchema::Table; use DBIx::DBSchema::Column; use DBIx::DBSchema::Index; @@ -1138,9 +1138,10 @@ sub tables_hashref { { columns => [ 'invnum' ], table => 'cust_bill_void', }, - { columns => [ 'pkgnum' ], - table => 'cust_pkg', - }, + #pkgnum 0 and -1 are used for special things + #{ columns => [ 'pkgnum' ], + # table => 'cust_pkg', + #}, { columns => [ 'pkgpart_override' ], table => 'part_pkg', references => [ 'pkgpart' ], @@ -2389,7 +2390,7 @@ sub tables_hashref { 'index' => [ [ 'billpaynum' ], [ 'billpkgnum' ], ], 'foreign_keys' => [ { columns => [ 'billpaynum' ], - table => 'cust_bill_pay_batch', + table => 'cust_bill_pay', }, { columns => [ 'billpkgnum' ], table => 'cust_bill_pkg', @@ -3953,6 +3954,7 @@ sub tables_hashref { 'foreign_keys' => [ { columns => [ 'jobnum' ], table => 'queue', + on_delete => 'CASCADE', }, ], }, @@ -3973,6 +3975,7 @@ sub tables_hashref { { columns => [ 'depend_jobnum' ], table => 'queue', references => [ 'jobnum' ], + on_delete => 'CASCADE', }, ], }, @@ -4903,7 +4906,7 @@ sub tables_hashref { #currently only u4: # terminating number (as opposed to dialed destination) - 'dst_term', 'varchar', '', $char_d, \"''", '', + 'dst_term', 'varchar', 'NULL', $char_d, '', '', #these don't seem to be logged by most of the SQL cdr_* modules #except tds under sql-illegal names, so; @@ -5843,7 +5846,7 @@ sub tables_hashref { 'statustext', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'upgradenum', - 'unique' => [ [ 'upgradenum' ] ], + 'unique' => [], 'index' => [ [ 'upgrade' ] ], }, diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 840df7558..2314c02c1 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -581,11 +581,14 @@ sub print_generic { my $countrydefault = $conf->config('countrydefault') || 'US'; foreach ( qw( address1 address2 city state zip country fax) ){ my $method = 'ship_'.$_; - $invoice_data{"ship_$_"} = _latex_escape($cust_main->$method); + $invoice_data{"ship_$_"} = $escape_function->($cust_main->$method); } - foreach ( qw( contact company ) ) { #compatibility - $invoice_data{"ship_$_"} = _latex_escape($cust_main->$_); + if ( length($cust_main->ship_company) ) { + $invoice_data{'ship_company'} = $escape_function->($cust_main->ship_company); + } else { + $invoice_data{'ship_company'} = $escape_function->($cust_main->company); } + $invoice_data{'ship_contact'} = $escape_function->($cust_main->contact); $invoice_data{'ship_country'} = '' if ( $invoice_data{'ship_country'} eq $countrydefault ); diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index d7f998bdf..e31ff14a5 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -323,6 +323,14 @@ sub cust_header { @cust_header; } +sub cust_sort_fields { + cust_header(@_); + #inefficientish, but tiny lists and only run once per page + + map { $_ eq 'custnum' ? 'custnum' : '' } @cust_fields; + +} + =item cust_sql_fields [ CUST_FIELDS_VALUE ] Returns a list of fields for the SELECT portion of an SQL query. diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index b16cb8648..bf508dd8c 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -904,7 +904,7 @@ sub rate_prefix { #${$opt{region_group_included_min}} -= $minutes # if $region_group && $rate_detail->region_group; - if ( $included_min->{$regionnum}{$ratetimenum} > $minutes ) { + if ( $included_min->{$regionnum}{$ratetimenum} >= $minutes ) { $charge_sec = 0; $included_min->{$regionnum}{$ratetimenum} -= $minutes; } else { diff --git a/FS/FS/contact_phone.pm b/FS/FS/contact_phone.pm index 0eb216668..610753fc0 100644 --- a/FS/FS/contact_phone.pm +++ b/FS/FS/contact_phone.pm @@ -4,6 +4,7 @@ use base qw( FS::Record ); use strict; use FS::Record qw( qsearch qsearchs ); use FS::contact; +use FS::phone_type; =head1 NAME @@ -144,6 +145,16 @@ sub contact { qsearchs( 'contact', { 'contactnum' => $self->contactnum } ); } +sub phone_type { + my $self = shift; + qsearchs('phone_type', { 'phonetypenum' => $self->phonetypenum } ); +} + +sub typename { + my $self = shift; + $self->phone_type->typename; +} + =back =head1 BUGS diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 4e34ef47b..d0e7048b7 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1970,14 +1970,15 @@ sub print_csv { my $time = $opt{'time'} || time; + my $tracctnum = ''; #leaking out from billco-specific sections :/ if ( $format eq 'billco' ) { my $account_num = $self->conf->config('billco-account_num', $cust_main->agentnum); - my $tracctnum = $account_num eq 'display_custnum' - ? $cust_main->display_custnum - : $opt{'tracctnum'}; + $tracctnum = $account_num eq 'display_custnum' + ? $cust_main->display_custnum + : $opt{'tracctnum'}; my $taxtotal = 0; $taxtotal += $_->{'amount'} foreach $self->_items_tax; @@ -2232,7 +2233,7 @@ sub print_csv { $csv->combine( '', # 1 | N/A-Leave Empty CHAR 2 '', # 2 | N/A-Leave Empty CHAR 15 - $opt{'tracctnum'}, # 3 | Account Number CHAR 15 + $tracctnum, # 3 | Account Number CHAR 15 $self->invnum, # 4 | Invoice Number CHAR 15 $lineseq++, # 5 | Line Sequence (sort order) NUM 6 $item->{'description'}, # 6 | Transaction Detail CHAR 100 diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index d768f8406..5126fea6b 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1662,7 +1662,7 @@ sub queue_fuzzyfiles_update { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - foreach my $field ( 'first', 'last', 'company' ) { + foreach my $field ( 'first', 'last', 'company', 'ship_company' ) { my $queue = new FS::queue { 'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield' }; @@ -1724,6 +1724,7 @@ sub check { || $self->ut_snumbern('spouse_birthdate') || $self->ut_snumbern('anniversary_date') || $self->ut_textn('company') + || $self->ut_textn('ship_company') || $self->ut_anything('comments') || $self->ut_numbern('referral_custnum') || $self->ut_textn('stateid') @@ -1741,11 +1742,13 @@ sub check { || $self->ut_currencyn('currency') ; - my $company = $self->company; - $company =~ s/^\s+//; - $company =~ s/\s+$//; - $company =~ s/\s+/ /g; - $self->company($company); + foreach (qw(company ship_company)) { + my $company = $self->get($_); + $company =~ s/^\s+//; + $company =~ s/\s+$//; + $company =~ s/\s+/ /g; + $self->set($_, $company); + } #barf. need message catalogs. i18n. etc. $error .= "Please select an advertising source." diff --git a/FS/FS/cust_main/Search.pm b/FS/FS/cust_main/Search.pm index 16db71271..b1438619b 100644 --- a/FS/FS/cust_main/Search.pm +++ b/FS/FS/cust_main/Search.pm @@ -21,6 +21,7 @@ $me = '[FS::cust_main::Search]'; @fuzzyfields = ( 'cust_main.first', 'cust_main.last', 'cust_main.company', + 'cust_main.ship_company', # if you're using it 'cust_location.address1', 'contact.first', 'contact.last', ); @@ -321,6 +322,7 @@ sub smart_search { $sql .= " ( LOWER(cust_main.first) = $q_value OR LOWER(cust_main.last) = $q_value OR LOWER(cust_main.company) = $q_value + OR LOWER(cust_main.ship_company) = $q_value "; #address1 (yes, it's a kludge) @@ -356,27 +358,30 @@ sub smart_search { #substring - my @hashrefs = ( + my @company_hashrefs = ( { 'company' => { op=>'ILIKE', value=>"%$value%" }, }, + { 'ship_company' => { op=>'ILIKE', value=>"%$value%" }, }, ); + my @hashrefs = (); + if ( $first && $last ) { - push @hashrefs, + @hashrefs = ( { 'first' => { op=>'ILIKE', value=>"%$first%" }, 'last' => { op=>'ILIKE', value=>"%$last%" }, }, - ; + ); } else { - push @hashrefs, + @hashrefs = ( { 'first' => { op=>'ILIKE', value=>"%$value%" }, }, { 'last' => { op=>'ILIKE', value=>"%$value%" }, }, - ; + ); } - foreach my $hashref ( @hashrefs ) { + foreach my $hashref ( @company_hashrefs, @hashrefs ) { push @cust_main, qsearch( { 'table' => 'cust_main', @@ -402,8 +407,6 @@ sub smart_search { #contact substring - shift @hashrefs; #no company column in contact table - foreach my $hashref ( @hashrefs ) { push @cust_main, @@ -439,7 +442,7 @@ sub smart_search { %fuzopts ); } - foreach my $field ( 'first', 'last', 'company' ) { + foreach my $field ( 'first', 'last', 'company', 'ship_company' ) { push @cust_main, FS::cust_main::Search->fuzzy_search( { $field => $value }, %fuzopts @@ -1193,6 +1196,7 @@ sub append_fuzzyfiles { #foreach my $fuzzy (@fuzzyfields) { foreach my $fuzzy ( 'cust_main.first', 'cust_main.last', 'cust_main.company', 'cust_location.address1', + 'cust_main.ship_company', ) { append_fuzzyfiles_fuzzyfield($fuzzy, shift); diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index 0669e1aa9..a7b91083f 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -36,6 +36,7 @@ FS::UID->install_callback( sub { } ); @encrypted_fields = ('payinfo'); +sub nohistory_fields { ('payinfo'); } =head1 NAME diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm index b93d8166d..db53b19c9 100644 --- a/FS/FS/cust_pay_batch.pm +++ b/FS/FS/cust_pay_batch.pm @@ -16,6 +16,9 @@ use FS::cust_bill; # 3 is even more information including possibly sensitive data $DEBUG = 0; +#@encrypted_fields = ('payinfo'); +sub nohistory_fields { ('payinfo'); } + =head1 NAME FS::cust_pay_batch - Object methods for batch cards diff --git a/FS/FS/cust_pay_pending.pm b/FS/FS/cust_pay_pending.pm index 8c6ef69ae..572a2ade2 100644 --- a/FS/FS/cust_pay_pending.pm +++ b/FS/FS/cust_pay_pending.pm @@ -12,6 +12,7 @@ use FS::cust_pay; @ISA = qw( FS::payinfo_transaction_Mixin FS::cust_main_Mixin FS::Record ); @encrypted_fields = ('payinfo'); +sub nohistory_fields { ('payinfo'); } =head1 NAME diff --git a/FS/FS/cust_pay_void.pm b/FS/FS/cust_pay_void.pm index 92a96cb96..c42dc18e0 100644 --- a/FS/FS/cust_pay_void.pm +++ b/FS/FS/cust_pay_void.pm @@ -16,6 +16,8 @@ use FS::cust_pay; use FS::cust_pkg; @encrypted_fields = ('payinfo'); +sub nohistory_fields { ('payinfo'); } + $otaker_upgrade_kludge = 0; =head1 NAME diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 5abdbe2ee..8d12ab9c7 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1859,7 +1859,7 @@ sub change { if ( $opt->{cust_main} ) { my $cust_main = $opt->{cust_main}; unless ( $cust_main->custnum ) { - my $error = $cust_main->insert; + my $error = $cust_main->insert( @{ $opt->{cust_main_insert_args}||[] } ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "inserting cust_main (transaction rolled back): $error"; @@ -3461,8 +3461,7 @@ sub attribute_since_sqlradacct { foreach my $cust_svc ( grep { my $part_svc = $_->part_svc; - $part_svc->svcdb eq 'svc_acct' - && scalar($part_svc->part_export_usage); + scalar($part_svc->part_export_usage); } $self->cust_svc ) { $sum += $cust_svc->attribute_since_sqlradacct($start, $end, $attrib); diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm index 064992955..d29db5cfa 100644 --- a/FS/FS/cust_refund.pm +++ b/FS/FS/cust_refund.pm @@ -13,6 +13,7 @@ use FS::cust_pay_refund; use FS::cust_main; @encrypted_fields = ('payinfo'); +sub nohistory_fields { ('payinfo'); } =head1 NAME diff --git a/FS/FS/part_event/Action/letter.pm b/FS/FS/part_event/Action/letter.pm index 57b7b7783..835dec2b9 100644 --- a/FS/FS/part_event/Action/letter.pm +++ b/FS/FS/part_event/Action/letter.pm @@ -20,6 +20,7 @@ sub option_fields { 'type' => 'select-table', 'table' => 'msg_template', 'name_col' => 'msgname', + 'hashref' => { disabled => '' }, 'disable_empty' => 1, }, ); diff --git a/FS/FS/part_event/Action/notice.pm b/FS/FS/part_event/Action/notice.pm index 8e22c6844..7c8ed1682 100644 --- a/FS/FS/part_event/Action/notice.pm +++ b/FS/FS/part_event/Action/notice.pm @@ -19,6 +19,7 @@ sub option_fields { 'msgnum' => { 'label' => 'Template', 'type' => 'select-table', 'table' => 'msg_template', + 'hashref' => { disabled => '' }, 'name_col' => 'msgname', 'disable_empty' => 1, }, diff --git a/FS/FS/part_event/Action/notice_to.pm b/FS/FS/part_event/Action/notice_to.pm index 194aeb84f..d300e3385 100644 --- a/FS/FS/part_event/Action/notice_to.pm +++ b/FS/FS/part_event/Action/notice_to.pm @@ -24,6 +24,7 @@ sub option_fields { 'type' => 'select-table', 'table' => 'msg_template', 'name_col' => 'msgname', + 'hashref' => { disabled => '' }, 'disable_empty' => 1, }, ); diff --git a/FS/FS/part_event/Action/svc_acct_notice.pm b/FS/FS/part_event/Action/svc_acct_notice.pm index d71a1371a..97a4ad686 100644 --- a/FS/FS/part_event/Action/svc_acct_notice.pm +++ b/FS/FS/part_event/Action/svc_acct_notice.pm @@ -18,6 +18,7 @@ sub option_fields { 'type' => 'select-table', 'table' => 'msg_template', 'name_col' => 'msgname', + 'hashref' => { disabled => '' }, 'disable_empty' => 1, }, ); diff --git a/FS/FS/part_export/rt_ticket.pm b/FS/FS/part_export/rt_ticket.pm index 7ae6105a0..72e387c56 100644 --- a/FS/FS/part_export/rt_ticket.pm +++ b/FS/FS/part_export/rt_ticket.pm @@ -21,7 +21,7 @@ my %template_select = ( %templates = (0 => '', map { $_->msgnum, $_->msgname } qsearch({ table => 'msg_template', - hashref => {}, + hashref => { disabled => '' }, order_by => 'ORDER BY msgnum ASC' }) ); diff --git a/FS/FS/part_export/send_email.pm b/FS/FS/part_export/send_email.pm index 6ba131f18..1fcb828b7 100644 --- a/FS/FS/part_export/send_email.pm +++ b/FS/FS/part_export/send_email.pm @@ -21,7 +21,7 @@ my %template_select = ( %templates = (0 => '', map { $_->msgnum, $_->msgname } qsearch({ table => 'msg_template', - hashref => {}, + hashref => { disabled => 1 }, order_by => 'ORDER BY msgnum ASC' }) ); diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index ce1369510..f7e86514b 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -267,17 +267,19 @@ sub export_pkg_change { my @fields = qw( pkgnum pkgpart agent_pkgid ); #others? my @date_fields = qw( order_date start_date setup bill last_bill susp adjourn - resume cancel uncancel expore contract_end ); + resume cancel uncancel expire contract_end ); no strict 'vars'; { no strict 'refs'; foreach (@fields) { - ${"old_$_"} = $old_cust_pkg->getfield($_); + ${"old_$_"} = $old_cust_pkg ? $old_cust_pkg->getfield($_) : ''; ${"new_$_"} = $new_cust_pkg->getfield($_); } foreach (@date_fields) { - ${"old_$_"} = time2str('%Y-%m-%d', $old_cust_pkg->getfield($_)); + ${"old_$_"} = $old_cust_pkg + ? time2str('%Y-%m-%d', $old_cust_pkg->getfield($_)) + : ''; ${"new_$_"} = time2str('%Y-%m-%d', $new_cust_pkg->getfield($_)); } } diff --git a/FS/FS/part_pkg/bulk_Common.pm b/FS/FS/part_pkg/bulk_Common.pm index 26550df70..4e8850ee5 100644 --- a/FS/FS/part_pkg/bulk_Common.pm +++ b/FS/FS/part_pkg/bulk_Common.pm @@ -47,7 +47,7 @@ sub price_info { #some false laziness-ish w/agent.pm... not a lot sub calc_recur { - my($self, $cust_pkg, $sdate, $details ) = @_; + my($self, $cust_pkg, $sdate, $details, $param ) = @_; my $conf = new FS::Conf; my $money_char = $conf->config('money_char') || '$'; @@ -106,10 +106,13 @@ sub calc_recur { } } - sprintf('%.2f', $self->base_recur($cust_pkg, $sdate) + $total_svc_charge ); -} + my $charge = $self->base_recur($cust_pkg, $sdate) + $total_svc_charge; + + $param->{'override_charges'} = $total_svc_charge / $self->freq; + my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); -sub can_discount { 0; } + sprintf('%.2f', $charge - $discount ); +} sub hide_svc_detail { 1; } diff --git a/FS/FS/queue.pm b/FS/FS/queue.pm index 3f8763da8..8ebadd417 100644 --- a/FS/FS/queue.pm +++ b/FS/FS/queue.pm @@ -193,20 +193,6 @@ deleted as well sub delete { my $self = shift; - 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; - - my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } ); - push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } ); - my $reportname = ''; if ( $self->status =~/^done/ ) { my $dropstring = rooturl(). '/misc/queued_report\?report='; @@ -216,20 +202,7 @@ sub delete { } my $error = $self->SUPER::delete; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - - foreach my $del ( @del ) { - $error = $del->delete; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; + return $error if $error; unlink $reportname if $reportname; diff --git a/FS/FS/svc_MAC_Mixin.pm b/FS/FS/svc_MAC_Mixin.pm new file mode 100644 index 000000000..737a8e83f --- /dev/null +++ b/FS/FS/svc_MAC_Mixin.pm @@ -0,0 +1,65 @@ +package FS::svc_MAC_Mixin; + +use strict; +#use FS::Record qw(qsearch); +#use FS::Conf; +# careful about importing anything here--it will end up in a LOT of +# namespaces + +#use vars qw(@subclasses $DEBUG $conf); + +#$DEBUG = 0; + +# any subclass that can have MAC addresses needs to be added here +#@subclasses = (qw(FS::svc_broadband FS::svc_cable)); + +#sub conf { +# $conf ||= FS::Conf->new; +#} + +=head1 NAME + +FS::MAC_Mixin - Mixin class for objects that have MAC addresses assigned. + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head1 METHODS + +=head1 METHODS + +=over 4 + +=item mac_addr_pretty + +=cut + +sub mac_addr_pretty { + my $self = shift; + $self->mac_addr_formatted('U',':'); +} + +=item mac_addr_formatted CASE DELIMITER + +Format the MAC address (for use by exports). If CASE starts with "l" +(for "lowercase"), it's returned in lowercase. DELIMITER is inserted +between octets. + +=cut + +sub mac_addr_formatted { + my $self = shift; + my ($case, $delim) = @_; + my $addr = $self->mac_addr; + $addr = lc($addr) if $case =~ /^l/i; + join( $delim || '', $addr =~ /../g ); +} + +=back + +=head1 BUGS + +=cut + +1; diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 6073902e8..b9c89cef7 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -3,6 +3,7 @@ use base qw( FS::svc_Radius_Mixin FS::svc_Tower_Mixin FS::svc_IP_Mixin + FS::svc_MAC_Mixin FS::svc_Common ); @@ -262,7 +263,7 @@ sub smart_search { =item label -Returns the IP address. +Returns the IP address, MAC address and description. =cut @@ -419,22 +420,6 @@ sub _check_duplicate { ''; } -=item mac_addr_formatted CASE DELIMITER - -Format the MAC address (for use by exports). If CASE starts with "l" -(for "lowercase"), it's returned in lowercase. DELIMITER is inserted -between octets. - -=cut - -sub mac_addr_formatted { - my $self = shift; - my ($case, $delim) = @_; - my $addr = $self->mac_addr; - $addr = lc($addr) if $case =~ /^l/i; - join( $delim || '', $addr =~ /../g ); -} - #class method sub _upgrade_data { my $class = shift; diff --git a/FS/FS/svc_cable.pm b/FS/FS/svc_cable.pm index 672a34d7b..5d281135b 100644 --- a/FS/FS/svc_cable.pm +++ b/FS/FS/svc_cable.pm @@ -1,5 +1,7 @@ package FS::svc_cable; -use base qw( FS::svc_Common ); #qw( FS::device_Common FS::svc_Common ); +use base qw( FS::svc_MAC_Mixin + FS::svc_Common + ); #FS::device_Common use strict; use Tie::IxHash; @@ -115,6 +117,22 @@ sub table_info { }; } +=item label + +Returns the MAC address and serial number. + +=cut + +sub label { + my $self = shift; + my @label = (); + push @label, 'MAC:'. $self->mac_addr_pretty + if $self->mac_addr; + push @label, 'Serial#'. $self->serialnum + if $self->serialnum; + return join(', ', @label); +} + =item insert Adds this record to the database. If there is an error, returns the error, diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm index 77b013726..887f7bed3 100644 --- a/FS/FS/svc_phone.pm +++ b/FS/FS/svc_phone.pm @@ -2,9 +2,12 @@ package FS::svc_phone; use strict; use base qw( FS::svc_Domain_Mixin FS::location_Mixin FS::svc_Common ); -use vars qw( $DEBUG $me @pw_set $conf $phone_name_max ); +use vars qw( $DEBUG $me @pw_set $conf $phone_name_max + $passwordmin $passwordmax + ); use Data::Dumper; use Scalar::Util qw( blessed ); +use List::Util qw( min ); use FS::Conf; use FS::Record qw( qsearch qsearchs dbh ); use FS::PagedSearch qw( psearch ); @@ -26,6 +29,8 @@ $DEBUG = 0; FS::UID->install_callback( sub { $conf = new FS::Conf; $phone_name_max = $conf->config('svc_phone-phone_name-max_length'); + $passwordmin = $conf->config('sip_passwordmin') || 0; + $passwordmax = $conf->config('sip_passwordmax') || 80; } ); @@ -534,10 +539,17 @@ sub check { } } - unless ( length($self->sip_password) ) { # option for this? + if ( length($self->sip_password) ) { + + return "SIP password must be longer than $passwordmin characters" + if length($self->sip_password) < $passwordmin; + return "SIP password must be shorter than $passwordmax characters" + if length($self->sip_password) > $passwordmax; + + } else { # option for this? $self->sip_password( - join('', map $pw_set[ int(rand $#pw_set) ], (0..16) ) + join('', map $pw_set[ int(rand $#pw_set) ], (1..min($passwordmax,16)) ) ); } @@ -638,7 +650,13 @@ sub radius_check { my $conf = new FS::Conf; - $check{'User-Password'} = $conf->config('svc_phone-radius-default_password'); + my $password; + if ( $conf->config('svc_phone-radius-password') eq 'countrycode_phonenum' ) { + $password = $self->countrycode. $self->phonenum; + } else { + $password = $conf->config('svc_phone-radius-default_password'); + } + $check{'User-Password'} = $password; %check; } diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued index 5eac06b24..f1a87cac9 100644 --- a/FS/bin/freeside-queued +++ b/FS/bin/freeside-queued @@ -12,6 +12,7 @@ use FS::Record qw(qsearch); use FS::queue; use FS::queue_depend; use FS::Log; +use FS::Cron::expire_user_pref qw( expire_user_pref ); # no autoloading for non-FS classes... use Net::SSH 0.07; @@ -66,6 +67,7 @@ while (1) { if ( $kids >= $max_kids ) { warn "WARNING: maximum $kids children reached\n" unless $warnkids++; &reap_kids; + expire_user_pref() unless $warnkids % 10; sleep 1; #waiting for signals is cheap next; } @@ -131,6 +133,7 @@ while (1) { undef $FS::UID::dbh; next; }; + expire_user_pref(); sleep $sleep_time; next; } diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade index 7cacf105c..45d2709da 100755 --- a/FS/bin/freeside-upgrade +++ b/FS/bin/freeside-upgrade @@ -84,6 +84,13 @@ if ( dbdef->table('areacode') and } } +if ( dbdef->table('upgrade_journal') ) { + push @bugfix, "SELECT SETVAL( 'upgrade_journal_upgradenum_seq', + ( SELECT MAX(upgradenum) FROM upgrade_journal ) + ) + "; +} + if ( $DRY_RUN ) { print join(";\n", @bugfix ). ";\n"; diff --git a/bin/restore-ship_company b/bin/restore-ship_company new file mode 100644 index 000000000..cee700962 --- /dev/null +++ b/bin/restore-ship_company @@ -0,0 +1,32 @@ +#!/usr/bin/perl + +use FS::UID 'adminsuidsetup'; +use FS::Record qw(qsearch qsearchs dbh); +use FS::cust_main; +my $user = shift or die "Usage: + restore-ship_company username [ max-age ] +"; +adminsuidsetup($user); + +$FS::UID::AutoCommit = 1; +local $FS::cust_main::import = 1; + +my $days = shift || 30; +my $time = time - (86400*$days); # by default, only restore within the last + # 30 days +foreach my $cust_main (qsearch('cust_main', { ship_company => '' })) { + my $custnum = $cust_main->custnum; + my $last_h = qsearchs({ + table => 'h_cust_main', + extra_sql => " WHERE custnum = $custnum". + " AND ship_company IS NOT NULL". + " AND history_date >= $time", + order_by => " ORDER BY history_date DESC LIMIT 1", + }); + next if !$last_h; + print "$custnum\t".$last_h->ship_company."\n"; + $cust_main->set('ship_company' => $last_h->ship_company); + my $error = $cust_main->replace; + warn "Error setting service company for customer #$custnum:\n $error\n" + if $error; +} diff --git a/bin/test_scrub b/bin/test_scrub index 45a257ab9..e39a28a4e 100644 --- a/bin/test_scrub +++ b/bin/test_scrub @@ -3,6 +3,7 @@ #This drops anything from the database that could cause live things to happen. #You'd want to do this on a test copy of your live database but NEVER on the #live database itself. +die "remove this line to run -- NEVER ON A LIVE DATABASE"; #-all exports (all records in part_export, part_export_option export_svc) #-all non-POST invoice destinations (cust_main_invoice) @@ -42,6 +43,17 @@ my $dsth = dbh->prepare("DELETE FROM cust_main_invoice WHERE dest != 'POST'") or die dbh->errstr; $dsth->execute or die $dsth->errstr; +foreach my $table (qw( cust_main + cust_pay_pending cust_pay cust_pay_void cust_pay_batch + cust_refund +)) { + my $ccsth = dbh->prepare(" + UPDATE $table SET payinfo = '4111111111111111' + WHERE payby = 'CARD' OR payby = 'DCRD' + ") or die dbh->errstr; + $ccsth->execute or die $ccsth->errstr; +} + my $sth = dbh->prepare("UPDATE part_event SET disabled = 'Y'"); $sth->execute or die $sth->errstr; diff --git a/fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm b/fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm index 0df24f7d7..6086717ec 100644 --- a/fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm +++ b/fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm @@ -1,8 +1,13 @@ #Add this to the modules section of radiusd.conf # perl { # #path to this module -# module=/usr/local/share/perl/5.8.8/FS/SelfService/FreeRadiusVoip.pm +# # deb 6 example +# #module=/usr/local/share/perl/5.10.1/FS/SelfService/FreeRadiusVoip.pm +# # deb 7 example +# module=/usr/local/share/perl/5.14.2/FS/SelfService/FreeRadiusVoip.pm +# # func_authorize = authorize; +# # } # #In the Authorize section @@ -11,9 +16,9 @@ # # #N/A# Add a line containing 'perl' to the Accounting section. # -# and on debian systems, add this to /etc/init.d/freeradius, with the +# and on debian 6 systems, add this to /etc/init.d/freeradius, with the # correct path (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=416266) -# LD_PRELOAD=/usr/lib/libperl.so.5.8.8 +# LD_PRELOAD=/usr/lib/libperl.so.5.10 # export LD_PRELOAD BEGIN { $FS::SelfService::skip_uid_check = 1; } @@ -44,9 +49,13 @@ sub authorize { if ( $response->{'error'} ) { $RAD_REPLY{'Reply-Message'} = $response->{'error'}; return RLM_MODULE_REJECT; - } else { + } elsif ( $response->{'seconds'} ) { $RAD_REPLY{'Session-Timeout'} = $response->{'seconds'}; return RLM_MODULE_OK; + } else { + # if the called number is free, put 1 in the Termination-Action attribute + $RAD_REPLY{'Termination-Action'} = 1; + return RLM_MODULE_OK; } } diff --git a/httemplate/browse/msg_template.html b/httemplate/browse/msg_template.html index bb5ac94a0..ef0b2dafd 100644 --- a/httemplate/browse/msg_template.html +++ b/httemplate/browse/msg_template.html @@ -5,14 +5,15 @@ 'query' => { 'table' => 'msg_template', }, 'count_query' => 'SELECT COUNT(*) FROM msg_template', 'disableable' => 1, - 'disabled_statuspos' => 2, + 'disabled_statuspos' => (scalar(@locales) + 3), 'agent_virt' => 1, 'agent_null_right' => ['View global templates','Edit global templates'], 'agent_pos' => 1, - 'header' => [ 'Name', '', map '', @locales ], - 'fields' => [ 'msgname', @locales ], - 'links' => [ $link, @locale_links ], - 'cell_style' => [ '', '', map $locale_style, @locales ], + 'header' => [ 'Name', '', map ('', @locales), '' ], + 'fields' => [ 'msgname', @locales, $disable_link_label ], + 'links' => [ $link, @locale_links, '' ], + 'link_onclicks' => [ '', map('', @locale_links), $disable_link ], + 'cell_style' => [ '', '', map ($locale_style, @locales), $locale_style ], ) %> <%init> @@ -30,7 +31,7 @@ if ( $curuser->access_right(['Edit templates', 'Edit global templates']) ) { my $link = [ "${p}edit/msg_template.html?msgnum=", 'msgnum' ]; -my $locale_style = 'font-size:0.8em; padding:3px; background-color:'; +my $locale_style = 'font-size:0.8em; padding:3px'; my (@locales, @locale_links); foreach my $l ( FS::Locales->locales ) { @@ -44,6 +45,20 @@ foreach my $l ( FS::Locales->locales ) { [ "${p}edit/msg_template.html?locale=$l;msgnum=", 'msgnum' ]; }; } - + +my $disable_link = sub { + my $template = shift; + include('/elements/popup_link_onclick.html', + action => $p.'misc/disable-msg_template.cgi?msgnum=' . + $template->msgnum . + ($template->disabled ? ';enable=1' : ''), + actionlabel => 'Disable lemplate', + ); +}; + +my $disable_link_label = sub { + my $template = shift; + $template->disabled ? '(enable)' : '(disable)' ; +}; </%init> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index d597d0bc2..480047cae 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -73,6 +73,7 @@ ><% mt('same as billing address') |h %> <DIV CLASS="fsinnerbox"> <TABLE ID="table_ship_location" WIDTH="100%"> + <& cust_main/before_ship_location.html, $cust_main &> <& /elements/location.html, object => $cust_main->ship_location, prefix => 'ship_', @@ -202,6 +203,7 @@ my $prospectnum = ''; my $locationnum = ''; my $same = ''; +$m->comp('/elements/handle_uri_query', 'secure'=>1); if ( $cgi->param('error') ) { diff --git a/httemplate/edit/cust_main/before_ship_location.html b/httemplate/edit/cust_main/before_ship_location.html new file mode 100644 index 000000000..badb5e8cd --- /dev/null +++ b/httemplate/edit/cust_main/before_ship_location.html @@ -0,0 +1,13 @@ +% if ( length($cust_main->ship_company) or +% $conf->exists('show_ship_company') ) { + <& /elements/tr-input-text.html, + label => mt('Company'), + field => 'ship_company', + curr_value => $cust_main->ship_company, + colspan => 6, + &> +% } +<%init> +my $cust_main = shift; +my $conf = FS::Conf->new; +</%init> diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html index 4140ec1ea..c2ebb093c 100644 --- a/httemplate/edit/cust_main/contact.html +++ b/httemplate/edit/cust_main/contact.html @@ -94,30 +94,13 @@ </%def> <%def phones> - <TR> - <TD ALIGN="right" VALIGN="top"><% mt('Phones') %></TD> - <TD COLSPAN=6> - - <TABLE CELLSPACING=0 CELLPADDING=0> - <TR> - <TD> - <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>> - <BR><FONT SIZE=-1><% $daytime_label %></FONT> - </TD> - <TD> </TD> - <TD> - <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>> - <BR><FONT SIZE=-1><% $night_label %></FONT> - </TD> - <TD> </TD> - <TD> - <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>> - <BR><FONT SIZE=-1><% $mobile_label %></FONT> - </TD> - </TR> - </TABLE> - </TD> - </TR> + <& /elements/tr-cust_main-phones.html, + 'prefix' => $pre, + 'cust_main' => $cust_main, + 'onchange' => $onchange, + 'disabled' => $disabled, + 'style' => $style, + &> </%def> <%def fax> @@ -136,9 +119,7 @@ my $r = qq!<font color="#ff0000">*</font> !; </%once> <%shared> -my( %opt, $cust_main, $pre, $onchange, $disabled, @style, $style, - $daytime_label, $night_label, $mobile_label, - ); +my( %opt, $cust_main, $pre, $onchange, $disabled, @style, $style ); </%shared> <%init> @@ -176,16 +157,6 @@ $opt{geocode} ||= $cust_main->get('geocode'); $opt{censustract} ||= $cust_main->censustract; -$daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/ - ? 'Day' - : FS::Msgcat::_gettext('daytime'); -$night_label = FS::Msgcat::_gettext('night') =~/^(night)?$/ - ? 'Night' - : FS::Msgcat::_gettext('night') || 'Night'; -$mobile_label = FS::Msgcat::_gettext('mobile') =~/^(mobile)?$/ - ? 'Mobile' - : FS::Msgcat::_gettext('mobile') || 'Mobile'; - my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/ ? 'Driver’s License' : FS::Msgcat::_gettext('stateid') || 'Driver’s License'; diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 6c965326b..9e27f2a4c 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -527,7 +527,7 @@ Example: % if ( $f->{curr_value_callback} ) { % $curr_value = &{ $f->{curr_value_callback} }( $cgi, $object, $field ), % } else { -% $curr_value = $object->$field(); +% $curr_value = $object->$field() if $field; % } % $curr_value = &{ $opt{'value_callback'} }( $f->{'field'}, $curr_value ) % if $opt{'value_callback'} && $mode ne 'error'; diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi index ff8be1a71..4fb8f622d 100755 --- a/httemplate/edit/process/cust_main.cgi +++ b/httemplate/edit/process/cust_main.cgi @@ -1,7 +1,7 @@ % if ( $error ) { % $cgi->param('error', $error); -% -<% $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ) %> +% my $query = $m->scomp('/elements/create_uri_query', 'secure'=>1); +<% $cgi->redirect(popurl(2). "cust_main.cgi?$query" ) %> % % } else { % diff --git a/httemplate/edit/process/detach-cust_pkg.html b/httemplate/edit/process/detach-cust_pkg.html index ab87eb536..782ffa5e0 100644 --- a/httemplate/edit/process/detach-cust_pkg.html +++ b/httemplate/edit/process/detach-cust_pkg.html @@ -30,16 +30,23 @@ my $cust_location = new FS::cust_location { map { $_ => scalar($cgi->param($_)) } FS::cust_main->location_fields }; +#false laziness w/process/cust_main.cgi +my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') ); +push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST'); +push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX'); +$cgi->param('invoicing_list', join(',', @invoicing_list) ); + my $cust_main = new FS::cust_main { ( map { ( $_, scalar($cgi->param($_)) ) } fields('cust_main') ), ( map { ( "ship_$_", '' ) } FS::cust_main->location_fields ), - 'bill_location' => $cust_location, - 'ship_location' => $cust_location, + 'bill_location' => $cust_location, + 'ship_location' => $cust_location, }; my $pkg_or_error = $cust_pkg->change( { - 'keep_dates' => 1, - 'cust_main' => $cust_main, + 'keep_dates' => 1, + 'cust_main' => $cust_main, + 'cust_main_insert_args' => [ {}, \@invoicing_list ], } ); my $error = ref($pkg_or_error) ? '' : $pkg_or_error; diff --git a/httemplate/elements/create_uri_query b/httemplate/elements/create_uri_query index 32d8e2f87..ce6249e0e 100644 --- a/httemplate/elements/create_uri_query +++ b/httemplate/elements/create_uri_query @@ -1,17 +1,34 @@ <% $query %>\ <%init> +my %opt = @_; + +if ( $opt{secure} ) { + + foreach my $param (grep /pay(info\d?|cvv)$/, $cgi->param) { + my $value = $cgi->param($param); + next unless length($value); + my $encrypted = FS::Record->encrypt( $value ); + $cgi->param($param, $encrypted); + } + +} + my $query = $cgi->query_string; -if ( length($query) > 1920 ) { #stupid IE 2083 URL limit +if ( length($query) > 1920 || $opt{secure} ) { #stupid IE 2083 URL limit my $session = int(rand(4294967296)); #XXX my $pref = new FS::access_user_pref({ 'usernum' => $FS::CurrentUser::CurrentUser->usernum, 'prefname' => "redirect$session", 'prefvalue' => $query, - 'expiration' => time + 3600, #1h? 1m? + 'expiration' => time + ( $opt{secure} ? 120 #2m? + : 3600 #1h? + ), }); + local($FS::Record::no_history) = 1; + my $pref_error = $pref->insert; if ( $pref_error ) { die "FATAL: couldn't even set redirect cookie: $pref_error". diff --git a/httemplate/elements/handle_uri_query b/httemplate/elements/handle_uri_query index eb7ea1ae1..2dea96a6d 100644 --- a/httemplate/elements/handle_uri_query +++ b/httemplate/elements/handle_uri_query @@ -1,8 +1,20 @@ <%init> + +my %opt = @_; + if ( $cgi->param('redirect') ) { my $session = $cgi->param('redirect'); + my $pref = $FS::CurrentUser::CurrentUser->option("redirect$session"); die "unknown redirect session $session\n" unless length($pref); $cgi = new CGI($pref); + + foreach my $param (grep /pay(info\d?|cvv)$/, $cgi->param) { + my $value = $cgi->param($param); + next unless length($value); + my $decrypted = FS::Record->decrypt( $value ); + $cgi->param($param, $decrypted); + } + } </%init> diff --git a/httemplate/elements/pager.html b/httemplate/elements/pager.html index a53300f53..d360e6478 100644 --- a/httemplate/elements/pager.html +++ b/httemplate/elements/pager.html @@ -1,13 +1,8 @@ -% my %opt = @_; -% my $pager = ''; -% % if ( $opt{'total'} != $opt{'num_rows'} && $opt{'maxrecords'} ) { % % unless ( $opt{'offset'} == 0 ) { % $cgi->param('offset', $opt{'offset'} - $opt{'maxrecords'}); - <A HREF="<% $cgi->self_url %>"><B><FONT SIZE="+1">Previous</FONT></B></A> - % } % % my $page = 0; @@ -47,9 +42,17 @@ % % unless ( $opt{'offset'} + $opt{'maxrecords'} > $opt{'total'} ) { % $cgi->param('offset', $opt{'offset'} + $opt{'maxrecords'}); - <A HREF="<% $cgi->self_url %>"><B><FONT SIZE="+1">Next</FONT></B></A> -% % } % +% $cgi->param('offset', $orig_offset); #so future $self_url invocations don't advance a page +% % } +<%init> + +my %opt = @_; + +my $orig_offset = $opt{'offset'}; + +</%init> + diff --git a/httemplate/elements/searchbar-cust_main.html b/httemplate/elements/searchbar-cust_main.html index 9a98417c8..5bfef484a 100644 --- a/httemplate/elements/searchbar-cust_main.html +++ b/httemplate/elements/searchbar-cust_main.html @@ -1,6 +1,6 @@ % if ( $curuser->access_right('List customers') ) { - <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0"> + <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="POST" STYLE="margin:0"> <INPUT NAME="search_cust" TYPE="text" VALUE="<% $cust_label |n %>" STYLE="width:<%$width%>" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" CLASS="fstext"><BR> <A HREF="<%$fsurl%>search/report_cust_main.html" CLASS="fslink" STYLE="font-size: 11px"><% mt('Advanced') |h %></A> <INPUT TYPE="submit" VALUE="<% mt('Search customers') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px"> diff --git a/httemplate/elements/tr-censustract.html b/httemplate/elements/tr-censustract.html new file mode 100644 index 000000000..bd014f11b --- /dev/null +++ b/httemplate/elements/tr-censustract.html @@ -0,0 +1,23 @@ +% if ($censustract) { +<TR> + <TD ALIGN="right"><% mt('Census tract') |h %></TD> + <TD COLSPAN=5> + <SPAN STYLE="background-color: #ffffff; border: 1px solid #ffffff"><% $censustract |h %></SPAN> + <% $censusyear |h %> + </TD> +</TR> +% } +<%init> + +my $location = shift; +my $conf = FS::Conf->new; +my ($censustract, $censusyear); +if ($location->censustract) { + $censustract = $location->censustract; + $censusyear = '('. ($location->censusyear || mt('unknown year')) . ')'; +} elsif ($conf->exists('cust_main-require_censustract')) { + $censustract = mt('unknown'); + $censusyear = ''; +} + +</%init> diff --git a/httemplate/elements/tr-cust_main-phones.html b/httemplate/elements/tr-cust_main-phones.html new file mode 100644 index 000000000..accf8ac64 --- /dev/null +++ b/httemplate/elements/tr-cust_main-phones.html @@ -0,0 +1,45 @@ + <TR> + <TD ALIGN="right" VALIGN="top"><% mt('Phones') %></TD> + <TD COLSPAN=6> + + <TABLE CELLSPACING=0 CELLPADDING=0> + <TR> + <TD> + <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>> + <BR><FONT SIZE=-1><% $daytime_label %></FONT> + </TD> + <TD> </TD> + <TD> + <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>> + <BR><FONT SIZE=-1><% $night_label %></FONT> + </TD> + <TD> </TD> + <TD> + <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>> + <BR><FONT SIZE=-1><% $mobile_label %></FONT> + </TD> + </TR> + </TABLE> + </TD> + </TR> +<%init> + +my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/ + ? 'Day' + : FS::Msgcat::_gettext('daytime'); +my $night_label = FS::Msgcat::_gettext('night') =~/^(night)?$/ + ? 'Night' + : FS::Msgcat::_gettext('night') || 'Night'; +my $mobile_label = FS::Msgcat::_gettext('mobile') =~/^(mobile)?$/ + ? 'Mobile' + : FS::Msgcat::_gettext('mobile') || 'Mobile'; + +my %opt = @_; + +my $pre = $opt{'prefix'}; +my $cust_main = $opt{'cust_main'}; +my $onchange = $opt{'onchange'}; +my $disabled = $opt{'disabled'}; +my $style = $opt{'style'}; + +</%init> diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html index e1fa825c1..4ed9cd48e 100644 --- a/httemplate/elements/tr-select-cust_location.html +++ b/httemplate/elements/tr-select-cust_location.html @@ -31,6 +31,9 @@ Example: else what.form.<%$_%>.value = ''; if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#dddddd'; % } + if(what.form.enter_censustract) { + what.form.enter_censustract.disabled = true; + } } function location_clear(what) { @@ -38,6 +41,9 @@ Example: var ftype = what.form.<%$_%>.tagName; if( ftype == 'INPUT' ) what.form.<%$_%>.value = ''; % } + if(what.form.enter_censustract) { + what.form.enter_censustract.value = ''; + } % if ( $opt{'alt_format'} ) { changeSelect(what.form.location_kind, ''); changeSelect(what.form.location_type, ''); @@ -51,6 +57,9 @@ Example: var ftype = what.form.<%$_%>.tagName; if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff'; % } + if(what.form.enter_censustract) { + what.form.enter_censustract.disabled = false; + } % if ( $opt{'alt_format'} ) { if ( what.form.location_type && what.form.location_type.options[what.form.location_type.selectedIndex].value ) { diff --git a/httemplate/misc/detach_pkg.html b/httemplate/misc/detach_pkg.html index 64b3e6e3f..366bbac3f 100755 --- a/httemplate/misc/detach_pkg.html +++ b/httemplate/misc/detach_pkg.html @@ -50,9 +50,26 @@ </TD> </TR> +% if ( $conf->config_bool('cust_main-require_phone') ) { +% #XXX should be sticky on errors +% my $ph_cust_main = FS::cust_main->new({}); +% foreach my $contact_phone ( $cust_contact->contact_phone ) { +% my $t = $contact_phone->typename; +% #countrycodes? interface doesn't parse/take em yet +% $ph_cust_main->daytime( $contact_phone->phonenum ) if $t eq 'Work'; +% $ph_cust_main->night( $contact_phone->phonenum ) if $t eq 'Home'; +% $ph_cust_main->mobile( $contact_phone->phonenum ) if $t eq 'Mobile'; +% $ph_cust_main->fax( $contact_phone->phonenum ) if $t eq 'Fax'; +% } + + <& /elements/tr-cust_main-phones.html, + 'cust_main' => $ph_cust_main, + &> +% } + </TABLE> -%#XXX payment info +%#payment info %#XXX should be sticky on errors... <& /edit/cust_main/billing.html, FS::cust_main->new({}), invoicing_list => [], diff --git a/httemplate/misc/disable-msg_template.cgi b/httemplate/misc/disable-msg_template.cgi new file mode 100644 index 000000000..1eb4d25e5 --- /dev/null +++ b/httemplate/misc/disable-msg_template.cgi @@ -0,0 +1,77 @@ +% if ( @error ) { +<& /elements/errorpage-popup.html, @error &> +% } else { +<& /elements/header-popup.html, "Template ${actioned}" &> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> +</BODY> +</HTML> +% } +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +my $conf = FS::Conf->new; +my @error; +my $actioned; + +die "access denied" + unless $curuser->access_right([ 'Edit templates', 'Edit global templates' ]); + +my $msgnum = $cgi->param('msgnum'); +$msgnum =~ /^\d+$/ or die "bad msgnum '$msgnum'"; +my $msg_template = qsearchs({ + table => 'msg_template', + hashref => { msgnum => $msgnum }, + extra_sql => ' AND '. + $curuser->agentnums_sql(null_right => 'Edit global templates'), +}); +die "unknown msgnum $msgnum" unless $msg_template; + +if ( $cgi->param('enable') ) { + $actioned = 'enabled'; + $msg_template->set('disabled' => ''); +} else { + $actioned = 'disabled'; + # make sure it's not in use anywhere + my @inuse; + + # notice, letter, notice_to events (if they're enabled) + my @events = qsearch({ + table => 'part_event_option', + addl_from => ' JOIN part_event USING (eventpart)', + hashref => { + optionname => 'msgnum', + optionvalue => $msgnum, + }, + extra_sql => ' AND disabled IS NULL', + }); + push @inuse, map {"Billing event #".$_->eventpart} @events; + + # send_email and rt_ticket exports + my @exports = qsearch( 'part_export_option', { + optionname => { op => 'LIKE', value => '%_template' }, + optionvalue => $msgnum, + }); + push @inuse, map {"Export #".$_->exportnum} @exports; + + # payment_receipt_msgnum, decline_msgnum, etc. + my @confs = qsearch( 'conf', { + name => { op => 'LIKE', value => '%_msgnum' }, + value => $msgnum, + }); + push @inuse, map {"Configuration setting ".$_->name} @confs; + # XXX pending queue jobs? + if (@inuse) { + @error = ("This template is in use. Check the following settings:", + @inuse); + } + + # good to go + $msg_template->set(disabled => 'Y'); +} +if (!@error) { + my $error = $msg_template->replace; + push @error, $error if $error; +} +</%init> diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html index ad67b8d7e..3b2ac3c5f 100644 --- a/httemplate/misc/email-customers.html +++ b/httemplate/misc/email-customers.html @@ -98,6 +98,7 @@ Template: <% include('/elements/select-table.html', 'label' => 'Template:', 'table' => 'msg_template', + 'hashref' => { disabled => '' }, 'name_col' => 'msgname', 'empty_label' => '(none)', 'onchange' => 'toggle(this)', diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi index d86641de3..f87862a3d 100644 --- a/httemplate/search/cust_bill_pkg.cgi +++ b/httemplate/search/cust_bill_pkg.cgi @@ -49,8 +49,9 @@ @currency, 'invnum', '_date', - #'pay_amount', - #'credit_amount', + '', #'pay_amount', + '', #'credit_amount', + FS::UI::Web::cust_sort_fields(), ], 'links' => [ @pkgnum_null, diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html index d65af669a..294b7babb 100644 --- a/httemplate/view/cust_main/contacts.html +++ b/httemplate/view/cust_main/contacts.html @@ -41,7 +41,14 @@ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->company |h %></TD> </TR> % } -% } # if $this eq 'bill' +% } elsif ( $this eq 'ship' ) { +% if ( $cust_main->ship_company ) { # mostly obsolete these days... + <TR> + <TD ALIGN="right"><% mt('Company') |h %></TD> + <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->ship_company |h %></TD> + </TR> +% } +% } % # now the actual address <TR> <TD ALIGN="right"><% mt('Address') |h %></TD> @@ -84,6 +91,7 @@ $cust_main->agentnum, &> % } +<& /elements/tr-censustract.html, $location &> % if ( $this eq 'bill' ) { % # billing contact phone numbers diff --git a/httemplate/view/cust_main/locations.html b/httemplate/view/cust_main/locations.html index 7eb52ca46..fdbbc396b 100755 --- a/httemplate/view/cust_main/locations.html +++ b/httemplate/view/cust_main/locations.html @@ -1,40 +1,60 @@ <STYLE> -span.loclabel { +div.loclabel { + display: inline-block; padding-left: 4px; padding-right: 4px; background-color: #cccccc; - border: 1px solid black + border: 1px solid black; + border-bottom: 0px; + border-radius: 4px 4px 0 0; +} +div.disabled { + font-style: italic; + color: #808080; } table.location { width: 100%; padding: 1px; border-spacing: 0px; } +.location-head th { + padding-bottom: 0px; + padding-left: 0px; + border-bottom: 1px solid black; + vertical-align: bottom; + text-align: left; + width: 100%; +} </STYLE> % foreach my $locationnum (@sorted) { % my $packages = $packages_in{$locationnum}; % my $loc = $locations{$locationnum}; % next if $loc->disabled and scalar(@$packages) == 0; <TABLE CLASS="grid location"> -<TR><TH COLSPAN=3 ALIGN="left" VALIGN="bottom" -STYLE="padding-bottom: 0px; - padding-left: 0px; - border-bottom-style: solid; - border-bottom-color: black; - border-bottom-width: 1px;"> -<SPAN CLASS="loclabel"> -% if ( $loc->disabled ) { -<FONT COLOR="#808080"><I> +<TR CLASS="location-head"> +<TH COLSPAN=5> +<DIV CLASS="<% $loc->disabled ? 'loclabel disabled' : 'loclabel' %>"> +<% $loc->location_label %> +% if ( $loc->censustract ) { + <BR> + <FONT SIZE=-1> + <% $loc->censustract %> (<% $loc->censusyear %> census) + </FONT> +% } elsif ( $conf->exists('cust_main-require_censustract') ) { + <BR> + <FONT SIZE=-1 COLOR="#ee3300"> + <% emt('Census tract unknown') %> + </FONT> % } -<% $loc->location_label %></SPAN> -<SPAN STYLE="float:right;"> +</DIV> +<DIV STYLE="display: inline; float:right;"> % if ( $locationnum && !$loc->disabled && ! $opt{no_links} ) { <% edit_location_link($locationnum) %> % } % if ( $locationnum && !$loc->disabled && !$active{$locationnum} && ! $opt{no_links} ) { <% disable_location_link($locationnum) %> % } -</SPAN></TH></TR> +</DIV></TH></TR> % if (@$packages) { <& packages/section.html, 'packages' => $packages, @@ -48,6 +68,7 @@ STYLE="padding-bottom: 0px; my %opt = @_; my $cust_main = $opt{'cust_main'}; my $all_packages = $opt{'packages'}; +my $conf = FS::Conf->new; my %locations = map { $_->locationnum => $_ } qsearch({ 'table' => 'cust_location', diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html index 5311aa5ae..7915195b4 100644 --- a/httemplate/view/cust_main/misc.html +++ b/httemplate/view/cust_main/misc.html @@ -171,17 +171,6 @@ % } -% if ( $conf->exists('cust_main-require_censustract') ) { - - <TR> - <TD ALIGN="right"> - <% mt('Census tract ([_1])', $cust_main->ship_location->censusyear) |h %> - </TD> - <TD BGCOLOR="#ffffff"><% $cust_main->ship_location->censustract %></TD> - </TR> - -% } - % if ( $cust_main->district ) { <TR> diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html index 5ff2b1e1f..db67d45b9 100644 --- a/httemplate/view/cust_main/packages/location.html +++ b/httemplate/view/cust_main/packages/location.html @@ -1,35 +1,49 @@ % if ( $cust_pkg->change_from_pkg -% and $cust_pkg->change_from_pkg->locationnum == $cust_pkg->locationnum ) +% and $cust_pkg->change_from_pkg->locationnum == $cust_pkg->locationnum) % { % # don't show the location % } else { -% if ( $default ) { - <DIV STYLE="font-style: italic; font-size: small"> -% } +% if ( !$conf->exists('cust_pkg-group_by_location') ) { +% if ( $default ) { + <DIV STYLE="font-style: italic; font-size: small"> +% } - <% $loc->location_label( 'join_string' => '<BR>', - 'double_space' => ' ', - 'escape_function' => \&encode_entities, - 'countrydefault' => $countrydefault, - ) - %> + <% $loc->location_label( 'join_string' => '<BR>', + 'double_space' => ' ', + 'escape_function' => \&encode_entities, + 'countrydefault' => $countrydefault, + ) + %> -% if ( $loc->latitude && $loc->longitude ) { - <BR> - <FONT SIZE=-1> - <% $loc->latitude %>, <% $loc->longitude %> - <& /elements/coord-links.html, - $loc->latitude, - $loc->longitude, - $opt{'cust_main'}->name_short. ': '. $opt{'part_pkg'}->pkg, - $opt{'cust_main'}->agentnum, - &> - </FONT> -% } +% if ( $loc->latitude && $loc->longitude ) { + <BR> + <FONT SIZE=-1> + <% $loc->latitude %>, <% $loc->longitude %> + <& /elements/coord-links.html, + $loc->latitude, + $loc->longitude, + $opt{'cust_main'}->name_short. ': '. $opt{'part_pkg'}->pkg, + $opt{'cust_main'}->agentnum, + &> + </FONT> +% } +% if ( $loc->censustract ) { + <BR> + <FONT SIZE=-1> + <% $loc->censustract %> (<% $loc->censusyear %> census) + </FONT> +% } elsif ( $conf->exists('cust_main-require_censustract') ) { + <BR> + <FONT SIZE=-1 COLOR="#ee3300"> + <% emt('Census tract unknown') %> + </FONT> +% } -% if ( $default ) { - </DIV> -% } +% if ( $default ) { + </DIV> +% } +% } # all of this is hidden if packages are grouped by location, because +% # it's in the top banner % if ( ! $cust_pkg->get('cancel') % && $FS::CurrentUser::CurrentUser->access_right('Change customer package') @@ -41,11 +55,13 @@ ( <%pkg_change_location_link($cust_pkg)%> ) % } % if ( $cust_pkg->locationnum && ! $opt{no_links} ) { - ( <%edit_location_link($cust_pkg->locationnum)%> ) + ( <%pkg_edit_location_link($cust_pkg->locationnum)%> ) % } </FONT> % } -% } +% } # if the package is a scheduled future package change without location +% # change, then don't show any of this at all. It's all implied by the +% # preceding package. <%init> my $conf = new FS::Conf; @@ -75,7 +91,7 @@ sub pkg_change_location_link { ); } -sub edit_location_link { +sub pkg_edit_location_link { my $locationnum = shift; include( '/elements/popup_link.html', 'action' => $p. "edit/cust_location.cgi?locationnum=$locationnum", |