summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2012-04-14 15:36:20 -0700
committerMark Wells <mark@freeside.biz>2012-04-14 15:36:20 -0700
commit5371ad9dd6bf63e29ffc85c8381f1ea2630b1749 (patch)
tree0109d86cf93a84697fe84fc29927a9f058cf3d93
parent5abe1300f16cf01a6311203deee29373162491c7 (diff)
parent401ba3ab0637de1bc460370949b75dca092375d8 (diff)
merge master
-rw-r--r--FS/FS/AccessRight.pm5
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm13
-rw-r--r--FS/FS/ClientAPI_XMLRPC.pm19
-rw-r--r--FS/FS/Conf.pm64
-rw-r--r--FS/FS/GeocodeCache.pm209
-rw-r--r--FS/FS/Report/Table.pm21
-rw-r--r--FS/FS/Report/Table/Monthly.pm31
-rw-r--r--FS/FS/Schema.pm13
-rw-r--r--FS/FS/Upgrade.pm1
-rw-r--r--FS/FS/access_right.pm45
-rw-r--r--FS/FS/cdr/cia.pm4
-rw-r--r--FS/FS/cust_bill.pm17
-rw-r--r--FS/FS/cust_main.pm24
-rw-r--r--FS/FS/cust_main/Search.pm36
-rw-r--r--FS/FS/cust_pay_void.pm30
-rw-r--r--FS/FS/cust_pkg.pm2
-rw-r--r--FS/FS/part_event/Condition.pm4
-rw-r--r--FS/FS/part_event/Condition/cust_bill_has_service.pm4
-rw-r--r--FS/FS/payby.pm5
-rwxr-xr-xFS/FS/svc_broadband.pm9
-rw-r--r--FS/FS/upgrade_journal.pm151
-rw-r--r--FS/MANIFEST2
-rw-r--r--FS/t/GeocodeCache.t5
-rw-r--r--FS/t/upgrade_journal.t5
-rwxr-xr-xbin/part_pkg-bulk_change72
-rw-r--r--fs_selfservice/FS-SelfService/SelfService.pm1
-rw-r--r--fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm20
-rw-r--r--httemplate/docs/about.html50
-rw-r--r--httemplate/docs/credits.html7
-rw-r--r--httemplate/edit/process/svc_broadband.cgi6
-rw-r--r--httemplate/elements/customer-table.html298
-rw-r--r--httemplate/elements/freeside.css28
-rw-r--r--httemplate/elements/menu.html27
-rw-r--r--httemplate/elements/select-rt-customfield.html34
-rw-r--r--httemplate/elements/table-grid.html13
-rw-r--r--httemplate/elements/tr-select-from_to.html2
-rw-r--r--httemplate/elements/tr-select-router_block_ip.html33
-rw-r--r--httemplate/graph/cust_signup.html29
-rw-r--r--httemplate/graph/elements/monthly.html9
-rw-r--r--httemplate/graph/report_cust_bill_pkg.html6
-rw-r--r--httemplate/graph/report_cust_signup.html6
-rw-r--r--httemplate/misc/batch-cust_pay.html24
-rw-r--r--httemplate/misc/process/batch-cust_pay.cgi2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-discount_terms.cgi19
-rw-r--r--httemplate/misc/xmlhttp-cust_main-search.cgi8
-rwxr-xr-xhttemplate/search/cust_main.cgi2
-rwxr-xr-xhttemplate/search/cust_pkg.cgi11
-rw-r--r--httemplate/search/cust_pkg_summary.cgi2
-rw-r--r--httemplate/search/cust_pkg_summary.html4
-rw-r--r--httemplate/search/cust_pkg_susp.cgi2
-rw-r--r--httemplate/search/cust_pkg_susp.html4
-rw-r--r--httemplate/search/elements/search-html.html6
-rw-r--r--httemplate/search/elements/search.html10
-rwxr-xr-xhttemplate/search/report_cust_pkg.html43
-rw-r--r--httemplate/search/report_rt_ticket.html32
-rw-r--r--httemplate/search/report_rt_transaction.html13
-rw-r--r--httemplate/search/rt_ticket.html125
-rw-r--r--httemplate/search/rt_transaction.html125
-rwxr-xr-xhttemplate/view/cust_main.cgi4
-rw-r--r--httemplate/view/cust_main/payment_history.html8
-rw-r--r--httemplate/view/cust_main/tickets.html9
-rwxr-xr-xhttemplate/view/logo-agent.cgi10
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">&copy; 2011 Freeside Internet Services, Inc.<BR>
+% if ( $agentnum ) {
+ <BR><BR>
+ <FONT SIZE="-2">Based on Freeside version <% $FS::VERSION %><BR>
+% } else {
+ <FONT SIZE="-1">
+% }
+&copy; 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>
&nbsp;&nbsp;&nbsp;&nbsp;
<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] + ' &nbsp; ';
- 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] + ' &nbsp; ';
- 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] + ' &nbsp; ';
- 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) + ' &nbsp; ';
- 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>
+ &nbsp;
+ <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>
- &nbsp;
- </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)) //&nbsp;
+ );
+
+ 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>