summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rwxr-xr-xhttemplate/browse/agent_type.cgi4
-rwxr-xr-xhttemplate/browse/part_export.cgi48
-rwxr-xr-xhttemplate/edit/agent_type.cgi2
-rw-r--r--httemplate/edit/cdr_type.cgi22
-rw-r--r--httemplate/edit/credit-cust_bill_pkg.html249
-rwxr-xr-xhttemplate/edit/cust_credit.cgi1
-rw-r--r--httemplate/edit/elements/part_export/broadband_snmp.html101
-rw-r--r--httemplate/edit/elements/part_export/foot.html6
-rw-r--r--httemplate/edit/elements/part_export/head.html19
-rw-r--r--httemplate/edit/part_export.cgi9
-rwxr-xr-xhttemplate/edit/part_pkg.cgi24
-rw-r--r--httemplate/edit/process/cdr_type.cgi1
-rw-r--r--httemplate/edit/process/credit-cust_bill_pkg.html44
-rwxr-xr-xhttemplate/edit/process/cust_credit.cgi4
-rw-r--r--httemplate/edit/process/part_export.cgi29
-rw-r--r--httemplate/edit/rate_time.cgi41
-rw-r--r--httemplate/elements/auto-table.html310
-rw-r--r--httemplate/elements/contact.html2
-rw-r--r--httemplate/elements/menu.html97
-rw-r--r--httemplate/elements/searchbar-address2.html2
-rw-r--r--httemplate/elements/searchbar-cust_bill.html2
-rw-r--r--httemplate/elements/searchbar-cust_main.html2
-rw-r--r--httemplate/elements/searchbar-cust_svc.html2
-rw-r--r--httemplate/elements/searchbar-prospect.html2
-rw-r--r--httemplate/elements/searchbar-ticket.html2
-rw-r--r--httemplate/elements/select-did.html69
-rw-r--r--httemplate/elements/select-mib-popup.html186
-rw-r--r--httemplate/elements/select-phonenum.html10
-rw-r--r--httemplate/elements/select-region.html88
-rw-r--r--httemplate/elements/select-terms.html2
-rw-r--r--httemplate/elements/standardize_locations.js19
-rw-r--r--httemplate/elements/xmlhttp.html32
-rw-r--r--httemplate/graph/elements/report.html4
-rw-r--r--httemplate/misc/phonenums.cgi10
-rw-r--r--httemplate/misc/regions.cgi26
-rw-r--r--httemplate/misc/xmlhttp-address_standardize.html8
-rw-r--r--httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html123
-rw-r--r--httemplate/misc/xmlhttp-mib-browse.html161
-rw-r--r--httemplate/search/cust_bill_pkg.cgi112
-rw-r--r--httemplate/search/cust_credit_bill_pkg.html81
-rw-r--r--httemplate/search/cust_pkg_susp.html2
-rw-r--r--httemplate/search/customer_accounting_summary.html11
-rw-r--r--httemplate/search/elements/search-html.html18
-rw-r--r--httemplate/search/elements/search-xls.html2
-rw-r--r--httemplate/search/elements/search.html4
-rw-r--r--httemplate/search/log.html221
-rw-r--r--httemplate/search/report_cust_bill_pkg.html118
-rw-r--r--httemplate/search/report_cust_credit_bill_pkg.html104
-rwxr-xr-xhttemplate/search/report_tax-xls.cgi3
-rw-r--r--httemplate/view/cust_main/payment_history.html10
-rw-r--r--httemplate/view/part_event-targets.html3
51 files changed, 2123 insertions, 329 deletions
diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi
index 1959302d2..7711dccf7 100755
--- a/httemplate/browse/agent_type.cgi
+++ b/httemplate/browse/agent_type.cgi
@@ -44,9 +44,9 @@ my $agent_type = shift;
[
{
#'data' => $part_pkg->pkg. ' - '. $part_pkg->comment,
- 'data' => $type_pkgs->pkg. ' - '.
+ 'data' => encode_entities($type_pkgs->pkg). ' - '.
( $type_pkgs->custom ? '(CUSTOM) ' : '' ).
- $type_pkgs->comment,
+ encode_entities($type_pkgs->comment),
'align' => 'left',
'link' => $p. 'edit/part_pkg.cgi?'. $type_pkgs->pkgpart,
},
diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi
index b7ecc00a6..91238a0fd 100755
--- a/httemplate/browse/part_export.cgi
+++ b/httemplate/browse/part_export.cgi
@@ -43,14 +43,56 @@ function part_export_areyousure(href) {
<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
<% itable() %>
% my %opt = $part_export->options;
-% foreach my $opt ( keys %opt ) {
+% my $defs = $part_export->info->{options};
+% my %multiples;
+% foreach my $opt (keys %$defs) { # is a Tie::IxHash
+% my $group = $defs->{$opt}->{multiple};
+% if ( $group ) {
+% my @values = split("\n", $opt{$opt});
+% $multiples{$group} ||= [];
+% push @{ $multiples{$group} }, [ $opt, @values ] if @values;
+% delete $opt{$opt};
+% } elsif (length($opt{$opt})) { # the normal case
+%# foreach my $opt ( keys %opt ) {
<TR>
<TD ALIGN="right" VALIGN="top" WIDTH="33%"><% $opt %>:&nbsp;</TD>
<TD ALIGN="left" WIDTH="67%"><% encode_entities($opt{$opt}) %></TD>
</TR>
-% }
-
+% delete $opt{$opt};
+% }
+% }
+% # now any that are somehow not in the options list
+% foreach my $opt (keys %opt) {
+% if ( length($opt{$opt}) ) {
+ <TR>
+ <TD ALIGN="right" VALIGN="top" WIDTH="33%"><% $opt %>:&nbsp;</TD>
+ <TD ALIGN="left" WIDTH="67%"><% encode_entities($opt{$opt}) %></TD>
+ </TR>
+% }
+% }
+% # now show any multiple-option groups
+% foreach (sort keys %multiples) {
+% my $set = $multiples{$_};
+ <TR><TD ALIGN="center" COLSPAN=2><TABLE CLASS="grid">
+ <TR>
+% foreach my $col (@$set) {
+ <TH><% shift @$col %></TH>
+% }
+ </TR>
+% while ( 1 ) {
+ <TR>
+% my $end = 1;
+% foreach my $col (@$set) {
+ <TD><% shift @$col %></TD>
+% $end = 0 if @$col;
+% }
+ </TR>
+% last if $end;
+% }
+ </TABLE></TD></TR>
+% } #foreach keys %multiples
+
</TABLE>
</TD>
diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi
index 8a6fbc255..b75757fb1 100755
--- a/httemplate/edit/agent_type.cgi
+++ b/httemplate/edit/agent_type.cgi
@@ -20,7 +20,7 @@ Select which packages agents of this type may sell to customers<BR>
'source_obj' => $agent_type,
'link_table' => 'type_pkgs',
'target_table' => 'part_pkg',
- 'name_callback' => sub { $_[0]->pkg_comment(nopkgpart => 1); },
+ 'name_callback' => sub { encode_entities( $_[0]->pkg_comment(nopkgpart => 1) ); },
'target_link' => $p.'edit/part_pkg.cgi?',
'disable-able' => 1,
diff --git a/httemplate/edit/cdr_type.cgi b/httemplate/edit/cdr_type.cgi
index 5d2c66216..c69610607 100644
--- a/httemplate/edit/cdr_type.cgi
+++ b/httemplate/edit/cdr_type.cgi
@@ -7,11 +7,24 @@ calls and SMS messages. Each CDR type must have a set of rates
configured in the rate tables.
<BR>
<FORM METHOD="POST" ACTION="<% "${p}edit/process/cdr_type.cgi" %>">
-<% include('/elements/auto-table.html',
- 'header' => [ 'Type#', 'Name' ],
- 'fields' => [ qw( cdrtypenum cdrtypename ) ],
+<TABLE ID="AutoTable" BORDER=0 CELLSPACING=0>
+ <TR>
+ <TH>Type#</TH>
+ <TH>Name</TH>
+ </TR>
+ <TR ID="cdr_template">
+ <TD>
+ <INPUT NAME="cdrtypenum" SIZE=16 MAXLENGTH=16 ALIGN="right">
+ </TD>
+ <TD>
+ <INPUT NAME="cdrtypename" SIZE=16 MAXLENGTH=16>
+ </TD>
+ </TR>
+<& /elements/auto-table.html,
+ 'template_row' => 'cdr_template',
'data' => \@data,
- ) %>
+&>
+</TABLE>
<INPUT TYPE="submit" VALUE="Apply changes"> </FORM> <BR>
<% include('/elements/footer.html') %>
<%init>
@@ -20,7 +33,6 @@ die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
my @data = (
- map { [ $_->cdrtypenum, $_->cdrtypename ] }
qsearch({
'table' => 'cdr_type',
'hashref' => {},
diff --git a/httemplate/edit/credit-cust_bill_pkg.html b/httemplate/edit/credit-cust_bill_pkg.html
new file mode 100644
index 000000000..e317936b3
--- /dev/null
+++ b/httemplate/edit/credit-cust_bill_pkg.html
@@ -0,0 +1,249 @@
+<& /elements/header-popup.html, 'Credit line items' &>
+
+<FORM ACTION="process/credit-cust_bill_pkg.html" METHOD="POST">
+<INPUT TYPE="hidden" NAME="crednum" VALUE="">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum |h %>">
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="">
+<INPUT TYPE="hidden" NAME="_date" VALUE="<% time %>">
+<table>
+
+% my $old_invnum = 0;
+%# foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+% foreach my $item ( @items ) {
+% my( $setuprecur, $cust_bill_pkg ) = @$item;
+
+% my $method = $setuprecur eq 'setup' ? 'setup' : 'recur';
+% my $amount = $cust_bill_pkg->$method();
+% my $credited = $cust_bill_pkg->credited('', '', 'setuprecur'=>$method);
+% $amount -= $credited;
+% $amount = sprintf('%.2f', $amount);
+% next unless $amount > 0;
+
+% if ( $cust_bill_pkg->invnum ne $old_invnum ) {
+ <TR><TD COLSPAN=3 BGCOLOR="#f8f8f8">&nbsp;</TD></TR>
+ <TR><TH COLSPAN=3 BGCOLOR="#f8f8f8" ALIGN="left">Invoice #<% $cust_bill_pkg->invnum %> - <% time2str($date_format, $cust_bill_pkg->cust_bill->_date) %></TD></TR>
+% $old_invnum = $cust_bill_pkg->invnum;
+% }
+
+ <TR>
+ <TD>
+ <INPUT TYPE = "checkbox"
+ NAME = "billpkgnum<% $cust_bill_pkg->billpkgnum.'-'. $setuprecur %>"
+ VALUE = "<% $amount %>"
+ onClick = "calc_total(this)"
+ data-amount = "<% $amount %>"
+ data-billpkgnum = "<% $cust_bill_pkg->billpkgnum %>"
+ data-setuprecur = "<% $setuprecur %>"
+ >
+ </TD>
+ <TD BGCOLOR="#ffffff"><% $cust_bill_pkg->desc |h %></TD>
+%# show one-time/setup vs recur vs usage?
+ <TD BGCOLOR="#ffffff" ALIGN="right"><% $money_char. $amount %></TD>
+ </TR>
+
+% }
+
+<TR><TD COLSPAN=3 BGCOLOR="#f8f8f8">&nbsp;</TD></TR>
+<TR>
+ <TD></TD>
+ <TD ALIGN="right">Subtotal: </TD>
+ <TD ALIGN="right" ID="subtotal_td"><% $money_char %><% sprintf('%.2f', 0) %></TD>
+</TR>
+<TR>
+ <TD></TD>
+ <TD ALIGN="right">Taxes: </TD>
+ <TD ALIGN="right" ID="taxtotal_td"><% $money_char %><% sprintf('%.2f', 0) %></TD>
+</TR>
+<TR>
+ <TD></TD>
+ <TH ALIGN="right">Total credit amount: </TD>
+ <TH ALIGN="right" ID="total_td"><% $money_char %><% sprintf('%.2f', 0) %></TD>
+</TR>
+<INPUT TYPE="hidden" NAME="amount" ID="total_el" VALUE="0.00">
+
+</table>
+
+<table>
+
+<& /elements/tr-select-reason.html,
+ 'field' => 'reasonnum',
+ 'reason_class' => 'R',
+ #XXX reconcile both this and show_taxes wanteding to enable this
+ 'control_button' => "document.getElementById('credit_button')",
+ 'cgi' => $cgi,
+&>
+
+<TR>
+ <TD ALIGN="right"><% mt('Additional info') |h %></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="addlinfo" VALUE="<% $cgi->param('addlinfo') |h %>">
+ </TD>
+</TR>
+
+</table>
+
+<BR>
+<INPUT TYPE="submit" ID="credit_button" VALUE="Credit" DISABLED>
+
+</FORM>
+
+<% include( '/elements/xmlhttp.html',
+ 'url' => $p.'misc/xmlhttp-cust_bill_pkg-calculate_taxes.html',
+ 'subs' => [ 'calculate_taxes' ],
+ )
+%>
+<SCRIPT TYPE="text/javascript">
+
+function show_taxes(arg) {
+ var argsHash = eval('(' + arg + ')');
+
+ //XXX add an 'ErrorMessage' section to the HTML and re-enable
+ //var error = argsHash['error'];
+
+ //var paragraph = document.getElementById('ErrorMessage');
+ //if (error) {
+ // paragraph.innerHTML = 'Error: ' + error;
+ // paragraph.style.color = '#ff0000';
+ //} else {
+ // paragraph.innerHTML = '';
+ //}
+
+ var taxlines = argsHash['taxlines'];
+
+//XXX display the tax lines? just a total will do for now
+//
+// var table = document.getElementById('ApplicationTable');
+//
+// var aFoundRow = 0;
+// for (i = 0; taxlines[i]; i++) {
+// var itemdesc = taxlines[i][0];
+// var locnum = taxlines[i][2];
+// if (taxlines[i][3]) {
+// locnum = taxlines[i][3];
+// }
+//
+// var found = 0;
+// for (var row = 2; table.rows[row]; row++) {
+// var inputs = table.rows[row].getElementsByTagName('input');
+// if (! inputs.length) {
+// while ( table.rows[row] ) {
+// table.deleteRow(row);
+// }
+// break;
+// }
+// if ( inputs.item(4).value == itemdesc && inputs.item(2).value == locnum )
+// {
+// inputs.item(0).value = taxlines[i][1];
+// aFoundRow = found = row;
+// break;
+// }
+// }
+// if (! found) {
+// var row = table.insertRow(table.rows.length);
+// var warning_cell = document.createElement('TD');
+// warning_cell.style.color = '#ff0000';
+// warning_cell.colSpan = 2;
+// warning_cell.innerHTML = 'Calculated Tax - ' + itemdesc + ' - ' +
+// taxlines[i][1] + ' will not be applied';
+// row.appendChild(warning_cell);
+// }
+// }
+//
+// if (aFoundRow) {
+// sub_changed(table.rows[aFoundRow].getElementsByTagName('input').item(0));
+// }
+
+ var subtotal = parseFloat( argsHash['subtotal'] );
+
+ var taxtotal = parseFloat( argsHash['taxtotal'] );
+ document.getElementById('taxtotal_td').innerHTML =
+ '<% $money_char %>' + taxtotal.toFixed(2);
+
+ var total = subtotal + taxtotal;
+ document.getElementById('total_td').innerHTML =
+ '<% $money_char %>' + total.toFixed(2);
+ document.getElementById('total_el').value = total.toFixed(2);
+
+ //XXX reconcile both this and the reason selector wanteding to enable this
+ if ( total > 0 ) {
+ document.getElementById('credit_button').disabled = false;
+ }
+
+}
+
+function calc_total(what) {
+
+ document.getElementById('credit_button').disabled = true;
+
+ var subtotal = 0;
+ // bah, a pain, just using an attribute var re = /^billpkgnum(\d+)$/;
+
+ var el = what.form.elements;
+ var billpkgnums = [];
+ var setuprecurs = [];
+ var amounts = [];
+ for (var i=0; i<el.length; i++) {
+ if ( el[i].type == 'checkbox' && el[i].checked ) {
+ subtotal += parseFloat( el[i].getAttribute('data-amount') );
+ amounts.push( el[i].getAttribute('data-amount') );
+ billpkgnums.push( el[i].getAttribute('data-billpkgnum') );
+ setuprecurs.push( el[i].getAttribute('data-setuprecur') );
+ }
+ }
+
+ document.getElementById('subtotal_td').innerHTML =
+ '<% $money_char %>' + subtotal.toFixed(2);
+
+ var args = new Array(
+ 'custnum', '<% $custnum %>',
+ 'subtotal', subtotal,
+ 'billpkgnums', billpkgnums.join(),
+ 'setuprecurs', setuprecurs.join(),
+ 'amounts', amounts.join()
+ );
+
+ calculate_taxes( args, show_taxes );
+
+}
+</SCRIPT>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" unless $curuser->access_right('Post credit');
+
+#a tiny bit of false laziness w/search/cust_bill_pkg.cgi, but we're pretty
+# specialized and a piece of UI, not a report
+#slightly more false laziness w/httemplate/edit/elements/ApplicationCommon.html
+# show_taxes & calc_total here/do_calculate_tax there
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+}) or die 'unknown customer';
+
+my @cust_bill_pkg = qsearch({
+ 'select' => 'cust_bill_pkg.*',
+ 'table' => 'cust_bill_pkg',
+ 'addl_from' => 'LEFT JOIN cust_bill USING (invnum)',
+ 'extra_sql' => "WHERE custnum = $custnum AND pkgnum != 0",
+ 'order_by' => 'ORDER BY invnum ASC, billpkgnum ASC',
+});
+
+my @items = map { my %hash = $_->disintegrate;
+ map [ $_, $hash{$_} ],
+ keys(%hash);
+ }
+ @cust_bill_pkg;
+
+#omit line items which have been previously credited? would be nice
+
+</%init>
diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi
index 6e8a9c989..4dba1e769 100755
--- a/httemplate/edit/cust_credit.cgi
+++ b/httemplate/edit/cust_credit.cgi
@@ -34,6 +34,7 @@
<TD>
<INPUT TYPE="text" NAME="addlinfo" VALUE="<% $cgi->param('addlinfo') |h %>">
</TD>
+ </TR>
% if ( $conf->exists('credits-auto-apply-disable') ) {
<INPUT TYPE="HIDDEN" NAME="apply" VALUE="no">
diff --git a/httemplate/edit/elements/part_export/broadband_snmp.html b/httemplate/edit/elements/part_export/broadband_snmp.html
new file mode 100644
index 000000000..4c0367c5a
--- /dev/null
+++ b/httemplate/edit/elements/part_export/broadband_snmp.html
@@ -0,0 +1,101 @@
+<%doc>
+</%doc>
+<& head.html, %opt &>
+<INPUT TYPE="hidden" NAME="options" VALUE="community,version,ip_addr_change_to_new,timeout">
+<& /elements/tr-select.html,
+ label => 'SNMP version',
+ field => 'version',
+ options => [ '', 'v1', 'v2c' ],
+ labels => { v1 => '1', v2c => '2c' },
+ curr_value => $part_export->option('version') &>
+<& /elements/tr-input-text.html,
+ label => 'Community',
+ field => 'community',
+ curr_value => $part_export->option('community'),
+&>
+<& /elements/tr-checkbox.html,
+ label => 'Send IP address changes to new address',
+ field => 'ip_addr_change_to_new',
+ value => 1,
+ curr_value => $part_export->option('ip_addr_change_to_new'),
+&>
+<& /elements/tr-input-text.html,
+ label => 'Timeout (seconds)',
+ field => 'timeout',
+ curr_value => $part_export->option('timeout'),
+&>
+</TABLE>
+<script type="text/javascript">
+function open_select_mib(obj) {
+ nd(1); // if there's already one open, close it
+ var rownum = obj.rownum;
+ var curr_oid = obj.value || '';
+ var url = '<%$fsurl%>/elements/select-mib-popup.html?' +
+ 'callback=receive_mib;' +
+ 'arg=' + rownum +
+ ';curr_value=' + curr_oid;
+ overlib(
+ OLiframeContent(url, 550, 450, '<% $popup_name %>', 0, 'auto'),
+ CAPTION, 'Select MIB object', STICKY, AUTOSTATUSCAP,
+ MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK,
+ BGCOLOR, '#333399', CGCOLOR, '#333399',
+ CLOSETEXT, 'Close'
+ );
+}
+function receive_mib(obj, rownum) {
+ //console.log(JSON.stringify(obj));
+ // we don't really need the numeric OID or any of the other properties
+ document.getElementById('oid'+rownum).value = obj.fullname;
+ document.getElementById('datatype'+rownum).value = obj.type;
+}
+</script>
+
+<table bgcolor="#cccccc" border=0 cellspacing=3>
+<TR>
+ <TH>Action</TH>
+ <TH>Object</TH>
+ <TH>Type</TH>
+ <TH>Value</TH>
+</TR>
+<TR id="mytemplate">
+ <TD>
+ <SELECT NAME="action">
+% foreach ('', qw(insert delete replace suspend unsuspend)) {
+ <OPTION VALUE="<%$_%>"><%$_%></OPTION>
+% }
+ </SELECT>
+ </TD>
+ <TD>
+ <INPUT NAME="oid" ID="oid" SIZE="60" onclick="open_select_mib(this)">
+ </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="datatype" ID="datatype" READONLY=1>
+ </TD>
+ <TD>
+ <INPUT NAME="value" ID="value">
+ </TD>
+</TR>
+<& /elements/auto-table.html,
+ template_row => 'mytemplate',
+ fieldorder => ['action', 'oid', 'datatype', 'value'],
+ data => \@data,
+&>
+<INPUT TYPE="hidden" NAME="multi_options" VALUE="action,oid,datatype,value">
+<& foot.html, %opt &>
+<%init>
+my %opt = @_;
+my $part_export = $opt{part_export} || FS::part_export->new;
+
+my @actions = split("\n", $part_export->option('action'));
+my @oids = split("\n", $part_export->option('oid'));
+my @types = split("\n", $part_export->option('datatype'));
+my @values = split("\n", $part_export->option('value'));
+
+my @data;
+while (@actions or @oids or @values) {
+ my @thisrow = (shift(@actions), shift(@oids), shift(@types), shift(@values));
+ push @data, \@thisrow if grep length($_), @thisrow;
+}
+
+my $popup_name = 'popup-'.time."-$$-".rand() * 2**32;
+</%init>
diff --git a/httemplate/edit/elements/part_export/foot.html b/httemplate/edit/elements/part_export/foot.html
new file mode 100644
index 000000000..9cb8073ce
--- /dev/null
+++ b/httemplate/edit/elements/part_export/foot.html
@@ -0,0 +1,6 @@
+</TABLE>
+<INPUT TYPE="hidden" NAME="nodomain" VALUE="<% $opt{export_info}{nodomain} %>">
+<INPUT TYPE="submit" VALUE="<% $opt{part_export}->exportnum ? 'Apply changes' : 'Add export' %>">
+<%init>
+my %opt = @_;
+</%init>
diff --git a/httemplate/edit/elements/part_export/head.html b/httemplate/edit/elements/part_export/head.html
new file mode 100644
index 000000000..cb0ab894a
--- /dev/null
+++ b/httemplate/edit/elements/part_export/head.html
@@ -0,0 +1,19 @@
+% if ( $export_info->{no_machine} ) {
+<INPUT TYPE="hidden" NAME="machine" VALUE="">
+<INPUT TYPE="hidden" NAME="svc_machine" VALUE="N">
+% } else {
+% # clone this from edit/part_export.cgi if this case ever gets used
+% }
+<INPUT TYPE="hidden" NAME="exporttype" VALUE="<%$layer |h%>">
+<% ntable('cccccc', 2) %>
+<TR>
+ <TD ALIGN="right" ><% emt('Description') %></TD>
+ <TD BGCOLOR="#ffffff" WIDTH="600"><% $notes %></TD>
+</TR>
+<%init>
+my %opt = @_;
+my $layer = $opt{layer};
+my $part_export = $opt{part_export};
+my $export_info = $opt{export_info};
+my $notes = $opt{notes} || $export_info->{notes};
+</%init>
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
index 0407ee77b..4dd253be8 100644
--- a/httemplate/edit/part_export.cgi
+++ b/httemplate/edit/part_export.cgi
@@ -62,6 +62,15 @@ my $widget = new HTML::Widgets::SelectLayers(
'html_between' => "</TD></TR></TABLE>\n",
'layer_callback' => sub {
my $layer = shift;
+ # create 'config_element' to generate the whole layer with a Mason component
+ if ( my $include = $exports->{$layer}{config_element} ) {
+ # might need to adjust the scope of this at some point
+ return $m->scomp($include,
+ part_export => $part_export,
+ layer => $layer,
+ export_info => $exports->{$layer}
+ );
+ }
my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!.
ntable("#cccccc",2);
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index f3ad8f52d..50aeb4595 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -622,23 +622,23 @@ END
my $warning =
'Changing the setup or recurring fee will create a new package definition. '.
'Continue?';
-
+
+$javascript .= "function confirm_submit(f) {";
if ( $conf->exists('part_pkg-lineage') ) {
$javascript .= "
- function confirm_submit(f) {
-
- var fields = Array('setup_fee','recur_fee');
- for(var i=0; i < fields.length; i++) {
- if ( f[fields[i]].value != f[fields[i]].defaultValue ) {
- return confirm('$warning');
- }
- }
- return true;
+
+ var fields = Array('setup_fee','recur_fee');
+ for(var i=0; i < fields.length; i++) {
+ if ( f[fields[i]].value != f[fields[i]].defaultValue ) {
+ return confirm('$warning');
+ }
}
";
}
-
-$javascript .= '</SCRIPT>';
+$javascript .= "
+ return true;
+}
+</SCRIPT>";
tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
diff --git a/httemplate/edit/process/cdr_type.cgi b/httemplate/edit/process/cdr_type.cgi
index b661de75d..ba9881dc4 100644
--- a/httemplate/edit/process/cdr_type.cgi
+++ b/httemplate/edit/process/cdr_type.cgi
@@ -10,7 +10,6 @@ die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
my %vars = $cgi->Vars;
-warn Dumper(\%vars)."\n";
my %old = map { $_->cdrtypenum => $_ } qsearch('cdr_type', {});
diff --git a/httemplate/edit/process/credit-cust_bill_pkg.html b/httemplate/edit/process/credit-cust_bill_pkg.html
new file mode 100644
index 000000000..8b2f3f3ea
--- /dev/null
+++ b/httemplate/edit/process/credit-cust_bill_pkg.html
@@ -0,0 +1,44 @@
+%if ($error) {
+% errorpage_popup($error); #XXX redirect back for correction...
+%} else {
+<& /elements/header-popup.html, 'Credit successful' &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post credit');
+
+my @billpkgnum_setuprecurs =
+ map { $_ =~ /^billpkgnum(\d+\-\w*)$/ or die 'gm#23'; $1; }
+ grep { $_ =~ /^billpkgnum\d+\-\w*$/ && $cgi->param($_) } $cgi->param;
+
+my @billpkgnums = ();
+my @setuprecurs = ();
+my @amounts = ();
+foreach my $billpkgnum_setuprecur (@billpkgnum_setuprecurs) {
+ my $amount = $cgi->param("billpkgnum$billpkgnum_setuprecur");
+ my( $billpkgnum, $setuprecur ) = split('-', $billpkgnum_setuprecur);
+ push @billpkgnums, $billpkgnum;
+ push @setuprecurs, $setuprecur;
+ push @amounts, $amount;
+}
+
+my $error = FS::cust_credit->credit_lineitems(
+ #the lineitems to credit
+ 'billpkgnums' => \@billpkgnums,
+ 'setuprecurs' => \@setuprecurs,
+ 'amounts' => \@amounts,
+
+ #the credit
+ 'newreasonnum' => scalar($cgi->param('newreasonnum')),
+ 'newreasonnum_type' => scalar($cgi->param('newreasonnumT')),
+ map { $_ => scalar($cgi->param($_)) }
+ #fields('cust_credit')
+ qw( custnum _date amount reason reasonnum addlinfo ), #pkgnum eventnum
+);
+
+</%init>
diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi
index 776112ac0..245f31af7 100755
--- a/httemplate/edit/process/cust_credit.cgi
+++ b/httemplate/edit/process/cust_credit.cgi
@@ -15,7 +15,7 @@
%
% $dbh->commit or die $dbh->errstr if $oldAutoCommit;
%
-<% header(emt('Credit sucessful')) %>
+<% header(emt('Credit successful')) %>
<SCRIPT TYPE="text/javascript">
window.top.location.reload();
</SCRIPT>
@@ -27,7 +27,7 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Post credit');
-$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
+$cgi->param('custnum') =~ /^(\d+)$/ or die "Illegal custnum!";
my $custnum = $1;
$cgi->param('reasonnum') =~ /^(-?\d+)$/ or die "Illegal reasonnum";
diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi
index 6432d6b15..bcb9c0df1 100644
--- a/httemplate/edit/process/part_export.cgi
+++ b/httemplate/edit/process/part_export.cgi
@@ -13,15 +13,40 @@ my $exportnum = $cgi->param('exportnum');
my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum;
+my %vars = $cgi->Vars;
#fixup options
#warn join('-', split(',',$cgi->param('options')));
my %options = map {
- my @values = $cgi->param($_);
- my $value = scalar(@values) > 1 ? join (' ', @values) : $values[0];
+ my $value = $vars{$_};
+ $value =~ s/\0/ /g; # deal with multivalued options
$value =~ s/\r\n/\n/g; #browsers? (textarea)
$_ => $value;
} split(',', $cgi->param('options'));
+# deal with multiline options
+# %vars should never contain incomplete rows, but just in case it does,
+# we make a list of all the row indices that contain values, and
+# then write a line in each option for each row, even if it's empty.
+# This ensures that all values with the same row index line up.
+my %optionrows;
+foreach my $option (split(',', $cgi->param('multi_options'))) {
+ $optionrows{$option} = {};
+ my %values; # bear with me
+ for (keys %vars) {
+ /^$option(\d+)/ or next;
+ $optionrows{$option}{$1} = $vars{$option.$1};
+ $optionrows{_ALL_}{$1} = 1 if length($vars{$option.$1});
+ }
+}
+foreach my $option (split(',', $cgi->param('multi_options'))) {
+ my $value = '';
+ foreach my $row (sort keys %{$optionrows{_ALL_}}) {
+ $value .= ($optionrows{$option}{$row} || '') . "\n";
+ }
+ chomp($value);
+ $options{$option} = $value;
+}
+
my $new = new FS::part_export ( {
map {
$_, scalar($cgi->param($_));
diff --git a/httemplate/edit/rate_time.cgi b/httemplate/edit/rate_time.cgi
index 7ee39efca..9e6b8736c 100644
--- a/httemplate/edit/rate_time.cgi
+++ b/httemplate/edit/rate_time.cgi
@@ -15,12 +15,34 @@
<TD><INPUT TYPE="text" NAME="ratetimename" VALUE="<% $rate_time ? $rate_time->ratetimename : '' %>"></TD>
</TR>
</TABLE>
-<% include('/elements/auto-table.html',
- 'header' => [ '', 'Start','','', '','End','','' ],
- 'fields' => [ qw(sd sh sm sa ed eh em ea) ],
- 'select' => [ ($day, $hour, $min, $ampm) x 2 ],
- 'data' => \@data,
- ) %>
+<TABLE>
+ <TR>
+ <TH COLSPAN=4 ALIGN="center">Start</TH>
+ <TH COLSPAN=4 ALIGN="center">End</TH>
+ </TR>
+ <TR id="mytemplate">
+% for my $pre (qw(s e)) {
+% for my $f (qw(d h m a)) { # day, hour, minute, am/pm
+ <TD>
+ <SELECT NAME="<%$pre.$f%>">
+% my $i = 0;
+% while ($i < @{ $choices{$f} }) {
+ <OPTION VALUE="<%$choices{$f}[$i]%>">
+% $i++;
+ <%$choices{$f}[$i]%></OPTION>
+% $i++;
+% }
+ </SELECT>
+ </TD>
+% } #$f
+% } #$pre
+ </TR>
+<& /elements/auto-table.html,
+ 'template_row' => 'mytemplate',
+ 'data' => \@data,
+ 'fieldorder' => [qw(sd sh sm sa ed eh em ea)],
+&>
+</TABLE>
<INPUT TYPE="submit" VALUE="<% $rate_time ? 'Apply changes' : 'Add period'%>">
</FORM>
<BR>
@@ -42,7 +64,12 @@ my $day = [ 0 => 'Sun',
my $hour = [ map( {$_, sprintf('%02d',$_) } 12, 1..11 )];
my $min = [ map( {$_, sprintf('%02d',$_) } 0,30 )];
my $ampm = [ 0 => 'AM', 1 => 'PM' ];
-
+my %choices = (
+ 'd' => $day,
+ 'h' => $hour,
+ 'm' => $min,
+ 'a' => $ampm,
+);
if($ratetimenum) {
$action = 'Edit';
$rate_time = qsearchs('rate_time', {ratetimenum => $ratetimenum})
diff --git a/httemplate/elements/auto-table.html b/httemplate/elements/auto-table.html
index 49222745a..9aff94e67 100644
--- a/httemplate/elements/auto-table.html
+++ b/httemplate/elements/auto-table.html
@@ -1,166 +1,180 @@
<%doc>
-
-Example:
-<% include('/elements/auto-table.html',
-
- ###
- # required
- ###
-
- 'header' => [ '#', 'Item', 'Amount' ],
- 'fields' => [ 'id', 'name', 'amount' ],
-
- ###
- # highly recommended
- ###
-
- 'size' => [ 4, 12, 8 ],
- 'maxl' => [ 4, 12, 8 ],
- 'align' => [ 'right', 'left', 'right' ],
-
- ###
- # optional
- ###
-
- 'data' => [ [ 1, 'Widget', 25 ],
- [ 12, 'Super Widget, 7 ] ],
- #or
- 'records' => [ qsearch('item', { } ) ],
- # or any other array of FS::Record objects
-
- 'select' => [ '',
- [ 1 => 'option 1',
- 2 => 'option 2', ...
- ], # options for second field
- '' ],
-
- 'prefix' => 'mytable_',
-) %>
-
-Values will be passed through as "mytable_id1", etc.
+(within a form)
+<table>
+<tr>
+ <th>Field 1</th>
+ <th>Field 2</th>
+</tr>
+<tr id="mytemplate">
+ <td><input type="text" name="field1"></td>
+ <td><select name="field2">...</td>
+ ...
+</tr>
+</table>
+<& /elements/auto-table.html,
+ table => 'mytable',
+ template_row = 'mytemplate',
+ rows => [
+ { field1 => 'foo', field2 => 'CA', ... },
+ { field1 => 'bar', field2 => 'TX', ... }, ...
+ ],
+&>
+
+ or if you prefer:
+...
+ fieldorder => [ 'field1', 'field2', ... ],
+ rows => [
+ [ 'foo', 'CA' ],
+ [ 'bar', 'TX' ],
+ ],
+
+In the process/ handler, something like:
+my @rows;
+my %vars = $cgi->Vars;
+for my $k ( keys %vars ) {
+ $k =~ /^${pre}magic(\d+)$/ or next;
+ my $rownum = $1;
+ # find all submitted names ending in this rownum
+ my %thisrow =
+ map { $_ => $vars{$_} }
+ grep /^(.*[\d])$rownum$/, keys %vars;
+ $thisrow->{num} = delete $thisrow{"${pre}magic$rownum"};
+ push @rows, $thisrow;
+}
</%doc>
-
-<TABLE ID="<% $prefix %>AutoTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
- <TR>
-% foreach (@header) {
- <TH><% $_ %></TH>
-% }
- </TR>
-% my $row = 0;
-% for ( $row = 0; $row < scalar @data; $row++ ) {
- <TR>
-% my $col = 0;
-% for ( $col = 0; $col < scalar @fields; $col++ ) {
-% my $id = $prefix . $fields[$col];
-% # don't suffix rownum in the final, blank row
-% $id .= $row if $row < (scalar @data) - 1;
- <TD>
-% my @o = @{ $select[$col] };
-% if( @o ) {
- <SELECT NAME="<% $id %>" ID="<% $id %>">
-% while(@o) {
-% my $val = shift @o;
- <OPTION VALUE=<% $val %><%
-$val eq $data[$row][$col] ? ' SELECTED' : ''%>><% shift @o %></OPTION>
-% }
- </SELECT>
-% }
-% else {
- <INPUT TYPE = "text"
- NAME = "<% $id %>"
- ID = "<% $id %>"
- SIZE = <% $size[$col] %>
- MAXLENGTH = <% $maxl[$col] %>
- STYLE = "text-align:<% $align[$col] %>"
- VALUE = "<% $data[$row][$col] %>"
-% if( $opt{'autoadd'} ) {
- onchange = "possiblyAddRow(this);"
-% }
- >
- </TD>
-% }
-% }
- <TD>
- <IMG SRC = "<% "${p}images/cross.png" %>"
- ALT = "X"
- onclick = "deleteRow(this);"
- >
- </TD>
- </TR>
-% }
-</TABLE>
-% if( !$opt{'autoadd'} ) {
-<INPUT TYPE="button" VALUE="Add" onclick="<% $prefix %>addRow();"><BR>
-% }
-
-<SCRIPT TYPE="text/javascript">
- var <% $prefix %>rownum = <% $row %>;
- var <% $prefix %>table = document.getElementById('<% $prefix %>AutoTable');
- // last row is initially blank, clone it and remove it
- var <% $prefix %>_blank =
- <% $prefix %>table.rows[<% $prefix %>table.rows.length-1].cloneNode(true);
-% if( !$opt{'autoadd'} ) {
- <% $prefix %>table.deleteRow(<% $prefix %>table.rows.length-1);
-% }
-
-
-
- function rownum_of(obj) {
- return (obj.parentNode.parentNode.sectionRowIndex);
+<tbody id="<%$pre%>autotable"></tbody>
+<script type="text/javascript">
+var <%$pre%>template;
+var <%$pre%>tbody;
+var <%$pre%>next_rownum;
+var <%$pre%>set_rownum;
+var <%$pre%>addRow;
+var <%$pre%>deleteRow;
+var <%$pre%>fieldorder = <% to_json($fieldorder) %>;
+
+function <%$pre%>possiblyAddRow_factory(obj) {
+ var callback = obj.onchange;
+ return function() {
+ if ( obj.rownum == <%$pre%>tbody.lastChild.rownum ) {
+ // then this is the last row, and it's being changed, so spawn a new row
+ <%$pre%>addRow();
+ }
+ if ( callback ) {
+ callback.apply(obj);
+ }
}
+}
- function <% $prefix %>possiblyAddRow(obj) {
- if ( <% $prefix %>rownum == rownum_of(obj) ) {
- <% $prefix %>addRow();
+function <%$pre%>set_rownum(obj, rownum) {
+ obj.rownum = rownum;
+ if ( obj.id ) {
+ obj.id = obj.id + rownum;
+ }
+ if ( obj.name ) {
+ obj.name = obj.name + rownum;
+ // also, in this case it's a form field that will be part of the record
+ // so set up an onchange handler
+ obj.onchange = <%$pre%>possiblyAddRow_factory(obj);
+ }
+ for (var i = 0; i < obj.children.length; i++) {
+ if ( obj.children[i] instanceof Node ) {
+ <%$pre%>set_rownum(obj.children[i], rownum);
}
}
+}
- function <% $prefix %>addRow() {
- var row = <% $prefix %>table.insertRow(-1);
- var cells = <% $prefix %>_blank.cells;
- for (i=0; i<cells.length; i++) {
- var node = row.appendChild(cells[i].cloneNode(true));
- var input = node.children[0];
- input.id = input.id + row.sectionRowIndex;
- input.name = input.name + row.sectionRowIndex;
+function <%$pre%>addRow(data) {
+ // duplicate the node
+ // warning: cloneNode doesn't clone event handlers that were set through
+ // the DOM
+ // if 'data' is an object, prepopulate the row's fields with the object's
+ // elements
+ // returns the rownum of the new row
+ var row = <%$pre%>template.cloneNode(true);
+ <%$pre%>tbody.appendChild(row);
+ var this_rownum = <%$pre%>next_rownum;
+ <%$pre%>set_rownum(row, this_rownum);
+ if(data instanceof Array) {
+ for (i = 0; i < data.length && i < <%$pre%>fieldorder.length; i++) {
+ var el = document.getElementsByName(<%$pre%>fieldorder[i] + this_rownum)[0];
+ if (el) {
+ el.value = data[i];
+ }
+ }
+ } else if (data instanceof Object) {
+ for (var field in data) {
+ var el = document.getElementsByName(field + this_rownum)[0];
+ if (el) {
+ el.value = data[field];
+% # doesn't work for checkbox
+ }
}
- <% $prefix %>rownum++;
+ } // else nothing
+ <%$pre%>next_rownum++;
+ return this_rownum;
+}
+
+function <%$pre%>deleteRow(rownum) {
+ if ( rownum == <%$pre%>tbody.lastChild.rownum ) {
+ // if this is the last row, spawn another one after it
+ <%$pre%>addRow();
}
+ var r = document.getElementById('<%$pre%>row' + rownum);
+ <%$pre%>tbody.removeChild(r);
+}
- function deleteRow(obj) {
- if(<% $prefix %>rownum == rownum_of(obj)) {
- <% $prefix %>addRow();
- }
- <% $prefix %>table.deleteRow(rownum_of(obj));
- <% $prefix %>rownum--;
- return(false);
+function <%$pre%>init() {
+ <%$pre%>template = document.getElementById(<% $template_row |js_string%>);
+ <%$pre%>tbody = document.getElementById('<%$pre%>autotable');
+ <%$pre%>next_rownum = <%$pre%>template.sectionRowIndex;
+ // detach the template row
+ var table = <%$pre%>template.parentNode;
+ table.removeChild(<%$pre%>template);
+ // give it an id
+ <%$pre%>template.id = <%$pre |js_string%> + 'row';
+ // and a magic identifier so we know it's been submitted
+ var magic = document.createElement('INPUT');
+ magic.setAttribute('type', 'hidden');
+ magic.setAttribute('name', '<%$pre%>magic');
+ magic.value = '1';
+ // and a delete button
+%# should this be enclosed in an actual <button> for aesthetics?
+ var delete_button = document.createElement('IMG');
+ delete_button.id = 'delete_button';
+ delete_button.src = '<%$fsurl%>images/cross.png';
+ delete_button.alt = 'X';
+ // use an inline string for this so that it will be cloned properly
+ delete_button.setAttribute('onclick', "<%$pre%>deleteRow(this.rownum);");
+ var delete_cell = document.createElement('TD');
+ delete_cell.appendChild(delete_button);
+ delete_cell.appendChild(magic); // it has to go somewhere
+ <%$pre%>template.appendChild(delete_cell);
+
+ // preload rows
+ var rows = <% to_json(\@rows) %>;
+ for (var i = 0; i < rows.length; i++) {
+ <%$pre%>addRow(rows[i]);
}
-</SCRIPT>
+ <%$pre%>addRow();
+}
+<%$pre%>init();
+</script>
<%init>
my %opt = @_;
-
-my @header = @{ $opt{'header'} };
-my @fields = @{ $opt{'fields'} };
-my @data = ();
-if($opt{'data'}) {
- @data = @{ $opt{'data'} };
-}
-elsif($opt{'records'}) {
- foreach my $rec (@{ $opt{'records'} }) {
- push @data, [ map { $rec->getfield($_) } @fields ];
+my $pre = '';
+$pre = $opt{'table'} . '_' if $opt{'table'};
+my $template_row = $opt{'template_row'}
+ or die "auto-table requires template_row\n"; # a DOM id
+
+# rows that we will preload, as hashrefs of name => value
+my @rows = @{ $opt{'data'} || [] };
+foreach (@rows) {
+ # allow an array of FS::Record objects to be passed
+ if ( blessed($_) and $_->isa('FS::Record') ) {
+ $_ = $_->hashref;
}
}
-# else @data = ();
-push @data, [ map {''} @fields ]; # make a blank row
-
-my $prefix = $opt{'prefix'};
-my @size = $opt{'size'} ? @{ $opt{'size'} } : (map {16} @fields);
-my @maxl = $opt{'maxl'} ? @{ $opt{'maxl'} } : @size;
-my @align = $opt{'align'} ? @{ $opt{'align'} } : (map {'right'} @fields);
-my @select = @{ $opt{'select'} || [] };
-foreach (0..scalar(@fields)-1) {
- $select[$_] ||= [];
-}
+my $fieldorder = $opt{'fieldorder'} || [];
</%init>
diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html
index b3e535344..490ba2303 100644
--- a/httemplate/elements/contact.html
+++ b/httemplate/elements/contact.html
@@ -15,7 +15,7 @@
<% ($contact_class->classnum == $classnum) ? 'SELECTED' : '' %>
><% $contact_class->classname |h %>
% }
- <SELECT><BR>
+ </SELECT><BR>
<FONT SIZE="-1">Type</FONT>
</TD>
% } else {
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index bfbc179b9..4e6109687 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -130,6 +130,8 @@ tie my %report_invoices, 'Tie::IxHash',
'Open invoices' => [ \%report_invoices_open, 'Open invoices' ],
'All invoices' => [ $fsurl. 'search/cust_bill.html?date', 'List all invoices' ],
'Advanced invoice reports' => [ $fsurl.'search/report_cust_bill.html', 'by agent, date range, etc.' ],
+ 'separator' => '',
+ 'Line items' => [ $fsurl. 'search/report_cust_bill_pkg.html', 'Individual line item detail' ],
;
tie my %report_discounts, 'Tie::IxHash',
@@ -231,13 +233,13 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) {
}
tie my %report_packages, 'Tie::IxHash';
-if ( $curuser->access_right('Edit package definitions')
- || $curuser->access_right('Edit global package definitions')
- )
-{
- $report_packages{'Package definitions (by # active)'} = [ $fsurl.'browse/part_pkg.cgi?active=1', 'Package definitions by number of active packages' ];
- $report_packages{'separator'} = '';
-}
+$report_packages{'Package definitions (by # active)'} = [ $fsurl.'browse/part_pkg.cgi?active=1', 'Package definitions by number of active packages' ]
+ if $curuser->access_right('Edit package definitions')
+ || $curuser->access_right('Edit global package definitions');
+$report_packages{'Package Costs Report'} = [ $fsurl.'graph/report_cust_pkg_cost.html', 'Package setup and recurring costs graph' ]
+ if $curuser->access_right('Financial reports');
+$report_packages{'separator'} = ''
+ if keys %report_packages;
if ( $curuser->access_right('Financial reports') ) {
$report_packages{'Package churn'} = [ $fsurl.'graph/report_cust_pkg.html', 'Orders, suspensions and cancellations summary graph' ];
$report_packages{'separator2'} = '';
@@ -292,6 +294,11 @@ tie my %report_ticketing, 'Tie::IxHash',
'Advanced ticket reports' => [ $fsurl.'rt/Search/Build.html?NewQuery=1', 'List tickets by any criteria' ],
;
+tie my %report_employees, 'Tie::IxHash',
+ 'Employee Commission Report' => [ $fsurl.'search/report_employee_commission.html', '' ],
+ 'Employee Audit Report' => [ $fsurl.'search/report_employee_audit.html', 'Employee audit report' ],
+;
+
tie my %report_bill_event, 'Tie::IxHash',
'All billing events' => [ $fsurl.'search/report_cust_event.html', 'All billing events for a date range' ],
'Billing event errors' => [ $fsurl.'search/report_cust_event.html?failed=1', 'Failed credit cards, processor or printer problems, etc.' ],
@@ -313,22 +320,32 @@ $report_payments{'Unapplied Payment Aging'} = [ $fsurl.'search/report_unapplied_
$report_payments{'Deleted Payments / Payment history table'} = [ $fsurl.'search/report_h_cust_pay.html', 'Deleted payments / payment history table' ]
if $conf->exists('payment-history-report');
+tie my %report_credits, 'Tie::IxHash',
+ 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ],
+ 'Credit application detail' => [ $fsurl.'search/report_cust_credit_bill_pkg.html', 'Line item application detail' ],
+ 'Unapplied Credits' => [ $fsurl.'search/report_cust_credit.html?unapplied=1', 'Unapplied credit report (by type and/or date range)' ],
+;
+
+tie my %report_refunds, 'Tie::IxHash',
+ 'Refund Report' => [ $fsurl.'search/report_cust_refund.html', 'Refund report (by type and/or date range)' ],
+ 'Unapplied Refunds' => [ $fsurl.'search/report_cust_refund.html?unapplied=1', 'Unapplied refund report (by type and/or date range)' ],
+;
+
+tie my %report_sales, 'Tie::IxHash',
+ 'Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time.html', 'Sales, credits and receipts summary graph' ],
+ 'Daily Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time_daily.html', 'Sales, credits and receipts (broken down by day) summary graph' ],
+ 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ],
+ 'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ],
+ 'Sales With Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ],
+;
+
tie my %report_financial, 'Tie::IxHash';
-if($curuser->access_right('Financial reports')) {
+if( $curuser->access_right('Financial reports') ) {
%report_financial = (
- 'Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time.html', 'Sales, credits and receipts summary graph' ],
- 'Daily Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time_daily.html', 'Sales, credits and receipts (broken down by day) summary graph' ],
- 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ],
- 'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ],
- 'Sales With Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ],
- 'Employee Commission Report' => [ $fsurl.'search/report_employee_commission.html', '' ],
- 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ],
- 'Unapplied Credits' => [ $fsurl.'search/report_cust_credit.html?unapplied=1', 'Unapplied credit report (by type and/or date range)' ],
- 'Refund Report' => [ $fsurl.'search/report_cust_refund.html', 'Refund report (by type and/or date range)' ],
- 'Unapplied Refunds' => [ $fsurl.'search/report_cust_refund.html?unapplied=1', 'Unapplied refund report (by type and/or date range)' ],
- 'Package Costs Report' => [ $fsurl.'graph/report_cust_pkg_cost.html', 'Package setup and recurring costs graph' ],
- 'Employee Audit Report' => [ $fsurl.'search/report_employee_audit.html', 'Employee audit report' ],
+ 'Sales' => [ \%report_sales, 'Sales reports', ],
+ 'Credits' => [ \%report_credits, 'Credit reports', ],
+ 'Refunds' => [ \%report_refunds, 'Refund reports', ],
);
$report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ];
$report_financial{'Prepaid Income'} = [ $fsurl.'search/report_prepaid_income.html', 'Prepaid income (unearned revenue) report' ];
@@ -346,36 +363,48 @@ if($curuser->access_right('Financial reports')) {
} # else $report_financial contains nothing.
+tie my %report_logs, 'Tie::IxHash';
+ $report_logs{'System log'} = [ $fsurl.'search/log.html', 'View system events and debugging information.' ],
+ if $curuser->access_right('View system logs')
+ || $curuser->access_right('Configuration');
+ $report_logs{'Outgoing messages'} = [ $fsurl.'search/cust_msg.html', 'View outgoing message log' ]
+ if $curuser->access_right('View email logs')
+ || $curuser->access_right('Configuration');
+
tie my %report_menu, 'Tie::IxHash';
-$report_menu{'Prospects'} = [ \%report_prospects, 'Prospect reports' ]
+$report_menu{'Prospects'} = [ \%report_prospects, 'Prospect reports' ]
if $curuser->access_right('List prospects');
-$report_menu{'Quotations'} = [ \%report_quotations, 'Quotation reports' ]
+$report_menu{'Quotations'} = [ \%report_quotations, 'Quotation reports' ]
if $curuser->access_right('List quotations');
-$report_menu{'Customers'} = [ \%report_customers, 'Customer reports' ]
+$report_menu{'Customers'} = [ \%report_customers, 'Customer reports' ]
if $curuser->access_right('List customers');
-$report_menu{'Invoices'} = [ \%report_invoices, 'Invoice reports' ]
+$report_menu{'Invoices'} = [ \%report_invoices, 'Invoice reports' ]
if $curuser->access_right('List invoices');
-$report_menu{'Discounts'} = [ \%report_discounts, 'Discount reports' ]
+$report_menu{'Discounts'} = [ \%report_discounts, 'Discount reports' ]
if $curuser->access_right('Financial reports');
-$report_menu{'Payments'} = [ \%report_payments, 'Payment reports' ]
+$report_menu{'Payments'} = [ \%report_payments, 'Payment reports' ]
if $curuser->access_right('Financial reports');
-$report_menu{'Packages'} = [ \%report_packages, 'Package reports' ]
+$report_menu{'Packages'} = [ \%report_packages, 'Package reports' ]
if $curuser->access_right('List packages');
-$report_menu{'Services'} = [ \%report_services, 'Services reports' ]
+$report_menu{'Services'} = [ \%report_services, 'Services reports' ]
if $curuser->access_right('List services');
-$report_menu{'Inventory'} = [ \%report_inventory, 'Inventory reports' ]
+$report_menu{'Inventory'} = [ \%report_inventory, 'Inventory reports' ]
if $curuser->access_right('Configuration'); #XXX List inventory?
-$report_menu{'Usage'} = [ \%report_rating, 'Usage reports' ]
+$report_menu{'Usage'} = [ \%report_rating, 'Usage reports' ]
if $curuser->access_right('List rating data');
-$report_menu{'Tickets'} = [ \%report_ticketing, 'Ticket reports' ]
+$report_menu{'Tickets'} = [ \%report_ticketing, 'Ticket reports' ]
if $conf->config('ticket_system')
;#&& FS::TicketSystem->access_right(\%session, 'Something');
+$report_menu{'Employees'} = [ \%report_employees, 'Employee reports' ]
+ if $curuser->access_right('Financial reports');
$report_menu{'Billing events'} = [ \%report_bill_event, 'Billing events' ]
if $curuser->access_right('Billing event reports');
-$report_menu{'Financial'} = [ \%report_financial, 'Financial reports' ]
+$report_menu{'Financial'} = [ \%report_financial, 'Financial reports' ]
if $curuser->access_right('Financial reports')
or $curuser->access_right('Receivables report');
-$report_menu{'SQL Query'} = [ $fsurl.'search/report_sql.html', 'SQL Query' ]
+$report_menu{'Logs'} = [ \%report_logs, 'System and email logs' ]
+ if (keys %report_logs); # empty if the user has no rights to it
+$report_menu{'SQL Query'} = [ $fsurl.'search/report_sql.html', 'SQL Query']
if $curuser->access_right('Raw SQL');
tie my %tools_importing, 'Tie::IxHash',
@@ -440,8 +469,6 @@ $tools_menu{'Time Queue'} = [ $fsurl.'search/report_timeworked.html', 'View pen
if $curuser->access_right('Time queue');
$tools_menu{'Attachments'} = [ $fsurl.'browse/cust_attachment.html', 'View customer attachments' ]
if !$conf->config('disable_cust_attachment') and $curuser->access_right('View attachments') and $curuser->access_right('Browse attachments');
-$tools_menu{'Outgoing messages'} = [ $fsurl.'search/cust_msg.html', 'View outgoing message log' ] #shouldn't this be in the reports menu?
- if $curuser->access_right('View email logs');
$tools_menu{'Importing'} = [ \%tools_importing, 'Import tools' ]
if $curuser->access_right('Import');
$tools_menu{'Exporting'} = [ \%tools_exporting, 'Export tools' ]
diff --git a/httemplate/elements/searchbar-address2.html b/httemplate/elements/searchbar-address2.html
index d5e2b37d7..5f3b1f233 100644
--- a/httemplate/elements/searchbar-address2.html
+++ b/httemplate/elements/searchbar-address2.html
@@ -6,7 +6,7 @@
<BR>
<INPUT TYPE="submit" VALUE="Search units" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px;padding-left:1px;padding-right:1px;margin-top:3px">
</FORM>
- <% $menu_position eq 'left' ? '<BR>' : '' %>
+ <% $menu_position eq 'left' ? '<BR>' : '' |n %>
% }
diff --git a/httemplate/elements/searchbar-cust_bill.html b/httemplate/elements/searchbar-cust_bill.html
index 7d24fbe9d..169315bf0 100644
--- a/httemplate/elements/searchbar-cust_bill.html
+++ b/httemplate/elements/searchbar-cust_bill.html
@@ -8,7 +8,7 @@
<BR>
<INPUT TYPE="submit" VALUE="<% mt('Search invoices') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px;padding-left:1px;padding-right:1px;margin-top:3px">
</FORM>
- <% $menu_position eq 'left' ? '<BR><BR>' : '' %>
+ <% $menu_position eq 'left' ? '<BR><BR>' : '' |n %>
% }
diff --git a/httemplate/elements/searchbar-cust_main.html b/httemplate/elements/searchbar-cust_main.html
index 5d79aaa5e..9a98417c8 100644
--- a/httemplate/elements/searchbar-cust_main.html
+++ b/httemplate/elements/searchbar-cust_main.html
@@ -5,7 +5,7 @@
<A HREF="<%$fsurl%>search/report_cust_main.html" CLASS="fslink" STYLE="font-size: 11px"><% mt('Advanced') |h %></A>
<INPUT TYPE="submit" VALUE="<% mt('Search customers') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
</FORM>
- <% $menu_position eq 'left' ? '<BR>' : '' %>
+ <% $menu_position eq 'left' ? '<BR>' : '' |n %>
% }
diff --git a/httemplate/elements/searchbar-cust_svc.html b/httemplate/elements/searchbar-cust_svc.html
index 766209d16..e4c2dc614 100644
--- a/httemplate/elements/searchbar-cust_svc.html
+++ b/httemplate/elements/searchbar-cust_svc.html
@@ -5,7 +5,7 @@
<A NOTYET="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #cccccc; font-size:11px"><% mt('Advanced') |h %></A>
<INPUT TYPE="submit" VALUE="<% mt('Search services') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
</FORM>
- <% $menu_position eq 'left' ? '<BR>' : '' %>
+ <% $menu_position eq 'left' ? '<BR>' : '' |n %>
% }
diff --git a/httemplate/elements/searchbar-prospect.html b/httemplate/elements/searchbar-prospect.html
index 68b90d4e3..ac363796e 100644
--- a/httemplate/elements/searchbar-prospect.html
+++ b/httemplate/elements/searchbar-prospect.html
@@ -5,7 +5,7 @@
<A HREF="<%$fsurl%>search/report_prospect_main.html" CLASS="fslink" STYLE="font-size: 11px">Adv</A>
<INPUT TYPE="submit" VALUE="Search prospects" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px;padding-left:1px;padding-right:1px">
</FORM>
- <% $menu_position eq 'left' ? '<BR>' : '' %>
+ <% $menu_position eq 'left' ? '<BR>' : '' |n %>
% }
diff --git a/httemplate/elements/searchbar-ticket.html b/httemplate/elements/searchbar-ticket.html
index 30624f7d3..ae86dbcec 100644
--- a/httemplate/elements/searchbar-ticket.html
+++ b/httemplate/elements/searchbar-ticket.html
@@ -5,7 +5,7 @@
<A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html?NewQuery=1" CLASS="fslink" STYLE="font-size:11px"><% mt('Advanced') |h %></A>
<INPUT TYPE="submit" VALUE="<% mt('Search tickets') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
</FORM>
- <% $menu_position eq 'left' ? '<BR>' : '' %>
+ <% $menu_position eq 'left' ? '<BR>' : '' |n %>
% }
diff --git a/httemplate/elements/select-did.html b/httemplate/elements/select-did.html
index a69450c2a..6e205d8ff 100644
--- a/httemplate/elements/select-did.html
+++ b/httemplate/elements/select-did.html
@@ -16,8 +16,10 @@ Example:
% if ( $export->option('restrict_selection') eq 'non-tollfree'
% || !$export->option('restrict_selection') ) {
<TABLE>
-
<TR>
+
+% if ( $export->get_dids_npa_select ) {
+
<TD VALIGN="top">
<% include('/elements/select-state.html',
'prefix' => 'phonenum_', #$field.'_',
@@ -29,40 +31,73 @@ Example:
%>
<BR><FONT SIZE="-1">State</FONT>
</TD>
+
+ <TD VALIGN="top">
+ <% include('/elements/select-areacode.html',
+ 'state_prefix' => 'phonenum_', #$field.'_',
+ 'svcpart' => $svcpart,
+ 'empty' => 'Select area code',
+ )
+ %>
+ <BR><FONT SIZE="-1">Area code</FONT>
+ </TD>
+
+ <TD VALIGN="top">
+ <% include('/elements/select-exchange.html',
+ 'svcpart' => $svcpart,
+ 'empty' => 'Select exchange',
+ )
+ %>
+ <BR><FONT SIZE="-1">City / Exchange</FONT>
+ </TD>
+
+% } else {
+
<TD VALIGN="top">
- <% include('/elements/select-areacode.html',
- 'state_prefix' => 'phonenum_', #$field.'_',
- 'svcpart' => $svcpart,
- 'empty' => 'Select area code',
- )
- %>
- <BR><FONT SIZE="-1">Area code</FONT>
- </TD>
- <TD VALIGN="top">
- <% include('/elements/select-exchange.html',
- 'svcpart' => $svcpart,
- 'empty' => 'Select exchange',
+ <% include('/elements/select.html',
+ 'field' => 'phonenum_state',
+ 'id' => 'phonenum_state',
+ 'options' => [ '', @{ $export->get_dids } ],
+ 'labels' => { '' => 'Select province' },
+ 'onchange' => 'phonenum_state_changed(this);',
)
%>
- <BR><FONT SIZE="-1">City / Exchange</FONT>
+ <BR><FONT SIZE="-1">Province</FONT>
</TD>
+
+ <TD VALIGN="top">
+ <% include('/elements/select-region.html',
+ 'state_prefix' => 'phonenum_', #$field.'_',
+ 'svcpart' => $svcpart,
+ 'empty' => 'Select region',
+ )
+ %>
+ <BR><FONT SIZE="-1">Region</FONT>
+ </TD>
+
+% }
+
<TD VALIGN="top">
<% include('/elements/select-phonenum.html',
'svcpart' => $svcpart,
'empty' => 'Select phone number',
'bulknum' => $bulknum,
'multiple' => $multiple,
+ 'region' => ! $export->get_dids_npa_select,
)
%>
<BR><FONT SIZE="-1">Phone number</FONT>
</TD>
- </TR>
+ </TR>
</TABLE>
% }
-% if ( $export->option('restrict_selection') eq 'tollfree'
-% || !$export->option('restrict_selection') ) {
+% if ( ( $export->option('restrict_selection') eq 'tollfree'
+% || !$export->option('restrict_selection')
+% )
+% and $export->get_dids_can_tollfree
+% ) {
<font size="-1">Toll-free</font>
<% include('/elements/select-phonenum.html',
'svcpart' => $svcpart,
diff --git a/httemplate/elements/select-mib-popup.html b/httemplate/elements/select-mib-popup.html
new file mode 100644
index 000000000..bd485ef65
--- /dev/null
+++ b/httemplate/elements/select-mib-popup.html
@@ -0,0 +1,186 @@
+<& /elements/header-popup.html &>
+<DIV STYLE="visibility: hidden; position: absolute" ID="measurebox"></DIV>
+<TABLE WIDTH="100%">
+<TR>
+ <TD WIDTH="30%" ALIGN="right">Module:</TD>
+ <TD><SELECT ID="select_module"></SELECT></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Object:</TD>
+ <TD><INPUT TYPE="text" NAME="path" ID="input_path" WIDTH="100%"></TD>
+</TR>
+<TR>
+ <TD COLSPAN=2>
+ <SELECT STYLE="width:100%" SIZE=12 ID="select_path"></SELECT>
+ </TD>
+</TR>
+<TR>
+ <TH ALIGN="center" COLSPAN=2 ID="mib_objectID"></TH>
+</TR>
+<TR>
+ <TD ALIGN="right">Module: </TD><TD ID="mib_moduleID"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Data type: </TD><TD ID="mib_type"></TD>
+</TR>
+<TR>
+ <TH COLSPAN=2>
+ <BUTTON ID="submit_button" onclick="submit()" DISABLED=1>Continue</BUTTON>
+ </TH>
+</TR>
+</TABLE>
+<& /elements/xmlhttp.html,
+ url => $p.'misc/xmlhttp-mib-browse.html',
+ subs => [qw( search get_module_list )],
+&>
+<SCRIPT TYPE="text/javascript">
+
+var selected_mib;
+
+function show_info(state) {
+ document.getElementById('mib_objectID').style.display =
+ document.getElementById('mib_moduleID').style.display =
+ document.getElementById('mib_type').style.display =
+ state ? '' : 'none';
+}
+
+function clear_list() {
+ var select_path = document.getElementById('select_path');
+ select_path.options.length = 0;
+}
+
+var measurebox = document.getElementById('measurebox');
+function add_item(value) {
+ var select_path = document.getElementById('select_path');
+ var input_path = document.getElementById('input_path');
+ var opt = document.createElement('option');
+ var v = value;
+ if ( v.match(/-$/) ) {
+ opt.className = 'leaf';
+ v = v.substring(0, v.length - 1);
+ }
+ var optvalue = v; // may not be the name we display
+ // shorten these if they don't fit in the box
+ if ( v.length > 30 ) { // unless they're already really short
+ measurebox.innerHTML = v;
+ while ( measurebox.clientWidth > select_path.clientWidth - 10
+ && v.match(/^\..*\./) ) {
+ v = v.replace(/^\.[^\.]+/, '');
+ measurebox.innerHTML = v;
+ }
+ if ( optvalue != v ) {
+ v = '...' + v;
+ }
+ }
+ opt.value = optvalue;
+ opt.text = v;
+ opt.selected = (input_path.value == v);
+ select_path.add(opt, null);
+}
+
+var timerID = 0;
+
+function populate(json_result) {
+ var result = JSON.parse(json_result);
+ clear_list();
+ for (var x in result['choices']) {
+ opt = document.createElement('option');
+ add_item(result['choices'][x]);
+ }
+ if ( result['objectID'] ) {
+ selected_mib = result;
+ show_info(true);
+ // show details on the selected node
+ document.getElementById('mib_objectID').innerHTML = result.objectID;
+ document.getElementById('mib_moduleID').innerHTML = result.moduleID;
+ document.getElementById('mib_type').innerHTML = result.type;
+ document.getElementById('submit_button').disabled = !result.type;
+ } else {
+ selected_mib = undefined;
+ show_info(false);
+ }
+}
+
+function populate_modules(json_result) {
+ var result = JSON.parse(json_result);
+ var select_module = document.getElementById('select_module');
+ var opt = document.createElement('option');
+ opt.value = 'ANY';
+ opt.text = '(any)';
+ select_module.add(opt, null);
+ for (var x in result['modules']) {
+ opt = document.createElement('option');
+ opt.value = opt.text = result['modules'][x];
+ select_module.add(opt, null);
+ }
+}
+
+function dispatch_search() {
+ // called from the interval timer
+ var search_string = document.getElementById('select_module').value + ':' +
+ document.getElementById('input_path').value;
+
+ search(search_string, populate);
+}
+
+function delayed_search() {
+ // onkeyup handler for the text input
+ // 500ms after the user stops typing, send the search request
+ if (timerID != 0) {
+ clearTimeout(timerID);
+ }
+ timerID = setTimeout(dispatch_search, 500);
+}
+
+function handle_choose_object() {
+ // onchange handler for the selector
+ // when the user picks an option, set the text input to that, and then
+ // search for it as though it was entered
+ var input_path = document.getElementById('input_path');
+ input_path.value = this.value;
+ dispatch_search();
+}
+
+function handle_choose_module() {
+ input_path.value = ''; // just to avoid confusion
+ delayed_search();
+}
+
+function submit() {
+% if ( $callback ) {
+ <% $callback %>;
+ parent.nd(1); // close popup
+% } else {
+ alert(document.getElementById('input_path').value);
+% }
+}
+
+var input_path = document.getElementById('input_path');
+input_path.onkeyup = delayed_search;
+var select_path = document.getElementById('select_path');
+select_path.onchange = handle_choose_object;
+var select_module = document.getElementById('select_module');
+select_module.onchange = handle_choose_module;
+% if ( $cgi->param('curr_value') ) {
+input_path.value = <% $cgi->param('curr_value') |js_string %>;
+% }
+dispatch_search();
+get_module_list('', populate_modules);
+
+</SCRIPT>
+<& /elements/footer.html &>
+<%init>
+my $callback = 'alert("(no callback defined)" + selected_mib.stringify)';
+$cgi->param('callback') =~ /^(\w+)$/;
+if ( $1 ) {
+ # construct the JS function call expresssion
+ $callback = 'window.parent.' . $1 . '(selected_mib';
+ foreach ($cgi->param('arg')) {
+ # pass-through arguments
+ /^(\w+)$/ or next;
+ $callback .= ",'$1'";
+ }
+ $callback .= ')';
+}
+
+</%init>
diff --git a/httemplate/elements/select-phonenum.html b/httemplate/elements/select-phonenum.html
index d555bf4b6..18abe3dea 100644
--- a/httemplate/elements/select-phonenum.html
+++ b/httemplate/elements/select-phonenum.html
@@ -12,7 +12,7 @@
what.options[length] = optionName;
}
- function <% $opt{'prefix'} %>exchange_changed(what, callback) {
+ function <% $opt{'prefix'} %><% $previous %>_changed(what, callback) {
what.form.<% $opt{'prefix'} %>phonenum.disabled = 'disabled';
what.form.<% $opt{'prefix'} %>phonenum.style.display = 'none';
@@ -21,7 +21,7 @@
var phonenumerror = document.getElementById('<% $opt{'prefix'} %>phonenumerror');
phonenumerror.style.display = 'none';
- exchange = what.options[what.selectedIndex].value;
+ var thing = "<% $previous eq 'region' ? '_REGION ' : '' %>" + what.options[what.selectedIndex].value;
function <% $opt{'prefix'} %>update_phonenums(phonenums) {
@@ -84,7 +84,7 @@
}
// go get the new phonenums
- <% $opt{'prefix'} %>get_phonenums( exchange, <% $opt{'svcpart'} %>, <% $opt{'prefix'} %>update_phonenums );
+ <% $opt{'prefix'} %>get_phonenums( thing, <% $opt{'svcpart'} %>, <% $opt{'prefix'} %>update_phonenums );
}
@@ -126,7 +126,7 @@
% unless ( $opt{'tollfree'} ) {
<DIV ID="phonenumwait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding phone numbers</B></DIV>
-<DIV ID="phonenumerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different city/exchange</B></DIV>
+<DIV ID="phonenumerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different <% $opt{'region'} ? 'region' : 'city/exchange' %></B></DIV>
% }
<SELECT <% $opt{multiple} ? 'MULTIPLE SIZE=25' : '' %>
@@ -146,4 +146,6 @@ my %opt = @_;
$opt{disabled} = 'disabled' unless exists $opt{disabled};
+my $previous = $opt{'region'} ? 'region' : 'exchange';
+
</%init>
diff --git a/httemplate/elements/select-region.html b/httemplate/elements/select-region.html
new file mode 100644
index 000000000..9823290db
--- /dev/null
+++ b/httemplate/elements/select-region.html
@@ -0,0 +1,88 @@
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/regions.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_regions' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $opt{'state_prefix'} %>state_changed(what, callback) {
+
+ what.form.<% $opt{'prefix'} %>region.disabled = 'disabled';
+ what.form.<% $opt{'prefix'} %>region.style.display = 'none';
+ var regionwait = document.getElementById('<% $opt{'prefix'} %>regionwait');
+ regionwait.style.display = '';
+ var regionerror = document.getElementById('<% $opt{'prefix'} %>regionerror');
+ regionerror.style.display = 'none';
+
+ what.form.<% $opt{'prefix'} %>phonenum.disabled = 'disabled';
+
+ state = what.options[what.selectedIndex].value;
+
+ function <% $opt{'prefix'} %>update_regions(regions) {
+
+ // blank the current region
+ for ( var i = what.form.<% $opt{'prefix'} %>region.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>region.options[i] = null;
+ // blank the current phonenum too
+ for ( var i = what.form.<% $opt{'prefix'} %>phonenum.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>phonenum.options[i] = null;
+ if ( what.form.<% $opt{'prefix'} %>phonenum.type != 'select-multiple' ) {
+ opt(what.form.<% $opt{'prefix'} %>phonenum, '', 'Select phone number');
+ }
+
+% if ($opt{empty}) {
+ opt(what.form.<% $opt{'prefix'} %>region, '', '<% $opt{empty} %>');
+% }
+
+ // add the new regions
+ var regionArray = eval('(' + regions + ')' );
+ for ( var s = 0; s < regionArray.length; s++ ) {
+ var regionLabel = regionArray[s];
+ if ( regionLabel == "" )
+ regionLabel = '(n/a)';
+ opt(what.form.<% $opt{'prefix'} %>region, regionArray[s], regionLabel);
+ }
+
+ regionwait.style.display = 'none';
+ if ( regionArray.length >= 1 ) {
+ what.form.<% $opt{'prefix'} %>region.disabled = '';
+ what.form.<% $opt{'prefix'} %>region.style.display = '';
+ } else {
+ var regionerror = document.getElementById('<% $opt{'prefix'} %>regionerror');
+ regionerror.style.display = '';
+ }
+
+ //run the callback
+ if ( callback != null )
+ callback();
+ }
+
+ // go get the new regions
+ <% $opt{'prefix'} %>get_regions( state, <% $opt{'svcpart'} %>, <% $opt{'prefix'} %>update_regions );
+
+ }
+
+</SCRIPT>
+
+<DIV ID="<% $opt{'prefix'} %>regionwait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding regions</B></DIV>
+
+<DIV ID="<% $opt{'prefix'} %>regionerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different state</B></DIV>
+
+<SELECT NAME="<% $opt{'prefix'} %>region" onChange="<% $opt{'prefix'} %>region_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+ <OPTION VALUE="">Select region</OPTION>
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+$opt{disabled} = 'disabled' unless exists $opt{disabled};
+
+</%init>
diff --git a/httemplate/elements/select-terms.html b/httemplate/elements/select-terms.html
index d63c49219..a66aa29ae 100644
--- a/httemplate/elements/select-terms.html
+++ b/httemplate/elements/select-terms.html
@@ -33,7 +33,7 @@ my $empty_label =
my $empty_value = $opt{'empty_value'} || '';
my @terms = ( emt('Payable upon receipt'),
- ( map "Net $_", 0, 3, 9, 10, 15, 20, 30, 45, 60, 90 ),
+ ( map "Net $_", 0, 3, 9, 10, 15, 18, 20, 30, 45, 60, 90 ),
);
my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : ();
diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js
index d9c1df7e6..15c5761a0 100644
--- a/httemplate/elements/standardize_locations.js
+++ b/httemplate/elements/standardize_locations.js
@@ -1,3 +1,9 @@
+function status_message(text, caption) {
+ text = '<P STYLE="position:absolute; top:50%; margin-top:-1em; width:100%; text-align:center"><B><FONT SIZE="+1">' + text + '</FONT></B></P>';
+ caption = caption || 'Please wait...';
+ overlib(text, WIDTH, 444, HEIGHT, 168, CAPTION, caption, STICKY, AUTOSTATUSCAP, CLOSECLICK, MIDX, 0, MIDY, 0);
+}
+
function form_address_info() {
var cf = document.<% $formname %>;
@@ -87,8 +93,7 @@ function standardize_locations() {
% if ( $conf->config('address_standardize_method') ) {
if ( changed ) {
- var startup_msg = '<P STYLE="position:absolute; top:50%; margin-top:-1em; width:100%; text-align:center"><B><FONT SIZE="+1">Verifying address...</FONT></B></P>';
- overlib(startup_msg, WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSECLICK, MIDX, 0, MIDY, 0);
+ status_message('Verifying address...');
address_standardize(JSON.stringify(address_info), confirm_standardize);
}
else {
@@ -116,8 +121,14 @@ function confirm_standardize(arg) {
replace_address(); // with the contents of returned['new']
- }
- else {
+ } else if ( returned['all_same'] ) {
+
+ // then all entered address fields are correct
+ // but we still need to set the lat/long fields and addr_clean
+ status_message('Verified');
+ replace_address();
+
+ } else {
var querystring = encodeURIComponent( JSON.stringify(returned) );
// confirmation popup: knows to call replace_address(),
diff --git a/httemplate/elements/xmlhttp.html b/httemplate/elements/xmlhttp.html
index ac6f9916e..a9e65c790 100644
--- a/httemplate/elements/xmlhttp.html
+++ b/httemplate/elements/xmlhttp.html
@@ -14,14 +14,15 @@ Example:
);
</%doc>
-<% include( '/elements/rs_init_object.html' ) %>
+<& /elements/rs_init_object.html &>
+<& /elements/init_overlib.html &>
<SCRIPT TYPE="text/javascript">
% foreach my $func ( @{$opt{'subs'}} ) {
%
% my $furl = $url;
% $furl =~ s/\"/\\\\\"/; #javascript escape
-%
+%#"
%
@@ -66,15 +67,26 @@ Example:
} else {
var data = xmlhttp.responseText;
//alert('received response: ' + data);
- a[a.length-1](data);
if ( data.indexOf("<b>System error</b>") > -1 ) {
- var w;
- if ( w = window.open("about:blank") ) {
- w.document.write(data);
- } else {
- // popup blocking? should use an overlib popup instead
- alert("Error popup disabled; try disabling popup blocking to see");
- }
+ // trim this a little
+ var end = data.indexOf('<a href="#raw">') - 1;
+ data = data.substring(0, end);
+
+ overlib(data,
+ WIDTH, 480, MIDX, 0, MIDY, 0,
+ CAPTION, 'Error', STICKY, AUTOSTATUSCAP, DRAGGABLE,
+ CLOSECLICK, BGCOLOR, '#f00', CGCOLOR, '#f00'
+ );
+ //var w;
+ //if ( w = window.open("about:blank") ) {
+ // w.document.write(data);
+ //} else {
+ // // popup blocking? should use an overlib popup instead
+ // alert("Error popup disabled; try disabling popup blocking to see");
+ //}
+ } else {
+ // invoke the callback
+ a[a.length-1](data);
}
}
}
diff --git a/httemplate/graph/elements/report.html b/httemplate/graph/elements/report.html
index 98b477826..b7073db31 100644
--- a/httemplate/graph/elements/report.html
+++ b/httemplate/graph/elements/report.html
@@ -126,8 +126,8 @@ any delimiter and linked from the elements in @data.
% $workbook->close();# or die "Error creating .xls file: $!";
%
% http_header('Content-Length' => length($output) );
-%
-<% $output %>
+% $m->print($output);
+%
% } elsif ( $cgi->param('_type') eq 'png' ) {
% # delete any items that shouldn't be on the graph
% if ( my $no_graph = $opt{'no_graph'} ) {
diff --git a/httemplate/misc/phonenums.cgi b/httemplate/misc/phonenums.cgi
index fd5de2ae6..5084628eb 100644
--- a/httemplate/misc/phonenums.cgi
+++ b/httemplate/misc/phonenums.cgi
@@ -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/regions.cgi b/httemplate/misc/regions.cgi
new file mode 100644
index 000000000..2450ea31a
--- /dev/null
+++ b/httemplate/misc/regions.cgi
@@ -0,0 +1,26 @@
+<% objToJson(\@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
index 1620642cb..988057163 100644
--- a/httemplate/misc/xmlhttp-address_standardize.html
+++ b/httemplate/misc/xmlhttp-address_standardize.html
@@ -24,6 +24,7 @@ if ($old{onlyship}) {
} else {
@prefixes = ('bill_', 'ship_');
}
+my $all_same = 1;
foreach my $pre ( @prefixes ) {
my $location = {
@@ -38,8 +39,13 @@ foreach my $pre ( @prefixes ) {
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;
+ }
}
-my $return = { old => \%old, new => \%new };
+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-cust_bill_pkg-calculate_taxes.html b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
new file mode 100644
index 000000000..993504619
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
@@ -0,0 +1,123 @@
+<% to_json($return) %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" unless $curuser->access_right('Post credit');
+
+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-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/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
index 4c0fa4a56..34c89e532 100644
--- a/httemplate/search/cust_bill_pkg.cgi
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -6,9 +6,12 @@
'count_addl' => \@total_desc,
'header' => [
emt('Description'),
+ @post_desc_header,
@peritem_desc,
emt('Invoice'),
emt('Date'),
+ emt('Paid'),
+ emt('Credited'),
FS::UI::Web::cust_header(),
],
'fields' => [
@@ -16,60 +19,76 @@
? $_[0]->get('pkg') # possibly use override.pkg
: $_[0]->get('itemdesc') # but i think this correct
},
+ @post_desc,
#strikethrough or "N/A ($amount)" or something these when
# they're not applicable to pkg_tax search
@peritem_sub,
'invnum',
sub { time2str('%b %d %Y', shift->_date ) },
+ sub { sprintf($money_char.'%.2f', shift->get('pay_amount')) },
+ sub { sprintf($money_char.'%.2f', shift->get('credit_amount')) },
\&FS::UI::Web::cust_fields,
],
'sort_fields' => [
'',
+ @post_desc_null,
@peritem,
'invnum',
'_date',
+ #'pay_amount',
+ #'credit_amount',
],
'links' => [
#'',
'',
+ @post_desc_null,
@peritem_null,
$ilink,
$ilink,
+ $pay_link,
+ $credit_link,
( map { $_ ne 'Cust. Status' ? $clink : '' }
FS::UI::Web::cust_header()
),
],
#'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(),
'align' => 'l'.
+ $post_desc_align.
$peritem_align.
- 'rc'.
+ 'rcrr'.
FS::UI::Web::cust_aligns(),
'color' => [
#'',
'',
+ @post_desc_null,
@peritem_null,
'',
'',
+ '',
+ '',
FS::UI::Web::cust_colors(),
],
'style' => [
#'',
'',
+ @post_desc_null,
@peritem_null,
'',
'',
+ '',
+ '',
FS::UI::Web::cust_styles(),
],
&>
<%doc>
-Output parameters:
+Output control parameters:
- distribute: Boolean. If true, recurring fees will be "prorated" for the
portion of the package date range (sdate-edate) that falls within the date
range of the report. Line items will be limited to those for which this
portion is > 0. This disables filtering on invoice date.
-- use_usage: Separate usage (cust_bill_pkg_detail records) from
+- usage: Separate usage (cust_bill_pkg_detail records) from
recurring charges. If set to "usage", will show usage instead of
recurring charges. If set to "recurring", will deduct usage and only
show the flat rate charge. If not passed, the "recurring charge" column
@@ -155,13 +174,24 @@ my $money_char = $conf->config('money_char') || '$';
my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
my @total = ( 'COUNT(*)', 'SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)');
my @total_desc = ( '%d line items', $money_char.'%.2f total' ); # sprintf strings
+
my @peritem = ( 'setup', 'recur' );
my @peritem_desc = ( 'Setup charge', 'Recurring charge' );
-my ($join_cust, $join_pkg ) = ('', '');
-my $use_usage;
+
+my @post_desc_header = ();
+my @post_desc = ();
+my @post_desc_null = ();
+my $post_desc_align = '';
+if ( $conf->exists('enable_taxclasses') ) {
+ push @post_desc_header, 'Tax class';
+ push @post_desc, 'taxclass';
+ push @post_desc_null, '';
+ $post_desc_align .= 'l';
+ push @select, 'part_pkg.taxclass'; # or should this use override?
+}
# valid in both the tax and non-tax cases
-$join_cust =
+my $join_cust =
" LEFT JOIN cust_bill USING (invnum)
LEFT JOIN cust_main USING (custnum)
";
@@ -200,26 +230,31 @@ if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
push @where, "cust_main.refnum = $1";
}
-# the non-tax case
-if ( $cgi->param('nottax') ) {
-
- push @where, 'cust_bill_pkg.pkgnum > 0';
+# custnum
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.custnum = $1";
+}
- # then we want the package and its definition
- $join_pkg =
+# we want the package and its definition if available
+my $join_pkg =
' LEFT JOIN cust_pkg USING (pkgnum)
LEFT JOIN part_pkg USING (pkgpart)';
- my $part_pkg = 'part_pkg';
- if ( $cgi->param('use_override') ) {
- # still need the real part_pkg for tax applicability,
- # so alias this one
- $join_pkg .= " LEFT JOIN part_pkg AS override ON (
- COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = part_pkg.pkgpart
- )";
- $part_pkg = 'override';
- }
- push @select, 'part_pkg.pkg'; # or should this use override?
+my $part_pkg = 'part_pkg';
+if ( $cgi->param('use_override') ) {
+ # still need the real part_pkg for tax applicability,
+ # so alias this one
+ $join_pkg .= " LEFT JOIN part_pkg AS override ON (
+ COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = part_pkg.pkgpart
+ )";
+ $part_pkg = 'override';
+}
+push @select, 'part_pkg.pkg'; # or should this use override?
+
+# the non-tax case
+if ( $cgi->param('nottax') ) {
+
+ push @where, 'cust_bill_pkg.pkgnum > 0';
my @tax_where; # will go into a subquery
my @exempt_where; # will also go into a subquery
@@ -374,8 +409,7 @@ if ( $cgi->param('nottax') ) {
}
# recur/usage separation
- $use_usage = $cgi->param('usage');
- if ( $use_usage eq 'recurring' ) {
+ if ( $cgi->param('usage') eq 'recurring' ) {
my $recur_no_usage = FS::cust_bill_pkg->charged_sql('', '', no_usage => 1);
push @select, "($recur_no_usage) AS recur_no_usage";
@@ -383,7 +417,7 @@ if ( $cgi->param('nottax') ) {
$total[1] = "SUM(cust_bill_pkg.setup + $recur_no_usage)";
$total_desc[1] .= ' (excluding usage)';
- } elsif ( $use_usage eq 'usage' ) {
+ } elsif ( $cgi->param('usage') eq 'usage' ) {
my $usage = FS::cust_bill_pkg->usage_sql();
push @select, "($usage) AS _usage";
@@ -486,6 +520,16 @@ if ( $cgi->param('nottax') ) {
} # nottax / istax
+
+#total payments
+my $pay_sub = "SELECT SUM(cust_bill_pay_pkg.amount) AS pay_amount,
+ billpkgnum
+ FROM cust_bill_pay_pkg
+ GROUP BY billpkgnum";
+$join_pkg .= " LEFT JOIN ($pay_sub) AS item_pay USING (billpkgnum)";
+push @select, 'item_pay.pay_amount';
+
+
# credit
if ( $cgi->param('credit') ) {
@@ -544,7 +588,20 @@ if ( $cgi->param('credit') ) {
push @peritem_desc, 'Credited', 'By', 'Reason';
push @total, 'SUM(credit_amount)';
push @total_desc, "$money_char%.2f credited";
-} # if credit
+
+} else {
+
+ #still want a credit total column
+
+ my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount,
+ billpkgnum
+ FROM cust_credit_bill_pkg
+ GROUP BY billpkgnum";
+ $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit USING (billpkgnum)";
+
+ push @select, 'item_credit.credit_amount';
+
+}
push @select, 'cust_main.custnum', FS::UI::Web::cust_sql_fields();
@@ -582,6 +639,9 @@ my $peritem_align = 'r' x scalar(@peritem);
my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+my $pay_link = ''; #[, 'billpkgnum', ];
+my $credit_link = [ "${p}search/cust_credit_bill_pkg.html?billpkgnum=", 'billpkgnum', ];
+
warn "\n\nQUERY:\n".Dumper($query)."\n\nCOUNT_QUERY:\n$count_query\n\n"
if $cgi->param('debug');
</%init>
diff --git a/httemplate/search/cust_credit_bill_pkg.html b/httemplate/search/cust_credit_bill_pkg.html
index 4612118a2..06fd881a8 100644
--- a/httemplate/search/cust_credit_bill_pkg.html
+++ b/httemplate/search/cust_credit_bill_pkg.html
@@ -1,10 +1,10 @@
<% include( 'elements/search.html',
- 'title' => 'Tax credits', #well, actually application of
- 'name' => 'tax credits', # credit to line item
- 'query' => $query,
- 'count_query' => $count_query,
- 'count_addl' => [ $money_char. '%.2f total', ],
- 'header' => [
+ 'title' => 'Credit application detail', #to line item
+ 'name_singular' => 'credit application',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ $money_char. '%.2f total', ],
+ 'header' => [
#'#',
'Amount',
@@ -16,13 +16,14 @@
# line item
'Description',
+ @post_desc_header,
#invoice
'Invoice',
'Date',
FS::UI::Web::cust_header(),
- ],
- 'fields' => [
+ ],
+ 'fields' => [
#'creditbillpkgnum',
sub { sprintf($money_char.'%.2f', shift->amount ) },
@@ -34,58 +35,67 @@
? $_[0]->get('pkg') # possibly use override.pkg
: $_[0]->get('itemdesc') # but i think this correct
},
+ @post_desc,
'invnum',
sub { time2str('%b %d %Y', shift->_date ) },
\&FS::UI::Web::cust_fields,
- ],
- 'sort_fields' => [
+ ],
+ 'sort_fields' => [
'amount',
'cust_credit_date',
'', #'otaker',
'', #reason
'', #line item description
+ @post_desc_null,
'invnum',
'_date',
#cust fields
- ],
- 'links' => [
+ ],
+ 'links' => [
'',
'',
'',
'',
'',
+ @post_desc_null,
$ilink,
$ilink,
( map { $_ ne 'Cust. Status' ? $clink : '' }
FS::UI::Web::cust_header()
),
- ],
- 'align' => 'rrlllrr'.FS::UI::Web::cust_aligns(),
- 'color' => [
+ ],
+ 'align' => 'rrlll'.
+ $post_desc_align.
+ 'rr'.
+ FS::UI::Web::cust_aligns(),
+ 'color' => [
'',
'',
'',
'',
'',
+ @post_desc_null,
'',
'',
FS::UI::Web::cust_colors(),
],
- 'style' => [
+ 'style' => [
'',
'',
'',
'',
'',
+ @post_desc_null,
'',
'',
FS::UI::Web::cust_styles(),
- ],
+ ],
)
%>
<%init>
#LOTS of false laziness below w/cust_bill_pkg.cgi
+# and a little w/cust_credit.html
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
@@ -98,14 +108,31 @@ my $agentnums_sql =
my @where = ( $agentnums_sql );
+if ( $cgi->param('usernum') =~ /^(\d+)$/ ) {
+ push @where, "cust_credit.usernum = $1";
+}
+
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
push @where, "cust_bill._date >= $beginning",
"cust_bill._date <= $ending";
+my($cr_begin, $cr_end) = FS::UI::Web::parse_beginning_ending($cgi, 'credit');
+push @where, "cust_credit._date >= $cr_begin",
+ "cust_credit._date <= $cr_end";
+
+#credit amount? seems more what is expected than the applied amount
+my @lt_gt = FS::UI::Web::parse_lt_gt($cgi, 'amount' );
+s/amount/cust_credit.amount/g foreach (@lt_gt);
+push @where, @lt_gt;
+
if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
push @where, "cust_main.agentnum = $1";
}
+if ( $cgi->param('billpkgnum') =~ /^(\d+)$/ ) {
+ push @where, "billpkgnum = $1";
+}
+
#classnum
# not specified: all classes
# 0: empty class
@@ -346,7 +373,7 @@ if ( $cgi->param('cust_tax') ) {
push @where, $cust_exempt;
}
-my $count_query = "SELECT COUNT(DISTINCT billpkgnum),
+my $count_query = "SELECT COUNT(DISTINCT creditbillpkgnum),
SUM(cust_credit_bill_pkg.amount)";
my $join_cust =
@@ -412,8 +439,8 @@ my $join_credit = ' LEFT JOIN cust_credit_bill USING ( creditbillnum )
LEFT JOIN cust_credit USING ( crednum ) ';
$count_query .= " FROM cust_credit_bill_pkg
- $join_pkg
$join_cust_bill_pkg
+ $join_pkg
$join_credit
$join_cust
$where";
@@ -428,10 +455,22 @@ push @select, 'part_pkg.pkg' unless $cgi->param('istax');
push @select, 'cust_main.custnum',
FS::UI::Web::cust_sql_fields();
+my @post_desc_header = ();
+my @post_desc = ();
+my @post_desc_null = ();
+my $post_desc_align = '';
+if ( $conf->exists('enable_taxclasses') ) {
+ push @post_desc_header, 'Tax class';
+ push @post_desc, 'taxclass';
+ push @post_desc_null, '';
+ $post_desc_align .= 'l';
+ push @select, 'part_pkg.taxclass'; # or should this use override?
+}
+
my $query = {
'table' => 'cust_credit_bill_pkg',
- 'addl_from' => "$join_pkg
- $join_cust_bill_pkg
+ 'addl_from' => "$join_cust_bill_pkg
+ $join_pkg
$join_credit
$join_cust",
'hashref' => {},
diff --git a/httemplate/search/cust_pkg_susp.html b/httemplate/search/cust_pkg_susp.html
index 2ac643260..bdc3c5402 100644
--- a/httemplate/search/cust_pkg_susp.html
+++ b/httemplate/search/cust_pkg_susp.html
@@ -23,6 +23,6 @@
<%init>
die "access denied"
- unless $curuser->access_right('Summarize packages');
+ unless $FS::CurrentUser::CurrentUser->access_right('Summarize packages');
</%init>
diff --git a/httemplate/search/customer_accounting_summary.html b/httemplate/search/customer_accounting_summary.html
index 5ce2e3a8f..0e9e24fa6 100644
--- a/httemplate/search/customer_accounting_summary.html
+++ b/httemplate/search/customer_accounting_summary.html
@@ -9,8 +9,7 @@
http_header('Content-Disposition' => qq!attachment;filename="$filename"!);
my $output = '';
- use IO::String;
- my $XLS = IO::String->new($output);;
+ my $XLS = IO::String->new($output);
my $workbook = $format->{class}->new($XLS)
or die "Error opening .xls file: $!";
@@ -73,9 +72,11 @@
$r++;
} #$row
$workbook->close;
+
+ http_header('Content-Length' => length($output));
+ $m->print($output);
</%perl>
-<% $output %>
-% } else {
+% } else {
<& /elements/header.html, $title &>
% my $myself = $cgi->self_url;
<P ALIGN="right" CLASS="noprint">
@@ -106,7 +107,7 @@ as <A HREF="<% "$myself;_type=xls" %>">Excel spreadsheet</A><BR>
% my $style = '';
% $style .= " rowspan=".$cell->{rowspan} if $cell->{rowspan} > 1;
% $style .= " colspan=".$cell->{colspan} if $cell->{colspan} > 1;
- <<%$td%><%$style%>><% $cell->{value} %></<%$td%>>
+ <<%$td%><%$style%>><% $cell->{value} |h %></<%$td%>>
% }
</tr>
% }
diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html
index 5c8001fad..7ccf356ea 100644
--- a/httemplate/search/elements/search-html.html
+++ b/httemplate/search/elements/search-html.html
@@ -259,6 +259,7 @@
%
% my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
% my $onclicks = $opt{'link_onclicks'} ? [ @{$opt{'link_onclicks'}} ] : [];
+% my $tooltips = $opt{'tooltips'} ? [ @{$opt{'tooltips'}} ] : [];
% my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
% my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : [];
% my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
@@ -360,6 +361,7 @@
% if ( $links ) {
% my $link = shift @$links;
% my $onclick = shift @$onclicks;
+% my $tooltip = shift @$tooltips;
%
% if ( ! $opt{'agent_virt'}
% || ( $null_link && ! $row->agentnum )
@@ -374,6 +376,14 @@
% if ref($onclick) eq 'CODE';
% $onclick = qq( onClick="$onclick") if $onclick;
%
+% $tooltip = &{$tooltip}($row)
+% if ref($tooltip) eq 'CODE';
+% $tooltip = qq! id="a$id" !.
+% qq! onmouseover="return overlib(!.
+% $m->interp->apply_escapes($tooltip, 'h', 'js_string').
+% qq!, FGCLASS, 'tooltip', REF, 'a$id', !.
+% qq!REFC, 'LL', REFP, 'UL')"! if $tooltip;
+%
% if ( $link ) {
% my( $url, $method ) = @{$link};
% if ( ref($method) eq 'CODE' ) {
@@ -381,11 +391,16 @@
% } else {
% $a = $url. $row->$method();
% }
-% $a = qq(<A HREF="$a"$onclick>);
+% $a = qq(<A HREF="$a"$onclick$tooltip>);
% }
% elsif ( $onclick ) {
% $a = qq(<A HREF="javascript:void(0);"$onclick>);
% }
+% elsif ( $tooltip ) {
+% $a = qq(<A $tooltip>);
+% }
+% $id++;
+
% }
%
% }
@@ -499,4 +514,5 @@ $count_sth->execute
my $count_arrayref = $count_sth->fetchrow_arrayref;
my $total = $count_arrayref->[0];
+my $id = 0;
</%init>
diff --git a/httemplate/search/elements/search-xls.html b/httemplate/search/elements/search-xls.html
index 94d88b096..26a51c4c7 100644
--- a/httemplate/search/elements/search-xls.html
+++ b/httemplate/search/elements/search-xls.html
@@ -1,4 +1,3 @@
-<% $data %>
<%init>
my %args = @_;
@@ -148,5 +147,6 @@ if ( $opt{'footer'} ) {
$workbook->close();# or die "Error creating .xls file: $!";
http_header('Content-Length' => length($data) );
+$m->print($data);
</%init>
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
index eca68a2f8..5a16a22fe 100644
--- a/httemplate/search/elements/search.html
+++ b/httemplate/search/elements/search.html
@@ -176,7 +176,9 @@ Example:
%
% } elsif ( $type =~ /\.xls$/ ) {
%
-<% include('search-xls.html', header=>$header, rows=>$rows, opt=>\%opt ) %>
+<& 'search-xls.html', header=>$header, rows=>$rows, opt=>\%opt &>\
+% # prevent the caller from polluting our output stream
+% $m->abort;
%
% } elsif ( $type eq 'xml' ) {
%
diff --git a/httemplate/search/log.html b/httemplate/search/log.html
new file mode 100644
index 000000000..d1bfb6cc9
--- /dev/null
+++ b/httemplate/search/log.html
@@ -0,0 +1,221 @@
+<& elements/search.html,
+ 'title' => 'System Log',
+ 'name_singular' => 'event',
+ 'html_init' => include('.head'),
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => [ #'#', # lognum, probably not useful
+ 'Date',
+ 'Level',
+ 'Context',
+ 'Applies To',
+ 'Message',
+ ],
+ 'fields' => [ #'lognum',
+ $date_sub,
+ $level_sub,
+ $context_sub,
+ $object_sub,
+ $message_sub,
+ ],
+ 'sort_fields' => [
+ '_date',
+ 'level',
+ '',
+ 'tablename,tablenum',
+ 'message',
+ ],
+ 'links' => [
+ '', #date
+ '', #level
+ '', #context
+ $object_link_sub,
+ '', #message
+ ],
+ 'tooltips' => [
+ '', #date
+ '', #level
+ $tt_sub,
+ '', #object
+ $tt_sub,
+ ],
+ 'color' => [
+ $color_sub,
+ $color_sub,
+ '',
+ '',
+ '',
+ ],
+ # aligns
+ 'download_label' => 'Download this log',
+&>\
+<%def .head>
+<STYLE type="text/css">
+a:link {text-decoration: none}
+a:visited {text-decoration: none}
+.tooltip {
+ background-color: #ffffff;
+ font-size: 100%;
+ font-weight: bold;
+}
+</STYLE>
+<FORM ACTION="<%$p%>search/log.html" METHOD="GET">
+<TABLE CELLSPACING="10">
+<TR>
+ <TD>From
+ <& /elements/input-date-field.html, {
+ name => 'beginning',
+ value => $cgi->param('beginning'),
+ } &>
+ </TD>
+ <TD>To
+ <& /elements/input-date-field.html, {
+ name => 'ending',
+ value => $cgi->param('ending') || '',
+ noinit => 1,
+ } &>
+ </TD>
+</TR>
+<TR>
+ <TD>Level
+ <& /elements/select.html,
+ field => 'min_level',
+ options => [ 0..7 ],
+ labels => { map {$_ => $FS::Log::LEVELS[$_]} 0..7 },
+ curr_value => $cgi->param('min_level'),
+ &>
+ to
+ <& /elements/select.html,
+ field => 'max_level',
+ options => [ 0..7 ],
+ labels => { map {$_ => $FS::Log::LEVELS[$_]} 0..7 },
+ curr_value => $cgi->param('max_level'),
+ &>
+ </TD>
+ <TD>
+ Context
+ <& /elements/select.html,
+ field => 'context',
+ options => \@contexts,
+ labels => { map {$_, $_} @contexts },
+ curr_value => ($cgi->param('context') || ''),
+ &>
+ </TD>
+</TR>
+<TR>
+ <TD COLSPAN=2>
+ Containing text
+ <& /elements/input-text.html,
+ field => 'message',
+ size => 30,
+ size => 30,
+ curr_value => ($cgi->param('message') || ''),
+ &>
+ <DIV STYLE="display:inline; float:right">
+ <INPUT TYPE="submit" VALUE="Refresh">
+ </DIV>
+ </TD>
+</TR>
+</TABLE>
+</%def>
+<%once>
+my $date_sub = sub { time2str('%Y-%m-%d %T', $_[0]->_date) };
+
+my $level_sub = sub { $FS::Log::LEVELS[$_[0]->level] };
+
+my $context_sub = sub {
+ my $log = shift;
+ ($log->context)[-1] . (scalar($log->context) > 1 ? '...' : '') ;
+ # XXX find a way to make this use less space (dropdown?)
+};
+
+my $tt_sub = sub {
+ my $log = shift;
+ my @context = $log->context;
+ # don't create a tooltip if there's only one context entry and the
+ # message isn't cut off
+ return '' if @context == 1 and length($log->message) <= 60;
+ my $html = '<DIV CLASS="tooltip">'.(shift @context).'</DIV>';
+ my $pre = '&#8627;';
+ foreach (@context, $log->message) {
+ $html .= "<DIV>$pre$_</DIV>";
+ $pre = '&nbsp;&nbsp;&nbsp;'.$pre;
+ }
+ $html;
+};
+
+my $object_sub = sub {
+ my $log = shift;
+ return '' unless $log->tablename;
+ # this is a sysadmin log; anyone reading it should be able to understand
+ # 'cust_main #2319' with no trouble.
+ $log->tablename . ' #' . $log->tablenum;
+};
+
+my $message_sub = sub {
+ my $log = shift;
+ my $message = $log->message;
+ if ( length($message) > 60 ) { # pretty arbitrary
+ $message = substr($message, 0, 57) . '...';
+ }
+ $message;
+};
+
+my $object_link_sub = sub {
+ my $log = shift;
+ my $table = $log->tablename or return;
+ # sigh
+ if ( grep {$_ eq $table} (qw( cust_bill cust_main cust_pkg cust_svc ))
+ or $table =~ /^svc_/ )
+ {
+
+ return [ $fsurl.'view/'.$table.'.cgi?'. $log->tablenum ];
+
+ } elsif ( grep {$_ eq $table} (qw( cust_msg cust_pay cust_pay_void
+ cust_refund cust_statement )) )
+ {
+
+ return [ $fsurl.'view/'.$table.'.html?', $log->tablenum ];
+
+ } else { # you're on your own
+
+ return '';
+
+ }
+};
+
+my @colors = (
+ '404040', #debug
+ '0000aa', #info
+ '00aa00', #notice
+ 'aa0066', #warning
+ '000000', #error
+ 'aa0000', #critical
+ 'ff0000', #alert
+ 'ff0000', #emergency
+);
+
+my $color_sub = sub { $colors[ $_[0]->level ]; };
+
+my @contexts = ('', sort FS::log_context->contexts);
+</%once>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right([ 'View system logs', 'Configuration' ]);
+
+$cgi->param('min_level', 0) unless defined($cgi->param('min_level'));
+$cgi->param('max_level', 7) unless defined($cgi->param('max_level'));
+
+my %search = ();
+$search{'date'} = [ FS::UI::Web::parse_beginning_ending($cgi) ];
+$search{'level'} = [ $cgi->param('min_level'), $cgi->param('max_level') ];
+foreach my $param (qw(agentnum context tablename tablenum custnum message)) {
+ if ( $cgi->param($param) ) {
+ $search{$param} = $cgi->param($param);
+ }
+}
+my $query = FS::log->search(\%search); # validates everything
+my $count_query = delete $query->{'count_query'};
+
+</%init>
diff --git a/httemplate/search/report_cust_bill_pkg.html b/httemplate/search/report_cust_bill_pkg.html
new file mode 100644
index 000000000..4f6ee78db
--- /dev/null
+++ b/httemplate/search/report_cust_bill_pkg.html
@@ -0,0 +1,118 @@
+<& /elements/header.html, mt('Line item report') &>
+
+<FORM ACTION="cust_bill_pkg.cgi" METHOD="GET">
+<!--<INPUT TYPE="hidden" NAME="magic" VALUE="_date">-->
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0
+
+<& /elements/tr-select-agent.html,
+ curr_value => scalar( $cgi->param('agentnum') ),
+ #label => emt('Line items for agent: '),
+ disable_empty => 0,
+&>
+
+<& /elements/tr-select-cust_main-status.html,
+ label => emt('Customer status'),
+&>
+
+<!-- customer
+<& /elements/tr-select-cust_class.html,
+ 'label' => emt('Class'),
+ 'multiple' => 1,
+ 'pre_options' => [ '' => emt('(none)') ],
+ 'all_selected' => 1,
+&>
+-->
+
+<& /elements/tr-input-beginning_ending.html &>
+
+<!-- needs support in cust_bill_pkg.cgi
+<& /elements/tr-input-lessthan_greaterthan.html,
+ label => emt('Amount'),
+ field => 'amount',
+&>
+-->
+
+<!-- customer payment method i guess
+ <& /elements/tr-select-payby.html,
+ label => emt('Payment method:'),
+ payby_type => 'cust',
+ multiple => 1,
+ all_selected => 1,
+ &>
+-->
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="nottax" VALUE="Y" onClick="nottax_changed(this)" onChange="nottax_change(thid)"></TD>
+ <TD><% mt('Omit taxes') |h %></TD>
+</TD>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="istax" VALUE="Y" onClick="istax_changed(this)" onChange="istax_change(thid)"></TD>
+ <TD><% mt('Taxes only') |h %></TD>
+</TD>
+
+<!--
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="credit" VALUE="Y"></TD>
+ <TD><% mt("Credit (what's this do?)") |h %></TD>
+</TD>
+-->
+
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+ function nottax_changed (what) {
+ if (what.checked && what.form.istax.checked) {
+ what.form.istax.checked = false;
+ }
+ }
+ function istax_changed (what) {
+ if (what.checked && what.form.nottax.checked) {
+ what.form.nottax.checked = false;
+ }
+ }
+</SCRIPT>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+#Financial reports?
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List invoices');
+
+my $conf = new FS::Conf;
+
+#other available params (cust_bill_pkg.cgi):
+#
+#distribute = 1
+#
+#(when nottax)
+# use_override something about part_pkg
+# classnum package class
+# taxclass / taxclassNULL
+# exempt_cust
+# exempt_pkg
+# region (country:state:county:city:district)
+# taxable
+# out (of taxable region)
+# usage
+#(when istax)
+# locationtaxid (& district/city/ciounty/state)
+# out (of taxable region)
+# taxclassNULL
+# report_group (itemdesc)
+# itemdesc
+#
+#taxname/taxnameNULL cust_main_county
+#taxnum cust_main_county
+#credit (hmm need to look more at what this does)
+
+
+</%init>
+
diff --git a/httemplate/search/report_cust_credit_bill_pkg.html b/httemplate/search/report_cust_credit_bill_pkg.html
new file mode 100644
index 000000000..2b9e1e69d
--- /dev/null
+++ b/httemplate/search/report_cust_credit_bill_pkg.html
@@ -0,0 +1,104 @@
+<& /elements/header.html, mt('Credit application report') &>
+
+<FORM ACTION="cust_credit_bill_pkg.html" METHOD="GET">
+<!--<INPUT TYPE="hidden" NAME="magic" VALUE="_date">-->
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0
+
+<& /elements/tr-select-user.html,
+ 'label' => emt('Employee: '),
+ 'access_user' => \%access_user,
+&>
+
+<& /elements/tr-select-agent.html,
+ curr_value => scalar( $cgi->param('agentnum') ),
+ #label => emt('Line items for agent: '),
+ disable_empty => 0,
+&>
+
+<!--
+<& /elements/tr-select-cust_main-status.html,
+ label => emt('Customer status'),
+&>
+-->
+
+<!-- customer
+<& /elements/tr-select-cust_class.html,
+ 'label' => emt('Class'),
+ 'multiple' => 1,
+ 'pre_options' => [ '' => emt('(none)') ],
+ 'all_selected' => 1,
+&>
+-->
+
+<!-- some sort of label saying this is the credit date... -->
+<& /elements/tr-input-beginning_ending.html,
+ 'prefix' => 'credit',
+&>
+
+<& /elements/tr-input-lessthan_greaterthan.html,
+ label => emt('Amount'),
+ field => 'amount',
+&>
+
+<!-- customer payment method i guess
+ <& /elements/tr-select-payby.html,
+ label => emt('Payment method:'),
+ payby_type => 'cust',
+ multiple => 1,
+ all_selected => 1,
+ &>
+-->
+
+<!--
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="nottax" VALUE="Y" onClick="nottax_changed(this)" onChange="nottax_change(thid)"></TD>
+ <TD><% mt('Omit taxes') |h %></TD>
+</TD>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="istax" VALUE="Y" onClick="istax_changed(this)" onChange="istax_change(thid)"></TD>
+ <TD><% mt('Taxes only') |h %></TD>
+</TD>
+
+<SCRIPT TYPE="text/javascript">
+ function nottax_changed (what) {
+ if (what.checked && what.form.istax.checked) {
+ what.form.istax.checked = false;
+ }
+ }
+ function istax_changed (what) {
+ if (what.checked && what.form.nottax.checked) {
+ what.form.nottax.checked = false;
+ }
+ }
+</SCRIPT>
+-->
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+#Financial reports?
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#false laziness w/report_cust_credit.html
+my $sth = dbh->prepare("SELECT DISTINCT usernum FROM cust_credit")
+ or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my @usernum = map $_->[0], @{$sth->fetchall_arrayref};
+my %access_user =
+ map { $_ => qsearchs('access_user',{'usernum'=>$_})->username }
+ @usernum;
+
+my $conf = new FS::Conf;
+
+</%init>
+
diff --git a/httemplate/search/report_tax-xls.cgi b/httemplate/search/report_tax-xls.cgi
index f19f85aaa..bb843a73f 100755
--- a/httemplate/search/report_tax-xls.cgi
+++ b/httemplate/search/report_tax-xls.cgi
@@ -1,4 +1,3 @@
-<% $data %>
<%init>
my $htmldoc = include('report_tax.cgi');
@@ -155,4 +154,6 @@ for my $x (0..scalar(@widths)-1) {
$workbook->close;
+http_header('Content-Length' => length($data));
+$m->print($data);
</%init>
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
index 166addbf4..6630d12a5 100644
--- a/httemplate/view/cust_main/payment_history.html
+++ b/httemplate/view/cust_main/payment_history.html
@@ -70,6 +70,16 @@
'actionlabel' => emt('Enter credit'),
'width' => 616, #make room for reasons #540 default
&>
+ |
+ <& /elements/popup_link-cust_main.html,
+ 'label' => emt('Credit line items'),
+ #'action' => "${p}search/cust_bill_pkg.cgi?nottax=1;type=select",
+ 'action' => "${p}edit/credit-cust_bill_pkg.html",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => emt('Credit line items'),
+ 'width' => 884, #763,
+ 'height' => 575,
+ &>
<BR>
% }
diff --git a/httemplate/view/part_event-targets.html b/httemplate/view/part_event-targets.html
index 2029fd4bc..e8b1266ef 100644
--- a/httemplate/view/part_event-targets.html
+++ b/httemplate/view/part_event-targets.html
@@ -65,9 +65,6 @@ When event is run on <& /elements/input-date-field.html, {
%}
<& /elements/footer.html &>
-<%once>
-use List::MoreUtils qw(uniq);
-</%once>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;