diff options
75 files changed, 1846 insertions, 491 deletions
diff --git a/FS/FS/ClientAPI.pm b/FS/FS/ClientAPI.pm index e4031b26e..1fea28c67 100644 --- a/FS/FS/ClientAPI.pm +++ b/FS/FS/ClientAPI.pm @@ -6,7 +6,7 @@ use vars qw( @EXPORT_OK %handler $domain $DEBUG $me ); @EXPORT_OK = qw( load_clientapi_modules ); -$DEBUG = 1; +$DEBUG = 0; $me = '[FS::ClientAPI]'; %handler = (); diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm index b6f8aa4c6..50597e2cb 100644 --- a/FS/FS/ClientAPI/MasonComponent.pm +++ b/FS/FS/ClientAPI/MasonComponent.pm @@ -22,6 +22,7 @@ my %allowed_comps = map { $_=>1 } qw( /misc/counties.cgi /misc/svc_acct-domains.cgi /misc/part_svc-columns.cgi + /edit/elements/svc_forward.html ); my %session_comps = map { $_=>1 } qw( diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index d9378d8a8..11523013c 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -139,6 +139,7 @@ sub skin_info { 'logo' => scalar($conf->config_binary('logo.png', $agentnum )), ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) } qw( head body_header body_footer company_address ) ), + 'money_char' => $conf->config("money_char") || '$', 'menu' => join("\n", $conf->config("ng_selfservice-menu", $agentnum ) ) || 'main.php Home @@ -443,11 +444,13 @@ sub customer_info { if ( $session->{'pkgnum'} ) { #XXX open invoices in the pkg-balances case } else { + $return{'money_char'} = $conf->config("money_char") || '$'; my @open = map { { - invnum => $_->invnum, - date => time2str("%b %o, %Y", $_->_date), - owed => $_->owed, + invnum => $_->invnum, + date => time2str("%b %o, %Y", $_->_date), + owed => $_->owed, + charged => $_->charged, }; } $cust_main->open_cust_bill; $return{open_invoices} = \@open; @@ -1554,25 +1557,31 @@ sub list_invoices { my @cust_bill = grep ! $_->hide, $cust_main->cust_bill; my $balance = 0; + my $invoices = [ + map { + #not super efficient, we also run cust_bill_pay/cust_credited inside owed + my @payments_and_credits = sort {$b->_date <=> $a->_date} ($_->cust_bill_pay,$_->cust_credited); + my $owed = $_->owed; + $balance += $owed; + +{ 'invnum' => $_->invnum, + '_date' => $_->_date, + 'date' => time2str("%b %o, %Y", $_->_date), + 'date_short' => time2str("%m-%d-%Y", $_->_date), + 'previous' => sprintf('%.2f', ($_->previous)[0]), + 'charged' => sprintf('%.2f', $_->charged), + 'owed' => sprintf('%.2f', $owed), + 'balance' => sprintf('%.2f', $balance), + 'lastpay' => @payments_and_credits + ? time2str("%b %o, %Y", $payments_and_credits[0]->_date) + : '', + } + } @cust_bill + ]; return { 'error' => '', 'balance' => $cust_main->balance, - 'invoices' => [ - map { - my $owed = $_->owed; - $balance += $owed; - +{ 'invnum' => $_->invnum, - '_date' => $_->_date, - 'date' => time2str("%b %o, %Y", $_->_date), - 'date_short' => time2str("%m-%d-%Y", $_->_date), - 'previous' => sprintf('%.2f', ($_->previous)[0]), - 'charged' => sprintf('%.2f', $_->charged), - 'owed' => sprintf('%.2f', $owed), - 'balance' => sprintf('%.2f', $balance), - } - } - @cust_bill - ], + 'money_char' => $conf->config("money_char") || '$', + 'invoices' => $invoices, 'legacy_invoices' => [ map { +{ 'legacyinvnum' => $_->legacyinvnum, @@ -2751,6 +2760,15 @@ sub provision_external { ); } +sub provision_forward { + my $p = shift; + _provision( 'FS::svc_forward', + ['srcsvc','src','dstsvc','dst'], + [], + $p, + ); +} + sub _provision { my( $class, $fields, $return_fields, $p ) = splice(@_, 0, 4); warn "_provision called for $class\n" @@ -2876,6 +2894,10 @@ sub part_svc_info { } } + if ($ret->{'svcdb'} eq 'svc_forward') { + $ret->{'forward_emails'} = {$cust_pkg->forward_emails()}; + } + $ret; } diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index 6db33ac86..547f230c8 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -667,7 +667,6 @@ sub new_customer { my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } ) or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" }; - my $svcpart = $part_pkg->svcpart($svc_x); my $reg_code = ''; if ( $packet->{'reg_code'} ) { @@ -685,50 +684,55 @@ sub new_customer { #my $error = $cust_pkg->check; #return { 'error' => $error } if $error; - #should be all auto-magic and shit my @svc = (); - if ( $svc_x eq 'svc_acct' ) { + unless ( $svc_x eq 'none' ) { - my $svc = new FS::svc_acct { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( username _password sec_phrase popnum domsvc ), - }; - - my @acct_snarf; - my $snarfnum = 1; - while ( exists($packet->{"snarf_machine$snarfnum"}) - && length($packet->{"snarf_machine$snarfnum"}) ) { - my $acct_snarf = new FS::acct_snarf ( { - 'machine' => $packet->{"snarf_machine$snarfnum"}, - 'protocol' => $packet->{"snarf_protocol$snarfnum"}, - 'username' => $packet->{"snarf_username$snarfnum"}, - '_password' => $packet->{"snarf_password$snarfnum"}, - } ); - $snarfnum++; - push @acct_snarf, $acct_snarf; - } - $svc->child_objects( \@acct_snarf ); - push @svc, $svc; + my $svcpart = $part_pkg->svcpart($svc_x); + #should be all auto-magic and shit + if ( $svc_x eq 'svc_acct' ) { - } elsif ( $svc_x eq 'svc_phone' ) { + my $svc = new FS::svc_acct { + 'svcpart' => $svcpart, + map { $_ => $packet->{$_} } + qw( username _password sec_phrase popnum domsvc ), + }; - push @svc, new FS::svc_phone ( { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( countrycode phonenum sip_password pin ), - } ); + my @acct_snarf; + my $snarfnum = 1; + while ( exists($packet->{"snarf_machine$snarfnum"}) + && length($packet->{"snarf_machine$snarfnum"}) ) { + my $acct_snarf = new FS::acct_snarf ( { + 'machine' => $packet->{"snarf_machine$snarfnum"}, + 'protocol' => $packet->{"snarf_protocol$snarfnum"}, + 'username' => $packet->{"snarf_username$snarfnum"}, + '_password' => $packet->{"snarf_password$snarfnum"}, + } ); + $snarfnum++; + push @acct_snarf, $acct_snarf; + } + $svc->child_objects( \@acct_snarf ); + push @svc, $svc; - } elsif ( $svc_x eq 'svc_pbx' ) { + } elsif ( $svc_x eq 'svc_phone' ) { - push @svc, new FS::svc_pbx ( { + push @svc, new FS::svc_phone ( { 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( id title ), - } ); + map { $_ => $packet->{$_} } + qw( countrycode phonenum sip_password pin ), + } ); + + } elsif ( $svc_x eq 'svc_pbx' ) { + + push @svc, new FS::svc_pbx ( { + 'svcpart' => $svcpart, + map { $_ => $packet->{$_} } + qw( id title ), + } ); - } else { - die "unknown signup service $svc_x"; + } else { + die "unknown signup service $svc_x"; + } + } if ($packet->{'mac_addr'} && $conf->exists('signup_server-mac_addr_svcparts')) @@ -983,7 +987,6 @@ sub new_customer_minimal { my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } ) or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" }; - my $svcpart = $part_pkg->svcpart($svc_x); my $cust_pkg = new FS::cust_pkg ( { #later#'custnum' => $custnum, @@ -992,35 +995,40 @@ sub new_customer_minimal { #my $error = $cust_pkg->check; #return { 'error' => $error } if $error; - #should be all auto-magic and shit - if ( $svc_x eq 'svc_acct' ) { + unless ( $svc_x eq 'none' ) { - my $svc = new FS::svc_acct { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( username _password sec_phrase popnum domsvc ), - }; + my $svcpart = $part_pkg->svcpart($svc_x); + #should be all auto-magic and shit + if ( $svc_x eq 'svc_acct' ) { - push @svc, $svc; + my $svc = new FS::svc_acct { + 'svcpart' => $svcpart, + map { $_ => $packet->{$_} } + qw( username _password sec_phrase popnum domsvc ), + }; - } elsif ( $svc_x eq 'svc_phone' ) { + push @svc, $svc; - push @svc, new FS::svc_phone ( { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( countrycode phonenum sip_password pin ), - } ); - - } elsif ( $svc_x eq 'svc_pbx' ) { + } elsif ( $svc_x eq 'svc_phone' ) { - push @svc, new FS::svc_pbx ( { + push @svc, new FS::svc_phone ( { 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( id title ), - } ); + map { $_ => $packet->{$_} } + qw( countrycode phonenum sip_password pin ), + } ); + + } elsif ( $svc_x eq 'svc_pbx' ) { + + push @svc, new FS::svc_pbx ( { + 'svcpart' => $svcpart, + map { $_ => $packet->{$_} } + qw( id title ), + } ); - } else { - die "unknown signup service $svc_x"; + } else { + die "unknown signup service $svc_x"; + } + } foreach my $svc ( @svc ) { @@ -1068,6 +1076,7 @@ sub new_customer_minimal { 'signup_service' => $svc_x, 'custnum' => $cust_main->custnum, 'session_id' => $session_id, + map { $_ => $cust_main->$_ } qw( first last company ), ); if ( $svc[0] ) { diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 38ccb6eaa..d2113616c 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -2377,6 +2377,7 @@ and customer address. Include units.', 'svc_acct' => 'Account (svc_acct)', 'svc_phone' => 'Phone number (svc_phone)', 'svc_pbx' => 'PBX (svc_pbx)', + 'none' => 'None - package only', ], }, @@ -5964,6 +5965,13 @@ and customer address. Include units.', 'type' => 'checkbox', }, + { + 'key' => 'default_appointment_length', + 'section' => 'UI', + 'description' => 'Default appointment length, in minutes (30 minute granularity).', + 'type' => 'text', + }, + { key => "apacheroot", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" }, { key => "apachemachine", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" }, { key => "apachemachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" }, diff --git a/FS/FS/L10N/es_cu.pm b/FS/FS/L10N/es_cu.pm new file mode 100644 index 000000000..f556c1f4b --- /dev/null +++ b/FS/FS/L10N/es_cu.pm @@ -0,0 +1,4 @@ +package FS::L10N::es_cu; +use base qw(FS::L10N::es_es); + +1; diff --git a/FS/FS/L10N/es_es.pm b/FS/FS/L10N/es_es.pm new file mode 100644 index 000000000..4071731b3 --- /dev/null +++ b/FS/FS/L10N/es_es.pm @@ -0,0 +1,6 @@ +package FS::L10N::es_es; +use base qw(FS::L10N::DBI); + +our %Lexicon = (); + +1; diff --git a/FS/FS/L10N/es_mx.pm b/FS/FS/L10N/es_mx.pm new file mode 100644 index 000000000..1901b9db4 --- /dev/null +++ b/FS/FS/L10N/es_mx.pm @@ -0,0 +1,4 @@ +package FS::L10N::es_mx; +use base qw(FS::L10N::es_es); + +1; diff --git a/FS/FS/L10N/es_us.pm b/FS/FS/L10N/es_us.pm new file mode 100644 index 000000000..838b0475c --- /dev/null +++ b/FS/FS/L10N/es_us.pm @@ -0,0 +1,4 @@ +package FS::L10N::es_us; +use base qw(FS::L10N::es_es); + +1; diff --git a/FS/FS/Locales.pm b/FS/FS/Locales.pm index 032c3b59a..849929cfa 100644 --- a/FS/FS/Locales.pm +++ b/FS/FS/Locales.pm @@ -31,6 +31,10 @@ tie our %locales, 'Tie::IxHash', 'en_US', { name => 'English', country => 'United States', }, 'en_AU', { name => 'English', country => 'Australia', }, 'en_CA', { name => 'English', country => 'Canada', }, + 'es_ES', { name => 'Spanish', country => 'Spain', }, + 'es_CU', { name => 'Spanish', country => 'Cuba', }, + 'es_MX', { name => 'Spanish', country => 'Mexico', }, + 'es_US', { name => 'Spanish', country => 'United States', }, 'fr_CA', { name => 'French', country => 'Canada', }, 'fr_FR', { name => 'French', country => 'France', }, 'fr_HT', { name => 'French', country => 'Haiti', }, diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 88d452ae9..72745fe13 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -3,7 +3,7 @@ package FS::Record; use strict; use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG %virtual_fields_cache - $conf $conf_encryption $money_char $lat_lower $lon_upper + $money_char $lat_lower $lon_upper $me $nowarn_identical $nowarn_classload $no_update_diff $no_history $qsearch_qualify_columns @@ -61,14 +61,20 @@ my $rsa_loaded; my $rsa_encrypt; my $rsa_decrypt; -$conf = ''; -$conf_encryption = ''; +our $conf = ''; +our $conf_encryption = ''; +our $conf_encryptionmodule = ''; +our $conf_encryptionpublickey = ''; +our $conf_encryptionprivatekey = ''; FS::UID->install_callback( sub { eval "use FS::Conf;"; die $@ if $@; $conf = FS::Conf->new; - $conf_encryption = $conf->exists('encryption'); + $conf_encryption = $conf->exists('encryption'); + $conf_encryptionmodule = $conf->config('encryptionmodule'); + $conf_encryptionpublickey = join("\n",$conf->config('encryptionpublickey')); + $conf_encryptionprivatekey = join("\n",$conf->config('encryptionprivatekey')); $money_char = $conf->config('money_char') || '$'; my $nw_coords = $conf->exists('geocode-require_nw_coordinates'); $lat_lower = $nw_coords ? 1 : -90; @@ -1158,7 +1164,7 @@ sub insert { # Encrypt before the database if ( defined(eval '@FS::'. $table . '::encrypted_fields') && scalar( eval '@FS::'. $table . '::encrypted_fields') - && $conf->exists('encryption') + && $conf_encryption ) { foreach my $field (eval '@FS::'. $table . '::encrypted_fields') { next if $field eq 'payinfo' @@ -1399,7 +1405,7 @@ sub replace { # Encrypt for replace my $saved = {}; - if ( $conf->exists('encryption') + if ( $conf_encryption && defined(eval '@FS::'. $new->table . '::encrypted_fields') && scalar( eval '@FS::'. $new->table . '::encrypted_fields') ) { @@ -2160,7 +2166,7 @@ sub _h_statement { ; # If we're encrypting then don't store the payinfo in the history - if ( $conf && $conf->exists('encryption') && $self->table ne 'banned_pay' ) { + if ( $conf_encryption && $self->table ne 'banned_pay' ) { @fields = grep { $_ ne 'payinfo' } @fields; } @@ -3057,7 +3063,7 @@ sub encrypt { my ($self, $value) = @_; my $encrypted = $value; - if ($conf->exists('encryption')) { + if ($conf_encryption) { if ($self->is_encrypted($value)) { # Return the original value if it isn't plaintext. $encrypted = $value; @@ -3105,7 +3111,7 @@ You should generally not have to worry about calling this, as the system handles sub decrypt { my ($self,$value) = @_; my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted. - if ($conf->exists('encryption') && $self->is_encrypted($value)) { + if ($conf_encryption && $self->is_encrypted($value)) { $self->loadRSA; if (ref($rsa_decrypt) =~ /::RSA/) { my $encrypted = unpack ("u*", $value); @@ -3121,8 +3127,8 @@ sub loadRSA { #Initialize the Module $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default - if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') { - $rsa_module = $conf->config('encryptionmodule'); + if ($conf_encryptionmodule && $conf_encryptionmodule ne '') { + $rsa_module = $conf_encryptionmodule; } if (!$rsa_loaded) { @@ -3130,15 +3136,13 @@ sub loadRSA { $rsa_loaded++; } # Initialize Encryption - if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') { - my $public_key = join("\n",$conf->config('encryptionpublickey')); - $rsa_encrypt = $rsa_module->new_public_key($public_key); + if ($conf_encryptionpublickey && $conf_encryptionpublickey ne '') { + $rsa_encrypt = $rsa_module->new_public_key($conf_encryptionpublickey); } # Intitalize Decryption - if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') { - my $private_key = join("\n",$conf->config('encryptionprivatekey')); - $rsa_decrypt = $rsa_module->new_private_key($private_key); + if ($conf_encryptionprivatekey && $conf_encryptionprivatekey ne '') { + $rsa_decrypt = $rsa_module->new_private_key($conf_encryptionprivatekey); } } diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 5dcf3c3c2..3a27b741b 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2447,6 +2447,7 @@ sub tables_hashref { 'columnlabel', 'varchar', 'NULL', $char_d, '', '', 'columnvalue', 'varchar', 'NULL', 512, '', '', 'columnflag', 'char', 'NULL', 1, '', '', + 'required', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'columnnum', 'unique' => [ [ 'svcpart', 'columnname' ] ], diff --git a/FS/FS/cdr/FS/FS/cdr/vvs.pm b/FS/FS/cdr/FS/FS/cdr/vvs.pm new file mode 100644 index 000000000..63a647ee8 --- /dev/null +++ b/FS/FS/cdr/FS/FS/cdr/vvs.pm @@ -0,0 +1,33 @@ +package FS::cdr::vvs; + +use strict; +use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year ); +use Time::Local; +use FS::cdr qw(_cdr_date_parser_maker); + +@ISA = qw(FS::cdr); + +%info = ( + 'name' => 'VVS', + 'weight' => 120, + 'header' => 1, + 'import_fields' => [ + + skip(1), # i_customer + 'accountcode', # account_id + 'src', # caller + 'dst', # called + skip(2), # reason + # call id + _cdr_date_parser_maker('startdate'), # time + 'billsec', # duration + skip(3), # ringtime + # status + # resller_charge + 'upstream_price',# customer_charge + ], +); + +sub skip { map {''} (1..$_[0]) } + +1; diff --git a/FS/FS/cdr/earthlink.pm b/FS/FS/cdr/earthlink.pm index 60cba654f..213a025c2 100644 --- a/FS/FS/cdr/earthlink.pm +++ b/FS/FS/cdr/earthlink.pm @@ -36,6 +36,8 @@ use Date::Parse; }, #TERM NUMBER skip(2), #TERM CITY / TERM STATE _cdr_min_parser_maker, #MINUTES + skip(1), #AMOUNT + 'disposition', #Call Type ], ); diff --git a/FS/FS/cdr/enswitch.pm b/FS/FS/cdr/enswitch.pm index 1cdd802fc..8026293e2 100644 --- a/FS/FS/cdr/enswitch.pm +++ b/FS/FS/cdr/enswitch.pm @@ -34,7 +34,13 @@ use FS::cdr_type; #Inbound called type, #Inbound called number, #Inbound destination type, Inbound destination number, - 'disposition', #Outbound calling type, + sub { my ($cdr, $data) = @_; + $data ||= 'none'; + + my $cdr_type = qsearchs('cdr_type', { 'cdrtypename' => $data } ); + $cdr->set('cdrtypenum', $cdr_type->cdrtypenum) if $cdr_type; + } , #Outbound calling type, + skip(11), #Outbound calling number, #Outbound called type, Outbound called number, #Outbound destination type, Outbound destination number, @@ -53,23 +59,4 @@ use FS::cdr_type; sub skip { map {''} (1..$_[0]) } -#create CDR types with names matching in_calling_type valuesj - 'none' -# (without the quotes) for blank -our %cdr_type = (); -sub in_calling_type { - my ($record, $data) = @_; - - $data ||= 'none'; - - my $cdr_type = exists($cdr_type{$data}) - ? $cdr_type{$data} - : qsearchs('cdr_type', { 'cdrtypename' => $data } ); - - $cdr_type{$data} = $cdr_type; - - $record->set('in_calling_type', $data); #for below - $record->set('cdrtypenum', $cdr_type->cdrtypenum) if $cdr_type; - -} - 1; diff --git a/FS/FS/cdr/ispphone.pm b/FS/FS/cdr/ispphone.pm index 2817d53c1..c2ba1862d 100644 --- a/FS/FS/cdr/ispphone.pm +++ b/FS/FS/cdr/ispphone.pm @@ -15,8 +15,16 @@ use Date::Parse; 'import_fields' => [ 'accountcode', # Accountcode - 'src', # Form - 'dst', # To + sub { my ($cdr, $src) = @_; + $src =~ s/^\s+//; + $cdr->set('src', $src); + + }, # Form + sub { my ($cdr, $dst) = @_; + $dst =~ s/^\s+//; + $cdr->set('dst', $dst); + + }, # To skip(1), # Country 'upstream_dst_regionname', # Description _cdr_date_parser_maker('startdate'), #DateTime diff --git a/FS/FS/cdr/vss.pm b/FS/FS/cdr/vss.pm index 6fc647aad..a550303df 100644 --- a/FS/FS/cdr/vss.pm +++ b/FS/FS/cdr/vss.pm @@ -13,7 +13,6 @@ use FS::cdr qw(_cdr_date_parser_maker); 'header' => 1, 'import_fields' => [ - skip(1), # Customer skip(1), # i_customer 'accountcode', # account_id 'src', # caller diff --git a/FS/FS/cust_bill_pay_pkg.pm b/FS/FS/cust_bill_pay_pkg.pm index 5d154d0b7..24ca7973a 100644 --- a/FS/FS/cust_bill_pay_pkg.pm +++ b/FS/FS/cust_bill_pay_pkg.pm @@ -1,14 +1,12 @@ package FS::cust_bill_pay_pkg; +use base qw( FS::cust_main_Mixin FS::Record ); use strict; -use vars qw( @ISA ); use FS::Conf; use FS::Record qw( qsearch qsearchs ); use FS::cust_bill_pay; use FS::cust_bill_pkg; -@ISA = qw(FS::Record); - =head1 NAME FS::cust_bill_pay_pkg - Object methods for cust_bill_pay_pkg records diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index 70c7adc7a..4448da62e 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -837,6 +837,8 @@ sub _item_discount { description => $self->mt('Discount'), amount => 0, ext_description => \@ext, + pkgpart => $self->pkgpart, + feepart => $self->feepart, # maybe should show quantity/unit discount? }; foreach my $pkg_discount (@pkg_discounts) { diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index d4bd1f304..5980bf5de 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -343,8 +343,9 @@ sub realtime_bop { my $cc_surcharge = 0; my $cc_surcharge_pct = 0; $cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage') - if $conf->config('credit-card-surcharge-percentage'); - + if $conf->config('credit-card-surcharge-percentage') + && $options{method} eq 'CC'; + # always add cc surcharge if called from event if($options{'cc_surcharge_from_event'} && $cc_surcharge_pct > 0) { $cc_surcharge = $options{'amount'} * $cc_surcharge_pct / 100; diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index a03a44653..14555dd67 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -2043,7 +2043,7 @@ sub change { return "modifying package: $error"; } else { $dbh->commit if $oldAutoCommit; - return ''; + return $self; } } @@ -3723,6 +3723,7 @@ Returns the parent customer object (see L<FS::cust_main>). sub cust_main { my $self = shift; + cluck 'cust_pkg->cust_main called' if $DEBUG; qsearchs( 'cust_main', { 'custnum' => $self->custnum } ); } @@ -5666,6 +5667,78 @@ sub bulk_change { ''; } +=item forward_emails + +Returns a hash of svcnums and corresponding email addresses +for svc_acct services that can be used as source or dest +for svc_forward services provisioned in this package. + +Accepts options I<svc_forward> OR I<svcnum> for a svc_forward +service; if included, will ensure the current values of the +specified service are included in the list, even if for some +other reason they wouldn't be. If called as a class method +with a specified service, returns only these current values. + +Caution: does not actually check if svc_forward services are +available to be provisioned on this package. + +=cut + +sub forward_emails { + my $self = shift; + my %opt = @_; + + #load optional service, thoroughly validated + die "Use svcnum or svc_forward, not both" + if $opt{'svcnum'} && $opt{'svc_forward'}; + my $svc_forward = $opt{'svc_forward'}; + $svc_forward ||= qsearchs('svc_forward',{ 'svcnum' => $opt{'svcnum'} }) + if $opt{'svcnum'}; + die "Specified service is not a forward service" + if $svc_forward && (ref($svc_forward) ne 'FS::svc_forward'); + die "Specified service not found" + if ($opt{'svcnum'} || $opt{'svc_forward'}) && !$svc_forward; + + my %email; + + ## everything below was basically copied from httemplate/edit/svc_forward.cgi + ## with minimal refactoring, not sure why we can't just load all svc_accts for this custnum + + #add current values from specified service, if there was one + if ($svc_forward) { + foreach my $method (qw( srcsvc_acct dstsvc_acct )) { + my $svc_acct = $svc_forward->$method(); + $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct; + } + } + + if (ref($self) eq 'FS::cust_pkg') { + + #and including the rest for this customer + my($u_part_svc,@u_acct_svcparts); + foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { + push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); + } + + my $custnum = $self->getfield('custnum'); + foreach my $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { + my $cust_pkgnum = $i_cust_pkg->getfield('pkgnum'); + #now find the corresponding record(s) in cust_svc (for this pkgnum!) + foreach my $acct_svcpart (@u_acct_svcparts) { + foreach my $i_cust_svc ( + qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, + 'svcpart' => $acct_svcpart } ) + ) { + my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } ); + $email{$svc_acct->svcnum} = $svc_acct->email; + } + } + } + } + + return %email; +} + # Used by FS::Upgrade to migrate to a new database. sub _upgrade_data { # class method my ($class, %opts) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_spool_csv.pm b/FS/FS/part_event/Action/cust_bill_spool_csv.pm index 250c83042..0d84e77c4 100644 --- a/FS/FS/part_event/Action/cust_bill_spool_csv.pm +++ b/FS/FS/part_event/Action/cust_bill_spool_csv.pm @@ -33,6 +33,10 @@ sub option_fields { empty_label => '(do not upload)', order_by => 'targetnum', }, + 'skip_nopost' => { label => 'Skip customers without postal billing enabled', + type => 'checkbox', + value => 'Y', + }, ); } @@ -44,6 +48,9 @@ sub do_action { #my $cust_main = $self->cust_main($cust_bill); my $cust_main = $cust_bill->cust_main; + return if $self->option('skip_nopost') + && ! grep { $_ eq 'POST' } $cust_main->invoicing_list; + $cust_bill->spool_csv( 'time' => $cust_event->_date, 'format' => $self->option('spoolformat'), diff --git a/FS/FS/part_event/Condition/cust_bill_age_before_payby.pm b/FS/FS/part_event/Condition/cust_bill_age_before_payby.pm index 96d9da8d8..5a81fbbd0 100644 --- a/FS/FS/part_event/Condition/cust_bill_age_before_payby.pm +++ b/FS/FS/part_event/Condition/cust_bill_age_before_payby.pm @@ -34,12 +34,12 @@ sub condition { 'order_by' => 'ORDER BY history_date DESC LIMIT 1', })) { - my $newest = $replace_new->history_date; + $newest = $replace_new->history_date; my $replace_old = qsearchs({ 'table' => 'h_cust_main', 'hashref' => { 'custnum' => $replace_new->custnum, 'history_action' => 'replace_old', - 'history_date' => $replace_new->history_date, + 'history_date' => $replace_new->history_date, #fuzz? } }) or next; #no replace_old? ignore and continue on i guess diff --git a/FS/FS/part_pkg_link.pm b/FS/FS/part_pkg_link.pm index 8e43d155e..ce071ef17 100644 --- a/FS/FS/part_pkg_link.pm +++ b/FS/FS/part_pkg_link.pm @@ -130,6 +130,7 @@ sub insert { return $error if $error; } + $dbh->commit if $oldAutoCommit; return; } @@ -166,7 +167,7 @@ sub delete { $dbh->rollback if $oldAutoCommit; return $error; } - $dbh->commit; + $dbh->commit if $oldAutoCommit; return; } diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index c2003c4aa..8a5fc35c9 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -96,8 +96,12 @@ the part_svc_column table appropriately (see L<FS::part_svc_column>). =item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>. +=item I<svcdb>__I<field>_label + =item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null or empty (no default), `D' for default, `F' for fixed (unchangeable), , `S' for selectable choice, `M' for manual selection from inventory, or `A' for automatic selection from inventory. For virtual fields, can also be 'X' for excluded. +=item I<svcdb>__I<field>_required - I<field> should always have a true value + =back If you want to add part_svc_column records for fields that do not exist as @@ -139,52 +143,53 @@ sub insert { # add part_svc_column records my $svcdb = $self->svcdb; -# my @rows = map { /^${svcdb}__(.*)$/; $1 } -# grep ! /_flag$/, -# grep /^${svcdb}__/, -# fields('part_svc'); - foreach my $field ( - grep { $_ ne 'svcnum' - && ( defined( $self->getfield($svcdb.'__'.$_.'_flag') ) - || $self->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ ) - } (fields($svcdb), @fields) - ) { - my $part_svc_column = $self->part_svc_column($field); - my $previous = qsearchs('part_svc_column', { - 'svcpart' => $self->svcpart, - 'columnname' => $field, - } ); + foreach my $field (fields($svcdb), @fields) { + next if $field eq 'svcnum'; + my $prefix = $svcdb.'__'; + if ( defined( $self->getfield($prefix.$field.'_flag')) + or defined($self->getfield($prefix.$field.'_required')) + or length($self->getfield($prefix.$field.'_label')) + ) { + my $part_svc_column = $self->part_svc_column($field); + my $previous = qsearchs('part_svc_column', { + 'svcpart' => $self->svcpart, + 'columnname' => $field, + } ); - my $flag = $self->getfield($svcdb.'__'.$field.'_flag'); - my $label = $self->getfield($svcdb.'__'.$field.'_label'); - if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { + my $flag = $self->getfield($prefix.$field.'_flag'); + my $label = $self->getfield($prefix.$field.'_label'); + my $required = $self->getfield($prefix.$field.'_required') ? 'Y' : ''; + if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { - if ( uc($flag) =~ /^([A-Z])$/ ) { - my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse} - || sub { shift }; - $part_svc_column->setfield('columnflag', $1); - $part_svc_column->setfield('columnvalue', - &$parser($self->getfield($svcdb.'__'.$field)) - ); - } + if ( uc($flag) =~ /^([A-Z])$/ ) { + my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse} + || sub { shift }; + $part_svc_column->setfield('columnflag', $1); + $part_svc_column->setfield('columnvalue', + &$parser($self->getfield($prefix.$field)) + ); + } - $part_svc_column->setfield('columnlabel', $label) - if $label !~ /^\s*$/; + $part_svc_column->setfield('columnlabel', $label) + if $label !~ /^\s*$/; + + $part_svc_column->setfield('required', $required); + + if ( $previous ) { + $error = $part_svc_column->replace($previous); + } else { + $error = $part_svc_column->insert; + } - if ( $previous ) { - $error = $part_svc_column->replace($previous); } else { - $error = $part_svc_column->insert; + $error = $previous ? $previous->delete : ''; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - } else { - $error = $previous ? $previous->delete : ''; - } - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; } - } # add export_svc records @@ -277,50 +282,54 @@ sub replace { # maintain part_svc_column records my $svcdb = $new->svcdb; - foreach my $field ( - grep { $_ ne 'svcnum' - && ( defined( $new->getfield($svcdb.'__'.$_.'_flag') ) - || $new->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ ) - } (fields($svcdb),@fields) - ) { - - my $part_svc_column = $new->part_svc_column($field); - my $previous = qsearchs('part_svc_column', { - 'svcpart' => $new->svcpart, - 'columnname' => $field, - } ); - - my $flag = $new->getfield($svcdb.'__'.$field.'_flag'); - my $label = $new->getfield($svcdb.'__'.$field.'_label'); + foreach my $field (fields($svcdb),@fields) { + next if $field eq 'svcnum'; + my $prefix = $svcdb.'__'; + if ( defined( $new->getfield($prefix.$field.'_flag')) + or defined($new->getfield($prefix.$field.'_required')) + or length($new->getfield($prefix.$field.'_label')) + ) { + my $part_svc_column = $new->part_svc_column($field); + my $previous = qsearchs('part_svc_column', { + 'svcpart' => $new->svcpart, + 'columnname' => $field, + } ); + + my $flag = $new->getfield($svcdb.'__'.$field.'_flag'); + my $label = $new->getfield($svcdb.'__'.$field.'_label'); + my $required = $new->getfield($svcdb.'__'.$field.'_required') ? 'Y' : ''; - if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { + if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { + + if ( uc($flag) =~ /^([A-Z])$/ ) { + $part_svc_column->setfield('columnflag', $1); + my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse} + || sub { shift }; + $part_svc_column->setfield('columnvalue', + &$parser($new->getfield($svcdb.'__'.$field)) + ); + } else { + $part_svc_column->setfield('columnflag', ''); + $part_svc_column->setfield('columnvalue', ''); + } - if ( uc($flag) =~ /^([A-Z])$/ ) { - $part_svc_column->setfield('columnflag', $1); - my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse} - || sub { shift }; - $part_svc_column->setfield('columnvalue', - &$parser($new->getfield($svcdb.'__'.$field)) - ); - } else { - $part_svc_column->setfield('columnflag', ''); - $part_svc_column->setfield('columnvalue', ''); - } + $part_svc_column->setfield('columnlabel', $label) + if $label !~ /^\s*$/; - $part_svc_column->setfield('columnlabel', $label) - if $label !~ /^\s*$/; + $part_svc_column->setfield('required', $required); - if ( $previous ) { - $error = $part_svc_column->replace($previous); + if ( $previous ) { + $error = $part_svc_column->replace($previous); + } else { + $error = $part_svc_column->insert; + } } else { - $error = $part_svc_column->insert; + $error = $previous ? $previous->delete : ''; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - } else { - $error = $previous ? $previous->delete : ''; - } - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; } } @@ -594,6 +603,7 @@ sub svc_x { =cut my $svc_defs; +my $svc_info; sub _svc_defs { return $svc_defs if $svc_defs; #cache @@ -648,7 +658,14 @@ sub _svc_defs { sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} } keys %info, ; - + + tie my %svc_info, 'Tie::IxHash', + map { $_ => $info{$_} } + sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} } + keys %info, + ; + + $svc_info = \%svc_info; #access via svc_table_info $svc_defs = \%svc_defs; #cache } @@ -700,6 +717,8 @@ some components specified by "select-.*.html", and a bunch more... =item select_allow_empty - Used with select_table, adds an empty option +=item required - This field should always have a true value (do not use with type checkbox or disabled) + =back =cut @@ -722,6 +741,27 @@ sub svc_table_fields { $def; } +=item svc_table_info TABLE + +Returns table_info for TABLE from cache, or empty +hashref if none is found. + +Caution: caches table_info for ALL services when run; +access a service's table_info directly unless you know +you're loading them all. + +Caution: does not standardize fields into hashrefs; +use L</svc_table_fields> to access fields. + +=cut + +sub svc_table_info { + my $class = shift; + my $table = shift; + $class->_svc_defs; #creates cache if needed + return $svc_info->{$table} || {}; +} + =back =head1 SUBROUTINES @@ -777,7 +817,7 @@ sub process { and ref($param->{ $f }) ) { $param->{ $f } = join(',', @{ $param->{ $f } }); } - ( $f, $f.'_flag', $f.'_label' ); + ( $f, $f.'_flag', $f.'_label', $f.'_required' ); } @fields; diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm index 38ce1fa80..75a2dfb1a 100644 --- a/FS/FS/part_svc_column.pm +++ b/FS/FS/part_svc_column.pm @@ -45,6 +45,8 @@ fields are currently supported: =item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, `A' for automatic selection from inventory, or `H' for selection from a hardware class. For virtual fields, can also be 'X' for excluded. +=item required - column value expected to be true + =back =head1 METHODS @@ -91,6 +93,7 @@ sub check { || $self->ut_alpha('columnname') || $self->ut_textn('columnlabel') || $self->ut_anything('columnvalue') + || $self->ut_flag('required') ; return $error if $error; diff --git a/FS/FS/pay_batch/RBC.pm b/FS/FS/pay_batch/RBC.pm index 45e888dc3..47fc8d49d 100644 --- a/FS/FS/pay_batch/RBC.pm +++ b/FS/FS/pay_batch/RBC.pm @@ -4,6 +4,7 @@ use strict; use vars qw(@ISA %import_info %export_info $name); use Date::Format 'time2str'; use FS::Conf; +use Encode 'encode'; my $conf; my ($client_num, $shortname, $longname, $trans_code, $testmode, $i, $declined, $totaloffset); @@ -173,9 +174,9 @@ $name = 'RBC'; ' '. sprintf("%010.0f",$cust_pay_batch->amount*100). ' '. - time2str("%Y%j", $pay_batch->download). - sprintf("%-30s", $cust_pay_batch->cust_main->first . ' ' . - $cust_pay_batch->cust_main->last). + time2str("%Y%j", time + 86400). + sprintf("%-30.30s", encode('utf8', $cust_pay_batch->cust_main->first . ' ' . + $cust_pay_batch->cust_main->last)). 'E'. # English ' '. sprintf("%-15s", $shortname). diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 61a5f6aa8..d70209aef 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -155,13 +155,48 @@ sub cust_linked { Checks the validity of fields in this record. -At present, this does nothing but call FS::Record::check (which, in turn, -does nothing but run virtual field checks). +Only checks fields marked as required in table_info or +part_svc_column definition. Should be invoked by service-specific +check using SUPER. Invokes FS::Record::check using SUPER. =cut sub check { my $self = shift; + + ## Checking required fields + + # get fields marked as required in table_info + my $required = {}; + my $labels = {}; + my $tinfo = $self->can('table_info') ? $self->table_info : {}; + if ($tinfo->{'manual_require'}) { + my $fields = $tinfo->{'fields'} || {}; + foreach my $field (keys %$fields) { + if (ref($fields->{$field}) && $fields->{$field}->{'required'}) { + $required->{$field} = 1; + $labels->{$field} = $fields->{$field}->{'label'}; + } + } + # add fields marked as required in database + foreach my $column ( + qsearch('part_svc_column',{ + 'svcpart' => $self->svcpart, + 'required' => 'Y' + }) + ) { + $required->{$column->columnname} = 1; + $labels->{$column->columnname} = $column->columnlabel; + } + # do the actual checking + foreach my $field (keys %$required) { + unless (length($self->get($field)) > 0) { + my $name = $labels->{$field} || $field; + return "$name is required\n" + } + } + } + $self->SUPER::check; } diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 93e3f2c6a..9636b3ed1 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -261,6 +261,7 @@ sub table_info { 'display_weight' => 10, 'cancel_weight' => 50, 'ip_field' => 'slipip', + 'manual_require' => 1, 'fields' => { 'dir' => 'Home directory', 'uid' => { @@ -284,6 +285,7 @@ sub table_info { disable_default => 1, disable_fixed => 1, disable_select => 1, + required => 1, }, 'password_selfchange' => { label => 'Password modification', type => 'checkbox', @@ -311,7 +313,9 @@ sub table_info { type => 'text', disable_inventory => 1, }, - '_password' => 'Password', + '_password' => { label => 'Password', + required => 1 + }, 'gid' => { label => 'GID', def_info => 'when blank, defaults to UID', @@ -334,6 +338,7 @@ sub table_info { select_key => 'svcnum', select_label => 'domain', disable_inventory => 1, + required => 1, }, 'pbxsvc' => { label => 'PBX', type => 'select-svc_pbx.html', diff --git a/FS/FS/svc_alarm.pm b/FS/FS/svc_alarm.pm index 0624f18a8..2332f11b6 100644 --- a/FS/FS/svc_alarm.pm +++ b/FS/FS/svc_alarm.pm @@ -214,6 +214,10 @@ sub check { ; return $error if $error; + #really just an signed int, but to discourage storing other data (e.g. phone) + return 'CS Receiver must be 9 digits or less' + if $self->cs_receiver =~ /\d{10}/; + $self->SUPER::check; } diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 40f2c9b2d..2cdc284d7 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -103,6 +103,7 @@ sub table_info { 'display_weight' => 50, 'cancel_weight' => 70, 'ip_field' => 'ip_addr', + 'manual_require' => 1, 'fields' => { 'svcnum' => 'Service', 'description' => 'Descriptive label', diff --git a/FS/FS/svc_dish.pm b/FS/FS/svc_dish.pm index 5c9e21710..2d249d17f 100644 --- a/FS/FS/svc_dish.pm +++ b/FS/FS/svc_dish.pm @@ -63,9 +63,10 @@ sub table_info { 'name' => 'Dish service', 'display_weight' => 58, 'cancel_weight' => 85, + 'manual_require' => 1, 'fields' => { 'svcnum' => { label => 'Service' }, - 'acctnum' => { label => 'DISH account#', %opts }, + 'acctnum' => { label => 'DISH account#', required => 1, %opts }, 'installdate' => { label => 'Install date', %opts }, 'note' => { label => 'Installation notes', %opts }, } diff --git a/FS/FS/svc_hardware.pm b/FS/FS/svc_hardware.pm index d9c3c464e..2be8954c8 100644 --- a/FS/FS/svc_hardware.pm +++ b/FS/FS/svc_hardware.pm @@ -76,6 +76,7 @@ sub table_info { 'name_plural' => 'Hardware', 'display_weight' => 59, 'cancel_weight' => 86, + 'manual_require' => 1, 'fields' => { 'svcnum' => { label => 'Service' }, 'typenum' => { label => 'Device type', @@ -84,6 +85,7 @@ sub table_info { disable_fixed => 1, disable_default => 1, disable_inventory => 1, + required => 1, }, 'serial' => { label => 'Serial number', %opts }, 'hw_addr' => { label => 'Hardware address', %opts }, @@ -287,7 +287,7 @@ install-init: install-apache: [ -e ${APACHE_CONF}/freeside-base.conf ] && rm ${APACHE_CONF}/freeside-base.conf || true [ -d ${APACHE_CONF} ] && \ - ( install -o root -m 755 htetc/freeside-base${APACHE_VERSION}.conf ${APACHE_CONF} && \ + ( install -o root -m 755 htetc/freeside-base2.conf ${APACHE_CONF} && \ ( [ ${RT_ENABLED} -eq 1 ] && install -o root -m 755 htetc/freeside-rt.conf ${APACHE_CONF} || true ) && \ ( [ ${TORRUS_ENABLED} -eq 1 ] && install -o root -m 755 htetc/freeside-torrus.conf ${APACHE_CONF} || true ) && \ perl -p -i -e "\ diff --git a/bin/xmlrpc-customer_login_pay b/bin/xmlrpc-customer_login_pay new file mode 100644 index 000000000..a867a6965 --- /dev/null +++ b/bin/xmlrpc-customer_login_pay @@ -0,0 +1,50 @@ +#!/usr/bin/perl + +use strict; +use Frontier::Client; +use Data::Dumper; + +my( $username, $password ) = ( @ARGV ); + +my $uri = new URI 'http://localhost:8080/'; + +my $server = new Frontier::Client ( 'url' => $uri ); + +my $result = $server->call('FS.ClientAPI_XMLRPC.login', + + 'email' => 'joe@test.com', + 'password' => 'testpass', +); + +die $result->{'error'} if $result->{'error'}; + +my $session_id = $result->{'session_id'}; +warn "$session_id"; + +my $process_payment = $server->call('FS.ClientAPI_XMLRPC.process_payment', + + # Required fields + 'session_id' => $session_id, + 'amount' => '1.00', + 'payname' => 'Joe Tester', + 'payinfo' => '4111111111111111', + 'month' => '1', # CC Expiration month + 'year' => '2019', # CC Expiration year + + #Optional fields (Only needed which these are not set on the customers account or when new information needs to be saved) + 'save' => 1, # Save this information to the customers account + 'auto' => 1, # Set this customers account to "automatic" payment type + 'address1' => '1234 Testerville Rd', + 'address2' => '', + 'city' => 'Testerville', + 'state' => 'MD', + 'zip' => '12345', + 'country' => 'US', + + + +); + +die $process_payment->{'error'} if $process_payment->{'error'}; + +1; diff --git a/debian/control b/debian/control index 06c5bbc30..986178a16 100644 --- a/debian/control +++ b/debian/control @@ -21,7 +21,7 @@ Description: Billing and trouble ticketing for service providers Package: freeside-lib Architecture: all -Depends: gnupg,ghostscript,gsfonts,gzip,latex-xcolor,libbusiness-creditcard-perl,libcache-cache-perl,libcache-simple-timedexpiry-perl,libchart-perl,libclass-container-perl,libclass-data-inheritable-perl,libclass-returnvalue-perl,libcolor-scheme-perl,libcompress-zlib-perl,libconvert-binhex-perl,libcrypt-passwdmd5-perl,libcrypt-ssleay-perl,libcss-squish-perl,libdate-manip-perl,libdbd-mysql-perl,libdbd-pg-perl,libdbi-perl,libdbix-dbschema-perl,libdbix-searchbuilder-perl,libdevel-stacktrace-perl,libdevel-symdump-perl,liberror-perl,libexcel-writer-xlsx-perl,libexception-class-perl,libfile-counterfile-perl,libfile-rsync-perl,libfont-afm-perl,libfreezethaw-perl,libfrontier-rpc-perl,libgd-gd2-perl,libgd-graph-perl,libhtml-format-perl,libhtml-mason-perl,libhtml-parser-perl,libhtml-scrubber-perl,libhtml-tagset-perl,libhtml-tree-perl,libhtml-widgets-selectlayers-perl,libio-stringy-perl,libipc-run-perl,libipc-run3-perl,libipc-sharelite-perl,libjavascript-rpc-perl,libjson-perl,liblingua-en-inflect-perl,liblingua-en-nameparse-perl,liblocale-gettext-perl,liblocale-maketext-fuzzy-perl,liblocale-maketext-lexicon-perl,liblocale-subcountry-perl,liblog-dispatch-perl,libmailtools-perl,libmime-tools-perl,libmodule-versions-report-perl,libnet-daemon-perl,libnet-ping-external-perl,libnet-scp-perl,libnet-ssh-perl,libnet-whois-raw-perl,libnetaddr-ip-perl,libnumber-format-perl,libpam-modules,libpam-runtime,libpaper-utils,libparams-validate-perl,libparse-recdescent-perl,libpg-perl,libregexp-common-perl,libspreadsheet-writeexcel-perl,libstring-approx-perl,libstring-shellquote-perl,libterm-readkey-perl,libtest-inline-perl,libtext-autoformat-perl,libtext-charwidth-perl,libtext-csv-perl,libtext-iconv-perl,libtext-quoted-perl,libtext-reform-perl,libtext-template-perl,libtext-wrapi18n-perl,libtext-wrapper-perl,libtie-ixhash-perl,libtime-duration-perl,libtime-modules-perl,libtimedate-perl,libtree-simple-perl,libuniversal-require-perl,liburi-perl,libwant-perl,libwww-perl,libxml-parser-perl,libyaml-perl,lmodern,locales,lpr,mime-support,ntp,ntpdate,perl,perl-base,perl-modules,postgresql,postgresql-client,psutils,rsync,openssh-client,texlive,texlive-latex-extra,texinfo,ttf-bitstream-vera,ttf-dustin,libdatetime-perl,libdatetime-format-strptime-perl,libfile-slurp-perl,libspreadsheet-parseexcel-perl,libauthen-passphrase-perl,libnet-domain-tld-perl,libbusiness-us-usps-webtools-perl,libxml-simple-perl,libemail-sender-perl,libemail-sender-transport-smtp-tls-perl,libemail-sender-perl,libemail-sender-transport-smtp-tls-perl,libhtml-defang-perl,libdatetime-format-natural-perl,libcgi-pm-perl,libfile-sharedir-perl,libmodule-versions-report-perl,libtext-wikiformat-perl,libnet-server-perl,libhttp-server-simple-perl,libhtml-rewriteattributes-perl,libmime-types-perl,libperlio-eol-perl,libgnupg-interface-perl,libdata-ical-perl,libcalendar-simple-perl,libdatetime-set-perl,libhook-lexwrap-perl,libhttp-server-simple-mason-perl,libxml-rss-perl,libipc-run-safehandles-perl,libpoe-perl,libsoap-lite-perl,libhtml-tableextract-perl,libhtml-element-extended-perl,libcam-pdf-perl,libnet-openssh-perl,libgd-barcode-perl,sam2p,libsys-sigaction-perl,libgeo-googleearth-pluggable-perl,libgeo-coder-googlev3-perl,libnet-snmp-perl,libcrypt-openssl-rsa-perl,libregexp-common-perl,libnet-cidr-perl,libregexp-ipv6-perl,libhtml-quoted-perl,libtext-password-pronounceable-perl,libconvert-color-perl,liburi-perl,libhtml-rewriteattributes-perl,libregexp-common-net-cidr-perl,liblog-dispatch-perl,libdbix-searchbuilder-perl,libencode-perl,libhtml-mason-psgihandler-perl,libdate-simple-perl,libsnmp-perl,libemail-valid-perl,libio-string-perl,libnet-smtp-ssl-perl,libgeo-coder-googlev3-perl,libcam-pdf-perl,libnet-openssh-perl,libhtml-quoted-perl,libregexp-ipv6-perl,libregexp-common-net-cidr-perl,libencode-perl,libexcel-writer-xlsx-perl,libgeo-googleearth-pluggable-perl,libhtml-mason-psgihandler-perl,libdate-simple-perl,libsnmp-perl,libemail-valid-perl,libparse-fixedlength-perl,libregexp-common-net-cidr-perl,libio-string-perl,libnet-mac-vendor-perl +Depends: gnupg,ghostscript,gsfonts,gzip,latex-xcolor,libbusiness-creditcard-perl,libcache-cache-perl,libcache-simple-timedexpiry-perl,libchart-perl,libclass-container-perl,libclass-data-inheritable-perl,libclass-returnvalue-perl,libcolor-scheme-perl,libcompress-zlib-perl,libconvert-binhex-perl,libcrypt-passwdmd5-perl,libcrypt-ssleay-perl,libcss-squish-perl,libdate-manip-perl,libdbd-mysql-perl,libdbd-pg-perl,libdbi-perl,libdbix-dbschema-perl,libdbix-searchbuilder-perl,libdevel-stacktrace-perl,libdevel-symdump-perl,liberror-perl,libexcel-writer-xlsx-perl,libexception-class-perl,libfile-counterfile-perl,libfile-rsync-perl,libfont-afm-perl,libfreezethaw-perl,libfrontier-rpc-perl,libgd-gd2-perl,libgd-graph-perl,libhtml-format-perl,libhtml-mason-perl,libhtml-parser-perl,libhtml-scrubber-perl,libhtml-tagset-perl,libhtml-tree-perl,libhtml-widgets-selectlayers-perl,libio-stringy-perl,libipc-run-perl,libipc-run3-perl,libipc-sharelite-perl,libjavascript-rpc-perl,libjson-perl,liblingua-en-inflect-perl,liblingua-en-nameparse-perl,liblocale-gettext-perl,liblocale-maketext-fuzzy-perl,liblocale-maketext-lexicon-perl,liblocale-subcountry-perl,liblog-dispatch-perl,libmailtools-perl,libmime-tools-perl,libmodule-versions-report-perl,libnet-daemon-perl,libnet-ping-external-perl,libnet-scp-perl,libnet-ssh-perl,libnet-whois-raw-perl,libnetaddr-ip-perl,libnumber-format-perl,libpam-modules,libpam-runtime,libpaper-utils,libparams-validate-perl,libparse-recdescent-perl,libpg-perl,libregexp-common-perl,libspreadsheet-writeexcel-perl,libstring-approx-perl,libstring-shellquote-perl,libterm-readkey-perl,libtest-inline-perl,libtext-autoformat-perl,libtext-charwidth-perl,libtext-csv-perl,libtext-iconv-perl,libtext-quoted-perl,libtext-reform-perl,libtext-template-perl,libtext-wrapi18n-perl,libtext-wrapper-perl,libtie-ixhash-perl,libtime-duration-perl,libtime-modules-perl,libtimedate-perl,libtree-simple-perl,libuniversal-require-perl,liburi-perl,libwant-perl,libwww-perl,libxml-parser-perl,libyaml-perl,lmodern,locales,lpr,mime-support,ntp,ntpdate,perl,perl-base,perl-modules,postgresql,postgresql-client,psutils,rsync,openssh-client,texlive,texlive-latex-extra,texinfo,ttf-bitstream-vera,fonts-dustin,libdatetime-perl,libdatetime-format-strptime-perl,libfile-slurp-perl,libspreadsheet-parseexcel-perl,libauthen-passphrase-perl,libnet-domain-tld-perl,libbusiness-us-usps-webtools-perl,libxml-simple-perl,libemail-sender-perl,libemail-sender-transport-smtp-tls-perl,libemail-sender-perl,libemail-sender-transport-smtp-tls-perl,libhtml-defang-perl,libdatetime-format-natural-perl,libcgi-pm-perl,libfile-sharedir-perl,libmodule-versions-report-perl,libtext-wikiformat-perl,libnet-server-perl,libhttp-server-simple-perl,libhtml-rewriteattributes-perl,libmime-types-perl,libperlio-eol-perl,libgnupg-interface-perl,libdata-ical-perl,libcalendar-simple-perl,libdatetime-set-perl,libhook-lexwrap-perl,libhttp-server-simple-mason-perl,libxml-rss-perl,libipc-run-safehandles-perl,libpoe-perl,libsoap-lite-perl,libhtml-tableextract-perl,libhtml-element-extended-perl,libcam-pdf-perl,libnet-openssh-perl,libgd-barcode-perl,sam2p,libsys-sigaction-perl,libgeo-googleearth-pluggable-perl,libgeo-coder-googlev3-perl,libnet-snmp-perl,libcrypt-openssl-rsa-perl,libregexp-common-perl,libnet-cidr-perl,libregexp-ipv6-perl,libhtml-quoted-perl,libtext-password-pronounceable-perl,libconvert-color-perl,liburi-perl,libhtml-rewriteattributes-perl,libregexp-common-net-cidr-perl,liblog-dispatch-perl,libdbix-searchbuilder-perl,libencode-perl,libhtml-mason-psgihandler-perl,libdate-simple-perl,libsnmp-perl,libemail-valid-perl,libio-string-perl,libnet-smtp-ssl-perl,libgeo-coder-googlev3-perl,libcam-pdf-perl,libnet-openssh-perl,libhtml-quoted-perl,libregexp-ipv6-perl,libregexp-common-net-cidr-perl,libencode-perl,libexcel-writer-xlsx-perl,libgeo-googleearth-pluggable-perl,libhtml-mason-psgihandler-perl,libdate-simple-perl,libsnmp-perl,libemail-valid-perl,libparse-fixedlength-perl,libregexp-common-net-cidr-perl,libio-string-perl,libnet-mac-vendor-perl,libnet-https-any-perl Suggests: libbusiness-onlinepayment-perl Description: Libraries for Freeside billing and trouble ticketing Freeside is a web-based billing and trouble ticketing application. diff --git a/debian/freeside-webui.links b/debian/freeside-webui.links deleted file mode 100644 index be5749a4a..000000000 --- a/debian/freeside-webui.links +++ /dev/null @@ -1,3 +0,0 @@ -etc/freeside/apache2/freeside-base2.conf etc/apache2/conf.d/freeside-base2.conf -etc/freeside/apache2/freeside-rt.conf etc/apache2/conf.d/freeside-rt.conf - diff --git a/debian/rules b/debian/rules index 85f6a6147..e3476d686 100755 --- a/debian/rules +++ b/debian/rules @@ -46,14 +46,13 @@ export DIST_CONF = ${FREESIDE_CONF}/default_conf #export MASON_HANDLER = /usr/share/freeside/handler.pl export MASON_HANDLER=$(TMP)-webui/usr/local/etc/freeside/handler.pl -#export FREESIDE_DOCUMENT_ROOT = /usr/share/freeside/www -export APACHE_DOCUMENT_ROOT := $(shell [ ${APACHE_VERSION} = '2.4' ] && echo '/var/www/html' || echo '/var/www') +export APACHE_VERSION := $(shell /usr/sbin/apache2 -v | grep -q '\/2\.4\.' && echo '2.4' || echo '2') +export APACHE_DOCUMENT_ROOT := $(shell [ ${APACHE_VERSION} = '2.4' ] && echo '/var/www/html' || echo '/var/www') export FREESIDE_DOCUMENT_ROOT = $(TMP)$(APACHE_DOCUMENT_ROOT)/freeside export INIT_FILE = $(TMP).init export INIT_INSTALL = /bin/true export HTTPD_RESTART = /bin/true -#export APACHE_CONF = /etc/apache2/conf.d -export APACHE_CONF = $(TMP)-webui/etc/freeside/apache2 +export APACHE_CONF := $(shell [ ${APACHE_VERSION} = '2.4' ] && echo "$(TMP)-webui/etc/apache2/conf-available" || echo "$(TMP)-webui/etc/apache2/conf.d") export FREESIDE_RESTART = /bin/true #XXX root? @@ -180,7 +179,7 @@ install-stamp: build-stamp perl -p -i -e "\ s'${TMP}(-webui)?''g;\ - " $(TMP)-webui/etc/freeside/apache2/* + " $(APACHE_CONF)/* # Install init $(MAKE) -e DESTDIR=$(TMP) install-init diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index 03a3f04c4..21bc9205a 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -82,6 +82,7 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'provision_phone' => 'MyAccount/provision_phone', 'provision_pbx' => 'MyAccount/provision_pbx', 'provision_external' => 'MyAccount/provision_external', + 'provision_forward' => 'MyAccount/provision_forward', 'unprovision_svc' => 'MyAccount/unprovision_svc', 'myaccount_passwd' => 'MyAccount/myaccount_passwd', 'reset_passwd' => 'MyAccount/reset_passwd', diff --git a/fs_selfservice/FS-SelfService/cgi/invoices.html b/fs_selfservice/FS-SelfService/cgi/invoices.html index 752805123..ffc44ec85 100644 --- a/fs_selfservice/FS-SelfService/cgi/invoices.html +++ b/fs_selfservice/FS-SelfService/cgi/invoices.html @@ -3,24 +3,34 @@ <%= if ( @invoices ) { - $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'. - '<TR><TH BGCOLOR="#ff6666" COLSPAN=4>All Invoices</TH></TR>'; - my $col1 = "ffffff"; - my $col2 = "dddddd"; + my $th = q!<TH STYLE="background: #ff9999; text-align: left; padding: .1em .5em;">!; + my $thr = q!<TH STYLE="background: #ff9999; text-align: right; padding: .1em .5em;">!; + $OUT .= '<TABLE STYLE="border: 0;" CELLSPACING="0">'. + '<TR>'.$th.'Invoice #</TH>'.$th.'Date</TH>'.$thr.'Charges</TH>' + .$th.'Date Paid</TH>'.$thr.'Owed</TH></TR>'; + my $col1 = "#ffffff"; + my $col2 = "#dddddd"; my $col = $col1; foreach my $invoice ( @invoices ) { - my $td = qq!<TD BGCOLOR="#$col">!; + my $td = qq!<TD STYLE="background: $col; padding: .1em .5em;">!; + my $tdr = qq!<TD STYLE="background: $col; padding: .1em .5em; text-align: right;">!; my $a=qq!<A HREF="${url}view_invoice;invnum=!. $invoice->{'invnum'}. '">'; $OUT .= - "<TR>$td${a}Invoice #". $invoice->{'invnum'}. "</A></TD>$td </TD>". - "$td$a". $invoice->{'date'}. "</A></TD>$td</TD>". + "<TR>". + $td . $a . $invoice->{'invnum'}. "</A></TD>" . + $td . $a . $invoice->{'date'} . "</A></TD>" . + $tdr . $a . $money_char . $invoice->{'charged'} . "</A></TD>" . + $td . $a . $invoice->{'lastpay'} . "</A></TD>" . + $tdr . $a . $money_char . $invoice->{'owed'} . "</A></TD>" . '</TR>'; $col = $col eq $col1 ? $col2 : $col1; } + my $tht = '<TH COLSPAN="4" STYLE="background: #ff9999; padding: .1em .5em; text-align: right;">'; + $OUT .= '<TR>'.$tht.'BALANCE DUE</TH>'.$tht.$money_char.$balance.'</TH></TR>'; $OUT .= '</TABLE><BR>'; } else { - $OUT .= 'You have no invoices.<BR><BR>'; + $OUT .= '<P>You have no invoices.</P>'; } %> diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount.html b/fs_selfservice/FS-SelfService/cgi/myaccount.html index 66e2c692e..309021a87 100644 --- a/fs_selfservice/FS-SelfService/cgi/myaccount.html +++ b/fs_selfservice/FS-SelfService/cgi/myaccount.html @@ -28,28 +28,34 @@ Hello <%= $name %>!<BR><BR> } %> <%= if ( @open_invoices ) { - $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'. - '<TR><TH BGCOLOR="#ff6666" COLSPAN=5>Open Invoices</TH></TR>'; - my $link = qq!<A HREF="<%= $url %>myaccount!; - my $col1 = $stripe1_bgcolor || '#ffffff'; - my $col2 = $stripe2_bgcolor || '#dddddd'; + my $th = q!<TH STYLE="background: #ff9999; text-align: left; padding: .1em .5em;">!; + my $thr = q!<TH STYLE="background: #ff9999; text-align: right; padding: .1em .5em;">!; + $OUT .= '<TABLE STYLE="border: 0;" CELLSPACING="0">'. + '<TR><TH BGCOLOR="#ff6666" COLSPAN="4">Open Invoices</TH></TR>'. + '<TR>'.$th.'Invoice #</TH>'.$th.'Date</TH>'.$thr.'Charges</TH>' + .$thr.'Owed</TH></TR>'; + my $col1 = "#ffffff"; + my $col2 = "#dddddd"; my $col = $col1; foreach my $invoice ( @open_invoices ) { - my $td = qq!<TD BGCOLOR="$col">!; + my $td = qq!<TD STYLE="background: $col; padding: .1em .5em;">!; + my $tdr = qq!<TD STYLE="background: $col; padding: .1em .5em; text-align: right;">!; my $a=qq!<A HREF="${url}view_invoice;invnum=!. $invoice->{'invnum'}. '">'; $OUT .= - "<TR>$td${a}Invoice #". $invoice->{'invnum'}. "</A></TD>$td</TD>". - "$td$a". $invoice->{'date'}. "</A></TD>$td</TD>". - qq!<TD BGCOLOR="$col" ALIGN="right">$a\$!. $invoice->{'owed'}. - '</A></TD>'. + "<TR>". + $td . $a . $invoice->{'invnum'}. "</A></TD>" . + $td . $a . $invoice->{'date'} . "</A></TD>" . + $tdr . $a . $money_char . $invoice->{'charged'} . "</A></TD>" . + $tdr . $a . $money_char . $invoice->{'owed'} . "</A></TD>" . '</TR>'; $col = $col eq $col1 ? $col2 : $col1; } $OUT .= '</TABLE><BR>'; } else { - $OUT .= 'You have no outstanding invoices.<BR><BR>'; + $OUT .= '<P>You have no outstanding invoices.</P>'; } + %> <%= diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html index 4802178d0..999c62da0 100644 --- a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html +++ b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html @@ -28,7 +28,7 @@ my %payby_mode; # $payby_mode{FOO} is true if FOO is thirdparty, false if it's B::OP, # nonexistent if it's not supported -if ( $balance > 0 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy +if ( ($balance || 0) > 0 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy if ( exists( $payby_mode{CARD} ) ) { push @menu, { title => 'Recharge my account with a credit card', diff --git a/fs_selfservice/FS-SelfService/cgi/provision_list.html b/fs_selfservice/FS-SelfService/cgi/provision_list.html index b558337de..086c79e57 100644 --- a/fs_selfservice/FS-SelfService/cgi/provision_list.html +++ b/fs_selfservice/FS-SelfService/cgi/provision_list.html @@ -96,7 +96,7 @@ foreach my $pkg ( '('. $part_svc->{'num_avail'}. ' available)'. '</TD></TR>' #self-service only supports these services so far - if grep { $part_svc->{'svcdb'} eq $_ } qw( svc_acct svc_external ); + if grep { $part_svc->{'svcdb'} eq $_ } qw( svc_acct svc_external svc_forward ); $col = $col eq $col1 ? $col2 : $col1; } diff --git a/fs_selfservice/FS-SelfService/cgi/provision_svc_forward.html b/fs_selfservice/FS-SelfService/cgi/provision_svc_forward.html new file mode 100644 index 000000000..470066323 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/provision_svc_forward.html @@ -0,0 +1,29 @@ +<%= include('header', 'Setup forward') %> + +<FORM name="OneTrueForm" action="<%= $selfurl %>" METHOD="POST"> +<INPUT TYPE="hidden" NAME="action" VALUE="process_svc_forward"> + +<%= '<P><B>' . $error . '</B></P>' if $error %> + +<%= +my $out = mason_comp( + 'session_id' => $session_id, + 'comp' => '/edit/elements/svc_forward.html', + 'args' => [ + 'svcnum' => $svcnum, + 'pkgnum' => $pkgnum, + 'svcpart' => $svcpart, + 'srcsvc' => $srcsvc, + 'dstsvc' => $dstsvc, + 'src' => $src, + 'dst' => $dst, + 'email' => \%forward_emails, + ] +); +$out->{'output'} || $out->{'error'}; +%> + +<INPUT TYPE="submit" VALUE="Setup"> +</FORM> + +<%= include('footer') %> diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi index d039cfd4d..1b728e2f9 100755 --- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi +++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi @@ -15,7 +15,7 @@ use FS::SelfService qw( access_info login_info login customer_info edit_info invoice payment_info process_payment realtime_collect process_prepay list_pkgs order_pkg signup_info order_recharge - part_svc_info provision_acct provision_external provision_phone + part_svc_info provision_acct provision_external provision_phone provision_forward unprovision_svc change_pkg suspend_pkg domainselector list_svcs list_svc_usage list_cdr_usage list_support_usage myaccount_passwd list_invoices create_ticket get_ticket did_report @@ -70,6 +70,7 @@ my @actions = ( qw( process_svc_acct process_svc_phone process_svc_external + process_svc_forward delete_svc view_usage view_usage_details @@ -847,7 +848,7 @@ sub provision_svc { my $result = part_svc_info( 'session_id' => $session_id, - map { $_ => $cgi->param($_) } qw( pkgnum svcpart svcnum ), + map { $_ => ($cgi->param($_) || '') } qw( pkgnum svcpart svcnum ), ); die $result->{'error'} if exists $result->{'error'} && $result->{'error'}; @@ -933,6 +934,33 @@ sub process_svc_external { ); } +sub process_svc_forward { + + my $result = provision_forward ( + 'session_id' => $session_id, + map { $_ => $cgi->param($_) || '' } qw( + pkgnum svcpart srcsvc src dstsvc dst ) + ); + + if ( exists $result->{'error'} && $result->{'error'} ) { + #warn "$result $result->{'error'}"; + $action = 'provision_svc_forward'; + return { + $cgi->Vars, + %{ part_svc_info( 'session_id' => $session_id, + map { $_ => $cgi->param($_) } qw( svcnum pkgnum svcpart ) + ) + }, + 'error' => $result->{'error'}, + }; + } else { + #just go to setup services page, results will be visible there + $action = 'provision'; + return provision(); + } + +} + sub delete_svc { unprovision_svc( 'session_id' => $session_id, @@ -1143,7 +1171,7 @@ package FS::SelfService::_selfservicecgi; use HTML::Entities; use FS::SelfService qw( - regionselector popselector domainselector location_form didselector + regionselector popselector domainselector location_form didselector mason_comp ); #false laziness w/agent.cgi diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage_details.html b/fs_selfservice/FS-SelfService/cgi/view_usage_details.html index 0388c2fad..27afe12a7 100644 --- a/fs_selfservice/FS-SelfService/cgi/view_usage_details.html +++ b/fs_selfservice/FS-SelfService/cgi/view_usage_details.html @@ -47,7 +47,9 @@ $OUT .= '</TD><TD ALIGN="right">'; $OUT .= Date::Format::time2str('%T%P %a %b %o %Y', $usage->{'acctstarttime'}); $OUT .= '</TD><TD ALIGN="right">'; - my $duration = $usage->{'acctstoptime'} - $usage->{'acctstarttime'}; + my $duration = ( $usage->{'acctstoptime'} && $usage->{'acctstarttime'} ) + ? ( $usage->{'acctstoptime'} - $usage->{'acctstarttime'} ) + : 0; $total += $duration; my $h = int($duration/3600); my $m = sprintf("%02d", int(($duration % 3600) / 60)); diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index 0d3685355..ec5f321dd 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -61,6 +61,8 @@ function part_export_areyousure(href) { <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Modifier</TH> + <TH CLASS="grid" BGCOLOR="#cccccc" STYLE="font-size: smaller;">Required</TH> + </TR> % my $conf = FS::Conf->new; % foreach my $part_svc ( @part_svc ) { @@ -78,6 +80,9 @@ function part_export_areyousure(href) { % $col->columnflag || ( $col->columnlabel !~ /^\S*$/ % && $col->columnlabel ne $def->{'label'} % ) +% || ( $col->required +% && !$def->{'required'} +% ) % ) % } % @dfields ; @@ -150,7 +155,7 @@ function part_export_areyousure(href) { </TD> % unless ( @fields ) { -% for ( 1..4 ) { +% for ( 1..5 ) { <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD> % } % } @@ -170,7 +175,6 @@ function part_export_areyousure(href) { <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD> <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $label %></TD> <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD> - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> % my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue); % if ( $flag =~ /^[MAH]$/ ) { @@ -189,6 +193,11 @@ function part_export_areyousure(href) { % } </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% if ($part_svc_column->required) { + Yes +% } + </TD> % $n1="</TR><TR>"; % } #foreach $field % if ( $part_svc->restrict_edit_password ) { diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index c80cdae19..c9eceb459 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -395,6 +395,7 @@ Example: % 'value' => $opt{curr_value}, % 'label' => $label, % 'noinit' => $f->{noinit}, +% 'required' => $f->{'required'}, % } % ); % } diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html index 2bb4f5e41..d9500320a 100644 --- a/httemplate/edit/elements/part_svc_column.html +++ b/httemplate/edit/elements/part_svc_column.html @@ -77,6 +77,7 @@ that field. <TH BGCOLOR="#cccccc">Field</TH> <TH BGCOLOR="#cccccc">Label</TH> <TH BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH> + <TH BGCOLOR="#cccccc"><% $manual_require ? 'Required?' : '' %></TH> </TR> % $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now % my $i = 0; @@ -210,9 +211,19 @@ that field. &> % } </TD> + <TD> +% if ($manual_require && +% (!$def->{'type'} || !(grep {$_ eq $def->{'type'}} ('checkbox','disabled'))) +% ) { + <INPUT ID="<% $name.'_required' %>" TYPE="checkbox" NAME="<% $svcdb %>__<% $field %>_required" VALUE="Y" + <% ($part_svc_column->required || $def->{'required'}) ? 'CHECKED' : '' %> + <% $def->{'required'} ? 'DISABLED' : '' %> + > +% } + </TD> </TR> <TR CLASS="row<%$i%>"> - <TD COLSPAN=2 CLASS="def_info"> + <TD COLSPAN=3 CLASS="def_info"> % if ( $def->{def_info} ) { (<% $def->{def_info} %>) </TD> @@ -228,7 +239,7 @@ that field. <TD COLSPAN=3 ALIGN="right"> <% emt('Require "Provision" access right to edit password') %> </TD> - <TD> + <TD COLSPAN=2> <INPUT TYPE="checkbox" NAME="restrict_edit_password" VALUE="Y" \ <% $part_svc->restrict_edit_password ? 'CHECKED' : '' %>> </TD> @@ -244,7 +255,7 @@ that field. <TD COLSPAN=3 ALIGN="right"> <% emt('This service has an attached router') %> </TD> - <TD> + <TD COLSPAN=2> <INPUT TYPE="checkbox" NAME="has_router" VALUE="Y" \ <% $part_svc->has_router ? 'CHECKED' : '' %>> </TD> @@ -301,4 +312,5 @@ if ( $svcdb eq 'svc_acct' } my @defs = map { FS::part_svc->svc_table_fields($svcdb)->{$_} } @fields; +my $manual_require = FS::part_svc->svc_table_info($svcdb)->{'manual_require'}; </%init> diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index 97b630f76..a4e345e40 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -102,6 +102,9 @@ my $columndef = $part_svc->part_svc_column($f->{'field'}); my $flag = $columndef->columnflag; + $f->{'required'} = 1 + if $columndef->required; + if ( $flag eq 'F' ) { #fixed $f->{'value'} = $columndef->columnvalue; if (length($columndef->columnvalue)) { diff --git a/httemplate/edit/elements/svc_forward.html b/httemplate/edit/elements/svc_forward.html new file mode 100644 index 000000000..e40ebe807 --- /dev/null +++ b/httemplate/edit/elements/svc_forward.html @@ -0,0 +1,83 @@ +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $opt{'svcnum'} %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $opt{'pkgnum'} %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $opt{'svcpart'} %>"> + +<SCRIPT TYPE="text/javascript"> +function srcchanged(what) { + if ( what.options[what.selectedIndex].value == 0 ) { + what.form.src.disabled = false; + what.form.src.style.backgroundColor = "white"; + } else { + what.form.src.disabled = true; + what.form.src.style.backgroundColor = "lightgrey"; + } +} +function dstchanged(what) { + if ( what.options[what.selectedIndex].value == 0 ) { + what.form.dst.disabled = false; + what.form.dst.style.backgroundColor = "white"; + } else { + what.form.dst.disabled = true; + what.form.dst.style.backgroundColor = "lightgrey"; + } +} +</SCRIPT> + +<% ntable("#cccccc",2) %> + +<TR> + <TD ALIGN="right">Email to</TD> + <TD> +% if ( $conf->exists('svc_forward-no_srcsvc') ) { + <INPUT NAME="srcsrc" TYPE="hidden" VALUE="0"> +% } else { + <SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)"> +% foreach my $somesvc (keys %email) { + <OPTION VALUE="<% $somesvc %>" + <% $somesvc eq $opt{'srcsvc'} ? 'SELECTED' : '' %> + ><% $email{$somesvc} %></OPTION> +% } + <OPTION VALUE="0" <% $opt{'src'} ? 'SELECTED' : '' %> + >(other email address)</OPTION> + </SELECT> +% } + +% my $src_disabled = $opt{'src'} +% || $conf->exists('svc_forward-no_srcsvc') +% || !scalar(%email); + <INPUT NAME = "src" + TYPE = "text" + VALUE = "<% $opt{'src'} %>" + <% $src_disabled ? '' : 'DISABLED STYLE="background-color: lightgrey"' %> + > + + </TD> +</TR> + +<TR><TD ALIGN="right">Forwards to</TD> +<TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)"> +% foreach my $somesvc (keys %email) { + + <OPTION<% $somesvc eq $opt{'dstsvc'} ? " SELECTED" : "" %> VALUE="<% $somesvc %>"><% $email{$somesvc} %></OPTION> +% } + +<OPTION <% $opt{'dst'} ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION> +</SELECT> +<INPUT TYPE="text" NAME="dst" VALUE="<% $opt{'dst'} %>" <% ( $opt{'dst'} || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>> +</TD></TR> +</TABLE> + +<%init> + +# This is used directly by selfservice, only use what's passed +# Do not look up sensitive information here + +my %opt = @_; + +my $conf = $opt{'conf'}; +$conf ||= new FS::Conf; + +my %email; +%email = %{$opt{'email'}} if ref($opt{'email'}) eq 'HASH'; + +</%init> diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index 47b020c5a..7a47f1550 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -101,6 +101,15 @@ function flag_changed(obj) { } } } + var required = document.getElementById(layer + '__' + field + '_required'); + if (required && !required.disabledinit) { + if (newflag == "F") { + required.checked = false; + required.disabled = true; + } else { + required.disabled = false; + } + } } window.onload = function() { @@ -111,6 +120,17 @@ window.onload = function() { obj.setAttribute('should_be_multiple', true); } } + var inputs = document.getElementsByTagName('INPUT'); + for(i = 0; i < inputs.length; i++) { + var obj = inputs[i]; + if (obj.type == 'checkbox') { + if ( obj.name.match(/_required$/) ) { + if ( obj.disabled ) { + obj.disabledinit = 1; + } + } + } + } for(i = 0; i < selects.length; i++) { var obj = selects[i]; if ( obj.name.match(/_flag$/) ) { diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index 90f48237f..a5e778981 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -30,8 +30,9 @@ <TD BGCOLOR="#eeeeee"><% $part_svc->svc %></TD> </TR> -<TR> - <TD ALIGN="right"><% mt('Username') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('Username'), + 'required' => $part_svc->part_svc_column('username')->required ) %> % if ( $svcnum && $conf->exists('svc_acct-no_edit_username') ) { <TD BGCOLOR="#eeeeee"><% $svc_acct->username() %></TD> <INPUT TYPE="hidden" NAME="username" VALUE="<% $username %>"> @@ -43,9 +44,10 @@ </TR> %if ( $part_svc->part_svc_column('_password')->columnflag ne 'F' ) { -<TR> % #XXX eventually should require "Edit Password" ACL - <TD ALIGN="right"><% mt('Password') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('Password'), + 'required' => $part_svc->part_svc_column('_password')->required ) %> <TD> <INPUT TYPE="text" ID="clear_password" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>> <& /elements/random_pass.html, 'clear_password' &> @@ -60,8 +62,9 @@ %if ( $conf->exists('security_phrase') % && $part_svc->part_svc_column('sec_phrase')->columnflag ne 'F' ) { - <TR> - <TD ALIGN="right"><% mt('Security phrase') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('Security phrase'), + 'required' => $part_svc->part_svc_column('sec_phrase')->required ) %> <TD> <INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $sec_phrase %>" SIZE=32> (<% mt('for forgotten passwords') |h %>) @@ -97,8 +100,9 @@ % ) % ); - <TR> - <TD ALIGN="right"><% mt('Domain') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('Domain'), + 'required' => $part_svc->part_svc_column('domsvc')->required ) %> <TD> <SELECT NAME="domsvc" SIZE=1> % foreach my $svcnum ( @@ -143,8 +147,9 @@ <INPUT TYPE="hidden" NAME="popnum" VALUE="<% $popnum %>"> % } else { - <TR> - <TD ALIGN="right"><% mt('Access number') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('Access number'), + 'required' => $part_svc->part_svc_column('popnum')->required ) %> <TD><% FS::svc_acct_pop::popselector($popnum) %></TD> </TR> % } @@ -156,6 +161,7 @@ 'curr_value' => $svc_acct->sectornum, #'part_svc' => $part_svc, #'cust_pkg' => $cust_pkg, + 'required' => $part_svc->part_svc_column('sectornum')->required, &> %} else { <INPUT TYPE="hidden" NAME="sectornum" VALUE="<% $svc_acct->sectornum %>"> @@ -176,6 +182,9 @@ % % if ( length($svc_acct->$xid()) ) { +<% include('/elements/tr-td-label.html', + 'label' => uc($xid), + 'required' => $part_svc->part_svc_column($xid)->required ) %> <TR> <TD ALIGN="right"><% uc($xid) %></TD> <TD BGCOLOR="#eeeeee"><% $svc_acct->$xid() %></TD> @@ -187,8 +196,9 @@ <INPUT TYPE="hidden" NAME="<% $xid %>" VALUE="<% $svc_acct->$xid() %>"> % } else { - <TR> - <TD ALIGN="right"><% uc($xid) %></TD> +<% include('/elements/tr-td-label.html', + 'label' => uc($xid), + 'required' => $part_svc->part_svc_column($xid)->required ) %> <TD> <INPUT TYPE="text" NAME="<% $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<% $svc_acct->$xid() %>"> </TD> @@ -205,8 +215,9 @@ % } else { - <TR> - <TD ALIGN="right"><% mt('Real Name') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('Real Name'), + 'required' => $part_svc->part_svc_column('finger')->required ) %> <TD> <INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>"> </TD> @@ -223,8 +234,9 @@ % } else { - <TR> - <TD ALIGN="right"><% mt('Home directory') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('Home directory'), + 'required' => $part_svc->part_svc_column('dir')->required ) %> <TD><INPUT TYPE="text" NAME="dir" VALUE="<% $svc_acct->dir %>"></TD> </TR> % } @@ -240,8 +252,9 @@ % } else { - <TR> - <TD ALIGN="right"><% mt('Shell') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('Shell'), + 'required' => $part_svc->part_svc_column('shell')->required ) %> <TD> <SELECT NAME="shell" SIZE=1> % @@ -269,7 +282,9 @@ % # (should we show this if slipip is fixed?) <& /elements/tr-select-router_block_ip.html, 'object' => $svc_acct, - 'ip_field' => 'slipip' + 'ip_field' => 'slipip', + 'required' => $part_svc->part_svc_column('routernum')->required, + 'ip_addr_required' => $part_svc->part_svc_column('slipip')->required, &> % } else { % # don't expose these to the user--they're only useful in the other case @@ -278,8 +293,9 @@ % if ( $part_svc->part_svc_column('slipip')->columnflag =~ /^[FA]$/ ) { <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>"> % } else { - <TR> - <TD ALIGN="right"><% mt('IP') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('IP'), + 'required' => $part_svc->part_svc_column('slipip')->required ) %> <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD> </TR> % } @@ -324,8 +340,9 @@ % } -<TR> - <TD ALIGN="right"><% mt('RADIUS groups') |h %></TD> +<% include('/elements/tr-td-label.html', + 'label' => mt('RADIUS groups'), + 'required' => $part_svc->part_svc_column('usergroup')->required ) %> % if ( $part_svc_usergroup->columnflag eq 'F' ) { <TD BGCOLOR="#eeeeee"><% join('<BR>', @groupnames) %></TD> % } else { diff --git a/httemplate/edit/svc_acct/communigate.html b/httemplate/edit/svc_acct/communigate.html index 6370a54dc..370bfb0e7 100644 --- a/httemplate/edit/svc_acct/communigate.html +++ b/httemplate/edit/svc_acct/communigate.html @@ -47,8 +47,9 @@ <INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>"> % } else { % my $quota_label = $communigate ? 'Mail storage limit' : 'Quota'; - <TR> - <TD ALIGN="right"><% $quota_label %></TD> +<% include('/elements/tr-td-label.html', + 'label' => $quota_label, + 'required' => $part_svc->part_svc_column('quota')->required ) %> <TD><INPUT TYPE="text" NAME="quota" VALUE="<% $svc_acct->quota %>"></TD> </TR> % } diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index 1b85460e6..81c694aa5 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -102,7 +102,16 @@ END my @fields = ( qw( description speed_down speed_up ), { field=>'sectornum', type=>'select-tower_sector', }, - { field=>'routernum', type=>'select-router_block_ip' }, + { field=>'routernum', type=>'select-router_block_ip', + include_opt_callback => sub { + my $svc_broadband = shift; + my $part_svc = $svc_broadband->part_svc; + return () unless $part_svc; #sanity check + my $col = $part_svc->part_svc_column('ip_addr'); + return () unless $col; #sanity check + return ('ip_addr_required' => $col->required); + }, + }, { field=>'mac_addr' , type=>'input-mac_addr' }, qw( latitude longitude altitude diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi index 73f6465b9..de56070ec 100755 --- a/httemplate/edit/svc_forward.cgi +++ b/httemplate/edit/svc_forward.cgi @@ -6,74 +6,19 @@ Service #<% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR> Service: <B><% $part_svc->svc %></B><BR><BR> <FORM ACTION="process/svc_forward.cgi" METHOD="POST"> -<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> -<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> -<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> - -<SCRIPT TYPE="text/javascript"> -function srcchanged(what) { - if ( what.options[what.selectedIndex].value == 0 ) { - what.form.src.disabled = false; - what.form.src.style.backgroundColor = "white"; - } else { - what.form.src.disabled = true; - what.form.src.style.backgroundColor = "lightgrey"; - } -} -function dstchanged(what) { - if ( what.options[what.selectedIndex].value == 0 ) { - what.form.dst.disabled = false; - what.form.dst.style.backgroundColor = "white"; - } else { - what.form.dst.disabled = true; - what.form.dst.style.backgroundColor = "lightgrey"; - } -} -</SCRIPT> - -<% ntable("#cccccc",2) %> - -<TR> - <TD ALIGN="right">Email to</TD> - <TD> -% if ( $conf->exists('svc_forward-no_srcsvc') ) { - <INPUT NAME="srcsrc" TYPE="hidden" VALUE="0"> -% } else { - <SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)"> -% foreach $_ (keys %email) { - <OPTION VALUE="<% $_ %>" - <% $_ eq $srcsvc ? 'SELECTED' : '' %> - ><% $email{$_} %></OPTION> -% } - <OPTION VALUE="0" <% $src ? 'SELECTED' : '' %> - >(other email address)</OPTION> - </SELECT> -% } - -% my $src_disabled = $src -% || $conf->exists('svc_forward-no_srcsvc') -% || !scalar(%email); - <INPUT NAME = "src" - TYPE = "text" - VALUE = "<% $src %>" - <% $src_disabled ? '' : 'DISABLED STYLE="background-color: lightgrey"' %> - > - - </TD> -</TR> - -<TR><TD ALIGN="right">Forwards to</TD> -<TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)"> -% foreach $_ (keys %email) { - - <OPTION<% $_ eq $dstsvc ? " SELECTED" : "" %> VALUE="<% $_ %>"><% $email{$_} %></OPTION> -% } - -<OPTION <% $dst ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION> -</SELECT> -<INPUT TYPE="text" NAME="dst" VALUE="<% $dst %>" <% ( $dst || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>> -</TD></TR> - </TABLE> + +<% include('elements/svc_forward.html', + 'conf' => $conf, + 'svcnum' => $svcnum, + 'pkgnum' => $pkgnum, + 'svcpart' => $svcpart, + 'srcsvc' => $srcsvc, + 'dstsvc' => $dstsvc, + 'src' => $src, + 'dst' => $dst, + 'email' => \%email, + ) %> + <BR><INPUT TYPE="submit" VALUE="Submit"> </FORM> @@ -136,44 +81,23 @@ my $action = $svc_forward->svcnum ? 'Edit' : 'Add'; my %email; -#starting with those currently attached -foreach my $method (qw( srcsvc_acct dstsvc_acct )) { - my $svc_acct = $svc_forward->$method(); - $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct; -} - if ($pkgnum) { #find all possible user svcnums (and emails) - #and including the rest for this customer - my($u_part_svc,@u_acct_svcparts); - foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { - push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); - } - - my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); - my($custnum)=$cust_pkg->getfield('custnum'); - my($i_cust_pkg); - foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { - my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); - my($acct_svcpart); - foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding - #record(s) in cust_svc ( for this - #pkgnum ! ) - foreach my $i_cust_svc ( - qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, - 'svcpart' => $acct_svcpart } ) - ) { - my $svc_acct = - qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } ); - $email{$svc_acct->svcnum} = $svc_acct->email; - } - } - } + my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); + die "Specified package not found" unless $cust_pkg; + %email = $cust_pkg->forward_emails('svc_forward' => $svc_forward); } elsif ( $action eq 'Add' ) { + die "\$action eq Add, but \$pkgnum is null!\n"; + +} else { + + use FS::cust_pkg; + %email = FS::cust_pkg->forward_emails('svc_forward' => $svc_forward); + } my($srcsvc,$dstsvc,$dst)=( diff --git a/httemplate/elements/make_appointment.html b/httemplate/elements/make_appointment.html new file mode 100644 index 000000000..ddf377f54 --- /dev/null +++ b/httemplate/elements/make_appointment.html @@ -0,0 +1,39 @@ +<& /elements/header-popup.html &> + +% #XXX actually, the action needs to repace the top-level window with this +% #$params->{'action'} = $p. 'rt/Search/Schedule.html?'. $query; +<FORM NAME="appointment_form" METHOD="POST" ACTION="<% $p %>elements/schedule-appointment.html"> + +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cgi->param('custnum') |h %>"> + +% my @sched_item = qsearch('sched_item', { 'disabled' => '', }); +% my @username = map $_->access_user->username, @sched_item; +% foreach my $username (@username) { + <INPUT TYPE="hidden" NAME="username" VALUE="<% $username |h %>"> +% } + +Length: +<SELECT NAME="LengthMin"> +% for ( my $hours = .5; $hours < 10.5; $hours += .5 ) { +% my $min = $hours * 60; + <OPTION VALUE="<% $min %>" + <% $min == $default_length ? 'SELECTED' : '' %> + ><% $hours %> hour<% $hours > 1 ? 's' : '' %> +% } +</SELECT> +<BR> +<BR> + +<INPUT TYPE="submit" VALUE="Schedule appointment"> + +</FORM> + +<& /elements/footer-popup.html &> +<%init> + +my $default_length = FS::Conf->new->config('default_appointment_length') || 180; + +#XXX '&pkgnum=$pkgnum";need to be for specific package/location, not just for a customer... default to active(/ordered) package in a pkg_class w/ticketing_queueid, otherwise, a popup? (well, we're already a popup now) we're getting complicated like form-creat_ticket.html + + +</%init> diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 1bab4ef46..34bba7e8c 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -328,13 +328,14 @@ tie my %report_bill_event, 'Tie::IxHash', ; tie my %report_payments, 'Tie::IxHash', - 'Payments' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ], + 'Payments' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ], + 'Payment application detail' => [ $fsurl.'search/report_cust_bill_pay_pkg.html', 'Line item application detail' ], ; $report_payments{'Pending Payments'} = [ $fsurl.'search/cust_pay_pending.html?magic=_date;statusNOT=done', 'Pending real-time payments' ] if $curuser->access_right('View customer pending payments'); +$report_payments{'Unapplied Payments'} = [ $fsurl.'search/report_cust_pay.html?unapplied=1', 'Unapplied payment report (by type and/or date range)' ]; $report_payments{'Voided Payments'} = [ $fsurl.'search/report_cust_pay.html?void=1', 'Voided payment report (by type and/or date range)' ] if $curuser->access_right('View customer pending payments'); -$report_payments{'Unapplied Payments'} = [ $fsurl.'search/report_cust_pay.html?unapplied=1', 'Unapplied payment report (by type and/or date range)' ]; $report_payments{'Payment Batches'} = [ $fsurl.'search/pay_batch.html', 'Payment batches (by status and/or date range)' ] if $conf->exists('batch-enable') || $conf->config('batch-enable_payby'); $report_payments{'Unapplied Payment Aging'} = [ $fsurl.'search/report_unapplied_cust_pay.html', 'Unapplied payment aging report' ]; diff --git a/httemplate/elements/popup_link-make_appointment.html b/httemplate/elements/popup_link-make_appointment.html new file mode 100644 index 000000000..60a337c04 --- /dev/null +++ b/httemplate/elements/popup_link-make_appointment.html @@ -0,0 +1,31 @@ +<%doc> + +Example: + + include('/elements/init_overlib.html') + + include( '/elements/popup_link-make-appointment.html', { #hashref or a list, either way + 'custnum' => 54, + }) + +</%doc> +<& /elements/popup_link.html, $params &>\ +<%init> + +my $params = { 'closetext' => 'Close' }; + +if (ref($_[0]) eq 'HASH') { + $params = { %$params, %{ $_[0] } }; +} else { + $params = { %$params, @_ }; +} + +#$params->{'label'} ||= 'schedule new appointment'; +$params->{'label'} ||= 'Schedule new appointment'; +$params->{'actionlabel'} ||= 'Schedule appointment'; +#$params->{'width'} ||= 350; +#$params->{'height'} ||= 220; + +$params->{'action'} = $p. '/elements/make_appointment.html?custnum='. $params->{'custnum'}; + +</%init> diff --git a/httemplate/elements/schedule-appointment.html b/httemplate/elements/schedule-appointment.html new file mode 100644 index 000000000..45a8a5bab --- /dev/null +++ b/httemplate/elements/schedule-appointment.html @@ -0,0 +1,20 @@ +<& /elements/header-popup.html &> +<h1>Scheduling appointment...</h1> +<SCRIPT TYPE="text/javascript"> + window.top.location.href = '<% $url %>'; +</SCRIPT> +<& /elements/footer-popup.html &> +<%init> + +my @username = map encode_entities($_), $cgi->param('username'); +my $LengthMin = encode_entities( $cgi->param('LengthMin') ); +my $custnum = encode_entities( $cgi->param('custnum') ); + +my $query = join('&', map "username=$_", @username). + "&LengthMin=$LengthMin". + "&custnum=$custnum"; + #XXX '&pkgnum=$pkgnum";need to be for specific package/location, not just for a customer... default to active(/ordered) package in a pkg_class w/ticketing_queueid, otherwise, a popup? we're getting complicated like form-creat_ticket.html + +my $url = $p. 'rt/Search/Schedule.html?'. $query; + +</%init> diff --git a/httemplate/elements/tr-input-date-field.html b/httemplate/elements/tr-input-date-field.html index ff9855184..f2a570b99 100644 --- a/httemplate/elements/tr-input-date-field.html +++ b/httemplate/elements/tr-input-date-field.html @@ -13,6 +13,7 @@ Example: 'usedatetime' => 1, #use DateTime->strftime to format the date # instead of Date::Format->time2str 'noinit' => 1, #first one on the page is enough + 'required' => 1, }, &> @@ -24,8 +25,9 @@ Example: <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT> % } -<TR> - <TD ALIGN="right"><% $label %></TD> +<% include('/elements/tr-td-label.html', + 'label' => $label, + 'required' => $required ) %> <TD COLSPAN=<% $colspan %>> <INPUT TYPE="text" NAME="<% $name %>" ID="<% $name %>_text" VALUE="<% $value %>"> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $name %>_button" STYLE="cursor: pointer" TITLE="<% mt('Select date') |h %>"> @@ -48,7 +50,7 @@ Example: <%init> -my($name, $value, $label, $format, $usedatetime, $noinit, $colspan); +my($name, $value, $label, $format, $usedatetime, $noinit, $colspan, $required); if ( ref($_[0]) ) { my $opt = shift; $name = $opt->{'name'}; @@ -58,6 +60,7 @@ if ( ref($_[0]) ) { $usedatetime = $opt->{'usedatetime'}; $noinit = $opt->{'noinit'}; $colspan = $opt->{'colspan'} || 1; + $required = $opt->{'required'}; } else { ($name, $value, $label, $format, $usedatetime) = @_; $colspan = 1; diff --git a/httemplate/elements/tr-select-hardware_type.html b/httemplate/elements/tr-select-hardware_type.html index c3066417b..b51afc0a5 100644 --- a/httemplate/elements/tr-select-hardware_type.html +++ b/httemplate/elements/tr-select-hardware_type.html @@ -1,5 +1,6 @@ -<TR> - <TD ALIGN="right"><% $opt{'label'} || 'Device type: ' %></TD> +<% include('/elements/tr-td-label.html', + 'label' => $opt{'label'} || 'Device type: ', + 'required' => $opt{'required'} ) %> <TD><% include('select-hardware_type.html', %opt) %></TD> </TR> diff --git a/httemplate/elements/tr-select-router_block_ip.html b/httemplate/elements/tr-select-router_block_ip.html index 11f7c4831..ee135686c 100644 --- a/httemplate/elements/tr-select-router_block_ip.html +++ b/httemplate/elements/tr-select-router_block_ip.html @@ -30,7 +30,7 @@ function clearhint_ip_addr (what) { what.value = ''; } </script> -<& /elements/tr-td-label.html, label => ($opt{'label'} || 'Router') &> +<& /elements/tr-td-label.html, label => ($opt{'label'} || 'Router'), required => $opt{'required'} &> <td> <& /elements/select-tiered.html, prefix => 'router_', tiers => [ { @@ -56,9 +56,9 @@ function clearhint_ip_addr (what) { ] &> </td></tr> -<& /elements/tr-td-label.html, label => 'IP address' &> +<& /elements/tr-td-label.html, label => 'IP address', required => $opt{'ip_addr_required'} &> <td> -% warn Dumper \%fixed; +% #warn Dumper \%fixed; % if ( exists $fixed{$ip_field} ) { <input type="hidden" id="input_ip_addr" name="<% $ip_field %>" value="<% $opt{'ip_addr'} |h%>"><% $opt{'ip_addr'} || '' %> diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index 12fca2086..a48aa0120 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -17,7 +17,11 @@ 'process-display' => scalar($conf->config('manual_process-display')), 'process-skip_first' => $conf->exists('manual_process-skip_first'), 'num_payments' => scalar($cust_main->cust_pay), - 'surcharge_percentage' => scalar($conf->config('credit-card-surcharge-percentage')), + 'surcharge_percentage' => + ( $payby eq 'CARD' + ? scalar($conf->config('credit-card-surcharge-percentage')) + : 0 + ), &> % if ( $conf->exists('part_pkg-term_discounts') ) { diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html index 778799b1f..e57913e5f 100644 --- a/httemplate/search/cdr.html +++ b/httemplate/search/cdr.html @@ -289,11 +289,11 @@ my $qsearch = join(' AND ', @qsearch); $qsearch = ( scalar(keys %$hashref) ? ' AND ' : ' WHERE ' ) . $qsearch if $qsearch; -my $query = { 'table' => 'cdr', - 'hashref' => $hashref, - 'extra_sql' => $qsearch, - 'order_by' => 'ORDER BY calldate', - }; +my $query = { 'table' => 'cdr', + 'hashref' => $hashref, + 'extra_sql' => $qsearch, + 'order_by' => 'ORDER BY calldate', + }; if ( $cgi->param('longest') =~ /^(\d+)$/ && $cgi->param('longest') > 0 ) { $cgi->param('maxrecords',$1); @@ -308,16 +308,26 @@ if ( $cgi->param('longest') =~ /^(\d+)$/ && $cgi->param('longest') > 0 ) { ### my %header = %{ FS::cdr->table_info->{'fields'} }; +$header{ratename} = 'Rate plan'; my @first = qw( acctid calldate clid charged_party src dst dcontext ); my %first = map { $_=>1 } @first; -my @fields = ( @first, grep !$first{$_}, fields('cdr') ); +my @fields = ( @first, + grep !$first{$_}, ( fields('cdr'), 'ratename' ) + ); if ( $cgi->param('show') ) { @fields = grep $cgi->param("show_$_"), @fields; } +if ( grep { $_ eq 'ratename' } @fields ) { + $query->{addl_from} .= ' + LEFT JOIN rate_detail ON (cdr.rated_ratedetailnum = rate_detail.ratedetailnum) + LEFT JOIN rate ON (rate_detail.ratenum = rate.ratenum) + '; +} + my @header = map { if ( exists($header{$_}) ) { $header{$_}; diff --git a/httemplate/search/cust_bill_pay_pkg.html b/httemplate/search/cust_bill_pay_pkg.html new file mode 100644 index 000000000..a6738f32c --- /dev/null +++ b/httemplate/search/cust_bill_pay_pkg.html @@ -0,0 +1,572 @@ +<& elements/search.html, + 'title' => 'Payment application detail', #to line item + 'name_singular' => 'payment application', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', ], + 'header' => [ + #'#', + + 'Amount', + 'By', + '#', + + #payment + 'Date', + 'By', + + #application + 'Applied', + + # line item + 'Description', + 'Location', + @post_desc_header, + + #invoice + 'Invoice', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + #'billpaypkgnum', + sub { sprintf($money_char.'%.2f', shift->paid ) }, + sub { $payby{shift->cust_bill_pay->cust_pay->payby} }, + sub { my $cust_pay = shift->cust_bill_pay->cust_pay; + $cust_pay->payby =~ /^(CARD|CHEK)$/ + ? $cust_pay->paymask : $cust_pay->payinfo; + }, + + sub { time2str('%b %d %Y', shift->get('cust_pay_date') ) }, + sub { shift->cust_bill_pay->cust_pay->otaker }, + + sub { sprintf($money_char.'%.2f', shift->amount ) }, + + sub { $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') # possibly use override.pkg + : $_[0]->get('itemdesc') # but i think this correct + }, + $location_sub, + @post_desc, + 'invnum', + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + ], + 'sort_fields' => [ + 'paid', + '', #payby + 'cust_pay_date', + '', #'otaker', + '', #amount + '', #line item description + '', #location + @post_desc_null, + 'invnum', + '_date', + #cust fields + ], + 'links' => [ + '', + '', + '', + '', + '', + '', + '', + '', + @post_desc_null, + $ilink, + $ilink, + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rcrrlrlllrrcl'. + $post_desc_align. + 'rr'. + FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + '', + '', + '', + '', + @post_desc_null, + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + '', + '', + '', + '', + @post_desc_null, + '', + '', + FS::UI::Web::cust_styles(), + ], + +&> +<%init> + +#(and now we're cust_bill_pay_pkg.html, false laziness w/cust_credit_bill_pkg) +#LOTS of false laziness below w/cust_bill_pkg.cgi +# and a little w/cust_credit.html + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $conf = new FS::Conf; + +my %payby = FS::payby->payby2shortname; + +#here is the agent virtualization +my $agentnums_sql = + $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' ); + +my @where = ( $agentnums_sql ); + +if ( $cgi->param('usernum') =~ /^(\d+)$/ ) { + push @where, "cust_pay.usernum = $1"; +} + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @where, "cust_bill._date >= $beginning", + "cust_bill._date <= $ending"; + +my($cr_begin, $cr_end) = FS::UI::Web::parse_beginning_ending($cgi, 'payment'); +push @where, "cust_pay._date >= $cr_begin", + "cust_pay._date <= $cr_end"; + +#payment amount? seems more what is expected than the applied amount +my @lt_gt = FS::UI::Web::parse_lt_gt($cgi, 'paid' ); +s/paid/cust_pay.paid/g foreach (@lt_gt); +push @where, @lt_gt; + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @where, "cust_main.agentnum = $1"; +} + +if ( $cgi->param('billpkgnum') =~ /^(\d+)$/ ) { + push @where, "billpkgnum = $1"; +} + +#classnum +# not specified: all classes +# 0: empty class +# N: classnum +my $use_override = $cgi->param('use_override'); +if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { + my $comparison = ''; + if ( $1 == 0 ) { + $comparison = "IS NULL"; + } else { + $comparison = "= $1"; + } + + if ( $use_override ) { + push @where, "( + part_pkg.classnum $comparison AND pkgpart_override IS NULL OR + override.classnum $comparison AND pkgpart_override IS NOT NULL + )"; + } else { + push @where, "part_pkg.classnum $comparison"; + } +} + +if ( $cgi->param('taxclass') + && ! $cgi->param('istax') #no part_pkg.taxclass in this case + #(should we save a taxclass or a link to taxnum + # in cust_bill_pkg or something like + # cust_bill_pkg_tax_location?) + ) +{ + + #override taxclass when use_override is specified? probably + #if ( $use_override ) { + # + # push @where, + # ' ( '. join(' OR ', + # map { + # ' ( part_pkg.taxclass = '. dbh->quote($_). + # ' AND pkgpart_override IS NULL '. + # ' OR '. + # ' override.taxclass = '. dbh->quote($_). + # ' AND pkgpart_override IS NOT NULL '. + # ' ) ' + # } + # $cgi->param('taxclass') + # ). + # ' ) '; + # + #} else { + + push @where, + ' ( '. join(' OR ', + map ' part_pkg.taxclass = '.dbh->quote($_), + $cgi->param('taxclass') + ). + ' ) '; + + #} + +} + +my @loc_param = qw( district city county state country ); + +if ( $cgi->param('out') ) { + + my ( $loc_sql, @param ) = FS::cust_pkg->location_sql( 'ornull' => 1 ); + while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution + $loc_sql =~ s/\?/'cust_main_county.'.shift(@param)/e; + } + + $loc_sql =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g + if $cgi->param('istax'); + + push @where, " + 0 = ( + SELECT COUNT(*) FROM cust_main_county + WHERE cust_main_county.tax > 0 + AND $loc_sql + ) + "; + + #not linked to by anything, but useful for debugging "out of taxable region" + if ( grep $cgi->param($_), @loc_param ) { + + my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param; + + my ( $loc_sql, @param ) = FS::cust_pkg->location_sql; + while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution + $loc_sql =~ s/\?/$ph{shift(@param)}/e; + } + + push @where, $loc_sql; + + } + +} elsif ( $cgi->param('country') ) { + + my @counties = $cgi->param('county'); + + if ( scalar(@counties) > 1 ) { + + #hacky, could be more efficient. care if it is ever used for more than the + # tax-report_groups filtering kludge + + my $locs_sql = + ' ( '. join(' OR ', map { + + my %ph = ( 'county' => dbh->quote($_), + map { $_ => dbh->quote( $cgi->param($_) ) } + qw( district city state country ) + ); + + my ( $loc_sql, @param ) = FS::cust_pkg->location_sql; + while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution + $loc_sql =~ s/\?/$ph{shift(@param)}/e; + } + + $loc_sql; + + } @counties + + ). ' ) '; + + push @where, $locs_sql; + + } else { + + my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param; + + my ( $loc_sql, @param ) = FS::cust_pkg->location_sql; + while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution + $loc_sql =~ s/\?/$ph{shift(@param)}/e; + } + + push @where, $loc_sql; + + } + + my($title, $name); + if ( $cgi->param('istax') ) { + $title = 'Tax credits'; + $name = 'tax credits'; + if ( $cgi->param('taxname') ) { + push @where, 'itemdesc = '. dbh->quote( $cgi->param('taxname') ); + #} elsif ( $cgi->param('taxnameNULL') { + } else { + push @where, "( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )"; + } + } elsif ( $cgi->param('nottax') ) { + $title = 'Payment applications to line items'; + $name = 'applications'; + #what can we usefully do with "taxname" ???? look up a class??? + } else { + $title = 'Payment applications to line items'; + $name = 'applications'; + #warn "neither nottax nor istax parameters specified"; + } + + if ( $cgi->param('taxclassNULL') + && ! $cgi->param('istax') #no taxclass in this case + ) + { + + my %hash = ( 'country' => scalar($cgi->param('country')) ); + foreach (qw( state county )) { + $hash{$_} = scalar($cgi->param($_)) if $cgi->param($_); + } + my $cust_main_county = qsearchs('cust_main_county', \%hash); + die "unknown base region for empty taxclass" unless $cust_main_county; + + my $same_sql = $cust_main_county->sql_taxclass_sameregion; + push @where, $same_sql if $same_sql; + + } + +} elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) { + + # this should really be shoved out to FS::cust_pkg->location_sql or something + # along with the code in report_newtax.cgi + + my %pn = ( + 'county' => 'tax_rate_location.county', + 'state' => 'tax_rate_location.state', + 'city' => 'tax_rate_location.city', + 'locationtaxid' => 'cust_bill_pkg_tax_rate_location.locationtaxid', + ); + + my %ph = map { ( $pn{$_} => dbh->quote( $cgi->param($_) || '' ) ) } + qw( city county state locationtaxid ); + + push @where, + join( ' AND ', map { "( $_ = $ph{$_} OR $ph{$_} = '' AND $_ IS NULL)" } + keys %ph + ); + +} + +if ( $cgi->param('itemdesc') ) { + if ( $cgi->param('itemdesc') eq 'Tax' ) { + push @where, "(itemdesc='Tax' OR itemdesc is null)"; + } else { + push @where, 'itemdesc='. dbh->quote($cgi->param('itemdesc')); + } +} + +if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ && $cgi->param('istax') ) { + my ( $group_op, $group_value ) = ( $1, $2 ); + if ( $group_op eq '=' ) { + #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%'); + push @where, 'itemdesc = '. dbh->quote($group_value); + } elsif ( $group_op eq '!=' ) { + push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )'; + } else { + die "guru meditation #00de: group_op $group_op\n"; + } + +} + +push @where, 'cust_bill_pkg.pkgnum != 0' if $cgi->param('nottax'); +push @where, 'cust_bill_pkg.pkgnum = 0' if $cgi->param('istax'); + +if ( $cgi->param('cust_tax') ) { + #false laziness -ish w/report_tax.cgi + my $cust_exempt; + if ( $cgi->param('taxname') ) { + my $q_taxname = dbh->quote($cgi->param('taxname')); + $cust_exempt = + "( tax = 'Y' + OR EXISTS ( SELECT 1 FROM cust_main_exemption + WHERE cust_main_exemption.custnum = cust_main.custnum + AND cust_main_exemption.taxname = $q_taxname ) + ) + "; + } else { + $cust_exempt = " tax = 'Y' "; + } + + push @where, $cust_exempt; +} + +my $count_query = "SELECT COUNT(DISTINCT billpaypkgnum), + SUM(cust_bill_pay_pkg.amount)"; + +my $join_cust = + ' JOIN cust_bill ON ( cust_bill_pkg.invnum = cust_bill.invnum )'. + FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg'); + + +my $join_pkg; + +my $join_cust_bill_pkg = 'LEFT JOIN cust_bill_pkg USING ( billpkgnum )'; + +if ( $cgi->param('nottax') ) { + + $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) + LEFT JOIN part_pkg AS override + ON pkgpart_override = override.pkgpart '; + $join_pkg .= ' LEFT JOIN cust_location USING ( locationnum ) ' + if $conf->exists('tax-pkg_address'); + +} elsif ( $cgi->param('istax') ) { + + #false laziness w/report_tax.cgi $taxfromwhere + if ( scalar( grep( /locationtaxid/, $cgi->param ) ) || + $cgi->param('iscredit') eq 'rate') { + + $join_pkg .= + ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum '; + if ( $cgi->param('iscredit') eq 'rate' ) { + $join_pkg .= ', billpkgtaxratelocationnum )'; + } elsif ( $conf->exists('tax-pkg_address') ) { + $join_pkg .= ', billpkgtaxlocationnum )'; + push @where, "billpkgtaxratelocationnum IS NULL"; + } else { + $join_pkg .= ' )'; + push @where, "billpkgtaxratelocationnum IS NULL"; + } + + $join_pkg .= ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) '; + + } elsif ( $conf->exists('tax-pkg_address') ) { + + $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum ) + LEFT JOIN cust_location USING ( locationnum ) '; + + #quelle kludge, somewhat false laziness w/report_tax.cgi + s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where; + } + +} else { + + #warn "neither nottax nor istax parameters specified"; + #same as before? + $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) '; + +} + +my $where = ' WHERE '. join(' AND ', @where); + +my $join_pay = ' LEFT JOIN cust_bill_pay USING ( billpaynum ) + LEFT JOIN cust_pay USING ( paynum ) '; + +$count_query .= " FROM cust_bill_pay_pkg + $join_cust_bill_pkg + $join_pkg + $join_pay + $join_cust + $where"; + +my @select = ( 'cust_bill_pay_pkg.*', + 'cust_bill_pkg.*', + 'cust_pay.paid', + 'cust_pay.otaker', + 'cust_pay._date AS cust_pay_date', + 'cust_bill._date', + ); +push @select, 'part_pkg.pkg' unless $cgi->param('istax'); +push @select, 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(); + +my @post_desc_header = (); +my @post_desc = (); +my @post_desc_null = (); +my $post_desc_align = ''; +if ( $conf->exists('enable_taxclasses') && ! $cgi->param('istax') ) { + push @post_desc_header, 'Tax class'; + push @post_desc, 'taxclass'; + push @post_desc_null, ''; + $post_desc_align .= 'l'; + push @select, 'part_pkg.taxclass'; # or should this use override? +} + +warn "$join_cust_bill_pkg + $join_pkg + $join_pay + $join_cust"; + +my $query = { + 'table' => 'cust_bill_pay_pkg', + 'addl_from' => "$join_cust_bill_pkg + $join_pkg + $join_pay + $join_cust", + 'hashref' => {}, + 'select' => join(', ', @select ), + 'extra_sql' => $where, + 'order_by' => 'ORDER BY billpaypkgnum', #cust_bill. or cust_pay._date? +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $tax_pkg_address = $conf->exists('tax-pkg_address'); +my $tax_ship_address = $conf->exists('tax-ship_address'); + +my $location_sub = sub { + #my $cust_bill_pay_pkg = shift; + my $self = shift; + my $tax_Xlocation = $self->cust_bill_pkg_tax_Xlocation; + if ( defined($tax_Xlocation) && $tax_Xlocation ) { + + if ( ref($tax_Xlocation) eq 'FS::cust_bill_pkg_tax_location' ) { + + if ( $tax_Xlocation->taxtype eq 'FS::cust_main_county' ) { + my $cust_main_county = $tax_Xlocation->cust_main_county; + if ( $cust_main_county ) { + $cust_main_county->label; + } else { + ''; #cust_main_county record is gone... history? yuck. + } + } else { + '(CCH tax_rate)'; #XXX FS::tax_rate.. vendor taxes not yet handled here + } + + } elsif ( ref($tax_Xlocation) eq 'FS::cust_bill_pkg_tax_rate_location' ) { + '(CCH)'; #XXX vendor taxes not yet handled here + } else { + 'unknown tax_Xlocation '. ref($tax_Xlocation); + } + + } else { + + my $cust_bill_pkg = $self->cust_bill_pkg; + if ( $cust_bill_pkg->pkgnum > 0 ) { + my $cust_pkg = $cust_bill_pkg->cust_pkg; + if ( $tax_pkg_address && (my $cust_location = $cust_pkg->cust_location) ){ + $cust_location->county_state_country; + } else { + my $cust_main = $cust_pkg->cust_main; + if ( $tax_ship_address && $cust_main->has_ship_address ) { + $cust_main->county_state_country('ship_'); + } else { + $cust_main->county_state_country; + } + } + + } else { + #tax? we shouldn't have wound up here then... + ''; #return customer ship or bill address? (depending on tax-ship_address) + } + + } + +}; + +</%init> diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html index 0eefd4ce7..ef5447838 100644 --- a/httemplate/search/report_cdr.html +++ b/httemplate/search/report_cdr.html @@ -188,7 +188,9 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); my @fields = fields('cdr'); +push @fields, 'ratename'; my $labels = FS::cdr->table_info->{'fields'}; +$labels->{ratename} = 'Rate plan'; my $conf = new FS::Conf; my $default_phone_countrycode = diff --git a/httemplate/search/report_cust_bill_pay_pkg.html b/httemplate/search/report_cust_bill_pay_pkg.html new file mode 100644 index 000000000..2347bab6d --- /dev/null +++ b/httemplate/search/report_cust_bill_pay_pkg.html @@ -0,0 +1,88 @@ +<& /elements/header.html, mt('Payment application report') &> + +<FORM ACTION="cust_bill_pay_pkg.html" METHOD="GET"> +<!--<INPUT TYPE="hidden" NAME="magic" VALUE="_date">--> + +<TABLE BGCOLOR="#cccccc" CELLSPACING=0> + +<& /elements/tr-select-user.html, + 'label' => emt('Employee: '), + 'access_user' => $access_user, +&> + +<& /elements/tr-select-agent.html, + curr_value => scalar( $cgi->param('agentnum') ), + #label => emt('Line items for agent: '), + disable_empty => 0, +&> + +<!-- +<& /elements/tr-select-cust_main-status.html, + label => emt('Customer status'), +&> +--> + +<!-- customer +<& /elements/tr-select-cust_class.html, + 'label' => emt('Class'), + 'multiple' => 1, + 'pre_options' => [ '' => emt('(none)') ], + 'all_selected' => 1, +&> +--> + +<!-- some sort of label saying this is the payment date... --> +<& /elements/tr-input-beginning_ending.html, + 'prefix' => 'payment', +&> + +<& /elements/tr-input-lessthan_greaterthan.html, + label => emt('Amount'), + field => 'paid', +&> + +<!-- +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="nottax" VALUE="Y" onClick="nottax_changed(this)" onChange="nottax_change(thid)"></TD> + <TD><% mt('Omit taxes') |h %></TD> +</TD> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="istax" VALUE="Y" onClick="istax_changed(this)" onChange="istax_change(thid)"></TD> + <TD><% mt('Taxes only') |h %></TD> +</TD> + +<SCRIPT TYPE="text/javascript"> + function nottax_changed (what) { + if (what.checked && what.form.istax.checked) { + what.form.istax.checked = false; + } + } + function istax_changed (what) { + if (what.checked && what.form.nottax.checked) { + what.form.nottax.checked = false; + } + } +</SCRIPT> +--> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>"> + +</FORM> + +<& /elements/footer.html &> +<%init> + +#Financial reports? +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $access_user = $FS::CurrentUser::CurrentUser->access_users_hashref('table' => 'cust_pay'); + +my $conf = new FS::Conf; + +</%init> + diff --git a/httemplate/view/Status.html b/httemplate/view/Status.html index f1060874c..eb7df6846 100644 --- a/httemplate/view/Status.html +++ b/httemplate/view/Status.html @@ -93,7 +93,7 @@ if ( $db eq 'PostgreSQL' ) { && FS::Record->scalar_sql('SHOW archive_mode') eq 'on'; my $slave = - FS::Record->scalar_sql('SHOW archive_command') =~ / postgres\@([\w\.\-]): / + FS::Record->scalar_sql('SHOW archive_command') =~ / postgres\@([\w\.\-]+): / ? $1 : ''; $status{'Replication'} = [ diff --git a/httemplate/view/cust_main/appointments.html b/httemplate/view/cust_main/appointments.html index 9bec7b9ae..335bd208b 100644 --- a/httemplate/view/cust_main/appointments.html +++ b/httemplate/view/cust_main/appointments.html @@ -2,16 +2,7 @@ <A NAME="appointments"><FONT CLASS="fsinnerbox-title">Appointments</FONT></A> % if ( $FS::CurrentUser::CurrentUser->access_right('Make appointment') ) { -%# just a link for now, should probably be FS::TicketSystem->href_schedule -%# maybe need to add a dropdown for type (install etc.) from a custom field -%# or maybe change to a popup and get that info there -% my @sched_item = qsearch('sched_item', { 'disabled' => '', }); -% my @username = map $_->access_user->username, @sched_item; -% my $query = join('&', map "username=$_", @username). -% '&LengthMin=180'. #XXX appointment length -% '&custnum='. $cust_main->custnum; #. -% #XXX '&pkgnum=$pkgnum";need to be for specific package/location, not just for a customer... default to active(/ordered) package in a pkg_class w/ticketing_queueid, otherwise, a popup? we're getting complicated like form-creat_ticket.html -<A HREF="<%$p%>rt/Search/Schedule.html?<% $query %>">Make appointment</A> + <& /elements/popup_link-make_appointment.html, custnum=>$cust_main->custnum &> % } %# | diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html index d7644b7de..39055f49a 100644 --- a/httemplate/view/cust_main/packages/services.html +++ b/httemplate/view/cust_main/packages/services.html @@ -10,6 +10,7 @@ function clearhint_search_cust_svc(obj, str) { } </SCRIPT> +% my $showdidlink = browse_received_did_inventory($cust_pkg->custnum); % foreach my $part_svc ( % $cust_pkg->part_svc( % 'summarize_size' => $opt{'cust_pkg-large_pkg_size'}, @@ -97,7 +98,7 @@ function clearhint_search_cust_svc(obj, str) { % local $opt{'num_avail'} = $part_svc->num_avail; <BR><% svc_provision_link($cust_pkg, $part_svc, \%opt, $curuser) %> % } -% if (browse_received_did_inventory($cust_pkg->custnum)) { +% if ($showdidlink) { <BR><A HREF="<%$p%>browse/did_order.html?custnum=<%$cust_pkg->custnum%>"><% mt('Browse Received DID Inventory') |h %></A> % } % } diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index 690f858ad..047abda0b 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -112,7 +112,7 @@ % } % if ( $curuser->access_right('Unsuspend customer package') ) { % if ( $cust_pkg->order_date eq $cust_pkg->get('susp') ) { #on hold - ( <% pkg_link('misc/unsusp_pkg', emt('Start bililng now'), $cust_pkg) %> ) + ( <% pkg_link('misc/unsusp_pkg', emt('Start billing now'), $cust_pkg) %> ) % } else { ( <% pkg_unsuspend_link($cust_pkg) %> ) ( <% pkg_resume_link($cust_pkg) %> ) diff --git a/ng_selfservice/index.php b/ng_selfservice/index.php index 540f1f800..62b6562c8 100644 --- a/ng_selfservice/index.php +++ b/ng_selfservice/index.php @@ -30,7 +30,7 @@ if ( $error ) { <TD> <INPUT TYPE="text" NAME="username" VALUE="<? echo htmlspecialchars($username); ?>"><? if ( $single_domain ) { echo '@'.$single_domain; } ?> </TD> -< /TR> + </TR> <INPUT TYPE="hidden" NAME="domain" VALUE="<? echo $single_domain ?>"> diff --git a/ng_selfservice/prepaid.php b/ng_selfservice/prepaid.php new file mode 100644 index 000000000..bf941f876 --- /dev/null +++ b/ng_selfservice/prepaid.php @@ -0,0 +1,202 @@ +<? $title ='Prepaid Card Account Recharge'; include('elements/header.php'); ?> +<? $current_menu = 'prepaid.php'; include('elements/menu.php'); ?> + +<? +// This page is currently only designed for packages that use prepaid pricing. +// Usage limits should be in seconds, and you currently cannot mix packages that have +// usage limits with packages that don't. Payments must be made by prepaid card. +// The account service must be flagged as primary service of package. +// +// You can't change packages if you have a positive balance, but you CAN use +// this form to only change package or only use a prepaid card--doing both isn't +// required. + +$prepaid_cardnum = isset($_POST['prepaid_cardnum']) ? $_POST['prepaid_cardnum'] : ''; +$pkgpart = isset($_POST['pkgpart']) ? $_POST['pkgpart'] : ''; +$pkgnum = isset($_POST['pkgnum']) ? $_POST['pkgnum'] : ''; +$success = ''; +$error = ''; + +if ($pkgnum || $pkgpart) { + if ($pkgnum && $pkgpart) { + $change_results = $freeside->change_pkg(array( + 'session_id' => $_COOKIE['session_id'], + 'pkgpart' => $pkgpart, + 'pkgnum' => $pkgnum, + )); + if ( isset($change_results['error']) && $change_results['error'] ) { + $error = $change_results['error']; + } else { + $success .= ' Package applied to your account.'; + $pkgnum = ''; + $pkgpart = ''; + } + } else if ($pkgnum) { + $error = 'No account selected'; + } else if ($pkgpart) { + $error = 'No package selected'; + } +} + +if ($prepaid_cardnum) { + $payment_results = $freeside->process_prepay(array( + 'session_id' => $_COOKIE['session_id'], + 'prepaid_cardnum' => $prepaid_cardnum, + )); + if ( isset($payment_results['error']) && $payment_results['error'] ) { + $error = $payment_results['error']; + } else { + $success .= ' Prepaid card applied to your account.'; + $prepaid_cardnum = ''; + } +} + +$customer_info = $freeside->customer_info_short( array( + 'session_id' => $_COOKIE['session_id'], +) ); +if ( isset($customer_info['error']) && $customer_info['error'] ) { + $error = $customer_info['error']; +} + +$signup_info = $freeside->signup_info( array('customer_session_id' => $_COOKIE['session_id'], 'keys' => ['part_pkg']) ); +if (isset($signup_info['error']) && $signup_info['error']) { + $error = $signup_info['error']; +} + +$list_pkgs = $freeside->list_pkgs( array( + 'session_id' => $_COOKIE['session_id'], +) ); +if ( isset($list_pkgs['error']) && $list_pkgs['error'] ) { + $error = $list_pkgs['error']; +} + +extract($customer_info); +extract($signup_info); +extract($list_pkgs); + +$actsvcs = array(); +$expsvcs = array(); +foreach ($cust_pkg as $pkg) { + $thissvc = array(); + $thissvc['svcnum'] = $pkg['primary_cust_svc']['svcnum']; + $thissvc['overlimit'] = $pkg['primary_cust_svc']['overlimit']; + $thissvc['label'] = $pkg['primary_cust_svc']['label'][1]; + $thissvc['pkgnum'] = $pkg['pkgnum']; + $thissvc['status'] = $pkg['status']; + $actsvcs[$thissvc['svcnum']] = $thissvc; + if ($thissvc['overlimit'] or ($thissvc['status'] != 'active')) { + $expsvcs[$thissvc['svcnum']] = $thissvc; + } +} + +if (count($actsvcs) > 0) { + $list_svcs = $freeside->list_svcs( array( + 'session_id' => $_COOKIE['session_id'], + ) ); + if ( isset($list_svcs['error']) && $list_svcs['error'] ) { + $error = $list_svcs['error']; + } + extract($list_svcs); + foreach ($svcs as $svc) { + if (isset($actsvcs[$svc['svcnum']])) { + $actsvcs[$svc['svcnum']]['seconds'] = strlen($svc['seconds']) ? $svc['seconds'] : 'Unlimited'; + } + } +} + +if ($success) { + echo '<P><B>' . $success . '</B></P>'; +} +include('elements/error.php'); + +if (count($actsvcs) > 0) { +?> +<TABLE STYLE="text-align: left;"> +<TR><TH>Account</TH><TH STYLE="text-align: right;">Seconds Remaining</TH></TR> +<? + foreach ($actsvcs as $svc) { + if ($svc['status'] == 'active') { + $slabel = $svc['seconds']; + } else { + $slabel = '<I>' . ucfirst($svc['status']) . '</I>'; + } +?> +<TR> +<TD><? echo $svc['label'] ?></TD> +<TD STYLE="text-align: right;"><? echo $slabel ?></TD> +</TR> +<? + } +?> +</TABLE> +<? +} +if ($balance != 0) { + $blabel = ($balance < 0) ? 'Credit' : 'Balance'; +?> + +<P><B><? echo $blabel ?>:</B> <? echo $money_char . abs($balance) ?></P> + +<? +} +?> + +<FORM NAME="OneTrueForm" METHOD="POST" ACTION="prepaid.php" onSubmit="document.OneTrueForm.process.disabled=true"> + +<? +if ($balance <= 0) { + if (count($expsvcs) > 0) { +?> + +<P> +<B>Select an account to recharge:</B><BR> +<SELECT NAME="pkgnum"> +<OPTION VALUE=""></OPTION> +<? foreach ($expsvcs as $svc) { ?> +<OPTION VALUE="<? echo $svc['pkgnum'] ?>"<? echo $pkgnum == $svc['pkgnum'] ? ' CHECKED' : '' ?>> +<? echo $svc['label'] ?> +</OPTION> +<? } ?> +</SELECT> +</P> + +<P> +<B>Select a package to add to account</B><BR> +<SELECT NAME="pkgpart"> +<OPTION VALUE=""></OPTION> +<? foreach ($part_pkg as $pkg) { ?> +<OPTION VALUE="<? echo $pkg['pkgpart'] ?>"<? echo $pkgpart == $pkg['pkgpart'] ? ' CHECKED' : '' ?>> +<? echo $pkg['pkg'] . ' - ' . $money_char . $pkg['options']['recur_fee'] ?> +</OPTION> +<? } ?> +</SELECT> +</P> + +<? + } else { +?> + +<P>You have no services to recharge at this time.</P> + +<? + } +} +if (($balance > 0) or (count($expsvcs) > 0)) { +?> + +<P> +<B>Enter prepaid card number:</B><BR> +<INPUT TYPE="text" NAME="prepaid_cardnum" VALUE="<? echo $prepaid_cardnum ?>"> +</P> + +<INPUT TYPE="submit" NAME="submit" VALUE="Submit"> + +<? +} +?> + +</FORM> + +<? include('elements/menu_footer.php'); ?> +<? include('elements/footer.php'); ?> + diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm deleted file mode 100644 index 2f46d4886..000000000 --- a/rt/lib/RT/Generated.pm +++ /dev/null @@ -1,81 +0,0 @@ -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC -# <sales@bestpractical.com> -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} - -package RT; -use warnings; -use strict; - -our $VERSION = '4.0.22'; - - - -$BasePath = '/opt/rt3'; -$EtcPath = '/opt/rt3/etc'; -$BinPath = '/opt/rt3/bin'; -$SbinPath = '/opt/rt3/sbin'; -$VarPath = '/opt/rt3/var'; -$LexiconPath = '/opt/rt3/share/po'; -$PluginPath = '/opt/rt3/plugins'; -$LocalPath = '/opt/rt3/local'; -$LocalEtcPath = '/opt/rt3/local/etc'; -$LocalLibPath = '/opt/rt3/local/lib'; -$LocalLexiconPath = '/opt/rt3/local/po'; -$LocalPluginPath = '/opt/rt3/local/plugins'; -# $MasonComponentRoot is where your rt instance keeps its mason html files -$MasonComponentRoot = '/var/www/freeside/rt'; -# $MasonLocalComponentRoot is where your rt instance keeps its site-local -# mason html files. -$MasonLocalComponentRoot = '/opt/rt3/local/html'; -# $MasonDataDir Where mason keeps its datafiles -$MasonDataDir = '/usr/local/etc/freeside/masondata'; -# RT needs to put session data (for preserving state between connections -# via the web interface) -$MasonSessionDir = '/opt/rt3/var/session_data'; - - -1; |