summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rw-r--r--httemplate/edit/agent_payment_gateway.html1
-rwxr-xr-xhttemplate/edit/cust_location.cgi28
-rw-r--r--httemplate/edit/part_export.cgi81
-rwxr-xr-xhttemplate/edit/part_pkg.cgi92
-rw-r--r--httemplate/edit/payment_gateway.html141
-rw-r--r--httemplate/edit/process/change-cust_pkg.html4
-rw-r--r--httemplate/edit/process/cust_location.cgi7
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi7
-rw-r--r--httemplate/edit/process/detach-cust_pkg.html47
-rw-r--r--httemplate/edit/process/part_export.cgi1
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi8
-rw-r--r--httemplate/edit/process/payment_gateway.html1
-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/contact.html6
-rw-r--r--httemplate/elements/dashboard-toplist.html3
-rw-r--r--httemplate/elements/menu.html10
-rw-r--r--httemplate/elements/search-svc_broadband.html204
-rw-r--r--httemplate/elements/select-tiered.html9
-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.html24
-rwxr-xr-xhttemplate/misc/cancel-unaudited.cgi37
-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
-rwxr-xr-xhttemplate/misc/cust_main-merge.html12
-rw-r--r--httemplate/misc/delete-note.html11
-rwxr-xr-xhttemplate/misc/detach_pkg.html104
-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/merge_cust.html42
-rw-r--r--httemplate/misc/order_pkg.html6
-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/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.html2
-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
-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_pay_pending.html5
-rw-r--r--httemplate/search/cust_svc.html24
-rw-r--r--httemplate/search/customer_accounting_summary.html4
-rw-r--r--httemplate/search/elements/cust_main_dayranges.html18
-rwxr-xr-xhttemplate/search/elements/cust_pay_or_refund.html9
-rw-r--r--httemplate/search/elements/search-xls.html25
-rw-r--r--httemplate/search/elements/search.html2
-rw-r--r--httemplate/search/employee_audit.html2
-rwxr-xr-xhttemplate/search/h_cust_pay.html5
-rw-r--r--httemplate/search/part_pkg.html2
-rw-r--r--httemplate/search/prepaid_income.html9
-rw-r--r--httemplate/search/report_cust_bill.html2
-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
-rwxr-xr-xhttemplate/search/unapplied_cust_pay.html5
-rw-r--r--httemplate/search/unearned_detail.html13
-rwxr-xr-xhttemplate/view/cust_main.cgi11
-rw-r--r--httemplate/view/cust_main/change_history.html7
-rwxr-xr-xhttemplate/view/cust_main/locations.html2
-rwxr-xr-xhttemplate/view/cust_main/notes.html6
-rwxr-xr-xhttemplate/view/cust_main/packages.html7
-rw-r--r--httemplate/view/cust_main/packages/contact.html87
-rw-r--r--httemplate/view/cust_main/packages/location.html14
-rw-r--r--httemplate/view/cust_main/packages/package.html5
-rwxr-xr-xhttemplate/view/cust_main/packages/section.html29
-rw-r--r--httemplate/view/cust_main/packages/status.html37
-rw-r--r--httemplate/view/cust_main/payment_history.html126
-rw-r--r--httemplate/view/elements/svc_Common.html36
-rw-r--r--httemplate/view/svc_Common.html2
-rwxr-xr-xhttemplate/view/svc_acct.cgi5
-rw-r--r--httemplate/view/svc_broadband.cgi35
104 files changed, 2002 insertions, 712 deletions
diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html
index 4a7cedf79..41a9f3e95 100644
--- a/httemplate/edit/agent_payment_gateway.html
+++ b/httemplate/edit/agent_payment_gateway.html
@@ -34,6 +34,7 @@ for <SELECT NAME="cardtype" MULTIPLE>
% "Switch",
% "Solo",
% 'ACH',
+% 'PayPal',
%) {
<OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %>
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/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..7cfab71d8 100644
--- a/httemplate/edit/payment_gateway.html
+++ b/httemplate/edit/payment_gateway.html
@@ -13,15 +13,16 @@
'gateway_action' => 'Action',
'gateway_options' => 'Options (Name/Value pairs, <BR>one element per line)',
'gateway_callback_url' => 'Callback URL',
+ 'gateway_cancel_url' => 'Cancel URL',
},
)
%>
<SCRIPT TYPE="text/javascript">
- var modulesForNamespace = <% to_json(\%modules_for_namespace, {canonical=>1}) %>;
- function changeNamespace(what) {
- var ns = what.value;
+ var modulesForNamespace = <% $json->encode(\%modules) %>;
+ function changeNamespace() {
+ var ns = document.getElementById('gateway_namespace').value;
var select_module = document.getElementById('gateway_module');
select_module.options.length = 0;
for (var x in modulesForNamespace[ns]) {
@@ -30,6 +31,7 @@
select_module.add(o, null);
}
}
+ window.onload = changeNamespace;
</SCRIPT>
<%init>
@@ -37,69 +39,71 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-my %modules = (
- '2CheckOut' => 'Business::OnlinePayment',
- 'AuthorizeNet' => 'Business::OnlinePayment',
- 'BankOfAmerica' => 'Business::OnlinePayment', #deprecated?
- 'Beanstream' => 'Business::OnlinePayment',
- 'Capstone' => 'Business::OnlinePayment',
- 'Cardstream' => 'Business::OnlinePayment',
- 'CashCow' => 'Business::OnlinePayment',
- 'CyberSource' => 'Business::OnlinePayment',
- 'eSec' => 'Business::OnlinePayment',
- 'eSelectPlus' => 'Business::OnlinePayment',
- 'eWayShared' => 'Business::OnlineThirdPartyPayment',
- 'ElavonVirtualMerchant' => 'Business::OnlinePayment',
- 'Exact' => 'Business::OnlinePayment',
- 'iAuthorizer' => 'Business::OnlinePayment',
- 'Ingotz' => 'Business::OnlinePayment',
- 'InternetSecure' => 'Business::OnlinePayment',
- 'Interswitchng' => 'Business::OnlineThirdPartyPayment',
- 'IPaymentTPG' => 'Business::OnlinePayment',
- 'IPPay' => 'Business::OnlinePayment',
- 'Iridium' => 'Business::OnlinePayment',
- 'Jettis' => 'Business::OnlinePayment',
- 'Jety' => 'Business::OnlinePayment',
- 'LinkPoint' => 'Business::OnlinePayment',
- 'MerchantCommerce' => 'Business::OnlinePayment',
- 'Network1Financial' => 'Business::OnlinePayment',
- 'OCV' => 'Business::OnlinePayment',
- 'OpenECHO' => 'Business::OnlinePayment',
- 'PayConnect' => 'Business::OnlinePayment',
- 'PayflowPro' => 'Business::OnlinePayment',
- 'PaymenTech' => 'Business::OnlinePayment',
- 'PaymentsGateway' => 'Business::OnlinePayment',
- 'PayPal' => 'Business::OnlinePayment',
- #'PaySystems' => 'Business::OnlinePayment',
- 'PlugnPay' => 'Business::OnlinePayment',
- 'PPIPayMover' => 'Business::OnlinePayment',
- 'Protx' => 'Business::OnlinePayment', #now SagePay
- 'PXPost' => 'Business::OnlinePayment',
- 'SagePay' => 'Business::OnlinePayment',
- 'SecureHostingUPG' => 'Business::OnlinePayment',
- 'Skipjack' => 'Business::OnlinePayment',
- 'StGeorge' => 'Business::OnlinePayment',
- 'SurePay' => 'Business::OnlinePayment',
- 'TCLink' => 'Business::OnlinePayment',
- 'TransactionCentral' => 'Business::OnlinePayment',
- 'TransFirsteLink' => 'Business::OnlinePayment',
- 'Vanco' => 'Business::OnlinePayment',
- 'viaKLIX' => 'Business::OnlinePayment',
- 'VirtualNet' => 'Business::OnlinePayment',
- 'WesternACH' => 'Business::OnlinePayment',
- 'WorldPay' => 'Business::OnlinePayment',
-
- 'KeyBank' => 'Business::BatchPayment',
- 'Paymentech' => 'Business::BatchPayment',
- 'TD_EFT' => 'Business::BatchPayment',
+my $json = JSON::XS->new;
+$json->canonical(1);
+my %modules = (
+ 'Business::OnlinePayment' => [
+ '2CheckOut',
+ 'AuthorizeNet',
+ 'BankOfAmerica', #deprecated?
+ 'Beanstream',
+ 'Capstone',
+ 'Cardstream',
+ 'CashCow',
+ 'CyberSource',
+ 'eSec',
+ 'eSelectPlus',
+ 'ElavonVirtualMerchant',
+ 'Exact',
+ 'iAuthorizer',
+ 'Ingotz',
+ 'InternetSecure',
+ 'IPaymentTPG',
+ 'IPPay',
+ 'Iridium',
+ 'Jettis',
+ 'Jety',
+ 'LinkPoint',
+ 'MerchantCommerce',
+ 'Network1Financial',
+ 'OCV',
+ 'OpenECHO',
+ 'PayConnect',
+ 'PayflowPro',
+ 'PaymenTech',
+ 'PaymentsGateway',
+ 'PayPal',
+ #'PaySystems',
+ 'PlugnPay',
+ 'PPIPayMover',
+ 'Protx', #now SagePay
+ 'PXPost',
+ 'SagePay',
+ 'SecureHostingUPG',
+ 'Skipjack',
+ 'StGeorge',
+ 'SurePay',
+ 'TCLink',
+ 'TransactionCentral',
+ 'TransFirsteLink',
+ 'Vanco',
+ 'viaKLIX',
+ 'VirtualNet',
+ 'WesternACH',
+ 'WorldPay',
+ ],
+ 'Business::OnlineThirdPartyPayment' => [
+ 'eWayShared',
+ 'Interswitchng',
+ 'PayPal',
+ ],
+ 'Business::BatchPayment' => [
+ 'KeyBank',
+ 'Paymentech',
+ 'TD_EFT',
+ ],
);
-my %modules_for_namespace;
-for (keys %modules) {
- $modules_for_namespace{$modules{$_}} ||= [];
- push @{ $modules_for_namespace{$modules{$_}} }, $_;
-}
-
my @actions = (
'Normal Authorization',
'Authorization Only',
@@ -125,7 +129,9 @@ my $fields = [
{
field => 'gateway_module',
type => 'select',
- options => [ sort { lc($a) cmp lc ($b) } keys %modules ],
+ # does it even make sense to list all modules here?
+ options => [ sort { lc($a) cmp lc ($b) }
+ map { @$_ } values %modules ],
},
'gateway_username',
'gateway_password',
@@ -140,6 +146,11 @@ my $fields = [
size => 40,
},
{
+ field => 'gateway_cancel_url',
+ type => 'text',
+ size => 40,
+ },
+ {
field => 'gateway_options',
type => 'textarea',
rows => '12',
diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html
index 2770f3283..c893f13a2 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({
'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..fd1b8740e 100644
--- a/httemplate/edit/process/cust_location.cgi
+++ b/httemplate/edit/process/cust_location.cgi
@@ -31,10 +31,9 @@ die "unknown locationnum $locationnum" unless $cust_location;
my $new = FS::cust_location->new({
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);
+my $error = $new->find_or_insert;
+$error ||= $cust_location->move_to($new);
</%init>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 054973f23..d295ed317 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -11,7 +11,7 @@
<%once>
my $me = '[edit/process/cust_main.cgi]';
-my $DEBUG = 0;
+my $DEBUG = 1;
</%once>
<%init>
@@ -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(\%hash);
}
if ( ($cgi->param('same') || '') eq 'Y' ) {
diff --git a/httemplate/edit/process/detach-cust_pkg.html b/httemplate/edit/process/detach-cust_pkg.html
new file mode 100644
index 000000000..ab87eb536
--- /dev/null
+++ b/httemplate/edit/process/detach-cust_pkg.html
@@ -0,0 +1,47 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). 'misc/detach_pkg.html?'. $cgi->query_string );
+% } else {
+
+ <% header(emt("Package detached")) %>
+ <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;
+
+my $cust_location = new FS::cust_location {
+ map { $_ => scalar($cgi->param($_)) } FS::cust_main->location_fields
+};
+
+my $cust_main = new FS::cust_main {
+ ( map { ( $_, scalar($cgi->param($_)) ) } fields('cust_main') ),
+ ( map { ( "ship_$_", '' ) } FS::cust_main->location_fields ),
+ 'bill_location' => $cust_location,
+ 'ship_location' => $cust_location,
+};
+
+my $pkg_or_error = $cust_pkg->change( {
+ 'keep_dates' => 1,
+ 'cust_main' => $cust_main,
+} );
+
+my $error = ref($pkg_or_error) ? '' : $pkg_or_error;
+
+</%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/payment_gateway.html b/httemplate/edit/process/payment_gateway.html
index 812c988c5..157449e89 100644
--- a/httemplate/edit/process/payment_gateway.html
+++ b/httemplate/edit/process/payment_gateway.html
@@ -15,6 +15,7 @@ my $args_callback = sub {
my @options = split(/\r?\n/, $cgi->param('gateway_options') );
pop @options
if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
+ @options = ( {} ) if !@options;
(@options)
};
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
index 2dadbccdc..14dbda166 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({
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..09398fdfb 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({
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/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 14d36c31d..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' ]
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..48469dc04 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;
@@ -181,6 +174,8 @@ for( $i = 0; $i < @$tiers; $i++ ) {
$children_of{$key}->{''} = $tier->{empty_label};
}
}
+ # ensure that there's always at least one empty label
+ $children_of{''}->{''} = $tier->{empty_label};
}
$tier->{by_key} = \%children_of;
}
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 ef06441c8..0b2f1f18c 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -23,10 +23,12 @@ 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);
% }
@@ -34,9 +36,6 @@ function custnum_update_callback(rownum, prefix) {
function invnum_update_callback(rownum, prefix) {
custnum_update_callback(rownum, prefix);
- var enable = document.getElementById('enable_app'+rownum);
- enable.checked = true;
- toggle_application_row.call(enable);
}
function select_discount_term(row, prefix) {
@@ -96,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);
}
}
diff --git a/httemplate/misc/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi
index 4919c6632..4b3084f00 100755
--- a/httemplate/misc/cancel-unaudited.cgi
+++ b/httemplate/misc/cancel-unaudited.cgi
@@ -15,19 +15,32 @@ my($query) = $cgi->keywords;
$query =~ /^(\d+)$/;
my $svcnum = $1;
-#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum});
-#die "Unknown svcnum!" unless $svc_acct;
-
+my $error = '';
my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
-die "Unknown svcnum!" unless $cust_svc;
-my $cust_pkg = $cust_svc->cust_pkg;
-if ( $cust_pkg ) {
- errorpage( 'This account has already been audited. Cancel the '.
- qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum.
- '#cust_pkg'. $cust_pkg->pkgnum. '">'.
- 'package</A> instead.');
-}
+if ( $cust_svc ) {
+ my $cust_pkg = $cust_svc->cust_pkg;
+ if ( $cust_pkg ) {
+ errorpage( 'This account has already been audited. Cancel the '.
+ qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum.
+ '#cust_pkg'. $cust_pkg->pkgnum. '">'.
+ 'package</A> instead.'); #'
+ }
-my $error = $cust_svc->cancel;
+ $error = $cust_svc->cancel;
+} else {
+ # the rare obscure case: svc_x without cust_svc
+ my $svc_x;
+ foreach my $svcdb (FS::part_svc->svc_tables) {
+ $svc_x = qsearchs($svcdb, { 'svcnum' => $svcnum });
+ last if $svc_x;
+ }
+ if ( $svc_x ) {
+ $error = $svc_x->return_inventory
+ || $svc_x->FS::Record::delete;
+ } else {
+ # the svcnum really doesn't exist
+ $error = "svcnum $svcnum not found";
+ }
+}
</%init>
diff --git a/httemplate/misc/change_pkg_contact.html b/httemplate/misc/change_pkg_contact.html
new file mode 100755
index 000000000..c88140ebf
--- /dev/null
+++ b/httemplate/misc/change_pkg_contact.html
@@ -0,0 +1,70 @@
+<& /elements/header-popup.html, mt("Change Package Contact") &>
+
+<& /elements/error.html &>
+
+<FORM ACTION="<% $p %>misc/process/change_pkg_contact.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right"><% mt('Package') |h %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+% if ( $cust_pkg->contactnum ) {
+ <TR>
+ <TH ALIGN="right"><% mt('Current Contact') %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+ <% $cust_pkg->contact_obj->line |h %>
+ </TD>
+ </TR>
+% }
+
+<& /elements/tr-select-contact.html,
+ 'label' => mt('New Contact'), #XXX test
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_pkg->cust_main,
+&>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE = "submit"
+ VALUE = "<% $cust_pkg->contactnum ? mt("Change contact") : mt("Add contact") |h %>"
+>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg =
+ qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die "unknown pkgnum $pkgnum";
+
+my $cust_main = $cust_pkg->cust_main
+ or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+ " ( pkgnum ". cust_pkg->pkgnum. ")";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/choose_tax_location.html b/httemplate/misc/choose_tax_location.html
index 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/cust_main-merge.html b/httemplate/misc/cust_main-merge.html
index 4decbef7a..3b4425fc8 100755
--- a/httemplate/misc/cust_main-merge.html
+++ b/httemplate/misc/cust_main-merge.html
@@ -31,7 +31,17 @@ if ( $cgi->param('new_custnum') =~ /^(\d+)$/ ) {
} );
die "No customer # $custnum" unless $cust_main;
- $error = $cust_main->merge($new_custnum);
+ if ( $cgi->param('merge') eq 'Y' ) {
+
+ #old-style merge: everything + delete old customer
+ $error = $cust_main->merge($new_custnum);
+
+ } else {
+
+ #new-style attach: move packages 3.0 style, that's it
+ $error = $cust_main->attach_pkgs($new_custnum);
+
+ }
} else {
$error = 'Select a customer to merge into';
diff --git a/httemplate/misc/delete-note.html b/httemplate/misc/delete-note.html
new file mode 100644
index 000000000..436326ff1
--- /dev/null
+++ b/httemplate/misc/delete-note.html
@@ -0,0 +1,11 @@
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer note');
+
+my ($notenum) = $cgi->keywords;
+$notenum =~ /^\d+$/ or die "bad notenum '$notenum'";
+my $note = FS::cust_main_note->by_key($notenum)
+ or die "notenum '$notenum' not found";
+$note->delete;
+</%init>
+<% $cgi->redirect($p.'view/cust_main.cgi?'.$note->custnum) %>
diff --git a/httemplate/misc/detach_pkg.html b/httemplate/misc/detach_pkg.html
new file mode 100755
index 000000000..64b3e6e3f
--- /dev/null
+++ b/httemplate/misc/detach_pkg.html
@@ -0,0 +1,104 @@
+<& /elements/header-popup.html, mt("Detach Package to New Customer") &>
+
+<SCRIPT TYPE="text/javascript" SRC="../elements/order_pkg.js"></SCRIPT>
+
+<& /elements/error.html &>
+
+<FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/detach-cust_pkg.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+% foreach my $f (qw( agentnum refnum )) {
+ <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $cust_main->$f() %>">
+% }
+<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<% $cust_main->custnum %>">
+% foreach my $f (FS::cust_main->location_fields) {
+ <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $loc->$f() |h %>">
+% }
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right"><% mt('Package') |h %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+% #always should be present for detaching, yes? #if ( $cust_pkg->contactnum ) {
+% my $cust_contact = $cust_pkg->contact_obj;
+
+ <INPUT TYPE="hidden" NAME="first" VALUE="<% $cust_contact->get('first') |h %>">
+ <INPUT TYPE="hidden" NAME="last" VALUE="<% $cust_contact->get('last') |h %>">
+
+ <TR>
+ <TH ALIGN="right"><% mt('Name') %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+ <% $cust_pkg->contact_obj->line |h %>
+ </TD>
+ </TR>
+% #}
+
+ <TR>
+ <TH ALIGN="right" VALIGN="top"><% mt('Address') %></TH>
+ <TD COLSPAN=7 BGCOLOR="#dddddd">
+
+ <% $loc->location_label( 'join_string' => '<BR>',
+ 'double_space' => ' &nbsp; ',
+ 'escape_function' => \&encode_entities,
+ 'countrydefault' => $countrydefault,
+ )
+ %>
+ </TD>
+ </TR>
+
+</TABLE>
+
+%#XXX payment info
+%#XXX should be sticky on errors...
+<& /edit/cust_main/billing.html, FS::cust_main->new({}),
+ invoicing_list => [],
+
+&>
+
+<BR>
+<BR>
+<INPUT NAME = "submitButton"
+ TYPE = "submit"
+ VALUE = "<% mt("Detach package") |h %>"
+>
+
+%#and a cancel button? or is the popup close sufficient?
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+my $countrydefault = $conf->config('countrydefault') || 'US';
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg =
+ qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die "unknown pkgnum $pkgnum";
+
+my $loc = $cust_pkg->cust_location_or_main;
+
+my $cust_main = $cust_pkg->cust_main
+ or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+ " ( pkgnum ". cust_pkg->pkgnum. ")";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/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/merge_cust.html b/httemplate/misc/merge_cust.html
index ad075be2f..9c869fa21 100644
--- a/httemplate/misc/merge_cust.html
+++ b/httemplate/misc/merge_cust.html
@@ -1,6 +1,6 @@
-<% include('/elements/header-popup.html', 'Merge customer' ) %>
+<& /elements/header-popup.html, 'Merge customer' &>
-<% include('/elements/error.html') %>
+<& /elements/error.html &>
<FORM NAME="cust_merge_popup" ID="cust_merge_popup" ACTION="<% popurl(1) %>cust_main-merge.html" METHOD=POST onSubmit="submit_merge(); return false;">
@@ -35,13 +35,43 @@ function do_submit_merge() {
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
<TABLE BORDER="0" CELLSPACING="2" STYLE="margin-left:auto; margin-right:auto">
- <% include('/elements/tr-search-cust_main.html',
+
+ <& /elements/tr-search-cust_main.html,
'label' => 'Merge into: ',
'field' => 'new_custnum',
'find_button' => 1,
'curr_value' => scalar($cgi->param('new_custnum')),
- )
- %>
+ &>
+
+% if ( $conf->exists('deletecustomers') ) {
+
+% if ( scalar($cust_main->ncancelled_pkgs) ) {
+ <TR>
+ <TD COLSPAN=2>
+ <& /elements/radio.html,
+ 'field' => 'merge',
+ 'value' => '',
+ 'curr_value' => scalar($cgi->param('merge')),
+ &>
+ Merge packages only.
+ </TD>
+ </TR>
+% } else {
+% $cgi->param('merge', 'Y');
+% }
+
+ <TR>
+ <TD COLSPAN=2>
+ <& /elements/radio.html,
+ 'field' => 'merge',
+ 'value' => 'Y',
+ 'curr_value' => scalar($cgi->param('merge')),
+ &>
+ Merge invoices, payments/credits, notes, tickets and delete this customer.
+ </TD>
+ </TR>
+% }
+
</TABLE>
<P ALIGN="CENTER">
@@ -54,6 +84,8 @@ function do_submit_merge() {
<%init>
+my $conf = new FS::Conf;
+
$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
my $custnum = $1;
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index 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_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/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
index d8c8ef44c..0d830826c 100644
--- a/httemplate/misc/xmlhttp-cust_main-email_search.html
+++ b/httemplate/misc/xmlhttp-cust_main-email_search.html
@@ -1,4 +1,4 @@
-<% JSON::to_json(\@result) %>\
+<% encode_json(\@result) %>\
<%init>
die 'access denied'
unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
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/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_pay_pending.html b/httemplate/search/cust_pay_pending.html
index 2afce0ce9..54c9935ef 100755
--- a/httemplate/search/cust_pay_pending.html
+++ b/httemplate/search/cust_pay_pending.html
@@ -1,4 +1,4 @@
-<% include( 'elements/cust_pay_or_refund.html',
+<& elements/cust_pay_or_refund.html,
'thing' => 'pay_pending',
'amount_field' => 'paid',
'name_singular' => 'pending payment',
@@ -10,8 +10,7 @@
$status_sub,
],
'redirect_empty' => $redirect_empty,
- )
-%>
+&>
<%init>
my %statusaction = (
diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html
index e2a83b7de..b245d3114 100644
--- a/httemplate/search/cust_svc.html
+++ b/httemplate/search/cust_svc.html
@@ -96,6 +96,7 @@ if ( length( $cgi->param('search_svc') ) ) {
my $extra_sql = ' WHERE '. join(' AND ', @extra_sql );
$sql_query = {
+ 'select' => 'svcnum',
'table' => 'cust_svc',
'addl_from' => $addl_from,
'hashref' => {},
@@ -105,8 +106,8 @@ if ( length( $cgi->param('search_svc') ) ) {
}
$sql_query->{'select'} = join(', ',
- 'cust_svc.*',
- 'part_svc.*',
+ $sql_query->{'select'},
+ #'part_svc.*',
'cust_main.custnum',
FS::UI::Web::cust_sql_fields(),
);
@@ -117,14 +118,17 @@ my $count_query = "SELECT COUNT(*) FROM cust_svc ". $sql_query->{addl_from}.
my $link = sub {
my $cust_svc = shift;
- my $url = svc_url(
- 'm' => $m,
- 'action' => 'view',
- #'part_svc' => $cust_svc->part_svc,
- 'svcdb' => $cust_svc->svcdb, #we have it from the joined search
- #'svc' => $cust_svc, #redundant
- 'query' => '',
- );
+ my $url;
+ if ( $cust_svc->svcpart ) {
+ $url = svc_url(
+ 'm' => $m,
+ 'action' => 'view',
+ 'svcdb' => $cust_svc->svcdb, #we have it from the joined search
+ 'query' => '',
+ );
+ } else { # bizarre unlinked service case
+ $url = $p.'view/svc_Common.html?svcdb='.$cust_svc->svcdb.';svcnum=';
+ }
[ $url, 'svcnum' ];
};
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..493365281 100644
--- a/httemplate/search/elements/cust_main_dayranges.html
+++ b/httemplate/search/elements/cust_main_dayranges.html
@@ -2,10 +2,10 @@
Example:
- include( 'elements/cust_main_dayranges.html',
+ <& elements/cust_main_dayranges.html,
'title' => 'Accounts Receivable Aging Summary',
'range_sub' => $mysub,
- )
+ &>
my $mysub = sub {
my( $start, $end ) = @_;
@@ -44,7 +44,7 @@ Example:
$row->{'rangecol_60_90'} ),
sprintf( $money_char.'%.2f',
$row->{'rangecol_90_0'} ),
- sprintf( '<b>'. $money_char.'%.2f'. '</b>',
+ sprintf( '<b>'.$money_char.'%.2f</b>',
$row->{'rangecol_0_0'} ),
('') x @pay_labels,
],
@@ -81,6 +81,9 @@ Example:
'', '', '', '', 'b',
( map '', @pay_labels ),
],
+ 'xls_format' => [ (map '', FS::UI::Web::cust_styles),
+ '', '', '', '', { bold => 1 },
+ ],
'color' => [
FS::UI::Web::cust_colors(),
'',
@@ -162,6 +165,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-xls.html b/httemplate/search/elements/search-xls.html
index 26a51c4c7..bc844a579 100644
--- a/httemplate/search/elements/search-xls.html
+++ b/httemplate/search/elements/search-xls.html
@@ -6,6 +6,8 @@ my $header = $args{'header'};
my $rows = $args{'rows'};
my %opt = %{ $args{'opt'} };
+my $style = $opt{'style'};
+
my $override = scalar(@$rows) >= 65536 ? 'XLSX' : '';
my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override);
@@ -42,6 +44,12 @@ my $header_format = $workbook->add_format(
bg_color => 55, #22,
bottom => 3,
);
+my $footer_format = $workbook->add_format(
+ italic => 1,
+ locked => 1,
+ bg_color => 55,
+ top => 3,
+);
my $default_format = $workbook->add_format(locked => 0);
my %money_format;
@@ -50,10 +58,24 @@ my $money_char = FS::Conf->new->config('money_char') || '$';
my %date_format;
xl_parse_date_init();
+my %bold_format;
+
my $writer = sub {
# Wrapper for $worksheet->write.
# Do any massaging of the value/format here.
my ($r, $c, $value, $format) = @_;
+ #warn "writer called with format $format\n";
+
+ if ( $style->[$c] eq 'b' or $value =~ /<b>/i ) { # the only one in common use
+ $value =~ s[</?b>][]ig;
+ if ( !exists($bold_format{$format}) ) {
+ $bold_format{$format} = $workbook->add_format();
+ $bold_format{$format}->copy($format);
+ $bold_format{$format}->set_bold();
+ }
+ $format = $bold_format{$format};
+ }
+
# convert HTML entities
# both Spreadsheet::WriteExcel and Excel::Writer::XLSX accept UTF-8 strings
$value = decode_entities($value);
@@ -86,6 +108,7 @@ my $writer = sub {
# String: replace line breaks with newlines
$value =~ s/<BR>/\n/gi;
}
+ #warn "writing with format $format\n";
$worksheet->write($r, $c, $value, $format);
};
@@ -140,7 +163,7 @@ if ( $opt{'footer'} ) {
if ( ref($item) eq 'CODE' ) {
$item = &{$item}();
}
- $writer->( $r, $c++, $item, $header_format );
+ $writer->( $r, $c++, $item, $footer_format );
}
}
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/h_cust_pay.html b/httemplate/search/h_cust_pay.html
index 99330fadd..6d2dd9955 100755
--- a/httemplate/search/h_cust_pay.html
+++ b/httemplate/search/h_cust_pay.html
@@ -1,9 +1,8 @@
-<% include( 'elements/cust_pay_or_refund.html',
+<& elements/cust_pay_or_refund.html,
'table' => 'h_cust_pay',
'amount_field' => 'paid',
'name_singular' => 'payment',
'name_verb' => 'paid',
'pre_header' => [ 'Transaction', 'By' ],
'pre_fields' => [ 'history_action', 'history_user' ],
- )
-%>
+&>
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/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_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/unapplied_cust_pay.html b/httemplate/search/unapplied_cust_pay.html
index e232291fe..f5c2bf0f9 100755
--- a/httemplate/search/unapplied_cust_pay.html
+++ b/httemplate/search/unapplied_cust_pay.html
@@ -1,9 +1,8 @@
-<% include( 'elements/cust_main_dayranges.html',
+<& elements/cust_main_dayranges.html,
#'title' => 'Prepaid Balance Aging Summary', #???
'title' => 'Unapplied Payments Aging Summary',
'range_sub' => \&unapplied_payments,
- )
-%>
+&>
<%init>
die "access denied"
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.cgi b/httemplate/view/cust_main.cgi
index ec3191971..be0100fb3 100755
--- a/httemplate/view/cust_main.cgi
+++ b/httemplate/view/cust_main.cgi
@@ -91,14 +91,19 @@ function areyousure(href, message) {
&> |
% }
-% if ( $curuser->access_right('Merge customer') ) {
+% if ( $curuser->access_right('Merge customer')
+% and ( scalar($cust_main->ncancelled_pkgs)
+% || $conf->exists('deletecustomers')
+% )
+% )
+% {
<& /elements/popup_link-cust_main.html,
{ 'action' => $p. 'misc/merge_cust.html',
'label' => emt('Merge this customer'),
'actionlabel' => emt('Merge customer'),
'cust_main' => $cust_main,
- 'width' => 480,
- 'height' => 192,
+ 'width' => 569,
+ 'height' => 210,
}
&> |
% }
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/locations.html b/httemplate/view/cust_main/locations.html
index b29d0ce4d..689c9a390 100755
--- a/httemplate/view/cust_main/locations.html
+++ b/httemplate/view/cust_main/locations.html
@@ -36,7 +36,7 @@ STYLE="padding-bottom: 0px;
% }
</SPAN></TH></TR>
% if (@$packages) {
-<& packages/section.html, 'packages' => $packages &>
+<& packages/section.html, 'packages' => $packages, 'cust_main' => $cust_main &>
% }
</TABLE><BR>
% } #foreach $locationnum
diff --git a/httemplate/view/cust_main/notes.html b/httemplate/view/cust_main/notes.html
index 1e9f464db..2de68ff46 100755
--- a/httemplate/view/cust_main/notes.html
+++ b/httemplate/view/cust_main/notes.html
@@ -63,7 +63,11 @@
%
% my $edit = '';
% if ($curuser->access_right('Edit customer note') ) {
-% $edit = qq! <A HREF="javascript:void(0);" $clickjs>(!.emt('edit').')</A>';
+% my $delete_url = $fsurl.'misc/delete-note.html?'.$notenum;
+% $edit = qq! <A HREF="javascript:void(0);" $clickjs>(!.emt('edit').')</A>'.
+% qq! <A HREF="$delete_url" !.
+% qq! onclick="return confirm('Delete this note?')">!.
+% '('.emt('delete').')</A>';
% }
%
% if ( $last_classnum != $note->classnum && !$skipheader ) {
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..fe8b71534
--- /dev/null
+++ b/httemplate/view/cust_main/packages/contact.html
@@ -0,0 +1,87 @@
+% if ( $contact ) {
+ <% $contact->line |h %>
+% if ( $show_change_link ) {
+ <FONT SIZE=-1>
+ (&nbsp;<%pkg_change_contact_link($cust_pkg)%>&nbsp;)
+ </FONT>
+% }
+% if ( $show_detach_link ) {
+ <FONT SIZE=-1>
+ (&nbsp;<%pkg_detach_link($cust_pkg)%>&nbsp;)
+ </FONT>
+% }
+% } elsif ( $show_contact_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_change_link =
+ ! $cust_pkg->get('cancel')
+ && $FS::CurrentUser::CurrentUser->access_right('Change customer package');
+
+my $show_detach_link =
+ ! $cust_pkg->get('cancel')
+ && $FS::CurrentUser::CurrentUser->access_right('Detach customer package');
+
+my $show_contact_link =
+ ! $cust_pkg->get('cancel')
+ ; #&& $FS::CurrentUser::CurrentUser->access_right('Add package contact'); #or something like that
+
+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('Add contact'),
+ 'cust_pkg' => $cust_pkg,
+ 'width' => 616,
+ 'height' => 192,
+ );
+}
+
+sub pkg_detach_link {
+ my $cust_pkg = shift;
+ #my $pkgpart = $cust_pkg->pkgpart;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/detach_pkg.html",
+ 'label' => emt('Detach'),
+ 'actionlabel' => emt('Detach'),
+ 'cust_pkg' => $cust_pkg,
+ 'width' => 616,
+ 'height' => 684,
+ );
+}
+
+#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 0b72d195e..520305a9a 100644
--- a/httemplate/view/cust_main/packages/package.html
+++ b/httemplate/view/cust_main/packages/package.html
@@ -186,11 +186,6 @@
% !$supplemental and
% $part_pkg->freq ne '0' ) {
<TR>
-% if ( !$opt{'show_location'} ) {
- <TD><FONT SIZE="-1">
- (&nbsp;<% pkg_change_location_link($cust_pkg) %>&nbsp;)
- </FONT></TD>
-% }
% if ( FS::Conf->new->exists('invoice-unitprice') ) {
<TD><FONT SIZE="-1">
(&nbsp;<% pkg_change_quantity_link($cust_pkg) %>&nbsp;)
diff --git a/httemplate/view/cust_main/packages/section.html b/httemplate/view/cust_main/packages/section.html
index 52246192f..391a13b5f 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 &><BR>
+ <& 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 {
@@ -60,6 +59,15 @@ my $show_location = $opt{'show_location'};
( $a->getfield('pkgnum') <=> $b->getfield('pkgnum') )
} @$packages;
+my %change_custnum = map { $_->change_custnum => 1 }
+ grep { $_->change_custnum }
+ grep { $_->getfield('cancel') }
+ @$packages;
+
+my $pkg_attached = ( scalar(keys %change_custnum) == 1
+ && ! grep { ! $_->getfield('cancel') } @$packages
+ );
+
my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
my %conf_opt = (
@@ -68,6 +76,7 @@ my %conf_opt = (
|| $curuser->option('cust_pkg-display_times')),
#for status.html
'cust_pkg-show_autosuspend' => $conf->exists('cust_pkg-show_autosuspend'),
+ 'pkg_attached' => $pkg_attached,
#for status.html pkg-balances
'pkg-balances' => $conf->exists('pkg-balances'),
'money_char' => ( $conf->config('money_char') || '$' ),
@@ -84,10 +93,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..c0213e90b 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
@@ -14,6 +14,8 @@
<% pkg_status_row($cust_pkg, emt('Cancelled'), 'cancel', 'color'=>'FF0000', %opt ) %>
+ <% pkg_status_row_detached($cust_pkg, %opt) %>
+
<% pkg_reason_row($cust_pkg, $cpr, color => 'ff0000', %opt) %>
% unless ( $cust_pkg->get('setup') ) {
@@ -29,7 +31,7 @@
% }
%
-% if ( $part_pkg->freq and !$supplemental ) { #?
+% if ( $part_pkg->freq && !$supplemental && !$cust_pkg->change_custnum ) { #?
<TR>
<TD COLSPAN=<%$opt{colspan}%>>
@@ -360,6 +362,37 @@ sub pkg_status_row_changed {
$html;
}
+sub pkg_status_row_detached {
+ my( $cust_pkg, %opt ) = @_;
+
+ return '' unless $cust_pkg->change_custnum;
+
+ my $html = '';
+
+ my $cust_main = $cust_pkg->change_cust_main;
+ if ( $cust_main ) {
+
+ my $cust_link = '<A HREF="cust_main.cgi?'. $cust_pkg->change_custnum. '">'.
+ encode_entities( $cust_main->name ).
+ '</A>';
+
+ my $what = $opt{'pkg_attached'} ? 'Attached' : 'Detached';
+
+ $html .= pkg_status_row_colspan( $cust_pkg,
+ emt("$what to customer #[_1]: ",
+ $cust_pkg->change_custnum
+ ).
+ $cust_link,
+ '',
+ 'size' => '-1',
+ 'align' => 'right',
+ 'colspan' => 4,
+ );
+ }
+
+ $html;
+}
+
sub pkg_status_row_noauto {
my( $cust_pkg, %opt ) = @_;
my $part_pkg = $opt{'part_pkg'};
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
index 66008ee29..915be49e5 100644
--- a/httemplate/view/cust_main/payment_history.html
+++ b/httemplate/view/cust_main/payment_history.html
@@ -228,36 +228,8 @@
%#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;
@@ -267,18 +239,9 @@
%
% $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,12 +511,40 @@ foreach my $cust_refund ($cust_main->cust_refund) {
}
-# sort history
+# 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 {
- @history = sort { $a->{date} <=> $b->{date} } @history;
-} # no other sort orders for now
+} # else it's already oldest-first, and there are no other options yet
sub translate_payby {
my ($payby,$payinfo) = (shift,shift);
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
index d735195fe..997ac142a 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -51,8 +51,10 @@ function areyousure(href) {
% }
<% mt('Service #') |h %><B><% $svcnum %></B>
-% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
+% if ( $custnum ) {
+% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
<& /view/elements/svc_edit_link.html, 'svc' => $svc_x, 'edit_url' => $url &>
+% }
<BR>
<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
@@ -127,7 +129,9 @@ function areyousure(href) {
% }
+% if ( $cust_svc ) {
<& /elements/table-tickets.html, object => $cust_svc &>
+% }
<% joblisting({'svcnum'=>$svcnum}, 1) %>
@@ -150,7 +154,7 @@ my $fields = $opt{'fields'}
my $svcnum;
if ( $cgi->param('svcnum') ) {
- $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparsable svcnum";
+ $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparseable svcnum";
$svcnum = $1;
} else {
my($query) = $cgi->keywords;
@@ -170,19 +174,29 @@ my $svc_x = qsearchs({
}) or die "Unknown svcnum $svcnum in ". $opt{'table'}. " table\n";
my $cust_svc = $svc_x->cust_svc;
-my($label, $value, $svcdb) = $cust_svc->label;
+my ($label, $value, $svcdb, $part_svc );
+my $labels = $opt{labels}; #not -> here
-my $part_svc = $cust_svc->part_svc;
+if ( $cust_svc ) {
+ ($label, $value, $svcdb) = $cust_svc->label;
-#false laziness w/edit/svc_Common.html
-#override default labels with service-definition labels if applicable
-my $labels = $opt{labels}; #not -> here
-foreach my $field ( keys %$labels ) {
- my $col = $part_svc->part_svc_column($field);
- $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\s*$/;
+ $part_svc = $cust_svc->part_svc;
+
+ #false laziness w/edit/svc_Common.html
+ #override default labels with service-definition labels if applicable
+ foreach my $field ( keys %$labels ) {
+ my $col = $part_svc->part_svc_column($field);
+ $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\s*$/;
+ }
+} else {
+ $label = "Unlinked $table";
+ $value = $svc_x->label;
+ $svcdb = $table;
+ # just to satisfy callbacks
+ $part_svc = FS::part_svc->new({ svcpart => 0, svcdb => $table });
}
-my $pkgnum = $cust_svc->pkgnum;
+my $pkgnum = $cust_svc->pkgnum if $cust_svc;
my($cust_pkg, $custnum);
if ($pkgnum) {
diff --git a/httemplate/view/svc_Common.html b/httemplate/view/svc_Common.html
index 7b46dc9c9..7e300b049 100644
--- a/httemplate/view/svc_Common.html
+++ b/httemplate/view/svc_Common.html
@@ -7,7 +7,7 @@
# false laziness w/edit/svc_Common.html
-$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb";
+$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparseable svcdb";
my $table = $1;
require "FS/$table.pm";
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 ) {