summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rw-r--r--httemplate/browse/msgcat.html2
-rwxr-xr-xhttemplate/browse/part_export.cgi15
-rwxr-xr-xhttemplate/browse/part_pkg.cgi111
-rw-r--r--httemplate/browse/part_pkg_usage.html112
-rwxr-xr-xhttemplate/browse/part_svc.cgi27
-rw-r--r--httemplate/browse/rate_region.html8
-rw-r--r--httemplate/docs/license.html2
-rwxr-xr-xhttemplate/edit/REAL_cust_pkg.cgi89
-rw-r--r--httemplate/edit/bulk-part_pkg.html74
-rw-r--r--httemplate/edit/credit-cust_bill_pkg.html5
-rwxr-xr-xhttemplate/edit/cust_location.cgi28
-rwxr-xr-xhttemplate/edit/cust_main.cgi41
-rw-r--r--httemplate/edit/cust_main/billing.html29
-rw-r--r--httemplate/edit/cust_main/bottomfixup.js4
-rw-r--r--httemplate/edit/cust_main/choose_tax_location.html87
-rw-r--r--httemplate/edit/cust_main/top_misc.html57
-rwxr-xr-xhttemplate/edit/cust_pkg.cgi36
-rw-r--r--httemplate/edit/cust_pkg_detail.html2
-rwxr-xr-xhttemplate/edit/cust_pkg_quantity.html49
-rwxr-xr-xhttemplate/edit/cust_refund.cgi8
-rw-r--r--httemplate/edit/elements/edit.html1
-rw-r--r--httemplate/edit/elements/part_svc_column.html303
-rw-r--r--httemplate/edit/elements/svc_Common.html27
-rw-r--r--httemplate/edit/part_export.cgi81
-rwxr-xr-xhttemplate/edit/part_pkg.cgi106
-rwxr-xr-xhttemplate/edit/part_svc.cgi541
-rw-r--r--httemplate/edit/part_tag.html2
-rw-r--r--httemplate/edit/payment_gateway.html2
-rw-r--r--httemplate/edit/phone_device.html17
-rwxr-xr-xhttemplate/edit/process/REAL_cust_pkg.cgi57
-rw-r--r--httemplate/edit/process/bulk-part_pkg.html30
-rw-r--r--httemplate/edit/process/change-cust_pkg.html4
-rw-r--r--httemplate/edit/process/credit-cust_bill_pkg.html2
-rw-r--r--httemplate/edit/process/cust_location.cgi5
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi38
-rw-r--r--httemplate/edit/process/cust_pkg_quantity.html33
-rw-r--r--httemplate/edit/process/cust_svc.cgi2
-rw-r--r--httemplate/edit/process/elements/process.html3
-rw-r--r--httemplate/edit/process/elements/svc_Common.html5
-rw-r--r--httemplate/edit/process/part_export.cgi1
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi17
-rw-r--r--httemplate/edit/process/part_pkg_usage.html67
-rw-r--r--httemplate/edit/process/quick-cust_pkg.cgi18
-rw-r--r--httemplate/edit/process/svc_phone.html17
-rw-r--r--httemplate/edit/quick-charge.html1
-rw-r--r--httemplate/edit/rate_region.cgi8
-rwxr-xr-xhttemplate/edit/svc_acct.cgi18
-rw-r--r--httemplate/edit/svc_broadband.cgi8
-rw-r--r--httemplate/edit/svc_phone.cgi10
-rw-r--r--httemplate/elements/auto-table.html63
-rw-r--r--httemplate/elements/change_history_common.html15
-rw-r--r--httemplate/elements/change_password.html41
-rw-r--r--httemplate/elements/checkbox-tristate.html78
-rw-r--r--httemplate/elements/contact.html6
-rw-r--r--httemplate/elements/dashboard-toplist.html3
-rw-r--r--httemplate/elements/fckeditor/fckeditor.js2
-rw-r--r--httemplate/elements/location.html7
-rw-r--r--httemplate/elements/menu.html23
-rw-r--r--httemplate/elements/order_pkg.js1
-rw-r--r--httemplate/elements/progress-init.html2
-rw-r--r--httemplate/elements/random_pass.html17
-rw-r--r--httemplate/elements/search-svc_broadband.html204
-rw-r--r--httemplate/elements/select-areacode.html4
-rw-r--r--httemplate/elements/select-did.html167
-rw-r--r--httemplate/elements/select-exchange.html4
-rw-r--r--httemplate/elements/select-mac.html2
-rw-r--r--httemplate/elements/select-part_svc.html5
-rw-r--r--httemplate/elements/select-phonenum.html4
-rw-r--r--httemplate/elements/select-region.html4
-rw-r--r--httemplate/elements/select-table.html3
-rw-r--r--httemplate/elements/select-tiered.html7
-rw-r--r--httemplate/elements/selectlayers.html2
-rw-r--r--httemplate/elements/standardize_locations.js51
-rw-r--r--httemplate/elements/tr-cust_svc.html3
-rw-r--r--httemplate/elements/tr-input-beginning_ending.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-did.html4
-rw-r--r--httemplate/elements/tr-select-discount_term.html4
-rw-r--r--httemplate/elements/tr-select-from_to.html2
-rw-r--r--httemplate/elements/tr-select-inventory_item.html48
-rw-r--r--httemplate/elements/tr-select-part_svc.html7
-rwxr-xr-xhttemplate/elements/tr-select-reason.html5
-rw-r--r--httemplate/elements/tr-select-voip_class.html3
-rw-r--r--httemplate/misc/areacodes.cgi2
-rw-r--r--httemplate/misc/batch-cust_pay.html27
-rwxr-xr-xhttemplate/misc/change_pkg.cgi3
-rwxr-xr-xhttemplate/misc/change_pkg_contact.html70
-rw-r--r--httemplate/misc/choose_tax_location.html3
-rw-r--r--httemplate/misc/confirm-address_standardize.html12
-rwxr-xr-xhttemplate/misc/confirm-cust_pkg-edit_dates.html289
-rw-r--r--httemplate/misc/cust-part_pkg.cgi2
-rw-r--r--httemplate/misc/email-customers.html8
-rw-r--r--httemplate/misc/exchanges.cgi2
-rw-r--r--httemplate/misc/location.cgi7
-rw-r--r--httemplate/misc/macinventory.cgi11
-rw-r--r--httemplate/misc/maestro-customer_status.html2
-rw-r--r--httemplate/misc/manage_cust_email.html106
-rw-r--r--httemplate/misc/order_pkg.html9
-rw-r--r--httemplate/misc/part_export/huawei_hlr-import_sim.html52
-rw-r--r--httemplate/misc/part_export/process/huawei_hlr-import_sim.html10
-rw-r--r--httemplate/misc/part_svc-columns.cgi2
-rw-r--r--httemplate/misc/phonenums.cgi2
-rw-r--r--httemplate/misc/process/change-password.html26
-rw-r--r--httemplate/misc/process/change_pkg_contact.html49
-rw-r--r--httemplate/misc/process/manage_cust_email.html32
-rw-r--r--httemplate/misc/process/payment.cgi10
-rw-r--r--httemplate/misc/regions.cgi2
-rw-r--r--httemplate/misc/xmlhttp-address_standardize.html12
-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.html4
-rw-r--r--httemplate/misc/xmlhttp-cust_main-censustract.html2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-discount_terms.cgi2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-email_search.html29
-rw-r--r--httemplate/misc/xmlhttp-cust_main-search.cgi8
-rw-r--r--httemplate/misc/xmlhttp-ping.html2
-rw-r--r--httemplate/misc/xmlhttp-svc_broadband-search.cgi22
-rw-r--r--httemplate/pref/pref-process.html3
-rw-r--r--httemplate/pref/pref.html21
-rwxr-xr-xhttemplate/search/477.html20
-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.html16
-rwxr-xr-xhttemplate/search/477partVI_census.html6
-rw-r--r--httemplate/search/agent_commission.html197
-rw-r--r--httemplate/search/agent_inventory.html5
-rwxr-xr-xhttemplate/search/bill_batch.cgi6
-rw-r--r--httemplate/search/cdr.html38
-rwxr-xr-xhttemplate/search/cust_bill.html5
-rw-r--r--httemplate/search/cust_bill_event.cgi8
-rw-r--r--httemplate/search/cust_bill_pay.html23
-rw-r--r--httemplate/search/cust_bill_pkg.cgi49
-rw-r--r--httemplate/search/cust_bill_pkg_discount.html18
-rw-r--r--httemplate/search/cust_bill_pkg_referral.html10
-rwxr-xr-xhttemplate/search/cust_credit.html14
-rw-r--r--httemplate/search/cust_credit_bill.html14
-rw-r--r--httemplate/search/cust_credit_bill_pkg.html78
-rw-r--r--httemplate/search/cust_credit_refund.html23
-rw-r--r--httemplate/search/cust_event.html13
-rw-r--r--httemplate/search/cust_main-zip.html5
-rwxr-xr-xhttemplate/search/cust_main.cgi2
-rwxr-xr-xhttemplate/search/cust_main.html3
-rwxr-xr-xhttemplate/search/cust_pay_batch.cgi100
-rwxr-xr-xhttemplate/search/cust_pkg.cgi8
-rw-r--r--httemplate/search/cust_pkg_discount.html10
-rw-r--r--httemplate/search/cust_pkg_svc.html6
-rw-r--r--httemplate/search/cust_svc.html2
-rw-r--r--httemplate/search/cust_tax_adjustment.html7
-rw-r--r--httemplate/search/cust_tax_exempt.cgi10
-rw-r--r--httemplate/search/cust_tax_exempt_pkg.cgi12
-rw-r--r--httemplate/search/customer_accounting_summary.html4
-rw-r--r--httemplate/search/elements/checkbox-foot.html86
-rw-r--r--httemplate/search/elements/cust_main_dayranges.html12
-rw-r--r--httemplate/search/elements/cust_pay_batch_top.html1
-rwxr-xr-xhttemplate/search/elements/cust_pay_or_refund.html212
-rw-r--r--httemplate/search/elements/report_cust_pay_or_refund.html74
-rw-r--r--httemplate/search/elements/report_svc_Common.html122
-rw-r--r--httemplate/search/elements/search-html.html12
-rw-r--r--httemplate/search/elements/search.html7
-rw-r--r--httemplate/search/elements/svc_Common.html48
-rw-r--r--httemplate/search/employee_audit.html2
-rw-r--r--httemplate/search/inventory_item.html8
-rw-r--r--httemplate/search/mailinglistmember.html5
-rw-r--r--httemplate/search/part_pkg.html8
-rwxr-xr-xhttemplate/search/pay_batch.cgi19
-rw-r--r--httemplate/search/phone_avail.html10
-rw-r--r--httemplate/search/phone_inventory_provisioned.html6
-rw-r--r--httemplate/search/prepaid_income.html9
-rw-r--r--httemplate/search/prepay_credit.html6
-rw-r--r--httemplate/search/prospect_main.html5
-rwxr-xr-xhttemplate/search/qual.cgi6
-rw-r--r--httemplate/search/queue.html7
-rwxr-xr-xhttemplate/search/quotation.html2
-rw-r--r--httemplate/search/reg_code.html6
-rwxr-xr-xhttemplate/search/report_477.html4
-rw-r--r--httemplate/search/report_agent_commission.html22
-rw-r--r--httemplate/search/report_cust_bill.html2
-rwxr-xr-xhttemplate/search/report_cust_main.html40
-rw-r--r--httemplate/search/report_employee_audit.html2
-rw-r--r--httemplate/search/report_employee_commission.html2
-rwxr-xr-xhttemplate/search/report_receivables.html10
-rw-r--r--httemplate/search/report_sqlradius_usage.html9
-rwxr-xr-xhttemplate/search/report_svc_acct.html4
-rw-r--r--httemplate/search/report_svc_phone.html36
-rw-r--r--httemplate/search/report_svc_phone_usage.html32
-rwxr-xr-xhttemplate/search/report_tax.cgi11
-rwxr-xr-xhttemplate/search/report_tax.html14
-rw-r--r--httemplate/search/rt_ticket.html31
-rw-r--r--httemplate/search/rt_transaction.html5
-rw-r--r--httemplate/search/sql.html6
-rw-r--r--httemplate/search/sqlradius.cgi17
-rw-r--r--httemplate/search/sqlradius.html57
-rwxr-xr-xhttemplate/search/svc_acct.cgi2
-rwxr-xr-xhttemplate/search/svc_broadband.cgi8
-rwxr-xr-xhttemplate/search/svc_dish.cgi8
-rwxr-xr-xhttemplate/search/svc_domain.cgi8
-rwxr-xr-xhttemplate/search/svc_external.cgi9
-rwxr-xr-xhttemplate/search/svc_forward.cgi8
-rw-r--r--httemplate/search/svc_hardware.cgi9
-rw-r--r--httemplate/search/svc_phone.cgi76
-rwxr-xr-xhttemplate/search/svc_www.cgi8
-rw-r--r--httemplate/search/timeworked.html7
-rw-r--r--httemplate/search/unearned_detail.html21
-rw-r--r--httemplate/search/unprovisioned_services.html9
-rw-r--r--httemplate/view/bill_batch.cgi2
-rw-r--r--httemplate/view/cust_main/billing.html4
-rw-r--r--httemplate/view/cust_main/change_history.html7
-rwxr-xr-xhttemplate/view/cust_main/packages.html37
-rw-r--r--httemplate/view/cust_main/packages/contact.html61
-rw-r--r--httemplate/view/cust_main/packages/location.html14
-rw-r--r--httemplate/view/cust_main/packages/package.html135
-rwxr-xr-xhttemplate/view/cust_main/packages/section.html68
-rw-r--r--httemplate/view/cust_main/packages/status.html146
-rw-r--r--httemplate/view/cust_main/payment_history.html152
-rw-r--r--httemplate/view/cust_main/payment_history/attempted_batch_payment.html13
-rw-r--r--httemplate/view/elements/svc_Common.html23
-rw-r--r--httemplate/view/elements/svc_devices.html159
-rw-r--r--httemplate/view/elements/svc_edit_link.html9
-rw-r--r--httemplate/view/elements/svc_export_status.html10
-rwxr-xr-xhttemplate/view/quotation-pdf.cgi29
-rwxr-xr-xhttemplate/view/svc_acct.cgi14
-rw-r--r--httemplate/view/svc_acct/basics.html26
-rw-r--r--httemplate/view/svc_broadband.cgi47
-rw-r--r--httemplate/view/svc_cert.cgi8
-rw-r--r--httemplate/view/svc_hardware.cgi9
-rw-r--r--httemplate/view/svc_phone.cgi15
231 files changed, 5367 insertions, 2185 deletions
diff --git a/httemplate/browse/msgcat.html b/httemplate/browse/msgcat.html
index ac8a3a437..7509cf7d4 100644
--- a/httemplate/browse/msgcat.html
+++ b/httemplate/browse/msgcat.html
@@ -1,5 +1,5 @@
<& elements/browse.html,
- title => mt('Message catalog'),
+ title => mt('Translation strings'),
name_singular => 'string', #mt? no, we need to do it through the quant/PL stuff
query => { 'table' => 'msgcat',
'hashref' => { 'locale' => $locale, },
diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi
index 91238a0fd..876633afc 100755
--- a/httemplate/browse/part_export.cgi
+++ b/httemplate/browse/part_export.cgi
@@ -38,6 +38,21 @@ function part_export_areyousure(href) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
<% $part_export->label_html %>
(<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A>&nbsp;|&nbsp;<A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>)
+% if ( my @actions = $part_export->actions ) {
+ <P STYLE="position: absolute">
+ Management:
+% while (@actions) {
+% my $label = shift @actions;
+% my $path = shift @actions;
+ <& /elements/popup_link.html,
+ 'label' => $label,
+ 'action' => $fsurl.$path.'?'.$part_export->exportnum,
+ 'actionlabel' => $label,
+ &><% @actions ? '&nbsp;|&nbsp;' : '' %>
+% }
+ </P>
+% } #if @actions
+
</TD>
<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
index 57a429747..bb5bc5215 100755
--- a/httemplate/browse/part_pkg.cgi
+++ b/httemplate/browse/part_pkg.cgi
@@ -1,6 +1,8 @@
<% include( 'elements/browse.html',
'title' => 'Package Definitions',
+ 'menubar' => \@menubar,
'html_init' => $html_init,
+ 'html_form' => $html_form,
'html_posttotal' => $html_posttotal,
'name' => 'package definitions',
'disableable' => 1,
@@ -20,6 +22,9 @@
'fields' => \@fields,
'links' => \@links,
'align' => $align,
+ 'link_field' => 'pkgpart',
+ 'html_init' => $html_init,
+ 'html_foot' => $html_foot,
)
%>
<%init>
@@ -33,6 +38,7 @@ my $acl_edit_global = $curuser->access_right($edit_global);
my $acl_config = $curuser->access_right('Configuration'); #to edit services
#and agent types
#and bulk change
+my $acl_edit_bulk = $curuser->access_right('Bulk edit package definitions');
die "access denied"
unless $acl_edit || $acl_edit_global;
@@ -130,9 +136,7 @@ $select = "
";
-my $html_init;
-#unless ( $cgi->param('active') ) {
- $html_init = qq!
+my $html_init = qq!
One or more service definitions are grouped together into a package
definition and given pricing information. Customers purchase packages
rather than purchase services directly.<BR><BR>
@@ -144,7 +148,6 @@ my $html_init;
</FORM>
<BR><BR>
!;
-#}
$cgi->param('dummy', 1);
@@ -274,6 +277,18 @@ push @fields, sub {
: ()
),
],
+ ( map { my $dst_pkg = $_->dst_pkg;
+ [
+ { data => 'Supplemental: &nbsp;'.
+ '<A HREF="#'. $dst_pkg->pkgpart . '">' .
+ $dst_pkg->pkg . '</A>',
+ align=> 'center',
+ colspan => 2,
+ }
+ ]
+ }
+ $part_pkg->supp_part_pkg_link
+ ),
( map {
my $dst_pkg = $_->dst_pkg;
[
@@ -423,6 +438,10 @@ if ( $taxclasses ) {
$align .= 'l';
}
+# make a table of report class optionnames => the actual
+my %report_optionname_name = map { 'report_option_'.$_->num, $_->name }
+ qsearch('part_pkg_report_option', { disabled => '' });
+
push @header, 'Plan options',
'Services';
#'Service', 'Quan', 'Primary';
@@ -433,8 +452,18 @@ push @fields,
if ( $part_pkg->plan ) {
my %options = $part_pkg->options;
-
- [ map {
+ # gather any options that are really report options,
+ # convert them to their user-friendly names,
+ # and sort them (I think?)
+ my @report_options =
+ sort { $a cmp $b }
+ map { $report_optionname_name{$_} }
+ grep { $options{$_}
+ and exists($report_optionname_name{$_}) }
+ keys %options;
+
+ my @rows = (
+ map {
[
{ 'data' => "$_: ",
'align' => 'right',
@@ -445,11 +474,30 @@ push @fields,
];
}
grep { $options{$_} =~ /\S/ }
- grep { $_ !~ /^(setup|recur)_fee$/ }
+ grep { $_ !~ /^(setup|recur)_fee$/
+ and $_ !~ /^report_option_\d+$/ }
keys %options
- ];
+ );
+ if ( @report_options ) {
+ push @rows,
+ [ { 'data' => 'Report classes',
+ 'align' => 'center',
+ 'style' => 'font-weight: bold',
+ 'colspan' => 2
+ } ];
+ foreach (@report_options) {
+ push @rows, [
+ { 'data' => $_,
+ 'align' => 'center',
+ 'colspan' => 2
+ }
+ ];
+ } # foreach @report_options
+ } # if @report_options
+
+ return \@rows;
- } else {
+ } else { # should never happen...
[ map { [
{ 'data' => uc($_),
@@ -470,6 +518,8 @@ push @fields,
sub {
my $part_pkg = shift;
+ my @part_pkg_usage = sort { $a->priority <=> $b->priority }
+ $part_pkg->part_pkg_usage;
[
(map {
@@ -512,7 +562,27 @@ push @fields,
]
}
$part_pkg->svc_part_pkg_link
- )
+ ),
+ ( scalar(@part_pkg_usage) ?
+ [ { data => 'Usage minutes',
+ align => 'center',
+ colspan => 2,
+ data_style => 'b',
+ link => $p.'browse/part_pkg_usage.html#pkgpart'.
+ $part_pkg->pkgpart
+ } ]
+ : ()
+ ),
+ ( map {
+ [ { data => $_->minutes,
+ align => 'right'
+ },
+ { data => $_->description,
+ align => 'left'
+ },
+ ]
+ } @part_pkg_usage
+ ),
];
};
@@ -527,4 +597,25 @@ $extra_count = ( $count_extra_sql ? ' AND ' : ' WHERE ' ). $extra_count
if $extra_count;
my $count_query = "SELECT COUNT(*) FROM part_pkg $count_extra_sql $extra_count";
+my $html_form = '';
+my $html_foot = '';
+if ( $acl_edit_bulk ) {
+ # insert a checkbox column
+ push @header, '';
+ push @fields, sub {
+ '<INPUT TYPE="checkbox" NAME="pkgpart" VALUE=' . $_[0]->pkgpart .'>';
+ };
+ push @links, '';
+ $align .= 'c';
+ $html_form = qq!<FORM ACTION="${p}edit/bulk-part_pkg.html" METHOD="POST">!;
+ $html_foot = include('/search/elements/checkbox-foot.html',
+ submit => 'edit report classes', # for now it's only report classes
+ ) . '</FORM>';
+}
+
+my @menubar;
+# show this if there are any voip_cdr packages defined
+if ( FS::part_pkg->count("plan = 'voip_cdr'") ) {
+ push @menubar, 'Per-package usage minutes' => $p.'browse/part_pkg_usage.html';
+}
</%init>
diff --git a/httemplate/browse/part_pkg_usage.html b/httemplate/browse/part_pkg_usage.html
new file mode 100644
index 000000000..209fd3a01
--- /dev/null
+++ b/httemplate/browse/part_pkg_usage.html
@@ -0,0 +1,112 @@
+<& /elements/header.html, 'Package usage minutes' &>
+<& /elements/menubar.html, 'Package definitions', $p.'browse/part_pkg.cgi' &>
+<STYLE TYPE="text/css">
+.pkg_head {
+ background-color: #dddddd;
+ font-style: italic;
+}
+.pkg_head > td {
+ border-style: solid;
+ border-radius: 3px;
+ border-color: #555555;
+ border-width: 1px;
+}
+.usage > td {
+ text-align: center;
+}
+.error {
+ color: #ff0000;
+}
+</STYLE>
+<FORM METHOD="POST" ACTION="<%$fsurl%>edit/process/part_pkg_usage.html">
+ <TABLE STYLE="margin-top: 1em">
+ <TR>
+ <TH>Minutes</TH>
+ <TH>Shared</TH>
+ <TH>Rollover</TH>
+ <TH>Description</TH>
+ <TH>Priority</TH>
+% foreach my $class (@usage_class) {
+ <TH><% $class->classname %></TH>
+% }
+ </TR>
+
+% my $error = $cgi->param('error');
+% foreach my $part_pkg (@part_pkg) {
+% my $pkgpart = $part_pkg->pkgpart;
+% my @part_pkg_usage;
+% if ( $error ) {
+% @part_pkg_usage = @{ $error->{$pkgpart} };
+% } else {
+% @part_pkg_usage = $part_pkg->part_pkg_usage;
+% foreach my $usage (@part_pkg_usage) {
+% foreach ($usage->classnums) {
+% $usage->set("class$_".'_', 'Y');
+% }
+% }
+% }
+ <TR CLASS="pkg_head" ID="pkgpart<%$pkgpart%>">
+ <TD COLSPAN=<%$n_cols%>><% $part_pkg->pkg_comment %></TD>
+% # make it easy to enumerate the pkgparts later
+ <INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart %>">
+ </TR>
+% # template row
+ <TR id="pkgpart<%$pkgpart%>_template" CLASS="usage">
+ <TD>
+ <INPUT TYPE="hidden" NAME="pkgusagepart">
+ <INPUT TYPE="text" NAME="minutes" ID="minutes" SIZE=7>
+ </TD>
+% foreach (qw(shared rollover)) {
+ <TD>
+ <INPUT TYPE="checkbox" NAME="<% $_ %>" ID="<% $_ %>" VALUE="Y">
+ </TD>
+% }
+ <TD>
+ <INPUT TYPE="text" NAME="description" ID="description" SIZE=20>
+ </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="priority" ID="priority" SIZE=3>
+ </TD>
+% foreach (@usage_class) {
+% my $classnum = 'class' . $_->classnum . '_';
+ <TD>
+ <INPUT TYPE="checkbox" NAME="<% $classnum %>" ID="<% $classnum %>" VALUE="Y">
+ </TD>
+% }
+ </TR>
+ <& /elements/auto-table.html,
+ table => "pkgpart$pkgpart",
+ template_row => "pkgpart$pkgpart".'_template',
+ data => \@part_pkg_usage,
+ &>
+% }
+ </TABLE>
+ <BR>
+ <INPUT TYPE="submit">
+</FORM>
+<& /elements/footer.html &>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right(
+ ['Edit package definitions', 'Edit global package definitions']
+ );
+
+my @where = ("(plan = 'voip_cdr' OR plan = 'voip_inbound')",
+ "freq != '0'",
+ "disabled IS NULL");
+push @where, FS::part_pkg->curuser_pkgs_sql
+ unless $curuser->access_right('Edit global package definitions');
+my $extra_sql = ' WHERE '.join(' AND ', @where);
+my @part_pkg = qsearch({
+ 'table' => 'part_pkg',
+ 'extra_sql' => $extra_sql,
+ 'order_by' => ' ORDER BY pkgpart',
+});
+
+my @usage_class = sort { $a->weight <=> $b->weight }
+ qsearch('usage_class', { disabled => '' });
+
+my $n_usage_classes = scalar(@usage_class);
+my $n_cols = $n_usage_classes + 5; # minutes, shared, rollover, desc, prio
+</%init>
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
index a8f4a7c84..0d3685355 100755
--- a/httemplate/browse/part_svc.cgi
+++ b/httemplate/browse/part_svc.cgi
@@ -82,6 +82,7 @@ function part_export_areyousure(href) {
% }
% @dfields ;
% my $rowspan = scalar(@fields) || 1;
+% $rowspan++ if $part_svc->restrict_edit_password;
% my $url = "${p}edit/part_svc.cgi?". $part_svc->svcpart;
%
% if ( $bgcolor eq $bgcolor1 ) {
@@ -174,24 +175,32 @@ function part_export_areyousure(href) {
% my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue);
% if ( $flag =~ /^[MAH]$/ ) {
% my $select_table = ($flag eq 'H') ? 'hardware_class' : 'inventory_class';
-% $select_class{$value} ||=
-% qsearchs($select_table, { 'classnum' => $value } );
+% foreach my $classnum ( split(',', $value) ) {
+% $select_class{$classnum} =
+% qsearchs($select_table, { 'classnum' => $classnum } );
%
- <% $select_class{$value}
- ? $select_class{$value}->classname
- : "WARNING: $select_table.classnum $value not found" %>
+ <% $select_class{$classnum}
+ ? $select_class{$classnum}->classname
+ : "WARNING: $select_table.classnum $classnum not found" %><BR>
+% }
% } else {
<% $value %>
-% }
+% }
</TD>
% $n1="</TR><TR>";
-% }
-%
+% } #foreach $field
+% if ( $part_svc->restrict_edit_password ) {
+ <TR>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" COLSPAN=4 ALIGN="left">
+ <B><% emt('Password editing restricted.') %></B>
+ </TD>
+ </TR>
+% }
</TR>
-% }
+% } #foreach $part_svc
</TABLE>
</BODY>
diff --git a/httemplate/browse/rate_region.html b/httemplate/browse/rate_region.html
index b958894cb..b0ce467c0 100644
--- a/httemplate/browse/rate_region.html
+++ b/httemplate/browse/rate_region.html
@@ -62,8 +62,14 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+my $sub_prefixes = sub {
+ my $region = shift;
+ $region->prefixes .
+ ($region->exact_match ? ' <I>(exact match only)</I>' : '');
+};
+
my @header = ( '#', 'Region', 'Country code', 'Prefixes' );
-my @fields = ( 'regionnum', 'regionname', 'ccode', 'prefixes' );
+my @fields = ( 'regionnum', 'regionname', 'ccode', $sub_prefixes );
my @links = ( ($link) x 4 );
my @align = ( 'right', 'left', 'right', 'left' );
my @xls_format = ( ({ locked=>1, bg_color=>22 }) x 4 );
diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html
index e40b2436b..05a89c1f0 100644
--- a/httemplate/docs/license.html
+++ b/httemplate/docs/license.html
@@ -6,7 +6,7 @@
<P>
-Copyright &copy; 2005-2012 Freeside Internet Services, Inc.<BR>
+Copyright &copy; 2005-2013 Freeside Internet Services, Inc.<BR>
Copyright &copy; 2000-2005 Ivan Kohler<BR>
Copyright &copy; 1999 Silicon Interactive Software Design<BR>
All rights reserved<BR>
diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi
index 166a3b7ea..99e911ae5 100755
--- a/httemplate/edit/REAL_cust_pkg.cgi
+++ b/httemplate/edit/REAL_cust_pkg.cgi
@@ -9,6 +9,29 @@
<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript">
+var submit_fields = [];
+function confirm_changes() {
+ var i;
+ var querystring = 'pkgnum=<%$pkgnum%>';
+ var f = document.forms.formname;
+ for(i = 0; i < submit_fields.length; i++) {
+ querystring += ';'
+ + submit_fields[i]
+ + '='
+ + encodeURIComponent(f.elements[submit_fields[i] + '_text'].value);
+ }
+ overlib(
+ OLiframeContent(
+ '<%$p%>/misc/confirm-cust_pkg-edit_dates.html?' + querystring,
+ 576, 576, 'confirm_popup'
+ ),
+ CAPTION, 'Package date changes', STICKY, AUTOSTATUSCAP, CLOSETEXT, '',
+ MIDX, 0, MIDY, 0, DRAGGABLE, BGCOLOR, '#333399', CGCOLOR, '#333399',
+ TEXTSIZE, 3
+ );
+}
+</SCRIPT>
<FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST">
<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
@@ -31,6 +54,15 @@
<TD BGCOLOR="#ffffff"><% $part_pkg->pkg %></TD>
</TR>
+% if ( $cust_pkg->main_pkgnum ) {
+% my $main_pkg = $cust_pkg->main_pkg;
+ <TR>
+ <TD ALIGN="right">Supplemental to</TD>
+ <TD BGCOLOR="#ffffff">Package #<% $cust_pkg->main_pkgnum%>:&nbsp;\
+ <% $main_pkg->part_pkg->pkg %></TD>
+ </TR>
+
+% }
<TR>
<TD ALIGN="right">Custom</TD>
<TD BGCOLOR="#ffffff"><% $part_pkg->custom %></TD>
@@ -38,7 +70,7 @@
<TR>
<TD ALIGN="right">Comment</TD>
- <TD BGCOLOR="#ffffff"><% $part_pkg->comment %></TD>
+ <TD BGCOLOR="#ffffff"><% $part_pkg->comment |h %></TD>
</TR>
<TR>
@@ -50,14 +82,14 @@
% if ( $cust_pkg->setup && ! $cust_pkg->start_date ) {
<& .row_display, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &>
% } else {
- <& .row_edit, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start', if_primary=>1 &>
% }
- <& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup' &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup', if_primary=>1 &>
<& .row_edit, cust_pkg=>$cust_pkg, column=>'last_bill', label=>$last_bill_or_renewed &>
<& .row_edit, cust_pkg=>$cust_pkg, column=>'bill', label=>$next_bill_or_prepaid_until &>
%#if ( $cust_pkg->contract_end or $part_pkg->option('contract_end_months',1) ) {
- <& .row_edit, cust_pkg=>$cust_pkg, column=>'contract_end',label=>'Contract end' &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'contract_end',label=>'Contract end', if_primary=>1 &>
%#}
<& .row_display, cust_pkg=>$cust_pkg, column=>'adjourn', label=>'Adjournment', note=>'(will <b>suspend</b> this package when the date is reached)' &>
<& .row_display, cust_pkg=>$cust_pkg, column=>'susp', label=>'Suspension' &>
@@ -73,10 +105,17 @@
$column
$label
$note => ''
+ $if_primary => 0
</%args>
% my $value = $cust_pkg->get($column);
% $value = $value ? time2str($format, $value) : "";
-
+%
+% # if_primary for the dates that can't be edited on supplemental packages
+% if ($if_primary and $cust_pkg->main_pkgnum) {
+ <INPUT TYPE="hidden" ID="<%$column%>_text" VALUE="<% $cust_pkg->get($column) %>">
+ <SCRIPT>submit_fields.push('<%$column%>');</SCRIPT>
+ <& .row_display, %ARGS &>
+% } else {
<TR>
<TD ALIGN="right"><% $label %> date</TD>
<TD>
@@ -104,8 +143,11 @@
button: "<% $column %>_button",
align: "BR"
});
- </SCRIPT>
+ submit_fields.push('<%$column%>');
+
+ </SCRIPT>
+% }
</%def>
<%def .row_display>
@@ -114,6 +156,7 @@
$column
$label
$note => ''
+ $is_primary => 0 #ignored
</%args>
% if ( $cust_pkg->get($column) ) {
<TR>
@@ -130,7 +173,7 @@
</TABLE>
<BR>
-<INPUT TYPE="submit" VALUE="<% mt('Apply changes') |h %>">
+<INPUT TYPE="button" VALUE="<% mt('Apply changes') |h %>" onclick="confirm_changes()">
</FORM>
<% include('/elements/footer.html') %>
@@ -160,38 +203,6 @@ if ( $cgi->param('error') ) {
my @errors = ();
my %errors = map { $_=>1 } split(',', $cgi->param('error'));
$cgi->param('error', '');
-
- if ( $errors{'_bill_areyousure'} ) {
- if ( $cgi->param('bill') =~ /^([\s\d\/\:\-\(\w\)]*)$/ ) {
- my $bill = $1;
- push @errors,
- "You are attempting to set the next bill date to $bill, which is
- in the past. This will charge the customer for the interval
- from $bill until now. Are you sure you want to do this? ".
- '<INPUT TYPE="checkbox" NAME="bill_areyousure" VALUE="1">';
- }
- }
-
- if ( $errors{'_setup_areyousure'} ) {
- push @errors,
- "You are attempting to remove the setup date. This will re-charge the
- customer for the setup fee. Are you sure you want to do this? ".
- '<INPUT TYPE="checkbox" NAME="setup_areyousure" VALUE="1">';
- }
-
- if ( $errors{'_setupadd_areyousure'} ) {
- push @errors,
- "You are attempting to add a setup date. This will prevent charging the
- customer for the setup fee. Are you sure you want to do this? ".
- '<INPUT TYPE="checkbox" NAME="setupadd_areyousure" VALUE="1">';
- }
-
- if ( $errors{'_start'} ) {
- push @errors,
- "You are attempting to add a start date to a package that has already
- started billing.";
- }
-
$error = join('<BR><BR>', @errors );
}
diff --git a/httemplate/edit/bulk-part_pkg.html b/httemplate/edit/bulk-part_pkg.html
new file mode 100644
index 000000000..a1c6f0c9b
--- /dev/null
+++ b/httemplate/edit/bulk-part_pkg.html
@@ -0,0 +1,74 @@
+<& /elements/header.html, 'Edit package report classes' &>
+%# change that title if we add any other editing controls
+
+%# this should be centralized somewhere
+<STYLE TYPE="text/css">
+.row0 { background-color: #eeeeee; }
+.row1 { background-color: #ffffff; }
+</STYLE>
+
+<FORM ACTION="process/bulk-part_pkg.html" METHOD="POST">
+<DIV>
+The following packages will be changed:<BR>
+% foreach my $pkgpart (sort keys(%part_pkg)) {
+<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart %>">
+<% $part_pkg{$pkgpart}->pkg_comment |h %><BR>
+% }
+</DIV>
+<BR>
+<& /elements/table-grid.html &>\
+<& /elements/tr-justtitle.html, value => mt('Report classes') &>
+% my $row = 0;
+% foreach my $num (sort keys %report_class) {
+ <TR CLASS="row<%$row % 2%>">
+ <TD>
+% if ( defined $initial_state{$num} ) {
+ <& /elements/checkbox.html,
+ field => 'report_option_'.$num,
+ value => 1,
+ curr_value => $initial_state{$num}
+ &>
+% } else {
+% # needs to be a tristate so that you can say "don't change it"
+ <& /elements/checkbox-tristate.html, field => 'report_option_'.$num &>
+% }
+ </TD>
+ <TD><% $report_class{$num}->name %></TD>
+ </TR>
+% $row++;
+% }
+</TABLE>
+<BR>
+<INPUT TYPE="submit">
+</FORM>
+<& /elements/footer.html &>
+<%init>
+die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Bulk edit package definitions');
+my @pkgparts = $cgi->param('pkgpart')
+ or die "no package definitions selected";
+
+my %part_pkg = map { $_ => FS::part_pkg->by_key($_) } @pkgparts;
+my %part_pkg_option = map { $_ => { $part_pkg{$_}->options } } @pkgparts;
+my %report_class = map { $_->num => $_ }
+ qsearch('part_pkg_report_option', { disabled => '' });
+
+my %initial_state;
+foreach my $num (keys %report_class) {
+ my $yes = 0;
+ my $no = 0;
+ foreach my $option (values %part_pkg_option) {
+ if ( $option->{"report_option_$num"} ) {
+ $yes = 1;
+ } else {
+ $no = 1;
+ }
+ }
+ if ( $yes and $no ) {
+ $initial_state{$num} = undef;
+ } elsif ( $yes ) {
+ $initial_state{$num} = 1;
+ } elsif ( $no ) {
+ $initial_state{$num} = 0;
+ } # else, uh, you didn't provide any pkgparts
+}
+</%init>
diff --git a/httemplate/edit/credit-cust_bill_pkg.html b/httemplate/edit/credit-cust_bill_pkg.html
index 3d1cf2438..a5ecb69e3 100644
--- a/httemplate/edit/credit-cust_bill_pkg.html
+++ b/httemplate/edit/credit-cust_bill_pkg.html
@@ -70,10 +70,11 @@
<TH ALIGN="right" COLSPAN=2>Total credit amount: </TD>
<TH ALIGN="right" ID="total_td"><% $money_char %><% sprintf('%.2f', 0) %></TD>
</TR>
-<INPUT TYPE="hidden" NAME="amount" ID="total_el" VALUE="0.00">
</table>
+<INPUT TYPE="hidden" NAME="amount" ID="total_el" VALUE="0.00">
+
<table>
<& /elements/tr-select-reason.html,
@@ -244,7 +245,7 @@ function calc_total(what) {
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
-die "access denied" unless $curuser->access_right('Post credit');
+die "access denied" unless $curuser->access_right('Credit line items');
#a tiny bit of false laziness w/search/cust_bill_pkg.cgi, but we're pretty
# specialized and a piece of UI, not a report
diff --git a/httemplate/edit/cust_location.cgi b/httemplate/edit/cust_location.cgi
index 80b27c2b3..b90ba66b8 100755
--- a/httemplate/edit/cust_location.cgi
+++ b/httemplate/edit/cust_location.cgi
@@ -7,20 +7,32 @@ ACTION="<% $p %>edit/process/cust_location.cgi" METHOD=POST>
<INPUT TYPE="hidden" NAME="locationnum" VALUE="<% $locationnum %>">
<% ntable('#cccccc') %>
-<% include('/elements/location.html',
- 'object' => $cust_location,
- 'no_asterisks' => 1,
- ) %>
+<& /elements/location.html,
+ 'object' => $cust_location,
+ 'no_asterisks' => 1,
+ # these are service locations, so they need all this stuff
+ 'enable_coords' => 1,
+ 'enable_district' => 1,
+ 'enable_censustract' => 1,
+&>
+<& /elements/standardize_locations.html,
+ 'form' => 'EditLocationForm',
+ 'callback' => 'document.EditLocationForm.submit();',
+&>
</TABLE>
<BR>
<SCRIPT TYPE="text/javascript">
-function areyousure() {
- return confirm('Modify this service location?');
+function go() {
+% if ( FS::Conf->new->config('address_standardize_method') ) {
+ standardize_locations();
+% } else {
+ confirm('Modify this service location?') &&
+ document.EditLocationForm.submit();
+% }
}
</SCRIPT>
-<INPUT TYPE="submit" VALUE="Submit" onclick="return areyousure()">
-
+<INPUT TYPE="button" NAME="submitButton" VALUE="Submit" onclick="go()">
</FORM>
</BODY>
</HTML>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index be00213e2..2908848c6 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -48,7 +48,7 @@
<TD STYLE="width:650px">
%#; padding-right:2px; vertical-align:top">
<FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT>
- <TABLE CLASS="fsinnerbox">
+ <TABLE CLASS="fsinnerbox" WIDTH="100%">
<& cust_main/before_bill_location.html, $cust_main &>
<& /elements/location.html,
object => $cust_main->bill_location,
@@ -62,7 +62,6 @@
<TR><TD STYLE="height:40px"></TD></TR>
<TR>
<TD STYLE="width:650px">
-%#; padding-left:2px; vertical-align:top">
<FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT>
<INPUT TYPE="checkbox"
NAME="same"
@@ -72,19 +71,17 @@
VALUE="Y"
<% $has_ship_address ? '' : 'CHECKED' %>
><% mt('same as billing address') |h %>
- <TABLE CLASS="fsinnerbox" ID="table_ship_location">
- <& /elements/location.html,
- object => $cust_main->ship_location,
- prefix => 'ship_',
- enable_censustract => 1,
- enable_district => 1,
- enable_coords => 1,
- &>
- </TABLE>
- <TABLE CLASS="fsinnerbox" ID="table_ship_location_blank"
- STYLE="display:none">
- <TR><TD></TD></TR>
- </TABLE>
+ <DIV CLASS="fsinnerbox">
+ <TABLE ID="table_ship_location" WIDTH="100%">
+ <& /elements/location.html,
+ object => $cust_main->ship_location,
+ prefix => 'ship_',
+ enable_censustract => 1,
+ enable_district => 1,
+ enable_coords => 1,
+ &>
+ </TABLE>
+ </DIV>
</TD>
</TR></TABLE>
@@ -94,19 +91,14 @@ function samechanged(what) {
%# document.getElementById('table_ship_location').style.visibility =
%# what.checked ? 'hidden' : 'visible';
var t1 = document.getElementById('table_ship_location');
- var t2 = document.getElementById('table_ship_location_blank');
if ( what.checked ) {
- t2.style.width = t1.clientWidth + 'px';
- t2.style.height = t1.clientHeight + 'px';
- t1.style.display = 'none';
- t2.style.display = '';
+ t1.style.visibility = 'hidden';
}
else {
- t2.style.display = 'none';
- t1.style.display = '';
+ t1.style.visibility = 'visible'
}
}
-samechanged(document.getElementById('same'));
+//samechanged(document.getElementById('same'));
</SCRIPT>
<BR>
@@ -285,7 +277,8 @@ if ( $cgi->param('error') ) {
my( $query ) = $cgi->keywords;
$query =~ /^(\d+)$/;
$custnum=$1;
- $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+ $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "custnum $custnum not found";
if ( $cust_main->dbdef_table->column('paycvv')
&& length($cust_main->paycvv) ) {
my $paycvv = $cust_main->paycvv;
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
index 2925ca87c..5a66f0a60 100644
--- a/httemplate/edit/cust_main/billing.html
+++ b/httemplate/edit/cust_main/billing.html
@@ -444,10 +444,11 @@
<TR><TD>&nbsp;</TD></TR>
+% my $curuser = $FS::CurrentUser::CurrentUser;
% my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
-
% if ( $conf->exists('cust_class-tax_exempt')
% || $conf->exists('tax-cust_exempt-groups-require_individual_nums')
+% || ! $curuser->access_right('Edit customer tax exemptions')
% )
% {
@@ -461,14 +462,16 @@
% }
-% foreach my $exempt_group ( @exempt_groups ) {
-% my $cust_main_exemption = $cust_main->tax_exemption($exempt_group);
-% #escape $exempt_group for NAME etc.
-% my $checked = ($cust_main_exemption || $cgi->param("tax_$exempt_group"));
- <TR>
- <TD>&nbsp;&nbsp;<INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" ID="tax_<% $exempt_group %>" VALUE="Y" <% $checked ? 'CHECKED' : '' %> onChange="tax_changed(this)"> Tax Exempt (<% $exempt_group %> taxes)</TD>
- <TD> - Exemption number <INPUT TYPE="text" NAME="tax_<% $exempt_group %>_num" ID="tax_<% $exempt_group %>_num" VALUE="<% $cgi->param("tax_$exempt_group".'_num') || ( $cust_main_exemption ? $cust_main_exemption->exempt_number : '' ) |h %>" <% $checked ? '' : 'DISABLED' %>></TD>
- </TR>
+% if ( $curuser->access_right('Edit customer tax exemptions') ) {
+% foreach my $exempt_group ( @exempt_groups ) {
+% my $cust_main_exemption = $cust_main->tax_exemption($exempt_group);
+% #escape $exempt_group for NAME etc.
+% my $checked = ($cust_main_exemption || $cgi->param("tax_$exempt_group"));
+ <TR>
+ <TD>&nbsp;&nbsp;<INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" ID="tax_<% $exempt_group %>" VALUE="Y" <% $checked ? 'CHECKED' : '' %> onChange="tax_changed(this)"> Tax Exempt (<% $exempt_group %> taxes)</TD>
+ <TD> - Exemption number <INPUT TYPE="text" NAME="tax_<% $exempt_group %>_num" ID="tax_<% $exempt_group %>_num" VALUE="<% $cgi->param("tax_$exempt_group".'_num') || ( $cust_main_exemption ? $cust_main_exemption->exempt_number : '' ) |h %>" <% $checked ? '' : 'DISABLED' %>></TD>
+ </TR>
+% }
% }
% unless ( $conf->exists('emailinvoiceonly') ) {
@@ -518,7 +521,13 @@
<% $conf->exists('cust_main-require_invoicing_list_email', $agentnum)
? $r : '' %>Email address(es)
</TD>
- <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"></TD>
+ <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>">
+ <INPUT TYPE="checkbox" NAME="message_noemail" VALUE="Y" <%
+ ( $cust_main->message_noemail eq 'Y' )
+ ? 'CHECKED'
+ : ''
+ %>> <% emt('Do not send notices') %>
+ </TD>
</TR>
% }
diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js
index 1cfa52d8f..0de6d9dab 100644
--- a/httemplate/edit/cust_main/bottomfixup.js
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -70,8 +70,8 @@ function copy_payby_fields() {
<& /elements/standardize_locations.js,
'callback' => 'submit_continue();',
- 'main_prefix' => 'bill_',
- 'no_company' => 1,
+ 'billship' => 1,
+ 'with_census' => 1, # no with_firm, apparently
&>
function copyelement(from, to) {
diff --git a/httemplate/edit/cust_main/choose_tax_location.html b/httemplate/edit/cust_main/choose_tax_location.html
deleted file mode 100644
index ac475c54b..000000000
--- a/httemplate/edit/cust_main/choose_tax_location.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<FORM NAME="choosegeocodeform">
-<CENTER><BR><B>Choose tax location</B><BR><BR>
-<P>the geocode is:<% $header %></P>
-<P STYLE="<% $style %>"><% $header %></P>
-
-<SELECT NAME='geocodes' ID='geocodes' STYLE="<% $style %>">
-% foreach my $location (@cust_tax_location) {
-% my %value = ( zip => $zip5,
-% map { $_ => $location->$_ }
-% qw ( city state geocode )
-% );
-% map { $value{$_} = $location{$_} } qw ( city state )
-% if $location{country} eq 'CA';
-%
-% my $value = encode_entities(objToJson({ %value })
-% );
-% my $content = '';
-% $content .= $location->$_. '&nbsp;' x ( $max{$_} - length($location->$_) )
-% foreach qw( city county state );
-% $content .= $location->cityflag eq 'I' ? 'Y' : 'N' ;
-% my $selected = '' ;
-% if ($geocode && $location->geocode eq $geocode) {
-% $selected = 'SELECTED';
-% }
- <OPTION VALUE="<% $value %>" STYLE="<% $style %>" <% $selected %>><% $content %>
-% }
-</SELECT><BR><BR>
-
-<TABLE><TR>
- <TD> <BUTTON TYPE="button" onClick="set_geocode(document.getElementById('geocodes'));"><IMG SRC="<%$p%>images/tick.png" ALT=""> Set location </BUTTON></TD>
- <TD><BUTTON TYPE="button" onClick="document.CustomerForm.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission </BUTTON></TD>
-</TR>
-</TABLE>
-
-</CENTER>
-</FORM>
-<%init>
-
-my $conf = new FS::Conf;
-
-my %location = ();
-
-($location{data_vendor}) = $cgi->param('data_vendor') =~ /^([-\w]+)$/;
-($location{city}) = $cgi->param('city') =~ /^([\w ]+)$/;
-($location{state}) = $cgi->param('state') =~ /^(\w+)$/;
-($location{zip}) = $cgi->param('zip') =~ /^([-\w ]+)$/;
-($location{country}) = $cgi->param('country') =~ /^([\w ]+)$/;
-
-my($geocode) = $cgi->param('geocode') =~ /^([\w]+)$/;
-
-my($zip5, $zip4) = split('-', $location{zip});
-
-#only support US & CA
-my $hashref = { 'data_vendor' => $location{data_vendor} };
-$hashref->{zip} = $location{country} eq 'CA' ? substr($zip5,0,1) : $zip5,
-
-my @keys = keys(%$hashref);
-my @cust_tax_location = ();
-until ( @cust_tax_location ) {
- @cust_tax_location = qsearch({ table => 'cust_tax_location',
- hashref => $hashref,
- order_by => 'LIMIT 50',
- });
- last unless scalar(@keys);
- delete $hashref->{ shift @keys };
-}
-
-my %max = ( city => 4, county => 6, state => 5);
-foreach my $location (@cust_tax_location) {
- foreach ( qw( city county state ) ) {
- my $length = length($location->$_);
- $max{$_} = ($length > $max{$_}) ? $length : $max{$_};
- }
-}
-foreach ( qw( city county state ) ) {
- $max{$_} = $location{$_} if $location{$_} > $max{$_};
- $max{$_}++;
-}
-
-my $header = '&nbsp;&nbsp;';
-$header .= $_. '&nbsp;' x ( $max{lc($_)} - length($_) )
- foreach qw( City County State );
-$header .= "In city?";
-
-my $style = "font-family:monospace;";
-
-</%init>
diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html
index cfed8e4f6..b7e86ba78 100644
--- a/httemplate/edit/cust_main/top_misc.html
+++ b/httemplate/edit/cust_main/top_misc.html
@@ -32,6 +32,44 @@
document.getElementById('contacts_div').style.display = 'none';
}
}
+
+ var ship_locked_agents = <% encode_json(\%ship_locked_agents) %>;
+ var ship_fields = ['address1', 'city', 'state', 'zip', 'country',
+ 'latitude', 'longitude', 'district'];
+ function agent_changed(what) {
+ var agentnum = what.value;
+ var f = what.form;
+ if ( ship_locked_agents[agentnum] ) {
+% # For this agent, the service location (except address2)
+% # should be locked to the agent's location.
+% # Set the ship_ fields to those values (just for display) and
+% # then disable them.
+ for(var x in ship_locked_agents[agentnum]) {
+ f['ship_'+x].value = ship_locked_agents[agentnum][x];
+ f['ship_'+x].disabled = true;
+ }
+ f['same'].checked = false;
+ f['same'].disabled = true;
+ } else {
+% # Unlock the ship_ location fields. If they were previously
+% # disabled, then they contain some agent's address, which is
+% # no longer meaningful. So set them back to the customer's
+% # current location.
+ for(var i=0; i<ship_fields.length; i++) {
+ x = ship_fields[i];
+ if ( f['ship_'+x].disabled ) {
+ f['ship_'+x].value = f['old_ship_'+x].value;
+ }
+ f['ship_'+x].disabled = false;
+ }
+ f['same'].disabled = false;
+ }
+ samechanged(f['same']);
+ }
+ window.onload = function() {
+ agent_changed(document.getElementById('agentnum'));
+ }
+
</SCRIPT>
% foreach my $field ($cust_main->virtual_fields) {
@@ -51,12 +89,13 @@
% $cust_main->agentnum($agentnum);
<INPUT TYPE="hidden" NAME="lock_agentnum" VALUE="<% $agentnum %>">
- <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agentnum %>">
+ <INPUT TYPE="hidden" NAME="agentnum" ID="agentnum"
+ VALUE="<% $agentnum %>">
<TR>
<TD ALIGN="right"><% mt('Agent') |h %></TD>
<TD CLASS="fsdisabled"><% $cust_main->agent->agent |h %></TD>
</TR>
-
+
% } else {
<& /elements/tr-select-agent.html,
@@ -65,6 +104,7 @@
'empty_label' => emt('Select agent'),
'disable_empty' => ( $cust_main->agentnum ? 1 : 0 ),
'viewall_right' => emt('None'),
+ 'onchange' => 'agent_changed(this)',
&>
% }
@@ -201,4 +241,17 @@ my $curuser = $FS::CurrentUser::CurrentUser;
my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+# which agents lock the service address, if any
+my %ship_locked_agents;
+foreach (qsearch('agent',{})) {
+ my $agentnum = $_->agentnum;
+ next unless $conf->exists('agent-ship_address', $_->agentnum);
+ my $cust_main = $_->agent_cust_main or next;
+ my $agent_ship_location = $cust_main->ship_location;
+ $ship_locked_agents{$agentnum} = +{
+ map { $_ => $agent_ship_location->$_ }
+ qw(address1 city state zip country latitude longitude district)
+ };
+}
+
</%init>
diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi
index dd1ed335f..88e925460 100755
--- a/httemplate/edit/cust_pkg.cgi
+++ b/httemplate/edit/cust_pkg.cgi
@@ -7,7 +7,6 @@
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
%#current packages
-%my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } );
%if (@cust_pkg) {
Current packages - select to remove (services are moved to a new package below)
@@ -18,13 +17,7 @@
</TR>
<BR><BR>
%
-%
-% foreach ( sort { $all_pkg{ $a->getfield('pkgpart') }
-% cmp $all_pkg{ $b->getfield('pkgpart') }
-% }
-% @cust_pkg
-% )
-% {
+% foreach ( @main_pkgs ) {
% my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') );
% my $checked = $remove_pkg{$pkgnum} ? ' CHECKED' : '';
%
@@ -36,6 +29,13 @@
<TD ALIGN="right"><% $pkgnum %>:</TD>
<TD><% $all_pkg{$pkgpart} %> - <% $all_comment{$pkgpart} %></TD>
</TR>
+% foreach my $supp_pkg ( @{ $supp_pkgs_of{$pkgnum} } ) {
+ <TR>
+ <TD></TD>
+ <TD></TD>
+ <TD>+ <% $all_pkg{$supp_pkg->pkgpart} %> - <% $all_comment{$supp_pkg->pkgpart} %></TD>
+ </TR>
+% }
% }
@@ -147,4 +147,24 @@ if ( $cgi->param('error') ) {
my $p1 = popurl(1);
+my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } );
+my @main_pkgs;
+my %supp_pkgs_of; # main pkgnum => arrayref of cust_pkgs
+
+
+foreach my $cust_pkg
+ ( sort { $all_pkg{ $a->pkgpart } cmp $all_pkg{ $b->getfield('pkgpart') } }
+ @cust_pkg
+ )
+ # XXX does not properly handle recursive supplemental links
+{
+ if ( my $main_pkgnum = $cust_pkg->main_pkgnum ) {
+ $supp_pkgs_of{$main_pkgnum} ||= [];
+ push @{ $supp_pkgs_of{$main_pkgnum} }, $cust_pkg;
+ } else {
+ push @main_pkgs, $cust_pkg;
+ $supp_pkgs_of{$cust_pkg->pkgnum} ||= [];
+ }
+}
+
</%init>
diff --git a/httemplate/edit/cust_pkg_detail.html b/httemplate/edit/cust_pkg_detail.html
index 009ed5c6e..5e107066d 100644
--- a/httemplate/edit/cust_pkg_detail.html
+++ b/httemplate/edit/cust_pkg_detail.html
@@ -28,7 +28,7 @@
<TR>
<TD ALIGN="right">Comment</TD>
- <TD BGCOLOR="#ffffff"><% $part_pkg->comment %></TD>
+ <TD BGCOLOR="#ffffff"><% $part_pkg->comment |h %></TD>
</TR>
<TR>
diff --git a/httemplate/edit/cust_pkg_quantity.html b/httemplate/edit/cust_pkg_quantity.html
new file mode 100755
index 000000000..ec47ed6cb
--- /dev/null
+++ b/httemplate/edit/cust_pkg_quantity.html
@@ -0,0 +1,49 @@
+<& /elements/header-popup.html, "Change Quantity" &>
+<& /elements/error.html &>
+
+<FORM ACTION="<% $p %>edit/process/cust_pkg_quantity.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+<& /elements/table-grid.html, 'bgcolor' => '#cccccc', 'cellpadding' => 2 &>
+
+ <TR>
+ <TH ALIGN="right">Current package&nbsp;</TH>
+ <TD CLASS="grid">
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+<& /elements/tr-input-text.html,
+ 'field' => 'quantity',
+ 'curr_value' => $cust_pkg->quantity,
+ 'label' => emt('Quantity')
+&>
+
+</TABLE>
+
+<BR>
+<INPUT NAME="submit" TYPE="submit" VALUE="Change">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+#some false laziness w/misc/change_pkg.cgi
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg = FS::cust_pkg->by_key($pkgnum) or die "unknown pkgnum $pkgnum";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi
index 656d5ebb5..df42e63ae 100755
--- a/httemplate/edit/cust_refund.cgi
+++ b/httemplate/edit/cust_refund.cgi
@@ -59,12 +59,12 @@
</TD>
</TR>
% }
-
+% if ( $cust_pay->processor ) {
<TR>
<TD ALIGN="right">Processor</TD>
<TD BGCOLOR="#ffffff"><% $cust_pay->processor %></TD>
</TR>
-% if ( length($auth) ) {
+% if ( length($cust_pay->auth) ) {
<TR>
<TD ALIGN="right">Authorization</TD>
@@ -78,10 +78,10 @@
<TD BGCOLOR="#ffffff"><% $cust_pay->order_number %></TD>
</TR>
% }
-% } #if $cust_pay
+% } # if ($cust_pay->processor)
</TABLE>
-% }
+% } #if $cust_pay
<BR>Refund
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index a24f23805..3e6bd5bec 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -329,6 +329,7 @@ Example:
% qw( country ), #select-country
% qw( width height ), #htmlarea
% qw( alt_format ), #select-cust_location
+% qw( classnum ), # select-inventory_item
% ;
%
% #select-table
diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html
new file mode 100644
index 000000000..d03c49d2f
--- /dev/null
+++ b/httemplate/edit/elements/part_svc_column.html
@@ -0,0 +1,303 @@
+<%doc>
+To be called from part_svc.cgi.
+<& elements/part_svc_column.html,
+ 'svc_acct',
+ # options...
+ 'part_svc' => $part_svc, # the existing part_svc to edit
+ 'clone' => 0, # or a svcpart to clone from
+&>
+
+</%doc>
+<%once>
+# the semantics of this could be better
+
+# all of these conditions are when NOT to allow that flag choice
+# don't allow the 'inventory' flags (M, A) to be chosen for
+# fields that aren't free-text
+my $inv_sub = sub { $_[0]->{disable_inventory} || $_[0]->{type} ne 'text' };
+tie my %flag, 'Tie::IxHash',
+ '' => { 'desc' => 'No default', 'condition' => sub { 0 } },
+ 'D' => { 'desc' => 'Default',
+ 'condition' =>
+ sub { $_[0]->{disable_default } }
+ },
+ 'F' => { 'desc' => 'Fixed (unchangeable)',
+ 'condition' =>
+ sub { $_[0]->{disable_fixed} },
+ },
+ 'S' => { 'desc' => 'Selectable Choice',
+ 'condition' =>
+ sub { $_[0]->{disable_select} },
+ },
+ 'M' => { 'desc' => 'Manual selection from inventory',
+ 'condition' => $inv_sub,
+ },
+ 'A' => { 'desc' => 'Automatically fill in from inventory',
+ 'condition' => $inv_sub,
+ },
+ 'H' => { 'desc' => 'Select from hardware class',
+ 'condition' => sub { $_[0]->{type} ne 'select-hardware' },
+ },
+ 'X' => { 'desc' => 'Excluded',
+ 'condition' => sub { 1 }, # obsolete
+ },
+;
+
+# the semantics of this could be much better
+sub flag_condition {
+ my $f = shift;
+ not &{ $flag{$f}->{'condition'} }(@_);
+}
+
+my %communigate_fields = (
+ 'svc_acct' => { map { $_=>1 }
+ qw( file_quota file_maxnum file_maxsize
+ password_selfchange password_recover
+ ),
+ grep /^cgp_/, fields('svc_acct')
+ },
+ 'svc_domain' => { map { $_=>1 }
+ qw( max_accounts trailer parent_svcnum ),
+ grep /^(cgp|acct_def)_/, fields('svc_domain')
+ },
+);
+</%once>
+<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $svcdb %>">
+<BR><BR>
+<& /elements/table.html &>
+ <TR><TH COLSPAN=<% $columns %>>Exports</TH></TR>
+ <TR>
+% # exports
+% foreach my $part_export (@part_export) {
+ <TD>
+ <INPUT TYPE="checkbox" \
+ NAME="exportnum<% $part_export->exportnum %>" \
+ VALUE=1 \
+ <% $has_export_svc{$part_export->exportnum} ? 'CHECKED' : '' %>>
+ <% $part_export->label_html %>
+ </TD>
+% $count++;
+% if ( $count % $columns == 0 ) {
+ </TR>
+ <TR>
+% }
+% }
+ </TR>
+</TABLE><BR><BR>
+For the selected table, you can give fields default or fixed (unchangeable)
+values, or select an inventory class to manually or automatically fill in
+that field.
+<& /elements/table-grid.html, cellpadding => 4 &>
+ <TR>
+ <TH BGCOLOR="#cccccc">Field</TH>
+ <TH BGCOLOR="#cccccc">Label</TH>
+ <TH BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>
+ </TR>
+% $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now
+% my $i = 0;
+% foreach my $field (@fields) {
+% my $def = shift @defs;
+% my $part_svc_column = $part_svc->part_svc_column($field);
+% my $flag = $part_svc_column->columnflag;
+% my $formatter = $def->{'format'} || sub { shift };
+% my $value = &{$formatter}($part_svc_column->columnvalue);
+ <TR CLASS="row<%$i%>">
+ <TD ROWSPAN=2 CLASS="grid" ALIGN="right">
+ <% $def->{'label'} || $field %>
+ </TD>
+ <TD ROWSPAN=2 CLASS="grid">
+ <INPUT NAME="<% $svcdb %>__<% $field %>_label"
+ STYLE="text-align: right"
+ VALUE="<% $part_svc_column->columnlabel || $def->{'label'} |h %>">
+ </TD>
+
+ <TD ROWSPAN=1 CLASS="grid">
+% # flag selection
+% if ( $def->{'type'} eq 'disabled' ) {
+% $flag = '';
+ No default
+% } else {
+% my $name = $svcdb.'__'.$field.'_flag';
+ <SELECT NAME="<%$name%>"
+ ID="<%$name%>"
+ STYLE="width:100%"
+ onchange="flag_changed(this)">
+% foreach my $f (keys %flag) {
+% if ( flag_condition($f, $def, $svcdb, $field) ) {
+ <OPTION VALUE="<%$f%>"<% $flag eq $f ? ' SELECTED' : ''%>>
+ <% $flag{$f}->{desc} %>
+ </OPTION>
+% }
+% }
+ </SELECT>
+% } # if $def->{'type'} eq 'disabled'
+ </TD>
+ <TD CLASS="grid">
+% # value entry/selection
+% my $name = $svcdb.'__'.$field;
+% # These are all MANDATORY SELECT types. Regardless of the flag value,
+% # there will never be a text input (either in svc_* or in part_svc) for
+% # these fields.
+% if ( $def->{'type'} eq 'checkbox' ) {
+ <& /elements/checkbox.html,
+ 'field' => $name,
+ 'curr_value' => $value,
+ 'value' => 'Y' &>
+%
+% } elsif ( $def->{'type'} eq 'select' ) {
+%
+% if ( $def->{'select_table'} ) {
+ <& /elements/select-table.html,
+ 'field' => $name,
+ 'id' => $name.'_select',
+ 'table' => $def->{'select_table'},
+ 'name_col' => $def->{'select_label'},
+ 'value_col' => $def->{'select_key'},
+ 'order_by' => dbdef->table($def->{'select_table'})->primary_key,
+ 'multiple' => $def->{'multiple'},
+ 'disable_empty' => 1,
+ 'curr_value' => $value,
+ &>
+% } else {
+% my (@options, %labels);
+% if ( $def->{'select_list'} ) {
+% @options = @{ $def->{'select_list'} };
+% @labels{@options} = @options;
+% } elsif ( $def->{'select_hash'} ) {
+% if ( ref($def->{'select_hash'}) eq 'ARRAY' ) {
+% tie my %hash, 'Tie::IxHash', @{ $def->{'select_hash'} };
+% $def->{'select_hash'} = \%hash;
+% }
+% @options = keys( %{ $def->{'select_hash'} } );
+% %labels = %{ $def->{'select_hash'} };
+% }
+ <& /elements/select.html,
+ 'field' => $name,
+ 'id' => $name.'_select',
+ 'options' => \@options,
+ 'labels' => \%labels,
+ 'multiple' => $def->{'multiple'},
+ 'curr_value' => $value,
+ &>
+% }
+% } elsif ( $def->{'type'} =~ /select-(.*?).html/ ) {
+ <& '/elements/'.$def->{'type'},
+ 'field' => $name,
+ 'id' => $name.'_select',
+ 'multiple' => $def->{'multiple'},
+ 'curr_value' => $value,
+ &>
+% } elsif ( $def->{'type'} eq 'communigate_pro-accessmodes' ) {
+ <& /elements/communigate_pro-accessmodes.html,
+ 'element_name_prefix' => $name.'_',
+ 'curr_value' => $value,
+ &>
+% } elsif ( $def->{'type'} eq 'textarea' ) {
+% # special cases
+ <TEXTAREA NAME="<%$name%>"><% $value |h %></TEXTAREA>
+% } elsif ( $def->{'type'} eq 'disabled' ) {
+ <INPUT TYPE="hidden" NAME="<%$name%>" VALUE="">
+% } else {
+% # the normal case: a text input, and a _select which is an inventory
+% # or hardware class
+ <INPUT TYPE="text"
+ NAME="<%$name%>"
+ ID="<%$name%>"
+ VALUE="<%$value%>">
+% # inventory class selection
+ <& /elements/select-table.html,
+ 'field' => $name.'_classnum',
+ 'id' => $name.'_select',
+ 'table' => 'inventory_class',
+ 'name_col' => 'classname',
+ 'curr_value' => $value,
+ 'empty_label' => 'Select inventory class',
+ 'multiple' => 1,
+ &>
+% }
+ </TD>
+ </TR>
+ <TR CLASS="row<%$i%>">
+ <TD COLSPAN=2 CLASS="def_info">
+% if ( $def->{def_info} ) {
+ (<% $def->{def_info} %>)
+ </TD>
+ </TR>
+% }
+% $i = 1-$i;
+% } # foreach my $field
+%
+% # special case: svc_acct password edit ACL
+% if ( $svcdb eq 'svc_acct' ) {
+% push @fields, 'restrict_edit_password';
+ <TR>
+ <TD COLSPAN=3 ALIGN="right">
+ <% emt('Require "Provision" access right to edit password') %>
+ </TD>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="restrict_edit_password" VALUE="Y" \
+ <% $part_svc->restrict_edit_password ? 'CHECKED' : '' %>>
+ </TD>
+ </TR>
+% }
+</TABLE>
+<& /elements/progress-init.html,
+ $svcdb, #form name
+ [ # form fields to send
+ qw(svc svcpart classnum selfservice_access disabled preserve exportnum),
+ @fields
+ ],
+ 'process/part_svc.cgi', # target
+ $p.'browse/part_svc.cgi', # redirect landing
+ $svcdb, #key
+&>
+% $svcpart = '' if $opt{clone};
+<BR>
+<INPUT NAME="submit"
+ TYPE="button"
+ VALUE="<% emt($svcpart ? 'Apply changes' : 'Add service') %>"
+ onclick="fixup_submit('<%$svcdb%>')"
+>
+<%init>
+my $svcdb = shift;
+my %opt = @_;
+my $columns = 3;
+my $count = 0;
+my $communigate = 0;
+my $conf = FS::Conf->new;
+
+my $part_svc = $opt{'part_svc'} || FS::part_svc->new;
+
+my @part_export;
+my $export_info = FS::part_export::export_info($svcdb);
+foreach (keys %{ $export_info }) {
+ push @part_export, qsearch('part_export', { exporttype => $_ });
+}
+$communigate = scalar(grep {$_->exporttype =~ /^communigate/} @part_export);
+
+my $svcpart = $opt{'clone'} || $part_svc->svcpart;
+my %has_export_svc;
+if ( $svcpart ) {
+ foreach (qsearch('export_svc', { svcpart => $svcpart })) {
+ $has_export_svc{$_->exportnum} = 1;
+ }
+}
+
+my @fields;
+if ( defined( dbdef->table($svcdb) ) ) { # when is it ever not defined?
+ @fields = grep {
+ $_ ne 'svcnum'
+ and ( $communigate || ! $communigate_fields{$svcdb}->{$_} )
+ and ( !FS::part_svc->svc_table_fields($svcdb)->{$_}->{disable_part_svc_column}
+ || $part_svc->part_svc_column($_)->columnflag )
+ } fields($svcdb);
+}
+if ( $svcdb eq 'svc_acct'
+ or ( $svcdb eq 'svc_broadband' and $conf->exists('svc_broadband-radius') )
+ )
+{
+ push @fields, 'usergroup';
+}
+
+my @defs = map { FS::part_svc->svc_table_fields($svcdb)->{$_} } @fields;
+</%init>
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
index 0d9d36c07..d46d1cb42 100644
--- a/httemplate/edit/elements/svc_Common.html
+++ b/httemplate/edit/elements/svc_Common.html
@@ -88,30 +88,13 @@
} elsif ( $flag eq 'A' ) {
$f->{'type'} = 'hidden';
} elsif ( $flag eq 'M' ) {
+ $f->{'type'} = 'select-inventory_item';
$f->{'empty_label'} = 'Select inventory item';
- $f->{'type'} = 'select-table';
- $f->{'table'} = 'inventory_item';
- $f->{'name_col'} = 'item';
- $f->{'value_col'} = 'item';
- $f->{'agent_virt'} = 1;
- $f->{'agent_null'} = 1;
- $f->{'hashref'} = {
- 'classnum'=>$columndef->columnvalue,
- #'svcnum' => '',
- };
- $f->{'extra_sql'} = 'AND ( svcnum IS NULL ';
- $f->{'extra_sql'} .= ' OR svcnum = '. $object->svcnum
- if $object->svcnum;
- $f->{'extra_sql'} .= ' ) ';
+ $f->{'extra_sql'} = 'WHERE ( svcnum IS NULL ' .
+ ($object->svcnum && ' OR svcnum = '.$object->svcnum) .
+ ')';
+ $f->{'classnum'} = $columndef->columnvalue;
$f->{'disable_empty'} = $object->svcnum ? 1 : 0;
- if ( $f->{'field'} eq 'mac_addr' ) {
- $f->{'compare_sub'} = sub {
- my($a, $b) = @_;
- $a =~ s/[-: ]//g;
- $b =~ s/[-: ]//g;
- lc($a) eq lc($b);
- };
- }
} elsif ( $flag eq 'H' ) {
$f->{'type'} = 'select-hardware_type';
$f->{'hashref'} = {
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 c3f4f88b6..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',
@@ -53,6 +54,7 @@
'discountnum' => 'Offer discounts for longer terms',
'bill_dst_pkgpart' => 'Include line item(s) from package',
'svc_dst_pkgpart' => 'Include services of package',
+ 'supp_dst_pkgpart' => 'Include complete package',
'report_option' => 'Report classes',
'fcc_ds0s' => 'Voice-grade equivalents',
'fcc_voip_class' => 'Category',
@@ -79,6 +81,7 @@
size => 40, #32
maxlength => 50,
},
+ #@locale_fields,
{field=>'comment', type=>'text', size=>40 }, #32
{ field => 'agentnum',
type => 'select-agent',
@@ -239,6 +242,19 @@
},
{ 'type' => 'tablebreak-tr-title',
+ 'value' => 'Supplemental packages',
+ 'colspan' => '4',
+ },
+ { 'field' => 'supp_dst_pkgpart',
+ 'type' => 'select-part_pkg',
+ 'm2_label' => 'Include complete package',
+ 'm2m_method' => 'supp_part_pkg_link',
+ 'm2m_dstcol' => 'dst_pkgpart',
+ 'm2_error_callback' =>
+ &{$m2_error_callback_maker}('supp'),
+ },
+
+ { 'type' => 'tablebreak-tr-title',
'value' => 'Pricing add-ons',
'colspan' => 4,
},
@@ -323,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;
@@ -354,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 ) = @_;
@@ -394,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' }; };
@@ -459,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 {
@@ -473,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 {
@@ -506,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/part_svc.cgi b/httemplate/edit/part_svc.cgi
index 007c24629..58c237efd 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -1,11 +1,111 @@
-<& /elements/header.html, "$action Service Definition",
- menubar('View all service definitions' => "${p}browse/part_svc.cgi"),
+<& /elements/header.html, "$action Service Definition" &>
+<& /elements/menubar.html,
+ 'View all service definitions' => "${p}browse/part_svc.cgi"
#" onLoad=\"visualize()\""
&>
<& /elements/init_overlib.html &>
-<BR>
+<BR><BR>
+
+<STYLE TYPE="text/css">
+.disabled {
+ background-color: #dddddd;
+}
+.hidden {
+ display: none;
+}
+.enabled {
+ background-color: #ffffff;
+}
+.row0 TD {
+ background-color: #eeeeee;
+}
+.row1 TD {
+ background-color: #ffffff;
+}
+.def_info {
+ text-align: center;
+ padding: 0px;
+ border-top: none;
+ font-size: smaller;
+ font-style: italic;
+}
+</STYLE>
+<SCRIPT TYPE="text/javascript">
+function fixup_submit(layer) {
+ document.forms[layer].submit.disabled = true;
+ fixup(document.forms[layer]);
+ window[layer+'process'].call();
+}
+
+function flag_changed(obj) {
+ var newflag = obj.value;
+ var a = obj.name.match(/(.*)__(.*)_flag/);
+ var layer = a[1];
+ var field = a[2];
+ var input = document.getElementById(layer + '__' + field);
+ // for fields that have both 'input' and 'select', 'select' is 'select from
+ // inventory class'.
+ var select = document.getElementById(layer + '__' + field + '_select');
+ if (newflag == "" || newflag == "X") { // disable
+ if ( input ) {
+ input.disabled = true;
+ input.className = 'disabled';
+ }
+ if ( select ) {
+ select.disabled = true;
+ select.className = 'hidden';
+ }
+ } else if ( newflag == 'D' || newflag == 'F' || newflag == 'S' ) {
+ if ( input ) {
+ // enable text box, disable inventory select
+ input.disabled = false;
+ input.className = 'enabled';
+ if ( select ) {
+ select.disabled = false;
+ select.className = 'hidden';
+ }
+ } else if ( select ) {
+ // enable select
+ select.disabled = false;
+ select.className = 'enabled';
+ if ( newflag == 'S' || select.getAttribute('should_be_multiple') ) {
+ select.multiple = true;
+ } else {
+ select.multiple = false;
+ }
+ }
+ } else if ( newflag == 'M' || newflag == 'A' || newflag == 'H' ) {
+ // these all require a class selection
+ if ( select ) {
+ select.disabled = false;
+ select.className = 'enabled';
+ if ( input ) {
+ input.disabled = false;
+ input.className = 'hidden';
+ }
+ }
+ }
+}
+
+window.onload = function() {
+ var selects = document.getElementsByTagName('SELECT');
+ for(i = 0; i < selects.length; i++) {
+ var obj = selects[i];
+ if ( obj.multiple ) {
+ obj.setAttribute('should_be_multiple', true);
+ }
+ }
+ for(i = 0; i < selects.length; i++) {
+ var obj = selects[i];
+ if ( obj.name.match(/_flag$/) ) {
+ flag_changed(obj);
+ }
+ }
+};
+
+</SCRIPT>
<FORM NAME="dummy">
@@ -53,386 +153,6 @@
<BR>
-% my %vfields;
-% #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm
-% # and generalize the subs
-% # condition sub is tested to see whether to disable display of this choice
-% # params: ( $def, $layer, $field ) (see SUB below)
-% my $inv_sub = sub {
-% $_[0]->{disable_inventory}
-% || $_[0]->{'type'} ne 'text'
-% };
-% tie my %flag, 'Tie::IxHash',
-% '' => { 'desc' => 'No default', },
-% 'D' => { 'desc' => 'Default',
-% 'condition' =>
-% sub { $_[0]->{disable_default} },
-% },
-% 'F' => { 'desc' => 'Fixed (unchangeable)',
-% 'condition' =>
-% sub { $_[0]->{disable_fixed} },
-% },
-% 'S' => { 'desc' => 'Selectable Choice',
-% 'condition' =>
-% sub { !ref($_[0]) || $_[0]->{disable_select} },
-% },
-% 'M' => { 'desc' => 'Manual selection from inventory',
-% 'condition' => $inv_sub,
-% },
-% 'A' => { 'desc' => 'Automatically fill in from inventory',
-% 'condition' => $inv_sub,
-% },
-% 'H' => { 'desc' => 'Select from hardware class',
-% 'condition' => sub { $_[0]->{type} ne 'select-hardware' },
-% },
-% 'X' => { 'desc' => 'Excluded',
-% 'condition' =>
-% sub { ! $vfields{$_[1]}->{$_[2]} },
-%
-% },
-% ;
-%
-% my @dbs = $hashref->{svcdb}
-% ? ( $hashref->{svcdb} )
-% : FS::part_svc->svc_tables();
-%
-% my $help = '';
-% unless ( $hashref->{svcpart} ) {
-% $help = '&nbsp;'.
-% include('/elements/popup_link.html',
-% 'action' => $p.'docs/part_svc-table.html',
-% 'label' => 'help',
-% 'actionlabel' => 'Service table help',
-% 'width' => 763,
-% #'height' => 400,
-% );
-% }
-%
-% tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs;
-% my $widget = new HTML::Widgets::SelectLayers(
-% #'selected_layer' => $p_svcdb,
-% 'selected_layer' => $hashref->{svcdb} || 'svc_acct',
-% 'options' => \%svcdb,
-% 'form_name' => 'dummy',
-% #'form_action' => 'process/part_svc.cgi',
-% 'form_action' => 'part_svc.cgi', #self
-% 'form_elements' => [qw( svc svcpart classnum selfservice_access
-% disabled preserve
-% )],
-% 'html_between' => $help,
-% 'layer_callback' => sub {
-% my $layer = shift;
-%
-% my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!;
-%
-% #$html .= $svcdb_info;
-%
-% my $columns = 3;
-% my $count = 0;
-% my $communigate = 0;
-% my @part_export =
-% map { qsearch( 'part_export', {exporttype => $_ } ) }
-% keys %{FS::part_export::export_info($layer)};
-% $html .= '<BR><BR>'. include('/elements/table.html') .
-% "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>";
-% foreach my $part_export ( @part_export ) {
-% $communigate++ if $part_export->exporttype =~ /^communigate/;
-% $html .= '<TD><INPUT TYPE="checkbox"'.
-% ' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" ';
-% $html .= 'CHECKED'
-% if ( $clone || $part_svc->svcpart ) #null svcpart search causing error
-% && qsearchs( 'export_svc', {
-% exportnum => $part_export->exportnum,
-% svcpart => $clone || $part_svc->svcpart });
-% $html .= '>'. $part_export->label_html. '</TD>';
-% $count++;
-% $html .= '</TR><TR>' unless $count % $columns;
-% }
-% $html .= '</TR></TABLE><BR><BR>'. $mod_info;
-%
-% $html .= include('/elements/table-grid.html', 'cellpadding' => 4 ).
-% '<TR>'.
-% '<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>'.
-% '<TH CLASS="grid" BGCOLOR="#cccccc">Label</TH>'.
-% '<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>'.
-% '</TR>';
-%
-% my $bgcolor1 = '#eeeeee';
-% my $bgcolor2 = '#ffffff';
-% my $bgcolor;
-%
-% #yucky kludge
-% my @fields = ();
-% if ( defined( dbdef->table($layer) ) ) {
-% @fields = grep {
-% $_ ne 'svcnum'
-% && ( $communigate || !$communigate_fields{$layer}->{$_} )
-% && ( !FS::part_svc->svc_table_fields($layer)
-% ->{$_}->{disable_part_svc_column}
-% || $part_svc->part_svc_column($_)->columnflag
-% )
-% } fields($layer);
-% }
-% push @fields, 'usergroup'
-% if $layer eq 'svc_acct'
-% or ( $layer eq 'svc_broadband' and
-% $conf->exists('svc_broadband-radius') ); # double kludge
-% # (but we do want to check the config, right?)
-% $part_svc->svcpart($clone) if $clone; #haha, undone below
-%
-%
-% foreach my $field (@fields) {
-%
-% #a few lines of false laziness w/browse/part_svc.cgi
-% my $def = FS::part_svc->svc_table_fields($layer)->{$field};
-% my $def_info = $def->{'def_info'};
-% my $formatter = $def->{'format'} || sub { shift };
-%
-% my $part_svc_column = $part_svc->part_svc_column($field);
-% my $label = $part_svc_column->columnlabel || $def->{'label'};
-% my $value = &$formatter($part_svc_column->columnvalue);
-% my $flag = $part_svc_column->columnflag;
-%
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
-%
-% $html .= qq!<TR><TD ROWSPAN=2 CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!.
-% ( $def->{'label'} || $field ).
-% "</TD>";
-%
-% $html .= qq!<TD ROWSPAN=2 CLASS="grid" BGCOLOR="$bgcolor"><INPUT NAME="${layer}__${field}_label" VALUE="!. encode_entities($label). '" STYLE="text-align:right"></TD>';
-%
-% $flag = '' if $def->{type} eq 'disabled';
-%
-% $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!;
-%
-% if ( $def->{type} eq 'disabled' ) {
-%
-% $html .= 'No default';
-%
-% } else {
-%
-% $html .= qq!<SELECT NAME="${layer}__${field}_flag"!.
-% qq! onChange="${layer}__${field}_flag_changed(this)">!;
-%
-% foreach my $f ( keys %flag ) {
-%
-% # need to template-ize more httemplate/edit/svc_* first
-% next if $f eq 'M' and $layer !~ /^svc_(broadband|external|phone|dish)$/;
-%
-% #here is where the SUB from above is called, to skip some choices
-% next if $flag{$f}->{condition}
-% && &{ $flag{$f}->{condition} }( $def, $layer, $field );
-%
-% $html .= qq!<OPTION VALUE="$f"!.
-% ' SELECTED'x($flag eq $f ).
-% '>'. $flag{$f}->{desc};
-%
-% }
-%
-% $html .= '</SELECT>';
-%
-% $html .= join("\n",
-% '<SCRIPT>',
-% " function ${layer}__${field}_flag_changed(what) {",
-% ' var f = what.options[what.selectedIndex].value;',
-% ' if ( f == "" || f == "X" ) { //disable',
-% " what.form.${layer}__${field}.disabled = true;".
-% " what.form.${layer}__${field}.style.backgroundColor = '#dddddd';".
-% " if ( what.form.${layer}__${field}_classnum ) {".
-% " what.form.${layer}__${field}_classnum.disabled = true;".
-% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#dddddd';".
-% " }".
-% ' } else if ( f == "D" || f == "F" || f =="S" ) { //enable, text box',
-% " what.form.${layer}__${field}.disabled = false;".
-% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
-% " if ( f == 'S' || '${field}' == 'usergroup' ) {". # kludge
-% " what.form.${layer}__${field}.multiple = true;".
-% " } else {".
-% " what.form.${layer}__${field}.multiple = false;".
-% " }".
-% " what.form.${layer}__${field}.style.display = '';".
-% " if ( what.form.${layer}__${field}_classnum ) {".
-% " what.form.${layer}__${field}_classnum.disabled = false;".
-% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
-% " what.form.${layer}__${field}_classnum.style.display = 'none';".
-% " }".
-% ' } else if ( f == "M" || f == "A" || f == "H" ) { '.
-% '//enable, inventory',
-% " what.form.${layer}__${field}.disabled = false;".
-% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
-% " what.form.${layer}__${field}.style.display = 'none';".
-% " if ( what.form.${layer}__${field}_classnum ) {".
-% " what.form.${layer}__${field}_classnum.disabled = false;".
-% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
-% " what.form.${layer}__${field}_classnum.style.display = '';".
-% " }".
-% ' }',
-% ' }',
-% '</SCRIPT>',
-% );
-%
-% }
-%
-% $html .= qq!</TD><TD CLASS="grid" BGCOLOR="$bgcolor">!;
-%
-% my $disabled = $flag ? ''
-% : 'DISABLED STYLE="background-color: #dddddd"';
-% my $nodisplay = ' STYLE="display:none"';
-%
-% if ( !$def->{type} || $def->{type} eq 'text' ) {
-%
-% my $is_inv = ( $flag =~ /^[MA]$/ );
-%
-% $html .=
-% qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value" !.
-% $disabled.
-% ( $is_inv ? $nodisplay : $disabled ).
-% '>';
-%
-% $html .= include('/elements/select-table.html',
-% 'element_name' => "${layer}__${field}_classnum",
-% 'id' => "${layer}__${field}_classnum",
-% 'element_etc' => ( $is_inv
-% ? $disabled
-% : $nodisplay
-% ),
-% 'table' => 'inventory_class',
-% 'name_col' => 'classname',
-% 'value' => $value,
-% 'empty_label' => 'Select inventory class',
-% );
-%
-% } elsif ( $def->{type} eq 'checkbox' ) {
-%
-% $html .= include('/elements/checkbox.html',
-% 'field' => $layer.'__'.$field,
-% 'curr_value' => $value,
-% 'value' => 'Y',
-% );
-%
-% } elsif ( $def->{type} eq 'select' ) {
-%
-% $html .= qq!<SELECT NAME="${layer}__${field}" $disabled!;
-% $html .= ' MULTIPLE' if $flag eq 'S';
-% $html .= '>';
-% $html .= '<OPTION> </OPTION>' unless $value;
-% if ( $def->{select_table} ) {
-% foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
-% my $rvalue = $record->getfield($def->{select_key});
-% my $select_label = $def->{select_label};
-% $html .= qq!<OPTION VALUE="$rvalue"!.
-% (grep(/^$rvalue$/, split(',',$value)) ? ' SELECTED>' : '>' ).
-% $record->$select_label(). '</OPTION>';
-% } #next $record
-% } elsif ( $def->{select_list} ) {
-% foreach my $item ( @{$def->{select_list}} ) {
-% $html .= qq!<OPTION VALUE="$item"!.
-% (grep(/^$item$/, split(',',$value)) ? ' SELECTED>' : '>' ).
-% $item. '</OPTION>';
-% } #next $item
-% } elsif ( $def->{select_hash} ) {
-% if ( ref($def->{select_hash}) eq 'ARRAY' ) {
-% tie my %hash, 'Tie::IxHash', @{ $def->{select_hash} };
-% $def->{select_hash} = \%hash;
-% }
-% foreach my $key ( keys %{$def->{select_hash}} ) {
-% $html .= qq!<OPTION VALUE="$key"!.
-% (grep(/^$key$/, split(',',$value)) ? ' SELECTED>' : '>' ).
-% $def->{select_hash}{$key}. '</OPTION>';
-% } #next $key
-% } #endif
-% $html .= '</SELECT>';
-%
-% } elsif ( $def->{type} eq 'textarea' ) {
-%
-% $html .=
-% qq!<TEXTAREA NAME="${layer}__${field}">!. encode_entities($value).
-% '</TEXTAREA>';
-%
-% } elsif ( $def->{type} =~ /select-(.*?).html/ ) {
-%
-% $html .= include("/elements/".$def->{type},
-% 'curr_value' => $value,
-% 'element_name' => "${layer}__${field}",
-% 'element_etc' => $disabled,
-% 'multiple' => ($def->{multiple} ||
-% $flag eq 'S'),
-% # allow the table def to force 'multiple'
-% );
-%
-% } elsif ( $def->{type} eq 'communigate_pro-accessmodes' ) {
-%
-% $html .= include('/elements/communigate_pro-accessmodes.html',
-% 'element_name_prefix' => "${layer}__${field}_",
-% 'curr_value' => $value,
-% #doesn't work#'element_etc' => $disabled,
-% );
-%
-% } elsif ( $def->{type} eq 'select-hardware' ) {
-%
-% $html .= qq!<INPUT TYPE="text" NAME="${layer}__${field}" $disabled>!;
-% $html .= include('/elements/select-hardware_class.html',
-% 'curr_value' => $value,
-% 'element_name' => "${layer}__${field}_classnum",
-% 'id' => "${layer}__${field}_classnum",
-% 'element_etc' => $flag ne 'H' && $nodisplay,
-% 'empty_label' => 'Select hardware class',
-% );
-%
-% } elsif ( $def->{type} eq 'disabled' ) {
-%
-% $html .=
-% qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!;
-%
-% } else {
-%
-% $html .= '<font color="#ff0000">unknown type '. $def->{type};
-%
-% }
-%
-% $html .= "</TD></TR>\n";
-
-% $def_info = "($def_info)" if $def_info;
-% $html .=
-% qq!<TR>!.
-% qq! <TD COLSPAN=2 BGCOLOR="$bgcolor" ALIGN="center" !.
-% qq! STYLE="padding:0; border-top: none">!.
-% qq! <FONT SIZE="-1"><I>$def_info</I></FONT>!.
-% qq! </TD>!.
-% qq!</TR>\n!;
-%
-% } #foreach my $field (@fields) {
-%
-% $part_svc->svcpart('') if $clone; #undone
-% $html .= "</TABLE>";
-%
-% $html .= include('/elements/progress-init.html',
-% $layer, #form name
-% [ qw(svc svcpart classnum selfservice_access
-% disabled preserve
-% exportnum),
-% @fields ],
-% 'process/part_svc.cgi',
-% $p.'browse/part_svc.cgi',
-% $layer,
-% );
-% $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'.
-% ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '.
-% ' onClick="document.'. "$layer.submit.disabled=true; ".
-% "fixup(document.$layer); $layer". 'process();">';
-%
-% #$html .= '<BR><INPUT TYPE="submit" VALUE="'.
-% # ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">';
-%
-% $html;
-%
-% },
-% );
-
<BR>
Table <% $widget->html %>
@@ -465,28 +185,43 @@ my $action = $part_svc->svcpart ? 'Edit' : 'Add';
my $hashref = $part_svc->hashref;
# my $p_svcdb = $part_svc->svcdb || 'svc_acct';
-my %communigate_fields = (
- 'svc_acct' => { map { $_=>1 }
- qw( file_quota file_maxnum file_maxsize
- password_selfchange password_recover
- ),
- grep /^cgp_/, fields('svc_acct')
- },
- 'svc_domain' => { map { $_=>1 }
- qw( max_accounts trailer parent_svcnum ),
- grep /^(cgp|acct_def)_/, fields('svc_domain')
- },
- #'svc_forward' => { map { $_=>1 } qw( ) },
- #'svc_mailinglist' => { map { $_=>1 } qw( ) },
- #'svc_cert' => { map { $_=>1 } qw( ) },
-);
-my $mod_info = '
-For the selected table, you can give fields default or fixed (unchangable)
-values, or select an inventory class to manually or automatically fill in
-that field.
-';
+my @dbs = $hashref->{svcdb}
+ ? ( $hashref->{svcdb} )
+ : FS::part_svc->svc_tables();
+
+my $help = '';
+unless ( $hashref->{svcpart} ) {
+ $help = '&nbsp;'.
+ include('/elements/popup_link.html',
+ 'action' => $p.'docs/part_svc-table.html',
+ 'label' => 'help',
+ 'actionlabel' => 'Service table help',
+ 'width' => 763,
+ #'height' => 400,
+ );
+}
+tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs;
+my $widget = new HTML::Widgets::SelectLayers(
+ #'selected_layer' => $p_svcdb,
+ 'selected_layer' => $hashref->{svcdb} || 'svc_acct',
+ 'options' => \%svcdb,
+ 'form_name' => 'dummy',
+ #'form_action' => 'process/part_svc.cgi',
+ 'form_action' => 'part_svc.cgi', #self
+ 'form_elements' => [qw( svc svcpart classnum selfservice_access
+ disabled preserve
+ )],
+ 'html_between' => $help,
+ 'layer_callback' => sub {
+ include('elements/part_svc_column.html',
+ shift,
+ 'part_svc' => $part_svc,
+ 'clone' => $clone
+ )
+ }
+);
</%init>
diff --git a/httemplate/edit/part_tag.html b/httemplate/edit/part_tag.html
index 5712560c1..2cf34c6e8 100644
--- a/httemplate/edit/part_tag.html
+++ b/httemplate/edit/part_tag.html
@@ -8,7 +8,7 @@
{ field=>'by_default', type=>'checkbox', value=>'Y' },
$tagcolor,
],
- 'labels' => { 'tagnum' => 'Tag #',
+ 'labels' => { 'tagnum' => 'Tag',
'tagname' => 'Tag',
'tagdesc' => 'Message',
'tagcolor' => 'Highlight Color',
diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html
index dfe52f109..a469beb7f 100644
--- a/httemplate/edit/payment_gateway.html
+++ b/httemplate/edit/payment_gateway.html
@@ -19,7 +19,7 @@
<SCRIPT TYPE="text/javascript">
- var modulesForNamespace = <% to_json(\%modules_for_namespace, {canonical=>1}) %>;
+ var modulesForNamespace = <% encode_json(\%modules_for_namespace, {canonical=>1}) %>;
function changeNamespace(what) {
var ns = what.value;
var select_module = document.getElementById('gateway_module');
diff --git a/httemplate/edit/phone_device.html b/httemplate/edit/phone_device.html
index 4aec63e5a..7bc88a8c7 100644
--- a/httemplate/edit/phone_device.html
+++ b/httemplate/edit/phone_device.html
@@ -32,12 +32,11 @@
%>
<%init>
-my @deviceparts_with_inventory;
-my @part_device = qsearch('part_device', {} );
-foreach my $part_device ( @part_device ) {
- push @deviceparts_with_inventory, $part_device->devicepart
- if $part_device->inventory_classnum;
-}
+my @deviceparts_with_inventory =
+ map $_->devicepart,
+ qsearch({ 'table' => 'part_device',
+ 'extra_sql' => 'WHERE inventory_classnum IS NOT NULL',
+ });
my $html_foot = sub {
my $js = "
@@ -72,9 +71,9 @@ my $html_foot = sub {
var devicepart = what.options[what.selectedIndex].value;
- var deviceparts_with_inventory = new Array(\"";
-$js .= join("\",\"",@deviceparts_with_inventory);
-$js .= "\");
+ var deviceparts_with_inventory = new Array(";
+$js .= join(',', map qq("$_"), @deviceparts_with_inventory);
+$js .= ");
var hasInventory = false;
for ( i = 0; i < deviceparts_with_inventory.length; i++ ) {
diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi
index 3e0ef59c1..fd2893487 100755
--- a/httemplate/edit/process/REAL_cust_pkg.cgi
+++ b/httemplate/edit/process/REAL_cust_pkg.cgi
@@ -19,36 +19,41 @@ die "access denied"
my $pkgnum = $cgi->param('pkgnum') or die;
my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
my %hash = $old->hash;
-$hash{$_}= $cgi->param($_) ? parse_datetime($cgi->param($_)) : ''
- foreach qw( start_date setup bill last_bill contract_end );
+foreach ( qw( start_date setup bill last_bill contract_end ) ) {
+ if ( $cgi->param($_) =~ /^(\d+)$/ ) {
+ $hash{$_} = $1;
+ } else {
+ $hash{$_} = '';
+ }
# adjourn, expire, resume not editable this way
-
-my @errors = ();
-
-push @errors, '_bill_areyousure'
- if $hash{'bill'} != $old->bill # if the next bill date was changed
- && $hash{'bill'} < time # to a date in the past
- && ! $cgi->param('bill_areyousure'); # and it wasn't confirmed
-
-push @errors, '_setup_areyousure'
- if ! $hash{'setup'} && $old->setup # if the setup date was removed
- && ! $cgi->param('setup_areyousure'); # and it wasn't confirmed
-
-push @errors, '_setupadd_areyousure'
- if $hash{'setup'} && ! $old->setup # if the setup date was added
- && ! $cgi->param('setupadd_areyousure'); # and it wasn't confirmed
-
-push @errors, '_start'
- if $hash{'start_date'} && !$old->start_date # if a start date was added
- && $hash{'setup'}; # but there's a setup date
+}
my $new;
my $error;
-if ( @errors ) {
- $error = join(',', @errors);
-} else {
- $new = new FS::cust_pkg \%hash;
- $error = $new->replace($old);
+$new = new FS::cust_pkg \%hash;
+$error = $new->replace($old);
+
+if (!$error) {
+ my @supp_pkgs = $old->supplemental_pkgs;
+ foreach $new (@supp_pkgs) {
+ foreach ( qw( start_date setup contract_end ) ) {
+ # propagate these to supplementals
+ $new->set($_, $hash{$_});
+ }
+ if ( $hash{'bill'} ne $old->get('bill') ) {
+ if ( $hash{'bill'} and $old->get('bill') ) {
+ # adjust by the same interval
+ my $diff = $hash{'bill'} - $old->get('bill');
+ $new->set('bill', $new->get('bill') + $diff);
+ } else {
+ # absolute date
+ $new->set('bill', $hash{'bill'});
+ }
+ }
+ $error = $new->replace;
+ $error .= ' (supplemental package '.$new->pkgnum.')' if $error;
+ last if $error;
+ }
}
</%init>
diff --git a/httemplate/edit/process/bulk-part_pkg.html b/httemplate/edit/process/bulk-part_pkg.html
new file mode 100644
index 000000000..4775a9334
--- /dev/null
+++ b/httemplate/edit/process/bulk-part_pkg.html
@@ -0,0 +1,30 @@
+% if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(3).'/edit/bulk-part_pkg.cgi?', $cgi->query_string) %>
+% } else {
+<% $cgi->redirect(popurl(3).'/browse/part_pkg.cgi') %>
+% }
+<%init>
+die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Bulk edit package definitions');
+
+my @pkgparts = $cgi->param('pkgpart')
+ or die "no package definitions selected";
+
+my %changes;
+foreach my $param (grep { /^report_option_\d+$/ } $cgi->param) {
+ if ( length($cgi->param($param)) ) {
+ if ( $cgi->param($param) == 1 ) {
+ $changes{$param} = 1;
+ } else {
+ $changes{$param} = '';
+ }
+ }
+}
+
+my $error;
+foreach my $pkgpart (@pkgparts) {
+ my $part_pkg = FS::part_pkg->by_key($pkgpart);
+ my %options = ( $part_pkg->options, %changes );
+ $error ||= $part_pkg->replace( options => \%options );
+}
+</%init>
diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html
index 2770f3283..77f261d56 100644
--- a/httemplate/edit/process/change-cust_pkg.html
+++ b/httemplate/edit/process/change-cust_pkg.html
@@ -32,11 +32,11 @@ my %change = map { $_ => scalar($cgi->param($_)) }
$change{'keep_dates'} = 1;
if ( $cgi->param('locationnum') == -1 ) {
- my $cust_location = new FS::cust_location {
+ my $cust_location = FS::cust_location->new_or_existing({
'custnum' => $cust_pkg->custnum,
map { $_ => scalar($cgi->param($_)) }
qw( address1 address2 city county state zip country )
- };
+ });
$change{'cust_location'} = $cust_location;
}
diff --git a/httemplate/edit/process/credit-cust_bill_pkg.html b/httemplate/edit/process/credit-cust_bill_pkg.html
index cbcf619ca..8e66368d4 100644
--- a/httemplate/edit/process/credit-cust_bill_pkg.html
+++ b/httemplate/edit/process/credit-cust_bill_pkg.html
@@ -10,7 +10,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Post credit');
+ unless $FS::CurrentUser::CurrentUser->access_right('Credit line items');
my @billpkgnum_setuprecurs =
map { $_ =~ /^billpkgnum(\d+\-\w*)$/ or die 'gm#23'; $1; }
diff --git a/httemplate/edit/process/cust_location.cgi b/httemplate/edit/process/cust_location.cgi
index b9f93db8b..56c3968f6 100644
--- a/httemplate/edit/process/cust_location.cgi
+++ b/httemplate/edit/process/cust_location.cgi
@@ -28,11 +28,10 @@ my $cust_location = qsearchs({
});
die "unknown locationnum $locationnum" unless $cust_location;
-my $new = FS::cust_location->new({
+my $new = FS::cust_location->new_or_existing({
custnum => $cust_location->custnum,
prospectnum => $cust_location->prospectnum,
- map { $_ => scalar($cgi->param($_)) }
- qw( address1 address2 city county state zip country )
+ map { $_ => scalar($cgi->param($_)) } FS::cust_main->location_fields
});
my $error = $cust_location->move_to($new);
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 31ec4ab12..c1f815550 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -16,8 +16,8 @@ my $DEBUG = 0;
</%once>
<%init>
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" unless $curuser->access_right('Edit customer');
my $conf = new FS::Conf;
@@ -62,6 +62,18 @@ $cgi->param('invoicing_list', join(',', @invoicing_list) );
$cgi->param('duplicate_of_custnum') =~ /^(\d+)$/;
my $duplicate_of = $1;
+# if this is enabled, enforce it
+if ( $conf->exists('agent-ship_address', $cgi->param('agentnum')) ) {
+ my $agent = FS::agent->by_key($cgi->param('agentnum'));
+ my $agent_cust_main = $agent->agent_cust_main;
+ if ( $agent_cust_main ) {
+ my $agent_location = $agent_cust_main->ship_location;
+ foreach (qw(address1 city state zip country latitude longitude district)) {
+ $cgi->param("ship_$_", $agent_location->get($_));
+ }
+ }
+}
+
my %locations;
for my $pre (qw(bill ship)) {
@@ -71,10 +83,7 @@ for my $pre (qw(bill ship)) {
}
$hash{'custnum'} = $cgi->param('custnum');
warn Dumper \%hash if $DEBUG;
- # if we can qsearchs it, then it's unchanged, so use that
- $locations{$pre} = qsearchs('cust_location', \%hash)
- || FS::cust_location->new( \%hash );
-
+ $locations{$pre} = FS::cust_location->new_or_existing(\%hash);
}
if ( ($cgi->param('same') || '') eq 'Y' ) {
@@ -156,9 +165,14 @@ foreach my $dfield (qw(
$new->setfield('paid', $cgi->param('paid') )
if $cgi->param('paid');
-my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
-my @tax_exempt = grep { $cgi->param("tax_$_") eq 'Y' } @exempt_groups;
-my %tax_exempt = map { $_ => scalar($cgi->param("tax_$_".'_num')) } @tax_exempt;
+my %options = ();
+if ( $curuser->access_right('Edit customer tax exemptions') ) {
+ my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
+ my @tax_exempt = grep { $cgi->param("tax_$_") eq 'Y' } @exempt_groups;
+ $options{'tax_exemption'} = {
+ map { $_ => scalar($cgi->param("tax_$_".'_num')) } @tax_exempt
+ };
+}
#perhaps this stuff should go to cust_main.pm
if ( $new->custnum eq '' or $duplicate_of ) {
@@ -266,8 +280,8 @@ if ( $new->custnum eq '' or $duplicate_of ) {
else {
# create the customer
$error ||= $new->insert( \%hash, \@invoicing_list,
- 'tax_exemption'=> \%tax_exempt,
- 'prospectnum' => scalar($cgi->param('prospectnum')),
+ %options,
+ prospectnum => scalar($cgi->param('prospectnum')),
);
my $conf = new FS::Conf;
@@ -328,7 +342,7 @@ if ( $new->custnum eq '' or $duplicate_of ) {
warn Dumper({ new => $new, old => $old }) if $DEBUG;
$error ||= $new->replace( $old, \@invoicing_list,
- 'tax_exemption' => \%tax_exempt,
+ %options,
);
warn "$me returned from replace" if $DEBUG;
diff --git a/httemplate/edit/process/cust_pkg_quantity.html b/httemplate/edit/process/cust_pkg_quantity.html
new file mode 100644
index 000000000..fb2657252
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg_quantity.html
@@ -0,0 +1,33 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). 'edit/cust_pkg_quantity.html?'. $cgi->query_string );
+% } else {
+
+ <& /elements/header-popup.html, "Quantity changed" &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $cust_pkg = qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => scalar($cgi->param('pkgnum')), },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die 'unknown pkgnum' unless $cust_pkg;
+
+$cgi->param('quantity') =~ /^(\d+)$/;
+my $quantity = $1;
+my $error = $cust_pkg->set_quantity($1);
+
+</%init>
diff --git a/httemplate/edit/process/cust_svc.cgi b/httemplate/edit/process/cust_svc.cgi
index e22cbb201..7cb1d6d8f 100644
--- a/httemplate/edit/process/cust_svc.cgi
+++ b/httemplate/edit/process/cust_svc.cgi
@@ -6,7 +6,7 @@
%}
<%init>
-die 'access deined'
+die 'access denied'
unless $FS::CurrentUser::CurrentUser->access_right('Change customer service');
my $svcnum = $cgi->param('svcnum');
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
index 2d39e9dce..fb1ee7a27 100644
--- a/httemplate/edit/process/elements/process.html
+++ b/httemplate/edit/process/elements/process.html
@@ -263,6 +263,9 @@ foreach my $value ( @values ) {
if ( !$error ) {
if ( $old_pkey ) {
+
+ &{ $opt{'edit_callback'} }( $new, $old ) if $opt{'edit_callback'};
+
$error = $new->replace($old, @args);
} else {
$error = $new->insert(@args);
diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html
index 5a8afbd6c..06f4c00b1 100644
--- a/httemplate/edit/process/elements/svc_Common.html
+++ b/httemplate/edit/process/elements/svc_Common.html
@@ -10,5 +10,10 @@ my %opt = @_;
my $table = $opt{'table'};
$opt{'fields'} ||= [ fields($table) ];
push @{ $opt{'fields'} }, qw( pkgnum svcpart );
+foreach (fields($table)) {
+ if ( $cgi->param($_.'_classnum') ) {
+ push @{ $opt{'fields'} }, $_.'_classnum';
+ }
+}
</%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 c388676df..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>
@@ -185,6 +186,15 @@ my @process_m2m = (
grep /^svc_dst_pkgpart/, $cgi->param
],
},
+ { 'link_table' => 'part_pkg_link',
+ 'target_table' => 'part_pkg',
+ 'base_field' => 'src_pkgpart',
+ 'target_field' => 'dst_pkgpart',
+ 'hashref' => { 'link_type' => 'supp', 'hidden' => '' },
+ 'params' => [ map $cgi->param($_),
+ grep /^supp_dst_pkgpart/, $cgi->param
+ ],
+ },
map {
my $hidden = $_;
{ 'link_table' => 'part_pkg_link',
@@ -235,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/part_pkg_usage.html b/httemplate/edit/process/part_pkg_usage.html
new file mode 100644
index 000000000..eb6c37b82
--- /dev/null
+++ b/httemplate/edit/process/part_pkg_usage.html
@@ -0,0 +1,67 @@
+% if ( $is_error ) {
+% $cgi->param('error' => \%part_pkg_usage);
+% # internal redirect, because it's a lot of state to pass through
+<& /browse/part_pkg_usage.html &>
+% } else {
+% # uh, not quite sure...
+<% $cgi->redirect($fsurl.'browse/part_pkg.cgi') %>
+% }
+<%init>
+my %vars = $cgi->Vars;
+my %part_pkg_usage;
+my $is_error;
+foreach my $pkgpart ($cgi->param('pkgpart')) {
+ next unless $pkgpart =~ /^\d+$/;
+ my $part_pkg = FS::part_pkg->by_key($pkgpart)
+ or die "unknown pkgpart $pkgpart";
+ my %old = map { $_->pkgusagepart => $_ } $part_pkg->part_pkg_usage;
+ $part_pkg_usage{$pkgpart} ||= [];
+ my @rows;
+ foreach (grep /^pkgpart$pkgpart/, keys %vars) {
+ /^pkgpart\d+_(\w+\D)(\d+)$/ or die "misspelled field name '$_'";
+ my $value = delete $vars{$_};
+ my $field = $1;
+ my $row = $2;
+ $rows[$row] ||= {};
+ $rows[$row]->{$field} = $value;
+ }
+
+ foreach my $row (@rows) {
+ next if !defined($row);
+ my $error;
+ my %classes;
+ foreach my $class (grep /^class/, keys %$row) {
+ $class =~ /^class(\d+)_$/;
+ my $classnum = $1;
+ $classes{$classnum} = delete $row->{$class};
+ }
+ my $usage = FS::part_pkg_usage->new($row);
+ $usage->set('pkgpart', $pkgpart);
+ if ( $usage->pkgusagepart and $row->{minutes} > 0 ) {
+ $error = $usage->replace(\%classes);
+ # and don't delete the existing one
+ delete($old{$usage->pkgusagepart});
+ } elsif ( $row->{minutes} > 0 ) {
+ $error = $usage->insert(\%classes);
+ } else {
+ next;
+ }
+ if ( $error ) {
+ $usage->set('error', $error);
+ $is_error = 1;
+ }
+ push @{ $part_pkg_usage{$pkgpart} }, $usage;
+ }
+
+ foreach my $usage (values %old) {
+ # all of these were not sent back by the client, so delete them
+ my $error = $usage->delete;
+ if ( $error ) {
+ $usage->set('error', $error);
+ $is_error = 1;
+ unshift @{ $part_pkg_usage{$pkgpart} }, $usage;
+ }
+ }
+
+}
+</%init>
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
index 2dadbccdc..0cc17d36b 100644
--- a/httemplate/edit/process/quick-cust_pkg.cgi
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -70,6 +70,9 @@ my $quantity = $1 || 1;
$cgi->param('refnum') =~ /^(\d*)$/
or die 'illegal refnum '. $cgi->param('refnum');
my $refnum = $1;
+$cgi->param('contactnum') =~ /^(\-?\d*)$/
+ or die 'illegal contactnum '. $cgi->param('contactnum');
+my $contactnum = $1;
$cgi->param('locationnum') =~ /^(\-?\d*)$/
or die 'illegal locationnum '. $cgi->param('locationnum');
my $locationnum = $1;
@@ -109,6 +112,7 @@ my %hash = (
: ''
),
'refnum' => $refnum,
+ 'contactnum' => $contactnum,
'locationnum' => $locationnum,
'discountnum' => $discountnum,
#for the create a new discount case
@@ -142,11 +146,19 @@ if ( $quotationnum ) {
my %opt = ( 'cust_pkg' => $cust_pkg );
+ if ( $contactnum == -1 ) {
+ my $contact = FS::contact->new({
+ 'custnum' => scalar($cgi->param('custnum')),
+ map { $_ => scalar($cgi->param("contactnum_$_")) } qw( first last )
+ });
+ $opt{'contact'} = $contact;
+ }
+
if ( $locationnum == -1 ) {
- my $cust_location = new FS::cust_location {
+ my $cust_location = FS::cust_location->new_or_existing({
map { $_ => scalar($cgi->param($_)) }
- qw( custnum address1 address2 city county state zip country geocode )
- };
+ ('custnum', FS::cust_main->location_fields)
+ });
$opt{'cust_location'} = $cust_location;
}
diff --git a/httemplate/edit/process/svc_phone.html b/httemplate/edit/process/svc_phone.html
index 7a3b43d32..9983ea2cb 100644
--- a/httemplate/edit/process/svc_phone.html
+++ b/httemplate/edit/process/svc_phone.html
@@ -2,6 +2,7 @@
'table' => 'svc_phone',
'args_callback' => $args_callback,
'value_callback' => $value_callback,
+ 'edit_callback' => $edit_callback,
%opt,
&>
<%init>
@@ -28,6 +29,9 @@ my $right = $opt{'bulk'} ? 'Bulk provision customer service'
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right($right);
+$cgi->param('phonenum', $cgi->param('phonenum_manual') )
+ if $cgi->param('phonenum_which') eq 'phonenum_manual';
+
my $tollfreephonenum = $cgi->param('tollfreephonenum');
$cgi->param('phonenum',$tollfreephonenum) if $tollfreephonenum =~ /^\d+$/;
@@ -36,10 +40,10 @@ my $args_callback = sub {
my %opt = ();
if ( $cgi->param('locationnum') == -1 ) {
- my $cust_location = new FS::cust_location {
+ my $cust_location = FS::cust_location->new_or_existing({
map { $_ => scalar($cgi->param($_)) }
qw( custnum address1 address2 city county state zip country )
- };
+ });
$opt{'cust_location'} = $cust_location;
}
@@ -48,8 +52,13 @@ my $args_callback = sub {
};
my $value_callback = sub {
- my ($field, $value) = @_;
- ($field =~ /_date$/) ? parse_datetime($value) : $value;
+ my ($field, $value) = @_;
+ ($field =~ /_date$/) ? parse_datetime($value) : $value;
+};
+
+my $edit_callback = sub {
+ my( $new, $old ) = @_;
+ $new->sip_password( $old->sip_password ) if $new->sip_password eq '*HIDDEN*';
};
</%init>
diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html
index 1d9647f2f..466091dfa 100644
--- a/httemplate/edit/quick-charge.html
+++ b/httemplate/edit/quick-charge.html
@@ -145,7 +145,6 @@ function bill_now_changed (what) {
<% mt('with terms') |h %>
<& /elements/select-terms.html,
'curr_value' => scalar($cgi->param('invoice_terms')),
- 'empty_value' => $default_terms,
'disabled' => ( $cgi->param('bill_now') ? 0 : 1 ),
&>
</TD>
diff --git a/httemplate/edit/rate_region.cgi b/httemplate/edit/rate_region.cgi
index 367bbafb6..a1c1bcb7d 100644
--- a/httemplate/edit/rate_region.cgi
+++ b/httemplate/edit/rate_region.cgi
@@ -33,6 +33,14 @@
</TD>
</TR>
+ <& /elements/tr-checkbox.html,
+ label => 'Exact match',
+ field => 'exact_match',
+ cell_style => 'font-weight: bold',
+ value => 'Y',
+ curr_value => $rate_region->exact_match
+ &>
+
</TABLE>
<BR>
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
index c1f74551d..627791ba7 100755
--- a/httemplate/edit/svc_acct.cgi
+++ b/httemplate/edit/svc_acct.cgi
@@ -9,19 +9,6 @@
<BR>
% }
-<SCRIPT TYPE="text/javascript">
-function randomPass() {
- var i=0;
- var pw_set='<% join('', 'a'..'z', 'A'..'Z', '0'..'9' ) %>';
- var pass='';
- while(i < 8) {
- i++;
- pass += pw_set.charAt(Math.floor(Math.random() * pw_set.length));
- }
- document.OneTrueForm.clear_password.value = pass;
-}
-</SCRIPT>
-
<FORM NAME="OneTrueForm" ACTION="<% $p1 %>process/svc_acct.cgi" METHOD=POST>
<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
@@ -57,10 +44,11 @@ function randomPass() {
%if ( $part_svc->part_svc_column('_password')->columnflag ne 'F' ) {
<TR>
+% #XXX eventually should require "Edit Password" ACL
<TD ALIGN="right"><% mt('Password') |h %></TD>
<TD>
- <INPUT TYPE="text" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>>
- <INPUT TYPE="button" VALUE="<% mt('Generate') |h %>" onclick="randomPass();">
+ <INPUT TYPE="text" ID="clear_password" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>>
+ <& /elements/random_pass.html, 'clear_password' &>
</TD>
</TR>
%}else{
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/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi
index 9647b6887..13bbe82a1 100644
--- a/httemplate/edit/svc_phone.cgi
+++ b/httemplate/edit/svc_phone.cgi
@@ -6,6 +6,11 @@
my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt ) = @_;
$svc_x->locationnum($cust_pkg->locationnum) if $cust_pkg;
},
+ 'svc_edit_callback' => sub {
+ my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt) = @_;
+ my $conf = new FS::Conf;
+ $svc_x->sip_password('*HIDDEN*') unless $conf->exists('showpasswords');
+ },
&>
<%init>
@@ -28,6 +33,11 @@ my $begin_callback = sub {
type => 'select-did',
label => 'Phone number',
multiple => $bulk,
+ },
+ { field => 'sim_imsi',
+ type => 'text',
+ size => 15,
+ maxlength => 15,
};
push @$fields, { field => 'domsvc',
diff --git a/httemplate/elements/auto-table.html b/httemplate/elements/auto-table.html
index 9aff94e67..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;
@@ -70,8 +70,8 @@ function <%$pre%>set_rownum(obj, rownum) {
if ( obj.id ) {
obj.id = obj.id + rownum;
}
- if ( obj.name ) {
- obj.name = obj.name + rownum;
+ if ( obj.getAttribute('name') ) {
+ obj.setAttribute('name', obj.getAttribute('name') + rownum);
// also, in this case it's a form field that will be part of the record
// so set up an onchange handler
obj.onchange = <%$pre%>possiblyAddRow_factory(obj);
@@ -96,17 +96,32 @@ function <%$pre%>addRow(data) {
<%$pre%>set_rownum(row, this_rownum);
if(data instanceof Array) {
for (i = 0; i < data.length && i < <%$pre%>fieldorder.length; i++) {
- var el = document.getElementsByName(<%$pre%>fieldorder[i] + this_rownum)[0];
+ var el = document.getElementsByName(<%$pre |js_string%> +
+ <%$pre%>fieldorder[i] +
+ this_rownum)[0];
if (el) {
- el.value = data[i];
+ if ( el.tagName.toLowerCase() == 'span' ) {
+ el.innerHTML = data[i];
+ } else if ( el.type == 'checkbox' ) {
+ el.checked = (el.value == data[i]);
+ } else {
+ el.value = data[i];
+ }
}
}
} else if (data instanceof Object) {
for (var field in data) {
- var el = document.getElementsByName(field + this_rownum)[0];
+ var el = document.getElementsByName(<%$pre |js_string%> +
+ field +
+ this_rownum)[0];
if (el) {
- el.value = data[field];
-% # doesn't work for checkbox
+ if ( el.tagName.toLowerCase() == 'span' ) {
+ el.innerHTML = data[field];
+ } else if ( el.type == 'checkbox' ) {
+ el.checked = (el.value == data[field]);
+ } else {
+ el.value = data[field];
+ }
}
}
} // else nothing
@@ -123,6 +138,20 @@ function <%$pre%>deleteRow(rownum) {
<%$pre%>tbody.removeChild(r);
}
+function <%$pre%>set_prefix(obj) {
+ if ( obj.id ) {
+ obj.id = <%$pre |js_string%> + obj.id;
+ }
+ if ( obj.getAttribute('name') ) {
+ obj.setAttribute('name', <%$pre |js_string%> + obj.getAttribute('name'));
+ }
+ for (var i = 0; i < obj.children.length; i++) {
+ if ( obj.children[i] instanceof Node ) {
+ <%$pre%>set_prefix(obj.children[i]);
+ }
+ }
+}
+
function <%$pre%>init() {
<%$pre%>template = document.getElementById(<% $template_row |js_string%>);
<%$pre%>tbody = document.getElementById('<%$pre%>autotable');
@@ -131,8 +160,10 @@ function <%$pre%>init() {
var table = <%$pre%>template.parentNode;
table.removeChild(<%$pre%>template);
// give it an id
- <%$pre%>template.id = <%$pre |js_string%> + 'row';
- // and a magic identifier so we know it's been submitted
+ <%$pre%>template.id = 'row';
+ // prefix the ids and names of the TR object and all its descendants
+ <%$pre%>set_prefix(<%$pre%>template);
+ // add a magic identifier so we know it's been submitted
var magic = document.createElement('INPUT');
magic.setAttribute('type', 'hidden');
magic.setAttribute('name', '<%$pre%>magic');
@@ -140,18 +171,26 @@ function <%$pre%>init() {
// and a delete button
%# should this be enclosed in an actual <button> for aesthetics?
var delete_button = document.createElement('IMG');
- delete_button.id = 'delete_button';
+ delete_button.id = '<%$pre%>delete_button';
delete_button.src = '<%$fsurl%>images/cross.png';
delete_button.alt = 'X';
// use an inline string for this so that it will be cloned properly
delete_button.setAttribute('onclick', "<%$pre%>deleteRow(this.rownum);");
+ // and an error display
+ var error_span = document.createElement('SPAN');
+ error_span.className = 'error';
+ error_span.style.color = '#FF0000';
+ error_span.setAttribute('name', '<%$pre%>error');
+ error_span.style.padding = '5px';
var delete_cell = document.createElement('TD');
+ delete_cell.style.textAlign = 'left';
delete_cell.appendChild(delete_button);
delete_cell.appendChild(magic); // it has to go somewhere
+ delete_cell.appendChild(error_span);
<%$pre%>template.appendChild(delete_cell);
// preload rows
- var rows = <% to_json(\@rows) %>;
+ var rows = <% encode_json(\@rows) %>;
for (var i = 0; i < rows.length; i++) {
<%$pre%>addRow(rows[i]);
}
diff --git a/httemplate/elements/change_history_common.html b/httemplate/elements/change_history_common.html
index 232664e39..34ce70b6c 100644
--- a/httemplate/elements/change_history_common.html
+++ b/httemplate/elements/change_history_common.html
@@ -15,13 +15,7 @@
<TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
</TR>
-% foreach my $item ( sort { $a->history_date <=> $b->history_date
-% #|| table order
-% || $a->historynum <=> $b->historynum
-% }
-% @history
-% )
-% {
+% foreach my $item ( @history ) {
% my $history_other = '';
% my $act = $item->history_action;
% if ( $act =~ /^replace/ ) {
@@ -196,4 +190,11 @@ $cust_pkg_date_format .= ' %l:%M:%S%P'
if $conf->exists('cust_pkg-display_times')
|| $curuser->option('cust_pkg-display_times');
+@history = sort { $a->history_date <=> $b->history_date
+ || $a->historynum <=> $b->historynum } @history;
+
+if ( $curuser->option('history_order') eq 'newest' ) {
+ @history = reverse @history;
+}
+
</%init>
diff --git a/httemplate/elements/change_password.html b/httemplate/elements/change_password.html
new file mode 100644
index 000000000..625ba1fb5
--- /dev/null
+++ b/httemplate/elements/change_password.html
@@ -0,0 +1,41 @@
+<STYLE>
+.passwordbox {
+ border: 1px solid #7e0079;
+ padding: 2px;
+ position: absolute;
+ font-size: 80%;
+ background-color: #ffffff;
+ display: none;
+}
+</STYLE>
+<A ID="<%$pre%>link" HREF="#" onclick="<%$pre%>toggle(true)">(<% mt('change') %>)</A>
+<DIV ID="<%$pre%>form" CLASS="passwordbox">
+ <FORM METHOD="POST" ACTION="<%$fsurl%>misc/process/change-password.html">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svc_acct->svcnum |h%>">
+ <INPUT TYPE="text" ID="<%$pre%>password" NAME="password" VALUE="<% $curr_value |h%>">
+ <& /elements/random_pass.html, $pre.'password', 'randomize' &>
+ <INPUT TYPE="submit" VALUE="change">
+ <INPUT TYPE="button" VALUE="cancel" onclick="<%$pre%>toggle(false)">
+% if ( $error ) {
+ <BR><SPAN STYLE="color: #ff0000"><% $error |h %></SPAN>
+% }
+ </FORM>
+</DIV>
+<SCRIPT TYPE="text/javascript">
+function <%$pre%>toggle(val) {
+ document.getElementById('<%$pre%>form').style.display =
+ val ? 'inline-block' : 'none';
+ document.getElementById('<%$pre%>link').style.display =
+ val ? 'none' : 'inline';
+}
+% if ( $error ) {
+<%$pre%>toggle(true);
+% }
+</SCRIPT>
+<%init>
+my %opt = @_;
+my $svc_acct = $opt{'svc_acct'};
+my $curr_value = $opt{'curr_value'} || '';
+my $pre = 'changepw'.$svc_acct->svcnum.'_';
+my $error = $cgi->param($pre.'error');
+</%init>
diff --git a/httemplate/elements/checkbox-tristate.html b/httemplate/elements/checkbox-tristate.html
new file mode 100644
index 000000000..4c26ed74e
--- /dev/null
+++ b/httemplate/elements/checkbox-tristate.html
@@ -0,0 +1,78 @@
+<%doc>
+A tristate checkbox (with three values: true, false, and null).
+Internally, this creates a checkbox, coupled via javascript to a hidden
+field that actually contains the value. For now, the only values these
+can have are 1, 0, and empty. Clicking the checkbox cycles between them.
+</%doc>
+<%shared>
+my $init = 0;
+</%shared>
+% if ( !$init ) {
+% $init = 1;
+<SCRIPT TYPE="text/javascript">
+function tristate_onclick() {
+ var checkbox = this;
+ var input = checkbox.input;
+ if ( input.value == "" ) {
+ input.value = "0";
+ checkbox.checked = false;
+ checkbox.indeterminate = false;
+ } else if ( input.value == "0" ) {
+ input.value = "1";
+ checkbox.checked = true;
+ checkbox.indeterminate = false;
+ } else if ( input.value == "1" ) {
+ input.value = "";
+ checkbox.checked = true;
+ checkbox.indeterminate = true
+ }
+}
+
+var tristates = [];
+var tristate_boxes = [];
+window.onload = function() { // don't do this until all of the checkboxes exist
+%# tristates = document.getElementsByClassName('tristate'); # curse you, IE8
+ var all_inputs = document.getElementsByTagName('input');
+ for (var i=0; i < all_inputs.length; i++) {
+ if ( all_inputs[i].className == 'tristate' ) {
+ tristates.push(all_inputs[i]);
+ }
+ }
+ for (var i=0; i < tristates.length; i++) {
+ tristate_boxes[i] =
+ document.getElementById('checkbox_' + tristates[i].name);
+ // make sure they can find each other
+ tristate_boxes[i].input = tristates[i];
+ tristates[i].checkbox = tristate_boxes[i];
+ // set event handler
+ tristate_boxes[i].onclick = tristate_onclick;
+ // set initial value
+ if ( tristates[i].value == "" ) {
+ tristate_boxes[i].indeterminate = true
+ }
+ if ( tristates[i].value != "0" ) {
+ tristate_boxes[i].checked = true;
+ }
+ }
+};
+</SCRIPT>
+% } # end of $init
+<INPUT TYPE="hidden" NAME="<% $opt{field} %>"
+ ID="<% $opt{id} %>"
+ VALUE="<% $curr_value %>"
+ CLASS="tristate">
+<INPUT TYPE="checkbox" ID="checkbox_<%$opt{field}%>" CLASS="partial">
+<%init>
+
+my %opt = @_;
+
+# might be useful but I'm not implementing it yet
+#my $onchange = $opt{'onchange'}
+# ? 'onChange="'. $opt{'onchange'}. '(this)"'
+# : '';
+
+$opt{'id'} ||= 'hidden_'.$opt{'field'};
+my $curr_value = $opt{curr_value};
+$curr_value = undef
+ unless $curr_value eq '0' or $curr_value eq '1';
+</%init>
diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html
index 490ba2303..3d5177612 100644
--- a/httemplate/elements/contact.html
+++ b/httemplate/elements/contact.html
@@ -2,9 +2,9 @@
<INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>">
- <TABLE>
+ <TABLE STYLE="display:inline">
<TR>
-% if ( @contact_class ) {
+% if ( @contact_class && ! $opt{name_only} ) {
<TD>
<SELECT NAME="<%$name%>_classnum" <% $onchange %>>
<OPTION VALUE="">
@@ -106,6 +106,6 @@ foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) {
$label{'comment'} = 'Comment';
-my @fields = keys %label;
+my @fields = $opt{'name_only'} ? qw( first last ) : keys %label;
</%init>
diff --git a/httemplate/elements/dashboard-toplist.html b/httemplate/elements/dashboard-toplist.html
index f4a372519..b80af7883 100644
--- a/httemplate/elements/dashboard-toplist.html
+++ b/httemplate/elements/dashboard-toplist.html
@@ -169,7 +169,6 @@ if ( $FS::TicketSystem::system eq 'RT_Internal'
ObjectCustomFieldValues.ObjectId = cust_tickets.Id
)
GROUP BY cust_tickets.custnum, ObjectCustomFieldValues.Content";
- #warn $sql."\n";
} else { # no custom_priority_field
$sql =
"SELECT cust_tickets.custnum,
@@ -181,10 +180,8 @@ if ( $FS::TicketSystem::system eq 'RT_Internal'
my $sth = dbh->prepare($sql) or die dbh->errstr;
$sth->execute or die $sth->errstr;
while ( my $row = $sth->fetchrow_hashref ) {
- #warn to_json($row)."\n";
$num_tickets_by_priority{ $row->{priority} }->{ $row->{custnum} } =
$row->{num_tickets};
}
}
-#warn Dumper \%num_tickets_by_priority;
</%init>
diff --git a/httemplate/elements/fckeditor/fckeditor.js b/httemplate/elements/fckeditor/fckeditor.js
index 8e0126bae..eb7d339af 100644
--- a/httemplate/elements/fckeditor/fckeditor.js
+++ b/httemplate/elements/fckeditor/fckeditor.js
@@ -304,7 +304,7 @@ function FCKeditor_IsCompatibleBrowser()
// Internet Explorer 5.5+
if ( /*@cc_on!@*/false && sAgent.indexOf("mac") == -1 )
{
- var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1] ;
+ var sBrowserVersion = navigator.appVersion.match(/MSIE ([\d.]+)/)[1] ;
return ( sBrowserVersion >= 5.5 ) ;
}
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
index 873fe1621..685523314 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -200,7 +200,7 @@ Example:
</TR>
% } else {
% foreach (qw(latitude longitude)) {
-<INPUT TYPE="hidden" NAME="<% $_ %>" VALUE="<% $object->get($_) |h%>">
+<INPUT TYPE="hidden" NAME="<% $_ %>" ID="<% $_ %>" VALUE="<% $object->get($_) |h%>">
% }
% }
<INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->coord_auto %>">
@@ -226,12 +226,13 @@ Example:
<TD COLSPAN=8>
<INPUT TYPE="text" SIZE=15
NAME="<%$pre%>district"
+ ID="<%$pre%>district"
VALUE="<% $object->district |h %>">
<% '(automatic)' %>
</TD>
</TR>
% } else {
- <INPUT TYPE="hidden" NAME="<%$pre%>district" VALUE="<% $object->district %>">
+ <INPUT TYPE="hidden" ID="<%$pre%>" NAME="<%$pre%>district" VALUE="<% $object->district %>">
% }
% }
@@ -239,7 +240,7 @@ Example:
%# keep a clean copy of the address so we know if we need
%# to re-standardize
% foreach (qw(address1 city state country zip latitude
-% longitude censustract addr_clean) ) {
+% longitude censustract district addr_clean) ) {
<INPUT TYPE="hidden" NAME="old_<%$pre.$_%>" ID="old_<%$pre.$_%>" VALUE="<% $object->get($_) |h%>">
% }
%# Placeholders
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 4e6109687..5689b12d2 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -194,7 +194,7 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) {
} elsif ( $svcdb eq 'svc_phone' ) {
$report_svc{"${name}' total usage by time period"} =
- [ $fsurl. 'search/report_svc_phone.html',
+ [ $fsurl. 'search/report_svc_phone_usage.html',
'Total usage (minutes, and amount billed) for the specified time period, per phone number.',
];
@@ -209,7 +209,7 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) {
$report_svc{"Advanced $lcsname reports"} =
[ $fsurl."search/report_$svcdb.html", '' ]
- if $svcdb =~ /^svc_(acct|broadband|hardware)$/
+ if $svcdb =~ /^svc_(acct|broadband|hardware|phone)$/
&& $curuser->access_right("Services: $name: Advanced search");
if ( $svcdb eq 'svc_phone' ) {
@@ -236,7 +236,7 @@ tie my %report_packages, 'Tie::IxHash';
$report_packages{'Package definitions (by # active)'} = [ $fsurl.'browse/part_pkg.cgi?active=1', 'Package definitions by number of active packages' ]
if $curuser->access_right('Edit package definitions')
|| $curuser->access_right('Edit global package definitions');
-$report_packages{'Package Costs Report'} = [ $fsurl.'graph/report_cust_pkg_cost.html', 'Package setup and recurring costs graph' ]
+$report_packages{'Package costs'} = [ $fsurl.'graph/report_cust_pkg_cost.html', 'Package setup and recurring costs graph' ]
if $curuser->access_right('Financial reports');
$report_packages{'separator'} = ''
if keys %report_packages;
@@ -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',
@@ -336,7 +338,8 @@ tie my %report_sales, 'Tie::IxHash',
'Daily Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time_daily.html', 'Sales, credits and receipts (broken down by day) summary graph' ],
'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ],
'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ],
- 'Sales With Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ],
+ 'Sales with Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ],
+ 'Sales with Agent Commissions' => [ $fsurl.'search/report_agent_commission.html' ],
;
tie my %report_financial, 'Tie::IxHash';
@@ -396,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' ]
@@ -463,6 +466,8 @@ $tools_menu{'Job Queue'} = [ $fsurl.'search/queue.html', 'View pending job queu
if $curuser->access_right('Job queue');
$tools_menu{'Ticketing'} = [ \%tools_ticketing, 'Ticketing tools' ]
if $conf->config('ticket_system');
+$tools_menu{'Customer email settings'} = [ $fsurl.'misc/manage_cust_email.html' ]
+ if $curuser->access_right('Edit customer');
$tools_menu{'Business card scan'} = [ $fsurl.'edit/prospect_main-upload.html' ]
if $curuser->access_right('New prospect');
$tools_menu{'Time Queue'} = [ $fsurl.'search/report_timeworked.html', 'View pending support time' ]
@@ -649,7 +654,7 @@ $config_misc{'Advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Whe
|| $curuser->access_right('Edit global advertising sources');
if ( $curuser->access_right('Configuration') ) {
$config_misc{'Custom fields'} = [ $fsurl.'browse/part_virtual_field.html', 'Locally defined fields', ];
- $config_misc{'Message catalog'} = [ $fsurl.'browse/msgcat.html', 'Change error messages and other customizable labels for each locale' ];
+ $config_misc{'Translation strings'} = [ $fsurl.'browse/msgcat.html', 'Translations and other customizable labels for each locale' ];
}
$config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ]
if $curuser->access_right('Edit inventory')
diff --git a/httemplate/elements/order_pkg.js b/httemplate/elements/order_pkg.js
index 8c1efd93a..1069a0ee4 100644
--- a/httemplate/elements/order_pkg.js
+++ b/httemplate/elements/order_pkg.js
@@ -44,4 +44,5 @@ function standardize_new_location() {
function submit_abort() {
document.OrderPkgForm.submitButton.disabled = false;
+ nd(1);
}
diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html
index 7a282a34c..cef54b824 100644
--- a/httemplate/elements/progress-init.html
+++ b/httemplate/elements/progress-init.html
@@ -108,7 +108,7 @@ function <%$key%>process () {
function <%$key%>myCallback( jobnum ) {
- overlib( OLiframeContent('<%$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, '<% $popup_name %>'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
+ overlib( OLiframeContent('<%$fsurl%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, '<% $popup_name %>'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
}
diff --git a/httemplate/elements/random_pass.html b/httemplate/elements/random_pass.html
new file mode 100644
index 000000000..b215b77d9
--- /dev/null
+++ b/httemplate/elements/random_pass.html
@@ -0,0 +1,17 @@
+<INPUT TYPE="button" VALUE="<% emt($label) %>" onclick="randomPass()">
+<SCRIPT TYPE="text/javascript">
+function randomPass() {
+ var i=0;
+ var pw_set='<% join('', 'a'..'z', 'A'..'Z', '0'..'9' ) %>';
+ var pass='';
+ while(i < 8) {
+ i++;
+ pass += pw_set.charAt(Math.floor(Math.random() * pw_set.length));
+ }
+ document.getElementById('<% $id %>').value = pass;
+}
+</SCRIPT>
+<%init>
+my $id = shift;
+my $label = shift || 'Generate';
+</%init>
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-areacode.html b/httemplate/elements/select-areacode.html
index a302befc2..f0f56d56d 100644
--- a/httemplate/elements/select-areacode.html
+++ b/httemplate/elements/select-areacode.html
@@ -17,7 +17,7 @@
what.form.<% $opt{'prefix'} %>areacode.disabled = 'disabled';
what.form.<% $opt{'prefix'} %>areacode.style.display = 'none';
var areacodewait = document.getElementById('<% $opt{'prefix'} %>areacodewait');
- areacodewait.style.display = '';
+ areacodewait.style.display = 'inline';
var areacodeerror = document.getElementById('<% $opt{'prefix'} %>areacodeerror');
areacodeerror.style.display = 'none';
@@ -61,7 +61,7 @@
what.form.<% $opt{'prefix'} %>areacode.style.display = '';
} else {
var areacodeerror = document.getElementById('<% $opt{'prefix'} %>areacodeerror');
- areacodeerror.style.display = '';
+ areacodeerror.style.display = 'inline';
}
//run the callback
diff --git a/httemplate/elements/select-did.html b/httemplate/elements/select-did.html
index 6e205d8ff..c39603156 100644
--- a/httemplate/elements/select-did.html
+++ b/httemplate/elements/select-did.html
@@ -18,6 +18,28 @@ Example:
<TABLE>
<TR>
+% my( $phonenum_checked, $manual_checked ) = ( '', '' );
+% if ( $export->get_dids_can_manual ) {
+% #not 100% perfect UI on error handling, but it'll do
+% if ( $opt{'curr_value'} ) {
+% $phonenum_checked = '';
+% $manual_checked = 'CHECKED';
+% } else {
+% $phonenum_checked = 'CHECKED';
+% $manual_checked = '';
+% }
+
+ <TD VALIGN="top">
+ <INPUT TYPE = "radio"
+ NAME = "phonenum_which"
+ VALUE = "phonenum"
+ onChange = "phonenum_which_changed(this)"
+ onClick = "phonenum_which_changed(this)"
+ <% $phonenum_checked %>
+ > Inventory
+ </TD>
+% }
+
% if ( $export->get_dids_npa_select ) {
<TD VALIGN="top">
@@ -27,9 +49,10 @@ Example:
'svcpart' => $svcpart,
'disable_empty' => 0,
'empty_label' => 'Select state',
+ 'disabled' => ( $manual_checked ? 1 : 0 ),
)
%>
- <BR><FONT SIZE="-1">State</FONT>
+ <BR><FONT SIZE="-1" ID="phonenum_state_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>State</FONT>
</TD>
<TD VALIGN="top">
@@ -39,19 +62,24 @@ Example:
'empty' => 'Select area code',
)
%>
- <BR><FONT SIZE="-1">Area code</FONT>
+ <BR><FONT SIZE="-1" ID="areacode_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>Area code</FONT>
</TD>
<TD VALIGN="top">
<% include('/elements/select-exchange.html',
- 'svcpart' => $svcpart,
- 'empty' => 'Select exchange',
+ 'svcpart' => $svcpart,
+ 'empty' => 'Select exchange',
)
%>
- <BR><FONT SIZE="-1">City / Exchange</FONT>
+ <BR><FONT SIZE="-1" ID="exchange_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>City / Exchange</FONT>
</TD>
% } else {
+%
+% #this code path currently only being used by fibernetics
+% # should change "Province" label to "State" or make it configurable
+% # if/when other folks need an areacode-less DID selector that goes
+% # directly from state to region
<TD VALIGN="top">
<% include('/elements/select.html',
@@ -60,9 +88,10 @@ Example:
'options' => [ '', @{ $export->get_dids } ],
'labels' => { '' => 'Select province' },
'onchange' => 'phonenum_state_changed(this);',
+ 'disabled' => ( $manual_checked ? 1 : 0 ),
)
%>
- <BR><FONT SIZE="-1">Province</FONT>
+ <BR><FONT SIZE="-1" ID="phonenum_state_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>Province</FONT>
</TD>
<TD VALIGN="top">
@@ -72,7 +101,7 @@ Example:
'empty' => 'Select region',
)
%>
- <BR><FONT SIZE="-1">Region</FONT>
+ <BR><FONT SIZE="-1" ID="region_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>Region</FONT>
</TD>
% }
@@ -86,10 +115,132 @@ Example:
'region' => ! $export->get_dids_npa_select,
)
%>
- <BR><FONT SIZE="-1">Phone number</FONT>
+ <BR><FONT SIZE="-1" ID="phonenum_phonenum_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>Phone number</FONT>
</TD>
</TR>
+
+% if ( $export->get_dids_can_manual ) {
+ <TR>
+
+ <TD VALIGN="top">
+ <INPUT TYPE = "radio"
+ NAME = "phonenum_which"
+ VALUE = "phonenum_manual"
+ onChange = "phonenum_which_changed(this)"
+ onClick = "phonenum_which_changed(this)"
+ <% $manual_checked %>
+ > Manual entry
+ </TD>
+
+ <TD VALIGN="top" COLSPAN=4>
+ <& /elements/input-text.html,
+ %opt,
+ field => 'phonenum_manual',
+ id => 'phonenum_manual',
+ type => 'text',
+ disabled => ( $phonenum_checked ? 1 : 0 ),
+ &>
+ </TD>
+ </TR>
+
+ <SCRIPT TYPE="text/javascript">
+ function phonenum_which_changed(what) {
+
+ if ( what.value == 'phonenum' && what.checked ) {
+
+ what.form.phonenum_manual.disabled = true;
+ what.form.phonenum_manual.style.backgroundColor = '#dddddd';
+
+ what.form.phonenum_state.disabled = false;
+
+ document.getElementById('phonenum_state_label').style.color = '#000000';
+ if ( document.getElementById('areacode_label') ) {
+ document.getElementById('areacode_label').style.color = '#000000';
+ }
+ if ( document.getElementById('exchange_label') ) {
+ document.getElementById('exchange_label').style.color = '#000000';
+ }
+ if ( document.getElementById('region_label') ) {
+ document.getElementById('region_label').style.color = '#000000';
+ }
+ document.getElementById('phonenum_phonenum_label').style.color = '#000000';
+
+ var value = what.form.phonenum_state.options[ what.form.phonenum_state.selectedIndex].value;
+
+ if ( value != '' ) {
+
+ if ( what.form.areacode ) {
+ what.form.areacode.disabled = false;
+
+ var areacode_value = what.form.areacode.options[ what.form.areacode.selectedIndex].value;
+
+ if ( areacode_value != '' ) {
+ what.form.exchange.disabled = false;
+
+ var exchange_value = what.form.exchange.options[ what.form.exchange.selectedIndex].value;
+
+ if ( exchange_value != '' ) {
+ what.form.phonenum.disabled = false;
+ }
+
+ }
+
+ }
+ if ( what.form.region ) {
+ what.form.region.disabled = false;
+
+ var region_value = what.form.region.options[ what.form.region.selectedIndex].value;
+
+ if ( region_value != '' ) {
+ what.form.phonenum.disabled = false;
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( what.value == 'phonenum_manual' && what.checked ) {
+
+ what.form.phonenum_manual.disabled = false;
+ what.form.phonenum_manual.style.backgroundColor = '#ffffff';
+
+ what.form.phonenum_state.disabled = true;
+
+ document.getElementById('phonenum_state_label').style.color = '#999999';
+ if ( document.getElementById('areacode_label') ) {
+ document.getElementById('areacode_label').style.color = '#999999';
+ }
+ if ( document.getElementById('exchange_label') ) {
+ document.getElementById('exchange_label').style.color = '#999999';
+ }
+ if ( document.getElementById('region_label') ) {
+ document.getElementById('region_label').style.color = '#999999';
+ }
+ document.getElementById('phonenum_phonenum_label').style.color = '#999999';
+
+ if ( what.form.areacode ) {
+ what.form.areacode.disabled = true;
+ }
+
+ if ( what.form.exchange ) {
+ what.form.exchange.disabled = true;
+ }
+
+ if ( what.form.region ) {
+ what.form.region.disabled = true;
+ }
+
+ what.form.phonenum.disabled = true;
+ }
+
+ }
+ </SCRIPT>
+
+% }
+
</TABLE>
% }
diff --git a/httemplate/elements/select-exchange.html b/httemplate/elements/select-exchange.html
index 9e4b5ce97..b9677094a 100644
--- a/httemplate/elements/select-exchange.html
+++ b/httemplate/elements/select-exchange.html
@@ -17,7 +17,7 @@
what.form.<% $opt{'prefix'} %>exchange.disabled = 'disabled';
what.form.<% $opt{'prefix'} %>exchange.style.display = 'none';
var exchangewait = document.getElementById('<% $opt{'prefix'} %>exchangewait');
- exchangewait.style.display = '';
+ exchangewait.style.display = 'inline';
var exchangeerror = document.getElementById('<% $opt{'prefix'} %>exchangeerror');
exchangeerror.style.display = 'none';
@@ -56,7 +56,7 @@
what.form.<% $opt{'prefix'} %>exchange.style.display = '';
} else {
var exchangeerror = document.getElementById('<% $opt{'prefix'} %>exchangeerror');
- exchangeerror.style.display = '';
+ exchangeerror.style.display = 'inline';
}
//run the callback
diff --git a/httemplate/elements/select-mac.html b/httemplate/elements/select-mac.html
index 8b1c71fea..4b406fce0 100644
--- a/httemplate/elements/select-mac.html
+++ b/httemplate/elements/select-mac.html
@@ -7,7 +7,7 @@
<% include( '/elements/input-text.html', %opt, 'type'=>'text' ) %>
<SELECT ID="<% $opt{'prefix'} %>sel_mac_addr" NAME="<% $opt{'prefix'} %>sel_mac_addr"
- notonChange="<% $opt{'prefix'} %>mac_addr_changed(this); <% $opt{'onchange'} %>"
+%# notonChange="<% $opt{'prefix'} %>mac_addr_changed(this); <% $opt{'onchange'} %>"
<% $opt{'disabled'} %> STYLE="display: none">
<OPTION VALUE="">Select MAC address</OPTION>
</SELECT>
diff --git a/httemplate/elements/select-part_svc.html b/httemplate/elements/select-part_svc.html
index 72ab7f6b0..743b2852e 100644
--- a/httemplate/elements/select-part_svc.html
+++ b/httemplate/elements/select-part_svc.html
@@ -13,6 +13,9 @@ my( %opt ) = @_;
$opt{'records'} = delete $opt{'part_svc'}
if $opt{'part_svc'};
-$opt{'records'} ||= [ qsearch( 'part_svc', {} ) ]; # { disabled=>'' } )
+my %hash = ();
+$hash{'svcdb'} = $opt{'svcdb'} if $opt{'svcdb'};
+
+$opt{'records'} ||= [ qsearch( 'part_svc', \%hash ) ]; # { disabled=>'' } )
</%init>
diff --git a/httemplate/elements/select-phonenum.html b/httemplate/elements/select-phonenum.html
index 18abe3dea..a8d9a7c3e 100644
--- a/httemplate/elements/select-phonenum.html
+++ b/httemplate/elements/select-phonenum.html
@@ -17,7 +17,7 @@
what.form.<% $opt{'prefix'} %>phonenum.disabled = 'disabled';
what.form.<% $opt{'prefix'} %>phonenum.style.display = 'none';
var phonenumwait = document.getElementById('<% $opt{'prefix'} %>phonenumwait');
- phonenumwait.style.display = '';
+ phonenumwait.style.display = 'inline';
var phonenumerror = document.getElementById('<% $opt{'prefix'} %>phonenumerror');
phonenumerror.style.display = 'none';
@@ -54,7 +54,7 @@
what.form.<% $opt{'prefix'} %>phonenum.style.display = '';
} else {
var phonenumerror = document.getElementById('<% $opt{'prefix'} %>phonenumerror');
- phonenumerror.style.display = '';
+ phonenumerror.style.display = 'inline';
}
//run the callback
diff --git a/httemplate/elements/select-region.html b/httemplate/elements/select-region.html
index 9823290db..7ed959269 100644
--- a/httemplate/elements/select-region.html
+++ b/httemplate/elements/select-region.html
@@ -17,7 +17,7 @@
what.form.<% $opt{'prefix'} %>region.disabled = 'disabled';
what.form.<% $opt{'prefix'} %>region.style.display = 'none';
var regionwait = document.getElementById('<% $opt{'prefix'} %>regionwait');
- regionwait.style.display = '';
+ regionwait.style.display = 'inline';
var regionerror = document.getElementById('<% $opt{'prefix'} %>regionerror');
regionerror.style.display = 'none';
@@ -56,7 +56,7 @@
what.form.<% $opt{'prefix'} %>region.style.display = '';
} else {
var regionerror = document.getElementById('<% $opt{'prefix'} %>regionerror');
- regionerror.style.display = '';
+ regionerror.style.display = 'inline';
}
//run the callback
diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html
index c0cd7a50b..b6c1573d1 100644
--- a/httemplate/elements/select-table.html
+++ b/httemplate/elements/select-table.html
@@ -8,7 +8,7 @@ Example:
# required
##
'table' => 'table_name',
- 'name_col' => 'name_column',
+ 'name_col' => 'name_column', #or method if you pass an order_by
#strongly recommended (you want your forms to be "sticky" on errors, right?)
'curr_value' => 'current_value',
@@ -111,6 +111,7 @@ Example:
<% $opt{'label_callback'}
? &{ $opt{'label_callback'} }( $record )
: $record->$name_col()
+ |h
%>
% }
diff --git a/httemplate/elements/select-tiered.html b/httemplate/elements/select-tiered.html
index e332eeff8..3ff5471ae 100644
--- a/httemplate/elements/select-tiered.html
+++ b/httemplate/elements/select-tiered.html
@@ -124,13 +124,6 @@ my %opt = @_;
my $pre = $opt{prefix} || '';
my $tiers = $opt{tiers} or die "no tiers defined";
-#my $json = JSON->new()->canonical(); #sort
-# something super weird and broken going on with JSON's auto-loading, just
-# using JSON alone errors out with
-# Can't locate object method "new" via package "null" (perhaps you forgot to
-# load "null"?)
-# yes, "null", not "JSON". so instead, using JSON::XS explicity...
-use JSON::XS;
my $json = JSON::XS->new();
$json->canonical;
diff --git a/httemplate/elements/selectlayers.html b/httemplate/elements/selectlayers.html
index 01fd590ca..cb1d2d619 100644
--- a/httemplate/elements/selectlayers.html
+++ b/httemplate/elements/selectlayers.html
@@ -236,7 +236,7 @@ sub layer_callback {
$date_noinit = 1;
}
else {
- $include = "input-$include" if $include =~ /^(text|money)$/;
+ $include = "input-$include" if $include =~ /^(text|money|percentage)$/;
$include = "tr-$include" unless $include eq 'hidden';
$html .= include( "/elements/$include.html",
%$lf,
diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js
index 15c5761a0..e98039d9d 100644
--- a/httemplate/elements/standardize_locations.js
+++ b/httemplate/elements/standardize_locations.js
@@ -7,8 +7,8 @@ function status_message(text, caption) {
function form_address_info() {
var cf = document.<% $formname %>;
- var returnobj = { onlyship: <% $onlyship ? 1 : 0 %> };
-% if ( !$onlyship ) {
+ var returnobj = { billship: <% $billship %> };
+% if ( $billship ) {
returnobj['same'] = cf.elements['same'].checked;
% }
% if ( $withfirm ) {
@@ -59,16 +59,12 @@ function standardize_locations() {
cf.elements['<% $pre %>coord_auto'].value = 'Y';
changed = true;
}
-
-% } #foreach $pre
-
// standardize if the old address wasn't clean
- if ( cf.elements['old_ship_addr_clean'].value == '' ||
- cf.elements['old_bill_addr_clean'].value == '' ) {
-
+ if ( cf.elements['<% $pre %>addr_clean'].value == '' ) {
changed = true;
-
}
+% } #foreach $pre
+
// or if it was clean but has been changed
for (var key in address_info) {
var old_el = cf.elements['old_'+key];
@@ -81,7 +77,7 @@ function standardize_locations() {
% # If address hasn't been changed, auto-confirm the existing value of
% # censustract so that we don't ask the user to confirm it again.
- if ( !changed ) {
+ if ( !changed && <% $withcensus %> ) {
if ( address_info['same'] ) {
cf.elements['bill_censustract'].value =
address_info['bill_censustract'];
@@ -195,12 +191,14 @@ function post_standardization() {
% if ( $conf->exists('enable_taxproducts') ) {
+ var cf = document.<% $formname %>;
+
if ( new String(cf.elements['<% $taxpre %>zip'].value).length < 10 )
{
var country_el = cf.elements['<% $taxpre %>country'];
var country = country_el.options[ country_el.selectedIndex ].value;
- var geocode = cf.elements['geocode'].value;
+ var geocode = cf.elements['bill_geocode'].value;
if ( country == 'CA' || country == 'US' ) {
@@ -222,14 +220,14 @@ function post_standardization() {
} else {
- cf.elements['geocode'].value = 'DEFAULT';
+ cf.elements['bill_geocode'].value = 'DEFAULT';
<% $post_geocode %>;
}
} else {
- cf.elements['geocode'].value = '';
+ cf.elements['bill_geocode'].value = '';
<% $post_geocode %>;
}
@@ -254,14 +252,14 @@ function update_geocode() {
cf.elements['<% $taxpre %>city'].value = argsHash['city'];
setselect(cf.elements['<% $taxpre %>state'], argsHash['state']);
cf.elements['<% $taxpre %>zip'].value = argsHash['zip'];
- cf.elements['geocode'].value = argsHash['geocode'];
+ cf.elements['bill_geocode'].value = argsHash['geocode'];
<% $post_geocode %>;
}
// popup a chooser
- overlib( OLresponseAJAX, CAPTION, 'Select tax location', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+ overlib( OLresponseAJAX, CAPTION, 'Select tax location', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
}
@@ -279,21 +277,18 @@ function setselect(el, value) {
my %opt = @_;
my $conf = new FS::Conf;
-my $withfirm = 1;
-my $withcensus = 1;
+my $withfirm = $opt{'with_firm'} ? 1 : 0;
+my $withcensus = $opt{'with_census'} ? 1 : 0;
+
+my @prefixes = '';
+my $billship = $opt{'billship'} ? 1 : 0; # whether to have bill_ and ship_ prefixes
+my $taxpre = '';
+if ($billship) {
+ @prefixes = qw(bill_ ship_);
+ $taxpre = $conf->exists('tax-ship_address') ? 'ship_' : 'bill_';
+}
my $formname = $opt{form} || 'CustomerForm';
-my $onlyship = $opt{onlyship} || '';
-#my $main_prefix = $opt{main_prefix} || '';
-#my $ship_prefix = $opt{ship_prefix} || ($onlyship ? '' : 'ship_');
-# The prefixes are now 'ship_' and 'bill_'.
-my $taxpre = 'bill_';
-$taxpre = 'ship_' if ( $conf->exists('tax-ship_address') || $onlyship );
my $post_geocode = $opt{callback} || 'post_geocode();';
-$withfirm = 0 if $opt{no_company};
-$withcensus = 0 if $opt{no_census};
-
-my @prefixes = ('ship_');
-unshift @prefixes, 'bill_' unless $onlyship;
</%init>
diff --git a/httemplate/elements/tr-cust_svc.html b/httemplate/elements/tr-cust_svc.html
index 1ca22f6d4..b66654f38 100644
--- a/httemplate/elements/tr-cust_svc.html
+++ b/httemplate/elements/tr-cust_svc.html
@@ -96,7 +96,8 @@ my $svc_unprovision_link =
my $manage_link = $opt{'manage_link'};
my $manage_target = '';
if ( $part_svc->svcdb eq 'svc_broadband' and $manage_link ) {
- my $ip_addr = $svc_x->ip_addr; #substitution for $manage_link
+ my $ip_addr = $svc_x->ip_addr; #substitution for $manage_link
+ my $mac_addr = $svc_x->mac_addr; # ditto
$manage_link = eval(qq("$manage_link"));
$opt{'manage_link_text'} ||= mt('Manage Device');
$opt{'manage_link_loc'} ||= 'bottom';
diff --git a/httemplate/elements/tr-input-beginning_ending.html b/httemplate/elements/tr-input-beginning_ending.html
index 7481c9bb6..ffc903875 100644
--- a/httemplate/elements/tr-input-beginning_ending.html
+++ b/httemplate/elements/tr-input-beginning_ending.html
@@ -74,7 +74,7 @@ my( $input_time, $time_format, $time_hint ) = ( '', '', '' );
my( $size, $maxlength ) = ( 11, 10 );
if ( $opt{'input_time'} ) {
$input_time = ', showsTime: true, timeFormat: "12"'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_2.3
- $time_format = ' %k:%M:%S'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_5.3.5
+ $time_format = ' %H:%M:%S'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_5.3.5
$time_hint = ' h:m:s';
$size = 21;
$maxlength = 27;
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-did.html b/httemplate/elements/tr-select-did.html
index 987ade689..2aa712f79 100644
--- a/httemplate/elements/tr-select-did.html
+++ b/httemplate/elements/tr-select-did.html
@@ -1,6 +1,6 @@
<% include('tr-td-label.html', @_ ) %>
-% if ( $opt{'curr_value'} ne '' && $use_selector ) {
+% if ( $use_selector && $opt{'curr_value'} ne '' && ! $can_edit ) {
<TD BGCOLOR="#dddddd" <% $cell_style %>><% $opt{'formatted_value'} || $opt{'curr_value'} || $opt{'value'} |h %></TD>
@@ -38,4 +38,6 @@ if ( scalar(@exports) > 1 ) {
my $use_selector = scalar(@exports) ? 1 : 0;
+my $can_edit = scalar(@exports) && $exports[0]->get_dids_can_edit;
+
</%init>
diff --git a/httemplate/elements/tr-select-discount_term.html b/httemplate/elements/tr-select-discount_term.html
index e9faeb228..d4218f848 100644
--- a/httemplate/elements/tr-select-discount_term.html
+++ b/httemplate/elements/tr-select-discount_term.html
@@ -24,7 +24,9 @@ function change_discount_term(what) {
id => 'discount_term',
options => [ '', @discount_term ],
labels => { '' => mt('1 month'),
- map { $_ => mt('[_1] months', $_) } @discount_term },
+ map { $_ => mt('[_1] months', sprintf('%.0f', $_)) }
+ @discount_term
+ },
curr_value => '',
onchange => $amount_id ? 'change_discount_term(this)' : '',
&>
diff --git a/httemplate/elements/tr-select-from_to.html b/httemplate/elements/tr-select-from_to.html
index a27412f99..ad9b40a6b 100644
--- a/httemplate/elements/tr-select-from_to.html
+++ b/httemplate/elements/tr-select-from_to.html
@@ -39,7 +39,7 @@
my %hash = (
'show_month_abbr' => 1,
'start_year' => '1999',
- 'end_year' => '2013', #haha, well...
+ 'end_year' => '2014',
@_,
);
</%init>
diff --git a/httemplate/elements/tr-select-inventory_item.html b/httemplate/elements/tr-select-inventory_item.html
new file mode 100644
index 000000000..669e85f27
--- /dev/null
+++ b/httemplate/elements/tr-select-inventory_item.html
@@ -0,0 +1,48 @@
+% if ( scalar(@classnums) == 0 ) {
+<& tr-fixed.html, %opt &>
+% } elsif ( scalar(@classnums) == 1 ) {
+% $opt{'extra_sql'} .= ' AND '.$classnum_sql;
+<& tr-select-table.html,
+ 'table' => 'inventory_item',
+ 'name_col' => 'item',
+ 'value_col' => 'item',
+ %opt
+&>
+% } else {
+<& tr-td-label.html, %opt &>
+<TD>
+<& select-tiered.html,
+ 'prefix' => $opt{'field'}.'_',
+ 'tiers' => [
+ {
+ field => $opt{'field'}.'_classnum',
+ table => 'inventory_class',
+ extra_sql => "WHERE $classnum_sql",
+ name_col => 'classname',
+ empty_label => '(all)',
+ },
+ {
+ field => $opt{'field'},
+ table => 'inventory_item',
+ name_col => 'item',
+ value_col => 'item',
+ link_col => 'classnum',
+ extra_sql => delete($opt{'extra_sql'}),
+ disable_empty => 1,
+ },
+ ],
+ %opt,
+&>
+</TD>
+</TR>
+% }
+<%init>
+my %opt = @_;
+my @classnums;
+if (ref($opt{'classnum'})) {
+ @classnums = @{ $opt{'classnum'} };
+} else {
+ @classnums = split(',', $opt{'classnum'});
+}
+my $classnum_sql = 'classnum IN('.join(',', @classnums).')';
+</%init>
diff --git a/httemplate/elements/tr-select-part_svc.html b/httemplate/elements/tr-select-part_svc.html
index af5148749..959ac8dd9 100644
--- a/httemplate/elements/tr-select-part_svc.html
+++ b/httemplate/elements/tr-select-part_svc.html
@@ -5,7 +5,7 @@
% } else {
<TR>
- <TD ALIGN="right"><% $opt{'label'} || 'Package definition' %></TD>
+ <TD ALIGN="right"><% $opt{'label'} || 'Service definition' %></TD>
<TD>
<% include( '/elements/select-part_svc.html',
'multiple' => 1,
@@ -21,6 +21,9 @@
my( %opt ) = @_;
-$opt{'part_svc'} ||= [ qsearch( 'part_svc', {} ) ]; # { disabled=>'' } )
+my %hash = ();
+$hash{'svcdb'} = $opt{'svcdb'} if $opt{'svcdb'};
+
+$opt{'part_svc'} ||= [ qsearch( 'part_svc', \%hash ) ]; # { disabled=>'' } )
</%init>
diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html
index c1df10b94..9a670a26b 100755
--- a/httemplate/elements/tr-select-reason.html
+++ b/httemplate/elements/tr-select-reason.html
@@ -154,15 +154,12 @@ my $controlledbutton = $opt{'control_button'};
my $id = $opt{'id'} || $func_suffix;
-my( $add_access_right, $access_right );
+my $add_access_right;
if ($class eq 'C') {
- $access_right = 'Cancel customer';
$add_access_right = 'Add on-the-fly cancel reason';
} elsif ($class eq 'S') {
- $access_right = 'Suspend customer package';
$add_access_right = 'Add on-the-fly suspend reason';
} elsif ($class eq 'R') {
- $access_right = 'Post credit';
$add_access_right = 'Add on-the-fly credit reason';
} else {
die "illegal class: $class";
diff --git a/httemplate/elements/tr-select-voip_class.html b/httemplate/elements/tr-select-voip_class.html
index dcc1487cc..afd3e1f8a 100644
--- a/httemplate/elements/tr-select-voip_class.html
+++ b/httemplate/elements/tr-select-voip_class.html
@@ -18,7 +18,8 @@ my @options = (
'' => '',
1 => 'VoIP without Broadband',
2 => 'VoIP with Broadband',
- 3 => 'Wholesale VoIP'
+ 3 => 'Wholesale VoIP',
+ 4 => 'Local Exchange (non-VoIP)',
);
</%init>
diff --git a/httemplate/misc/areacodes.cgi b/httemplate/misc/areacodes.cgi
index 9d32a3baf..4b31deb00 100644
--- a/httemplate/misc/areacodes.cgi
+++ b/httemplate/misc/areacodes.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@areacodes) %>
+<% encode_json(\@areacodes) %>\
<%init>
my( $state, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
index 887b92489..0b2f1f18c 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -23,15 +23,21 @@ function add_row_callback(rownum, prefix) {
function custnum_update_callback(rownum, prefix) {
var custnum = document.getElementById('custnum'+rownum).value;
- document.getElementById('enable_app'+rownum).disabled = (
- custnum == 0 ||
- num_open_invoices[rownum] < 2
- );
+ // if there is a custnum and more than one open invoice, enable
+ // (and check) the box
+ var show_applications = (custnum > 0 && num_open_invoices[rownum] > 1);
+ var enable_app_checkbox = document.getElementById('enable_app'+rownum);
+ enable_app_checkbox.disabled = show_applications;
+
% if ( $use_discounts ) {
select_discount_term(rownum, prefix);
% }
}
+function invnum_update_callback(rownum, prefix) {
+ custnum_update_callback(rownum, prefix);
+}
+
function select_discount_term(row, prefix) {
var custnum_obj = document.getElementById('custnum'+prefix+row);
var select_obj = document.getElementById('discount_term'+prefix+row);
@@ -89,6 +95,17 @@ function toggle_application_row(ev, next) {
next.call(this, rownum);
}
);
+ } else {
+ var row = document.getElementById('row'+rownum);
+ var table_rows = row.parentNode.rows;
+ for (i = row.sectionRowIndex; i < table_rows.count; i++) {
+ if ( table_rows[i].id.indexof('row'+rownum+'.') > -1 ) {
+ table_rows.removeChild(table_rows[i]);
+ } else {
+ break;
+ }
+ }
+ lock_payment_row(rownum, false);
}
}
@@ -198,7 +215,6 @@ function change_app_amount() {
&& amount_unapplied(rownum) > 0 ) {
create_application_row(rownum, parseInt(appnum) + 1);
-
}
}
@@ -352,6 +368,7 @@ function preload() {
footer_align => \@footer_align,
onchange => \@onchange,
custnum_update_callback => 'custnum_update_callback',
+ invnum_update_callback => 'invnum_update_callback',
add_row_callback => 'add_row_callback',
&>
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
index 7b08f7b10..03e336cba 100755
--- a/httemplate/misc/change_pkg.cgi
+++ b/httemplate/misc/change_pkg.cgi
@@ -32,9 +32,6 @@
<& /elements/standardize_locations.html,
'form' => "OrderPkgForm",
- 'onlyship' => 1,
- 'no_company' => 1,
- 'no_census' => 1,
'callback' => 'document.OrderPkgForm.submit();',
&>
diff --git a/httemplate/misc/change_pkg_contact.html b/httemplate/misc/change_pkg_contact.html
new file mode 100755
index 000000000..d9da5beec
--- /dev/null
+++ b/httemplate/misc/change_pkg_contact.html
@@ -0,0 +1,70 @@
+<& /elements/header-popup.html, mt("Change Package Contact") &>
+
+<& /elements/error.html &>
+
+<FORM ACTION="<% $p %>misc/process/change_pkg_contact.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right"><% mt('Package') |h %></TH>
+ <TD COLSPAN=7>
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+% if ( $cust_pkg->contactnum ) {
+ <TR>
+ <TH ALIGN="right"><% mt('Current Contact') %></TH>
+ <TD COLSPAN=7>
+ <% $cust_pkg->contact_obj->line |h %>
+ </TD>
+ </TR>
+% }
+
+<& /elements/tr-select-contact.html,
+ 'label' => mt('New Contact'), #XXX test
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_pkg->cust_main,
+&>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE = "submit"
+ VALUE = "<% $cust_pkg->contactnum ? mt("Change contact") : mt("Add contact") |h %>"
+>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg =
+ qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die "unknown pkgnum $pkgnum";
+
+my $cust_main = $cust_pkg->cust_main
+ or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+ " ( pkgnum ". cust_pkg->pkgnum. ")";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/choose_tax_location.html b/httemplate/misc/choose_tax_location.html
index dce04c77d..23099c421 100644
--- a/httemplate/misc/choose_tax_location.html
+++ b/httemplate/misc/choose_tax_location.html
@@ -1,6 +1,5 @@
<FORM NAME="choosegeocodeform">
<CENTER><BR><B>Choose tax location</B><BR><BR>
-<P>the geocode is:<% $header %></P>
<P STYLE="<% $style %>"><% $header %></P>
<SELECT NAME='geocodes' ID='geocodes' STYLE="<% $style %>">
@@ -12,7 +11,7 @@
% map { $value{$_} = $location{$_} } qw ( city state )
% if $location{country} eq 'CA';
%
-% my $value = encode_entities(objToJson({ %value })
+% my $value = encode_entities(encode_json({ %value })
% );
% my $content = '';
% $content .= $location->$_. '&nbsp;' x ( $max{$_} - length($location->$_) )
diff --git a/httemplate/misc/confirm-address_standardize.html b/httemplate/misc/confirm-address_standardize.html
index 57201ea5a..420e8ea1d 100644
--- a/httemplate/misc/confirm-address_standardize.html
+++ b/httemplate/misc/confirm-address_standardize.html
@@ -11,16 +11,14 @@ Confirm address standardization
</B><BR><BR>
<TABLE WIDTH="100%">
-% my @prefixes;
-% if ( $old{onlyship} ) {
-% @prefixes = ('ship_');
-% } elsif ( $old{same} ) {
+% my @prefixes = ('');
+% if ( $old{same} ) {
% @prefixes = ('bill_');
-% } else {
+% } elsif ( $old{billship} ) {
% @prefixes = ('bill_', 'ship_');
% }
% for my $pre (@prefixes) {
-% my $name = $pre eq 'ship_' ? 'service' : 'billing';
+% my $name = $pre eq 'bill_' ? 'billing' : 'service';
% if ( $new{$pre.'addr_clean'} ) {
<TR>
<TH>Entered <%$name%> address</TH>
@@ -128,6 +126,6 @@ my $q = decode_json($cgi->param('q'));
my %old = %{ $q->{old} };
my %new = %{ $q->{new} };
-my $addresses = $old{onlyship} ? 'address' : 'addresses';
+my $addresses = $old{billship} ? 'addresses' : 'address';
</%init>
diff --git a/httemplate/misc/confirm-cust_pkg-edit_dates.html b/httemplate/misc/confirm-cust_pkg-edit_dates.html
new file mode 100755
index 000000000..8e548527a
--- /dev/null
+++ b/httemplate/misc/confirm-cust_pkg-edit_dates.html
@@ -0,0 +1,289 @@
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit customer package dates');
+
+my %arg = $cgi->Vars;
+
+my $pkgnum = $arg{'pkgnum'};
+$pkgnum =~ /^\d+$/ or die "bad pkgnum '$pkgnum'";
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+my %hash = $cust_pkg->hash;
+foreach (qw( start_date setup bill last_bill contract_end )) {
+ # adjourn, expire, resume not editable this way
+ if( $arg{$_} =~ /^\d+$/ ) {
+ $hash{$_} = $arg{$_};
+ } elsif ( $arg{$_} ) {
+ $hash{$_} = parse_datetime($arg{$_});
+ } else {
+ $hash{$_} = '';
+ }
+}
+
+my (@changes, @confirm, @errors);
+
+my $part_pkg = $cust_pkg->part_pkg;
+my @supp_pkgs = $cust_pkg->supplemental_pkgs;
+my $main_pkg = $cust_pkg->main_pkg;
+
+my $conf = FS::Conf->new;
+my $date_format = $conf->config('date_format') || '%b %o, %Y';
+# Start date
+if ( $hash{'start_date'} != $cust_pkg->get('start_date') and !$hash{'setup'} ) {
+ my $start = '';
+ $start = time2str($date_format, $hash{'start_date'}) if $hash{'start_date'};
+ my $text = 'Set this package';
+ if ( @supp_pkgs ) {
+ $text .= ' and all its supplemental packages';
+ }
+ $text .= ' to start billing';
+ if ( $start ) {
+ $text .= ' on [_1].';
+ push @changes, mt($text, $start);
+ } else {
+ $text .= ' immediately.';
+ push @changes, mt($text);
+ }
+ push @confirm, '';
+}
+
+# Setup date changes
+if ( $hash{'setup'} != $cust_pkg->get('setup') ) {
+ my $setup = time2str($date_format, $hash{'setup'});
+ my $has_setup_fee = grep { $_->part_pkg->option('setup_fee',1) > 0 }
+ $cust_pkg, @supp_pkgs;
+ if ( !$hash{'setup'} ) {
+ my $text = 'Remove the setup date';
+ $text .= ' from this and all its supplemental packages' if @supp_pkgs;
+ $text .= '.';
+ push @changes, mt($text);
+ if ( $has_setup_fee ) {
+ push @confirm, mt('This will re-charge the customer for the setup fee.');
+ } else {
+ push @confirm, '';
+ }
+ } elsif ( $hash{'setup'} and !$cust_pkg->get('setup') ) {
+ my $text = 'Add a setup date of [_1]';
+ $text .= ' to this and all its supplemental packages' if @supp_pkgs;
+ $text .= '.';
+ push @changes, mt($text, $setup);
+ if ( $has_setup_fee ) {
+ push @confirm, mt('This will prevent charging the setup fee.');
+ } else {
+ push @confirm, '';
+ }
+ } else {
+ my $text = 'Set the setup date to [_1]';
+ $text .= ' on this and all its supplemental packages' if @supp_pkgs;
+ $text .= '.';
+ push @changes, mt($text, $setup);
+ push @confirm, '';
+ }
+}
+
+# Check for start date + setup date
+if ( $hash{'start_date'} and $hash{'setup'} ) {
+ if ( $cust_pkg->get('setup') ) {
+ push @errors, mt('Since the package has already started billing, it '.
+ 'cannot have a start date.');
+ } else {
+ push @errors, mt('You cannot set both a start date and a setup date on '.
+ 'the same package.');
+ }
+}
+
+# Last bill date change
+if ( $hash{'last_bill'} != $cust_pkg->get('last_bill') ) {
+ my $last_bill = time2str($date_format, $hash{'last_bill'});
+ my $name = 'last bill date';
+ $name = 'last renewal date' if $part_pkg->is_prepaid;
+ if ( $hash{'last_bill'} ) {
+ push @changes, mt('Set the [_1] to [_2].', $name, $last_bill);
+ } else {
+ push @changes, mt('Remove the [_1].', $name);
+ }
+ push @confirm, '';
+ # I don't think we want to adjust this on supplemental packages.
+}
+
+# Bill date change
+if ( $hash{'bill'} != $cust_pkg->get('bill') ) {
+ my $bill = time2str($date_format, $hash{'bill'});
+ $bill = 'today' if !$hash{'bill'}; # or 'the end of today'?...
+ my $name = 'next bill date';
+ $name = 'end of the prepaid period' if $part_pkg->is_prepaid;
+ push @changes, mt('Set the [_1] to [_2].', $name, $bill);
+
+ if ( $hash{'bill'} < time and $hash{'bill'} ) {
+ push @confirm,
+ mt('The customer will be charged for the interval from [_1] until now.',
+ $bill);
+ } elsif ( !$hash{'bill'} and ($hash{'last_bill'} or $hash{'setup'}) ) {
+ my $last_bill =
+ time2str($date_format, $hash{'last_bill'} || $hash{'setup'});
+ push @confirm,
+ mt('The customer will be charged for the interval from [_1] until now.',
+ $last_bill);
+ } else {
+ push @confirm, '';
+ }
+
+ if ( @supp_pkgs ) {
+ push @changes, '';
+ if ( $cust_pkg->get('bill') and $hash{'bill'} ) {
+ # the package already has a bill date, so adjust the dates
+ # of supplementals by the same interval
+ my $diff = $hash{'bill'} - $cust_pkg->get('bill');
+ my $sign = $diff < 0 ? -1 : 1;
+ $diff = $diff * $sign / 86400;
+ if ( $diff < 1 ) {
+ $diff = mt('[quant,_1,hour]', int($diff * 24));
+ } else {
+ $diff = mt('[quant,_1,day]', int($diff));
+ }
+ push @confirm,
+ mt('[_1] supplemental package will also be billed [_2] [_3].',
+ (@supp_pkgs > 1 ? 'Each' : 'The'),
+ $diff,
+ ($sign > 0 ? 'later' : 'earlier')
+ );
+ } else {
+ # the package hasn't been billed yet, or you've set bill = null
+ push @confirm,
+ mt('[_1] supplemental package will also be billed on [_2].',
+ (@supp_pkgs > 1 ? 'Each' : 'The'),
+ $bill
+ );
+ }
+ } #if @supp_pkgs
+
+ if ( $main_pkg ) {
+ push @changes, '';
+ push @confirm,
+ mt('This package is a supplemental package. The bill date of its '.
+ 'main package will not be adjusted.');
+ }
+}
+
+# Contract end change
+if ( $hash{'contract_end'} != $cust_pkg->get('contract_end') ) {
+ if ( $hash{'contract_end'} ) {
+ my $contract_end = time2str($date_format, $hash{'contract_end'});
+ push @changes,
+ mt('Set this package\'s contract end date to [_1]', $contract_end);
+ } else {
+ push @changes, mt('Remove this package\'s contract end date.');
+ }
+ if ( @supp_pkgs ) {
+ my $text = 'This change will also apply to ' .
+ (@supp_pkgs > 1 ?
+ 'all supplemental packages.':
+ 'the supplemental package.');
+ push @confirm, mt($text);
+ } else {
+ push @confirm, '';
+ }
+}
+
+my $title = '';
+if ( @errors ) {
+ $title = 'Error changing package dates';
+} else {
+ $title = 'Confirm date changes';
+}
+</%init>
+<& /elements/header-popup.html, { title => $title, etc => 'BGCOLOR=""' } &>
+<STYLE TYPE="text/css">
+.error {
+ color: #ff0000;
+ font-weight: bold;
+ text-align: center;
+}
+.confirm { color: #ff0000 }
+.button-container {
+ position: fixed;
+ bottom: 5px;
+ text-align: center;
+ width: 100%
+}
+</STYLE>
+<DIV STYLE="text-align: center; padding:1em">
+<% emt('Package #') %><B><% $pkgnum %></B>: <B><% $cust_pkg->part_pkg->pkg %></B><BR>
+% if ( @changes ) {
+ <% emt('The following changes will be made:') %>
+% } else {
+ <% emt('No changes will be made.') %>
+% }
+</DIV>
+<TABLE WIDTH="100%">
+% if ( @errors ) {
+% foreach my $error ( @errors ) {
+<TR>
+ <TD><IMG SRC="<%$p%>images/cross.png"></TD>
+ <TD CLASS="error"><% $error %></TD>
+</TR>
+% }
+% } else {
+% while (@changes, @confirm) {
+% my $text = shift @changes;
+% if (length $text) {
+<TR>
+ <TD><IMG SRC="<%$p%>images/tick.png"></TD>
+ <TD><% $text %></TD>
+</TR>
+% }
+% $text = shift @confirm;
+% if (length $text) {
+<TR>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="areyousure" VALUE=1 onclick="submit_ready()">
+ </TD>
+ <TD CLASS="confirm"><% $text %></TD>
+</TR>
+% }
+% }
+% }
+</TABLE>
+%# action buttons
+<DIV CLASS="button-container">
+ <BUTTON TYPE="button" STYLE="width:145px" ID="submit_cancel"\
+ onclick="submit_cancel()">
+ <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel
+ </BUTTON>
+% if (!@errors ) {
+ <BUTTON TYPE="button" STYLE="width:145px" ID="submit_continue"\
+ onclick="submit_continue()">
+ <IMG SRC="<%$p%>images/tick.png" ALT=""> Continue
+ </BUTTON>
+</DIV>
+% }
+<FORM NAME="DateEditForm" STYLE="display:none" TARGET="_parent" ACTION="<%$p%>edit/process/REAL_cust_pkg.cgi" METHOD="POST">
+% foreach (keys %hash) {
+<INPUT TYPE="hidden" NAME="<%$_%>" VALUE="<% $hash{$_} |h%>">
+% }
+</FORM>
+<SCRIPT>
+function submit_ready() {
+ var ready = true;
+ var checkboxes = document.getElementsByName('areyousure');
+ var i;
+ for (i=0; i < checkboxes.length; i++) {
+ if (! checkboxes[i].checked ) {
+ ready = false;
+ }
+ }
+ document.getElementById('submit_continue').disabled = !ready;
+ return ready;
+}
+function submit_cancel() {
+ parent.nd(1);
+}
+function submit_continue() {
+ if ( submit_ready() ) {
+ document.forms.DateEditForm.submit();
+ }
+}
+submit_ready();
+</SCRIPT>
+<& /elements/footer.html &>
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/email-customers.html b/httemplate/misc/email-customers.html
index d26e40298..fcd79d7f8 100644
--- a/httemplate/misc/email-customers.html
+++ b/httemplate/misc/email-customers.html
@@ -120,9 +120,11 @@ Template:
<TR>
<TD ALIGN="right" VALIGN="top" STYLE="padding-top:3px">Message: </TD>
- <TD><& '/elements/htmlarea.html',
- 'field' => 'html_body',
- 'width' => 600 &></TD>
+ <TD><& /elements/htmlarea.html,
+ 'field' => 'html_body',
+ 'width' => 763,
+ &>
+ </TD>
</TR>
</TABLE>
diff --git a/httemplate/misc/exchanges.cgi b/httemplate/misc/exchanges.cgi
index 8a67f7bab..0de4ace25 100644
--- a/httemplate/misc/exchanges.cgi
+++ b/httemplate/misc/exchanges.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@exchanges) %>
+<% encode_json(\@exchanges) %>\
<%init>
my( $areacode, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/location.cgi b/httemplate/misc/location.cgi
index 188c5c3df..fab61dd01 100644
--- a/httemplate/misc/location.cgi
+++ b/httemplate/misc/location.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\%hash) %>
+<% encode_json(\%hash) %>\
<%init>
my $locationnum = $cgi->param('arg');
@@ -24,8 +24,9 @@ my $cust_location = qsearchs({
my %hash = ();
%hash = map { $_ => $cust_location->$_() }
- qw( address1 address2 city county state zip country
- location_kind location_type location_number )
+ ( FS::cust_main->location_fields,
+ qw( location_kind location_type location_number )
+ )
if $cust_location;
</%init>
diff --git a/httemplate/misc/macinventory.cgi b/httemplate/misc/macinventory.cgi
index b07da9726..cec0e3121 100644
--- a/httemplate/misc/macinventory.cgi
+++ b/httemplate/misc/macinventory.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@macs) %>
+<% encode_json(\@macs) %>\
<%init>
# XXX: this should be agent-virtualized / limited
@@ -13,13 +13,8 @@ die "unknown devicepart $devicepart" unless $part_device;
my $inventory_class = $part_device->inventory_class;
die "devicepart $devicepart has no inventory" unless $inventory_class;
-my @inventory_item =
+my @macs =
+ map $_->item,
qsearch('inventory_item', { 'classnum' => $inventory_class->classnum } );
-my @macs;
-
-foreach my $inventory_item ( @inventory_item ) {
- push @macs, $inventory_item->item;
-}
-
</%init>
diff --git a/httemplate/misc/maestro-customer_status.html b/httemplate/misc/maestro-customer_status.html
index 8acae2b2a..a872d4997 100644
--- a/httemplate/misc/maestro-customer_status.html
+++ b/httemplate/misc/maestro-customer_status.html
@@ -1,4 +1,4 @@
-<% objToJson( $return ) %>
+<% encode_json( $return ) %>\
<%init>
my $return;
diff --git a/httemplate/misc/manage_cust_email.html b/httemplate/misc/manage_cust_email.html
new file mode 100644
index 000000000..3ece459bb
--- /dev/null
+++ b/httemplate/misc/manage_cust_email.html
@@ -0,0 +1,106 @@
+<& /elements/header.html, 'Manage customer email settings' &>
+<STYLE TYPE="text/css">
+.hidden { display: none }
+</STYLE>
+<& /elements/xmlhttp.html,
+ url => $p.'misc/xmlhttp-cust_main-email_search.html',
+ subs => ['email_search']
+&>
+<SCRIPT TYPE="text/javascript">
+
+function receive_search(result) {
+ var recs = JSON.parse(result);
+ var tbody = document.getElementById('tbody_results');
+ var j = tbody.rows.length;
+ for(var i = 0; i < j; i++) {
+ tbody.deleteRow(tbody.rows[i]);
+ }
+ if (recs.length > 0) {
+ for(var i = 0; i < recs.length; i++) {
+ var rec = recs[i];
+ var row = tbody.insertRow(i);
+ row.style.backgroundColor = (i % 2 ? '#eeeeee' : '#ffffff');
+
+ var cell = row.insertCell(0); // custnum
+ cell.appendChild( document.createTextNode(rec[0]) );
+ cell = row.insertCell(1); // customer name
+ cell.appendChild( document.createTextNode(rec[1]) );
+ cell = row.insertCell(2); // email
+ cell.appendChild( document.createTextNode(rec[2]) );
+
+ cell = row.insertCell(3); // invoice_email
+ var input = document.createElement('INPUT');
+ input.type = 'hidden';
+ input.name = 'custnum';
+ input.value = rec[0];
+ cell.appendChild(input);
+
+ input = document.createElement('INPUT');
+ input.type = 'checkbox';
+ input.name = 'custnum' + rec[0] + '_invoice_email';
+ input.value = 'Y';
+ input.checked = (rec[3] != 'Y');
+ cell.appendChild(input);
+ cell.style.textAlign = 'center';
+
+ cell = row.insertCell(4); // message_email
+ input = document.createElement('INPUT');
+ input.type = 'checkbox';
+ input.name = 'custnum' + rec[0] + '_message_email';
+ input.value = 'Y';
+ input.checked = (rec[4] != 'Y');
+ cell.appendChild(input);
+ cell.style.textAlign = 'center';
+ }
+ document.getElementById('div_found').style.display = '';
+ } else {
+ document.getElementById('div_notfound').style.display = '';
+ }
+}
+
+function start_search() {
+ document.getElementById('div_found').style.display = 'none';
+ document.getElementById('div_notfound').style.display = 'none';
+ var email = document.getElementById('input_email').value;
+ email_search(email, receive_search);
+}
+% if ( $cgi->param('search') ) {
+window.onload = start_search;
+% }
+</SCRIPT>
+<FORM ACTION="<%$p%>misc/process/manage_cust_email.html" METHOD="POST">
+<DIV>
+% if ( $cgi->param('done') ) {
+<P STYLE="font-weight: bold; color: #00ff00">Changes saved.</P>
+% } elsif ( $cgi->param('error') ) {
+<P STYLE="font-weight: bold; color: #ff0000"><% $cgi->param('error') |h %></P>
+% }
+ Email address:
+ <INPUT TYPE="text" ID="input_email" NAME="search"\
+ VALUE="<% $cgi->param('search') |h %>">
+ <INPUT TYPE="button" onclick="start_search()" VALUE="find">
+</DIV>
+<DIV ID="div_notfound" STYLE="display: none; padding: 1em">
+No matching email addresses found.
+</DIV>
+<DIV ID="div_found" STYLE="display: none">
+<TABLE CLASS="grid" STYLE="border-spacing: 0px">
+ <THEAD>
+ <TR STYLE="background-color: #dddddd">
+ <TH>#</TH>
+ <TH>Customer</TH>
+ <TH>Email</TH>
+ <TH>Send invoices</TH>
+ <TH>Send other notices</TH>
+ </TR>
+ </THEAD>
+ <TBODY ID="tbody_results"></TBODY>
+</TABLE>
+<INPUT TYPE="submit" VALUE="Save changes">
+</FORM>
+<& /elements/footer.html &>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+</%init>
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index bfc7b6903..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"
@@ -129,9 +135,6 @@
<& /elements/standardize_locations.html,
'form' => "OrderPkgForm",
- 'onlyship' => 1,
- 'no_company' => 1,
- 'no_census' => 1,
'callback' => 'document.OrderPkgForm.submit();',
&>
diff --git a/httemplate/misc/part_export/huawei_hlr-import_sim.html b/httemplate/misc/part_export/huawei_hlr-import_sim.html
new file mode 100644
index 000000000..9b87b3d2a
--- /dev/null
+++ b/httemplate/misc/part_export/huawei_hlr-import_sim.html
@@ -0,0 +1,52 @@
+<& /elements/header-popup.html, 'Import SIMs' &>
+Import a file containing SIM card properties.<BR>
+Each row should contain the following fields, separated by spaces:<BR>
+IMSI, ICCID, PIN1, PUK1, PIN2, PUK2, ACC, Ki<BR>
+<BR>
+<& /elements/form-file_upload.html,
+ 'name' => 'ImportForm',
+ 'action' => 'process/huawei_hlr-import_sim.html',
+ 'num_files' => 1,
+ 'fields' => [ 'exportnum', 'classnum', 'agentnum', ],
+ 'message' => 'Inventory import successful',
+ 'onsubmit' => "document.ImportForm.submitButton.disabled=true;",
+&>
+<TABLE CLASS="inv" WIDTH="100%">
+ <INPUT TYPE="hidden" NAME="exportnum" VALUE="<%$exportnum%>">
+ <& /elements/file-upload.html,
+ 'field' => 'file',
+ 'label' => 'Filename',
+ &>
+ <& /elements/tr-select-agent.html,
+ 'disable_empty' => 1,
+ &>
+ <& /elements/tr-select-table.html,
+ 'table' => 'inventory_class',
+ 'name_col' => 'classname',
+ 'label' => 'Inventory class',
+ 'disable_empty' => 1,
+ &>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ NAME = "submitButton"
+ ID = "submitButton"
+ VALUE = "Import file"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my ($exportnum) = $cgi->keywords;
+$exportnum =~ /^\d+$/ or die "bad exportnum '$exportnum'";
+my $part_export = FS::part_export->by_key($exportnum)
+ or die "export $exportnum not found";
+</%init>
diff --git a/httemplate/misc/part_export/process/huawei_hlr-import_sim.html b/httemplate/misc/part_export/process/huawei_hlr-import_sim.html
new file mode 100644
index 000000000..d46700d5f
--- /dev/null
+++ b/httemplate/misc/part_export/process/huawei_hlr-import_sim.html
@@ -0,0 +1,10 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC
+ 'FS::part_export::huawei_hlr::process_import_sim', $cgi;
+
+</%init>
diff --git a/httemplate/misc/part_svc-columns.cgi b/httemplate/misc/part_svc-columns.cgi
index 060256154..a86164d06 100644
--- a/httemplate/misc/part_svc-columns.cgi
+++ b/httemplate/misc/part_svc-columns.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@output) %>
+<% encode_json(\@output) %>\
<%init>
my $conf = new FS::Conf;
diff --git a/httemplate/misc/phonenums.cgi b/httemplate/misc/phonenums.cgi
index 5084628eb..a048280bb 100644
--- a/httemplate/misc/phonenums.cgi
+++ b/httemplate/misc/phonenums.cgi
@@ -1,4 +1,4 @@
-<% objToJson(\@phonenums) %>
+<% encode_json(\@phonenums) %>\
<%init>
my( $exchangestring, $svcpart ) = $cgi->param('arg');
diff --git a/httemplate/misc/process/change-password.html b/httemplate/misc/process/change-password.html
new file mode 100644
index 000000000..7cab9c4e3
--- /dev/null
+++ b/httemplate/misc/process/change-password.html
@@ -0,0 +1,26 @@
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+$cgi->param('svcnum') =~ /^(\d+)$/ or die "illegal svcnum";
+my $svcnum = $1;
+my $svc_acct = FS::svc_acct->by_key($svcnum)
+ or die "svc_acct $svcnum not found";
+my $part_svc = $svc_acct->part_svc;
+die "access denied" unless (
+ $curuser->access_right('Provision customer service') or
+ ( $curuser->access_right('Edit password') and
+ ! $part_svc->restrict_edit_password )
+ );
+my $error = $svc_acct->set_password($cgi->param('password'))
+ || $svc_acct->replace;
+
+# annoyingly specific to view/svc_acct.cgi, for now...
+$cgi->delete('password');
+</%init>
+% if ( $error ) {
+% $cgi->param('svcnum', $svcnum);
+% $cgi->param("changepw${svcnum}_error", $error);
+% } else {
+% $cgi->query_string($svcnum);
+% }
+<% $cgi->redirect($fsurl.'view/svc_acct.cgi?'.$cgi->query_string) %>
diff --git a/httemplate/misc/process/change_pkg_contact.html b/httemplate/misc/process/change_pkg_contact.html
new file mode 100644
index 000000000..2795c1197
--- /dev/null
+++ b/httemplate/misc/process/change_pkg_contact.html
@@ -0,0 +1,49 @@
+<% header(emt("Package contact $past_method")) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Change customer package');
+
+#untaint pkgnum
+my $pkgnum = $cgi->param('pkgnum');
+$pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); #needs agent virt
+
+my $contactnum = $cgi->param('contactnum');
+$contactnum =~ /^(-?\d*)$/ or die "Illegal contactnum";
+$contactnum = $1;
+
+my $past_method = $cust_pkg->contactnum ? 'changed' : 'added';
+
+my $error = '';
+
+if ( $contactnum == -1 ) {
+
+ #little false laziness w/edit/process/quick-cust_pkg.cgi, also the whole
+ # thing should be a single transaction
+ my $contact = new FS::contact {
+ 'custnum' => $cust_pkg->custnum,
+ map { $_ => scalar($cgi->param("contactnum_$_")) } qw( first last )
+ };
+ $error = $contact->insert;
+ $cust_pkg->contactnum( $contact->contactnum );
+
+} else {
+ $cust_pkg->contactnum($contactnum);
+}
+
+$error ||= $cust_pkg->replace;
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "change_pkg_contact.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/process/manage_cust_email.html b/httemplate/misc/process/manage_cust_email.html
new file mode 100644
index 000000000..5bf1470d1
--- /dev/null
+++ b/httemplate/misc/process/manage_cust_email.html
@@ -0,0 +1,32 @@
+<% $cgi->redirect($fsurl.'misc/manage_cust_email.html?' .
+ $cgi->query_string) %>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+my $error;
+foreach my $custnum ($cgi->param('custnum')) {
+ my $cust = FS::cust_main->by_key($custnum)
+ or die "customer not found: $custnum\n";
+ my $new_invoice_noemail =
+ $cgi->param('custnum'.$custnum.'_invoice_email') ? '' : 'Y';
+ my $new_message_noemail =
+ $cgi->param('custnum'.$custnum.'_message_email') ? '' : 'Y';
+ if ( $new_invoice_noemail ne $cust->invoice_noemail
+ or $new_message_noemail ne $cust->message_noemail ) {
+
+ $cust->set('invoice_noemail', $new_invoice_noemail);
+ $cust->set('message_noemail', $new_message_noemail);
+ $error ||= $cust->replace;
+
+ }
+ $cgi->delete('custnum'.$custnum.'_invoice_email');
+ $cgi->delete('custnum'.$custnum.'_message_email');
+}
+$cgi->delete('custnum');
+if ( $error ) {
+ $cgi->param('error' => $error); # probably unnecessary...
+} else {
+ $cgi->param('done' => 1) unless $error;
+}
+</%init>
diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi
index 506e26684..981614e76 100644
--- a/httemplate/misc/process/payment.cgi
+++ b/httemplate/misc/process/payment.cgi
@@ -210,7 +210,15 @@ if ( $cgi->param('save') ) {
$new->set( 'paycvv' => '');
}
- $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
+ if ( $payby eq 'CARD' ) {
+ my $bill_location = FS::cust_location->new;
+ $bill_location->set( $_ => $cgi->param($_) )
+ foreach @{$payby2fields{$payby}};
+ $new->set('bill_location' => $bill_location);
+ # will do nothing if the fields are all unchanged
+ } else {
+ $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
+ }
my $error = $new->replace($cust_main);
errorpage("payment processed successfully, but error saving info: $error")
diff --git a/httemplate/misc/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 988057163..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
@@ -16,12 +16,10 @@ my %old = %{ decode_json($cgi->param('arg')) }
my %new;
-my @prefixes;
-if ($old{onlyship}) {
- @prefixes = ('ship_');
-} elsif ( $old{same} ) {
+my @prefixes = ('');
+if ( $old{same} ) {
@prefixes = ('bill_');
-} else {
+} elsif ( $old{billship} ) {
@prefixes = ('bill_', 'ship_');
}
my $all_same = 1;
@@ -44,6 +42,8 @@ foreach my $pre ( @prefixes ) {
$all_same = 0 if ( $new{$pre.$_} ne $old{$pre.$_} );
last if !$all_same;
}
+
+ $all_same = 0 if $new{$pre.'error'};
}
my $return = { old => \%old, new => \%new, all_same => $all_same };
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 993504619..c0db3e2c4 100644
--- a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
+++ b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
@@ -1,8 +1,8 @@
-<% to_json($return) %>
+<% encode_json($return) %>\
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
-die "access denied" unless $curuser->access_right('Post credit');
+die "access denied" unless $curuser->access_right('Credit line items');
my $DEBUG = 0;
diff --git a/httemplate/misc/xmlhttp-cust_main-censustract.html b/httemplate/misc/xmlhttp-cust_main-censustract.html
index 4b00898da..4c708a4c4 100644
--- a/httemplate/misc/xmlhttp-cust_main-censustract.html
+++ b/httemplate/misc/xmlhttp-cust_main-censustract.html
@@ -1,4 +1,4 @@
-<% objToJson($return) %>
+<% encode_json($return) %>\
<%init>
my %arg = $cgi->param('arg');
diff --git a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
index b524e69fc..36b18b455 100644
--- a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
+++ b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi
@@ -16,7 +16,7 @@
% }
% }
%
-<% objToJson($return) %>
+<% encode_json($return) %>\
% }
<%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-email_search.html b/httemplate/misc/xmlhttp-cust_main-email_search.html
new file mode 100644
index 000000000..0d830826c
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_main-email_search.html
@@ -0,0 +1,29 @@
+<% encode_json(\@result) %>\
+<%init>
+die 'access denied'
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+my $sub = $cgi->param('sub');
+my $email = $cgi->param('arg');
+my @where = (
+ "cust_main_invoice.dest != 'POST'",
+ "cust_main_invoice.dest LIKE ".dbh->quote('%'.$email.'%'),
+ $FS::CurrentUser::CurrentUser->agentnums_sql(table => 'cust_main'),
+);
+my @cust_main = qsearch({
+ 'table' => 'cust_main',
+ 'select' => 'cust_main.*, cust_main_invoice.dest',
+ 'addl_from' => 'JOIN cust_main_invoice USING (custnum)',
+ 'extra_sql' => 'WHERE '.join(' AND ', @where),
+});
+
+my @result = map {
+ [ $_->custnum,
+ $_->name,
+ $_->dest,
+ $_->invoice_noemail,
+ $_->message_noemail,
+ ]
+} @cust_main;
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi
index acf7e70e2..73c9ff8ec 100644
--- a/httemplate/misc/xmlhttp-cust_main-search.cgi
+++ b/httemplate/misc/xmlhttp-cust_main-search.cgi
@@ -5,7 +5,7 @@
% # cust_main-agent_custid-format') eq 'ww?d+'
% $return = findbycustnum_or_agent_custid($1);
% }
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } elsif ( $sub eq 'smart_search' ) {
%
% my $string = $cgi->param('arg');
@@ -22,14 +22,14 @@
% @cust_main
% ];
%
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } elsif ( $sub eq 'invnum_search' ) {
%
% my $string = $cgi->param('arg');
% if ( $string =~ /^(\d+)$/ ) {
% my $inv = qsearchs('cust_bill', { 'invnum' => $1 });
% my $return = $inv ? findbycustnum($inv->custnum) : [];
-<% objToJson($return) %>
+<% encode_json($return) %>\
% } else { #return nothing
[]
% }
@@ -47,7 +47,7 @@
% city => $_->city,
% };
% }
-<% objToJson($return) %>
+<% encode_json($return) %>\
% }
<%init>
diff --git a/httemplate/misc/xmlhttp-ping.html b/httemplate/misc/xmlhttp-ping.html
index e99303207..01baa3f57 100644
--- a/httemplate/misc/xmlhttp-ping.html
+++ b/httemplate/misc/xmlhttp-ping.html
@@ -1,4 +1,4 @@
-<% objToJson($return) %>
+<% encode_json($return) %>\
<%init>
my $conf = new FS::Conf;
diff --git a/httemplate/misc/xmlhttp-svc_broadband-search.cgi b/httemplate/misc/xmlhttp-svc_broadband-search.cgi
new file mode 100644
index 000000000..578e6140e
--- /dev/null
+++ b/httemplate/misc/xmlhttp-svc_broadband-search.cgi
@@ -0,0 +1,22 @@
+% if ( $sub eq 'smart_search' ) {
+%
+% my $string = $cgi->param('arg');
+% my @svc_broadband = FS::svc_broadband->smart_search( $string );
+% my $return = [ map { my $cust_pkg = $_->cust_svc->cust_pkg;
+% [ $_->svcnum,
+% $_->label. ( $cust_pkg
+% ? ' ('. $cust_pkg->cust_main->name. ')'
+% : ''
+% ),
+% ];
+% }
+% @svc_broadband,
+% ];
+%
+<% encode_json($return) %>\
+% }
+<%init>
+
+my $sub = $cgi->param('sub');
+
+</%init>
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index c4fef0311..6b94f7175 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -49,6 +49,7 @@ unless ( $error ) { # if ($access_user) {
#XXX autogen
my @paramlist = qw( locale menu_position default_customer_view
+ history_order
spreadsheet_format mobile_menu
enable_fuzzy_on_exact
disable_html_editor disable_enter_submit_onetimecharge
@@ -57,7 +58,7 @@ unless ( $error ) { # if ($access_user) {
vonage-fromnumber vonage-username vonage-password
cust_pkg-display_times
show_pkgnum show_confitem_counts export_getsettings
- show_db_profile save_db_profile
+ show_db_profile save_db_profile save_tmp_typesetting
height width availHeight availWidth colorDepth
);
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index 1e9671dcc..5babb0181 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -75,6 +75,21 @@ Interface
</SELECT>
</TD>
</TR>
+
+% my $history_order = $curuser->option('history_order') || 'oldest';
+ <TR>
+ <TH ALIGN="right">Customer history sort order: </TH>
+ <TD COLSPAN=2>
+ <& /elements/select.html,
+ field => 'history_order',
+ curr_value => $history_order,
+ options => [ 'oldest', 'newest' ],
+ labels => { 'oldest' => 'Oldest first',
+ 'newest' => 'Newest first',
+ },
+ &>
+ </TD>
+ </TR>
<TR>
<TH ALIGN="right">Spreadsheet download format: </TH>
@@ -92,7 +107,7 @@ Interface
</TR>
<TR>
- <TH ALIGN="right" COLSPAN=1>Enable approximate customer searching even when an exact match is found: </TH>
+ <TH ALIGN="right" COLSPAN=1>Enable approximate customer searching <BR>even when an exact match is found: </TH>
<TD ALIGN="left" COLSPAN=2>
<INPUT TYPE="checkbox" NAME="enable_fuzzy_on_exact" VALUE="1" <% $curuser->option('enable_fuzzy_on_exact') ? 'CHECKED' : '' %>>
</TD>
@@ -157,6 +172,10 @@ Development
<TH>Save database profiling logs (when available): </TH>
<TD><INPUT TYPE="checkbox" NAME="save_db_profile" VALUE="1" <% $curuser->option('save_db_profile') ? 'CHECKED' : '' %>></TD>
</TR>
+ <TR>
+ <TH>Save temporary invoice typesetting files: </TH>
+ <TD><INPUT TYPE="checkbox" NAME="save_tmp_typesetting" VALUE="1" <% $curuser->option('save_tmp_typesetting') ? 'CHECKED' : '' %>></TD>
+ </TR>
</TABLE>
<BR>
diff --git a/httemplate/search/477.html b/httemplate/search/477.html
index 6f5fcdf3b..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) %>>
% }
@@ -97,6 +108,11 @@ for(my $i=0; $i < scalar(@part2b_row_option); $i++) {
&FS::Report::FCC_477::save_fcc477map("part2b_row_option_$i",$part2b_row_option[$i]);
}
+my $part5_report_option = $cgi->param('part5_report_option');
+if ( $part5_report_option ) {
+ FS::Report::FCC_477::save_fcc477map('part5_report_option', $part5_report_option);
+}
+
my $url_mangler = sub {
my $part = shift;
my $url = $cgi->url('-path_info' => 1, '-full' => 1);
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 66f3a8651..000000000
--- a/httemplate/search/477partIA_detail.html
+++ /dev/null
@@ -1,129 +0,0 @@
-<% include( '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 f5c2bc251..000000000
--- a/httemplate/search/477partIA_summary.html
+++ /dev/null
@@ -1,89 +0,0 @@
-<% include( '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 d2cc8c3e9..95c00a3e0 100755
--- a/httemplate/search/477partIIA.html
+++ b/httemplate/search/477partIIA.html
@@ -1,17 +1,44 @@
-<% include( '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 2fd5119d1..b2dd9ca95 100755
--- a/httemplate/search/477partV.html
+++ b/httemplate/search/477partV.html
@@ -1,4 +1,7 @@
-<% include( 'elements/search.html',
+% if ( $cgi->param('_type') =~ /^xml$/ ) {
+<zip_code>
+% }
+<& elements/search.html,
'html_init' => $html_init,
'name' => 'zip code',
'query' => $sql_query,
@@ -12,8 +15,11 @@
'url' => $opt{url} || '',
'really_disable_download' => 1,
- )
-%>
+
+&>
+% if ( $cgi->param('_type') =~ /^xml$/ ) {
+</zip_code>
+% }
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -32,8 +38,8 @@ for ( qw(agentnum magic state) ) {
}
$search_hash{'country'} = 'US';
$search_hash{'classnum'} = [ $cgi->param('classnum') ];
-$search_hash{report_option} = $cgi->param('partv_report_option')
- if $cgi->param('partv_report_option');
+$search_hash{report_option} = $cgi->param('part5_report_option')
+ if $cgi->param('part5_report_option');
my $sql_query = FS::cust_pkg->search( { %search_hash,
'fcc_line' => 1,
diff --git a/httemplate/search/477partVI_census.html b/httemplate/search/477partVI_census.html
index 8425c4b48..59a6fb50d 100755
--- a/httemplate/search/477partVI_census.html
+++ b/httemplate/search/477partVI_census.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'html_init' => '<H2>Part VI</H2>',
'html_foot' => $html_foot,
'name' => 'regions',
@@ -24,8 +24,8 @@
'url' => $opt{url} || '',
'xml_row_element' => 'Datarow',
'really_disable_download' => 1,
- )
-%>
+
+&>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
diff --git a/httemplate/search/agent_commission.html b/httemplate/search/agent_commission.html
new file mode 100644
index 000000000..b94ae9f6e
--- /dev/null
+++ b/httemplate/search/agent_commission.html
@@ -0,0 +1,197 @@
+%# 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 {
+ border-left: none;
+ border-right: none;
+ padding-top: 0.5em;
+ font-weight: bold;
+ background-color: #ffffff;
+}
+td.money { text-align: right; }
+td.money:before { content: '<% $money_char %>'; }
+.row0 { background-color: #eeeeee; }
+.row1 { background-color: #ffffff; }
+</STYLE>
+<& /elements/table-grid.html &>
+ <TR STYLE="background-color: #cccccc">
+ <TH CLASS="grid">Package</TH>
+ <TH CLASS="grid">Sales</TH>
+ <TH CLASS="grid">Percentage</TH>
+ <TH CLASS="grid">Commission</TH>
+ </TR>
+% my ($custnum, $sales, $commission, $row, $bgcolor) = (0, 0, 0, 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;
+ <TR>
+ <TD COLSPAN=4 CLASS="cust_head">
+ <A HREF="<%$p%>view/cust_main.cgi?<%$cust_main->custnum%>"><% $label %></A>
+ </TD>
+ </TR>
+% }
+ <TR CLASS="row<% $bgcolor %>">
+ <TD CLASS="grid"><% $cust_pkg->pkg_label %></TD>
+ <TD CLASS="money"><% sprintf('%.2f', $cust_pkg->sum_charged) %></TD>
+ <TD ALIGN="right"><% $cust_pkg->percent %>%</TD>
+ <TD CLASS="money"><% sprintf('%.2f',
+ $cust_pkg->sum_charged * $cust_pkg->percent / 100) %></TD>
+ </TR>
+% $sales += $cust_pkg->sum_charged;
+% $commission += $cust_pkg->sum_charged * $cust_pkg->percent / 100;
+% $row++;
+% $bgcolor = 1-$bgcolor;
+% $custnum = $cust_pkg->custnum;
+% }
+ <TR STYLE="background-color: #f5f6be">
+ <TD CLASS="grid">
+ <% emt('[quant,_1,package] with commission', $row) %>
+ </TD>
+ <TD CLASS="money"><% sprintf('%.2f', $sales) %></TD>
+ <TD></TD>
+ <TD CLASS="money"><% sprintf('%.2f', $commission) %></TD>
+ </TR>
+</TABLE>
+<& /elements/footer.html &>
+% }
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my ($begin, $end) = FS::UI::Web::parse_beginning_ending($cgi);
+$cgi->param('agentnum') =~ /^(\d+)$/ or die "bad agentnum";
+my $agentnum = $1;
+my $agent = FS::agent->by_key($agentnum);
+
+my $title = $agent->agent . ' commissions';
+
+my $sum_charged =
+ '(SELECT SUM(setup + recur) 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 @select = (
+ 'cust_pkg.*',
+ 'agent_pkg_class.commission_percent AS percent',
+ "$sum_charged AS sum_charged",
+);
+
+my $query = {
+ 'table' => 'cust_pkg',
+ 'select' => join(',', @select),
+ 'addl_from' => 'JOIN cust_main USING (custnum) '.
+ 'JOIN part_pkg USING (pkgpart) '.
+ 'JOIN agent_pkg_class ON ( '.
+ 'cust_main.agentnum = agent_pkg_class.agentnum AND '.
+ '( agent_pkg_class.classnum = part_pkg.classnum OR '.
+ '(agent_pkg_class IS NULL AND part_pkg.classnum IS NULL)'.
+ ' ) ) ',
+ 'extra_sql' => "WHERE cust_main.agentnum = $agentnum AND ".
+ 'agent_pkg_class.commission_percent > 0 AND '.
+ "$sum_charged > 0",
+ 'order_by' => 'ORDER BY cust_pkg.custnum ASC',
+};
+
+my @cust_pkg = qsearch($query);
+
+my $money_char = FS::Conf->new->config('money_char') || '$';
+
+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/agent_inventory.html b/httemplate/search/agent_inventory.html
index ac65371ca..015aca46b 100644
--- a/httemplate/search/agent_inventory.html
+++ b/httemplate/search/agent_inventory.html
@@ -1,4 +1,4 @@
-<% include('elements/search.html',
+<& elements/search.html,
'title' => 'Inventory summary per agent',
'name_singular' => 'agent',
'query' => { 'table' => 'agent',
@@ -10,8 +10,7 @@
" AND $agentnums_sql",
'header' => \@header,
'fields' => \@fields,
- )
-%>
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/bill_batch.cgi b/httemplate/search/bill_batch.cgi
index b6676f261..b740bdc68 100755
--- a/httemplate/search/bill_batch.cgi
+++ b/httemplate/search/bill_batch.cgi
@@ -26,7 +26,7 @@ function start() {
% -expires => '-1d',
% );
% $r->headers_out->add( 'Set-Cookie' => $cookie->as_string );
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Invoice Batches',
'name_singular' => 'batch',
'query' => { 'table' => 'bill_batch',
@@ -67,9 +67,7 @@ function start() {
'agent_pos' => 1,
'html_foot' => include('.foot'),
- )
-
-%>
+&>
%}
<%def .foot>
<SCRIPT type="text/javascript">
diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html
index d0d7292d1..ca303d386 100644
--- a/httemplate/search/cdr.html
+++ b/httemplate/search/cdr.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'name' => 'call detail records',
'query' => $query,
@@ -9,27 +9,8 @@
'fields' => \@fields,
'links' => \@links,
'html_form' => qq!<FORM NAME="cdrForm" ACTION="$p/misc/cdr.cgi" METHOD="POST">!,
- #false laziness w/queue.html
- 'html_foot' => sub {
- if ( $areboxes ) {
- '<BR><INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">'.
- '<INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">'.
- qq!<BR><INPUT TYPE="submit" NAME="action" VALUE="reprocess selected" onClick="return confirm('Are you sure you want to reprocess the selected CDRs?')">!.
- qq!<INPUT TYPE="submit" NAME="action" VALUE="delete selected" onClick="return confirm('Are you sure you want to delete the selected CDRs?')"><BR>!.
- '<SCRIPT TYPE="text/javascript">'.
- ' function setAll(setTo) { '.
- ' theForm = document.cdrForm;'.
- ' for (i=0,n=theForm.elements.length;i<n;i++)'.
- ' if (theForm.elements[i].name.indexOf("acctid") != -1)'.
- ' theForm.elements[i].checked = setTo;'.
- ' }'.
- '</SCRIPT>';
- } else {
- '';
- }
- },
- )
-%>
+ 'html_foot' => $html_foot,
+&>
<%init>
die "access denied"
@@ -44,8 +25,6 @@ my $totalminutes_sub = sub {
my $conf = new FS::Conf;
-my $areboxes = 0;
-
my $title = 'Call Detail Records';
my $hashref = {};
@@ -355,7 +334,6 @@ my %links = (
@fields = map { exists($fields{$_}) ? $fields{$_} : $_ } @fields;
unshift @fields, sub {
return '' unless $edit_data;
- $areboxes = 1;
my $cdr = shift;
my $acctid = $cdr->acctid;
qq!<INPUT NAME="acctid$acctid" TYPE="checkbox" VALUE="1">!;
@@ -409,4 +387,14 @@ if ( $topmode ) {
$nototalminutes = 1;
}
+my $html_foot = include('/search/elements/checkbox-foot.html',
+ actions => [
+ { submit => "reprocess selected",
+ name => "action",
+ confirm => "Are you sure you want to reprocess the selected CDRs?" },
+ { submit => "delete selected",
+ name => "action",
+ confirm => "Are you sure you want to delete the selected CDRs?" },
+ ]
+);
</%init>
diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html
index 3c0530e4f..473aed311 100755
--- a/httemplate/search/cust_bill.html
+++ b/httemplate/search/cust_bill.html
@@ -62,7 +62,7 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('List invoices');
-my $join_cust_main = 'LEFT JOIN cust_main USING ( custnum )';
+my $join_cust_main = FS::UI::Web::join_cust_main('cust_bill');
#here is the agent virtualization
my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql;
@@ -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') ];
}
@@ -198,7 +198,6 @@ if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) {
};
}
-
my $link = [ "${p}view/cust_bill.cgi?", 'invnum', ];
my $clink = sub {
my $cust_bill = shift;
diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi
index 90c89139c..9fb533a5f 100644
--- a/httemplate/search/cust_bill_event.cgi
+++ b/httemplate/search/cust_bill_event.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'html_init' => $html_init,
'menubar' => $menubar,
@@ -60,8 +60,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -100,7 +100,7 @@ my $where = 'WHERE '. FS::cust_bill_event->search_sql_where( \%search );
my $join = 'LEFT JOIN part_bill_event USING ( eventpart ) '.
'LEFT JOIN cust_bill USING ( invnum ) '.
- 'LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_bill');
my $sql_query = {
'table' => 'cust_bill_event',
diff --git a/httemplate/search/cust_bill_pay.html b/httemplate/search/cust_bill_pay.html
index 79de74985..ff20458d8 100644
--- a/httemplate/search/cust_bill_pay.html
+++ b/httemplate/search/cust_bill_pay.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'name' => 'net payments',
'query' => $sql_query,
@@ -71,8 +71,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -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;
}
@@ -117,8 +120,8 @@ my $where = 'WHERE '. join(' AND ', @search);
#
my $count_query = 'SELECT COUNT(*), SUM(amount)
FROM cust_bill_pay
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum ) '.
+ LEFT JOIN cust_bill USING ( invnum ) '.
+ FS::UI::Web::join_cust_main('cust_bill') .
$where;
my $sql_query = {
@@ -137,8 +140,8 @@ my $sql_query = {
'hashref' => {},
'extra_sql' => $where,
'addl_from' => 'LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_pay USING ( paynum )
- LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum )',
+ LEFT JOIN cust_pay USING ( paynum ) '.
+ FS::UI::Web::join_cust_main('cust_bill')
};
my $cust_bill_link = sub {
diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
index 1e67e9320..3a3b0feb9 100644
--- a/httemplate/search/cust_bill_pkg.cgi
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -222,9 +222,9 @@ if ( $conf->exists('enable_taxclasses') ) {
# valid in both the tax and non-tax cases
my $join_cust =
- " LEFT JOIN cust_bill USING (invnum)
- LEFT JOIN cust_main USING (custnum)
- ";
+ " LEFT JOIN cust_bill USING (invnum)".
+ # use cust_pkg.locationnum if it exists
+ FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg');
#agent virtualization
my $agentnums_sql =
@@ -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";
@@ -278,11 +281,11 @@ my $join_pkg =
LEFT JOIN part_pkg USING (pkgpart)';
my $part_pkg = 'part_pkg';
-if ( $cgi->param('use_override') ) {
+if ( $cgi->param('use_override') ) { #"Separate sub-packages from parents"
# still need the real part_pkg for tax applicability,
# so alias this one
$join_pkg .= " LEFT JOIN part_pkg AS override ON (
- COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = part_pkg.pkgpart
+ COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = override.pkgpart
)";
$part_pkg = 'override';
}
@@ -559,12 +562,11 @@ if ( $cgi->param('nottax') ) {
#total payments
-my $pay_sub = "SELECT SUM(cust_bill_pay_pkg.amount) AS pay_amount,
- billpkgnum
- FROM cust_bill_pay_pkg
- GROUP BY billpkgnum";
-$join_pkg .= " LEFT JOIN ($pay_sub) AS item_pay USING (billpkgnum)";
-push @select, 'item_pay.pay_amount';
+my $pay_sub = "SELECT SUM(cust_bill_pay_pkg.amount)
+ FROM cust_bill_pay_pkg
+ WHERE cust_bill_pkg.billpkgnum = cust_bill_pay_pkg.billpkgnum
+ ";
+push @select, "($pay_sub) AS pay_amount";
# credit
@@ -630,13 +632,12 @@ if ( $cgi->param('credit') ) {
#still want a credit total column
- my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount,
- billpkgnum
- FROM cust_credit_bill_pkg
- GROUP BY billpkgnum";
- $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit USING (billpkgnum)";
-
- push @select, 'item_credit.credit_amount';
+ my $credit_sub = "
+ SELECT SUM(cust_credit_bill_pkg.amount)
+ FROM cust_credit_bill_pkg
+ WHERE cust_bill_pkg.billpkgnum = cust_credit_bill_pkg.billpkgnum
+ ";
+ push @select, "($credit_sub) AS credit_amount";
}
@@ -647,7 +648,7 @@ $where &&= "WHERE $where";
my $query = {
'table' => 'cust_bill_pkg',
- 'addl_from' => "$join_cust $join_pkg",
+ 'addl_from' => "$join_pkg $join_cust",
'hashref' => {},
'select' => join(",\n", @select ),
'extra_sql' => $where,
@@ -656,7 +657,7 @@ my $query = {
my $count_query =
'SELECT ' . join(',', @total) .
- " FROM cust_bill_pkg $join_cust $join_pkg
+ " FROM cust_bill_pkg $join_pkg $join_cust
$where";
@peritem_desc = map {emt($_)} @peritem_desc;
diff --git a/httemplate/search/cust_bill_pkg_discount.html b/httemplate/search/cust_bill_pkg_discount.html
index bb8038a44..f598341a0 100644
--- a/httemplate/search/cust_bill_pkg_discount.html
+++ b/httemplate/search/cust_bill_pkg_discount.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Discounts',
'name' => 'discounts',
'query' => $query,
@@ -68,8 +68,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
#a little false laziness below w/cust_bill_pkg.cgi
@@ -127,12 +127,12 @@ my $join_cust_pkg_discount =
'LEFT JOIN cust_pkg_discount USING (pkgdiscountnum)';
my $join_cust =
- ' JOIN cust_bill_pkg USING ( billpkgnum )
- JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum ) ';
+ ' JOIN cust_bill USING ( invnum ) '.
+ FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg');
my $join_pkg =
- ' LEFT JOIN cust_pkg ON ( cust_bill_pkg.pkgnum = cust_pkg.pkgnum )
+ ' JOIN cust_bill_pkg USING ( billpkgnum )
+ LEFT JOIN cust_pkg ON ( cust_bill_pkg.pkgnum = cust_pkg.pkgnum )
LEFT JOIN part_pkg USING ( pkgpart ) ';
#LEFT JOIN part_pkg AS override
# ON pkgpart_override = override.pkgpart ';
@@ -140,7 +140,7 @@ my $join_pkg =
my $where = ' WHERE '. join(' AND ', @where);
$count_query .=
- " FROM cust_bill_pkg_discount $join_cust_pkg_discount $join_cust $join_pkg ".
+ " FROM cust_bill_pkg_discount $join_cust_pkg_discount $join_pkg $join_cust ".
$where;
my @select = (
@@ -155,7 +155,7 @@ push @select, 'cust_main.custnum',
my $query = {
'table' => 'cust_bill_pkg_discount',
- 'addl_from' => "$join_cust_pkg_discount $join_cust $join_pkg",
+ 'addl_from' => "$join_cust_pkg_discount $join_pkg $join_cust",
'hashref' => {},
'select' => join(', ', @select ),
'extra_sql' => $where,
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 f5d8fa19f..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;
}
@@ -137,7 +141,7 @@ my $where = 'WHERE '. join(' AND ', @search);
my $count_query = 'SELECT COUNT(*), SUM(amount) ';
$count_query .= ', SUM(' . FS::cust_credit->unapplied_sql . ') ' if $unapplied;
-$count_query .= 'FROM cust_credit LEFT JOIN cust_main USING ( custnum ) '.
+$count_query .= 'FROM cust_credit'. FS::UI::Web::join_cust_main('cust_credit').
$where;
my @count_addl = ( $money_char.'%.2f total credited (gross)' );
@@ -148,7 +152,7 @@ my $sql_query = {
'select' => join(', ',@select),
'hashref' => {},
'extra_sql' => $where,
- 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'addl_from' => FS::UI::Web::join_cust_main('cust_credit')
};
</%init>
diff --git a/httemplate/search/cust_credit_bill.html b/httemplate/search/cust_credit_bill.html
index 9fd6a987a..88f897d70 100644
--- a/httemplate/search/cust_credit_bill.html
+++ b/httemplate/search/cust_credit_bill.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'name' => 'net credits',
'query' => $sql_query,
@@ -64,8 +64,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -103,8 +103,8 @@ my $where = 'WHERE '. join(' AND ', @search);
#
my $count_query = 'SELECT COUNT(*), SUM(amount)
FROM cust_credit_bill
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum ) '.
+ LEFT JOIN cust_bill USING ( invnum ) '.
+ FS::UI::Web::join_cust_main('cust_bill') .
$where;
my $sql_query = {
@@ -121,8 +121,8 @@ my $sql_query = {
'hashref' => {},
'extra_sql' => $where,
'addl_from' => 'LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_credit USING ( crednum )
- LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum )',
+ LEFT JOIN cust_credit USING ( crednum )'.
+ FS::UI::Web::join_cust_main('cust_bill')
};
my $cust_bill_link = sub {
diff --git a/httemplate/search/cust_credit_bill_pkg.html b/httemplate/search/cust_credit_bill_pkg.html
index 06fd881a8..63d70c27e 100644
--- a/httemplate/search/cust_credit_bill_pkg.html
+++ b/httemplate/search/cust_credit_bill_pkg.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Credit application detail', #to line item
'name_singular' => 'credit application',
'query' => $query,
@@ -16,6 +16,7 @@
# line item
'Description',
+ 'Location',
@post_desc_header,
#invoice
@@ -35,6 +36,7 @@
? $_[0]->get('pkg') # possibly use override.pkg
: $_[0]->get('itemdesc') # but i think this correct
},
+ $location_sub,
@post_desc,
'invnum',
sub { time2str('%b %d %Y', shift->_date ) },
@@ -46,6 +48,7 @@
'', #'otaker',
'', #reason
'', #line item description
+ '', #location
@post_desc_null,
'invnum',
'_date',
@@ -57,6 +60,7 @@
'',
'',
'',
+ '',
@post_desc_null,
$ilink,
$ilink,
@@ -64,7 +68,7 @@
FS::UI::Web::cust_header()
),
],
- 'align' => 'rrlll'.
+ 'align' => 'rrllll'.
$post_desc_align.
'rr'.
FS::UI::Web::cust_aligns(),
@@ -74,6 +78,7 @@
'',
'',
'',
+ '',
@post_desc_null,
'',
'',
@@ -85,13 +90,14 @@
'',
'',
'',
+ '',
@post_desc_null,
'',
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
#LOTS of false laziness below w/cust_bill_pkg.cgi
@@ -377,8 +383,8 @@ my $count_query = "SELECT COUNT(DISTINCT creditbillpkgnum),
SUM(cust_credit_bill_pkg.amount)";
my $join_cust =
- ' JOIN cust_bill ON ( cust_bill_pkg.invnum = cust_bill.invnum )
- LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum ) ';
+ ' JOIN cust_bill ON ( cust_bill_pkg.invnum = cust_bill.invnum )'.
+ FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg');
my $join_pkg;
@@ -423,10 +429,9 @@ if ( $cgi->param('nottax') ) {
s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where;
}
-} else {
+} else {
- #die?
- warn "neiether nottax nor istax parameters specified";
+ #warn "neither nottax nor istax parameters specified";
#same as before?
$join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
LEFT JOIN part_pkg USING ( pkgpart ) ';
@@ -459,7 +464,7 @@ my @post_desc_header = ();
my @post_desc = ();
my @post_desc_null = ();
my $post_desc_align = '';
-if ( $conf->exists('enable_taxclasses') ) {
+if ( $conf->exists('enable_taxclasses') && ! $cgi->param('istax') ) {
push @post_desc_header, 'Tax class';
push @post_desc, 'taxclass';
push @post_desc_null, '';
@@ -485,4 +490,57 @@ my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
my $conf = new FS::Conf;
my $money_char = $conf->config('money_char') || '$';
+my $tax_pkg_address = $conf->exists('tax-pkg_address');
+my $tax_ship_address = $conf->exists('tax-ship_address');
+
+my $location_sub = sub {
+ #my $cust_credit_bill_pkg = shift;
+ my $self = shift;
+ my $tax_Xlocation = $self->cust_bill_pkg_tax_Xlocation;
+ if ( defined($tax_Xlocation) && $tax_Xlocation ) {
+
+ if ( ref($tax_Xlocation) eq 'FS::cust_bill_pkg_tax_location' ) {
+
+ if ( $tax_Xlocation->taxtype eq 'FS::cust_main_county' ) {
+ my $cust_main_county = $tax_Xlocation->cust_main_county;
+ if ( $cust_main_county ) {
+ $cust_main_county->label;
+ } else {
+ ''; #cust_main_county record is gone... history? yuck.
+ }
+ } else {
+ '(CCH tax_rate)'; #XXX FS::tax_rate.. vendor taxes not yet handled here
+ }
+
+ } elsif ( ref($tax_Xlocation) eq 'FS::cust_bill_pkg_tax_rate_location' ) {
+ '(CCH)'; #XXX vendor taxes not yet handled here
+ } else {
+ 'unknown tax_Xlocation '. ref($tax_Xlocation);
+ }
+
+ } else {
+
+ my $cust_bill_pkg = $self->cust_bill_pkg;
+ if ( $cust_bill_pkg->pkgnum > 0 ) {
+ my $cust_pkg = $cust_bill_pkg->cust_pkg;
+ if ( $tax_pkg_address && (my $cust_location = $cust_pkg->cust_location) ){
+ $cust_location->county_state_country;
+ } else {
+ my $cust_main = $cust_pkg->cust_main;
+ if ( $tax_ship_address && $cust_main->has_ship_address ) {
+ $cust_main->county_state_country('ship_');
+ } else {
+ $cust_main->county_state_country;
+ }
+ }
+
+ } else {
+ #tax? we shouldn't have wound up here then...
+ ''; #return customer ship or bill address? (depending on tax-ship_address)
+ }
+
+ }
+
+};
+
</%init>
diff --git a/httemplate/search/cust_credit_refund.html b/httemplate/search/cust_credit_refund.html
index 75138e99d..817420054 100644
--- a/httemplate/search/cust_credit_refund.html
+++ b/httemplate/search/cust_credit_refund.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'name' => 'net refunds',
'query' => $sql_query,
@@ -57,8 +57,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -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;
}
@@ -103,8 +106,8 @@ my $where = 'WHERE '. join(' AND ', @search);
#
my $count_query = 'SELECT COUNT(*), SUM(cust_credit_refund.amount)
FROM cust_credit_refund
- LEFT JOIN cust_credit USING ( crednum )
- LEFT JOIN cust_main USING ( custnum ) '.
+ LEFT JOIN cust_credit USING ( crednum ) '.
+ FS::UI::Web::join_cust_main('cust_credit') .
$where;
my $sql_query = {
@@ -121,8 +124,8 @@ my $sql_query = {
'hashref' => {},
'extra_sql' => $where,
'addl_from' => 'LEFT JOIN cust_credit USING ( crednum )
- LEFT JOIN cust_refund USING ( refundnum )
- LEFT JOIN cust_main ON ( cust_credit.custnum = cust_main.custnum )',
+ LEFT JOIN cust_refund USING ( refundnum )'.
+ FS::UI::Web::join_cust_main('cust_credit')
};
#my $cust_credit_link = sub {
diff --git a/httemplate/search/cust_event.html b/httemplate/search/cust_event.html
index deb34b9e5..bfc5f43e8 100644
--- a/httemplate/search/cust_event.html
+++ b/httemplate/search/cust_event.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'html_init' => $html_init,
'menubar' => $menubar,
@@ -62,8 +62,7 @@
#'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+&>
<%once>
my $status_sub = sub {
@@ -175,7 +174,13 @@ $search{'ending'} = $ending;
my $where = ' WHERE '. FS::cust_event->search_sql_where( \%search );
-my $join = FS::cust_event->join_sql();
+my $join = FS::cust_event->join_sql() .
+ 'LEFT JOIN cust_location bill_location '.
+ 'ON (cust_main.bill_locationnum = bill_location.locationnum) '.
+ 'LEFT JOIN cust_location ship_location '.
+ 'ON (cust_main.ship_locationnum = ship_location.locationnum)';
+ # warning: does not show the true service address for package events.
+ # the query to do that would be painfully slow.
my $sql_query = {
'table' => 'cust_event',
diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html
index 08800d431..f5f8c8f3c 100644
--- a/httemplate/search/cust_main-zip.html
+++ b/httemplate/search/cust_main-zip.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Zip code Search Results',
'name' => 'zip codes',
'query' => $sql_query,
@@ -6,8 +6,7 @@
'header' => [ 'Zip code', 'Customers', ],
'fields' => [ 0, 1 ],
'links' => [ '', $link ],
- )
-%>
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi
index 8e3c8133e..2c09c692c 100755
--- a/httemplate/search/cust_main.cgi
+++ b/httemplate/search/cust_main.cgi
@@ -244,7 +244,7 @@
% my $pkg_rowspan = shift @pkg_rowspans;
<% $n1 %><TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN="<% $pkg_rowspan%>">
- <A HREF="<% $pkgview %>"><FONT SIZE=-1><% $pkg_comment %></FONT></A>
+ <A HREF="<% $pkgview %>"><FONT SIZE=-1><% $pkg_comment |h %></FONT></A>
</TD>
% my $n2 = '';
diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html
index 8b39ea962..24348ff8a 100755
--- a/httemplate/search/cust_main.html
+++ b/httemplate/search/cust_main.html
@@ -42,10 +42,11 @@ my %search_hash = ();
#scalars
my @scalars = qw (
agentnum status address zip paydate_year paydate_month invoice_terms
- no_censustract with_geocode with_email no_POST
+ no_censustract with_geocode with_email POST no_POST
custbatch usernum
cancelled_pkgs
cust_fields flattened_pkgs
+ all_tags
);
for my $param ( @scalars ) {
diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi
index 800df8702..9f9eb30ce 100755
--- a/httemplate/search/cust_pay_batch.cgi
+++ b/httemplate/search/cust_pay_batch.cgi
@@ -1,4 +1,4 @@
-<% include('elements/search.html',
+<& elements/search.html,
'title' => 'Batch payment details',
'name' => 'batch details',
'query' => $sql_query,
@@ -7,55 +7,41 @@
'disable_download' => 1,
'header' => [ '#',
'Inv #',
- 'Customer',
+ 'Cust #',
'Customer',
'Card Name',
'Card',
'Exp',
'Amount',
'Status',
+ '', # error_message
],
- 'fields' => [ sub {
- shift->[0];
- },
- sub {
- shift->[1];
- },
- sub {
- shift->[2];
- },
- sub {
- my $cpb = shift;
- $cpb->[3] . ', ' . $cpb->[4];
- },
- sub {
- shift->[5];
- },
- sub {
- my $cardnum = shift->[6];
- 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4));
- },
- sub {
- shift->[7] =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
- my( $mon, $year ) = ( $2, $1 );
- $mon = "0$mon" if length($mon) == 1;
- "$mon/$year";
- },
- sub {
- shift->[8];
- },
- sub {
- shift->[9];
- },
- ],
- 'align' => 'lllllllrl',
- 'links' => [ ['', sub{'#';}],
- ["${p}view/cust_bill.cgi?", sub{shift->[1];},],
- ["${p}view/cust_main.cgi?", sub{shift->[2];},],
- ["${p}view/cust_main.cgi?", sub{shift->[2];},],
+ 'fields' => [ 'paybatchnum',
+ 'invnum',
+ 'custnum',
+ sub { $_[0]->cust_main->name_short },
+ 'payname',
+ 'mask_payinfo',
+ sub {
+ return('') if $_[0]->payby ne 'CARD';
+ $_[0]->get('exp') =~ /^\d\d(\d\d)-(\d\d)/;
+ sprintf('%02d/%02d',$1,$2);
+ },
+ sub {
+ sprintf('%.02f', $_[0]->amount)
+ },
+ 'status',
+ 'error_message',
+ ],
+ 'align' => 'rrrlllcrll',
+ 'links' => [ '',
+ ["${p}view/cust_bill.cgi?", 'invnum'],
+ (["${p}view/cust_main.cgi?", 'custnum']) x 2,
],
- )
-%>
+ 'link_onclicks' => [ ('') x 8,
+ $sub_receipt
+ ],
+&>
<%init>
my $conf = new FS::Conf;
@@ -101,7 +87,7 @@ if ( $cgi->param('payby') ) {
}
if ( not $cgi->param('dcln') ) {
- push @search, "cpb.status IS DISTINCT FROM 'Approved'";
+ push @search, "cust_pay_batch.status IS DISTINCT FROM 'Approved'";
}
my ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
@@ -119,18 +105,30 @@ push @search, $curuser->agentnums_sql({ table => 'pay_batch',
my $search = ' WHERE ' . join(' AND ', @search);
-$count_query = 'SELECT COUNT(*) FROM cust_pay_batch AS cpb ' .
+$count_query = 'SELECT COUNT(*) FROM cust_pay_batch ' .
'LEFT JOIN cust_main USING ( custnum ) ' .
'LEFT JOIN pay_batch USING ( batchnum )' .
$search;
-#grr
-$sql_query = "SELECT paybatchnum,invnum,custnum,cpb.last,cpb.first," .
- "cpb.payname,cpb.payinfo,cpb.exp,amount,cpb.status " .
- "FROM cust_pay_batch AS cpb " .
- 'LEFT JOIN cust_main USING ( custnum ) ' .
- 'LEFT JOIN pay_batch USING ( batchnum ) ' .
- "$search ORDER BY $orderby";
+$sql_query = {
+ 'table' => 'cust_pay_batch',
+ 'select' => 'cust_pay_batch.*, cust_main.*, cust_pay.paynum',
+ 'hashref' => {},
+ 'addl_from' => 'LEFT JOIN pay_batch USING ( batchnum ) '.
+ 'LEFT JOIN cust_main USING ( custnum ) '.
+
+ 'LEFT JOIN cust_pay USING ( batchnum, custnum ) ',
+ 'extra_sql' => $search,
+ 'order_by' => "ORDER BY $orderby",
+};
+
+my $sub_receipt = sub {
+ my $paynum = shift->paynum or return '';
+ include('/elements/popup_link_onclick.html',
+ 'action' => $p.'view/cust_pay.html?link=popup;paynum='.$paynum,
+ 'actionlabel' => emt('Payment Receipt'),
+ );
+};
my $html_init = '';
if ( $pay_batch ) {
diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi
index 887ec6039..110da91ae 100755
--- a/httemplate/search/cust_pkg.cgi
+++ b/httemplate/search/cust_pkg.cgi
@@ -9,6 +9,7 @@
emt('Package'),
emt('Class'),
emt('Status'),
+ emt('Ordered by'),
emt('Setup'),
emt('Base Recur'),
emt('Freq.'),
@@ -34,6 +35,7 @@
sub { $_[0]->pkg; },
'classname',
sub { ucfirst(shift->status); },
+ 'otaker',
sub { sprintf( $money_char.'%.2f',
shift->part_pkg->option('setup_fee'),
);
@@ -96,13 +98,14 @@
'',
'',
'',
+ '',
FS::UI::Web::cust_colors(),
'',
],
- 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+ 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
FS::UI::Web::cust_styles() ],
'size' => [ '', '', '', '', '-1' ],
- 'align' => 'rrlccrrlrrrrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r',
+ 'align' => 'rrlcccrrlrrrrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r',
'links' => [
$link,
$link,
@@ -119,6 +122,7 @@
'',
'',
'',
+ '',
'', # link to changed-from package?
'',
'',
diff --git a/httemplate/search/cust_pkg_discount.html b/httemplate/search/cust_pkg_discount.html
index d70c3116f..23af1802e 100644
--- a/httemplate/search/cust_pkg_discount.html
+++ b/httemplate/search/cust_pkg_discount.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Package discounts',
'name' => 'discounts',
'query' => $query,
@@ -50,8 +50,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -92,8 +92,8 @@ my $count_query = "SELECT COUNT(*), SUM(amount)";
my $join = ' LEFT JOIN discount USING ( discountnum )
LEFT JOIN cust_pkg USING ( pkgnum )
- LEFT JOIN part_pkg USING ( pkgpart )
- LEFT JOIN cust_main USING ( custnum ) ';
+ LEFT JOIN part_pkg USING ( pkgpart ) '.
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
my $where = ' WHERE '. join(' AND ', @where);
diff --git a/httemplate/search/cust_pkg_svc.html b/httemplate/search/cust_pkg_svc.html
index 9c5b32fc7..cdc70351a 100644
--- a/httemplate/search/cust_pkg_svc.html
+++ b/httemplate/search/cust_pkg_svc.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $part_svc->svc.' services in package #'.$pkgnum,
'name' => 'services',
'html_form' => $html_form,
@@ -30,8 +30,8 @@
('')x4,
],
'html_foot' => sub { $areboxes ? $html_foot : '' }
- )
-%>
+
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html
index 2adcbd76f..e2a83b7de 100644
--- a/httemplate/search/cust_svc.html
+++ b/httemplate/search/cust_svc.html
@@ -62,7 +62,7 @@ if ( length( $cgi->param('search_svc') ) ) {
my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
my @extra_sql = ();
diff --git a/httemplate/search/cust_tax_adjustment.html b/httemplate/search/cust_tax_adjustment.html
index 925476516..6125a1c04 100644
--- a/httemplate/search/cust_tax_adjustment.html
+++ b/httemplate/search/cust_tax_adjustment.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'name_singular' => 'tax adjustment',
'query' => $query,
@@ -12,9 +12,8 @@
},
],
'links' => [ '', '', '', $ilink ],
- )
-%>
-
+
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/cust_tax_exempt.cgi b/httemplate/search/cust_tax_exempt.cgi
index 3704b208a..005d77c33 100644
--- a/httemplate/search/cust_tax_exempt.cgi
+++ b/httemplate/search/cust_tax_exempt.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Legacy tax exemptions',
'name' => 'legacy tax exemptions',
'query' => $query,
@@ -46,13 +46,11 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
-my $join_cust = "
- LEFT JOIN cust_main USING ( custnum )
-";
+my $join_cust = FS::UI::Web::join_cust_main('cust_tax_exempt');
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions');
diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi
index 1b767f846..40b9ed78f 100644
--- a/httemplate/search/cust_tax_exempt_pkg.cgi
+++ b/httemplate/search/cust_tax_exempt_pkg.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Tax exemptions',
'name' => 'tax exemptions',
'query' => $query,
@@ -77,14 +77,12 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+&>
<%once>
my $join_cust = "
- JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
-";
+ JOIN cust_bill USING ( invnum )" .
+ FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg');
my $join_pkg = "
LEFT JOIN cust_pkg USING ( pkgnum )
@@ -93,8 +91,8 @@ my $join_pkg = "
my $join = "
JOIN cust_bill_pkg USING ( billpkgnum )
- $join_cust
$join_pkg
+ $join_cust
";
</%once>
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/checkbox-foot.html b/httemplate/search/elements/checkbox-foot.html
new file mode 100644
index 000000000..be1caab91
--- /dev/null
+++ b/httemplate/search/elements/checkbox-foot.html
@@ -0,0 +1,86 @@
+<%doc>
+<& /elements/search.html,
+ # options...
+ html_foot => include('elements/checkbox-foot.html',
+ actions => [
+ { label => 'Edit selected packages',
+ action => 'popup_package_edit()',
+ },
+ { submit => 'Delete selected packages',
+ confirm => 'Really delete these packages?'
+ },
+ ],
+ filter => '.name = "pkgpart"', # see below
+ ),
+&>
+
+This creates a footer for a search page containing a column of checkboxes.
+Typically this is used to select several items from the search result and
+apply some change to all of them at once. The footer always provides
+"select all" and "unselect all" buttons.
+
+"actions" is an arrayref of action buttons to show. Each element of the
+array is a hashref of either:
+
+- "submit" and, optionally, "confirm". Creates a submit button. The value
+of "submit" becomes the "value" property of the button (and thus its label).
+If "confirm" is specified, the button will have an onclick handler that
+displays the value of "confirm" in a popup message box and asks the user to
+confirm the choice.
+
+- "onclick" and "label". Creates a non-submit button that executes the
+Javascript code in "onclick". "label" is used as the text of the button.
+
+If you want only a single action, you can forget the arrayref-of-hashrefs
+business and just put "submit" and "confirm" (or "onclick" and "label")
+elements in the argument list.
+
+"filter" is a javascript expression to limit which checkboxes are included in
+the "select/unselect all" actions. By default, any input with type="checkbox"
+will be included. If this option is given, it will be evaluated with the
+HTML node in a variable named "obj". The expression should return true or
+false.
+
+</%doc>
+<DIV ID="checkbox_footer" STYLE="display:block">
+<INPUT TYPE="button" VALUE="<% emt('select all') %>" onclick="setAll(true)">
+<INPUT TYPE="button" VALUE="<% emt('unselect all') %>" onclick="setAll(false)">
+<BR>
+% foreach my $action (@$actions) {
+% if ( $action->{onclick} ) {
+<INPUT TYPE="button" <% $action->{name} %> onclick="<% $opt{onclick} %>"\
+ VALUE="<% $action->{label} |h%>">
+% } elsif ( $action->{submit} ) {
+<INPUT TYPE="submit" <% $action->{name} %> <% $action->{confirm} %>\
+ VALUE="<% $action->{submit} |h%>">
+% } # else do nothing
+% } #foreach
+</DIV>
+<SCRIPT>
+var checkboxes = [];
+var inputs = document.getElementsByTagName('input');
+for (var i = 0; i < inputs.length; i++) {
+ var obj = inputs[i];
+ if ( obj.type == "checkbox" && <% $filter %> ) {
+ checkboxes.push(obj);
+ }
+}
+%# avoid the need for "$areboxes" late-evaluation hackery
+if ( checkboxes.length == 0 ) {
+ document.getElementById('checkbox_footer').style.display = 'none';
+}
+function setAll(setTo) {
+ for (var i = 0; i < checkboxes.length; i++) {
+ checkboxes[i].checked = setTo;
+ }
+}
+</SCRIPT>
+<%init>
+my %opt = @_;
+my $actions = $opt{'actions'} || [ \%opt ];
+foreach (@$actions) {
+ $_->{confirm} &&= qq!onclick="return confirm('! . $_->{confirm} . qq!')"!;
+ $_->{name} &&= qq!NAME="! . $_->{name} . qq!"!;
+}
+my $filter = $opt{filter} || 'true';
+</%init>
diff --git a/httemplate/search/elements/cust_main_dayranges.html b/httemplate/search/elements/cust_main_dayranges.html
index eb7566494..cf2d495b1 100644
--- a/httemplate/search/elements/cust_main_dayranges.html
+++ b/httemplate/search/elements/cust_main_dayranges.html
@@ -162,6 +162,15 @@ if ( grep { $cgi->param('status') eq $_ } FS::cust_main->statuses() ) {
push @where, FS::cust_main->$method();
}
+# cust_classnum (false laziness w/prepaid_income.html, elements/cust_pay_or_refund.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
+if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
+ push @where, 'COALESCE( cust_main.classnum, 0) IN ( '.
+ join(',', map { $_ || '0' } @classnums ).
+ ' )'
+ if @classnums;
+}
+
#here is the agent virtualization
push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
@@ -172,10 +181,11 @@ my $count_sql = "select count(*) from cust_main $where";
my $sql_query = {
'table' => 'cust_main',
+ 'addl_from' => FS::UI::Web::join_cust_main('cust_main'),
'hashref' => {},
'select' => join(',',
#'cust_main.*',
- 'custnum',
+ 'cust_main.custnum',
$range_cols,
$packages_cols,
FS::UI::Web::cust_sql_fields(),
diff --git a/httemplate/search/elements/cust_pay_batch_top.html b/httemplate/search/elements/cust_pay_batch_top.html
index 1dcc37ac1..bf3047769 100644
--- a/httemplate/search/elements/cust_pay_batch_top.html
+++ b/httemplate/search/elements/cust_pay_batch_top.html
@@ -120,6 +120,7 @@ my $fixed = $conf->config("batch-fixed_format-$payby");
tie my %download_formats, 'Tie::IxHash', (
'' => 'Default batch mode',
+'NACHA' => '94 byte NACHA',
'csv-td_canada_trust-merchant_pc_batch' =>
'CSV file for TD Canada Trust Merchant PC Batch',
'csv-chase_canada-E-xactBatch' =>
diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html
index eeef0c0e1..7b2a17058 100755
--- a/httemplate/search/elements/cust_pay_or_refund.html
+++ b/httemplate/search/elements/cust_pay_or_refund.html
@@ -51,6 +51,7 @@ Examples:
'sort_fields' => \@sort_fields,
'align' => $align,
'links' => \@links,
+ 'link_onclicks' => \@link_onclicks,
'color' => \@color,
'style' => \@style,
&>
@@ -134,11 +135,12 @@ if ( $cgi->param('tax_names') ) {
}
}
-my @header = ();
-my @fields = ();
-my @sort_fields = ();
+my @header;
+my @fields;
+my @sort_fields;
my $align = '';
-my @links = ();
+my @links;
+my @link_onclicks;
if ( $opt{'pre_header'} ) {
push @header, @{ $opt{'pre_header'} };
$align .= 'c' x scalar(@{ $opt{'pre_header'} });
@@ -147,6 +149,16 @@ if ( $opt{'pre_header'} ) {
push @sort_fields, @{ $opt{'pre_fields'} };
}
+my $sub_receipt = sub {
+ my $obj = shift;
+ my $objnum = $obj->primary_key . '=' . $obj->get($obj->primary_key);
+
+ include('/elements/popup_link_onclick.html',
+ 'action' => $p.'view/cust_pay.html?link=popup;'.$objnum,
+ 'actionlabel' => emt('Payment Receipt'),
+ );
+};
+
push @header, "\u$name_singular",
'Amount',
;
@@ -155,6 +167,7 @@ push @links, '', '';
push @fields, 'payby_payinfo_pretty',
sub { sprintf('$%.2f', shift->$amount_field() ) },
;
+push @link_onclicks, $sub_receipt, '',
push @sort_fields, '', $amount_field;
if ( $unapplied ) {
@@ -239,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;
}
@@ -250,78 +266,121 @@ if ( $cgi->param('magic') ) {
}
if ( $cgi->param('payby') ) {
- $cgi->param('payby') =~
- /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/
- or die "illegal payby ". $cgi->param('payby');
- push @search, "$table.payby = '$1'";
- if ( $3 ) {
-
- my $cardtype = $3;
-
- my $search;
- if ( $cardtype eq 'VisaMC' ) {
- #avoid posix regexes for portability
- $search =
- " ( ( substring($table.payinfo from 1 for 1) = '4' ".
- " AND substring($table.payinfo from 1 for 4) != '4936' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT SIMILAR TO '49030[2-9]' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT SIMILAR TO '49033[5-9]' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT SIMILAR TO '49110[1-2]' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT SIMILAR TO '49117[4-9]' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT SIMILAR TO '49118[1-2]' ".
- " )".
- " OR substring($table.payinfo from 1 for 2) = '51' ".
- " OR substring($table.payinfo from 1 for 2) = '52' ".
- " OR substring($table.payinfo from 1 for 2) = '53' ".
- " OR substring($table.payinfo from 1 for 2) = '54' ".
- " OR substring($table.payinfo from 1 for 2) = '54' ".
- " OR substring($table.payinfo from 1 for 2) = '55' ".
- " OR substring($table.payinfo from 1 for 2) = '36' ". #Diner's int'l processed as Visa/MC inside US
- " ) ";
- } elsif ( $cardtype eq 'Amex' ) {
- $search =
- " ( substring($table.payinfo from 1 for 2 ) = '34' ".
- " OR substring($table.payinfo from 1 for 2 ) = '37' ".
- " ) ";
- } elsif ( $cardtype eq 'Discover' ) {
- $search =
- " ( substring($table.payinfo from 1 for 4 ) = '6011' ".
- " OR substring($table.payinfo from 1 for 2 ) = '65' ".
- " OR substring($table.payinfo from 1 for 3 ) = '622' ". #China Union Pay processed as Discover outside CN
- " ) ";
- } elsif ( $cardtype eq 'Maestro' ) {
- $search =
- " ( substring($table.payinfo from 1 for 2 ) = '63' ".
- " OR substring($table.payinfo from 1 for 2 ) = '67' ".
- " OR substring($table.payinfo from 1 for 6 ) = '564182' ".
- " OR substring($table.payinfo from 1 for 4 ) = '4936' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " SIMILAR TO '49030[2-9]' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " SIMILAR TO '49033[5-9]' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " SIMILAR TO '49110[1-2]' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " SIMILAR TO '49117[4-9]' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " SIMILAR TO '49118[1-2]' ".
- " ) ";
- } else {
- die "unknown card type $cardtype";
- }
- my $masksearch = $search;
- $masksearch =~ s/$table\.payinfo/$table.paymask/gi;
+ my @all_payby_search = ();
+ foreach my $payby ( $cgi->param('payby') ) {
+
+ $payby =~
+ /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/
+ or die "illegal payby $payby";
+
+ my $payby_search = "$table.payby = '$1'";
+
+ if ( $3 ) {
+
+ my $cardtype = $3;
+
+ my $search;
+ if ( $cardtype eq 'VisaMC' ) {
+ #avoid posix regexes for portability
+ $search =
+ " ( ( substring($table.payinfo from 1 for 1) = '4' ".
+ " AND substring($table.payinfo from 1 for 4) != '4936' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49030[2-9]' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49033[5-9]' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49110[1-2]' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49117[4-9]' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49118[1-2]' ".
+ " )".
+ " OR substring($table.payinfo from 1 for 2) = '51' ".
+ " OR substring($table.payinfo from 1 for 2) = '52' ".
+ " OR substring($table.payinfo from 1 for 2) = '53' ".
+ " OR substring($table.payinfo from 1 for 2) = '54' ".
+ " OR substring($table.payinfo from 1 for 2) = '54' ".
+ " OR substring($table.payinfo from 1 for 2) = '55' ".
+# " OR substring($table.payinfo from 1 for 2) = '36' ". #Diner's int'l was processed as Visa/MC inside US, now Discover
+ " ) ";
+ } elsif ( $cardtype eq 'Amex' ) {
+ $search =
+ " ( substring($table.payinfo from 1 for 2 ) = '34' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '37' ".
+ " ) ";
+ } elsif ( $cardtype eq 'Discover' ) {
+
+ my $conf = new FS::Conf;
+ my $country = $conf->config('countrydefault') || 'US';
+
+ $search =
+ " ( substring($table.payinfo from 1 for 4 ) = '6011' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '65' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '300' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '301' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '302' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '303' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '304' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '305' ".
+ " OR substring($table.payinfo from 1 for 4 ) = '3095' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '36' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '38' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '39' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '644' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '645' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '646' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '647' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '648' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '649' ".
+ ( $country =~ /^(US|CA)$/
+ ?" OR substring($table.payinfo from 1 for 4 ) = '3528' ". # JCB cards in the 3528-3589 range identified as Discover inside US/CA
+ " OR substring($table.payinfo from 1 for 4 ) = '3529' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '353' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '354' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '355' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '356' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '357' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '358' "
+ :""
+ ).
+ " OR substring($table.payinfo from 1 for 3 ) = '622' ". #China Union Pay processed as Discover outside CN
+ " ) ";
+ } elsif ( $cardtype eq 'Maestro' ) {
+ $search =
+ " ( substring($table.payinfo from 1 for 2 ) = '63' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '67' ".
+ " OR substring($table.payinfo from 1 for 6 ) = '564182' ".
+ " OR substring($table.payinfo from 1 for 4 ) = '4936' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49030[2-9]' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49033[5-9]' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49110[1-2]' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49117[4-9]' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49118[1-2]' ".
+ " ) ";
+ } else {
+ die "unknown card type $cardtype";
+ }
+
+ my $masksearch = $search;
+ $masksearch =~ s/$table\.payinfo/$table.paymask/gi;
+
+ $payby_search = "( $payby_search AND ( $search OR ( $table.paymask IS NOT NULL AND $masksearch ) ) )";
- push @search,
- "( $search OR ( $table.paymask IS NOT NULL AND $masksearch ) )";
+ }
+
+ push @all_payby_search, $payby_search;
}
+
+ push @search, ' ( '. join(' OR ', @all_payby_search). ' ) ' if @all_payby_search;
+
}
if ( $cgi->param('payinfo') ) {
@@ -350,6 +409,7 @@ if ( $cgi->param('magic') ) {
}
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+
push @search, "_date >= $beginning ",
"_date <= $ending";
@@ -411,7 +471,7 @@ if ( $cgi->param('magic') ) {
#here is the agent virtualization
push @search, $curuser->agentnums_sql;
- my $addl_from = ' LEFT JOIN cust_main USING ( custnum ) ';
+ my $addl_from = FS::UI::Web::join_cust_main($table);
my $group_by = '';
if ( $cgi->param('tax_names') ) {
diff --git a/httemplate/search/elements/report_cust_pay_or_refund.html b/httemplate/search/elements/report_cust_pay_or_refund.html
index 0e04ab0dd..0462f1cd9 100644
--- a/httemplate/search/elements/report_cust_pay_or_refund.html
+++ b/httemplate/search/elements/report_cust_pay_or_refund.html
@@ -30,68 +30,33 @@ Examples:
<TR>
<TD ALIGN="right"><% ucfirst(PL($name_singular)) %> of type: </TD>
<TD>
- <SELECT NAME="payby" onChange="payby_changed(this)">
- <OPTION VALUE=""><% mt('all') |h %></OPTION>
- <OPTION VALUE="CARD"><% mt('credit card (all)') |h %></OPTION>
- <OPTION VALUE="CARD-VisaMC"><% mt('credit card (Visa/MasterCard)') |h %></OPTION>
- <OPTION VALUE="CARD-Amex"><% mt('credit card (American Express)') |h %></OPTION>
- <OPTION VALUE="CARD-Discover"><% mt('credit card (Discover)') |h %></OPTION>
- <OPTION VALUE="CARD-Maestro"><% mt('credit card (Maestro/Switch/Solo)') |h %></OPTION>
- <OPTION VALUE="CHEK"><% mt('electronic check / ACH') |h %></OPTION>
- <OPTION VALUE="BILL"><% mt('check') |h %></OPTION>
- <OPTION VALUE="PREP"><% mt('prepaid card') |h %></OPTION>
- <OPTION VALUE="CASH"><% mt('cash') |h %></OPTION>
- <OPTION VALUE="WEST"><% mt('Western Union') |h %></OPTION>
- <OPTION VALUE="MCRD"><% mt('manual credit card') |h %></OPTION>
+ <SELECT NAME="payby" SIZE=10 MULTIPLE>
+%# <OPTION VALUE=""><% mt('all') |h %></OPTION>
+%# <OPTION VALUE="CARD"><% mt('credit card (all)') |h %></OPTION>
+ <OPTION VALUE="CARD-VisaMC" SELECTED><% mt('credit card (Visa/MasterCard)') |h %></OPTION>
+ <OPTION VALUE="CARD-Amex" SELECTED><% mt('credit card (American Express)') |h %></OPTION>
+ <OPTION VALUE="CARD-Discover" SELECTED><% mt('credit card (Discover)') |h %></OPTION>
+ <OPTION VALUE="CARD-Maestro" SELECTED><% mt('credit card (Maestro/Switch/Solo)') |h %></OPTION>
+ <OPTION VALUE="CHEK" SELECTED><% mt('electronic check / ACH') |h %></OPTION>
+ <OPTION VALUE="BILL" SELECTED><% mt('check') |h %></OPTION>
+ <OPTION VALUE="PREP" SELECTED><% mt('prepaid card') |h %></OPTION>
+ <OPTION VALUE="CASH" SELECTED><% mt('cash') |h %></OPTION>
+ <OPTION VALUE="WEST" SELECTED><% mt('Western Union') |h %></OPTION>
+ <OPTION VALUE="MCRD" SELECTED><% mt('manual credit card') |h %></OPTION>
</SELECT>
</TD>
</TR>
- <SCRIPT TYPE="text/javascript">
-
- function payby_changed(what) {
- if ( what.value == 'BILL' ) {
- show('payinfo');
- hide('ccpay');
- } else if ( what.value.match(/^CARD|CHEK/) ) {
- hide('payinfo');
- show('ccpay');
- } else {
- hide('payinfo');
- hide('ccpay');
- }
- }
-
- function show(what) {
- document.getElementById(what+'_caption').style.color = '#000000';
- document.getElementById(what).disabled = false;
- document.getElementById(what).style.backgroundColor = '#ffffff';
- }
-
- function hide(what) {
- document.getElementById(what+'_caption').style.color = '#bbbbbb';
- document.getElementById(what).disabled = true;
- document.getElementById(what).style.backgroundColor = '#dddddd';
- }
-
-
-
- </SCRIPT>
-
<TR>
- <TD ALIGN="right"><FONT ID="payinfo_caption" COLOR="#bbbbbb"><% mt('Check #:') |h %> </FONT></TD>
+ <TD ALIGN="right"><% mt('Check #:') |h %> </TD>
<TD>
- <INPUT TYPE="text" ID="payinfo" NAME="payinfo" DISABLED STYLE="background-color: #dddddd">
+ <INPUT TYPE="text" ID="payinfo" NAME="payinfo">
</TD>
</TR>
<TR>
- <TD ALIGN="right">
- <FONT ID="ccpay_caption" COLOR="#bbbbbb">
- <% mt('Transaction #') |h %>
- </FONT>
- </TD>
+ <TD ALIGN="right"><% mt('Transaction #:') |h %> </TD>
<TD>
- <INPUT TYPE="text" ID="ccpay" NAME="ccpay" DISABLED STYLE="background-color: #dddddd">
+ <INPUT TYPE="text" ID="ccpay" NAME="ccpay">
</TD>
</TR>
@@ -108,7 +73,8 @@ Examples:
<TD>
<TABLE>
<& /elements/tr-input-beginning_ending.html,
- layout => 'horiz',
+ layout => 'horiz',
+ input_time => $conf->exists('report-cust_pay-select_time'),
&>
</TABLE>
</TD>
@@ -158,6 +124,8 @@ my $name_singular = $opt{'name_singular'};
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+my $conf = new FS::Conf;
+
my $void = $cgi->param('void') ? 1 : 0;
my $unapplied = $cgi->param('unapplied') ? 1 : 0;
diff --git a/httemplate/search/elements/report_svc_Common.html b/httemplate/search/elements/report_svc_Common.html
new file mode 100644
index 000000000..434197078
--- /dev/null
+++ b/httemplate/search/elements/report_svc_Common.html
@@ -0,0 +1,122 @@
+<%doc>
+
+Example:
+
+ <& elements/report_svc_Common.html,
+
+ #required
+ 'table' => 'svc_something',
+ 'title' => 'Page title',
+
+ #optional
+ 'action' => 'svc_tablename.html', #defaults to svc_tablename.html
+
+ &>
+
+</%doc>
+<& /elements/header.html, $title &>
+
+<FORM ACTION="<% $opt{'action'} || $opt{'table'}. '.html' %>" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="advanced">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1"><% mt('Search options') |h %></FONT></TH>
+ </TR>
+
+% unless ( $custnum ) {
+
+ <& /elements/tr-select-agent.html,
+ curr_value => scalar( $cgi->param('agentnum') ),
+ disable_empty => 0,
+ &>
+
+ <& /elements/tr-select-cust_main-status.html,
+ label => 'Customer Status',
+ field => 'cust_status',
+ &>
+
+ <& /elements/tr-select-payby.html,
+ label => emt('Payment method:'),
+ payby_type => 'cust',
+ multiple => 1,
+ all_selected => 1,
+ &>
+
+ <& /elements/tr-input-money.html,
+ label => 'Balance over',
+ field => 'balance',
+ &>
+
+ <& /elements/tr-input-text.html,
+ label => 'Balance age (days)',
+ field => 'balance_days',
+ size => 4,
+ &>
+
+% }
+
+% # just this customer's domains?
+%# <& /elements/tr-select-domain.html,
+%# 'element_name' => 'domsvc',
+%# 'curr_value' => scalar( $cgi->param('domsvc') ),
+%# 'disable_empty' => 0,
+%# &>
+
+ <& /elements/tr-selectmultiple-part_pkg.html &>
+
+ <& /elements/tr-select-part_svc.html,
+ 'svcdb' => $svcdb,
+ 'label' => 'Services',
+ &>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2>&nbsp;</TH>
+ </TR>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1"><% mt('Display options') |h %></FONT></TH>
+ </TR>
+
+% #"package fields" ala advanced svc_acct search?
+% #move to /elements/tr-select-cust_pkg-fields and use it from there if so...
+
+ <& /elements/tr-select-cust-fields.html &>
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+my(%opt) = @_;
+
+my $svcdb = $opt{'table'};
+
+my $name = "FS::$svcdb"->table_info->{'name_plural'}
+ || PL( "FS::$svcdb"->table_info->{'name'} );
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right("Services: $name: Advanced search");
+
+my $title = $opt{'title'};
+
+#false laziness w/report_cust_pkg.html
+my( $custnum, $cust_main) = ('', '');
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ }) or die "unknown custnum $custnum";
+ $title = mt("$title: [_1]", $cust_main->name);
+}
+
+</%init>
diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html
index 7ccf356ea..e760bc546 100644
--- a/httemplate/search/elements/search-html.html
+++ b/httemplate/search/elements/search-html.html
@@ -253,7 +253,17 @@
% $bgcolor = $bgcolor1;
% }
- <TR>
+% my $trid = '';
+% if ( $opt{'link_field' } ) {
+% my $link_field = $opt{'link_field'};
+% if ( ref($link_field) eq 'CODE' ) {
+% $trid = &{$link_field}($row);
+% } else {
+% $trid = $row->$link_field();
+% }
+% }
+ <TR ID="<%$trid |h%>">
+
% if ( $opt{'fields'} ) {
%
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
index 5a16a22fe..d44b45465 100644
--- a/httemplate/search/elements/search.html
+++ b/httemplate/search/elements/search.html
@@ -167,6 +167,11 @@ Example:
# miscellany
'download_label' => 'Download this report',
# defaults to 'Download full results'
+ 'link_field' => 'pkgpart'
+ # will create internal links for each row,
+ # with the value of this field as the NAME attribute
+ # If this is a coderef, will evaluate it, passing the
+ # row as an argument, and use the result as the NAME.
&>
</%doc>
@@ -348,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/elements/svc_Common.html b/httemplate/search/elements/svc_Common.html
new file mode 100644
index 000000000..56c75bba3
--- /dev/null
+++ b/httemplate/search/elements/svc_Common.html
@@ -0,0 +1,48 @@
+<& search.html, %opt &>
+<%doc>
+Currently does nothing but insert the classnames for fields chosen from an
+inventory class.
+</%doc>
+<%init>
+my %opt = @_;
+my $query = $opt{query};
+my $svcdb = $query->{'table'};
+
+# to avoid looking up the inventory class of every service in the database,
+# keep as much of the base query as possible.
+my $item_query = { %$query };
+$item_query->{'table'} = 'inventory_item';
+$item_query->{'addl_from'} =
+ " JOIN ( $svcdb ". $query->{'addl_from'} .
+ ") ON inventory_item.svcnum = $svcdb.svcnum ".
+ " JOIN inventory_class ON (inventory_item.classnum = inventory_class.classnum)";
+# avoid conflict with inventory_item.agentnum
+$item_query->{'extra_sql'} =~ s/ agentnum/ cust_main.agentnum/g;
+$item_query->{'select'} = 'inventory_item.svcnum, '.
+ 'inventory_item.svc_field, '.
+ 'inventory_class.classname';
+my @items = qsearch($item_query);
+my %item_fields;
+foreach my $i (@items) {
+ $item_fields{ $i->svc_field } ||= {};
+ $item_fields{ $i->svc_field }{ $i->svcnum } = $i->classname;
+}
+
+$opt{'sort_fields'} ||= [];
+for ( my $i = 0; $i < @{ $opt{'fields'} }; $i++ ) {
+ my $f = $opt{'fields'}[$i];
+ next if ref($f); # it's not a plain table column
+ $opt{'sort_fields'}[$i] ||= $f;
+ my $classnames = $item_fields{$f}; # hashref of svcnum -> classname
+ next if !$classnames; # there are no inventory items in this column
+ $opt{'fields'}[$i] = sub {
+ my $svc = $_[0];
+ if ( exists($classnames->{$svc->svcnum}) ) {
+ return $svc->$f . '<BR><I>('. $classnames->{$svc->svcnum} . ')</I>';
+ } else {
+ return $svc->$f;
+ }
+ }; #sub
+}
+
+</%init>
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/inventory_item.html b/httemplate/search/inventory_item.html
index 086c8e92d..0e4251f74 100644
--- a/httemplate/search/inventory_item.html
+++ b/httemplate/search/inventory_item.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'menubar' => [ 'View inventory classes' =>
@@ -87,8 +87,8 @@
<INPUT TYPE="hidden" NAME="classnum" VALUE="$classnum">
<INPUT TYPE="hidden" NAME="avail" VALUE="! .$cgi->param('avail') . '">', #'
'html_foot' => $sub_foot,
- )
-%>
+
+&>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -157,7 +157,7 @@ my $link_cust = sub {
my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
' LEFT JOIN part_svc USING ( svcpart ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
my $areboxes = 0;
my $sub_checkbox = sub {
diff --git a/httemplate/search/mailinglistmember.html b/httemplate/search/mailinglistmember.html
index ee395f416..a678d45ed 100644
--- a/httemplate/search/mailinglistmember.html
+++ b/httemplate/search/mailinglistmember.html
@@ -1,4 +1,4 @@
-<% include('elements/search.html',
+<& elements/search.html,
'title' => $title,
'name_singular' => 'member',
'query' => $query,
@@ -6,8 +6,7 @@
'header' => [ 'Email address' ],
'fields' => [ $email_sub, ], #just this one for now
'html_init' => $html_init,
- )
-%>
+&>
<%init>
#XXX ACL:
diff --git a/httemplate/search/part_pkg.html b/httemplate/search/part_pkg.html
index 57da9d459..a90f13c95 100644
--- a/httemplate/search/part_pkg.html
+++ b/httemplate/search/part_pkg.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => $title,
'name_singular' => $name,
'header' => \@header,
@@ -14,8 +14,8 @@
'links' => \@links,
'align' => $align,
'sort_fields' => [],
- )
-%>
+
+&>
<%init>
#this is about reports about packages definitions (starting w/commission ones)
@@ -23,7 +23,7 @@
my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
- unless $curuser->access_right('Financial reports');
+ unless $curuser->access_right('Employees: Commission Report'); #that's all this does so far
my $conf = new FS::Conf;
my $money_char = $conf->config('money_char') || '$';
diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi
index aeaa012f4..620996abd 100755
--- a/httemplate/search/pay_batch.cgi
+++ b/httemplate/search/pay_batch.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Payment Batches',
'name_singular' => 'batch',
'query' => { 'table' => 'pay_batch',
@@ -101,8 +101,7 @@
],
'html_init' => $html_init,
'html_foot' => include('.upload_incoming'),
- )
-%>
+&>
<%def .upload_incoming>
% if ( FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment' AND disabled IS NULL") > 0 ) {
<& /elements/form-file_upload.html,
@@ -149,16 +148,10 @@ my $count_query = 'SELECT COUNT(*) FROM pay_batch';
my($begin, $end) = ( '', '' );
my @where;
-if ( $cgi->param('beginning')
- && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
- $begin = parse_datetime($1);
- push @where, "download >= $begin";
-}
-if ( $cgi->param('ending')
- && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
- $end = parse_datetime($1) + 86399;
- push @where, "download < $end";
-}
+
+my($beginning,$ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @where, "( (download >= $beginning AND download <= $ending) ".
+ ' OR download IS NULL )';
my @status;
if ( $cgi->param('open') ) {
diff --git a/httemplate/search/phone_avail.html b/httemplate/search/phone_avail.html
index 1335379ae..faf354420 100644
--- a/httemplate/search/phone_avail.html
+++ b/httemplate/search/phone_avail.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Phone Number (DID) Search Results',
'name_singular' => 'phone number',
'query' => {
@@ -81,8 +81,8 @@
FS::UI::Web::cust_styles(),
'',
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -125,9 +125,11 @@ my $search = scalar(@search)
my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
#' LEFT JOIN part_svc USING ( svcpart ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
my $count_query = "SELECT COUNT(*) FROM phone_avail $search"; #$addl_from?
+# All of these relationships are left joined in the many-to-one direction,
+# so including $addl_from won't affect the count. Logic!
my $hashref = {};
$hashref->{'ordernum'} = $1 if $cgi->param('ordernum') =~ /^(\d+)$/;
diff --git a/httemplate/search/phone_inventory_provisioned.html b/httemplate/search/phone_inventory_provisioned.html
index 03d21547d..b3efdbd77 100644
--- a/httemplate/search/phone_inventory_provisioned.html
+++ b/httemplate/search/phone_inventory_provisioned.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'LATA Search Results',
'name_singular' => 'LATA',
'query' => {
@@ -72,8 +72,8 @@
'',
'',
],
- )
-%>
+
+&>
<%init>
die "access denied"
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/prepay_credit.html b/httemplate/search/prepay_credit.html
index 36403511b..7566e657e 100644
--- a/httemplate/search/prepay_credit.html
+++ b/httemplate/search/prepay_credit.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Unused Prepaid Cards'.
($agent ? ' for '. $agent->agent : ''),
'menubar' => [
@@ -47,8 +47,8 @@
$agent ? [ "${p}edit/agent.cgi?", 'agentnum' ] : '';
},
],
- )
-%>
+
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/prospect_main.html b/httemplate/search/prospect_main.html
index 328d1202f..ab37b9089 100644
--- a/httemplate/search/prospect_main.html
+++ b/httemplate/search/prospect_main.html
@@ -1,4 +1,4 @@
-<% include('elements/search.html',
+<& elements/search.html,
'title' => 'Prospect Search Results',
'name_singular' => 'prospect',
'query' => $query,
@@ -23,8 +23,7 @@
'', #link to contact edit???
],
'agent_virt' => 1,
- )
-%>
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/qual.cgi b/httemplate/search/qual.cgi
index 7133ef056..7b718e498 100755
--- a/httemplate/search/qual.cgi
+++ b/httemplate/search/qual.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Qualifications',
'name_singular' => 'qualification',
'query' => { 'table' => 'qual',
@@ -51,8 +51,8 @@
'',
'',
],
- )
-%>
+
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/queue.html b/httemplate/search/queue.html
index 1c124706c..141c535da 100644
--- a/httemplate/search/queue.html
+++ b/httemplate/search/queue.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Job Queue',
'name' => 'jobs',
'html_form' => qq!<FORM NAME="jobForm" ACTION="$p/misc/queue.cgi" METHOD="POST">!,
@@ -120,9 +120,8 @@
'';
}
},
- )
-
-%>
+
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/quotation.html b/httemplate/search/quotation.html
index 259c85c22..fbc35bea1 100755
--- a/httemplate/search/quotation.html
+++ b/httemplate/search/quotation.html
@@ -72,7 +72,7 @@ die "access denied"
unless $curuser->access_right('List quotations');
my $join_prospect_main = 'LEFT JOIN prospect_main USING ( prospectnum )';
-my $join_cust_main = 'LEFT JOIN cust_main ON ( quotation.custnum = cust_main.custnum )';
+my $join_cust_main = FS::UI::Web::join_cust_main('quotation');
#here is the agent virtualization
my $agentnums_sql = ' ( '. $curuser->agentnums_sql( table=>'prospect_main' ).
diff --git a/httemplate/search/reg_code.html b/httemplate/search/reg_code.html
index f7d6d2061..42211e571 100644
--- a/httemplate/search/reg_code.html
+++ b/httemplate/search/reg_code.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Unused Registration Codes for '.
$agent->agent,
'name' => 'registration codes',
@@ -23,8 +23,8 @@
#$plink,
'',
],
- )
-%>
+
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/report_477.html b/httemplate/search/report_477.html
index f593a94d8..b842b1f3f 100755
--- a/httemplate/search/report_477.html
+++ b/httemplate/search/report_477.html
@@ -231,7 +231,9 @@
'table' => 'part_pkg_report_option',
'name_col' => 'name',
'hashref' => { 'disabled' => '' },
- 'element_name' => 'partv_report_option',
+ 'element_name' => 'part5_report_option',
+ 'curr_value' =>
+ FS::Report::FCC_477::restore_fcc477map("part5_report_option"),
)
%>
</TD>
diff --git a/httemplate/search/report_agent_commission.html b/httemplate/search/report_agent_commission.html
new file mode 100644
index 000000000..79f94c52e
--- /dev/null
+++ b/httemplate/search/report_agent_commission.html
@@ -0,0 +1,22 @@
+<% include('/elements/header.html', 'Agent commission report' ) %>
+
+<FORM ACTION="agent_commission.html">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+<% include( '/elements/tr-select-agent.html', disable_empty => 1 ) %>
+
+<% include( '/elements/tr-input-beginning_ending.html', ) %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html
index 51618fb24..b339c80e0 100644
--- a/httemplate/search/report_cust_bill.html
+++ b/httemplate/search/report_cust_bill.html
@@ -4,7 +4,7 @@
<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
-<TABLE BGCOLOR="#cccccc" CELLSPACING=0
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
% unless ( $custnum ) {
<& /elements/tr-select-agent.html,
diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html
index acc49aec6..bac4346cf 100755
--- a/httemplate/search/report_cust_main.html
+++ b/httemplate/search/report_cust_main.html
@@ -96,11 +96,21 @@
</TR>
% }
- <& /elements/tr-select-cust_tag.html,
- 'cgi' => $cgi,
- 'is_report' => 1,
- 'multiple' => 1,
- &>
+ <TR>
+ <TD ALIGN="right">Tags</TD>
+ <TD>
+ <& /elements/select-cust_tag.html,
+ 'cgi' => $cgi,
+ 'is_report' => 1,
+ 'multiple' => 1,
+ &>
+ <DIV STYLE="display:inline-block; vertical-align:baseline">
+ <INPUT TYPE="radio" NAME="all_tags" VALUE="0" CHECKED> Any of these
+ <BR>
+ <INPUT TYPE="radio" NAME="all_tags" VALUE="1"> All of these
+ </DIV>
+ </TD>
+ </TR>
<& /elements/tr-select-payby.html,
'payby_type' => 'cust',
@@ -174,10 +184,28 @@
</TR>
<TR>
+ <TD ALIGN="right" VALIGN="center"><% mt('With postal mail invoices') |h %></TD>
+ <TD><INPUT TYPE="checkbox" NAME="POST" ID="POST" onClick="POST_changed();"></TD>
+ </TR>
+
+ <TR>
<TD ALIGN="right" VALIGN="center"><% mt('Without postal mail invoices') |h %></TD>
- <TD><INPUT TYPE="checkbox" NAME="no_POST"></TD>
+ <TD><INPUT TYPE="checkbox" NAME="no_POST" ID="no_POST" onClick="no_POST_changed();"></TD>
</TR>
+ <SCRIPT TYPE="text/javascript">
+ function POST_changed() {
+ if ( document.getElementById('POST').checked == true ) {
+ document.getElementById('no_POST').checked = false;
+ }
+ }
+ function no_POST_changed() {
+ if ( document.getElementById('no_POST').checked == true ) {
+ document.getElementById('POST').checked = false;
+ }
+ }
+ </SCRIPT>
+
<TR>
<TH CLASS="background" COLSPAN=2>&nbsp;</TH>
</TR>
diff --git a/httemplate/search/report_employee_audit.html b/httemplate/search/report_employee_audit.html
index 757b8232f..461849b76 100644
--- a/httemplate/search/report_employee_audit.html
+++ b/httemplate/search/report_employee_audit.html
@@ -23,7 +23,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+ unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report');
my %tables = (
cust_pay => 'Payments',
diff --git a/httemplate/search/report_employee_commission.html b/httemplate/search/report_employee_commission.html
index 51afad3b5..ebfcae82d 100644
--- a/httemplate/search/report_employee_commission.html
+++ b/httemplate/search/report_employee_commission.html
@@ -25,6 +25,6 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+ unless $FS::CurrentUser::CurrentUser->access_right('Employees: Commission Report');
</%init>
diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html
index 5cff0f4fc..854b24a00 100755
--- a/httemplate/search/report_receivables.html
+++ b/httemplate/search/report_receivables.html
@@ -15,7 +15,15 @@
<& /elements/tr-select-cust_main-status.html,
'label' => emt('Customer Status'),
&>
-
+
+ <& /elements/tr-select-cust_class.html,
+ 'label' => emt('Customer class'),
+ 'field' => 'cust_classnum',
+ 'multiple' => 1,
+ 'pre_options' => [ '' => emt('(none)') ],
+ 'all_selected' => 1,
+ &>
+
<TR>
<TD ALIGN="right"><% mt('Customers') |h %></TD>
<TD>
diff --git a/httemplate/search/report_sqlradius_usage.html b/httemplate/search/report_sqlradius_usage.html
index 01215e834..7e54465d3 100644
--- a/httemplate/search/report_sqlradius_usage.html
+++ b/httemplate/search/report_sqlradius_usage.html
@@ -8,13 +8,18 @@
'empty_label' => 'all',
&>
-% my @exporttypes = map { "'$_'" } qw(sqlradius broadband_sqlradius);
+%#more future-proof to actually ask all exports if they ->can('usage_sessions')
+% my @exporttypes = qw( sqlradius sqlradius_withdomain broadband_sqlradius
+% phone_sqlradius radiator
+% );
<& /elements/tr-select-table.html,
'label' => 'Export',
'table' => 'part_export',
'name_col' => 'label',
'hashref' => {},
- 'extra_sql' => ' WHERE exporttype IN('.join(',', @exporttypes).')',
+ 'extra_sql' => ' WHERE exporttype IN ( '.
+ join(',', map "'$_'", @exporttypes).
+ ')',
'disable_empty' => 1,
'order_by' => 'ORDER BY exportnum',
&>
diff --git a/httemplate/search/report_svc_acct.html b/httemplate/search/report_svc_acct.html
index 74bf5538e..e47f72726 100755
--- a/httemplate/search/report_svc_acct.html
+++ b/httemplate/search/report_svc_acct.html
@@ -116,7 +116,7 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Services: Accounts: Advanced search'); #?
-my $title = emt('Account Report');
+my $title = mt('Account Report');
#false laziness w/report_cust_pkg.html
my $custnum = '';
@@ -127,7 +127,7 @@ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
'hashref' => { 'custnum' => $custnum },
'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
}) or die "unknown custnum $custnum";
- $title = emt("Account Report: [_1]", $cust_main->name);
+ $title = mt("Account Report: [_1]", $cust_main->name);
}
</%init>
diff --git a/httemplate/search/report_svc_phone.html b/httemplate/search/report_svc_phone.html
index 9f1042608..63ca03e16 100644
--- a/httemplate/search/report_svc_phone.html
+++ b/httemplate/search/report_svc_phone.html
@@ -1,32 +1,6 @@
-<% include('/elements/header.html', 'Phone number total usage' ) %>
+<& elements/report_svc_Common.html,
+ 'table' => 'svc_phone',
+ 'title' => 'Phone number report',
-<FORM ACTION="svc_phone.cgi" METHOD="GET">
-
-<INPUT TYPE="hidden" NAME="magic" VALUE="all">
-<INPUT TYPE="hidden" NAME="usage_total" VALUE="1">
-
-<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
-
-%# <TR>
-%# <TH CLASS="background" COLSPAN=2 ALIGN="left">
-%# <FONT SIZE="+1">Search options</FONT>
-%# </TH>
-%# </TR>
-
- <% include ( '/elements/tr-input-beginning_ending.html', prefix=>'usage' ) %>
-
-</TABLE>
-
-<BR>
-<INPUT TYPE="submit" VALUE="Search phone numbers">
-
-</FORM>
-
-<% include('/elements/footer.html') %>
-<%init>
-
-#? 'List services' ? something new?
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
-
-</%init>
+ 'action' => 'svc_phone.cgi',
+&>
diff --git a/httemplate/search/report_svc_phone_usage.html b/httemplate/search/report_svc_phone_usage.html
new file mode 100644
index 000000000..9f1042608
--- /dev/null
+++ b/httemplate/search/report_svc_phone_usage.html
@@ -0,0 +1,32 @@
+<% include('/elements/header.html', 'Phone number total usage' ) %>
+
+<FORM ACTION="svc_phone.cgi" METHOD="GET">
+
+<INPUT TYPE="hidden" NAME="magic" VALUE="all">
+<INPUT TYPE="hidden" NAME="usage_total" VALUE="1">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+%# <TR>
+%# <TH CLASS="background" COLSPAN=2 ALIGN="left">
+%# <FONT SIZE="+1">Search options</FONT>
+%# </TH>
+%# </TR>
+
+ <% include ( '/elements/tr-input-beginning_ending.html', prefix=>'usage' ) %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search phone numbers">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+#? 'List services' ? something new?
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+</%init>
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/rt_ticket.html b/httemplate/search/rt_ticket.html
index 1ed5a3883..f5ac023b5 100644
--- a/httemplate/search/rt_ticket.html
+++ b/httemplate/search/rt_ticket.html
@@ -1,21 +1,21 @@
-<% include('elements/search.html',
+<& elements/search.html,
'title' => 'Time worked summary',
'name_singular' => 'ticket',
'query' => $query,
'count_query' => $count_query,
'count_addl' => [ $format_seconds_sub,
- $applied_time ? $format_seconds_sub : () ],
+ $applied ? $format_seconds_sub : () ],
'header' => [ 'Ticket #',
'Ticket',
'Time',
- $applied_time ? 'Applied' : (),
+ $applied ? 'Applied' : (),
],
'fields' => [ 'ticketid',
sub { encode_entities(shift->get('subject')) },
sub { my $seconds = shift->get('ticket_time');
&{ $format_seconds_sub }( $seconds );
},
- ($applied_time ?
+ ($applied ?
sub { my $seconds = shift->get('applied_time');
&{ $format_seconds_sub }( $seconds );
} : () ),
@@ -23,7 +23,7 @@
'sort_fields' => [ 'ticketid',
'subject',
'transaction_time',
- $applied_time ? 'applied_time' : (),
+ $applied ? 'applied_time' : (),
],
'links' => [
$link,
@@ -31,8 +31,7 @@
'',
'',
],
- )
-%>
+&>
<%once>
my $format_seconds_sub = sub {
@@ -60,7 +59,6 @@ my @select = (
);
my @select_total = ( 'COUNT(*)' );
-my ($transaction_time, $applied_time);
my $join = 'JOIN Users ON Transactions.Creator = Users.Id '; #.
my $twhere = "
@@ -68,6 +66,8 @@ my $twhere = "
AND Transactions.ObjectId = Tickets.Id
";
+my $transaction_time;
+my $applied = '';
my $cfname = '';
if ( $cgi->param('cfname') =~ /^\w(\w|\s)*$/ ) {
@@ -104,15 +104,14 @@ if ( $cgi->param('cfname') =~ /^\w(\w|\s)*$/ ) {
$twhere .= " AND CustomFields.Name = '$cfname'
AND (ocfv_new.Id IS NOT NULL OR ocfv_old.Id IS NOT NULL OR ocfv_main.Id IS NOT NULL)";
-}
-else {
+} else {
+
$transaction_time = "
CASE transactions.type when 'Set'
THEN (to_number(newvalue,'999999')-to_number(oldvalue, '999999')) * 60
ELSE timetaken*60
END";
- my $applied = '';
if ( $cgi->param('svcnum') =~ /^\s*(\d+)\s*$/ ) {
$twhere .= " AND EXISTS( SELECT 1 FROM acct_rt_transaction WHERE acct_rt_transaction.transaction_id = Transactions.id AND svcnum = $1 )";
$applied = "AND svcnum = $1";
@@ -122,13 +121,11 @@ else {
AND ( ( Transactions.Type = 'Set'
AND Transactions.Field = 'TimeWorked'
AND Transactions.NewValue != Transactions.OldValue )
- OR ( ( Transactions.Type='Create' OR Transactions.Type='Comment' OR Transactions.Type='Correspond' OR Transactions.Type='Touch' )
+ OR ( Transactions.Type IN ( 'Create', 'Comment', 'Correspond', 'Touch' )
AND Transactions.TimeTaken > 0
)
)";
- $applied_time = "( SELECT SUM(support) FROM acct_rt_transaction LEFT JOIN Transactions ON ( transaction_id = Id ) $twhere $applied )";
-
}
@@ -155,9 +152,13 @@ my $ticket_time = "( SELECT SUM($transaction_time) $transactions )";
push @select, "$ticket_time AS ticket_time";
push @select_total, "SUM($ticket_time)";
-if ( $applied_time) {
+if ( $applied ) {
+
+ my $applied_time = "( SELECT SUM(support) FROM acct_rt_transaction LEFT JOIN Transactions ON ( transaction_id = Id ) $twhere $applied )";
+
push @select, "$applied_time AS applied_time";
push @select_total, "SUM($applied_time)";
+
}
my $query = {
diff --git a/httemplate/search/rt_transaction.html b/httemplate/search/rt_transaction.html
index 1ae607be1..eb250fb27 100644
--- a/httemplate/search/rt_transaction.html
+++ b/httemplate/search/rt_transaction.html
@@ -1,4 +1,4 @@
-<% include('elements/search.html',
+<& elements/search.html,
'title' => 'Time worked',
'name_singular' => 'transaction',
'query' => $query,
@@ -29,8 +29,7 @@
'',
'',
],
- )
-%>
+&>
<%once>
my $format_seconds_sub = sub {
diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html
index bf5446975..71aa00671 100644
--- a/httemplate/search/sql.html
+++ b/httemplate/search/sql.html
@@ -1,9 +1,9 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Query Results',
'name' => 'rows',
'query' => "SELECT $sql",
- )
-%>
+
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/sqlradius.cgi b/httemplate/search/sqlradius.cgi
index 5363944e4..22984731a 100644
--- a/httemplate/search/sqlradius.cgi
+++ b/httemplate/search/sqlradius.cgi
@@ -51,7 +51,7 @@
% @{ $part_export->usage_sessions( {
% 'stoptime_start' => $beginning,
% 'stoptime_end' => $ending,
-% 'open_sessions' => $open_sessions,
+% 'session_status' => $status,
% 'starttime_start' => $starttime_beginning,
% 'starttime_end' => $starttime_ending,
% 'svc_acct' => $cgi_svc_acct,
@@ -117,9 +117,9 @@ if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) {
$ending = $1;
}
-my $open_sessions = '';
-if ( $cgi->param('open_sessions') =~ /^(\d*)$/ ) {
- $open_sessions = $1;
+my $status = '';
+if ( $cgi->param('session_status') =~ /^(closed|open)$/ ) {
+ $status = $1;
}
my( $starttime_beginning, $starttime_ending ) = ( '', '' );
@@ -242,8 +242,15 @@ my $time_format = sub {
$pretty;
};
+my $time_format_or_open = sub {
+ my $time = shift;
+ return '<CENTER>OPEN</CENTER>' if $time == 0;
+ &{$time_format}($time);
+};
+
my $duration_format = sub {
my $seconds = shift;
+ return '' if $seconds eq ''; # open session
my $hour = int($seconds/3600);
my $min = int( ($seconds%3600) / 60 );
my $sec = $seconds%60;
@@ -339,7 +346,7 @@ tie %fields, 'Tie::IxHash',
'acctstoptime' => {
name => 'End&nbsp;time',
attrib => 'Acct-Stop-Time',
- fmt => $time_format,
+ fmt => $time_format_or_open,
align => 'left',
},
'acctsessiontime' => {
diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html
index 7b9fce310..547a9bb44 100644
--- a/httemplate/search/sqlradius.html
+++ b/httemplate/search/sqlradius.html
@@ -52,8 +52,9 @@
<TR>
<TD>Show:</TD>
<TD>
- <INPUT TYPE="radio" NAME="open_sessions" VALUE="0" onClick="open_changed(this);" CHECKED>Completed sessions<BR>
- <INPUT TYPE="radio" NAME="open_sessions" VALUE="1" onClick="open_changed(this);">Open sessions
+ <INPUT TYPE="radio" NAME="session_status" VALUE="" onClick="enable_stop(true);" CHECKED>All sessions<BR>
+ <INPUT TYPE="radio" NAME="session_status" VALUE="closed" onClick="enable_stop(true);">Completed sessions<BR>
+ <INPUT TYPE="radio" NAME="session_status" VALUE="open" onClick="enable_stop(false);">Open sessions
</TD>
</TR>
@@ -69,41 +70,31 @@
<SCRIPT TYPE="text/javascript">
- function open_changed(what) {
-
- var value=get_open_value(what);
- if ( value == '1' ) {
- what.form.stoptime_beginning_text.disabled = true;
- what.form.stoptime_ending_text.disabled = true;
- what.form.stoptime_beginning_text.style.backgroundColor = '#dddddd';
- what.form.stoptime_ending_text.style.backgroundColor = '#dddddd';
- what.form.stoptime_beginning_button.style.display = 'none';
- what.form.stoptime_ending_button.style.display = 'none';
- what.form.stoptime_beginning_disabled.style.display = '';
- what.form.stoptime_ending_disabled.style.display = '';
- } else if ( value == '0' ) {
- what.form.stoptime_beginning_text.disabled = false;
- what.form.stoptime_ending_text.disabled = false;
- what.form.stoptime_beginning_text.style.backgroundColor = '#ffffff';
- what.form.stoptime_ending_text.style.backgroundColor = '#ffffff';
- what.form.stoptime_beginning_button.style.display = '';
- what.form.stoptime_ending_button.style.display = '';
- what.form.stoptime_beginning_disabled.style.display = 'none';
- what.form.stoptime_ending_disabled.style.display = 'none';
+ function enable_stop(value) {
+
+ var f = document.OneTrueForm;
+ if ( value ) {
+ f.stoptime_beginning_text.disabled = false;
+ f.stoptime_ending_text.disabled = false;
+ f.stoptime_beginning_text.style.backgroundColor = '#ffffff';
+ f.stoptime_ending_text.style.backgroundColor = '#ffffff';
+ f.stoptime_beginning_button.style.display = '';
+ f.stoptime_ending_button.style.display = '';
+ f.stoptime_beginning_disabled.style.display = 'none';
+ f.stoptime_ending_disabled.style.display = 'none';
+ } else {
+ f.stoptime_beginning_text.disabled = true;
+ f.stoptime_ending_text.disabled = true;
+ f.stoptime_beginning_text.style.backgroundColor = '#dddddd';
+ f.stoptime_ending_text.style.backgroundColor = '#dddddd';
+ f.stoptime_beginning_button.style.display = 'none';
+ f.stoptime_ending_button.style.display = 'none';
+ f.stoptime_beginning_disabled.style.display = '';
+ f.stoptime_ending_disabled.style.display = '';
}
}
- function get_open_value(what) {
- var rad_val = '';
- for (var i=0; i < what.form.open_sessions.length; i++) {
- if (what.form.open_sessions[i].checked) {
- var rad_val = what.form.open_sessions[i].value;
- }
- }
- return rad_val;
- }
-
</SCRIPT>
<TR>
diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi
index 92e1c500c..b9e5a7cc9 100755
--- a/httemplate/search/svc_acct.cgi
+++ b/httemplate/search/svc_acct.cgi
@@ -1,4 +1,4 @@
-<& elements/search.html,
+<& elements/svc_Common.html,
'title' => emt('Account Search Results'),
'name' => emt('accounts'),
'query' => $sql_query,
diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi
index ee62e9084..8366d214b 100755
--- a/httemplate/search/svc_broadband.cgi
+++ b/httemplate/search/svc_broadband.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/svc_Common.html,
'title' => 'Broadband Search Results',
'name' => 'broadband services',
'html_init' => $html_init,
@@ -49,8 +49,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied" unless
@@ -72,7 +72,7 @@ else {
}
if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
- $search_hash{'order_by'} = $1;
+ $search_hash{'order_by'} = "ORDER BY $1";
}
my $sql_query = FS::svc_broadband->search(\%search_hash);
diff --git a/httemplate/search/svc_dish.cgi b/httemplate/search/svc_dish.cgi
index 94da03537..1f8cbc395 100755
--- a/httemplate/search/svc_dish.cgi
+++ b/httemplate/search/svc_dish.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/svc_Common.html,
'title' => 'Dish Network Search Results',
'name' => 'services',
'query' => $sql_query,
@@ -34,8 +34,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -61,7 +61,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
' LEFT JOIN part_svc USING ( svcpart ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
#here is the agent virtualization
push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi
index 9827b8d38..56cfa30c8 100755
--- a/httemplate/search/svc_domain.cgi
+++ b/httemplate/search/svc_domain.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => "Domain Search Results",
'name' => 'domains',
'query' => $sql_query,
@@ -34,8 +34,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -66,7 +66,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
' LEFT JOIN part_svc USING ( svcpart ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
#here is the agent virtualization
push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi
index cb51d44fd..b282939a7 100755
--- a/httemplate/search/svc_external.cgi
+++ b/httemplate/search/svc_external.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/svc_Common.html,
'title' => 'External service search results',
'name' => 'external services',
'query' => $sql_query,
@@ -40,9 +40,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
-
+
+&>
<%init>
die "access denied"
@@ -90,7 +89,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
' LEFT JOIN part_svc USING ( svcpart ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
#here is the agent virtualization
push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi
index f17f131ab..6a23bb3bb 100755
--- a/httemplate/search/svc_forward.cgi
+++ b/httemplate/search/svc_forward.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => "Mail forward Search Results",
'name' => 'mail forwards',
'query' => $sql_query,
@@ -39,8 +39,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -67,7 +67,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
' LEFT JOIN part_svc USING ( svcpart ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
#here is the agent virtualization
push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
diff --git a/httemplate/search/svc_hardware.cgi b/httemplate/search/svc_hardware.cgi
index ec09be82b..93fc2c391 100644
--- a/httemplate/search/svc_hardware.cgi
+++ b/httemplate/search/svc_hardware.cgi
@@ -1,4 +1,4 @@
-<% include('elements/search.html',
+<& elements/svc_Common.html,
'title' => 'Hardware service search results',
'name' => 'installations',
'query' => $sql_query,
@@ -34,8 +34,7 @@
FS::UI::Web::cust_colors() ],
'style' => [ $svc_cancel_style, ('') x 7,
FS::UI::Web::cust_styles() ],
- )
-%>
+&>
<%init>
die "access denied"
@@ -44,8 +43,8 @@ die "access denied"
my $addl_from = '
LEFT JOIN cust_svc USING ( svcnum )
LEFT JOIN part_svc USING ( svcpart )
- LEFT JOIN cust_pkg USING ( pkgnum )
- LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN cust_pkg USING ( pkgnum )'.
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg').'
LEFT JOIN hardware_type USING ( typenum )';
my @extra_sql;
diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi
index 29434083f..f3a056475 100644
--- a/httemplate/search/svc_phone.cgi
+++ b/httemplate/search/svc_phone.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/svc_Common.html,
'title' => "Phone number search results",
'name' => 'phone numbers',
'query' => $sql_query,
@@ -9,7 +9,7 @@
'Country code',
'Phone number',
@header,
- FS::UI::Web::cust_header(),
+ FS::UI::Web::cust_header($cgi->param('cust_fields')),
],
'fields' => [ 'svcnum',
'svc',
@@ -24,7 +24,7 @@
$link,
( map '', @header ),
( map { $_ ne 'Cust. Status' ? $link_cust : '' }
- FS::UI::Web::cust_header()
+ FS::UI::Web::cust_header($cgi->param('cust_fields'))
),
],
'align' => 'rlrr'.
@@ -46,8 +46,8 @@
( map '', @header ),
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -56,8 +56,6 @@ die "access denied"
my $conf = new FS::Conf;
my @select = ();
-my %svc_phone = ();
-my @extra_sql = ();
my $orderby = 'ORDER BY svcnum';
my @header = ();
@@ -65,9 +63,12 @@ my @fields = ();
my $link = [ "${p}view/svc_phone.cgi?", 'svcnum' ];
my $redirect = $link;
+my %search_hash = ();
+my @extra_sql = ();
+
if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
- push @extra_sql, 'pkgnum IS NULL'
+ $search_hash{'unlinked'} = 1
if $cgi->param('magic') eq 'unlinked';
if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
@@ -119,52 +120,31 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
}
+} elsif ( $cgi->param('magic') =~ /^advanced$/ ) {
+
+ for (qw( agentnum custnum cust_status balance balance_days cust_fields )) {
+ $search_hash{$_} = $cgi->param($_) if length($cgi->param($_));
+ }
+
+ for (qw( payby pkgpart svcpart )) {
+ $search_hash{$_} = [ $cgi->param($_) ] if $cgi->param($_);
+ }
+
} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
- push @extra_sql, "svcpart = $1";
+ $search_hash{'svcpart'} = [ $1 ];
} else {
$cgi->param('phonenum') =~ /^([\d\- ]+)$/;
- ( $svc_phone{'phonenum'} = $1 ) =~ s/\D//g;
+ my $phonenum = $1;
+ $phonenum =~ s/\D//g;
+ push @extra_sql, "phonenum = '$phonenum'";
}
-my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
- ' LEFT JOIN part_svc USING ( svcpart ) '.
- ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
-
-#here is the agent virtualization
-push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
- 'null_right' => 'View/link unlinked services'
- );
-
-my $extra_sql = '';
-if ( @extra_sql ) {
- $extra_sql = ( keys(%svc_phone) ? ' AND ' : ' WHERE ' ).
- join(' AND ', @extra_sql );
-}
+$search_hash{'addl_select'} = \@select;
+$search_hash{'order_by'} = $orderby;
+$search_hash{'where'} = \@extra_sql;
-my $count_query = "SELECT COUNT(*) FROM svc_phone $addl_from ";
-if ( keys %svc_phone ) {
- $count_query .= ' WHERE '.
- join(' AND ', map "$_ = ". dbh->quote($svc_phone{$_}),
- keys %svc_phone
- );
-}
-$count_query .= $extra_sql;
-
-my $sql_query = {
- 'table' => 'svc_phone',
- 'hashref' => \%svc_phone,
- 'select' => join(', ',
- 'svc_phone.*',
- 'part_svc.svc',
- @select,
- 'cust_main.custnum',
- FS::UI::Web::cust_sql_fields(),
- ),
- 'extra_sql' => $extra_sql,
- 'order_by' => $orderby,
- 'addl_from' => $addl_from,
-};
+my $sql_query = FS::svc_phone->search(\%search_hash);
+my $count_query = delete($sql_query->{'count_query'});
#smaller false laziness w/svc_*.cgi here
my $link_cust = sub {
diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi
index adc31c88a..7410262e8 100755
--- a/httemplate/search/svc_www.cgi
+++ b/httemplate/search/svc_www.cgi
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/svc_Common.html,
'title' => 'Virtual Host Search Results',
'name' => 'virtual hosts',
'query' => $sql_query,
@@ -45,8 +45,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -73,7 +73,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
' LEFT JOIN part_svc USING ( svcpart ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
#here is the agent virtualization
push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
diff --git a/httemplate/search/timeworked.html b/httemplate/search/timeworked.html
index bbfd0334d..fa4b89539 100644
--- a/httemplate/search/timeworked.html
+++ b/httemplate/search/timeworked.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Time Worked',
'name' => 'time',
'html_form' => qq!<FORM NAME="timeForm" ACTION="${p}misc/timeworked.html" METHOD="POST">!,
@@ -33,9 +33,8 @@
'',
],
'html_foot' => $html_foot,
- )
-
-%>
+
+&>
<%init>
die "access denied"
diff --git a/httemplate/search/unearned_detail.html b/httemplate/search/unearned_detail.html
index f61de052e..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;
}
@@ -210,8 +209,8 @@ push @select, '(edate - 82799) AS before_edate';
#usage always excluded
# always 'nottax', not 'istax'
-$join_cust = ' JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum ) ';
+$join_cust = ' JOIN cust_bill USING ( invnum ) '.
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
$join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum )
LEFT JOIN part_pkg USING ( pkgpart )
@@ -222,7 +221,7 @@ my $where = ' WHERE '. join(' AND ', @where);
my $count_query = "SELECT COUNT(DISTINCT billpkgnum),
SUM( $unearned_base ), SUM( $unearned_sql )
- FROM cust_bill_pkg $join_cust $join_pkg $where";
+ FROM cust_bill_pkg $join_pkg $join_cust $where";
push @select, 'part_pkg.pkg',
'part_pkg.freq',
@@ -231,7 +230,7 @@ push @select, 'part_pkg.pkg',
my $query = {
'table' => 'cust_bill_pkg',
- 'addl_from' => "$join_cust $join_pkg",
+ 'addl_from' => "$join_pkg $join_cust",
'hashref' => {},
'select' => join(",\n", @select ),
'extra_sql' => $where,
diff --git a/httemplate/search/unprovisioned_services.html b/httemplate/search/unprovisioned_services.html
index f85e4fb19..a7791ba86 100644
--- a/httemplate/search/unprovisioned_services.html
+++ b/httemplate/search/unprovisioned_services.html
@@ -1,4 +1,4 @@
-<% include( 'elements/search.html',
+<& elements/search.html,
'title' => 'Unprovisioned Service Search Results',
'name' => 'packages with unprovisioned services',
'query' => {
@@ -54,8 +54,8 @@
'',
FS::UI::Web::cust_styles(),
],
- )
-%>
+
+&>
<%init>
die "access denied"
@@ -74,7 +74,8 @@ my $search = " where cust_pkg.cancel is null and pkg_svc.quantity > 0 and "
. " cust_svc.pkgnum = cust_pkg.pkgnum and "
. " cust_svc.svcpart = pkg_svc.svcpart) $svcpart_limit";
-my $addl_from = " join pkg_svc using (pkgpart) join cust_main using (custnum) ";
+my $addl_from = " join pkg_svc using (pkgpart) ".
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg');
# this was very painful to derive but it appears correct
#select cust_pkg.custnum,cust_pkg.pkgpart,cust_pkg.pkgnum, pkg_svc.svcpart from cust_pkg join
diff --git a/httemplate/view/bill_batch.cgi b/httemplate/view/bill_batch.cgi
index 7d640395e..55ee4be1c 100644
--- a/httemplate/view/bill_batch.cgi
+++ b/httemplate/view/bill_batch.cgi
@@ -13,7 +13,7 @@
'hashref' => { },
'addl_from' =>
'LEFT JOIN cust_bill USING ( invnum ) '.
- 'LEFT JOIN cust_main USING ( custnum )',
+ FS::UI::Web::join_cust_main('cust_bill'),
'extra_sql' => " WHERE batchnum = $batchnum",
},
'count_query' => "SELECT COUNT(*) FROM cust_bill_batch WHERE batchnum = $batchnum",
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
index 5c46803d2..b863a734b 100644
--- a/httemplate/view/cust_main/billing.html
+++ b/httemplate/view/cust_main/billing.html
@@ -247,6 +247,10 @@
<TD ALIGN="right"><% mt('Email address(es)') |h %></TD>
<TD BGCOLOR="#ffffff">
<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %>
+% if ( $cust_main->message_noemail ) {
+ <BR>
+ <SPAN STYLE="font-size: small"><% emt('(do not send notices)') %></SPAN>
+% }
</TD>
</TR>
% }
diff --git a/httemplate/view/cust_main/change_history.html b/httemplate/view/cust_main/change_history.html
index ea84b8f75..bf32a49f9 100644
--- a/httemplate/view/cust_main/change_history.html
+++ b/httemplate/view/cust_main/change_history.html
@@ -43,10 +43,12 @@ tie my %tables, 'Tie::IxHash',
'svc_external' => 'External service',
'svc_phone' => 'Phone',
'phone_device' => 'Phone device',
+ 'cust_pkg_discount' => 'Discount',
#? it gets provisioned anyway 'phone_avail' => 'Phone',
;
-my $svc_join = 'JOIN cust_svc USING ( svcnum ) JOIN cust_pkg USING ( pkgnum )';
+my $pkg_join = "JOIN cust_pkg USING ( pkgnum )";
+my $svc_join = "JOIN cust_svc USING ( svcnum ) $pkg_join";
my %table_join = (
'svc_acct' => $svc_join,
@@ -58,6 +60,7 @@ my %table_join = (
'svc_external' => $svc_join,
'svc_phone' => $svc_join,
'phone_device' => $svc_join,
+ 'cust_pkg_discount'=> $pkg_join,
);
@@ -104,7 +107,7 @@ my $conf = new FS::Conf;
my $curuser = $FS::CurrentUser::CurrentUser;
-die "access deined"
+die "access denied"
unless $curuser->access_right('View customer history');
# find out the beginning of this customer history, if possible
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index 7d7930634..546dd89c3 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -1,3 +1,29 @@
+<STYLE TYPE="text/css">
+td.package {
+ vertical-align: top;
+ border-width: 0;
+ border-style: solid;
+ border-color: #bbbbff;
+}
+table.package {
+ border: none;
+ padding: 0;
+ border-spacing: 0;
+ width: 100%;
+}
+table.usage {
+ border: 1px solid black;
+ margin: auto;
+ width: 60%;
+ border-spacing: 0px;
+}
+.shared > * {
+ background-color: #ffffaa;
+}
+.row0 { background-color: #eeeeee; }
+.row1 { background-color: #ffffff; }
+
+</STYLE>
% my $s = 0;
% if ( $curuser->access_right('Qualify service') ) {
@@ -75,7 +101,7 @@
<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,
@@ -87,7 +113,6 @@
<& packages/section.html,
'cust_main' => $cust_main,
'packages' => $packages,
- 'show_location' => $show_location,
&>
</TABLE>
% }
@@ -114,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, @$packages); # ? '1' : '0';
-
my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
#subroutines
@@ -178,6 +199,10 @@ sub get_packages {
}
$num_old_packages -= scalar(@packages);
+
+ # don't include supplemental packages in this list; they'll be found from
+ # their main packages
+ @packages = grep !$_->main_pkgnum, @packages;
( \@packages, $num_old_packages );
}
diff --git a/httemplate/view/cust_main/packages/contact.html b/httemplate/view/cust_main/packages/contact.html
new file mode 100644
index 000000000..93129915f
--- /dev/null
+++ b/httemplate/view/cust_main/packages/contact.html
@@ -0,0 +1,61 @@
+% if ( $contact ) {
+ <% $contact->line |h %>
+% if ( $show_link ) {
+ <FONT SIZE=-1>
+ (&nbsp;<%pkg_change_contact_link($cust_pkg)%>&nbsp;)
+ </FONT>
+% }
+% } elsif ( $show_link ) {
+ <FONT SIZE=-1>
+ (&nbsp;<%pkg_add_contact_link($cust_pkg)%>&nbsp;)
+ </FONT>
+% }
+<%init>
+
+my $conf = new FS::Conf;
+my %opt = @_;
+
+my $cust_pkg = $opt{'cust_pkg'};
+
+my $show_link =
+ ! $cust_pkg->get('cancel')
+ && $FS::CurrentUser::CurrentUser->access_right('Change customer package');
+
+my $contact = $cust_pkg->contact_obj;
+
+sub pkg_change_contact_link {
+ my $cust_pkg = shift;
+ #my $pkgpart = $cust_pkg->pkgpart;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/change_pkg_contact.html",
+ 'label' => emt('Change'), # contact'),
+ 'actionlabel' => emt('Change'),
+ 'cust_pkg' => $cust_pkg,
+ 'width' => 616,
+ 'height' => 220,
+ );
+}
+
+sub pkg_add_contact_link {
+ my $cust_pkg = shift;
+ #my $pkgpart = $cust_pkg->pkgpart;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/change_pkg_contact.html",
+ 'label' => emt('Add contact'),
+ 'actionlabel' => emt('Change'),
+ 'cust_pkg' => $cust_pkg,
+ 'width' => 616,
+ 'height' => 192,
+ );
+}
+
+#sub edit_contact_link {
+# my $contactnum = shift;
+# include( '/elements/popup_link.html',
+# 'action' => $p. "edit/cust_contact.cgi?contactnum=$contactnum",
+# 'label' => emt('Edit contact'),
+# 'actionlabel' => emt('Edit'),
+# );
+#}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html
index 34e3a64c3..f2d379841 100644
--- a/httemplate/view/cust_main/packages/location.html
+++ b/httemplate/view/cust_main/packages/location.html
@@ -1,7 +1,5 @@
-<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" WIDTH="20%">
-
-% unless ( $cust_pkg->locationnum ) {
- <I><FONT SIZE=-1>(<% mt('default service address') |h %>)</FONT><BR>
+% if ( $default ) {
+ <DIV STYLE="font-style: italic; font-size: small">
% }
<% $loc->location_label( 'join_string' => '<BR>',
@@ -24,8 +22,8 @@
</FONT>
% }
-% unless ( $cust_pkg->locationnum ) {
- </I>
+% if ( $default ) {
+ </DIV>
% }
% if ( ! $cust_pkg->get('cancel')
@@ -41,19 +39,19 @@
</FONT>
% }
-</TD>
<%init>
my $conf = new FS::Conf;
my %opt = @_;
-my $bgcolor = $opt{'bgcolor'};
my $cust_pkg = $opt{'cust_pkg'};
my $countrydefault = $opt{'countrydefault'} || 'US';
my $statedefault = $opt{'statedefault'}
|| ($countrydefault eq 'US' ? 'CA' : '');
my $loc = $cust_pkg->cust_location_or_main;
+# dubious--they should all have a location now
+my $default = $cust_pkg->locationnum == $opt{'cust_main'}->ship_locationnum;
sub pkg_change_location_link {
my $cust_pkg = shift;
diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html
index 5d93ad46f..520305a9a 100644
--- a/httemplate/view/cust_main/packages/package.html
+++ b/httemplate/view/cust_main/packages/package.html
@@ -1,5 +1,6 @@
-<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="top">
- <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+<TD CLASS="inv package" BGCOLOR="<% $bgcolor %>" VALIGN="top"
+ STYLE="border-left-width: <% $supplemental * 30 %>px">
+ <TABLE CLASS="inv package">
<TR>
<TD COLSPAN=2>
<A NAME="cust_pkg<% $cust_pkg->pkgnum %>"
@@ -17,50 +18,62 @@
<B><% $cust_pkg->quantity %></B>
</TD>
</TR>
-% }
+% }
<TR>
<TD COLSPAN=2>
<FONT SIZE=-1>
-% unless ( $cust_pkg->get('cancel') ) {
+% unless ( $cust_pkg->get('cancel') ) {
%
-% my $br = 0;
-% if ( $curuser->access_right('Change customer package') ) {
-% $br=1;
- (&nbsp;<%pkg_change_link($cust_pkg)%>&nbsp;)
-% }
+% if ( $supplemental or $part_pkg->freq eq '0' ) {
+% # Supplemental packages can't be changed independently.
+% # One-time charges don't need to be changed.
+% # For both of those, we only show "Edit dates", "Add comments",
+% # and "Add invoice details".
+% if ( $curuser->access_right('Edit customer package dates') ) {
+ (&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
+% }
+% } else {
+% # the usual case: links to change package definition,
+% # discount, and customization
+% my $br = 0;
+% if ( $curuser->access_right('Change customer package') ) {
+% $br=1;
+ (&nbsp;<%pkg_change_link($cust_pkg)%>&nbsp;)
+% }
%
-% if ( $curuser->access_right('Edit customer package dates') ) {
-% $br=1;
- (&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
-% }
+% if ( $curuser->access_right('Edit customer package dates') ) {
+% $br=1;
+ (&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
+% }
%
-% if ( $curuser->access_right('Discount customer package')
-% && $part_pkg->can_discount
-% && ! scalar($cust_pkg->cust_pkg_discount_active)
-% && ! scalar($cust_pkg->part_pkg->part_pkg_discount)
-% )
-% {
-% $br=1;
- (&nbsp;<%pkg_discount_link($cust_pkg)%>&nbsp;)
-% }
+% if ( $curuser->access_right('Discount customer package')
+% && $part_pkg->can_discount
+% && ! scalar($cust_pkg->cust_pkg_discount_active)
+% && ! scalar($cust_pkg->part_pkg->part_pkg_discount)
+% )
+% {
+% $br=1;
+ (&nbsp;<%pkg_discount_link($cust_pkg)%>&nbsp;)
+% }
%
-% if ( $curuser->access_right('Customize customer package') ) {
-% $br=1;
- (&nbsp;<%pkg_customize_link($cust_pkg,$part_pkg)%>&nbsp;)
-% }
+% if ( $curuser->access_right('Customize customer package') ) {
+% $br=1;
+ (&nbsp;<%pkg_customize_link($cust_pkg,$part_pkg)%>&nbsp;)
+% }
%
- <% $br ? '<BR>' : '' %>
-% }
+ <% $br ? '<BR>' : '' %>
+% }
-% if ( $cust_pkg->num_cust_event
-% && ( $curuser->access_right('Billing event reports')
-% || $curuser->access_right('View customer billing events')
-% )
-% ) {
- (&nbsp;<%pkg_event_link($cust_pkg)%>&nbsp;)
-% }
+% if ( $cust_pkg->num_cust_event
+% && ( $curuser->access_right('Billing event reports')
+% || $curuser->access_right('View customer billing events')
+% )
+% ) {
+ (&nbsp;<%pkg_event_link($cust_pkg)%>&nbsp;)
+% }
+% } #!$supplemental
</FONT>
</TD>
@@ -170,15 +183,40 @@
</TR>
% if ( $curuser->access_right('Change customer package') and
% !$cust_pkg->get('cancel') and
-% !$opt{'show_location'}) {
+% !$supplemental and
+% $part_pkg->freq ne '0' ) {
<TR>
+% if ( FS::Conf->new->exists('invoice-unitprice') ) {
<TD><FONT SIZE="-1">
- (&nbsp;<% pkg_change_location_link($cust_pkg) %>&nbsp;)
+ (&nbsp;<% pkg_change_quantity_link($cust_pkg) %>&nbsp;)
</FONT></TD>
+% }
</TR>
% }
% }
</TABLE>
+% if ( @cust_pkg_usage ) {
+ <TABLE CLASS="usage inv">
+ <TR><TH COLSPAN=4><% mt('Included usage') %></TH></TR>
+% foreach my $usage (@cust_pkg_usage) {
+% my $part = $usage->part_pkg_usage;
+% my $ratio = 255 * ($usage->minutes / $part->minutes);
+% $ratio = 255 if $ratio > 255; # because rollover
+% my $color = sprintf('STYLE="font-weight: bold; color: #%02x%02x00"', 255 - $ratio, $ratio);
+% my $trstyle = '';
+% $trstyle = ' CLASS="shared"' if $part->shared;
+ <TR<%$trstyle%>>
+ <TD ALIGN="right"><% $part->description %>: </TD>
+ <TD <%$color%> ALIGN="right"><% $usage->minutes %></TD>
+ <TD <%$color%>> / </TD>
+ <TD <%$color%>><% $part->minutes %></TD>
+% if ( $part->shared ) {
+ <TD><I>(shared)</I></TD>
+% }
+ </TR>
+% }
+ </TABLE>
+% }
</TD>
@@ -196,6 +234,18 @@ my $countrydefault = $opt{'countrydefault'} || 'US';
my $statedefault = $opt{'statedefault'}
|| ($countrydefault eq 'US' ? 'CA' : '');
+my $supplemental = $opt{'supplemental'} || 0;
+
+$cust_pkg->pkgnum =~ /^(\d+)$/;
+my $pkgnum = $1;
+my @cust_pkg_usage = qsearch({
+ 'select' => 'cust_pkg_usage.*',
+ 'table' => 'cust_pkg_usage',
+ 'addl_from' => ' JOIN part_pkg_usage USING (pkgusagepart)',
+ 'extra_sql' => " WHERE pkgnum = $1",
+ 'order_by' => ' ORDER BY priority ASC, description ASC',
+});
+
#subroutines
#false laziness w/status.html
@@ -229,6 +279,17 @@ sub pkg_change_location_link {
);
}
+sub pkg_change_quantity_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'edit/cust_pkg_quantity.html?',
+ 'label' => emt('Change quantity'),
+ 'actionlabel' => emt('Change'),
+ 'cust_pkg' => shift,
+ 'width' => 390,
+ 'height' => 220,
+ );
+}
+
sub pkg_dates_link { pkg_link('edit/REAL_cust_pkg', emt('Edit dates'), @_ ); }
sub pkg_discount_link {
diff --git a/httemplate/view/cust_main/packages/section.html b/httemplate/view/cust_main/packages/section.html
index 85f0c795a..5f54c0a36 100755
--- a/httemplate/view/cust_main/packages/section.html
+++ b/httemplate/view/cust_main/packages/section.html
@@ -1,53 +1,48 @@
% if ( @$packages ) {
-% my $bgcolor1 = '#eeeeee';
-% my $bgcolor2 = '#ffffff';
-% my $bgcolor = '';
-
<TR>
% #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>
% #$FS::cust_pkg::DEBUG = 2;
% foreach my $cust_pkg (@$packages) {
+ <& .packagerow, $cust_pkg,
+ 'cust_main' => $opt{'cust_main'},
+ 'bgcolor' => $opt{'bgcolor'},
+ %conf_opt
+ &>
+% }
+% } else { # there are no packages
+<BR>
+% }
+<%def .packagerow>
%
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
-%
-% my %iopt = (
-% 'bgcolor' => $bgcolor,
-% 'cust_pkg' => $cust_pkg,
-% 'part_pkg' => $cust_pkg->part_pkg,
-% 'cust_main' => $opt{'cust_main'},
-% %conf_opt,
-% );
-%
-
+% my ($cust_pkg, %iopt) = @_;
+% $iopt{'cust_pkg'} = $cust_pkg;
+% $iopt{'part_pkg'} = $cust_pkg->part_pkg;
<!--pkgnum: <% $cust_pkg->pkgnum %>-->
- <TR>
+ <TR CLASS="row<%$row % 2%>">
<& package.html, %iopt &>
- <& status.html, %iopt &>
-% if ( $show_location ) {
- <& location.html, %iopt &>
-% }
+ <& status.html, %iopt &>
+ <TD CLASS="inv" BGCOLOR="<% $iopt{bgcolor} %>" WIDTH="20%" VALIGN="top">
+ <& contact.html, %iopt &>
+ <& location.html, %iopt &>
+ </TD>
<& services.html, %iopt &>
</TR>
-
-% } #foreach $cust_pkg
-%# </TABLE>
-% } #if @$packages
-% else {
-<BR>
+% $row++;
+% # include supplemental packages if any
+% $iopt{'supplemental'} = ($iopt{'supplemental'} || 0) + 1;
+% foreach my $supp_pkg ($cust_pkg->supplemental_pkgs) {
+ <& .packagerow, $supp_pkg, %iopt &>
% }
-
+</%def>
+<%shared>
+my $row = 0;
+</%shared>
<%init>
my %opt = @_;
@@ -56,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 {
@@ -89,10 +83,8 @@ my %conf_opt = (
'manage_link_loc' => scalar($conf->config('svc_broadband-manage_link_loc')),
'manage_link-new_window' => $conf->exists('svc_broadband-manage_link-new_window'),
'maestro-status_test' => $conf->exists('maestro-status_test'),
- 'cust_pkg-large_pkg_size' => $conf->config('cust_pkg-large_pkg_size'),
+ 'cust_pkg-large_pkg_size' => scalar($conf->config('cust_pkg-large_pkg_size')),
- # for packages.html Change location link
- 'show_location' => $show_location,
);
</%init>
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
index e9017745b..9d5a88e0f 100644
--- a/httemplate/view/cust_main/packages/status.html
+++ b/httemplate/view/cust_main/packages/status.html
@@ -1,9 +1,11 @@
-<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
-% if ( $cust_pkg->order_date ) {
+% if ( $supplemental ) {
+ <% pkg_status_row_colspan($cust_pkg, emt('Supplemental'), '', 'color' => '7777FF', %opt) %>
+% } elsif ( $cust_pkg->order_date ) {
<% pkg_status_row($cust_pkg, emt('Ordered'), 'order_date', %opt ) %>
% }
@@ -12,30 +14,25 @@
<% pkg_status_row($cust_pkg, emt('Cancelled'), 'cancel', 'color'=>'FF0000', %opt ) %>
- <% pkg_status_row_colspan( $cust_pkg,
- ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
- 'align'=>'right', 'color'=>'ff0000', 'size'=>'-2', 'colspan'=>$colspan,
- %opt
- )
- %>
+ <% pkg_reason_row($cust_pkg, $cpr, color => 'ff0000', %opt) %>
% unless ( $cust_pkg->get('setup') ) {
- <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', 'colspan'=>$colspan, %opt, ) %>
+ <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', %opt, ) %>
% } else {
<% pkg_status_row( $cust_pkg, emt('Setup'), 'setup', %opt ) %>
- <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, emt('Suspended'), 'susp', %opt, curuser=>$curuser ) %>
% }
%
-% if ( $part_pkg->freq ) { #?
+% if ( $part_pkg->freq and !$supplemental ) { #?
<TR>
- <TD COLSPAN=<%$colspan%>>
+ <TD COLSPAN=<%$opt{colspan}%>>
<FONT SIZE=-1>
% if ( $curuser->access_right('Un-cancel customer package') ) {
(&nbsp;<% pkg_uncancel_link($cust_pkg) %>&nbsp;)
@@ -52,26 +49,21 @@
<% pkg_status_row( $cust_pkg, emt('Suspended'), 'susp', 'color'=>'FF9900', %opt ) %>
- <% pkg_status_row_colspan( $cust_pkg,
- ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
- 'align'=>'right', 'color'=>'FF9900', 'size'=>'-2', 'colspan'=>$colspan,
- %opt,
- )
- %>
+ <% pkg_reason_row( $cust_pkg, $cpr, 'color' => 'FF9900', %opt ) %>
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
% unless ( $cust_pkg->get('setup') ) {
- <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', 'colspan'=>$colspan, %opt ) %>
+ <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', %opt ) %>
% } else {
<% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt ) %>
% }
<% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
- <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
% if ( $cust_pkg->option('suspend_bill', 1)
% || ( $part_pkg->option('suspend_bill', 1)
@@ -85,31 +77,33 @@
<% pkg_status_row_if( $cust_pkg, emt('Expires'), 'expire', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %>
- <TR>
- <TD COLSPAN=<%$colspan%>>
- <FONT SIZE=-1>
-% if ( $curuser->access_right('Unsuspend customer package') ) {
- (&nbsp;<% pkg_unsuspend_link($cust_pkg) %>&nbsp;)
- (&nbsp;<% pkg_resume_link($cust_pkg) %>&nbsp;)
-% }
-% if ( $curuser->access_right('Cancel customer package immediately') ) {
- (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
-% }
- </FONT>
- </TD>
- </TR>
-
+% if ( !$supplemental ) {
+ <TR>
+ <TD COLSPAN=<%$opt{colspan}%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Unsuspend customer package') ) {
+ (&nbsp;<% pkg_unsuspend_link($cust_pkg) %>&nbsp;)
+ (&nbsp;<% pkg_resume_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Cancel customer package immediately') ) {
+ (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
+% }
+ </FONT>
+ </TD>
+ </TR>
+% }
+%
% } else { #status: active
%
% unless ( $cust_pkg->get('setup') ) { #not setup
%
% unless ( $part_pkg->freq ) {
- <% pkg_status_row_colspan( $cust_pkg, emt('Not yet billed (one-time charge)'), '', 'colspan'=>$colspan, %opt ) %>
+ <% pkg_status_row_colspan( $cust_pkg, emt('Not yet billed (one-time charge)'), '', %opt ) %>
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
<% pkg_status_row_if(
$cust_pkg,
@@ -121,8 +115,9 @@
<% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+% if (!$supplemental) {
<TR>
- <TD COLSPAN=<%$colspan%>>
+ <TD COLSPAN=<%$opt{colspan}%>>
<FONT SIZE=-1>
% if ( $curuser->access_right('Cancel customer package immediately') ) {
(&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
@@ -130,14 +125,15 @@
</FONT>
</TD>
</TR>
+% }
% } else {
- <% pkg_status_row_colspan($cust_pkg, emt("Not yet billed ($billed_or_prepaid [_1])", myfreq($part_pkg) ), '', 'colspan'=>$colspan, %opt ) %>
+ <% pkg_status_row_colspan($cust_pkg, emt("Not yet billed ($billed_or_prepaid [_1])", myfreq($part_pkg) ), '', %opt ) %>
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
<% pkg_status_row_if($cust_pkg, emt('Start billing'), 'start_date', %opt) %>
<% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
@@ -148,13 +144,13 @@
%
% unless ( $part_pkg->freq ) {
- <% pkg_status_row_colspan($cust_pkg, emt('One-time charge'), '', 'colspan'=>$colspan, %opt ) %>
+ <% pkg_status_row_colspan($cust_pkg, emt('One-time charge'), '', %opt ) %>
<% pkg_status_row($cust_pkg, emt('Billed'), 'setup', %opt) %>
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
<% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
@@ -170,7 +166,7 @@
<% pkg_status_row_colspan( $cust_pkg,
emt('Overlimit'),
$billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
- 'color'=>'FFD000', 'colspan'=>$colspan,
+ 'color'=>'FFD000',
%opt
)
%>
@@ -179,15 +175,15 @@
<% pkg_status_row_colspan( $cust_pkg,
emt('Active'),
$billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
- 'color'=>'00CC00', 'colspan'=>$colspan,
+ 'color'=>'00CC00',
%opt
)
%>
% }
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
<% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt) %>
@@ -202,7 +198,7 @@
% $cust_pkg->set('autosuspend', $autosuspend) if $autosuspend;
% }
- <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, $next_bill_or_prepaid_until, 'bill', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if($cust_pkg, emt('Will automatically suspend by'), 'autosuspend', %opt) %>
@@ -212,10 +208,10 @@
<% pkg_status_row_if( $cust_pkg, emt('Expires'), 'expire', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %>
-% if ( $part_pkg->freq ) {
+% if ( $part_pkg->freq and !$supplemental ) {
<TR>
- <TD COLSPAN=<%$colspan%>>
+ <TD COLSPAN=<%$opt{colspan}%>>
<FONT SIZE=-1>
% if ( $curuser->access_right('Suspend customer package') ) {
(&nbsp;<% pkg_suspend_link($cust_pkg) %>&nbsp;)
@@ -251,8 +247,10 @@ my $bgcolor = $opt{'bgcolor'};
my $cust_pkg = $opt{'cust_pkg'};
my $part_pkg = $opt{'part_pkg'};
my $curuser = $FS::CurrentUser::CurrentUser;
-my $colspan = $opt{'cust_pkg-display_times'} ? 8 : 4;
my $width = $opt{'cust_pkg-display_times'} ? '38%' : '56%';
+my $supplemental = $opt{'supplemental'};
+
+$opt{colspan} = $opt{'cust_pkg-display_times'} ? 8 : 4;
#false laziness w/edit/REAL_cust_pkg.cgi
my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until );
@@ -285,9 +283,27 @@ sub pkg_link {
sub pkg_status_row {
my( $cust_pkg, $title, $field, %opt ) = @_;
+ if ( $field and $cust_pkg->main_pkgnum ) {
+ # for supplemental packages, we mostly only show these if they're
+ # different from the main package
+ my $main_pkg = $cust_pkg-> main_pkg;
+ if ( $main_pkg->get($field) ne $cust_pkg->get($field)
+ # with some exceptions
+ or $field eq 'bill'
+ or $field eq 'last_bill'
+ or $field eq 'setup'
+ or $field eq 'susp'
+ or $field eq 'cancel'
+ ) {
+ # handle it normally
+ } else {
+ return '';
+ }
+ }
+
my $color = $opt{'color'};
- my $html = qq(<TR><TD WIDTH="<%$width%>" ALIGN="right">);
+ my $html = qq(<TR><TD WIDTH="$width" ALIGN="right">);
$html .= qq(<FONT COLOR="#$color"><B>) if length($color);
$html .= qq($title&nbsp;);
$html .= qq(</B></FONT>) if length($color);
@@ -338,7 +354,6 @@ sub pkg_status_row_changed {
'',
'size' => '-1',
'align' => 'right',
- 'colspan' => $opt{'colspan'},
);
}
@@ -356,9 +371,7 @@ sub pkg_status_row_noauto {
return '' unless $cust_main->payby =~ /^(CARD|CHEK)$/;
my $what = lc(FS::payby->shortname($cust_main->payby));
- pkg_status_row_colspan( $cust_pkg, emt("No automatic $what charge"), '',
- 'colspan' => $opt{'colspan'},
- );
+ pkg_status_row_colspan( $cust_pkg, emt("No automatic $what charge"), '');
}
sub pkg_status_row_discount {
@@ -382,15 +395,24 @@ sub pkg_status_row_discount {
$cust_pkg_discount->pkgdiscountnum.
'">'.emt('remove discount').'</A>)</FONT>';
- $html .= pkg_status_row_colspan( $cust_pkg, $label, '',
- 'colspan' => $opt{'colspan'},
- );
+ $html .= pkg_status_row_colspan( $cust_pkg, $label, '', %opt );
}
$html;
}
+sub pkg_reason_row {
+ my ($cust_pkg, $cpr, %opt) = @_;
+ return '' if $cust_pkg->main_pkgnum;
+
+ my $reasontext = '';
+ $reasontext = $cpr->reasontext . ' by ' . $cpr->otaker if $cpr;
+ pkg_status_row_colspan( $cust_pkg, $reasontext, '',
+ 'align'=>'right', 'size'=>'-2', %opt
+ );
+}
+
sub pkg_status_row_colspan {
my($cust_pkg, $title, $addl, %opt) = @_;
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
index 942b42f54..915be49e5 100644
--- a/httemplate/view/cust_main/payment_history.html
+++ b/httemplate/view/cust_main/payment_history.html
@@ -34,7 +34,7 @@
<A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;custnum=<% $custnum %>"><% mt('Enter Western Union payment') |h %></A>
% }
-<BR>
+<% $s ? '<BR>' : '' %>
% $s=0;
% if ( ( $payby{'CARD'} || $payby{'DCRD'} )
@@ -58,11 +58,13 @@
<A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<% $custnum %>"><% mt('Post manual (offline/POS) credit card payment') |h %></A>
% }
-<BR>
+<% $s ? '<BR>' : '' %>
-%# credit link
+%# credit links
+% $s=0;
% if ( $curuser->access_right('Post credit') ) {
+ <% $s++ ? ' | ' : '' %>
<& /elements/popup_link-cust_main.html,
'label' => emt('Enter credit'),
'action' => "${p}edit/cust_credit.cgi",
@@ -70,7 +72,9 @@
'actionlabel' => emt('Enter credit'),
'width' => 616, #make room for reasons #540 default
&>
- |
+% }
+% if ( $curuser->access_right('Credit line items') ) {
+ <% $s++ ? ' | ' : '' %>
<& /elements/popup_link-cust_main.html,
'label' => emt('Credit line items'),
#'action' => "${p}search/cust_bill_pkg.cgi?nottax=1;type=select",
@@ -80,8 +84,8 @@
'width' => 968, #763,
'height' => 575,
&>
- <BR>
% }
+<% $s ? '<BR>' : '' %>
%# refund links
@@ -224,57 +228,20 @@
%#display payment history
-%my $money_char = $conf->config('money_char') || '$';
-%
-%sub balance_forward_row {
-% my( $b, $date, $money_char ) = @_;
-% ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/-&nbsp;\$/;
-
- <TR ID="balance_forward_row">
- <TD CLASS="grid" BGCOLOR="#dddddd">
- <% time2str($date_format, $date) %>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="#dddddd">
- <I><% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %></I>
- (<A HREF="javascript:void(0);" onClick="show_history();"><% mt('show prior history') |h %></A>)
- </TD>
-
- <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
- <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
- <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
- <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
- <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
-
- </TR>
-%}
-%
-%my $balance = 0;
%my %target = ();
%
-%my $years = $conf->config('payment_history-years') || 2;
-%my $older_than = time - $years * 31556926; #60*60*24*365.2422
%my $hidden = 0;
%my $seen = 0;
%my $old_history = 0;
%my $lastdate = 0;
%
-%foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
+%foreach my $item ( @history ) {
%
% $lastdate = $item->{'date'};
%
-% my $display;
-% if ( $item->{'date'} < $older_than ) {
+% my $display = '';
+% if ( $item->{'hide'} ) {
% $display = ' STYLE="display:none" ';
-% $hidden = 1;
-% } else {
-%
-% $display = '';
-%
-% if ( $hidden && ! $seen++ ) {
-% balance_forward_row($balance, $item->{'date'}, $money_char);
-% }
-%
% }
%
% if ( $bgcolor eq $bgcolor1 ) {
@@ -310,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 %>">
@@ -355,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>
@@ -382,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;
@@ -497,6 +479,17 @@ foreach my $cust_pay_pending ($cust_main->cust_pay_pending_attempt) {
#'target' => $target, #XXX
};
}
+#declined batch payments
+foreach my $cust_pay_batch (
+ $cust_main->cust_pay_batch(hashref => {status => 'Declined'})
+) {
+ my $pay_batch = $cust_pay_batch->pay_batch;
+ push @history, {
+ 'date' => $pay_batch->upload,
+ 'desc' => include('payment_history/attempted_batch_payment.html', $cust_pay_batch, %opt),
+ 'void_payment' => $cust_pay_batch->amount,
+ };
+}
#credits (some false laziness w/payments)
foreach my $cust_credit ($cust_main->cust_credit) {
@@ -518,6 +511,41 @@ foreach my $cust_refund ($cust_main->cust_refund) {
}
+# sort in forward order first, and calculate running balances
+my $years = $conf->config('payment_history-years') || 2;
+my $older_than = time - $years * 31556926; #60*60*24*365.2422
+my $balance = 0;
+
+@history = sort { $a->{date} <=> $b->{date} } @history;
+my $i = 0;
+my $balance_forward;
+foreach my $item (@history) {
+ $balance += $item->{'charge'} if exists $item->{'charge'};
+ $balance -= $item->{'payment'} if exists $item->{'payment'};
+ $balance -= $item->{'credit'} if exists $item->{'credit'};
+ $balance += $item->{'refund'} if exists $item->{'refund'};
+ $balance = sprintf("%.2f", $balance);
+ $balance =~ s/^\-0\.00$/0.00/;
+ $item->{'balance'} = $balance;
+
+ if ( $item->{'date'} < $older_than ) {
+ $item->{'hide'} = 1;
+ } elsif ( $history[$i-1]->{'hide'} ) {
+ # this is the end of the hidden section
+ $history[$i-1]->{'balance_forward'} = 1;
+ }
+ $i++;
+}
+if ( @history and $history[-1]->{'hide'} ) {
+ # then everything is hidden
+ $history[-1]->{'balance_forward'} = 1;
+}
+
+# then sort in user-pref order
+if ( $curuser->option('history_order') eq 'newest' ) {
+ @history = sort { $b->{date} <=> $a->{date} } @history;
+} # else it's already oldest-first, and there are no other options yet
+
sub translate_payby {
my ($payby,$payinfo) = (shift,shift);
my %payby = (
diff --git a/httemplate/view/cust_main/payment_history/attempted_batch_payment.html b/httemplate/view/cust_main/payment_history/attempted_batch_payment.html
new file mode 100644
index 000000000..95947f512
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/attempted_batch_payment.html
@@ -0,0 +1,13 @@
+<I><% mt('Payment attempt') |h %> <% $info |h %></I>
+<%init>
+
+my( $cust_pay_batch, %opt ) = @_;
+
+my ($payby,$payinfo) = translate_payinfo($cust_pay_batch);
+$payby = translate_payby($payby,$payinfo);
+my $info = $payby ? "($payby$payinfo)" : '';
+
+$info .= ': '. $cust_pay_batch->error_message
+ if length($cust_pay_batch->error_message);
+
+</%init>
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
index f7c685c28..d735195fe 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -52,26 +52,39 @@ function areyousure(href) {
<% mt('Service #') |h %><B><% $svcnum %></B>
% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
-| <& /view/elements/svc_edit_link.html, 'svc' => $svc_x, 'edit_url' => $url &>
+<& /view/elements/svc_edit_link.html, 'svc' => $svc_x, 'edit_url' => $url &>
<BR>
<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+% my @inventory_items = $svc_x->inventory_item;
% foreach my $f ( @$fields ) {
%
-% my($field, $type, $value, $hack_strict_refs);
+% my($field, $type, $value);
% if ( ref($f) ) {
% $field = $f->{'field'};
-% $hack_strict_refs = \&{ $f->{'value'} } if $f->{'value'};
-% $value = $f->{'value'} ? &$hack_strict_refs($svc_x) : $svc_x->$field;
% $type = $f->{'type'} || 'text';
+% if ( $f->{'value_callback'} ) {
+% my $hack_strict_refs = \&{ $f->{'value_callback'} };
+% $value = &$hack_strict_refs($svc_x);
+% } else {
+% $value = exists($f->{'value'}) ? $f->{'value'} : $svc_x->$field;
+% }
% } else {
% $field = $f;
-% $value = $svc_x->$field;
% $type = 'text';
+% $value = $svc_x->$field;
% }
%
% my $columndef = $part_svc->part_svc_column($field);
+% if ( $columndef->columnflag =~ /^[MA]$/ && $columndef->columnvalue =~ /,/ )
+% {
+% # inventory-select field with multiple classes
+% # show the class name to disambiguate
+% my ($item) = grep { $_->svc_field eq $field } @inventory_items;
+% my $class = qsearchs('inventory_class', { classnum => $item->classnum });
+% $value .= ' <i>('. $class->classname . ')</i>' if $class;
+% }
% unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) {
<TR>
diff --git a/httemplate/view/elements/svc_devices.html b/httemplate/view/elements/svc_devices.html
index d71c82f07..38c6d0919 100644
--- a/httemplate/view/elements/svc_devices.html
+++ b/httemplate/view/elements/svc_devices.html
@@ -12,91 +12,86 @@
)
</%doc>
-<% $devices %>
+%if ( @devices || $num_part_device || $table eq 'dsl_device' ) {
+% my $svcnum = $svc_x->svcnum;
+
+ Devices
+ (<A HREF="<%$p%>edit/<%$table%>.html?svcnum=<%$svcnum%>">Add device</A>)
+ <BR>
+
+% if ( @devices ) {
+
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Are you sure you want to delete this device?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+ <& /elements/table-grid.html &>
+ <TR>
+% if ( $table eq 'phone_device' ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ </TR>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+%
+% foreach my $device ( @devices ) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">);
+%
+% my $devicenum = $device->devicenum;
+% my $export_links = '';
+% $export_links = join( '<BR>', @{ $device->export_links } )
+% if $device->can('export_links');
+
+ <TR>
+% if ( $table eq 'phone_device' ) { #$devices->can('part_device')
+ <% $td %><% $device->part_device->devicename |h %></TD>
+% }
+ <% $td %><% $device->mac_addr %></TD>
+ <% $td %><% $export_links %></TD>
+ <% $td %>(
+% unless ( $opt{'no_edit'} ) {
+ <A HREF="<%$p%>edit/<%$table%>.html?<%$devicenum%>">edit</A> |
+% }
+ <A HREF="javascript:areyousure('<%$p%>misc/delete-<%$table%>.html?<%$devicenum%>')">delete</A>
+ )</TD>
+ </TR>
+% }
+ </TABLE>
+ <BR>
+
+% }
+ <BR>
+%}
<%init>
- my %opt = @_;
- my $table = $opt{'table'}; #part_device, dsl_device
- my $svc_x = $opt{'svc_x'};
-
- my $devices = '';
-
- my $num_part_device = 0;
- if ( $table eq 'phone_device' ) {
- my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device")
- #WHERE disabled = '' OR disabled IS NULL;");
- or die dbh->errstr;
- $sth->execute or die $sth->errstr;
- $num_part_device = $sth->fetchrow_arrayref->[0];
+my %opt = @_;
+my $table = $opt{'table'}; #part_device, dsl_device
+my $svc_x = $opt{'svc_x'};
+
+my $num_part_device = 0;
+if ( $table eq 'phone_device' ) {
+ my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device")
+ #WHERE disabled = '' OR disabled IS NULL;");
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ $num_part_device = $sth->fetchrow_arrayref->[0];
}
- my @devices = $svc_x->$table();
-
- #should move the below to proper mason code above instead of making $devices
- if ( @devices || $num_part_device || $table eq 'dsl_device' ) {
- my $svcnum = $svc_x->svcnum;
- $devices .=
- qq[Devices (<A HREF="${p}edit/$table.html?svcnum=$svcnum">Add device</A>)<BR>];
- if ( @devices ) {
-
- $devices .= qq!
- <SCRIPT>
- function areyousure(href) {
- if (confirm("Are you sure you want to delete this device?") == true)
- window.location.href = href;
- }
- </SCRIPT>
- !;
-
-
- $devices .=
- include('/elements/table-grid.html').
- '<TR>';
-
- $devices .=
- '<TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>'
- if $table eq 'phone_device';
-
- $devices .=
- '<TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>'.
- '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'.
- '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'.
- '</TR>';
- my $bgcolor1 = '#eeeeee';
- my $bgcolor2 = '#ffffff';
- my $bgcolor = '';
-
- foreach my $device ( @devices ) {
-
- if ( $bgcolor eq $bgcolor1 ) {
- $bgcolor = $bgcolor2;
- } else {
- $bgcolor = $bgcolor1;
- }
- my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">);
-
- my $devicenum = $device->devicenum;
- my $export_links = join( '<BR>', @{ $device->export_links } )
- if $device->can('export_links');
-
- $devices .= '<TR>';
- $devices .= $td. $device->part_device->devicename. '</TD>'
- if $table eq 'phone_device'; #$devices->can('part_device');
-
- $devices .= $td. $device->mac_addr. '</TD>'.
- $td. $export_links. '</TD>'.
- "$td( ";
-
- $devices .= qq(<A HREF="${p}edit/$table.html?$devicenum">edit</A> | )
- unless $opt{'no_edit'};
-
- $devices .= qq(<A HREF="javascript:areyousure('${p}misc/delete-$table.html?$devicenum')">delete</A>).
- ' )</TD>'.
- '</TR>';
- }
- $devices .= '</TABLE><BR>';
- }
- $devices .= '<BR>';
- }
+my @devices = $svc_x->$table();
</%init>
diff --git a/httemplate/view/elements/svc_edit_link.html b/httemplate/view/elements/svc_edit_link.html
index d65db0a8f..5438ed266 100644
--- a/httemplate/view/elements/svc_edit_link.html
+++ b/httemplate/view/elements/svc_edit_link.html
@@ -7,8 +7,12 @@ function areyousure_delete() {
window.location.href = '<% $cancel_url %>';
}
</SCRIPT>
-<A HREF="<% $edit_url %>"><% mt("Edit this [_1]", $label) |h %></A> |
-<A HREF="javascript:areyousure_delete()"><% mt('Unprovision this Service') |h %></A>
+% if ( $curuser->access_right('Provision customer service') ) {
+| <A HREF="<% $edit_url %>"><% mt("Edit this [_1]", $label) |h %></A>
+% }
+% if ( $curuser->access_right('Unprovision customer service') ) {
+| <A HREF="javascript:areyousure_delete()"><% mt('Unprovision this Service') |h %></A>
+% }
% }
<%init>
my %opt = @_;
@@ -20,4 +24,5 @@ my $cancel_url = $p . 'misc/unprovision.cgi?' . $svc_x->svcnum;
my $cust_svc = $svc_x->cust_svc; # always exists
my $cancel_date = $cust_svc->pkg_cancel_date;
my ($label) = $cust_svc->label;
+my $curuser = $FS::CurrentUser::CurrentUser;
</%init>
diff --git a/httemplate/view/elements/svc_export_status.html b/httemplate/view/elements/svc_export_status.html
index d96bb277d..4ce869e27 100644
--- a/httemplate/view/elements/svc_export_status.html
+++ b/httemplate/view/elements/svc_export_status.html
@@ -7,7 +7,15 @@
% foreach my $key ( sort {$a cmp $b} keys %$hashref ) {
<TR>
<TD ALIGN="right"><% $key |h %></TD>
- <TD BGCOLOR="#ffffff"><% $hashref->{$key} |h %></TD>
+ <TD BGCOLOR="#ffffff">
+% if ( ref($hashref->{$key}) eq 'ARRAY' ) {
+% foreach (@{ $hashref->{$key} }) {
+ <% $_ |h %><BR>
+% }
+% } else {
+ <% $hashref->{$key} |h %>
+% }
+ </TD>
</TR>
% }
diff --git a/httemplate/view/quotation-pdf.cgi b/httemplate/view/quotation-pdf.cgi
new file mode 100755
index 000000000..7f62ce173
--- /dev/null
+++ b/httemplate/view/quotation-pdf.cgi
@@ -0,0 +1,29 @@
+<% $content %>\
+<%init>
+
+#false laziness w/elements/cust_bill-typeset
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Generate quotation'); #View quotations ?
+
+my $quotationnum = $cgi->param('quotationnum');
+
+my $conf = new FS::Conf;
+
+my $quotation = qsearchs({
+ 'select' => 'quotation.*',
+ 'table' => 'quotation',
+ #'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'quotationnum' => $quotationnum },
+ #'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Quotation #$quotationnum not found!" unless $quotation;
+
+my $content = $quotation->print_pdf(); #\%opt);
+
+http_header('Content-Type' => 'application/pdf');
+http_header('Content-Disposition' => "filename=$quotationnum.pdf" );
+http_header('Content-Length' => length($content) );
+http_header('Cache-control' => 'max-age=60' );
+
+</%init>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
index 199591356..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,
@@ -37,13 +39,15 @@
&>
<% mt('Service #') |h %><B><% $svcnum %></B>
-|
<& /view/elements/svc_edit_link.html, 'svc' => $svc_acct &>
<& svc_acct/change_svc.html,
'part_svc' => \@part_svc,
%gopt,
&>
+</FORM>
+
+
<& svc_acct/basics.html,
'svc_acct' => $svc_acct,
'part_svc' => $part_svc,
@@ -90,8 +94,12 @@ die "access denied"
my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
' LEFT JOIN cust_main USING ( custnum ) ';
-
-my($query) = $cgi->keywords;
+my $query;
+if ( $cgi->keywords ) {
+ ($query) = $cgi->keywords;
+} else {
+ $query = $cgi->param('svcnum');
+}
$query =~ /^(\d+)$/;
my $svcnum = $1;
my $svc_acct = qsearchs({
diff --git a/httemplate/view/svc_acct/basics.html b/httemplate/view/svc_acct/basics.html
index 2d9953fcc..04e7bcff8 100644
--- a/httemplate/view/svc_acct/basics.html
+++ b/httemplate/view/svc_acct/basics.html
@@ -20,7 +20,7 @@
% if ( $password =~ /^\*\w+\* (.*)$/ ) {
% $password = $1;
% $show_pw .= '<I>('. mt('login disabled') .')</I> ';
-% }
+% }
% if ( ! $password
% && $svc_acct->_password_encryption ne 'plain'
% && $svc_acct->_password
@@ -28,13 +28,27 @@
% {
% $show_pw .= '<I>('. uc($svc_acct->_password_encryption). ' '.mt('encrypted').')</I>';
% } elsif ( $conf->exists('showpasswords') ) {
-% $show_pw .= '<PRE>'. encode_entities($password). '</PRE>';
+% $show_pw .= '<SPAN >'. encode_entities($password). '</PRE>';
% } else {
+% $password = '';
% $show_pw .= '<I>('. mt('hidden') .')</I>';
-% }
-% $password = '';
-<& /view/elements/tr.html, label=>mt('Password'), value=>$show_pw &>
-
+% }
+<TR>
+ <TD ALIGN="right"><% mt('Password') %></TD>
+ <TD STYLE="background-color: #ffffff; white-space: nowrap">
+ <% $show_pw %>
+% my $curuser = $FS::CurrentUser::CurrentUser;
+% if ( $curuser->access_right('Provision customer service') or
+% ($curuser->access_right('Edit password') and
+% ! $part_svc->restrict_edit_password) )
+% {
+ <& /elements/change_password.html,
+ 'svc_acct' => $svc_acct,
+ 'curr_value' => $password,
+ &>
+% }
+ </TD>
+</TR>
% if ( $conf->exists('security_phrase') ) {
<& /view/elements/tr.html, label=>mt('Security phrase'), value=>$svc_acct->sec_phrase &>
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
index 75e673c4f..7d6520e57 100644
--- a/httemplate/view/svc_broadband.cgi
+++ b/httemplate/view/svc_broadband.cgi
@@ -26,23 +26,31 @@ $labels{'coordinates'} = 'Latitude/Longitude';
my @fields = (
'description',
- { field => 'routernum', value => \&router },
+ { field => 'routernum', value_callback => \&router },
'speed_down',
'speed_up',
- { field => 'ip_addr', value => \&ip_addr },
- { field => 'sectornum', value => \&sectornum },
- { field => 'mac_addr', value => \&mac_addr },
+ { field => 'ip_addr', value_callback => \&ip_addr },
+ { field => 'sectornum', value_callback => \&sectornum },
+ { field => 'mac_addr', value_callback => \&mac_addr },
#'latitude',
#'longitude',
- { field => 'coordinates', value => \&coordinates },
+ { 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',
);
push @fields,
- { field => 'usergroup', value => \&usergroup }
+ { field => 'usergroup', value_callback => \&usergroup }
if $conf->exists('svc_broadband-radius');
sub router {
@@ -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 ) {
diff --git a/httemplate/view/svc_cert.cgi b/httemplate/view/svc_cert.cgi
index 0cd66b422..964b808ab 100644
--- a/httemplate/view/svc_cert.cgi
+++ b/httemplate/view/svc_cert.cgi
@@ -17,7 +17,7 @@ my %labels = map { $_ => ( ref($fields->{$_})
my @fields = (
{ field=>'privatekey',
- value=> sub {
+ value_callback=> sub {
my $svc_cert = shift;
if ( $svc_cert->privatekey && $svc_cert->check_privatekey ) {
'<FONT COLOR="#33ff33">Verification OK</FONT>';
@@ -31,7 +31,7 @@ my @fields = (
qw( common_name organization organization_unit city state country cert_contact
),
{ 'field'=>'csr',
- 'value'=> sub {
+ 'value_callback'=> sub {
my $svc_cert = shift;
if ( $svc_cert->csr ) {
@@ -67,7 +67,7 @@ my @fields = (
},
},
{ 'field'=>'certificate',
- 'value'=> sub {
+ 'value_callback'=> sub {
my $svc_cert = shift;
if ( $svc_cert->certificate ) {
@@ -137,7 +137,7 @@ my @fields = (
},
},
{ 'field'=>'cacert',
- 'value'=> sub {
+ 'value_callback'=> sub {
my $svc_cert = shift;
if ( $svc_cert->cacert ) {
diff --git a/httemplate/view/svc_hardware.cgi b/httemplate/view/svc_hardware.cgi
index 7f5e889d8..eef1c1140 100644
--- a/httemplate/view/svc_hardware.cgi
+++ b/httemplate/view/svc_hardware.cgi
@@ -13,17 +13,20 @@ my %labels = map { $_ => ( ref($fields->{$_})
: $fields->{$_}
);
} keys %$fields;
+
+$labels{'display_hw_addr'} = 'Hardware address';
+
my $model = { field => 'typenum',
type => 'text',
- value => sub { $_[0]->hardware_type->description }
+ value_callback => sub { $_[0]->hardware_type->description }
};
my $status = { field => 'statusnum',
type => 'text',
- value => sub { $_[0]->status_label }
+ value_callback => sub { $_[0]->status_label }
};
my $note = { field => 'note',
type => 'text',
- value => sub { encode_entities($_[0]->note) }
+ value_callback => sub { encode_entities($_[0]->note) }
};
my @fields = (
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
index 323be63dc..ed95c4cea 100644
--- a/httemplate/view/svc_phone.cgi
+++ b/httemplate/view/svc_phone.cgi
@@ -16,9 +16,20 @@ my %labels = map { $_ => ( ref($fields->{$_})
);
} keys %$fields;
-my @fields = qw( countrycode phonenum );
+my @fields = qw( countrycode phonenum sim_imsi );
push @fields, 'domain' if $conf->exists('svc_phone-domain');
-push @fields, qw( pbx_title sip_password pin phone_name forwarddst email );
+push @fields, qw( pbx_title );
+
+if ( $conf->exists('showpasswords') ) {
+ push @fields, qw( sip_password );
+} else {
+ push @fields, { 'field' => 'sip_password', #'_HIDDEN_sip_password',
+ 'type' => 'fixed',
+ 'value' => '<I>('. mt('hidden') .')</I>',
+ };
+}
+
+push @fields, qw( pin phone_name forwarddst email );
if ( $conf->exists('svc_phone-lnp') ) {
push @fields, 'lnp_status',