summaryrefslogtreecommitdiff
path: root/httemplate/misc
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2013-07-02 21:11:29 -0700
committerIvan Kohler <ivan@freeside.biz>2013-07-02 21:11:29 -0700
commit3d0a1bb06b895c5be6e3f0517d355442a6b1e125 (patch)
tree84069ebc3254825b952a482e11cdbbbc69f6fe85 /httemplate/misc
parentf3b99c11d6eed33f467dda360180a698a85c54e8 (diff)
parentd62206a94d9d49ef96640e0a8ec492679f8345e9 (diff)
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Diffstat (limited to 'httemplate/misc')
-rw-r--r--httemplate/misc/areacodes.cgi2
-rw-r--r--httemplate/misc/batch-cust_pay.html51
-rwxr-xr-xhttemplate/misc/cancel-unaudited.cgi37
-rwxr-xr-xhttemplate/misc/change_pkg.cgi2
-rwxr-xr-xhttemplate/misc/change_pkg_contact.html70
-rw-r--r--httemplate/misc/choose_tax_location.html3
-rw-r--r--httemplate/misc/confirm-address_standardize.html131
-rw-r--r--httemplate/misc/confirm-censustract.html79
-rw-r--r--httemplate/misc/cust-part_pkg.cgi2
-rwxr-xr-xhttemplate/misc/cust_main-merge.html12
-rw-r--r--httemplate/misc/cust_main_note-import.cgi12
-rwxr-xr-xhttemplate/misc/delete-customer.cgi64
-rw-r--r--httemplate/misc/delete-note.html11
-rwxr-xr-xhttemplate/misc/detach_pkg.html104
-rw-r--r--httemplate/misc/did_order_provision.html2
-rw-r--r--httemplate/misc/email-customers.html39
-rw-r--r--httemplate/misc/exchanges.cgi2
-rw-r--r--httemplate/misc/location.cgi7
-rw-r--r--httemplate/misc/macinventory.cgi11
-rw-r--r--httemplate/misc/maestro-customer_status-test.html34
-rw-r--r--httemplate/misc/maestro-customer_status.cgi16
-rw-r--r--httemplate/misc/maestro-customer_status.html16
-rw-r--r--httemplate/misc/manage_cust_email.html106
-rw-r--r--httemplate/misc/merge_cust.html42
-rw-r--r--httemplate/misc/order_pkg.html13
-rw-r--r--httemplate/misc/part_export/huawei_hlr-import_sim.html52
-rw-r--r--httemplate/misc/part_export/process/huawei_hlr-import_sim.html10
-rw-r--r--httemplate/misc/part_svc-columns.cgi2
-rw-r--r--httemplate/misc/phonenums.cgi12
-rw-r--r--httemplate/misc/process/change-password.html26
-rw-r--r--httemplate/misc/process/change_pkg_contact.html49
-rwxr-xr-xhttemplate/misc/process/delete-customer.cgi33
-rw-r--r--httemplate/misc/process/manage_cust_email.html32
-rw-r--r--httemplate/misc/process/payment.cgi10
-rwxr-xr-xhttemplate/misc/process/void-cust_bill.html2
-rw-r--r--httemplate/misc/regions.cgi26
-rw-r--r--httemplate/misc/xmlhttp-address_standardize.html51
-rw-r--r--httemplate/misc/xmlhttp-calculate_taxes.html2
-rw-r--r--httemplate/misc/xmlhttp-cust_bill-search.html38
-rw-r--r--httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html123
-rw-r--r--httemplate/misc/xmlhttp-cust_main-address_standardize.html93
-rw-r--r--httemplate/misc/xmlhttp-cust_main-censustract.html2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-discount_terms.cgi2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-duplicates.html6
-rw-r--r--httemplate/misc/xmlhttp-cust_main-email_search.html29
-rw-r--r--httemplate/misc/xmlhttp-cust_main-search.cgi8
-rw-r--r--httemplate/misc/xmlhttp-mib-browse.html161
-rw-r--r--httemplate/misc/xmlhttp-ping.html2
-rw-r--r--httemplate/misc/xmlhttp-svc_broadband-search.cgi22
49 files changed, 1307 insertions, 354 deletions
diff --git a/httemplate/misc/areacodes.cgi b/httemplate/misc/areacodes.cgi
index 9d32a3baf..4b31deb00 100644
--- a/httemplate/misc/areacodes.cgi
+++ b/httemplate/misc/areacodes.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@areacodes) %>
+<% encode_json(\@areacodes) %>\
<%init>
my( $state, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
index 887b92489..cc1a26a0e 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -5,6 +5,15 @@
<& /elements/error.html &>
+<STYLE TYPE="text/css">
+.select_invnum {
+ text-align: right;
+ width: 220px;
+}
+.select_invnum * {
+ font-family: monospace;
+}
+</STYLE>
<SCRIPT TYPE="text/javascript">
function warnUnload() {
if(document.getElementById("OneTrueTable").rows.length > 3 &&
@@ -23,15 +32,21 @@ function add_row_callback(rownum, prefix) {
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 there is a custnum and more than one open invoice, enable
+ // (and check) the box
+ var show_applications = !(custnum > 0 && num_open_invoices[rownum] > 1);
+ var enable_app_checkbox = document.getElementById('enable_app'+rownum);
+ enable_app_checkbox.disabled = show_applications;
+
% if ( $use_discounts ) {
select_discount_term(rownum, prefix);
% }
}
+function invnum_update_callback(rownum, prefix) {
+ custnum_update_callback(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);
@@ -89,6 +104,17 @@ function toggle_application_row(ev, next) {
next.call(this, rownum);
}
);
+ } else {
+ var row = document.getElementById('row'+rownum);
+ var table_rows = row.parentNode.rows;
+ for (i = row.sectionRowIndex; i < table_rows.count; i++) {
+ if ( table_rows[i].id.indexof('row'+rownum+'.') > -1 ) {
+ table_rows.removeChild(table_rows[i]);
+ } else {
+ break;
+ }
+ }
+ lock_payment_row(rownum, false);
}
}
@@ -168,21 +194,23 @@ function choose_app_invnum() {
function focus_app_invnum() {
% # invoice numbers just display as invoice numbers
var rownum = this.getAttribute('rownum');
- var add_opt = function(obj, value) {
+ var add_opt = function(obj, value, label) {
var o = document.createElement('OPTION');
- o.text = value;
+ o.text = label;
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);
+ add_opt(this, this_invoice.invnum, this_invoice.label);
} else {
- add_opt(this, '');
+ add_opt(this, '', '');
}
for ( var x in invoices_for_row[rownum] ) {
- add_opt(this, invoices_for_row[rownum][x].invnum);
+ add_opt(this,
+ invoices_for_row[rownum][x].invnum,
+ invoices_for_row[rownum][x].label);
}
}
@@ -198,7 +226,6 @@ function change_app_amount() {
&& amount_unapplied(rownum) > 0 ) {
create_application_row(rownum, parseInt(appnum) + 1);
-
}
}
@@ -220,8 +247,7 @@ function create_application_row(rownum, appnum) {
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.className = 'select_invnum';
select_invnum.onchange = choose_app_invnum;
select_invnum.onfocus = focus_app_invnum;
@@ -352,6 +378,7 @@ function preload() {
footer_align => \@footer_align,
onchange => \@onchange,
custnum_update_callback => 'custnum_update_callback',
+ invnum_update_callback => 'invnum_update_callback',
add_row_callback => 'add_row_callback',
&>
diff --git a/httemplate/misc/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi
index 4919c6632..4b3084f00 100755
--- a/httemplate/misc/cancel-unaudited.cgi
+++ b/httemplate/misc/cancel-unaudited.cgi
@@ -15,19 +15,32 @@ my($query) = $cgi->keywords;
$query =~ /^(\d+)$/;
my $svcnum = $1;
-#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum});
-#die "Unknown svcnum!" unless $svc_acct;
-
+my $error = '';
my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
-die "Unknown svcnum!" unless $cust_svc;
-my $cust_pkg = $cust_svc->cust_pkg;
-if ( $cust_pkg ) {
- errorpage( 'This account has already been audited. Cancel the '.
- qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum.
- '#cust_pkg'. $cust_pkg->pkgnum. '">'.
- 'package</A> instead.');
-}
+if ( $cust_svc ) {
+ my $cust_pkg = $cust_svc->cust_pkg;
+ if ( $cust_pkg ) {
+ errorpage( 'This account has already been audited. Cancel the '.
+ qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum.
+ '#cust_pkg'. $cust_pkg->pkgnum. '">'.
+ 'package</A> instead.'); #'
+ }
-my $error = $cust_svc->cancel;
+ $error = $cust_svc->cancel;
+} else {
+ # the rare obscure case: svc_x without cust_svc
+ my $svc_x;
+ foreach my $svcdb (FS::part_svc->svc_tables) {
+ $svc_x = qsearchs($svcdb, { 'svcnum' => $svcnum });
+ last if $svc_x;
+ }
+ if ( $svc_x ) {
+ $error = $svc_x->return_inventory
+ || $svc_x->FS::Record::delete;
+ } else {
+ # the svcnum really doesn't exist
+ $error = "svcnum $svcnum not found";
+ }
+}
</%init>
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
index 2ab9329a1..03e336cba 100755
--- a/httemplate/misc/change_pkg.cgi
+++ b/httemplate/misc/change_pkg.cgi
@@ -32,8 +32,6 @@
<& /elements/standardize_locations.html,
'form' => "OrderPkgForm",
- 'onlyship' => 1,
- 'no_company' => 1,
'callback' => 'document.OrderPkgForm.submit();',
&>
diff --git a/httemplate/misc/change_pkg_contact.html b/httemplate/misc/change_pkg_contact.html
new file mode 100755
index 000000000..c88140ebf
--- /dev/null
+++ b/httemplate/misc/change_pkg_contact.html
@@ -0,0 +1,70 @@
+<& /elements/header-popup.html, mt("Change Package Contact") &>
+
+<& /elements/error.html &>
+
+<FORM ACTION="<% $p %>misc/process/change_pkg_contact.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right"><% mt('Package') |h %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+% if ( $cust_pkg->contactnum ) {
+ <TR>
+ <TH ALIGN="right"><% mt('Current Contact') %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+ <% $cust_pkg->contact_obj->line |h %>
+ </TD>
+ </TR>
+% }
+
+<& /elements/tr-select-contact.html,
+ 'label' => mt('New Contact'), #XXX test
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_pkg->cust_main,
+&>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE = "submit"
+ VALUE = "<% $cust_pkg->contactnum ? mt("Change contact") : mt("Add contact") |h %>"
+>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg =
+ qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die "unknown pkgnum $pkgnum";
+
+my $cust_main = $cust_pkg->cust_main
+ or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+ " ( pkgnum ". cust_pkg->pkgnum. ")";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/choose_tax_location.html b/httemplate/misc/choose_tax_location.html
index dce04c77d..23099c421 100644
--- a/httemplate/misc/choose_tax_location.html
+++ b/httemplate/misc/choose_tax_location.html
@@ -1,6 +1,5 @@
<FORM NAME="choosegeocodeform">
<CENTER><BR><B>Choose tax location</B><BR><BR>
-<P>the geocode is:<% $header %></P>
<P STYLE="<% $style %>"><% $header %></P>
<SELECT NAME='geocodes' ID='geocodes' STYLE="<% $style %>">
@@ -12,7 +11,7 @@
% map { $value{$_} = $location{$_} } qw ( city state )
% if $location{country} eq 'CA';
%
-% my $value = encode_entities(objToJson({ %value })
+% my $value = encode_entities(encode_json({ %value })
% );
% my $content = '';
% $content .= $location->$_. '&nbsp;' x ( $max{$_} - length($location->$_) )
diff --git a/httemplate/misc/confirm-address_standardize.html b/httemplate/misc/confirm-address_standardize.html
new file mode 100644
index 000000000..420e8ea1d
--- /dev/null
+++ b/httemplate/misc/confirm-address_standardize.html
@@ -0,0 +1,131 @@
+<STYLE type="text/css">
+th { line-height: 150% }
+</STYLE>
+<CENTER><BR><B>
+% if ( $new{bill_error} or $new{ship_error} ) {
+Address standardization error
+% }
+% else {
+Confirm address standardization
+% }
+
+</B><BR><BR>
+<TABLE WIDTH="100%">
+% my @prefixes = ('');
+% if ( $old{same} ) {
+% @prefixes = ('bill_');
+% } elsif ( $old{billship} ) {
+% @prefixes = ('bill_', 'ship_');
+% }
+% for my $pre (@prefixes) {
+% my $name = $pre eq 'bill_' ? 'billing' : 'service';
+% if ( $new{$pre.'addr_clean'} ) {
+ <TR>
+ <TH>Entered <%$name%> address</TH>
+ <TH>Standardized <%$name%> address</TH>
+ </TR>
+ <TR>
+% if ( $old{$pre.'company'} ) {
+ <TR>
+ <TD><% $old{$pre.'company'} %></TD>
+ <TD><% $new{$pre.'company'} %></TD>
+ </TR>
+% }
+ <TR>
+ <TD><% $old{$pre.'address1'} %></TD>
+ <TD><% $new{$pre.'address1'} %></TD>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'address2'} %></TD>
+ <TD><% $new{$pre.'address2'} %></TD>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'city'} %>, <% $old{$pre.'state'} %> <% $old{$pre.'zip'} %></TD>
+ <TD><% $new{$pre.'city'} %>, <% $new{$pre.'state'} %> <% $new{$pre.'zip'} %></TD>
+ </TR>
+
+% } # if addr_clean
+% elsif ( $new{$pre.'error'} ) {
+ <TR>
+ <TH>Entered <%$name%> address</TH>
+ </TR>
+% if ( $old{$pre.'company'} ) {
+ <TR>
+ <TD><% $old{$pre.'company'} %></TD>
+ </TR>
+% }
+ <TR>
+ <TD><% $old{$pre.'address1'} %></TD>
+ <TD ROWSPAN=3><FONT COLOR="#ff0000"><B><% $new{$pre.'error'} %></B></FONT></TD>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'address2'} %></TD>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'city'} %>, <% $old{$pre.'state'} %> <% $old{$pre.'zip'} %></TD>
+ </TR>
+% } #if error
+% } # for $pre
+
+%# only do this part if address standardization provided a censustract
+% my $pre = $old{same} ? 'bill_' : 'ship_';
+% my $censustract = $new{$pre.'censustract'};
+% my $census_error = $new{$pre.'census_error'};
+% if ( $censustract ) {
+ <TR>
+ <TH>Entered census tract</TH>
+ <TH>Calculated census tract</TH>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'censustract'} %></TD>
+ <TD>
+% if ( $census_error ) {
+ <FONT COLOR="#ff0000"><% $census_error %></FONT>
+% } else {
+ <% $censustract %>
+% }
+ </TD>
+ </TR>
+% } #if censustract
+
+% if ( $new{bill_error} or $new{ship_error} ) {
+ <TR>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="confirm_manual_address();">
+ <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered <%$addresses%>
+ </BUTTON></TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="submit_abort();">
+ <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
+ </BUTTON></TD>
+ </TR>
+% }
+% else {
+ <TR>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="confirm_manual_address()();">
+ <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered <%$addresses%>
+ </BUTTON></TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="replace_address();">
+ <IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized <%$addresses%>
+ </BUTTON></TD>
+ </TR>
+ <TR ALIGN="center"><TD COLSPAN=2>
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="submit_abort();">
+ <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
+ </BUTTON>
+ </TD></TR>
+</TABLE>
+% } # !error
+<%init>
+
+# slightly weird interface...
+my $q = decode_json($cgi->param('q'));
+#warn Dumper($q);
+my %old = %{ $q->{old} };
+my %new = %{ $q->{new} };
+
+my $addresses = $old{billship} ? 'addresses' : 'address';
+
+</%init>
diff --git a/httemplate/misc/confirm-censustract.html b/httemplate/misc/confirm-censustract.html
new file mode 100644
index 000000000..6a11617e7
--- /dev/null
+++ b/httemplate/misc/confirm-censustract.html
@@ -0,0 +1,79 @@
+<CENTER><BR><B>
+% if ( $error ) {
+Census tract error
+% }
+% else {
+Confirm census tract
+% }
+</B><BR>
+% my $querystring = "census_year=$year&latitude=".$cache->get('latitude').'&longitude='.$cache->get('longitude');
+<A HREF="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?<% $querystring %>"
+ TARGET="_blank">Map service module location</A><BR>
+% $querystring = "census_year=$year&zip_code=".$cache->get('zip');
+<A HREF="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?<% $querystring %>"
+ TARGET="_blank">Map zip code center</A><BR>
+<BR>
+<TABLE>
+ <TR>
+ <TH style="width:50%">Entered census tract</TH>
+ <TH style="width:50%">Calculated census tract</TH>
+ </TR>
+ <TR>
+ <TD><% $old_tract %></TD>
+% if ( $error ) {
+ <TD><FONT COLOR="#ff0000"><% $error %></FONT></TD>
+% } else {
+ <TD><% $new_tract %></TD>
+% }
+ </TR>
+ <TR>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button"
+ onclick="set_censustract('<% $old_tract %>', '<% $year %>')">
+ <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered census tract
+ </BUTTON>
+ </TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button"
+ onclick="set_censustract('<% $new_tract %>', '<% $year %>')">
+ <IMG SRC="<%$p%>images/tick.png" ALT=""> Use calculated census tract
+ </BUTTON>
+ </TD>
+ </TR>
+ <TR>
+ <TD COLSPAN=2 ALIGN="center">
+ <BUTTON TYPE="button" onclick="submit_abort()">
+ <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
+ </BUTTON>
+ </TD>
+ </TR>
+</TABLE></CENTER>
+<%init>
+
+local $SIG{__DIE__}; #disable Mason error trap
+
+my $DEBUG = 0;
+
+my $conf = new FS::Conf;
+
+warn $cgi->param('q') if $DEBUG;
+
+my $q = decode_json($cgi->param('q'))
+ or die "bad argument '".$cgi->param('q')."'";
+
+my $pre = $q->{'same'} ? 'bill_' : 'ship_';
+my %location = (
+ map { $_ => $q->{$pre.$_} }
+ qw( company address1 address2 city state zip country latitude longitude )
+);
+
+my $old_tract = $q->{$pre.'censustract'};
+my $cache = eval { FS::GeocodeCache->new(%location) };
+$cache->set_censustract;
+my $year = FS::Conf->new->config('census_year');
+my $new_tract = $cache->get('censustract');
+my $error = $cache->get('censustract_error');
+
+warn Dumper($cache) if $DEBUG;
+
+</%init>
diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi
index a277ba407..43b92297e 100644
--- a/httemplate/misc/cust-part_pkg.cgi
+++ b/httemplate/misc/cust-part_pkg.cgi
@@ -1,4 +1,4 @@
-<% objToJson( \@return ) %>
+<% encode_json( \@return ) %>\
<%init>
my( $custnum, $prospectnum, $classnum ) = $cgi->param('arg');
diff --git a/httemplate/misc/cust_main-merge.html b/httemplate/misc/cust_main-merge.html
index 4decbef7a..3b4425fc8 100755
--- a/httemplate/misc/cust_main-merge.html
+++ b/httemplate/misc/cust_main-merge.html
@@ -31,7 +31,17 @@ if ( $cgi->param('new_custnum') =~ /^(\d+)$/ ) {
} );
die "No customer # $custnum" unless $cust_main;
- $error = $cust_main->merge($new_custnum);
+ if ( $cgi->param('merge') eq 'Y' ) {
+
+ #old-style merge: everything + delete old customer
+ $error = $cust_main->merge($new_custnum);
+
+ } else {
+
+ #new-style attach: move packages 3.0 style, that's it
+ $error = $cust_main->attach_pkgs($new_custnum);
+
+ }
} else {
$error = 'Select a customer to merge into';
diff --git a/httemplate/misc/cust_main_note-import.cgi b/httemplate/misc/cust_main_note-import.cgi
index 72ac556fd..186289517 100644
--- a/httemplate/misc/cust_main_note-import.cgi
+++ b/httemplate/misc/cust_main_note-import.cgi
@@ -164,7 +164,7 @@
<OPTION VALUE="">---</OPTION>
% my $i=0;
% foreach (@cust_main) {
- <OPTION <% $i ? '' : 'SELECTED' %> VALUE="<% $_->custnum %>"><% $_->name %></OPTION>
+ <OPTION <% $i ? '' : 'SELECTED' %> VALUE="<% $_->custnum %>"><% $_->name |h %></OPTION>
% $i++;
% }
</SELECT>
@@ -172,15 +172,15 @@
var customer_select<% $row %> = document.getElementById("cust_select<% $row %>");
customer_select<% $row %>.onchange = select_customer;
</SCRIPT>
- <INPUT TYPE="hidden" NAME="name<% $row %>" ID="name<% $row %>" VALUE="<% $i ? $cust_main[0]->name : '' %>">
+ <INPUT TYPE="hidden" NAME="name<% $row %>" ID="name<% $row %>" VALUE="<% $i ? $cust_main[0]->name : '' |h %>">
</TD>
<TD>
- <% $first %>
- <INPUT TYPE="hidden" NAME="first<% $row %>" VALUE="<% $first %>">
+ <% $first |h %>
+ <INPUT TYPE="hidden" NAME="first<% $row %>" VALUE="<% $first |h %>">
</TD>
<TD>
- <% $last %>
- <INPUT TYPE="hidden" NAME="last<% $row %>" VALUE="<% $last %>">
+ <% $last |h %>
+ <INPUT TYPE="hidden" NAME="last<% $row %>" VALUE="<% $last |h %>">
</TD>
<TD>
<% $note %>
diff --git a/httemplate/misc/delete-customer.cgi b/httemplate/misc/delete-customer.cgi
deleted file mode 100755
index 203ed36a5..000000000
--- a/httemplate/misc/delete-customer.cgi
+++ /dev/null
@@ -1,64 +0,0 @@
-<% include('/elements/header.html', 'Delete customer') %>
-
-<% include('/elements/error.html') %>
-
-<FORM ACTION="<% popurl(1) %>process/delete-customer.cgi" METHOD=POST>
-<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum |h %>">
-
-%if ( qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ) ) {
- Move uncancelled packages to customer number
- <INPUT TYPE="text" NAME="new_custnum" VALUE="<% $new_custnum |h %>"><BR><BR>
-%}
-
-This will <B>completely remove</B> all traces of this customer record. This
-is <B>not</B> what you want if this is a real customer who has simply
-canceled service with you. For that, cancel all of the customer's packages.
-(you can optionally hide cancelled customers with the <A HREF="../config/config-view.cgi#hidecancelledcustomers">hidecancelledcustomers</A> configuration option)
-<BR>
-<BR>Are you <B>absolutely sure</B> you want to delete this customer?
-<BR><INPUT TYPE="submit" VALUE="Yes">
-</FORM>
-
-<% include('/elements/footer.html') %>
-
-%#Deleting a customer you have financial records on (i.e. credits) is
-%#typically considered fraudulant bookkeeping. Remember, deleting
-%#customers should ONLY be used for completely bogus records. You should
-%#NOT delete real customers who simply discontinue service.
-%#
-%#For real customers who simply discontinue service, cancel all of the
-%#customer's packages. Customers with all cancelled packages are not
-%#billed. There is no need to take further action to prevent billing on
-%#customers with all cancelled packages.
-%#
-%#Also see the "hidecancelledcustomers" and "hidecancelledpackages"
-%#configuration options, which will allow you to surpress the display of
-%#cancelled customers and packages, respectively.
-
-<%init>
-
-my $conf = new FS::Conf;
-die "Customer deletions not enabled in configuration"
- unless $conf->exists('deletecustomers');
-
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Delete customer');
-
-my($custnum, $new_custnum);
-if ( $cgi->param('error') ) {
- $custnum = $cgi->param('custnum');
- $new_custnum = $cgi->param('new_custnum');
-} else {
- my($query) = $cgi->keywords;
- $query =~ /^(\d+)$/ or die "Illegal query: $query";
- $custnum = $1;
- $new_custnum = '';
-}
-my $cust_main = qsearchs( {
- 'table' => 'cust_main',
- 'hashref' => { 'custnum' => $custnum },
- 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-} )
- or die 'Unknown custnum';
-
-</%init>
diff --git a/httemplate/misc/delete-note.html b/httemplate/misc/delete-note.html
new file mode 100644
index 000000000..436326ff1
--- /dev/null
+++ b/httemplate/misc/delete-note.html
@@ -0,0 +1,11 @@
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer note');
+
+my ($notenum) = $cgi->keywords;
+$notenum =~ /^\d+$/ or die "bad notenum '$notenum'";
+my $note = FS::cust_main_note->by_key($notenum)
+ or die "notenum '$notenum' not found";
+$note->delete;
+</%init>
+<% $cgi->redirect($p.'view/cust_main.cgi?'.$note->custnum) %>
diff --git a/httemplate/misc/detach_pkg.html b/httemplate/misc/detach_pkg.html
new file mode 100755
index 000000000..64b3e6e3f
--- /dev/null
+++ b/httemplate/misc/detach_pkg.html
@@ -0,0 +1,104 @@
+<& /elements/header-popup.html, mt("Detach Package to New Customer") &>
+
+<SCRIPT TYPE="text/javascript" SRC="../elements/order_pkg.js"></SCRIPT>
+
+<& /elements/error.html &>
+
+<FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/detach-cust_pkg.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+% foreach my $f (qw( agentnum refnum )) {
+ <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $cust_main->$f() %>">
+% }
+<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<% $cust_main->custnum %>">
+% foreach my $f (FS::cust_main->location_fields) {
+ <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $loc->$f() |h %>">
+% }
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right"><% mt('Package') |h %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+% #always should be present for detaching, yes? #if ( $cust_pkg->contactnum ) {
+% my $cust_contact = $cust_pkg->contact_obj;
+
+ <INPUT TYPE="hidden" NAME="first" VALUE="<% $cust_contact->get('first') |h %>">
+ <INPUT TYPE="hidden" NAME="last" VALUE="<% $cust_contact->get('last') |h %>">
+
+ <TR>
+ <TH ALIGN="right"><% mt('Name') %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+ <% $cust_pkg->contact_obj->line |h %>
+ </TD>
+ </TR>
+% #}
+
+ <TR>
+ <TH ALIGN="right" VALIGN="top"><% mt('Address') %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+
+ <% $loc->location_label( 'join_string' => '<BR>',
+ 'double_space' => ' &nbsp; ',
+ 'escape_function' => \&encode_entities,
+ 'countrydefault' => $countrydefault,
+ )
+ %>
+ </TD>
+ </TR>
+
+</TABLE>
+
+%#XXX payment info
+%#XXX should be sticky on errors...
+<& /edit/cust_main/billing.html, FS::cust_main->new({}),
+ invoicing_list => [],
+
+&>
+
+<BR>
+<BR>
+<INPUT NAME = "submitButton"
+ TYPE = "submit"
+ VALUE = "<% mt("Detach package") |h %>"
+>
+
+%#and a cancel button? or is the popup close sufficient?
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+my $countrydefault = $conf->config('countrydefault') || 'US';
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg =
+ qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die "unknown pkgnum $pkgnum";
+
+my $loc = $cust_pkg->cust_location_or_main;
+
+my $cust_main = $cust_pkg->cust_main
+ or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+ " ( pkgnum ". cust_pkg->pkgnum. ")";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/did_order_provision.html b/httemplate/misc/did_order_provision.html
index 1df9444ab..8739c1619 100644
--- a/httemplate/misc/did_order_provision.html
+++ b/httemplate/misc/did_order_provision.html
@@ -21,7 +21,7 @@
% my $avail = keys(%$cust_pkg_phone);
% $anyavail = 1 if $avail;
<TR>
- <TD><% $cust_main->name %></TD>
+ <TD><% $cust_main->name |h %></TD>
<TD>
% if ( !$avail ) {
No suitable packages exist for this customer.
diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html
index d26e40298..ad67b8d7e 100644
--- a/httemplate/misc/email-customers.html
+++ b/httemplate/misc/email-customers.html
@@ -104,13 +104,19 @@ Template:
)
%><BR>
<TABLE BGCOLOR="#cccccc" CELLSPACING=0 WIDTH="100%" id="table_no_template">
- <% include('/elements/tr-input-text.html',
- 'field' => 'from',
- 'label' => 'From:',
- 'size' => 50,
- )
- %>
-
+ <& /elements/tr-td-label.html, 'label' => 'From:' &>
+ <TD><& /elements/input-text.html,
+ 'field' => 'from_name',
+ 'value' => $conf->config('company_name'), #?
+ 'size' => 20,
+ &>&nbsp;&lt;\
+ <& /elements/input-text.html,
+ 'field' => 'from_addr',
+ 'type' => 'email', # HTML5, woot
+ 'value' => $conf->config('invoice_from'),
+ 'size' => 20,
+ &>&gt;</TD>
+
<% include('/elements/tr-input-text.html',
'field' => 'subject',
'label' => 'Subject:',
@@ -120,9 +126,11 @@ Template:
<TR>
<TD ALIGN="right" VALIGN="top" STYLE="padding-top:3px">Message: </TD>
- <TD><& '/elements/htmlarea.html',
- 'field' => 'html_body',
- 'width' => 600 &></TD>
+ <TD><& /elements/htmlarea.html,
+ 'field' => 'html_body',
+ 'width' => 763,
+ &>
+ </TD>
</TR>
</TABLE>
@@ -149,6 +157,7 @@ Template:
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Bulk send customer notices');
+my $conf = FS::Conf->new;
my $table = $cgi->param('table') or die "'table' required";
my %search;
if ( $cgi->param('search') ) {
@@ -167,7 +176,15 @@ else {
my $title = 'Send customer notices';
my $num_cust;
-my $from = $cgi->param('from') || '';
+my $from = '';
+if ( $cgi->param('from') ) {
+ $from = $cgi->param('from');
+} elsif ( $cgi->param('from_name') ) {
+ $from = ($cgi->param('from_name') . ' <' . $cgi->param('from_addr') . '>');
+} elsif ( $cgi->param('from_addr') ) {
+ $from = $cgi->param('from_addr');
+}
+
my $subject = $cgi->param('subject') || '';
my $html_body = $cgi->param('html_body') || '';
diff --git a/httemplate/misc/exchanges.cgi b/httemplate/misc/exchanges.cgi
index 8a67f7bab..0de4ace25 100644
--- a/httemplate/misc/exchanges.cgi
+++ b/httemplate/misc/exchanges.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@exchanges) %>
+<% encode_json(\@exchanges) %>\
<%init>
my( $areacode, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/location.cgi b/httemplate/misc/location.cgi
index 188c5c3df..fab61dd01 100644
--- a/httemplate/misc/location.cgi
+++ b/httemplate/misc/location.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\%hash) %>
+<% encode_json(\%hash) %>\
<%init>
my $locationnum = $cgi->param('arg');
@@ -24,8 +24,9 @@ my $cust_location = qsearchs({
my %hash = ();
%hash = map { $_ => $cust_location->$_() }
- qw( address1 address2 city county state zip country
- location_kind location_type location_number )
+ ( FS::cust_main->location_fields,
+ qw( location_kind location_type location_number )
+ )
if $cust_location;
</%init>
diff --git a/httemplate/misc/macinventory.cgi b/httemplate/misc/macinventory.cgi
index b07da9726..cec0e3121 100644
--- a/httemplate/misc/macinventory.cgi
+++ b/httemplate/misc/macinventory.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@macs) %>
+<% encode_json(\@macs) %>\
<%init>
# XXX: this should be agent-virtualized / limited
@@ -13,13 +13,8 @@ die "unknown devicepart $devicepart" unless $part_device;
my $inventory_class = $part_device->inventory_class;
die "devicepart $devicepart has no inventory" unless $inventory_class;
-my @inventory_item =
+my @macs =
+ map $_->item,
qsearch('inventory_item', { 'classnum' => $inventory_class->classnum } );
-my @macs;
-
-foreach my $inventory_item ( @inventory_item ) {
- push @macs, $inventory_item->item;
-}
-
</%init>
diff --git a/httemplate/misc/maestro-customer_status-test.html b/httemplate/misc/maestro-customer_status-test.html
deleted file mode 100644
index 006492919..000000000
--- a/httemplate/misc/maestro-customer_status-test.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<% include('/elements/header.html', {
- 'title' => "Customer $custnum status",
- }) %>
-
-<% include('/elements/small_custview.html', $custnum, '', 1) %>
-<BR>
-
-<table style="border:1px solid #000000">
-% foreach my $key (keys %$return) {
-% my $value = $return->{$key};
-% $value = join(', ', @$value) if ref($value) eq 'ARRAY';
- <TR>
- <TD ALIGN="right"><% $key %>:</TD>
- <TD><B><% $value %></B></TD>
- </TR>
-% }
-</table>
-
-<% include('/elements/footer.html') %>
-<%init>
-
-my $return;
-
-my($custnum, $svcnum) = $cgi->keywords;
-if ( $custnum =~ /^(\d+)$/ ) {
-
- use FS::Maestro;
- $return = FS::Maestro::customer_status($1, $svcnum);
-
-} else {
- $return = { 'error' => 'No custnum' };
-}
-
-</%init>
diff --git a/httemplate/misc/maestro-customer_status.cgi b/httemplate/misc/maestro-customer_status.cgi
deleted file mode 100644
index ffeb53c91..000000000
--- a/httemplate/misc/maestro-customer_status.cgi
+++ /dev/null
@@ -1,16 +0,0 @@
-<% $uri->query %>
-<%init>
-
-my $uri = new URI;
-
-my($custnum, $svcnum) = $cgi->keywords;
-if ( $custnum =~ /^(\d+)$/ ) {
-
- use FS::Maestro;
- $uri->query_form( FS::Maestro::customer_status($1) );
-
-} else {
- $uri->query_form( { 'error' => 'No custnum' } );
-}
-
-</%init>
diff --git a/httemplate/misc/maestro-customer_status.html b/httemplate/misc/maestro-customer_status.html
deleted file mode 100644
index 8acae2b2a..000000000
--- a/httemplate/misc/maestro-customer_status.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<% objToJson( $return ) %>
-<%init>
-
-my $return;
-
-my($custnum, $svcnum) = $cgi->keywords;
-if ( $custnum =~ /^(\d+)$/ ) {
-
- use FS::Maestro;
- $return = FS::Maestro::customer_status($1, $svcnum);
-
-} else {
- $return = { 'error' => 'No custnum' };
-}
-
-</%init>
diff --git a/httemplate/misc/manage_cust_email.html b/httemplate/misc/manage_cust_email.html
new file mode 100644
index 000000000..3ece459bb
--- /dev/null
+++ b/httemplate/misc/manage_cust_email.html
@@ -0,0 +1,106 @@
+<& /elements/header.html, 'Manage customer email settings' &>
+<STYLE TYPE="text/css">
+.hidden { display: none }
+</STYLE>
+<& /elements/xmlhttp.html,
+ url => $p.'misc/xmlhttp-cust_main-email_search.html',
+ subs => ['email_search']
+&>
+<SCRIPT TYPE="text/javascript">
+
+function receive_search(result) {
+ var recs = JSON.parse(result);
+ var tbody = document.getElementById('tbody_results');
+ var j = tbody.rows.length;
+ for(var i = 0; i < j; i++) {
+ tbody.deleteRow(tbody.rows[i]);
+ }
+ if (recs.length > 0) {
+ for(var i = 0; i < recs.length; i++) {
+ var rec = recs[i];
+ var row = tbody.insertRow(i);
+ row.style.backgroundColor = (i % 2 ? '#eeeeee' : '#ffffff');
+
+ var cell = row.insertCell(0); // custnum
+ cell.appendChild( document.createTextNode(rec[0]) );
+ cell = row.insertCell(1); // customer name
+ cell.appendChild( document.createTextNode(rec[1]) );
+ cell = row.insertCell(2); // email
+ cell.appendChild( document.createTextNode(rec[2]) );
+
+ cell = row.insertCell(3); // invoice_email
+ var input = document.createElement('INPUT');
+ input.type = 'hidden';
+ input.name = 'custnum';
+ input.value = rec[0];
+ cell.appendChild(input);
+
+ input = document.createElement('INPUT');
+ input.type = 'checkbox';
+ input.name = 'custnum' + rec[0] + '_invoice_email';
+ input.value = 'Y';
+ input.checked = (rec[3] != 'Y');
+ cell.appendChild(input);
+ cell.style.textAlign = 'center';
+
+ cell = row.insertCell(4); // message_email
+ input = document.createElement('INPUT');
+ input.type = 'checkbox';
+ input.name = 'custnum' + rec[0] + '_message_email';
+ input.value = 'Y';
+ input.checked = (rec[4] != 'Y');
+ cell.appendChild(input);
+ cell.style.textAlign = 'center';
+ }
+ document.getElementById('div_found').style.display = '';
+ } else {
+ document.getElementById('div_notfound').style.display = '';
+ }
+}
+
+function start_search() {
+ document.getElementById('div_found').style.display = 'none';
+ document.getElementById('div_notfound').style.display = 'none';
+ var email = document.getElementById('input_email').value;
+ email_search(email, receive_search);
+}
+% if ( $cgi->param('search') ) {
+window.onload = start_search;
+% }
+</SCRIPT>
+<FORM ACTION="<%$p%>misc/process/manage_cust_email.html" METHOD="POST">
+<DIV>
+% if ( $cgi->param('done') ) {
+<P STYLE="font-weight: bold; color: #00ff00">Changes saved.</P>
+% } elsif ( $cgi->param('error') ) {
+<P STYLE="font-weight: bold; color: #ff0000"><% $cgi->param('error') |h %></P>
+% }
+ Email address:
+ <INPUT TYPE="text" ID="input_email" NAME="search"\
+ VALUE="<% $cgi->param('search') |h %>">
+ <INPUT TYPE="button" onclick="start_search()" VALUE="find">
+</DIV>
+<DIV ID="div_notfound" STYLE="display: none; padding: 1em">
+No matching email addresses found.
+</DIV>
+<DIV ID="div_found" STYLE="display: none">
+<TABLE CLASS="grid" STYLE="border-spacing: 0px">
+ <THEAD>
+ <TR STYLE="background-color: #dddddd">
+ <TH>#</TH>
+ <TH>Customer</TH>
+ <TH>Email</TH>
+ <TH>Send invoices</TH>
+ <TH>Send other notices</TH>
+ </TR>
+ </THEAD>
+ <TBODY ID="tbody_results"></TBODY>
+</TABLE>
+<INPUT TYPE="submit" VALUE="Save changes">
+</FORM>
+<& /elements/footer.html &>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+</%init>
diff --git a/httemplate/misc/merge_cust.html b/httemplate/misc/merge_cust.html
index ad075be2f..c923b7b1f 100644
--- a/httemplate/misc/merge_cust.html
+++ b/httemplate/misc/merge_cust.html
@@ -1,6 +1,6 @@
-<% include('/elements/header-popup.html', 'Merge customer' ) %>
+<& /elements/header-popup.html, 'Merge customer' &>
-<% include('/elements/error.html') %>
+<& /elements/error.html &>
<FORM NAME="cust_merge_popup" ID="cust_merge_popup" ACTION="<% popurl(1) %>cust_main-merge.html" METHOD=POST onSubmit="submit_merge(); return false;">
@@ -35,13 +35,43 @@ function do_submit_merge() {
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
<TABLE BORDER="0" CELLSPACING="2" STYLE="margin-left:auto; margin-right:auto">
- <% include('/elements/tr-search-cust_main.html',
+
+ <& /elements/tr-search-cust_main.html,
'label' => 'Merge into: ',
'field' => 'new_custnum',
'find_button' => 1,
'curr_value' => scalar($cgi->param('new_custnum')),
- )
- %>
+ &>
+
+% if ( 0 ) { #we start supporting payment info merge again in some way
+
+% if ( scalar($cust_main->ncancelled_pkgs) ) {
+ <TR>
+ <TD COLSPAN=2>
+ <& /elements/radio.html,
+ 'field' => 'merge',
+ 'value' => '',
+ 'curr_value' => scalar($cgi->param('merge')),
+ &>
+ Merge packages only.
+ </TD>
+ </TR>
+% } else {
+% $cgi->param('merge', 'Y');
+% }
+
+ <TR>
+ <TD COLSPAN=2>
+ <& /elements/radio.html,
+ 'field' => 'merge',
+ 'value' => 'Y',
+ 'curr_value' => scalar($cgi->param('merge')),
+ &>
+ Merge invoices, payments/credits, notes, tickets and delete<!-- ^Warchive --> this customer.
+ </TD>
+ </TR>
+% }
+
</TABLE>
<P ALIGN="CENTER">
@@ -54,6 +84,8 @@ function do_submit_merge() {
<%init>
+my $conf = new FS::Conf;
+
$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
my $custnum = $1;
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index c5f4509ab..39734427e 100644
--- a/httemplate/misc/order_pkg.html
+++ b/httemplate/misc/order_pkg.html
@@ -93,6 +93,12 @@
&>
% }
+<& /elements/tr-select-contact.html,
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_main,
+ 'prospect_main' => $prospect_main,
+&>
+
% if ( $cgi->param('lock_locationnum') ) {
<INPUT TYPE = "hidden"
@@ -128,10 +134,9 @@
% unless ( $cgi->param('lock_locationnum') ) {
<& /elements/standardize_locations.html,
- 'form' => "OrderPkgForm",
- 'onlyship' => 1,
- 'no_company' => 1,
- 'callback' => 'document.OrderPkgForm.submit();',
+ 'form' => "OrderPkgForm",
+ 'callback' => 'document.OrderPkgForm.submit();',
+ 'with_census' => 1,
&>
% }
diff --git a/httemplate/misc/part_export/huawei_hlr-import_sim.html b/httemplate/misc/part_export/huawei_hlr-import_sim.html
new file mode 100644
index 000000000..9b87b3d2a
--- /dev/null
+++ b/httemplate/misc/part_export/huawei_hlr-import_sim.html
@@ -0,0 +1,52 @@
+<& /elements/header-popup.html, 'Import SIMs' &>
+Import a file containing SIM card properties.<BR>
+Each row should contain the following fields, separated by spaces:<BR>
+IMSI, ICCID, PIN1, PUK1, PIN2, PUK2, ACC, Ki<BR>
+<BR>
+<& /elements/form-file_upload.html,
+ 'name' => 'ImportForm',
+ 'action' => 'process/huawei_hlr-import_sim.html',
+ 'num_files' => 1,
+ 'fields' => [ 'exportnum', 'classnum', 'agentnum', ],
+ 'message' => 'Inventory import successful',
+ 'onsubmit' => "document.ImportForm.submitButton.disabled=true;",
+&>
+<TABLE CLASS="inv" WIDTH="100%">
+ <INPUT TYPE="hidden" NAME="exportnum" VALUE="<%$exportnum%>">
+ <& /elements/file-upload.html,
+ 'field' => 'file',
+ 'label' => 'Filename',
+ &>
+ <& /elements/tr-select-agent.html,
+ 'disable_empty' => 1,
+ &>
+ <& /elements/tr-select-table.html,
+ 'table' => 'inventory_class',
+ 'name_col' => 'classname',
+ 'label' => 'Inventory class',
+ 'disable_empty' => 1,
+ &>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ NAME = "submitButton"
+ ID = "submitButton"
+ VALUE = "Import file"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my ($exportnum) = $cgi->keywords;
+$exportnum =~ /^\d+$/ or die "bad exportnum '$exportnum'";
+my $part_export = FS::part_export->by_key($exportnum)
+ or die "export $exportnum not found";
+</%init>
diff --git a/httemplate/misc/part_export/process/huawei_hlr-import_sim.html b/httemplate/misc/part_export/process/huawei_hlr-import_sim.html
new file mode 100644
index 000000000..d46700d5f
--- /dev/null
+++ b/httemplate/misc/part_export/process/huawei_hlr-import_sim.html
@@ -0,0 +1,10 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC
+ 'FS::part_export::huawei_hlr::process_import_sim', $cgi;
+
+</%init>
diff --git a/httemplate/misc/part_svc-columns.cgi b/httemplate/misc/part_svc-columns.cgi
index 060256154..a86164d06 100644
--- a/httemplate/misc/part_svc-columns.cgi
+++ b/httemplate/misc/part_svc-columns.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@output) %>
+<% encode_json(\@output) %>\
<%init>
my $conf = new FS::Conf;
diff --git a/httemplate/misc/phonenums.cgi b/httemplate/misc/phonenums.cgi
index fd5de2ae6..a048280bb 100644
--- a/httemplate/misc/phonenums.cgi
+++ b/httemplate/misc/phonenums.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@phonenums) %>
+<% encode_json(\@phonenums) %>\
<%init>
my( $exchangestring, $svcpart ) = $cgi->param('arg');
@@ -21,13 +21,13 @@ if ( $exchangestring ) {
my %opts = ();
if ( $exchangestring eq 'tollfree' ) {
$opts{'tollfree'} = 1;
- }
- #elsif ( $exchangestring =~ /^([\w\s\:\,\(\)\-]+), ([A-Z][A-Z])$/ ) {
- elsif ( $exchangestring =~ /^(.+), ([A-Z][A-Z])$/ ) {
+ } elsif ( $exchangestring =~ /^_REGION (.*)$/ ) {
+ $opts{'region'} = $1;
+ #} elsif ( $exchangestring =~ /^([\w\s\:\,\(\)\-]+), ([A-Z][A-Z])$/ ) {
+ } elsif ( $exchangestring =~ /^(.+), ([A-Z][A-Z])$/ ) {
$opts{'ratecenter'} = $1;
$opts{'state'} = $2;
- }
- else {
+ } else {
$exchangestring =~ /\((\d{3})-(\d{3})-XXXX\)\s*$/i
or die "unparsable exchange: $exchangestring";
my( $areacode, $exchange ) = ( $1, $2 );
diff --git a/httemplate/misc/process/change-password.html b/httemplate/misc/process/change-password.html
new file mode 100644
index 000000000..7cab9c4e3
--- /dev/null
+++ b/httemplate/misc/process/change-password.html
@@ -0,0 +1,26 @@
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+$cgi->param('svcnum') =~ /^(\d+)$/ or die "illegal svcnum";
+my $svcnum = $1;
+my $svc_acct = FS::svc_acct->by_key($svcnum)
+ or die "svc_acct $svcnum not found";
+my $part_svc = $svc_acct->part_svc;
+die "access denied" unless (
+ $curuser->access_right('Provision customer service') or
+ ( $curuser->access_right('Edit password') and
+ ! $part_svc->restrict_edit_password )
+ );
+my $error = $svc_acct->set_password($cgi->param('password'))
+ || $svc_acct->replace;
+
+# annoyingly specific to view/svc_acct.cgi, for now...
+$cgi->delete('password');
+</%init>
+% if ( $error ) {
+% $cgi->param('svcnum', $svcnum);
+% $cgi->param("changepw${svcnum}_error", $error);
+% } else {
+% $cgi->query_string($svcnum);
+% }
+<% $cgi->redirect($fsurl.'view/svc_acct.cgi?'.$cgi->query_string) %>
diff --git a/httemplate/misc/process/change_pkg_contact.html b/httemplate/misc/process/change_pkg_contact.html
new file mode 100644
index 000000000..2795c1197
--- /dev/null
+++ b/httemplate/misc/process/change_pkg_contact.html
@@ -0,0 +1,49 @@
+<% header(emt("Package contact $past_method")) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Change customer package');
+
+#untaint pkgnum
+my $pkgnum = $cgi->param('pkgnum');
+$pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); #needs agent virt
+
+my $contactnum = $cgi->param('contactnum');
+$contactnum =~ /^(-?\d*)$/ or die "Illegal contactnum";
+$contactnum = $1;
+
+my $past_method = $cust_pkg->contactnum ? 'changed' : 'added';
+
+my $error = '';
+
+if ( $contactnum == -1 ) {
+
+ #little false laziness w/edit/process/quick-cust_pkg.cgi, also the whole
+ # thing should be a single transaction
+ my $contact = new FS::contact {
+ 'custnum' => $cust_pkg->custnum,
+ map { $_ => scalar($cgi->param("contactnum_$_")) } qw( first last )
+ };
+ $error = $contact->insert;
+ $cust_pkg->contactnum( $contact->contactnum );
+
+} else {
+ $cust_pkg->contactnum($contactnum);
+}
+
+$error ||= $cust_pkg->replace;
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "change_pkg_contact.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/process/delete-customer.cgi b/httemplate/misc/process/delete-customer.cgi
deleted file mode 100755
index 12011311a..000000000
--- a/httemplate/misc/process/delete-customer.cgi
+++ /dev/null
@@ -1,33 +0,0 @@
-%if ( $error ) {
-% $cgi->param('error', $error);
-<% $cgi->redirect(popurl(2). "delete-customer.cgi?". $cgi->query_string ) %>
-%} elsif ( $new_custnum ) {
-<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$new_custnum") %>
-%} else {
-<% $cgi->redirect(popurl(3)) %>
-%}
-<%init>
-
-my $conf = new FS::Conf;
-die "Customer deletions not enabled in configuration"
- unless $conf->exists('deletecustomers');
-
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Delete customer');
-
-$cgi->param('custnum') =~ /^(\d+)$/;
-my $custnum = $1;
-my $new_custnum;
-if ( $cgi->param('new_custnum') ) {
- $cgi->param('new_custnum') =~ /^(\d+)$/
- or die "Illegal new customer number: ". $cgi->param('new_custnum');
- $new_custnum = $1;
-} else {
- $new_custnum = '';
-}
-my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } )
- or die "Customer not found: $custnum";
-
-my $error = $cust_main->delete('new_custnum' => $new_custnum);
-
-</%init>
diff --git a/httemplate/misc/process/manage_cust_email.html b/httemplate/misc/process/manage_cust_email.html
new file mode 100644
index 000000000..5bf1470d1
--- /dev/null
+++ b/httemplate/misc/process/manage_cust_email.html
@@ -0,0 +1,32 @@
+<% $cgi->redirect($fsurl.'misc/manage_cust_email.html?' .
+ $cgi->query_string) %>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+my $error;
+foreach my $custnum ($cgi->param('custnum')) {
+ my $cust = FS::cust_main->by_key($custnum)
+ or die "customer not found: $custnum\n";
+ my $new_invoice_noemail =
+ $cgi->param('custnum'.$custnum.'_invoice_email') ? '' : 'Y';
+ my $new_message_noemail =
+ $cgi->param('custnum'.$custnum.'_message_email') ? '' : 'Y';
+ if ( $new_invoice_noemail ne $cust->invoice_noemail
+ or $new_message_noemail ne $cust->message_noemail ) {
+
+ $cust->set('invoice_noemail', $new_invoice_noemail);
+ $cust->set('message_noemail', $new_message_noemail);
+ $error ||= $cust->replace;
+
+ }
+ $cgi->delete('custnum'.$custnum.'_invoice_email');
+ $cgi->delete('custnum'.$custnum.'_message_email');
+}
+$cgi->delete('custnum');
+if ( $error ) {
+ $cgi->param('error' => $error); # probably unnecessary...
+} else {
+ $cgi->param('done' => 1) unless $error;
+}
+</%init>
diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi
index 506e26684..981614e76 100644
--- a/httemplate/misc/process/payment.cgi
+++ b/httemplate/misc/process/payment.cgi
@@ -210,7 +210,15 @@ if ( $cgi->param('save') ) {
$new->set( 'paycvv' => '');
}
- $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
+ if ( $payby eq 'CARD' ) {
+ my $bill_location = FS::cust_location->new;
+ $bill_location->set( $_ => $cgi->param($_) )
+ foreach @{$payby2fields{$payby}};
+ $new->set('bill_location' => $bill_location);
+ # will do nothing if the fields are all unchanged
+ } else {
+ $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
+ }
my $error = $new->replace($cust_main);
errorpage("payment processed successfully, but error saving info: $error")
diff --git a/httemplate/misc/process/void-cust_bill.html b/httemplate/misc/process/void-cust_bill.html
index 899901a50..accee27fd 100755
--- a/httemplate/misc/process/void-cust_bill.html
+++ b/httemplate/misc/process/void-cust_bill.html
@@ -1,6 +1,6 @@
%if ( $error ) {
% $cgi->param('error', $error);
-<% $cgi->redirect(popurl(1). "void-cust_bill.html?". $cgi->query_string ) %>
+<% $cgi->redirect(popurl(2). "void-cust_bill.html?". $cgi->query_string ) %>
%} else {
<& /elements/header-popup.html, 'Invoice voided' &>
<SCRIPT TYPE="text/javascript">
diff --git a/httemplate/misc/regions.cgi b/httemplate/misc/regions.cgi
new file mode 100644
index 000000000..31538b08e
--- /dev/null
+++ b/httemplate/misc/regions.cgi
@@ -0,0 +1,26 @@
+<% encode_json(\@regions) %>\
+<%init>
+
+my( $state, $svcpart ) = $cgi->param('arg');
+
+my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } );
+die "unknown svcpart $svcpart" unless $part_svc;
+
+my @regions = ();
+if ( $state ) {
+
+ my @exports = $part_svc->part_export_did;
+ if ( scalar(@exports) > 1 ) {
+ die "more than one DID-providing export attached to svcpart $svcpart";
+ } elsif ( ! @exports ) {
+ die "no DID providing export attached to svcpart $svcpart";
+ }
+ my $export = $exports[0];
+
+ my $something = $export->get_dids('state'=>$state);
+
+ @regions = @{ $something };
+
+}
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-address_standardize.html b/httemplate/misc/xmlhttp-address_standardize.html
new file mode 100644
index 000000000..618265364
--- /dev/null
+++ b/httemplate/misc/xmlhttp-address_standardize.html
@@ -0,0 +1,51 @@
+<% encode_json($return) %>\
+<%init>
+
+local $SIG{__DIE__}; #disable Mason error trap
+
+my $DEBUG = 0;
+
+my $conf = new FS::Conf;
+
+my $sub = $cgi->param('sub');
+
+warn $cgi->param('arg') if $DEBUG;
+
+my %old = %{ decode_json($cgi->param('arg')) }
+ or die "bad argument '".$cgi->param('arg')."'";
+
+my %new;
+
+my @prefixes = ('');
+if ( $old{same} ) {
+ @prefixes = ('bill_');
+} elsif ( $old{billship} ) {
+ @prefixes = ('bill_', 'ship_');
+}
+my $all_same = 1;
+foreach my $pre ( @prefixes ) {
+
+ my $location = {
+ map { $_ => $old{$pre.$_} }
+ qw( company address1 address2 city state zip country )
+ };
+
+ my $cache = eval { FS::GeocodeCache->standardize($location) };
+ $cache->set_coord;
+ # don't do set_censustract here, though censustract may be set by now
+
+ foreach ( keys(%$cache) ) {
+ $new{$pre.$_} = $cache->get($_);
+ }
+
+ foreach ( qw(address1 address2 city state zip country) ) {
+ $all_same = 0 if ( $new{$pre.$_} ne $old{$pre.$_} );
+ last if !$all_same;
+ }
+
+ $all_same = 0 if $new{$pre.'error'};
+}
+
+my $return = { old => \%old, new => \%new, all_same => $all_same };
+warn "result:\n".encode_json($return) if $DEBUG;
+</%init>
diff --git a/httemplate/misc/xmlhttp-calculate_taxes.html b/httemplate/misc/xmlhttp-calculate_taxes.html
index d3dc36acf..ed7bd0173 100644
--- a/httemplate/misc/xmlhttp-calculate_taxes.html
+++ b/httemplate/misc/xmlhttp-calculate_taxes.html
@@ -1,4 +1,4 @@
-<% objToJson($return) %>
+<% encode_json($return) %>\
<%init>
my $DEBUG = 0;
diff --git a/httemplate/misc/xmlhttp-cust_bill-search.html b/httemplate/misc/xmlhttp-cust_bill-search.html
index 46f15d1ab..6082dc771 100644
--- a/httemplate/misc/xmlhttp-cust_bill-search.html
+++ b/httemplate/misc/xmlhttp-cust_bill-search.html
@@ -1,18 +1,40 @@
-<% encode_json(\@return) %>
+<% encode_json(\@return) %>\
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
die 'access denied' unless $curuser->access_right('View invoices');
my @return;
+my $date_format = FS::Conf->new->config('date_format') || '%m/%d/%Y';
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 };
+ if ( $custnum =~ /^(\d+)$/ ) {
+#warn "searching invoices for $custnum\n";
+ my $cust_main = FS::cust_main->by_key($custnum);
+ if ( $curuser->agentnums_href->{ $cust_main->agentnum } ) {
+ my @open_bills = $cust_main->open_cust_bill;
+ my $invnum_len;
+ my $owed_len;
+ my $date_len;
+ foreach my $cust_bill (@open_bills) {
+ my $invnum = $cust_bill->invnum;
+ my $owed = $cust_bill->owed;
+ my $date = time2str($date_format, $cust_bill->_date);
+ $invnum_len = length($invnum) if length($invnum) > $invnum_len;
+ $owed_len = length($owed) if length($owed) > $owed_len;
+ $date_len = length($date) if length($date) > $date_len;
+
+ push @return, { $cust_bill->hash,
+ 'owed' => $owed,
+ 'date' => $date };
+ }
+ my $format = '%' . $invnum_len . 'd - %' . $date_len . 's - '.
+ (FS::Conf->new->config('money_char') || '$') .
+ '%' . $owed_len . '.2f';
+ foreach (@return) {
+ $_->{label} = sprintf($format, $_->{invnum}, $_->{date}, $_->{owed});
+ }
+ } #if agentnum
+ } #if $custnum
}
</%init>
diff --git a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
new file mode 100644
index 000000000..c0db3e2c4
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
@@ -0,0 +1,123 @@
+<% encode_json($return) %>\
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" unless $curuser->access_right('Credit line items');
+
+my $DEBUG = 0;
+
+my $conf = new FS::Conf;
+
+my $sub = $cgi->param('sub');
+
+my $return = {};
+
+if ( $sub eq 'calculate_taxes' ) {
+
+ {
+
+ my %arg = $cgi->param('arg');
+ $return = \%arg;
+ warn join('', map "$_: $arg{$_}\n", keys %arg )
+ if $DEBUG;
+
+ #some false laziness w/cust_credit::credit_lineitems
+
+ my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $arg{custnum} },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die 'unknown customer';
+
+ my @billpkgnums = split(',', $arg{billpkgnums});
+ my @setuprecurs = split(',', $arg{setuprecurs});
+ my @amounts = split(',', $arg{amounts});
+
+ my @cust_bill_pkg = ();
+ my $taxlisthash = {};
+ while ( @billpkgnums ) {
+ my $billpkgnum = shift @billpkgnums;
+ my $setuprecur = shift @setuprecurs;
+ my $amount = shift @amounts;
+
+ my $cust_bill_pkg = qsearchs({
+ 'table' => 'cust_bill_pkg',
+ 'hashref' => { 'billpkgnum' => $billpkgnum },
+ 'addl_from' => 'LEFT JOIN cust_bill USING (invnum)',
+ 'extra_sql' => 'AND custnum = '. $cust_main->custnum,
+ }) or die "unknown billpkgnum $billpkgnum";
+
+ #shouldn't be passed# next if $cust_bill_pkg->pkgnum == 0;
+
+ if ( $setuprecur eq 'setup' ) {
+ $cust_bill_pkg->setup($amount);
+ $cust_bill_pkg->recur(0);
+ $cust_bill_pkg->unitrecur(0);
+ $cust_bill_pkg->type('');
+ } else {
+ $cust_bill_pkg->recur($amount);
+ $cust_bill_pkg->setup(0);
+ $cust_bill_pkg->unitsetup(0);
+ }
+
+ push @cust_bill_pkg, $cust_bill_pkg;
+
+ my $part_pkg = $cust_bill_pkg->part_pkg;
+ $cust_main->_handle_taxes( $part_pkg,
+ $taxlisthash,
+ $cust_bill_pkg,
+ $cust_bill_pkg->cust_pkg,
+ $cust_bill_pkg->cust_bill->_date,
+ $cust_bill_pkg->cust_pkg->pkgpart,
+ );
+
+ }
+
+ if ( @cust_bill_pkg ) {
+
+ my $listref_or_error =
+ $cust_main->calculate_taxes( \@cust_bill_pkg, $taxlisthash, $cust_bill_pkg[0]->cust_bill->_date );
+
+ unless ( ref( $listref_or_error ) ) {
+ $return->{error} = $listref_or_error;
+ last;
+ }
+
+ my @taxlines = ();
+ my $taxtotal = 0;
+ $return->{taxlines} = \@taxlines;
+ foreach my $taxline ( @$listref_or_error ) {
+ my $amount = $taxline->setup;
+ my $desc = $taxline->desc;
+ foreach my $location ( @{$taxline->cust_bill_pkg_tax_location}, @{$taxline->cust_bill_pkg_tax_rate_location} ) {
+ my $taxlocnum = $location->locationnum || '';
+ my $taxratelocnum = $location->taxratelocationnum || '';
+ $location->cust_bill_pkg_desc($taxline->desc); #ugh @ that kludge
+ $taxtotal += $location->amount;
+ push @taxlines,
+ #[ $location->desc, $taxline->setup, $taxlocnum, $taxratelocnum ];
+ [ $location->desc, $location->amount, $taxlocnum, $taxratelocnum ];
+ $amount -= $location->amount;
+ }
+ if ($amount > 0) {
+ $taxtotal += $amount;
+ push @taxlines,
+ [ $taxline->itemdesc. ' (default)', sprintf('%.2f', $amount), '', '' ];
+ }
+ }
+
+ $return->{taxlines} = \@taxlines;
+ $return->{taxtotal} = sprintf('%.2f', $taxtotal);
+
+ } else {
+
+ $return->{taxlines} = [];
+ $return->{taxtotal} = '0.00';
+
+ }
+
+ }
+
+}
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-address_standardize.html b/httemplate/misc/xmlhttp-cust_main-address_standardize.html
deleted file mode 100644
index d0627cd59..000000000
--- a/httemplate/misc/xmlhttp-cust_main-address_standardize.html
+++ /dev/null
@@ -1,93 +0,0 @@
-<% objToJson($return) %>
-<%init>
-
-my $DEBUG = 0;
-
-my $conf = new FS::Conf;
-
-my $sub = $cgi->param('sub');
-
-my $return = {};
-
-if ( $sub eq 'address_standardize' ) {
-
- my %arg = $cgi->param('arg');
- $return = \%arg;
- warn join('', map "$_: $arg{$_}\n", keys %arg )
- if $DEBUG;
-
- my $userid = $conf->config('usps_webtools-userid');
- my $password = $conf->config('usps_webtools-password');
-
- if ( length($userid) && length($password) ) {
-
- my $verifier = Business::US::USPS::WebTools::AddressStandardization->new( {
- UserID => $userid, #$ENV{USPS_WEBTOOLS_USERID},
- Password => $password, #$ENV{USPS_WEBTOOLS_PASSWORD},
- #Testing => 1,
- } );
-
- foreach my $pre ( '', 'ship_' ) {
- next unless ($pre || !$arg{onlyship});
-
- my($zip5, $zip4) = split('-',$arg{$pre.'zip'});
-
- my %usps_args = (
- FirmName => $arg{$pre.'company'},
- Address2 => $arg{$pre.'address1'},
- Address1 => $arg{$pre.'address2'},
- City => $arg{$pre.'city'},
- State => $arg{$pre.'state'},
- Zip5 => $zip5,
- Zip4 => $zip4,
- );
- warn join('', map "$_: $usps_args{$_}\n", keys %usps_args )
- if $DEBUG;
-
- my $hash = $verifier->verify_address( %usps_args );
-
- warn $verifier->response
- if $DEBUG;
-
- unless ( $verifier->is_error ) {
-
- my $zip = $hash->{Zip5};
- $zip .= '-'. $hash->{Zip4} if $hash->{Zip4} =~ /\d/;
-
- $return = {
- %$return,
- "new_$pre".'company' => $hash->{FirmName},
- "new_$pre".'address1' => $hash->{Address2},
- "new_$pre".'address2' => $hash->{Address1},
- "new_$pre".'city' => $hash->{City},
- "new_$pre".'state' => $hash->{State},
- "new_$pre".'zip' => $zip,
- };
-
- my @fields = (qw( company address1 address2 city state zip )); #hmm
-
- my $changed =
- scalar( grep { $return->{$pre.$_} ne $return->{"new_$pre$_"} }
- @fields
- )
- ? 1 : 0;
-
- $return->{$pre.'address_standardized'} = $changed;
-
- } else {
-
- $return->{$pre.'error'} = "USPS WebTools error: ".
- $verifier->{error}{description};
-
-
- }
-
- }
-
- }
-
- $return;
-
-}
-
-</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-censustract.html b/httemplate/misc/xmlhttp-cust_main-censustract.html
index 4b00898da..4c708a4c4 100644
--- a/httemplate/misc/xmlhttp-cust_main-censustract.html
+++ b/httemplate/misc/xmlhttp-cust_main-censustract.html
@@ -1,4 +1,4 @@
-<% objToJson($return) %>
+<% encode_json($return) %>\
<%init>
my %arg = $cgi->param('arg');
diff --git a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
index b524e69fc..36b18b455 100644
--- a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
+++ b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
@@ -16,7 +16,7 @@
% }
% }
%
-<% objToJson($return) %>
+<% encode_json($return) %>\
% }
<%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-duplicates.html b/httemplate/misc/xmlhttp-cust_main-duplicates.html
index 6654b3e39..7ee00af66 100644
--- a/httemplate/misc/xmlhttp-cust_main-duplicates.html
+++ b/httemplate/misc/xmlhttp-cust_main-duplicates.html
@@ -8,9 +8,9 @@ Choose an existing customer
<TR>
<TD ALIGN="right" VALIGN="top"><B><% $custnum %>: </B></TD>
<TD ALIGN="left">
- <% $_->name %>&mdash;<B><FONT COLOR="#<%$_->statuscolor%>"><%$_->ucfirst_cust_status%></FONT></B><BR>
-<% $_->address1 %><BR>
-<% $_->city %>,&nbsp;<% $_->state %>&nbsp;&nbsp;<% $_->zip %>
+ <% $_->name |h %>&mdash;<B><FONT COLOR="#<%$_->statuscolor%>"><%$_->ucfirst_cust_status%></FONT></B><BR>
+<% $_->address1 |h %><BR>
+<% $_->city |h %>,&nbsp;<% $_->state %>&nbsp;&nbsp;<% $_->zip %>
</TD>
<TD ALIGN="center">
<INPUT TYPE="radio" NAME="dup_custnum" VALUE="<%$custnum%>">
diff --git a/httemplate/misc/xmlhttp-cust_main-email_search.html b/httemplate/misc/xmlhttp-cust_main-email_search.html
new file mode 100644
index 000000000..0d830826c
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_main-email_search.html
@@ -0,0 +1,29 @@
+<% encode_json(\@result) %>\
+<%init>
+die 'access denied'
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+my $sub = $cgi->param('sub');
+my $email = $cgi->param('arg');
+my @where = (
+ "cust_main_invoice.dest != 'POST'",
+ "cust_main_invoice.dest LIKE ".dbh->quote('%'.$email.'%'),
+ $FS::CurrentUser::CurrentUser->agentnums_sql(table => 'cust_main'),
+);
+my @cust_main = qsearch({
+ 'table' => 'cust_main',
+ 'select' => 'cust_main.*, cust_main_invoice.dest',
+ 'addl_from' => 'JOIN cust_main_invoice USING (custnum)',
+ 'extra_sql' => 'WHERE '.join(' AND ', @where),
+});
+
+my @result = map {
+ [ $_->custnum,
+ $_->name,
+ $_->dest,
+ $_->invoice_noemail,
+ $_->message_noemail,
+ ]
+} @cust_main;
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi
index acf7e70e2..73c9ff8ec 100644
--- a/httemplate/misc/xmlhttp-cust_main-search.cgi
+++ b/httemplate/misc/xmlhttp-cust_main-search.cgi
@@ -5,7 +5,7 @@
% # cust_main-agent_custid-format') eq 'ww?d+'
% $return = findbycustnum_or_agent_custid($1);
% }
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } elsif ( $sub eq 'smart_search' ) {
%
% my $string = $cgi->param('arg');
@@ -22,14 +22,14 @@
% @cust_main
% ];
%
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } elsif ( $sub eq 'invnum_search' ) {
%
% my $string = $cgi->param('arg');
% if ( $string =~ /^(\d+)$/ ) {
% my $inv = qsearchs('cust_bill', { 'invnum' => $1 });
% my $return = $inv ? findbycustnum($inv->custnum) : [];
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } else { #return nothing
[]
% }
@@ -47,7 +47,7 @@
% city => $_->city,
% };
% }
-<% objToJson($return) %>
+<% encode_json($return) %>\
% }
<%init>
diff --git a/httemplate/misc/xmlhttp-mib-browse.html b/httemplate/misc/xmlhttp-mib-browse.html
new file mode 100644
index 000000000..f3084ff6f
--- /dev/null
+++ b/httemplate/misc/xmlhttp-mib-browse.html
@@ -0,0 +1,161 @@
+%#<% Data::Format::HTML->new->format($index{by_path}) %>
+% my $json = "JSON"->new->canonical;
+<% $json->encode($result) %>
+<%init>
+#<%once> #enable me in production
+use SNMP;
+SNMP::initMib();
+my $mib = \%SNMP::MIB;
+
+# make an index of the leaf nodes
+my %index = (
+ by_objectID => {}, # {.1.3.6.1.2.1.1.1}
+ by_fullname => {}, # {iso.org.dod.internet.mgmt.mib-2.system.sysDescr}
+ by_path => {}, # {iso}{org}{dod}{internet}{mgmt}{mib-2}{system}{sysDescr}
+ module => {}, #{SNMPv2-MIB}{by_path}{iso}{org}...
+ #{SNMPv2-MIB}{by_fullname}{iso.org...}
+);
+
+my %name_of_oid = (); # '.1.3.6.1' => 'iso.org.dod.internet'
+
+# build up path names
+my $fullname;
+$fullname = sub {
+ my $oid = shift;
+ return $name_of_oid{$oid} if exists $name_of_oid{$oid};
+
+ my $object = $mib->{$oid};
+ my $myname = '.' . $object->{label};
+ # cut off the last element and recurse
+ $oid =~ /^(\.[\d\.]+)?(\.\d+)$/;
+ if ( length($1) ) {
+ $myname = $fullname->($1) . $myname;
+ }
+ return $name_of_oid{$oid} = $myname
+};
+
+my @oids = keys(%$mib); # dotted numeric OIDs
+foreach my $oid (@oids) {
+ my $object = {};
+ %$object = %{ $mib->{$oid} }; # untie it
+ # and remove references
+ delete $object->{parent};
+ delete $object->{children};
+ delete $object->{nextNode};
+ $index{by_objectID}{$oid} = $object;
+ my $myname = $fullname->($oid);
+ $object->{fullname} = $myname;
+ $index{by_fullname}{$myname} = $object;
+ my $moduleID = $object->{moduleID};
+ $index{module}{$moduleID} ||= { by_fullname => {}, by_path => {} };
+ $index{module}{$moduleID}{by_fullname}{$myname} = $object;
+}
+my @names = sort {$a cmp $b} keys %{ $index{by_fullname} };
+foreach my $myname (@names) {
+ my $obj = $index{by_fullname}{$myname};
+ my $moduleID = $obj->{moduleID};
+ my @parts = split('\.', $myname);
+ shift @parts; # always starts with an empty string
+ for ($index{by_path}, $index{module}{$moduleID}{by_path}) {
+ my $subindex = $_;
+ for my $this_part (@parts) {
+ $subindex = $subindex->{$this_part} ||= {};
+ }
+ # $subindex now = $index{by_path}{foo}{bar}{baz}.
+ # set {''} = the object with that name.
+ # and set object $index{by_path}{foo}{bar}{baz}{''} =
+ # the object named .foo.bar.baz
+ $subindex->{''} = $obj;
+ }
+}
+
+#</%once>
+#<%init>
+# no ACL for this
+my $sub = $cgi->param('sub');
+my $result = {};
+if ( $sub eq 'search' ) {
+ warn "search: ".$cgi->param('arg')."\n";
+ my ($module, $string) = split(':', $cgi->param('arg'), 2);
+ my $idx; # the branch of the index to use for this search
+ if ( $module eq 'ANY' ) {
+ $idx = \%index;
+ } elsif (exists($index{module}{$module}) ) {
+ $idx = $index{module}{$module};
+ } else {
+ warn "unknown MIB moduleID: $module\n";
+ $idx = {}; # will return nothing, because you've somehow sent a bad moduleID
+ }
+ if ( exists($index{by_fullname}{$string}) ) {
+ warn "exact match\n";
+ # don't make this module-selective--if the path matches an existing
+ # object, return that object
+ %$result = %{ $index{by_fullname}{$string} }; # put the object info in $result
+ #warn Dumper $result;
+ }
+ my @choices; # menu options to return
+ if ( $string =~ /^[\.\d]+$/ ) {
+ # then this is a numeric path
+ # ignore the module filter, and return everything starting with $string
+ if ( $string =~ /^\./ ) {
+ @choices = grep /^\Q$string\E/, keys %{$index{by_objectID}};
+ } else {
+ # or everything containing it
+ @choices = grep /\Q$string\E/, keys %{$index{by_objectID}};
+ }
+ @choices = map { $index{by_objectID}{$_}->{fullname} } @choices;
+ } elsif ( $string eq '' or $string =~ /^\./ ) {
+ # then this is an absolute path
+ my @parts = split('\.', $string);
+ shift @parts;
+ my $subindex = $idx->{by_path};
+ my $path = '';
+ @choices = keys %$subindex;
+ # walk all the specified path parts
+ foreach my $this_part (@parts) {
+ # stop before walking off the map
+ last if !exists($subindex->{$this_part});
+ $subindex = $subindex->{$this_part};
+ $path .= '.' . $this_part;
+ @choices = grep {$_} keys %$subindex;
+ }
+ # skip uninteresting nodes: those that aren't accessible nodes (have no
+ # data type), and have only one path forward
+ while ( scalar(@choices) == 1
+ and (!exists $subindex->{''} or $subindex->{''}->{type} eq '') ) {
+
+ $subindex = $subindex->{ $choices[0] };
+ $path .= '.' . $choices[0];
+ @choices = grep {$_} keys %$subindex;
+
+ }
+
+ # if we are on an existing node, and the entered path didn't exactly
+ # match another node, return the current node as the result
+ if (!keys %$result and exists($subindex->{''})) {
+ %$result = %{ $subindex->{''} };
+ }
+ # prepend the path up to this point
+ foreach (@choices) {
+ $_ = $path.'.'.$_;
+ # also label accessible nodes for the UI
+ if ( exists($subindex->{$_}{''}) and $subindex->{$_}{''}{'type'} ) {
+ $_ .= '-';
+ }
+ }
+ # also include one level above the originally requested path,
+ # for tree-like navigation
+ if ( $string =~ /^(.+)\.[^\.]+/ ) {
+ unshift @choices, $1;
+ }
+ } else {
+ # then this is a full-text search
+ warn "/$string/\n";
+ @choices = grep /\Q$string\E/i, keys(%{ $idx->{by_fullname} });
+ }
+ @choices = sort @choices;
+ $result->{choices} = \@choices;
+} elsif ( $sub eq 'get_module_list' ) {
+ $result = { modules => [ sort keys(%{ $index{module} }) ] };
+}
+</%init>
diff --git a/httemplate/misc/xmlhttp-ping.html b/httemplate/misc/xmlhttp-ping.html
index e99303207..01baa3f57 100644
--- a/httemplate/misc/xmlhttp-ping.html
+++ b/httemplate/misc/xmlhttp-ping.html
@@ -1,4 +1,4 @@
-<% objToJson($return) %>
+<% encode_json($return) %>\
<%init>
my $conf = new FS::Conf;
diff --git a/httemplate/misc/xmlhttp-svc_broadband-search.cgi b/httemplate/misc/xmlhttp-svc_broadband-search.cgi
new file mode 100644
index 000000000..578e6140e
--- /dev/null
+++ b/httemplate/misc/xmlhttp-svc_broadband-search.cgi
@@ -0,0 +1,22 @@
+% if ( $sub eq 'smart_search' ) {
+%
+% my $string = $cgi->param('arg');
+% my @svc_broadband = FS::svc_broadband->smart_search( $string );
+% my $return = [ map { my $cust_pkg = $_->cust_svc->cust_pkg;
+% [ $_->svcnum,
+% $_->label. ( $cust_pkg
+% ? ' ('. $cust_pkg->cust_main->name. ')'
+% : ''
+% ),
+% ];
+% }
+% @svc_broadband,
+% ];
+%
+<% encode_json($return) %>\
+% }
+<%init>
+
+my $sub = $cgi->param('sub');
+
+</%init>