my @select = (
"$censustract AS censustract",
- 'technology',
+ '(technology - technology % 10) AS media_type',
+ # media types are multiples of 10
'broadband_downstream',
'broadband_upstream',
"SUM($q)",
my @where = (
'(pkgnum != 0 OR feepart IS NOT NULL)',
- $self->with_classnum($opt{'classnum'}, $opt{'use_override'}),
$self->with_report_option(%opt),
$self->with_refnum(%opt),
$self->with_cust_classnum(%opt)
);
+ my $where_classnum = $self->with_classnum($opt{'classnum'}, $opt{'use_override'});
+ if ($opt{'project'}) {
+ $where_classnum =~ s/\bcust_bill_pkg/v_cust_bill_pkg/g;
+ }
+ push @where, $where_classnum;
+
if ( $opt{'distribute'} ) {
$where[0] = 'pkgnum != 0'; # specifically exclude fees
push @where, "cust_main.agentnum = $agentnum" if $agentnum;
case. If it's not set, the 'unused_credit_cancel' part_pkg option will
be used.
+=item delay_cancel - for internal use, to allow proper handling of
+supplemental packages when the main package is flagged to suspend
+before cancelling
+
=back
If there is an error, returns the error, otherwise returns false.
my $date = $options{'date'} if $options{'date'}; # expire/cancel later
$date = '' if ($date && $date <= $cancel_time); # complain instead?
- my $delay_cancel = undef;
+ my $delay_cancel = $options{'delay_cancel'};
if ( !$date && $self->part_pkg->option('delay_cancel',1)
&& (($self->status eq 'active') || ($self->status eq 'suspended'))
) {
return $error;
}
}
-
} #unless $date
my %hash = $self->hash;
if ( $date ) {
$hash{'expire'} = $date;
if ($delay_cancel) {
- $hash{'susp'} = $cancel_time unless $self->susp;
+ # just to be sure these are clear
$hash{'adjourn'} = undef;
$hash{'resume'} = undef;
}
}
foreach my $supp_pkg ( $self->supplemental_pkgs ) {
- if ($delay_cancel) {
- $error = $supp_pkg->suspend(%options, 'from_main' => 1, 'reason' => undef);
- } else {
- $error = $supp_pkg->cancel(%options, 'from_main' => 1);
- }
+ $error = $supp_pkg->cancel(%options,
+ 'from_main' => 1,
+ 'date' => $date, #in case it got changed by delay_cancel
+ 'delay_cancel' => $delay_cancel,
+ );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "canceling supplemental pkg#".$supp_pkg->pkgnum.": $error";
}
}
- foreach my $usage ( $self->cust_pkg_usage ) {
- $error = $usage->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "deleting usage pools: $error";
+ if ($delay_cancel && !$options{'from_main'}) {
+ $error = $new->suspend(
+ 'from_cancel' => 1,
+ 'time' => $cancel_time
+ );
+ }
+
+ unless ($date) {
+ foreach my $usage ( $self->cust_pkg_usage ) {
+ $error = $usage->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "deleting usage pools: $error";
+ }
}
}
=item from_main - allows a supplemental package to be suspended, rather
than redirecting the method call to its main package. For internal use.
+=item from_cancel - used when suspending from the cancel method, forces
+this to skip everything besides basic suspension. For internal use.
+
=back
If there is an error, returns the error, otherwise returns false.
}
# some false laziness with sub cancel
- if ( !$options{nobill} && !$date &&
+ if ( !$options{nobill} && !$date && !$options{'from_cancel'} &&
$self->part_pkg->option('bill_suspend_as_cancel',1) ) {
# kind of a kludge--'bill_suspend_as_cancel' to avoid having to
# make the entire cust_main->bill path recognize 'suspend' and
unless ( $date ) { # then we are suspending now
- # credit remaining time if appropriate
- # (if required by the package def, or the suspend reason)
- my $unused_credit = $self->part_pkg->option('unused_credit_suspend',1)
- || ( defined($reason) && $reason->unused_credit );
+ unless ($options{'from_cancel'}) {
+ # credit remaining time if appropriate
+ # (if required by the package def, or the suspend reason)
+ my $unused_credit = $self->part_pkg->option('unused_credit_suspend',1)
+ || ( defined($reason) && $reason->unused_credit );
- if ( $unused_credit ) {
- warn "crediting unused time on pkg#".$self->pkgnum."\n" if $DEBUG;
- my $error = $self->credit_remaining('suspend', $suspend_time);
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
+ if ( $unused_credit ) {
+ warn "crediting unused time on pkg#".$self->pkgnum."\n" if $DEBUG;
+ my $error = $self->credit_remaining('suspend', $suspend_time);
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
}
}
}
my $conf = new FS::Conf;
- if ( $conf->config('suspend_email_admin') ) {
+ if ( $conf->config('suspend_email_admin') && !$options{'from_cancel'} ) {
my $error = send_email(
'from' => $conf->config('invoice_from', $self->cust_main->agentnum),
sub is_status_delay_cancel {
my ($self) = @_;
+ if ( $self->main_pkgnum and $self->pkglinknum ) {
+ return $self->main_pkg->is_status_delay_cancel;
+ }
return 0 unless $self->part_pkg->option('delay_cancel',1);
return 0 unless $self->status eq 'suspended';
return 0 unless $self->expire;
use strict;
use Tie::IxHash;
use FS::payby;
+use FS::Record qw(qsearch);
use base qw( FS::part_event::Condition );
sub _bulk_cust_svc {
my( $self, $cust_pkg, $sdate ) = @_;
# END START
- $cust_pkg->h_cust_svc( $$sdate, $cust_pkg->last_bill );
+ return $self->_only_svcs_filter($cust_pkg->h_cust_svc( $$sdate, $cust_pkg->last_bill ));
}
sub _bulk_setup {
use vars qw($DEBUG $me %info);
use Date::Format;
use FS::Conf;
+use FS::Record qw(qsearchs);
$DEBUG = 0;
$me = '[FS::part_pkg::bulk_Common]';
'instead of a detailed list',
'type' => 'checkbox',
},
+ 'only_svcs' => {
+ 'name' => 'Only charge fees for these services',
+ 'type' => 'select_multiple',
+ 'select_table' => 'part_svc',
+ 'select_key' => 'svcpart',
+ 'select_label' => 'svc',
+ 'disable_empty' => 1,
+ 'parse' => sub { @_ }, #should this be the default in /edit/process/part_pkg.cgi?
+ },
},
- 'fieldorder' => [ 'svc_setup_fee', 'svc_recur_fee',
+ 'fieldorder' => [ 'svc_setup_fee', 'svc_recur_fee', 'only_svcs',
'summarize_svcs', 'no_prorate' ],
'weight' => 51,
);
sub can_usageprice { 0; }
+sub _only_svcs_filter {
+ my ($self, @cust_svc) = @_;
+ my @only_svcs = split(', ',$self->option('only_svcs',1));
+ if (@only_svcs) {
+ @cust_svc = grep {
+ my $svcpart = $_->svcpart;
+ grep(/^$svcpart$/,@only_svcs);
+ } @cust_svc;
+ }
+ return @cust_svc;
+}
+
1;
sub _bulk_cust_svc {
my( $self, $cust_pkg, $sdate ) = @_;
- $cust_pkg->cust_svc;
+ return $self->_only_svcs_filter($cust_pkg->cust_svc);
}
sub _bulk_setup {
' )
<BR><BR>';
+# pass the page selection through so we can jump back to the current spot
+if ( $cgi->param('maxrecords') =~ /^(\d+)$/ ) {
+ $html_form .= qq!<INPUT TYPE="hidden" NAME="maxrecords" VALUE="$1">!;
+}
+if ( $cgi->param('offset') =~ /^(\d+)$/ ) {
+ $html_form .= qq!<INPUT TYPE="hidden" NAME="offset" VALUE="$1">!;
+}
# restore this only after creating $html_form
$cgi->param('classnum', $classnum) if length($classnum);
}
function filter_change() {
- window.location = '! . $cgi->self_url . qq!?classnum='
+ window.location = '<% $cgi->self_url %>?classnum='
+ document.getElementById('classnum').value;
}
</script>
% }
<% $cgi->redirect($fsurl.'browse/part_pkg-fcc.html?redirect='.$session) %>
% } else {
-<% $cgi->redirect($fsurl.'browse/part_pkg-fcc.html?classnum='.$classnum.$jump) %>
+<% $cgi->redirect($dest) %>
% }
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
$error{$pkgpart} = $error if $error;
}
-my $classnum = $cgi->param('classnum');
+my $dest = $fsurl.'browse/part_pkg-fcc.html?';
+foreach (qw(classnum maxrecords offset)) {
+ if ( $cgi->param($_) =~ /^(\d+)$/ ) {
+ $dest .= "$_=$1;";
+ }
+}
-my $jump = '';
if ( $cgi->param('jump') =~ /^pkgpart(\d+)$/ ) {
- $jump = '#'.$1;
+ $dest .= "#$1";
}
</%init>
my $discounts = {};
my $discount_descripsub = sub {
my($item) = @_;
- $pkgpart{$item->pkgpart} ||= $item->part_pkg->pkg;
+ $pkgpart{$item->pkgpart} ||= qsearchs({
+ 'table' => 'part_pkg',
+ 'hashref' => {'pkgpart' => $item->pkgpart}
+ })->pkg;
my $dnum = $item->discountnum;
$discounts->{$dnum} ||= qsearchs({
'table'=>'discount',
#required
'action' => 'content.html', # uri for content of popup
+
+ #alternately, use instead of action
+ 'js_action' => 'url', # javascript variable or expression
#strongly recommended
'actionlabel => 'You clicked', # popup title
$params = { @_ };
}
-$action = $params->{'action'} if exists $params->{'action'};
+$action = q(') . $params->{'action'} . q(') if exists $params->{'action'};
+$action = $params->{'js_action'} if exists $params->{'js_action'};
$actionlabel = $params->{'actionlabel'} if exists $params->{'actionlabel'};
$width = $params->{'width'} if exists $params->{'width'};
$height = $params->{'height'} if exists $params->{'height'};
my $popup_name = 'popup-'.time. "-$$-". rand() * 2**32;
my $onclick =
- "overlib( OLiframeContent('$action', $width, $height, '$popup_name', 0, '$scrolling' ), ".
+ "overlib( OLiframeContent($action, $width, $height, '$popup_name', 0, '$scrolling' ), ".
"CAPTION, '$actionlabel', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, ".
"DRAGGABLE, CLOSECLICK, ".
"BGCOLOR, '$color', CGCOLOR, '$color', CLOSETEXT, '$closetext'".
+% if ($popup) {
+<% include('/elements/header-popup.html', $title) %>
+% } else {
<% include('/elements/header.html', $title) %>
+% }
+
<FORM NAME="OneTrueForm" ACTION="email-customers.html" METHOD="POST">
<INPUT TYPE="hidden" NAME="table" VALUE="<% $table %>">
%# multi-valued search params. We are no longer in search context, so we
%# pack the search into a Storable string for later use.
<INPUT TYPE="hidden" NAME="search" VALUE="<% encode_base64(nfreeze(\%search)) %>">
+<INPUT TYPE="hidden" NAME="popup" VALUE="<% $popup %>">
+<INPUT TYPE="hidden" NAME="url" VALUE="<% $url | h %>">
% if ( $cgi->param('action') eq 'send' ) {
'OneTrueForm',
[ qw( search table from subject html_body text_body msgnum ) ],
'process/email-customers.html',
- { 'message' => "Notice sent" }, #would be nice to show #, but..
+ $pdest,
)
%>
my $table = $cgi->param('table') or die "'table' required";
my $agent_virt_agentnum = $cgi->param('agent_virt_agentnum') || '';
+my $popup = $cgi->param('popup');
+my $url = $cgi->param('url');
+my $pdest = { 'message' => "Notice sent" };
+$pdest->{'url'} = $cgi->param('url') if $url;
+
my %search;
if ( $cgi->param('search') ) {
%search = %{ thaw(decode_base64($cgi->param('search'))) };
}
else {
%search = $cgi->Vars;
- delete $search{$_} for qw( action table from subject html_body text_body );
+ delete $search{$_} for qw( action table from subject html_body text_body popup url );
# FS::$table->search is expected to know which parameters might be
# multi-valued, and to accept scalar values for them also. No good
# solution to this since CGI can't tell whether a parameter _might_
checkboxes[i].checked = setTo;
}
}
+function toCGIString() {
+ var out = '';
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (checkboxes[i].checked) {
+ out += '&' + checkboxes[i].name + '=' + checkboxes[i].value;
+ }
+ }
+ return out;
+}
</SCRIPT>
<%init>
my %opt = @_;
<& elements/cust_main_dayranges.html,
'title' => 'Accounts Receivable Aging Summary',
'range_sub' => $mysub,
+ 'email_link' => 1, #adds an action column with an email link if true
&>
my $mysub = sub {
'query' => $sql_query,
'count_query' => $count_sql,
'header' => [
+ @act_blank,
@cust_header,
'0-30',
'30-60',
@pay_head,
],
'footer' => [
+ @act_blank,
'Total',
( map '',( 1 .. $#cust_header ),),
sprintf( $money_char.'%.2f',
('') x @pay_labels,
],
'fields' => [
+ @act_fields,
FS::UI::Web::cust_fields_subs(),
format_rangecol('0_30'),
format_rangecol('30_60'),
@pay_labels,
],
'links' => [
+ @act_blank,
( map { $_ ne 'Cust. Status' ? $clink : '' }
@cust_header
),
'',
@pay_links,
],
- 'align' => FS::UI::Web::cust_aligns().
+ 'align' => $act_align.
+ FS::UI::Web::cust_aligns().
'rrrrr'.
('c' x @pay_labels),
- 'size' => [ ( map '', @cust_header ),
+ 'size' => [
+ @act_blank,
+ ( map '', @cust_header ),
#'-1', '', '', '', '', '', ],
- '', '', '', '', '', '',
+ '', '', '', '', '',
( map '', @pay_labels ),
- ],
- 'style' => [ FS::UI::Web::cust_styles(),
+ ],
+ 'style' => [
+ @act_blank,
+ FS::UI::Web::cust_styles(),
#'b', '', '', '', '', 'b', ],
'', '', '', '', 'b',
( map '', @pay_labels ),
],
- 'xls_format' => [ (map '', FS::UI::Web::cust_styles),
+ 'xls_format' => [
+ @act_blank,
+ (map '', FS::UI::Web::cust_styles),
'', '', '', '', { bold => 1 },
],
'color' => [
+ @act_blank,
FS::UI::Web::cust_colors(),
'',
'',
'',
'',
'',
- '',
( map '', @pay_labels ),
],
+ 'html_foot' => $html_foot,
%opt,
&>
<%init>
@payby );
}
+my (@act_blank, @act_fields, $act_align, $html_foot);
+if (delete($opt{'email_checkboxes'})) {
+ my $email_link = q!var url = toCGIString(); !;
+ $email_link .= q/if (!url) { alert('No customers selected'); return false; }; /;
+ $email_link .= q!url = '!;
+ $email_link .= "${p}misc/email-customers.html?table=cust_main";
+ $email_link .= q!' + url + '&popup=1&url=javascript%3Awindow.top.location.reload%28%29%3B'; !;
+ $email_link .= include('/elements/popup_link_onclick.html',
+ 'js_action' => 'url',
+ 'actionlabel' => 'Send Customer Email',
+ 'width' => '900',
+ 'height' => '500',
+ );
+ $html_foot = include('checkbox-foot.html',
+ label => 'Email selected customers',
+ onclick => $email_link,
+ );
+ push @act_fields, sub {
+ my $row = shift;
+ my $custnum = $row->custnum;
+ qq!<input type="checkbox" name="custnum" value="$custnum">!;
+ };
+ $act_align = 'l';
+ push @act_blank, '';
+}
+
</%init>
<%once>
'title' => emt('Accounts Receivable Aging Summary'),
'range_sub' => \&balance,
'payment_links' => 1,
+ 'email_checkboxes' => 1,
&>
<%init>
<% pkg_status_row( $cust_pkg, emt('On Hold'), '', 'color'=>'7E0079', %opt ) %>
% } else { #status: suspended
-
- <% pkg_status_row( $cust_pkg, emt('Suspended'), 'susp', 'color'=>'FF9900', %opt ) %>
-% my $cpr = $cust_pkg->last_cust_pkg_reason('susp');
+% my ($cpr,$susplabel);
+% if ($cust_pkg->is_status_delay_cancel) {
+% $cpr = $cust_pkg->last_cust_pkg_reason('expire');
+% $susplabel = 'Suspended (Cancelled)';
+% } else {
+% $cpr = $cust_pkg->last_cust_pkg_reason('susp');
+% $susplabel = 'Suspended';
+% }
+ <% pkg_status_row( $cust_pkg, emt($susplabel), 'susp', 'color'=>'FF9900', %opt ) %>
<% pkg_reason_row( $cust_pkg, $cpr, 'color' => 'FF9900', %opt ) %>
% }