diff options
author | Mark Wells <mark@freeside.biz> | 2012-04-14 15:36:20 -0700 |
---|---|---|
committer | Mark Wells <mark@freeside.biz> | 2012-04-14 15:36:20 -0700 |
commit | 5371ad9dd6bf63e29ffc85c8381f1ea2630b1749 (patch) | |
tree | 0109d86cf93a84697fe84fc29927a9f058cf3d93 | |
parent | 5abe1300f16cf01a6311203deee29373162491c7 (diff) | |
parent | 401ba3ab0637de1bc460370949b75dca092375d8 (diff) |
merge master
62 files changed, 1373 insertions, 450 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 1bfae03ad..d2417f069 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -253,9 +253,11 @@ tie my %rights, 'Tie::IxHash', ### 'Reporting/listing rights' => [ 'List customers', + 'List all customers', 'List zip codes', #NEW 'List invoices', 'List packages', + 'Summarize packages', 'List services', 'List service passwords', @@ -266,6 +268,8 @@ tie my %rights, 'Tie::IxHash', { rightname=> 'List inventory', global=>1 }, { rightname=>'View email logs', global=>1 }, + 'Download report data', + #{ rightname => 'List customers of all agents', global=>1 }, ], @@ -361,6 +365,7 @@ sub default_superuser_rights { 'Delete payment', 'Delete credit', #? 'Delete refund', #? + 'Edit customer package dates', 'Time queue', 'Redownload resolved batches', 'Raw SQL', diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index acd0c6e85..7bc3011d2 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -151,12 +151,25 @@ sub login_info { %{ skin_info($p) }, 'phone_login' => $conf->exists('selfservice_server-phone_login'), 'single_domain'=> scalar($conf->config('selfservice_server-single_domain')), + 'banner_url' => scalar($conf->config('selfservice-login_banner_url')), + 'banner_image_md5' => + md5_hex($conf->config_binary('selfservice-login_banner_image')), ); return \%info; } +sub login_banner_image { + my $p = shift; + my $conf = new FS::Conf; + my $image = $conf->config_binary('selfservice-login_banner_image'); + return { + 'md5' => md5_hex($image), + 'image' => $image, + }; +} + #false laziness w/FS::ClientAPI::passwd::passwd sub login { my $p = shift; diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index 1e068f428..98e1910c3 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -37,17 +37,21 @@ $DEBUG = 0; $FS::ClientAPI::DEBUG = $DEBUG; #false laziness w/FS::SelfService/XMLRPC.pm, same problem as below but worse +our %typefix_skin_info = ( + 'logo' => 'base64', + 'title_left_image' => 'base64', + 'title_right_image' => 'base64', + 'menu_top_image' => 'base64', + 'menu_body_image' => 'base64', + 'menu_bottom_image' => 'base64', +); our %typefix = ( 'invoice_pdf' => { 'invoice_pdf' => 'base64', }, 'legacy_invoice_pdf' => { 'invoice_pdf' => 'base64', }, - 'skin_info' => { 'logo' => 'base64', - 'title_left_image' => 'base64', - 'title_right_image' => 'base64', - 'menu_top_image' => 'base64', - 'menu_body_image' => 'base64', - 'menu_bottom_image' => 'base64', - }, + 'skin_info' => \%typefix_skin_info, + 'login_info' => \%typefix_skin_info, 'invoice_logo' => { 'logo' => 'base64', }, + 'login_banner_image' => { 'image' => 'base64', }, ); sub AUTOLOAD { @@ -94,6 +98,7 @@ sub ss2clientapi { 'chfn' => 'passwd/passwd', 'chsh' => 'passwd/passwd', 'login_info' => 'MyAccount/login_info', + 'login_banner_image' => 'MyAccount/login_banner_image', 'login' => 'MyAccount/login', 'logout' => 'MyAccount/logout', 'switch_acct' => 'MyAccount/switch_acct', diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 04ca35ac2..63fc8869c 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1594,6 +1594,13 @@ and customer address. Include units.', }, { + 'key' => 'disable_maxselect', + 'section' => 'UI', + 'description' => 'Prevent changing the number of records per page.', + 'type' => 'checkbox', + }, + + { 'key' => 'session-start', 'section' => 'session', 'description' => 'If defined, the command which is executed on the Freeside machine when a session begins. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.', @@ -2850,6 +2857,14 @@ and customer address. Include units.', }, { + 'key' => 'company_url', + 'section' => 'UI', + 'description' => 'Your company URL', + 'type' => 'text', + 'per_agent' => 1, + }, + + { 'key' => 'company_address', 'section' => 'required', 'description' => 'Your company address', @@ -3587,9 +3602,19 @@ and customer address. Include units.', 'section' => 'billing', 'description' => 'Display format for line item date ranges on invoice line items.', 'type' => 'select', - 'select_hash' => [ '' => 'STARTDATE-ENDDATE', - 'month_of' => 'Month of MONTHNAME', + 'select_hash' => [ '' => 'STARTDATE-ENDDATE', + 'month_of' => 'Month of MONTHNAME', + 'X_month' => 'DATE_DESC MONTHNAME', ], + 'per_agent' => 1, + }, + + { + 'key' => 'cust_bill-line_item-date_description', + 'section' => 'billing', + 'description' => 'Text to display for "DATE_DESC" when using cust_bill-line_item-date_style DATE_DESC MONTHNAME.', + 'type' => 'text', + 'per_agent' => 1, }, { @@ -3896,7 +3921,17 @@ and customer address. Include units.', 'section' => 'UI', 'description' => 'Prefix the customer number with this string for display purposes.', 'type' => 'text', - #and then probably agent-virt this to merge these instances + 'per_agent' => 1, + }, + + { + 'key' => 'cust_main-custnum-display_special', + 'section' => 'UI', + 'description' => 'Use this customer number prefix format', + 'type' => 'select', + 'select_hash' => [ '' => '', + 'CoStAg' => 'CoStAg (country, state, agent name or display_prefix)', + 'CoStCl' => 'CoStCl (country, state, class name)' ], }, { @@ -4164,6 +4199,20 @@ and customer address. Include units.', }, { + 'key' => 'selfservice-login_banner_image', + 'section' => 'self-service', + 'description' => 'Banner image shown on the login page, in PNG format.', + 'type' => 'image', + }, + + { + 'key' => 'selfservice-login_banner_url', + 'section' => 'self-service', + 'description' => 'Link for the login banner.', + 'type' => 'text', + }, + + { 'key' => 'selfservice-bulk_format', 'section' => 'deprecated', 'description' => 'Parameter arrangement for selfservice bulk features', @@ -4885,7 +4934,14 @@ and customer address. Include units.', }, }, - + { + 'key' => 'brand-agent', + 'section' => 'UI', + 'description' => 'Brand the backoffice interface (currently Help->About) using the company_name, company_url and logo.png configuration settings of the selected agent. Typically used when selling or bundling hosted access to the backoffice interface. NOTE: The AGPL software license has specific requirements for source code availability in this situation.', + 'type' => 'select-agent', + }, + + { 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/GeocodeCache.pm b/FS/FS/GeocodeCache.pm new file mode 100644 index 000000000..7829c4df2 --- /dev/null +++ b/FS/FS/GeocodeCache.pm @@ -0,0 +1,209 @@ +package FS::GeocodeCache; + +use strict; +use vars qw($conf $DEBUG); +use base qw( FS::geocode_Mixin ); +use FS::Record qw( qsearch qsearchs ); +use FS::Conf; +use FS::Misc::Geo; + +use Data::Dumper; + +FS::UID->install_callback( sub { $conf = new FS::Conf; } ); + +$DEBUG = 0; + +=head1 NAME + +FS::GeocodeCache - An address undergoing the geocode process. + +=head1 SYNOPSIS + + use FS::GeocodeCache; + + $record = FS::GeocodeCache->standardize(%location_hash); + +=head1 DESCRIPTION + +An FS::GeocodeCache object represents a street address in the process of +being geocoded. FS::GeocodeCache inherits from FS::geocode_Mixin. + +Most methods on this object throw an exception on error. + +FS::GeocodeCache has the following fields, with the same meaning as in +L<FS::cust_location>: + +=over 4 + +=item address1 + +=item address2 + +=item city + +=item county + +=item state + +=item zip + +=item latitude + +=item longitude + +=item addr_clean + +=item country + +=item censustract + +=item geocode + +=item district + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new cache object. For internal use. See C<standardize>. + +=cut + +# minimalist constructor +sub new { + my $class = shift; + my $self = { + company => '', + address1 => '', + address2 => '', + city => '', + state => '', + zip => '', + country => '', + latitude => '', + longitude => '', + addr_clean => '', + censustract => '', + @_ + }; + bless $self, $class; +} + +# minimalist accessor, for compatibility with geocode_Mixin +sub get { + $_[0]->{$_[1]} +} + +sub set { + $_[0]->{$_[1]} = $_[2]; +} + +sub location_hash { %{$_[0]} }; + +=item set_censustract + +Look up the censustract, if it's not already filled in, and return it. +On error, sets 'error' and returns nothing. + +This uses the "get_censustract_*" methods in L<FS::Misc::Geo>; currently +the only one is 'ffiec'. + +=cut + +sub set_censustract { + my $self = shift; + + if ( $self->get('censustract') =~ /^\d{9}\.\d{2}$/ ) { + return $self->get('censustract'); + } + my $censusyear = $conf->config('census_year'); + return if !$censusyear; + + my $method = 'ffiec'; + # configurable censustract-only lookup goes here if it's ever needed. + $method = "get_censustract_$method"; + my $censustract = eval { FS::Misc::Geo->$method($self, $censusyear) }; + $self->set("censustract_error", $@); + $self->set("censustract", $censustract); +} + +=item set_coord + +Set the latitude and longitude fields if they're not already set. Returns +those values, in order. + +=cut + +sub set_coord { # the one in geocode_Mixin will suffice + my $self = shift; + if ( !$self->get('latitude') || !$self->get('longitude') ) { + $self->SUPER::set_coord; + $self->set('coord_error', $@); + } + return $self->get('latitude'), $self->get('longitude'); +} + +=head1 CLASS METHODS + +=over 4 + +=item standardize LOCATION + +Given a location hash or L<FS::geocode_Mixin> object, standardize the +address using the configured method and return an L<FS::GeocodeCache> +object. + +The methods are the "standardize_*" functions in L<FS::Geo::Misc>. + +=cut + +sub standardize { + my $class = shift; + my $location = shift; + $location = { $location->location_hash } + if UNIVERSAL::can($location, 'location_hash'); + + local $Data::Dumper::Terse = 1; + warn "standardizing location:\n".Dumper($location) if $DEBUG; + + my $method = $conf->config('address_standardize_method'); + + if ( $method ) { + $method = "standardize_$method"; + my $new_location = eval { FS::Misc::Geo->$method( $location ) }; + if ( $new_location ) { + $location = { + addr_clean => 'Y', + %$new_location + # standardize_* can return an address with addr_clean => '' if + # the address is somehow questionable + } + } + else { + # XXX need an option to decide what to do on error + $location->{'addr_clean'} = ''; + $location->{'error'} = $@; + } + warn "result:\n".Dumper($location) if $DEBUG; + } + # else $location = $location + my $cache = $class->new(%$location); + return $cache; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm index 3942543b5..b0e911f84 100644 --- a/FS/FS/Report/Table.pm +++ b/FS/FS/Report/Table.pm @@ -32,21 +32,32 @@ options in %opt. =over 4 -=item signups: The number of customers signed up. +=item signups: The number of customers signed up. Options are "refnum" +(limit by advertising source) and "indirect" (boolean, tells us to limit +to customers that have a referral_custnum that matches the advertising source). =cut sub signups { my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_; - my @where = ( - $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, 'signupdate') + my @where = ( $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, + 'cust_main.signupdate') ); - if ( $opt{'refnum'} ) { + my $join = ''; + if ( $opt{'indirect'} ) { + $join = " JOIN cust_main AS referring_cust_main". + " ON (cust_main.referral_custnum = referring_cust_main.custnum)"; + + if ( $opt{'refnum'} ) { + push @where, "referring_cust_main.refnum = ".$opt{'refnum'}; + } + } + elsif ( $opt{'refnum'} ) { push @where, "refnum = ".$opt{'refnum'}; } $self->scalar_sql( - "SELECT COUNT(*) FROM cust_main WHERE ".join(' AND ', @where) + "SELECT COUNT(*) FROM cust_main $join WHERE ".join(' AND ', @where) ); } diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm index 41216992f..87c13a8ca 100644 --- a/FS/FS/Report/Table/Monthly.pm +++ b/FS/FS/Report/Table/Monthly.pm @@ -49,9 +49,8 @@ sub data { my $syear = $self->{'start_year'}; my $emonth = $self->{'end_month'}; my $eyear = $self->{'end_year'}; - # how far to extrapolate into the future - my $pmonth = $self->{'project_month'}; - my $pyear = $self->{'project_year'}; + # whether to extrapolate into the future + my $projecting = $self->{'projection'}; # sanity checks if ( $eyear < $syear or @@ -61,17 +60,14 @@ sub data { my $agentnum = $self->{'agentnum'}; - if ( $pyear > $eyear or - ($pyear == $eyear and $pmonth > $emonth) ) { + if ( $projecting ) { - # create the entire projection set first to avoid timing problems + $self->init_projection; - $self->init_projection if $pmonth; - - my $thisyear = $eyear; - my $thismonth = $emonth; - while ( $thisyear < $pyear || - ( $thisyear == $pyear and $thismonth <= $pmonth ) + my $thismonth = $smonth; + my $thisyear = $syear; + while ( $thisyear < $eyear || + ( $thisyear == $eyear and $thismonth <= $emonth ) ) { my $speriod = timelocal(0,0,0,1,$thismonth-1,$thisyear); $thismonth++; @@ -84,10 +80,8 @@ sub data { my %data; - my $max_year = $pyear || $eyear; - my $max_month = $pmonth || $emonth; - - my $projecting = 0; # are we currently projecting? + my $max_year = $eyear; + my $max_month = $emonth; while ( $syear < $max_year || ( $syear == $max_year && $smonth < $max_month+1 ) ) { @@ -101,11 +95,6 @@ sub data { push @{$data{label}}, "$smonth/$syear"; } - if ( $syear > $eyear || ( $syear == $eyear && $smonth >= $emonth + 1 ) ) { - # start getting data from the projection - $projecting = 1; - } - my $speriod = timelocal(0,0,0,1,$smonth-1,$syear); push @{$data{speriod}}, $speriod; if ( ++$smonth == 13 ) { $syear++; $smonth=1; } diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 2deb88441..e69b0bc2c 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3629,6 +3629,19 @@ sub tables_hashref { 'index' => [], }, + 'upgrade_journal' => { + 'columns' => [ + 'upgradenum', 'serial', '', '', '', '', + '_date', 'int', '', '', '', '', + 'upgrade', 'varchar', '', $char_d, '', '', + 'status', 'varchar', '', $char_d, '', '', + 'statustext', 'varchar', 'NULL', $char_d, '', '', + ], + 'primary_key' => 'upgradenum', + 'unique' => [ [ 'upgradenum' ] ], + 'index' => [ [ 'upgrade' ] ], + }, + %{ tables_hashref_torrus() }, # tables of ours for doing torrus virtual port combining diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm index 8f66c66b5..aabc4e72f 100644 --- a/FS/FS/Upgrade.pm +++ b/FS/FS/Upgrade.pm @@ -7,6 +7,7 @@ use Tie::IxHash; use FS::UID qw( dbh driver_name ); use FS::Conf; use FS::Record qw(qsearchs qsearch str2time_sql); +use FS::upgrade_journal; use FS::svc_domain; $FS::svc_domain::whois_hack = 1; diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm index ef8cc6cd8..341055bfc 100644 --- a/FS/FS/access_right.pm +++ b/FS/FS/access_right.pm @@ -180,6 +180,51 @@ sub _upgrade_data { # class method } + my @all_groups = qsearch('access_group', {}); + + my %onetime = ( + 'List customers' => 'List all customers', + 'List packages' => 'Summarize packages', + ); + + foreach my $old_acl ( keys %onetime ) { + my $new_acl = $onetime{$old_acl}; #support arrayref too? + ( my $journal = 'ACL_'.lc($new_acl) ) =~ s/ /_/g; + next if FS::upgrade_journal->is_done($journal); + + # grant $new_acl to all groups who have $old_acl + for my $group (@all_groups) { + if ( $group->access_right($old_acl) ) { + my $access_right = FS::access_right->new( { + 'righttype' => 'FS::access_group', + 'rightobjnum' => $group->groupnum, + 'rightname' => $new_acl, + } ); + my $error = $access_right->insert; + die $error if $error; + } + } + + FS::upgrade_journal->set_done($journal); + } + + ### ACL_download_report_data + if ( !FS::upgrade_journal->is_done('ACL_download_report_data') ) { + + # grant to everyone + for my $group (@all_groups) { + my $access_right = FS::access_right->new( { + 'righttype' => 'FS::access_group', + 'rightobjnum' => $group->groupnum, + 'rightname' => 'Download report data', + } ); + my $error = $access_right->insert; + die $error if $error; + } + + FS::upgrade_journal->set_done('ACL_download_report_data'); + } + ''; } diff --git a/FS/FS/cdr/cia.pm b/FS/FS/cdr/cia.pm index 61343338a..070f3fb0d 100644 --- a/FS/FS/cdr/cia.pm +++ b/FS/FS/cdr/cia.pm @@ -28,8 +28,8 @@ use FS::cdr qw(_cdr_date_parser_maker); 'dst', # DNIS 'src', # ANI skip(2), # Call Type, Toll Free, - skip(1), # Chair Conference Entry Code - 'accountcode', # Participant Conference Entry Code, + 'accountcode', # Chair Conference Entry Code + skip(1), # Participant Conference Entry Code, ], ); diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 945771e0d..a76170a9b 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -2006,7 +2006,7 @@ sub print_csv { ); } elsif ( lc($opt{'format'}) eq 'oneline' ) { #name? - + my ($previous_balance) = $self->previous; my $totaldue = sprintf('%.2f', $self->owed + $previous_balance); my @items = map { @@ -2017,6 +2017,7 @@ sub print_csv { $csv->combine( $cust_main->agentnum, + $cust_main->agent->agent, $self->custnum, $cust_main->first, $cust_main->last, @@ -4886,6 +4887,8 @@ sub _items_cust_bill_pkg { my $maxlength = $conf->config('cust_bill-latex_lineitem_maxlength') || 50; + my $cust_main = $self->cust_main;#for per-agent cust_bill-line_item-ate_style + my @b = (); my ($s, $r, $u) = ( undef, undef, undef ); foreach my $cust_bill_pkg ( @$cust_bill_pkgs ) @@ -5021,14 +5024,24 @@ sub _items_cust_bill_pkg { my $description = ($is_summary && $type && $type eq 'U') ? "Usage charges" : $desc; + #pry be a bit more efficient to look some of this conf stuff up + # outside the loop unless ( $conf->exists('disable_line_item_date_ranges') || $cust_pkg->part_pkg->option('disable_line_item_date_ranges',1) ) { my $time_period; - my $date_style = $conf->config('cust_bill-line_item-date_style'); + my $date_style = $conf->config( 'cust_bill-line_item-date_style', + $cust_main->agentnum + ); if ( defined($date_style) && $date_style eq 'month_of' ) { $time_period = time2str('The month of %B', $cust_bill_pkg->sdate); + } elsif ( defined($date_style) && $date_style eq 'X_month' ) { + my $desc = $conf->config( 'cust_bill-line_item-date_description', + $cust_main->agentnum + ); + $desc .= ' ' unless $desc =~ /\s$/; + $time_period = $desc. time2str('%B', $cust_bill_pkg->sdate); } else { $time_period = time2str($date_format, $cust_bill_pkg->sdate). " - ". time2str($date_format, $cust_bill_pkg->edate); diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index ebc049182..af65ca7de 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -3963,12 +3963,32 @@ cust_main-default_agent_custid is set and it has a value, custnum otherwise. sub display_custnum { my $self = shift; + + my $prefix = $conf->config('cust_main-custnum-display_prefix', $self->agentnum) || ''; + if ( my $special = $conf->config('cust_main-custnum-display_special') ) { + if ( $special eq 'CoStAg' ) { + $prefix = uc( join('', + $self->country, + ($self->state =~ /^(..)/), + $prefix || ($self->agent->agent =~ /^(..)/) + ) ); + } + elsif ( $special eq 'CoStCl' ) { + $prefix = uc( join('', + $self->country, + ($self->state =~ /^(..)/), + ($self->classnum ? $self->cust_class->classname =~ /^(..)/ : '__') + ) ); + } + # add any others here if needed + } + my $length = $conf->config('cust_main-custnum-display_length'); if ( $conf->exists('cust_main-default_agent_custid') && $self->agent_custid ){ return $self->agent_custid; - } elsif ( $conf->config('cust_main-custnum-display_prefix') ) { + } elsif ( $prefix ) { $length = 8 if !defined($length); - return $conf->config('cust_main-custnum-display_prefix'). + return $prefix . sprintf('%0'.$length.'d', $self->custnum) } elsif ( $length ) { return sprintf('%0'.$length.'d', $self->custnum); diff --git a/FS/FS/cust_main/Search.pm b/FS/FS/cust_main/Search.pm index 62464e4aa..1e9eee79d 100644 --- a/FS/FS/cust_main/Search.pm +++ b/FS/FS/cust_main/Search.pm @@ -127,6 +127,12 @@ sub smart_search { || ( $conf->config('cust_main-agent_custid-format') eq 'ww?d+' && $search =~ /^\s*(\w\w?\d+)\s*$/ ) + || ( $conf->config('cust_main-custnum-display_special') + # it's not currently possible for special prefixes to contain + # digits, so just strip off any alphabetic prefix and match + # the rest to custnum + && $search =~ /^\s*[[:alpha:]]*(\d+)\s*$/ + ) || ( $conf->exists('address1-search' ) && $search =~ /^\s*(\d+\-?\w*)\s*$/ #i.e. 1234A or 9432-D ) @@ -143,25 +149,23 @@ sub smart_search { } ); } - #if this becomes agent-virt need to get a list of all prefixes the current - #user can see (via their agents) - my $prefix = $conf->config('cust_main-custnum-display_prefix'); - if ( $prefix && $prefix eq substr($num, 0, length($prefix)) ) { - push @cust_main, qsearch( { - 'table' => 'cust_main', - 'hashref' => { 'custnum' => 0 + substr($num, length($prefix)), - %options, + # for all agents this user can see, if any of them have custnum prefixes + # that match the search string, include customers that match the rest + # of the custnum and belong to that agent + foreach my $agentnum ( $FS::CurrentUser::CurrentUser->agentnums ) { + my $p = $conf->config('cust_main-custnum-display_prefix', $agentnum); + next if !$p; + if ( $p eq substr($num, 0, length($p)) ) { + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => 0 + substr($num, length($p)), + 'agentnum' => $agentnum, + %options, }, - 'extra_sql' => " AND $agentnums_sql", #agent virtualization - } ); + } ); + } } - push @cust_main, qsearch( { - 'table' => 'cust_main', - 'hashref' => { 'agent_custid' => $num, %options }, - 'extra_sql' => " AND $agentnums_sql", #agent virtualization - } ); - if ( $conf->exists('address1-search') ) { my $len = length($num); $num = lc($num); diff --git a/FS/FS/cust_pay_void.pm b/FS/FS/cust_pay_void.pm index f1193cd24..bebcfd4cc 100644 --- a/FS/FS/cust_pay_void.pm +++ b/FS/FS/cust_pay_void.pm @@ -68,9 +68,7 @@ order taker (see L<FS::access_user>) =item payby -`CARD' (credit cards), `CHEK' (electronic check/ACH), -`LECB' (phone bill billing), `BILL' (billing), `CASH' (cash), -`WEST' (Western Union), `MCRD' (Manual credit card), or `COMP' (free) +Payment Type (See L<FS::payinfo_Mixin> for valid values) =item payinfo @@ -186,6 +184,7 @@ sub check { || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum') || $self->ut_numbern('void_date') || $self->ut_textn('reason') + || $self->payinfo_check ; return $error if $error; @@ -197,31 +196,6 @@ sub check { $self->void_date(time) unless $self->void_date; - $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP|PREP|CASH|WEST|MCRD)$/ - or return "Illegal payby"; - $self->payby($1); - - #false laziness with cust_refund::check - if ( $self->payby eq 'CARD' ) { - my $payinfo = $self->payinfo; - $payinfo =~ s/\D//g; - $self->payinfo($payinfo); - if ( $self->payinfo ) { - $self->payinfo =~ /^(\d{13,16}|\d{8,9})$/ - or return "Illegal (mistyped?) credit card number (payinfo)"; - $self->payinfo($1); - validate($self->payinfo) or return "Illegal credit card number"; - return "Unknown card type" if $self->payinfo !~ /^99\d{14}$/ #token - && cardtype($self->payinfo) eq "Unknown"; - } else { - $self->payinfo('N/A'); - } - - } else { - $error = $self->ut_textn('payinfo'); - return $error if $error; - } - $self->void_usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->void_usernum; diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 855accc0c..bee1b82fb 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -3300,7 +3300,7 @@ sub search { "NOT (".FS::cust_pkg->onetime_sql . ")"; } else { - foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end cancel )) { + foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel )) { next unless exists($params->{$field}); diff --git a/FS/FS/part_event/Condition.pm b/FS/FS/part_event/Condition.pm index b3948153e..fc69f1d0c 100644 --- a/FS/FS/part_event/Condition.pm +++ b/FS/FS/part_event/Condition.pm @@ -363,7 +363,7 @@ sub condition_sql_option_option { #used for part_event/Condition/cust_bill_has_service.pm and has_cust_tag.pm #a little false laziness w/above and condition_sql_option_integer sub condition_sql_option_option_integer { - my( $class, $option, $driver_name ) = @_; + my( $class, $option ) = @_; ( my $condname = $class ) =~ s/^.*:://; @@ -375,7 +375,7 @@ sub condition_sql_option_option_integer { AND part_event_condition_option.optionvalue = 'HASH' )"; - my $integer = ($driver_name =~ /^mysql/) ? 'UNSIGNED INTEGER' : 'INTEGER'; + my $integer = (driver_name =~ /^mysql/) ? 'UNSIGNED INTEGER' : 'INTEGER'; my $optionname = "CAST(optionname AS $integer)"; diff --git a/FS/FS/part_event/Condition/cust_bill_has_service.pm b/FS/FS/part_event/Condition/cust_bill_has_service.pm index 65c996437..6e981ee03 100644 --- a/FS/FS/part_event/Condition/cust_bill_has_service.pm +++ b/FS/FS/part_event/Condition/cust_bill_has_service.pm @@ -42,9 +42,7 @@ sub condition_sql { my( $class, $table, %opt ) = @_; my $servicenums = - $class->condition_sql_option_option_integer( 'has_service', - $opt{'driver_name'}, - ); + $class->condition_sql_option_option_integer('has_service'); my $sql = qq| 0 < ( SELECT COUNT(cs.svcpart) FROM cust_bill_pkg cbp, cust_svc cs diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm index 33ed42507..d1961a58d 100644 --- a/FS/FS/payby.pm +++ b/FS/FS/payby.pm @@ -176,6 +176,11 @@ sub realtime { # can use realtime payment facilities return $hash{$payby}->{realtime}; } +sub payby2shortname { + my $self = shift; + map { $_ => $hash{$_}->{shortname} } $self->payby; +} + sub payby2longname { my $self = shift; map { $_ => $hash{$_}->{longname} } $self->payby; diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 2b6be2c6c..64cc3770e 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -428,7 +428,8 @@ sub check { } else { my $addr_block = $self->addr_block; - unless ( $addr_block and $addr_block->manual_flag ) { + if ( $self->ip_addr eq '' + and not ( $addr_block and $addr_block->manual_flag ) ) { my $error = $self->assign_ip_addr; return $error if $error; } @@ -525,6 +526,12 @@ sub _check_ip_addr { else { return 'Cannot parse address: '.$self->ip_addr unless $self->NetAddr; } + + if ( $self->addr_block + and not $self->addr_block->NetAddr->contains($self->NetAddr) ) { + return 'Address '.$self->ip_addr.' not in block '.$self->addr_block->cidr; + } + # if (my $dup = qsearchs('svc_broadband', { # ip_addr => $self->ip_addr, # svcnum => {op=>'!=', value => $self->svcnum} diff --git a/FS/FS/upgrade_journal.pm b/FS/FS/upgrade_journal.pm new file mode 100644 index 000000000..8f6d121a3 --- /dev/null +++ b/FS/FS/upgrade_journal.pm @@ -0,0 +1,151 @@ +package FS::upgrade_journal; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::upgrade_journal - Object methods for upgrade_journal records + +=head1 SYNOPSIS + + use FS::upgrade_journal; + + $record = new FS::upgrade_journal \%hash; + $record = new FS::upgrade_journal { 'column' => 'value' }; + + $error = $record->insert; + + # Typical use case + my $upgrade = 'rename_all_customers_to_Bob'; + if ( ! FS::upgrade_journal->is_done($upgrade) ) { + ... # do the upgrade, then, if it succeeds + FS::upgrade_journal->set_done($upgrade); + } + +=head1 DESCRIPTION + +An FS::upgrade_journal object records an upgrade procedure that was run +on the database. FS::upgrade_journal inherits from FS::Record. The +following fields are currently supported: + +=over 4 + +=item upgradenum - primary key + +=item _date - unix timestamp when the upgrade was run + +=item upgrade - string identifier for the upgrade procedure; must match /^\w+$/ + +=item status - either 'done' or 'failed' + +=item statustext - any other message that needs to be recorded + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new upgrade record. To add it to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'upgrade_journal'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +sub delete { die "upgrade_journal records can't be deleted" } +sub replace { die "upgrade_journal records can't be modified" } + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + if ( !$self->_date ) { + $self->_date(time); + } + + my $error = + $self->ut_numbern('upgradenum') + || $self->ut_number('_date') + || $self->ut_alpha('upgrade') + || $self->ut_text('status') + || $self->ut_textn('statustext') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 CLASS METHODS + +=over 4 + +=item is_done UPGRADE + +Returns the upgrade entry with identifier UPGRADE and status 'done', if +there is one. This is an easy way to check whether an upgrade has been done. + +=cut + +sub is_done { + my ($class, $upgrade) = @_; + qsearch('upgrade_journal', { 'status' => 'done', 'upgrade' => $upgrade }) +} + +=item set_done UPGRADE + +Creates and inserts an upgrade entry with the current time, status 'done', +and identifier UPGRADE. Dies on error. + +=cut + +sub set_done { + my ($class, $upgrade) = @_; + my $new = $class->new({ 'status' => 'done', 'upgrade' => $upgrade }); + my $error = $new->insert; + die $error if $error; + $new; +} + + +=head1 BUGS + +Despite how it looks, this is not currently suitable for use as a mutex. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index e97f8702a..9cff85651 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -634,3 +634,5 @@ FS/contact_class.pm t/contact_class.t FS/GeocodeCache.pm t/GeocodeCache.t +FS/upgrade_journal.pm +t/upgrade_journal.t diff --git a/FS/t/GeocodeCache.t b/FS/t/GeocodeCache.t new file mode 100644 index 000000000..eae6f0d01 --- /dev/null +++ b/FS/t/GeocodeCache.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::GeocodeCache; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/upgrade_journal.t b/FS/t/upgrade_journal.t new file mode 100644 index 000000000..0822effc5 --- /dev/null +++ b/FS/t/upgrade_journal.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::upgrade_journal; +$loaded=1; +print "ok 1\n"; diff --git a/bin/part_pkg-bulk_change b/bin/part_pkg-bulk_change new file mode 100755 index 000000000..aecfea580 --- /dev/null +++ b/bin/part_pkg-bulk_change @@ -0,0 +1,72 @@ +#!/usr/bin/perl + +use strict; +use vars qw( $opt_r $opt_o $opt_v ); +use Getopt::Std; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch qsearchs); +use FS::part_pkg; +use FS::part_pkg_option; + +getopts('ro:v:'); + +my $user = shift or &usage; +adminsuidsetup $user; + +foreach my $part_pkg ( qsearch('part_pkg', {}) ) { + next if ! $part_pkg->freq && $opt_r; + + my %hash = ( + 'pkgpart' => $part_pkg->pkgpart, + 'optionname' => $opt_o, + ); + + my $part_pkg_option = qsearchs('part_pkg_option', \%hash); + + if ( $part_pkg_option ) { + next if $part_pkg_option->optionvalue eq $opt_v; + $part_pkg_option->optionvalue($opt_v); + my $error = $part_pkg_option->replace; + die $error if $error; + } else { + $part_pkg_option = new FS::part_pkg_option { %hash, 'optionvalue'=>$opt_v }; + my $error = $part_pkg_option->insert; + die $error if $error; + } + +} + +sub usage { + die "usage: part_pkg-bulk_change [ -r ] -o option_name -v option_value employee_username\n"; +} + +=head1 NAME + +cust_main-bulk_change + +=head1 SYNOPSIS + + part_pkg-bulk_change [ -r ] -o option_name -v option_value employee_username + +=head1 DESCRIPTION + +Command-line tool to change the payby field for a group of customers. + +-r: recurring package definitions only + +-o: part_pkg_option optionname + +-v: part_pkg_option optionvalue + +employee_username + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::part_pkg> + +=cut + +1; + diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index 0686c3d0f..af04fcc74 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -26,6 +26,7 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'chfn' => 'passwd/passwd', 'chsh' => 'passwd/passwd', 'login_info' => 'MyAccount/login_info', + 'login_banner_image' => 'MyAccount/login_banner_image', 'login' => 'MyAccount/login', 'logout' => 'MyAccount/logout', 'switch_acct' => 'MyAccount/switch_acct', diff --git a/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm b/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm index 59c8756f8..14f8a0c44 100644 --- a/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm +++ b/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm @@ -33,17 +33,21 @@ $DEBUG = 0; $FS::SelfService::DEBUG = $DEBUG; #false laziness w/FS::ClientAPI_XMLRPC.pm +our %typefix_skin_info = ( + 'logo' => 'base64', + 'title_left_image' => 'base64', + 'title_right_image' => 'base64', + 'menu_top_image' => 'base64', + 'menu_body_image' => 'base64', + 'menu_bottom_image' => 'base64', +); our %typefix = ( 'invoice_pdf' => { 'invoice_pdf' => 'base64', }, 'legacy_invoice_pdf' => { 'invoice_pdf' => 'base64', }, - 'skin_info' => { 'logo' => 'base64', - 'title_left_image' => 'base64', - 'title_right_image' => 'base64', - 'menu_top_image' => 'base64', - 'menu_body_image' => 'base64', - 'menu_bottom_image' => 'base64', - }, - 'invoice_logo' => { 'logo' => 'base64', }, + 'skin_info' => \%typefix_skin_info, + 'login_info' => \%typefix_skin_info, + 'invoice_logo' => { 'logo' => 'base64', }, + 'login_banner_image' => { 'image' => 'base64', }, ); sub AUTOLOAD { diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html index f0994e506..e3bb28298 100644 --- a/httemplate/docs/about.html +++ b/httemplate/docs/about.html @@ -1,14 +1,34 @@ -<% include('/elements/header-popup.html', { title=>'Freeside', nobr=>1 } ) %> +<% include('/elements/header-popup.html', { title=>$title, nobr=>1 } ) %> <% include('/elements/init_overlib.html') %> <CENTER> -<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER="0"><BR> -<H3>version <% $FS::VERSION %></H3> +% if ( $agentnum ) { + + <IMG SRC="<%$fsurl%>view/logo-agent.cgi?agentnum=<%$agentnum%>" BORDER="0"><BR> + +% my $url = $conf->config('company_url', $agentnum); +% if ( $url ) { + <BR><BR> + <A HREF="<% $conf->config('company_url', $agentnum) %>" TARGET="_blank"><%$title%> homepage</A> +% } + +% } else { + + <IMG SRC="<%$fsurl%>images/small-logo.png" BORDER="0"><BR> + <H3>version <% $FS::VERSION %></H3> + +% } </CENTER> <CENTER> -<FONT SIZE="-1">© 2011 Freeside Internet Services, Inc.<BR> +% if ( $agentnum ) { + <BR><BR> + <FONT SIZE="-2">Based on Freeside version <% $FS::VERSION %><BR> +% } else { + <FONT SIZE="-1"> +% } +© 2012 Freeside Internet Services, Inc.<BR> All rights reserved.<BR> Licensed under the terms of the<BR> GNU <b>Affero</b> General Public License.<BR> @@ -17,6 +37,9 @@ GNU <b>Affero</b> General Public License.<BR> <BR> <CENTER> +% if ( $agentnum ) { + <FONT SIZE="-2"> +% } <A HREF="credits.html">Credits</A> <A HREF="javascript:void(0)" onClick="openLicense()">License</A> @@ -24,13 +47,18 @@ GNU <b>Affero</b> General Public License.<BR> <BR><BR> <A HREF="http://www.freeside.biz/freeside" TARGET="_blank">Freeside homepage</A> +% if ( $agentnum ) { + </FONT> +% } </CENTER> <BR> -<CENTER> -<FONT SIZE="-3">"The sky was yellow and the sun was blue" -R. Hunter</FONT> -</CENTER> +% unless ( $agentnum ) { + <CENTER> + <FONT SIZE="-3">"A selfish heart is trouble, but a foolish heart is worse" -R. Hunter</FONT> + </CENTER> +% } <SCRIPT TYPE="text/javascript"> @@ -51,3 +79,11 @@ function openLicense() { </BODY> </HTML> +<%init> + +my $conf = new FS::Conf; +my $agentnum = $conf->config('brand-agent'); + +my $title = $agentnum ? $conf->config('company_name', $agentnum) : 'Freeside'; + +</%init> diff --git a/httemplate/docs/credits.html b/httemplate/docs/credits.html index 9bb1decea..c1d0d8705 100644 --- a/httemplate/docs/credits.html +++ b/httemplate/docs/credits.html @@ -120,6 +120,9 @@ Tim Yardley<BR> <BR> <BR> <BR> +<BR> +<BR> +<BR> <SCRIPT TYPE="text/javascript"> @@ -154,12 +157,12 @@ function myHeight() { return document.body.document.height; else */ - return 1850; // approx height (add more per contributors) + return 1900; // approx height (add more per contributors) } document.body.style.overflow = 'hidden'; -var startingPosition = 360; +var startingPosition = 340; //huh, adjust for firefox var ua = navigator.userAgent; diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi index 31def255c..90eab4aad 100644 --- a/httemplate/edit/process/svc_broadband.cgi +++ b/httemplate/edit/process/svc_broadband.cgi @@ -13,9 +13,9 @@ die "access denied" sub precheck { my $cgi = shift; - if ( !defined($cgi->param('ip_addr')) ) { - $cgi->param('ip_addr', $cgi->param('prev_ip_addr') || ''); - } + my $ip_addr = $cgi->param('ip_addr'); + $ip_addr =~ s/[^\d\.]//g; # converts '(automatic)' to null + $cgi->param('ip_addr', $ip_addr); $cgi->param("usergroup", [ $cgi->param('usergroup') ]); '' } diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html index aa085c41a..a517ece2a 100644 --- a/httemplate/elements/customer-table.html +++ b/httemplate/elements/customer-table.html @@ -67,7 +67,45 @@ Example: this.value = ''; } - + + function update_customer(searchrow, customerArray) { + + var custnum_obj = document.getElementById('custnum'+searchrow); + var customer = document.getElementById('customer'+searchrow); + var customer_select = document.getElementById('cust_select'+searchrow); + + custnum_obj.disabled = false; + custnum_obj.style.backgroundColor = '#ffffff'; + customer.disabled = false; + customer.style.backgroundColor = '#ffffff'; + + if ( customerArray.length == 0 ) { + + custnum_obj.value = 'Not found'; + customer.value = 'Not found'; + custnum_obj.style.color = '#ff0000'; + customer.style.color = '#ff0000'; + + customer.style.display = ''; + customer_select.style.display = 'none'; + return false; + + } else if ( customerArray.length == 5 ) { + + custnum_obj.value = customerArray[0]; + custnum_obj.style.color = '#000000'; + customer.value = customerArray[1]; + + update_balance_text(searchrow, customerArray[2]); + update_status_text( searchrow, customerArray[3]); + update_status_color(searchrow, '#'+customerArray[4]); + + customer.style.display = ''; + customer_select.style.display = 'none'; + return true; + } + } + function <% $opt{prefix} %>search_invnum() { this.style.color = '#000000' @@ -99,55 +137,25 @@ Example: customer_select.style.display = 'none'; var custnum_obj = document.getElementById('custnum'+searchrow); - var balance = document.getElementById('balance'+searchrow); - var status = document.getElementById('status'+searchrow); - balance.innerHTML = ''; - status.innerHTML = ''; + update_balance_text(searchrow, ''); + update_status_text(searchrow, ''); + update_status_color(searchrow, '#000000'); function search_invnum_update(customers) { var customerArray = eval('(' + customers + ')'); - - custnum_obj.disabled = false; - custnum_obj.style.backgroundColor = '#ffffff'; - customer.disabled = false; - customer.style.backgroundColor = '#ffffff'; - - if ( customerArray.length == 0 ) { - - custnum_obj.value = 'Not found'; - customer.value = 'Not found'; - custnum_obj.style.color = '#ff0000'; - customer.style.color = '#ff0000'; - - customer.style.display = ''; - customer_select.style.display = 'none'; - - } else if ( customerArray.length == 5 ) { - - custnum_obj.value = customerArray[0]; - custnum_obj.style.color = '#000000'; - customer.value = customerArray[1]; - balance.innerHTML = '<% $money_char %>' + customerArray[2] + ' '; - status.innerHTML = customerArray[3]; - status.style.color = '#'+customerArray[4]; - - customer.style.display = ''; - customer_select.style.display = 'none'; + update_customer(searchrow, customerArray); % if ( $opt{invnum_update_callback} ) { <% $opt{invnum_update_callback} %>(searchrow, '<% $opt{prefix} %>') % } - } - } invnum_search( invnum, search_invnum_update ); } - function <% $opt{prefix} %>search_custnum() { this.style.color = '#000000' @@ -180,42 +188,19 @@ Example: var invnum = document.getElementById('invnum'+searchrow); invnum.value = ''; - - var balance = document.getElementById('balance'+searchrow); - balance.innerHTML = ''; - - var status = document.getElementById('status'+searchrow); - status.innerHTML = ''; - function search_custnum_update(customers) { - - var customerArray = eval('(' + customers + ')'); + update_balance_text(searchrow, ''); + update_status_text( searchrow, ''); + update_status_color(searchrow, '#000000'); - customer.disabled = false; - customer.style.backgroundColor = '#ffffff'; - - if ( customerArray.length == 0 ) { - - customer.value = 'Not found'; - customer.style.color = '#ff0000'; - custnum_obj.style.color = '#ff0000'; - - } else if ( customerArray.length == 5 ) { - - custnum_obj.value = customerArray[0]; - custnum_obj.style.color = '#000000'; - customer.value = customerArray[1]; - balance.innerHTML = '<% $money_char %>' + customerArray[2] + ' '; - status.innerHTML = customerArray[3]; - status.style.color = '#'+customerArray[4]; + function search_custnum_update(customers) { - customer.style.display = ''; - customer_select.style.display = 'none'; + var customerArray = eval('(' + customers + ')') || []; + update_customer(searchrow, customerArray); % if ( $opt{custnum_update_callback} ) { <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>') % } - } } custnum_search(custnum, search_custnum_update ); @@ -251,39 +236,16 @@ Example: var customer_select = document.getElementById('cust_select'+searchrow); - var balance = document.getElementById('balance'+searchrow); - balance.innerHTML = ''; - - var status = document.getElementById('status'+searchrow); - status.innerHTML = ''; - function search_customer_update(customers) { - var customerArray = eval('(' + customers + ')'); + var customerArrayArray = eval('(' + customers + ')') || [ [] ]; custnum_obj.disabled = false; custnum_obj.style.backgroundColor = '#ffffff'; - if ( customerArray.length == 0 ) { - - custnum_obj.value = 'Not found'; - custnum_obj.style.color = '#ff0000'; - customer_obj.style.color = '#ff0000'; - - customer_obj.style.display = ''; - customer_select.style.display = 'none'; - - } else if ( customerArray.length == 1 ) { - - custnum_obj.value = customerArray[0][0]; - customer_obj.value = customerArray[0][1]; - balance.innerHTML = '<% $money_char %>' + customerArray[0][2] + ' '; - status.innerHTML = customerArray[0][3]; - status.style.color = '#'+customerArray[0][4]; - - customer_obj.style.display = ''; - customer_select.style.display = 'none'; + if ( customerArrayArray.length == 1 ) { + update_customer(customerArrayArray[1]); % if ( $opt{custnum_update_callback} ) { <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>') % } @@ -294,14 +256,16 @@ Example: custnum_obj.style.color = '#ff0000'; //blank the current list - for ( var i = customer_select.length; i >= 0; i-- ) - customer_select.options[i] = null; + customer_select.options.length = 0; opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000'); - //add the multiple customers - for ( var s = 0; s < customerArray.length; s++ ) - opt(customer_select, customerArray[s][0] + '_' + customerArray[s][2] + '_' + customerArray[s][3] + '_' + customerArray[s][4], customerArray[s][1], '#000000'); + for ( var s = 0; s < customerArrayArray.length; s++ ) { + opt(customer_select, + JSON.stringify(customerArrayArray[s]), + customerArrayArray[s][1], + '#000000'); + } opt(customer_select, 'cancel', '(Edit search string)', '#000000'); @@ -341,27 +305,7 @@ Example: } else { - var pos_underscore1 = custnum_balance_status.indexOf('_'); - var pos_underscore2 = custnum_balance_status.indexOf('_',pos_underscore1+1); - var pos_underscore3 = custnum_balance_status.indexOf('_',pos_underscore2+1); - var custnum = custnum_balance_status.substring(0,pos_underscore1); - var balance = custnum_balance_status.substring(pos_underscore1+1,pos_underscore2) + ' '; - var status = custnum_balance_status.substring(pos_underscore2+1,pos_underscore3); - var color = custnum_balance_status.substring(pos_underscore3+1); - - custnum_obj.value = custnum; - custnum_obj.style.color = '#000000'; - - customer_obj.value = customer; - customer_obj.style.color = '#000000'; - - balance_obj.innerHTML = '<% $money_char %>' + balance; - - status_obj.innerHTML = status; - status_obj.style.color = '#'+color; - - this.style.display = 'none'; - customer_obj.style.display = ''; + update_customer(searchrow, JSON.parse(custnum_balance_status)); % if ( $opt{custnum_update_callback} ) { <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>') @@ -378,6 +322,23 @@ Example: what.options[length] = optionName; } + function update_status_text(rownum, newval) { + document.getElementById('status'+rownum).value = newval; + document.getElementById('status'+rownum+'_text').innerHTML = newval; + } + + function update_status_color(rownum, newval) { + document.getElementById('statuscolor'+rownum).value = newval; + document.getElementById('status'+rownum+'_text').style.color = newval; + } + + function update_balance_text(rownum, newval) { + document.getElementById('balance'+rownum).value = newval; + document.getElementById('balance'+rownum+'_text').innerHTML = newval; + } + + + </SCRIPT> <TABLE ID="<% $opt{prefix} %>OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0> @@ -430,14 +391,26 @@ Example: </SCRIPT> </TD> - <TD> - <SPAN + <TD STYLE="text-align: center"> + <SPAN + ID = "status<% $row %>_text" + rownum = "<% $row %>" + STYLE = "font-weight: bold; + color: <%$param->{"statuscolor$row"} || '#000000'%>" + + ><% $param->{"status$row"} %></SPAN> + <INPUT TYPE = "hidden" NAME = "status<% $row %>" ID = "status<% $row %>" + VALUE = "<% $param->{"status$row"} %>" + rownum = "<% $row %>" + > + <INPUT TYPE = "hidden" + NAME = "statuscolor<% $row %>" + ID = "statuscolor<% $row %>" + VALUE = "<% $param->{"statuscolor$row"} %>" rownum = "<% $row %>" - STYLE = "text-align:center; font-weight: bold" > - </SPAN> </TD> <TD> @@ -456,6 +429,21 @@ Example: </SCRIPT> </TD> + <TD STYLE="text-align:right"> + <% $money_char %> + <SPAN + ID = "balance<% $row %>_text" + rownum = "<% $row %>" + ><% $param->{"balance$row"} %></SPAN> + + <INPUT TYPE = "hidden" + NAME = "balance<% $row %>" + ID = "balance<% $row %>" + VALUE = "<% $param->{"balance$row"} %>" + rownum = "<% $row %>" + > + </TD> + % my $col = 0; % foreach my $field ( @{$opt{fields}} ) { % my $value; @@ -494,15 +482,6 @@ Example: </TD> % $col++; % } - <TD STYLE="text-align:right;"> - <SPAN - NAME = "balance<% $row %>" - ID = "balance<% $row %>" - rownum = "<% $row %>" - > - </SPAN> - - </TD> </TR> % } @@ -613,15 +592,28 @@ Example: row.appendChild(custnum_cell); var status_cell = document.createElement('TD'); + status_cell.style.textAlign = 'center'; - var status_span = document.createElement('SPAN'); - status_span.setAttribute('name', 'status'+<% $opt{prefix} %>rownum); - status_span.setAttribute('id', 'status'+<% $opt{prefix} %>rownum); - status_span.style.textAlign = 'center'; - status_span.style.fontWeight = 'bold'; - status_span.setAttribute('rownum', <% $opt{prefix} %>rownum); - status_cell.appendChild(status_span); + var status_span = document.createElement('SPAN'); + status_span.setAttribute('id', 'status'+<% $opt{prefix} %>rownum+'_text'); + status_span.style.fontWeight = 'bold'; + status_span.setAttribute('rownum', <% $opt{prefix} %>rownum); + status_cell.appendChild(status_span); + var status_input = document.createElement('INPUT'); + status_input.setAttribute('type', 'hidden'); + status_input.setAttribute('name', 'status'+<% $opt{prefix} %>rownum); + status_input.setAttribute('id', 'status'+<% $opt{prefix} %>rownum); + status_input.setAttribute('rownum', <% $opt{prefix} %>rownum); + status_cell.appendChild(status_input); + + var statuscolor_input = document.createElement('INPUT'); + statuscolor_input.setAttribute('type', 'hidden'); + statuscolor_input.setAttribute('name', 'statuscolor'+<% $opt{prefix} %>rownum); + statuscolor_input.setAttribute('id', 'statuscolor'+<% $opt{prefix} %>rownum); + statuscolor_input.setAttribute('rownum', <% $opt{prefix} %>rownum); + status_cell.appendChild(statuscolor_input); + row.appendChild(status_cell); var customer_cell = document.createElement('TD'); @@ -649,13 +641,25 @@ Example: row.appendChild(customer_cell); var balance_cell = document.createElement('TD'); - balance_cell.style.textAlign = 'right'; - - var balance_span = document.createElement('SPAN'); - balance_span.setAttribute('name', 'balance'+<% $opt{prefix} %>rownum); - balance_span.setAttribute('id', 'balance'+<% $opt{prefix} %>rownum); - balance_span.setAttribute('rownum', <% $opt{prefix} %>rownum); - balance_cell.appendChild(balance_span); + + balance_cell.style.textAlign = 'right'; + balance_cell.appendChild(document.createTextNode('<%$money_char%>')); + + var balance_span = document.createElement('SPAN'); + balance_span.setAttribute('id', 'balance'+<% $opt{prefix} %>rownum+'_text'); + balance_span.setAttribute('rownum', <% $opt{prefix} %>rownum); + balance_cell.appendChild(balance_span); + + balance_cell.appendChild( + document.createTextNode(String.fromCharCode(160)) // + ); + + var balance_input = document.createElement('INPUT'); + balance_input.setAttribute('type', 'hidden'); + balance_input.setAttribute('name', 'balance'+<% $opt{prefix} %>rownum); + balance_input.setAttribute('id', 'balance'+<% $opt{prefix} %>rownum); + balance_input.setAttribute('rownum', <% $opt{prefix} %>rownum); + balance_cell.appendChild(balance_input); row.appendChild(balance_cell); @@ -676,9 +680,10 @@ Example: my_cell.appendChild(my_text); % } +% my $name = (ref($field) eq 'CODE') ? "column${col}_" : $field; var my_input = document.createElement('INPUT'); - my_input.setAttribute('name', '<% $field %>'+<% $opt{prefix} %>rownum); - my_input.setAttribute('id', '<% $field %>'+<% $opt{prefix} %>rownum); + my_input.setAttribute('name', '<% $name %>'+<% $opt{prefix} %>rownum); + my_input.setAttribute('id', '<% $name %>'+<% $opt{prefix} %>rownum); my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>'; my_input.setAttribute('size', <% $sizes->[$col] || 10 %>); % if ($types->[$col] eq 'immutable') { @@ -725,7 +730,7 @@ my $conf = new FS::Conf; $opt{prefix} = '' unless defined $opt{prefix}; $opt{prefix} .= '_' if $opt{prefix}; -my $types = $opt{'types'} ? [ @{$opt{'types'}} ] : []; +my $types = $opt{'type'} ? [ @{$opt{'type'}} ] : []; my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : []; my $param = $opt{param}; @@ -742,5 +747,4 @@ my %align = ( ); my $money_char = $conf->config('money_char') || '$'; - </%init> diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css index 44a4a3ca2..c291e1e33 100644 --- a/httemplate/elements/freeside.css +++ b/httemplate/elements/freeside.css @@ -250,3 +250,31 @@ div.fstabcontainer { background-color:#f8f8f8; } +table.grid { + border: 1px solid #cccccc; + -moz-box-shadow: 1px 1px 2px #666666; + -webkit-box-shadow: 1px 1px 2px #666666; + box-shadow: 1px 1px 2px #666666; + filter: progid:DXImageTransform.Microsoft.Shadow(color='#666666', Direction=135, Strength=2); +} + +th.grid { + padding-left: 3px; + padding-right: 3px; + padding-bottom: 2px; + border: none; + empty-cells: show; +} + +td.grid { + padding-left: 3px; + padding-right: 3px; + padding-bottom: 2px; + border: none; + empty-cells: show; +} + +table.inv { border: none } +th.inv { border: none } +td.inv { border: none } + diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 3b0969f5c..b1cbebf34 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -28,7 +28,7 @@ % 'width' => 300, % 'height' => 375, % 'color' => '#7e0079', -% 'scrolling' => 'no', +% #'scrolling' => 'no', % ); % $fs_popup =~ s/return false;//; function about_freeside() { @@ -106,7 +106,7 @@ $report_customers_lists{'with USPS-unvalidated addresses'} = [ $fsurl. 'search/c tie my %report_customers, 'Tie::IxHash'; $report_customers{'List customers'} = [ \%report_customers_lists, 'List customers' ] - if $curuser->access_right('List customers'); + if $curuser->access_right('List all customers'); $report_customers{'Zip code distribution'} = [ $fsurl. 'search/report_cust_main-zip.html', 'Zip codes by number of customers' ]; $report_customers{'Customer signup report'} = [ $fsurl. 'graph/report_cust_signup.html', 'New customer signups by date' ], $report_customers{'Advanced customer reports'} = [ $fsurl. 'search/report_cust_main.html', 'by status, signup date, agent, etc.' ] @@ -238,9 +238,11 @@ if ( $curuser->access_right('Financial reports') ) { $report_packages{'separator2'} = ''; } $report_packages{'All customer packages'} = [ $fsurl.'search/cust_pkg.cgi?pkgnum', 'List all customer packages', ]; -$report_packages{'Package summary'} = [ $fsurl.'search/cust_pkg_summary.html', 'Show package sales summary', ]; +$report_packages{'Package summary'} = [ $fsurl.'search/cust_pkg_summary.html', 'Show package sales summary', ] + if $curuser->access_right('Summarize packages'); $report_packages{'Suspended customer packages'} = [ $fsurl.'search/cust_pkg.cgi?magic=suspended', 'List suspended packages' ]; -$report_packages{'Suspension summary'} = [ $fsurl.'search/cust_pkg_susp.html', 'Show suspension activity', ]; +$report_packages{'Suspension summary'} = [ $fsurl.'search/cust_pkg_susp.html', 'Show suspension activity', ] + if $curuser->access_right('Summarize packages'); $report_packages{'Customer packages with unconfigured services'} = [ $fsurl.'search/cust_pkg.cgi?APKG_pkgnum', 'List packages which have provisionable services' ]; $report_packages{'FCC Form 477 packages'} = [ $fsurl.'search/report_477.html', 'Summarize packages by census tract for particular types' ] if $conf->exists('cust_main-require_censustract'); @@ -626,11 +628,18 @@ $help_menu{'Ticketing documentation'} = [ 'http://wiki.bestpractical.com/', 'Req $help_menu{'Networking monitoring documentation'} = [ 'http://torrus.org/userguide.pod.html', 'Torrus User Guide' ] if $conf->config('network_monitoring_system') eq 'Torrus_Internal'; $help_menu{'separator'} = ''; -$help_menu{"About Freeside v$FS::VERSION"} = [ "javascript:about_freeside()", '' ]; -$help_menu{"About RT v$RT::VERSION"} = [ 'http://www.bestpractical.com/rt', 'Request Tracker Homepage' ] - if $conf->config('ticket_system') eq 'RT_Internal'; -$help_menu{"About Torrus v1.0.9"} = [ 'http://www.torrus.org/', 'Torrus Homepage' ] #XXX manual version - if $conf->config('network_monitoring_system') eq 'Torrus_Internal'; + +my $agentnum = $conf->config('brand-agent'); +if ( $agentnum ) { + my $company_name = $conf->config('company_name', $agentnum); + $help_menu{"About $company_name"} = [ "javascript:about_freeside()", '' ]; +} else { + $help_menu{"About Freeside v$FS::VERSION"} = [ "javascript:about_freeside()", '' ]; + $help_menu{"About RT v$RT::VERSION"} = [ 'http://www.bestpractical.com/rt', 'Request Tracker Homepage' ] + if $conf->config('ticket_system') eq 'RT_Internal'; + $help_menu{"About Torrus v1.0.9"} = [ 'http://www.torrus.org/', 'Torrus Homepage' ] #XXX manual version + if $conf->config('network_monitoring_system') eq 'Torrus_Internal'; +} tie my %menu, 'Tie::IxHash'; diff --git a/httemplate/elements/select-rt-customfield.html b/httemplate/elements/select-rt-customfield.html new file mode 100644 index 000000000..7a45bb14b --- /dev/null +++ b/httemplate/elements/select-rt-customfield.html @@ -0,0 +1,34 @@ +<SELECT NAME="<% $opt{name} %>"> +% while ( @fields ) { +<OPTION VALUE="<% shift @fields %>"><% shift @fields %></OPTION> +% } +</SELECT> +<%once> +RT::Init(); +</%once> +<%init> +my %opt = @_; +my $lookuptype = $opt{lookuptype}; +my $valuetype = $opt{valuetype}; +# get a list of TimeValue-type custom fields +my $CurrentUser = RT::CurrentUser->new(); +$CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username); +die "RT not configured" unless $CurrentUser->id; +my $CFs = RT::CustomFields->new($CurrentUser); + +$CFs->Limit(FIELD => 'LookupType', + OPERATOR => 'ENDSWITH', + VALUE => $lookuptype) + if $lookuptype; + +$CFs->Limit(FIELD => 'Type', + VALUE => $valuetype) + if $valuetype; + +my @fields; +push @fields, '', $opt{empty_label} if exists($opt{empty_label}); + +while (my $CF = $CFs->Next) { + push @fields, $CF->Name, ($CF->Description || $CF->Name); +} +</%init> diff --git a/httemplate/elements/table-grid.html b/httemplate/elements/table-grid.html index 4d7deeaa4..4f4200570 100644 --- a/httemplate/elements/table-grid.html +++ b/httemplate/elements/table-grid.html @@ -1,15 +1,4 @@ -<STYLE TYPE="text/css"> - -.grid TH { padding-left: 3px; padding-right: 3px; padding-bottom: 2px; border: none; empty-cells: show } -.grid TD { padding-left: 3px; padding-right: 3px; padding-bottom: 2px; border: none; empty-cells: show } - -.inv table { border: none } -.inv TH { border: none } -.inv TD { border: none } - -</STYLE> - -<TABLE CLASS="grid" CELLSPACING=<% $opt{cellspacing} %> CELLPADDING=<% $opt{cellpadding} %> <% $opt{bgcolor} %> STYLE="border: 1px solid #cccccc;"> +<TABLE CLASS="grid" CELLSPACING=<% $opt{cellspacing} %> CELLPADDING=<% $opt{cellpadding} %> <% $opt{bgcolor} %>> <%init> diff --git a/httemplate/elements/tr-select-from_to.html b/httemplate/elements/tr-select-from_to.html index 100381234..a27412f99 100644 --- a/httemplate/elements/tr-select-from_to.html +++ b/httemplate/elements/tr-select-from_to.html @@ -39,7 +39,7 @@ my %hash = ( 'show_month_abbr' => 1, 'start_year' => '1999', - 'end_year' => '2012', #haha, well... + 'end_year' => '2013', #haha, well... @_, ); </%init> diff --git a/httemplate/elements/tr-select-router_block_ip.html b/httemplate/elements/tr-select-router_block_ip.html index ed8fe810f..95d1787b8 100644 --- a/httemplate/elements/tr-select-router_block_ip.html +++ b/httemplate/elements/tr-select-router_block_ip.html @@ -1,23 +1,34 @@ <script type="text/javascript"> var manual_addr_routernum = <% encode_json(\%manual_addr_routernum) %>; var ip_addr_curr_value = <% $opt{'ip_addr'} |js_string %>; -function lock_ip_addr(obj, i) { - var routernum = obj.value; - var select_blocknum = document.getElementsByName('blocknum')[0]; +var blocknum_curr_value = <% $opt{'blocknum'} |js_string %>; +function update_ip_addr(obj, i) { + var routernum = document.getElementById('router_select_0').value; + var select_blocknum = document.getElementById('router_select_1'); + var blocknum = select_blocknum.value; var input_ip_addr = document.getElementById('input_ip_addr'); if ( manual_addr_routernum[routernum] == 'Y' ) { -%# enable ip_addr, default it to its previous value, and hide block selection +%# hide block selection and default ip address to its previous value select_blocknum.style.display = 'none'; input_ip_addr.value = ip_addr_curr_value; - input_ip_addr.disabled = false; } else { %# the reverse select_blocknum.style.display = ''; - input_ip_addr.disabled = true; - input_ip_addr.value = '(automatic)'; +%# default ip address to null, unless the router/block are set to the +%# previous value, in which case default it to current value + if ( routernum == router_curr_values[0] && + blocknum == router_curr_values[1] ) { + input_ip_addr.value = ip_addr_curr_value; + } else { + input_ip_addr.value = <% mt('(automatic)') |js_string %>; + } } } +function clearhint_ip_addr (what) { + if ( what.value == <% mt('(automatic)') |js_string %> ) + what.value = ''; +} </script> <& /elements/tr-td-label.html, label => ($opt{'label'} || 'Router') &> <td> @@ -27,7 +38,7 @@ function lock_ip_addr(obj, i) { records => \@routers, name_col => 'routername', value_col => 'routernum', - onchange => 'lock_ip_addr', + onchange => 'update_ip_addr', curr_value=> $opt{'routernum'}, }, { @@ -39,6 +50,7 @@ function lock_ip_addr(obj, i) { name_col => 'cidr', link_col => 'routernum', empty_label => '(any)', + onchange => 'update_ip_addr', curr_value => $opt{'blocknum'}, }, ] @@ -52,12 +64,11 @@ function lock_ip_addr(obj, i) { % } % else { <input type="text" id="input_ip_addr" name="ip_addr" - value="<% $opt{'ip_addr'} |h%>"> + value="<% $opt{'ip_addr'} |h%>" onfocus="clearhint_ip_addr(this)"> % } </td> </tr> -<input type="hidden" name="prev_ip_addr" value="<% $opt{'ip_addr'} |h%>"> <script type="text/javascript"> -lock_ip_addr(document.getElementsByName('routernum')[0],0); +update_ip_addr(); </script> <%init> diff --git a/httemplate/graph/cust_signup.html b/httemplate/graph/cust_signup.html index dd9100f1e..a3eb702f2 100644 --- a/httemplate/graph/cust_signup.html +++ b/httemplate/graph/cust_signup.html @@ -9,7 +9,7 @@ 'agentnum' => $agentnum, 'sprintf' => '%u', 'disable_money' => 1, - 'bottom_total' => (scalar @items > 1 ? 1 : 0), + 'bottom_total' => (scalar @items > 1 && !$indirect ? 1 : 0), 'bottom_link' => $bottom_link, 'link_fromparam' => 'signupdate_begin', 'link_toparam' => 'signupdate_end', @@ -59,25 +59,34 @@ elsif ( $cgi->param('refnum') =~ /^(\d*)$/ ) { } } +my $indirect = ($cgi->param('indirect') eq 'Y' ? 1 : 0); + my (@items, @labels, @colors, @params, @links); my $hue = 0; -my $hue_increment = 125; +my $hue_increment = 75; my @signup_colors; foreach my $referral (@referral) { + my %params = ('refnum' => $referral->refnum) unless $all_referral; + push @items, 'signups'; push @labels, ( $all_referral ? 'Signups' : $referral->referral ); - push @params, ( $all_referral ? [] : [ 'refnum' => $referral->refnum ] ); + push @params, [ %params ]; push @links, $link . ($all_referral ? '' : "refnum=".$referral->refnum.';'); - if ( !@signup_colors ) { - @signup_colors = Color::Scheme->new - ->from_hue($hue) - ->scheme('analogic') - ->colors; - $hue += $hue_increment; + # rotate hue for each referral type + @signup_colors = Color::Scheme->new->from_hue($hue)->colors; + $hue += $hue_increment; + push @colors, $signup_colors[0]; + if ( $indirect ) { + push @items, 'signups'; + push @labels, $all_referral ? + 'Referrals' : + $referral->referral . ' referrals'; + push @params, [ %params, 'indirect' => 1 ]; + push @links, ''; + push @colors, $signup_colors[1]; } - push @colors, shift @signup_colors; } </%init> diff --git a/httemplate/graph/elements/monthly.html b/httemplate/graph/elements/monthly.html index 072798c2a..86530dd39 100644 --- a/httemplate/graph/elements/monthly.html +++ b/httemplate/graph/elements/monthly.html @@ -92,10 +92,7 @@ $opt{'start_year'} ||= $cgi->param('start_year'); # || 1899+$curyear; $opt{'end_month'} ||= $cgi->param('end_month'); # || $curmon+1; $opt{'end_year'} ||= $cgi->param('end_year'); # || 1900+$curyear; -#find end of projection -$opt{'project_month'} ||= $cgi->param('project_month') || 0; -$opt{'project_year'} ||= $cgi->param('project_year') || 0; -# setting these to zero prevents projection on reports that don't support it +$opt{'projection'} ||= $cgi->param('projection') ? 1 : 0; if ( $opt{'daily'} ) { # daily granularity $opt{'start_day'} ||= $cgi->param('start_day'); @@ -118,9 +115,7 @@ my %reportopts = ( 'end_day' => $opt{'end_day'}, 'end_month' => $opt{'end_month'}, 'end_year' => $opt{'end_year'}, - 'project_day' => $opt{'project_day'}, - 'project_month' => $opt{'project_month'}, - 'project_year' => $opt{'project_year'}, + 'projection' => $opt{'projection'}, 'agentnum' => $opt{'agentnum'}, 'remove_empty' => $opt{'remove_empty'}, 'doublemonths' => $opt{'doublemonths'}, diff --git a/httemplate/graph/report_cust_bill_pkg.html b/httemplate/graph/report_cust_bill_pkg.html index f2c486cf4..07d4421e8 100644 --- a/httemplate/graph/report_cust_bill_pkg.html +++ b/httemplate/graph/report_cust_bill_pkg.html @@ -7,10 +7,8 @@ <% include('/elements/tr-select-from_to.html' ) %> <TR> - <TD ALIGN="right">Project to:</TD> - <TD><& /elements/select-month_year.html, - prefix => 'project', - show_month_abbr => 1 &></TD> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="projection" VALUE="1"></TD> + <TD>Show projected data for future months</TD> </TR> <% include('/elements/tr-select-agent.html', diff --git a/httemplate/graph/report_cust_signup.html b/httemplate/graph/report_cust_signup.html index 9d3f5006b..12dec8e6a 100644 --- a/httemplate/graph/report_cust_signup.html +++ b/httemplate/graph/report_cust_signup.html @@ -22,6 +22,12 @@ ) %> +<& /elements/tr-td-label.html, label => 'Show customer referrals' &> +<TD> + <INPUT TYPE="checkbox" NAME="indirect" VALUE="Y"> +</TD> +</TR> + </TABLE> <BR><INPUT TYPE="submit" VALUE="Display"> diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html index 11fdeee61..2e798652d 100644 --- a/httemplate/misc/batch-cust_pay.html +++ b/httemplate/misc/batch-cust_pay.html @@ -62,10 +62,10 @@ function select_discount_term(row, prefix) { name_singular => 'payment', header => \@header, fields => \@fields, - types => \@types, + type => \@types, align => \@align, - sizes => \@sizes, - colors => \@colors, + size => \@sizes, + color => \@colors, param => \%param, footer => \@footer, footer_align => \@footer_align, @@ -96,18 +96,19 @@ die "access denied" my $conf = new FS::Conf; my $money_char = $conf->config('money_char') || '$'; -my @header = ( '', 'Amount', 'Check #' ); -my @fields = ( sub { "$money_char" }, 'paid', 'payinfo' ); -my @types = ( 'immutable', '', '' ); -my @align = ( 'c', 'r', 'r' ); -my @sizes = ( 0, 8, 10 ); -my @colors = ( '', '', '' ); +my @header = ( 'Amount', 'Check #' ); +my @fields = ( 'paid', 'payinfo' ); +my @types = ( '', '' ); +my @align = ( 'r', 'r' ); +my @sizes = ( 8, 10 ); +my @colors = ( '', '' ); my %param = (); -my @footer = ( "$money_char", '_TOTAL', '' ); -my @footer_align = ( 'c', 'r', 'r' ); +my @footer = ( '_TOTAL', '' ); +my @footer_align = ( 'r', 'r' ); my $custnum_update_callback = ''; if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) { + #push @header, 'Discount'; push @header, ''; push @fields, 'discount_term'; push @types, 'immutable'; @@ -119,6 +120,7 @@ if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) { $custnum_update_callback = 'select_discount_term'; } +#push @header, 'Error'; push @header, ''; push @fields, 'error'; push @types, 'immutable'; diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi index aa371266c..a6b90ea74 100644 --- a/httemplate/misc/process/batch-cust_pay.cgi +++ b/httemplate/misc/process/batch-cust_pay.cgi @@ -19,7 +19,7 @@ % 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, % }); % } -% if ( !$cust_main ) { # not found, try agent_custid +% if ( length($custnum) and !$cust_main ) { # not found, try agent_custid % $cust_main = qsearchs({ % 'table' => 'cust_main', % 'hashref' => { 'agent_custid' => $custnum }, diff --git a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi index 71e2da597..b524e69fc 100644 --- a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi +++ b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi @@ -2,15 +2,18 @@ % % my $return = []; % my $custnum = $cgi->param('arg'); -% my $cust_main = ''; -% $cust_main = qsearchs({ -% 'table' => 'cust_main', -% 'hashref' => { 'custnum' => $custnum }, -% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, -% }); +% if ( $custnum =~ /^\d+$/ ) { +% my $cust_main = ''; +% $cust_main = qsearchs({ +% 'table' => 'cust_main', +% 'hashref' => { 'custnum' => $custnum }, +% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +% }); % -% if ($cust_main) { -% $return = [ map [ $_, "$_ months" ], $cust_main->discount_terms ]; +% if ($cust_main) { +% $return = [ map [ $_, sprintf("%d months", $_) ], +% $cust_main->discount_terms ]; +% } % } % <% objToJson($return) %> diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi index 68c5bf597..436501e8b 100644 --- a/httemplate/misc/xmlhttp-cust_main-search.cgi +++ b/httemplate/misc/xmlhttp-cust_main-search.cgi @@ -18,9 +18,13 @@ % } elsif ( $sub eq 'invnum_search' ) { % % my $string = $cgi->param('arg'); -% my $inv = qsearchs('cust_bill', { 'invnum' => $string }); -% my $return = $inv ? findbycustnum($inv->custnum,0) : []; +% if ( $string =~ /^(\d+)$/ ) { +% my $inv = qsearchs('cust_bill', { 'invnum' => $1 }); +% my $return = $inv ? findbycustnum($inv->custnum,0) : []; <% objToJson($return) %> +% } else { #return nothing +[] +% } % } % elsif ( $sub eq 'exact_search' ) { % # XXX possibly should query each element separately diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index aae8c7e99..859ef04e6 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -334,7 +334,7 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" - unless $curuser->access_right('List customers'); + unless $curuser->access_right('List all customers'); my $conf = new FS::Conf; my $maxrecords = $conf->config('maxsearchrecordsperpage'); diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index 297edee90..887ec6039 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -20,6 +20,7 @@ emt('Susp. delay'), emt('Expire'), emt('Contract end'), + emt('Changed'), emt('Cancel'), emt('Reason'), FS::UI::Web::cust_header( @@ -45,7 +46,7 @@ sub { FS::part_pkg::freq_pretty(shift); }, ( map { time_or_blank($_) } - qw( setup last_bill bill adjourn susp dundate expire contract_end cancel ) ), + qw( setup last_bill bill adjourn susp dundate expire contract_end change_date cancel ) ), sub { my $self = shift; my $return = ''; @@ -94,13 +95,14 @@ '', '', '', + '', FS::UI::Web::cust_colors(), '', ], - 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '', FS::UI::Web::cust_styles() ], 'size' => [ '', '', '', '', '-1' ], - 'align' => 'rrlccrrlrrrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r', + 'align' => 'rrlccrrlrrrrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r', 'links' => [ $link, $link, @@ -117,6 +119,7 @@ '', '', '', + '', # link to changed-from package? '', '', '', @@ -182,7 +185,7 @@ my %disable = ( '' => {}, ); -foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end cancel active )) { +foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); diff --git a/httemplate/search/cust_pkg_summary.cgi b/httemplate/search/cust_pkg_summary.cgi index cea4cdcd2..fbeeb92ce 100644 --- a/httemplate/search/cust_pkg_summary.cgi +++ b/httemplate/search/cust_pkg_summary.cgi @@ -25,7 +25,7 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" - unless $curuser->access_right('List packages'); + unless $curuser->access_right('Summarize packages'); my $title = 'Package Summary Report'; my ($begin, $end) = FS::UI::Web::parse_beginning_ending($cgi); diff --git a/httemplate/search/cust_pkg_summary.html b/httemplate/search/cust_pkg_summary.html index a0ef47210..f9adf044e 100644 --- a/httemplate/search/cust_pkg_summary.html +++ b/httemplate/search/cust_pkg_summary.html @@ -21,4 +21,8 @@ <% include('/elements/footer.html') %> <%init> + +die "access denied" + unless $curuser->access_right('Summarize packages'); + </%init> diff --git a/httemplate/search/cust_pkg_susp.cgi b/httemplate/search/cust_pkg_susp.cgi index 9ab5992d9..d6bbc43d6 100644 --- a/httemplate/search/cust_pkg_susp.cgi +++ b/httemplate/search/cust_pkg_susp.cgi @@ -25,7 +25,7 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" - unless $curuser->access_right('List packages'); + unless $curuser->access_right('Summarize packages'); my $money_char = FS::Conf->new()->config('money_char') || '$'; diff --git a/httemplate/search/cust_pkg_susp.html b/httemplate/search/cust_pkg_susp.html index c59e6c158..2ac643260 100644 --- a/httemplate/search/cust_pkg_susp.html +++ b/httemplate/search/cust_pkg_susp.html @@ -21,4 +21,8 @@ <% include('/elements/footer.html') %> <%init> + +die "access denied" + unless $curuser->access_right('Summarize packages'); + </%init> diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html index af0c8fc09..53167c26e 100644 --- a/httemplate/search/elements/search-html.html +++ b/httemplate/search/elements/search-html.html @@ -130,7 +130,9 @@ </TD> -% unless ( $opt{'disable_download'} || $type eq 'html-print' ) { +% if ( $curuser->access_right('Download report data') +% and !$opt{'disable_download'} +% and $type ne 'html-print' ) { <TD ALIGN="right"> @@ -470,6 +472,8 @@ % } <%init> +my $curuser = $FS::CurrentUser::CurrentUser; + my %args = @_; my $type = $args{'type'}; my $header = $args{'header'}; diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index 81ec4d082..9bc66b6fa 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -170,7 +170,6 @@ Example: % <% include('search-csv.html', header=>$header, rows=>$rows, opt=>\%opt ) %> % -% #} elsif ( $type eq 'excel' ) { % } elsif ( $type =~ /\.xls$/ ) { % <% include('search-xls.html', header=>$header, rows=>$rows, opt=>\%opt ) %> @@ -179,7 +178,7 @@ Example: % <% include('search-xml.html', rows=>$rows, opt=>\%opt ) %> % -% } else { # regular HTML +% } else { % <% include('search-html.html', type => $type, @@ -205,6 +204,11 @@ my $curuser = $FS::CurrentUser::CurrentUser; my $type = $cgi->param('_type') =~ /^(csv|\w*\.xls|xml|select|html(-print)?)$/ ? $1 : 'html' ; +if ( !$curuser->access_right('Download report data') ) { + $opt{'disable_download'} = 1; + $type = 'html'; +} + my %align = ( 'l' => 'left', 'r' => 'right', @@ -363,6 +367,8 @@ unless ( $type =~ /^(csv|\w*.xls)$/) { $maxrecords ||= $confmax; } + $opt{'disable_maxselect'} ||= $conf->exists('disable_maxselect'); + $limit = $maxrecords ? "LIMIT $maxrecords" : ''; $offset = $cgi->param('offset') =~ /^(\d+)$/ ? $1 : 0; diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html index 3da59c2ac..e47bbb1e5 100755 --- a/httemplate/search/report_cust_pkg.html +++ b/httemplate/search/report_cust_pkg.html @@ -88,23 +88,35 @@ %> % } - + <TR> + <TD COLSPAN=2> + <TABLE> + <TR> + <TD></TD> + <TD>From date <i>(m/d/y)</i></TD> + <TD>To date <i>(m/d/y)</i></TD> + </TR> +% my $noinit = 0; % foreach my $field (@date_fields) { - <TR> - <TD ALIGN="right" VALIGN="center"><% $label{$field} %></TD> - <TD> - <TABLE> - <% include( '/elements/tr-input-beginning_ending.html', - prefix => $field, - layout => 'horiz', - ) - %> - </TABLE> - </TD> - </TR> - -% } + <TR> + <TD ALIGN="right" VALIGN="center"><% $label{$field} %></TD> +% foreach (qw(beginning ending)) { + <TD> + <& /elements/input-date-field.html, { + 'name' => $field.'_'.$_, + 'value' => '', + 'noinit' => $noinit, + 'format' => '%m/%d/%Y', + } &> + </TD> +% $noinit = 1; +% } + </TR> +% } #foreach $field + </TABLE> + </TD> + </TR> <SCRIPT TYPE="text/javascript"> @@ -186,6 +198,7 @@ tie my %label, 'Tie::IxHash', 'dundate' => 'Suspension delayed until', 'expire' => 'Expires', 'contract_end' => 'Contract ends', + 'change_date' => 'Changed from other package', 'cancel' => 'Cancelled', ; my @date_fields = keys %label; diff --git a/httemplate/search/report_rt_ticket.html b/httemplate/search/report_rt_ticket.html index 79a601b4b..f0d7a4200 100644 --- a/httemplate/search/report_rt_ticket.html +++ b/httemplate/search/report_rt_ticket.html @@ -6,10 +6,20 @@ <% include ( '/elements/tr-input-beginning_ending.html' ) %> + <& /elements/tr-td-label.html, label => 'Time category:' &> + <TD> + <& /elements/select-rt-customfield.html, + name => 'cfname', + lookuptype => 'RT::Transaction', + valuetype => 'TimeValue', + empty_label => 'Worked', + &> + </TD></TR> + <% include ( '/elements/tr-select-otaker.html' ) %> <TR> - <TD>Account</TD> + <TD ALIGN="right">Account:</TD> <TD> <SELECT NAME="svcnum"> <OPTION VALUE="">(all) @@ -48,4 +58,24 @@ if ( @pkgparts ) { }); } +# get a list of TimeValue-type custom fields +RT::Init(); +my $CurrentUser = RT::CurrentUser->new(); +$CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username); +die "RT not configured" unless $CurrentUser->id; +my $CFs = RT::CustomFields->new($CurrentUser); + +$CFs->Limit(FIELD => 'LookupType', + OPERATOR => 'ENDSWITH', + VALUE => 'RT::Transaction'); + +$CFs->Limit(FIELD => 'Type', + VALUE => 'TimeValue'); + +my @time_fields = ('', 'Worked'); +while (my $CF = $CFs->Next) { + push @time_fields, $CF->Name, ($CF->Description || $CF->Name); +} + + </%init> diff --git a/httemplate/search/report_rt_transaction.html b/httemplate/search/report_rt_transaction.html index 0232b8070..b8454d968 100644 --- a/httemplate/search/report_rt_transaction.html +++ b/httemplate/search/report_rt_transaction.html @@ -6,6 +6,17 @@ <% include ( '/elements/tr-input-beginning_ending.html' ) %> + <& /elements/tr-td-label.html, label => 'Time category:' &> + <TD> + <& /elements/select-rt-customfield.html, + name => 'cfname', + lookuptype => 'RT::Transaction', + valuetype => 'TimeValue', + empty_label => 'Worked', + &> + </TD></TR> + + <% include ( '/elements/tr-select-otaker.html' ) %> <% include ( '/elements/tr-input-text.html', @@ -15,7 +26,7 @@ %> <TR> - <TD>Account</TD> + <TD ALIGN="right">Account:</TD> <TD> <SELECT NAME="svcnum"> <OPTION VALUE="">(all) diff --git a/httemplate/search/rt_ticket.html b/httemplate/search/rt_ticket.html index abe13157d..1ed5a3883 100644 --- a/httemplate/search/rt_ticket.html +++ b/httemplate/search/rt_ticket.html @@ -3,25 +3,27 @@ 'name_singular' => 'ticket', 'query' => $query, 'count_query' => $count_query, - 'count_addl' => [ $format_seconds_sub, $format_seconds_sub, ], + 'count_addl' => [ $format_seconds_sub, + $applied_time ? $format_seconds_sub : () ], 'header' => [ 'Ticket #', 'Ticket', 'Time', - 'Applied', + $applied_time ? 'Applied' : (), ], 'fields' => [ 'ticketid', sub { encode_entities(shift->get('subject')) }, - sub { my $seconds = shift->get('transaction_time'); + sub { my $seconds = shift->get('ticket_time'); &{ $format_seconds_sub }( $seconds ); }, - sub { my $seconds = shift->get('support'); + ($applied_time ? + sub { my $seconds = shift->get('applied_time'); &{ $format_seconds_sub }( $seconds ); - }, + } : () ), ], 'sort_fields' => [ 'ticketid', 'subject', 'transaction_time', - 'support_time', + $applied_time ? 'applied_time' : (), ], 'links' => [ $link, @@ -48,34 +50,87 @@ $seconds)%3600)/60)."m"; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); +local $FS::Record::nowarn_classload = 1; + #some amount of false laziness w/timeworked.html... -my $transactiontime = " - CASE transactions.type when 'Set' - THEN (to_number(newvalue,'999999')-to_number(oldvalue, '999999')) * 60 - ELSE timetaken*60 - END -"; +my @select = ( + 'Tickets.Id AS ticketid', + 'Tickets.Subject' +); +my @select_total = ( 'COUNT(*)' ); +my ($transaction_time, $applied_time); my $join = 'JOIN Users ON Transactions.Creator = Users.Id '; #. -# 'LEFT JOIN acct_rt_transaction '. -# ' ON Transactions.Id = acct_rt_transaction.transaction_id'; my $twhere = " - WHERE objecttype='RT::Ticket' - AND Transactions.ObjectId = Tickets.Id + WHERE Transactions.ObjectType = 'RT::Ticket' + AND Transactions.ObjectId = Tickets.Id +"; + +my $cfname = ''; +if ( $cgi->param('cfname') =~ /^\w(\w|\s)*$/ ) { + + $cfname = $cgi->param('cfname'); + + $transaction_time = "(CASE Transactions.Type + WHEN 'CustomField' THEN + ( coalesce(to_number(ocfv_new.Content,'999999'),0) + - coalesce(to_number(ocfv_old.Content,'999999'),0) ) + ELSE ( to_number(ocfv_main.Content,'999999') ) + END) * 60"; + + $join .= " + LEFT JOIN ObjectCustomFieldValues ocfv_new + ON ( ocfv_new.Id = Transactions.NewReference ) + LEFT JOIN ObjectCustomFieldValues ocfv_old + ON ( ocfv_old.Id = Transactions.OldReference ) + LEFT JOIN ObjectCustomFieldValues ocfv_main + ON ( ocfv_main.ObjectType = 'RT::Transaction' + AND ocfv_main.ObjectId = Transactions.Id ) + JOIN CustomFields + ON ( ( CustomFields.LookupType = 'RT::Queue-RT::Ticket-RT::Transaction' + AND CustomFields.Id = ocfv_main.CustomField + AND ocfv_main.Id IS NOT NULL + ) + OR + ( CustomFields.LookupType = 'RT::Queue-RT::Ticket' + AND (CustomFields.Id = ocfv_new.CustomField OR ocfv_new.Id IS NULL) + AND (CustomFields.Id = ocfv_old.CustomField OR ocfv_old.Id IS NULL) + AND ocfv_main.Id IS NULL + ) ) + "; + + $twhere .= " AND CustomFields.Name = '$cfname' + AND (ocfv_new.Id IS NOT NULL OR ocfv_old.Id IS NOT NULL OR ocfv_main.Id IS NOT NULL)"; + +} +else { + $transaction_time = " + CASE transactions.type when 'Set' + THEN (to_number(newvalue,'999999')-to_number(oldvalue, '999999')) * 60 + ELSE timetaken*60 + END"; + + my $applied = ''; + if ( $cgi->param('svcnum') =~ /^\s*(\d+)\s*$/ ) { + $twhere .= " AND EXISTS( SELECT 1 FROM acct_rt_transaction WHERE acct_rt_transaction.transaction_id = Transactions.id AND svcnum = $1 )"; + $applied = "AND svcnum = $1"; + } + + $twhere .= " AND ( ( Transactions.Type = 'Set' AND Transactions.Field = 'TimeWorked' AND Transactions.NewValue != Transactions.OldValue ) OR ( ( Transactions.Type='Create' OR Transactions.Type='Comment' OR Transactions.Type='Correspond' OR Transactions.Type='Touch' ) AND Transactions.TimeTaken > 0 ) - ) -"; -#AND transaction_time != 0 -#AND $wheretimeleft + )"; + + $applied_time = "( SELECT SUM(support) FROM acct_rt_transaction LEFT JOIN Transactions ON ( transaction_id = Id ) $twhere $applied )"; + +} -my $support = ''; my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); # TIMESTAMP is Pg-specific... ? @@ -92,25 +147,21 @@ if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) { $twhere .= " AND Users.name = '$1' "; } -if ( $cgi->param('svcnum') =~ /^\s*(\d+)\s*$/ ) { - $twhere .= " AND EXISTS( SELECT 1 FROM acct_rt_transaction WHERE acct_rt_transaction.transaction_id = Transactions.id AND svcnum = $1 )"; - $support = "AND svcnum = $1"; -} - my $transactions = "FROM Transactions $join $twhere"; my $where = "WHERE EXISTS ( SELECT 1 $transactions )"; -my $transaction_time = "( SELECT SUM($transactiontime) $transactions )"; -my $support_time = "( SELECT SUM(support) FROM acct_rt_transaction LEFT JOIN Transactions ON ( transaction_id = Id ) $twhere $support )"; +my $ticket_time = "( SELECT SUM($transaction_time) $transactions )"; +push @select, "$ticket_time AS ticket_time"; +push @select_total, "SUM($ticket_time)"; + +if ( $applied_time) { + push @select, "$applied_time AS applied_time"; + push @select_total, "SUM($applied_time)"; +} my $query = { - 'select' => join(', ', - 'Tickets.Id AS ticketid', - 'Tickets.Subject', - "$transaction_time AS transaction_time", - "$support_time AS support", - ), + 'select' => join(', ', @select), 'table' => 'tickets', #Pg-ism #'table' => 'Tickets', 'addl_from' => '', #$join, @@ -118,13 +169,9 @@ my $query = { 'order by' => 'ORDER BY Created', }; -my $count_query = +my $count_query = "SELECT ".join(', ', @select_total)." FROM Tickets $where"; #"SELECT COUNT(*), SUM($transactiontime), SUM(acct_rt_transaction.support) FROM Transactions $join $where"; #"SELECT COUNT(*), ( SUM($transactiontime) $transactions ) FROM Tickets"; # $join $where"; - "SELECT COUNT(*), - SUM( $transaction_time ), - SUM( $support_time ) - FROM Tickets $where"; # $join $where"; my $link = [ "${p}rt/Ticket/Display.html?id=", sub { shift->get('ticketid'); } ]; diff --git a/httemplate/search/rt_transaction.html b/httemplate/search/rt_transaction.html index be9cd0bc9..1ae607be1 100644 --- a/httemplate/search/rt_transaction.html +++ b/httemplate/search/rt_transaction.html @@ -3,12 +3,13 @@ 'name_singular' => 'transaction', 'query' => $query, 'count_query' => $count_query, - 'count_addl' => [ $format_seconds_sub, $format_seconds_sub, ], + 'count_addl' => [ $format_seconds_sub, + $applied_time ? $format_seconds_sub : () ], 'header' => [ 'Ticket #', 'Ticket', 'Date', 'Time', - 'Applied', + $applied_time ? 'Applied' : (), ], 'fields' => [ 'ticketid', sub { encode_entities(shift->get('subject')) }, @@ -16,9 +17,10 @@ sub { my $seconds = shift->get('transaction_time'); &{ $format_seconds_sub }( $seconds ); }, - sub { my $seconds = shift->get('support'); + ($applied_time ? + sub { my $seconds = shift->get('applied_time'); &{ $format_seconds_sub }( $seconds ); - }, + } : () ), ], 'links' => [ $link, @@ -44,21 +46,81 @@ $seconds)%3600)/60)."m"; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); +local $FS::Record::nowarn_classload = 1; #some amount of false laziness w/timeworked.html... -my $transactiontime = " - CASE Transactions.Type when 'Set' - THEN (to_number(NewValue,'999999')-to_number(OldValue, '999999')) * 60 - ELSE TimeTaken*60 - END -"; +my @select = ( + 'Transactions.*', + 'Tickets.Id AS ticketid', + 'Tickets.Subject', + 'Users.name AS otaker', +); +my @select_total = ( 'COUNT(*)' ); +my ($transaction_time, $applied_time); my $join = 'JOIN Tickets ON Transactions.ObjectId = Tickets.Id '. 'JOIN Users ON Transactions.Creator = Users.Id '; #. -# 'LEFT JOIN acct_rt_transaction '. -# ' ON Transactions.Id = acct_rt_transaction.transaction_id'; -my $where = " - WHERE objecttype='RT::Ticket' + +my $where = "WHERE Transactions.ObjectType = 'RT::Ticket'"; + +my $cfname = ''; +if ( $cgi->param('cfname') =~ /^\w(\w|\s)*$/ ) { + + # a TimeValue-type custom field + $cfname = $cgi->param('cfname'); + + $transaction_time = "(CASE Transactions.Type + WHEN 'CustomField' THEN + ( coalesce(to_number(ocfv_new.Content,'999999'),0) + - coalesce(to_number(ocfv_old.Content,'999999'),0) ) + ELSE ( to_number(ocfv_main.Content,'999999') ) + END) * 60"; + + # complicated because we have to deal with the case of editing the + # ticket custom field directly (OldReference/NewReference) as well as + # entering a transaction with a custom field value (ObjectId) + $join .= " + LEFT JOIN ObjectCustomFieldValues ocfv_new + ON ( ocfv_new.Id = Transactions.NewReference ) + LEFT JOIN ObjectCustomFieldValues ocfv_old + ON ( ocfv_old.Id = Transactions.OldReference ) + LEFT JOIN ObjectCustomFieldValues ocfv_main + ON ( ocfv_main.ObjectType = 'RT::Transaction' + AND ocfv_main.ObjectId = Transactions.Id ) + JOIN CustomFields + ON ( ( CustomFields.LookupType = 'RT::Queue-RT::Ticket-RT::Transaction' + AND CustomFields.Id = ocfv_main.CustomField + AND ocfv_main.Id IS NOT NULL + ) + OR + ( CustomFields.LookupType = 'RT::Queue-RT::Ticket' + AND (CustomFields.Id = ocfv_new.CustomField OR ocfv_new.Id IS NULL) + AND (CustomFields.Id = ocfv_old.CustomField OR ocfv_old.Id IS NULL) + AND ocfv_main.Id IS NULL + ) ) + "; + + $where .= " AND CustomFields.Name = '$cfname' + AND (ocfv_new.Id IS NOT NULL OR ocfv_old.Id IS NOT NULL OR ocfv_main.Id IS NOT NULL)"; + +} +else { + + # the intrinsic TimeWorked/TimeTaken fields + $transaction_time = "CASE Transactions.Type when 'Set' + THEN (to_number(NewValue,'999999')-to_number(OldValue, '999999')) * 60 + ELSE TimeTaken*60 + END"; + + my $applied = ''; + if ( $cgi->param('svcnum') =~ /^\s*(\d+)\s*$/ ) { + $where .= " AND EXISTS( SELECT 1 FROM acct_rt_transaction WHERE acct_rt_transaction.transaction_id = Transactions.id AND svcnum = $1 )"; + $applied = "AND svcnum = $1"; + } + + $applied_time = "( SELECT SUM(support) from acct_rt_transaction where transaction_id = Transactions.id $applied )"; + + $where .= " AND ( ( Transactions.Type = 'Set' AND Transactions.Field = 'TimeWorked' AND Transactions.NewValue != Transactions.OldValue ) @@ -66,11 +128,17 @@ my $where = " AND Transactions.TimeTaken > 0 ) ) -"; + "; + +} #AND transaction_time != 0 #AND $wheretimeleft - -my $support = ''; +push @select, "($transaction_time) AS transaction_time"; +push @select_total, "SUM($transaction_time)"; +if ( $applied_time ) { + push @select, "($applied_time) AS applied_time"; + push @select_total, "SUM($applied_time)"; +} my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); # TIMESTAMP is Pg-specific... ? @@ -91,22 +159,8 @@ if ( $cgi->param('ticketid') =~ /^\s*(\d+)\s*$/ ) { $where .= " AND Tickets.Id = $1"; } -if ( $cgi->param('svcnum') =~ /^\s*(\d+)\s*$/ ) { - $where .= " AND EXISTS( SELECT 1 FROM acct_rt_transaction WHERE acct_rt_transaction.transaction_id = Transactions.id AND svcnum = $1 )"; - $support = "AND svcnum = $1"; -} - -my $support_time = "( SELECT SUM(support) from acct_rt_transaction where transaction_id = Transactions.id $support )"; - my $query = { - 'select' => join(', ', - 'Transactions.*', - 'Tickets.Id AS ticketid', - 'Tickets.Subject', - 'Users.name AS otaker', - "$transactiontime AS transaction_time", - "$support_time AS support", - ), + 'select' => join(', ', @select), 'table' => 'transactions', #Pg-ism #'table' => 'Transactions', 'addl_from' => $join, @@ -114,12 +168,7 @@ my $query = { 'order by' => 'ORDER BY Created', }; -my $count_query = - #"SELECT COUNT(*), SUM($transactiontime), SUM(acct_rt_transaction.support) FROM Transactions $join $where"; - "SELECT COUNT(*), - SUM($transactiontime), - SUM($support_time) - FROM Transactions $join $where"; +my $count_query = 'SELECT '.join(', ', @select_total). " FROM Transactions $join $where"; my $link = [ "${p}rt/Ticket/Display.html?id=", sub { shift->get('ticketid'); } ]; diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi index fda4db0d9..9ce55b0b9 100755 --- a/httemplate/view/cust_main.cgi +++ b/httemplate/view/cust_main.cgi @@ -232,9 +232,9 @@ function areyousure(href, message) { % } % if ( $view eq 'jumbo' ) { - <BR><BR> - <A NAME="tickets"><FONT SIZE="+2"><% mt('Tickets') |h %></FONT></A><BR> + <BR> % } +<BR> % if ( $view eq 'tickets' || $view eq 'jumbo' ) { diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index c453ffadc..c7a7c8024 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -500,14 +500,15 @@ foreach my $cust_refund ($cust_main->cust_refund) { sub translate_payby { my ($payby,$payinfo) = (shift,shift); my %payby = ( + FS::payby->payby2shortname, BILL => $payinfo ? emt('Check #') : '', CHEK => emt('Electronic check '), PREP => emt('Prepaid card '), CARD => emt('Credit card #'), COMP => emt('Complimentary by '), - CASH => emt('Cash'), - WEST => emt('Western Union'), - MCRD => emt('Manual credit card'), + #CASH => emt('Cash'), + #WEST => emt('Western Union'), + #MCRD => emt('Manual credit card'), ); $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby; $payby; @@ -516,6 +517,7 @@ sub translate_payby { sub translate_payby_refund { my ($payby,$payinfo) = (shift,shift); my %payby = ( + FS::payby->payby2shortname, BILL => $payinfo ? emt('Check #') : emt('Check'), CHEK => emt('Electronic check '), CARD => emt('Credit card #'), diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html index adfaead6e..194e90742 100644 --- a/httemplate/view/cust_main/tickets.html +++ b/httemplate/view/cust_main/tickets.html @@ -9,6 +9,7 @@ function updateTicketLink() { keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value; } </SCRIPT> +<A NAME="tickets"><FONT CLASS="fsinnerbox-title">Tickets</FONT></A> <A id="CreateTicketLink" HREF="<% $new_link %>"><% mt('Create new ticket') |h %></A> <% mt('in queue') |h %> %# fetch list of queues in which the user can create tickets @@ -28,12 +29,12 @@ function updateTicketLink() { <SCRIPT DEFER TYPE="text/javascript">updateTicketLink();</SCRIPT> % } </FORM> + | +View +<A HREF="<% $open_link %>"><% mt($openlabel) |h %></A> | +<A HREF="<% $res_link %>"><% mt('resolved') |h %></A> <BR> -(<A HREF="<% $open_link %>"><% mt("View $openlabel tickets for this customer") |h %></A>) -(<A HREF="<% $res_link %>"><% mt('View resolved tickets for this customer') |h %></A>) -<BR><BR> - <& /elements/table-grid.html &> % my $bgcolor1 = '#eeeeee'; % my $bgcolor2 = '#ffffff'; diff --git a/httemplate/view/logo-agent.cgi b/httemplate/view/logo-agent.cgi new file mode 100755 index 000000000..0f654140c --- /dev/null +++ b/httemplate/view/logo-agent.cgi @@ -0,0 +1,10 @@ +<% $conf->config_binary("logo.png", $agentnum) %> +<%init> + +my $conf = new FS::Conf; + +my $agentnum = $cgi->param('agentnum'); + +http_header('Content-Type' => 'image/png' ); + +</%init> |