summaryrefslogtreecommitdiff
path: root/httemplate/misc
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate/misc')
-rw-r--r--httemplate/misc/batch-cust_pay.html375
-rwxr-xr-xhttemplate/misc/cancel_pkg.html79
-rw-r--r--httemplate/misc/cust-part_pkg.cgi18
-rwxr-xr-xhttemplate/misc/cust_main-cancel.cgi2
-rw-r--r--httemplate/misc/cust_main-import.cgi4
-rwxr-xr-xhttemplate/misc/cust_main-suspend.cgi75
-rwxr-xr-xhttemplate/misc/cust_main-unsuspend.cgi56
-rw-r--r--httemplate/misc/delete-ftp_target.html18
-rw-r--r--httemplate/misc/download-batch.cgi13
-rw-r--r--httemplate/misc/order_pkg.html14
-rw-r--r--httemplate/misc/process/batch-cust_pay.cgi115
-rwxr-xr-xhttemplate/misc/process/cancel_pkg.html40
-rw-r--r--httemplate/misc/suspend_cust.html79
-rw-r--r--httemplate/misc/unsuspend_cust.html68
-rw-r--r--httemplate/misc/xmlhttp-cust_bill-search.html18
-rw-r--r--httemplate/misc/xmlhttp-cust_main-discount_terms.cgi19
-rw-r--r--httemplate/misc/xmlhttp-cust_main-search.cgi90
17 files changed, 935 insertions, 148 deletions
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
index 11fdeee61..887b92489 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -1,6 +1,9 @@
-<% include('/elements/header.html', 'Quick payment entry') %>
+<& /elements/header.html, {
+ title => 'Quick payment entry',
+ etc => 'onload="preload()"'
+} &>
-<% include('/elements/error.html') %>
+<& /elements/error.html &>
<SCRIPT TYPE="text/javascript">
function warnUnload() {
@@ -14,6 +17,21 @@ function warnUnload() {
}
window.onbeforeunload = warnUnload;
+function add_row_callback(rownum, prefix) {
+ document.getElementById('enable_app'+rownum).disabled = true;
+}
+
+function custnum_update_callback(rownum, prefix) {
+ var custnum = document.getElementById('custnum'+rownum).value;
+ document.getElementById('enable_app'+rownum).disabled = (
+ custnum == 0 ||
+ num_open_invoices[rownum] < 2
+ );
+% if ( $use_discounts ) {
+ select_discount_term(rownum, prefix);
+% }
+}
+
function select_discount_term(row, prefix) {
var custnum_obj = document.getElementById('custnum'+prefix+row);
var select_obj = document.getElementById('discount_term'+prefix+row);
@@ -46,6 +64,265 @@ function select_discount_term(row, prefix) {
discount_terms(custnum_obj.value, select_discount_term_update);
}
+
+var invoices_for_row = new Object;
+
+function update_invoices(rownum, invoices) {
+ invoices_for_row[rownum] = new Object;
+ // only called before create_application_row
+ for ( var i=0; i<invoices.length; i++ ) {
+ invoices_for_row[rownum][ invoices[i].invnum ] = invoices[i];
+ }
+}
+
+function toggle_application_row(ev, next) {
+ if (!next) next = function(){}; //optional continuation
+ var rownum = this.getAttribute('rownum');
+ if ( this.checked ) {
+ var custnum = document.getElementById('custnum'+rownum).value;
+ if (!custnum) return;
+ lock_payment_row(rownum, true);
+ custnum_search_open( custnum,
+ function(returned) {
+ update_invoices(rownum, JSON.parse(returned));
+ create_application_row(rownum, 0);
+ next.call(this, rownum);
+ }
+ );
+ }
+}
+
+function lock_payment_row(rownum, flag) {
+% foreach (qw(invnum custnum customer)) {
+ obj = document.getElementById('<% $_ %>'+rownum);
+ obj.readOnly = flag;
+% }
+ document.getElementById('enable_app'+rownum).disabled = flag;
+}
+
+function delete_application_row() {
+ var rownum = this.getAttribute('rownum');
+ var appnum = this.getAttribute('appnum');
+ var tr_app = document.getElementById('row'+rownum+'.'+appnum);
+ var select_invnum = document.getElementById('invnum'+rownum+'.'+appnum);
+ if ( select_invnum.value ) {
+ invoices_for_row[rownum][ select_invnum.value ] = select_invnum.curr_invoice;
+ }
+
+ tr_app.parentNode.removeChild(tr_app);
+ if ( appnum > 0 ) {
+ document.getElementById('delete'+rownum+'.'+(appnum-1)).style.display = '';
+ }
+ else {
+ lock_payment_row(rownum, false);
+ document.getElementById('enable_app'+rownum).checked = false;
+ }
+}
+
+function amount_unapplied(rownum) {
+ var appnum = 0;
+ var total = 0;
+ var payment_amount = parseFloat(document.getElementById('paid'+rownum).value)
+ || 0;
+ while (true) {
+ var input_amount = document.getElementById('amount'+rownum+'.'+appnum);
+ if ( input_amount ) {
+ total += parseFloat(input_amount.value || 0);
+ appnum++;
+ }
+ else {
+ return payment_amount - total;
+ }
+ }
+}
+
+var change_app_amount;
+
+function choose_app_invnum() {
+ var rownum = this.getAttribute('rownum');
+ var appnum = this.getAttribute('appnum');
+ var last_invoice = this.curr_invoice;
+ if ( last_invoice ) {
+ invoices_for_row[rownum][ last_invoice['invnum'] ] = last_invoice;
+ }
+
+ if ( this.value ) {
+ var this_invoice = invoices_for_row[rownum][this.value];
+ this.curr_invoice = invoices_for_row[rownum][this.value];
+ var span_owed = document.getElementById('owed'+rownum+'.'+appnum);
+ span_owed.innerHTML = this_invoice['owed'] + '&nbsp;';
+ delete invoices_for_row[rownum][this.value];
+
+ var input_amount = document.getElementById('amount'+rownum+'.'+appnum);
+ if ( input_amount.value == '' ) {
+ input_amount.value =
+ Math.max(
+ 0, Math.min( amount_unapplied(rownum), this_invoice['owed'])
+ ).toFixed(2);
+ // trigger onchange
+ change_app_amount.call(input_amount);
+ }
+ }
+}
+
+function focus_app_invnum() {
+% # invoice numbers just display as invoice numbers
+ var rownum = this.getAttribute('rownum');
+ var add_opt = function(obj, value) {
+ var o = document.createElement('OPTION');
+ o.text = value;
+ o.value = value;
+ obj.add(o);
+ }
+ this.options.length = 0;
+ var this_invoice = this.curr_invoice;
+ if ( this_invoice ) {
+ add_opt(this, this_invoice.invnum);
+ } else {
+ add_opt(this, '');
+ }
+ for ( var x in invoices_for_row[rownum] ) {
+ add_opt(this, invoices_for_row[rownum][x].invnum);
+ }
+}
+
+function change_app_amount() {
+ var rownum = this.getAttribute('rownum');
+ var appnum = this.getAttribute('appnum');
+%# maybe some kind of warning if amount_unapplied < 0?
+%# only spawn a new application row if there are open invoices left,
+%# and this is the highest-numbered application row for the customer,
+%# and the sum of the applied amounts is < the amount of the payment,
+ if ( Object.keys(invoices_for_row[rownum]).length > 0
+ && !document.getElementById( 'row'+rownum+'.'+(parseInt(appnum) + 1) )
+ && amount_unapplied(rownum) > 0 ) {
+
+ create_application_row(rownum, parseInt(appnum) + 1);
+
+ }
+}
+
+function create_application_row(rownum, appnum) {
+ var payment_row = document.getElementById('row'+rownum);
+ var tr_app = document.createElement('TR');
+ tr_app.setAttribute('rownum', rownum);
+ tr_app.setAttribute('appnum', appnum);
+ tr_app.setAttribute('id', 'row'+rownum+'.'+appnum);
+
+ var td_invnum = document.createElement('TD');
+ td_invnum.setAttribute('colspan', 4);
+ td_invnum.style.textAlign = 'right';
+ td_invnum.appendChild(
+ document.createTextNode('<% mt('Apply to Invoice ') %>')
+ );
+ var select_invnum = document.createElement('SELECT');
+ select_invnum.setAttribute('rownum', rownum);
+ select_invnum.setAttribute('appnum', appnum);
+ select_invnum.setAttribute('id', 'invnum'+rownum+'.'+appnum);
+ select_invnum.setAttribute('name', 'invnum'+rownum+'.'+appnum);
+ select_invnum.style.textAlign = 'right';
+ select_invnum.style.width = '50px';
+ select_invnum.onchange = choose_app_invnum;
+ select_invnum.onfocus = focus_app_invnum;
+
+ td_invnum.appendChild(select_invnum);
+ tr_app.appendChild(td_invnum);
+
+ var td_owed = document.createElement('TD');
+ td_owed.style.textAlign= 'right';
+ var span_owed = document.createElement('SPAN');
+ span_owed.setAttribute('rownum', rownum);
+ span_owed.setAttribute('appnum', appnum);
+ span_owed.setAttribute('id', 'owed'+rownum+'.'+appnum);
+ td_owed.appendChild(span_owed);
+ tr_app.appendChild(td_owed);
+
+ var td_amount = document.createElement('TD');
+ td_amount.style.textAlign = 'right';
+ var input_amount = document.createElement('INPUT');
+ input_amount.size = 6;
+ input_amount.setAttribute('rownum', rownum);
+ input_amount.setAttribute('appnum', appnum);
+ input_amount.setAttribute('name', 'amount'+rownum+'.'+appnum);
+ input_amount.setAttribute('id', 'amount'+rownum+'.'+appnum);
+ input_amount.style.textAlign = 'right';
+ input_amount.onchange = change_app_amount;
+ td_amount.appendChild(input_amount);
+ tr_app.appendChild(td_amount);
+
+ var td_delete = document.createElement('TD');
+ td_delete.setAttribute('colspan', <% scalar(@fields)-2 %>);
+ var button_delete = document.createElement('INPUT');
+ button_delete.setAttribute('rownum', rownum);
+ button_delete.setAttribute('appnum', appnum);
+ button_delete.setAttribute('id', 'delete'+rownum+'.'+appnum);
+ button_delete.setAttribute('type', 'button');
+ button_delete.setAttribute('value', 'X');
+ button_delete.onclick = delete_application_row;
+ button_delete.style.color = '#ff0000';
+ button_delete.style.fontWeight = 'bold';
+ button_delete.style.paddingLeft = '2px';
+ button_delete.style.paddingRight = '2px';
+ td_delete.appendChild(button_delete);
+ tr_app.appendChild(td_delete);
+
+ var td_error = document.createElement('TD');
+ var span_error = document.createElement('SPAN');
+ span_error.setAttribute('rownum', rownum);
+ span_error.setAttribute('appnum', appnum);
+ span_error.setAttribute('id', 'error'+rownum+'.'+appnum);
+ span_error.style.color = '#ff0000';
+ td_error.appendChild(span_error);
+ tr_app.appendChild(td_error);
+
+ if ( appnum > 0 ) {
+ //remove delete button on the previous row
+ document.getElementById('delete'+rownum+'.'+(appnum-1)).style.display = 'none';
+ }
+ rownum++;
+ var next_row = document.getElementById('row'+rownum); // always exists
+ payment_row.parentNode.insertBefore(tr_app, next_row);
+
+}
+
+%# for error handling--ugly, but the alternative is translating the whole
+%# process of creating rows into Mason
+var row_array = <% encode_json(\@rows) %>;
+function preload() {
+ var rownum;
+ var appnum;
+ for (rownum=0; rownum < row_array.length; rownum++) {
+ if ( row_array[rownum].length ) {
+ var enable = document.getElementById('enable_app'+rownum);
+ enable.checked = true;
+ var preload_row = function(r) {//continuation from toggle_application_row
+ for (appnum=0; appnum < row_array[r].length; appnum++) {
+ this_app = row_array[r][appnum];
+ var x = r + '.' + appnum;
+ //set invnum
+ var select_invnum = document.getElementById('invnum'+x);
+ focus_app_invnum.call(select_invnum);
+ for (i=0; i<select_invnum.options.length; i++) {
+ if (select_invnum.options[i].value == this_app.invnum) {
+ select_invnum.selectedIndex = i;
+ }
+ }
+ choose_app_invnum.call(select_invnum);
+ //set amount
+ var input_amount = document.getElementById('amount'+x);
+ input_amount.value = this_app.amount;
+
+ //set error
+ var span_error = document.getElementById('error'+x);
+ span_error.innerHTML = this_app.error;
+ change_app_amount.call(input_amount); //creates next row
+ } //for appnum
+ }; //preload_row function
+ toggle_application_row.call(enable, null, preload_row);
+ } // if row_array[rownum].length
+ } //for rownum
+}
+
</SCRIPT>
<% include('/elements/xmlhttp.html',
@@ -57,21 +334,26 @@ function select_discount_term(row, prefix) {
<FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.btnsubmit.disabled=true;window.onbeforeunload = null;">
<!-- <B>Batch</B> <INPUT TYPE="text" NAME="paybatch"><BR><BR> -->
+<& /elements/xmlhttp.html,
+ url => $p.'misc/xmlhttp-cust_bill-search.html',
+ subs => ['custnum_search_open']
+&>
-<% include( "/elements/customer-table.html",
- name_singular => 'payment',
- header => \@header,
- fields => \@fields,
- types => \@types,
- align => \@align,
- sizes => \@sizes,
- colors => \@colors,
- param => \%param,
- footer => \@footer,
- footer_align => \@footer_align,
- custnum_update_callback => $custnum_update_callback,
- )
-%>
+<& /elements/customer-table.html,
+ name_singular => 'payment',
+ header => \@header,
+ fields => \@fields,
+ type => \@types,
+ align => \@align,
+ size => \@sizes,
+ color => \@colors,
+ param => \%param,
+ footer => \@footer,
+ footer_align => \@footer_align,
+ onchange => \@onchange,
+ custnum_update_callback => 'custnum_update_callback',
+ add_row_callback => 'add_row_callback',
+&>
<BR>
<INPUT TYPE="button" VALUE="Post payment batch" name="btnsubmit" onclick="window.onbeforeunload = null; document.OneTrueForm.submit(); this.disabled = true;">
@@ -96,18 +378,20 @@ 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 $custnum_update_callback = '';
+my @footer = ( '_TOTAL', '' );
+my @footer_align = ( 'r', 'r' );
+my @onchange = ( '', '' );;
+my $use_discounts = '';
if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) {
+ #push @header, 'Discount';
push @header, '';
push @fields, 'discount_term';
push @types, 'immutable';
@@ -116,9 +400,21 @@ if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) {
push @colors, '';
push @footer, '';
push @footer_align, '';
- $custnum_update_callback = 'select_discount_term';
+ push @onchange, '';
+ $use_discounts = 'Y';
}
+push @header, 'Allocate';
+push @fields, 'enable_app';
+push @types, 'checkbox';
+push @align, 'c';
+push @sizes, '0';
+push @colors, '';
+push @footer, '';
+push @footer_align, '';
+push @onchange, 'toggle_application_row';
+
+#push @header, 'Error';
push @header, '';
push @fields, 'error';
push @types, 'immutable';
@@ -127,7 +423,34 @@ push @sizes, '0';
push @colors, '#ff0000';
push @footer, '';
push @footer_align, '';
+push @onchange, '';
$m->comp('/elements/handle_uri_query');
+# set up for preloading
+my @rows;
+my @row_errors;
+if ( $cgi->param('error') ) {
+ my $param = $cgi->Vars;
+ my $enum = 0; #errors numbered separately
+ for( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+ $rows[$row] = [];
+ $row_errors[$row] = $param->{"error$enum"};
+ $enum++;
+ for( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) {
+ next if !$param->{"invnum$row.$app"};
+ my %this_app = map { $_ => ($param->{$_.$row.'.'.$app} || '') }
+ qw( invnum amount );
+ $this_app{'error'} = $param->{"error$enum"} || '';
+ $param->{"error$enum"} = ''; # don't pass this error through
+ $rows[$row][$app] = \%this_app;
+ $enum++;
+ }
+ }
+ for( my $row = 0; $row < @row_errors; $row++ ) {
+ $param->{"error$row"} = $row_errors[$row];
+ }
+}
+#warn Dumper {rows => \@rows, row_errors => \@row_errors };
+
</%init>
diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html
index 42cc56dfe..f9a46a898 100755
--- a/httemplate/misc/cancel_pkg.html
+++ b/httemplate/misc/cancel_pkg.html
@@ -13,22 +13,53 @@
% my $date_init = 0;
% if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') {
% $submit =~ /^(\w*)\s/;
-<& /elements/tr-input-date-field.html, {
- 'name' => 'date',
- 'value' => $date,
- 'label' => mt("$1 package on"),
- 'format' => $date_format,
-} &>
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'date',
+ 'value' => $date,
+ 'label' => mt("$1 package on"),
+ 'format' => $date_format,
+ } &>
% $date_init = 1;
% }
-% unless ( $method eq 'resume' ) { #the only one that doesn't need a reason
-<& /elements/tr-select-reason.html,
- 'field' => 'reasonnum',
- 'reason_class' => $class,
- 'curr_value' => $reasonnum,
- 'control_button' => "document.getElementById('confirm_cancel_pkg_button')",
-&>
+% if ($method eq 'uncancel' ) {
+%
+% #XXX customer also requested setup
+% # setup: what usefulness is changing or blanking this? re-charge setup fee?
+% # an option that says that would be better if that's what we want to do
+
+% # last_bill: isn't this informational? what good would editing it do?
+% # something about invoice display?
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'last_bill',
+ 'value' => ( $cgi->param('last_bill') || $cust_pkg->get('last_bill') ),
+ 'label' => mt("Last bill date"),
+ 'format' => $date_format,
+ } &>
+
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'bill',
+ 'value' => ( $cgi->param('bill') || $cust_pkg->get('bill') ),
+ 'label' => mt("Next bill date"),
+ 'format' => $date_format,
+ } &>
+
+ <& /elements/tr-checkbox.html,
+ 'label' => mt("Uncancel even if a service can't be re-provisioned"),
+ 'field' => 'svc_not_fatal',
+ 'value' => 'Y',
+ &>
+
+% $date_init = 1;
+% }
+
+% unless ( $method eq 'resume' || $method eq 'uncancel' ) {
+ <& /elements/tr-select-reason.html,
+ field => 'reasonnum',
+ reason_class => $class,
+ curr_value => $reasonnum,
+ control_button => "document.getElementById('confirm_cancel_pkg_button')",
+ &>
% }
% if ( $method eq 'adjourn' || $method eq 'suspend' ) {
@@ -49,26 +80,27 @@
% ? str2time($cgi->param('resume_date'))
% : $cust_pkg->get('resume');
-<& /elements/tr-input-date-field.html, {
- 'name' => 'resume_date',
- 'value' => $resume_date,
- 'label' => mt('Unsuspend on'),
- 'format' => $date_format,
- 'noinit' => $date_init,
-} &>
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'resume_date',
+ 'value' => $resume_date,
+ 'label' => mt('Unsuspend on'),
+ 'format' => $date_format,
+ 'noinit' => $date_init,
+ } &>
% }
</TABLE>
<BR>
<INPUT TYPE="submit" NAME="submit" ID="confirm_cancel_pkg_button"
VALUE="<% mt($submit) |h %>"
- <% $method ne 'resume' ? 'DISABLED' : '' %>>
+ <% $method !~ /^(resume|uncancel)$/ ? 'DISABLED' : '' %>>
</FORM>
</BODY>
</HTML>
<%init>
+use Date::Parse qw(str2time);
my $conf = new FS::Conf;
my $date_format = $conf->config('date_format') || '%m/%d/%Y';
@@ -111,6 +143,10 @@ if ($method eq 'cancel') {
$class = '';
$submit = 'Unsuspend Later';
$right = 'Unsuspend customer package'; #later?
+} elsif ( $method eq 'uncancel') {
+ $class = '';
+ $submit = 'Un-Cancel';
+ $right = 'Un-cancel customer package'; #later?
} else {
die 'illegal query (unknown method param)';
}
@@ -119,6 +155,7 @@ my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied" unless $curuser->access_right($right);
my $title = ucfirst($method) . ' Package';
+$title =~ s/Uncancel/Un-cancel/;
my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum})
or die "Unknown pkgnum: $pkgnum";
diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi
index dcd033ff2..a277ba407 100644
--- a/httemplate/misc/cust-part_pkg.cgi
+++ b/httemplate/misc/cust-part_pkg.cgi
@@ -1,11 +1,19 @@
<% objToJson( \@return ) %>
<%init>
-my( $custnum, $classnum ) = $cgi->param('arg');
+my( $custnum, $prospectnum, $classnum ) = $cgi->param('arg');
-#XXX i guess i should be agent-virtualized. cause "packages a customer can
-#order" is such a huge deal
-my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+
+my $agent;
+if ( $custnum ) {
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die 'unknown custnum';
+ $agent = $cust_main->agent;
+} else {
+ my $prospect_main = qsearchs('prospect_main', {'prospectnum'=>$prospectnum} )
+ or die 'unknown prospectnum';
+ $agent = $prospect_main->agent;
+}
my %hash = ( 'disabled' => '' );
if ( $classnum > 0 ) {
@@ -19,7 +27,7 @@ my @part_pkg = qsearch({
'hashref' => \%hash,
'extra_sql' =>
' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ).
- ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent ),
+ ' AND '. FS::part_pkg->agent_pkgs_sql( $agent ),
'order_by' => 'ORDER BY pkg',
});
diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi
index 002c0011a..2ae9f1021 100755
--- a/httemplate/misc/cust_main-cancel.cgi
+++ b/httemplate/misc/cust_main-cancel.cgi
@@ -1,4 +1,4 @@
-<& /elements/header.html, mt("Customer cancelled") &>
+<& /elements/header-popup.html, mt("Customer cancelled") &>
<SCRIPT TYPE="text/javascript">
window.top.location.reload();
</SCRIPT>
diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi
index 664651069..74f9b4c89 100644
--- a/httemplate/misc/cust_main-import.cgi
+++ b/httemplate/misc/cust_main-import.cgi
@@ -35,6 +35,7 @@ Import a file containing customer records.
<OPTION VALUE="extended-plus_company_and_options">Extended plus company and options
<OPTION VALUE="svc_external">External service
<OPTION VALUE="svc_external_svc_phone">External service and phone service
+ <OPTION VALUE="birthdates-acct_phone_hardware">Birthdates and account, phone and hardware services
</SELECT>
</TD>
</TR>
@@ -106,6 +107,9 @@ Uploaded files can be CSV (comma-separated value) files or Excel spreadsheets.
<b>External service and phone service</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, id, title, countrycode, phonenum, sip_password, pin</i>
<BR><BR>
+<b>Birthdates and account, phone and hardware services</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, birthdate, spouse_birthdate, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, username, _password, countrycode, phonenum, sip_password, pin, typenum, ip_addr, hw_addr, serial</i>
+<BR><BR>
+
<%$req%> Required fields
<BR><BR>
diff --git a/httemplate/misc/cust_main-suspend.cgi b/httemplate/misc/cust_main-suspend.cgi
new file mode 100755
index 000000000..61851364e
--- /dev/null
+++ b/httemplate/misc/cust_main-suspend.cgi
@@ -0,0 +1,75 @@
+<& /elements/header-popup.html, mt("Customer suspended") &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%init>
+
+#false laziness w/cust_main-cancel.cgi
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Suspend customer');
+
+my $custnum;
+my $adjourn = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ $adjourn = $cgi->param('adjourn');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ || die "Illegal custnum";
+ $custnum = $1;
+}
+
+#false laziness w/process/cancel_pkg.html
+
+#untaint reasonnum
+my $reasonnum = $cgi->param('reasonnum');
+$reasonnum =~ /^(-?\d+)$/ || die "Illegal reasonnum";
+$reasonnum = $1;
+
+if ($reasonnum == -1) {
+ $reasonnum = {
+ 'typenum' => scalar( $cgi->param('newreasonnumT') ),
+ 'reason' => scalar( $cgi->param('newreasonnum' ) ),
+ };
+}
+
+#eslaf
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+
+my @errors;
+if($cgi->param('now_or_later')) {
+ $adjourn = parse_datetime($adjourn);
+ if($adjourn) {
+ #warn "setting adjourn dates on custnum#$custnum\n";
+ my @pkgs = $cust_main->unsuspended_pkgs;
+ @errors = grep {$_} map { $_->suspend(
+ 'reason' => $reasonnum,
+ 'date' => $adjourn,
+ ) } @pkgs;
+ }
+ else {
+ @errors = ("error parsing adjourn date: ".$cgi->param('adjourn'));
+ }
+}
+else {
+ warn "suspending $cust_main";
+ @errors = $cust_main->suspend(
+ 'reason' => $reasonnum,
+ );
+}
+my $error = join(' / ', @errors) if scalar(@errors);
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(1). "suspend_cust.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/cust_main-unsuspend.cgi b/httemplate/misc/cust_main-unsuspend.cgi
new file mode 100755
index 000000000..eb4a2c8f8
--- /dev/null
+++ b/httemplate/misc/cust_main-unsuspend.cgi
@@ -0,0 +1,56 @@
+<& /elements/header-popup.html, mt("Customer unsuspended") &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%init>
+
+#false laziness w/cust_main-cancel.cgi
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Unsuspend customer');
+
+my $custnum;
+my $resume = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ $resume = $cgi->param('resume');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ || die "Illegal custnum";
+ $custnum = $1;
+}
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+
+my @errors;
+if($cgi->param('now_or_later')) {
+ $resume = parse_datetime($resume);
+ if($resume) {
+ #warn "setting resume dates on custnum#$custnum\n";
+ my @pkgs = $cust_main->suspended_pkgs;
+ @errors = grep {$_} map { $_->unsuspend(
+ 'date' => $resume,
+ ) } @pkgs;
+ }
+ else {
+ @errors = ("error parsing adjourn date: ".$cgi->param('adjourn'));
+ }
+}
+else {
+ warn "unsuspending $cust_main";
+ @errors = $cust_main->unsuspend;
+}
+my $error = join(' / ', @errors) if scalar(@errors);
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(1). "unsuspend_cust.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/delete-ftp_target.html b/httemplate/misc/delete-ftp_target.html
new file mode 100644
index 000000000..c8bd29701
--- /dev/null
+++ b/httemplate/misc/delete-ftp_target.html
@@ -0,0 +1,18 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect("${p}browse/ftp_target.html") %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal targetnum";
+my $targetnum = $1;
+
+my $target = qsearchs('ftp_target',{'targetnum'=>$targetnum});
+my $error = $target->delete;
+
+</%init>
diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi
index 23deba712..f3a31eb3b 100644
--- a/httemplate/misc/download-batch.cgi
+++ b/httemplate/misc/download-batch.cgi
@@ -1,4 +1,4 @@
-<% $pay_batch->export_batch($format) %><%init>
+<% $pay_batch->export_batch(%opt) %><%init>
#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
http_header('Content-Type' => 'text/plain' ); # not necessarily correct...
@@ -10,9 +10,14 @@ if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) {
die "No batch number (bad URL) \n";
}
-my $format;
-if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
- $format = $1;
+my %opt;
+if ( $cgi->param('gatewaynum') =~ /^(\d+)$/ ) {
+ my $gateway = FS::payment_gateway->by_key($1);
+ die "gatewaynum $1 not found" unless $gateway;
+ $opt{'gateway'} = $gateway;
+}
+elsif ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
+ $opt{'format'} = $1;
}
my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } );
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index 2332f2028..7aa024a34 100644
--- a/httemplate/misc/order_pkg.html
+++ b/httemplate/misc/order_pkg.html
@@ -32,6 +32,15 @@
&>
% }
+% if ( $conf->exists('invoice-unitprice') ) {
+ <TR>
+ <TH ALIGN="right"><% mt('Quantity') |h %> </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="quantity" SIZE=4 VALUE="<% $quantity %>">
+ </TD>
+ </TR>
+% }
+
<TR>
<TH ALIGN="right"><% mt('Start date') |h %> </TD>
<TD COLSPAN=6>
@@ -163,6 +172,11 @@ if ( $cgi->param('lock_pkgpart') ) {
my $pkgpart = $part_pkg ? $part_pkg->pkgpart : scalar($cgi->param('pkgpart'));
+my $quantity = 1;
+if ( $cgi->param('quantity') =~ /^\s*(\d+)\s*$/ ) {
+ $quantity = $1;
+}
+
my $format = $date_format. ' %T %z (%Z)'; #false laziness w/REAL_cust_pkg.cgi?
my $start_date = '';
if( ! $conf->exists('order_pkg-no_start_date') ) {
diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi
index aa371266c..1105af943 100644
--- a/httemplate/misc/process/batch-cust_pay.cgi
+++ b/httemplate/misc/process/batch-cust_pay.cgi
@@ -1,51 +1,69 @@
-% die "access denied"
-% unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch');
-%
-% my $param = $cgi->Vars;
-%
-% #my $paybatch = $param->{'paybatch'};
-% my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
-%
-% my @cust_pay = ();
-% #my $row = 0;
-% #while ( exists($param->{"custnum$row"}) ) {
-% for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
-% my $custnum = $param->{"custnum$row"};
-% my $cust_main;
-% if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) {
-% $cust_main = qsearchs({
-% 'table' => 'cust_main',
-% 'hashref' => { 'custnum' => $1 },
-% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-% });
-% }
-% if ( !$cust_main ) { # not found, try agent_custid
-% $cust_main = qsearchs({
-% 'table' => 'cust_main',
-% 'hashref' => { 'agent_custid' => $custnum },
-% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-% });
-% }
-% $custnum = $cust_main->custnum if $cust_main;
-% # if !$cust_main, then this will throw an error on batch_insert
-%
-% push @cust_pay, new FS::cust_pay {
-% 'custnum' => $custnum,
-% 'paid' => $param->{"paid$row"},
-% 'payby' => 'BILL',
-% 'payinfo' => $param->{"payinfo$row"},
-% 'discount_term' => $param->{"discount_term$row"},
-% 'paybatch' => $paybatch,
-% }
-% if $param->{"custnum$row"}
-% || $param->{"paid$row"}
-% || $param->{"payinfo$row"};
-% #$row++;
-% }
-%
-% my @errors = FS::cust_pay->batch_insert(@cust_pay);
-% my $num_errors = scalar(grep $_, @errors);
-%
+<%init>
+my $DEBUG = 0;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch');
+
+my $param = $cgi->Vars;
+warn Dumper($param) if $DEBUG;
+
+#my $paybatch = $param->{'paybatch'};
+my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
+my @cust_pay = ();
+#my $row = 0;
+#while ( exists($param->{"custnum$row"}) ) {
+for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+ my $custnum = $param->{"custnum$row"};
+ my $cust_main;
+ if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) {
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $1 },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ });
+ }
+ if ( length($custnum) and !$cust_main ) { # not found, try agent_custid
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'agent_custid' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ });
+ }
+ $custnum = $cust_main->custnum if $cust_main;
+ # if !$cust_main, then this will throw an error on batch_insert
+
+ my $cust_pay = new FS::cust_pay {
+ 'custnum' => $custnum,
+ 'paid' => $param->{"paid$row"},
+ 'payby' => 'BILL',
+ 'payinfo' => $param->{"payinfo$row"},
+ 'discount_term' => $param->{"discount_term$row"},
+ 'paybatch' => $paybatch,
+ }
+ if $param->{"custnum$row"}
+ || $param->{"paid$row"}
+ || $param->{"payinfo$row"};
+ next if !$cust_pay;
+ #$row++;
+
+ # payment applications, if any
+ my @cust_bill_pay = ();
+ for ( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) {
+ next if !$param->{"invnum$row.$app"};
+ push @cust_bill_pay, new FS::cust_bill_pay {
+ 'invnum' => $param->{"invnum$row.$app"},
+ 'amount' => $param->{"amount$row.$app"}
+ };
+ }
+ $cust_pay->set('apply_to', \@cust_bill_pay) if scalar(@cust_bill_pay) > 0;
+
+ push @cust_pay, $cust_pay;
+
+}
+
+my @errors = FS::cust_pay->batch_insert(@cust_pay);
+my $num_errors = scalar(grep $_, @errors);
+</%init>
% if ( $num_errors ) {
%
% $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : '').
@@ -65,4 +83,3 @@
%
<% $cgi->redirect(popurl(3). "search/cust_pay.html?magic=paybatch;paybatch=$paybatch") %>
% }
-
diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html
index 4f8e11b7f..79e489c70 100755
--- a/httemplate/misc/process/cancel_pkg.html
+++ b/httemplate/misc/process/cancel_pkg.html
@@ -6,19 +6,21 @@
</HTML>
<%once>
-my %past = ( 'cancel' => 'cancelled',
- 'expire' => 'expired',
- 'suspend' => 'suspended',
- 'adjourn' => 'adjourned',
- 'resume' => 'scheduled to resume',
+my %past = ( 'cancel' => 'cancelled',
+ 'expire' => 'expired',
+ 'suspend' => 'suspended',
+ 'adjourn' => 'adjourned',
+ 'resume' => 'scheduled to resume',
+ 'uncancel' => 'un-cancelled',
);
#i'm sure this is false laziness with somewhere, at least w/misc/cancel_pkg.html
-my %right = ( 'cancel' => 'Cancel customer package immediately',
- 'expire' => 'Cancel customer package later',
- 'suspend' => 'Suspend customer package',
- 'adjourn' => 'Suspend customer package later',
- 'resume' => 'Unsuspend customer package', #later?
+my %right = ( 'cancel' => 'Cancel customer package immediately',
+ 'expire' => 'Cancel customer package later',
+ 'suspend' => 'Suspend customer package',
+ 'adjourn' => 'Suspend customer package later',
+ 'resume' => 'Unsuspend customer package', #later?
+ 'uncancel' => 'Un-cancel customer package',
);
</%once>
@@ -26,7 +28,8 @@ my %right = ( 'cancel' => 'Cancel customer package immediately',
#untaint method
my $method = $cgi->param('method');
-$method =~ /^(cancel|expire|suspend|adjourn|resume)$/ or die "Illegal method";
+$method =~ /^(cancel|expire|suspend|adjourn|resume|uncancel)$/
+ or die "Illegal method";
$method = $1;
my $past_method = $past{$method};
@@ -39,7 +42,7 @@ $pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum";
$pkgnum = $1;
my $date = time;
-if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume'){
+if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') {
#untaint date
$date = $cgi->param('date'); #huh?
parse_datetime($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date";
@@ -64,7 +67,7 @@ my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} );
#untaint reasonnum
my $reasonnum = $cgi->param('reasonnum');
-if ( $method ne 'unsuspend' ) { #i.e. 'resume'
+if ( $method !~ /^(unsuspend|uncancel)$/ ) {
$reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum";
$reasonnum = $1;
@@ -76,9 +79,20 @@ if ( $method ne 'unsuspend' ) { #i.e. 'resume'
}
}
+#for uncancel
+my $last_bill =
+ $cgi->param('last_bill') ? parse_datetime($cgi->param('last_bill')) : '';
+my $bill =
+ $cgi->param('bill') ? parse_datetime($cgi->param('bill')) : '';
+
+my $svc_fatal = ( $cgi->param('svc_not_fatal') ne 'Y' );
+
my $error = $cust_pkg->$method( 'reason' => $reasonnum,
'date' => $date,
'resume_date' => $resume_date,
+ 'last_bill' => $last_bill,
+ 'bill' => $bill,
+ 'svc_fatal' => $svc_fatal,
'options' => $options,
);
diff --git a/httemplate/misc/suspend_cust.html b/httemplate/misc/suspend_cust.html
new file mode 100644
index 000000000..b41f36f8c
--- /dev/null
+++ b/httemplate/misc/suspend_cust.html
@@ -0,0 +1,79 @@
+<& /elements/header-popup.html, mt('Suspend customer') &>
+
+<& /elements/error.html &>
+
+<FORM NAME="cust_suspend_popup" ACTION="<% popurl(1) %>cust_main-suspend.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+ <P ALIGN="center"><B><% mt('Suspend this customer?') |h %></B>
+
+<TABLE BORDER="0" CELLSPACING="2"
+STYLE="margin-left:auto; margin-right:auto">
+<TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="radio" NAME="now_or_later" VALUE="0" onclick="toggle(false)" CHECKED />
+ </TD>
+ <TD ALIGN="left"><% mt('Suspend now') |h %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="radio" NAME="now_or_later" VALUE="1" onclick="toggle(true)" />
+ </TD>
+ <TD ALIGN="left"><% mt('Suspend on date: ') |h %>
+ <& /elements/input-date-field.html, {
+ 'name' => 'adjourn',
+ 'value' => time,
+ } &>
+ </TD>
+</TR>
+</TABLE>
+<SCRIPT type="text/javascript">
+function toggle(val) {
+ document.getElementById("adjourn_text").disabled = !val;
+ document.getElementById("adjourn_button").style.visibility =
+ val ? 'visible' : 'hidden';
+}
+toggle(false);
+</SCRIPT>
+
+<TABLE BGCOLOR="#cccccc", BORDER="0" CELLSPACING="2"
+STYLE="margin-left:auto; margin-right:auto">
+<& /elements/tr-select-reason.html,
+ 'field' => 'reasonnum',
+ 'reason_class' => 'C',
+ 'cgi' => $cgi,
+ 'control_button' => "document.getElementById('confirm_suspend_cust_button')",
+&>
+
+</TABLE>
+
+<BR>
+<P ALIGN="CENTER">
+<INPUT TYPE="submit" NAME="submit" ID="confirm_suspend_cust_button" VALUE="<% mt('Suspend customer') |h %>" DISABLED>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<INPUT TYPE="BUTTON" VALUE="<% mt("Don't suspend") |h %>" onClick="parent.cClick();">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+#false laziness w/cancel_cust.html
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('Suspend customer');
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+die "No customer # $custnum" unless $cust_main;
+
+</%init>
+
diff --git a/httemplate/misc/unsuspend_cust.html b/httemplate/misc/unsuspend_cust.html
new file mode 100644
index 000000000..600eb268a
--- /dev/null
+++ b/httemplate/misc/unsuspend_cust.html
@@ -0,0 +1,68 @@
+<& /elements/header-popup.html, mt('Unsuspend customer') &>
+
+<& /elements/error.html &>
+
+<FORM NAME="cust_unsuspend_popup" ACTION="<% popurl(1) %>cust_main-unsuspend.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+ <P ALIGN="center"><B><% mt('Unsuspend this customer?') |h %></B>
+
+<TABLE BORDER="0" CELLSPACING="2"
+STYLE="margin-left:auto; margin-right:auto">
+<TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="radio" NAME="now_or_later" VALUE="0" onclick="toggle(false)" CHECKED />
+ </TD>
+ <TD ALIGN="left"><% mt('Unsuspend now') |h %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="radio" NAME="now_or_later" VALUE="1" onclick="toggle(true)" />
+ </TD>
+ <TD ALIGN="left"><% mt('Unsuspend on date: ') |h %>
+ <& /elements/input-date-field.html, {
+ 'name' => 'resume',
+ 'value' => time,
+ } &>
+ </TD>
+</TR>
+</TABLE>
+<SCRIPT type="text/javascript">
+function toggle(val) {
+ document.getElementById("resume_text").disabled = !val;
+ document.getElementById("resume_button").style.visibility =
+ val ? 'visible' : 'hidden';
+}
+toggle(false);
+</SCRIPT>
+
+<BR>
+<P ALIGN="CENTER">
+<INPUT TYPE="submit" NAME="submit" ID="confirm_unsuspend_cust_button" VALUE="<% mt('Unsuspend customer') |h %>">
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<INPUT TYPE="BUTTON" VALUE="<% mt("Don't unsuspend") |h %>" onClick="parent.cClick();">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+#false laziness w/cancel_cust.html
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('Unsuspend customer');
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+die "No customer # $custnum" unless $cust_main;
+
+</%init>
+
diff --git a/httemplate/misc/xmlhttp-cust_bill-search.html b/httemplate/misc/xmlhttp-cust_bill-search.html
new file mode 100644
index 000000000..46f15d1ab
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_bill-search.html
@@ -0,0 +1,18 @@
+<% encode_json(\@return) %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die 'access denied' unless $curuser->access_right('View invoices');
+my @return;
+if ( $cgi->param('sub') eq 'custnum_search_open' ) {
+ my $custnum = $cgi->param('arg');
+ #warn "searching invoices for $custnum\n";
+ my $cust_main = FS::cust_main->by_key($custnum);
+ @return = map {
+ +{ $_->hash,
+ 'owed' => $_->owed }
+ } $cust_main->open_cust_bill
+ if $curuser->agentnums_href->{ $cust_main->agentnum };
+}
+
+</%init>
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..16f7cd2bc 100644
--- a/httemplate/misc/xmlhttp-cust_main-search.cgi
+++ b/httemplate/misc/xmlhttp-cust_main-search.cgi
@@ -1,9 +1,9 @@
% if ( $sub eq 'custnum_search' ) {
% my $custnum = $cgi->param('arg');
% my $return = [];
-% if ( $custnum =~ /^(\d+)$/ ) {
-% $return = findbycustnum($1,0);
-% $return = findbycustnum($1,1) if(!scalar(@$return));
+% if ( $custnum =~ /^(\d+)$/ ) { #should also handle
+% # cust_main-agent_custid-format') eq 'ww?d+'
+% $return = findbycustnum_or_agent_custid($1);
% }
<% objToJson($return) %>
% } elsif ( $sub eq 'smart_search' ) {
@@ -12,15 +12,27 @@
% my @cust_main = smart_search( 'search' => $string,
% 'no_fuzzy_on_exact' => 1, #pref?
% );
-% my $return = [ map [ $_->custnum, $_->name, $_->balance, $_->ucfirst_status, $_->statuscolor ], @cust_main ];
+% my $return = [ map [ $_->custnum,
+% $_->name,
+% $_->balance,
+% $_->ucfirst_status,
+% $_->statuscolor,
+% scalar($_->open_cust_bill)
+% ],
+% @cust_main
+% ];
%
<% objToJson($return) %>
% } 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) : [];
<% objToJson($return) %>
+% } else { #return nothing
+[]
+% }
% }
% elsif ( $sub eq 'exact_search' ) {
% # XXX possibly should query each element separately
@@ -39,22 +51,58 @@
% }
<%init>
-my $conf = new FS::Conf;
-
my $sub = $cgi->param('sub');
-sub findbycustnum{
- my $custnum = shift;
- my $agent = shift;
- my $hashref = { 'custnum' => $custnum };
- $hashref = { 'agent_custid' => $custnum } if $agent;
- my $c = qsearchs({
- 'table' => 'cust_main',
- 'hashref' => $hashref,
- 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
- });
- return [ $c->custnum, $c->name, $c->balance, $c->ucfirst_status, $c->statuscolor ]
- if $c;
- [];
+sub findbycustnum {
+
+ my $c = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => shift },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ }) or return [];
+
+ [ $c->custnum,
+ $c->name,
+ $c->balance,
+ $c->ucfirst_status,
+ $c->statuscolor,
+ scalar($c->open_cust_bill)
+ ];
}
+
+sub findbycustnum_or_agent_custid {
+ my $num = shift;
+
+ my @or = ( 'agent_custid = ?' );
+ my @param = ( $num );
+
+ if ( $num =~ /^\d+$/ && $num <= 2147483647 ) { #need a bigint custnum? wow
+ my $conf = new FS::Conf;
+ if ( $conf->exists('cust_main-default_agent_custid') ) {
+ push @or, "( agent_custid IS NULL AND custnum = $num )";
+ } else {
+ push @or, "custnum = $num";
+ }
+ }
+
+ my $extra_sql = ' WHERE '. $FS::CurrentUser::CurrentUser->agentnums_sql.
+ ' AND ( '. join(' OR ', @or). ' )';
+
+ [ map [ $_->custnum,
+ $_->name,
+ $_->balance,
+ $_->ucfirst_status,
+ $_->statuscolor,
+ scalar($_->open_cust_bill),
+ ],
+
+ qsearch({
+ 'table' => 'cust_main',
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'extra_param' => \@param,
+ })
+ ];
+}
+
</%init>