summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rwxr-xr-xhttemplate/browse/part_export.cgi15
-rw-r--r--httemplate/edit/bulk-part_pkg.html2
-rwxr-xr-xhttemplate/edit/cust_location.cgi28
-rwxr-xr-xhttemplate/edit/cust_main.cgi36
-rw-r--r--httemplate/edit/cust_main/billing.html8
-rwxr-xr-xhttemplate/edit/cust_pkg_quantity.html49
-rw-r--r--httemplate/edit/part_export.cgi81
-rwxr-xr-xhttemplate/edit/part_pkg.cgi92
-rw-r--r--httemplate/edit/payment_gateway.html2
-rw-r--r--httemplate/edit/process/change-cust_pkg.html4
-rw-r--r--httemplate/edit/process/cust_location.cgi5
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi5
-rw-r--r--httemplate/edit/process/cust_pkg_quantity.html33
-rw-r--r--httemplate/edit/process/part_export.cgi1
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi8
-rw-r--r--httemplate/edit/process/quick-cust_pkg.cgi18
-rw-r--r--httemplate/edit/process/svc_phone.html4
-rw-r--r--httemplate/edit/svc_broadband.cgi8
-rw-r--r--httemplate/elements/auto-table.html4
-rw-r--r--httemplate/elements/change_history_common.html15
-rw-r--r--httemplate/elements/contact.html6
-rw-r--r--httemplate/elements/dashboard-toplist.html3
-rw-r--r--httemplate/elements/menu.html12
-rw-r--r--httemplate/elements/progress-init.html2
-rw-r--r--httemplate/elements/search-svc_broadband.html204
-rw-r--r--httemplate/elements/select-tiered.html7
-rw-r--r--httemplate/elements/selectlayers.html2
-rw-r--r--httemplate/elements/tr-search-svc_broadband.html15
-rw-r--r--httemplate/elements/tr-select-contact.html204
-rw-r--r--httemplate/elements/tr-select-cust_location.html33
-rw-r--r--httemplate/elements/tr-select-voip_class.html3
-rw-r--r--httemplate/misc/areacodes.cgi2
-rw-r--r--httemplate/misc/batch-cust_pay.html27
-rwxr-xr-xhttemplate/misc/change_pkg_contact.html70
-rw-r--r--httemplate/misc/choose_tax_location.html2
-rw-r--r--httemplate/misc/cust-part_pkg.cgi2
-rw-r--r--httemplate/misc/exchanges.cgi2
-rw-r--r--httemplate/misc/location.cgi7
-rw-r--r--httemplate/misc/macinventory.cgi2
-rw-r--r--httemplate/misc/maestro-customer_status.html2
-rw-r--r--httemplate/misc/manage_cust_email.html106
-rw-r--r--httemplate/misc/order_pkg.html6
-rw-r--r--httemplate/misc/part_export/huawei_hlr-import_sim.html52
-rw-r--r--httemplate/misc/part_export/process/huawei_hlr-import_sim.html10
-rw-r--r--httemplate/misc/part_svc-columns.cgi2
-rw-r--r--httemplate/misc/phonenums.cgi2
-rw-r--r--httemplate/misc/process/change_pkg_contact.html49
-rw-r--r--httemplate/misc/process/manage_cust_email.html32
-rw-r--r--httemplate/misc/regions.cgi2
-rw-r--r--httemplate/misc/xmlhttp-address_standardize.html2
-rw-r--r--httemplate/misc/xmlhttp-calculate_taxes.html2
-rw-r--r--httemplate/misc/xmlhttp-cust_bill-search.html18
-rw-r--r--httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-censustract.html2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-discount_terms.cgi2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-email_search.html29
-rw-r--r--httemplate/misc/xmlhttp-cust_main-search.cgi8
-rw-r--r--httemplate/misc/xmlhttp-ping.html2
-rw-r--r--httemplate/misc/xmlhttp-svc_broadband-search.cgi22
-rw-r--r--httemplate/pref/pref-process.html1
-rw-r--r--httemplate/pref/pref.html17
-rwxr-xr-xhttemplate/search/477.html15
-rwxr-xr-xhttemplate/search/477partIA.html165
-rwxr-xr-xhttemplate/search/477partIA_detail.html129
-rwxr-xr-xhttemplate/search/477partIA_summary.html89
-rwxr-xr-xhttemplate/search/477partIIA.html185
-rwxr-xr-xhttemplate/search/477partIIB.html16
-rwxr-xr-xhttemplate/search/477partV.html6
-rw-r--r--httemplate/search/agent_commission.html100
-rwxr-xr-xhttemplate/search/cust_bill.html2
-rw-r--r--httemplate/search/cust_bill_pay.html9
-rw-r--r--httemplate/search/cust_bill_pkg.cgi11
-rw-r--r--httemplate/search/cust_bill_pkg_referral.html10
-rwxr-xr-xhttemplate/search/cust_credit.html10
-rw-r--r--httemplate/search/cust_credit_refund.html9
-rwxr-xr-xhttemplate/search/cust_main.cgi2
-rwxr-xr-xhttemplate/search/cust_main.html3
-rwxr-xr-xhttemplate/search/cust_pkg.cgi8
-rw-r--r--httemplate/search/customer_accounting_summary.html4
-rw-r--r--httemplate/search/elements/cust_main_dayranges.html9
-rwxr-xr-xhttemplate/search/elements/cust_pay_or_refund.html9
-rw-r--r--httemplate/search/elements/search.html2
-rw-r--r--httemplate/search/employee_audit.html2
-rw-r--r--httemplate/search/part_pkg.html2
-rwxr-xr-xhttemplate/search/pay_batch.cgi4
-rw-r--r--httemplate/search/prepaid_income.html9
-rw-r--r--httemplate/search/report_cust_bill.html2
-rwxr-xr-xhttemplate/search/report_cust_main.html40
-rw-r--r--httemplate/search/report_employee_audit.html2
-rw-r--r--httemplate/search/report_employee_commission.html2
-rwxr-xr-xhttemplate/search/report_receivables.html10
-rwxr-xr-xhttemplate/search/report_tax.cgi11
-rwxr-xr-xhttemplate/search/report_tax.html14
-rw-r--r--httemplate/search/sqlradius.cgi17
-rw-r--r--httemplate/search/sqlradius.html57
-rw-r--r--httemplate/search/unearned_detail.html13
-rw-r--r--httemplate/view/cust_main/billing.html4
-rw-r--r--httemplate/view/cust_main/change_history.html7
-rwxr-xr-xhttemplate/view/cust_main/packages.html7
-rw-r--r--httemplate/view/cust_main/packages/contact.html61
-rw-r--r--httemplate/view/cust_main/packages/location.html14
-rw-r--r--httemplate/view/cust_main/packages/package.html30
-rwxr-xr-xhttemplate/view/cust_main/packages/section.html19
-rw-r--r--httemplate/view/cust_main/packages/status.html2
-rw-r--r--httemplate/view/cust_main/payment_history.html127
-rwxr-xr-xhttemplate/view/svc_acct.cgi5
-rw-r--r--httemplate/view/svc_broadband.cgi35
107 files changed, 2017 insertions, 665 deletions
diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi
index 91238a0fd..876633afc 100755
--- a/httemplate/browse/part_export.cgi
+++ b/httemplate/browse/part_export.cgi
@@ -38,6 +38,21 @@ function part_export_areyousure(href) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
<% $part_export->label_html %>
(<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A>&nbsp;|&nbsp;<A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>)
+% if ( my @actions = $part_export->actions ) {
+ <P STYLE="position: absolute">
+ Management:
+% while (@actions) {
+% my $label = shift @actions;
+% my $path = shift @actions;
+ <& /elements/popup_link.html,
+ 'label' => $label,
+ 'action' => $fsurl.$path.'?'.$part_export->exportnum,
+ 'actionlabel' => $label,
+ &><% @actions ? '&nbsp;|&nbsp;' : '' %>
+% }
+ </P>
+% } #if @actions
+
</TD>
<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
diff --git a/httemplate/edit/bulk-part_pkg.html b/httemplate/edit/bulk-part_pkg.html
index 751bf7e5d..a1c6f0c9b 100644
--- a/httemplate/edit/bulk-part_pkg.html
+++ b/httemplate/edit/bulk-part_pkg.html
@@ -12,7 +12,7 @@
The following packages will be changed:<BR>
% foreach my $pkgpart (sort keys(%part_pkg)) {
<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart %>">
-<% $part_pkg{$pkgpart}->pkg_comment %><BR>
+<% $part_pkg{$pkgpart}->pkg_comment |h %><BR>
% }
</DIV>
<BR>
diff --git a/httemplate/edit/cust_location.cgi b/httemplate/edit/cust_location.cgi
index 80b27c2b3..b90ba66b8 100755
--- a/httemplate/edit/cust_location.cgi
+++ b/httemplate/edit/cust_location.cgi
@@ -7,20 +7,32 @@ ACTION="<% $p %>edit/process/cust_location.cgi" METHOD=POST>
<INPUT TYPE="hidden" NAME="locationnum" VALUE="<% $locationnum %>">
<% ntable('#cccccc') %>
-<% include('/elements/location.html',
- 'object' => $cust_location,
- 'no_asterisks' => 1,
- ) %>
+<& /elements/location.html,
+ 'object' => $cust_location,
+ 'no_asterisks' => 1,
+ # these are service locations, so they need all this stuff
+ 'enable_coords' => 1,
+ 'enable_district' => 1,
+ 'enable_censustract' => 1,
+&>
+<& /elements/standardize_locations.html,
+ 'form' => 'EditLocationForm',
+ 'callback' => 'document.EditLocationForm.submit();',
+&>
</TABLE>
<BR>
<SCRIPT TYPE="text/javascript">
-function areyousure() {
- return confirm('Modify this service location?');
+function go() {
+% if ( FS::Conf->new->config('address_standardize_method') ) {
+ standardize_locations();
+% } else {
+ confirm('Modify this service location?') &&
+ document.EditLocationForm.submit();
+% }
}
</SCRIPT>
-<INPUT TYPE="submit" VALUE="Submit" onclick="return areyousure()">
-
+<INPUT TYPE="button" NAME="submitButton" VALUE="Submit" onclick="go()">
</FORM>
</BODY>
</HTML>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index 5671e7a2b..2908848c6 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -48,7 +48,7 @@
<TD STYLE="width:650px">
%#; padding-right:2px; vertical-align:top">
<FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT>
- <TABLE CLASS="fsinnerbox">
+ <TABLE CLASS="fsinnerbox" WIDTH="100%">
<& cust_main/before_bill_location.html, $cust_main &>
<& /elements/location.html,
object => $cust_main->bill_location,
@@ -62,7 +62,6 @@
<TR><TD STYLE="height:40px"></TD></TR>
<TR>
<TD STYLE="width:650px">
-%#; padding-left:2px; vertical-align:top">
<FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT>
<INPUT TYPE="checkbox"
NAME="same"
@@ -72,19 +71,17 @@
VALUE="Y"
<% $has_ship_address ? '' : 'CHECKED' %>
><% mt('same as billing address') |h %>
- <TABLE CLASS="fsinnerbox" ID="table_ship_location">
- <& /elements/location.html,
- object => $cust_main->ship_location,
- prefix => 'ship_',
- enable_censustract => 1,
- enable_district => 1,
- enable_coords => 1,
- &>
- </TABLE>
- <TABLE CLASS="fsinnerbox" ID="table_ship_location_blank"
- STYLE="display:none">
- <TR><TD></TD></TR>
- </TABLE>
+ <DIV CLASS="fsinnerbox">
+ <TABLE ID="table_ship_location" WIDTH="100%">
+ <& /elements/location.html,
+ object => $cust_main->ship_location,
+ prefix => 'ship_',
+ enable_censustract => 1,
+ enable_district => 1,
+ enable_coords => 1,
+ &>
+ </TABLE>
+ </DIV>
</TD>
</TR></TABLE>
@@ -94,16 +91,11 @@ function samechanged(what) {
%# document.getElementById('table_ship_location').style.visibility =
%# what.checked ? 'hidden' : 'visible';
var t1 = document.getElementById('table_ship_location');
- var t2 = document.getElementById('table_ship_location_blank');
if ( what.checked ) {
- t2.style.width = t1.clientWidth + 'px';
- t2.style.height = t1.clientHeight + 'px';
- t1.style.display = 'none';
- t2.style.display = '';
+ t1.style.visibility = 'hidden';
}
else {
- t2.style.display = 'none';
- t1.style.display = '';
+ t1.style.visibility = 'visible'
}
}
//samechanged(document.getElementById('same'));
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
index 6ba73ad68..5a66f0a60 100644
--- a/httemplate/edit/cust_main/billing.html
+++ b/httemplate/edit/cust_main/billing.html
@@ -521,7 +521,13 @@
<% $conf->exists('cust_main-require_invoicing_list_email', $agentnum)
? $r : '' %>Email address(es)
</TD>
- <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"></TD>
+ <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>">
+ <INPUT TYPE="checkbox" NAME="message_noemail" VALUE="Y" <%
+ ( $cust_main->message_noemail eq 'Y' )
+ ? 'CHECKED'
+ : ''
+ %>> <% emt('Do not send notices') %>
+ </TD>
</TR>
% }
diff --git a/httemplate/edit/cust_pkg_quantity.html b/httemplate/edit/cust_pkg_quantity.html
new file mode 100755
index 000000000..ec47ed6cb
--- /dev/null
+++ b/httemplate/edit/cust_pkg_quantity.html
@@ -0,0 +1,49 @@
+<& /elements/header-popup.html, "Change Quantity" &>
+<& /elements/error.html &>
+
+<FORM ACTION="<% $p %>edit/process/cust_pkg_quantity.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+<& /elements/table-grid.html, 'bgcolor' => '#cccccc', 'cellpadding' => 2 &>
+
+ <TR>
+ <TH ALIGN="right">Current package&nbsp;</TH>
+ <TD CLASS="grid">
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+<& /elements/tr-input-text.html,
+ 'field' => 'quantity',
+ 'curr_value' => $cust_pkg->quantity,
+ 'label' => emt('Quantity')
+&>
+
+</TABLE>
+
+<BR>
+<INPUT NAME="submit" TYPE="submit" VALUE="Change">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+#some false laziness w/misc/change_pkg.cgi
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg = FS::cust_pkg->by_key($pkgnum) or die "unknown pkgnum $pkgnum";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
index 4dd253be8..2897cf39d 100644
--- a/httemplate/edit/part_export.cgi
+++ b/httemplate/edit/part_export.cgi
@@ -2,6 +2,34 @@
<% include('/elements/error.html') %>
+<SCRIPT TYPE="text/javascript">
+ function svc_machine_changed (what, layer) {
+ if ( what.checked ) {
+ var machine = document.getElementById(layer + "_machine");
+ var part_export_machine =
+ document.getElementById(layer + "_part_export_machine");
+ if ( what.value == 'Y' ) {
+ machine.disabled = true;
+ part_export_machine.disabled = false;
+ } else if ( what.value == 'N' ) {
+ machine.disabled = false;
+ part_export_machine.disabled = true;
+ }
+ }
+ }
+
+ function part_export_machine_changed (what, layer) {
+ var select_default = document.getElementById(layer + '_default_machine');
+ var selected = select_default.value;
+ select_default.options.length = 0;
+ var choices = what.value.split("\n");
+ for (var i = 0; i < choices.length; i++) {
+ select_default.options[i] = new Option(choices[i]);
+ }
+ select_default.value = selected;
+ }
+
+</SCRIPT>
<FORM NAME="dummy">
<INPUT TYPE="hidden" NAME="exportnum" VALUE="<% $part_export->exportnum %>">
@@ -58,7 +86,6 @@ my $widget = new HTML::Widgets::SelectLayers(
'form_name' => 'dummy',
'form_action' => 'process/part_export.cgi',
'form_text' => [qw( exportnum exportname )],
-# 'form_checkbox' => [qw()],
'html_between' => "</TD></TR></TABLE>\n",
'layer_callback' => sub {
my $layer = shift;
@@ -87,7 +114,8 @@ my $widget = new HTML::Widgets::SelectLayers(
if ( $exports->{$layer}{svc_machine} ) {
my( $N_CHK, $Y_CHK) = ( 'CHECKED', '' );
my( $machine_DISABLED, $pem_DISABLED) = ( '', 'DISABLED' );
- my $part_export_machine = '';
+ my @part_export_machine;
+ my $default_machine = '';
if ( $cgi->param('svc_machine') eq 'Y'
|| $machine eq '_SVC_MACHINE'
)
@@ -97,38 +125,43 @@ my $widget = new HTML::Widgets::SelectLayers(
$machine_DISABLED = 'DISABLED';
$pem_DISABLED = '';
$machine = '';
- $part_export_machine =
- $cgi->param('part_export_machine')
- || join "\n",
+ @part_export_machine = $cgi->param('part_export_machine');
+ if (!@part_export_machine) {
+ @part_export_machine =
map $_->machine,
grep ! $_->disabled,
$part_export->part_export_machine;
+ }
+ $default_machine =
+ $cgi->param('default_machine_name')
+ || $part_export->default_export_machine;
}
- my $oc = qq(onChange="${layer}_svc_machine_changed(this)");
+ my $oc = qq(onChange="svc_machine_changed(this, '$layer')");
$html .= qq[
<INPUT TYPE="radio" NAME="svc_machine" VALUE="N" $N_CHK $oc>
<INPUT TYPE="text" NAME="machine" ID="${layer}_machine" VALUE="$machine" $machine_DISABLED>
<BR>
<INPUT TYPE="radio" NAME="svc_machine" VALUE="Y" $Y_CHK $oc>
- Selected in each customer service from these choices
- <TEXTAREA NAME="part_export_machine" ID="${layer}_part_export_machine" $pem_DISABLED>$part_export_machine</TEXTAREA>
-
- <SCRIPT TYPE="text/javascript">
- function ${layer}_svc_machine_changed (what) {
- if ( what.checked ) {
- var machine = document.getElementById("${layer}_machine");
- var part_export_machine = document.getElementById("${layer}_part_export_machine");
- if ( what.value == 'Y' ) {
- machine.disabled = true;
- part_export_machine.disabled = false;
- } else if ( what.value == 'N' ) {
- machine.disabled = false;
- part_export_machine.disabled = true;
- }
- }
- }
- </SCRIPT>
+ <DIV STYLE="display:inline-block; vertical-align: top; text-align: right">
+ Selected in each customer service from these choices:
+ <TEXTAREA STYLE="vertical-align: top" NAME="part_export_machine"
+ ID="${layer}_part_export_machine"
+ onchange="part_export_machine_changed(this, '$layer')"
+ $pem_DISABLED>] .
+
+ join("\n", @part_export_machine) .
+
+ qq[</TEXTAREA>
+ <BR>
+ Default:
+ <SELECT NAME="default_machine_name" ID="${layer}_default_machine">
];
+ foreach (@part_export_machine) {
+ $_ = encode_entities($_); # oh noes, XSS
+ my $sel = ($default_machine eq $_) ? ' SELECTED' : '';
+ $html .= qq!<OPTION VALUE="$_"$sel>$_</OPTION>\n!;
+ }
+ $html .= '</DIV></SELECT>'
} else {
$html .= qq(<INPUT TYPE="text" NAME="machine" VALUE="$machine">).
'<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">';
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index 7baf84d11..fadde354e 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -28,7 +28,8 @@
'labels' => {
'pkgpart' => 'Package Definition',
- 'pkg' => 'Package (customer-visible)',
+ 'pkg' => 'Package',
+ %locale_field_labels,
'comment' => 'Comment (customer-hidden)',
'classnum' => 'Package class',
'addon_classnum' => 'Restrict additional orders to package class',
@@ -80,6 +81,7 @@
size => 40, #32
maxlength => 50,
},
+ #@locale_fields,
{field=>'comment', type=>'text', size=>40 }, #32
{ field => 'agentnum',
type => 'select-agent',
@@ -337,6 +339,22 @@ my $agent_clone_extra_sql =
my $conf = new FS::Conf;
my $taxproducts = $conf->exists('enable_taxproducts');
+my @locales = grep { ! /^en_/i } $conf->config('available-locales'); #should filter from the default locale lang instead of en_
+my %locale_labels = map {
+ ( $_ => 'Package -- '. FS::Locales->description($_) )
+} @locales;
+@locales =
+ sort { $locale_labels{$a} cmp $locale_labels{$b} }
+ @locales;
+
+my $n = 0;
+my %locale_field_labels = (
+ map {
+ ( 'pkgpartmsgnum'. $n++. '_pkg' => $locale_labels{$_} );
+ }
+ @locales
+);
+
my $sth = dbh->prepare("SELECT COUNT(*) FROM part_pkg_report_option".
" WHERE disabled IS NULL OR disabled = '' ")
or die dbh->errstr;
@@ -368,6 +386,42 @@ my $recur_show_zero_disabled = 1;
my $pkgpart = '';
+my $splice_locale_fields = sub {
+ my( $fields, $pkey_value_callback, $pkg_value_callback ) = @_;
+
+ my $n = 0;
+ my @locale_fields = (
+ map {
+ my $pkey_value= $pkey_value_callback ? &$pkey_value_callback($_) : '';
+ my $pkg_value = $pkg_value_callback
+ ? $pkg_value_callback eq 'cgiparam'
+ ? $cgi->param('pkgpartmsgnum'. $n. '_pkg')
+ : &$pkg_value_callback($_)
+ : '';
+ (
+ { field => 'pkgpartmsgnum'. $n,
+ type => 'hidden',
+ value => $pkey_value,
+ },
+ { field => 'pkgpartmsgnum'. $n. '_locale',
+ type => 'hidden',
+ value => $_,
+ },
+ { field => 'pkgpartmsgnum'. $n++. '_pkg',
+ type => 'text',
+ size => 40,
+ #maxlength => 50,
+ value => $pkg_value,
+ },
+ );
+
+ }
+ @locales
+ );
+ splice(@$fields, 7, 0, @locale_fields); #XXX 7 is arbitrary above
+
+};
+
my $error_callback = sub {
my($cgi, $object, $fields, $opt ) = @_;
@@ -408,6 +462,16 @@ my $error_callback = sub {
$pkgpart = $object->pkgpart;
+ &$splice_locale_fields(
+ $fields,
+ sub {
+ my $locale = shift;
+ my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
+ $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : '';
+ },
+ 'cgiparam'
+ );
+
};
my $new_hashref_callback = sub { { 'plan' => 'flat' }; };
@@ -473,6 +537,20 @@ my $edit_callback = sub {
$pkgpart = $object->pkgpart;
+ &$splice_locale_fields(
+ $fields,
+ sub {
+ my $locale = shift;
+ my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
+ $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : '';
+ },
+ sub {
+ my $locale = shift;
+ my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
+ $part_pkg_msgcat ? $part_pkg_msgcat->pkg : '';
+ }
+ );
+
};
my $new_callback = sub {
@@ -487,6 +565,8 @@ my $new_callback = sub {
$options{'suspend_bill'}=1 if $conf->exists('part_pkg-default_suspend_bill');
+ &$splice_locale_fields($fields, '', '');
+
};
my $clone_callback = sub {
@@ -520,6 +600,16 @@ my $clone_callback = sub {
foreach (qw( setup_fee recur_fee disable_line_item_date_ranges ));
$recur_disabled = $object->freq ? 0 : 1;
+
+ &$splice_locale_fields(
+ $fields,
+ '',
+ sub {
+ my $locale = shift;
+ my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
+ $part_pkg_msgcat ? $part_pkg_msgcat->pkg : '';
+ }
+ );
};
my $discount_error_callback = sub {
diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html
index dfe52f109..a469beb7f 100644
--- a/httemplate/edit/payment_gateway.html
+++ b/httemplate/edit/payment_gateway.html
@@ -19,7 +19,7 @@
<SCRIPT TYPE="text/javascript">
- var modulesForNamespace = <% to_json(\%modules_for_namespace, {canonical=>1}) %>;
+ var modulesForNamespace = <% encode_json(\%modules_for_namespace, {canonical=>1}) %>;
function changeNamespace(what) {
var ns = what.value;
var select_module = document.getElementById('gateway_module');
diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html
index 2770f3283..77f261d56 100644
--- a/httemplate/edit/process/change-cust_pkg.html
+++ b/httemplate/edit/process/change-cust_pkg.html
@@ -32,11 +32,11 @@ my %change = map { $_ => scalar($cgi->param($_)) }
$change{'keep_dates'} = 1;
if ( $cgi->param('locationnum') == -1 ) {
- my $cust_location = new FS::cust_location {
+ my $cust_location = FS::cust_location->new_or_existing({
'custnum' => $cust_pkg->custnum,
map { $_ => scalar($cgi->param($_)) }
qw( address1 address2 city county state zip country )
- };
+ });
$change{'cust_location'} = $cust_location;
}
diff --git a/httemplate/edit/process/cust_location.cgi b/httemplate/edit/process/cust_location.cgi
index b9f93db8b..56c3968f6 100644
--- a/httemplate/edit/process/cust_location.cgi
+++ b/httemplate/edit/process/cust_location.cgi
@@ -28,11 +28,10 @@ my $cust_location = qsearchs({
});
die "unknown locationnum $locationnum" unless $cust_location;
-my $new = FS::cust_location->new({
+my $new = FS::cust_location->new_or_existing({
custnum => $cust_location->custnum,
prospectnum => $cust_location->prospectnum,
- map { $_ => scalar($cgi->param($_)) }
- qw( address1 address2 city county state zip country )
+ map { $_ => scalar($cgi->param($_)) } FS::cust_main->location_fields
});
my $error = $cust_location->move_to($new);
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 054973f23..c1f815550 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -83,10 +83,7 @@ for my $pre (qw(bill ship)) {
}
$hash{'custnum'} = $cgi->param('custnum');
warn Dumper \%hash if $DEBUG;
- # if we can qsearchs it, then it's unchanged, so use that
- $locations{$pre} = qsearchs('cust_location', \%hash)
- || FS::cust_location->new( \%hash );
-
+ $locations{$pre} = FS::cust_location->new_or_existing(\%hash);
}
if ( ($cgi->param('same') || '') eq 'Y' ) {
diff --git a/httemplate/edit/process/cust_pkg_quantity.html b/httemplate/edit/process/cust_pkg_quantity.html
new file mode 100644
index 000000000..fb2657252
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg_quantity.html
@@ -0,0 +1,33 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). 'edit/cust_pkg_quantity.html?'. $cgi->query_string );
+% } else {
+
+ <& /elements/header-popup.html, "Quantity changed" &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $cust_pkg = qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => scalar($cgi->param('pkgnum')), },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die 'unknown pkgnum' unless $cust_pkg;
+
+$cgi->param('quantity') =~ /^(\d+)$/;
+my $quantity = $1;
+my $error = $cust_pkg->set_quantity($1);
+
+</%init>
diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi
index bcb9c0df1..e0c470675 100644
--- a/httemplate/edit/process/part_export.cgi
+++ b/httemplate/edit/process/part_export.cgi
@@ -56,6 +56,7 @@ my $new = new FS::part_export ( {
if ( $cgi->param('svc_machine') eq 'Y' ) {
$new->machine('_SVC_MACHINE');
$new->part_export_machine_textarea( $cgi->param('part_export_machine') );
+ $new->default_machine_name( $cgi->param('default_machine_name') );
}
my $error;
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
index 2ac57f90b..932e33b1d 100755
--- a/httemplate/edit/process/part_pkg.cgi
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -10,6 +10,7 @@
'precheck_callback' => $precheck_callback,
'args_callback' => $args_callback,
'process_m2m' => \@process_m2m,
+ 'process_o2m' => \@process_o2m,
)
%>
<%init>
@@ -244,4 +245,11 @@ if ( $cgi->param('pkgpart') || ! $conf->exists('agent_defaultpkg') ) {
};
}
+my @process_o2m = (
+ {
+ 'table' => 'part_pkg_msgcat',
+ 'fields' => [qw( locale pkg )],
+ },
+);
+
</%init>
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
index 2dadbccdc..0cc17d36b 100644
--- a/httemplate/edit/process/quick-cust_pkg.cgi
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -70,6 +70,9 @@ my $quantity = $1 || 1;
$cgi->param('refnum') =~ /^(\d*)$/
or die 'illegal refnum '. $cgi->param('refnum');
my $refnum = $1;
+$cgi->param('contactnum') =~ /^(\-?\d*)$/
+ or die 'illegal contactnum '. $cgi->param('contactnum');
+my $contactnum = $1;
$cgi->param('locationnum') =~ /^(\-?\d*)$/
or die 'illegal locationnum '. $cgi->param('locationnum');
my $locationnum = $1;
@@ -109,6 +112,7 @@ my %hash = (
: ''
),
'refnum' => $refnum,
+ 'contactnum' => $contactnum,
'locationnum' => $locationnum,
'discountnum' => $discountnum,
#for the create a new discount case
@@ -142,11 +146,19 @@ if ( $quotationnum ) {
my %opt = ( 'cust_pkg' => $cust_pkg );
+ if ( $contactnum == -1 ) {
+ my $contact = FS::contact->new({
+ 'custnum' => scalar($cgi->param('custnum')),
+ map { $_ => scalar($cgi->param("contactnum_$_")) } qw( first last )
+ });
+ $opt{'contact'} = $contact;
+ }
+
if ( $locationnum == -1 ) {
- my $cust_location = new FS::cust_location {
+ my $cust_location = FS::cust_location->new_or_existing({
map { $_ => scalar($cgi->param($_)) }
- qw( custnum address1 address2 city county state zip country geocode )
- };
+ ('custnum', FS::cust_main->location_fields)
+ });
$opt{'cust_location'} = $cust_location;
}
diff --git a/httemplate/edit/process/svc_phone.html b/httemplate/edit/process/svc_phone.html
index 27e975568..9983ea2cb 100644
--- a/httemplate/edit/process/svc_phone.html
+++ b/httemplate/edit/process/svc_phone.html
@@ -40,10 +40,10 @@ my $args_callback = sub {
my %opt = ();
if ( $cgi->param('locationnum') == -1 ) {
- my $cust_location = new FS::cust_location {
+ my $cust_location = FS::cust_location->new_or_existing({
map { $_ => scalar($cgi->param($_)) }
qw( custnum address1 address2 city county state zip country )
- };
+ });
$opt{'cust_location'} = $cust_location;
}
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
index 0d4b9897b..1b85460e6 100644
--- a/httemplate/edit/svc_broadband.cgi
+++ b/httemplate/edit/svc_broadband.cgi
@@ -104,8 +104,12 @@ my @fields = (
{ field=>'sectornum', type=>'select-tower_sector', },
{ field=>'routernum', type=>'select-router_block_ip' },
{ field=>'mac_addr' , type=>'input-mac_addr' },
- qw( latitude longitude altitude vlan_profile
- performance_profile authkey plan_id )
+ qw(
+ latitude longitude altitude
+ radio_serialnum radio_location poe_location rssi suid
+ ),
+ { field=>'shared_svcnum', type=>'search-svc_broadband', },
+ qw( vlan_profile performance_profile authkey plan_id ),
);
if ( $conf->exists('svc_broadband-radius') ) {
diff --git a/httemplate/elements/auto-table.html b/httemplate/elements/auto-table.html
index 3a3bd405d..5118b91ff 100644
--- a/httemplate/elements/auto-table.html
+++ b/httemplate/elements/auto-table.html
@@ -50,7 +50,7 @@ var <%$pre%>next_rownum;
var <%$pre%>set_rownum;
var <%$pre%>addRow;
var <%$pre%>deleteRow;
-var <%$pre%>fieldorder = <% to_json($fieldorder) %>;
+var <%$pre%>fieldorder = <% encode_json($fieldorder) %>;
function <%$pre%>possiblyAddRow_factory(obj) {
var callback = obj.onchange;
@@ -190,7 +190,7 @@ function <%$pre%>init() {
<%$pre%>template.appendChild(delete_cell);
// preload rows
- var rows = <% to_json(\@rows) %>;
+ var rows = <% encode_json(\@rows) %>;
for (var i = 0; i < rows.length; i++) {
<%$pre%>addRow(rows[i]);
}
diff --git a/httemplate/elements/change_history_common.html b/httemplate/elements/change_history_common.html
index 232664e39..34ce70b6c 100644
--- a/httemplate/elements/change_history_common.html
+++ b/httemplate/elements/change_history_common.html
@@ -15,13 +15,7 @@
<TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
</TR>
-% foreach my $item ( sort { $a->history_date <=> $b->history_date
-% #|| table order
-% || $a->historynum <=> $b->historynum
-% }
-% @history
-% )
-% {
+% foreach my $item ( @history ) {
% my $history_other = '';
% my $act = $item->history_action;
% if ( $act =~ /^replace/ ) {
@@ -196,4 +190,11 @@ $cust_pkg_date_format .= ' %l:%M:%S%P'
if $conf->exists('cust_pkg-display_times')
|| $curuser->option('cust_pkg-display_times');
+@history = sort { $a->history_date <=> $b->history_date
+ || $a->historynum <=> $b->historynum } @history;
+
+if ( $curuser->option('history_order') eq 'newest' ) {
+ @history = reverse @history;
+}
+
</%init>
diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html
index 490ba2303..3d5177612 100644
--- a/httemplate/elements/contact.html
+++ b/httemplate/elements/contact.html
@@ -2,9 +2,9 @@
<INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>">
- <TABLE>
+ <TABLE STYLE="display:inline">
<TR>
-% if ( @contact_class ) {
+% if ( @contact_class && ! $opt{name_only} ) {
<TD>
<SELECT NAME="<%$name%>_classnum" <% $onchange %>>
<OPTION VALUE="">
@@ -106,6 +106,6 @@ foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) {
$label{'comment'} = 'Comment';
-my @fields = keys %label;
+my @fields = $opt{'name_only'} ? qw( first last ) : keys %label;
</%init>
diff --git a/httemplate/elements/dashboard-toplist.html b/httemplate/elements/dashboard-toplist.html
index f4a372519..b80af7883 100644
--- a/httemplate/elements/dashboard-toplist.html
+++ b/httemplate/elements/dashboard-toplist.html
@@ -169,7 +169,6 @@ if ( $FS::TicketSystem::system eq 'RT_Internal'
ObjectCustomFieldValues.ObjectId = cust_tickets.Id
)
GROUP BY cust_tickets.custnum, ObjectCustomFieldValues.Content";
- #warn $sql."\n";
} else { # no custom_priority_field
$sql =
"SELECT cust_tickets.custnum,
@@ -181,10 +180,8 @@ if ( $FS::TicketSystem::system eq 'RT_Internal'
my $sth = dbh->prepare($sql) or die dbh->errstr;
$sth->execute or die $sth->errstr;
while ( my $row = $sth->fetchrow_hashref ) {
- #warn to_json($row)."\n";
$num_tickets_by_priority{ $row->{priority} }->{ $row->{custnum} } =
$row->{num_tickets};
}
}
-#warn Dumper \%num_tickets_by_priority;
</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 00c43bb09..5689b12d2 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -294,9 +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_employees, 'Tie::IxHash';
+$report_employees{'Employee Commission Report'} = [ $fsurl.'search/report_employee_commission.html', '' ]
+ if $curuser->access_right('Employees: Commission Report');
+$report_employees{'Employee Audit Report'} = [ $fsurl.'search/report_employee_audit.html', 'Employee audit report' ]
+ if $curuser->access_right('Employees: Audit Report');
;
tie my %report_bill_event, 'Tie::IxHash',
@@ -397,7 +399,7 @@ $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');
+ if keys %report_employees;
$report_menu{'Billing events'} = [ \%report_bill_event, 'Billing events' ]
if $curuser->access_right('Billing event reports');
$report_menu{'Financial'} = [ \%report_financial, 'Financial reports' ]
@@ -464,6 +466,8 @@ $tools_menu{'Job Queue'} = [ $fsurl.'search/queue.html', 'View pending job queu
if $curuser->access_right('Job queue');
$tools_menu{'Ticketing'} = [ \%tools_ticketing, 'Ticketing tools' ]
if $conf->config('ticket_system');
+$tools_menu{'Customer email settings'} = [ $fsurl.'misc/manage_cust_email.html' ]
+ if $curuser->access_right('Edit customer');
$tools_menu{'Business card scan'} = [ $fsurl.'edit/prospect_main-upload.html' ]
if $curuser->access_right('New prospect');
$tools_menu{'Time Queue'} = [ $fsurl.'search/report_timeworked.html', 'View pending support time' ]
diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html
index 7a282a34c..cef54b824 100644
--- a/httemplate/elements/progress-init.html
+++ b/httemplate/elements/progress-init.html
@@ -108,7 +108,7 @@ function <%$key%>process () {
function <%$key%>myCallback( jobnum ) {
- overlib( OLiframeContent('<%$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, '<% $popup_name %>'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
+ overlib( OLiframeContent('<%$fsurl%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, '<% $popup_name %>'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
}
diff --git a/httemplate/elements/search-svc_broadband.html b/httemplate/elements/search-svc_broadband.html
new file mode 100644
index 000000000..d83516172
--- /dev/null
+++ b/httemplate/elements/search-svc_broadband.html
@@ -0,0 +1,204 @@
+<%doc>
+
+Example:
+
+ include( '/elements/search-svc_broadband.html,
+ 'field' => 'svcnum',
+ #slightly deprecated old synonym for field#'field_name'=>'svcnum',
+ 'find_button' => 1, #add a "find" button to the field
+ 'curr_value' => 54, #current value
+ 'value => 32, #deprecated synonym for curr_value
+ );
+
+</%doc>
+<INPUT TYPE="hidden" NAME="<% $field %>" ID="<% $field %>" VALUE="<% $value %>">
+
+<!-- some false laziness w/ misc/batch-cust_pay.html, though not as bad as i'd thought at first... -->
+
+<INPUT TYPE = "text"
+ NAME = "<% $field %>_search"
+ ID = "<% $field %>_search"
+ SIZE = "32"
+ VALUE="<% $svc_broadband ? $svc_broadband->label : '(svcnum, ip or mac)' %>"
+ onFocus="clearhint_<% $field %>_search(this);"
+ onClick="clearhint_<% $field %>_search(this);"
+ onChange="smart_<% $field %>_search(this);"
+>
+
+% if ( $opt{'find_button'} ) {
+ <INPUT TYPE = "button"
+ VALUE = 'Find',
+ NAME = "<% $field %>_findbutton"
+ onClick = "smart_<% $field %>_search(this.form.<% $field %>_search);"
+ >
+% }
+
+<SELECT NAME="<% $field %>_select" ID="<% $field %>_select" STYLE="color:#ff0000; display:none" onChange="select_<% $field %>(this);">
+</SELECT>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p. 'misc/xmlhttp-svc_broadband-search.cgi',
+ 'subs' => [ 'smart_search' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_<% $field %>_search (what) {
+
+ what.style.color = '#000000';
+
+ if ( what.value == '(svcnum, ip or mac)' )
+ what.value = '';
+
+ if ( what.value.indexOf('Service not found: ') == 0 )
+ what.value = what.value.substr(20);
+
+ }
+
+ var <% $field %>_search_active = false;
+
+ function smart_<% $field %>_search(what) {
+
+ if ( <% $field %>_search_active )
+ return;
+
+ var service = what.value;
+
+ if ( service == 'searching...' || service == ''
+ || service.indexOf('Service not found: ') == 0 )
+ return;
+
+ if ( what.getAttribute('magic') == 'nosearch' ) {
+ what.setAttribute('magic', '');
+ return;
+ }
+
+ //what.value = 'searching...'
+ what.disabled = true;
+ what.style.color= '#000000';
+ what.style.backgroundColor = '#dddddd';
+
+ var service_select = document.getElementById('<% $field %>_select');
+
+ //alert("search for customer " + customer);
+
+ function <% $field %>_search_update(services) {
+
+ //alert('customers returned: ' + customers);
+
+ var serviceArray = eval('(' + services + ')');
+
+ what.disabled = false;
+ what.style.backgroundColor = '#ffffff';
+
+ if ( serviceArray.length == 0 ) {
+
+ what.form.<% $field %>.value = '';
+
+ what.value = 'Service not found: ' + what.value;
+ what.style.color = '#ff0000';
+
+ what.style.display = '';
+ service_select.style.display = 'none';
+
+ } else if ( serviceArray.length == 1 ) {
+
+ //alert('one customer found: ' + customerArray[0]);
+
+ what.form.<% $field %>.value = serviceArray[0][0];
+ what.value = serviceArray[0][1];
+
+ what.style.display = '';
+ service_select.style.display = 'none';
+
+ } else {
+
+ //alert('multiple customers found, have to create select dropdown');
+
+ //blank the current list
+ for ( var i = service_select.length; i >= 0; i-- )
+ service_select.options[i] = null;
+
+ opt(service_select, '', 'Multiple services match "' + service + '" - select one', '#ff0000');
+
+ //add the multiple services
+ for ( var s = 0; s < serviceArray.length; s++ )
+ opt(service_select, serviceArray[s][0], serviceArray[s][1], '#000000');
+
+ opt(service_select, 'cancel', '(Edit search string)', '#000000');
+
+ what.style.display = 'none';
+ service_select.style.display = '';
+
+ }
+
+ <% $field %>_search_active = false;
+
+ }
+
+ <% $field %>_search_active = true;
+
+ smart_search( service, <% $field %>_search_update );
+
+
+ }
+
+ function select_<% $field %> (what) {
+
+ var svcnum = what.options[what.selectedIndex].value;
+ var service = what.options[what.selectedIndex].text;
+
+ var service_obj = document.getElementById('<% $field %>_search');
+
+ if ( svcnum == '' ) {
+ //what.style.color = '#ff0000';
+
+ } else if ( svcnum == 'cancel' ) {
+
+ service_obj.style.color = '#000000';
+
+ what.style.display = 'none';
+ service_obj.style.display = '';
+ service_obj.focus();
+
+ } else {
+
+ what.form.<% $field %>.value = svcnum;
+
+ service_obj.value = service;
+ service_obj.style.color = '#000000';
+
+ what.style.display = 'none';
+ service_obj.style.display = '';
+
+ }
+
+ }
+
+ function opt(what,value,text,color) {
+ var optionName = new Option(text, value, false, false);
+ optionName.style.color = color;
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+</SCRIPT>
+<%init>
+
+my( %opt ) = @_;
+
+my $field = $opt{'field'} || $opt{'field_name'} || 'svcnum';
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+my $svc_broadband = '';
+if ( $value ) {
+ $svc_broadband = qsearchs({
+ 'table' => 'svc_broadband',
+ 'hashref' => { 'svcnum' => $value },
+ #have to join to cust_main for an agentnum 'extra_sql' => " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql,
+ });
+}
+
+</%init>
diff --git a/httemplate/elements/select-tiered.html b/httemplate/elements/select-tiered.html
index e332eeff8..3ff5471ae 100644
--- a/httemplate/elements/select-tiered.html
+++ b/httemplate/elements/select-tiered.html
@@ -124,13 +124,6 @@ my %opt = @_;
my $pre = $opt{prefix} || '';
my $tiers = $opt{tiers} or die "no tiers defined";
-#my $json = JSON->new()->canonical(); #sort
-# something super weird and broken going on with JSON's auto-loading, just
-# using JSON alone errors out with
-# Can't locate object method "new" via package "null" (perhaps you forgot to
-# load "null"?)
-# yes, "null", not "JSON". so instead, using JSON::XS explicity...
-use JSON::XS;
my $json = JSON::XS->new();
$json->canonical;
diff --git a/httemplate/elements/selectlayers.html b/httemplate/elements/selectlayers.html
index 01fd590ca..cb1d2d619 100644
--- a/httemplate/elements/selectlayers.html
+++ b/httemplate/elements/selectlayers.html
@@ -236,7 +236,7 @@ sub layer_callback {
$date_noinit = 1;
}
else {
- $include = "input-$include" if $include =~ /^(text|money)$/;
+ $include = "input-$include" if $include =~ /^(text|money|percentage)$/;
$include = "tr-$include" unless $include eq 'hidden';
$html .= include( "/elements/$include.html",
%$lf,
diff --git a/httemplate/elements/tr-search-svc_broadband.html b/httemplate/elements/tr-search-svc_broadband.html
new file mode 100644
index 000000000..cd7c11500
--- /dev/null
+++ b/httemplate/elements/tr-search-svc_broadband.html
@@ -0,0 +1,15 @@
+<& tr-td-label.html, @_ &>
+
+ <TD <% $colspan %> <% $cell_style %> ID="<% $opt{input_id} || $opt{id}.'_input0' %>"><& search-svc_broadband.html, @_ &></TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-select-contact.html b/httemplate/elements/tr-select-contact.html
new file mode 100644
index 000000000..d6bc67f36
--- /dev/null
+++ b/httemplate/elements/tr-select-contact.html
@@ -0,0 +1,204 @@
+<%doc>
+
+Example:
+
+ include('/elements/tr-select-contact.html',
+ 'cgi' => $cgi,
+
+ 'cust_main' => $cust_main,
+ #or
+ 'prospect_main' => $prospect_main,
+
+ #optional
+ 'empty_label' => '(default contact)',
+ )
+
+</%doc>
+
+<SCRIPT TYPE="text/javascript">
+
+ function contact_disable(what) {
+% for (@contact_fields) {
+ what.form.<%$_%>.disabled = true;
+ var ftype = what.form.<%$_%>.tagName;
+ if( ftype == 'SELECT') changeSelect(what.form.<%$_%>, '');
+ else what.form.<%$_%>.value = '';
+ if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#dddddd';
+% }
+ }
+
+ function contact_clear(what) {
+% for (@contact_fields) {
+ var ftype = what.form.<%$_%>.tagName;
+ if( ftype == 'INPUT' ) what.form.<%$_%>.value = '';
+% }
+ }
+
+ function contact_enable(what) {
+% for (@contact_fields) {
+ what.form.<%$_%>.disabled = false;
+ var ftype = what.form.<%$_%>.tagName;
+ if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff';
+% }
+ }
+
+ function contactnum_changed(what) {
+ var contactnum = what.options[what.selectedIndex].value;
+ if ( contactnum == -1 ) { //Add new contact
+ contact_clear(what);
+
+ contact_enable(what);
+ return;
+ }
+
+% if ( $editable ) {
+ if ( contactnum == 0 ) {
+% }
+
+% #sleep/wait until dropdowns are updated?
+ contact_disable(what);
+
+% if ( $editable ) {
+ } else {
+
+% #sleep/wait until dropdowns are updated?
+ contact_enable(what);
+
+ }
+% }
+
+ }
+
+ function changeSelect(what, value) {
+ for ( var i=0; i<what.length; i++) {
+ if ( what.options[i].value == value ) {
+ what.selectedIndex = i;
+ }
+ }
+ }
+
+</SCRIPT>
+
+<TR>
+ <<%$th%> ALIGN="right" VALIGN="top"><% $opt{'label'} || emt('Service contact') %></<%$th%>>
+ <TD VALIGN="top" COLSPAN=7>
+ <SELECT NAME = "contactnum"
+ ID = "contactnum"
+ STYLE = "vertical-align:top;margin:3px"
+ onchange = "contactnum_changed(this);"
+ >
+% if ( $cust_main ) {
+ <OPTION VALUE=""><% $opt{'empty_label'} || '(customer default)' |h %>
+% }
+%
+% foreach my $contact ( @contact ) {
+ <OPTION VALUE="<% $contact->contactnum %>"
+ <% $contactnum == $contact->contactnum ? 'SELECTED' : '' %>
+ ><% $contact->line |h %>
+% }
+% if ( $addnew ) {
+ <OPTION VALUE="-1"
+ <% $contactnum == -1 ? 'SELECTED' : '' %>
+ >New contact
+% }
+ </SELECT>
+
+<% include('/elements/contact.html',
+ 'object' => $contact,
+ #'onchange' ? probably not
+ 'disabled' => $disabled,
+ 'name_only' => 1,
+ )
+%>
+
+ </TD>
+</TR>
+
+<SCRIPT TYPE="text/javascript">
+ contactnum_changed(document.getElementById('contactnum'));
+</SCRIPT>
+<%init>
+
+#based on / kinda false laziness w/tr-select-cust_contact.html
+
+my $conf = new FS::Conf;
+
+my %opt = @_;
+my $cgi = $opt{'cgi'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $cust_main = $opt{'cust_main'};
+my $prospect_main = $opt{'prospect_main'};
+die "cust_main or prospect_main required" unless $cust_main or $prospect_main;
+
+my $contactnum = '';
+if ( $cgi->param('error') ) {
+ $cgi->param('contactnum') =~ /^(\-?\d*)$/ or die "illegal contactnum";
+ $contactnum = $1;
+} else {
+ if ( length($opt{'curr_value'}) ) {
+ $contactnum = $opt{'curr_value'};
+ } elsif ($prospect_main) {
+ my @cust_contact = $prospect_main->cust_contact;
+ $contactnum = $cust_contact[0]->contactnum if scalar(@cust_contact)==1;
+ } else { #$cust_main
+ $cgi->param('contactnum') =~ /^(\-?\d*)$/ or die "illegal contactnum";
+ $contactnum = $1;
+ }
+}
+
+##probably could use explicit controls
+#my $editable = $cust_main ? 0 : 1; #could use explicit control
+my $editable = 0;
+my $addnew = $cust_main ? 1 : ( $contactnum>0 ? 0 : 1 );
+
+my @contact_fields = map "contactnum_$_", qw( first last );
+
+my $contact; #the one that shows by default in the contact edit space
+if ( $contactnum && $contactnum > 0 ) {
+ $contact = qsearchs('contact', { 'contactnum' => $contactnum } )
+ or die "unknown contactnum";
+} else {
+ $contact = new FS::contact;
+ if ( $contactnum == -1 ) {
+ $contact->$_( $cgi->param($_) ) foreach @contact_fields; #XXX
+ } elsif ( $cust_pkg && $cust_pkg->contactnum ) {
+ my $pkg_contact = $cust_pkg->contact_obj;
+ $contact->$_( $pkg_contact->$_ ) foreach @contact_fields; #XXX why are we making a new one gagain??
+ $opt{'empty_label'} ||= 'package contact: '.$pkg_contact->line;
+ } elsif ( $cust_main ) {
+ $contact = new FS::contact; #I think
+ }
+}
+
+my $contact_sort = sub {
+ lc($a->last) cmp lc($b->last)
+ or lc($a->first) cmp lc($b->first)
+};
+
+my @contact;
+push @contact, $cust_main->cust_contact if $cust_main;
+push @contact, $prospect_main->contact if $prospect_main;
+push @contact, $contact
+ if !$cust_main && $contact && $contact->contactnum > 0
+ && ! grep { $_->contactnum == $contact->contactnum } @contact;
+
+@contact = sort $contact_sort grep !$_->disabled, @contact;
+
+$contact = $contact[0]
+ if ( $prospect_main )
+ && !$opt{'is_optional'}
+ && @contact;
+
+my $disabled =
+ ( $contactnum < 0
+ || ( $editable && $contactnum )
+ || ( $prospect_main
+ && !$opt{'is_optional'} && !@contact && $addnew
+ )
+ )
+ ? ''
+ : 'DISABLED';
+
+my $th = $opt{'no_bold'} ? 'TD' : 'TH';
+
+</%init>
diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html
index 7ffbd6c14..780bf96ad 100644
--- a/httemplate/elements/tr-select-cust_location.html
+++ b/httemplate/elements/tr-select-cust_location.html
@@ -153,25 +153,16 @@ Example:
}
}
+ var location_fields = <% encode_json(\@location_fields) %>;
function update_location( string ) {
- var hash = eval('('+string+')');
- document.getElementById('address1').value = hash['address1'];
- document.getElementById('city').value = hash['city'];
- document.getElementById('zip').value = hash['zip'];
-
-% if ( $opt{'alt_format'} ) {
- changeSelect( document.getElementById('location_kind'), hash['location_kind']);
- changeSelect( document.getElementById('location_type'), hash['location_type']);
- document.getElementById('location_number').value = hash['location_number'];
-% } else {
- document.getElementById('address2').value = hash['address2'];
-% }
-
- var country_el = document.getElementById('country');
-
- changeSelect( country_el, hash['country'] );
-
- country_changed( country_el,
+ var hash = JSON.parse(string);
+ for(var i = 0; i < location_fields.length; i++) {
+ var f = location_fields[i];
+ if (hash[f] && document.getElementById(f)) {
+ document.getElementById(f).value = hash[f];
+ }
+ }
+ country_changed( document.getElementById('country'),
fix_state_factory( hash['state'],
hash['county']
)
@@ -185,7 +176,7 @@ Example:
<TD COLSPAN=7>
<SELECT NAME = "locationnum"
ID = "locationnum"
- onChange = "locationnum_changed(this);"
+ onchange = "locationnum_changed(this);"
>
% if ( $cust_main ) {
<OPTION VALUE="<% $cust_main->ship_locationnum %>"><% $opt{'empty_label'} || '(default service address)' |h %>
@@ -258,9 +249,7 @@ if ( $cgi->param('error') ) {
my $editable = $cust_main ? 0 : 1; #could use explicit control
my $addnew = $cust_main ? 1 : ( $locationnum>0 ? 0 : 1 );
-my @location_fields = qw( address1 address2 city county state zip country
- latitude longitude
- );
+my @location_fields = FS::cust_main->location_fields;
if ( $opt{'alt_format'} ) {
push @location_fields, qw( location_type location_number location_kind );
}
diff --git a/httemplate/elements/tr-select-voip_class.html b/httemplate/elements/tr-select-voip_class.html
index dcc1487cc..afd3e1f8a 100644
--- a/httemplate/elements/tr-select-voip_class.html
+++ b/httemplate/elements/tr-select-voip_class.html
@@ -18,7 +18,8 @@ my @options = (
'' => '',
1 => 'VoIP without Broadband',
2 => 'VoIP with Broadband',
- 3 => 'Wholesale VoIP'
+ 3 => 'Wholesale VoIP',
+ 4 => 'Local Exchange (non-VoIP)',
);
</%init>
diff --git a/httemplate/misc/areacodes.cgi b/httemplate/misc/areacodes.cgi
index 9d32a3baf..4b31deb00 100644
--- a/httemplate/misc/areacodes.cgi
+++ b/httemplate/misc/areacodes.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@areacodes) %>
+<% encode_json(\@areacodes) %>\
<%init>
my( $state, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
index 887b92489..0b2f1f18c 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -23,15 +23,21 @@ function add_row_callback(rownum, prefix) {
function custnum_update_callback(rownum, prefix) {
var custnum = document.getElementById('custnum'+rownum).value;
- document.getElementById('enable_app'+rownum).disabled = (
- custnum == 0 ||
- num_open_invoices[rownum] < 2
- );
+ // if there is a custnum and more than one open invoice, enable
+ // (and check) the box
+ var show_applications = (custnum > 0 && num_open_invoices[rownum] > 1);
+ var enable_app_checkbox = document.getElementById('enable_app'+rownum);
+ enable_app_checkbox.disabled = show_applications;
+
% if ( $use_discounts ) {
select_discount_term(rownum, prefix);
% }
}
+function invnum_update_callback(rownum, prefix) {
+ custnum_update_callback(rownum, prefix);
+}
+
function select_discount_term(row, prefix) {
var custnum_obj = document.getElementById('custnum'+prefix+row);
var select_obj = document.getElementById('discount_term'+prefix+row);
@@ -89,6 +95,17 @@ function toggle_application_row(ev, next) {
next.call(this, rownum);
}
);
+ } else {
+ var row = document.getElementById('row'+rownum);
+ var table_rows = row.parentNode.rows;
+ for (i = row.sectionRowIndex; i < table_rows.count; i++) {
+ if ( table_rows[i].id.indexof('row'+rownum+'.') > -1 ) {
+ table_rows.removeChild(table_rows[i]);
+ } else {
+ break;
+ }
+ }
+ lock_payment_row(rownum, false);
}
}
@@ -198,7 +215,6 @@ function change_app_amount() {
&& amount_unapplied(rownum) > 0 ) {
create_application_row(rownum, parseInt(appnum) + 1);
-
}
}
@@ -352,6 +368,7 @@ function preload() {
footer_align => \@footer_align,
onchange => \@onchange,
custnum_update_callback => 'custnum_update_callback',
+ invnum_update_callback => 'invnum_update_callback',
add_row_callback => 'add_row_callback',
&>
diff --git a/httemplate/misc/change_pkg_contact.html b/httemplate/misc/change_pkg_contact.html
new file mode 100755
index 000000000..d9da5beec
--- /dev/null
+++ b/httemplate/misc/change_pkg_contact.html
@@ -0,0 +1,70 @@
+<& /elements/header-popup.html, mt("Change Package Contact") &>
+
+<& /elements/error.html &>
+
+<FORM ACTION="<% $p %>misc/process/change_pkg_contact.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right"><% mt('Package') |h %></TH>
+ <TD COLSPAN=7>
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+% if ( $cust_pkg->contactnum ) {
+ <TR>
+ <TH ALIGN="right"><% mt('Current Contact') %></TH>
+ <TD COLSPAN=7>
+ <% $cust_pkg->contact_obj->line |h %>
+ </TD>
+ </TR>
+% }
+
+<& /elements/tr-select-contact.html,
+ 'label' => mt('New Contact'), #XXX test
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_pkg->cust_main,
+&>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE = "submit"
+ VALUE = "<% $cust_pkg->contactnum ? mt("Change contact") : mt("Add contact") |h %>"
+>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg =
+ qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die "unknown pkgnum $pkgnum";
+
+my $cust_main = $cust_pkg->cust_main
+ or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+ " ( pkgnum ". cust_pkg->pkgnum. ")";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/choose_tax_location.html b/httemplate/misc/choose_tax_location.html
index 6ef7623b3..23099c421 100644
--- a/httemplate/misc/choose_tax_location.html
+++ b/httemplate/misc/choose_tax_location.html
@@ -11,7 +11,7 @@
% map { $value{$_} = $location{$_} } qw ( city state )
% if $location{country} eq 'CA';
%
-% my $value = encode_entities(objToJson({ %value })
+% my $value = encode_entities(encode_json({ %value })
% );
% my $content = '';
% $content .= $location->$_. '&nbsp;' x ( $max{$_} - length($location->$_) )
diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi
index a277ba407..43b92297e 100644
--- a/httemplate/misc/cust-part_pkg.cgi
+++ b/httemplate/misc/cust-part_pkg.cgi
@@ -1,4 +1,4 @@
-<% objToJson( \@return ) %>
+<% encode_json( \@return ) %>\
<%init>
my( $custnum, $prospectnum, $classnum ) = $cgi->param('arg');
diff --git a/httemplate/misc/exchanges.cgi b/httemplate/misc/exchanges.cgi
index 8a67f7bab..0de4ace25 100644
--- a/httemplate/misc/exchanges.cgi
+++ b/httemplate/misc/exchanges.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@exchanges) %>
+<% encode_json(\@exchanges) %>\
<%init>
my( $areacode, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/location.cgi b/httemplate/misc/location.cgi
index 188c5c3df..fab61dd01 100644
--- a/httemplate/misc/location.cgi
+++ b/httemplate/misc/location.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\%hash) %>
+<% encode_json(\%hash) %>\
<%init>
my $locationnum = $cgi->param('arg');
@@ -24,8 +24,9 @@ my $cust_location = qsearchs({
my %hash = ();
%hash = map { $_ => $cust_location->$_() }
- qw( address1 address2 city county state zip country
- location_kind location_type location_number )
+ ( FS::cust_main->location_fields,
+ qw( location_kind location_type location_number )
+ )
if $cust_location;
</%init>
diff --git a/httemplate/misc/macinventory.cgi b/httemplate/misc/macinventory.cgi
index 7ed5c6607..cec0e3121 100644
--- a/httemplate/misc/macinventory.cgi
+++ b/httemplate/misc/macinventory.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@macs) %>
+<% encode_json(\@macs) %>\
<%init>
# XXX: this should be agent-virtualized / limited
diff --git a/httemplate/misc/maestro-customer_status.html b/httemplate/misc/maestro-customer_status.html
index 8acae2b2a..a872d4997 100644
--- a/httemplate/misc/maestro-customer_status.html
+++ b/httemplate/misc/maestro-customer_status.html
@@ -1,4 +1,4 @@
-<% objToJson( $return ) %>
+<% encode_json( $return ) %>\
<%init>
my $return;
diff --git a/httemplate/misc/manage_cust_email.html b/httemplate/misc/manage_cust_email.html
new file mode 100644
index 000000000..3ece459bb
--- /dev/null
+++ b/httemplate/misc/manage_cust_email.html
@@ -0,0 +1,106 @@
+<& /elements/header.html, 'Manage customer email settings' &>
+<STYLE TYPE="text/css">
+.hidden { display: none }
+</STYLE>
+<& /elements/xmlhttp.html,
+ url => $p.'misc/xmlhttp-cust_main-email_search.html',
+ subs => ['email_search']
+&>
+<SCRIPT TYPE="text/javascript">
+
+function receive_search(result) {
+ var recs = JSON.parse(result);
+ var tbody = document.getElementById('tbody_results');
+ var j = tbody.rows.length;
+ for(var i = 0; i < j; i++) {
+ tbody.deleteRow(tbody.rows[i]);
+ }
+ if (recs.length > 0) {
+ for(var i = 0; i < recs.length; i++) {
+ var rec = recs[i];
+ var row = tbody.insertRow(i);
+ row.style.backgroundColor = (i % 2 ? '#eeeeee' : '#ffffff');
+
+ var cell = row.insertCell(0); // custnum
+ cell.appendChild( document.createTextNode(rec[0]) );
+ cell = row.insertCell(1); // customer name
+ cell.appendChild( document.createTextNode(rec[1]) );
+ cell = row.insertCell(2); // email
+ cell.appendChild( document.createTextNode(rec[2]) );
+
+ cell = row.insertCell(3); // invoice_email
+ var input = document.createElement('INPUT');
+ input.type = 'hidden';
+ input.name = 'custnum';
+ input.value = rec[0];
+ cell.appendChild(input);
+
+ input = document.createElement('INPUT');
+ input.type = 'checkbox';
+ input.name = 'custnum' + rec[0] + '_invoice_email';
+ input.value = 'Y';
+ input.checked = (rec[3] != 'Y');
+ cell.appendChild(input);
+ cell.style.textAlign = 'center';
+
+ cell = row.insertCell(4); // message_email
+ input = document.createElement('INPUT');
+ input.type = 'checkbox';
+ input.name = 'custnum' + rec[0] + '_message_email';
+ input.value = 'Y';
+ input.checked = (rec[4] != 'Y');
+ cell.appendChild(input);
+ cell.style.textAlign = 'center';
+ }
+ document.getElementById('div_found').style.display = '';
+ } else {
+ document.getElementById('div_notfound').style.display = '';
+ }
+}
+
+function start_search() {
+ document.getElementById('div_found').style.display = 'none';
+ document.getElementById('div_notfound').style.display = 'none';
+ var email = document.getElementById('input_email').value;
+ email_search(email, receive_search);
+}
+% if ( $cgi->param('search') ) {
+window.onload = start_search;
+% }
+</SCRIPT>
+<FORM ACTION="<%$p%>misc/process/manage_cust_email.html" METHOD="POST">
+<DIV>
+% if ( $cgi->param('done') ) {
+<P STYLE="font-weight: bold; color: #00ff00">Changes saved.</P>
+% } elsif ( $cgi->param('error') ) {
+<P STYLE="font-weight: bold; color: #ff0000"><% $cgi->param('error') |h %></P>
+% }
+ Email address:
+ <INPUT TYPE="text" ID="input_email" NAME="search"\
+ VALUE="<% $cgi->param('search') |h %>">
+ <INPUT TYPE="button" onclick="start_search()" VALUE="find">
+</DIV>
+<DIV ID="div_notfound" STYLE="display: none; padding: 1em">
+No matching email addresses found.
+</DIV>
+<DIV ID="div_found" STYLE="display: none">
+<TABLE CLASS="grid" STYLE="border-spacing: 0px">
+ <THEAD>
+ <TR STYLE="background-color: #dddddd">
+ <TH>#</TH>
+ <TH>Customer</TH>
+ <TH>Email</TH>
+ <TH>Send invoices</TH>
+ <TH>Send other notices</TH>
+ </TR>
+ </THEAD>
+ <TBODY ID="tbody_results"></TBODY>
+</TABLE>
+<INPUT TYPE="submit" VALUE="Save changes">
+</FORM>
+<& /elements/footer.html &>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+</%init>
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index 993ea366c..e09ba986d 100644
--- a/httemplate/misc/order_pkg.html
+++ b/httemplate/misc/order_pkg.html
@@ -93,6 +93,12 @@
&>
% }
+<& /elements/tr-select-contact.html,
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_main,
+ 'prospect_main' => $prospect_main,
+&>
+
% if ( $cgi->param('lock_locationnum') ) {
<INPUT TYPE = "hidden"
diff --git a/httemplate/misc/part_export/huawei_hlr-import_sim.html b/httemplate/misc/part_export/huawei_hlr-import_sim.html
new file mode 100644
index 000000000..9b87b3d2a
--- /dev/null
+++ b/httemplate/misc/part_export/huawei_hlr-import_sim.html
@@ -0,0 +1,52 @@
+<& /elements/header-popup.html, 'Import SIMs' &>
+Import a file containing SIM card properties.<BR>
+Each row should contain the following fields, separated by spaces:<BR>
+IMSI, ICCID, PIN1, PUK1, PIN2, PUK2, ACC, Ki<BR>
+<BR>
+<& /elements/form-file_upload.html,
+ 'name' => 'ImportForm',
+ 'action' => 'process/huawei_hlr-import_sim.html',
+ 'num_files' => 1,
+ 'fields' => [ 'exportnum', 'classnum', 'agentnum', ],
+ 'message' => 'Inventory import successful',
+ 'onsubmit' => "document.ImportForm.submitButton.disabled=true;",
+&>
+<TABLE CLASS="inv" WIDTH="100%">
+ <INPUT TYPE="hidden" NAME="exportnum" VALUE="<%$exportnum%>">
+ <& /elements/file-upload.html,
+ 'field' => 'file',
+ 'label' => 'Filename',
+ &>
+ <& /elements/tr-select-agent.html,
+ 'disable_empty' => 1,
+ &>
+ <& /elements/tr-select-table.html,
+ 'table' => 'inventory_class',
+ 'name_col' => 'classname',
+ 'label' => 'Inventory class',
+ 'disable_empty' => 1,
+ &>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ NAME = "submitButton"
+ ID = "submitButton"
+ VALUE = "Import file"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my ($exportnum) = $cgi->keywords;
+$exportnum =~ /^\d+$/ or die "bad exportnum '$exportnum'";
+my $part_export = FS::part_export->by_key($exportnum)
+ or die "export $exportnum not found";
+</%init>
diff --git a/httemplate/misc/part_export/process/huawei_hlr-import_sim.html b/httemplate/misc/part_export/process/huawei_hlr-import_sim.html
new file mode 100644
index 000000000..d46700d5f
--- /dev/null
+++ b/httemplate/misc/part_export/process/huawei_hlr-import_sim.html
@@ -0,0 +1,10 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC
+ 'FS::part_export::huawei_hlr::process_import_sim', $cgi;
+
+</%init>
diff --git a/httemplate/misc/part_svc-columns.cgi b/httemplate/misc/part_svc-columns.cgi
index 060256154..a86164d06 100644
--- a/httemplate/misc/part_svc-columns.cgi
+++ b/httemplate/misc/part_svc-columns.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@output) %>
+<% encode_json(\@output) %>\
<%init>
my $conf = new FS::Conf;
diff --git a/httemplate/misc/phonenums.cgi b/httemplate/misc/phonenums.cgi
index 5084628eb..a048280bb 100644
--- a/httemplate/misc/phonenums.cgi
+++ b/httemplate/misc/phonenums.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@phonenums) %>
+<% encode_json(\@phonenums) %>\
<%init>
my( $exchangestring, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/process/change_pkg_contact.html b/httemplate/misc/process/change_pkg_contact.html
new file mode 100644
index 000000000..2795c1197
--- /dev/null
+++ b/httemplate/misc/process/change_pkg_contact.html
@@ -0,0 +1,49 @@
+<% header(emt("Package contact $past_method")) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Change customer package');
+
+#untaint pkgnum
+my $pkgnum = $cgi->param('pkgnum');
+$pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); #needs agent virt
+
+my $contactnum = $cgi->param('contactnum');
+$contactnum =~ /^(-?\d*)$/ or die "Illegal contactnum";
+$contactnum = $1;
+
+my $past_method = $cust_pkg->contactnum ? 'changed' : 'added';
+
+my $error = '';
+
+if ( $contactnum == -1 ) {
+
+ #little false laziness w/edit/process/quick-cust_pkg.cgi, also the whole
+ # thing should be a single transaction
+ my $contact = new FS::contact {
+ 'custnum' => $cust_pkg->custnum,
+ map { $_ => scalar($cgi->param("contactnum_$_")) } qw( first last )
+ };
+ $error = $contact->insert;
+ $cust_pkg->contactnum( $contact->contactnum );
+
+} else {
+ $cust_pkg->contactnum($contactnum);
+}
+
+$error ||= $cust_pkg->replace;
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "change_pkg_contact.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/process/manage_cust_email.html b/httemplate/misc/process/manage_cust_email.html
new file mode 100644
index 000000000..5bf1470d1
--- /dev/null
+++ b/httemplate/misc/process/manage_cust_email.html
@@ -0,0 +1,32 @@
+<% $cgi->redirect($fsurl.'misc/manage_cust_email.html?' .
+ $cgi->query_string) %>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+my $error;
+foreach my $custnum ($cgi->param('custnum')) {
+ my $cust = FS::cust_main->by_key($custnum)
+ or die "customer not found: $custnum\n";
+ my $new_invoice_noemail =
+ $cgi->param('custnum'.$custnum.'_invoice_email') ? '' : 'Y';
+ my $new_message_noemail =
+ $cgi->param('custnum'.$custnum.'_message_email') ? '' : 'Y';
+ if ( $new_invoice_noemail ne $cust->invoice_noemail
+ or $new_message_noemail ne $cust->message_noemail ) {
+
+ $cust->set('invoice_noemail', $new_invoice_noemail);
+ $cust->set('message_noemail', $new_message_noemail);
+ $error ||= $cust->replace;
+
+ }
+ $cgi->delete('custnum'.$custnum.'_invoice_email');
+ $cgi->delete('custnum'.$custnum.'_message_email');
+}
+$cgi->delete('custnum');
+if ( $error ) {
+ $cgi->param('error' => $error); # probably unnecessary...
+} else {
+ $cgi->param('done' => 1) unless $error;
+}
+</%init>
diff --git a/httemplate/misc/regions.cgi b/httemplate/misc/regions.cgi
index 2450ea31a..31538b08e 100644
--- a/httemplate/misc/regions.cgi
+++ b/httemplate/misc/regions.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@regions) %>
+<% encode_json(\@regions) %>\
<%init>
my( $state, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/xmlhttp-address_standardize.html b/httemplate/misc/xmlhttp-address_standardize.html
index 15f266ab0..618265364 100644
--- a/httemplate/misc/xmlhttp-address_standardize.html
+++ b/httemplate/misc/xmlhttp-address_standardize.html
@@ -1,4 +1,4 @@
-<% encode_json($return) %>
+<% encode_json($return) %>\
<%init>
local $SIG{__DIE__}; #disable Mason error trap
diff --git a/httemplate/misc/xmlhttp-calculate_taxes.html b/httemplate/misc/xmlhttp-calculate_taxes.html
index d3dc36acf..ed7bd0173 100644
--- a/httemplate/misc/xmlhttp-calculate_taxes.html
+++ b/httemplate/misc/xmlhttp-calculate_taxes.html
@@ -1,4 +1,4 @@
-<% objToJson($return) %>
+<% encode_json($return) %>\
<%init>
my $DEBUG = 0;
diff --git a/httemplate/misc/xmlhttp-cust_bill-search.html b/httemplate/misc/xmlhttp-cust_bill-search.html
index 46f15d1ab..c60a0b05c 100644
--- a/httemplate/misc/xmlhttp-cust_bill-search.html
+++ b/httemplate/misc/xmlhttp-cust_bill-search.html
@@ -1,4 +1,4 @@
-<% encode_json(\@return) %>
+<% encode_json(\@return) %>\
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -6,13 +6,15 @@ die 'access denied' unless $curuser->access_right('View invoices');
my @return;
if ( $cgi->param('sub') eq 'custnum_search_open' ) {
my $custnum = $cgi->param('arg');
- #warn "searching invoices for $custnum\n";
- my $cust_main = FS::cust_main->by_key($custnum);
- @return = map {
- +{ $_->hash,
- 'owed' => $_->owed }
- } $cust_main->open_cust_bill
- if $curuser->agentnums_href->{ $cust_main->agentnum };
+ if ( $custnum =~ /^(\d+)$/ ) {
+#warn "searching invoices for $custnum\n";
+ my $cust_main = FS::cust_main->by_key($custnum);
+ @return = map {
+ +{ $_->hash,
+ 'owed' => $_->owed }
+ } $cust_main->open_cust_bill
+ if $curuser->agentnums_href->{ $cust_main->agentnum };
+ }
}
</%init>
diff --git a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
index f618d55ba..c0db3e2c4 100644
--- a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
+++ b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
@@ -1,4 +1,4 @@
-<% to_json($return) %>
+<% encode_json($return) %>\
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
diff --git a/httemplate/misc/xmlhttp-cust_main-censustract.html b/httemplate/misc/xmlhttp-cust_main-censustract.html
index 4b00898da..4c708a4c4 100644
--- a/httemplate/misc/xmlhttp-cust_main-censustract.html
+++ b/httemplate/misc/xmlhttp-cust_main-censustract.html
@@ -1,4 +1,4 @@
-<% objToJson($return) %>
+<% encode_json($return) %>\
<%init>
my %arg = $cgi->param('arg');
diff --git a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
index b524e69fc..36b18b455 100644
--- a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
+++ b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
@@ -16,7 +16,7 @@
% }
% }
%
-<% objToJson($return) %>
+<% encode_json($return) %>\
% }
<%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-email_search.html b/httemplate/misc/xmlhttp-cust_main-email_search.html
new file mode 100644
index 000000000..0d830826c
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_main-email_search.html
@@ -0,0 +1,29 @@
+<% encode_json(\@result) %>\
+<%init>
+die 'access denied'
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+my $sub = $cgi->param('sub');
+my $email = $cgi->param('arg');
+my @where = (
+ "cust_main_invoice.dest != 'POST'",
+ "cust_main_invoice.dest LIKE ".dbh->quote('%'.$email.'%'),
+ $FS::CurrentUser::CurrentUser->agentnums_sql(table => 'cust_main'),
+);
+my @cust_main = qsearch({
+ 'table' => 'cust_main',
+ 'select' => 'cust_main.*, cust_main_invoice.dest',
+ 'addl_from' => 'JOIN cust_main_invoice USING (custnum)',
+ 'extra_sql' => 'WHERE '.join(' AND ', @where),
+});
+
+my @result = map {
+ [ $_->custnum,
+ $_->name,
+ $_->dest,
+ $_->invoice_noemail,
+ $_->message_noemail,
+ ]
+} @cust_main;
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi
index acf7e70e2..73c9ff8ec 100644
--- a/httemplate/misc/xmlhttp-cust_main-search.cgi
+++ b/httemplate/misc/xmlhttp-cust_main-search.cgi
@@ -5,7 +5,7 @@
% # cust_main-agent_custid-format') eq 'ww?d+'
% $return = findbycustnum_or_agent_custid($1);
% }
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } elsif ( $sub eq 'smart_search' ) {
%
% my $string = $cgi->param('arg');
@@ -22,14 +22,14 @@
% @cust_main
% ];
%
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } elsif ( $sub eq 'invnum_search' ) {
%
% my $string = $cgi->param('arg');
% if ( $string =~ /^(\d+)$/ ) {
% my $inv = qsearchs('cust_bill', { 'invnum' => $1 });
% my $return = $inv ? findbycustnum($inv->custnum) : [];
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } else { #return nothing
[]
% }
@@ -47,7 +47,7 @@
% city => $_->city,
% };
% }
-<% objToJson($return) %>
+<% encode_json($return) %>\
% }
<%init>
diff --git a/httemplate/misc/xmlhttp-ping.html b/httemplate/misc/xmlhttp-ping.html
index e99303207..01baa3f57 100644
--- a/httemplate/misc/xmlhttp-ping.html
+++ b/httemplate/misc/xmlhttp-ping.html
@@ -1,4 +1,4 @@
-<% objToJson($return) %>
+<% encode_json($return) %>\
<%init>
my $conf = new FS::Conf;
diff --git a/httemplate/misc/xmlhttp-svc_broadband-search.cgi b/httemplate/misc/xmlhttp-svc_broadband-search.cgi
new file mode 100644
index 000000000..578e6140e
--- /dev/null
+++ b/httemplate/misc/xmlhttp-svc_broadband-search.cgi
@@ -0,0 +1,22 @@
+% if ( $sub eq 'smart_search' ) {
+%
+% my $string = $cgi->param('arg');
+% my @svc_broadband = FS::svc_broadband->smart_search( $string );
+% my $return = [ map { my $cust_pkg = $_->cust_svc->cust_pkg;
+% [ $_->svcnum,
+% $_->label. ( $cust_pkg
+% ? ' ('. $cust_pkg->cust_main->name. ')'
+% : ''
+% ),
+% ];
+% }
+% @svc_broadband,
+% ];
+%
+<% encode_json($return) %>\
+% }
+<%init>
+
+my $sub = $cgi->param('sub');
+
+</%init>
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index 84f0832bf..6b94f7175 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -49,6 +49,7 @@ unless ( $error ) { # if ($access_user) {
#XXX autogen
my @paramlist = qw( locale menu_position default_customer_view
+ history_order
spreadsheet_format mobile_menu
enable_fuzzy_on_exact
disable_html_editor disable_enter_submit_onetimecharge
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index 9537fed34..5babb0181 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -75,6 +75,21 @@ Interface
</SELECT>
</TD>
</TR>
+
+% my $history_order = $curuser->option('history_order') || 'oldest';
+ <TR>
+ <TH ALIGN="right">Customer history sort order: </TH>
+ <TD COLSPAN=2>
+ <& /elements/select.html,
+ field => 'history_order',
+ curr_value => $history_order,
+ options => [ 'oldest', 'newest' ],
+ labels => { 'oldest' => 'Oldest first',
+ 'newest' => 'Newest first',
+ },
+ &>
+ </TD>
+ </TR>
<TR>
<TH ALIGN="right">Spreadsheet download format: </TH>
@@ -92,7 +107,7 @@ Interface
</TR>
<TR>
- <TH ALIGN="right" COLSPAN=1>Enable approximate customer searching even when an exact match is found: </TH>
+ <TH ALIGN="right" COLSPAN=1>Enable approximate customer searching <BR>even when an exact match is found: </TH>
<TD ALIGN="left" COLSPAN=2>
<INPUT TYPE="checkbox" NAME="enable_fuzzy_on_exact" VALUE="1" <% $curuser->option('enable_fuzzy_on_exact') ? 'CHECKED' : '' %>>
</TD>
diff --git a/httemplate/search/477.html b/httemplate/search/477.html
index 04764c1da..eed3df946 100755
--- a/httemplate/search/477.html
+++ b/httemplate/search/477.html
@@ -3,6 +3,14 @@
<Form_477_submission xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://specialreports.fcc.gov/wcb/Form477/XMLSchema-instance/form_477_upload_Schema.xsd" >
% } else { #html
<& /elements/header.html, "FCC Form 477 Results - $state" &>
+%# XXX when we stop supporting IE8, add this to freeside.css using :nth-child
+%# selectors, and remove it from everywhere else
+<STYLE TYPE="text/css">
+.grid TH { background-color: #cccccc; padding: 0px 3px 2px; text-align: right }
+.row0 TD { background-color: #eeeeee; padding: 0px 3px 2px; text-align: right }
+.row1 TD { background-color: #ffffff; padding: 0px 3px 2px; text-align: right }
+</STYLE>
+
<TABLE WIDTH="100%">
<TR>
<TD></TD>
@@ -38,8 +46,11 @@
% if ( $type eq 'xml' ) {
<<% 'Part_IA_'. chr(65 + $tech) %>>
% }
-<& "477part${part}_summary.html", 'tech_code' => $tech, 'url' => $url &>
-<& "477part${part}_detail.html", 'tech_code' => $tech, 'url' => $url &>
+<& "477part${part}.html",
+ 'tech_code' => $tech,
+ 'url' => $url,
+ 'type' => $type
+&>
% if ( $type eq 'xml' ) {
</<% 'Part_IA_'. chr(65 + $tech) %>>
% }
diff --git a/httemplate/search/477partIA.html b/httemplate/search/477partIA.html
new file mode 100755
index 000000000..1cd0b70e0
--- /dev/null
+++ b/httemplate/search/477partIA.html
@@ -0,0 +1,165 @@
+% if ( $opt{'type'} eq 'xml' ) {
+%# container element <Part_IA_$tech> is in 477.html
+% my $col = 'a';
+% foreach ( @summary_row ) {
+% my $el = $xml_prefix . $col . '1'; # PartIA_Aa1, PartIA_Ab1, etc.
+ <<% $el %>><% $_ %><<% "/$el" %>>
+% $col++;
+% }
+% foreach my $col_data ( @data ) {
+% my $row = 1;
+% foreach my $cell ( @$col_data ) {
+% my $el = $xml_prefix . $col . $row; # PartIA_Af1, PartIA_Af2...
+ <<% $el %>><% $cell->[0] %><<% "/$el" %>>
+% if ( $percentages ) {
+% $el = $xml_percent . $col . $row; # Part_p_IA_Af1, ...
+ <<% $el %>><% $cell->[1] %><<% "/$el" %>>
+% }
+% $row++;
+% } # foreach $cell
+% $col++;
+% } # foreach $col_data
+% } else { # not XML
+
+<H2><% $title %> totals</H2>
+<& /elements/table-grid.html &>
+ <TR>
+% foreach ( 'Total Connections',
+% '% owned loop',
+% '% billed to end users',
+% '% residential',
+% '% residential > 200 kbps') {
+ <TH WIDTH="20%"><% $_ |h %></TH>
+% }
+ </TR>
+ <TR CLASS="row0">
+% foreach ( @summary_row ) {
+ <TD><% $_ %></TD>
+% }
+ </TR>
+</TABLE>
+<H2><% $title %> breakdown by speed</H2>
+<TABLE CLASS="grid" CELLSPACING=0>
+ <TR>
+ <TH WIDTH="12%"></TH>
+% for (my $col = 0; $col < scalar(@download_option); $col++) {
+ <TH WIDTH="11%">
+ <% $FS::Report::FCC_477::download[$col] |h %>
+ </TH>
+% }
+ </TR>
+% for (my $row = 0; $row < scalar(@upload_option); $row++) {
+ <TR CLASS="row<% $row % 2%>">
+ <TD STYLE="text-align: left; font-weight: bold">
+% if ( $asymmetric ) {
+ <% $FS::Report::FCC_477::upload[$row] |h %>
+% }
+ </TD>
+% for (my $col = 0; $col < scalar(@download_option); $col++) {
+ <TD>
+ <% $data[$col][$row][0] %>
+% if ( $percentages ) {
+ <BR><% $data[$col][$row][1] %>
+% }
+ </TD>
+% } # for $col
+ </TR>
+% } # for $row
+</TABLE>
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my %opt = @_;
+my %search_hash;
+
+for ( qw(agentnum state) ) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+$search_hash{'status'} = 'active';
+$search_hash{'country'} = 'US';
+$search_hash{'classnum'} = [ $cgi->param('classnum') ];
+
+# arrays of report_option_ numbers, running parallel to
+# the download and upload speed arrays
+my @download_option = $cgi->param('part1_column_option');
+my @upload_option = $cgi->param('part1_row_option');
+
+my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi);
+
+my $total_count = 0;
+my $total_residential = 0;
+my $above_200 = 0;
+my $tech_code = $opt{tech_code};
+my $technology = $FS::Report::FCC_477::technology[$tech_code] || 'unknown';
+my $title = "Part IA $technology";
+my $xml_prefix = 'PartIA_'. chr(65 + $tech_code);
+my $xml_percent = 'Part_p_IA_'. chr(65 + $tech_code); # yes, seriously
+
+# whether to show the results as a matrix (upload speeds in rows) or a single
+# row
+my $asymmetric = 1;
+if ( $technology eq 'Symmetric xDSL' or $technology eq 'Other Wireline' ) {
+ $asymmetric = 0;
+ @upload_option = ( undef );
+}
+# whether to show residential percentages in each cell of the matrix
+my $percentages = ($technology eq 'Terrestrial Mobile Wireless');
+
+my $query = FS::cust_pkg->search(\%search_hash);
+my $count_query = $query->{'count_query'};
+
+my $is_residential = " AND COALESCE(cust_main.company, '') = ''";
+my $has_option = sub {
+ my $optionnum = shift;
+ $optionnum =~ /^\d+$/ ?
+ " AND EXISTS(
+ SELECT 1 FROM part_pkg_option
+ WHERE part_pkg_option.pkgpart = part_pkg.pkgpart
+ AND optionname = 'report_option_$optionnum'
+ AND optionvalue = '1'
+ )" : '';
+};
+
+# limit to those that have technology option $tech_code
+$count_query .= $has_option->($technology_option[$tech_code]);
+
+my @data;
+for ( my $row = 0; $row < scalar @upload_option; $row++ ) {
+ for ( my $col = 0; $col < scalar @download_option; $col++ ) {
+
+ my $this_count_query = $count_query .
+ $has_option->($upload_option[$row]) .
+ $has_option->($download_option[$col]);
+
+ my $count = FS::Record->scalar_sql($this_count_query);
+ my $residential = FS::Record->scalar_sql($this_count_query . $is_residential);
+
+ my $percent = sprintf('%.2f', $count ? 100 * $residential / $count : 0);
+ $data[$col][$row] = [ $count, $percent ];
+
+ $total_count += $count;
+ $total_residential += $residential;
+ $above_200 += $residential if $row > 0 or !$asymmetric;
+ }
+}
+
+my $total_percentage =
+ sprintf("%.2f", $total_count ? 100*$total_residential/$total_count : 0);
+
+my $above_200_percentage =
+ sprintf("%.2f", $total_count ? 100*$above_200/$total_count : 0);
+
+my @summary_row = (
+ $total_count,
+ 100.00, # own local loop--consistent with previous practice, but probably wrong
+ 100.00, # billed to end user--also wrong
+ $total_percentage, # residential percentage
+ $above_200_percentage,
+);
+
+</%init>
diff --git a/httemplate/search/477partIA_detail.html b/httemplate/search/477partIA_detail.html
deleted file mode 100755
index 666032d0c..000000000
--- a/httemplate/search/477partIA_detail.html
+++ /dev/null
@@ -1,129 +0,0 @@
-<& elements/search.html,
- 'html_init' => $html_init,
- 'name' => 'lines',
- 'query' => $query,
- 'count_query' => $count_query,
- 'really_disable_download' => 1,
- 'disable_download' => 1,
- 'nohtmlheader' => 1,
- 'disable_total' => 1,
- 'header' => [ '', @column_option_name ],
- 'xml_elements' => [ @xml_elements ],
- 'xml_omit_empty' => 1,
- 'fields' => [ @fields ],
-
-&>
-<%init>
-
-my $curuser = $FS::CurrentUser::CurrentUser;
-
-die "access denied"
- unless $curuser->access_right('List packages');
-
-my %opt = @_;
-my %search_hash = ();
-
-for ( qw(agentnum magic state) ) {
- $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
-}
-$search_hash{'country'} = 'US';
-
-$search_hash{'classnum'} = [ $cgi->param('classnum') ];
-
-my @column_option = grep { /^\d+/ } $cgi->param('part1_column_option')
- if $cgi->param('part1_column_option');
-
-my @row_option = grep { /^\d+/ } $cgi->param('part1_row_option')
- if $cgi->param('part1_row_option');
-
-my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi);
-
-my @column_option_name = scalar(@column_option)
- ? ( map { my $part_pkg_report_option =
- qsearchs({ 'table' => 'part_pkg_report_option',
- 'hashref' => { num => $_ },
- });
- $part_pkg_report_option ? $part_pkg_report_option->name
- : 'no such report option';
- } @column_option
- )
- : ( 'all packages' );
-
-my $where = join(' OR ', map { "num = $_" } @row_option );
-my %row_option_name = $where ?
- ( map { $_->num => $_->name }
- qsearch({ 'table' => 'part_pkg_report_option',
- 'hashref' => {},
- 'extra_sql' => "WHERE $where",
- })
- ) :
- ();
-
-my $tech_code = $opt{tech_code};
-my $technology = $FS::Report::FCC_477::technology[$tech_code] || 'unknown';
-my $html_init = "<H2>Part IA $technology breakdown by speeds</H2>";
-my $xml_prefix = 'PartIA_'. chr(65 + $tech_code);
-
-if ($cgi->param('_type') eq 'xml') {
- #rotate data pi/2
- my @temp = @column_option;
- @column_option = @row_option;
- @row_option = @temp;
-}
-
-my $query = 'SELECT '. join(' UNION ALL SELECT ',@row_option);
-my $count_query = 'SELECT '. scalar(@row_option);
-
-my $xml_element = 'OOPS, I was never set';
-my $rowchar = 101; # 'e' -- rows are columns! (pi/2)
-
-my $value = sub {
- my ($rowref, $column) = (shift, shift);
- my $row = $rowref->[0];
-
- if ($column eq 'name') {
- return $row_option_name{$row} || 'no such report option';
- } elsif ( $column =~ /^(\d+)$/ ) {
- my @report_option = ( $row || '',
- $column_option[$column] || '',
- $technology_option[$tech_code] || '',
- );
-
- my ( $count, $residential ) = FS::cust_pkg->fcc_477_count(
- { %search_hash, 'report_option' => join(',', @report_option) }
- );
-
- my $percentage = sprintf('%.2f', $count ? 100 * $residential / $count : 0);
- my $return = $count;
-
- if ($cgi->param('_type') eq 'xml') {
- $rowchar++ if $column == 0;
- $xml_element = $xml_prefix. chr($rowchar). ($column+1);
- $return = '' if $count == 0 and $cgi->param('_type') eq 'xml';
- } else {
- $return .= "<BR>$percentage% residential";
- }
-
- return $return;
- } else {
- return '<FONT SIZE="+1" COLOR="#ff0000">Bad call to column_value</FONT>';
- }
-};
-
-my @fields = map { my $ci = $_; sub { &{$value}(shift, $ci); } }
- ( 'name', (0 .. $#column_option) );
-shift @fields if $cgi->param('_type') eq 'xml';
-
-my @xml_elements = ( # -- columns are rows! (pi/2)
- sub { return $xml_element; },
- sub { return $xml_element; },
- sub { return $xml_element; },
- sub { return $xml_element; },
- sub { return $xml_element; },
- sub { return $xml_element; },
- sub { return $xml_element; },
- sub { return $xml_element; },
- sub { return $xml_element; },
-);
-
-</%init>
diff --git a/httemplate/search/477partIA_summary.html b/httemplate/search/477partIA_summary.html
deleted file mode 100755
index ebf081c71..000000000
--- a/httemplate/search/477partIA_summary.html
+++ /dev/null
@@ -1,89 +0,0 @@
-<& elements/search.html,
- 'html_init' => $html_init,
- 'name' => 'lines',
- 'query' => 'SELECT 1',
- 'count_query' => 'SELECT 1',
- 'really_disable_download' => 1,
- 'disable_download' => 1,
- 'nohtmlheader' => 1,
- 'disable_total' => 1,
- 'header' => [
- 'Total Connections',
- '% owned loop',
- '% billed to end users',
- '% residential',
- '% residential &gt; 200kbps',
- ],
- 'xml_elements' => [
- $xml_prefix. 'a1',
- $xml_prefix. 'b1',
- $xml_prefix. 'c1',
- $xml_prefix. 'd1',
- $xml_prefix. 'e1',
- ],
- 'fields' => [
- sub { $total_count },
- sub { '100.00' },
- sub { '100.00' },
- sub { $total_percentage },
- sub { $above_200_percentage },
- ],
-
-&>
-<%init>
-
-my $curuser = $FS::CurrentUser::CurrentUser;
-
-die "access denied"
- unless $curuser->access_right('List packages');
-
-my %opt = @_;
-my %search_hash = ();
-
-for ( qw(agentnum magic state) ) {
- $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
-}
-$search_hash{'country'} = 'US';
-$search_hash{'classnum'} = [ $cgi->param('classnum') ];
-
-my @column_option = grep { /^\d+$/ } $cgi->param('part1_column_option')
- if $cgi->param('part1_column_option');
-
-my @row_option = grep { /^\d+$/ } $cgi->param('part1_row_option')
- if $cgi->param('part1_row_option');
-
-my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi);
-
-my $total_count = 0;
-my $total_residential = 0;
-my $above_200 = 0;
-my $tech_code = $opt{tech_code};
-my $technology = $FS::Report::FCC_477::technology[$tech_code] || 'unknown';
-my $html_init = "<H2>Part IA $technology totals</H2>";
-my $xml_prefix = 'PartIA_'. chr(65 + $tech_code);
-
-my $not_first_row = 0; # ugh;
-foreach my $row ( @row_option ) {
- foreach my $column ( @column_option ) {
-
- my @report_option = ( $row || '-1', $column || '-1', $technology_option[$tech_code] );
-
- my ( $count, $residential ) = FS::cust_pkg->fcc_477_count(
- { %search_hash, 'report_option' => join(',', @report_option) }
- );
-
- $total_count += $count;
- $total_residential += $residential;
- $above_200 += $residential if $not_first_row;
- }
- $not_first_row++;
-}
-
-my $total_percentage =
- sprintf("%.2f", $total_count ? 100*$total_residential/$total_count : 0);
-
-my $above_200_percentage =
- sprintf("%.2f", $total_count ? 100*$above_200/$total_count : 0);
-
-
-</%init>
diff --git a/httemplate/search/477partIIA.html b/httemplate/search/477partIIA.html
index 6a532299b..95c00a3e0 100755
--- a/httemplate/search/477partIIA.html
+++ b/httemplate/search/477partIIA.html
@@ -1,17 +1,44 @@
-<& elements/search.html,
- 'html_init' => $html_init,
- 'name' => 'lines',
- 'query' => $query,
- 'count_query' => 'SELECT 11',
- 'really_disable_download' => 1,
- 'disable_download' => 1,
- 'nohtmlheader' => 1,
- 'disable_total' => 1,
- 'header' => [ @headers ],
- 'xml_elements' => [ @xml_elements ],
- 'fields' => [ @fields ],
-
-&>
+% if ( $cgi->param('_type') eq 'xml' ) {
+% my @cols = qw(a b c d);
+% for ( my $row = 0; $row < scalar(@rows); $row++ ) {
+% for my $col (0..3) {
+% if ( exists($data[$col][$row]) and $data[$col][$row] > 0 ) {
+<PartII_<% $row + 1 %><% $cols[$col] %>>\
+<% $data[$col][$row] %>\
+</PartII_<% $row + 1 %><% $cols[$col] %>>
+% }
+% } #for $col
+% } #for $row
+% } else { # HTML mode
+% # fake up the search-html.html header
+<H2>Part IIA</H2>
+<TABLE>
+ <TR><TD VALIGN="bottom"><BR></TD></TR>
+ <TR><TD COLSPAN=2>
+ <TABLE CLASS="grid" CELLSPACING=0>
+ <TR>
+% foreach (@row1_headers) {
+ <TH><% $_ %></TH>
+% }
+ </TR>
+% my $row = 0;
+% foreach my $rowhead (@rows) {
+ <TR CLASS="row<%$row % 2%>">
+ <TD STYLE="text-align: left; font-weight: bold"><% $rowhead %></TD>
+% for my $col (0..3) {
+ <TD>
+% if ( exists($data[$col][$row]) ) {
+ <% $data[$col][$row] %>
+% }
+ </TD>
+% } # for $col
+ </TR>
+% $row++;
+% } #for $rowhead
+ </TABLE>
+ </TD></TR>
+</TABLE>
+% } #XML/HTML
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -19,83 +46,76 @@ my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
unless $curuser->access_right('List packages');
-my $html_init = '<H2>Part IIA</H2>';
my %search_hash = ();
-
-for ( qw(agentnum magic state) ) {
- $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
-}
-$search_hash{'country'} = 'US';
-$search_hash{'classnum'} = [ $cgi->param('classnum') ];
-
-my @row_option = grep { /^\d+$/ } $cgi->param('part2a_row_option')
- if $cgi->param('part2a_row_option');
-
-# fudge in two rows of LD carrier
-unshift @row_option, $row_option[0];
-
-# fudge in the first pair of rows
-unshift @row_option, '';
-unshift @row_option, '';
-
-my $query = 'SELECT '. join(' UNION SELECT ', 1..11);
-my $total_count = 0;
-my $column_value = sub {
- my $row = shift;
-
- my @report_option = ( $row_option[$row - 1] || '' );
-
- my $sql_query = FS::cust_pkg->search(
- { %search_hash, 'report_option' => join(',', @report_option) }
- );
-
- my $count_sql = delete($sql_query->{'count_query'});
- if ( $row == 2 || $row == 4 ) {
- $count_sql =~ s/COUNT\(\*\) FROM/sum(COALESCE(CASE WHEN cust_main.company IS NULL OR cust_main.company = '' THEN CASE WHEN part_pkg.fcc_ds0s IS NOT NULL AND part_pkg.fcc_ds0s > 0 THEN part_pkg.fcc_ds0s WHEN pkg_class.fcc_ds0s IS NOT NULL AND pkg_class.fcc_ds0s > 0 THEN pkg_class.fcc_ds0s ELSE 0 END ELSE 0 END, 0) ) FROM/
- or die "couldn't parse count_sql";
- } else {
- $count_sql =~ s/COUNT\(\*\) FROM/sum(COALESCE(CASE WHEN part_pkg.fcc_ds0s IS NOT NULL AND part_pkg.fcc_ds0s > 0 THEN part_pkg.fcc_ds0s WHEN pkg_class.fcc_ds0s IS NOT NULL AND pkg_class.fcc_ds0s > 0 THEN pkg_class.fcc_ds0s ELSE 0 END, 0)) FROM/
- or die "couldn't parse count_sql";
- }
-
- my $count_sth = dbh->prepare($count_sql)
- or die "Error preparing $count_sql: ". dbh->errstr;
- $count_sth->execute
- or die "Error executing $count_sql: ". $count_sth->errstr;
- my $count_arrayref = $count_sth->fetchrow_arrayref;
- my $count = $count_arrayref->[0];
+$search_hash{'agentnum'} = $cgi->param('agentnum');
+$search_hash{'state'} = $cgi->param('state');
+$search_hash{'classnum'} = [ $cgi->param('classnum') ];
+$search_hash{'status'} = 'active';
- $total_count = $count if $row == 1;
- $count = sprintf('%.2f', $total_count ? 100*$count/$total_count : 0)
- if $row != 1;
+my @row_option;
+foreach ($cgi->param('part2a_row_option')) {
+ push @row_option, (/^\d+$/ ? $_ : undef);
+}
- return "$count";
+my $is_residential = "AND COALESCE(cust_main.company, '') = ''";
+my $has_report_option = sub {
+ map {
+ defined($row_option[$_]) ?
+ " AND EXISTS(
+ SELECT 1 FROM part_pkg_option
+ WHERE part_pkg_option.pkgpart = part_pkg.pkgpart
+ AND optionname = 'report_option_" . $row_option[$_]."'
+ AND optionvalue = '1'
+ )" : ' AND FALSE'
+ } @_
};
-my @headers = (
- '',
- 'End user lines',
- 'UNE-P replacement',
- 'UNE (unswitched)',
- 'UNE-P',
+# an arrayref for each column
+my @data;
+# get the skeleton of the query
+my $sql_query = FS::cust_pkg->search(\%search_hash);
+my $from_where = $sql_query->{'count_query'};
+$from_where =~ s/^SELECT COUNT\(\*\) //;
+
+# for row 1
+my $query_ds0 = "SELECT SUM(COALESCE(part_pkg.fcc_ds0s, pkg_class.fcc_ds0s, 0))
+ $from_where AND fcc_voip_class = '4'"; # 4 = Local Exchange
+
+my $total_lines = FS::Record->scalar_sql($query_ds0);
+# always return zero for the number of resold lines, until an actual ILEC
+# starts using this report
+
+@data = (
+ [ $total_lines ],
+ [ 0 ],
+ [ 0 ],
+ [ 0 ],
);
-my @xml_elements = (
- sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}a" },
- sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}b" },
- sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}c" },
- sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}d" },
+my @row_conds = (
+ $is_residential,
+ $has_report_option->(0), # LD carrier
+ ($has_report_option->(0))[0] . $is_residential,
+ $has_report_option->(1..7),
);
+if ( $total_lines > 0 ) {
+ foreach (@row_conds) {
+ my $sql = $query_ds0 . $_;
+ my $lines = FS::Record->scalar_sql($sql);
+ my $percent = sprintf('%.2f', 100 * $lines / $total_lines);
+ push @{ $data[0] }, $percent;
+ }
+}
my @rows = (
'lines',
'% residential',
'% LD carrier',
- '% residential and LD carrier',
- '% own loops',
- '% obtained unswitched UNE loops',
+ '% residential and LD',
+ '% owned loops',
+ '% unswitched UNE',
'% UNE-P',
'% UNE-P replacement',
'% FTTP',
@@ -103,13 +123,12 @@ my @rows = (
'% wireless',
);
-my @fields = (
- sub { my $row = shift; $rows[$row->[0] - 1]; },
- sub { my $row = shift; &{$column_value}($row->[0]); },
- sub { 0; },
- sub { 0; },
- sub { 0; },
+my @row1_headers = (
+ '',
+ 'End user lines',
+ 'UNE-P replacement',
+ 'unswitched UNE',
+ 'UNE-P',
);
-shift @fields if $cgi->param('_type') eq 'xml';
</%init>
diff --git a/httemplate/search/477partIIB.html b/httemplate/search/477partIIB.html
index c58310d36..5b9b30769 100755
--- a/httemplate/search/477partIIB.html
+++ b/httemplate/search/477partIIB.html
@@ -3,9 +3,10 @@
% for ( my $row = 0; $row < scalar(@rows); $row++ ) {
% for my $col (0..2) {
% if ( exists($data[$col][$row]) ) {
-<PartII_<% $row %><% $cols[$col] %>>
+<PartII_<% $row + 1 %><% $cols[$col] %>>\
+<% $data[$col][$row] %>\
+</PartII_<% $row + 1 %><% $cols[$col] %>>
% }
-</PartII_<% $row %><% $cols[$col] %>>
% } #for $col
% } #for $row
% } else { # HTML mode
@@ -14,19 +15,18 @@
<TABLE>
<TR><TD VALIGN="bottom"><BR></TD></TR>
<TR><TD COLSPAN=2>
- <TABLE CLASS="grid" CELLSPACING=0 STYLE="border: 1px solid #cccccc;" BGCOLOR="#cccccc">
+ <TABLE CLASS="grid" CELLSPACING=0>
<TR>
% foreach (@headers) {
- <TH class="grid"><% $_ %></TH>
+ <TH><% $_ %></TH>
% }
</TR>
-% my @bgcolor = ('eeeeee','ffffff');
% my $row = 0;
% foreach my $rowhead (@rows) {
- <TR>
- <TD CLASS="grid" BGCOLOR="#<% $bgcolor[$row % 2] %>"><% $rowhead %></TD>
+ <TR CLASS="row<% $row % 2 %>">
+ <TD STYLE="text-align: left; font-weight: bold"><% $rowhead %></TD>
% for my $col (0..2) {
- <TD CLASS="grid" BGCOLOR="#<% $bgcolor[$row % 2] %>">
+ <TD>
% if ( exists($data[$col][$row]) ) {
<% $data[$col][$row] %>
% }
diff --git a/httemplate/search/477partV.html b/httemplate/search/477partV.html
index 2106a44d6..b2dd9ca95 100755
--- a/httemplate/search/477partV.html
+++ b/httemplate/search/477partV.html
@@ -1,3 +1,6 @@
+% if ( $cgi->param('_type') =~ /^xml$/ ) {
+<zip_code>
+% }
<& elements/search.html,
'html_init' => $html_init,
'name' => 'zip code',
@@ -14,6 +17,9 @@
&>
+% if ( $cgi->param('_type') =~ /^xml$/ ) {
+</zip_code>
+% }
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
diff --git a/httemplate/search/agent_commission.html b/httemplate/search/agent_commission.html
index b8fbe200f..b94ae9f6e 100644
--- a/httemplate/search/agent_commission.html
+++ b/httemplate/search/agent_commission.html
@@ -1,6 +1,12 @@
%# still not a good way to do rows grouped by some field in a search.html
%# report
+% if ( $type eq 'xls' ) {
+<% $data %>\
+% } else {
<& /elements/header.html, $title &>
+<P ALIGN="right" CLASS="noprint">
+Download full results<BR>
+as <A HREF="<% $cgi->self_url %>;_type=xls">Excel spreadsheet</A></P>
<BR>
<STYLE TYPE="text/css">
td.cust_head {
@@ -58,6 +64,7 @@ td.money:before { content: '<% $money_char %>'; }
</TR>
</TABLE>
<& /elements/footer.html &>
+% }
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
@@ -100,10 +107,91 @@ my @cust_pkg = qsearch($query);
my $money_char = FS::Conf->new->config('money_char') || '$';
-#my $count_query =
-# 'SELECT COUNT(*) FROM cust_pkg '.$query->{'addl_from'}.$query->{'extra_sql'}.
-# ' AND EXISTS(SELECT 1 FROM cust_bill_pkg JOIN cust_bill USING (invnum) '.
-# ' WHERE cust_bill_pkg.pkgnum = cust_pkg.pkgnum AND '.
-# "cust_bill._date >= $begin AND cust_bill._date < $end".
-# ')';
+my $data = '';
+my $type = $cgi->param('_type');
+if ( $type eq 'xls') {
+ # some false laziness with the above...
+ my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format;
+ my $filename = 'agent_commission' . $format->{extension};
+ http_header('Content-Type' => $format->{mime_type});
+ http_header('Content-Disposition' => qq!attachment;filename="$filename"!);
+ my $XLS = IO::Scalar->new(\$data);
+ my $workbook = $format->{class}->new($XLS);
+ my $worksheet = $workbook->add_worksheet(substr($title, 0, 31));
+
+ my $cust_head_format = $workbook->add_format(
+ bold => 1,
+ underline => 1,
+ text_wrap => 0,
+ bg_color => 'white',
+ );
+
+ my $col_head_format = $workbook->add_format(
+ bold => 1,
+ align => 'center',
+ bg_color => 'silver'
+ );
+
+ my @format;
+ foreach (0, 1) {
+ my %bg = (bg_color => $_ ? 'white' : 'silver');
+ $format[$_] = {
+ 'text' => $workbook->add_format(%bg),
+ 'money' => $workbook->add_format(%bg, num_format => $money_char.'#0.00'),
+ 'percent' => $workbook->add_format(%bg, num_format => '0.00%'),
+ };
+ }
+ my $total_format = $workbook->add_format(
+ bg_color => 'yellow',
+ num_format => $money_char.'#0.00',
+ top => 1
+ );
+
+ my ($r, $c) = (0, 0);
+ foreach (qw(Package Sales Percentage Commission)) {
+ $worksheet->write($r, $c++, $_, $col_head_format);
+ }
+ $r++;
+
+ my ($custnum, $sales, $commission, $row, $bgcolor) = (0, 0, 0, 0);
+ my $label_length = 0;
+ foreach my $cust_pkg ( @cust_pkg ) {
+ if ( $custnum ne $cust_pkg->custnum ) {
+ # start of a new customer section
+ my $cust_main = $cust_pkg->cust_main;
+ my $label = $cust_main->custnum . ': '. $cust_main->name;
+ $bgcolor = 0;
+ $worksheet->set_row($r, 20);
+ $worksheet->merge_range($r, 0, $r, 3, $label, $cust_head_format);
+ $r++;
+ }
+ $c = 0;
+ my $percent = $cust_pkg->percent / 100;
+ $worksheet->write($r, $c++, $cust_pkg->pkg_label, $format[$bgcolor]{text});
+ $worksheet->write($r, $c++, $cust_pkg->sum_charged, $format[$bgcolor]{money});
+ $worksheet->write($r, $c++, $percent, $format[$bgcolor]{percent});
+ $worksheet->write($r, $c++, ($cust_pkg->sum_charged * $percent),
+ $format[$bgcolor]{money});
+
+ $label_length = max($label_length, length($cust_pkg->pkg_label));
+ $sales += $cust_pkg->sum_charged;
+ $commission += $cust_pkg->sum_charged * $cust_pkg->percent / 100;
+ $row++;
+ $bgcolor = 1-$bgcolor;
+ $custnum = $cust_pkg->custnum;
+ $r++;
+ }
+
+ $c = 0;
+ $label_length = max($label_length, 20);
+ $worksheet->set_column($c, $c, $label_length);
+ $worksheet->write($r, $c++, mt('[quant,_1,package] with commission', $row),
+ $total_format);
+ $worksheet->set_column($c, $c + 2, 11);
+ $worksheet->write($r, $c++, $sales, $total_format);
+ $worksheet->write($r, $c++, '', $total_format);
+ $worksheet->write($r, $c++, $commission, $total_format);
+
+ $workbook->close;
+}
</%init>
diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html
index 88cdaf5ab..473aed311 100755
--- a/httemplate/search/cust_bill.html
+++ b/httemplate/search/cust_bill.html
@@ -97,7 +97,7 @@ if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) {
$search{'refnum'} = $1;
}
- if ( $cgi->param('cust_classnum') ) {
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
$search{'cust_classnum'} = [ $cgi->param('cust_classnum') ];
}
diff --git a/httemplate/search/cust_bill_pay.html b/httemplate/search/cust_bill_pay.html
index 0b64e650f..ff20458d8 100644
--- a/httemplate/search/cust_bill_pay.html
+++ b/httemplate/search/cust_bill_pay.html
@@ -99,9 +99,12 @@ if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) {
$title = $part_referral->referral. " $title";
}
-if ( $cgi->param('cust_classnum') ) {
- my @classnums = grep /^\d+$/, $cgi->param('cust_classnum');
- push @search, 'cust_main.classnum IN('.join(',',@classnums).')'
+# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, prepaid_income.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @search, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
if @classnums;
}
diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
index 0f51d9481..3a3b0feb9 100644
--- a/httemplate/search/cust_bill_pkg.cgi
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -260,13 +260,16 @@ if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
push @where, "cust_main.refnum = $1";
}
-# cust_classnum
-if ( $cgi->param('cust_classnum') ) {
- my @classnums = grep /^\d+$/, $cgi->param('cust_classnum');
- push @where, 'cust_main.classnum IN('.join(',',@classnums).')'
+# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @where, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
if @classnums;
}
+
# custnum
if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
push @where, "cust_main.custnum = $1";
diff --git a/httemplate/search/cust_bill_pkg_referral.html b/httemplate/search/cust_bill_pkg_referral.html
index 1289ff7ee..c4dde32a0 100644
--- a/httemplate/search/cust_bill_pkg_referral.html
+++ b/httemplate/search/cust_bill_pkg_referral.html
@@ -156,9 +156,13 @@ if ( @refnum ) {
push @where, 'cust_main.refnum IN ('.join(',', @refnum).')';
}
-my @cust_classnums = grep /^\d+$/, $cgi->param('cust_classnum');
-if ( @cust_classnums ) {
- push @where, 'cust_main.classnum IN ('.join(',', @cust_classnums).')';
+# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @where, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
+ if @classnums;
}
if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html
index d1f41df00..cabf8c002 100755
--- a/httemplate/search/cust_credit.html
+++ b/httemplate/search/cust_credit.html
@@ -103,9 +103,13 @@ if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) {
$title = $part_referral->referral. " $title";
}
-if ( $cgi->param('cust_classnum') ) {
- my @classnums = grep /^\d+$/, $cgi->param('cust_classnum');
- push @search, 'cust_main.classnum IN('.join(',',@classnums).')'
+
+# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit_refund.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @search, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
if @classnums;
}
diff --git a/httemplate/search/cust_credit_refund.html b/httemplate/search/cust_credit_refund.html
index 1504f0fbe..817420054 100644
--- a/httemplate/search/cust_credit_refund.html
+++ b/httemplate/search/cust_credit_refund.html
@@ -85,9 +85,12 @@ if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) {
$title = $part_referral->referral. " $title";
}
-if ( $cgi->param('cust_classnum') ) {
- my @classnums = grep /^\d+$/, $cgi->param('cust_classnum');
- push @search, 'cust_main.classnum IN('.join(',',@classnums).')'
+# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @search, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
if @classnums;
}
diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi
index 8e3c8133e..2c09c692c 100755
--- a/httemplate/search/cust_main.cgi
+++ b/httemplate/search/cust_main.cgi
@@ -244,7 +244,7 @@
% my $pkg_rowspan = shift @pkg_rowspans;
<% $n1 %><TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN="<% $pkg_rowspan%>">
- <A HREF="<% $pkgview %>"><FONT SIZE=-1><% $pkg_comment %></FONT></A>
+ <A HREF="<% $pkgview %>"><FONT SIZE=-1><% $pkg_comment |h %></FONT></A>
</TD>
% my $n2 = '';
diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html
index 8b39ea962..24348ff8a 100755
--- a/httemplate/search/cust_main.html
+++ b/httemplate/search/cust_main.html
@@ -42,10 +42,11 @@ my %search_hash = ();
#scalars
my @scalars = qw (
agentnum status address zip paydate_year paydate_month invoice_terms
- no_censustract with_geocode with_email no_POST
+ no_censustract with_geocode with_email POST no_POST
custbatch usernum
cancelled_pkgs
cust_fields flattened_pkgs
+ all_tags
);
for my $param ( @scalars ) {
diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi
index 887ec6039..110da91ae 100755
--- a/httemplate/search/cust_pkg.cgi
+++ b/httemplate/search/cust_pkg.cgi
@@ -9,6 +9,7 @@
emt('Package'),
emt('Class'),
emt('Status'),
+ emt('Ordered by'),
emt('Setup'),
emt('Base Recur'),
emt('Freq.'),
@@ -34,6 +35,7 @@
sub { $_[0]->pkg; },
'classname',
sub { ucfirst(shift->status); },
+ 'otaker',
sub { sprintf( $money_char.'%.2f',
shift->part_pkg->option('setup_fee'),
);
@@ -96,13 +98,14 @@
'',
'',
'',
+ '',
FS::UI::Web::cust_colors(),
'',
],
- 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
FS::UI::Web::cust_styles() ],
'size' => [ '', '', '', '', '-1' ],
- 'align' => 'rrlccrrlrrrrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r',
+ 'align' => 'rrlcccrrlrrrrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r',
'links' => [
$link,
$link,
@@ -119,6 +122,7 @@
'',
'',
'',
+ '',
'', # link to changed-from package?
'',
'',
diff --git a/httemplate/search/customer_accounting_summary.html b/httemplate/search/customer_accounting_summary.html
index 12c896276..b48ff21e3 100644
--- a/httemplate/search/customer_accounting_summary.html
+++ b/httemplate/search/customer_accounting_summary.html
@@ -142,8 +142,6 @@ $title .= $sel_part_referral->referral.' '
$title .= 'Customer Accounting Summary Report';
-my @cust_classnums = grep /^\d+$/, $cgi->param('cust_classnum');
-
my @items = ('netsales', 'cashflow');
my @params = ( [], [] );
my $setuprecur = '';
@@ -173,7 +171,7 @@ foreach (qw(agentnum refnum status)) {
}
}
$search_hash{'classnum'} = [ $cgi->param('cust_classnum') ]
- if $cgi->param('cust_classnum');
+ if grep { $_ eq 'cust_classnum' } $cgi->param;
my $query = FS::cust_main::Search->search(\%search_hash);
my @custs = qsearch($query);
diff --git a/httemplate/search/elements/cust_main_dayranges.html b/httemplate/search/elements/cust_main_dayranges.html
index c9c71f274..cf2d495b1 100644
--- a/httemplate/search/elements/cust_main_dayranges.html
+++ b/httemplate/search/elements/cust_main_dayranges.html
@@ -162,6 +162,15 @@ if ( grep { $cgi->param('status') eq $_ } FS::cust_main->statuses() ) {
push @where, FS::cust_main->$method();
}
+# cust_classnum (false laziness w/prepaid_income.html, elements/cust_pay_or_refund.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @where, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
+ if @classnums;
+}
+
#here is the agent virtualization
push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html
index 3e5d504c6..7b2a17058 100755
--- a/httemplate/search/elements/cust_pay_or_refund.html
+++ b/httemplate/search/elements/cust_pay_or_refund.html
@@ -252,9 +252,12 @@ if ( $cgi->param('magic') ) {
$title = $part_referral->referral. " $title";
}
- if ( $cgi->param('cust_classnum') ) {
- my @classnums = grep /^\d+$/, $cgi->param('cust_classnum');
- push @search, 'cust_main.classnum IN('.join(',',@classnums).')'
+ # cust_classnum (false laziness w/ elements/cust_main_dayranges.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
+ if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @search, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
if @classnums;
}
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
index 68c488837..d44b45465 100644
--- a/httemplate/search/elements/search.html
+++ b/httemplate/search/elements/search.html
@@ -353,7 +353,7 @@ if ( $opt{'disableable'} ) {
my $limit = '';
my($confmax, $maxrecords, $offset );
-unless ( $type =~ /^(csv|\w*.xls)$/) {
+unless ( $type =~ /^(csv|xml|\w*.xls)$/) {
# html mode
unless (exists($opt{count_query}) && length($opt{count_query})) {
( $opt{count_query} = $opt{query} ) =~
diff --git a/httemplate/search/employee_audit.html b/httemplate/search/employee_audit.html
index 753c7bff3..2bc6ff46e 100644
--- a/httemplate/search/employee_audit.html
+++ b/httemplate/search/employee_audit.html
@@ -7,7 +7,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+ unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report');
my %tables = (
cust_pay => 'Payments',
diff --git a/httemplate/search/part_pkg.html b/httemplate/search/part_pkg.html
index 2178346e2..a90f13c95 100644
--- a/httemplate/search/part_pkg.html
+++ b/httemplate/search/part_pkg.html
@@ -23,7 +23,7 @@
my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
- unless $curuser->access_right('Financial reports');
+ unless $curuser->access_right('Employees: Commission Report'); #that's all this does so far
my $conf = new FS::Conf;
my $money_char = $conf->config('money_char') || '$';
diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi
index 00c6ba1b9..620996abd 100755
--- a/httemplate/search/pay_batch.cgi
+++ b/httemplate/search/pay_batch.cgi
@@ -150,8 +150,8 @@ my($begin, $end) = ( '', '' );
my @where;
my($beginning,$ending) = FS::UI::Web::parse_beginning_ending($cgi);
-push @where, "download >= $beginning",
- "download <= $ending";
+push @where, "( (download >= $beginning AND download <= $ending) ".
+ ' OR download IS NULL )';
my @status;
if ( $cgi->param('open') ) {
diff --git a/httemplate/search/prepaid_income.html b/httemplate/search/prepaid_income.html
index 03d121d70..cb58a666d 100644
--- a/httemplate/search/prepaid_income.html
+++ b/httemplate/search/prepaid_income.html
@@ -129,10 +129,13 @@ if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
push @where, FS::cust_main->cust_status_sql . " = '$status'";
}
-if ( $cgi->param('cust_classnum') ) {
- my @classnums = grep /^\d+$/, $cgi->param('cust_classnum');
+# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
$link .= ";cust_classnum=$_" foreach @classnums;
- push @where, 'cust_main.classnum IN('.join(',',@classnums).')'
+ push @where, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
if @classnums;
}
diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html
index 51618fb24..b339c80e0 100644
--- a/httemplate/search/report_cust_bill.html
+++ b/httemplate/search/report_cust_bill.html
@@ -4,7 +4,7 @@
<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
-<TABLE BGCOLOR="#cccccc" CELLSPACING=0
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
% unless ( $custnum ) {
<& /elements/tr-select-agent.html,
diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html
index acc49aec6..bac4346cf 100755
--- a/httemplate/search/report_cust_main.html
+++ b/httemplate/search/report_cust_main.html
@@ -96,11 +96,21 @@
</TR>
% }
- <& /elements/tr-select-cust_tag.html,
- 'cgi' => $cgi,
- 'is_report' => 1,
- 'multiple' => 1,
- &>
+ <TR>
+ <TD ALIGN="right">Tags</TD>
+ <TD>
+ <& /elements/select-cust_tag.html,
+ 'cgi' => $cgi,
+ 'is_report' => 1,
+ 'multiple' => 1,
+ &>
+ <DIV STYLE="display:inline-block; vertical-align:baseline">
+ <INPUT TYPE="radio" NAME="all_tags" VALUE="0" CHECKED> Any of these
+ <BR>
+ <INPUT TYPE="radio" NAME="all_tags" VALUE="1"> All of these
+ </DIV>
+ </TD>
+ </TR>
<& /elements/tr-select-payby.html,
'payby_type' => 'cust',
@@ -174,10 +184,28 @@
</TR>
<TR>
+ <TD ALIGN="right" VALIGN="center"><% mt('With postal mail invoices') |h %></TD>
+ <TD><INPUT TYPE="checkbox" NAME="POST" ID="POST" onClick="POST_changed();"></TD>
+ </TR>
+
+ <TR>
<TD ALIGN="right" VALIGN="center"><% mt('Without postal mail invoices') |h %></TD>
- <TD><INPUT TYPE="checkbox" NAME="no_POST"></TD>
+ <TD><INPUT TYPE="checkbox" NAME="no_POST" ID="no_POST" onClick="no_POST_changed();"></TD>
</TR>
+ <SCRIPT TYPE="text/javascript">
+ function POST_changed() {
+ if ( document.getElementById('POST').checked == true ) {
+ document.getElementById('no_POST').checked = false;
+ }
+ }
+ function no_POST_changed() {
+ if ( document.getElementById('no_POST').checked == true ) {
+ document.getElementById('POST').checked = false;
+ }
+ }
+ </SCRIPT>
+
<TR>
<TH CLASS="background" COLSPAN=2>&nbsp;</TH>
</TR>
diff --git a/httemplate/search/report_employee_audit.html b/httemplate/search/report_employee_audit.html
index 757b8232f..461849b76 100644
--- a/httemplate/search/report_employee_audit.html
+++ b/httemplate/search/report_employee_audit.html
@@ -23,7 +23,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+ unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report');
my %tables = (
cust_pay => 'Payments',
diff --git a/httemplate/search/report_employee_commission.html b/httemplate/search/report_employee_commission.html
index 51afad3b5..ebfcae82d 100644
--- a/httemplate/search/report_employee_commission.html
+++ b/httemplate/search/report_employee_commission.html
@@ -25,6 +25,6 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+ unless $FS::CurrentUser::CurrentUser->access_right('Employees: Commission Report');
</%init>
diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html
index 5cff0f4fc..854b24a00 100755
--- a/httemplate/search/report_receivables.html
+++ b/httemplate/search/report_receivables.html
@@ -15,7 +15,15 @@
<& /elements/tr-select-cust_main-status.html,
'label' => emt('Customer Status'),
&>
-
+
+ <& /elements/tr-select-cust_class.html,
+ 'label' => emt('Customer class'),
+ 'field' => 'cust_classnum',
+ 'multiple' => 1,
+ 'pre_options' => [ '' => emt('(none)') ],
+ 'all_selected' => 1,
+ &>
+
<TR>
<TD ALIGN="right"><% mt('Customers') |h %></TD>
<TD>
diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi
index 42a52d154..479b99044 100755
--- a/httemplate/search/report_tax.cgi
+++ b/httemplate/search/report_tax.cgi
@@ -250,8 +250,10 @@ my $conf = new FS::Conf;
my $out = 'Out of taxable region(s)';
my %label_opt = ( out => 1 ); #enable 'Out of Taxable Region' label
-$label_opt{no_city} = 1 unless $cgi->param('show_cities');
-$label_opt{no_taxclass} = 1 unless $cgi->param('show_taxclasses');
+$label_opt{with_city} = 1 if $cgi->param('show_cities');
+$label_opt{with_district} = 1 if $cgi->param('show_districts');
+
+$label_opt{with_taxclass} = 1 if $cgi->param('show_taxclasses');
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
@@ -487,7 +489,8 @@ my $tot_tax = 0;
my $tot_credit = 0;
my @loc_params = qw(country state county);
-push @loc_params, qw(city district) if $cgi->param('show_cities');
+push @loc_params, 'city' if $cgi->param('show_cities');
+push @loc_params, 'district' if $cgi->param('show_districts');
foreach my $r ( qsearch({ 'table' => 'cust_main_county', })) {
my $taxnum = $r->taxnum;
@@ -522,7 +525,7 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county', })) {
}
if ( $cgi->param('show_taxclasses') ) {
- my $base_label = $r->label(%label_opt, 'no_taxclass' => 1);
+ my $base_label = $r->label(%label_opt, 'with_taxclass' => 0);
$base_regions{$base_label} ||=
{
label => $base_label,
diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html
index 2ab0e0b2e..8a207aafb 100755
--- a/httemplate/search/report_tax.html
+++ b/httemplate/search/report_tax.html
@@ -34,9 +34,21 @@
% if ( $city ) {
<TR>
- <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_cities" VALUE="1"></TD>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_cities" VALUE="1" onclick="toggle_show_cities(this)"></TD>
<TD>Show cities</TD>
</TR>
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_districts" VALUE="1" DISABLED></TD>
+ <TD>Show districts</TD>
+ </TR>
+ <SCRIPT TYPE="text/javascript">
+ function toggle_show_cities() {
+ what = document.getElementsByName('show_cities')[0];
+ what.form.show_districts.disabled = !what.checked;
+ what.form.show_districts.checked = what.checked;
+ }
+ toggle_show_cities();
+ </SCRIPT>
% }
% if ( $conf->exists('enable_taxclasses') ) {
diff --git a/httemplate/search/sqlradius.cgi b/httemplate/search/sqlradius.cgi
index 5363944e4..22984731a 100644
--- a/httemplate/search/sqlradius.cgi
+++ b/httemplate/search/sqlradius.cgi
@@ -51,7 +51,7 @@
% @{ $part_export->usage_sessions( {
% 'stoptime_start' => $beginning,
% 'stoptime_end' => $ending,
-% 'open_sessions' => $open_sessions,
+% 'session_status' => $status,
% 'starttime_start' => $starttime_beginning,
% 'starttime_end' => $starttime_ending,
% 'svc_acct' => $cgi_svc_acct,
@@ -117,9 +117,9 @@ if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) {
$ending = $1;
}
-my $open_sessions = '';
-if ( $cgi->param('open_sessions') =~ /^(\d*)$/ ) {
- $open_sessions = $1;
+my $status = '';
+if ( $cgi->param('session_status') =~ /^(closed|open)$/ ) {
+ $status = $1;
}
my( $starttime_beginning, $starttime_ending ) = ( '', '' );
@@ -242,8 +242,15 @@ my $time_format = sub {
$pretty;
};
+my $time_format_or_open = sub {
+ my $time = shift;
+ return '<CENTER>OPEN</CENTER>' if $time == 0;
+ &{$time_format}($time);
+};
+
my $duration_format = sub {
my $seconds = shift;
+ return '' if $seconds eq ''; # open session
my $hour = int($seconds/3600);
my $min = int( ($seconds%3600) / 60 );
my $sec = $seconds%60;
@@ -339,7 +346,7 @@ tie %fields, 'Tie::IxHash',
'acctstoptime' => {
name => 'End&nbsp;time',
attrib => 'Acct-Stop-Time',
- fmt => $time_format,
+ fmt => $time_format_or_open,
align => 'left',
},
'acctsessiontime' => {
diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html
index 7b9fce310..547a9bb44 100644
--- a/httemplate/search/sqlradius.html
+++ b/httemplate/search/sqlradius.html
@@ -52,8 +52,9 @@
<TR>
<TD>Show:</TD>
<TD>
- <INPUT TYPE="radio" NAME="open_sessions" VALUE="0" onClick="open_changed(this);" CHECKED>Completed sessions<BR>
- <INPUT TYPE="radio" NAME="open_sessions" VALUE="1" onClick="open_changed(this);">Open sessions
+ <INPUT TYPE="radio" NAME="session_status" VALUE="" onClick="enable_stop(true);" CHECKED>All sessions<BR>
+ <INPUT TYPE="radio" NAME="session_status" VALUE="closed" onClick="enable_stop(true);">Completed sessions<BR>
+ <INPUT TYPE="radio" NAME="session_status" VALUE="open" onClick="enable_stop(false);">Open sessions
</TD>
</TR>
@@ -69,41 +70,31 @@
<SCRIPT TYPE="text/javascript">
- function open_changed(what) {
-
- var value=get_open_value(what);
- if ( value == '1' ) {
- what.form.stoptime_beginning_text.disabled = true;
- what.form.stoptime_ending_text.disabled = true;
- what.form.stoptime_beginning_text.style.backgroundColor = '#dddddd';
- what.form.stoptime_ending_text.style.backgroundColor = '#dddddd';
- what.form.stoptime_beginning_button.style.display = 'none';
- what.form.stoptime_ending_button.style.display = 'none';
- what.form.stoptime_beginning_disabled.style.display = '';
- what.form.stoptime_ending_disabled.style.display = '';
- } else if ( value == '0' ) {
- what.form.stoptime_beginning_text.disabled = false;
- what.form.stoptime_ending_text.disabled = false;
- what.form.stoptime_beginning_text.style.backgroundColor = '#ffffff';
- what.form.stoptime_ending_text.style.backgroundColor = '#ffffff';
- what.form.stoptime_beginning_button.style.display = '';
- what.form.stoptime_ending_button.style.display = '';
- what.form.stoptime_beginning_disabled.style.display = 'none';
- what.form.stoptime_ending_disabled.style.display = 'none';
+ function enable_stop(value) {
+
+ var f = document.OneTrueForm;
+ if ( value ) {
+ f.stoptime_beginning_text.disabled = false;
+ f.stoptime_ending_text.disabled = false;
+ f.stoptime_beginning_text.style.backgroundColor = '#ffffff';
+ f.stoptime_ending_text.style.backgroundColor = '#ffffff';
+ f.stoptime_beginning_button.style.display = '';
+ f.stoptime_ending_button.style.display = '';
+ f.stoptime_beginning_disabled.style.display = 'none';
+ f.stoptime_ending_disabled.style.display = 'none';
+ } else {
+ f.stoptime_beginning_text.disabled = true;
+ f.stoptime_ending_text.disabled = true;
+ f.stoptime_beginning_text.style.backgroundColor = '#dddddd';
+ f.stoptime_ending_text.style.backgroundColor = '#dddddd';
+ f.stoptime_beginning_button.style.display = 'none';
+ f.stoptime_ending_button.style.display = 'none';
+ f.stoptime_beginning_disabled.style.display = '';
+ f.stoptime_ending_disabled.style.display = '';
}
}
- function get_open_value(what) {
- var rad_val = '';
- for (var i=0; i < what.form.open_sessions.length; i++) {
- if (what.form.open_sessions[i].checked) {
- var rad_val = what.form.open_sessions[i].value;
- }
- }
- return rad_val;
- }
-
</SCRIPT>
<TR>
diff --git a/httemplate/search/unearned_detail.html b/httemplate/search/unearned_detail.html
index 425aa5a4e..285fb50a7 100644
--- a/httemplate/search/unearned_detail.html
+++ b/httemplate/search/unearned_detail.html
@@ -114,13 +114,12 @@ if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
push @where, "cust_bill._date >= $beginning",
"cust_bill._date <= $ending";
-if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
- push @where, "cust_main.agentnum = $1";
-}
-
-if ( $cgi->param('cust_classnum') ) {
- my @classnums = grep /^\d+$/, $cgi->param('cust_classnum');
- push @where, 'cust_main.classnum IN('.join(',',@classnums).')'
+# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @where, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
if @classnums;
}
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
index 5c46803d2..b863a734b 100644
--- a/httemplate/view/cust_main/billing.html
+++ b/httemplate/view/cust_main/billing.html
@@ -247,6 +247,10 @@
<TD ALIGN="right"><% mt('Email address(es)') |h %></TD>
<TD BGCOLOR="#ffffff">
<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %>
+% if ( $cust_main->message_noemail ) {
+ <BR>
+ <SPAN STYLE="font-size: small"><% emt('(do not send notices)') %></SPAN>
+% }
</TD>
</TR>
% }
diff --git a/httemplate/view/cust_main/change_history.html b/httemplate/view/cust_main/change_history.html
index ea84b8f75..bf32a49f9 100644
--- a/httemplate/view/cust_main/change_history.html
+++ b/httemplate/view/cust_main/change_history.html
@@ -43,10 +43,12 @@ tie my %tables, 'Tie::IxHash',
'svc_external' => 'External service',
'svc_phone' => 'Phone',
'phone_device' => 'Phone device',
+ 'cust_pkg_discount' => 'Discount',
#? it gets provisioned anyway 'phone_avail' => 'Phone',
;
-my $svc_join = 'JOIN cust_svc USING ( svcnum ) JOIN cust_pkg USING ( pkgnum )';
+my $pkg_join = "JOIN cust_pkg USING ( pkgnum )";
+my $svc_join = "JOIN cust_svc USING ( svcnum ) $pkg_join";
my %table_join = (
'svc_acct' => $svc_join,
@@ -58,6 +60,7 @@ my %table_join = (
'svc_external' => $svc_join,
'svc_phone' => $svc_join,
'phone_device' => $svc_join,
+ 'cust_pkg_discount'=> $pkg_join,
);
@@ -104,7 +107,7 @@ my $conf = new FS::Conf;
my $curuser = $FS::CurrentUser::CurrentUser;
-die "access deined"
+die "access denied"
unless $curuser->access_right('View customer history');
# find out the beginning of this customer history, if possible
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index 24a12cc46..546dd89c3 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -101,7 +101,7 @@ table.usage {
<TR>
<TD COLSPAN=2>
-% if ( $conf->exists('cust_pkg-group_by_location') and $show_location ) {
+% if ( $conf->exists('cust_pkg-group_by_location') ) {
<& locations.html,
'cust_main' => $cust_main,
'packages' => $packages,
@@ -113,7 +113,6 @@ table.usage {
<& packages/section.html,
'cust_main' => $cust_main,
'packages' => $packages,
- 'show_location' => $show_location,
&>
</TABLE>
% }
@@ -140,10 +139,6 @@ my $curuser = $FS::CurrentUser::CurrentUser;
my( $packages, $num_old_packages ) = get_packages($cust_main, $conf);
-
-my $show_location = $conf->exists('cust_pkg-always_show_location')
- || (grep $_->locationnum ne $cust_main->ship_locationnum, @$packages);
-
my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
#subroutines
diff --git a/httemplate/view/cust_main/packages/contact.html b/httemplate/view/cust_main/packages/contact.html
new file mode 100644
index 000000000..93129915f
--- /dev/null
+++ b/httemplate/view/cust_main/packages/contact.html
@@ -0,0 +1,61 @@
+% if ( $contact ) {
+ <% $contact->line |h %>
+% if ( $show_link ) {
+ <FONT SIZE=-1>
+ (&nbsp;<%pkg_change_contact_link($cust_pkg)%>&nbsp;)
+ </FONT>
+% }
+% } elsif ( $show_link ) {
+ <FONT SIZE=-1>
+ (&nbsp;<%pkg_add_contact_link($cust_pkg)%>&nbsp;)
+ </FONT>
+% }
+<%init>
+
+my $conf = new FS::Conf;
+my %opt = @_;
+
+my $cust_pkg = $opt{'cust_pkg'};
+
+my $show_link =
+ ! $cust_pkg->get('cancel')
+ && $FS::CurrentUser::CurrentUser->access_right('Change customer package');
+
+my $contact = $cust_pkg->contact_obj;
+
+sub pkg_change_contact_link {
+ my $cust_pkg = shift;
+ #my $pkgpart = $cust_pkg->pkgpart;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/change_pkg_contact.html",
+ 'label' => emt('Change'), # contact'),
+ 'actionlabel' => emt('Change'),
+ 'cust_pkg' => $cust_pkg,
+ 'width' => 616,
+ 'height' => 220,
+ );
+}
+
+sub pkg_add_contact_link {
+ my $cust_pkg = shift;
+ #my $pkgpart = $cust_pkg->pkgpart;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/change_pkg_contact.html",
+ 'label' => emt('Add contact'),
+ 'actionlabel' => emt('Change'),
+ 'cust_pkg' => $cust_pkg,
+ 'width' => 616,
+ 'height' => 192,
+ );
+}
+
+#sub edit_contact_link {
+# my $contactnum = shift;
+# include( '/elements/popup_link.html',
+# 'action' => $p. "edit/cust_contact.cgi?contactnum=$contactnum",
+# 'label' => emt('Edit contact'),
+# 'actionlabel' => emt('Edit'),
+# );
+#}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html
index 34e3a64c3..f2d379841 100644
--- a/httemplate/view/cust_main/packages/location.html
+++ b/httemplate/view/cust_main/packages/location.html
@@ -1,7 +1,5 @@
-<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" WIDTH="20%">
-
-% unless ( $cust_pkg->locationnum ) {
- <I><FONT SIZE=-1>(<% mt('default service address') |h %>)</FONT><BR>
+% if ( $default ) {
+ <DIV STYLE="font-style: italic; font-size: small">
% }
<% $loc->location_label( 'join_string' => '<BR>',
@@ -24,8 +22,8 @@
</FONT>
% }
-% unless ( $cust_pkg->locationnum ) {
- </I>
+% if ( $default ) {
+ </DIV>
% }
% if ( ! $cust_pkg->get('cancel')
@@ -41,19 +39,19 @@
</FONT>
% }
-</TD>
<%init>
my $conf = new FS::Conf;
my %opt = @_;
-my $bgcolor = $opt{'bgcolor'};
my $cust_pkg = $opt{'cust_pkg'};
my $countrydefault = $opt{'countrydefault'} || 'US';
my $statedefault = $opt{'statedefault'}
|| ($countrydefault eq 'US' ? 'CA' : '');
my $loc = $cust_pkg->cust_location_or_main;
+# dubious--they should all have a location now
+my $default = $cust_pkg->locationnum == $opt{'cust_main'}->ship_locationnum;
sub pkg_change_location_link {
my $cust_pkg = shift;
diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html
index d0fc182cb..520305a9a 100644
--- a/httemplate/view/cust_main/packages/package.html
+++ b/httemplate/view/cust_main/packages/package.html
@@ -24,16 +24,19 @@
<TD COLSPAN=2>
<FONT SIZE=-1>
-% unless ( $cust_pkg->get('cancel') ) {
+% unless ( $cust_pkg->get('cancel') ) {
%
-% if ( $supplemental ) {
-% # then only show "Edit dates", "Add invoice details", and "Add
-% # comments".
+% if ( $supplemental or $part_pkg->freq eq '0' ) {
+% # Supplemental packages can't be changed independently.
+% # One-time charges don't need to be changed.
+% # For both of those, we only show "Edit dates", "Add comments",
+% # and "Add invoice details".
% if ( $curuser->access_right('Edit customer package dates') ) {
(&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
% }
% } else {
-% # the usual case
+% # the usual case: links to change package definition,
+% # discount, and customization
% my $br = 0;
% if ( $curuser->access_right('Change customer package') ) {
% $br=1;
@@ -181,11 +184,13 @@
% if ( $curuser->access_right('Change customer package') and
% !$cust_pkg->get('cancel') and
% !$supplemental and
-% !$opt{'show_location'}) {
+% $part_pkg->freq ne '0' ) {
<TR>
+% if ( FS::Conf->new->exists('invoice-unitprice') ) {
<TD><FONT SIZE="-1">
- (&nbsp;<% pkg_change_location_link($cust_pkg) %>&nbsp;)
+ (&nbsp;<% pkg_change_quantity_link($cust_pkg) %>&nbsp;)
</FONT></TD>
+% }
</TR>
% }
% }
@@ -274,6 +279,17 @@ sub pkg_change_location_link {
);
}
+sub pkg_change_quantity_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'edit/cust_pkg_quantity.html?',
+ 'label' => emt('Change quantity'),
+ 'actionlabel' => emt('Change'),
+ 'cust_pkg' => shift,
+ 'width' => 390,
+ 'height' => 220,
+ );
+}
+
sub pkg_dates_link { pkg_link('edit/REAL_cust_pkg', emt('Edit dates'), @_ ); }
sub pkg_discount_link {
diff --git a/httemplate/view/cust_main/packages/section.html b/httemplate/view/cust_main/packages/section.html
index 52246192f..5f54c0a36 100755
--- a/httemplate/view/cust_main/packages/section.html
+++ b/httemplate/view/cust_main/packages/section.html
@@ -3,9 +3,7 @@
% #my $width = $show_location ? 'WIDTH="25%"' : 'WIDTH="33%"';
<TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Package') |h %></TH>
<TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH>
-% if ( $show_location ) {
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Location') |h %></TH>
-% }
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Contact/Location') |h %></TH>
<TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Services') |h %></TH>
</TR>
@@ -13,6 +11,7 @@
% foreach my $cust_pkg (@$packages) {
<& .packagerow, $cust_pkg,
'cust_main' => $opt{'cust_main'},
+ 'bgcolor' => $opt{'bgcolor'},
%conf_opt
&>
% }
@@ -27,10 +26,11 @@
<!--pkgnum: <% $cust_pkg->pkgnum %>-->
<TR CLASS="row<%$row % 2%>">
<& package.html, %iopt &>
- <& status.html, %iopt &>
-% if ( $iopt{'show_location'} ) {
- <& location.html, %iopt &>
-% }
+ <& status.html, %iopt &>
+ <TD CLASS="inv" BGCOLOR="<% $iopt{bgcolor} %>" WIDTH="20%" VALIGN="top">
+ <& contact.html, %iopt &>
+ <& location.html, %iopt &>
+ </TD>
<& services.html, %iopt &>
</TR>
% $row++;
@@ -51,7 +51,6 @@ my $conf = new FS::Conf;
my $curuser = $FS::CurrentUser::CurrentUser;
my $packages = $opt{'packages'};
-my $show_location = $opt{'show_location'};
# Sort order is hardcoded for now, can change this if needed.
@$packages = sort {
@@ -84,10 +83,8 @@ my %conf_opt = (
'manage_link_loc' => scalar($conf->config('svc_broadband-manage_link_loc')),
'manage_link-new_window' => $conf->exists('svc_broadband-manage_link-new_window'),
'maestro-status_test' => $conf->exists('maestro-status_test'),
- 'cust_pkg-large_pkg_size' => $conf->config('cust_pkg-large_pkg_size'),
+ 'cust_pkg-large_pkg_size' => scalar($conf->config('cust_pkg-large_pkg_size')),
- # for packages.html Change location link
- 'show_location' => $show_location,
);
</%init>
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
index 6be0296a3..9d5a88e0f 100644
--- a/httemplate/view/cust_main/packages/status.html
+++ b/httemplate/view/cust_main/packages/status.html
@@ -1,4 +1,4 @@
-<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="top">
<TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
%#this should use cust_pkg->status and cust_pkg->statuscolor eventually
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
index 7701cb6b0..915be49e5 100644
--- a/httemplate/view/cust_main/payment_history.html
+++ b/httemplate/view/cust_main/payment_history.html
@@ -228,57 +228,20 @@
%#display payment history
-%my $money_char = $conf->config('money_char') || '$';
-%
-%sub balance_forward_row {
-% my( $b, $date, $money_char ) = @_;
-% ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/-&nbsp;\$/;
-
- <TR ID="balance_forward_row">
- <TD CLASS="grid" BGCOLOR="#dddddd">
- <% time2str($date_format, $date) %>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="#dddddd">
- <I><% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %></I>
- (<A HREF="javascript:void(0);" onClick="show_history();"><% mt('show prior history') |h %></A>)
- </TD>
-
- <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
- <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
- <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
- <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
- <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
-
- </TR>
-%}
-%
-%my $balance = 0;
%my %target = ();
%
-%my $years = $conf->config('payment_history-years') || 2;
-%my $older_than = time - $years * 31556926; #60*60*24*365.2422
%my $hidden = 0;
%my $seen = 0;
%my $old_history = 0;
%my $lastdate = 0;
%
-%foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
+%foreach my $item ( @history ) {
%
% $lastdate = $item->{'date'};
%
-% my $display;
-% if ( $item->{'date'} < $older_than ) {
+% my $display = '';
+% if ( $item->{'hide'} ) {
% $display = ' STYLE="display:none" ';
-% $hidden = 1;
-% } else {
-%
-% $display = '';
-%
-% if ( $hidden && ! $seen++ ) {
-% balance_forward_row($balance, $item->{'date'}, $money_char);
-% }
-%
% }
%
% if ( $bgcolor eq $bgcolor1 ) {
@@ -314,16 +277,8 @@
%
% my $target = exists($item->{'target'}) ? $item->{'target'} : '';
%
-% $balance += $item->{'charge'} if exists $item->{'charge'};
-% $balance -= $item->{'payment'} if exists $item->{'payment'};
-% $balance -= $item->{'credit'} if exists $item->{'credit'};
-% $balance += $item->{'refund'} if exists $item->{'refund'};
-% $balance = sprintf("%.2f", $balance);
-% $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
-% ( my $showbalance = $money_char. $balance ) =~ s/^\$\-/-&nbsp;\$/;
-%
-%
-
+% my $showbalance = $money_char . $item->{'balance'};
+% $showbalance =~ s/^\$\-/-&nbsp;\$/;
<TR <% $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>>
<TD VALIGN="top" CLASS="grid" BGCOLOR="<% $bgcolor %>">
@@ -359,11 +314,11 @@
<% $showbalance %>
</TD>
</TR>
-% }
-%if ( scalar(@history) && $hidden && ! $seen++ ) {
-% balance_forward_row($balance, $lastdate, $money_char);
-%}
+% if ( $item->{'balance_forward'} ) {
+<& .balance_forward_row, $item->{'balance'}, $item->{'date'} &>
+% }
+%} # foreach $item
</TABLE>
</TD>
@@ -386,14 +341,37 @@ function show_history () {
}
</SCRIPT>
+<%def .balance_forward_row>
+% my( $b, $date ) = @_;
+% ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/-&nbsp;\$/;
-<%init>
+ <TR ID="balance_forward_row">
+ <TD CLASS="grid" BGCOLOR="#dddddd">
+ <% time2str($date_format, $date) %>
+ </TD>
-my( $cust_main ) = @_;
-my $custnum = $cust_main->custnum;
+ <TD CLASS="grid" BGCOLOR="#dddddd">
+ <I><% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %></I>
+ (<A HREF="javascript:void(0);" onClick="show_history();"><% mt('show prior history') |h %></A>)
+ </TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
+
+ </TR>
+</%def>
+<%shared>
my $conf = new FS::Conf;
my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+my $money_char = $conf->config('money_char') || '$';
+</%shared>
+<%init>
+
+my( $cust_main ) = @_;
+my $custnum = $cust_main->custnum;
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -533,6 +511,41 @@ foreach my $cust_refund ($cust_main->cust_refund) {
}
+# sort in forward order first, and calculate running balances
+my $years = $conf->config('payment_history-years') || 2;
+my $older_than = time - $years * 31556926; #60*60*24*365.2422
+my $balance = 0;
+
+@history = sort { $a->{date} <=> $b->{date} } @history;
+my $i = 0;
+my $balance_forward;
+foreach my $item (@history) {
+ $balance += $item->{'charge'} if exists $item->{'charge'};
+ $balance -= $item->{'payment'} if exists $item->{'payment'};
+ $balance -= $item->{'credit'} if exists $item->{'credit'};
+ $balance += $item->{'refund'} if exists $item->{'refund'};
+ $balance = sprintf("%.2f", $balance);
+ $balance =~ s/^\-0\.00$/0.00/;
+ $item->{'balance'} = $balance;
+
+ if ( $item->{'date'} < $older_than ) {
+ $item->{'hide'} = 1;
+ } elsif ( $history[$i-1]->{'hide'} ) {
+ # this is the end of the hidden section
+ $history[$i-1]->{'balance_forward'} = 1;
+ }
+ $i++;
+}
+if ( @history and $history[-1]->{'hide'} ) {
+ # then everything is hidden
+ $history[-1]->{'balance_forward'} = 1;
+}
+
+# then sort in user-pref order
+if ( $curuser->option('history_order') eq 'newest' ) {
+ @history = sort { $b->{date} <=> $a->{date} } @history;
+} # else it's already oldest-first, and there are no other options yet
+
sub translate_payby {
my ($payby,$payinfo) = (shift,shift);
my %payby = (
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
index 76631baad..858ccbe67 100755
--- a/httemplate/view/svc_acct.cgi
+++ b/httemplate/view/svc_acct.cgi
@@ -22,6 +22,7 @@
% }
+
<& svc_acct/radius_usage.html,
'svc_acct' => $svc_acct,
'part_svc' => $part_svc,
@@ -29,6 +30,7 @@
%gopt,
&>
+
<& svc_acct/change_svc_form.html,
'part_svc' => \@part_svc,
'svcnum' => $svcnum,
@@ -43,6 +45,9 @@
%gopt,
&>
+</FORM>
+
+
<& svc_acct/basics.html,
'svc_acct' => $svc_acct,
'part_svc' => $part_svc,
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
index 05b6ac56d..7d6520e57 100644
--- a/httemplate/view/svc_broadband.cgi
+++ b/httemplate/view/svc_broadband.cgi
@@ -36,6 +36,14 @@ my @fields = (
#'longitude',
{ field => 'coordinates', value_callback => \&coordinates },
'altitude',
+
+ 'radio_serialnum',
+ 'radio_location',
+ 'poe_location',
+ 'rssi',
+ 'suid',
+ { field => 'shared_svcnum', value_callback=> \&shared_svcnum, }, #value_callback =>
+
'vlan_profile',
'authkey',
'plan_id',
@@ -112,9 +120,36 @@ sub coordinates {
);
}
+sub shared_svcnum {
+ my $svc_broadband = shift;
+ return '' unless $svc_broadband->shared_svcnum;
+
+ my $shared_svc_broadband =
+ qsearchs('svc_broadband', { 'svcnum' => $svc_broadband->shared_svcnum,
+ }
+ #agent virt?
+ )
+ or return '';
+ my $shared_cust_pkg = $shared_svc_broadband->cust_svc->cust_pkg;
+
+ $shared_svc_broadband->label.
+ ( $shared_cust_pkg
+ ? ' ('. $shared_cust_pkg->cust_main->name. ')'
+ : ''
+ );
+}
+
sub svc_callback {
# trying to move to the callback style
my ($cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt) = @_;
+
+ if ( $part_svc->part_svc_column('latitude')->columnflag eq 'F'
+ && $part_svc->part_svc_column('longitude')->columnflag eq 'F'
+ )
+ {
+ @$fields = grep { !ref($_) || $_->{field} ne 'coordinates' } @$fields;
+ }
+
# again, we assume at most one of these exports per part_svc
my ($nas_export) = $part_svc->part_export('broadband_nas');
if ( $nas_export ) {