L<FS::cust_main::Import> - Batch customer importing
+L<FS::contact::Import> - Batch contact importing
+
=head2 Database record classes
L<FS::Record> - Database record base class
for (@cust_main_editable_fields) {
$return{$_} = $cust_main->get($_);
}
+ $return{$_} = $cust_main->masked($_) for qw/ss stateid/;
+
#maybe a little more expensive, but it should be cached by now
for (@location_editable_fields) {
$return{$_} = $cust_main->bill_location->get($_);
}
1;
-
},
{
+ 'key' => 'email-to-voice_domain',
+ 'section' => 'email_to_voice_services',
+ 'description' => 'The domain name that phone numbers will be attached to for sending email to voice emails via a 3rd party email to voice service. You will get this domain from your email to voice service provider. This is utilized on the email customer page or when using the email to voice billing event action. There you will be able to select the phone number for the email to voice service.',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
'key' => 'next-bill-ignore-time',
'section' => 'billing',
'description' => 'Ignore the time portion of next bill dates when billing, matching anything from 00:00:00 to 23:59:59 on the billing day.',
'amount', @money_type, '', '',
'status', 'varchar', 'NULL', $char_d, '', '',
'error_message', 'varchar', 'NULL', $char_d, '', '',
+ 'paycode', 'varchar', 'NULL', $char_d, '', '',
],
'primary_key' => 'paybatchnum',
'unique' => [],
$self->due_date ? $self->time2str_local(shift, $self->due_date) : '';
}
+=item invoice_pay_by_msg
+
+ displays the invoice_pay_by_msg or default Please pay by [_1] if empty.
+
+=cut
+
+sub invoice_pay_by_msg {
+ my $self = shift;
+ my $msg = '';
+ my $please_pay_by =
+ $self->conf->config('invoice_pay_by_msg', $self->agentnum)
+ || 'Please pay by [_1]';
+ $msg .= ' - ' . $self->mt($please_pay_by, $self->due_date2str('short')) . ' ';
+
+ $msg;
+}
+
=item balance_due_msg
=cut
# _items_total) and not here
# (yes, or if invoice_sections is enabled; this is just for compatibility)
if ( $self->due_date ) {
- my $please_pay_by =
- $self->conf->config('invoice_pay_by_msg', $self->agentnum)
- || 'Please pay by [_1]';
- $msg .= ' - ' . $self->mt($please_pay_by, $self->due_date2str('short')).
- ' '
+ $msg .= $self->invoice_pay_by_msg
unless $self->conf->config_bool('invoice_omit_due_date',$self->agentnum);
} elsif ( $self->terms ) {
$msg .= ' - '. $self->mt($self->terms);
my @cust_bill_pkg = grep { $_->feepart } $self->cust_bill_pkg;
my $escape_function = $options{escape_function};
- my $locale = $self->cust_main->locale;
+ my $locale = $self->cust_main
+ ? $self->cust_main->locale
+ : $self->prospect_main->locale;
my @items;
foreach my $cust_bill_pkg (@cust_bill_pkg) {
#mark certain taxes as system-maintained,
# and fix whitespace
'cust_main_county' => [],
+
+ #upgrade part_event_condition_option agentnum to a multiple hash value
+ 'part_event_condition_option' =>[],
+
;
\%hash;
use FS::cust_location;
use FS::contact_phone;
use FS::contact_email;
+use FS::contact::Import;
use FS::queue;
use FS::cust_pkg;
use FS::phone_type; #for cgi_contact_fields
use strict;
use vars qw( $DEBUG ); #$conf );
+use Storable qw(thaw);
+use MIME::Base64;
use Data::Dumper;
use FS::Misc::DateTime qw( parse_datetime );
use FS::Record qw( qsearchs );
sub process_batch_import {
my $job = shift;
- my $param = shift;
+ #my $param = shift;
+ my $param = thaw(decode_base64(shift));
warn Dumper($param) if $DEBUG;
my $files = $param->{'uploaded_files'}
+++ /dev/null
-package FS::contact_import;
-
-use strict;
-use vars qw( $DEBUG ); #$conf );
-use Storable qw(thaw);
-use Data::Dumper;
-use MIME::Base64;
-use FS::Misc::DateTime qw( parse_datetime );
-use FS::Record qw( qsearchs );
-use FS::contact;
-use FS::cust_main;
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::contact_import - Batch contact importing
-
-=head1 SYNOPSIS
-
- use FS::contact_import;
-
- #import
- FS::contact_import::batch_import( {
- file => $file, #filename
- type => $type, #csv or xls
- format => $format, #default
- agentnum => $agentnum,
- job => $job, #optional job queue job, for progressbar updates
- pkgbatch => $pkgbatch, #optional batch unique identifier
- } );
- die $error if $error;
-
- #ajax helper
- use FS::UI::Web::JSRPC;
- my $server =
- new FS::UI::Web::JSRPC 'FS::contact_import::process_batch_import', $cgi;
- print $server->process;
-
-=head1 DESCRIPTION
-
-Batch contact importing.
-
-=head1 SUBROUTINES
-
-=item process_batch_import
-
-Load a batch import as a queued JSRPC job
-
-=cut
-
-sub process_batch_import {
- my $job = shift;
- #my $param = shift;
- my $param = thaw(decode_base64(shift));
- warn Dumper($param) if $DEBUG;
-
- my $files = $param->{'uploaded_files'}
- or die "No files provided.\n";
-
- my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
-
- my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
-
- my $file = $dir. $files{'file'};
-
- my $type;
- if ( $file =~ /\.(\w+)$/i ) {
- $type = lc($1);
- } else {
- #or error out???
- warn "can't parse file type from filename $file; defaulting to CSV";
- $type = 'csv';
- }
-
- my $error =
- FS::contact_import::batch_import( {
- job => $job,
- file => $file,
- type => $type,
- agentnum => $param->{'agentnum'},
- 'format' => $param->{'format'},
- } );
-
- unlink $file;
-
- die "$error\n" if $error;
-
-}
-
-=item batch_import
-
-=cut
-
-my %formatfields = (
- 'default' => [ qw( custnum last first title comment selfservice_access emailaddress phonetypenum1 phonetypenum3 phonetypenum2 ) ],
-);
-
-sub _formatfields {
- \%formatfields;
-}
-
-## not tested but maybe allow 2nd format to attach location in the future
-my %import_options = (
- 'table' => 'contact',
-
- 'preinsert_callback' => sub {
- my($record, $param) = @_;
- my @location_params = grep /^location\./, keys %$param;
- if (@location_params) {
- my $cust_location = FS::cust_location->new({
- 'custnum' => $record->custnum,
- });
- foreach my $p (@location_params) {
- $p =~ /^location.(\w+)$/;
- $cust_location->set($1, $param->{$p});
- }
-
- my $error = $cust_location->find_or_insert; # this avoids duplicates
- return "error creating location: $error" if $error;
- $record->set('locationnum', $cust_location->locationnum);
- }
- '';
- },
-
-);
-
-sub _import_options {
- \%import_options;
-}
-
-sub batch_import {
- my $opt = shift;
-
- my $iopt = _import_options;
- $opt->{$_} = $iopt->{$_} foreach keys %$iopt;
-
- my $format = delete $opt->{'format'};
-
- my $formatfields = _formatfields();
- die "unknown format $format" unless $formatfields->{$format};
-
- my @fields;
- foreach my $field ( @{ $formatfields->{$format} } ) {
- push @fields, $field;
- }
-
- $opt->{'fields'} = \@fields;
-
- FS::Record::batch_import( $opt );
-
-}
-
-=head1 BUGS
-
-Not enough documentation.
-
-=head1 SEE ALSO
-
-L<FS::contact>
-
-=cut
-
-1;
\ No newline at end of file
}
}
-
- if ( $conf->exists('invoice_show_prior_due_date') ) {
+ if ( $conf->exists('invoice_show_prior_due_date') && !$conf->exists('invoice_omit_due_date') ) {
# then the due date should be shown with Total New Charges,
# and should NOT be shown with the Balance Due message.
if ( $self->due_date ) {
- # localize the "Please pay by" message and the date itself
- # (grammar issues with this, yeah)
- $new_charges_desc .= ' - ' . $self->mt('Please pay by') . ' ' .
- $self->due_date2str('short');
+ $new_charges_desc .= $self->invoice_pay_by_msg;
} elsif ( $self->terms ) {
# phrases like "due on receipt" should be localized
$new_charges_desc .= ' - ' . $self->mt($self->terms);
} );
foreach (qw( address1 address2 city state zip country latitude longitude
- payby payinfo paydate payname ))
+ payby payinfo paydate payname paycode paytype ))
{
$options{$_} = '' unless exists($options{$_});
}
'exp' => $options{paydate} || $self->paydate,
'payname' => $options{payname} || $self->payname,
'amount' => $amount, # consolidating
+ 'paycode' => $options{paycode} || '',
} );
$cust_pay_batch->paybatchnum($old_cust_pay_batch->paybatchnum)
$self->agent->payment_gateway( 'method' => $options{method},
#'payinfo' => $payinfo,
);
- my( $processor, $login, $password, $namespace ) =
+ ( $processor, $login, $password, $namespace ) =
map { my $method = "gateway_$_"; $payment_gateway->$method }
qw( module username password namespace );
sub email_search_result {
my($class, $param) = @_;
+ my $conf = FS::Conf->new;
+ my $send_to_domain = $conf->config('email-to-voice_domain');
+
my $msgnum = $param->{msgnum};
my $from = delete $param->{from};
my $subject = delete $param->{subject};
my $html_body = delete $param->{html_body};
my $text_body = delete $param->{text_body};
my $to_contact_classnum = delete $param->{to_contact_classnum};
+ my $emailtovoice_name = delete $param->{emailtovoice_contact};
+
my $error = '';
+ my $to = $emailtovoice_name . '@' . $send_to_domain unless !$emailtovoice_name;
+
my $job = delete $param->{'job'}
or die "email_search_result must run from the job queue.\n";
next; # unlinked object; nothing else we can do
}
+ my %to = {};
+ if ($to) { $to{'to'} = $to; }
+
if ( $msg_template ) {
# Now supports other context objects.
%message = $msg_template->prepare(
'cust_main' => $cust_main,
'object' => $obj,
'to_contact_classnum' => $to_contact_classnum,
+ %to
);
} else {
if (!@classes) {
@classes = ( 'invoice' );
}
- my @to = $cust_main->contact_list_email(@classes);
+ my @to = $to ? split(',', $to) : $cust_main->contact_list_email(@classes);
next if !@to;
%message = (
} #if $msg_template
# For non-cust_main searches, we avoid duplicates based on message
- # body text.
+ # body text.
my $unique = $cust_main->custnum;
$unique .= sha1($message{'text_body'}) if $class ne 'FS::cust_main';
if( $sent_to{$unique} ) {
push @where, $FS::CurrentUser::CurrentUser->agentnums_sql('table'=>'cust_main');
}
+ push @where, "cust_pkg_reason.reasonnum = '".$params->{reasonnum}."'" if $params->{reasonnum};
+
my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
my $addl_from = 'LEFT JOIN part_pkg USING ( pkgpart ) '.
'LEFT JOIN cust_location USING ( locationnum ) '.
FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
+ if ($params->{reasonnum}) {
+ $addl_from .= 'LEFT JOIN cust_pkg_reason ON (cust_pkg_reason.pkgnum = cust_pkg.pkgnum) ';
+ }
+
my $select;
my $count_query;
if ( $params->{'select_zip5'} ) {
--- /dev/null
+package FS::part_event::Action::notice_to_emailtovoice;
+
+use strict;
+use base qw( FS::part_event::Action );
+use FS::Record qw( qsearchs );
+use FS::msg_template;
+use FS::Conf;
+
+sub description { 'Email a email to voice notice'; }
+
+sub eventtable_hashref {
+ {
+ 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ 'cust_pay' => 1,
+ 'cust_pay_batch' => 1,
+ 'cust_statement' => 1,
+ 'svc_acct' => 1,
+ };
+}
+
+sub option_fields {
+
+ #my $conf = new FS::Conf;
+ #my $to_domain = $conf->config('email-to-voice_domain');
+
+(
+ 'to_name' => { 'label' => 'Address To',
+ 'type' => 'select',
+ 'options' => [ 'mobile', 'fax', 'daytime' ],
+ 'option_labels' => { 'mobile' => 'Mobile Phone #',
+ 'fax' => 'Fax #',
+ 'daytime' => 'Day Time #',
+ },
+ 'post_field_label' => ' <font color="red">Make sure you have setup your email-to-voice_domain config option in your Configuration settings.</font>',
+ },
+
+ 'msgnum' => { 'label' => 'Template',
+ 'type' => 'select-table',
+ 'table' => 'msg_template',
+ 'name_col' => 'msgname',
+ 'hashref' => { disabled => '' },
+ 'disable_empty' => 1,
+ },
+ );
+
+}
+
+sub default_weight { 56; } #?
+
+sub do_action {
+ my( $self, $object ) = @_;
+
+ my $conf = new FS::Conf;
+ my $to_domain = $conf->config('email-to-voice_domain')
+ or die "Can't send notice with out send-to-domain, being set in global config \n";
+
+ my $cust_main = $self->cust_main($object);
+
+ my $msgnum = $self->option('msgnum');
+ my $name = $self->option('to_name');
+
+ my $msg_template = qsearchs('msg_template', { 'msgnum' => $msgnum } )
+ or die "Template $msgnum not found";
+
+ my $to_name = $cust_main->$name
+ or die "Can't send notice with out " . $cust_main->$name . " number set";
+
+ ## remove - from phone number
+ $to_name =~ s/-//g;
+
+ #my $to = $to_name . '@' . $self->option('to_domain');
+ my $to = $to_name . '@' . $to_domain;
+
+ $msg_template->send(
+ 'to' => $to,
+ 'cust_main' => $cust_main,
+ 'object' => $object,
+ );
+
+}
+
+1;
sub option_fields {
(
- 'agentnum' => { label=>'Agent', type=>'select-agent', },
+ 'agentnum' => { label=>'Agent', type=>'select-agent', multiple => '1' },
);
}
my $cust_main = $self->cust_main($object);
- my $agentnum = $self->option('agentnum');
-
- $cust_main->agentnum == $agentnum;
+ my $hashref = $self->option('agentnum') || {};
+ grep $hashref->{ $_->agentnum }, $cust_main->agent;
}
sub condition_sql {
my( $class, $table, %opt ) = @_;
- "cust_main.agentnum = " . $class->condition_sql_option_integer('agentnum', $opt{'driver_name'});
+ "cust_main.agentnum IN " . $class->condition_sql_option_option_integer('agentnum', $opt{'driver_name'});
}
1;
sub condition_sql {
my( $class, $table, %opt ) = @_;
- # XXX: can be made faster with optimizations?
- # -remove some/all sub-selects?
- # -remove the two main separate selects?
-
- "0 = (select count(1) from cust_pkg
- where cust_pkg.no_auto = 'Y' and cust_pkg.pkgnum in
- (select distinct cust_bill_pkg.pkgnum
- from cust_bill_pkg, cust_pkg
- where cust_bill_pkg.pkgnum = cust_pkg.pkgnum
- and cust_bill_pkg.invnum = cust_bill.invnum
- and cust_bill_pkg.pkgnum > 0
- )
- )
- AND
- 0 = (select count(1) from part_pkg
- where part_pkg.no_auto = 'Y' and part_pkg.pkgpart in
- (select cust_pkg.pkgpart from cust_pkg
- where pkgnum in
- (select distinct cust_bill_pkg.pkgnum
- from cust_bill_pkg, cust_pkg
- where cust_bill_pkg.pkgnum = cust_pkg.pkgnum
- and cust_bill_pkg.invnum = cust_bill.invnum
- and cust_bill_pkg.pkgnum > 0
- )
- )
- )
+ # can be made still faster with optimizations?
+
+ "NOT EXISTS ( SELECT 1 FROM cust_pkg
+ LEFT JOIN part_pkg USING (pkgpart)
+ WHERE ( cust_pkg.no_auto = 'Y' OR part_pkg.no_auto = 'Y' )
+ AND cust_pkg.pkgnum IN
+ ( SELECT DISTINCT cust_bill_pkg.pkgnum
+ FROM cust_bill_pkg
+ WHERE cust_bill_pkg.invnum = cust_bill.invnum
+ AND cust_bill_pkg.pkgnum > 0
+ )
+ )
";
}
}
}
+use FS::upgrade_journal;
+sub _upgrade_data { #class method
+ my ($class, %opts) = @_;
+
+ # migrate part_event_condition_option agentnum to part_event_condition_option_option agentnum
+ unless ( FS::upgrade_journal->is_done('agentnum_to_hash') ) {
+
+ foreach my $condition_option (qsearch('part_event_condition_option', { optionname => 'agentnum', })) {
+ my %options;
+ my $optionvalue = $condition_option->get("optionvalue");
+ if ($optionvalue eq 'HASH' ) { next; }
+ elsif ($optionvalue eq '') {
+ foreach my $agent (qsearch('agent', {})) {
+ $options{$agent->agentnum} = '1';
+ }
+
+ }
+ else {
+ $options{$optionvalue} = '1';
+ }
+
+ $condition_option->optionvalue(ref(\%options));
+ my $error = $condition_option->replace(\%options);
+ die $error if $error;
+
+ }
+
+ FS::upgrade_journal->set_done('agentnum_to_hash');
+
+ }
+
+}
+
=back
=head1 SEE ALSO
if (($cust_pay_batch->cust_main->paytype eq "Business checking" || $cust_pay_batch->cust_main->paytype eq "Business savings") && $cust_pay_batch->cust_main->company);
$i++;
+
+ ## set to D for debit by default, then override to what cust_pay_batch has as payments may not have paycode.
+ my $debitorcredit = 'D';
+ $debitorcredit = $cust_pay_batch->paycode unless !$cust_pay_batch->paycode;
+
sprintf("%06u", $i).
- 'D'.
+ $debitorcredit.
sprintf("%3s",$trans_code).
sprintf("%10s",$client_num).
' '.
},
);
+## this format can handle credit transactions
+sub can_handle_credits {
+ 1;
+}
+
1;
FS/category_Common.pm
t/category_Common.t
FS/contact.pm
+FS/contact/Import.pm
t/contact.t
FS/contact_phone.pm
t/contact_phone.t
" blib/lib/FS/part_export/*.pm;\
perl -p -i -e "\
s|%%%FREESIDE_CACHE%%%|${FREESIDE_CACHE}|g;\
- " blib/lib/FS/cust_main/*.pm blib/lib/FS/cust_pkg/*.pm;\
+ " blib/lib/FS/cust_main/*.pm blib/lib/FS/cust_pkg/*.pm blib/lib/FS/contact/*.pm;\
perl -p -i -e "\
s|%%%FREESIDE_LOG%%%|${FREESIDE_LOG}|g;\
" blib/lib/FS/Daemon/*.pm;\
my %deleteable = map { $_ => 1 } @deleteable;
my @sections = (qw(
- required billing invoicing notification UI API self-service ticketing
- network_monitoring username password session shell BIND telephony
+ required billing invoicing notification email_to_voice_services UI
+ API self-service ticketing network_monitoring username password
+ session shell BIND telephony
), '', 'deprecated'
);
<TD ALIGN="right">Check #</TD>
<TD COLSPAN=2><INPUT TYPE="text" NAME="payinfo" VALUE="<% $payinfo %>" SIZE=10></TD>
</TR>
+% }
+% elsif ($payby eq 'CHEK' || $payby eq 'CARD') {
+
+% if ( $conf->exists("batch-enable")
+% || grep $payby eq $_, $conf->config('batch-enable_payby')
+% ) {
+% if ( grep $payby eq $_, $conf->config('realtime-disable_payby') ) {
+ <INPUT TYPE="hidden" NAME="batch" VALUE="1">
+% } else {
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="batch" VALUE="1" ID="batch" <% ($batchnum || $batch) ? 'checked' : '' %> ></TD>
+ <TD ALIGN="left"> <% mt('Add to current batch') |h %></TD>
+ </TR>
+% }
+% }
+
% } else {
<INPUT TYPE="hidden" NAME="payinfo" VALUE="">
% }
my $payinfo = $cgi->param('payinfo');
my $reason = $cgi->param('reason');
my $link = $cgi->param('popup') ? 'popup' : '';
+my $batch = $cgi->param('batch');
die "access denied"
unless $FS::CurrentUser::CurrentUser->refund_access_right($payby);
-my( $paynum, $cust_pay ) = ( '', '' );
+my( $paynum, $cust_pay, $batchnum ) = ( '', '', '' );
if ( $cgi->param('paynum') =~ /^(\d+)$/ ) {
$paynum = $1;
$cust_pay = qsearchs('cust_pay', { paynum=>$paynum } )
or die "unknown payment # $paynum";
$refund ||= $cust_pay->unrefunded;
+ $batchnum = $cust_pay->batchnum;
if ( $custnum ) {
die "payment # $paynum is not for specified customer # $custnum"
unless $custnum == $cust_pay->custnum;
} elsif ( $payby =~ /^(CARD|CHEK)$/ ) {
my %options = ();
my $bop = $FS::payby::payby2bop{$1};
+
+ my %payby2fields = (
+ 'CARD' => [ qw( address1 address2 city county state zip country ) ],
+ 'CHEK' => [ qw( ss paytype paystate stateid stateid_state ) ],
+ );
+ my %type = ( 'CARD' => 'credit card',
+ 'CHEK' => 'electronic check (ACH)',
+ );
+
+##
+# now run the refund
+##
+
$cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/
or die "illegal refund amount ". $cgi->param('refund');
my $refund = "$1$2";
$cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!";
my $paynum = $1;
my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01';
- $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/;
- $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund,
+
+ if ( $cgi->param('batch') ) {
+ $paydate = "2037-12-01" unless $paydate;
+ $error ||= $cust_main->batch_card(
+ 'payby' => $payby,
+ 'amount' => $refund,
+ #'payinfo' => $payinfo,
+ #'paydate' => $paydate,
+ #'payname' => $payname,
+ 'paycode' => 'C',
+ map { $_ => scalar($cgi->param($_)) }
+ @{$payby2fields{$payby}}
+ );
+ errorpage($error) if $error;
+
+ my %hash = map {
+ $_, scalar($cgi->param($_))
+ } fields('cust_refund');
+
+ my $new = new FS::cust_refund ( { 'paynum' => $paynum,
+ %hash,
+ } );
+ $error = $new->insert;
+
+ # if not a batch refund run realtime.
+ } else {
+ $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/;
+ $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund,
'paynum' => $paynum,
'reasonnum' => $reasonnum,
%options );
-} else {
+ }
+} else { # run cash refund.
my %hash = map {
$_, scalar($cgi->param($_))
} fields('cust_refund');
<SCRIPT SRC="<% $fsurl %>elements/printtofit.js"></SCRIPT>
% }
% }
+ <SCRIPT SRC="<% $fsurl %>elements/topreload.js"></SCRIPT>
<% $head |n %>
</HEAD>
<BODY <% $etc |n %>>
--- /dev/null
+<%doc>
+
+Example:
+
+ <& /elements/popup-topreload, mt('Action completed') &>
+
+</%doc>
+<& /elements/header-popup.html, encode_entities($message) &>
+ <SCRIPT TYPE="text/javascript">
+ topreload();
+ </SCRIPT>
+<& /elements/footer-popup.html &>
+<%init>
+
+my $message = shift;
+
+</%init>
\ No newline at end of file
--- /dev/null
+<SELECT NAME="<% $opt{'field_name'} %>" ID="<% $opt{'field_name'} %>">
+
+ <OPTION VALUE="" selected="selected">Select a phone number
+
+% foreach $p (@$phone_types) {
+ <OPTION VALUE="<% $phones_formatted{$p} %>"><% $p |h%> (<% $cust_phones->$p |h %>)
+%}
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+my $cust_num = $opt{'cust_num'};
+my $phone_types = $opt{'phone_types'};
+my $format = $opt{'format'};
+
+my $cust_phones = qsearchs('cust_main', { 'custnum' => $cust_num })
+ or die 'unknown custnum' . $cust_num;
+
+my %phones_formatted = map {
+ $_ => format_phone_number($cust_phones->$_, $format)
+} @$phone_types;
+
+sub format_phone_number {
+ my ($n, $f) = @_;
+ if ($f eq 'xxxxxxxxxx') { $n =~ s/-//g; }
+ return $n;
+}
+
+</%init>
\ No newline at end of file
%
% }
-</SELECT>
+</SELECT> <% $opt{'post_field_label'} %>
% }
<%init>
--- /dev/null
+ window.topreload = function() {
+ if (window != window.top) {
+ window.top.location.reload();
+ }
+ }
+
\ No newline at end of file
--- /dev/null
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Customer Phones' %></TD>
+ <TD>
+ <% include( '/elements/select-cust_phone.html', %opt ) %>
+ </TD>
+ </TR>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
include( '/elements/tr-select-reason.html',
#required
- 'field' => 'reasonnum',
- 'reason_class' => 'C', # currently 'C', 'R', 'F', 'S' or 'X'
- # for cancel, credit, refund, suspend or void credit
+ 'field' => 'reasonnum', # field name
+ 'reason_class' => 'C', # one of those in %FS::reason_type::class_name
+ 'label' => 'Your Label', # field display label
#recommended
'cgi' => $cgi, #easiest way for things to be properly "sticky" on errors
#optional
- 'control_button' => 'element_name', #button to be enabled when a reason is
- #selected
+ 'control_button' => 'element_name', #button to be enabled when a reason is
+ #selected
'id' => 'element_id',
+ 'hide_add' => '1', # setting this will hide the add new reason link,
+ # even if the user has access to add a new reason.
+ 'hide_onload' => '1', # setting this will hide reason select box on page load,
+ # allowing for it do be displayed later.
+ 'pre_options' => [ 0 => 'all'], # an array of pre options. Defaults to 0 => 'select reason...'
#deprecated ways to keep things "sticky" on errors
# (requires duplicate code in each using file to parse cgi params)
</SCRIPT>
%# sadly can't just use add_inline here, as we have non-text fields
+
<& tr-select-table.html,
- 'label' => 'Reason',
+ 'label' => $label,
'field' => $name,
'id' => $id,
'table' => 'reason',
'records' => \@reasons,
+ 'label_callback' => sub { my $reason = shift;
+ $reason->type . ' : ' . $reason->reason },
'name_col' => 'label',
'disable_empty' => 1,
- 'pre_options' => [ 0 => 'Select reason...' ],
+ 'pre_options' => \@pre_options,
'post_options' => \@post_options,
'curr_value' => $init_reason,
'onchange' => $id.'_changed()',
+ 'hide_onload' => $opt{'hide_onload'},
&>
% # "add new reason" fields
% # should be a <fieldset>, but that doesn't fit well into the table
-% if ( $curuser->access_right($add_access_right) ) {
+% if ( $curuser->access_right($add_access_right) && !$hide_addnew ) {
<TR id="<% $id %>_new_fields">
<TD COLSPAN=2>
<TABLE CLASS="inv" STYLE="text-align: left">
my $name = $opt{'field'};
my $class = $opt{'reason_class'};
+my $label = $opt{'label'} ? $opt{'label'} : 'Reason';
+my $hide_addnew = $opt{'hide_addnew'} ? $opt{'hide_addnew'} : '';
my $init_reason;
if ( $opt{'cgi'} ) {
my $id = $opt{'id'} || $name;
$id =~ s/\./_/g; # for edit/part_event
+my $label_id = $opt{'label_id'} || '';
+
my $add_access_right;
if ($class eq 'C') {
$add_access_right = 'Add on-the-fly cancel reason';
' ON (reason.reason_type = reason_type.typenum)',
'hashref' => { disabled => '' },
'extra_sql' => " AND reason_type.class = '$class'",
+ 'order_by' => ' ORDER BY type, reason',
});
+my @pre_options = ( 0 => 'Select reason...' );
+@pre_options = @{ $opt{'pre_options'} } if $opt{'pre_options'};
+
my @post_options;
-if ( $curuser->access_right($add_access_right) ) {
+if ( $curuser->access_right($add_access_right) && !$hide_addnew ) {
@post_options = ( -1 => 'Add new reason' );
}
-<TR>
+<%doc>
+
+Actually <TR> <TH> $label </TH>
+
+Note that this puts the 'label' argument into the document verbatim, with no
+escaping or localization.
+
+</%doc>
+<TR id="<% $opt{'id'} %>_row" <% $row_style %>>
<TD ALIGN = "right"
VALIGN = "<% $opt{'valign'} || 'top' %>"
$style .= '; '. $opt{'cell_style'}
if $opt{'cell_style'};
+my $row_style = 'style="visibility:collapse;"' if $opt{'hide_onload'};
+
my $required = $opt{'required'} ? '<font color="#ff0000">*</font> ' : '';
</%init>
--- /dev/null
+<% include('/elements/header-popup.html', "Suspend Packages") %>
+
+% if ( $cgi->param('error') ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT>
+ <BR><BR>
+% }
+
+<FORM ACTION="<% $p %>misc/process/bulk_suspend_pkg.cgi" METHOD=POST>
+
+%# some false laziness w/search/cust_pkg.cgi
+
+<INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords |h %>">
+% for my $param (
+% qw(
+% agentnum cust_status cust_main_salesnum salesnum custnum magic status
+% custom pkgbatch zip reasonnum
+% 477part 477rownum date
+% report_option
+% ),
+% grep { /^location_\w+$/ || /^report_option_any/ } $cgi->param
+% ) {
+ <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>">
+% }
+%
+% for my $param (qw( censustract censustract2 ) ) {
+% next unless grep { $_ eq $param } $cgi->param;
+ <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>">
+% }
+%
+% for my $param (qw( pkgpart classnum refnum towernum )) {
+% foreach my $value ($cgi->param($param)) {
+ <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $value |h %>">
+% }
+% }
+%
+% foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) {
+%
+ <INPUT TYPE="hidden" NAME="<% $field %>_null" VALUE="<% $cgi->param("${field}_null") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>_begin" VALUE="<% $cgi->param("${field}_begin") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>_beginning" VALUE="<% $cgi->param("${field}_beginning") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>_end" VALUE="<% $cgi->param("${field}_end") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>_ending" VALUE="<% $cgi->param("${field}_ending") |h %>">
+% }
+
+<% ntable('#cccccc') %>
+
+% my $date_init = 0;
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'suspend_date',
+ 'value' => $date,
+ 'label' => mt("Suspend package on"),
+ 'format' => $date_format,
+ } &>
+% $date_init = 1;
+
+ <& /elements/tr-select-reason.html,
+ field => 'suspend_reasonnum',
+ reason_class => 'S',
+ &>
+
+% if ( $FS::CurrentUser::CurrentUser->access_right('Unsuspend customer package')) {
+
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'suspend_resume_date',
+ 'value' => '',
+ 'label' => mt('Unsuspend on'),
+ 'format' => $date_format,
+ 'noinit' => $date_init,
+ } &>
+% }
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Suspend Packages">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+#use Date::Parse qw(str2time);
+#<table style="background-color: #cccccc; border-spacing: 2; width: 100%">
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my $date = time;
+
+</%init>
\ No newline at end of file
--- /dev/null
+<% include('/elements/header-popup.html', "Unsuspend Packages") %>
+
+% if ( $cgi->param('error') ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT>
+ <BR><BR>
+% }
+
+<FORM ACTION="<% $p %>misc/process/bulk_unsuspend_pkg.cgi" METHOD=POST>
+
+%# some false laziness w/search/cust_pkg.cgi
+
+<INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords |h %>">
+% for my $param (
+% qw(
+% agentnum cust_status cust_main_salesnum salesnum custnum magic status
+% custom pkgbatch zip reasonnum
+% 477part 477rownum date
+% report_option
+% ),
+% grep { /^location_\w+$/ || /^report_option_any/ } $cgi->param
+% ) {
+ <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>">
+% }
+%
+% for my $param (qw( censustract censustract2 ) ) {
+% next unless grep { $_ eq $param } $cgi->param;
+ <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>">
+% }
+%
+% for my $param (qw( pkgpart classnum refnum towernum )) {
+% foreach my $value ($cgi->param($param)) {
+ <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $value |h %>">
+% }
+% }
+%
+% foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) {
+%
+ <INPUT TYPE="hidden" NAME="<% $field %>_null" VALUE="<% $cgi->param("${field}_null") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>_begin" VALUE="<% $cgi->param("${field}_begin") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>_beginning" VALUE="<% $cgi->param("${field}_beginning") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>_end" VALUE="<% $cgi->param("${field}_end") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>_ending" VALUE="<% $cgi->param("${field}_ending") |h %>">
+% }
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TD><INPUT TYPE="checkbox" NAME="confirm"></TD>
+ <TD>Confirm Unsuspend Packages</TD>
+ </TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Unsuspend Packages">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+</%init>
$opt{'format'} = $1;
}
-my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } );
+my $credit_transactions = "EXISTS (SELECT 1 FROM cust_pay_batch WHERE batchnum = $batchnum AND paycode = 'C') AS arecredits";
+my $pay_batch = qsearchs({ 'select' => "*, $credit_transactions",
+ 'table' => 'pay_batch',
+ 'hashref' => { batchnum => $batchnum },
+ });
die "Batch not found: '$batchnum'" if !$pay_batch;
+if ($pay_batch->{Hash}->{arecredits}) {
+ my $export_format = "FS::pay_batch::".$opt{'format'};
+ die "This format can not handle refunds." unless $export_format->can('can_handle_credits');
+}
+
</%init>
<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 %>">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% scalar($cgi->param('custnum')) |h %>">
% if ( $cgi->param('action') eq 'send' ) {
<& /elements/progress-init.html,
'OneTrueForm',
[ qw( search table from subject html_body text_body
- msgnum to_contact_classnum ) ],
+ msgnum to_contact_classnum emailtovoice_contact custnum ) ],
$process_url,
$pdest,
&>
% } elsif ( $cgi->param('action') eq 'preview' ) {
-
<INPUT TYPE="hidden" NAME="to_contact_classnum" VALUE="<% join(',', @contact_classnum) %>">
<FONT SIZE="+2">Preview notice</FONT>
<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
<INPUT TYPE="hidden" NAME="msgnum" VALUE="<% scalar($cgi->param('msgnum')) %>">
+ <INPUT TYPE="hidden" NAME="emailtovoice_contact" VALUE="<% scalar $cgi->param('emailtovoice_contact') |h %>">
% if ( $msg_template ) {
<% include('/elements/tr-fixed.html',
'label' => 'Template:',
&>
<BR>
% # select destination contact classes
-Send to contacts:
+<TABLE CELLSPACING=0 id="send_to_contacts_table">
+<TR>
+ <TD>Send to contacts:</TD>
+ <TD>
+ <div id="contactclassesdiv">
<& /elements/checkboxes.html,
'style' => 'display: inline; vertical-align: top',
'disable_links' => 1,
$name eq 'invoice' #others default to unchecked
},
&>
+ </div>
+% if ($send_to_domain) {
+ <div>
+ <INPUT TYPE="checkbox" NAME="emailtovoice" ID="emailtovoice" VALUE="ON" onclick="toggleDiv(this)">Email to voice
+ </div>
+ <div id="emailtovoicediv" style="display:none">
+
+ <& /elements/select-cust_phone.html,
+ 'cust_num' => $cgi->param('custnum'),
+ 'field_name' => 'emailtovoice_contact',
+ 'format' => 'xxxxxxxxxx',
+ 'phone_types' => [ 'daytime', 'night', 'fax', 'mobile' ],
+ &>@<% $send_to_domain |h %>
+ </div>
+% }
+ </TD>
+</TR>
+</TABLE>
<BR>
% # if sending a one-off message, show a form to edit it
<TABLE BGCOLOR="#cccccc" CELLSPACING=0 WIDTH="100%" id="table_no_template">
%#Substitution vars:
<INPUT TYPE="hidden" NAME="action" VALUE="preview">
+ <INPUT TYPE="hidden" NAME="custnum" VALUE="<% scalar($cgi->param('custnum')) |h %>">
<INPUT TYPE="submit" VALUE="Preview notice">
% } #end not action or alternate form
</SCRIPT>
% }
+<SCRIPT TYPE="text/javascript">
+function toggleDiv(obj) {
+ var box_contactclasses = document.getElementById('contactclassesdiv');
+ var box_emailtovoice = document.getElementById('emailtovoicediv');
+
+ box_emailtovoice.style.display = (box_emailtovoice.style.display == 'none') ? 'block' : 'none';
+ document.getElementById('emailtovoice_contact').options[0].selected=true;
+
+ box_contactclasses.style.display = (box_contactclasses.style.display == 'none') ? 'block' : 'none';
+}
+</SCRIPT>
+
<& /elements/footer.html &>
<%init>
$opt{'acl'} ||= 'Bulk send customer notices';
+my $email_to;
+
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right($opt{'acl'});
my $conf = FS::Conf->new;
my @no_search_fields = qw( action table from subject html_body text_body popup url );
+my $send_to_domain = $conf->config('email-to-voice_domain');
+
my $form_action = $opt{'form_action'} || 'email-customers.html';
my $process_url = $opt{'process_url'} || 'process/email-customers.html';
my $title = $opt{'title'} || 'Send customer notices';
my $popup = $cgi->param('popup');
my $url = $cgi->param('url');
+if (!$url && $cgi->param('custnum')) { $url = $fsurl."view/cust_main.cgi?".$cgi->param('custnum'); }
my $pdest = { 'message' => "Notice sent" };
$pdest->{'url'} = $cgi->param('url') if $url;
my %message = $msg_template->prepare(%msgopts);
($from, $subject, $html_body) = @message{'from', 'subject', 'html_body'};
}
+}
+
+if ($cgi->param('action')) {
# contact_class_X params in preview
- foreach my $param ( $cgi->param ) {
- if ( $param =~ /^contact_class_(\w+)$/ ) {
- push @contact_classnum, $1;
- if ( $1 eq 'invoice' ) {
+ if ($cgi->param('emailtovoice_contact')) {
+ $email_to = $cgi->param('emailtovoice_contact') . '@' . $send_to_domain;
+ push @contact_classnum, 'emailtovoice';
+ push @contact_classname, $email_to;
+ }
+ elsif ($cgi->param('to_contact_classnum')) {
+ foreach my $c (split(/,/, $cgi->param('to_contact_classnum'))) {
+ push @contact_classnum, $c;
+ if ( $c eq 'invoice' ) {
push @contact_classname, 'Invoice recipients';
} else {
- my $contact_class = FS::contact_class->by_key($1);
+ my $contact_class = FS::contact_class->by_key($c);
push @contact_classname, encode_entities($contact_class->classname);
}
}
}
+ else {
+ foreach my $param ( $cgi->param ) {
+ if ( $param =~ /^contact_class_(\w+)$/ ) {
+ push @contact_classnum, $1;
+ if ( $1 eq 'invoice' ) {
+ push @contact_classname, 'Invoice recipients';
+ } else {
+ my $contact_class = FS::contact_class->by_key($1);
+ push @contact_classname, encode_entities($contact_class->classname);
+ }
+ }
+ }
+ }
}
my @contact_checkboxes = (
[ 'invoice' => { label => 'Invoice recipients' } ]
);
+
foreach my $class (qsearch('contact_class', { disabled => '' })) {
push @contact_checkboxes, [
$class->classnum,
{ label => $class->classname }
];
}
+
</%init>
--- /dev/null
+% if ($error) {
+<% $cgi->redirect(popurl(2)."bulk_suspend_pkg.cgi?".$cgi->query_string ) %>
+% }
+<% include('/elements/popup-topreload.html', "Packages Suspended") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+my $error;
+
+if (!$error) {
+
+ my %search_hash = ();
+
+ $search_hash{'query'} = $cgi->param('query');
+
+ #scalars
+ for (qw( agentnum cust_status cust_main_salesnum salesnum custnum magic status
+ custom cust_fields pkgbatch zip reasonnum
+ 477part 477rownum date
+ ))
+ {
+ $search_hash{$_} = $cgi->param($_) if length($cgi->param($_));
+ }
+
+ #arrays
+ for my $param (qw( pkgpart classnum refnum towernum )) {
+ $search_hash{$param} = [ $cgi->param($param) ]
+ if grep { $_ eq $param } $cgi->param;
+ }
+
+ #scalars that need to be passed if empty
+ for my $param (qw( censustract censustract2 )) {
+ $search_hash{$param} = $cgi->param($param) || ''
+ if grep { $_ eq $param } $cgi->param;
+ }
+
+ #location flags (checkboxes)
+ my @loc = grep /^\w+$/, $cgi->param('loc');
+ $search_hash{"location_$_"} = 1 foreach @loc;
+
+ my $report_option = $cgi->param('report_option');
+ $search_hash{report_option} = $report_option if $report_option;
+
+ for my $param (grep /^report_option_any/, $cgi->param) {
+ $search_hash{$param} = $cgi->param($param);
+ }
+
+ ###
+ # parse dates
+ ###
+
+ #false laziness w/report_cust_pkg.html and bulk_pkg_increment_bill.cgi
+ my %disable = (
+ 'all' => {},
+ 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
+ 'active' => { 'susp'=>1, 'cancel'=>1 },
+ 'suspended' => { 'cancel' => 1 },
+ 'cancelled' => {},
+ '' => {},
+ );
+
+ foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) {
+
+ $search_hash{$field.'_null'} = scalar( $cgi->param($field.'_null') );
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+
+ next if $beginning == 0 && $ending == 4294967295
+ or $disable{$cgi->param('status')}->{$field};
+
+ $search_hash{$field} = [ $beginning, $ending ];
+
+ }
+
+ my $sql_query = FS::cust_pkg->search(\%search_hash);
+ $sql_query->{'select'} = 'cust_pkg.pkgnum';
+
+ ## set suspend info
+ $cgi->param('suspend_reasonnum') =~ /^(\d+)$/ or die "Illegal Reason";
+ my $suspend_reasonnum = $1;
+
+ my $suspend_date = time;
+ parse_datetime($cgi->param('suspend_date')) =~ /^(\d+)$/ or die "Illegal date";
+ $suspend_date = $1;
+
+ my $suspend_resume_date = '';
+ (parse_datetime($cgi->param('suspend_resume_date')) =~ /^(\d+)$/ or die "Illegal resume date") if $cgi->param('suspend_resume_date');
+ $suspend_resume_date = $1;
+
+ foreach my $pkgnum (map { $_->pkgnum } qsearch($sql_query)) {
+ my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+
+ $error = $cust_pkg->suspend('reason' => $suspend_reasonnum,
+ 'date' => $suspend_date,
+ 'resume_date' => $suspend_resume_date,
+ );
+ }
+
+}
+
+$cgi->param("error", substr($error, 0, 512)); # arbitrary length believed
+ # suited for all supported
+ # browsers
+</%init>
\ No newline at end of file
--- /dev/null
+% if ($error) {
+<% $cgi->redirect(popurl(2)."bulk_unsuspend_pkg.cgi?".$cgi->query_string ) %>
+% }
+<% include('/elements/popup-topreload.html', "Packages Unsuspended") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+my $error;
+$error = 'Unsuspend packages not confirmed' if !$cgi->param('confirm');
+
+if (!$error) {
+
+ my %search_hash = ();
+
+ $search_hash{'query'} = $cgi->param('query');
+
+ #scalars
+ for (qw( agentnum cust_status cust_main_salesnum salesnum custnum magic status
+ custom cust_fields pkgbatch zip reasonnum
+ 477part 477rownum date
+ ))
+ {
+ $search_hash{$_} = $cgi->param($_) if length($cgi->param($_));
+ }
+
+ #arrays
+ for my $param (qw( pkgpart classnum refnum towernum )) {
+ $search_hash{$param} = [ $cgi->param($param) ]
+ if grep { $_ eq $param } $cgi->param;
+ }
+
+ #scalars that need to be passed if empty
+ for my $param (qw( censustract censustract2 )) {
+ $search_hash{$param} = $cgi->param($param) || ''
+ if grep { $_ eq $param } $cgi->param;
+ }
+
+ #location flags (checkboxes)
+ my @loc = grep /^\w+$/, $cgi->param('loc');
+ $search_hash{"location_$_"} = 1 foreach @loc;
+
+ my $report_option = $cgi->param('report_option');
+ $search_hash{report_option} = $report_option if $report_option;
+
+ for my $param (grep /^report_option_any/, $cgi->param) {
+ $search_hash{$param} = $cgi->param($param);
+ }
+
+ ###
+ # parse dates
+ ###
+
+ #false laziness w/report_cust_pkg.html and bulk_pkg_increment_bill.cgi
+ my %disable = (
+ 'all' => {},
+ 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
+ 'active' => { 'susp'=>1, 'cancel'=>1 },
+ 'suspended' => { 'cancel' => 1 },
+ 'cancelled' => {},
+ '' => {},
+ );
+
+ foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) {
+
+ $search_hash{$field.'_null'} = scalar( $cgi->param($field.'_null') );
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+
+ next if $beginning == 0 && $ending == 4294967295
+ or $disable{$cgi->param('status')}->{$field};
+
+ $search_hash{$field} = [ $beginning, $ending ];
+
+ }
+
+ my $sql_query = FS::cust_pkg->search(\%search_hash);
+ $sql_query->{'select'} = 'cust_pkg.pkgnum';
+
+ foreach my $pkgnum (map { $_->pkgnum } qsearch($sql_query)) {
+ my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ $error = $cust_pkg->unsuspend;
+ }
+
+}
+
+$cgi->param("error", substr($error, 0, 512)); # arbitrary length believed
+ # suited for all supported
+ # browsers
+</%init>
\ No newline at end of file
unless $FS::CurrentUser::CurrentUser->access_right('Import');
my $server =
- new FS::UI::Web::JSRPC 'FS::contact_import::process_batch_import', $cgi;
+ new FS::UI::Web::JSRPC 'FS::contact::Import::process_batch_import', $cgi;
</%init>
#scalars
for (qw( agentnum cust_status cust_main_salesnum salesnum custnum magic status
- custom cust_fields pkgbatch zip
+ reasonnum custom cust_fields pkgbatch zip
477part 477rownum date
))
{
'height' => 210,
). '<BR>';
+ $text .= include( '/elements/popup_link.html',
+ 'label' => emt('Suspend these packages'),
+ 'action' => "${p}misc/bulk_suspend_pkg.cgi?$query",
+ 'actionlabel' => emt('Suspend Packages'),
+ 'width' => 569,
+ 'height' => 210,
+ ). '<BR>' if $search_hash{status} eq 'active';
+
+ $text .= include( '/elements/popup_link.html',
+ 'label' => emt('Unsuspend these packages'),
+ 'action' => "${p}misc/bulk_unsuspend_pkg.cgi?$query",
+ 'actionlabel' => emt('Unsuspend Packages'),
+ 'width' => 569,
+ 'height' => 210,
+ ). '<BR>' if $search_hash{status} eq 'suspended';
+
if ( $curuser->access_right('Edit customer package dates') ) {
$text .= include( '/elements/popup_link.html',
'label' => emt('Increment next bill date'),
'onchange' => 'status_changed(this);',
&>
+ <& /elements/tr-select-reason.html,
+ 'field' => 'reasonnum',
+ 'reason_class' => 'S',
+ 'label' => 'Suspended Reason',
+ 'label_id' => 'reasonnum_label',
+ 'hide_addnew' => '1',
+ 'hide_onload' => '1',
+ 'cgi' => $cgi,
+ 'control_button' => 'confirm_suspend_cust_button',
+ 'pre_options' => [ 0 => 'all' ],
+ &>
+
<SCRIPT TYPE="text/javascript">
function status_changed(what) {
+ if (what.options[what.selectedIndex].value == 'suspended') {
+ document.getElementById('reasonnum_row').style.visibility = 'visible';
+ }
+ else {
+ document.getElementById('reasonnum_row').style.visibility = 'collapse';
+ }
+
% foreach my $status ( '', FS::cust_pkg->statuses() ) {
if ( what.options[what.selectedIndex].value == '<% $status %>' ) {