summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2013-09-03 22:52:26 -0700
committerIvan Kohler <ivan@freeside.biz>2013-09-03 22:52:26 -0700
commit36ad5e538cb56de33c779e34baf9abdf63c4312e (patch)
treede42d922496c881b69643656f055b68e64e96d72
parent04c1a98c9bcf2b6f0da8f18b627eec703dbafd49 (diff)
parentaae697565c071b2880d0106b00a4a01d0ddab7bf (diff)
Merge branch 'master' of git.freeside.biz:/home/git/freeside
-rw-r--r--FS/FS/Conf.pm6
-rw-r--r--FS/FS/Cron/alert_expiration.pm190
-rw-r--r--FS/FS/cdr.pm8
-rw-r--r--FS/FS/msg_template.pm58
-rw-r--r--FS/FS/part_event/Condition/cust_paydate_within.pm11
-rwxr-xr-xFS/bin/freeside-daily9
-rw-r--r--httemplate/edit/elements/edit.html2
-rw-r--r--httemplate/edit/msg_template.html7
-rw-r--r--httemplate/elements/ckeditor/plugins/blockprotect/plugin.js128
-rw-r--r--httemplate/elements/htmlarea.html24
-rw-r--r--httemplate/elements/onload.js13
-rwxr-xr-xhttemplate/search/477partIA.html6
-rwxr-xr-xhttemplate/search/477partIIA.html2
-rwxr-xr-xhttemplate/search/477partIIB.html2
-rwxr-xr-xhttemplate/search/477partVI_census.html2
-rw-r--r--rt/lib/RT/Interface/Web_Vendor.pm14
16 files changed, 253 insertions, 229 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 065fd8c4e..476d490d2 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -779,8 +779,8 @@ sub reason_type_options {
{
'key' => 'alert_expiration',
- 'section' => 'notification',
- 'description' => 'Enable alerts about billing method expiration (i.e. expiring credit cards).',
+ 'section' => 'deprecated',
+ 'description' => 'Enable alerts about credit card expiration. This is obsolete and no longer works.',
'type' => 'checkbox',
'per_agent' => 1,
},
@@ -795,7 +795,7 @@ sub reason_type_options {
{
'key' => 'alerter_msgnum',
- 'section' => 'notification',
+ 'section' => 'deprecated',
'description' => 'Template to use for credit card expiration alerts.',
%msg_template_options,
},
diff --git a/FS/FS/Cron/alert_expiration.pm b/FS/FS/Cron/alert_expiration.pm
deleted file mode 100644
index 5961e6155..000000000
--- a/FS/FS/Cron/alert_expiration.pm
+++ /dev/null
@@ -1,190 +0,0 @@
-package FS::Cron::alert_expiration;
-
-use vars qw( @ISA @EXPORT_OK);
-use Exporter;
-use FS::Record qw(qsearch qsearchs);
-use FS::Conf;
-use FS::cust_main;
-use FS::Misc;
-use Time::Local;
-use Date::Parse qw(str2time);
-
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw( alert_expiration );
-
-my $warning_time = 30 * 24 * 60 * 60;
-my $urgent_time = 15 * 24 * 60 * 60;
-my $panic_time = 5 * 24 * 60 * 60;
-my $window_time = 24 * 60 * 60;
-
-sub alert_expiration {
- my $conf = new FS::Conf;
- my $smtpmachine = $conf->config('smtpmachine');
-
- my %opt = @_;
- my ($_date) = $opt{'d'} ? str2time($opt{'d'}) : $^T;
- $_date += $opt{'y'} * 86400 if $opt{'y'};
- my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($_date)) [0..5];
- $mon++;
-
- my $debug = 0;
- $debug = 1 if $opt{'v'};
- $debug = $opt{'l'} if $opt{'l'};
-
- $FS::cust_main::DEBUG = $debug;
-
- # Get a list of customers.
-
- my %limit;
- $limit{'agentnum'} = $opt{'a'} if $opt{'a'};
- $limit{'payby'} = $opt{'p'} if $opt{'p'};
-
- my @customers;
-
- if(my @custnums = @ARGV) {
- # We're given an explicit list of custnums, so select those. Then check against
- # -a and -p to avoid doing anything unexpected.
- foreach (@custnums) {
- my $customer = FS::cust_main->by_key($_);
- if($customer and (!$opt{'a'} or $customer->agentnum == $opt{'a'})
- and (!$opt{'p'} or $customer->payby eq $opt{'p'}) ) {
- push @customers, $customer;
- }
- }
- }
- else { # no @ARGV
- @customers = qsearch('cust_main', \%limit);
- }
- return if(!@customers);
- foreach my $customer (@customers) {
- next if !($customer->ncancelled_pkgs); # skip inactive customers
- my $paydate = $customer->paydate;
- next if $paydate =~ /^\s*$/; # skip empty expiration dates
-
- my $custnum = $customer->custnum;
- my $first = $customer->first;
- my $last = $customer->last;
- my $company = $customer->company;
- my $payby = $customer->payby;
- my $payinfo = $customer->payinfo;
- my $daytime = $customer->daytime;
- my $night = $customer->night;
-
- my ($paymonth, $payyear) = $customer->paydate_monthyear;
- $paymonth--; # localtime() convention
- $payday = 1; # This is enforced by FS::cust_main::check.
- my $expire_time;
- if($payby eq 'CARD' || $payby eq 'DCRD') {
- # Credit cards expire at the end of the month/year.
- if($paymonth == 11) {
- $payyear++;
- $paymonth = 0;
- } else {
- $paymonth++;
- }
- $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear) - 1;
- }
- else {
- $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
- }
-
- if (grep { $expire_time < $_date + $_ &&
- $expire_time > $_date + $_ - $window_time }
- ($warning_time, $urgent_time, $panic_time) ) {
- # Send an expiration notice.
- my $agentnum = $customer->agentnum;
- my $error = '';
-
- my $msgnum = $conf->config('alerter_msgnum', $agentnum);
- if ( $msgnum ) { # new hotness
- my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } );
- $customer->setfield('expdate', $expire_time);
- $error = $msg_template->send('cust_main' => $customer,
- 'object' => $customer);
- }
- else { #!$msgnum, the hard way
- $mail_sender = $conf->config('invoice_from', $agentnum);
- $failure_recipient = $conf->config('invoice_from', $agentnum)
- || 'postmaster';
-
- my @alerter_template = $conf->config('alerter_template', $agentnum)
- or die 'cannot load config file alerter_template';
-
- my $alerter = new Text::Template(TYPE => 'ARRAY',
- SOURCE => [
- map "$_\n", @alerter_template
- ])
- or die "can't create Text::Template object: $Text::Template::ERROR";
-
- $alerter->compile()
- or die "can't compile template: $Text::Template::ERROR";
-
- my @invoicing_list = $customer->invoicing_list;
- my @to_addrs = grep { $_ ne 'POST' } @invoicing_list;
- if(@to_addrs) {
- # Set up template fields.
- my %fill_in;
- $fill_in{$_} = $customer->getfield($_)
- foreach(qw(first last company));
- $fill_in{'expdate'} = $expire_time;
- $fill_in{'company_name'} = $conf->config('company_name', $agentnum);
- $fill_in{'company_address'} =
- join("\n",$conf->config('company_address',$agentnum))."\n";
- if($payby eq 'CARD' || $payby eq 'DCRD') {
- $fill_in{'payby'} = "credit card (".
- substr($customer->payinfo, 0, 2) . "xxxxxxxxxx" .
- substr($payinfo, -4) . ")";
- }
- elsif($payby eq 'COMP') {
- $fill_in{'payby'} = 'complimentary account';
- }
- else {
- $fill_in{'payby'} = 'current method';
- }
- # Send it already!
- $error = FS::Misc::send_email (
- from => $mail_sender,
- to => [ @to_addrs ],
- subject => 'Billing Arrangement Expiration',
- body => [ $alerter->fill_in( HASH => \%fill_in ) ],
- );
- }
- else { # if(@to_addrs)
- push @{$agent_failure_body{$customer->agentnum}},
- sprintf(qq{%5d %-32.32s %4s %10s %12s %12s},
- $custnum,
- $first . " " . $last . " " . $company,
- $payby,
- $paydate,
- $daytime,
- $night );
- }
- } # if($msgnum)
-
-# should we die here rather than report failure as below?
- die "can't send expiration alert: $error"
- if $error;
-
- } # if(expired)
- } # foreach(@customers)
-
- # Failure notification
- foreach my $agentnum (keys %agent_failure_body) {
- $mail_sender = $conf->config('invoice_from', $agentnum)
- if($conf->exists('invoice_from', $agentnum));
- $failure_recipient = $conf->config('invoice_from', $agentnum)
- if($conf->exists('invoice_from', $agentnum));
- my $error = FS::Misc::send_email (
- from => $mail_sender,
- to => $failure_recipient,
- subject => 'Unnotified Billing Arrangement Expirations',
- body => [ @{$agent_failure_body{$agentnum}} ],
- );
- die "can't send alerter failure email to $failure_recipient: $error"
- if $error;
- }
-
-}
-
-1;
diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm
index e127e8bc5..9d72c39e4 100644
--- a/FS/FS/cdr.pm
+++ b/FS/FS/cdr.pm
@@ -932,8 +932,12 @@ sub rate_prefix {
}
#should preserve (display?) this
- my $charge_min = ( $charge_sec - $conn_seconds ) / 60;
- $charge += ($rate_detail->min_charge * $charge_min) if $charge_min > 0; #still not rounded
+ if ( $granularity == 0 ) { # per call rate
+ $charge += $rate_detail->min_charge;
+ } else {
+ my $charge_min = ( $charge_sec - $conn_seconds ) / 60;
+ $charge += ($rate_detail->min_charge * $charge_min) if $charge_min > 0; #still not rounded
+ }
}
diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm
index bef2b4b55..4e1f4da24 100644
--- a/FS/FS/msg_template.pm
+++ b/FS/FS/msg_template.pm
@@ -732,6 +732,64 @@ sub _upgrade_data {
$conf->delete($subject, $agentnum) if $subject;
}
}
+
+ if ( $conf->exists('alert_expiration', $agentnum) ) {
+ my $msgnum = $conf->exists('alerter_msgnum', $agentnum);
+ my $template = FS::msg_template->by_key($msgnum) if $msgnum;
+ if (!$template) {
+ warn "template for alerter_msgnum $msgnum not found\n";
+ next;
+ }
+ # this is now a set of billing events
+ foreach my $days (30, 15, 5) {
+ my $event = FS::part_event->new({
+ 'agentnum' => $agentnum,
+ 'event' => "Card expiration warning - $days days",
+ 'eventtable' => 'cust_main',
+ 'check_freq' => '1d',
+ 'action' => 'notice',
+ 'disabled' => 'Y', #initialize first
+ });
+ my $error = $event->insert( 'msgnum' => $msgnum );
+ if ($error) {
+ warn "error creating expiration alert event:\n$error\n\n";
+ next;
+ }
+ # make it work like before:
+ # only send each warning once before the card expires,
+ # only warn active customers,
+ # only warn customers with CARD/DCRD,
+ # only warn customers who get email invoices
+ my %conds = (
+ 'once_every' => { 'run_delay' => '30d' },
+ 'cust_paydate_within' => { 'within' => $days.'d' },
+ 'cust_status' => { 'status' => { 'active' => 1 } },
+ 'payby' => { 'payby' => { 'CARD' => 1,
+ 'DCRD' => 1, }
+ },
+ 'message_email' => {},
+ );
+ foreach (keys %conds) {
+ my $condition = FS::part_event_condition->new({
+ 'conditionname' => $_,
+ 'eventpart' => $event->eventpart,
+ });
+ $error = $condition->insert( %{ $conds{$_} });
+ if ( $error ) {
+ warn "error creating expiration alert event:\n$error\n\n";
+ next;
+ }
+ }
+ $error = $event->initialize;
+ if ( $error ) {
+ warn "expiration alert event was created, but not initialized:\n$error\n\n";
+ }
+ } # foreach $days
+ $conf->delete('alerter_msgnum', $agentnum);
+ $conf->delete('alert_expiration', $agentnum);
+
+ } # if alerter_msgnum
+
}
foreach my $msg_template ( qsearch('msg_template', {}) ) {
if ( $msg_template->subject || $msg_template->body ) {
diff --git a/FS/FS/part_event/Condition/cust_paydate_within.pm b/FS/FS/part_event/Condition/cust_paydate_within.pm
index 4808e9083..bfe3e84d2 100644
--- a/FS/FS/part_event/Condition/cust_paydate_within.pm
+++ b/FS/FS/part_event/Condition/cust_paydate_within.pm
@@ -23,7 +23,7 @@ sub eventtable_hashref {
sub option_fields {
(
- 'within' => { 'label' => 'Expiration date within',
+ 'within' => { 'label' => 'Credit card will expire within',
'type' => 'freq',
},
);
@@ -32,14 +32,17 @@ sub option_fields {
sub condition {
my( $self, $cust_main, %opt ) = @_;
my $expire_time = $cust_main->paydate_epoch or return 0;
- $opt{'time'} >= $self->option_age_from('within', $expire_time);
+ $opt{'time'} >= $self->option_age_from('within', $expire_time) and
+ $opt{'time'} < $expire_time;
}
sub condition_sql {
my ($self, $table, %opt) = @_;
my $expire_time = FS::cust_main->paydate_epoch_sql or return 'true';
- $opt{'time'} . ' >= ' .
- $self->condition_sql_option_age_from('within', $expire_time);
+ ' ( '. $opt{'time'} . ' >= ' .
+ $self->condition_sql_option_age_from('within', $expire_time) .
+ ' AND ' . $opt{'time'} . ' < ' . $expire_time . ' ) ';
+
}
1;
diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily
index b6ee5188e..14d797f49 100755
--- a/FS/bin/freeside-daily
+++ b/FS/bin/freeside-daily
@@ -38,10 +38,13 @@ upload(%opt);
use FS::Cron::set_lata_have_usage qw(set_lata_have_usage);
set_lata_have_usage(%opt);
-# Send alerts about upcoming credit card expiration.
-use FS::Cron::alert_expiration qw(alert_expiration);
+# we used to send alerts about upcoming credit card expiration here
my $conf = new FS::Conf;
-alert_expiration(%opt) if($conf->exists('alert_expiration'));
+if($conf->exists('alert_expiration')) {
+ warn "WARNING: the alert_expiration option is obsolete. If you ran
+ freeside-upgrade, it should have configured credit card expiration alerts
+ as billing events.\n";
+}
#what to do about the below when using -m? that is the question.
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index 08408297b..ed677d7ab 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -328,7 +328,7 @@ Example:
% qw( hashref agent_virt agent_null agent_null_right ),#*-table
% qw( formatted_value ), #fixed
% qw( country ), #select-country
-% qw( width height ), #htmlarea
+% qw( width height config ), #htmlarea
% qw( alt_format ), #select-cust_location
% qw( classnum ), # select-inventory_item
% ;
diff --git a/httemplate/edit/msg_template.html b/httemplate/edit/msg_template.html
index 7205ba844..06cac440e 100644
--- a/httemplate/edit/msg_template.html
+++ b/httemplate/edit/msg_template.html
@@ -67,7 +67,8 @@ if ( $curuser->access_right('Edit global templates')
{ field => 'subject', size=>60, },
{ field => 'body',
type => 'htmlarea',
- width => 763
+ width => 763,
+ config=> { extraPlugins => 'blockprotect' },
},
;
} else { #readonly
@@ -328,8 +329,8 @@ my $widget = new HTML::Widgets::SelectLayers(
my $sidebar = '
<SCRIPT TYPE="text/javascript">
function insertHtml(what) {
- var oEditor = FCKeditorAPI.GetInstance("body");
- oEditor.InsertHtml(what);
+ var oEditor = CKEDITOR.instances["body"];
+ oEditor.insertHtml(what);
};
function areyousure(url, message) {
diff --git a/httemplate/elements/ckeditor/plugins/blockprotect/plugin.js b/httemplate/elements/ckeditor/plugins/blockprotect/plugin.js
new file mode 100644
index 000000000..61c993a9f
--- /dev/null
+++ b/httemplate/elements/ckeditor/plugins/blockprotect/plugin.js
@@ -0,0 +1,128 @@
+/**
+ * The "blockprotect" plugin. Adapted from the "placeholder" plugin.
+ */
+
+(function() {
+ var delim_o = '{';
+ var delim_c = '}';
+
+ var create_block = function(content) {
+ // fix nbsp's
+ content = content.replace(/&nbsp;/gi, ' ');
+ // escape the content
+ var el = document.createElement('SPAN');
+ // IE8 compat
+ if( typeof(el.textContent) != 'undefined' ) {
+ el.textContent = content;
+ } else if( typeof(el.innerText) != 'undefined' ) {
+ el.innerText = content;
+ }
+ el.setAttribute('class', 'cke_blockprotect');
+ return el.outerHTML;
+ };
+ var block_writeHtml = function( element ) {
+ // to unescape the element contents, write it out as HTML,
+ // stick that into a SPAN element, and then extract the text
+ // content of that.
+ var inner_writer = new CKEDITOR.htmlParser.basicWriter;
+ element.writeChildrenHtml(inner_writer);
+
+ var el = document.createElement('SPAN');
+ el.innerHTML = inner_writer.getHtml();
+ if( typeof(el.textContent) != 'undefined' ) {
+ return el.textContent;
+ } else if( typeof(el.innerText) != 'undefined' ) {
+ return el.innerText;
+ }
+ };
+ var to_protected_html = function(data) {
+ var depth = 0;
+ var chunk = '';
+ var out = '';
+ var p = 0; // position in the string
+ while( 1 ) {
+ // find the next delimiter of either kind
+ var i = data.indexOf(delim_o, p);
+ var j = data.indexOf(delim_c, p);
+ if (i == -1 && j == -1) {
+ // then there are no more delimiters
+ break;
+ } else if ((i < j || j == -1) && i != -1) {
+ // next delimiter is an open
+ // push everything from current position to
+ // the delimiter
+ if ( i > p ) chunk += data.substr(p, i - p);
+ p = i + 1;
+ if ( depth == 0 ) {
+ // start of a protected block
+ out += chunk;
+ chunk = '';
+ }
+ chunk += delim_o;
+ depth++;
+ } else if ((j < i || i == -1) && j != -1) {
+ // next delimiter is a close
+ if ( j > p ) chunk += data.substr(p, j - p);
+ p = j + 1;
+ depth--;
+ chunk += delim_c
+ if ( depth == 0 ) {
+ // end of a protected block
+ out += create_block(chunk);
+ chunk = '';
+ } else if ( depth < 0 ) {
+ depth = 0;
+ }
+ } else {
+ // can't happen
+ }
+ }
+ // append any text after the last delimiter
+ if ( depth ) {
+ out += create_block(data.substr(p));
+ } else {
+ out += data.substr(p);
+ }
+ return out;
+ };
+
+ CKEDITOR.plugins.add( 'blockprotect', {
+ afterInit: function( editor ) {
+ CKEDITOR.addCss( '.cke_blockprotect' +
+ '{' +
+ 'background-color: #ffff88;' +
+ ( CKEDITOR.env.gecko ? 'cursor: default;' : '' ) +
+ '}'
+ );
+
+ // keep these from getting stripped out
+ editor.filter.allow('span(cke_blockprotect)',
+ 'blockprotect', true);
+
+ // add filter at the front of toHtml
+ editor.on( 'toHtml',
+ function( evt ) {
+ evt.data.dataValue =
+ to_protected_html(evt.data.dataValue);
+ return evt;
+ },
+ this, null, 0
+ );
+
+ editor.dataProcessor.htmlFilter.addRules({
+ elements: {
+ span: function( element ) {
+ if ( element.className = 'cke_blockprotect' ) {
+ // defeat HTML escaping
+ var content = block_writeHtml(element);
+ element.writeHtml = function(writer, filter) {
+ writer.text(content);
+ }
+ }
+ } // span function
+ } // elements
+ });
+ }
+ }); // plugins.add
+}) ();
+
diff --git a/httemplate/elements/htmlarea.html b/httemplate/elements/htmlarea.html
index f9dcffd3f..c98993d40 100644
--- a/httemplate/elements/htmlarea.html
+++ b/httemplate/elements/htmlarea.html
@@ -6,6 +6,7 @@ Example:
'field' => 'fieldname',
'curr_value' => $curr_value,
'height' => 800,
+ 'config' => { extraPlugins => 'blockprotect' },
);
</%doc>
@@ -19,21 +20,24 @@ Example:
<SCRIPT TYPE="text/javascript">
- CKEDITOR.replace('<% $opt{'field'} %>', {
-% if ( $opt{'width'} ) {
- width: <% $opt{'width'} %>,
-% }
- height: <% $opt{'height'} || 420 %>,
- startupFocus: true,
- toolbarCanCollapse: true,
- basePath: '<% $p %>elements/ckeditor/',
- enterMode: 2
- });
+ CKEDITOR.replace('<% $opt{'field'} %>',
+ <% encode_json($config) %>
+ );
</SCRIPT>
<%init>
my %opt = @_;
+my $config = {
+ 'height' => ($opt{height} || 420),
+ 'startupFocus' => JSON::true,
+ 'skin' => 'kama',
+ 'toolbarCanCollapse' => JSON::true,
+ 'basePath' => $p.'elements/ckeditor/',
+ 'enterMode' => 2,
+ %{ $opt{config} || {} },
+};
+$config->{width} = $opt{width} if defined($opt{width});
</%init>
diff --git a/httemplate/elements/onload.js b/httemplate/elements/onload.js
index bfa7eef94..c7bbbb283 100644
--- a/httemplate/elements/onload.js
+++ b/httemplate/elements/onload.js
@@ -12,11 +12,12 @@ Usage:
</%doc>
(function() {
- var tmp = window.onload;
- window.onload = function() {
- if (typeof(tmp)== 'function') {
- tmp();
- }
+ var myonload = function() {
<% $m->content %>
- };
+ }
+ if ( window.addEventListener ) {
+ window.addEventListener('load', myonload);
+ } else if ( window.attachEvent ) {
+ window.attachEvent('onload', myonload);
+ }
})();
diff --git a/httemplate/search/477partIA.html b/httemplate/search/477partIA.html
index 1cd0b70e0..5ee44dad5 100755
--- a/httemplate/search/477partIA.html
+++ b/httemplate/search/477partIA.html
@@ -139,7 +139,7 @@ for ( my $row = 0; $row < scalar @upload_option; $row++ ) {
my $count = FS::Record->scalar_sql($this_count_query);
my $residential = FS::Record->scalar_sql($this_count_query . $is_residential);
- my $percent = sprintf('%.2f', $count ? 100 * $residential / $count : 0);
+ my $percent = sprintf('%.3f', $count ? 100 * $residential / $count : 0);
$data[$col][$row] = [ $count, $percent ];
$total_count += $count;
@@ -149,10 +149,10 @@ for ( my $row = 0; $row < scalar @upload_option; $row++ ) {
}
my $total_percentage =
- sprintf("%.2f", $total_count ? 100*$total_residential/$total_count : 0);
+ sprintf("%.3f", $total_count ? 100*$total_residential/$total_count : 0);
my $above_200_percentage =
- sprintf("%.2f", $total_count ? 100*$above_200/$total_count : 0);
+ sprintf("%.3f", $total_count ? 100*$above_200/$total_count : 0);
my @summary_row = (
$total_count,
diff --git a/httemplate/search/477partIIA.html b/httemplate/search/477partIIA.html
index 95c00a3e0..907a176e5 100755
--- a/httemplate/search/477partIIA.html
+++ b/httemplate/search/477partIIA.html
@@ -104,7 +104,7 @@ if ( $total_lines > 0 ) {
foreach (@row_conds) {
my $sql = $query_ds0 . $_;
my $lines = FS::Record->scalar_sql($sql);
- my $percent = sprintf('%.2f', 100 * $lines / $total_lines);
+ my $percent = sprintf('%.3f', 100 * $lines / $total_lines);
push @{ $data[0] }, $percent;
}
}
diff --git a/httemplate/search/477partIIB.html b/httemplate/search/477partIIB.html
index 5b9b30769..cb181f4fd 100755
--- a/httemplate/search/477partIIB.html
+++ b/httemplate/search/477partIIB.html
@@ -120,7 +120,7 @@ foreach (@col_conds) {
if ( $col_data[0] == 0 ) {
$col_data[$row] = ''; # show nothing in this row, then
} else {
- $col_data[$row] = sprintf('%.2f', 100 * $count / $col_data[0]) . '%';
+ $col_data[$row] = sprintf('%.3f', 100 * $count / $col_data[0]) . '%';
}
} #if $row == 0
$row++;
diff --git a/httemplate/search/477partVI_census.html b/httemplate/search/477partVI_census.html
index 59a6fb50d..0dafc6b21 100755
--- a/httemplate/search/477partVI_census.html
+++ b/httemplate/search/477partVI_census.html
@@ -75,7 +75,7 @@ push @fields,
$state_pkgcount{$state} += $row->quantity;
$row->quantity;
},
- sub { my $row = shift; sprintf "%.2f", $row->residential },
+ sub { my $row = shift; sprintf "%.3f", $row->residential },
;
my %search_hash = ();
diff --git a/rt/lib/RT/Interface/Web_Vendor.pm b/rt/lib/RT/Interface/Web_Vendor.pm
index 0c061e2de..3aad3fee2 100644
--- a/rt/lib/RT/Interface/Web_Vendor.pm
+++ b/rt/lib/RT/Interface/Web_Vendor.pm
@@ -264,7 +264,12 @@ sub ProcessTicketBasics {
my $DateObj = RT::Date->new($session{'CurrentUser'});
if ( $to_date ) {
$DateObj->Set(Format => 'unknown', Value => $to_date);
- $ARGSRef->{'WillResolve'} = $DateObj->ISO;
+ if ( $DateObj->Unix > time ) {
+ $ARGSRef->{'WillResolve'} = $DateObj->ISO;
+ } else {
+ warn "Ticket ".$TicketObj->Id.": WillResolve date '$to_date' not accepted.\n";
+ # and then don't set it in ARGSRef
+ }
} elsif ( $TicketObj and $TicketObj->WillResolveObj->Unix > 0 ) {
$DateObj->Set(Value => 0);
$ARGSRef->{'WillResolve'} = $DateObj->ISO;
@@ -343,6 +348,13 @@ sub ProcessTicketDates {
Value => $ARGSRef->{ $field . '_Date' }
);
+ if ( $field eq 'WillResolve'
+ and $DateObj->Unix > 0
+ and $DateObj->Unix <= time ) {
+ push @results, "Can't set WillResolve date in the past.";
+ next;
+ }
+
my $obj = $field . "Obj";
if ( ( defined $DateObj->Unix )
and ( $DateObj->Unix != $Ticket->$obj()->Unix() ) )